Public/Add-MSIXApplication.ps1
|
function Add-MSIXApplication { <# .SYNOPSIS Adds an Application entry (with VisualElements) to an MSIX package's manifest. .DESCRIPTION Inserts a single Application element into AppxManifest.xml. Defaults for Id and DisplayName are derived from the Executable filename when not specified. AssetId binds VisualElements logo paths to PNG files in the Assets folder (typically generated beforehand by New-MSIXAssetFrom). When AssetId is omitted, all logos fall back to Assets\StoreLogo.png. .PARAMETER MSIXFolder Path to the expanded MSIX package folder. .PARAMETER Executable Package-relative path to the executable (.exe / .ps1 / .cmd / .vbs / .js). The file must already exist inside the package. .PARAMETER Id Application Id. Defaults to a sanitised form of the executable's base name. .PARAMETER DisplayName Display name shown in Start menu. Defaults to the executable's base name. .PARAMETER AssetId Asset prefix used for VisualElements logo paths (Assets\<AssetId>-*.png). When empty, all logos use Assets\StoreLogo.png. .PARAMETER Description Application description. Defaults to DisplayName. .PARAMETER EntryPoint EntryPoint attribute. Defaults to Windows.FullTrustApplication. .EXAMPLE Add-MSIXApplication -MSIXFolder $pkg -Executable 'NITMSIXEventlogTracer.ps1' ` -DisplayName 'NIT Eventlog Tracer' -AssetId 'PSTracer' .NOTES https://www.nick-it.de Andreas Nick, 2026 #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] [System.IO.DirectoryInfo] $MSIXFolder, [Parameter(Mandatory = $true, Position = 1)] [string] $Executable, [string] $Id, [string] $DisplayName, [string] $AssetId, [string] $Description, [string] $EntryPoint = 'Windows.FullTrustApplication' ) process { $manifestPath = Join-Path $MSIXFolder.FullName 'AppxManifest.xml' if (-not (Test-Path $manifestPath)) { Write-Error "AppxManifest.xml not found in: $($MSIXFolder.FullName)" return $null } $exeFull = Join-Path $MSIXFolder.FullName $Executable if (-not (Test-Path $exeFull)) { Write-Error "Executable not found in package: $Executable (looked in $exeFull)" return $null } # Defaults from executable base name $exeBase = [IO.Path]::GetFileNameWithoutExtension($Executable) if ([string]::IsNullOrEmpty($Id)) { # Sanitise to MSIX Application/@Id pattern: [A-Za-z][-A-Za-z0-9.]{0,62} $sanitised = ($exeBase -replace '[^A-Za-z0-9.\-]', '') $sanitised = $sanitised -replace '^[^A-Za-z]+', '' if ($sanitised.Length -eq 0) { Write-Error "Could not derive a valid Id from executable '$Executable'. Provide -Id explicitly." return $null } if ($sanitised.Length -gt 63) { $sanitised = $sanitised.Substring(0, 63) } $Id = $sanitised Write-Verbose "Derived Id: $Id" } if ([string]::IsNullOrEmpty($DisplayName)) { $DisplayName = $exeBase Write-Verbose "Derived DisplayName: $DisplayName" } if ([string]::IsNullOrEmpty($Description)) { $Description = $DisplayName } # Decide logo paths if ([string]::IsNullOrEmpty($AssetId)) { $logo150 = 'Assets\StoreLogo.png' $logo44 = 'Assets\StoreLogo.png' $logo71 = 'Assets\StoreLogo.png' $logo310 = 'Assets\StoreLogo.png' $wide310 = 'Assets\StoreLogo.png' Write-Verbose "AssetId not set - all logos use Assets\StoreLogo.png" } else { $logo150 = "Assets\$AssetId-Square150x150Logo.png" $logo44 = "Assets\$AssetId-Square44x44Logo.png" $logo71 = "Assets\$AssetId-Square71x71Logo.png" $logo310 = "Assets\$AssetId-Square310x310Logo.png" $wide310 = "Assets\$AssetId-Wide310x150Logo.png" } $xml = New-Object System.Xml.XmlDocument $xml.PreserveWhitespace = $false $xml.Load($manifestPath) $foundationNs = 'http://schemas.microsoft.com/appx/manifest/foundation/windows10' $uapNs = 'http://schemas.microsoft.com/appx/manifest/uap/windows10' # Ensure xmlns:uap is declared on Package if (-not $xml.DocumentElement.HasAttribute('xmlns:uap')) { $xml.DocumentElement.SetAttribute('xmlns:uap', $uapNs) $ignorable = $xml.DocumentElement.GetAttribute('IgnorableNamespaces') if ($ignorable -notmatch '\buap\b') { $xml.DocumentElement.SetAttribute('IgnorableNamespaces', ($ignorable.TrimEnd() + ' uap').Trim()) } Write-Verbose "Added uap namespace to manifest root" } $appsNode = $xml.SelectSingleNode("//*[local-name()='Applications']") if ($null -eq $appsNode) { $appsNode = $xml.CreateElement('Applications', $foundationNs) $null = $xml.DocumentElement.AppendChild($appsNode) } # Reject duplicate Id foreach ($existing in @($appsNode.SelectNodes("*[local-name()='Application']"))) { if ($existing.GetAttribute('Id') -eq $Id) { Write-Error "An Application with Id '$Id' already exists in the manifest." return $null } } $app = $xml.CreateElement('Application', $foundationNs) $app.SetAttribute('Id', $Id) $app.SetAttribute('Executable', $Executable) $app.SetAttribute('EntryPoint', $EntryPoint) $ve = $xml.CreateElement('uap:VisualElements', $uapNs) $ve.SetAttribute('BackgroundColor', 'transparent') $ve.SetAttribute('DisplayName', $DisplayName) $ve.SetAttribute('Square150x150Logo', $logo150) $ve.SetAttribute('Square44x44Logo', $logo44) $ve.SetAttribute('Description', $Description) $tile = $xml.CreateElement('uap:DefaultTile', $uapNs) $tile.SetAttribute('Wide310x150Logo', $wide310) $tile.SetAttribute('Square310x310Logo', $logo310) $tile.SetAttribute('Square71x71Logo', $logo71) $null = $ve.AppendChild($tile) $null = $app.AppendChild($ve) $null = $appsNode.AppendChild($app) $xml.Save($manifestPath) Write-Verbose "Added Application '$Id' (Executable=$Executable) to manifest" return [PSCustomObject]@{ Id = $Id DisplayName = $DisplayName Executable = $Executable AssetId = $AssetId } } } |