Untitled
unknown
plain_text
3 days ago
10 kB
62
Indexable
$ErrorActionPreference = 'Stop'
# --- Config ---
$ShareFolder = '\\<SHARED FOLDER PATH HERE>'
$CsvPath = Join-Path $ShareFolder 'sb-status.csv'
$DebugDir = Join-Path $ShareFolder 'locks'
$LogsDir = Join-Path $ShareFolder 'logs'
$LocalLogDir = '$env:TEMP'
$LockDir = Join-Path $ShareFolder 'sb-status.lock' # directory used as lock
$LockWaitSec = 10
$LockPollMs = 250
$LockStaleMin = 15
# Fixed CSV schema (forces header + column order)
$CsvColumns = @(
'ComputerName',
'UpdatedAt',
'UEFICA2023Status',
'UEFICA2023Error',
'WindowsUEFICA2023Capable'
)
function Ensure-Dir {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
New-Item $Path -ItemType Directory -Force | Out-Null
}
}
function Test-ShareAvailable {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { return $false }
try {
$tmp = Join-Path $Path ("._writetest_{0}.tmp" -f ([guid]::NewGuid().ToString('N')))
New-Item $tmp -ItemType File -Force | Out-Null
Remove-Item $tmp -Force -ErrorAction Stop
return $true
} catch {
return $false
}
}
function Write-LogLine {
param(
[string]$Path,
[string]$Level,
[string]$Message
)
$ts = (Get-Date).ToString('o')
$line = "{0} [{1}] {2}" -f $ts, $Level, $Message
Add-Content -LiteralPath $Path -Value $line -Encoding UTF8
}
# Attempt lock cleanup on normal engine exit (does NOT run if process is forcibly killed)
Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
try {
if ($script:LockAcquired -and (Test-Path -LiteralPath $script:LockDirPath)) {
Remove-Item -LiteralPath $script:LockDirPath -Recurse -Force -ErrorAction SilentlyContinue
try { Log 'INFO' ("Exiting handler removed lock: {0}" -f $script:LockDirPath) } catch { }
}
} catch { }
} | Out-Null
function Get-LockAgeMinutes {
param([string]$Path)
try {
$di = Get-Item -LiteralPath $Path -ErrorAction Stop
return ((Get-Date) - $di.LastWriteTime).TotalMinutes
} catch {
return $null
}
}
function Break-StaleLockDir {
param([string]$Path, [int]$StaleMinutes)
if (-not (Test-Path -LiteralPath $Path)) { return }
$age = Get-LockAgeMinutes -Path $Path
if ($null -ne $age -and $age -ge $StaleMinutes) {
try {
Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop
Log 'WARN' ("Broke stale lock (age {0:N1} min): {1}" -f $age, $Path)
} catch {
Log 'WARN' ("Failed to break stale lock: {0} :: {1}" -f $Path, $_.Exception.Message)
}
}
}
function Acquire-LockDir {
param(
[string]$Path,
[int]$WaitSeconds,
[int]$PollMs,
[int]$StaleMinutes
)
$start = Get-Date
while ($true) {
Break-StaleLockDir -Path $Path -StaleMinutes $StaleMinutes
try {
# Atomic on SMB: create dir fails if it already exists
New-Item $Path -ItemType Directory -Force:$false | Out-Null
$ownerPath = Join-Path $Path 'owner.txt'
$owner = "Computer={0}`r`nUser={1}`r`nPID={2}`r`nTime={3:o}`r`n" -f $env:COMPUTERNAME, $env:USERNAME, $PID, (Get-Date)
Set-Content $ownerPath -Value $owner -Encoding UTF8 -Force
Log 'INFO' ("Acquired lock: {0}" -f $Path)
return $true
} catch {
if (((Get-Date) - $start).TotalSeconds -ge $WaitSeconds) {
Log 'WARN' ("Timed out waiting for lock: {0}" -f $Path)
return $false
}
Start-Sleep -Milliseconds $PollMs
}
}
}
function Release-LockDir {
param([string]$Path)
try {
Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop
Log 'INFO' ("Released lock: {0}" -f $Path)
} catch {
Log 'WARN' ("Could not remove lock (permissions or share issue): {0} :: {1}" -f $Path, $_.Exception.Message)
}
}
function Ensure-ObjectHasColumns {
param(
[psobject]$Obj,
[string[]]$Columns
)
foreach ($c in $Columns) {
if ($null -eq $Obj.PSObject.Properties[$c]) {
Add-Member -InputObject $Obj -MemberType NoteProperty -Name $c -Value $null -Force
}
}
}
# --- Main ---
$computer = $env:COMPUTERNAME
$now = Get-Date
$script:LockAcquired = $false
$script:LockDirPath = $LockDir
# Fail fast if share not reachable/writable
if (-not (Test-ShareAvailable -Path $ShareFolder)) {
Write-Host "Share not available: $ShareFolder"
exit 0
}
# Prepare dirs (best-effort for debug; logs should exist if possible)
try { Ensure-Dir -Path $DebugDir } catch { }
try { Ensure-Dir -Path $LogsDir } catch { } # don't exit; we'll fallback to local
try { Ensure-Dir -Path $LocalLogDir } catch { }
$ShareLogFile = Join-Path $LogsDir ("{0}.log" -f $computer)
$LocalLogFile = Join-Path $LocalLogDir ("{0}.log" -f $computer)
$UseShareLog = $false
try {
# Try to touch the share log
if (-not (Test-Path -LiteralPath $ShareLogFile)) {
New-Item $ShareLogFile -ItemType File -Force | Out-Null
}
$UseShareLog = $true
} catch {
$UseShareLog = $false
try {
Write-LogLine -Path $LocalLogFile -Level 'WARN' -Message ("Share log unavailable: {0}" -f $_.Exception.Message)
} catch { }
}
function Log {
param([string]$Level, [string]$Message)
if ($UseShareLog) {
try { Write-LogLine -Path $ShareLogFile -Level $Level -Message $Message; return } catch { $UseShareLog = $false }
}
try { Write-LogLine -Path $LocalLogFile -Level $Level -Message $Message } catch { }
}
Log 'INFO' ("Run started. User={0} PID={1}" -f $env:USERNAME, $PID)
# Debug marker (best-effort)
try {
$stamp = $now.ToString('yyyyMMdd_HHmmssfff')
$dbgPath = Join-Path $DebugDir ("{0}_{1}.lock" -f $computer, $stamp)
$dbgMsg = "Computer={0}`r`nUser={1}`r`nPID={2}`r`nTime={3:o}`r`n" -f $computer, $env:USERNAME, $PID, $now
Set-Content -LiteralPath $dbgPath -Value $dbgMsg -Encoding UTF8 -Force
} catch {
Log 'WARN' ("Could not write debug marker: {0}" -f $_.Exception.Message)
}
# Read registry values
$regPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot\Servicing'
$vals = @{
UEFICA2023Status = $null
UEFICA2023Error = $null
WindowsUEFICA2023Capable = $null
}
try {
$p = Get-ItemProperty -Path $regPath -ErrorAction Stop
foreach ($k in @('UEFICA2023Status','UEFICA2023Error','WindowsUEFICA2023Capable')) {
if ($p.PSObject.Properties[$k]) {
$raw = $p.$k
# If it's already numeric, keep it. If it's a numeric string, parse it. Otherwise keep the string.
if ($raw -is [byte] -or $raw -is [int16] -or $raw -is [int32] -or $raw -is [int64] -or $raw -is [uint32]) {
$vals[$k] = $raw
} elseif (($raw -is [string]) -and ($raw -match '^\s*\d+\s*$')) {
$vals[$k] = [int]$raw.Trim()
} else {
$vals[$k] = $raw
}
}
}
Log 'INFO' ("Registry: Status={0} Error={1} Capable={2}" -f $vals.UEFICA2023Status, $vals.UEFICA2023Error, $vals.WindowsUEFICA2023Capable)
} catch {
Log 'WARN' ("Registry read failed: {0}" -f $_.Exception.Message)
}
# Acquire lock (held until CSV update completes)
$locked = Acquire-LockDir -Path $LockDir -WaitSeconds $LockWaitSec -PollMs $LockPollMs -StaleMinutes $LockStaleMin
if (-not $locked) { Write-Host "Failed to acquire lock. Skipping CSV update."; exit 0 }
$script:LockAcquired = $true
try {
Log 'INFO' "Lock acquired."
$records = @()
if (Test-Path -LiteralPath $CsvPath) {
try {
$records = Import-Csv -LiteralPath $CsvPath
Log 'INFO' ("Loaded CSV rows: {0}" -f $records.Count)
} catch {
$backup = Join-Path $ShareFolder ("sb-status.corrupt_{0}.csv" -f (Get-Date).ToString('yyyyMMdd_HHmmss'))
Copy-Item -LiteralPath $CsvPath -Destination $backup -Force -ErrorAction SilentlyContinue
$records = @()
Log 'WARN' ("CSV unreadable; backed up to {0}" -f $backup)
}
} else {
Log 'INFO' "CSV does not exist; will create."
}
# Normalize all imported rows to schema
foreach ($r in $records) {
Ensure-ObjectHasColumns -Obj $r -Columns $CsvColumns
}
# Map by ComputerName
$map = @{}
foreach ($r in $records) {
if ($r.ComputerName -and $r.ComputerName.Trim() -ne '') {
$map[$r.ComputerName.ToUpperInvariant()] = $r
}
}
# Upsert row (do not write CSV unless status changed)
$key = $computer.ToUpperInvariant()
$isNew = $false
if (-not $map.ContainsKey($key)) {
$map[$key] = [pscustomobject]@{}
Ensure-ObjectHasColumns -Obj $map[$key] -Columns $CsvColumns
$map[$key].ComputerName = $computer
$isNew = $true
Log 'INFO' "Row action: ADDED"
} else {
Log 'INFO' "Row action: UPDATED"
}
$prevStatus = $map[$key].UEFICA2023Status
$prevError = $map[$key].UEFICA2023Error
$prevCapable = $map[$key].WindowsUEFICA2023Capable
$newStatus = $vals.UEFICA2023Status
$newError = $vals.UEFICA2023Error
$newCapable = $vals.WindowsUEFICA2023Capable
$changed = $isNew -or ($prevStatus -ne $newStatus) -or ($prevError -ne $newError) -or ($prevCapable -ne $newCapable)
if (-not $changed) {
Log 'INFO' "No status change; skipping CSV write."
return
}
$map[$key].UpdatedAt = $now.ToString('o')
$map[$key].UEFICA2023Status = $newStatus
$map[$key].UEFICA2023Error = $newError
$map[$key].WindowsUEFICA2023Capable = $newCapable
# Export with forced columns (prevents missing headers/values)
$out = $map.Values | ForEach-Object {
Ensure-ObjectHasColumns -Obj $_ -Columns $CsvColumns
$_ | Select-Object $CsvColumns
} | Sort-Object ComputerName
$tmp = Join-Path $ShareFolder ("sb-status.{0}.{1}.tmp" -f $computer, ([guid]::NewGuid().ToString('N')))
$out | Export-Csv -LiteralPath $tmp -NoTypeInformation -Encoding UTF8 -Force
Move-Item -LiteralPath $tmp -Destination $CsvPath -Force
Log 'INFO' ("CSV write OK. Total rows now: {0}" -f $out.Count)
} catch {
Log 'ERROR' ("CSV update failed: {0}" -f $_.Exception.Message)
} finally {
Release-LockDir -Path $LockDir
$script:LockAcquired = $false
Log 'INFO' "Lock released. Run finished."
}
exit 0
Editor is loading...
Leave a Comment