Simplify user tasks like bulk creation, updates, password resets, deletions, license checks & more β all from one place.
π Launch ToolkitKeeping tabs on unlicensed users helps avoid surprise costs and ensures accounts are provisioned correctly. This simple, no-functions script pulls all unlicensed, enabled member accounts and emails the list (as a CSV attachment) to an admin mailbox using Microsoft Graph PowerShell.
# ===== Simple Graph PowerShell Script =====
# Fetch all UNLICENSED users and email the list to admin
# Requires: Microsoft.Graph module
# Scopes: User.Read.All, Mail.Send
# --- Variables ---
$FromUser = "admin@contoso.com" # Sender (must have mailbox)
$To = "it-ops@contoso.com" # Recipient
$Subject = "Unlicensed users report"
$CsvOutDir = "$env:TEMP"
# --- Connect to Microsoft Graph ---
Import-Module Microsoft.Graph -ErrorAction Stop
Connect-MgGraph -Scopes "User.Read.All","Mail.Send"
# --- Build Filter: unlicensed members only, enabled accounts ---
# Note: Using $count in filter requires -ConsistencyLevel eventual and -CountVariable
$filter = "assignedLicenses/`$count eq 0 and userType eq 'Member' and accountEnabled eq true"
# --- Fetch Users ---
$selectProps = "id","displayName","userPrincipalName","jobTitle","department","accountEnabled","createdDateTime"
$null = $null
$users = Get-MgUser -All -Filter $filter -ConsistencyLevel eventual -CountVariable total -Property $selectProps |
Select-Object $selectProps
# --- Export to CSV ---
if (-not (Test-Path -Path $CsvOutDir)) { New-Item -ItemType Directory -Path $CsvOutDir | Out-Null }
$ts = Get-Date -Format "yyyyMMdd_HHmmss"
$csvPath = Join-Path $CsvOutDir ("UnlicensedUsers_{0}.csv" -f $ts)
$users | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
# --- Prepare HTML Body ---
$summaryHtml = @"
<html>
<body style='font-family:Segoe UI,Arial,sans-serif'>
<Unlicensed Users Report>
<p>Total unlicensed users: <b>$($users.Count)</b></p>
<p>The full list is attached as a CSV.</p>
</body>
</html>
"@
# --- Prepare Attachment ---
$fileBytes = [System.IO.File]::ReadAllBytes($csvPath)
$base64Content = [System.Convert]::ToBase64String($fileBytes)
$csvFileName = [System.IO.Path]::GetFileName($csvPath)
$attachment = @{
"@odata.type" = "#microsoft.graph.fileAttachment"
name = $csvFileName
contentBytes = $base64Content
contentType = "text/csv"
}
# --- Prepare Mail Object (use ${Subject} to avoid colon parsing issue) ---
$mail = @{
message = @{
subject = "${Subject}"
body = @{
contentType = "HTML"
content = $summaryHtml
}
toRecipients = @(@{ emailAddress = @{ address = $To } })
attachments = @($attachment)
}
saveToSentItems = $true
}
# --- Send Email ---
Send-MgUserMail -UserId $FromUser -BodyParameter $mail
Write-Host "Done. CSV saved at: $csvPath" -ForegroundColor Green
You set the sender ($FromUser), recipient ($To), subject, and export directory for the CSV.
The script loads the unified Microsoft.Graph module and connects using delegated scopes:
The OData filter targets unlicensed (assignedLicenses/$count eq 0), enabled (accountEnabled eq true) members (userType eq 'Member'). Because $count is used, the call includes -ConsistencyLevel eventual.
Get-MgUser pulls matching users and selects a compact set of properties (ID, DisplayName, UPN, JobTitle, Department, etc.).
The results are written to a timestamped CSV file in $CsvOutDir.
The script builds a small HTML summary and attaches the CSV as a fileAttachment (base64-encoded) to the message body.
Send-MgUserMail sends the message from $FromUser to $To and saves a copy to Sent Items.
Error | Cause | Solution |
---|---|---|
Authorization_RequestDenied | Missing User.Read.All or Mail.Send delegated consent | Reconnect and grant required permissions; tenant admin consent may be needed. |
The term 'Get-MgUser' is not recognized | Microsoft.Graph not installed/loaded | Install-Module Microsoft.Graph -Scope CurrentUser then Import-Module Microsoft.Graph. |
Empty CSV file | No users match the filter (already licensed, disabled, or not members) | Verify criteria; remove parts of the filter (e.g., test only assignedLicenses/$count eq 0). |
Email not delivered | $FromUser isnβt mailbox-enabled or lacks send ability | Use a mailbox-enabled user or shared mailbox; confirm send permissions. |
HTML rendering looks plain | Client strips styles | Keep inline styles minimal; consider plain text if needed. |
This lightweight script gives you a fast, reliable way to spot and share unlicensed user accounts across your tenant. It pairs a precise Graph filter with a simple email workflow, making it ideal for routine compliance checks and onboarding/offboarding audits. Tweak the filter or delivery details as your processes evolve β the foundation is ready.
© m365corner.com. All Rights Reserved. Design by HTML Codex