Public/Export-M365BitLockerReport.ps1
|
function Export-M365BitLockerReport { <# .SYNOPSIS Generates an HTML report of BitLocker recovery-key escrow coverage. .DESCRIPTION Reads BitLocker recovery keys via Graph (informationProtection/bitlocker/recoveryKeys) and shows which devices have escrowed keys, volume type and creation date (the key value itself is NOT retrieved). Read-only. Requires BitlockerKey.Read.All. .PARAMETER Path Target path of the HTML file. Default: .\BitLocker-Report.html .EXAMPLE Export-M365BitLockerReport -BrandName 'CloudNest365' #> [CmdletBinding()] [OutputType([pscustomobject])] param( [string]$Path = (Join-Path (Get-Location) 'BitLocker-Report.html'), [string]$BrandName = 'TenantToolbox', [string]$BrandTagline = 'M365 Tenant Administration', [switch]$Csv, [switch]$Excel, [string]$DataPath, [switch]$NoHtml, [switch]$PassThru, [switch]$NoOpen ) Assert-TTGraph Write-TTLog -Level INFO -Message "Reading BitLocker recovery keys ..." try { $keys = Get-TTGraphCollection "https://graph.microsoft.com/v1.0/informationProtection/bitlocker/recoveryKeys?`$top=100" } catch { if ("$_" -match 'Forbidden|403') { Write-Warning "Access denied. Needs BitlockerKey.Read.All. Reconnect: Connect-TenantToolbox -UseDeviceCode" } else { Write-Warning "Could not read BitLocker keys: $_" } $keys = @() } $data = foreach ($k in $keys) { [pscustomobject]@{ DeviceId = $k.deviceId; KeyId = $k.id; VolumeType = $k.volumeType; Created = $k.createdDateTime } } $data = @($data) $total = $data.Count $devices = @($data | Select-Object -ExpandProperty DeviceId -Unique | Where-Object { $_ }).Count $os = @($data | Where-Object { $_.VolumeType -eq 'operatingSystemVolume' }).Count $genAt = Get-Date -Format 'yyyy-MM-dd HH:mm'; $tenant = (Get-MgContext).TenantId $rows = foreach ($k in ($data | Sort-Object Created -Descending)) { $created = if ($k.Created) { ([datetime]$k.Created).ToString('yyyy-MM-dd') } else { '–' } $searchAttr = TTEnc ("$($k.DeviceId) $($k.VolumeType)".ToLower()) @" <tr class="item" data-name="$searchAttr" data-search="$searchAttr"> <td><b>$(TTEnc $k.DeviceId)</b></td><td>$(TTEnc $k.VolumeType)</td><td><span class="b b-ok">escrowed</span></td><td>$created</td> </tr> "@ } $kpiHtml = New-TTKpis @( @{ n = $total; l = 'Recovery keys'; filter = 'all' } @{ n = $devices; l = 'Devices covered'; kind = 'ok' } @{ n = $os; l = 'OS volumes'; kind = 'info' } ) $toolbar = New-TTToolbar -SearchPlaceholder 'Search device id ...' -Filters @( @{ label = 'All'; key = 'all' } ) $body = @" <div class="panel"><table class="tbl"><thead><tr><th data-sort="name">Device id</th><th>Volume</th><th>Key</th><th>Created</th></tr></thead> <tbody> $($rows -join "`n") </tbody></table><div class="empty" id="empty">No key matches the search.</div></div> "@ $html = New-TTHtmlPage -Title 'BitLocker' -Heading 'BitLocker Recovery Coverage' -Sub "Tenant $tenant · generated $genAt · $total keys on $devices devices" ` -BrandName $BrandName -BrandTagline $BrandTagline -KpiHtml $kpiHtml -ToolbarHtml $toolbar -BodyHtml $body Write-TTLog -Level INFO -Message "BitLocker report created: $Path ($total keys, $devices devices)." $flat = $data | Select-Object DeviceId, VolumeType, @{N = 'Created'; E = { if ($_.Created) { ([datetime]$_.Created).ToString('yyyy-MM-dd') } } } Complete-TTReport -Html $html -Path $Path -Data $flat -Csv:$Csv -Excel:$Excel -DataPath $DataPath -NoHtml:$NoHtml -NoOpen:$NoOpen -Kind 'BitLocker-Report' if ($PassThru) { $data } } |