Introduction to Microsoft Graph API with PowerShell
The Microsoft Graph API is the unified REST endpoint for Microsoft 365, Azure Active Directory, Teams, SharePoint, Outlook, OneDrive, and many other Microsoft cloud services. From Windows Server 2019, PowerShell can call Graph API endpoints directly using the Microsoft.Graph PowerShell SDK or raw Invoke-RestMethod calls with OAuth 2.0 tokens. This enables automation of cloud identity, licensing, Teams management, and compliance reporting from on-premises scripts and scheduled tasks.
Registering an App in Azure AD for API Access
All Graph API calls require an OAuth 2.0 access token. For server-side automation (unattended scripts), use the client credentials flow with a service principal:
# Install the AzureAD module to register the app
Install-Module -Name AzureAD -Force
Connect-AzureAD
# Create the application registration
$app = New-AzureADApplication `
-DisplayName 'ServerAutomation-GraphAPI' `
-IdentifierUris 'https://corp.local/GraphAutomation' `
-ReplyUrls 'https://localhost'
# Create a service principal for the app
$sp = New-AzureADServicePrincipal -AppId $app.AppId
# Create a client secret
$startDate = Get-Date
$endDate = $startDate.AddYears(2)
$secret = New-AzureADApplicationPasswordCredential `
-ObjectId $app.ObjectId `
-CustomKeyIdentifier 'ServerAutomation' `
-StartDate $startDate `
-EndDate $endDate
Write-Output "App ID (client_id): $($app.AppId)"
Write-Output "Tenant ID: $((Get-AzureADTenantDetail).ObjectId)"
Write-Output "Client Secret: $($secret.Value)"
Write-Warning "Store the secret securely - it cannot be retrieved again"
# Grant API permissions (done in Azure AD portal or via PowerShell)
# Portal: App registrations > ServerAutomation-GraphAPI > API permissions
# Add: Microsoft Graph > Application permissions > User.Read.All, Group.Read.All, Directory.Read.All
# Then click "Grant admin consent for [tenant]"
Installing the Microsoft Graph PowerShell SDK
# Install on Windows Server 2019
Install-Module Microsoft.Graph -Force -AllowClobber
# Install a specific scope module only (reduces install time)
Install-Module Microsoft.Graph.Users -Force
Install-Module Microsoft.Graph.Groups -Force
Install-Module Microsoft.Graph.Reports -Force
Install-Module Microsoft.Graph.Identity.DirectoryManagement -Force
# Verify
Get-Module -ListAvailable Microsoft.Graph* | Select-Object Name, Version
Authenticating with Client Credentials (Unattended)
# Store credentials securely (never hardcode secrets)
$tenantId = '00000000-0000-0000-0000-000000000001'
$clientId = '00000000-0000-0000-0000-000000000002'
$clientSecret = Get-Content 'C:Secretsgraph-client-secret.txt' | ConvertTo-SecureString
# Connect using the SDK
$credential = New-Object System.Management.Automation.PSCredential($clientId, $clientSecret)
Connect-MgGraph -TenantId $tenantId -ClientSecretCredential $credential -NoWelcome
# Verify connection
Get-MgContext | Select-Object TenantId, AppName, Scopes
# Alternative: Raw OAuth token via Invoke-RestMethod
$tokenBody = @{
client_id = $clientId
client_secret = (New-Object PSCredential 'x', $clientSecret).GetNetworkCredential().Password
scope = 'https://graph.microsoft.com/.default'
grant_type = 'client_credentials'
}
$tokenResponse = Invoke-RestMethod `
-Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" `
-Method POST -Body $tokenBody
$accessToken = $tokenResponse.access_token
# Use the token in Graph API calls
$headers = @{ Authorization = "Bearer $accessToken" }
Common Graph API Operations with PowerShell SDK
# --- USER MANAGEMENT ---
# Get all users with specific properties
Get-MgUser -All -Property DisplayName, UserPrincipalName, AccountEnabled, Department, AssignedLicenses |
Select-Object DisplayName, UserPrincipalName, AccountEnabled, Department |
Export-Csv 'C:ReportsAllUsers_Graph.csv' -NoTypeInformation
# Get users without a license
$unlicensed = Get-MgUser -All -Property DisplayName, UserPrincipalName, AssignedLicenses |
Where-Object { $_.AssignedLicenses.Count -eq 0 -and $_.AccountEnabled }
$unlicensed | Select-Object DisplayName, UserPrincipalName | Format-Table
# Create a new user via Graph
$userParams = @{
AccountEnabled = $true
DisplayName = 'Jane Doe'
MailNickname = 'janedoe'
UserPrincipalName = '[email protected]'
PasswordProfile = @{
ForceChangePasswordNextSignIn = $true
Password = 'TempPass!2024'
}
Department = 'Finance'
JobTitle = 'Financial Analyst'
}
New-MgUser -BodyParameter $userParams
License Management via Graph API
# List available SKUs (license types) in the tenant
Get-MgSubscribedSku | Select-Object SkuPartNumber, ConsumedUnits,
@{N='Available';E={$_.PrepaidUnits.Enabled - $_.ConsumedUnits}}
# Assign Microsoft 365 E3 license to a user
$skuId = (Get-MgSubscribedSku | Where-Object { $_.SkuPartNumber -eq 'ENTERPRISEPACK' }).SkuId
Set-MgUserLicense -UserId '[email protected]' `
-AddLicenses @{ SkuId = $skuId } `
-RemoveLicenses @()
# Bulk assign licenses from CSV
$users = Import-Csv 'C:ImportsUsersToLicense.csv'
foreach ($u in $users) {
Set-MgUserLicense -UserId $u.UserPrincipalName `
-AddLicenses @{ SkuId = $skuId } `
-RemoveLicenses @()
Write-Output "Licensed: $($u.UserPrincipalName)"
}
Microsoft 365 Groups and Teams Management
# Create a Microsoft 365 Group (also creates a Teams team if Teams license assigned)
$groupParams = @{
DisplayName = 'Project Orion'
Description = 'Project Orion collaboration group'
MailNickname = 'project-orion'
GroupTypes = @('Unified')
MailEnabled = $true
SecurityEnabled = $false
Visibility = 'Private'
}
$group = New-MgGroup -BodyParameter $groupParams
# Add members to the group
Add-MgGroupMember -GroupId $group.Id -DirectoryObjectId (Get-MgUser -UserId '[email protected]').Id
# List all Teams in the tenant (requires Teams.ReadBasic.All permission)
Get-MgTeam -All | Select-Object DisplayName, Id, Visibility | Format-Table
# Get all channels in a team
Get-MgTeamChannel -TeamId $group.Id | Select-Object DisplayName, MembershipType
# Create a channel
New-MgTeamChannel -TeamId $group.Id `
-DisplayName 'Engineering' `
-Description 'Engineering discussion channel' `
-MembershipType Standard
Usage and Compliance Reports
# Get Microsoft 365 service usage reports (requires Reports.Read.All)
# Exchange activity report (last 30 days)
$report = Invoke-MgGraphRequest `
-Method GET `
-Uri 'https://graph.microsoft.com/v1.0/reports/getEmailActivityUserDetail(period=''D30'')' `
-OutputType PSObject
# SharePoint site usage
Invoke-MgGraphRequest `
-Method GET `
-Uri 'https://graph.microsoft.com/v1.0/reports/getSharePointSiteUsageDetail(period=''D30'')' `
-OutputFilePath 'C:ReportsSharePointUsage.csv'
# Get sign-in logs (requires AuditLog.Read.All)
$signIns = Get-MgAuditLogSignIn -Filter "createdDateTime ge 2024-01-01" `
-Top 1000 -Property UserDisplayName, UserPrincipalName, AppDisplayName,
IpAddress, Status, ConditionalAccessStatus
$failedSignIns = $signIns | Where-Object { $_.Status.ErrorCode -ne 0 }
$failedSignIns | Select-Object UserPrincipalName, AppDisplayName, IpAddress |
Export-Csv 'C:ReportsFailedSignIns.csv' -NoTypeInformation
Automating with Scheduled Tasks on Windows Server 2019
# Securely store the client secret as an encrypted file
$clientSecretPlain = Read-Host -Prompt 'Enter client secret' -AsSecureString
$clientSecretPlain | Export-Clixml 'C:Secretsgraph-secret.xml'
# In the scheduled script, decrypt:
$clientSecret = Import-Clixml 'C:Secretsgraph-secret.xml'
$credential = New-Object PSCredential($clientId, $clientSecret)
Connect-MgGraph -TenantId $tenantId -ClientSecretCredential $credential -NoWelcome
# Register the task
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-NonInteractive -NoProfile -File "C:ScriptsGraph-DailyReport.ps1"'
$trigger = New-ScheduledTaskTrigger -Daily -At '06:00AM'
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 1)
Register-ScheduledTask -TaskName 'Graph Daily Report' -Action $action `
-Trigger $trigger -Settings $settings -RunLevel Highest `
-User 'SYSTEM'
Conclusion
Using the Microsoft Graph API from PowerShell on Windows Server 2019 enables comprehensive automation of Microsoft 365 identity, licensing, Teams, and compliance reporting. The Microsoft.Graph SDK abstracts the raw REST calls while preserving the ability to use Invoke-RestMethod for any Graph endpoint. Proper credential handling using encrypted XML files or Azure Key Vault references, combined with least-privilege Graph API permissions, ensures that automation scripts operate securely in production environments.