300 lines
6.4 KiB
PowerShell
300 lines
6.4 KiB
PowerShell
# 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
|
|
if ($MyInvocation.MyCommand.ScriptBlock.Module) {
|
|
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
|
|
Stop-Spinner
|
|
}
|
|
}
|