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.