Eigenverft.Manifested.Drydock.Powershell.ps1

function Test-InstallationScopeCapability {
<#
.SYNOPSIS
Resolves the effective installation scope from current privileges (no parameters).
.DESCRIPTION
Returns exactly one string:
- "AllUsers" if the session is elevated (Administrator),
- "CurrentUser" otherwise.
.EXAMPLE
Test-InstallationScopeCapability
.OUTPUTS
System.String
#>

    [CmdletBinding()]
    param()

    $isAdmin = $false
    try {
        $id  = [Security.Principal.WindowsIdentity]::GetCurrent()
        $pri = [Security.Principal.WindowsPrincipal]$id
        $isAdmin = $pri.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    } catch {
        $isAdmin = $false
    }

    if ($isAdmin) { 'AllUsers' } else { 'CurrentUser' }
}

function Set-PSGalleryTrust {
<#
.SYNOPSIS
Ensures the 'PSGallery' repository exists locally and is trusted (parameterless).
.DESCRIPTION
- Parameterless on purpose: resolves the effective scope internally via Test-InstallationScopeCapability.
- Prefers PowerShellGet repository cmdlets; falls back to PackageManagement if needed.
- Local operations only; does not force a network call.
.EXAMPLE
Set-PSGalleryTrust
#>

    [CmdletBinding()]
    param()

    $effectiveScope = Test-InstallationScopeCapability
    Write-Host "[Info] Ensuring PSGallery trust at effective scope: '$effectiveScope'."

    # Prefer PSRepository (PowerShellGet) path
    $hasPsRepositoryCmdlets = $false
    try { if (Get-Command Get-PSRepository -ErrorAction SilentlyContinue) { $hasPsRepositoryCmdlets = $true } } catch {}

    if ($hasPsRepositoryCmdlets) {
        try {
            $repo = Get-PSRepository -Name 'PSGallery' -ErrorAction Stop
            if ($repo.InstallationPolicy -ne 'Trusted') {
                Write-Host "[Action] Setting PSGallery InstallationPolicy to 'Trusted'..."
                Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted -ErrorAction Stop
                Write-Host "[Success] PSGallery is now trusted."
            } else {
                Write-Host "[OK] PSGallery is already trusted."
            }
            return
        } catch {
            Write-Host "[Action] Registering PSGallery locally..."
            try {
                Register-PSRepository -Name 'PSGallery' `
                    -SourceLocation 'https://www.powershellgallery.com/api/v2' `
                    -ScriptSourceLocation 'https://www.powershellgallery.com/api/v2' `
                    -InstallationPolicy Trusted -ErrorAction Stop
                Write-Host "[Success] PSGallery registered and trusted."
            } catch {
                Write-Host "Error: Failed to register PSGallery via Register-PSRepository: $($_.Exception.Message)" -ForegroundColor Red
            }
            return
        }
    }

    # Fallback: PackageManagement path
    try {
        $pkgSrc = Get-PackageSource -Name 'PSGallery' -ProviderName 'PowerShellGet' -ErrorAction SilentlyContinue
        if ($pkgSrc) {
            if (-not $pkgSrc.IsTrusted) {
                Write-Host "[Action] Marking PSGallery as trusted via Set-PackageSource..."
                Set-PackageSource -Name 'PSGallery' -Trusted -ProviderName 'PowerShellGet' -ErrorAction Stop | Out-Null
                Write-Host "[Success] PSGallery is now trusted."
            } else {
                Write-Host "[OK] PSGallery is already trusted (PackageManagement)."
            }
        } else {
            Write-Host "[Action] Adding PSGallery (fallback path)..."
            try {
                Register-PSRepository -Name 'PSGallery' `
                    -SourceLocation 'https://www.powershellgallery.com/api/v2' `
                    -ScriptSourceLocation 'https://www.powershellgallery.com/api/v2' `
                    -InstallationPolicy Trusted -ErrorAction Stop
                Write-Host "[Success] PSGallery registered and trusted."
            } catch {
                Write-Host "Error: Could not register PSGallery (fallback path): $($_.Exception.Message)" -ForegroundColor Red
            }
        }
    } catch {
        Write-Host "Error: Failed to evaluate or set PSGallery trust state: $($_.Exception.Message)" -ForegroundColor Red
    }
}

function Use-Tls12 {
<#
.SYNOPSIS
Ensures TLS 1.2 for outbound HTTPS in Windows PowerShell 5.x.
 
.DESCRIPTION
Adds TLS 1.2 to [Net.ServicePointManager]::SecurityProtocol for the current session.
Prevents "Could not create SSL/TLS secure channel" when using PowerShellGet/NuGet.
 
.EXAMPLE
Use-Tls12
#>

    [CmdletBinding()]
    param()
    $tls12 = [Net.SecurityProtocolType]::Tls12
    if (([Net.ServicePointManager]::SecurityProtocol -band $tls12) -eq 0) {
        [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor $tls12
    }
}

function Test-PSGalleryConnectivity {
<#
.SYNOPSIS
Fast connectivity test to PowerShell Gallery with HEAD→GET fallback.
.DESCRIPTION
Attempts a HEAD request to https://www.powershellgallery.com/api/v2/.
If the server returns 405 (Method Not Allowed), retries with GET.
Considers HTTP 200–399 as reachable. Writes status and returns $true/$false.
.EXAMPLE
Test-PSGalleryConnectivity
.OUTPUTS
System.Boolean
#>

    [CmdletBinding()]
    param()

    $url = 'https://www.powershellgallery.com/api/v2/'
    $timeoutMs = 5000

    function Invoke-WebCheck {
        param([string]$Method)

        try {
            $req = [System.Net.HttpWebRequest]::Create($url)
            $req.Method            = $Method
            $req.Timeout           = $timeoutMs
            $req.ReadWriteTimeout  = $timeoutMs
            $req.AllowAutoRedirect = $true
            $req.UserAgent         = 'WindowsPowerShell/5.1 PSGalleryConnectivityCheck'

            # NOTE: No proxy credential munging here—use system defaults.
            $res = $req.GetResponse()
            $status = [int]$res.StatusCode
            $res.Close()

            if ($status -ge 200 -and $status -lt 400) {
                Write-Host "[OK] PSGallery reachable via $Method (HTTP $status)."
                return $true
            } else {
                Write-Host "Error: PSGallery returned HTTP $status on $Method." -ForegroundColor Red
                return $false
            }
        } catch [System.Net.WebException] {
            $wex = $_.Exception
            $resp = $wex.Response
            if ($resp -and $resp -is [System.Net.HttpWebResponse]) {
                $status = [int]$resp.StatusCode
                $resp.Close()
                if ($status -eq 405 -and $Method -eq 'HEAD') {
                    # Fallback handled by caller
                    return $null
                }
                Write-Host "Error: PSGallery $Method failed (HTTP $status): $($wex.Message)" -ForegroundColor Red
                return $false
            } else {
                Write-Host "Error: PSGallery $Method failed: $($wex.Message)" -ForegroundColor Red
                return $false
            }
        } catch {
            Write-Host "Error: PSGallery $Method failed: $($_.Exception.Message)" -ForegroundColor Red
            return $false
        }
    }

    # Try HEAD first for speed; if 405, fall back to GET.
    $headResult = Invoke-WebCheck -Method 'HEAD'
    if ($headResult -eq $true) { return $true }
    if ($null -eq $headResult) {
        # 405 from HEAD → retry with GET
        $getResult = Invoke-WebCheck -Method 'GET'
        return [bool]$getResult
    }

    return $false
}

function Initialize-NugetPackageProvider {
<#
.SYNOPSIS
Ensures the NuGet package provider (>= 2.8.5.201) is available for the exact scope.
.DESCRIPTION
- Exact scope handling (AllUsers | CurrentUser).
- If -Scope is omitted, resolves scope via Test-InstallationScopeCapability.
- Local-first: only installs/updates when needed.
- Write-Host only (PS5-compatible).
.PARAMETER Scope
Exact scope ('AllUsers' or 'CurrentUser'). If omitted, chosen automatically.
.EXAMPLE
Initialize-NugetPackageProvider
.EXAMPLE
Initialize-NugetPackageProvider -Scope AllUsers
#>

    [Diagnostics.CodeAnalysis.SuppressMessage("PSUseApprovedVerbs","")]
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('AllUsers','CurrentUser')]
        [string]$Scope = 'CurrentUser'
    )

    # 1) Resolve scope
    $resolvedScope = if ($PSBoundParameters.ContainsKey('Scope')) {
        Write-Host "[Init] Using explicitly provided scope: $Scope"
        $Scope
    } else {
        $auto = Test-InstallationScopeCapability
        Write-Host "[Default] No scope provided; using '$auto' based on permission check."
        $auto
    }

    # 2) Gate explicit AllUsers if not elevated
    if ($PSBoundParameters.ContainsKey('Scope') -and $resolvedScope -eq 'AllUsers' -and (Test-InstallationScopeCapability) -ne 'AllUsers') {
        Write-Host "Error: Requested 'AllUsers' but session is not elevated. Start PowerShell as Administrator or omit -Scope." -ForegroundColor Red
        Write-Host "[Result] Aborted: insufficient privileges for 'AllUsers'."
        return
    }
    Write-Host "[OK] Operating with scope '$resolvedScope'."

    # 3) Minimum required version
    $requiredMinVersion = [Version]'2.8.5.201'
    Write-Host "[Check] Minimum required NuGet provider version: $requiredMinVersion"

    # 4) Local detection
    try {
        Write-Host "[Check] Inspecting existing NuGet provider..."
        $installedProvider = Get-PackageProvider -ListAvailable -ErrorAction SilentlyContinue |
                             Where-Object { $_.Name -ieq 'NuGet' } |
                             Select-Object -First 1
    } catch {
        Write-Host "Error: Failed to query package providers: $($_.Exception.Message)" -ForegroundColor Red
        Write-Host "[Result] Aborted: provider enumeration failed."
        return
    }

    $needsInstall = $true
    if ($installedProvider) {
        try {
            $currentVersion = [Version]$installedProvider.Version
            Write-Host "[Info] Found NuGet provider version: $currentVersion"
            $needsInstall = ($currentVersion -lt $requiredMinVersion)
        } catch {
            Write-Host "[Warn] Could not interpret provider version; will attempt reinstallation."
            $needsInstall = $true
        }
    } else {
        Write-Host "[Info] NuGet provider not found."
    }

    # 5) Install/Update if needed
    if ($needsInstall) {
        Write-Host "[Action] Installing/updating NuGet provider to >= $requiredMinVersion (Scope: $resolvedScope)..."
        $originalProgressPreference = $global:ProgressPreference
        try {
            $global:ProgressPreference = 'SilentlyContinue'
            $installCmdlet = Get-Command Install-PackageProvider -ErrorAction SilentlyContinue
            $installParams = @{
                Name           = 'NuGet'
                MinimumVersion = $requiredMinVersion
                Force          = $true
                ErrorAction    = 'Stop'
            }
            if ($installCmdlet -and $installCmdlet.Parameters.ContainsKey('Scope')) { $installParams['Scope'] = $resolvedScope }

            Install-PackageProvider @installParams | Out-Null
            Write-Host "[Success] NuGet provider installed/updated for '$resolvedScope'."
            Write-Host "[Result] Compliant: provider version >= $requiredMinVersion."
        } catch {
            Write-Host "Error: Installation in scope '$resolvedScope' failed: $($_.Exception.Message)" -ForegroundColor Red
            Write-Host "[Result] Failed: installation/update did not complete."
        } finally {
            $global:ProgressPreference = $originalProgressPreference
        }
        return
    }

    Write-Host "[Skip] Provider already meets minimum ($requiredMinVersion); no action required."
    Write-Host "[Result] No changes necessary."
}

