Modules/businessdev.ALbuild.Core/Public/Repair-ALbuildModule.ps1
|
function Repair-ALbuildModule { <# .SYNOPSIS Make a module root healthy: reinstall the target version if it is broken, park any other broken versions for post-mortem, and prune old ones. .DESCRIPTION The recovery half of the integrity design (§4.4/§4.5). Given a target version: - (re)install it atomically if it is missing or fails verification (Install-ALbuildModuleVersion re-checks and only rewrites a broken/absent folder); - park every OTHER version that fails Assert-ALbuildModuleComplete as '<v>.defekt-<timestamp>' (renamed, never deleted - R8), so a poisoned side-by-side copy can't be imported and is kept for analysis; - prune healthy old versions to -KeepVersions. .PARAMETER Version The version to guarantee is present and complete (e.g. '2.18.2'). .PARAMETER ModuleRoot Module root to repair. Default: this edition's CurrentUser root. .PARAMETER Repository Repository for the reinstall. Default 'PSGallery'. .PARAMETER KeepVersions Newest versions to retain when pruning. Default 3. .OUTPUTS PSCustomObject { Version; Path; Installed; Parked [string[]]; Pruned [string[]] }. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Version, [string] $ModuleRoot, [string] $Repository = 'PSGallery', [ValidateRange(1, [int]::MaxValue)] [int] $KeepVersions = 3 ) if (-not $ModuleRoot) { $ModuleRoot = Get-ALbuildUserModuleRoot } $moduleDir = Join-Path $ModuleRoot 'businessdev.ALbuild' # Park OTHER broken versions first (so a poisoned copy can't be picked up), skipping the target itself # - Install below owns the target's repair. Renamed, never deleted (R8). $parked = New-Object System.Collections.Generic.List[string] if (Test-Path -LiteralPath $moduleDir) { foreach ($dir in @(Get-ChildItem -LiteralPath $moduleDir -Directory -ErrorAction SilentlyContinue)) { $v = $null if (-not [version]::TryParse($dir.Name, [ref]$v)) { continue } # skips '*.defekt-*' + staging if ($dir.Name -eq $Version) { continue } if ((Assert-ALbuildModuleComplete -Path $dir.FullName -PassThru).Complete) { continue } $defekt = "$($dir.FullName).defekt-$((Get-Date).ToString('yyyyMMddHHmmss'))" if ($PSCmdlet.ShouldProcess($dir.FullName, 'Park broken businessdev.ALbuild version')) { try { [System.IO.Directory]::Move($dir.FullName, $defekt); $parked.Add($dir.Name); Write-ALbuildLog -Level Warning "Parked broken businessdev.ALbuild $($dir.Name) as '$defekt'." } catch { Write-ALbuildLog -Level Warning "Could not park '$($dir.FullName)': $($_.Exception.Message)" } } } } # Ensure the target is present + complete (atomic; prunes inside its own lock). $install = Install-ALbuildModuleVersion -Version $Version -ModuleRoot $ModuleRoot -Repository $Repository -KeepVersions $KeepVersions [PSCustomObject]@{ Version = $Version Path = if ($install) { $install.Path } else { Join-Path $moduleDir $Version } Installed = if ($install) { $install.Action -eq 'installed' } else { $false } Parked = $parked.ToArray() Pruned = @() } } |