src/Engine/Install.ps1
|
# Install.ps1 — gallery operations: install, update, uninstall. # Microsoft.PowerShell.PSResourceGet is the primary engine with a # PowerShellGet fallback (older machines). Read-only lookups live in State.ps1. # Which install engine is active? (surfaced in the UI; defuses the # PSResourceGet-vs-PowerShellGet confusion) function Get-PSMMInstallEngine { [CmdletBinding()] param() if (Get-Command Install-PSResource -ErrorAction SilentlyContinue) { 'PSResourceGet' } else { 'PowerShellGet' } } # Is this session elevated? Drives which scopes the UI offers (#28). function Test-PSMMElevated { [CmdletBinding()] param() if ($IsWindows) { $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() return ([System.Security.Principal.WindowsPrincipal]$id).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } [System.Environment]::UserName -eq 'root' } # Classify a module base path into an install scope. # Heuristic: anything under $HOME is CurrentUser, everything else AllUsers — # holds for the standard PSModulePath layout on Windows, Linux and macOS. function Get-PSMMScopeForPath { [CmdletBinding()] param([Parameter(Mandatory)][string]$Path) if ($Path.StartsWith($HOME, [System.StringComparison]::OrdinalIgnoreCase)) { 'CurrentUser' } else { 'AllUsers' } } # Install or update one module. Honours an optional version pin (exact or # NuGet range) and the target scope. Throws on failure — callers decide how # to report; a bulk operation must survive one module failing. function Install-PSMMModule { [CmdletBinding()] param( [Parameter(Mandatory)][string]$Name, [switch]$Update, [string]$Version, [ValidateSet('CurrentUser', 'AllUsers')][string]$Scope = 'CurrentUser' ) if (Get-Command Install-PSResource -ErrorAction SilentlyContinue) { if ($Version) { # A pin always installs the pinned version/range, update or not. Install-PSResource -Name $Name -Version $Version -Scope $Scope -TrustRepository -Reinstall:$Update -ErrorAction Stop } elseif ($Update -and (Get-Command Update-PSResource -ErrorAction SilentlyContinue) -and (Get-Module -ListAvailable -Name $Name)) { Update-PSResource -Name $Name -Scope $Scope -ErrorAction Stop } else { Install-PSResource -Name $Name -Scope $Scope -TrustRepository -ErrorAction Stop } } else { # PowerShellGet fallback: exact pins map to -RequiredVersion; NuGet # ranges are a PSResourceGet feature, so fall back to latest with a # warning rather than failing the whole operation. $exact = $Version -and $Version -match '^\d+(\.\d+){1,3}$' if ($Version -and -not $exact) { Write-Warning "psmm: version range '$Version' for '$Name' needs PSResourceGet - installing latest instead" } $params = @{ Name = $Name; Scope = $Scope; Force = $true; AllowClobber = $true; ErrorAction = 'Stop' } if ($exact) { $params.RequiredVersion = $Version } Install-Module @params } } # Remove one specific installed version (duplicate-version cleanup). function Uninstall-PSMMModuleVersion { [CmdletBinding()] param( [Parameter(Mandatory)][string]$Name, [Parameter(Mandatory)][string]$Version ) if (Get-Command Uninstall-PSResource -ErrorAction SilentlyContinue) { Uninstall-PSResource -Name $Name -Version "[$Version]" -ErrorAction Stop } else { Uninstall-Module -Name $Name -RequiredVersion $Version -Force -ErrorAction Stop } } # One entry's startup action, honouring the orthogonal Mode x Install matrix. # Mode decides load-vs-not; Install decides disk/gallery policy. function Invoke-PSMMEntryAction { [CmdletBinding()] param([Parameter(Mandatory)] $Entry) $name = $Entry.Name if ($Entry.Mode -eq 'Ignore') { return 'ignored' } $installed = [bool](Get-Module -ListAvailable -Name $name) $note = '' switch ($Entry.Install) { 'CheckOnly' { if (-not $installed) { return 'not installed (check-only)' }; $note = 'present' } 'IfMissing' { if (-not $installed) { Install-PSMMModule -Name $name -Version $Entry.Version; $note = 'installed' } else { $note = 'present' } } 'Latest' { Install-PSMMModule -Name $name -Update -Version $Entry.Version; $note = 'latest' } } if ($Entry.Mode -eq 'InstallOnly') { return "$note (not loaded)" } if (-not (Get-Module -Name $name)) { Import-PSMMModuleTimed -Entry $Entry return "$note + loaded" } return "$note + already loaded" } # Import one entry's module, honouring an exact pin and recording how long the # import took (ImportMs — surfaced in the startup report and the UI, because # "which module makes my shell slow?" is the question everyone asks). function Import-PSMMModuleTimed { [CmdletBinding()] param([Parameter(Mandatory)] $Entry) $sw = [System.Diagnostics.Stopwatch]::StartNew() try { if ($Entry.PinnedExact) { Import-Module -Name $Entry.Name -RequiredVersion $Entry.Version -ErrorAction Stop } else { Import-Module -Name $Entry.Name -ErrorAction Stop } } finally { $sw.Stop() $Entry.ImportMs = [int]$sw.Elapsed.TotalMilliseconds } } |