function Initialize-PowerShellGet {
<#
.SYNOPSIS
Ensures the PowerShellGet module is present/updated with PSGallery trusted; resolves scope automatically when omitted.
.DESCRIPTION
- Exact scope handling (AllUsers | CurrentUser). If -Scope not provided, resolves via Test-InstallationScopeCapability.
- Local-first: if installed PowerShellGet >= minimum, no online contact is made.
- Calls Initialize-NugetPackageProvider (prereq) and Set-PSGalleryTrust (trust).
- Write-Host only (PS5-compatible).
.PARAMETER Scope
Exact scope ('AllUsers' or 'CurrentUser'). If omitted, chosen automatically.
.EXAMPLE
Initialize-PowerShellGet
.EXAMPLE
Initialize-PowerShellGet -Scope AllUsers
#>

    [Diagnostics.CodeAnalysis.SuppressMessage("PSUseApprovedVerbs","")]
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('AllUsers','CurrentUser')]
        [string]$Scope = 'CurrentUser'
    )

    # 1) Resolve scope
    $resolvedScope = if ($PSBoundParameters.ContainsKey('Scope')) {
        Write-Host "[Init] Using explicitly provided scope: $Scope"
        $Scope
    } else {
        $auto = Test-InstallationScopeCapability
        Write-Host "[Default] No scope provided; using '$auto' based on permission check."
        $auto
    }

    # 2) Gate explicit AllUsers if not elevated
    if ($PSBoundParameters.ContainsKey('Scope') -and $resolvedScope -eq 'AllUsers' -and (Test-InstallationScopeCapability) -ne 'AllUsers') {
        Write-Host "Error: Requested 'AllUsers' but session is not elevated. Start PowerShell as Administrator or omit -Scope." -ForegroundColor Red
        Write-Host "[Result] Aborted: insufficient privileges for 'AllUsers'."
        return
    }
    Write-Host "[OK] Operating with scope '$resolvedScope'."

    # 3) Minimum required version
    $requiredMinVersion = [Version]'2.2.5.1'
    Write-Host "[Check] Minimum required PowerShellGet version: $requiredMinVersion"

    # 4) Local detection
    $installed = $null
    try {
        $installed = Get-Module -ListAvailable -Name 'PowerShellGet' |
                     Sort-Object Version -Descending |
                     Select-Object -First 1
    } catch {
        Write-Host "[Warn] Failed to enumerate installed PowerShellGet: $($_.Exception.Message)"
    }

    if ($installed) {
        Write-Host "[Info] Found PowerShellGet version: $($installed.Version) at $($installed.ModuleBase)"
        if ([Version]$installed.Version -ge $requiredMinVersion) {
            Set-PSGalleryTrust
            Write-Host "[Skip] Installed PowerShellGet meets minimum; no online update performed."
            Write-Host "[Result] No changes necessary."
            return
        }
        Write-Host "[Info] Installed version is below minimum; update will be attempted."
    } else {
        Write-Host "[Info] PowerShellGet not found; installation will be attempted."
    }

    # 5) Prep: Ensure NuGet provider, then trust PSGallery
    try {
        Write-Host "[Prep] Ensuring NuGet provider via Initialize-NugetPackageProvider..."
        Initialize-NugetPackageProvider -Scope $resolvedScope
    } catch {
        Write-Host "[Warn] Initialize-NugetPackageProvider reported an issue: $($_.Exception.Message)"
    }

    Set-PSGalleryTrust

    # 6) Install/Update (online only when needed)
    Write-Host "[Action] Installing/Updating PowerShellGet (Scope: $resolvedScope)..."
    $originalProgressPreference = $global:ProgressPreference
    try {
        $global:ProgressPreference = 'SilentlyContinue'
        $installCmdlet = Get-Command Install-Module -ErrorAction SilentlyContinue
        if (-not $installCmdlet) {
            Write-Host "Error: Install-Module is not available. Ensure PowerShellGet cmdlets are loaded." -ForegroundColor Red
            Write-Host "[Result] Failed: cannot proceed with installation."
            return
        }

        $installParams = @{
            Name         = 'PowerShellGet'
            Repository   = 'PSGallery'
            Force        = $true
            AllowClobber = $true
            ErrorAction  = 'Stop'
        }
        if ($installCmdlet.Parameters.ContainsKey('Scope')) { $installParams['Scope'] = $resolvedScope }

        Install-Module @installParams
        Write-Host "[Success] PowerShellGet installed/updated successfully."
        Write-Host "[Result] PowerShellGet is compliant (>= $requiredMinVersion)."
    } catch {
        Write-Host "Error: Installing/Updating PowerShellGet failed: $($_.Exception.Message)" -ForegroundColor Red
        Write-Host "[Result] Failed: PowerShellGet could not be installed/updated."
    } finally {
        $global:ProgressPreference = $originalProgressPreference
    }
}

