Src/Private/Get-AbrEntraIDDevices.ps1
|
function Get-AbrEntraIDDevices { <# .SYNOPSIS Documents Entra ID registered and joined devices. .NOTES Version: 0.1.20 Author: Pai Wei Sing #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory)] [string]$TenantId ) begin { Write-PScriboMessage -Message "Collecting Entra ID Devices for tenant $TenantId." Show-AbrDebugExecutionTime -Start -TitleMessage 'Devices' } process { #region Devices # Section{} created unconditionally so catch{} always writes inside it Section -Style Heading2 'Devices' { Paragraph "The following section documents the devices registered or joined to tenant $TenantId." BlankLine try { Write-Host " - Retrieving devices..." $Devices = Get-MgDevice -All ` -Property Id,DisplayName,OperatingSystem,OperatingSystemVersion,TrustType,IsCompliant,IsManaged,ApproximateLastSignInDateTime,RegisteredDateTime,AccountEnabled ` -ErrorAction Stop if ($Devices) { #region Device Summary $AADJoined = @($Devices | Where-Object { $_.TrustType -eq 'AzureAd' }).Count $HybridJoined = @($Devices | Where-Object { $_.TrustType -eq 'ServerAd' }).Count $Registered = @($Devices | Where-Object { $_.TrustType -eq 'Workplace' }).Count $Compliant = @($Devices | Where-Object { $_.IsCompliant }).Count $Managed = @($Devices | Where-Object { $_.IsManaged }).Count $Enabled = @($Devices | Where-Object { $_.AccountEnabled }).Count $Stale90 = @($Devices | Where-Object { $_.ApproximateLastSignInDateTime -and ((Get-Date) - $_.ApproximateLastSignInDateTime).Days -gt 90 }).Count $DevSumObj = [System.Collections.ArrayList]::new() $devSumInObj = [ordered] @{ 'Total Devices' = @($Devices).Count 'Entra ID Joined' = $AADJoined 'Hybrid Azure AD Joined' = $HybridJoined 'Workplace Registered' = $Registered 'Compliant Devices' = $Compliant 'Managed Devices' = $Managed 'Enabled Devices' = $Enabled 'Stale (>90 days no sign-in)' = $Stale90 } $DevSumObj.Add([pscustomobject]$devSumInObj) | Out-Null $null = (& { if ($HealthCheck.EntraID.Devices) { $null = ($DevSumObj | Where-Object { [int]$_.'Stale (>90 days no sign-in)' -gt 0 } | Set-Style -Style Warning | Out-Null) $null = ($DevSumObj | Where-Object { [int]$_.'Compliant Devices' -lt [int]$_.Enabled } | Set-Style -Style Warning | Out-Null) } }) $DevSumTableParams = @{ Name = "Device Summary - $TenantId"; List = $true; ColumnWidths = 55, 45 } if ($Report.ShowTableCaptions) { $DevSumTableParams['Caption'] = "- $($DevSumTableParams.Name)" } $DevSumObj | Table @DevSumTableParams #region Device Compliance Donut Chart -- generated outside PScribo scope try { if (Get-Command New-AbrDonutChart -ErrorAction SilentlyContinue) { [int]$TotalDev = @($Devices).Count [int]$NonCompliant = $TotalDev - $Compliant [int]$devPct = $(if ($TotalDev -gt 0) { [math]::Round(($Compliant / $TotalDev) * 100, 0) } else { 0 }) $devSegs = @( @{ Label = 'Compliant'; Value = [int]$Compliant; Color = '#2d8f4e' } @{ Label = 'Non-Compliant'; Value = [int]$NonCompliant; Color = '#c0392b' } ) $script:Charts['Devices'] = New-AbrDonutChart -Segments $devSegs -CentreText "$devPct%" -SubText 'Compliant' -Title "Device Compliance -- $TenantId" } } catch { Write-AbrDebugLog "Device chart failed: $($_.Exception.Message)" 'WARN' 'CHART' } if ($script:Charts['Devices']) { BlankLine Image -Text 'Device Compliance' -Base64 $script:Charts['Devices'] -Percent 65 -Align Center Paragraph "Figure: Device Compliance -- $Compliant of $($Devices.Count) devices compliant ($devPct%)" BlankLine } #endregion #endregion #region Device Inventory Table (InfoLevel 2: per-device detail) if ($InfoLevel.Devices -ge 2) { $DevObj = [System.Collections.ArrayList]::new() foreach ($Device in ($Devices | Sort-Object DisplayName)) { $JoinType = switch ($Device.TrustType) { 'AzureAd' { 'Entra ID Joined' } 'ServerAd' { 'Hybrid AAD Joined' } 'Workplace' { 'Workplace Registered' } default { $Device.TrustType } } $LastSignIn = if ($Device.ApproximateLastSignInDateTime) { $DaysAgo = ((Get-Date) - $Device.ApproximateLastSignInDateTime).Days "$($Device.ApproximateLastSignInDateTime.ToString('yyyy-MM-dd')) ($DaysAgo days ago)" } else { 'Never / Unknown' } $devInObj = [ordered] @{ 'Device Name' = $Device.DisplayName 'OS' = $Device.OperatingSystem 'OS Version' = $Device.OperatingSystemVersion 'Join Type' = $JoinType 'Compliant' = if ($Device.IsCompliant) { 'Yes' } else { 'No' } 'Managed' = if ($Device.IsManaged) { 'Yes' } else { 'No' } 'Enabled' = if ($Device.AccountEnabled) { 'Yes' } else { 'No' } 'Last Sign-In' = $LastSignIn 'Registered' = if ($Device.RegisteredDateTime) { ($Device.RegisteredDateTime).ToString('yyyy-MM-dd') } else { '--' } } $DevObj.Add([pscustomobject](ConvertTo-HashToYN $devInObj)) | Out-Null } $null = (& { if ($HealthCheck.EntraID.Devices) { $null = ($DevObj | Where-Object { $_.'Compliant' -eq 'No' -and $_.'Enabled' -eq 'Yes' } | Set-Style -Style Warning | Out-Null) $null = ($DevObj | Where-Object { $_.'Enabled' -eq 'No' } | Set-Style -Style Critical | Out-Null) $null = ($DevObj | Where-Object { $_.'Last Sign-In' -like '*Unknown*' } | Set-Style -Style Warning | Out-Null) } }) $DevTableParams = @{ Name = "Device Inventory - $TenantId"; List = $false; ColumnWidths = 16, 9, 9, 14, 7, 7, 7, 20, 11 } if ($Report.ShowTableCaptions) { $DevTableParams['Caption'] = "- $($DevTableParams.Name)" } $DevObj | Table @DevTableParams $null = ($script:ExcelSheets['Devices'] = $DevObj) } # end InfoLevel.Devices -ge 2 #region ACSC E8 Devices Assessment BlankLine Paragraph "ACSC Essential Eight Maturity Level Assessment -- Device Management:" BlankLine try { $TotalDev = @($Devices).Count $ManagedCount = @($Devices | Where-Object { $_.IsManaged }).Count $CompliantCnt = @($Devices | Where-Object { $_.IsCompliant }).Count $Stale90Cnt = @($Devices | Where-Object { $_.ApproximateLastSignInDateTime -and ((Get-Date) - $_.ApproximateLastSignInDateTime).Days -gt 90 }).Count $Stale45Cnt = @($Devices | Where-Object { $_.ApproximateLastSignInDateTime -and ((Get-Date) - $_.ApproximateLastSignInDateTime).Days -gt 45 }).Count $ManagedPct = if ($TotalDev -gt 0) { [math]::Round(($ManagedCount / $TotalDev) * 100, 0) } else { 0 } $CompliantPct = if ($TotalDev -gt 0) { [math]::Round(($CompliantCnt / $TotalDev) * 100, 0) } else { 0 } #region ACSC E8 Assessment (definitions from Src/Compliance/ACSC.E8.json) $_ComplianceVars = @{ 'TotalDev' = $TotalDev 'ManagedCount' = $ManagedCount 'ManagedPct' = $ManagedPct 'CompliantCnt' = $CompliantCnt 'CompliantPct' = $CompliantPct 'Stale90Cnt' = $Stale90Cnt 'Stale45Cnt' = $Stale45Cnt 'DeviceCompliancePolicyExists' = $DeviceCompliancePolicyExists 'DeviceCompliancePolicySummary' = $DeviceCompliancePolicySummary } $E8DevChecks = Build-AbrComplianceChecks ` -Definitions (Get-AbrE8Checks -Section 'Devices') ` -Framework E8 ` -CallerVariables $_ComplianceVars New-AbrE8AssessmentTable -Checks $E8DevChecks -Name 'Device Management' -TenantId $TenantId # Consolidated into ACSC E8 Assessment sheet if ($E8DevChecks) { $null = $script:E8AllChecks.AddRange([object[]](@($E8DevChecks | Select-Object @{N='Section';E={'Devices'}}, ML, Control, Status, Detail ))) } #endregion if ($script:IncludeCISBaseline) { BlankLine Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment -- Device Management:" BlankLine #region CIS Assessment (definitions from Src/Compliance/CIS.M365.json) $CISDevChecks = Build-AbrComplianceChecks ` -Definitions (Get-AbrCISChecks -Section 'Devices') ` -Framework CIS ` -CallerVariables $_ComplianceVars New-AbrCISAssessmentTable -Checks $CISDevChecks -Name 'Device Management' -TenantId $TenantId # Consolidated into CIS Assessment sheet if ($CISDevChecks) { $null = $script:CISAllChecks.AddRange([object[]](@($CISDevChecks | Select-Object @{N='Section';E={'Devices'}}, CISControl, Level, Status, Detail ))) } #endregion } } catch { Write-AbrSectionError -Section 'E8 Devices Assessment' -Message "$($_.Exception.Message)" } #endregion } else { Paragraph "No devices were found in tenant $TenantId." } } catch { Write-AbrSectionError -Section 'Devices section' -Message "$($_.Exception.Message)" } } # end Section Devices #endregion } end { Show-AbrDebugExecutionTime -End -TitleMessage 'Devices' } } |