Files
WSUS/Dashboard/report.ps1
2025-10-31 08:55:43 +01:00

232 lines
10 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#Requires -RunAsAdministrator
$ErrorActionPreference = 'Stop'
# ****************************** MODULE IMPORT & INITIALIZATION ******************************
# HTML reporting module
Import-Module -Name PSWriteHTML -ErrorAction Stop
Write-Host "Starting WSUS report generation..."
# Load WSUS admin assembly explicitly (no deprecated LoadWithPartialName)
$wsusDll = "$env:ProgramFiles\Update Services\Tools\Microsoft.UpdateServices.Administration.dll"
if (Test-Path $wsusDll) { Add-Type -Path $wsusDll } else { throw "WSUS Admin DLL not found: $wsusDll" }
# Connect to local WSUS and pull data
$wsusServer = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer()
$updates = $wsusServer.GetUpdates()
$computers = $wsusServer.GetComputerTargets()
$targetGroups = $wsusServer.GetComputerTargetGroups()
# Map groupId -> groupName for quick lookups
$groupHashTable = @{}
$targetGroups | ForEach-Object { $groupHashTable[$_.ID] = $_.Name }
Write-Host "Retrieved WSUS data: Updates, Computers, and Target Groups."
# Ensure output folder exists
$outFile = "C:\exploit\report\default.htm"
$outDir = Split-Path $outFile -Parent
if (-not (Test-Path $outDir)) { New-Item -Path $outDir -ItemType Directory -Force | Out-Null }
# ****************************** REPORT GENERATION START ******************************
New-HTML -TitleText 'WSUS Report' {
# ****************************** COMPUTERS BY TARGET GROUP ******************************
# Build a simple dataset: group name + computer count (skip 'All Computers')
$DataTable = New-Object System.Collections.Generic.List[Object]
foreach ($group in $targetGroups) {
$computersCount = $group.GetComputerTargets().Count
if ($computersCount -gt 0 -and $group.Name -ne 'All Computers') {
$DataTable.Add([PSCustomObject]@{
TargetName = $group.Name
Total = $computersCount
})
}
}
Write-Host "Populated DataTable with computer counts per Target Group."
New-HTMLSection -HeaderText 'Computers By TargetGroup' {
# Table
New-HTMLPanel {
New-HTMLTable -DataTable $DataTable -HideFooter -DataTableID 'IDtargetGroup' {
# Bind events to the correct table ID
New-TableEvent -ID 'IDtargetGroup' -SourceColumnID 0 -TargetColumnId 0
}
}
# Pie chart per group
New-HTMLPanel {
New-HTMLChart {
New-ChartToolbar -Download
foreach ($row in $DataTable) { New-ChartPie -Name $row.TargetName -Value $row.Total }
}
}
}
# ****************************** COMPUTER LIST (HIDDEN) ******************************
$ListeComputersWSUS = @()
foreach ($computer in $computers) {
foreach ($tg in $computer.GetComputerTargetGroups()) {
if ($tg.Name -ne 'All Computers') {
$ListeComputersWSUS += [PSCustomObject]@{
TargetName = $tg.Name
ComputerName = $computer.FullDomainName
}
}
}
}
New-HTMLSection -HeaderText 'Computers' -Invisible {
New-HTMLTable -DataTable $ListeComputersWSUS -DataTableID 'AllComputertargetGroup' -HideFooter
}
# ****************************** DEPLOYMENT STATUS OF KB UPDATES ******************************
$DateNow = Get-Date
$ListeKB = @()
$MaxDaysReport = 90 # (kept for future filtering if needed)
foreach ($update in $updates) {
foreach ($approval in $update.GetUpdateApprovals()) {
foreach ($tg in $targetGroups) {
if ($tg.Id -eq $approval.ComputerTargetGroupId) {
$DateKB = $approval.GoLiveTime
$LastChange = New-TimeSpan -Start $DateKB -End $DateNow
$ActionType = $approval.Action
if ($ActionType -eq 'Install') {
$KBTitre = $update.Title
$installKB = 0; $MissKB = 0
# Summaries per group for this update
foreach ($sum in $update.GetSummaryPerComputerTargetGroup()) {
$groupName = $groupHashTable[$sum.ComputerTargetGroupId]
if ($tg.Name -eq $groupName) {
$installKB = $sum.InstalledCount + $sum.InstalledPendingRebootCount
$MissKB = $sum.UnknownCount + $sum.NotInstalledCount + $sum.DownloadedCount + $sum.FailedCount
}
}
# Keep only meaningful KBs (exclude ARM64, SSU, Flash, specific KBs)
if (($installKB -ne 0 -or $MissKB -ne 0) -and
($KBTitre -notmatch 'ARM64') -and
($KBTitre -notmatch 'Servicing Stack Update') -and
($KBTitre -notmatch 'Flash Player') -and
($KBTitre -notlike '*KB4470788*') -and
($KBTitre -notlike '*KB4499728*')) {
$ListeKB += [PSCustomObject]@{
KBTitre = $KBTitre
KBGroup = $tg.Name
KBChang = $LastChange.Days
ActionType = $ActionType
installKB = $installKB
MissKB = $MissKB
}
}
}
}
}
}
}
# Sort by age then title; get unique titles for chart grouping
$ListeKB = $ListeKB | Sort-Object KBChang, KBTitre
$groupedByTitle = @{}
foreach ($o in $ListeKB) {
if (-not $groupedByTitle.ContainsKey($o.KBTitre)) { $groupedByTitle[$o.KBTitre] = @() }
$groupedByTitle[$o.KBTitre] += $o
}
foreach ($Title in $groupedByTitle.Keys) {
# Timeline per group (how many days since approval)
New-HTMLSection -HeaderText $Title {
New-HTMLChart -Title $Title -TitleAlignment center {
foreach ($o in $groupedByTitle[$Title]) {
New-ChartTimeline -DateFrom (Get-Date).AddDays(-$o.KBChang - 1) -DateTo (Get-Date) -Name $o.KBGroup
}
}
}
# Donut charts (Installed vs Missing) per group hidden
New-HTMLSection -HeaderText 'Computers' -Invisible {
foreach ($o in $groupedByTitle[$Title]) {
New-HTMLPanel {
New-HTMLChart -Title $o.KBGroup {
New-ChartLegend -Name "Installed","Missing" -Color Green,Red
New-ChartDonut -Name "Installed" -Value $o.installKB
New-ChartDonut -Name "Missing" -Value $o.MissKB
}
}
}
}
# ------------------------- KB and Computers status table (hidden) -------------------------
$updateScope2 = New-Object Microsoft.UpdateServices.Administration.UpdateScope
$updateScope2.ApprovedStates = [Microsoft.UpdateServices.Administration.ApprovedStates]::LatestRevisionApproved
$updateScope2.UpdateApprovalActions = [Microsoft.UpdateServices.Administration.UpdateApprovalActions]::Install
$updateScope2.UpdateSources = [Microsoft.UpdateServices.Administration.UpdateSources]::MicrosoftUpdate
$updateScope2.ExcludedInstallationStates = @('NotApplicable','Installed','InstalledPendingReboot')
$allComputers = $wsusServer.GetComputerTargets()
$resultsComputerStatus = @()
foreach ($pc in $allComputers) {
$updatelist = $pc.GetUpdateInstallationInfoPerUpdate($updateScope2)
foreach ($ui in $updatelist) {
$updInfo = $ui.GetUpdate()
$approvalGroup = $ui.GetUpdateApprovalTargetGroup().Name
if ($approvalGroup -ne 'All Computers' -and $updInfo.IsApproved) {
$resultsComputerStatus += [pscustomobject][Ordered]@{
ComputerName = $pc.FullDomainName
Status = $ui.UpdateInstallationState
ApprovalTargetGroup= $approvalGroup
Approved = $updInfo.IsApproved
Title = $updInfo.Title
}
}
}
}
New-HTMLSection -HeaderText 'Computers' -Invisible {
foreach ($o in $groupedByTitle[$Title]) {
New-HTMLPanel {
$specific = $resultsComputerStatus |
Where-Object { $_.Title -like "*$Title*" -and $_.ApprovalTargetGroup -like "*$($o.KBGroup)*" } |
Select-Object ComputerName, Status
New-HTMLTable -DataTable $specific -HideFooter -HideButtons
}
}
}
}
# ****************************** COMPUTERS IN ERROR ******************************
$computersInError = @()
$updateScope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
$updateScope.IncludedInstallationStates = 'Failed'
$computerScope = New-Object Microsoft.UpdateServices.Administration.ComputerTargetScope
$computerScope.IncludedInstallationStates = 'Failed'
$computersInError = $wsusServer.GetComputerTargets($computerScope) | ForEach-Object {
$compName = $_.FullDomainName
$failedTitles = ($_.GetUpdateInstallationInfoPerUpdate($updateScope) | ForEach-Object {
$_.GetUpdate().Title
}) -join ', '
if ($failedTitles) {
[pscustomobject]@{
Computername = $compName
TargetGroups = ($_.GetComputerTargetGroups() | Select-Object -ExpandProperty Name) -join ', '
Updates = $failedTitles
}
}
} | Sort-Object Computername
New-HTMLSection -HeaderText 'Computers in ERROR' {
New-HTMLTable -DataTable $computersInError -DataTableID 'NewIDtoSearchInChartERR' -HideFooter
}
# ****************************** FOOTER ******************************
New-HTMLFooter {
New-HTMLText -Text ("Report generated (GMT): {0:u}" -f (Get-Date).ToUniversalTime()) -Color Blue -Alignment center
}
} -FilePath $outFile -Online
Write-Host "WSUS report has been generated successfully at $outFile"