function Initialize-PackageManagement {
<#
.SYNOPSIS
Ensures the PackageManagement module is present/updated for the exact scope with local-first behavior.
.DESCRIPTION
- Exact scope handling (AllUsers | CurrentUser). If -Scope is omitted, resolves via Test-InstallationScopeCapability.
- Local-first: if installed PackageManagement >= minimum baseline, no online call is made.
- Preps NuGet provider via Initialize-NugetPackageProvider; ensures PSGallery is trusted via Set-PSGalleryTrust.
- Write-Host only (PS5-compatible).
.PARAMETER Scope
Exact scope name ('AllUsers' or 'CurrentUser'). If omitted, chosen automatically.
.EXAMPLE
Initialize-PackageManagement
.EXAMPLE
Initialize-PackageManagement -Scope AllUsers
#>

    [Diagnostics.CodeAnalysis.SuppressMessage("PSUseApprovedVerbs","")]
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('AllUsers','CurrentUser')]
        [string]$Scope = 'CurrentUser'
    )

    # 1) Resolve scope
    $resolvedScope = if ($PSBoundParameters.ContainsKey('Scope')) {
        Write-Host "[Init] Using explicitly provided scope: $Scope"
        $Scope
    } else {
        $auto = Test-InstallationScopeCapability
        Write-Host "[Default] No scope provided; using '$auto' based on permission check."
        $auto
    }

    # 2) Gate explicit AllUsers if not elevated
    if ($PSBoundParameters.ContainsKey('Scope') -and $resolvedScope -eq 'AllUsers' -and (Test-InstallationScopeCapability) -ne 'AllUsers') {
        Write-Host "Error: Requested 'AllUsers' but session is not elevated. Start PowerShell as Administrator or omit -Scope." -ForegroundColor Red
        Write-Host "[Result] Aborted: insufficient privileges for 'AllUsers'."
        return
    }
    Write-Host "[OK] Operating with scope '$resolvedScope'."

    # 3) Minimum required version
    $requiredMinVersion = [Version]'1.4.8.1'  # Adjust baseline if your estate requires a different floor
    Write-Host "[Check] Minimum required PackageManagement version: $requiredMinVersion"

    # 4) Local detection
    $installed = $null
    try {
        $installed = Get-Module -ListAvailable -Name 'PackageManagement' |
                     Sort-Object Version -Descending |
                     Select-Object -First 1
    } catch {
        Write-Host "[Warn] Failed to enumerate installed PackageManagement: $($_.Exception.Message)"
    }

    if ($installed) {
        Write-Host "[Info] Found PackageManagement version: $($installed.Version) at $($installed.ModuleBase)"
        if ([Version]$installed.Version -ge $requiredMinVersion) {
            Set-PSGalleryTrust
            Write-Host "[Skip] Installed PackageManagement meets minimum; no online update performed."
            Write-Host "[Result] No changes necessary."
            return
        }
        Write-Host "[Info] Installed version is below minimum; update will be attempted."
    } else {
        Write-Host "[Info] PackageManagement not found; installation will be attempted."
    }

    # 5) Prep: Ensure NuGet provider, then trust PSGallery
    try {
        Write-Host "[Prep] Ensuring NuGet provider via Initialize-NugetPackageProvider..."
        Initialize-NugetPackageProvider -Scope $resolvedScope
    } catch {
        Write-Host "[Warn] Initialize-NugetPackageProvider reported an issue: $($_.Exception.Message)"
    }

    Set-PSGalleryTrust

    # 6) Install/Update (online only when needed)
    Write-Host "[Action] Installing/Updating PackageManagement (Scope: $resolvedScope)..."
    $originalProgressPreference = $global:ProgressPreference
    try {
        $global:ProgressPreference = 'SilentlyContinue'

        $installCmdlet = Get-Command Install-Module -ErrorAction SilentlyContinue
        if (-not $installCmdlet) {
            Write-Host "Error: Install-Module is not available. Ensure PowerShellGet cmdlets are loaded." -ForegroundColor Red
            Write-Host "[Result] Failed: cannot proceed with installation."
            return
        }

        # Intentionally avoid Find-Module to keep offline unless installation is required.
        $installParams = @{
            Name               = 'PackageManagement'
            Repository         = 'PSGallery'
            Force              = $true
            AllowClobber       = $true
            SkipPublisherCheck = $true
            ErrorAction        = 'Stop'
        }
        if ($installCmdlet.Parameters.ContainsKey('Scope')) { $installParams['Scope'] = $resolvedScope }

        try {
            Install-Module @installParams
            Write-Host "[Success] PackageManagement installed/updated successfully."
            Write-Host "[Result] PackageManagement is compliant (>= $requiredMinVersion)."
        } catch {
            Write-Host "Error: Install-Module for PackageManagement failed: $($_.Exception.Message)" -ForegroundColor Red
            # Fallback in case the module exists but is locked/older in certain paths
            try {
                Write-Host "[Fallback] Attempting Update-Module -Name PackageManagement -Force ..."
                Update-Module -Name 'PackageManagement' -Force -ErrorAction Stop
                Write-Host "[Success] Update-Module completed."
                Write-Host "[Result] PackageManagement updated."
            } catch {
                Write-Host "Error: Update-Module failed: $($_.Exception.Message)" -ForegroundColor Red
                Write-Host "[Result] Failed: PackageManagement not updated."
            }
        }
    } finally {
        $global:ProgressPreference = $originalProgressPreference
    }
}

function Initialize-PowerShellBootstrap {
<#
.SYNOPSIS
Runs the initialization sequence on Windows PowerShell 5.x only (skips on PS 6/7+).
 
.DESCRIPTION
- Detects edition/version; exits early on PowerShell Core (6/7+).
- On PS5.x:
  - Enables TLS 1.2 (local, idempotent).
  - Resolves effective scope (or honors -Scope).
  - Applies PSGallery trust (local-only).
  - Proceeds with NuGet → PowerShellGet → PackageManagement
    only if PSGallery connectivity succeeds.
 
.PARAMETER Scope
Optional exact scope ('AllUsers' or 'CurrentUser'). If omitted, scope is resolved via Test-InstallationScopeCapability.
 
.EXAMPLE
Initialize-PowerShellBootstrap
.EXAMPLE
Initialize-PowerShellBootstrap -Scope AllUsers
#>

    [Diagnostics.CodeAnalysis.SuppressMessage("PSUseApprovedVerbs","")]
    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('AllUsers','CurrentUser')]
        [string]$Scope
    )

    Write-Host "[Bootstrap] Starting PowerShell environment initialization..."

    $psVer = $PSVersionTable.PSVersion
    $psEd  = $PSVersionTable.PSEdition
    $isWinPS5 = ($psEd -eq 'Desktop' -and $psVer.Major -eq 5)

    if (-not $isWinPS5) {
        Write-Host "[Bootstrap] Detected PowerShell $psVer ($psEd). Skipping Windows PowerShell 5.x bootstrap; nothing to do."
        return
    }

    Write-Host "[Bootstrap] Detected Windows PowerShell $psVer ($psEd). Continuing with PS5-specific bootstrap..."

    # 1) TLS 1.2 for PS5 sessions (local, safe)
    Use-Tls12

    # 2) Resolve scope once (info only; initializers still enforce their own gates)
    $resolvedScope = if ($PSBoundParameters.ContainsKey('Scope')) {
        Write-Host "[Bootstrap] Using explicit scope: $Scope"
        $Scope
    } else {
        $auto = Test-InstallationScopeCapability
        Write-Host "[Bootstrap] No scope provided; resolved effective scope: $auto"
        $auto
    }

    # 3) Local-only step first (no network)
    Write-Host "[Bootstrap] Applying local PSGallery trust state..."
    Set-PSGalleryTrust

    # 4) Connectivity gate for online steps
    Write-Host "[Bootstrap] Checking PSGallery connectivity..."
    if (-not (Test-PSGalleryConnectivity)) {
        Write-Host "Error: PSGallery not reachable. Online initialization steps will be skipped." -ForegroundColor Red
        Write-Host "[Bootstrap] Result: Partial (local trust applied)."
        return
    }

    # 5) Online steps in recommended order
    Write-Host "[Bootstrap] Connectivity OK. Proceeding with online steps..."
    Initialize-NugetPackageProvider -Scope $resolvedScope
    Initialize-PowerShellGet       -Scope $resolvedScope
    Initialize-PackageManagement   -Scope $resolvedScope

    Write-Host "[Bootstrap] Completed successfully."
}

function Initialize-PowerShellMiniBootstrap {
<#
.SYNOPSIS
Performs a minimal, non-interactive bootstrap for Windows PowerShell 5.x (CurrentUser scope): enables TLS 1.2, ensures the NuGet provider (>= 2.8.5.201), trusts PSGallery, installs/updates PowerShellGet and PackageManagement if newer, and imports them; silently skips on PowerShell 6/7+.
#>

    param()
    $Install=@('PowerShellGet','PackageManagement');$Scope='CurrentUser';if($PSVersionTable.PSVersion.Major -ne 5){return};[Net.ServicePointManager]::SecurityProtocol=[Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12; $minNuget=[Version]'2.8.5.201'; Install-PackageProvider -Name NuGet -MinimumVersion $minNuget -Scope $Scope -Force -ForceBootstrap | Out-Null; try { Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction Stop } catch { Register-PSRepository -Name PSGallery -SourceLocation 'https://www.powershellgallery.com/api/v2' -ScriptSourceLocation 'https://www.powershellgallery.com/api/v2' -InstallationPolicy Trusted -ErrorAction Stop }; Find-Module -Name $Install -Repository PSGallery | Select-Object Name,Version | Where-Object { -not (Get-Module -ListAvailable -Name $_.Name | Sort-Object Version -Descending | Select-Object -First 1 | Where-Object Version -eq $_.Version) } | ForEach-Object { Install-Module -Name $_.Name -RequiredVersion $_.Version -Repository PSGallery -Scope $Scope -Force -AllowClobber; try { Remove-Module -Name $_.Name -ErrorAction SilentlyContinue } catch {}; Import-Module -Name $_.Name -MinimumVersion $_.Version -Force }
}

function Import-Script {
<#
.SYNOPSIS
    Imports one or more scripts by globalizing their function and filter declarations, then executing them.
 
.DESCRIPTION
    This function reads each .ps1 file, parses it with the PowerShell AST, and rewrites every
    function/filter declaration to include the global: scope (replacing script:, local:, private: if present).
    The transformed script is then executed so all such commands are available in the global/session scope.
    Compatible with Windows PowerShell 5.1 and PowerShell 7+.
    ASCII only.
 
.PARAMETER File
    One or more script paths. Variables like $PSScriptRoot are expanded.
 
.PARAMETER ErrorIfMissing
    If set, writes a non-terminating error for each missing file and continues.
 
.EXAMPLE
    Import-Script -File @("$PSScriptRoot\cicd.migration.ps1")
 
.NOTES
    Only function/filter declarations are globalized. If the source script must expose variables
    globally, set them as $global:Var inside the source script.
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position=0)]
        [string[]]$File,
        [switch]$ErrorIfMissing
    )

    foreach ($f in $File) {
        if ([string]::IsNullOrWhiteSpace($f)) { continue }

        # Expand variables (e.g., $PSScriptRoot) before checking.
        $expanded = $ExecutionContext.InvokeCommand.ExpandString($f)

        if (-not (Test-Path -LiteralPath $expanded)) {
            if ($ErrorIfMissing) { Write-Error "Import-Script: file not found: $expanded" }
            continue
        }

        # Read script as text
        $code = [System.IO.File]::ReadAllText($expanded)

        # Parse to AST (PS 5.1 and 7+)
        $tokens = $null; $errors = $null
        $ast = [System.Management.Automation.Language.Parser]::ParseInput($code, [ref]$tokens, [ref]$errors)
        if ($errors -and $errors.Count -gt 0) {
            throw "Import-Script: parse errors in '$expanded'."
        }

        # Find all function/filter definitions
        $funcAsts = $ast.FindAll({ param($a) $a -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)

        # Collect edits: make each declaration global:
        $edits = @()
        foreach ($fd in $funcAsts) {
            $headerText = $fd.Extent.Text
            $m = [regex]::Match(
                $headerText,
                '^(?im)\s*(?<kw>function|filter)\s+(?<scope>(?:global|script|local|private):)?(?<name>[A-Za-z_][\w-]*)'
            )
            if (-not $m.Success) { continue }

            if ($m.Groups['scope'].Success -and $m.Groups['scope'].Value -eq 'global:') {
                continue
            }

            $headerStart = $fd.Extent.StartOffset
            if ($m.Groups['scope'].Success) {
                # Replace existing non-global scope with global:
                $start = $headerStart + $m.Groups['scope'].Index
                $end   = $start + $m.Groups['scope'].Length
                $edits += [pscustomobject]@{ Start=$start; End=$end; Text='global:' }
            } else {
                # Insert global: right before the function name
                $insertAt = $headerStart + $m.Groups['name'].Index
                $edits += [pscustomobject]@{ Start=$insertAt; End=$insertAt; Text='global:' }
            }
        }

        if ($edits.Count -gt 0) {
            foreach ($e in ($edits | Sort-Object Start -Descending)) {
                $prefix = $code.Substring(0, $e.Start)
                $suffix = $code.Substring($e.End)
                $code = $prefix + $e.Text + $suffix
            }
        }

        # Execute transformed script so global: declarations register in session scope
        & ([scriptblock]::Create($code))
    }
}

