Commands/Endpoint/Eigenverft.Manifested.Sandbox.Cmd.PackageEndpoint.ps1

<#
    Public package definition endpoint (scan root) management surface.
 
    Parameters named EndpointName identify a row by endpointName in PackageEndpointInventory.json.
    Invoke-Package -PublisherId is unrelated: it filters package definitions by trusted publisher identity.
#>


function Get-PackageEndpoint {
    [CmdletBinding()]
    param(
        [string]$EndpointName
    )

    $endpoints = @(Get-PackageEndpointSummaries)
    if ([string]::IsNullOrWhiteSpace($EndpointName)) {
        return $endpoints
    }

    $match = @($endpoints | Where-Object { [string]::Equals([string]$_.EndpointName, $EndpointName, [System.StringComparison]::OrdinalIgnoreCase) })
    if ($match.Count -eq 0) {
        throw "Package endpoint '$EndpointName' was not found in '$((Get-PackageEndpointInventoryPath))'."
    }

    return $match
}

function Add-PackageEndpoint {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$EndpointName,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$BasePath,

        [int]$SearchOrder = -1,

        [string]$After,

        [switch]$Disabled,

        [switch]$TrustUnsigned,

        [AllowNull()]
        [string]$TrustReason = $null
    )

    Assert-PackageEndpointName -EndpointName $EndpointName

    $documentInfo = Get-PackageEndpointInventoryEditInfo
    if (Get-PackageEndpointSourceProperty -Document $documentInfo.Document -EndpointName $EndpointName) {
        throw "Package endpoint '$EndpointName' already exists in '$($documentInfo.Path)'. Use Set-PackageEndpoint to modify it."
    }

    $resolvedSearchOrder = if ($SearchOrder -ge 0) {
        $SearchOrder
    }
    elseif (-not [string]::IsNullOrWhiteSpace($After)) {
        Get-PackageEndpointSearchOrderAfter -Document $documentInfo.Document -AfterEndpointName $After
    }
    else {
        Get-PackageNextEndpointSearchOrder -Document $documentInfo.Document
    }
    $resolvedTrustReason = if ($TrustUnsigned.IsPresent) {
        if (-not [string]::IsNullOrWhiteSpace($TrustReason)) {
            [string]$TrustReason
        }
        else {
            'Trusted by Add-PackageEndpoint -TrustUnsigned.'
        }
    }
    else {
        $null
    }
    $source = New-PackageFilesystemRepositorySource -EndpointName $EndpointName -BasePath $BasePath -SearchOrder $resolvedSearchOrder -Enabled (-not $Disabled.IsPresent) -Trusted $TrustUnsigned.IsPresent -TrustReason $resolvedTrustReason

    if ($PSCmdlet.ShouldProcess($documentInfo.Path, "Add package endpoint '$EndpointName'")) {
        $documentInfo.Document.endpoints = @($documentInfo.Document.endpoints) + $source
        Save-PackageEndpointInventoryDocument -DocumentInfo $documentInfo
    }

    $afterSummary = Get-PackageEndpoint -EndpointName $EndpointName
    $notes = New-Object System.Collections.Generic.List[string]
    if ($Disabled.IsPresent) {
        $notes.Add("Endpoint '$EndpointName' was added disabled; package commands will not use it until enabled.") | Out-Null
    }
    if ($TrustUnsigned.IsPresent) {
        $notes.Add("Endpoint '$EndpointName' trusts unsigned filesystem definitions by explicit local configuration.") | Out-Null
    }
    else {
        $notes.Add("Endpoint '$EndpointName' was added untrusted; run Trust-PackageEndpoint -EndpointName '$EndpointName' -AllowUnsignedDefinitions before executing definitions from it.") | Out-Null
    }

    return New-PackageEndpointCommandResult -Action 'Add' -EndpointName $EndpointName -InventoryPath $documentInfo.Path -Before $null -After $afterSummary -Status 'Added' -Notes @($notes.ToArray())
}

