Public/Export-OpmApplicationFragment.ps1

<#
.SYNOPSIS
  Save a PacKit application fragment to a .packit metadata folder (or a file).
 
.DESCRIPTION
  Serializes a 'PacKit.ApplicationFragment' to the exact PacKit on-disk format
  (UTF-8 no BOM, CRLF, byte-faithful) so the PacKit app can load it.
 
  Two ways to choose the destination:
    -SourceFolder <dir> (default) -> writes <dir>\.packit\<AppId>.xml, creating
                                      the .packit folder if needed. This is the
                                      "instrument an application" target.
    -LiteralPath <file> -> writes exactly that file.
 
  By default path-bearing attributes are written verbatim, guaranteeing a clean
  Import->Export round-trip. Pass -RelativizePaths to rewrite any ABSOLUTE
  IconPath / detection-script / package Path / SourceFolder relative to the
  .packit folder (matching how PacKit itself stores them); already-relative
  values are left untouched and the input object is never mutated.
 
.EXAMPLE
  $app | Export-OpmApplicationFragment -SourceFolder 'C:\src\Acme'
 
.EXAMPLE
  Export-OpmApplicationFragment -Fragment $app -LiteralPath '.\out\app.xml' -WhatIf
#>

function Export-OpmApplicationFragment {
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'BySourceFolder')]
    [OutputType([string])]
    param(
        [Parameter(Mandatory, ValueFromPipeline)]
        [psobject] $Fragment,

        [Parameter(Mandatory, ParameterSetName = 'BySourceFolder', Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string] $SourceFolder,

        [Parameter(Mandatory, ParameterSetName = 'ByLiteralPath')]
        [ValidateNotNullOrEmpty()]
        [string] $LiteralPath,

        [Parameter()]
        [switch] $RelativizePaths,

        [Parameter()]
        [switch] $PassThru
    )

    process {
        if (-not (Test-OpmGuid -Value $Fragment.AppId)) {
            throw "Fragment AppId '$($Fragment.AppId)' is not a valid PacKit GUID (expected braced UPPERCASE form). Use New-OpmApplicationFragment to create one."
        }
        if ([string]::IsNullOrEmpty($Fragment.Name)) {
            throw 'Fragment Name must not be empty. PacKit requires a non-empty application name.'
        }

        if ($PSCmdlet.ParameterSetName -eq 'BySourceFolder') {
            $opmFolder = Join-Path $SourceFolder (Get-OpmSchema).OpmFolderName
            $target = Join-Path $opmFolder ($Fragment.AppId + '.xml')
        }
        else {
            $target = $LiteralPath
        }

        $toEmit = $Fragment
        if ($RelativizePaths) {
            $baseDir = [System.IO.Path]::GetDirectoryName([System.IO.Path]::GetFullPath($target))
            # Deep-clone via round-trip so the caller's object is never mutated.
            $toEmit = ConvertFrom-OpmXmlString -Xml (ConvertTo-OpmXmlString -Fragment $Fragment)
            $toEmit.IconPath = Get-OpmRelativePath -BaseDirectory $baseDir -Path $toEmit.IconPath
            $toEmit.DetectionRule.ScriptPath = Get-OpmRelativePath -BaseDirectory $baseDir -Path $toEmit.DetectionRule.ScriptPath
            foreach ($pkg in @($toEmit.Packages)) {
                $pkg.Path = Get-OpmRelativePath -BaseDirectory $baseDir -Path $pkg.Path
                $pkg.SourceFolder = Get-OpmRelativePath -BaseDirectory $baseDir -Path $pkg.SourceFolder
            }
        }

        $xml = ConvertTo-OpmXmlString -Fragment $toEmit

        if ($PSCmdlet.ShouldProcess($target, 'Write PacKit application fragment')) {
            $written = Write-OpmBytes -Path $target -Content $xml
            if ($PassThru) { return $Fragment }
            return $written
        }
    }
}