Files
PowerShell-scripts/SCCM-WU/SCCM-Update-Install-Forensics.ps1

220 lines
12 KiB
PowerShell

<#
.SYNOPSIS
SCCM Update Install Forensics - wie/wat heeft de patch-installatie en reboot getriggerd?
.DESCRIPTION
Doorzoekt CCM logs, WMI, event logs en registry om te bepalen of een update-installatie
user-initiated (Software Center) of deployment-enforced was, en wat de reboot triggerde.
.USAGE
Lokaal op de server draaien als admin:
.\Get-SCCMInstallForensics.ps1 -Hours 72
Optioneel: -OutFile C:\Temp\rapport.txt
#>
param(
[int]$Hours = 72,
[string]$LogPath = "$env:windir\CCM\Logs",
[string]$OutFile
)
$ErrorActionPreference = 'SilentlyContinue'
$since = (Get-Date).AddHours(-$Hours)
$report = New-Object System.Collections.Generic.List[string]
function Add-Line { param($t) $report.Add($t); Write-Host $t }
function Add-Section { param($t) Add-Line ""; Add-Line ("=" * 80); Add-Line " $t"; Add-Line ("=" * 80) }
# CCM logtijd parser: "message" component="..." time="HH:mm:ss.fff+060" date="MM-dd-yyyy"
function Get-CMLogEntries {
param([string[]]$LogNames, [string]$Pattern)
$results = @()
foreach ($name in $LogNames) {
$files = Get-ChildItem -Path $LogPath -Filter $name -File | Sort-Object LastWriteTime
foreach ($f in $files) {
if ($f.LastWriteTime -lt $since) { continue }
$matches = Select-String -Path $f.FullName -Pattern $Pattern
foreach ($m in $matches) {
$line = $m.Line
$ts = $null
if ($line -match 'time="(\d{2}:\d{2}:\d{2})[^"]*"\s+date="(\d{2}-\d{2}-\d{4})"') {
$ts = [datetime]::ParseExact("$($Matches[2]) $($Matches[1])", "MM-dd-yyyy HH:mm:ss", $null)
}
if ($ts -and $ts -lt $since) { continue }
$msg = if ($line -match '<!\[LOG\[(.*?)\]LOG\]') { $Matches[1] } else { $line }
$results += [pscustomobject]@{
Time = $ts
Log = $f.Name
Msg = $msg.Trim()
}
}
}
}
$results | Sort-Object Time
}
Add-Line "SCCM UPDATE/REBOOT FORENSICS - $(hostname) - $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Add-Line "Analyseperiode: laatste $Hours uur (vanaf $($since.ToString('yyyy-MM-dd HH:mm')))"
# -----------------------------------------------------------------------------
Add-Section "1. USER-INITIATED VS ENFORCED (UpdatesDeployment.log)"
# -----------------------------------------------------------------------------
$udi = Get-CMLogEntries -LogNames 'UpdatesDeployment*.log' -Pattern 'user initiated|InstallUpdates|EnumerateUpdates|Progress received|assignment.*deadline|ProcessAssignment|Started evaluation'
$userInit = $udi | Where-Object { $_.Msg -match "user initiated\s*=\s*'?True'?|userInitiated\s*=\s*1" }
$enforced = $udi | Where-Object { $_.Msg -match "user initiated\s*=\s*'?False'?" }
if ($userInit) {
Add-Line "[!] USER-INITIATED INSTALL GEVONDEN:"
$userInit | ForEach-Object { Add-Line " $($_.Time) | $($_.Msg)" }
} else {
Add-Line "[i] Geen expliciete 'user initiated = True' regels gevonden."
}
if ($enforced) {
Add-Line ""
Add-Line "[i] Enforced (deadline) installs:"
$enforced | Select-Object -First 10 | ForEach-Object { Add-Line " $($_.Time) | $($_.Msg)" }
}
Add-Line ""
Add-Line "--- Relevante UpdatesDeployment regels (max 40):"
$udi | Select-Object -Last 40 | ForEach-Object { Add-Line " $($_.Time) | $($_.Msg)" }
# -----------------------------------------------------------------------------
Add-Section "2. SOFTWARE CENTER ACTIVITEIT (SCClient / SCNotify logs)"
# -----------------------------------------------------------------------------
$scFiles = Get-ChildItem -Path $LogPath -Filter 'SCClient_*' -File | Where-Object LastWriteTime -ge $since
$scnFiles = Get-ChildItem -Path $LogPath -Filter 'SCNotify_*' -File | Where-Object LastWriteTime -ge $since
if ($scFiles -or $scnFiles) {
Add-Line "[!] Software Center is actief geweest binnen de periode door deze users:"
($scFiles + $scnFiles) | ForEach-Object {
if ($_.Name -match '_(.+?)@(.+?)_\d') { $u = "$($Matches[1])\$($Matches[2])" }
elseif ($_.Name -match 'SC(?:Client|Notify)_(.+?)_\d') { $u = $Matches[1] }
else { $u = '?' }
Add-Line " $($_.LastWriteTime) | $($_.Name) | user: $u"
}
Add-Line ""
$scActions = Get-CMLogEntries -LogNames 'SCClient_*.log' -Pattern 'Install|InstallAll|Restart|clicked|invoked|UpdateAction'
if ($scActions) {
Add-Line "--- Software Center acties:"
$scActions | Select-Object -Last 30 | ForEach-Object { Add-Line " $($_.Time) | $($_.Msg)" }
}
} else {
Add-Line "[i] Geen SCClient/SCNotify logs binnen de periode -> niemand had Software Center open."
}
# -----------------------------------------------------------------------------
Add-Section "3. REBOOT TRIGGER (RebootCoordinator.log)"
# -----------------------------------------------------------------------------
$rb = Get-CMLogEntries -LogNames 'RebootCoordinator*.log' -Pattern 'Reboot|Restart|initiat|schedul|shutdown|user'
if ($rb) { $rb | ForEach-Object { Add-Line " $($_.Time) | $($_.Msg)" } }
else { Add-Line "[i] Geen reboot-activiteit in RebootCoordinator.log binnen de periode." }
# -----------------------------------------------------------------------------
Add-Section "4. MAINTENANCE WINDOW EVALUATIE (ServiceWindowManager.log)"
# -----------------------------------------------------------------------------
$sw = Get-CMLogEntries -LogNames 'ServiceWindowManager*.log' -Pattern 'service window|ServiceWindow|Biggest|No service|OnIsServiceWindowAvailable|Schedule'
if ($sw) {
$noMW = $sw | Where-Object { $_.Msg -match 'No service windows exist' }
if ($noMW) { Add-Line "[!] LET OP: 'No service windows exist' gevonden -> client mag altijd installeren!" }
$sw | Select-Object -Last 30 | ForEach-Object { Add-Line " $($_.Time) | $($_.Msg)" }
} else { Add-Line "[i] Geen ServiceWindowManager entries binnen de periode." }
Add-Line ""
Add-Line "--- Actuele maintenance windows via WMI (root\ccm\clientsdk CCM_ServiceWindow):"
$mws = Get-CimInstance -Namespace 'root\ccm\clientsdk' -ClassName CCM_ServiceWindow
if ($mws) {
foreach ($mw in $mws) {
$type = switch ($mw.Type) { 1{'All deployments'} 2{'Program'} 3{'Reboot required'} 4{'Software updates'} 5{'Task sequence'} 6{'User defined'} default{$mw.Type} }
Add-Line " ID: $($mw.ID) | Type: $type | Start: $($mw.StartTime) | Eind: $($mw.EndTime)"
}
} else {
Add-Line " [!] GEEN maintenance windows op deze client gedefinieerd."
}
# -----------------------------------------------------------------------------
Add-Section "5. UPDATE HANDLER / INSTALL DETAILS (UpdatesHandler.log, WUAHandler.log)"
# -----------------------------------------------------------------------------
$uh = Get-CMLogEntries -LogNames 'UpdatesHandler*.log','WUAHandler*.log' -Pattern 'Initiating|install|Successfully completed|reboot|pending|commit'
$uh | Select-Object -Last 40 | ForEach-Object { Add-Line " $($_.Time) | $($_.Msg)" }
# -----------------------------------------------------------------------------
Add-Section "6. POLICY / DEADLINE CHECK (PolicyAgent.log, UpdatesStore.log)"
# -----------------------------------------------------------------------------
$pa = Get-CMLogEntries -LogNames 'PolicyAgent*.log' -Pattern 'assignment|deadline|policy.*update|CCM_UpdateCIAssignment'
$pa | Select-Object -Last 20 | ForEach-Object { Add-Line " $($_.Time) | $($_.Msg)" }
Add-Line ""
Add-Line "--- Actieve update assignments via WMI (deadlines):"
$assignments = Get-CimInstance -Namespace 'root\ccm\policy\machine\actualconfig' -ClassName CCM_UpdateCIAssignment
if ($assignments) {
foreach ($a in $assignments) {
$deadline = if ($a.EnforcementDeadline) { [Management.ManagementDateTimeConverter]::ToDateTime($a.EnforcementDeadline) } else { 'geen' }
Add-Line " '$($a.AssignmentName)' | Deadline: $deadline | Ignore MW (install): $($a.OverrideServiceWindows) | Ignore MW (reboot): $($a.RebootOutsideOfServiceWindows)"
if ($a.OverrideServiceWindows -or $a.RebootOutsideOfServiceWindows) {
Add-Line " [!] DEZE DEPLOYMENT NEGEERT MAINTENANCE WINDOWS!"
}
}
} else {
Add-Line " [i] Geen actieve update assignments in policy."
}
# -----------------------------------------------------------------------------
Add-Section "7. WINDOWS EVENT LOGS - WIE STARTTE DE REBOOT?"
# -----------------------------------------------------------------------------
# Event 1074 = geplande/geinitieerde shutdown incl. proces en user
$ev1074 = Get-WinEvent -FilterHashtable @{ LogName='System'; Id=1074; StartTime=$since } -ErrorAction SilentlyContinue
if ($ev1074) {
Add-Line "--- Event 1074 (shutdown initiated):"
foreach ($e in $ev1074) {
Add-Line " $($e.TimeCreated) | User: $($e.Properties[6].Value) | Proces: $($e.Properties[0].Value) | Reden: $($e.Properties[2].Value) | Type: $($e.Properties[4].Value)"
}
} else { Add-Line "[i] Geen 1074 events binnen de periode." }
$ev6008 = Get-WinEvent -FilterHashtable @{ LogName='System'; Id=6008; StartTime=$since } -ErrorAction SilentlyContinue
if ($ev6008) {
Add-Line "--- Event 6008 (unexpected shutdown):"
$ev6008 | ForEach-Object { Add-Line " $($_.TimeCreated) | $($_.Message -replace '\r?\n',' ')" }
}
# WindowsUpdateClient events: 19=install success, 20=failure, 22=reboot required
$wuEv = Get-WinEvent -FilterHashtable @{ LogName='System'; ProviderName='Microsoft-Windows-WindowsUpdateClient'; StartTime=$since } -ErrorAction SilentlyContinue
if ($wuEv) {
Add-Line ""
Add-Line "--- WindowsUpdateClient events (geinstalleerde updates):"
$wuEv | Select-Object -First 25 | ForEach-Object { Add-Line " $($_.TimeCreated) | ID $($_.Id) | $($_.Message -replace '\r?\n',' ' -replace '\s+',' ')" }
}
# Interactieve logons rond het install-moment (4624 type 2/10)
Add-Line ""
Add-Line "--- Interactieve logons (4624, type 2=console / 10=RDP) binnen periode:"
$logons = Get-WinEvent -FilterHashtable @{ LogName='Security'; Id=4624; StartTime=$since } -MaxEvents 2000 -ErrorAction SilentlyContinue |
Where-Object { $_.Properties[8].Value -in 2,10 }
if ($logons) {
$logons | ForEach-Object {
Add-Line " $($_.TimeCreated) | User: $($_.Properties[5].Value)\$($_.Properties[6].Value) | Type: $($_.Properties[8].Value) | Bron: $($_.Properties[18].Value)"
}
} else { Add-Line " [i] Geen interactieve logons gevonden (of Security log te kort/geen rechten)." }
# -----------------------------------------------------------------------------
Add-Section "8. CONCLUSIE-INDICATOREN"
# -----------------------------------------------------------------------------
$conclusions = @()
if ($userInit) { $conclusions += "[!] Bewijs voor USER-INITIATED install via Software Center gevonden (sectie 1)." }
if ($scFiles) { $conclusions += "[!] Software Center was open bij minimaal 1 user (sectie 2) - correleer tijdstippen." }
if (-not $mws) { $conclusions += "[!] Geen maintenance windows op de client - enforced installs mochten op elk moment draaien (sectie 4)." }
if ($assignments | Where-Object { $_.OverrideServiceWindows -or $_.RebootOutsideOfServiceWindows }) {
$conclusions += "[!] Er is een deployment die MW's negeert (sectie 6)."
}
$deadlinePassed = $assignments | Where-Object { $_.EnforcementDeadline -and ([Management.ManagementDateTimeConverter]::ToDateTime($_.EnforcementDeadline) -lt (Get-Date)) }
if ($deadlinePassed) { $conclusions += "[i] Er zijn assignments met een verstreken deadline - enforced install mogelijk direct na collection-membership of policy refresh." }
$sccmReboot = $ev1074 | Where-Object { $_.Properties[0].Value -match 'CcmExec|wuauclt|TrustedInstaller|CcmRestart' }
if ($sccmReboot) { $conclusions += "[i] Reboot geinitieerd door SCCM/WU proces bevestigd via event 1074." }
$userReboot = $ev1074 | Where-Object { $_.Properties[6].Value -notmatch 'SYSTEM|^$' }
if ($userReboot) { $conclusions += "[!] Er is een reboot geinitieerd onder een USER-account (geen SYSTEM) - zie 1074 events." }
if ($conclusions) { $conclusions | ForEach-Object { Add-Line $_ } }
else { Add-Line "[i] Geen duidelijke indicatoren. Vergelijk handmatig de tijdstippen in secties 1, 3 en 7." }
# -----------------------------------------------------------------------------
if ($OutFile) {
$report | Out-File -FilePath $OutFile -Encoding UTF8
Write-Host "`nRapport weggeschreven naar: $OutFile" -ForegroundColor Green
}