private/Get-WtwInstallInfo.ps1
|
function Get-WtwInstallInfo { <# .SYNOPSIS Describe how the running copy of wtw got onto this machine. .DESCRIPTION wtw can be loaded three ways, and the right way to update it differs for each one: Repo - imported straight from a source checkout. Updating means `git pull`, not touching the Gallery. Manual - copied into ~/.wtw/module by `wtw install` from a checkout. `Update-Module wtw` does not touch this copy at all, because PowerShellGet never installed it. Gallery - the PowerShell Gallery package, either materialised into ~/.wtw/module by `wtw update` or sitting under a PSModulePath entry from Install-Module. Telling a manual install to run `Update-Module wtw` is the failure this exists to prevent: the command either errors ("not installed by using Install-Module") or silently updates a *different* copy that the profile loader, the zsh/bash wrappers, and the cmd shim never import — all of which name ~/.wtw/module/wtw.psm1 by explicit path. `wtw install` records provenance in ~/.wtw/module/INSTALLATION.json; installs that predate that file are reported as Manual, which is what they are. .PARAMETER IncludeGalleryCopies Also enumerate wtw modules found on PSModulePath and work out whether one of them shadows the loaded copy for a bare `Import-Module wtw`. Skipped by default because enumerating PSModulePath is far too slow for the per-command update notice. .EXAMPLE Get-WtwInstallInfo -IncludeGalleryCopies Returns the flavour, version, and any shadowing Gallery copies. #> [CmdletBinding()] param( [string] $ModuleRoot = $script:WtwModuleRoot, [string] $InstallRoot = (Join-Path $HOME '.wtw' 'module'), [switch] $IncludeGalleryCopies ) $resolvedRoot = try { [IO.Path]::GetFullPath($ModuleRoot) } catch { $ModuleRoot } $resolvedInstall = try { [IO.Path]::GetFullPath($InstallRoot) } catch { $InstallRoot } $version = $null $manifestPath = Join-Path $resolvedRoot 'wtw.psd1' if (Test-Path -LiteralPath $manifestPath -PathType Leaf) { try { $parsed = $null $text = [string](Import-PowerShellDataFile -LiteralPath $manifestPath).ModuleVersion if ([version]::TryParse($text, [ref] $parsed)) { $version = $parsed } } catch { # A manifest we cannot read is reported as an unknown version rather # than taking wtw down. } } $record = Read-WtwJsonFile -Path (Join-Path $resolvedRoot 'INSTALLATION.json') $isInstallRoot = $resolvedRoot -eq $resolvedInstall $flavour = if ($isInstallRoot) { $origin = [string](Get-WtwPropertyValue -Object $record -Name 'Origin' -DefaultValue '') if ($origin -in @('Gallery', 'Manual')) { $origin } else { 'Manual' } } elseif (Test-WtwPathUnderModulePath -Path $resolvedRoot) { 'Gallery' } else { 'Repo' } $galleryCopies = @() $shadowedBy = $null if ($IncludeGalleryCopies) { try { $galleryCopies = @( Get-Module -ListAvailable -Name 'wtw' -ErrorAction SilentlyContinue | Where-Object { [IO.Path]::GetFullPath($_.ModuleBase) -ne $resolvedRoot } | Sort-Object Version -Descending | ForEach-Object { [pscustomobject]@{ Version = $_.Version Path = $_.ModuleBase } } ) } catch { $galleryCopies = @() } # A bare `Import-Module wtw` resolves through PSModulePath, so any copy # there wins over ~/.wtw/module no matter which one the shell wrappers # load. Worth saying out loud when the versions disagree. $shadowedBy = @($galleryCopies | Where-Object { $null -eq $version -or $_.Version -ne $version }) | Select-Object -First 1 } [pscustomobject]@{ Flavour = $flavour ModuleRoot = $resolvedRoot InstallRoot = $resolvedInstall Version = $version SourcePath = [string](Get-WtwPropertyValue -Object $record -Name 'SourcePath' -DefaultValue '') SourceCommit = [string](Get-WtwPropertyValue -Object $record -Name 'SourceCommit' -DefaultValue '') InstalledAtUtc = ConvertTo-WtwUtcDate -Value (Get-WtwPropertyValue -Object $record -Name 'InstalledAtUtc') GalleryCopies = $galleryCopies ShadowedBy = $shadowedBy UpdateCommand = switch ($flavour) { 'Repo' { 'git pull && wtw install' } default { 'wtw update' } } } } function Test-WtwPathUnderModulePath { <# .SYNOPSIS True when Path sits inside one of the PSModulePath roots. .DESCRIPTION Used to tell an Install-Module copy apart from a checkout. Compared as normalised path prefixes so a trailing separator or a relative entry in PSModulePath does not produce a false negative. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string] $Path ) $target = try { [IO.Path]::GetFullPath($Path) } catch { return $false } $separator = [IO.Path]::DirectorySeparatorChar foreach ($entry in @([string]$env:PSModulePath -split [IO.Path]::PathSeparator)) { if ([string]::IsNullOrWhiteSpace($entry)) { continue } $root = try { [IO.Path]::GetFullPath($entry) } catch { continue } $prefix = $root.TrimEnd($separator, [IO.Path]::AltDirectorySeparatorChar) + $separator if ($target.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) { return $true } } return $false } |