Public/New-MSIXPackage.ps1

function New-MSIXPackage {
<#
.SYNOPSIS
    Creates an empty MSIX skeleton folder from the bundled template.

.DESCRIPTION
    Copies the MSIXTemplate (empty Registry.dat / User.dat / UserClasses.dat /
    Resources.pri / Assets / AppxManifest.xml) into a fresh folder and fills the
    manifest placeholders. Returns the created folder so it can be piped into
    Add-MSIXApplication, New-MSIXAssetFrom, Add-MSIXPsfFrameworkFiles, etc.

.PARAMETER OutputFolder
    Destination folder for the skeleton.

.PARAMETER Name
    Identity Name (e.g. 'MyApp'). Must match [A-Za-z][-A-Za-z0-9.]*.

.PARAMETER Publisher
    Publisher subject (e.g. 'CN=Contoso').

.PARAMETER Version
    Package version, four-part (e.g. '1.0.0.0').

.PARAMETER DisplayName
    Display name shown in Settings. Defaults to Name.

.PARAMETER PublisherDisplayName
    Friendly publisher name. Defaults to the CN= part of Publisher.

.PARAMETER Description
    Package description. Defaults to DisplayName.

.PARAMETER ProcessorArchitecture
    x64 (default), x86, or neutral.

.PARAMETER Force
    Overwrites OutputFolder if it already exists.

.EXAMPLE
    $pkg = New-MSIXPackage -OutputFolder C:\Temp\MyApp -Name 'MyApp' `
        -Publisher 'CN=Contoso' -Version '1.0.0.0'

.NOTES
    https://www.nick-it.de
    Andreas Nick, 2026
#>

    [CmdletBinding()]
    [OutputType([System.IO.DirectoryInfo])]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [System.IO.DirectoryInfo] $OutputFolder,

        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[A-Za-z][-A-Za-z0-9.]*$')]
        [string] $Name,

        [Parameter(Mandatory = $true)]
        [ValidatePattern('^CN=')]
        [string] $Publisher,

        [Parameter(Mandatory = $true)]
        [ValidatePattern('^\d+\.\d+\.\d+\.\d+$')]
        [string] $Version,

        [string] $DisplayName,
        [string] $PublisherDisplayName,
        [string] $Description,

        [ValidateSet('x64', 'x86', 'neutral')]
        [string] $ProcessorArchitecture = 'x64',

        [switch] $Force
    )

    process {
        $templateRoot = Join-Path $Script:ScriptPath 'Libs\MSIXTemplate'
        if (-not (Test-Path $templateRoot)) {
            Write-Error "MSIX template folder not found: $templateRoot"
            return $null
        }

        if (Test-Path $OutputFolder.FullName) {
            if (-not $Force) {
                Write-Error "OutputFolder already exists: $($OutputFolder.FullName). Use -Force to overwrite."
                return $null
            }
            Write-Verbose "Removing existing OutputFolder: $($OutputFolder.FullName)"
            Remove-Item $OutputFolder.FullName -Recurse -Force -ErrorAction Stop
        }

        Write-Verbose "Creating skeleton folder: $($OutputFolder.FullName)"
        New-Item -ItemType Directory -Path $OutputFolder.FullName -Force | Out-Null

        Copy-Item -Path (Join-Path $templateRoot '*') -Destination $OutputFolder.FullName -Recurse -Force
        Write-Verbose "Copied template files from: $templateRoot"

        # Apply defaults derived from required parameters
        if ([string]::IsNullOrEmpty($DisplayName)) { $DisplayName = $Name }
        if ([string]::IsNullOrEmpty($Description)) { $Description = $DisplayName }
        if ([string]::IsNullOrEmpty($PublisherDisplayName)) {
            $cnMatch = [regex]::Match($Publisher, 'CN=([^,]+)')
            $PublisherDisplayName = if ($cnMatch.Success) { $cnMatch.Groups[1].Value.Trim() } else { $Name }
        }

        $manifestPath = Join-Path $OutputFolder.FullName 'AppxManifest.xml'
        $manifest = Get-Content $manifestPath -Raw
        $manifest = $manifest.
            Replace('{{Name}}',                  $Name).
            Replace('{{Publisher}}',             $Publisher).
            Replace('{{Version}}',               $Version).
            Replace('{{ProcessorArchitecture}}', $ProcessorArchitecture).
            Replace('{{DisplayName}}',           $DisplayName).
            Replace('{{PublisherDisplayName}}',  $PublisherDisplayName).
            Replace('{{Description}}',           $Description)
        Set-Content -Path $manifestPath -Value $manifest -Encoding UTF8 -NoNewline
        Write-Verbose "Manifest finalised: $manifestPath"

        return Get-Item $OutputFolder.FullName
    }
}