Public/Get-UserM365Details.ps1
|
function Get-UserM365Details { <# .SYNOPSIS Retrieves Microsoft 365 details for a user via Microsoft Graph. .DESCRIPTION Queries Microsoft Graph for license assignments, mailbox statistics, OneDrive usage, Teams activity, MFA status, and Conditional Access policy assignments. Requires an active Microsoft.Graph connection with appropriate scopes (User.Read.All, MailboxSettings.Read, etc.). .PARAMETER Identity User Principal Name (UPN) of the target user. .OUTPUTS PSCustomObject with M365 details. .EXAMPLE Get-UserM365Details -Identity "john.smith@contoso.com" #> [CmdletBinding()] param( [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string]$Identity ) process { # Verify Graph connection try { $context = Get-MgContext -ErrorAction Stop if (-not $context) { throw "Not connected to Microsoft Graph. Run Connect-MgGraph first." } } catch { throw "Microsoft Graph is not available: $($_.Exception.Message). Run Connect-MgGraph -Scopes 'User.Read.All','Mail.Read','Directory.Read.All' first." } Write-Verbose "Querying Microsoft Graph for user: $Identity" # ------------------------------------------------------------------ # Get base user object # ------------------------------------------------------------------ try { $mgUser = Get-MgUser -UserId $Identity -Property 'Id,DisplayName,UserPrincipalName,AssignedLicenses,AccountEnabled' -ErrorAction Stop } catch { throw "Failed to retrieve user '$Identity' from Microsoft Graph: $($_.Exception.Message)" } $userId = $mgUser.Id # ------------------------------------------------------------------ # License assignments - resolve SKU IDs to friendly names # ------------------------------------------------------------------ $licenseNames = @() if ($mgUser.AssignedLicenses) { try { $subscribedSkus = Get-MgSubscribedSku -ErrorAction Stop $skuLookup = @{} foreach ($sku in $subscribedSkus) { $skuLookup[$sku.SkuId] = $sku.SkuPartNumber } $friendlyMap = @{ 'ENTERPRISEPREMIUM' = 'Office 365 E5' 'ENTERPRISEPACK' = 'Office 365 E3' 'ENTERPRISEPACKWITHOUTPROPLUS' = 'Office 365 E3 (no ProPlus)' 'SPE_E5' = 'Microsoft 365 E5' 'SPE_E3' = 'Microsoft 365 E3' 'SPE_F1' = 'Microsoft 365 F1' 'FLOW_FREE' = 'Power Automate Free' 'POWER_BI_STANDARD' = 'Power BI Free' 'POWER_BI_PRO' = 'Power BI Pro' 'TEAMS_EXPLORATORY' = 'Teams Exploratory' 'STREAM' = 'Microsoft Stream' 'PROJECTPREMIUM' = 'Project Plan 5' 'VISIOCLIENT' = 'Visio Plan 2' 'EMS_E5' = 'Enterprise Mobility + Security E5' 'EMS_E3' = 'Enterprise Mobility + Security E3' 'AAD_PREMIUM' = 'Azure AD Premium P1' 'AAD_PREMIUM_P2' = 'Azure AD Premium P2' 'EXCHANGESTANDARD' = 'Exchange Online Plan 1' 'EXCHANGEENTERPRISE' = 'Exchange Online Plan 2' 'MCOSTANDARD' = 'Skype for Business Plan 2' 'PHONESYSTEM_VIRTUALUSER'= 'Phone System Virtual User' 'MICROSOFT_BUSINESS_CENTER' = 'Microsoft Business Center' 'WIN10_PRO_ENT_SUB' = 'Windows 10/11 Enterprise E3' 'WINDOWS_STORE' = 'Windows Store for Business' 'DEFENDER_ENDPOINT_P1' = 'Microsoft Defender for Endpoint P1' } foreach ($lic in $mgUser.AssignedLicenses) { $partNumber = $skuLookup[$lic.SkuId] if ($partNumber) { $friendly = $friendlyMap[$partNumber] $licenseNames += if ($friendly) { $friendly } else { $partNumber } } else { $licenseNames += $lic.SkuId } } } catch { Write-Warning "Could not resolve license names: $($_.Exception.Message)" $licenseNames = $mgUser.AssignedLicenses | ForEach-Object { $_.SkuId } } } # ------------------------------------------------------------------ # Mailbox statistics # ------------------------------------------------------------------ $mailboxSize = 'N/A' $mailboxItemCount = 0 try { $mailFolderStats = Get-MgUserMailFolder -UserId $userId -MailFolderId 'inbox' -ErrorAction Stop $mailboxItemCount = $mailFolderStats.TotalItemCount # Use reporting API for total size $uri = "https://graph.microsoft.com/v1.0/reports/getMailboxUsageDetail(period='D7')" try { $reportRaw = Invoke-MgGraphRequest -Method GET -Uri $uri -ErrorAction Stop $reportContent = if ($reportRaw -is [string]) { $reportRaw } else { [System.Text.Encoding]::UTF8.GetString($reportRaw) } $reportLines = $reportContent -split "`n" | Where-Object { $_ -match $Identity } if ($reportLines) { $fields = ($reportLines | Select-Object -First 1) -split ',' # Storage Used (Byte) is typically field index 5 in the CSV if ($fields.Count -ge 6 -and $fields[5] -match '^\d+$') { $bytes = [long]$fields[5] if ($bytes -ge 1GB) { $mailboxSize = '{0:N2} GB' -f ($bytes / 1GB) } elseif ($bytes -ge 1MB) { $mailboxSize = '{0:N2} MB' -f ($bytes / 1MB) } else { $mailboxSize = '{0:N0} KB' -f ($bytes / 1KB) } } } } catch { Write-Verbose "Mailbox usage report not available: $($_.Exception.Message)" } } catch { Write-Verbose "Could not retrieve mailbox stats: $($_.Exception.Message)" } # ------------------------------------------------------------------ # OneDrive usage # ------------------------------------------------------------------ $oneDriveUsage = 'N/A' try { $drive = Get-MgUserDrive -UserId $userId -ErrorAction Stop if ($drive.Quota) { $used = $drive.Quota.Used if ($used -ge 1GB) { $oneDriveUsage = '{0:N2} GB' -f ($used / 1GB) } elseif ($used -ge 1MB) { $oneDriveUsage = '{0:N2} MB' -f ($used / 1MB) } elseif ($used) { $oneDriveUsage = '{0:N0} KB' -f ($used / 1KB) } else { $oneDriveUsage = '0 KB' } } } catch { Write-Verbose "Could not retrieve OneDrive usage: $($_.Exception.Message)" } # ------------------------------------------------------------------ # Teams activity (last activity date) # ------------------------------------------------------------------ $teamsActivity = 'N/A' try { $teamsUri = "https://graph.microsoft.com/v1.0/reports/getTeamsUserActivityUserDetail(period='D30')" $teamsRaw = Invoke-MgGraphRequest -Method GET -Uri $teamsUri -ErrorAction Stop $teamsContent = if ($teamsRaw -is [string]) { $teamsRaw } else { [System.Text.Encoding]::UTF8.GetString($teamsRaw) } $teamsLines = $teamsContent -split "`n" | Where-Object { $_ -match $Identity } if ($teamsLines) { $tFields = ($teamsLines | Select-Object -First 1) -split ',' # Last Activity Date is typically field index 3 if ($tFields.Count -ge 4 -and $tFields[3] -match '\d{4}-\d{2}-\d{2}') { $teamsActivity = $tFields[3] } } } catch { Write-Verbose "Could not retrieve Teams activity: $($_.Exception.Message)" } # ------------------------------------------------------------------ # SharePoint sites # ------------------------------------------------------------------ $sharePointSites = 0 try { $memberOf = Get-MgUserMemberOf -UserId $userId -All -ErrorAction Stop $sharePointSites = ($memberOf | Where-Object { $_.AdditionalProperties['@odata.type'] -eq '#microsoft.graph.group' -and $_.AdditionalProperties['groupTypes'] -contains 'Unified' }).Count } catch { Write-Verbose "Could not enumerate SharePoint sites: $($_.Exception.Message)" } # ------------------------------------------------------------------ # Shared mailbox access # ------------------------------------------------------------------ $sharedMailboxAccess = @() try { $permsUri = "https://graph.microsoft.com/v1.0/users/$userId/mailboxSettings" $mbxSettings = Invoke-MgGraphRequest -Method GET -Uri $permsUri -ErrorAction Stop # Shared mailbox access requires Exchange Online cmdlets typically; # Graph approach: search for mailboxes where user has delegate access # This is best-effort via Graph - full delegate enumeration needs EXO Write-Verbose "Shared mailbox enumeration is limited via Graph. For full results, use Exchange Online PowerShell." } catch { Write-Verbose "Could not retrieve shared mailbox access: $($_.Exception.Message)" } # ------------------------------------------------------------------ # MFA status and methods # ------------------------------------------------------------------ $mfaStatus = 'Unknown' $mfaMethods = @() try { $authMethods = Get-MgUserAuthenticationMethod -UserId $userId -ErrorAction Stop $methodTypes = @() foreach ($method in $authMethods) { $odataType = $method.AdditionalProperties['@odata.type'] switch -Wildcard ($odataType) { '*microsoftAuthenticator*' { $methodTypes += 'Microsoft Authenticator' } '*phoneAuthentication*' { $methodTypes += 'Phone (SMS/Call)' } '*fido2*' { $methodTypes += 'FIDO2 Security Key' } '*windowsHelloForBusiness*' { $methodTypes += 'Windows Hello for Business' } '*emailAuthentication*' { $methodTypes += 'Email' } '*temporaryAccessPass*' { $methodTypes += 'Temporary Access Pass' } '*softwareOath*' { $methodTypes += 'Software OATH Token' } '*passwordAuthentication*' { <# password is not MFA #> } default { if ($odataType) { $methodTypes += $odataType -replace '.*\.', '' } } } } $mfaMethods = $methodTypes | Select-Object -Unique # If there are methods beyond just password, MFA is enabled $nonPasswordMethods = $authMethods | Where-Object { $_.AdditionalProperties['@odata.type'] -notmatch 'passwordAuthentication' } $mfaStatus = if ($nonPasswordMethods.Count -gt 0) { 'Enabled' } else { 'Disabled' } } catch { Write-Warning "Could not retrieve MFA details: $($_.Exception.Message)" } # ------------------------------------------------------------------ # Conditional Access policies that apply # ------------------------------------------------------------------ $caPolicies = @() try { $allPolicies = Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies' -ErrorAction Stop foreach ($policy in $allPolicies.value) { if ($policy.state -ne 'enabled') { continue } $applies = $false $conditions = $policy.conditions # Check if user is directly included if ($conditions.users.includeUsers -contains 'All' -or $conditions.users.includeUsers -contains $userId) { $applies = $true } # Check if user is in an included group if (-not $applies -and $conditions.users.includeGroups) { $userGroupIds = ($memberOf | ForEach-Object { $_.Id }) foreach ($groupId in $conditions.users.includeGroups) { if ($userGroupIds -contains $groupId) { $applies = $true break } } } # Check exclusions if ($applies) { if ($conditions.users.excludeUsers -contains $userId) { $applies = $false } if ($conditions.users.excludeGroups) { $userGroupIds = if (-not $userGroupIds) { $memberOf | ForEach-Object { $_.Id } } else { $userGroupIds } foreach ($groupId in $conditions.users.excludeGroups) { if ($userGroupIds -contains $groupId) { $applies = $false break } } } } if ($applies) { $caPolicies += $policy.displayName } } } catch { Write-Verbose "Could not retrieve Conditional Access policies: $($_.Exception.Message)" } [PSCustomObject]@{ LicenseAssignments = $licenseNames MailboxSize = $mailboxSize MailboxItemCount = $mailboxItemCount OneDriveUsage = $oneDriveUsage TeamsActivity = $teamsActivity SharePointSites = $sharePointSites SharedMailboxAccess = $sharedMailboxAccess MFAStatus = $mfaStatus MFAMethods = $mfaMethods ConditionalAccessPolicies = $caPolicies } } } |