Public/New-MSIXApplicationVariant.ps1
|
function New-MSIXApplicationVariant { <# .SYNOPSIS Clones an Application entry in AppxManifest.xml under a new Id. .DESCRIPTION Creates a deep copy of an existing Application element and inserts it into the Applications section with a new @Id. Optionally overrides the Executable attribute and the AppListEntry visibility on the cloned entry. Use this to create a second launcher variant for the same executable that requires different command-line arguments. After cloning, call Add-MSXIXPSFShim on the new Id to wire it through a dedicated PSF launcher with the desired -Arguments value. .PARAMETER MSIXFolder Path to the expanded MSIX package folder (must contain AppxManifest.xml). .PARAMETER SourceAppId @Id of the Application entry to clone. .PARAMETER NewAppId @Id to assign to the cloned Application entry. Must be unique within the manifest. .PARAMETER Executable Optional. Overrides the Executable attribute on the cloned entry. Useful when the variant should launch a different binary. .PARAMETER AppListEntry Optional. Sets the AppListEntry attribute on the cloned uap:VisualElements element. Accepted values: 'default', 'none'. Use 'none' to hide the variant from the Start menu (typical for argument-only variants). .PARAMETER DisplayName Optional. Sets the DisplayName on the cloned uap:VisualElements (the Start-menu name). The variant reuses the source app's existing logo assets - no new assets are generated. .PARAMETER WithoutExtensions Optional switch. Removes the cloned <Extensions> (file type associations, protocols, shortcuts, aliases) from the variant. Use this for a pure launcher variant so it does not duplicate / conflict with the source app's registrations. .EXAMPLE # Create a hidden WinRAR variant for silent operation, then wire it through PSF New-MSIXApplicationVariant -MSIXFolder "C:\MSIXTemp\WinRAR" ` -SourceAppId "WinRAR" -NewAppId "WinRAR_Silent" -AppListEntry none Add-MSXIXPSFShim -MSIXFolder "C:\MSIXTemp\WinRAR" ` -MISXAppID "WinRAR_Silent" -Arguments "-s" -PSFArchitektur x64 .OUTPUTS System.String The new Application Id ($NewAppId) on success. .NOTES The cloned entry inherits all child elements (Extensions, VisualElements, etc.) from the source. Review the manifest after cloning to remove or adjust child elements that should not be duplicated (e.g. execution aliases). https://www.nick-it.de Andreas Nick, 2026 #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [System.IO.DirectoryInfo] $MSIXFolder, [Parameter(Mandatory = $true, Position = 1)] [String] $SourceAppId, [Parameter(Mandatory = $true, Position = 2)] [String] $NewAppId, [String] $Executable = '', [ValidateSet('default', 'none')] [String] $AppListEntry = '', [String] $DisplayName = '', [Alias('NoExtensions')] [switch] $WithoutExtensions ) $manifestPath = Join-Path $MSIXFolder 'AppxManifest.xml' if (-not (Test-Path $manifestPath)) { Write-Error "AppxManifest.xml not found in: $($MSIXFolder.FullName)" return } $manifest = New-Object xml $nsmgr = New-Object System.Xml.XmlNamespaceManager $manifest.NameTable $AppXNamespaces.GetEnumerator() | ForEach-Object { $nsmgr.AddNamespace($_.Key, $_.Value) } $manifest.Load($manifestPath) $sourceApp = $manifest.SelectSingleNode( "//ns:Package/ns:Applications/ns:Application[@Id='$SourceAppId']", $nsmgr) if ($null -eq $sourceApp) { Write-Error "Application '$SourceAppId' not found in AppxManifest.xml." return } $conflict = $manifest.SelectSingleNode( "//ns:Package/ns:Applications/ns:Application[@Id='$NewAppId']", $nsmgr) if ($null -ne $conflict) { Write-Error "Application '$NewAppId' already exists in AppxManifest.xml." return } $clone = $sourceApp.CloneNode($true) $null = $clone.SetAttribute('Id', $NewAppId) if ($Executable -ne '') { $null = $clone.SetAttribute('Executable', $Executable) } if ($AppListEntry -ne '') { $veNode = $clone.SelectSingleNode('uap:VisualElements', $nsmgr) if ($null -ne $veNode) { $null = $veNode.SetAttribute('AppListEntry', $AppListEntry) } else { Write-Warning "uap:VisualElements not found on cloned Application '$NewAppId' — AppListEntry not set." } } if ($DisplayName -ne '') { $veNode = $clone.SelectSingleNode('uap:VisualElements', $nsmgr) if ($null -ne $veNode) { $null = $veNode.SetAttribute('DisplayName', $DisplayName) } else { Write-Warning "uap:VisualElements not found on cloned Application '$NewAppId' — DisplayName not set." } } if ($WithoutExtensions) { # A launcher variant should not re-register the source app's FTAs / protocols / shortcuts - # that would duplicate them and conflict with the source app. Drop the cloned Extensions. $extNodes = @($clone.SelectNodes("*[local-name()='Extensions']")) foreach ($x in $extNodes) { $null = $clone.RemoveChild($x) } if ($extNodes.Count -gt 0) { Write-Verbose "Removed $($extNodes.Count) Extensions node(s) from variant '$NewAppId'." } } $applicationsNode = $manifest.SelectSingleNode("//ns:Package/ns:Applications", $nsmgr) $null = $applicationsNode.AppendChild($clone) $manifest.PreserveWhitespace = $false $manifest.Save($manifestPath) Write-Verbose "Application '$SourceAppId' cloned as '$NewAppId'." Write-Output $NewAppId } |