220 lines
12 KiB
PowerShell
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
|
|
} |