
M365 Admin Skill
- 124 installs
- 404kidwiz/claude-supercode-skills
Administer and automate Microsoft 365 environment tasks including user management and resource provisioning.
About
Automates Microsoft 365 administration tasks including user management, resource allocation, and environment configuration. Teams use it when scaling up M365 deployments, automating license management, or standardizing their cloud infrastructure.
- M365 automation
- User administration
- Resource provisioning
M365 Admin by the numbers
- 124 all-time installs (skills.sh)
- Ranked #1,313 of 3,347 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Aug 11, 2026 (Skillselion catalog sync)
npx skills add https://github.com/404kidwiz/claude-supercode-skills --skill m365-adminAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 124 |
|---|---|
| Repository | 404kidwiz/claude-supercode-skills ↗ |
What it does
Administer and automate Microsoft 365 environment tasks including user management and resource provisioning.
Files
Microsoft 365 Administrator
Purpose
Provides Microsoft 365 administration and automation expertise specializing in Exchange Online, Teams, SharePoint, and Graph API operations. Manages secure identity, workload automation, licensing optimization, and compliance configuration across the Microsoft 365 ecosystem.
When to Use
- Exchange Online mailbox management and lifecycle
- Microsoft Teams team lifecycle automation
- SharePoint site management and security
- License assignment and optimization
- Microsoft Graph PowerShell automation
- User provisioning and onboarding workflows
- Compliance and security configuration
This skill provides expert Microsoft 365 administration and automation capabilities. It designs, builds, and reviews scripts and workflows across Exchange Online, Teams, SharePoint, and other Microsoft cloud workloads with focus on automation, licensing optimization, and Graph API operations.
When to Use
User needs:
- Exchange Online mailbox management and lifecycle
- Microsoft Teams team lifecycle automation
- SharePoint site management and security
- License assignment and optimization
- Microsoft Graph PowerShell automation
- User provisioning and onboarding workflows
- Compliance and security configuration
- Guest access and external sharing management
What This Skill Does
This skill automates and manages Microsoft 365 workloads through PowerShell and Graph API. It handles mailbox operations, team lifecycle management, SharePoint administration, license auditing and optimization, and ensures secure identity and compliance across the Microsoft 365 platform.
M365 Workloads Covered
- Exchange Online (mailboxes, distribution groups, transport rules)
- Microsoft Teams (team creation, membership, channel management)
- SharePoint Online (sites, permissions, sharing settings)
- Microsoft Graph API (identity, users, groups, app registrations)
- Licensing and subscription management
- Security and compliance configuration
Core Capabilities
Exchange Online Management
- Mailbox provisioning and lifecycle management
- Distribution groups and mail-enabled security groups
- Transport rules and compliance policies
- Message trace and audit workflows
- Calendar and resource management
- Email flow configuration and routing
Teams + SharePoint Administration
- Team lifecycle automation (create, archive, delete)
- SharePoint site provisioning and permissions
- Guest access and external sharing validation
- Collaboration security workflows
- Channel and tab management
- Document library and folder structure
Licensing + Graph API
- License assignment, auditing, and optimization
- Microsoft Graph PowerShell for identity automation
- Service principal and app registration management
- Role-based access control (RBAC) configuration
- User and group synchronization
- Conditional access policies
Automation Patterns
- User onboarding and offboarding workflows
- Bulk operations across departments
- Scheduled maintenance and cleanup tasks
- Compliance and security audit automation
- Reporting and analytics generation
- Self-healing and remediation scripts
Tool Restrictions
- Read: Access M365 configuration files, scripts, and documentation
- Write/Edit: Create PowerShell scripts and automation workflows
- Bash: Execute PowerShell commands and M365 CLI tools
- Glob/Grep: Search M365-related code and configuration files
Integration with Other Skills
- azure-infra-engineer: Identity/hybrid alignment and Azure AD integration
- powershell-7-expert: PowerShell scripting and Graph API automation
- powershell-module-architect: Module structure for cloud tooling
- it-ops-orchestrator: M365 workflows involving infrastructure and automation
- security-auditor: Security compliance and access reviews
Example Interactions
Scenario 1: User Onboarding Automation
User: "Automate new employee onboarding with mailbox, Teams, and license assignment"
Interaction: 1. Skill designs onboarding workflow with required information 2. Creates PowerShell script using Microsoft Graph:
- Creates user account in Azure AD
- Assigns appropriate M365 licenses
- Provisions Exchange Online mailbox
- Creates user's departmental Team with default channels
- Adds user to relevant distribution groups and SharePoint sites
- Sends welcome email with resources
3. Implements error handling and logging 4. Tests workflow with test accounts
Scenario 2: SharePoint External Sharing Audit
User: "Audit all SharePoint sites for external sharing and fix misconfigured sites"
Interaction: 1. Skill audits all SharePoint site sharing settings via Graph API 2. Identifies misconfigured sites with external sharing enabled 3. Generates report showing:
- Site owners and administrators
- Current sharing settings and external users
- Business justification for external access
4. Implements remediation script to:
- Disable external sharing on non-compliant sites
- Set appropriate sharing policies
- Add compliance notifications
5. Provides ongoing monitoring solution
Scenario 3: License Optimization
User: "Audit and optimize M365 licenses across the organization"
Interaction: 1. Skill queries all assigned licenses via Microsoft Graph 2. Analyzes usage data and last activity timestamps 3. Identifies:
- Unused licenses for reclamation
- Over-licensed users for downgrade
- Underutilized premium features
4. Generates optimization plan:
- Reclaims X unused licenses saving $Y/month
- Recommends license package changes
- Suggests automation for license assignment
5. Implements automated license provisioning workflow
Best Practices
- Validation: Always validate connections and permissions before modifications
- Least Privilege: Apply RBAC principles for all automation accounts
- Testing: Test scripts in non-production environments first
- Backup: Audit and backup affected objects before bulk changes
- Documentation: Document all automation scripts with comments and examples
- Error Handling: Implement robust error handling and logging
- Monitoring: Add monitoring and alerting for critical workflows
- Approval: Include approval workflows for high-impact changes
Examples
Example 1: Enterprise User Onboarding Automation
Scenario: A company with 500+ employees needs automated onboarding across M365 workloads.
Implementation Approach: 1. Graph API Integration: Created PowerShell scripts using Microsoft Graph API 2. Workflow Design: Sequential provisioning with dependency handling 3. Error Handling: Retry logic and notification system 4. Testing: Validated with test accounts before production
Onboarding Workflow: 1. Create Azure AD user account with proper attributes 2. Assign M365 licenses based on job role 3. Provision Exchange Online mailbox 4. Create Teams team with department channels 5. Add to SharePoint sites and distribution groups 6. Send welcome email with credentials
Results:
- Onboarding time: 4 hours → 15 minutes
- 100% consistency across all users
- Zero manual errors in 6 months
Example 2: SharePoint Security Audit and Remediation
Scenario: Need to audit all SharePoint sites for external sharing compliance.
Audit Process: 1. Data Collection: Retrieved all site collections via Graph API 2. Analysis: Identified sharing settings and external users 3. Risk Assessment: Categorized sites by sensitivity level 4. Remediation: Applied policies based on risk level
Findings:
| Category | Sites | External Users | Risk Level |
|---|---|---|---|
| High | 23 | 156 | Critical |
| Medium | 45 | 34 | Medium |
| Low | 120 | 8 | Low |
Actions Taken:
- Disabled external sharing on high-risk sites
- Implemented approval workflow for external access
- Added monitoring and alerting for policy violations
Example 3: M365 License Optimization Project
Scenario: Optimize M365 license usage and reduce costs by identifying unused licenses.
Optimization Approach: 1. License Audit: Queried all assigned licenses via Graph API 2. Usage Analysis: Analyzed sign-in activity and service usage 3. Optimization Plan: Identified reclamation opportunities 4. Implementation: Automated license reassignment process
Results:
- 127 unused licenses reclaimed
- $45,000 annual savings
- 15% reduction in license costs
- Automated monitoring for license utilization
Best Practices
PowerShell Automation
- Use Microsoft Graph API: Modern approach for M365 management
- Module Best Practices: Use latest ExchangeOnlineManagement module
- Error Handling: Implement try/catch blocks for all operations
- Logging: Comprehensive logging for audit trails
- Testing: Always test scripts in non-production first
Security and Compliance
- Least Privilege: Use application permissions, not user delegated
- Conditional Access: Implement for sensitive operations
- Audit Logging: Enable unified audit logging
- Data Protection: Encrypt sensitive data at rest and in transit
- Compliance: Follow organizational compliance requirements
User Lifecycle Management
- Onboarding: Automated provisioning with approval workflows
- Changes: Handle role changes with proper access updates
- Offboarding: Complete deprovisioning with data retention
- Licensing: Regular audits and optimization
- Self-Service: Enable user self-service where appropriate
Performance Optimization
- Batch Operations: Use batch API calls for bulk operations
- Rate Limiting: Handle throttling gracefully
- Caching: Cache frequently accessed data
- Parallel Processing: Use parallel execution for independent tasks
- Monitoring: Track script performance and duration
Anti-Patterns
PowerShell Automation Anti-Patterns
- Sequential Everything: Not leveraging parallel processing - use parallel execution for independent operations
- No Error Handling: Scripts that fail silently - implement comprehensive try/catch/finally
- Hardcoded Values: Embedding usernames, URLs in scripts - use parameters and configuration
- Chatty API Calls: Making excessive API calls - batch operations and use delta queries
Security Anti-Patterns
- Over-Privileged Accounts: Using admin accounts for routine tasks - apply least privilege principles
- Credential Hardcoding: Storing passwords in scripts - use secure credential storage
- Audit Neglect: Not enabling unified audit logging - enable and monitor audit logs
- Permission Creep: Accumulating permissions without review - conduct regular access reviews
User Management Anti-Patterns
- Manual Provisioning: Creating users manually instead of automation - automate user lifecycle
- License Waste: Assigning licenses without tracking usage - monitor and optimize license usage
- Orphaned Accounts: Leaving accounts after user departure - implement deprovisioning automation
- Inconsistent Naming: No naming convention enforcement - implement and enforce naming standards
Configuration Anti-Patterns
- Configuration Drift: Environments diverging over time - use configuration management
- Setting Shadow IT: Users creating unauthorized configurations - monitor and govern settings
- Over-Sharing: Excessive external sharing permissions - audit and restrict sharing settings
- Policy Overlap: Multiple conflicting policies - consolidate and prioritize policies
Automation Scripts and References
The M365 admin skill includes comprehensive automation scripts and reference documentation located in:
Scripts (scripts/ directory)
- create_m365_users.ts: TypeScript classes and functions for user lifecycle management, license assignment, password validation, and bulk operations
- configure_teams.ts: Microsoft Teams management including team creation, channel management, member management, team settings, and archiving
- setup_exchange.ts: Exchange Online administration with mailbox management, auto-reply configuration, distribution groups, calendar events, and email automation
References (references/ directory)
- m365_quickstart.md: Quick start guide with app registration, authentication, common patterns, and troubleshooting
- admin_patterns.md: Comprehensive patterns for user lifecycle, Teams templates, email automation, license management, security and compliance, and backup/recovery
Output Format
This skill delivers:
- PowerShell automation scripts for M365 workloads
- Graph API integration code and examples
- Configuration templates and manifests
- Audit reports and compliance summaries
- Onboarding/offboarding workflow scripts
- License optimization recommendations and implementations
All outputs include:
- Detailed script documentation and comments
- Error handling and logging patterns
- Testing instructions and validation steps
- RBAC configuration guidance
- Troubleshooting procedures and common issues
- Security best practices and compliance considerations
Microsoft 365 Administration Patterns
Common patterns and best practices for Microsoft 365 administration using TypeScript and Microsoft Graph API.
User Management Patterns
User Lifecycle Management
interface UserLifecycleState {
onboarding: UserOnboardingConfig;
offboarding: UserOffboardingConfig;
lifecycle: UserLifecycleConfig;
}
interface UserOnboardingConfig {
assignLicenses: string[];
addToGroups: string[];
sendWelcomeEmail: boolean;
createTeamsMembership: boolean;
}
async function onboardUser(
userManager: M365UserManager,
teamsManager: TeamsManager,
exchangeManager: ExchangeManager,
userConfig: M365UserConfig,
onboardingConfig: UserOnboardingConfig
): Promise<boolean> {
try {
// Create user
const userResult = await userManager.createUser(userConfig);
if (!userResult.success || !userResult.userId) {
console.error('Failed to create user');
return false;
}
const userId = userResult.userId;
// Assign licenses
if (onboardingConfig.assignLicenses.length > 0) {
await userManager.assignLicenses(userId, onboardingConfig.assignLicenses);
}
// Add to groups
for (const groupId of onboardingConfig.addToGroups) {
await exchangeManager.addMemberToDistributionGroup(groupId, userId);
}
// Create Teams membership
if (onboardingConfig.createTeamsMembership) {
const teamName = `${userConfig.displayName}'s Team`;
const teamResult = await teamsManager.createTeam({
displayName: teamName,
description: `Personal team for ${userConfig.displayName}`
});
if (teamResult.success && teamResult.teamId) {
await teamsManager.addMember(teamResult.teamId, {
userId: userId,
role: 'Owner'
});
}
}
// Send welcome email
if (onboardingConfig.sendWelcomeEmail) {
await sendWelcomeEmail(userManager, exchangeManager, userId);
}
console.log(`User ${userConfig.displayName} onboarded successfully`);
return true;
} catch (error: any) {
console.error(`Onboarding failed: ${error.message}`);
return false;
}
}Bulk User Operations
async function bulkUserOperation(
userManager: M365UserManager,
users: M365UserConfig[],
operation: 'create' | 'update' | 'delete',
concurrencyLimit: number = 5
): Promise<{ succeeded: number; failed: number; errors: string[] }> {
let succeeded = 0;
let failed = 0;
const errors: string[] = [];
// Process users in batches
for (let i = 0; i < users.length; i += concurrencyLimit) {
const batch = users.slice(i, i + concurrencyLimit);
const batchPromises = batch.map(async (user) => {
try {
let result;
switch (operation) {
case 'create':
result = await userManager.createUser(user);
break;
case 'update':
result = await userManager.updateUser(user.userPrincipalName, user);
break;
case 'delete':
result = await userManager.deleteUser(user.userPrincipalName);
break;
}
if (result.success) {
succeeded++;
} else {
failed++;
errors.push(`User ${user.userPrincipalName}: ${result.errors?.join(', ')}`);
}
} catch (error: any) {
failed++;
errors.push(`User ${user.userPrincipalName}: ${error.message}`);
}
});
await Promise.all(batchPromises);
// Add delay between batches to respect API limits
if (i + concurrencyLimit < users.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return { succeeded, failed, errors };
}Guest User Management
async function inviteGuest(
userManager: M365UserManager,
email: string,
redirectUrl: string
): Promise<boolean> {
try {
const guestInvitation = {
invitedUserEmailAddress: email,
sendInvitationMessage: true,
inviteRedirectUrl: redirectUrl
};
await userManager.graphClient
.api('/invitations')
.post(guestInvitation);
console.log(`Guest invitation sent to ${email}`);
return true;
} catch (error: any) {
console.error(`Failed to invite guest: ${error.message}`);
return false;
}
}
async function manageGuestAccess(
userManager: M365UserManager,
email: string,
groups: string[],
action: 'add' | 'remove'
): Promise<boolean> {
try {
const guest = await userManager.getUserByEmail(email);
if (!guest) {
console.error(`Guest ${email} not found`);
return false;
}
for (const groupId of groups) {
if (action === 'add') {
await exchangeManager.addMemberToDistributionGroup(groupId, guest.id);
} else {
await exchangeManager.removeMemberFromDistributionGroup(groupId, guest.id);
}
}
return true;
} catch (error: any) {
console.error(`Failed to manage guest access: ${error.message}`);
return false;
}
}Teams Administration Patterns
Team Template System
interface TeamTemplate {
name: string;
description: string;
channels: ChannelTemplate[];
apps: string[];
policies: TeamPolicies;
}
interface ChannelTemplate {
name: string;
description?: string;
isFavorite: boolean;
}
interface TeamPolicies {
allowCreateUpdateRemoveChannels: boolean;
allowCreateUpdateRemoveConnectors: boolean;
allowAddRemoveApps: boolean;
}
const teamTemplates: Record<string, TeamTemplate> = {
'project-team': {
name: 'Project Team',
description: 'Standard project team configuration',
channels: [
{ name: 'General', description: 'General discussions', isFavorite: true },
{ name: 'Announcements', description: 'Project announcements', isFavorite: true },
{ name: 'Development', description: 'Development discussions', isFavorite: false },
{ name: 'Testing', description: 'Testing discussions', isFavorite: false },
{ name: 'Documentation', description: 'Project documentation', isFavorite: false }
],
apps: ['planner', 'onenote', 'tabs'],
policies: {
allowCreateUpdateRemoveChannels: true,
allowCreateUpdateRemoveConnectors: false,
allowAddRemoveApps: false
}
},
'department-team': {
name: 'Department Team',
description: 'Standard department team configuration',
channels: [
{ name: 'General', description: 'General discussions', isFavorite: true },
{ name: 'Announcements', description: 'Department announcements', isFavorite: true },
{ name: 'HR', description: 'HR communications', isFavorite: false },
{ name: 'Events', description: 'Department events', isFavorite: false }
],
apps: ['planner', 'forms', 'tabs'],
policies: {
allowCreateUpdateRemoveChannels: true,
allowCreateUpdateRemoveConnectors: true,
allowAddRemoveApps: false
}
}
};
async function createTeamFromTemplate(
teamsManager: TeamsManager,
templateKey: string,
teamName: string
): Promise<string | null> {
const template = teamTemplates[templateKey];
if (!template) {
console.error(`Template ${templateKey} not found`);
return null;
}
// Create team
const teamResult = await teamsManager.createTeam({
displayName: teamName,
description: template.description
});
if (!teamResult.success || !teamResult.teamId) {
return null;
}
const teamId = teamResult.teamId;
// Create channels from template
for (const channel of template.channels) {
await teamsManager.createChannel(teamId, {
displayName: channel.name,
description: channel.description,
isFavoriteByDefault: channel.isFavorite
});
}
// Apply policies
await teamsManager.updateTeamSettings(teamId, {
memberSettings: {
allowCreateUpdateRemoveChannels: template.policies.allowCreateUpdateRemoveChannels,
allowCreateUpdateRemoveConnectors: template.policies.allowCreateUpdateRemoveConnectors,
allowAddRemoveApps: template.policies.allowAddRemoveApps
}
});
return teamId;
}Team Hierarchy Management
interface TeamHierarchy {
parent: string;
children: string[];
sharedChannels: string[];
}
async function createTeamHierarchy(
teamsManager: TeamsManager,
hierarchy: TeamHierarchy
): Promise<boolean> {
try {
// Create parent team
const parentResult = await teamsManager.createTeam({
displayName: hierarchy.parent,
description: 'Parent team'
});
if (!parentResult.success || !parentResult.teamId) {
return false;
}
const parentTeamId = parentResult.teamId;
// Create child teams
const childTeamIds: string[] = [];
for (const child of hierarchy.children) {
const childResult = await teamsManager.createTeam({
displayName: child,
description: `Child team of ${hierarchy.parent}`
});
if (childResult.success && childResult.teamId) {
childTeamIds.push(childResult.teamId);
}
}
// Create shared channels in parent team
for (const sharedChannel of hierarchy.sharedChannels) {
const channelId = await teamsManager.createChannel(parentTeamId, {
displayName: sharedChannel,
description: 'Shared channel with child teams',
isFavoriteByDefault: false
});
// Share channel with child teams
if (channelId) {
for (const childTeamId of childTeamIds) {
// Note: Shared channels require specific Graph API calls
// This is a simplified example
console.log(`Sharing channel ${sharedChannel} with child team`);
}
}
}
return true;
} catch (error: any) {
console.error(`Failed to create team hierarchy: ${error.message}`);
return false;
}
}Exchange Online Patterns
Email Automation Patterns
async function sendBulkEmail(
exchangeManager: ExchangeManager,
senderUserId: string,
recipients: string[],
subject: string,
body: string,
batchSize: number = 10,
delayBetweenBatches: number = 2000
): Promise<{ sent: number; failed: number; errors: string[] }> {
let sent = 0;
let failed = 0;
const errors: string[] = [];
for (let i = 0; i < recipients.length; i += batchSize) {
const batch = recipients.slice(i, i + batchSize);
for (const recipient of batch) {
try {
await exchangeManager.sendMessage(senderUserId, [recipient], subject, body);
sent++;
} catch (error: any) {
failed++;
errors.push(`Failed to send to ${recipient}: ${error.message}`);
}
}
// Delay between batches
if (i + batchSize < recipients.length) {
await new Promise(resolve => setTimeout(resolve, delayBetweenBatches));
}
}
return { sent, failed, errors };
}Calendar Management
async function scheduleMeeting(
exchangeManager: ExchangeManager,
organizerId: string,
attendees: string[],
subject: string,
duration: number,
startAfter: Date,
preferredTimes: string[]
): Promise<Date | null> {
// Find common available time slots
const availableSlots = await findAvailableTimeSlots(
exchangeManager,
organizerId,
attendees,
startAfter,
preferredTimes
);
if (availableSlots.length === 0) {
console.error('No available time slots found');
return null;
}
// Schedule meeting at first available slot
const slot = availableSlots[0];
const meetingStart = new Date(slot.start);
const meetingEnd = new Date(meetingStart.getTime() + duration * 60000);
const eventId = await exchangeManager.createCalendarEvent(organizerId, {
subject: subject,
start: meetingStart.toISOString(),
end: meetingEnd.toISOString(),
attendees: attendees,
isOnlineMeeting: true
});
if (eventId) {
return meetingStart;
}
return null;
}
async function findAvailableTimeSlots(
exchangeManager: ExchangeManager,
organizerId: string,
attendees: string[],
startDate: Date,
preferredTimes: string[]
): Promise<Array<{ start: string; end: string }>> {
const slots: Array<{ start: string; end: string }> = [];
for (const time of preferredTimes) {
// Check each attendee's calendar for this time slot
let isAvailable = true;
const [hours, minutes] = time.split(':').map(Number);
const slotStart = new Date(startDate);
slotStart.setHours(hours, minutes, 0, 0);
const slotEnd = new Date(slotStart.getTime() + 60 * 60000); // 1 hour
for (const attendee of attendees) {
const events = await exchangeManager.getCalendarEvents(
attendee,
slotStart.toISOString(),
slotEnd.toISOString()
);
if (events.length > 0) {
isAvailable = false;
break;
}
}
if (isAvailable) {
slots.push({
start: slotStart.toISOString(),
end: slotEnd.toISOString()
});
}
}
return slots;
}Security and Compliance Patterns
Conditional Access Management
async function configureConditionalAccess(
accessToken: string,
policyConfig: {
name: string;
conditions: any;
grantControls: any;
sessionControls: any;
}
): Promise<boolean> {
try {
const policy = {
displayName: policyConfig.name,
state: 'enabled',
conditions: policyConfig.conditions,
grantControls: {
operator: 'OR',
builtInControls: policyConfig.grantControls
},
sessionControls: policyConfig.sessionControls
};
const response = await fetch(
'https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(policy)
}
);
return response.ok;
} catch (error: any) {
console.error(`Failed to configure conditional access: ${error.message}`);
return false;
}
}Audit Log Monitoring
async function monitorAuditLogs(
userManager: M365UserManager,
eventType: string,
callback: (logEntry: any) => void,
pollingInterval: number = 60000
): Promise<void> {
let lastCheckTime = new Date();
while (true) {
try {
const logs = await userManager.graphClient
.api('/auditLogs/directoryAudits')
.filter(`activityDateTime ge ${lastCheckTime.toISOString()}`)
.filter(`activityDisplayName eq '${eventType}'`)
.orderby('activityDateTime desc')
.get();
for (const log of logs.value) {
callback(log);
}
lastCheckTime = new Date();
await new Promise(resolve => setTimeout(resolve, pollingInterval));
} catch (error: any) {
console.error(`Audit log monitoring error: ${error.message}`);
await new Promise(resolve => setTimeout(resolve, pollingInterval));
}
}
}License Management
interface LicenseAllocation {
skuId: string;
allocated: number;
available: number;
}
async function getLicenseAvailability(
userManager: M365UserManager
): Promise<Record<string, LicenseAllocation>> {
try {
const subscribedSku = await userManager.graphClient
.api('/subscribedSkus')
.select('skuId,prepaidUnits')
.get();
const availability: Record<string, LicenseAllocation> = {};
for (const sku of subscribedSku.value) {
availability[sku.skuId] = {
skuId: sku.skuId,
allocated: sku.prepaidUnits.consumed,
available: sku.prepaidUnits.enabled - sku.prepaidUnits.consumed
};
}
return availability;
} catch (error: any) {
console.error(`Failed to get license availability: ${error.message}`);
return {};
}
}
async function assignLicenseWithAvailabilityCheck(
userManager: M365UserManager,
userId: string,
licenseSkuId: string
): Promise<boolean> {
// Check availability
const availability = await getLicenseAvailability(userManager);
const licenseInfo = availability[licenseSkuId];
if (!licenseInfo || licenseInfo.available <= 0) {
console.error(`No available licenses for SKU ${licenseSkuId}`);
return false;
}
// Assign license
return await userManager.assignLicenses(userId, [licenseSkuId]);
}Backup and Recovery
async function backupTeam(
teamsManager: TeamsManager,
teamId: string
): Promise<any> {
try {
const backup = {
team: await teamsManager.getTeamSettings(teamId),
channels: await teamsManager.listChannels(teamId),
members: await teamsManager.listMembers(teamId),
timestamp: new Date().toISOString()
};
return backup;
} catch (error: any) {
console.error(`Failed to backup team: ${error.message}`);
return null;
}
}
async function restoreTeam(
teamsManager: TeamsManager,
backup: any
): Promise<boolean> {
try {
// Create team
const teamResult = await teamsManager.createTeam({
displayName: backup.team.displayName,
description: backup.team.description
});
if (!teamResult.success || !teamResult.teamId) {
return false;
}
// Create channels
for (const channel of backup.channels) {
await teamsManager.createChannel(teamResult.teamId, {
displayName: channel.displayName,
description: channel.description,
isFavoriteByDefault: channel.isFavoriteByDefault
});
}
// Add members
for (const member of backup.members) {
await teamsManager.addMember(teamResult.teamId, {
userId: member.id,
role: member.roles[0] || 'Member'
});
}
return true;
} catch (error: any) {
console.error(`Failed to restore team: ${error.message}`);
return false;
}
}Microsoft 365 Admin - Quick Start Guide
This guide helps you get started with the M365 admin skill's scripts and tools.
Prerequisites
- Node.js 16+ installed
- Microsoft 365 tenant with Global Administrator access
- App registered in Azure AD with necessary permissions
- TypeScript installed globally
Azure AD App Registration
1. Create App Registration:
# Go to Azure Portal → App registrations → New registration
# Name: M365 Admin Script
# Supported account types: Accounts in this organizational directory only2. Add API Permissions:
- Microsoft Graph → User.ReadWrite.All
- Microsoft Graph → Group.ReadWrite.All
- Microsoft Graph → Team.Create
- Microsoft Graph → TeamSettings.ReadWrite.All
3. Generate Client Secret:
# Go to Certificates & secrets → New client secret
# Copy the secret value (you won't see it again)4. Grant Admin Consent:
# Go to API permissions → Click Grant admin consent for your organizationInstallation
npm install @azure/identity @microsoft/microsoft-graph-client @azure/msal-node isomorphic-fetch
npm install -D typescript @types/nodeAuthentication
The scripts use Azure AD app authentication:
const userManager = new M365UserManager(
'client-id',
'client-secret',
'tenant-id'
);Or use environment variables:
export AZURE_CLIENT_ID='your-client-id'
export AZURE_CLIENT_SECRET='your-client-secret'
export AZURE_TENANT_ID='your-tenant-id'Quick Examples
Managing Users
import { M365UserManager } from './scripts/create_m365_users';
const userManager = new M365UserManager(
'client-id',
'client-secret',
'tenant-id'
);
// Create a new user
const userResult = await userManager.createUser({
displayName: 'John Doe',
mailNickname: 'jdoe',
userPrincipalName: 'jdoe@yourdomain.com',
password: 'SecurePassword123!',
accountEnabled: true,
usageLocation: 'US',
licenses: ['LICENSE-ID-HERE']
});
if (userResult.success) {
console.log(`User created with ID: ${userResult.userId}`);
}
// List users
const users = await userManager.listUsers();
console.log(users.map(u => u.displayName));
// Block a user
await userManager.blockUser('user-id');
// Reset password
await userManager.resetPassword('user-id', 'NewPassword123!');Configuring Teams
import { TeamsManager } from './scripts/configure_teams';
const teamsManager = new TeamsManager(
'client-id',
'client-secret',
'tenant-id'
);
// Create a new team
const teamResult = await teamsManager.createTeam({
displayName: 'Project Alpha Team',
description: 'Team for Project Alpha development'
});
if (teamResult.success) {
console.log(`Team created with ID: ${teamResult.teamId}`);
// Create channels
const channelId = await teamsManager.createChannel(teamResult.teamId!, {
displayName: 'General',
description: 'General discussions',
isFavoriteByDefault: true
});
// Add members
await teamsManager.addMember(teamResult.teamId!, {
userId: 'user-id',
role: 'Owner'
});
}
// List all teams
const teams = await teamsManager.listTeams();
console.log(teams.map(t => t.displayName));
// Create group chat
const chatId = await teamsManager.createGroupChat(['user1-id', 'user2-id', 'user3-id']);Managing Exchange Online
import { ExchangeManager } from './scripts/setup_exchange';
const exchangeManager = new ExchangeManager(
'client-id',
'client-secret',
'tenant-id'
);
// Create mailbox for user
const mailboxResult = await exchangeManager.createMailbox('user-id');
// Configure auto-reply
await exchangeManager.configureAutoReply(
'user-id',
'Internal: I am out of office',
'External: I am out of office',
'2024-01-01T00:00:00Z',
'2024-01-15T00:00:00Z'
);
// Create distribution group
await exchangeManager.createDistributionGroup({
displayName: 'All Employees',
mailNickname: 'all-employees',
description: 'Distribution list for all employees',
members: ['user-id-1', 'user-id-2']
});
// Get inbox messages
const messages = await exchangeManager.getInboxMessages('user-id', 20);
console.log(messages);
// Send email
await exchangeManager.sendMessage(
'user-id',
['recipient@example.com'],
'Subject',
'<p>Email body in HTML</p>'
);
// Create calendar event
const eventId = await exchangeManager.createCalendarEvent('user-id', {
subject: 'Team Meeting',
body: 'Weekly team sync',
start: '2024-01-15T10:00:00Z',
end: '2024-01-15T11:00:00Z',
attendees: ['attendee1@example.com', 'attendee2@example.com'],
location: 'Conference Room A',
isOnlineMeeting: true
});Common Patterns
Bulk User Creation
const users = [
{
displayName: 'User One',
mailNickname: 'userone',
userPrincipalName: 'user1@domain.com',
password: 'Password123!',
accountEnabled: true,
usageLocation: 'US'
},
{
displayName: 'User Two',
mailNickname: 'usertwo',
userPrincipalName: 'user2@domain.com',
password: 'Password123!',
accountEnabled: true,
usageLocation: 'US'
}
];
const { successes, failures } = await bulkCreateUsers(userManager, users);
console.log(`Created: ${successes.length}, Failed: ${failures.length}`);Team Onboarding Workflow
async function onboardNewTeam(teamName: string, ownerIds: string[], memberIds: string[]) {
// Create team
const team = await teamsManager.createTeam({ displayName: teamName });
if (team.success && team.teamId) {
// Create standard channels
await teamsManager.createChannel(team.teamId, {
displayName: 'General',
description: 'General discussions'
});
await teamsManager.createChannel(team.teamId, {
displayName: 'Announcements',
description: 'Important announcements'
});
await teamsManager.createChannel(team.teamId, {
displayName: 'Documents',
description: 'Shared documents'
});
// Add owner
for (const ownerId of ownerIds) {
await teamsManager.addMember(team.teamId, {
userId: ownerId,
role: 'Owner'
});
}
// Add members
for (const memberId of memberIds) {
await teamsManager.addMember(team.teamId, {
userId: memberId,
role: 'Member'
});
}
}
}User Offboarding Workflow
async function offboardUser(userId: string) {
// Block user account
await userManager.blockUser(userId);
// Reset password
await userManager.resetPassword(userId, generateRandomPassword());
// Remove from all Teams (would require listing teams)
const teams = await teamsManager.listTeams();
for (const team of teams) {
const members = await teamsManager.listMembers(team.id);
for (const member of members) {
if (member.id === userId) {
await teamsManager.removeMember(team.id, member.id);
}
}
}
// Remove from distribution groups (would require listing groups)
const groups = await exchangeManager.getDistributionGroups();
for (const group of groups) {
await exchangeManager.removeMemberFromDistributionGroup(group.id, userId);
}
}Best Practices
1. Validate inputs - Use built-in validation functions 2. Handle errors gracefully - Check result.success before proceeding 3. Use least privilege - Grant only necessary permissions to the app 4. Log all operations - Track user management for audit purposes 5. Test in non-production - Test scripts in a test tenant first 6. Implement retry logic - Add retries for transient failures 7. Use environment variables - Store credentials securely 8. Monitor API limits - Be aware of Graph API throttling limits
Troubleshooting
Authentication Failed
Error: Access token request failedSolutions: 1. Verify client ID, client secret, and tenant ID are correct 2. Check if app registration is still active 3. Ensure admin consent has been granted 4. Verify API permissions are correct
User Already Exists
Error: Request returned status code 400 with message: Another object with the same value for property userPrincipalName already existsSolutions: 1. Check if user already exists using getUserByEmail() 2. Use updateUser() instead of creating a new user 3. Or delete the existing user first
Permission Denied
Error: Access is denied. Check credentials and try again.Solutions: 1. Verify app has necessary permissions 2. Grant admin consent for the permissions 3. Check if you're using the correct app registration
Team Creation Failed
Error: Failed to create teamSolutions: 1. Verify team name doesn't already exist 2. Check if Graph API permissions are correct 3. Ensure Microsoft Teams license is assigned to users
License Assignment Failed
Error: Insufficient licenses availableSolutions: 1. Check available licenses in Microsoft 365 admin center 2. Verify license SKU ID is correct 3. Purchase additional licenses if needed
API Limits
Microsoft Graph API has throttling limits:
- Requests per minute: Up to 100 requests per app
- Requests per 10 seconds: Up to 15 requests per app
- Concurrent requests: Up to 10 requests
For bulk operations, implement batching and retry logic:
async function bulkOperationWithRetry<T>(
operation: () => Promise<T>,
maxRetries = 3
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error: any) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 2000 * (i + 1)));
}
}
throw new Error('Max retries exceeded');
}Security Considerations
1. Never hardcode credentials - Use environment variables or key vault 2. Rotate client secrets regularly - Update secrets every 90 days 3. Use certificate-based auth - More secure than client secrets for production 4. Monitor audit logs - Review Microsoft 365 audit logs regularly 5. Implement conditional access - Require MFA for admin operations 6. Use privileged identity management - Just-in-time admin access 7. Regularly review permissions - Remove unnecessary app permissions
Additional Resources
import { ClientSecretCredential } from '@azure/identity';
import { Client } from '@microsoft/microsoft-graph-client';
import 'isomorphic-fetch';
export interface TeamConfig {
displayName: string;
description?: string;
teamId?: string;
}
export interface TeamCreationResult {
success: boolean;
teamId?: string;
errors?: string[];
}
export interface ChannelConfig {
displayName: string;
description?: string;
isFavoriteByDefault?: boolean;
}
export interface TeamMemberConfig {
userId: string;
role: 'Owner' | 'Member' | 'Guest';
}
export class TeamsManager {
private graphClient: Client;
constructor(private clientId: string, private clientSecret: string, private tenantId: string) {
const credential = new ClientSecretCredential(
this.tenantId,
this.clientId,
this.clientSecret
);
this.graphClient = Client.initWithMiddleware({
authProvider: {
getAccessToken: async () => {
const token = await credential.getToken('https://graph.microsoft.com/.default');
return token.token;
}
}
});
}
async createTeam(config: TeamConfig): Promise<TeamCreationResult> {
const result: TeamCreationResult = { success: false, errors: [] };
try {
if (!config.displayName) {
throw new Error('Team display name is required');
}
const team = {
template@odata.bind: 'https://graph.microsoft.com/v1.0/teamsTemplates(\'standard\')',
displayName: config.displayName,
description: config.description || '',
'members@odata.bind': []
};
const response = await this.graphClient.api('/teams').post(team);
result.success = true;
result.teamId = response.id;
} catch (error: any) {
result.errors?.push(`Team creation failed: ${error.message}`);
}
return result;
}
async deleteTeam(teamId: string): Promise<boolean> {
try {
await this.graphClient.api(`/teams/${teamId}`).delete();
return true;
} catch (error: any) {
console.error(`Failed to delete team: ${error.message}`);
return false;
}
}
async createChannel(teamId: string, config: ChannelConfig): Promise<string | null> {
try {
if (!config.displayName) {
throw new Error('Channel display name is required');
}
const channel = {
displayName: config.displayName,
description: config.description || '',
isFavoriteByDefault: config.isFavoriteByDefault ?? false
};
const response = await this.graphClient
.api(`/teams/${teamId}/channels`)
.post(channel);
return response.id;
} catch (error: any) {
console.error(`Failed to create channel: ${error.message}`);
return null;
}
}
async addMember(teamId: string, member: TeamMemberConfig): Promise<boolean> {
try {
const teamMember = {
'@odata.type': '#microsoft.graph.aadUserConversationMember',
roles: [member.role],
'user@odata.bind': `https://graph.microsoft.com/v1.0/users('${member.userId}')`
};
await this.graphClient
.api(`/teams/${teamId}/members`)
.post(teamMember);
return true;
} catch (error: any) {
console.error(`Failed to add team member: ${error.message}`);
return false;
}
}
async removeMember(teamId: string, membershipId: string): Promise<boolean> {
try {
await this.graphClient
.api(`/teams/${teamId}/members/${membershipId}`)
.delete();
return true;
} catch (error: any) {
console.error(`Failed to remove team member: ${error.message}`);
return false;
}
}
async listTeams(): Promise<any[]> {
try {
const response = await this.graphClient
.api('/me/joinedTeams')
.select('id,displayName,description,teamId')
.get();
return response.value || [];
} catch (error: any) {
console.error(`Failed to list teams: ${error.message}`);
return [];
}
}
async listChannels(teamId: string): Promise<any[]> {
try {
const response = await this.graphClient
.api(`/teams/${teamId}/channels`)
.select('id,displayName,description,createdDateTime')
.get();
return response.value || [];
} catch (error: any) {
console.error(`Failed to list channels: ${error.message}`);
return [];
}
}
async listMembers(teamId: string): Promise<any[]> {
try {
const response = await this.graphClient
.api(`/teams/${teamId}/members`)
.select('id,displayName,email,roles')
.expand('user')
.get();
return response.value || [];
} catch (error: any) {
console.error(`Failed to list members: ${error.message}`);
return [];
}
}
async updateTeam(teamId: string, updates: Partial<TeamConfig>): Promise<boolean> {
try {
const updateData: any = {};
if (updates.displayName) updateData.displayName = updates.displayName;
if (updates.description !== undefined) updateData.description = updates.description;
await this.graphClient
.api(`/teams/${teamId}`)
.patch(updateData);
return true;
} catch (error: any) {
console.error(`Failed to update team: ${error.message}`);
return false;
}
}
async archiveTeam(teamId: string): Promise<boolean> {
try {
await this.graphClient
.api(`/teams/${teamId}/archive`)
.post({ shouldSetSpoSiteReadOnlyForMembers: true });
return true;
} catch (error: any) {
console.error(`Failed to archive team: ${error.message}`);
return false;
}
}
async unarchiveTeam(teamId: string): Promise<boolean> {
try {
await this.graphClient
.api(`/teams/${teamId}/unarchive`)
.post();
return true;
} catch (error: any) {
console.error(`Failed to unarchive team: ${error.message}`);
return false;
}
}
async createGroupChat(userIds: string[]): Promise<string | null> {
try {
const chat = {
chatType: 'group',
members: userIds.map(id => ({
'@odata.type': '#microsoft.graph.aadUserConversationMember',
roles: ['owner'],
'user@odata.bind': `https://graph.microsoft.com/v1.0/users('${id}')`
}))
};
const response = await this.graphClient
.api('/chats')
.post(chat);
return response.id;
} catch (error: any) {
console.error(`Failed to create group chat: ${error.message}`);
return null;
}
}
async getTeamSettings(teamId: string): Promise<any> {
try {
const response = await this.graphClient
.api(`/teams/${teamId}`)
.select('id,displayName,description,guestSettings,memberSettings,messagingSettings,funSettings')
.get();
return response;
} catch (error: any) {
console.error(`Failed to get team settings: ${error.message}`);
return null;
}
}
async updateTeamSettings(teamId: string, settings: any): Promise<boolean> {
try {
await this.graphClient
.api(`/teams/${teamId}`)
.patch(settings);
return true;
} catch (error: any) {
console.error(`Failed to update team settings: ${error.message}`);
return false;
}
}
}
import { ClientSecretCredential, DefaultAzureCredential } from '@azure/identity';
import { Client } from '@microsoft/microsoft-graph-client';
import 'isomorphic-fetch';
export interface M365UserConfig {
displayName: string;
mailNickname: string;
userPrincipalName: string;
password: string;
accountEnabled: boolean;
usageLocation?: string;
licenses?: string[];
}
export interface UserCreationResult {
success: boolean;
userId?: string;
errors?: string[];
}
export class M365UserManager {
private graphClient: Client;
constructor(private clientId: string, private clientSecret: string, private tenantId: string) {
const credential = new ClientSecretCredential(
this.tenantId,
this.clientId,
this.clientSecret
);
this.graphClient = Client.initWithMiddleware({
authProvider: {
getAccessToken: async () => {
const token = await credential.getToken('https://graph.microsoft.com/.default');
return token.token;
}
}
});
}
async createUser(config: M365UserConfig): Promise<UserCreationResult> {
const result: UserCreationResult = { success: false, errors: [] };
try {
if (!config.displayName || !config.userPrincipalName || !config.password) {
throw new Error('displayName, userPrincipalName, and password are required');
}
const user = {
accountEnabled: config.accountEnabled ?? true,
displayName: config.displayName,
mailNickname: config.mailNickname || config.userPrincipalName.split('@')[0],
passwordProfile: {
forceChangePasswordNextSignIn: true,
password: config.password
},
usageLocation: config.usageLocation || 'US',
userPrincipalName: config.userPrincipalName
};
const response = await this.graphClient.api('/users').post(user);
if (response.id && config.licenses && config.licenses.length > 0) {
await this.assignLicenses(response.id, config.licenses);
}
result.success = true;
result.userId = response.id;
} catch (error: any) {
result.errors?.push(`User creation failed: ${error.message}`);
}
return result;
}
async deleteUser(userId: string): Promise<boolean> {
try {
await this.graphClient.api(`/users/${userId}`).delete();
return true;
} catch (error: any) {
console.error(`Failed to delete user: ${error.message}`);
return false;
}
}
async assignLicenses(userId: string, licenseIds: string[]): Promise<boolean> {
try {
const licenseAssignment = {
addLicenses: licenseIds.map(id => ({
disabledPlans: [],
skuId: id
})),
removeLicenses: []
};
await this.graphClient.api(`/users/${userId}/assignLicense`).post(licenseAssignment);
return true;
} catch (error: any) {
console.error(`Failed to assign licenses: ${error.message}`);
return false;
}
}
async getUserByEmail(email: string): Promise<any> {
try {
const user = await this.graphClient
.api(`/users/${email}`)
.select('id,displayName,mail,userPrincipalName,accountEnabled')
.get();
return user;
} catch (error: any) {
console.error(`Failed to get user: ${error.message}`);
return null;
}
}
async listUsers(filter?: string): Promise<any[]> {
try {
let request = this.graphClient
.api('/users')
.select('id,displayName,mail,userPrincipalName,accountEnabled,createdDateTime')
.top(999);
if (filter) {
request = request.filter(filter);
}
const response = await request.get();
return response.value || [];
} catch (error: any) {
console.error(`Failed to list users: ${error.message}`);
return [];
}
}
async updateUser(userId: string, updates: Partial<M365UserConfig>): Promise<boolean> {
try {
const updateData: any = {};
if (updates.displayName) updateData.displayName = updates.displayName;
if (updates.accountEnabled !== undefined) updateData.accountEnabled = updates.accountEnabled;
if (updates.usageLocation) updateData.usageLocation = updates.usageLocation;
await this.graphClient.api(`/users/${userId}`).patch(updateData);
return true;
} catch (error: any) {
console.error(`Failed to update user: ${error.message}`);
return false;
}
}
async resetPassword(userId: string, newPassword: string): Promise<boolean> {
try {
await this.graphClient.api(`/users/${userId}/changePassword`).post({
currentPassword: newPassword,
newPassword: newPassword
});
return true;
} catch (error: any) {
console.error(`Failed to reset password: ${error.message}`);
return false;
}
}
async blockUser(userId: string): Promise<boolean> {
return this.updateUser(userId, { accountEnabled: false });
}
async unblockUser(userId: string): Promise<boolean> {
return this.updateUser(userId, { accountEnabled: true });
}
}
export function validateUserPrincipalName(upn: string): boolean {
const upnPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return upnPattern.test(upn);
}
export function validatePassword(password: string): { valid: boolean; errors: string[] } {
const errors: string[] = [];
if (password.length < 8) {
errors.push('Password must be at least 8 characters long');
}
if (!/[A-Z]/.test(password)) {
errors.push('Password must contain at least one uppercase letter');
}
if (!/[a-z]/.test(password)) {
errors.push('Password must contain at least one lowercase letter');
}
if (!/[0-9]/.test(password)) {
errors.push('Password must contain at least one number');
}
if (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) {
errors.push('Password must contain at least one special character');
}
return {
valid: errors.length === 0,
errors
};
}
export async function bulkCreateUsers(
userManager: M365UserManager,
users: M365UserConfig[]
): Promise<{ successes: UserCreationResult[]; failures: UserCreationResult[] }> {
const successes: UserCreationResult[] = [];
const failures: UserCreationResult[] = [];
for (const user of users) {
const result = await userManager.createUser(user);
if (result.success) {
successes.push(result);
} else {
failures.push(result);
}
}
return { successes, failures };
}
import { ClientSecretCredential } from '@azure/identity';
import { Client } from '@microsoft/microsoft-graph-client';
import 'isomorphic-fetch';
export interface MailboxConfig {
displayName: string;
email: string;
userId: string;
}
export interface MailboxResult {
success: boolean;
mailboxId?: string;
errors?: string[];
}
export interface DistributionGroupConfig {
displayName: string;
mailNickname: string;
description?: string;
members?: string[];
}
export interface CalendarEventConfig {
subject: string;
body?: string;
start: string;
end: string;
attendees?: string[];
location?: string;
isOnlineMeeting?: boolean;
}
export class ExchangeManager {
private graphClient: Client;
constructor(private clientId: string, private clientSecret: string, private tenantId: string) {
const credential = new ClientSecretCredential(
this.tenantId,
this.clientId,
this.clientSecret
);
this.graphClient = Client.initWithMiddleware({
authProvider: {
getAccessToken: async () => {
const token = await credential.getToken('https://graph.microsoft.com/.default');
return token.token;
}
}
});
}
async createMailbox(userId: string): Promise<MailboxResult> {
const result: MailboxResult = { success: false, errors: [] };
try {
if (!userId) {
throw new Error('User ID is required');
}
const user = await this.graphClient
.api(`/users/${userId}`)
.select('id,mail,displayName')
.get();
if (!user.mail) {
throw new Error('User does not have a mailbox');
}
result.success = true;
result.mailboxId = user.id;
} catch (error: any) {
result.errors?.push(`Mailbox creation failed: ${error.message}`);
}
return result;
}
async getMailboxSettings(userId: string): Promise<any> {
try {
const settings = await this.graphClient
.api(`/users/${userId}/mailboxSettings`)
.get();
return settings;
} catch (error: any) {
console.error(`Failed to get mailbox settings: ${error.message}`);
return null;
}
}
async updateMailboxSettings(userId: string, settings: any): Promise<boolean> {
try {
await this.graphClient
.api(`/users/${userId}/mailboxSettings`)
.patch(settings);
return true;
} catch (error: any) {
console.error(`Failed to update mailbox settings: ${error.message}`);
return false;
}
}
async configureAutoReply(
userId: string,
internalReply: string,
externalReply: string,
startDate?: string,
endDate?: string
): Promise<boolean> {
try {
const autoReplySettings: any = {
status: 'Enabled',
internalReplyMessage: internalReply,
externalReplyMessage: externalReply
};
if (startDate && endDate) {
autoReplySettings.scheduledStartDateTime = {
dateTime: startDate,
timeZone: 'UTC'
};
autoReplySettings.scheduledEndDateTime = {
dateTime: endDate,
timeZone: 'UTC'
};
}
await this.graphClient
.api(`/users/${userId}/mailboxSettings/automaticRepliesSetting`)
.patch(autoReplySettings);
return true;
} catch (error: any) {
console.error(`Failed to configure auto-reply: ${error.message}`);
return false;
}
}
async createDistributionGroup(config: DistributionGroupConfig): Promise<boolean> {
try {
const group = {
displayName: config.displayName,
mailNickname: config.mailNickname,
description: config.description || '',
mailEnabled: true,
securityEnabled: false,
'members@odata.bind': config.members?.map(id =>
`https://graph.microsoft.com/v1.0/users('${id}')`
) || []
};
await this.graphClient.api('/groups').post(group);
return true;
} catch (error: any) {
console.error(`Failed to create distribution group: ${error.message}`);
return false;
}
}
async addMemberToDistributionGroup(groupId: string, userId: string): Promise<boolean> {
try {
await this.graphClient
.api(`/groups/${groupId}/members/$ref`)
.post({
'@odata.id': `https://graph.microsoft.com/v1.0/users('${userId}')`
});
return true;
} catch (error: any) {
console.error(`Failed to add member to group: ${error.message}`);
return false;
}
}
async removeMemberFromDistributionGroup(groupId: string, userId: string): Promise<boolean> {
try {
await this.graphClient
.api(`/groups/${groupId}/members/${userId}/$ref`)
.delete();
return true;
} catch (error: any) {
console.error(`Failed to remove member from group: ${error.message}`);
return false;
}
}
async getDistributionGroups(): Promise<any[]> {
try {
const response = await this.graphClient
.api('/groups')
.filter("mailEnabled eq true and securityEnabled eq false")
.select('id,displayName,mail,description')
.get();
return response.value || [];
} catch (error: any) {
console.error(`Failed to get distribution groups: ${error.message}`);
return [];
}
}
async createCalendarEvent(userId: string, event: CalendarEventConfig): Promise<string | null> {
try {
const eventBody = {
subject: event.subject,
body: {
contentType: 'HTML',
content: event.body || ''
},
start: {
dateTime: event.start,
timeZone: 'UTC'
},
end: {
dateTime: event.end,
timeZone: 'UTC'
},
location: event.location ? {
displayName: event.location
} : undefined,
attendees: event.attendees?.map(email => ({
emailAddress: {
address: email
},
type: 'required'
})),
isOnlineMeeting: event.isOnlineMeeting ?? false
};
const response = await this.graphClient
.api(`/users/${userId}/calendar/events`)
.post(eventBody);
return response.id;
} catch (error: any) {
console.error(`Failed to create calendar event: ${error.message}`);
return null;
}
}
async getCalendarEvents(userId: string, startDate: string, endDate: string): Promise<any[]> {
try {
const response = await this.graphClient
.api(`/users/${userId}/calendar/calendarView`)
.query({
startDateTime: startDate,
endDateTime: endDate
})
.select('id,subject,start,end,location,attendees')
.orderby('start/dateTime')
.get();
return response.value || [];
} catch (error: any) {
console.error(`Failed to get calendar events: ${error.message}`);
return [];
}
}
async getInboxMessages(userId: string, limit: number = 50): Promise<any[]> {
try {
const response = await this.graphClient
.api(`/users/${userId}/mailFolders/Inbox/messages`)
.select('id,subject,from,receivedDateTime,isRead,hasAttachments')
.top(limit)
.orderby('receivedDateTime desc')
.get();
return response.value || [];
} catch (error: any) {
console.error(`Failed to get inbox messages: ${error.message}`);
return [];
}
}
async sendMessage(userId: string, to: string[], subject: string, body: string): Promise<boolean> {
try {
const message = {
subject: subject,
body: {
contentType: 'HTML',
content: body
},
toRecipients: to.map(email => ({
emailAddress: {
address: email
}
}))
};
await this.graphClient
.api(`/users/${userId}/sendMail`)
.post({ message: message });
return true;
} catch (error: any) {
console.error(`Failed to send message: ${error.message}`);
return false;
}
}
async forwardMessage(userId: string, messageId: string, to: string[]): Promise<boolean> {
try {
await this.graphClient
.api(`/users/${userId}/messages/${messageId}/forward`)
.post({
toRecipients: to.map(email => ({
emailAddress: {
address: email
}
}))
});
return true;
} catch (error: any) {
console.error(`Failed to forward message: ${error.message}`);
return false;
}
}
async deleteMessage(userId: string, messageId: string): Promise<boolean> {
try {
await this.graphClient
.api(`/users/${userId}/messages/${messageId}`)
.delete();
return true;
} catch (error: any) {
console.error(`Failed to delete message: ${error.message}`);
return false;
}
}
}