Commands/Module/Eigenverft.Manifested.Package.Cmd.Module.ps1

<#
    Root entry helpers: Get-PackageVersion, Get-PackageDefinitionAuthoringGuide, and Update-PackageVersion.
    Imported by Eigenverft.Manifested.Package.psm1.
#>


function Get-PackageDefinitionAuthoringGuide {
<#
.SYNOPSIS
Prints package-definition authoring guidance and authoring target endpoint status.
 
.DESCRIPTION
Loads the module-local PackageDefinitionAuthoring.md skill, evaluates endpoints marked with
authoringTarget in PackageEndpointInventory.json, probes writable filesystem-backed targets,
selects the best usable target by search order preference, and appends the full authoring guide.
 
When no usable authoring target exists, the output includes troubleshooting text for the agent
to explain endpoint configuration to the user. The command does not throw for ordinary blocked
or unmarked target states.
 
.PARAMETER For
Optional package definition id inserted into a short task preface.
 
.PARAMETER EndpointName
Restrict evaluation to one named endpoint. The endpoint must still be marked authoringTarget
and pass the write probe to be selected.
 
.PARAMETER EndpointPreference
When multiple usable targets exist, First selects the lowest searchOrder and Last the highest.
 
.PARAMETER DraftOnly
Prepends draft-only mode instructions: keep the definition unsigned, skip signing and trusted verification, and use catalog validation only where helpful.
 
.EXAMPLE
Get-PackageDefinitionAuthoringGuide -For 'TotalCommander'
 
Shows task-specific preface, endpoint target guidance, and the full authoring guide.
 
.EXAMPLE
Get-PackageDefinitionAuthoringGuide -For 'TotalCommander' -DraftOnly
 
Same as above with an explicit draft-only mode block (unsigned JSON, no sign or RequireTrusted steps).
 
.EXAMPLE
Get-PackageDefinitionAuthoringGuide -EndpointName moduleDefaults -EndpointPreference First
 
Evaluates only moduleDefaults when it is marked as an authoring target.
#>

    [CmdletBinding()]
    param(
        [string]$For,

        [string]$EndpointName,

        [ValidateSet('First', 'Last')]
        [string]$EndpointPreference = 'First',

        [switch]$DraftOnly
    )

    $moduleProjectRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
    $skillPath = Join-Path $moduleProjectRoot 'AgentSkills\PackageDefinitionAuthoring.md'
    if (-not (Test-Path -LiteralPath $skillPath -PathType Leaf)) {
        throw "Package definition authoring guide was not found at '$skillPath'."
    }

    $skillText = Get-Content -LiteralPath $skillPath -Raw
    $evaluation = Get-PackageAuthoringTargetEvaluation -EndpointName $EndpointName -EndpointPreference $EndpointPreference

    foreach ($warning in @($evaluation.Warnings)) {
        if (-not [string]::IsNullOrWhiteSpace($warning)) {
            Write-Warning $warning
        }
    }

    $lines = New-Object System.Collections.Generic.List[string]

    if (-not [string]::IsNullOrWhiteSpace($For)) {
        $lines.Add("Task: create or update package definition '$For'.") | Out-Null
        $lines.Add('') | Out-Null
    }

    if ($DraftOnly) {
        $lines.Add('## Authoring mode') | Out-Null
        $lines.Add('') | Out-Null
        $lines.Add('Mode: **draft-only** (unsigned).') | Out-Null
        $lines.Add('') | Out-Null
        $lines.Add('AgentAction: You are in draft-only mode. Author or update JSON under **Selection** when available. Keep `definitionPublication.definitionSignature.kind` as `unsigned`. Do not run `Sign-PackageDefinition`, `Resign-PackageDefinition`, or trusted verification (`-RequireTrusted`, `Verify-PackageDefinitionSignature -RequireTrusted`, and similar) for this task unless the maintainer explicitly ends draft-only.') | Out-Null
        $lines.Add('') | Out-Null
        $lines.Add('Validation: `Test-PackageDefinitionCatalog` on the draft file is allowed and encouraged when it helps catch schema issues.') | Out-Null
        $lines.Add('') | Out-Null
    }

    $lines.Add('## Runtime endpoint status') | Out-Null
    $lines.Add('') | Out-Null

    if ($evaluation.SelectedTarget) {
        if ($DraftOnly) {
            $lines.Add('AgentAction: **Draft-only mode.** Proceed with **Start Here** step 3 onward; write under Selection path. Do not sign or require trusted verification.') | Out-Null
        }
        else {
            $lines.Add('AgentAction: Proceed with **Start Here** step 3 onward; write under Selection path.') | Out-Null
        }
    }
    elseif ([string]::Equals($evaluation.TroubleshootingKind, 'NoMarkedTarget', [System.StringComparison]::Ordinal)) {
        $lines.Add('AgentAction: Stop JSON edits. Read **Start Here** step 1 and **Troubleshooting for agents** (NoMarkedTarget).') | Out-Null
    }
    elseif ([string]::Equals($evaluation.TroubleshootingKind, 'AllMarkedBlocked', [System.StringComparison]::Ordinal)) {
        $lines.Add('AgentAction: Stop JSON edits. Read **Start Here** step 1 and **Troubleshooting for agents** (AllMarkedBlocked).') | Out-Null
    }
    else {
        $lines.Add('AgentAction: Read **Start Here** in the guide below before editing JSON.') | Out-Null
    }
    $lines.Add('') | Out-Null

    $lines.Add("InventoryPath: $($evaluation.InventoryPath)") | Out-Null
    if (-not [string]::Equals($evaluation.TroubleshootingKind, 'None', [System.StringComparison]::Ordinal)) {
        $lines.Add("TroubleshootingKind: $($evaluation.TroubleshootingKind)") | Out-Null
    }

    if ($evaluation.Candidates.Count -eq 0) {
        $lines.Add('MarkedCandidates: (none)') | Out-Null
    }
    else {
        $lines.Add('MarkedCandidates:') | Out-Null
        foreach ($candidate in @($evaluation.Candidates)) {
            $pathText = if ([string]::IsNullOrWhiteSpace([string]$candidate.ResolvedRootPath)) { '(unresolved)' } else { [string]$candidate.ResolvedRootPath }
            $skipText = if ([string]::IsNullOrWhiteSpace([string]$candidate.SkipReason)) { '' } else { " skipReason=$($candidate.SkipReason)" }
            $lines.Add("- $($candidate.EndpointName) | $($candidate.Kind) | order=$($candidate.SearchOrder) | enabled=$($candidate.Enabled) | effective=$($candidate.Effective) | $($candidate.Status) | $pathText$skipText") | Out-Null
        }
    }

    if ($evaluation.SelectedTarget) {
        $selected = $evaluation.SelectedTarget
        $lines.Add("Selection: $($selected.EndpointName) | $($selected.Status) | $($selected.ResolvedRootPath)") | Out-Null
    }
    else {
        $lines.Add('Selection: (none)') | Out-Null
    }

    if ($evaluation.Warnings.Count -gt 0) {
        $lines.Add('') | Out-Null
        $lines.Add('Warnings:') | Out-Null
        foreach ($warning in @($evaluation.Warnings)) {
            if (-not [string]::IsNullOrWhiteSpace($warning)) {
                $lines.Add("- $warning") | Out-Null
            }
        }
    }

    $lines.Add('') | Out-Null
    $lines.Add('See **Start Here** and **Authoring Targets And Endpoints** in the guide below.') | Out-Null
    $lines.Add('') | Out-Null
    $lines.Add('---') | Out-Null
    $lines.Add('') | Out-Null
    $lines.Add($skillText.TrimEnd()) | Out-Null

    return ($lines -join [Environment]::NewLine)
}