function Export-OfflineModuleBundle {
<#
.SYNOPSIS
Stage PSGallery modules into Root\Nuget, copy NuGet provider into Root\Provider, and emit an offline installer script. (PS 5.1)
 
.DESCRIPTION
Resolves modules (including dependencies) using Find-Module -IncludeDependencies, then downloads each as a .nupkg
via Save-Package into Root\Nuget. Copies the local NuGet provider DLLs into Root\Provider so an offline machine can
bootstrap the provider. Always emits "Install-ModulesFromRepoFolder.ps1" in the root folder, which contains the
installer function plus a ready-to-run invocation that targets the folder it resides in.
 
.REQUIREMENTS
Machine A (online, where you run this Save function):
- Windows PowerShell 5.1.
- Working internet access to https://www.powershellgallery.com/api/v2 .
- PackageManagement module available (built-in on PS 5.1).
- PowerShellGet v2 available (built-in on PS 5.1; can be upgraded but not required).
- NuGet package provider already installed and functional (Save-Package must work).
- TLS 1.2 allowed outbound (this function enables TLS 1.2 for the process if needed).
- Write permissions to the specified -Folder path.
 
Artifacts created under the root -Folder:
- Nuget\ : contains the downloaded .nupkg files for the specified modules and their dependencies.
- Provider\: contains NuGet provider DLLs copied from the local machine (used to bootstrap offline).
- Install-ModulesFromRepoFolder.ps1: the self-contained offline installer and invocation line.
 
Machine B (offline, where you will run the emitted installer):
- Windows PowerShell 5.1.
- PackageManagement and PowerShellGet present (the old in-box versions are fine; they will be upgraded).
- Local admin rights required ONLY if you intend to install for AllUsers; otherwise CurrentUser is fine.
- ExecutionPolicy must allow running the emitted .ps1 (e.g., set to RemoteSigned/Bypass as appropriate).
- No internet is required; all content comes from the copied Root folder.
- Write permissions to ProgramFiles (if installing for AllUsers) or to user Documents (CurrentUser).
 
Failure cases to be aware of:
- If NuGet provider is not present on Machine B and Provider\ is missing or incomplete, install will fail.
- If the Nuget\ folder does not contain a requested module (name mismatch or missing package), only that module fails.
- Locked module directories or insufficient permissions can prevent installation (especially AllUsers scope).
 
.PARAMETER Folder
Root folder that will contain "Nuget" and "Provider". Created if missing.
 
.PARAMETER Name
One or more module names to stage.
 
.PARAMETER Version
(Optional) Exact version to stage for all names; latest if omitted.
 
.EXAMPLE
PS> Export-OfflineModuleBundle -Folder C:\temp\export -Name @('PowerShellGet','PackageManagement','Pester','PSScriptAnalyzer','Eigenverft.Manifested.Drydock')
 
.TROUBLESHOOTING
- On Machine B, if the script reports missing NuGet provider, verify the "Provider" folder exists and contains NuGet*.dll.
- If Install-Module errors with repository issues, confirm the "Nuget" folder exists and holds the .nupkg files.
- If execution is blocked, set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass for the current session.
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Folder,
        [Parameter(Mandatory, Position=1)]
        [string[]]$Name,
        [string]$Version
    )

    # TLS 1.2 for PSGallery on PS 5.1
    try {
        if (-not ([Net.ServicePointManager]::SecurityProtocol -band [Net.SecurityProtocolType]::Tls12)) {
            [Net.ServicePointManager]::SecurityProtocol =
                [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
        }
    } catch { }

    if (-not (Test-Path -LiteralPath $Folder)) {
        New-Item -ItemType Directory -Path $Folder -Force | Out-Null
    }

    $nugetDir    = Join-Path $Folder "Nuget"
    $providerDir = Join-Path $Folder "Provider"
    if (-not (Test-Path -LiteralPath $nugetDir))    { New-Item -ItemType Directory -Path $nugetDir -Force | Out-Null }
    if (-not (Test-Path -LiteralPath $providerDir)) { New-Item -ItemType Directory -Path $providerDir -Force | Out-Null }

    $feed = "https://www.powershellgallery.com/api/v2"
    $repo = "PSGallery"

    # Resolve full dependency closure with PowerShellGet (works on PS5.1)
    # Use a name->version map so we can Save-Package without IncludeDependencies.
    $needed = @{}
    foreach ($n in $Name) {
        try {
            $findParams = @{ Name = $n; Repository = $repo; ErrorAction = "Stop"; IncludeDependencies = $true }
            if ($Version) { $findParams["RequiredVersion"] = $Version }
            $mods = Find-Module @findParams
            foreach ($m in $mods) {
                # Record the highest version seen for each name (simple dedupe)
                if (-not $needed.ContainsKey($m.Name)) {
                    $needed[$m.Name] = $m.Version
                } else {
                    try {
                        # Compare as [version]; fallback to string compare if needed
                        $cur = [version]$needed[$m.Name]
                        $new = [version]$m.Version
                        if ($new -gt $cur) { $needed[$m.Name] = $m.Version }
                    } catch {
                        if ($m.Version -gt $needed[$m.Name]) { $needed[$m.Name] = $m.Version }
                    }
                }
            }
        } catch {
            Write-Error "Failed to resolve '$n' from $($repo): $($_.Exception.Message)"
        }
    }

    # Fall back: if dependency resolution returned nothing, at least try the requested names
    if ($needed.Count -eq 0) {
        foreach ($n in $Name) { $needed[$n] = $Version }
    }

    # >>> CHANGE: ensure a stable (non-prerelease) version is pinned for every entry
    foreach ($k in @($needed.Keys)) {
        $ver = $needed[$k]
        $looksPrerelease = $ver -and ($ver.ToString() -match '-')
        if (-not $ver -or $looksPrerelease) {
            try {
                # Find-Module (no -AllowPrerelease) returns latest stable version
                $resolved = Find-Module -Name $k -Repository $repo -ErrorAction Stop
                if ($resolved -and $resolved.Version) {
                    $needed[$k] = $resolved.Version
                } else {
                    # Leave as-is; Save-Package step will block to avoid prerelease
                    $needed[$k] = $null
                }
            } catch {
                # Leave null to trigger safe skip during Save-Package
                $needed[$k] = $null
            }
        }
    }
    # <<< END CHANGE

    # Download each required module version via Save-Package (no IncludeDependencies for compatibility)
    foreach ($pair in $needed.GetEnumerator()) {
        $mn = $pair.Key
        $mv = $pair.Value
        try {
            $p = @{
                Name         = $mn
                Path         = $nugetDir
                ProviderName = "NuGet"
                Source       = $feed
                ErrorAction  = "Stop"
            }
            if ($mv) {
                $p["RequiredVersion"] = $mv
            } else {
                # >>> CHANGE: skip to avoid accidentally pulling a prerelease
                Write-Error "No stable version found for '$mn' on $repo. Skipping to avoid prerelease."
                continue
                # <<< END CHANGE
            }
            [void](Save-Package @p)
        } catch {
            Write-Error "Failed to save '$mn' into '$nugetDir': $($_.Exception.Message)"
        }
    }

    # Copy NuGet provider DLLs for offline bootstrap (search ProgramFiles, LocalAppData, ProgramData)
    $providerCandidates = @(
        (Join-Path $Env:ProgramFiles "PackageManagement\ProviderAssemblies\NuGet"),
        (Join-Path $Env:LOCALAPPDATA  "PackageManagement\ProviderAssemblies\NuGet"),
        (Join-Path $Env:ProgramData   "PackageManagement\ProviderAssemblies\NuGet")
    ) | Where-Object { Test-Path -LiteralPath $_ }

    foreach ($src in $providerCandidates) {
        try {
            Copy-Item -Path (Join-Path $src "*") -Destination $providerDir -Recurse -Force -ErrorAction SilentlyContinue
        } catch {
            Write-Verbose "Provider copy from '$src' failed: $($_.Exception.Message)"
        }
    }

    # Emit installer script with function + invocation using $PSScriptRoot (UTF-8 for path safety)
    $installerPath = Join-Path $Folder "Install-ModulesFromRepoFolder.ps1"

$functionText = @'
function Install-ModulesFromRepoFolder {
<#
.SYNOPSIS
Install modules from Root\Nuget using a temporary PSRepository; bootstrap NuGet provider from Root\Provider. (PS 5.1)
 
.REQUIREMENTS
- Windows PowerShell 5.1.
- Root folder contains:
  - Provider\ with NuGet*.dll for offline bootstrap (if provider is missing).
  - Nuget\ with staged .nupkg files.
- If installing for AllUsers, run elevated.
 
.DESCRIPTION
1) If NuGet provider is missing, copy DLLs from Root\Provider to the proper provider path:
   - AllUsers (admin): %ProgramFiles%\PackageManagement\ProviderAssemblies\NuGet
   - CurrentUser (non-admin): %LocalAppData%\PackageManagement\ProviderAssemblies\NuGet
2) Register Root\Nuget as a temporary repository.
3) Install PackageManagement, then PowerShellGet, then remaining modules.
   Use -AllowClobber and -SkipPublisherCheck to handle in-box command collisions and publisher changes.
