Untitled

 avatar
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