function Get-PackageVersion {
<#
.SYNOPSIS
Shows the resolved module version, shipped package-definition examples, and other exported commands.
 
.DESCRIPTION
Resolves the highest available or loaded Eigenverft.Manifested.Package module version, lists
example Invoke-Package lines for each shipped definition JSON discovered under the packaged
endpoint defaults tree (when package bootstrap commands are available), then lists remaining exported
commands in alphabetical order.
 
.EXAMPLE
Get-PackageVersion
 
Displays module information, per-definition Invoke-Package examples, and other exported commands.
#>

    [CmdletBinding()]
    param()

    $moduleName = 'Eigenverft.Manifested.Package'
    $moduleInfo = @(Get-Module -ListAvailable -Name $moduleName | Sort-Object -Descending -Property Version | Select-Object -First 1)
    $loadedModule = @(Get-Module -Name $moduleName | Sort-Object -Descending -Property Version | Select-Object -First 1)

    if (-not $moduleInfo) {
        if ($loadedModule) {
            $moduleInfo = $loadedModule
        }
        elseif ($ExecutionContext.SessionState.Module -and $ExecutionContext.SessionState.Module.Name -eq $moduleName) {
            $moduleInfo = @($ExecutionContext.SessionState.Module)
        }
    }

    if (-not $moduleInfo) {
        throw "Could not resolve the installed or loaded version of module '$moduleName'."
    }

    $commandSourceModule = $loadedModule | Select-Object -First 1
    if (-not $commandSourceModule -and $ExecutionContext.SessionState.Module -and $ExecutionContext.SessionState.Module.Name -eq $moduleName) {
        $commandSourceModule = $ExecutionContext.SessionState.Module
    }

    $exportedCommandNames = @()
    if ($commandSourceModule -and $commandSourceModule.ExportedCommands) {
        $exportedCommandNames = @(
            $commandSourceModule.ExportedCommands.Keys |
                Sort-Object
        )
    }

    if (-not $exportedCommandNames) {
        $exportedCommandNames = @(
            Get-Command -Module $moduleName -ErrorAction SilentlyContinue |
                Select-Object -ExpandProperty Name -Unique |
                Sort-Object
        )
    }

    $definitionIds = @()
    $defaultDefinitionPublisherId = 'Eigenverft'
    if (Get-Command Get-PackageDefaultPublisherId -ErrorAction SilentlyContinue) {
        try {
            $defaultDefinitionPublisherId = [string](Get-PackageDefaultPublisherId)
        }
        catch {
        }
    }

    if (Get-Command Get-PackageShippedEndpointRoot -ErrorAction SilentlyContinue) {
        try {
            $endpointRoot = Get-PackageShippedEndpointRoot
            $definitionRoot = Join-Path $endpointRoot 'Defaults'
            if (Test-Path -LiteralPath $definitionRoot -PathType Container) {
                foreach ($jsonFile in Get-ChildItem -LiteralPath $definitionRoot -Filter *.json -File -Recurse) {
                    try {
                        $doc = Get-Content -LiteralPath $jsonFile.FullName -Raw | ConvertFrom-Json
                        $sv = if ($doc.PSObject.Properties['schemaVersion']) { [string]$doc.schemaVersion } else { '' }
                        $id = if ($doc.PSObject.Properties['definitionPublication'] -and $doc.definitionPublication.PSObject.Properties['definitionId']) { [string]$doc.definitionPublication.definitionId } elseif ($doc.PSObject.Properties['id']) { [string]$doc.id } else { '' }
                        if (-not [string]::IsNullOrWhiteSpace($sv) -and -not [string]::IsNullOrWhiteSpace($id) -and $doc.PSObject.Properties['packageOperations']) {
                            $definitionIds += $id
                        }
                    }
                    catch {
                    }
                }
            }
        }
        catch {
        }
    }

    $definitionIds = @($definitionIds | Sort-Object -Unique)

    $outputLines = @(
        'Module: {0}' -f $moduleName
        'Version: {0}' -f $moduleInfo[0].Version.ToString()
    )

    if ($definitionIds.Count -gt 0) {
        $outputLines += @(
            ('Shipped package definitions (signed publisherId ''{0}''; optional ''Invoke-Package -PublisherId'' pins a definition publisher label; endpoints live in PackageEndpointInventory.json):' -f $defaultDefinitionPublisherId)
            ($definitionIds | ForEach-Object { "- Invoke-Package -DefinitionId '{0}'" -f $_ })
            'Use -DesiredState Removed to uninstall a package-owned install when the definition supports it.'
        )
        $bulkIds = @($definitionIds | Where-Object { $_ -ne 'VSCodeUser' })
        if ($bulkIds.Count -gt 0) {
            $outputLines += 'Assign many at once (comma-separated; VSCodeUser omitted here - use VSCodeRuntime for the portable layout or invoke VSCodeUser separately):'
            $outputLines += ("- Invoke-Package -DefinitionId {0}" -f ($bulkIds -join ','))
        }
        $outputLines += ''
        $outputLines += 'Team setup example:'
        $outputLines += "- Add-TeamPackageDepot -BasePath '\\team-share\PackageDepot'"
        $outputLines += "- Add-TeamPackageEndpoint -BasePath '\\team-share\PackageEndpoint'"
        $outputLines += "- Invoke-Package -DefinitionId 'OtherTextEditorFromTeamRepos'"
        $outputLines += "Valid unknown embedded signing certificates prompt for trust; admins can preseed trust with: Import-PackageTrust -Path '<public-signing-cert.cer>'"
        $outputLines += "Maintainers can create a local signing certificate with: New-PackageSigningCertificate -Name 'My Team' -PublisherId 'My Team' -CommonName 'My Team Package Catalog Signing' -Password <securestring>"
        $outputLines += "Then sign definitions with: Sign-PackageDefinition -Path '\\team-share\PackageEndpoint\MyPackage.json' -Cert 'MyTeam'"
        $outputLines += "Team package JSON files should be signed and set definitionPublication.publisherId to the signing-key publisher."
        $outputLines += ''
    }
    else {
        $outputLines += @(
            'Shipped package definitions: (none discovered; import the full module to scan Endpoint/Defaults.)'
            ''
        )
    }

    $outputLines += 'Other exported commands:'
    if ($exportedCommandNames) {
        $outputLines += @(
            $exportedCommandNames | ForEach-Object { '- {0}' -f $_ }
        )
    }
    else {
        $outputLines += '- None found'
    }

    return ($outputLines -join [Environment]::NewLine)
}

