common.ps1 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  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. # Shared terminal design system (skills/_lib/term.ps1) — colorized, ASCII-aware
  13. # framing on stderr (data stays plain on stdout via Write-Data). term.ps1 honors
  14. # NO_COLOR / FORCE_COLOR / TERM_ASCII. Degrade to plain text if the lib is gone.
  15. $script:__WinTermLib = Join-Path $PSScriptRoot '..\..\..\_lib\term.ps1'
  16. if (Test-Path $script:__WinTermLib) {
  17. . $script:__WinTermLib
  18. Initialize-Term
  19. $script:__WinHaveTerm = $true
  20. } else {
  21. $script:__WinHaveTerm = $false
  22. function Get-TermColor { param($Token, $Text) return $Text }
  23. }
  24. function Write-Log {
  25. # All logs to stderr — never pollute stdout. Colorized via term.ps1 (the [TAG]
  26. # text stays literal/greppable; color is amplification, never the only signal).
  27. param(
  28. [Parameter(Mandatory)][ValidateSet('INFO','WARN','ERROR','PASS','FAIL','DEBUG')]$Level,
  29. [Parameter(Mandatory)][string]$Message
  30. )
  31. $token = switch ($Level) {
  32. 'PASS' { 'green' }
  33. 'FAIL' { 'red' }
  34. 'ERROR' { 'red' }
  35. 'WARN' { 'orange' }
  36. 'INFO' { 'cyan' }
  37. 'DEBUG' { 'dim' }
  38. }
  39. [Console]::Error.WriteLine((Get-TermColor $token "[$Level]") + " $Message")
  40. }
  41. function Write-Section {
  42. # Cyan section header (no long === rules — design principle #4: whitespace,
  43. # not rules, separates sections). ASCII-aware via term.ps1.
  44. param([Parameter(Mandatory)][string]$Title)
  45. [Console]::Error.WriteLine("")
  46. [Console]::Error.WriteLine((Get-TermColor cyan "== $Title =="))
  47. }
  48. function Write-Data {
  49. # Plain data row to stdout — only thing that should go there
  50. param([Parameter(Mandatory, ValueFromPipeline)][object]$Object)
  51. process { $Object | Out-String -Stream | Where-Object { $_ -ne '' } | ForEach-Object { [Console]::Out.WriteLine($_) } }
  52. }
  53. function ConvertTo-Bytes12 {
  54. # Build a 12-byte StartupApproved value: [status][3-byte pad][8-byte FILETIME]
  55. param(
  56. [Parameter(Mandatory)][ValidateRange(0,255)][byte]$StatusByte
  57. )
  58. $ts = [BitConverter]::GetBytes([DateTime]::Now.ToFileTime())
  59. [byte[]](@($StatusByte, 0, 0, 0) + $ts)
  60. }
  61. function Get-StartupApprovedKey {
  62. # Ensure the StartupApproved key exists; return its registry path
  63. param(
  64. [Parameter(Mandatory)][ValidateSet('Run','Run32','StartupFolder')]$Variant,
  65. [ValidateSet('HKCU','HKLM')]$Hive = 'HKCU'
  66. )
  67. $key = "${Hive}:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\$Variant"
  68. if (-not (Test-Path $key)) {
  69. New-Item -Path $key -Force -ErrorAction Stop | Out-Null
  70. }
  71. return $key
  72. }
  73. function Test-IsElevated {
  74. # Returns true if running as Administrator
  75. $id = [Security.Principal.WindowsIdentity]::GetCurrent()
  76. $principal = New-Object Security.Principal.WindowsPrincipal($id)
  77. return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
  78. }
  79. function Get-DiskMap {
  80. # Map physical disk number -> friendly name / type / drive letters
  81. # Returns array of [PSCustomObject] with Number, Model, BusType, MediaType, FirmwareVersion, SizeGB, DriveLetters
  82. Get-Disk | ForEach-Object {
  83. $disk = $_
  84. $letters = (Get-Partition -DiskNumber $disk.Number -ErrorAction SilentlyContinue |
  85. Where-Object { $_.DriveLetter } | Select-Object -ExpandProperty DriveLetter) -join ','
  86. $physical = Get-PhysicalDisk | Where-Object { $_.DeviceId -eq $disk.Number } | Select-Object -First 1
  87. [PSCustomObject]@{
  88. Number = $disk.Number
  89. Model = $disk.FriendlyName
  90. BusType = $disk.BusType
  91. MediaType = if ($physical) { $physical.MediaType } else { 'Unknown' }
  92. FirmwareVersion = $disk.FirmwareVersion
  93. SizeGB = [math]::Round($disk.Size / 1GB, 0)
  94. DriveLetters = $letters
  95. HealthStatus = $disk.HealthStatus
  96. SerialNumber = if ($physical) { $physical.SerialNumber } else { $null }
  97. }
  98. }
  99. }
  100. function Resolve-HarddiskRef {
  101. # Resolve a "\Device\HarddiskN" reference to a disk map row
  102. param([Parameter(Mandatory)][string]$Reference)
  103. if ($Reference -match 'Harddisk(\d+)' -or $Reference -match '^Disk\s*(\d+)' -or $Reference -match '^(\d+)$') {
  104. $num = [int]$matches[1]
  105. return Get-DiskMap | Where-Object { $_.Number -eq $num } | Select-Object -First 1
  106. }
  107. return $null
  108. }
  109. function Format-EventMessage {
  110. # Truncate + collapse whitespace for table display
  111. param(
  112. [Parameter(Mandatory, ValueFromPipeline)][string]$Message,
  113. [int]$MaxLength = 120
  114. )
  115. process {
  116. $cleaned = $Message -replace '\s+', ' '
  117. if ($cleaned.Length -le $MaxLength) { return $cleaned }
  118. return $cleaned.Substring(0, $MaxLength - 3) + '...'
  119. }
  120. }
  121. # Export common state for caller scripts
  122. $script:CommonLoaded = $true