4) Unregister the temporary repository.
 
.PARAMETER Folder
Root folder containing Nuget and optionally Provider.
 
.PARAMETER Name
Module names to install from Root\Nuget.
 
.PARAMETER Scope
CurrentUser, AllUsers, or Auto (default). Auto selects AllUsers when the current process is elevated;
otherwise CurrentUser.
 
.EXAMPLE
PS> Install-ModulesFromRepoFolder -Folder C:\repo -Name Pester,PSScriptAnalyzer
#>
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Folder,
        [Parameter(Mandatory, Position=1)]
        [string[]]$Name,
        [ValidateSet("CurrentUser","AllUsers","Auto")]
        [string]$Scope = "Auto"
    )
 
    Write-Host "[INFO] Starting offline installation..."
    Write-Host ("[INFO] Root folder: {0}" -f $Folder)
 
    if (-not (Test-Path -LiteralPath $Folder)) {
        throw "Folder not found: $Folder"
    }
 
    $nugetDir = Join-Path $Folder "Nuget"
    $providerDir = Join-Path $Folder "Provider"
    if (-not (Test-Path -LiteralPath $nugetDir)) {
        throw "Required subfolder missing: $nugetDir"
    }
 
    # Determine elevation of current process
    $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()
               ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
 
    # Resolve effective scope from requested Scope + elevation
    $effectiveScope = if ($Scope -eq "Auto") {
        if ($isAdmin) { "AllUsers" } else { "CurrentUser" }
    } else {
        $Scope
    }
    Write-Host ("[INFO] Effective installation scope: {0}" -f $effectiveScope)
 
    # Pick provider target based on effective scope and elevation
    $targetProviderRoot = if ($isAdmin -or $effectiveScope -eq "AllUsers") {
        Join-Path $Env:ProgramFiles "PackageManagement\ProviderAssemblies\NuGet"
    } else {
        Join-Path $Env:LOCALAPPDATA "PackageManagement\ProviderAssemblies\NuGet"
    }
 
    $providerPresent =
        @(Get-ChildItem -Path $Env:ProgramFiles,$Env:LOCALAPPDATA,$Env:ProgramData `
        -Recurse -ErrorAction SilentlyContinue `
        -Include 'Microsoft.PackageManagement.NuGetProvider.dll','NuGet*.dll').Count -gt 0
 
    if (-not $providerPresent) {
        if (-not (Test-Path $targetProviderRoot)) { New-Item -ItemType Directory -Force -Path $targetProviderRoot | Out-Null }
        Copy-Item -Path (Join-Path $providerDir '*') -Destination $targetProviderRoot -Recurse -Force
    }
 
    # Ensure NuGet provider from Provider if missing
    $nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
    if (-not $nuget) {
        Write-Host "[INFO] NuGet provider not found. Attempting offline bootstrap from Provider folder..."
        if (-not (Test-Path -LiteralPath $providerDir)) {
            throw ("NuGet provider not found. Expected staged provider under '{0}'." -f $providerDir)
        }
        if (-not (Test-Path -LiteralPath $targetProviderRoot)) {
            New-Item -ItemType Directory -Path $targetProviderRoot -Force | Out-Null
        }
        Copy-Item -Path (Join-Path $providerDir "*") -Destination $targetProviderRoot -Recurse -Force -ErrorAction Stop
        $nuget = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
        if (-not $nuget) { throw "NuGet provider bootstrap failed after copy." }
        Write-Host ("[OK] NuGet provider bootstrapped to: {0}" -f $targetProviderRoot)
    } else {
        Write-Host "[OK] NuGet provider is available."
    }
 
    # Register temp repo at Root\Nuget
    $repoName = ("TempRepo_{0}" -f ([Guid]::NewGuid().ToString("N").Substring(0,8)))
    Write-Host ("[INFO] Registering temporary repository '{0}' at: {1}" -f $repoName, $nugetDir)
    Register-PSRepository -Name $repoName -SourceLocation $nugetDir -PublishLocation $nugetDir -InstallationPolicy Trusted
 
    try {
        # Priority install: PackageManagement, then PowerShellGet
        $priority = @("PackageManagement","PowerShellGet")
 
        function Test-PackagePresent([string]$moduleName, [string]$rootNuget) {
            $pattern = Join-Path $rootNuget ("{0}*.nupkg" -f $moduleName)
            return (Test-Path -Path $pattern)
        }
 
        foreach ($m in $priority) {
            if (($Name -contains $m) -or (Test-PackagePresent -moduleName $m -rootNuget $nugetDir)) {
                Write-Host ("[INFO] Installing priority module: {0}" -f $m)
                try {
                    Install-Module -Name $m -Repository $repoName -Scope $effectiveScope -Force -AllowClobber -SkipPublisherCheck -ErrorAction Stop
                    Write-Host ("[OK] Installed: {0}" -f $m)
                } catch {
                    Write-Error ("Failed to install priority module '{0}' from '{1}': {2}" -f $m, $nugetDir, $_.Exception.Message)
                }
            }
        }
 
        # Install remaining requested modules
        $remaining = $Name | Where-Object { $priority -notcontains $_ }
        foreach ($n in $remaining) {
            Write-Host ("[INFO] Installing module: {0}" -f $n)
            try {
                Install-Module -Name $n -Repository $repoName -Scope $effectiveScope -Force -AllowClobber -SkipPublisherCheck -ErrorAction Stop
                Write-Host ("[OK] Installed: {0}" -f $n)
            } catch {
                Write-Error ("Failed to install '{0}' from '{1}': {2}" -f $n, $nugetDir, $_.Exception.Message)
            }
        }
 
        Write-Host "[OK] Installation sequence completed."
    }
    finally {
        Write-Host ("[INFO] Unregistering temporary repository: {0}" -f $repoName)
        try { Unregister-PSRepository -Name $repoName -ErrorAction SilentlyContinue } catch { }
    }
 
    [void](Read-Host "Press Enter to continue")
}
'@


    $namesList = ($Name -join ",")
    $usageLine = 'Install-ModulesFromRepoFolder -Folder "$PSScriptRoot" -Name ' + $namesList

    ($functionText + "`r`n" + $usageLine + "`r`n") | Out-File -FilePath $installerPath -Encoding utf8 -Force

    # NEW: emit a convenience CMD launcher in the root that runs the PS1
    $cmdPath = Join-Path $Folder "Install-ModulesFromRepoFolder.cmd"
    $cmdText = "@echo off`r`n" +
               "setlocal`r`n" +
               "powershell.exe -NoProfile -ExecutionPolicy Unrestricted -File ""%~dp0Install-ModulesFromRepoFolder.ps1""`r`n" +
               "endlocal`r`n"
    $cmdText | Out-File -FilePath $cmdPath -Encoding ASCII -Force

    # Return staged package paths for confirmation
    Get-ChildItem -LiteralPath $nugetDir -Filter *.nupkg | Select-Object -ExpandProperty FullName
}