# Embedded from the sibling Eigenverft.Manifested.Drydock version helper so module
# self-update reporting has no runtime dependency on Drydock.
function Convert-64SecVersionComponentsToDateTime {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [int]$VersionBuild,

        [Parameter(Mandatory = $true)]
        [int]$VersionMajor,

        [Parameter(Mandatory = $true)]
        [int]$VersionMinor,

        [Parameter(Mandatory = $true)]
        [int]$VersionRevision
    )

    if ($VersionMinor -lt 0) {
        throw 'VersionMinor must be >= 0.'
    }

    $year = [int]($VersionMinor / 10)
    $high = $VersionMinor - ($year * 10)
    if ($high -lt 0) {
        $year -= 1
        $high += 10
    }
    elseif ($high -gt 9) {
        $year += 1
        $high -= 10
    }

    if ($year -lt 1 -or $year -gt 9999) {
        throw "Decoded year $year out of 1..9999."
    }
    if ($high -lt 0 -or $high -gt 7) {
        throw "HighPart $high out of range 0..7 not an encoded 64s version."
    }

    $low = $VersionRevision -band 0xFFFF
    if ($VersionRevision -ne $low) {
        throw "VersionRevision $VersionRevision exceeds 16 bits."
    }

    $shifted = ($high -shl 16) -bor $low
    $secondsInYear = if ([datetime]::IsLeapYear($year)) { 31622400 } else { 31536000 }
    $maxShifted = [int][math]::Floor(($secondsInYear - 1) / 64)
    if ($shifted -gt $maxShifted) {
        throw "ShiftedSeconds $shifted exceeds max $maxShifted for year $year components invalid."
    }

    $startOfYearUtc = New-Object datetime ($year, 1, 1, 0, 0, 0, [datetimekind]::Utc)
    return @{
        VersionBuild     = $VersionBuild
        VersionMajor     = $VersionMajor
        ComputedDateTime = $startOfYearUtc.AddSeconds($shifted * 64)
    }
}

