πŸ”§ New: User Management Graph PowerShell Toolkit

Simplify user tasks like bulk creation, updates, password resets, deletions, license checks & more β€” all from one place.

πŸš€ Launch Toolkit

Fetch Unlicensed Users & Email Report with Graph PowerShell

Keeping 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.


i) Script

# ===== 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
                            


ii) How the Script Works

  1. Variables
  2. You set the sender ($FromUser), recipient ($To), subject, and export directory for the CSV.

  3. Connect to Microsoft Graph
  4. The script loads the unified Microsoft.Graph module and connects using delegated scopes:

    • User.Read.All to read users
    • Mail.Send to send the email from $FromUser
  5. Build the Filter
  6. 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.

  7. Fetch Users
  8. Get-MgUser pulls matching users and selects a compact set of properties (ID, DisplayName, UPN, JobTitle, Department, etc.).

  9. Export to CSV
  10. The results are written to a timestamped CSV file in $CsvOutDir.

  11. Compose Email
  12. The script builds a small HTML summary and attaches the CSV as a fileAttachment (base64-encoded) to the message body.

  13. Send the Email
  14. Send-MgUserMail sends the message from $FromUser to $To and saves a copy to Sent Items.


iii) Further Enhancements

  • Schedule with Task Scheduler: Run daily/weekly to keep ops updated.
  • Add CSV Preview in Email: Include the first 10 rows as an HTML table in the mail body.
  • Filter Variants: Add toggles for guests, disabled accounts, or specific departments/locations.
  • App-Only Auth: Convert to certificate-based app auth for unattended automation.
  • Logging & Metrics: Write event logs (success/failure), track counts over time, or push to a dashboard.

Possible Errors & Solutions

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.

Conclusion

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.


Graph PowerShell Explorer Widget

20 Graph PowerShell cmdlets with easily accessible "working" examples.


Permission Required

Example:


                


                


                

© m365corner.com. All Rights Reserved. Design by HTML Codex