Files
asg/scripts/User-Recertification.ps1
mattspeer 5ef82842f2 refactor(scripts): optimize system remediation and M365 export scripts
- teams: harden Classic Teams removal, protect New Teams, and clean HKU run keys
- dotnet: add bootstrapper EXE uninstall support and remove orphaned 3.1.* folders
- teams: add MSIX package targeting and version parsing for vulnerable New Teams builds
- m365: improve Graph SDK export speed, reuse sessions, and add null-safe handling
2026-10-04 10:58:05 -05:00

109 lines
3.6 KiB
PowerShell

<#
.SYNOPSIS
Exports Microsoft 365 user recertification and licensing report using Microsoft Graph SDK.
.DESCRIPTION
Retrieves all M365 users, resolves license SKU IDs to readable part numbers,
extracts last interactive sign-in dates, and exports the dataset to CSV.
.EXAMPLE
.\Export-M365UserCertification.ps1 -OutputPath ".\M365-User-Certification-Export.csv"
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$OutputPath = ".\M365-User-Certification-Export.csv"
)
# Silence Graph cmdlet progress bars to drastically decrease execution time
$OriginalProgressPreference = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'
try {
# Check existing Microsoft Graph session
$CurrentContext = Get-MgContext
$RequiredScopes = @("User.Read.All", "Directory.Read.All", "AuditLog.Read.All")
if (-not $CurrentContext) {
Write-Host "Connecting to Microsoft Graph..." -ForegroundColor Cyan
Connect-MgGraph -Scopes $RequiredScopes
}
else {
Write-Host "Connected as $($CurrentContext.Account)" -ForegroundColor Green
}
# Resolve relative path to absolute path
$ResolvedOutputPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath)
Write-Host "Building license SKU lookup table..." -ForegroundColor Cyan
$SkuLookup = @{}
Get-MgSubscribedSku -All | ForEach-Object {
$SkuLookup[$_.SkuId.ToString()] = $_.SkuPartNumber
}
# Explicitly define target properties (signInActivity requires AuditLog.Read.All)
$Properties = @(
"id",
"displayName",
"userPrincipalName",
"userType",
"accountEnabled",
"assignedLicenses",
"externalUserState",
"signInActivity"
)
Write-Host "Fetching user data from Microsoft Graph..." -ForegroundColor Cyan
$Users = Get-MgUser -All -Property $Properties
Write-Host "Processing $($Users.Count) users..." -ForegroundColor Cyan
$Export = foreach ($User in $Users) {
# Resolve licenses
$AssignedLicenses = if ($User.AssignedLicenses -and $User.AssignedLicenses.Count -gt 0) {
($User.AssignedLicenses | ForEach-Object {
$SkuId = $_.SkuId.ToString()
if ($SkuLookup.ContainsKey($SkuId)) {
$SkuLookup[$SkuId]
}
else {
$SkuId
}
}) -join "; "
}
else {
"None"
}
# Safe null-check for SignInActivity
$LastSignIn = if ($User.SignInActivity -and $User.SignInActivity.LastSignInDateTime) {
$User.SignInActivity.LastSignInDateTime
}
else {
"Never / No Record"
}
[PSCustomObject]@{
Name = $User.DisplayName
UserPrincipalName = $User.UserPrincipalName
UserType = $User.UserType
AccountEnabled = $User.AccountEnabled
AssignedLicenses = $AssignedLicenses
GuestInvitationState = $User.ExternalUserState
LastInteractiveSignInDate = $LastSignIn
}
}
# Export results to CSV
$Export |
Sort-Object Name |
Export-Csv -Path $ResolvedOutputPath -NoTypeInformation -Encoding UTF8
Write-Host "Export complete: $ResolvedOutputPath" -ForegroundColor Green
}
catch {
Write-Error "An error occurred during execution: $_"
}
finally {
# Restore original progress preference
$ProgressPreference = $OriginalProgressPreference
}