sync: auto-sync from ACG-TECH03L at 2026-04-17 14:10:20
Author: Howard Enos Machine: ACG-TECH03L Timestamp: 2026-04-17 14:10:20
This commit is contained in:
@@ -1,8 +1,23 @@
|
|||||||
# ==========================================
|
# ============================================================
|
||||||
# UNIVERSAL WORKSTATION AUDIT SCRIPT
|
# UNIVERSAL WORKSTATION AUDIT SCRIPT
|
||||||
# Works on Windows 10 / 11 (domain or workgroup)
|
#
|
||||||
# Outputs: HOSTNAME_workstation_audit_DATE.json to C:\Temp
|
# Script Version : 2.0.0
|
||||||
# ==========================================
|
# Schema Version : 2.0 (output JSON shape; bump on breaking changes)
|
||||||
|
# Build Date : 2026-04-17
|
||||||
|
# Sections : 49 (originals 1-33; security/diag additions 34-49)
|
||||||
|
# Compatibility : Windows 10 21H2 -> Windows 11 25H2 (admin required)
|
||||||
|
#
|
||||||
|
# Outputs : C:\Temp\<HOSTNAME>_workstation_audit_<DATE>.json
|
||||||
|
# (UTF-8, ConvertTo-Json depth 10)
|
||||||
|
#
|
||||||
|
# Each top-level JSON key is one of: _metadata, _errors, then one
|
||||||
|
# property per section in execution order. Section names are stable;
|
||||||
|
# never rename without bumping Schema Version.
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
$ScriptVersion = "2.0.1"
|
||||||
|
$ScriptSchemaVersion = "2.0"
|
||||||
|
$ScriptBuildDate = "2026-04-17"
|
||||||
|
|
||||||
# Auto-relaunch with ExecutionPolicy Bypass if needed
|
# Auto-relaunch with ExecutionPolicy Bypass if needed
|
||||||
if ($MyInvocation.MyCommand.Path) {
|
if ($MyInvocation.MyCommand.Path) {
|
||||||
@@ -27,22 +42,37 @@ $JsonFile = "$OutputDir\${Name}_workstation_audit_$Date.json"
|
|||||||
|
|
||||||
if (!(Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir | Out-Null }
|
if (!(Test-Path $OutputDir)) { New-Item -ItemType Directory -Path $OutputDir | Out-Null }
|
||||||
|
|
||||||
|
# Self-SHA (file-on-disk runs only; iex-streamed runs report "iex-streamed")
|
||||||
|
$ScriptSelfSHA256 = if ($MyInvocation.MyCommand.Path -and (Test-Path $MyInvocation.MyCommand.Path)) {
|
||||||
|
try { (Get-FileHash -Path $MyInvocation.MyCommand.Path -Algorithm SHA256 -ErrorAction Stop).Hash }
|
||||||
|
catch { "sha-computation-failed" }
|
||||||
|
} else { "iex-streamed" }
|
||||||
|
|
||||||
# Structured data collector
|
# Structured data collector
|
||||||
$audit = [ordered]@{
|
$audit = [ordered]@{
|
||||||
_metadata = [ordered]@{
|
_metadata = [ordered]@{
|
||||||
ScriptVersion = "1.0"
|
ScriptVersion = $ScriptVersion
|
||||||
ScriptType = "Workstation"
|
ScriptSchemaVersion = $ScriptSchemaVersion
|
||||||
RunDate = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
|
ScriptBuildDate = $ScriptBuildDate
|
||||||
RunBy = "$env:USERDOMAIN\$env:USERNAME"
|
ScriptSelfSHA256 = $ScriptSelfSHA256
|
||||||
Hostname = $env:COMPUTERNAME
|
ScriptType = "Workstation"
|
||||||
|
SectionCount = 49
|
||||||
|
RunDate = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
|
||||||
|
RunDateUtc = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
||||||
|
RunBy = "$env:USERDOMAIN\$env:USERNAME"
|
||||||
|
Hostname = $env:COMPUTERNAME
|
||||||
|
OSCaption = (Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction SilentlyContinue).Caption
|
||||||
|
OSBuild = (Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction SilentlyContinue).BuildNumber
|
||||||
|
PowerShellVersion = "$($PSVersionTable.PSVersion)"
|
||||||
}
|
}
|
||||||
_errors = @()
|
_errors = @()
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "======================================="
|
Write-Host "============================================="
|
||||||
Write-Host " UNIVERSAL WORKSTATION AUDIT v1.0"
|
Write-Host " UNIVERSAL WORKSTATION AUDIT v$ScriptVersion (schema $ScriptSchemaVersion)"
|
||||||
Write-Host " $((Get-Date).ToString('yyyy-MM-dd HH:mm:ss'))"
|
Write-Host " Build $ScriptBuildDate - $($audit._metadata.SectionCount) sections"
|
||||||
Write-Host "======================================="
|
Write-Host " $($audit._metadata.RunDate)"
|
||||||
|
Write-Host "============================================="
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
@@ -1292,10 +1322,10 @@ try {
|
|||||||
NISEngineVersion = "$($mp.NISEngineVersion)"
|
NISEngineVersion = "$($mp.NISEngineVersion)"
|
||||||
AntispywareSignatureLastUpdated = if ($mp.AntispywareSignatureLastUpdated) { $mp.AntispywareSignatureLastUpdated.ToString("yyyy-MM-dd HH:mm:ss") } else { $null }
|
AntispywareSignatureLastUpdated = if ($mp.AntispywareSignatureLastUpdated) { $mp.AntispywareSignatureLastUpdated.ToString("yyyy-MM-dd HH:mm:ss") } else { $null }
|
||||||
AntivirusSignatureLastUpdated = if ($mp.AntivirusSignatureLastUpdated) { $mp.AntivirusSignatureLastUpdated.ToString("yyyy-MM-dd HH:mm:ss") } else { $null }
|
AntivirusSignatureLastUpdated = if ($mp.AntivirusSignatureLastUpdated) { $mp.AntivirusSignatureLastUpdated.ToString("yyyy-MM-dd HH:mm:ss") } else { $null }
|
||||||
AntivirusSignatureAgeDays = $mp.AntivirusSignatureAge
|
AntivirusSignatureAgeDays = if ($mp.AntivirusSignatureAge -eq 4294967295) { $null } else { $mp.AntivirusSignatureAge }
|
||||||
NISSignatureAgeDays = $mp.NISSignatureAge
|
NISSignatureAgeDays = if ($mp.NISSignatureAge -eq 4294967295) { $null } else { $mp.NISSignatureAge }
|
||||||
FullScanAgeDays = $mp.FullScanAge
|
FullScanAgeDays = if ($mp.FullScanAge -eq 4294967295) { $null } else { $mp.FullScanAge }
|
||||||
QuickScanAgeDays = $mp.QuickScanAge
|
QuickScanAgeDays = if ($mp.QuickScanAge -eq 4294967295) { $null } else { $mp.QuickScanAge }
|
||||||
FullScanEndTime = if ($mp.FullScanEndTime) { $mp.FullScanEndTime.ToString("yyyy-MM-dd HH:mm:ss") } else { $null }
|
FullScanEndTime = if ($mp.FullScanEndTime) { $mp.FullScanEndTime.ToString("yyyy-MM-dd HH:mm:ss") } else { $null }
|
||||||
QuickScanEndTime = if ($mp.QuickScanEndTime) { $mp.QuickScanEndTime.ToString("yyyy-MM-dd HH:mm:ss") } else { $null }
|
QuickScanEndTime = if ($mp.QuickScanEndTime) { $mp.QuickScanEndTime.ToString("yyyy-MM-dd HH:mm:ss") } else { $null }
|
||||||
IsTamperProtected = if ($mp.PSObject.Properties.Match('IsTamperProtected').Count) { [bool]$mp.IsTamperProtected } else { $null }
|
IsTamperProtected = if ($mp.PSObject.Properties.Match('IsTamperProtected').Count) { [bool]$mp.IsTamperProtected } else { $null }
|
||||||
@@ -1419,6 +1449,12 @@ try {
|
|||||||
if ($def.Status.QuickScanAgeDays -ne $null -and $def.Status.QuickScanAgeDays -gt 14) {
|
if ($def.Status.QuickScanAgeDays -ne $null -and $def.Status.QuickScanAgeDays -gt 14) {
|
||||||
$audit.SecuritySummary += "Defender quick scan stale ($($def.Status.QuickScanAgeDays)d)"
|
$audit.SecuritySummary += "Defender quick scan stale ($($def.Status.QuickScanAgeDays)d)"
|
||||||
}
|
}
|
||||||
|
if ($def.Status.QuickScanAgeDays -eq $null) {
|
||||||
|
$audit.SecuritySummary += "Defender quick scan has never run"
|
||||||
|
}
|
||||||
|
if ($def.Status.FullScanAgeDays -eq $null) {
|
||||||
|
$audit.SecuritySummary += "Defender full scan has never run"
|
||||||
|
}
|
||||||
if ($def.ControlledFolderAccess.EnableControlledFolderAccess -eq "0") {
|
if ($def.ControlledFolderAccess.EnableControlledFolderAccess -eq "0") {
|
||||||
$audit.SecuritySummary += "Controlled Folder Access (anti-ransomware) disabled"
|
$audit.SecuritySummary += "Controlled Folder Access (anti-ransomware) disabled"
|
||||||
}
|
}
|
||||||
@@ -1584,6 +1620,13 @@ Write-Host "=== 38. SERVICES INVENTORY ===" -ForegroundColor Cyan
|
|||||||
try {
|
try {
|
||||||
$allSvc = @(Get-CimInstance -ClassName Win32_Service -ErrorAction SilentlyContinue)
|
$allSvc = @(Get-CimInstance -ClassName Win32_Service -ErrorAction SilentlyContinue)
|
||||||
$suspSvc = @()
|
$suspSvc = @()
|
||||||
|
# Allowlist for known-good Microsoft / vendor paths under ProgramData (auto-updating engines etc.)
|
||||||
|
$svcAllowlistPatterns = @(
|
||||||
|
'(?i)^[A-Z]:\\ProgramData\\Microsoft\\Windows Defender\\Platform\\',
|
||||||
|
'(?i)^[A-Z]:\\ProgramData\\Microsoft\\Windows Defender\\Definition Updates\\',
|
||||||
|
'(?i)^[A-Z]:\\ProgramData\\Package Cache\\',
|
||||||
|
'(?i)^[A-Z]:\\ProgramData\\Microsoft\\Click[Tt]o[Rr]un\\'
|
||||||
|
)
|
||||||
foreach ($s in $allSvc) {
|
foreach ($s in $allSvc) {
|
||||||
$reasons = @()
|
$reasons = @()
|
||||||
$path = "$($s.PathName)"
|
$path = "$($s.PathName)"
|
||||||
@@ -1593,16 +1636,20 @@ try {
|
|||||||
elseif ($path -match '^(\S+\.exe)') { $exe = $matches[1] }
|
elseif ($path -match '^(\S+\.exe)') { $exe = $matches[1] }
|
||||||
else { $exe = ($path -split '\s')[0] }
|
else { $exe = ($path -split '\s')[0] }
|
||||||
|
|
||||||
|
# Check against allowlist
|
||||||
|
$isAllowed = $false
|
||||||
|
foreach ($pat in $svcAllowlistPatterns) { if ($exe -match $pat) { $isAllowed = $true; break } }
|
||||||
|
|
||||||
# Path with spaces but not quoted (privilege-escalation classic)
|
# Path with spaces but not quoted (privilege-escalation classic)
|
||||||
if ($path -and $path -notmatch '^"' -and $path -match '\s' -and $path -match '^([A-Z]:\\[^"]+\s+[^"]+\.exe)') {
|
if ($path -and $path -notmatch '^"' -and $path -match '\s' -and $path -match '^([A-Z]:\\[^"]+\s+[^"]+\.exe)') {
|
||||||
$reasons += "Unquoted path with spaces"
|
$reasons += "Unquoted path with spaces"
|
||||||
}
|
}
|
||||||
# Binary in user-writable dir
|
# Binary in user-writable dir (skip if allowlisted)
|
||||||
if ($exe -match '(?i)\\(users|programdata|temp|public|appdata)\\') {
|
if (-not $isAllowed -and $exe -match '(?i)\\(users|programdata|temp|public|appdata)\\') {
|
||||||
$reasons += "Binary in user-writable directory: $exe"
|
$reasons += "Binary in user-writable directory: $exe"
|
||||||
}
|
}
|
||||||
# Not in standard system paths
|
# Not in standard system paths (skip if allowlisted)
|
||||||
if ($exe -and $exe -notmatch '(?i)^[A-Z]:\\(Windows|Program Files|Program Files \(x86\))') {
|
if (-not $isAllowed -and $exe -and $exe -notmatch '(?i)^[A-Z]:\\(Windows|Program Files|Program Files \(x86\))') {
|
||||||
if ($exe -notmatch '(?i)^[A-Z]:\\Users') {
|
if ($exe -notmatch '(?i)^[A-Z]:\\Users') {
|
||||||
$reasons += "Binary outside standard system paths: $exe"
|
$reasons += "Binary outside standard system paths: $exe"
|
||||||
}
|
}
|
||||||
@@ -2744,21 +2791,61 @@ try {
|
|||||||
|
|
||||||
# =====================================================
|
# =====================================================
|
||||||
# DONE - SAVE JSON
|
# DONE - SAVE JSON
|
||||||
|
# (Banner moved to AFTER save so completion message reflects truth.
|
||||||
|
# ConvertTo-Json on PS 5.1 is slow on multi-MB hashtables; we time it,
|
||||||
|
# use [IO.File]::WriteAllText for fast write, and provide a per-section
|
||||||
|
# fallback if the monolithic serialize fails.)
|
||||||
# =====================================================
|
# =====================================================
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "======================================="
|
Write-Host "[INFO] All section data collected. Serializing to JSON (can take 30-90s on large profiles)..." -ForegroundColor Cyan
|
||||||
Write-Host " WORKSTATION AUDIT COMPLETE"
|
$saveStart = Get-Date
|
||||||
Write-Host "======================================="
|
$saveOk = $false
|
||||||
|
$saveErr = ""
|
||||||
|
try {
|
||||||
|
$jsonText = $audit | ConvertTo-Json -Depth 8 -ErrorAction Stop
|
||||||
|
[System.IO.File]::WriteAllText($JsonFile, $jsonText, [System.Text.UTF8Encoding]::new($false))
|
||||||
|
$saveOk = $true
|
||||||
|
$saveDuration = ((Get-Date) - $saveStart).TotalSeconds
|
||||||
|
$jsonSizeKB = [math]::Round((Get-Item $JsonFile).Length / 1KB, 1)
|
||||||
|
Write-Host "[OK] JSON written: $JsonFile ($jsonSizeKB KB in $([math]::Round($saveDuration,1))s)" -ForegroundColor Green
|
||||||
|
} catch {
|
||||||
|
$saveErr = $_.Exception.Message
|
||||||
|
Write-Host "[ERROR] Monolithic JSON serialize/write failed: $saveErr" -ForegroundColor Red
|
||||||
|
Write-Host "[INFO] Attempting per-section fallback export..." -ForegroundColor Yellow
|
||||||
|
try {
|
||||||
|
$fallbackDir = ($JsonFile -replace '\.json$','') + "_partial"
|
||||||
|
New-Item -ItemType Directory -Path $fallbackDir -Force | Out-Null
|
||||||
|
$secOk = 0; $secFail = 0
|
||||||
|
foreach ($k in @($audit.Keys)) {
|
||||||
|
$partPath = Join-Path $fallbackDir "$k.json"
|
||||||
|
try {
|
||||||
|
$partText = $audit[$k] | ConvertTo-Json -Depth 8 -ErrorAction Stop
|
||||||
|
[System.IO.File]::WriteAllText($partPath, $partText, [System.Text.UTF8Encoding]::new($false))
|
||||||
|
$secOk++
|
||||||
|
} catch {
|
||||||
|
[System.IO.File]::WriteAllText((Join-Path $fallbackDir "$k.ERROR.txt"), "Failed: $($_.Exception.Message)", [System.Text.UTF8Encoding]::new($false))
|
||||||
|
$secFail++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host "[OK] Fallback complete: $secOk sections saved, $secFail failed. Folder: $fallbackDir" -ForegroundColor Yellow
|
||||||
|
} catch {
|
||||||
|
Write-Host "[ERROR] Per-section fallback also failed: $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "======================================="
|
||||||
|
if ($saveOk) {
|
||||||
|
Write-Host " WORKSTATION AUDIT COMPLETE"
|
||||||
|
} else {
|
||||||
|
Write-Host " WORKSTATION AUDIT FINISHED (SAVE ISSUE)"
|
||||||
|
}
|
||||||
|
Write-Host "======================================="
|
||||||
$errorCount = $audit._errors.Count
|
$errorCount = $audit._errors.Count
|
||||||
if ($errorCount -gt 0) {
|
if ($errorCount -gt 0) {
|
||||||
Write-Host "Completed with $errorCount section errors (see _errors in JSON)" -ForegroundColor Yellow
|
Write-Host "Section errors: $errorCount (see _errors in JSON)" -ForegroundColor Yellow
|
||||||
} else {
|
} else {
|
||||||
Write-Host "All sections completed successfully" -ForegroundColor Green
|
Write-Host "All sections completed successfully" -ForegroundColor Green
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "JSON data: $JsonFile"
|
Write-Host "JSON data: $JsonFile"
|
||||||
Write-Host "======================================="
|
Write-Host "======================================="
|
||||||
|
|
||||||
# Save JSON
|
|
||||||
$audit | ConvertTo-Json -Depth 10 | Out-File $JsonFile -Encoding UTF8
|
|
||||||
|
|||||||
Reference in New Issue
Block a user