# .SYNOPSIS ConfigMgr client health report: checks a list of computers and writes a self-contained HTML report. .DESCRIPTION Example script - Antonio Di Giacomo, 2026. Review and test it in a lab before using it in production. It needs PowerShell remoting (WinRM) to the computers and local admin rights on them. Checks on every computer: - reachable the computer answers over WinRM - service CcmExec (SMS Agent Host) is running - client version ConfigMgr client version (compared with -MinClientVersion) - WMI the root\ccm namespace answers - site assignment the client is assigned to a site - machine policy last policy request is recent (-MaxPolicyAgeHours) - hardware inventory last hardware inventory cycle is recent (-MaxInventoryAgeDays) - free space free space on the system drive (-MinFreeGB) - pending reboot Windows or ConfigMgr is waiting for a restart Status per computer: Healthy (no problems), Warning (only minor problems), Critical (unreachable, service stopped, WMI broken or no site assignment). .EXAMPLE .\Get-CMClientHealth.ps1 -ComputerName (Get-Content .\pc.txt) -OutFile .\report.html #> [CmdletBinding()] param( [Parameter(Mandatory)] [string[]] $ComputerName, [version] $MinClientVersion = '5.0.9128.1000', [int] $MaxPolicyAgeHours = 24, [int] $MaxInventoryAgeDays = 7, [int] $MinFreeGB = 10, [string] $OutFile = '.\ClientHealth.html', [int] $ThrottleLimit = 32 ) $check = { $r = [ordered]@{ Service = $false; Version = $null; Wmi = $false; Site = $null; PolicyHours = $null InventoryDays = $null; FreeGB = $null; PendingReboot = $false } $r.Service = (Get-Service CcmExec -ErrorAction SilentlyContinue).Status -eq 'Running' try { $c = Get-CimInstance -Namespace root\ccm -ClassName SMS_Client -ErrorAction Stop $r.Wmi = $true $r.Version = $c.ClientVersion $r.Site = (Invoke-CimMethod -Namespace root\ccm -ClassName SMS_Client -MethodName GetAssignedSite -ErrorAction Stop).sSiteCode } catch { } try { # inventory actions: {00000000-0000-0000-0000-000000000001} = hardware inventory $hw = Get-CimInstance -Namespace root\ccm\invagt -ClassName InventoryActionStatus -Filter "InventoryActionID='{00000000-0000-0000-0000-000000000001}'" -ErrorAction Stop if ($hw.LastCycleStartedDate) { $r.InventoryDays = [int]((Get-Date) - $hw.LastCycleStartedDate).TotalDays } } catch { } try { $file = Get-Item "$env:windir\CCM\Logs\PolicyAgent.log" -ErrorAction Stop $r.PolicyHours = [int]((Get-Date) - $file.LastWriteTime).TotalHours # last policy activity } catch { } $sys = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='$env:SystemDrive'" $r.FreeGB = [math]::Round($sys.FreeSpace / 1GB, 1) $r.PendingReboot = (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending') -or (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired') try { $cm = Invoke-CimMethod -Namespace root\ccm\ClientSDK -ClassName CCM_ClientUtilities -MethodName DetermineIfRebootPending -ErrorAction Stop if ($cm.RebootPending -or $cm.IsHardRebootPending) { $r.PendingReboot = $true } } catch { } [pscustomobject]$r } Write-Verbose "Checking $($ComputerName.Count) computers..." $found = Invoke-Command -ComputerName $ComputerName -ScriptBlock $check -ThrottleLimit $ThrottleLimit -ErrorAction SilentlyContinue -ErrorVariable failed $rows = foreach ($pc in $ComputerName) { $x = $found | Where-Object PSComputerName -eq $pc $p = [System.Collections.Generic.List[string]]::new() $critical = $false if (-not $x) { $p.Add('Unreachable'); $critical = $true } else { if (-not $x.Service) { $p.Add('CcmExec stopped'); $critical = $true } if (-not $x.Wmi) { $p.Add('WMI root\ccm not answering'); $critical = $true } elseif (-not $x.Site) { $p.Add('No site assignment'); $critical = $true } if ($x.Version -and [version]$x.Version -lt $MinClientVersion) { $p.Add("Old client $($x.Version)") } if ($null -eq $x.PolicyHours -or $x.PolicyHours -gt $MaxPolicyAgeHours) { $p.Add('No recent machine policy') } if ($null -eq $x.InventoryDays -or $x.InventoryDays -gt $MaxInventoryAgeDays) { $p.Add('Hardware inventory out of date') } if ($x.FreeGB -lt $MinFreeGB) { $p.Add("Low disk space ($($x.FreeGB) GB)") } if ($x.PendingReboot) { $p.Add('Restart pending') } } [pscustomobject]@{ Computer = $pc Status = if ($critical) { 'Critical' } elseif ($p.Count) { 'Warning' } else { 'Healthy' } Version = $x.Version Site = $x.Site FreeGB = $x.FreeGB Problems = $p -join '; ' } } # ---- HTML report (self-contained, no external files) ---- Add-Type -AssemblyName System.Web $enc = { param($s) [System.Web.HttpUtility]::HtmlEncode([string]$s) } $n = { param($s) @($rows | Where-Object Status -eq $s).Count } $tr = foreach ($r in $rows | Sort-Object @{ e = { @{ Critical = 0; Warning = 1; Healthy = 2 }[$_.Status] } }, Computer) { "
$(Get-Date -Format 'yyyy-MM-dd HH:mm') · $($rows.Count) computers
| Computer | Status | Client version | Site | Free GB | Problems |
|---|