function Add-TeamPackageEndpoint {
    <#
    .NOTES
    By default the team endpoint is added trusted for unsigned filesystem definitions so offline
    team shares work without a separate Trust-PackageEndpoint step. Use -Untrusted to require an
    explicit Trust-PackageEndpoint -AllowUnsignedDefinitions later (stricter workflow).
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$BasePath,

        [ValidateNotNullOrEmpty()]
        [string]$EndpointName = 'teamPackageRepository',

        [int]$SearchOrder = -1,

        [string]$After,

        [switch]$Disabled,

        [switch]$Untrusted
    )

    $parameters = @{
        EndpointName = $EndpointName
        BasePath     = $BasePath
    }
    if ($SearchOrder -ge 0) {
        $parameters.SearchOrder = $SearchOrder
    }
    elseif (-not [string]::IsNullOrWhiteSpace($After)) {
        $parameters.After = $After
    }
    else {
        $parameters.SearchOrder = 150
    }
    if ($Disabled.IsPresent) {
        $parameters.Disabled = $true
    }
    if (-not $Untrusted.IsPresent) {
        $parameters.TrustUnsigned = $true
        $parameters.TrustReason = 'Trusted by Add-TeamPackageEndpoint (team share default).'
    }
    if ($PSBoundParameters.ContainsKey('WhatIf')) {
        $parameters.WhatIf = [bool]$PSBoundParameters.WhatIf
    }
    if ($PSBoundParameters.ContainsKey('Confirm')) {
        $parameters.Confirm = [bool]$PSBoundParameters.Confirm
    }

    return Add-PackageEndpoint @parameters
}

function Set-PackageEndpoint {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$EndpointName,

        [string]$BasePath,

        [Nullable[int]]$SearchOrder,

        [switch]$Enable,

        [switch]$Disable,

        [switch]$Untrust
    )

    Assert-PackageEndpointName -EndpointName $EndpointName
    if ($Enable.IsPresent -and $Disable.IsPresent) { throw 'Use either -Enable or -Disable, not both.' }

    $before = Get-PackageEndpoint -EndpointName $EndpointName
    $documentInfo = Get-PackageEndpointInventoryEditInfo
    $sourceProperty = Get-PackageEndpointSourceProperty -Document $documentInfo.Document -EndpointName $EndpointName
    if (-not $sourceProperty) {
        throw "Package endpoint '$EndpointName' was not found in '$($documentInfo.Path)'. Use Add-PackageEndpoint to create it."
    }

    $source = $sourceProperty.Value
    if ($PSBoundParameters.ContainsKey('BasePath')) {
        if (-not [string]::Equals([string]$source.kind, 'filesystem', [System.StringComparison]::OrdinalIgnoreCase)) {
            throw "Package endpoint '$EndpointName' is kind '$($source.kind)'. Only filesystem endpoints support -BasePath."
        }
        $source | Add-Member -MemberType NoteProperty -Name 'basePath' -Value $BasePath -Force
    }
    if ($PSBoundParameters.ContainsKey('SearchOrder')) { $source | Add-Member -MemberType NoteProperty -Name 'searchOrder' -Value ([int]$SearchOrder.Value) -Force }
    if ($Enable.IsPresent) { $source | Add-Member -MemberType NoteProperty -Name 'enabled' -Value $true -Force }
    if ($Disable.IsPresent) { $source | Add-Member -MemberType NoteProperty -Name 'enabled' -Value $false -Force }
    if ($Untrust.IsPresent) {
        if ([string]::Equals([string]$source.kind, 'moduleLocal', [System.StringComparison]::OrdinalIgnoreCase)) {
            throw "Package endpoint '$EndpointName' is moduleLocal and cannot be untrusted."
        }
        $source | Add-Member -MemberType NoteProperty -Name 'trusted' -Value $false -Force
        $source | Add-Member -MemberType NoteProperty -Name 'trustMode' -Value 'unsigned' -Force
        if ($source.PSObject.Properties['trustedAtUtc']) { $source.PSObject.Properties.Remove('trustedAtUtc') }
        if ($source.PSObject.Properties['trustReason']) { $source.PSObject.Properties.Remove('trustReason') }
    }

    if ($PSCmdlet.ShouldProcess($documentInfo.Path, "Set package endpoint '$EndpointName'")) {
        Save-PackageEndpointInventoryDocument -DocumentInfo $documentInfo
    }

    $after = Get-PackageEndpoint -EndpointName $EndpointName
    $notes = New-Object System.Collections.Generic.List[string]
    if (-not [bool]$after.Enabled) {
        $notes.Add("Endpoint '$EndpointName' was updated but remains disabled; package commands will not use it until enabled.") | Out-Null
    }
    if (-not [bool]$after.Trusted) {
        $notes.Add("Endpoint '$EndpointName' is untrusted; package commands will not execute definitions from it.") | Out-Null
    }
    if ($PSBoundParameters.ContainsKey('BasePath')) {
        $notes.Add("Endpoint '$EndpointName' base path is now '$($after.BasePath)' and resolves to '$($after.ResolvedRootPath)'.") | Out-Null
    }

    return New-PackageEndpointCommandResult -Action 'Set' -EndpointName $EndpointName -InventoryPath $documentInfo.Path -Before $before -After $after -Status 'Updated' -Notes @($notes.ToArray())
}