function Uninstall-PreviousModuleVersions {
<#
.SYNOPSIS
Removes older versions of a PowerShell module, keeping only the latest per scope.
 
.DESCRIPTION
Default Mode=Auto. If the session is elevated (Administrator on Windows or root on Unix), it cleans both CurrentUser and AllUsers.
If not elevated, it cleans only CurrentUser and reports AllUsers installs that cannot be removed.
Works on Windows PowerShell 5.1 and PowerShell 7+ on Windows/Linux/macOS.
 
.PARAMETER ModuleName
Name of the module to clean. Older versions beyond the newest per scope are removed.
 
.PARAMETER Mode
Auto (default), CurrentUser, AllUsers, Both. In non-elevated sessions, AllUsers removals are skipped and reported.
 
.PARAMETER PassThru
Emit a summary of planned/performed actions as objects.
 
.EXAMPLE
Uninstall-PreviousModuleVersions -ModuleName 'Pester'
Cleans old versions in Auto mode.
 
.EXAMPLE
Uninstall-PreviousModuleVersions -ModuleName 'Az' -WhatIf
Shows what would be removed without making changes.
 
.OUTPUTS
System.Object (when -PassThru is used)
 
.NOTES
Honors -WhatIf/-Confirm via SupportsShouldProcess. Keeps exactly one latest version per scope.
#>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [Alias('romv')]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ModuleName,

        [ValidateSet('Auto','CurrentUser','AllUsers','Both')]
        [string]$Mode = 'Auto',

        [switch]$PassThru
    )

    # Fail fast if PowerShellGet isn't present (PS5-safe).
    if (-not (Get-Command Get-InstalledModule -ErrorAction SilentlyContinue)) {
        Write-Error "PowerShellGet is not available (Get-InstalledModule missing). Install/Import PowerShellGet first."
        return
    }

    # --- Elevation + OS detection (names chosen to avoid $IsWindows collision on PS7) ---
    $onWindowsOS = $false
    try { $onWindowsOS = [System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT } catch { $onWindowsOS = $true }

    $isElevated = $false
    try {
        if ($onWindowsOS) {
            $id  = [Security.Principal.WindowsIdentity]::GetCurrent()
            $pri = [Security.Principal.WindowsPrincipal]$id
            $isElevated = $pri.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
        } else {
            $uid = & id -u 2>$null
            if ($LASTEXITCODE -eq 0) { if ([int]$uid -eq 0) { $isElevated = $true } }
        }
    } catch { $isElevated = $false }

    # --- Decide scopes based on Mode + elevation ---
    $scopesToProcess = @()
    switch ($Mode) {
        'Auto'       { if ($isElevated) { $scopesToProcess = @('CurrentUser','AllUsers') } else { $scopesToProcess = @('CurrentUser') } }
        'CurrentUser'{ $scopesToProcess = @('CurrentUser') }
        'AllUsers'   { $scopesToProcess = @('AllUsers') }
        'Both'       { if ($isElevated) { $scopesToProcess = @('CurrentUser','AllUsers') } else { $scopesToProcess = @('CurrentUser') } }
    }

    try {
        $installed = Get-InstalledModule -Name $ModuleName -AllVersions -ErrorAction SilentlyContinue
        if (-not $installed) {
            Write-Host "No installed module found with the name '$ModuleName'." -ForegroundColor Yellow
            return
        }

        # Build candidate user roots from PSModulePath and well-known doc paths (PS5-safe; cross-platform).
        $pathSep = [IO.Path]::PathSeparator
        $modulePaths = @()
        if ($env:PSModulePath) { $modulePaths = $env:PSModulePath -split [regex]::Escape($pathSep) }

        $userHomePath = if ($onWindowsOS) { $env:USERPROFILE } else { $env:HOME }
        if (-not $userHomePath) { try { $userHomePath = [Environment]::GetFolderPath('UserProfile') } catch { $userHomePath = $null } }

        $userDocsPath = $null
        if ($onWindowsOS) { try { $userDocsPath = [Environment]::GetFolderPath('MyDocuments') } catch { $userDocsPath = $null } }

        $candidateUserRoots = @()
        if ($modulePaths) {
            $candidateUserRoots += ($modulePaths | Where-Object { $_ -and $userHomePath -and $_.StartsWith($userHomePath, [System.StringComparison]::OrdinalIgnoreCase) })
        }
        if ($onWindowsOS -and $userDocsPath) {
            $candidateUserRoots += (Join-Path $userDocsPath 'WindowsPowerShell\Modules')
            $candidateUserRoots += (Join-Path $userDocsPath 'PowerShell\Modules')
        }

        $userRoots = @()
        foreach ($r in $candidateUserRoots) {
            if ($r) { try { $userRoots += [IO.Path]::GetFullPath($r) } catch { $userRoots += $r } }
        }
        $userRoots = $userRoots | Sort-Object -Unique

        # Annotate each install with inferred scope (CurrentUser if under user roots; otherwise AllUsers).
        $annotated = foreach ($m in $installed) {
            $p = $m.InstalledLocation
            try { if ($p) { $p = [IO.Path]::GetFullPath($p) } } catch { }

            $isUserScope = $false
            foreach ($ur in $userRoots) {
                if ($ur -and $p -and $p.StartsWith($ur, $true, [Globalization.CultureInfo]::InvariantCulture)) {
                    $isUserScope = $true
                    break
                }
            }

            # PS5-safe: compute value first (avoid inline 'if' in hashtable)
            $scopeLabel = 'AllUsers'
            if ($isUserScope) { $scopeLabel = 'CurrentUser' }

            [pscustomobject]@{
                Name              = $m.Name
                Version           = $m.Version
                InstalledLocation = $p
                Scope             = $scopeLabel
            }
        }

        # --- Extra report for AllUsers when that scope isn't processed (e.g., not elevated in Auto) ---
        if ( ($annotated | Where-Object Scope -eq 'AllUsers') -and -not ($scopesToProcess -contains 'AllUsers') ) {
            $allAU   = $annotated | Where-Object Scope -eq 'AllUsers' | Sort-Object Version -Descending
            $auLatest = $allAU[0]
            $auOld    = $allAU | Select-Object -Skip 1

            $reason = if ($isElevated) { 'skipped by Mode' } else { 'not elevated' }

            if ($auLatest) {
                Write-Host ("[AllUsers] {0}: will retain latest v{1} at '{2}' (cannot modify AllUsers now)." -f $reason, $auLatest.Version, $auLatest.InstalledLocation) -ForegroundColor Yellow
            }

            if ($auOld) {
                $versions = ($auOld | Select-Object -ExpandProperty Version) -join ', '
                Write-Host ("[AllUsers] Skipped removals (require elevation): {0}" -f $versions) -ForegroundColor Yellow
                foreach ($i in $auOld) {
                    Write-Host (" - v{0} at '{1}'" -f $i.Version, $i.InstalledLocation) -ForegroundColor DarkYellow
                    if ($PassThru) { $summary += [pscustomobject]@{ Scope='AllUsers'; Version=$i.Version; Action='Skipped (NotProcessed)'; Path=$i.InstalledLocation } }
                }
                if ($PassThru -and $auLatest) {
                    $summary += [pscustomobject]@{ Scope='AllUsers'; Version=$auLatest.Version; Action='Retained (Latest, NotProcessed)'; Path=$auLatest.InstalledLocation }
                }
            } else {
                Write-Host "[AllUsers] Only one version present; nothing to remove in AllUsers." -ForegroundColor Yellow
                if ($PassThru -and $auLatest) {
                    $summary += [pscustomobject]@{ Scope='AllUsers'; Version=$auLatest.Version; Action='Retained (Only Version, NotProcessed)'; Path=$auLatest.InstalledLocation }
                }
            }
        }

        # Clear notice if we're non-elevated and not processing AllUsers, but such installs exist.
        $hasAllUsers = ($annotated | Where-Object Scope -eq 'AllUsers')
        if (-not $isElevated -and $hasAllUsers -and -not ($scopesToProcess -contains 'AllUsers')) {
            $v = ($hasAllUsers | Sort-Object Version -Descending | Select-Object -ExpandProperty Version) -join ', '
            Write-Host ("AllUsers installs detected for '{0}': {1}. Run elevated (Admin/root) to remove them." -f $ModuleName, $v) -ForegroundColor Yellow
        }

        $summary = @()

        foreach ($scope in $scopesToProcess) {
            $inScope = $annotated | Where-Object Scope -eq $scope
            if (-not $inScope) {
                Write-Host "[$scope] No installs found for '$ModuleName'." -ForegroundColor Yellow
                continue
            }

            # Keep exactly the newest version in this scope; remove all others.
            $sorted = $inScope | Sort-Object Version -Descending
            $latest = $sorted[0]
            $old    = $sorted | Select-Object -Skip 1

            if (-not $old) {
                Write-Host "[$scope] Only one version present (v$($latest.Version)). Nothing to remove in $scope." -ForegroundColor Green
                continue
            }

            Write-Host ("[{0}] Retaining latest v{1} at '{2}'." -f $scope, $latest.Version, $latest.InstalledLocation) -ForegroundColor Cyan

            foreach ($item in $old) {
                $target = "{0} v{1} ({2})" -f $item.Name, $item.Version, $scope
                if ($PSCmdlet.ShouldProcess($target, 'Uninstall-Module')) {
                    try {
                        Uninstall-Module -Name $item.Name -RequiredVersion $item.Version -Force -ErrorAction Stop
                        Write-Host ("[{0}] Removed v{1} from '{2}'." -f $scope, $item.Version, $item.InstalledLocation) -ForegroundColor Green
                        if ($PassThru) { $summary += [pscustomobject]@{ Scope=$scope; Version=$item.Version; Action='Removed'; Path=$item.InstalledLocation } }
                    } catch {
                        Write-Error ("[{0}] Failed to remove {1} v{2}: {3}" -f $scope, $item.Name, $item.Version, $_.Exception.Message)
                        if ($PassThru) { $summary += [pscustomobject]@{ Scope=$scope; Version=$item.Version; Action='Error'; Path=$item.InstalledLocation; Error=$_.Exception.Message } }
                    }
                } else {
                    if ($PassThru) { $summary += [pscustomobject]@{ Scope=$scope; Version=$item.Version; Action='Planned (WhatIf)'; Path=$item.InstalledLocation } }
                }
            }
        }

        Write-Host ("Cleanup complete (mode: {0}, elevated: {1}, processed scopes: {2})." -f $Mode, $isElevated, ($scopesToProcess -join ', ')) -ForegroundColor Green
        if ($PassThru) { $summary }
    }
    catch {
        Write-Error "An error occurred while removing old versions for '$ModuleName': $($_.Exception.Message)"
    }
}