function Convert-64SecPowershellVersionToDateTime {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [int]$VersionBuild,

        [Parameter(Mandatory = $true)]
        [int]$VersionMajor,

        [Parameter(Mandatory = $true)]
        [int]$VersionMinor
    )

    $result = Convert-64SecVersionComponentsToDateTime `
        -VersionBuild $VersionBuild `
        -VersionMajor 0 `
        -VersionMinor $VersionMajor `
        -VersionRevision $VersionMinor

    return @{
        VersionFull      = "$VersionBuild.$VersionMajor.$VersionMinor"
        VersionBuild     = $VersionBuild
        ComputedDateTime = $result.ComputedDateTime
    }
}

function Select-PackageCommandParameters {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [System.Management.Automation.CommandInfo]$Command,

        [Parameter(Mandatory = $true)]
        [hashtable]$CandidateParameters,

        [string[]]$RequiredParameters = @()
    )

    foreach ($requiredParameter in @($RequiredParameters)) {
        if (-not $Command.Parameters.ContainsKey($requiredParameter)) {
            throw "Command '$($Command.Name)' does not support required parameter '-$requiredParameter'. Install a supported PowerShellGet version and retry."
        }
    }

    $selectedParameters = @{}
    foreach ($entry in $CandidateParameters.GetEnumerator()) {
        if ($Command.Parameters.ContainsKey([string]$entry.Key)) {
            $selectedParameters[[string]$entry.Key] = $entry.Value
        }
        else {
            Write-Verbose ("Command '{0}' does not support optional parameter '-{1}'; omitting it." -f $Command.Name, $entry.Key)
        }
    }

    return $selectedParameters
}

function Get-PackageModuleVersionState {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ModuleName,

        [System.Management.Automation.PSModuleInfo]$ExecutingModule
    )

    $loadedModules = @(Get-Module -Name $ModuleName -ErrorAction SilentlyContinue)
    $installedModules = @(Get-Module -ListAvailable -Name $ModuleName -ErrorAction SilentlyContinue)
    $versions = New-Object System.Collections.Generic.List[version]

    if ($null -ne $ExecutingModule -and $null -ne $ExecutingModule.Version) {
        $versions.Add([version]$ExecutingModule.Version) | Out-Null
    }

    foreach ($module in @($loadedModules + $installedModules)) {
        if ($null -eq $module -or $null -eq $module.Version) {
            continue
        }

        try {
            $versions.Add([version]([string]$module.Version)) | Out-Null
        }
        catch {
            Write-Verbose ("Ignoring unreadable module version '{0}' from '{1}'." -f $module.Version, $module.Path)
        }
    }

    $highestVersion = @($versions | Sort-Object -Descending | Select-Object -First 1)

    return [pscustomobject]@{
        ExecutingModule        = $ExecutingModule
        ExecutingVersion       = if ($null -ne $ExecutingModule) { [version]$ExecutingModule.Version } else { $null }
        LoadedModules          = @($loadedModules)
        InstalledModules       = @($installedModules)
        HighestRelevantVersion = if ($highestVersion.Count -gt 0) { [version]$highestVersion[0] } else { $null }
    }
}

