Im using this custom test to report on noncompliant devices and understand where we need to manually remediate vs what we can automate. However, the setting that shows why the device is not compliant is not avialable as a cache type. Can we add it as a cachetype so we can have something like Compliance State and Non-compliance Setting (ie secureboot, bitlocker etc) ?
# Non-compliant devices, primary user, last sync
$Devices = Get-CIPPTestData -Type 'ManagedDevices'
$NonCompliant = $Devices | Where-Object {
$_.complianceState -notin @('compliant', 'unknown')
}
$Results = $NonCompliant | Select-Object @{Name='DeviceId'; Expression={ $_.id }},
@{Name='DeviceName'; Expression={ $_.deviceName }},
@{Name='PrimaryUser'; Expression={ if ($_.userDisplayName) { $_.userDisplayName } else { $_.userPrincipalName } }},
@{Name='UserPrincipalName'; Expression={ $_.userPrincipalName }},
@{Name='Ownership'; Expression={
if ($_.ownerType -eq 'company') { 'Corporate' }
elseif ($_.ownerType -eq 'personal') { 'Personal' }
elseif ($_.ownerType) { $_.ownerType }
else { $null }
}},
@{Name='ComplianceState'; Expression={ $_.complianceState }},
@{Name='OS'; Expression={ "$($_.operatingSystem) $($_.osVersion)" }},
@{Name='LastSync'; Expression={ $_.lastSyncDateTime }},
@{Name='DaysSinceLastSync'; Expression={
if ($_.lastSyncDateTime) {
$span = New-TimeSpan -Start (Get-Date $_.lastSyncDateTime) -End (Get-Date)
$span.Days
} else {
$null
}
}},
@{Name='EnrolledDate'; Expression={ $_.enrolledDateTime }},
@{Name='GracePeriodExpires'; Expression={ $_.complianceGracePeriodExpirationDateTime }},
@{Name='DeviceLink'; Expression={ "https://cipp.empower.net.au/endpoint/MEM/devices/device?deviceId=$($_.id)" }}
$Results = @($Results | Sort-Object -Property @{Expression='LastSync'; Descending=$true})
# Split into stale (>30 days since sync) vs recent
$StaleResults = @($Results | Where-Object { $null -ne $_.DaysSinceLastSync -and $_.DaysSinceLastSync -gt 30 })
$RecentResults = @($Results | Where-Object { $null -eq $_.DaysSinceLastSync -or $_.DaysSinceLastSync -le 30 })
# Summary by compliance state (own table)
$StateGroups = $Results | Group-Object -Property ComplianceState
$StateCounts = $StateGroups | Select-Object @{Name='State'; Expression={ $_.Name }},
@{Name='Count'; Expression={ $_.Count }}
$stateHeader = "| State | Count |
|---|---|"
$stateRows = $StateCounts | ForEach-Object {
"| $($_.State) | $($_.Count) |"
}
$stateTable = @($stateHeader) + @($stateRows) -join "
"
# Summary by sync recency (separate table)
$syncHeader = "| Sync Status | Count |
|---|---|"
$syncRows = @(
"| Recent (synced within 30 days) | $(@($RecentResults).Count) |"
"| Stale (no sync in over 30 days) | $(@($StaleResults).Count) |"
)
$syncTable = @($syncHeader) + @($syncRows) -join "
"
$header = "### %tenantname%: Non-Compliant Devices
Non-compliant devices: $(@($Results).Count)
**By Compliance State**
$stateTable
**By Sync Recency**
$syncTable"
$detailHeader = "| Device | Primary User | Link | Days Since Sync | Ownership | Compliance State | OS | Last Sync | Grace Period Expires |
|---|---|---|---|---|---|---|---|---|"
$recentRows = $RecentResults | ForEach-Object {
$device = if ($_.DeviceName) { ([string]$_.DeviceName) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
$user = if ($_.PrimaryUser) { ([string]$_.PrimaryUser) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
$link = if ($_.DeviceLink) { "[View Device]($($_.DeviceLink))" } else { '_(none)_' }
$days = if ($null -ne $_.DaysSinceLastSync) { [string]$_.DaysSinceLastSync } else { '_(none)_' }
$owner = if ($_.Ownership) { ([string]$_.Ownership) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
$state = if ($_.ComplianceState) { ([string]$_.ComplianceState) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
$os = if ($_.OS) { ([string]$_.OS) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
$sync = if ($_.LastSync) { ([string]$_.LastSync) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
$grace = if ($_.GracePeriodExpires) { ([string]$_.GracePeriodExpires) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
"| $device | $user | $link | $days | $owner | $state | $os | $sync | $grace |"
}
$recentTable = @($detailHeader) + @($recentRows) -join "
"
$staleRows = $StaleResults | ForEach-Object {
$device = if ($_.DeviceName) { ([string]$_.DeviceName) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
$user = if ($_.PrimaryUser) { ([string]$_.PrimaryUser) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
$link = if ($_.DeviceLink) { "[View Device]($($_.DeviceLink))" } else { '_(none)_' }
$days = if ($null -ne $_.DaysSinceLastSync) { [string]$_.DaysSinceLastSync } else { '_(none)_' }
$owner = if ($_.Ownership) { ([string]$_.Ownership) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
$state = if ($_.ComplianceState) { ([string]$_.ComplianceState) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
$os = if ($_.OS) { ([string]$_.OS) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
$sync = if ($_.LastSync) { ([string]$_.LastSync) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
$grace = if ($_.GracePeriodExpires) { ([string]$_.GracePeriodExpires) -replace '\|','\|' -replace '\r?\n',' ' } else { '_(none)_' }
"| $device | $user | $link | $days | $owner | $state | $os | $sync | $grace |"
}
$staleTable = @($detailHeader) + @($staleRows) -join "
"
$md = if (@($Results).Count -gt 0) {
$recentSection = if (@($RecentResults).Count -gt 0) {
"### Recent — Synced Within 30 Days ($(@($RecentResults).Count))
" + $recentTable
} else {
"### Recent — Synced Within 30 Days (0)
None."
}
$staleSection = if (@($StaleResults).Count -gt 0) {
"### Stale — No Sync in Over 30 Days ($(@($StaleResults).Count))
" + $staleTable
} else {
"### Stale — No Sync in Over 30 Days (0)
None."
}
$header + "
---
" + $recentSection + "
---
" + $staleSection
} else {
"### %tenantname%: Non-Compliant Devices
All devices report compliant."
}
@{
CIPPStatus = if (@($Results).Count -gt 0) { 'Failed' } else { 'Passed' }
CIPPResults = $Results
CIPPResultMarkdown = $md
}
Please confirm:
Problem Statement
Im using this custom test to report on noncompliant devices and understand where we need to manually remediate vs what we can automate. However, the setting that shows why the device is not compliant is not avialable as a cache type. Can we add it as a cachetype so we can have something like Compliance State and Non-compliance Setting (ie secureboot, bitlocker etc) ?
Benefits for MSPs
Easily report on noncompliant devices for huge fleets/multiple customers
Value or Importance
Easily report on noncompliant devices for huge fleets/multiple customers
PowerShell Commands (Optional)
Easily report on noncompliant devices for huge fleets/multiple customers