From 761100a9316baed757dae5626f4f310fd091f064 Mon Sep 17 00:00:00 2001 From: Stephan Yelle Date: Thu, 26 Feb 2026 21:09:29 -0500 Subject: [PATCH] attempted to add self heal if get-psrepository is missing --- src/svsmsp.install.ps1 | 374 +++++++++++++++++++---------------------- 1 file changed, 174 insertions(+), 200 deletions(-) diff --git a/src/svsmsp.install.ps1 b/src/svsmsp.install.ps1 index 4f78409..e0acf0e 100644 --- a/src/svsmsp.install.ps1 +++ b/src/svsmsp.install.ps1 @@ -1,3 +1,117 @@ +function Repair-PowerShellGetStack { + <# + .SYNOPSIS + Repairs (self-heals) the local PowerShellGet/PackageManagement installation so PowerShellGet v2 cmdlets (e.g. Get-PSRepository) are available. + + .DESCRIPTION + Normalizes common broken module layouts for PowerShellGet and PackageManagement that prevent Import-Module PowerShellGet from working. + This can happen when the modules are installed/unpacked into non-standard folder names such as: + - PowerShellGet\PowerShellGet.2.2.5\ + - PowerShellGet\\ + - PackageManagement\PackageManagement.1.4.8.1\ + - PowerShellGet\PackageManagement.1.4.8.1\ (wrong root) + + The function: + 1) Ensures the Program Files module path is present in $env:PSModulePath + 2) Moves PackageManagement out of the wrong root if found under the PowerShellGet folder + 3) Renames/moves module folders into the standard versioned layout: + ...\Modules\PackageManagement\\ + ...\Modules\PowerShellGet\\ + 4) Imports the requested versions and verifies Get-PSRepository is available + + Safe to run multiple times. It does not download anything from the internet; it only rearranges/loads locally present modules. + + .PARAMETER PowerShellGetVersion + The PowerShellGet version to normalize and import (default: 2.2.5). + + .PARAMETER PackageManagementVersion + The PackageManagement version to normalize and import (default: 1.4.8.1). + #> + + [CmdletBinding()] + param( + [Version]$PowerShellGetVersion = [Version]'2.2.5', + [Version]$PackageManagementVersion= [Version]'1.4.8.1' + ) + + function _EnsurePSModulePath { + $all = Join-Path $env:ProgramFiles 'WindowsPowerShell\Modules' + if (($env:PSModulePath -split ';') -notcontains $all) { + $env:PSModulePath = "$env:PSModulePath;$all" + } + } + + function _NormalizeModuleFolder { + param( + [Parameter(Mandatory)][string]$ModuleName, + [Parameter(Mandatory)][Version]$Version + ) + + $base = Join-Path (Join-Path $env:ProgramFiles 'WindowsPowerShell\Modules') $ModuleName + New-Item -ItemType Directory -Path $base -Force | Out-Null + + $wanted = Join-Path $base $Version.ToString() + + # common wrong names: + $candidates = @( + Join-Path $base ("{0}.{1}" -f $ModuleName, $Version.ToString()), + Join-Path $base ("{0}.{1}" -f $ModuleName, $Version.ToString()), + Join-Path $base ("{0}.{1}" -f $ModuleName, $Version.ToString(3)) + ) | Select-Object -Unique + + $manifestName = "{0}.psd1" -f $ModuleName + $foundManifests = Get-ChildItem -Path $base -Recurse -Filter $manifestName -ErrorAction SilentlyContinue + foreach ($m in $foundManifests) { + $candidates += (Split-Path $m.FullName -Parent) + } + $candidates = $candidates | Select-Object -Unique + + if (Test-Path $wanted) { return } + + foreach ($src in $candidates) { + if (-not (Test-Path $src)) { continue } + if (-not (Test-Path (Join-Path $src $manifestName))) { continue } + + try { + if ($src -eq $wanted) { return } + if (Test-Path $wanted) { Remove-Item -Recurse -Force $wanted } + Move-Item -Path $src -Destination $wanted -Force + return + } catch { + try { + if (Test-Path $wanted) { Remove-Item -Recurse -Force $wanted } + Copy-Item -Path $src -Destination $wanted -Recurse -Force + return + } catch { } + } + } + } + + _EnsurePSModulePath + + $pmWrong = Join-Path (Join-Path (Join-Path $env:ProgramFiles 'WindowsPowerShell\Modules') 'PowerShellGet') ("PackageManagement.{0}" -f $PackageManagementVersion.ToString()) + if (Test-Path $pmWrong) { + $pmRightBase = Join-Path (Join-Path $env:ProgramFiles 'WindowsPowerShell\Modules') 'PackageManagement' + New-Item -ItemType Directory -Path $pmRightBase -Force | Out-Null + $pmRight = Join-Path $pmRightBase $PackageManagementVersion.ToString() + if (Test-Path $pmRight) { Remove-Item -Recurse -Force $pmRight } + try { Move-Item -Path $pmWrong -Destination $pmRight -Force } catch { Copy-Item -Path $pmWrong -Destination $pmRight -Recurse -Force } + } + + _NormalizeModuleFolder -ModuleName 'PackageManagement' -Version $PackageManagementVersion + _NormalizeModuleFolder -ModuleName 'PowerShellGet' -Version $PowerShellGetVersion + + Remove-Module PackageManagement,PowerShellGet -Force -ErrorAction SilentlyContinue + Import-Module PackageManagement -RequiredVersion $PackageManagementVersion -Force -ErrorAction Stop + Import-Module PowerShellGet -RequiredVersion $PowerShellGetVersion -Force -ErrorAction Stop + + if (-not (Get-Command Get-PSRepository -ErrorAction SilentlyContinue)) { + throw "PowerShellGet loaded but Get-PSRepository still missing." + } + + return $true +} + function Install-SVSMSP { [CmdletBinding()] param ( @@ -163,23 +277,34 @@ function Install-SVSMSP { } function Start-Cleanup { - Write-LogHybrid "Cleanup mode enabled. Starting cleanup..." "Info" "SVSModule" + Write-LogHybrid "Cleanup mode enabled. Starting cleanup..." "Info" "SVSModule" - # Uninstall module using whichever tooling exists + # Self-heal PowerShellGet only on systems missing PSGet v2 cmdlets + if (-not (Get-Command Get-PSRepository -ErrorAction SilentlyContinue)) { try { - if (Get-Command Uninstall-PSResource -ErrorAction SilentlyContinue) { - Uninstall-PSResource -Name $NewModuleName -Scope AllUsers -ErrorAction SilentlyContinue - } - } catch { } + Repair-PowerShellGetStack | Out-Null + } catch { + Write-LogHybrid "PowerShellGet self-heal failed: $($_.Exception.Message). Skipping PSGet v2 repo operations." "Warning" "SVSModule" -LogToEvent + } + } - try { - Uninstall-Module -Name $NewModuleName -AllVersions -Force -ErrorAction SilentlyContinue - } catch { } + # Uninstall module using whichever tooling exists + try { + if (Get-Command Uninstall-PSResource -ErrorAction SilentlyContinue) { + Uninstall-PSResource -Name $NewModuleName -Scope AllUsers -ErrorAction SilentlyContinue + } + } catch { } - try { - Uninstall-Package -Name $NewModuleName -ProviderName NuGet -AllVersions -Force -ErrorAction SilentlyContinue | Out-Null - } catch { } + try { + Uninstall-Module -Name $NewModuleName -AllVersions -Force -ErrorAction SilentlyContinue + } catch { } + try { + Uninstall-Package -Name $NewModuleName -ProviderName NuGet -AllVersions -Force -ErrorAction SilentlyContinue | Out-Null + } catch { } + + # PSGet v2 repo cleanup (guarded) + if (Get-Command Get-PSRepository -ErrorAction SilentlyContinue) { if (Get-PSRepository -Name $NewRepositoryName -ErrorAction SilentlyContinue) { try { Unregister-PSRepository -Name $NewRepositoryName -ErrorAction Stop @@ -188,45 +313,50 @@ function Install-SVSMSP { Write-LogHybrid "Failed to unregister PSRepository ${NewRepositoryName}: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent } } + } else { + Write-LogHybrid "Skipping PSGet v2 repo cleanup: Get-PSRepository not available on this system." "Info" "SVSModule" -LogToEvent + } - if (Get-Command Get-PSResourceRepository -ErrorAction SilentlyContinue) { - if (Get-PSResourceRepository -Name $NewRepositoryName -ErrorAction SilentlyContinue) { - try { - Unregister-PSResourceRepository -Name $NewRepositoryName -ErrorAction Stop - Write-LogHybrid "$NewRepositoryName repository unregistered (PSResourceGet)." "Success" "SVSModule" -LogToEvent - } catch { - Write-LogHybrid "Failed to unregister PSResource repo ${NewRepositoryName}: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent - } - } - } - - if (Get-PackageSource -Name $NewRepositoryName -ErrorAction SilentlyContinue) { + # PSResourceGet repo cleanup (already guarded) + if (Get-Command Get-PSResourceRepository -ErrorAction SilentlyContinue) { + if (Get-PSResourceRepository -Name $NewRepositoryName -ErrorAction SilentlyContinue) { try { - Unregister-PackageSource -Name $NewRepositoryName -ErrorAction SilentlyContinue | Out-Null - Write-LogHybrid "$NewRepositoryName package source unregistered (NuGet provider)." "Success" "SVSModule" -LogToEvent - } catch { } - } - - if (Get-Module -Name $NewModuleName) { - try { - Remove-Module $NewModuleName -Force -ErrorAction Stop - Write-LogHybrid "$NewModuleName module removed from current session." "Success" "SVSModule" -LogToEvent + Unregister-PSResourceRepository -Name $NewRepositoryName -ErrorAction Stop + Write-LogHybrid "$NewRepositoryName repository unregistered (PSResourceGet)." "Success" "SVSModule" -LogToEvent } catch { - Write-LogHybrid "Failed to remove $NewModuleName from session: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent - } - } - - $cscePath = 'C:\CSCE' - if (Test-Path $cscePath) { - try { - Remove-Item -Path $cscePath -Recurse -Force - Write-LogHybrid "Deleted '$cscePath' contents." "Success" "SVSModule" -LogToEvent - } catch { - Write-LogHybrid "Failed to delete '$cscePath': $($_.Exception.Message)" "Warning" "SVSModule" -LogToEvent + Write-LogHybrid "Failed to unregister PSResource repo ${NewRepositoryName}: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent } } } + # NuGet provider source cleanup (guarded by Get-PackageSource existence) + if (Get-PackageSource -Name $NewRepositoryName -ErrorAction SilentlyContinue) { + try { + Unregister-PackageSource -Name $NewRepositoryName -ErrorAction SilentlyContinue | Out-Null + Write-LogHybrid "$NewRepositoryName package source unregistered (NuGet provider)." "Success" "SVSModule" -LogToEvent + } catch { } + } + + if (Get-Module -Name $NewModuleName) { + try { + Remove-Module $NewModuleName -Force -ErrorAction Stop + Write-LogHybrid "$NewModuleName module removed from current session." "Success" "SVSModule" -LogToEvent + } catch { + Write-LogHybrid "Failed to remove $NewModuleName from session: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent + } + } + + $cscePath = 'C:\CSCE' + if (Test-Path $cscePath) { + try { + Remove-Item -Path $cscePath -Recurse -Force + Write-LogHybrid "Deleted '$cscePath' contents." "Success" "SVSModule" -LogToEvent + } catch { + Write-LogHybrid "Failed to delete '$cscePath': $($_.Exception.Message)" "Warning" "SVSModule" -LogToEvent + } + } +} + function Remove-SVSDeploymentRegKey { $regKey = 'HKLM:\Software\SVS' try { @@ -325,159 +455,3 @@ function Install-SVSMSP { Start-ToolkitInstallation } - - - -<# Function as is before we attempted to have it attempt to use - -Prefer PSResourceGet when it exists (and your ProGet v3 index works). -Fallback to PowerShellGet v2 (Register-PSRepository / Install-Module) if PSResourceGet isn't available. -Last-resort fallback to your proven NuGet provider path (Register-PackageSource / Install-Package + optional “promote”) for the cursed endpoints where PSGet v2 explodes. - -function Install-SVSMSP { - param ( - [switch] $Cleanup, - [switch] $InstallToolkit, - [Parameter(Mandatory = $false)][array] $AllModules = @(@{ ModuleName = "SVS_Toolkit" }, @{ ModuleName = "SVSMSP" }), - [Parameter(Mandatory = $false)][array] $AllRepositories = @(@{ RepoName = "SVS_Repo" }, @{ RepoName = "SVS_Toolkit" }), - [Parameter(Mandatory = $false)][string] $NewModuleName = "SVSMSP", - [Parameter(Mandatory = $false)][string] $NewRepositoryName= "SVS_Repo", - [Parameter(Mandatory = $false)][string] $NewRepositoryURL = "https://proget.svstools.ca/nuget/SVS_Repo/" - ) - - function Start-Cleanup { - Write-LogHybrid "Cleanup mode enabled. Starting cleanup..." "Info" "SVSModule" - - try { - Uninstall-Module -Name SVSMSP -AllVersions -Force -ErrorAction Stop - Write-LogHybrid "SVSMSP module uninstalled from system." "Success" "SVSModule" -LogToEvent - } - catch { - if ($_.Exception.Message -match 'No match was found') { - Write-LogHybrid "No existing SVSMSP module found to uninstall." "Warning" "SVSModule" -LogToEvent - } - else { - Write-LogHybrid "Failed to uninstall SVSMSP: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent - } - } - - if (Get-PSRepository -Name SVS_Repo -ErrorAction SilentlyContinue) { - try { - Unregister-PSRepository -Name SVS_Repo -ErrorAction Stop - Write-LogHybrid "SVS_Repo repository unregistered." "Success" "SVSModule" -LogToEvent - } - catch { - Write-LogHybrid "Failed to unregister SVS_Repo: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent - } - } - - if (Get-Module -Name SVSMSP) { - try { - Remove-Module SVSMSP -Force -ErrorAction Stop - Write-LogHybrid "SVSMSP module removed from current session." "Success" "SVSModule" -LogToEvent - } - catch { - Write-LogHybrid "Failed to remove SVSMSP from session: $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent - } - } - - $cscePath = 'C:\CSCE' - if (Test-Path $cscePath) { - try { - Remove-Item -Path $cscePath -Recurse -Force - Write-LogHybrid "Deleted '$cscePath' contents." "Success" "SVSModule" -LogToEvent - } catch { - Write-LogHybrid "Failed to delete '$cscePath': $($_.Exception.Message)" "Warning" "SVSModule" -LogToEvent - } - } - } - - function Remove-SVSDeploymentRegKey { - $regKey = 'HKLM:\Software\SVS' - try { - if (Test-Path $regKey) { - Remove-Item -Path $regKey -Recurse -Force - Write-LogHybrid "Registry key '$regKey' deleted successfully." "Success" "SVSModule" -LogToEvent - } - else { - Write-LogHybrid "Registry key '$regKey' not found; nothing to delete." "Info" "SVSModule" -LogToEvent - } - } - catch { - Write-LogHybrid "Failed to delete registry key '$regKey': $($_.Exception.Message)" "Error" "SVSModule" -LogToEvent - } - } - - function Repair-SVSMspEventLogBinding { - param( - [string]$EventSource = "SVSMSP_Module", - [string]$TargetLog = "SVSMSP Events" - ) - - Write-LogHybrid "Checking Event Log binding for source '$EventSource'..." Info SVSModule -LogToEvent - - try { - if (-not [System.Diagnostics.EventLog]::SourceExists($EventSource)) { - Write-LogHybrid "Event source '$EventSource' not found. Nothing to repair." Info SVSModule -LogToEvent - return - } - - $currentLog = [System.Diagnostics.EventLog]::LogNameFromSourceName($EventSource, '.') - } - catch { - Write-LogHybrid "Failed to query Event Log binding for '$EventSource': $($_.Exception.Message)" Warning SVSModule -LogToEvent - return - } - - if (-not $currentLog) { - Write-LogHybrid "Could not determine current log for event source '$EventSource'. Skipping repair." Warning SVSModule -LogToEvent - return - } - - if ($currentLog -eq $TargetLog) { - Write-LogHybrid "Event source '$EventSource' already bound to '$TargetLog'." Info SVSModule -LogToEvent - return - } - - Write-LogHybrid "Rebinding event source '$EventSource' from '$currentLog' to '$TargetLog'..." Warning SVSModule -LogToEvent - - try { - [System.Diagnostics.EventLog]::DeleteEventSource($EventSource) - New-EventLog -LogName $TargetLog -Source $EventSource -ErrorAction Stop - Write-LogHybrid "Event source '$EventSource' rebound to '$TargetLog'." Success SVSModule -LogToEvent - } - catch { - Write-LogHybrid "Failed to rebind event source '$EventSource' to log '$TargetLog': $($_.Exception.Message)" Error SVSModule -LogToEvent - } - } - - function Start-ToolkitInstallation { - Initialize-NuGetProvider - Start-Cleanup - - Write-LogHybrid "Registering repo $NewRepositoryName…" "Info" "SVSModule" -LogToEvent - if (-not (Get-PSRepository -Name $NewRepositoryName -ErrorAction SilentlyContinue)) { - Register-PSRepository -Name $NewRepositoryName -SourceLocation $NewRepositoryURL -InstallationPolicy Trusted - } - - Write-LogHybrid "Installing module $NewModuleName…" "Info" "SVSModule" -LogToEvent - Install-Module -Name $NewModuleName -Repository $NewRepositoryName -Scope AllUsers -Force - - Repair-SVSMspEventLogBinding -EventSource "SVSMSP_Module" -TargetLog "SVSMSP Events" - - Write-LogHybrid "Toolkit installation completed." "Success" "SVSModule" -LogToEvent - } - - Write-LogHybrid "Install-SVSMSP called" "Info" "SVSModule" -LogToEvent - - if ($Cleanup) { - Start-Cleanup - Remove-SVSDeploymentRegKey - return - } - - if ($InstallToolkit) { Start-ToolkitInstallation; return } - - Start-ToolkitInstallation -} -#> \ No newline at end of file