function Test-PackageModuleInstallationScope {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object]$Module,

        [Parameter(Mandatory = $true)]
        [ValidateSet('CurrentUser', 'AllUsers')]
        [string]$Scope
    )

    $modulePath = [string]$Module.Path
    if ([string]::IsNullOrWhiteSpace($modulePath)) {
        return [pscustomobject]@{ Known = $false; Matches = $false; ScopeRoot = $null }
    }

    $packageScopeIsWindows = [System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT
    $scopeRoot = $null
    if ($packageScopeIsWindows) {
        $shellModuleDirectory = if ($PSVersionTable.PSEdition -eq 'Desktop') {
            'WindowsPowerShell\Modules'
        }
        else {
            'PowerShell\Modules'
        }

        if ($Scope -eq 'CurrentUser') {
            $documentsPath = [System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::MyDocuments)
            if (-not [string]::IsNullOrWhiteSpace($documentsPath)) {
                $scopeRoot = Join-Path -Path $documentsPath -ChildPath $shellModuleDirectory
            }
        }
        elseif (-not [string]::IsNullOrWhiteSpace($env:ProgramFiles)) {
            $scopeRoot = Join-Path -Path $env:ProgramFiles -ChildPath $shellModuleDirectory
        }
    }
    elseif ($Scope -eq 'CurrentUser' -and -not [string]::IsNullOrWhiteSpace($HOME)) {
        $scopeRoot = Join-Path -Path $HOME -ChildPath '.local/share/powershell/Modules'
    }
    elseif ($Scope -eq 'AllUsers') {
        $scopeRoot = '/usr/local/share/powershell/Modules'
    }

    if ([string]::IsNullOrWhiteSpace($scopeRoot)) {
        return [pscustomobject]@{ Known = $false; Matches = $false; ScopeRoot = $null }
    }

    $fullModulePath = [System.IO.Path]::GetFullPath($modulePath)
    $fullScopeRoot = [System.IO.Path]::GetFullPath($scopeRoot).TrimEnd(
        [System.IO.Path]::DirectorySeparatorChar,
        [System.IO.Path]::AltDirectorySeparatorChar
    )
    $pathComparison = if ($packageScopeIsWindows) {
        [System.StringComparison]::OrdinalIgnoreCase
    }
    else {
        [System.StringComparison]::Ordinal
    }
    $scopePrefix = $fullScopeRoot + [System.IO.Path]::DirectorySeparatorChar

    return [pscustomobject]@{
        Known = $true
        Matches = $fullModulePath.StartsWith($scopePrefix, $pathComparison)
        ScopeRoot = $fullScopeRoot
    }
}