function Find-ModuleScopeClutter {
<#
.SYNOPSIS
Detects modules that are installed in both user and system scopes.
 
.DESCRIPTION
Scans all discoverable modules (Get-Module -ListAvailable), classifies each module's path
as CurrentUser or AllUsers by comparing against user-owned roots derived from PSModulePath
and common platform locations, and outputs module names that appear in both scopes.
Default output is just the module names (unique, sorted) for easy piping.
 
.PARAMETER Detailed
If set, also writes a human-readable table showing per-scope versions and paths.
 
.PARAMETER PassThru
If set, returns rich objects with Name and grouped details; otherwise writes names to the pipeline.
 
.EXAMPLE
Find-ModuleScopeClutter
# Prints only module names that are installed in both scopes.
 
.EXAMPLE
Find-ModuleScopeClutter -Detailed
# Prints a readable table with versions/paths per scope, plus the names to the pipeline.
 
.OUTPUTS
System.String (default) or System.Object (with -PassThru)
 
.NOTES
- PS5-compatible; no reliance on $IsWindows/$IsLinux/$IsMacOS.
- External-reviewer note: Classification is heuristic (based on user-home prefixes); uncommon custom roots may classify as AllUsers.
- Silent skip on Windows: PSReadLine and Pester are ignored to reduce noise from in-box modules.
#>

    [CmdletBinding()]
    param(
        [switch]$Detailed,
        [switch]$PassThru
    )

    # Reviewer: Avoid helper functions per user's style; keep logic inline and PS5-safe.

    # OS hint (avoid $IsWindows collision on PS7 by using our own name).
    $onWindowsOS = $false
    try { $onWindowsOS = [System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT } catch { $onWindowsOS = $true }

    # Gather candidate "user roots" from PSModulePath + well-known locations.
    $pathSep      = [IO.Path]::PathSeparator
    $modulePaths  = @()
    if ($env:PSModulePath) { $modulePaths = $env:PSModulePath -split [regex]::Escape($pathSep) }

    # Derive user home in a PS5-friendly way.
    $userHomePath = if ($onWindowsOS) { $env:USERPROFILE } else { $env:HOME }
    if (-not $userHomePath) { try { $userHomePath = [Environment]::GetFolderPath('UserProfile') } catch { $userHomePath = $null } }

    # Windows: include common Documents-based user module roots (these are typical on WinPS 5.1).
    $userDocsPath = $null
    if ($onWindowsOS) { try { $userDocsPath = [Environment]::GetFolderPath('MyDocuments') } catch { $userDocsPath = $null } }

    $candidateUserRoots = @()
    if ($modulePaths) {
        # Any PSModulePath entry under the user's home is considered "user scope".
        $candidateUserRoots += ($modulePaths | Where-Object {
            $_ -and $userHomePath -and $_.StartsWith($userHomePath, [System.StringComparison]::OrdinalIgnoreCase)
        })
    }
    if ($onWindowsOS -and $userDocsPath) {
        $candidateUserRoots += (Join-Path $userDocsPath 'WindowsPowerShell\Modules')
        $candidateUserRoots += (Join-Path $userDocsPath 'PowerShell\Modules')
    }

    # Normalize and distinct user roots.
    $userRoots = @()
    foreach ($r in $candidateUserRoots) {
        if ($r) {
            try { $userRoots += [IO.Path]::GetFullPath($r) } catch { $userRoots += $r }
        }
    }
    $userRoots = $userRoots | Sort-Object -Unique

    # Enumerate all available modules from every path.
    $all = Get-Module -ListAvailable | Select-Object Name, Version, ModuleBase

    # Annotate each with inferred scope (PS5-safe StartsWith overload).
    $annotated = foreach ($m in $all) {
        $base = $m.ModuleBase
        try { if ($base) { $base = [IO.Path]::GetFullPath($base) } } catch { }

        $isUser = $false
        foreach ($ur in $userRoots) {
            if ($ur -and $base -and $base.StartsWith($ur, $true, [Globalization.CultureInfo]::InvariantCulture)) {
                $isUser = $true; break
            }
        }

        # Note: if we can't map to a user root, treat as AllUsers by default.
        $scopeLabel = if ($isUser) { 'CurrentUser' } else { 'AllUsers' }

        # Select minimal fields; duplicates (same Name/Version/Path) are harmless.
        [pscustomobject]@{
            Name       = $m.Name
            Version    = $m.Version
            ModuleBase = $base
            Scope      = $scopeLabel
        }
    }

    # Group by name and keep only those that appear in both scopes.
    $clutter = $annotated |
        Group-Object Name |
        Where-Object { ($_.Group.Scope | Select-Object -Unique).Count -ge 2 }

    # --- Minimal change: silently ignore common in-box modules on Windows to avoid confusion.
    if ($onWindowsOS) {
        $skip = @('PSReadLine','Pester')
        $clutter = $clutter | Where-Object { $skip -notcontains $_.Name }
    }
    # ---

    if (-not $clutter) {
        Write-Host "No modules found installed in both user and system scopes." -ForegroundColor Green
        return
    }

    # Default output: just the names (unique, sorted).
    $names = $clutter | Select-Object -ExpandProperty Name | Sort-Object -Unique

    if ($Detailed) {
        # Reviewer: Provide concise per-scope detail without overwhelming the user.
        foreach ($g in $clutter) {
            $userSide = $g.Group | Where-Object Scope -eq 'CurrentUser' | Sort-Object Version -Descending
            $sysSide  = $g.Group | Where-Object Scope -eq 'AllUsers'   | Sort-Object Version -Descending

            $uVers = if ($userSide) { ($userSide | Select-Object -Expand Version) -join ', ' } else { '-' }
            $sVers = if ($sysSide)  { ($sysSide  | Select-Object -Expand Version) -join ', ' } else { '-' }

            Write-Host ("{0}`n CurrentUser: {1}`n AllUsers : {2}" -f $g.Name, $uVers, $sVers) -ForegroundColor Yellow
        }
    }

    if ($PassThru) {
        # Return rich objects if desired (useful for CI/pipelines).
        $objects = foreach ($g in $clutter) {
            [pscustomobject]@{
                Name         = $g.Name
                CurrentUser  = $g.Group | Where-Object Scope -eq 'CurrentUser' | Sort-Object Version -Descending | Select-Object Name,Version,ModuleBase,Scope
                AllUsers     = $g.Group | Where-Object Scope -eq 'AllUsers'    | Sort-Object Version -Descending | Select-Object Name,Version,ModuleBase,Scope
            }
        }
        $objects
    } else {
        # Emit names to pipeline (so caller can pipe into Remove-OldModuleVersions).
        $names
    }
}

function Update-ManifestModuleVersion {
    <#
    .SYNOPSIS
        Updates the ModuleVersion in a PowerShell module manifest (psd1) file.
 
    .DESCRIPTION
        This function reads a PowerShell module manifest file as text, uses a regular expression to update the
        ModuleVersion value while preserving the file's comments and formatting, and writes the updated content back
        to the file. If a directory path is supplied, the function recursively searches for the first *.psd1 file and uses it.
 
    .PARAMETER ManifestPath
        The file or directory path to the module manifest (psd1) file. If a directory is provided, the function will
        search recursively for the first *.psd1 file.
 
    .PARAMETER NewVersion
        The new version string to set for the ModuleVersion property.
 
    .EXAMPLE
        PS C:\> Update-ManifestModuleVersion -ManifestPath "C:\projects\MyDscModule" -NewVersion "2.0.0"
        Updates the ModuleVersion of the first PSD1 manifest found in the given directory to "2.0.0".
    #>

    [CmdletBinding()]
    [alias("ummv")]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ManifestPath,

        [Parameter(Mandatory = $true)]
        [string]$NewVersion
    )

    # Check if the provided path exists
    if (-not (Test-Path $ManifestPath)) {
        throw "The path '$ManifestPath' does not exist."
    }

    # If the path is a directory, search recursively for the first *.psd1 file.
    $item = Get-Item $ManifestPath
    if ($item.PSIsContainer) {
        $psd1File = Get-ChildItem -Path $ManifestPath -Filter *.psd1 -Recurse | Select-Object -First 1
        if (-not $psd1File) {
            throw "No PSD1 manifest file found in directory '$ManifestPath'."
        }
        $ManifestPath = $psd1File.FullName
    }

    Write-Verbose "Using manifest file: $ManifestPath"

    # Read the manifest file content as text using .NET method.
    $content = [System.IO.File]::ReadAllText($ManifestPath)

    # Define the regex pattern to locate the ModuleVersion value.
    $pattern = "(?<=ModuleVersion\s*=\s*')[^']+(?=')"

    # Replace the current version with the new version using .NET regex.
    $updatedContent = [System.Text.RegularExpressions.Regex]::Replace($content, $pattern, $NewVersion)

    # Write the updated content back to the manifest file.
    [System.IO.File]::WriteAllText($ManifestPath, $updatedContent)
}

