common.ps1 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. # windows-ops common helpers
  2. # Dot-source from any script: . "$PSScriptRoot\_lib\common.ps1"
  3. # Semantic exit codes (matches ATP §7.8)
  4. $script:EXIT_OK = 0
  5. $script:EXIT_ERROR = 1
  6. $script:EXIT_USAGE = 2
  7. $script:EXIT_NOT_FOUND = 3
  8. $script:EXIT_VALIDATION = 4
  9. $script:EXIT_PRECONDITION = 5
  10. $script:EXIT_TIMEOUT = 6
  11. $script:EXIT_UNAVAILABLE = 7
  12. function Write-Log {
  13. # All logs to stderr — never pollute stdout
  14. param(
  15. [Parameter(Mandatory)][ValidateSet('INFO','WARN','ERROR','PASS','FAIL','DEBUG')]$Level,
  16. [Parameter(Mandatory)][string]$Message
  17. )
  18. $color = switch ($Level) {
  19. 'PASS' { 'Green' }
  20. 'FAIL' { 'Red' }
  21. 'ERROR' { 'Red' }
  22. 'WARN' { 'Yellow' }
  23. 'INFO' { 'Cyan' }
  24. 'DEBUG' { 'DarkGray' }
  25. }
  26. [Console]::Error.WriteLine("[$Level] $Message")
  27. # Re-emit colorised version when stderr is a TTY (for human readability)
  28. if ([Console]::IsErrorRedirected -eq $false) {
  29. # Can't easily colorise stderr in PS — accept plain text, color reserved for TTY-only contexts
  30. }
  31. }
  32. function Write-Section {
  33. param([Parameter(Mandatory)][string]$Title)
  34. $line = '=' * 60
  35. [Console]::Error.WriteLine("")
  36. [Console]::Error.WriteLine($line)
  37. [Console]::Error.WriteLine(" $Title")
  38. [Console]::Error.WriteLine($line)
  39. }
  40. function Write-Data {
  41. # Plain data row to stdout — only thing that should go there
  42. param([Parameter(Mandatory, ValueFromPipeline)][object]$Object)
  43. process { $Object | Out-String -Stream | Where-Object { $_ -ne '' } | ForEach-Object { [Console]::Out.WriteLine($_) } }
  44. }
  45. function ConvertTo-Bytes12 {
  46. # Build a 12-byte StartupApproved value: [status][3-byte pad][8-byte FILETIME]
  47. param(
  48. [Parameter(Mandatory)][ValidateRange(0,255)][byte]$StatusByte
  49. )
  50. $ts = [BitConverter]::GetBytes([DateTime]::Now.ToFileTime())
  51. [byte[]](@($StatusByte, 0, 0, 0) + $ts)
  52. }
  53. function Get-StartupApprovedKey {
  54. # Ensure the StartupApproved key exists; return its registry path
  55. param(
  56. [Parameter(Mandatory)][ValidateSet('Run','Run32','StartupFolder')]$Variant,
  57. [ValidateSet('HKCU','HKLM')]$Hive = 'HKCU'
  58. )
  59. $key = "${Hive}:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\$Variant"
  60. if (-not (Test-Path $key)) {
  61. New-Item -Path $key -Force -ErrorAction Stop | Out-Null
  62. }
  63. return $key
  64. }
  65. function Test-IsElevated {
  66. # Returns true if running as Administrator
  67. $id = [Security.Principal.WindowsIdentity]::GetCurrent()
  68. $principal = New-Object Security.Principal.WindowsPrincipal($id)
  69. return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
  70. }
  71. function Get-DiskMap {
  72. # Map physical disk number -> friendly name / type / drive letters
  73. # Returns array of [PSCustomObject] with Number, Model, BusType, MediaType, FirmwareVersion, SizeGB, DriveLetters
  74. Get-Disk | ForEach-Object {
  75. $disk = $_
  76. $letters = (Get-Partition -DiskNumber $disk.Number -ErrorAction SilentlyContinue |
  77. Where-Object { $_.DriveLetter } | Select-Object -ExpandProperty DriveLetter) -join ','
  78. $physical = Get-PhysicalDisk | Where-Object { $_.DeviceId -eq $disk.Number } | Select-Object -First 1
  79. [PSCustomObject]@{
  80. Number = $disk.Number
  81. Model = $disk.FriendlyName
  82. BusType = $disk.BusType
  83. MediaType = if ($physical) { $physical.MediaType } else { 'Unknown' }
  84. FirmwareVersion = $disk.FirmwareVersion
  85. SizeGB = [math]::Round($disk.Size / 1GB, 0)
  86. DriveLetters = $letters
  87. HealthStatus = $disk.HealthStatus
  88. SerialNumber = if ($physical) { $physical.SerialNumber } else { $null }
  89. }
  90. }
  91. }
  92. function Resolve-HarddiskRef {
  93. # Resolve a "\Device\HarddiskN" reference to a disk map row
  94. param([Parameter(Mandatory)][string]$Reference)
  95. if ($Reference -match 'Harddisk(\d+)' -or $Reference -match '^Disk\s*(\d+)' -or $Reference -match '^(\d+)$') {
  96. $num = [int]$matches[1]
  97. return Get-DiskMap | Where-Object { $_.Number -eq $num } | Select-Object -First 1
  98. }
  99. return $null
  100. }
  101. function Format-EventMessage {
  102. # Truncate + collapse whitespace for table display
  103. param(
  104. [Parameter(Mandatory, ValueFromPipeline)][string]$Message,
  105. [int]$MaxLength = 120
  106. )
  107. process {
  108. $cleaned = $Message -replace '\s+', ' '
  109. if ($cleaned.Length -le $MaxLength) { return $cleaned }
  110. return $cleaned.Substring(0, $MaxLength - 3) + '...'
  111. }
  112. }
  113. # Export common state for caller scripts
  114. $script:CommonLoaded = $true