schon/scripts/lib/utils.ps1
2026-01-25 23:16:38 +03:00

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
}
}