# Shared utilities for Windows scripts # Provides: colors, progress indicators, interactive detection Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' # Detect if running in interactive shell function Test-Interactive { # Check if both input and output are from terminal # In non-interactive environments (CI/CD), this will be false $isInteractive = [Environment]::UserInteractive -and (-not [Console]::IsInputRedirected) -and (-not [Console]::IsOutputRedirected) return $isInteractive } # Global spinner state $script:SpinnerJob = $null $script:SpinnerFrames = @('⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏') # Logging functions function Write-Info { param([string]$Message) if (Test-Interactive) { Write-Host $Message -ForegroundColor Blue } else { Write-Output $Message } } function Write-Success { param([string]$Message) if (Test-Interactive) { Write-Host $Message -ForegroundColor Green } else { Write-Output $Message } } function Write-Warning-Custom { param([string]$Message) if (Test-Interactive) { Write-Host $Message -ForegroundColor Yellow } else { Write-Output "WARNING: $Message" } } function Write-Error-Custom { param([string]$Message) if (Test-Interactive) { Write-Host $Message -ForegroundColor Red } else { Write-Output "ERROR: $Message" *>&1 } } function Write-Step { param([string]$Message) if (Test-Interactive) { Write-Host $Message -ForegroundColor Magenta } else { Write-Output $Message } } function Write-Result { param([string]$Message) if (Test-Interactive) { Write-Host $Message -ForegroundColor Cyan } else { Write-Output $Message } } # Spinner functions (only for interactive shells) function Start-Spinner { param([string]$Message = "Processing...") if (-not (Test-Interactive)) { Write-Output $Message return } $script:SpinnerJob = Start-Job -ScriptBlock { param($Frames, $Msg) $i = 0 while ($true) { $frame = $Frames[$i % $Frames.Length] [Console]::SetCursorPosition(0, [Console]::CursorTop) Write-Host -NoNewline "$frame $Msg" Start-Sleep -Milliseconds 100 $i++ } } -ArgumentList $script:SpinnerFrames, $Message } function Stop-Spinner { if ($script:SpinnerJob -ne $null) { Stop-Job -Job $script:SpinnerJob -ErrorAction SilentlyContinue Remove-Job -Job $script:SpinnerJob -Force -ErrorAction SilentlyContinue $script:SpinnerJob = $null if (Test-Interactive) { # Clear the spinner line [Console]::SetCursorPosition(0, [Console]::CursorTop) Write-Host (' ' * ([Console]::WindowWidth - 1)) -NoNewline [Console]::SetCursorPosition(0, [Console]::CursorTop) } } } function Update-Spinner { param([string]$Message) if (-not (Test-Interactive)) { Write-Output $Message return } if ($script:SpinnerJob -ne $null) { Stop-Spinner Start-Spinner -Message $Message } } # Execute command with progress indicator function Invoke-WithProgress { param( [string]$Message, [scriptblock]$ScriptBlock ) if (Test-Interactive) { Start-Spinner -Message $Message try { $output = & $ScriptBlock 2>&1 $exitCode = $LASTEXITCODE Stop-Spinner if ($exitCode -eq 0 -or $null -eq $exitCode) { Write-Success "✓ $Message - Done!" } else { Write-Error-Custom "✗ $Message - Failed!" if ($output) { Write-Output $output } } return $exitCode } catch { Stop-Spinner Write-Error-Custom "✗ $Message - Failed!" throw } } else { # Non-interactive: just show message and run Write-Output "→ $Message" try { & $ScriptBlock $exitCode = $LASTEXITCODE if ($exitCode -eq 0 -or $null -eq $exitCode) { Write-Output "✓ $Message - Done!" } else { Write-Output "✗ $Message - Failed!" } return $exitCode } catch { Write-Output "✗ $Message - Failed!" throw } } } # Check system requirements function Test-SystemRequirements { param( [int]$MinCpu = 4, [int]$MinRamGB = 6, [int]$MinDiskGB = 20 ) Write-Step "Checking system requirements..." # Check CPU cores $cpuCount = [Environment]::ProcessorCount if ($cpuCount -lt $MinCpu) { Write-Error-Custom "Insufficient CPU cores: $cpuCount (minimum: $MinCpu)" return $false } # Check RAM $totalMemBytes = (Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory $totalMemGB = [Math]::Round($totalMemBytes / 1GB, 2) if ($totalMemGB -lt $MinRamGB) { Write-Error-Custom "Insufficient RAM: ${totalMemGB}GB (minimum: ${MinRamGB}GB)" return $false } # Check disk space $currentDrive = Split-Path -Qualifier $PWD $driveLetter = $currentDrive.Substring(0, 1) $freeGB = [Math]::Round((Get-PSDrive -Name $driveLetter).Free / 1GB, 2) if ($freeGB -lt $MinDiskGB) { Write-Error-Custom "Insufficient disk space: ${freeGB}GB (minimum: ${MinDiskGB}GB)" return $false } Write-Success "System requirements met: CPU cores=$cpuCount, RAM=${totalMemGB}GB, FreeDisk=${freeGB}GB" return $true } # Confirm action function Confirm-Action { param([string]$Prompt = "Are you sure?") if (-not (Test-Interactive)) { # In non-interactive mode, assume yes return $true } $response = Read-Host "$Prompt [y/N]" return $response -match '^[yY]([eE][sS])?$' } # Ensure spinner cleanup on exit $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { Stop-Spinner }