function Enable-PackageUpdatedModuleVersion {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ModuleName,

        [Parameter(Mandatory = $true)]
        [version]$Version,

        [Parameter(Mandatory = $true)]
        [object]$InstalledModule
    )

    if ([string]::IsNullOrWhiteSpace([string]$InstalledModule.Path) -or
        -not (Test-Path -LiteralPath $InstalledModule.Path -PathType Leaf)) {
        return [pscustomobject]@{
            Active = $false
            Reason = 'The exact installed module manifest could not be resolved.'
            CommandVersion = $null
        }
    }

    try {
        $installedModuleBase = if (-not [string]::IsNullOrWhiteSpace([string]$InstalledModule.ModuleBase)) {
            [System.IO.Path]::GetFullPath([string]$InstalledModule.ModuleBase)
        }
        else {
            [System.IO.Path]::GetFullPath((Split-Path -Parent $InstalledModule.Path))
        }
        $loadedBefore = @(Get-Module -Name $ModuleName -ErrorAction SilentlyContinue)
        if ($loadedBefore.Count -gt 1) {
            $currentCommand = Get-Command -Name 'Update-PackageVersion' -ErrorAction SilentlyContinue
            return [pscustomobject]@{
                Active = $false
                Reason = 'Multiple module instances were already loaded, so same-session replacement was not attempted.'
                CommandVersion = if ($currentCommand -and $currentCommand.Module) { [version]$currentCommand.Module.Version } else { $null }
            }
        }

        Import-Module -Name $InstalledModule.Path -Force -Global -DisableNameChecking -ErrorAction Stop | Out-Null

        $activeCommand = Get-Command -Name 'Update-PackageVersion' -ErrorAction Stop
        $activeModule = $activeCommand.Module
        $activeVersionMatches =
            $null -ne $activeModule -and
            [string]::Equals($activeModule.Name, $ModuleName, [System.StringComparison]::OrdinalIgnoreCase) -and
            $null -ne $activeModule.Version -and
            ([version]$activeModule.Version -eq $Version)
        $activeModuleBaseMatches =
            $activeVersionMatches -and
            -not [string]::IsNullOrWhiteSpace([string]$activeModule.ModuleBase) -and
            [string]::Equals(
                [System.IO.Path]::GetFullPath($activeModule.ModuleBase),
                $installedModuleBase,
                [System.StringComparison]::OrdinalIgnoreCase
            )
        $loadedAfter = @(Get-Module -Name $ModuleName -ErrorAction SilentlyContinue)
        $matchingLoadedModules = @(
            $loadedAfter | Where-Object {
                $null -ne $_.Version -and
                [version]$_.Version -eq $Version -and
                -not [string]::IsNullOrWhiteSpace([string]$_.ModuleBase) -and
                [string]::Equals(
                    [System.IO.Path]::GetFullPath($_.ModuleBase),
                    $installedModuleBase,
                    [System.StringComparison]::OrdinalIgnoreCase
                )
            }
        )
        $loadedStateMatches = $loadedAfter.Count -eq 1 -and $matchingLoadedModules.Count -eq 1

        if ($activeModuleBaseMatches -and $loadedStateMatches) {
            return [pscustomobject]@{
                Active = $true
                Reason = $null
                CommandVersion = $Version
            }
        }

        return [pscustomobject]@{
            Active = $false
            Reason = if (-not $activeModuleBaseMatches) {
                'The active Update-PackageVersion command did not resolve to the newly installed module.'
            }
            else {
                'The new command resolved correctly, but duplicate or mismatched loaded module state remained.'
            }
            CommandVersion = if ($null -ne $activeModule -and $null -ne $activeModule.Version) { [version]$activeModule.Version } else { $null }
        }
    }
    catch {
        Write-Verbose ("The new module was installed but could not be activated safely in this session: {0}" -f $_.Exception.Message)
        return [pscustomobject]@{
            Active = $false
            Reason = $_.Exception.Message
            CommandVersion = $null
        }
    }
}