function Update-ManifestReleaseNotes {
<#
.SYNOPSIS
    Updates the ReleaseNotes value in a PowerShell module manifest (psd1).
 
.DESCRIPTION
    Reads the manifest as raw text and replaces only the ReleaseNotes value inside PrivateData.PSData.
    Supports single-quoted, double-quoted, and here-string forms while preserving comments and formatting.
    No extra helpers; compatible with Windows PowerShell 5.1 and PowerShell 7+.
 
.PARAMETER ManifestPath
    File or directory path. If a directory is provided, the first *.psd1 found recursively is used.
 
.PARAMETER NewReleaseNotes
    The new ReleaseNotes text. Multiline supported.
 
.EXAMPLE
    Update-ManifestReleaseNotes -ManifestPath .\MyModule -NewReleaseNotes "Fixed bugs; improved logging."
#>

    [CmdletBinding()]
    [Alias('umrn')]
    param(
        [Parameter(Mandatory)]
        [string]$ManifestPath,
        [Parameter(Mandatory)]
        [string]$NewReleaseNotes
    )

    # Resolve a concrete psd1 path (inline; no helper functions)
    if (-not (Test-Path -LiteralPath $ManifestPath)) {
        throw "The path '$ManifestPath' does not exist."
    }
    $item = Get-Item -LiteralPath $ManifestPath
    if ($item.PSIsContainer) {
        $psd1 = Get-ChildItem -LiteralPath $ManifestPath -Filter *.psd1 -Recurse | Select-Object -First 1
        if (-not $psd1) { throw "No PSD1 manifest file found under '$ManifestPath'." }
        $ManifestPath = $psd1.FullName
    }

    # Read, replace, write (keep it simple to match your original style)
    $content = [System.IO.File]::ReadAllText($ManifestPath)

    # Define patterns that capture prefix/content/suffix so we can rebuild safely (avoids replacement-string $ pitfalls).
    $opts = [System.Text.RegularExpressions.RegexOptions]::Singleline -bor
            [System.Text.RegularExpressions.RegexOptions]::IgnoreCase  -bor
            [System.Text.RegularExpressions.RegexOptions]::CultureInvariant

    $defs = @(
        @{ Style='hsq'; Pattern='(?s)(?<prefix>\bReleaseNotes\s*=\s*@'')(?<content>.*?)(?<suffix>''@)' }  # @' ... '@
        @{ Style='hdq'; Pattern='(?s)(?<prefix>\bReleaseNotes\s*=\s*@"")(?<content>.*?)(?<suffix>""@)' }  # @" ... "@
        @{ Style='sq' ; Pattern='(?<prefix>\bReleaseNotes\s*=\s*'')(?<content>(?:''''|[^''])*)(?<suffix>'')' } # '...'
        @{ Style='dq' ; Pattern='(?<prefix>\bReleaseNotes\s*=\s*"")(?<content>(?:``.|`"|[^""])*?)(?<suffix>"")' } # "..."
    )

    $updated = $false
    foreach ($d in $defs) {
        $rx = [System.Text.RegularExpressions.Regex]::new($d.Pattern, $opts)
        if ($rx.IsMatch($content)) {
            $content = $rx.Replace($content, {
                param($m)
                switch ($d.Style) {
                    'sq'  { $enc = $NewReleaseNotes -replace "'", "''" }               # Single-quoted: double single quotes
                    'dq'  { $t = $NewReleaseNotes -replace '`','``'; $t = $t -replace '"','`"'; $enc = $t -replace '\$','`$' }
                    'hsq' { $enc = $NewReleaseNotes }                                   # Single-quoted here-string: literal
                    'hdq' { $t = $NewReleaseNotes -replace '`','``'; $t = $t -replace '"','`"'; $enc = $t -replace '\$','`$' }
                }
                # Rebuild exact structure to keep whitespace/comments intact
                $m.Groups['prefix'].Value + $enc + $m.Groups['suffix'].Value
            }, 1)
            $updated = $true
            break
        }
    }

    if (-not $updated) {
        throw "Could not locate a 'ReleaseNotes' assignment (supported forms: quoted or here-string)."
    }

    [System.IO.File]::WriteAllText($ManifestPath, $content)
}

function Update-ManifestPrerelease {
<#
.SYNOPSIS
    Updates the Prerelease value in a PowerShell module manifest (psd1).
 
.DESCRIPTION
    - If -NewPrerelease is non-empty, replaces only the value and preserves the original quoting style
      (single-quoted, double-quoted, or here-string).
    - If -NewPrerelease is '' (empty) or $null, keeps the 'Prerelease' assignment but sets it to an
      empty string: Prerelease = '' (for here-strings, converts to a single-quoted empty value).
    - Raw-text approach; PS 5.1 and 7+ compatible.
 
.PARAMETER ManifestPath
    File or directory path. If a directory is provided, the first *.psd1 found recursively is used.
 
.PARAMETER NewPrerelease
    New prerelease label (e.g. "preview1", "beta.2", "rc.1").
    Pass empty string "" to set an EMPTY value (release) while keeping the key.
 
.EXAMPLE
    Update-ManifestPrerelease -ManifestPath .\MyModule\MyModule.psd1 -NewPrerelease "beta.2"
 
.EXAMPLE
    # Keep the key but make it empty
    Update-ManifestPrerelease -ManifestPath .\MyModule\MyModule.psd1 -NewPrerelease ""
#>

    [CmdletBinding()]
    [Alias('umpr')]
    param(
        [Parameter(Mandatory)]
        [string]$ManifestPath,
        [Parameter(Mandatory)]
        [AllowEmptyString()]
        [AllowNull()]
        [string]$NewPrerelease
    )

    if (-not (Test-Path -LiteralPath $ManifestPath)) {
        throw "The path '$ManifestPath' does not exist."
    }
    $item = Get-Item -LiteralPath $ManifestPath
    if ($item.PSIsContainer) {
        $psd1 = Get-ChildItem -LiteralPath $ManifestPath -Filter *.psd1 -Recurse | Select-Object -First 1
        if (-not $psd1) { throw "No PSD1 manifest file found under '$ManifestPath'." }
        $ManifestPath = $psd1.FullName
    }

    $content  = [System.IO.File]::ReadAllText($ManifestPath)
    $original = $content

    $optsAll  = [System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor
                [System.Text.RegularExpressions.RegexOptions]::CultureInvariant
    $optsLine = $optsAll -bor [System.Text.RegularExpressions.RegexOptions]::Multiline
    $optsHere = $optsLine -bor [System.Text.RegularExpressions.RegexOptions]::Singleline

    # Precompute escapes for non-empty replacements
    $encSQ = if ([string]::IsNullOrEmpty($NewPrerelease)) { '' } else { $NewPrerelease -replace "'", "''" }
    $encDQ = if ([string]::IsNullOrEmpty($NewPrerelease)) { '' } else { $NewPrerelease -replace '(["`$])','`$1' }

    # Patterns in one list; we stop on the first match.
    $defs = @(
        # Here-strings first (line-anchored)
        @{ Style='hsq'; Options=$optsHere; Pattern='^(?<indent>[ \t]*)\bPrerelease\s*=\s*@''(?<content>.*?)''@(?<trail>[ \t]*#.*)?(?<nl>\r?\n?)' }
        @{ Style='hdq'; Options=$optsHere; Pattern='^(?<indent>[ \t]*)\bPrerelease\s*=\s*@"(?<content>.*?)"@(?<trail>[ \t]*#.*)?(?<nl>\r?\n?)' }
        # Quoted single-line
        @{ Style='sq' ; Options=$optsLine; Pattern='(?<prefix>\bPrerelease\s*=\s*'')(?<content>(?:''''|[^''])*)(?<suffix>'')' }
        @{ Style='dq' ; Options=$optsLine; Pattern='(?<prefix>\bPrerelease\s*=\s*")(?<content>(?:``.|`"|[^"])*)?(?<suffix>")' }
    )

    foreach ($d in $defs) {
        $rx = [regex]::new($d.Pattern, $d.Options)
        if (-not $rx.IsMatch($content)) { continue }

        $content = $rx.Replace($content, {
            param($m)

            switch ($d.Style) {
                # Here-strings: if empty -> convert to single-quoted empty; else keep HS and inject content.
                'hsq' {
                    if ([string]::IsNullOrEmpty($NewPrerelease)) {
                        $m.Groups['indent'].Value + "Prerelease = ''" + $m.Groups['trail'].Value + $m.Groups['nl'].Value
                    }
                    else {
                        $m.Groups['indent'].Value + "Prerelease = @'"+ $m.Groups['content'].Value.GetType() | Out-Null
                        "@"
                    }
                }
                'hdq' {
                    if ([string]::IsNullOrEmpty($NewPrerelease)) {
                        $m.Groups['indent'].Value + "Prerelease = ''" + $m.Groups['trail'].Value + $m.Groups['nl'].Value
                    }
                    else {
                        $m.Groups['indent'].Value + "@"
                        # We escape for double-quoted HS (expandable)
                        "Prerelease = @""$encDQ""@" + $m.Groups['trail'].Value + $m.Groups['nl'].Value
                    }
                }
                # Quoted forms: keep the quote style, replace inside the quotes.
                'sq' { $m.Groups['prefix'].Value + $encSQ + $m.Groups['suffix'].Value }
                'dq' { $m.Groups['prefix'].Value + $encDQ + $m.Groups['suffix'].Value }
            }
        }, 1)

        break
    }

    if ($content -eq $original) {
        throw "Could not locate a 'Prerelease' assignment (supported forms: quoted or here-string)."
    }

    if ($content -ne $original) {
        [System.IO.File]::WriteAllText($ManifestPath, $content)
    }
}