function Trust-PackageEndpoint {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$EndpointName,

        [switch]$AllowUnsignedDefinitions
    )

    Assert-PackageEndpointName -EndpointName $EndpointName
    if (-not $AllowUnsignedDefinitions.IsPresent) {
        throw "Trust-PackageEndpoint v1 supports only explicit unsigned filesystem trust. Re-run with -AllowUnsignedDefinitions if this is intentional."
    }

    $before = Get-PackageEndpoint -EndpointName $EndpointName
    $documentInfo = Get-PackageEndpointInventoryEditInfo
    $sourceProperty = Get-PackageEndpointSourceProperty -Document $documentInfo.Document -EndpointName $EndpointName
    if (-not $sourceProperty) {
        throw "Package endpoint '$EndpointName' was not found in '$($documentInfo.Path)'."
    }

    $source = $sourceProperty.Value
    if (-not [string]::Equals([string]$source.kind, 'filesystem', [System.StringComparison]::OrdinalIgnoreCase)) {
        throw "Package endpoint '$EndpointName' is kind '$($source.kind)'. Trust-PackageEndpoint v1 only supports filesystem endpoints."
    }

    $source | Add-Member -MemberType NoteProperty -Name 'trusted' -Value $true -Force
    $source | Add-Member -MemberType NoteProperty -Name 'trustMode' -Value 'unsignedExplicit' -Force
    $source | Add-Member -MemberType NoteProperty -Name 'trustedAtUtc' -Value ([DateTime]::UtcNow.ToString('o')) -Force
    $source | Add-Member -MemberType NoteProperty -Name 'trustReason' -Value 'Trusted by Trust-PackageEndpoint -AllowUnsignedDefinitions.' -Force

    if ($PSCmdlet.ShouldProcess($documentInfo.Path, "Trust package endpoint '$EndpointName'")) {
        Save-PackageEndpointInventoryDocument -DocumentInfo $documentInfo
    }

    $after = Get-PackageEndpoint -EndpointName $EndpointName
    return New-PackageEndpointCommandResult -Action 'Trust' -EndpointName $EndpointName -InventoryPath $documentInfo.Path -Before $before -After $after -Status 'Trusted' -Notes @(
        "Endpoint '$EndpointName' now trusts unsigned filesystem definitions by explicit local configuration."
    )
}

function Remove-PackageEndpoint {
    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$EndpointName,

        [switch]$Force
    )

    Assert-PackageEndpointName -EndpointName $EndpointName
    if ([string]::Equals($EndpointName, (Get-PackageDefaultEndpointName), [System.StringComparison]::OrdinalIgnoreCase) -and -not $Force.IsPresent) {
        throw "Removing '$EndpointName' can leave Package without its shipped definition scan endpoint. Re-run with -Force if this is intentional."
    }

    $before = Get-PackageEndpoint -EndpointName $EndpointName
    $documentInfo = Get-PackageEndpointInventoryEditInfo
    $sourceProperty = Get-PackageEndpointSourceProperty -Document $documentInfo.Document -EndpointName $EndpointName
    if (-not $sourceProperty) {
        throw "Package endpoint '$EndpointName' was not found in '$($documentInfo.Path)'."
    }

    if ($PSCmdlet.ShouldProcess($documentInfo.Path, "Remove package endpoint '$EndpointName'")) {
        $documentInfo.Document.endpoints = @($documentInfo.Document.endpoints | Where-Object {
                -not ([string]::Equals([string]$_.endpointName, $EndpointName, [System.StringComparison]::OrdinalIgnoreCase))
            })
        Save-PackageEndpointInventoryDocument -DocumentInfo $documentInfo
    }

    return New-PackageEndpointCommandResult -Action 'Remove' -EndpointName $EndpointName -InventoryPath $documentInfo.Path -Before $before -After $null -Status 'Removed' -Notes @(
        "Endpoint '$EndpointName' was removed from configuration only. Repository files, installed packages, and local definition snapshots were not deleted."
    )
}