function Update-PackageVersion {
<#
.SYNOPSIS
Update Eigenverft.Manifested.Package from the PowerShell Gallery when a newer stable version exists.
 
.DESCRIPTION
Queries PSGallery for the latest stable version, compares it with the highest version currently
running, loaded, or installed, and installs only when PSGallery is newer. The command requests the
exact discovered version when the installed PowerShellGet supports RequiredVersion and verifies that
the version is visible after installation.
 
On Windows, the internal proxy bootstrap first probes the PSGallery API and prepares session proxy
settings. Find-Module and Install-Module then run as separate PowerShellGet operations. Their
parameters are filtered independently against the commands available in the current session so old
PowerShellGet versions do not receive unsupported optional parameters such as AllowClobber.
 
After installation, the command attempts to activate the exact installed module globally and verifies
that Update-PackageVersion resolves to it. If that cannot be proven, the installed update is retained
and the command tells the user to open a new PowerShell session.
 
.PARAMETER Scope
CurrentUser (default) or AllUsers (elevation normally required). Scope controls where a newer version
is installed; version comparison still considers every visible copy so the command never downgrades or
needlessly reinstalls the module into another scope.
 
.EXAMPLE
Update-PackageVersion -Scope CurrentUser
 
Checks PSGallery and updates the current-user installation only when a newer stable version exists.
 
.EXAMPLE
Update-PackageVersion -Scope AllUsers -WhatIf
 
Performs proxy preparation, gallery discovery, and version comparison, then previews the installation
without changing the installed module.
#>

    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [ValidateSet('CurrentUser', 'AllUsers')]
        [string]$Scope = 'CurrentUser'
    )

    $moduleName = 'Eigenverft.Manifested.Package'
    $repository = 'PSGallery'
    $galleryApiUri = [uri]'https://www.powershellgallery.com/api/v2/'
    $proxyModuleParameters = @{}
    $packageIsWindows = [System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT
    $executingModule = $MyInvocation.MyCommand.Module
    $beforeState = Get-PackageModuleVersionState -ModuleName $moduleName -ExecutingModule $executingModule
    $executingVersion = $beforeState.ExecutingVersion
    $relevantVersion = $beforeState.HighestRelevantVersion

    if ($null -eq $relevantVersion) {
        throw "The version of the currently executing '$moduleName' module could not be determined."
    }

    Write-Verbose ("Executing version/path: {0} / {1}; loaded versions/paths: {2}; installed versions/paths: {3}." -f
        $executingVersion,
        $executingModule.Path,
        (@($beforeState.LoadedModules | ForEach-Object { '{0} [{1}]' -f $_.Version, $_.Path }) -join '; '),
        (@($beforeState.InstalledModules | ForEach-Object { '{0} [{1}]' -f $_.Version, $_.Path }) -join '; '))

    if ($packageIsWindows) {
        # Proxy preparation is connectivity setup, not the requested module installation.
        # Prevent an outer -WhatIf from leaking into the helper's internal Set-Variable calls.
        $callerWhatIfPreference = $WhatIfPreference
        try {
            $WhatIfPreference = $false

            # Manual proxy UI and non-interactive failure are handled by the private proxy bootstrap.
            Initialize-ProxyAccessProfile -TestUri $galleryApiUri -SuppressStatus

            if ($null -ne $Global:ProxyParamsPrepareSession) {
                $null = $Global:ProxyParamsPrepareSession.Invoke()
            }
            $installGv = Get-Variable -Scope Global -Name ProxyParamsInstallModule -ErrorAction SilentlyContinue
            if ($installGv -and $installGv.Value -is [hashtable] -and $installGv.Value.Count -gt 0) {
                foreach ($entry in $installGv.Value.GetEnumerator()) {
                    $proxyModuleParameters[[string]$entry.Key] = $entry.Value
                }
            }
        }
        finally {
            $WhatIfPreference = $callerWhatIfPreference
        }
    }
    else {
        try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch { }
        try {
            $wp = [System.Net.WebRequest]::GetSystemWebProxy()
            [System.Net.WebRequest]::DefaultWebProxy = $wp
            if ($wp) { $wp.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials }
        } catch { }
    }

    $findCommand = Get-Command -Name 'Find-Module' -ErrorAction Stop
    $findCandidateParameters = @{}
    foreach ($entry in $proxyModuleParameters.GetEnumerator()) {
        $findCandidateParameters[[string]$entry.Key] = $entry.Value
    }
    $findCandidateParameters['Name'] = $moduleName
    $findCandidateParameters['Repository'] = $repository
    $findCandidateParameters['ErrorAction'] = 'Stop'
    $findParameters = Select-PackageCommandParameters `
        -Command $findCommand `
        -CandidateParameters $findCandidateParameters `
        -RequiredParameters @('Name', 'Repository')

    Write-Verbose ("Querying repository '{0}' with Find-Module {1} from '{2}'; reachability is handled separately by the proxy bootstrap." -f
        $repository,
        $findCommand.Version,
        $findCommand.Source)
    Write-Verbose ("Supported Find-Module parameters: {0}." -f (@($findCommand.Parameters.Keys | Sort-Object) -join ', '))
    Write-Verbose ("Effective Find-Module parameter names: {0}; proxy parameter names: {1}." -f
        (@($findParameters.Keys | Sort-Object) -join ', '),
        (@($proxyModuleParameters.Keys | Sort-Object) -join ', '))
    $activePackageManagementModules = @(Get-Module -Name 'PackageManagement' -ErrorAction SilentlyContinue)
    if ($activePackageManagementModules.Count -gt 0) {
        Write-Verbose ("Loaded PackageManagement versions/paths: {0}." -f
            (@($activePackageManagementModules | ForEach-Object { '{0} [{1}]' -f $_.Version, $_.Path }) -join '; '))
    }
    $repositoryModules = @(& $findCommand @findParameters)
    $repositoryVersions = @(
        foreach ($repositoryModule in $repositoryModules) {
            if ($null -eq $repositoryModule -or
                -not [string]::Equals([string]$repositoryModule.Name, $moduleName, [System.StringComparison]::OrdinalIgnoreCase)) {
                continue
            }

            try {
                [pscustomobject]@{
                    Module  = $repositoryModule
                    Version = [version]([string]$repositoryModule.Version)
                }
            }
            catch {
                throw "PSGallery returned an invalid version '$($repositoryModule.Version)' for '$moduleName'."
            }
        }
    )

    $latestRepositoryModule = @($repositoryVersions | Sort-Object -Property Version -Descending | Select-Object -First 1)
    if ($latestRepositoryModule.Count -eq 0) {
        throw "PSGallery did not return a stable version for '$moduleName'."
    }

    $latestRepositoryVersion = [version]$latestRepositoryModule[0].Version
    try {
        $dateResult = Convert-64SecPowershellVersionToDateTime `
            -VersionBuild $latestRepositoryVersion.Major `
            -VersionMajor $latestRepositoryVersion.Minor `
            -VersionMinor $latestRepositoryVersion.Build
        Write-Verbose ("Decoded repository build date: {0:o} ({1})." -f
            $dateResult.ComputedDateTime,
            $dateResult.ComputedDateTime.Kind)
    }
    catch {
        Write-Verbose ("The optional repository build-date conversion was skipped: {0}" -f $_.Exception.Message)
    }
    Write-Verbose ("Highest relevant local version: {0}; latest stable PSGallery version: {1}." -f
        $relevantVersion, $latestRepositoryVersion)

    if ($latestRepositoryVersion -le $relevantVersion) {
        return "No newer version was found. Installed version: $relevantVersion."
    }

    if (-not $PSCmdlet.ShouldProcess($moduleName, "Install version $latestRepositoryVersion for scope $Scope from $repository")) {
        return "A newer version was found. Installed version: $relevantVersion. Available version: $latestRepositoryVersion. No installation was performed."
    }

    $installCommand = Get-Command -Name 'Install-Module' -ErrorAction Stop
    $installCandidateParameters = @{}
    foreach ($entry in $proxyModuleParameters.GetEnumerator()) {
        $installCandidateParameters[[string]$entry.Key] = $entry.Value
    }
    $installCandidateParameters['Name'] = $moduleName
    $installCandidateParameters['Repository'] = $repository
    $installCandidateParameters['Scope'] = $Scope
    $installCandidateParameters['RequiredVersion'] = $latestRepositoryVersion
    $installCandidateParameters['Force'] = $true
    $installCandidateParameters['AllowClobber'] = $true
    $installCandidateParameters['PassThru'] = $true
    $installCandidateParameters['ErrorAction'] = 'Stop'
    $installParameters = Select-PackageCommandParameters `
        -Command $installCommand `
        -CandidateParameters $installCandidateParameters `
        -RequiredParameters @('Name', 'Repository', 'Scope')

    $exactVersionRequested = $installParameters.ContainsKey('RequiredVersion')
    Write-Verbose ("Installing with Install-Module {0} from '{1}'; effective parameter names: {2}." -f
        $installCommand.Version,
        $installCommand.Source,
        (@($installParameters.Keys | Sort-Object) -join ', '))
    $installResult = @(& $installCommand @installParameters)
    if ($installResult.Count -gt 0) {
        Write-Verbose ("Install-Module returned version/location: {0}." -f
            (@($installResult | ForEach-Object { '{0} [{1}]' -f $_.Version, $_.InstalledLocation }) -join '; '))
    }

    $afterState = Get-PackageModuleVersionState -ModuleName $moduleName -ExecutingModule $executingModule
    $installedCandidates = @(
        $afterState.InstalledModules |
            Where-Object {
                $null -ne $_.Version -and
                $(if ($exactVersionRequested) {
                    [version]$_.Version -eq $latestRepositoryVersion
                }
                else {
                    [version]$_.Version -gt $relevantVersion
                })
            } |
            Sort-Object -Property Version -Descending
    )

    if ($installedCandidates.Count -eq 0) {
        if ($exactVersionRequested) {
            throw "Install-Module completed, but '$moduleName' version '$latestRepositoryVersion' was not visible through Get-Module -ListAvailable."
        }

        throw "Install-Module completed, but no newer '$moduleName' version was visible through Get-Module -ListAvailable."
    }

    $scopeEvaluations = @(
        foreach ($candidate in $installedCandidates) {
            [pscustomobject]@{
                Module = $candidate
                Scope = Test-PackageModuleInstallationScope -Module $candidate -Scope $Scope
            }
        }
    )
    $scopeMatchedCandidates = @($scopeEvaluations | Where-Object { $_.Scope.Matches })
    if ($scopeMatchedCandidates.Count -gt 0) {
        $installedModule = $scopeMatchedCandidates[0].Module
    }
    elseif (@($scopeEvaluations | Where-Object { $_.Scope.Known }).Count -gt 0) {
        $expectedScopeRoot = @($scopeEvaluations | Where-Object { $_.Scope.Known } | Select-Object -First 1)[0].Scope.ScopeRoot
        throw "Install-Module completed, but the newer '$moduleName' version was not visible in requested scope '$Scope' below '$expectedScopeRoot'."
    }
    else {
        Write-Verbose ("The requested installation scope path could not be determined; continuing with version/path verification only.")
        $installedModule = $installedCandidates[0]
    }

    $installedVersion = [version]$installedModule.Version
    Write-Verbose ("Verified installed version/path/scope: {0} / {1} / {2}." -f
        $installedVersion, $installedModule.Path, $Scope)
    $activation = Enable-PackageUpdatedModuleVersion `
        -ModuleName $moduleName `
        -Version $installedVersion `
        -InstalledModule $installedModule

    if ($activation.Active) {
        return "$moduleName was updated from $relevantVersion to $installedVersion. The new version is active for subsequent commands in this session."
    }

    Write-Verbose ("Restart required after update: {0}" -f $activation.Reason)
    if ($null -ne $activation.CommandVersion -and [version]$activation.CommandVersion -eq $installedVersion) {
        return "$moduleName was updated from $relevantVersion to $installedVersion. Subsequent commands resolve to the new version, but older module state is still loaded. Open a new PowerShell session to clear it."
    }

    return "$moduleName was updated from $relevantVersion to $installedVersion. This session is still using $executingVersion. Open a new PowerShell session to activate the update."
}