Public/Add-MSIXSharedContainer.ps1
|
function Add-MSIXSharedContainer { <# .SYNOPSIS Creates a Windows shared package container by name, accepting wildcard patterns or literal PackageFamilyNames. .DESCRIPTION Convenience wrapper around the Appx Add-AppSharedPackageContainer cmdlet. Builds the required AppSharedPackageContainer XML on the fly from the given package list, writes it to a temp file, and forwards it together with the optional -Force / -ForceApplicationShutdown / -Merge switches. Each entry in -Package is handled as follows: - Wildcard pattern (contains "*" or "?") — resolved against installed Appx packages by matching either the Name or the PackageFamilyName. - Anything else — taken as a literal PackageFamilyName. Shared package containers require Windows Server 2025 or Windows 11 24H2+. .PARAMETER Name Name of the shared package container (becomes the AppSharedPackageContainer/@Name attribute, e.g. "JavaContainer"). .PARAMETER Package One or more package identifiers A container requires at least two distinct PackageFamilyNames! .PARAMETER Force Forwarded to Add-AppSharedPackageContainer. Replaces an existing container with the same name. .PARAMETER ForceApplicationShutdown Forwarded to Add-AppSharedPackageContainer. Terminates running app processes that block the container operation. .PARAMETER Merge Forwarded to Add-AppSharedPackageContainer. Merges into an existing container instead of creating a new one. .PARAMETER ExportXml Writes the generated AppSharedPackageContainer XML to the given path and returns — does NOT call Add-AppSharedPackageContainer. Useful for building the XML on one machine and importing it later (e.g. with `Add-AppSharedPackageContainer -Path …` on a target system). When this is set, -Force / -ForceApplicationShutdown / -Merge are ignored and the availability of the underlying Appx cmdlet is not required. .EXAMPLE Add-MSIXSharedContainer -Name 'JavaContainer' -Package '*java*','*Freecol*' Resolves both wildcard patterns against installed packages and groups all matches into a new container named JavaContainer. .EXAMPLE Add-MSIXSharedContainer -Name 'JavaContainer' ` -Package 'Freecol_0cfjrh7p5ggd2','openjdkjre_0cfjrh7p5ggd2' -Force Uses literal PFNs and replaces any existing JavaContainer. .EXAMPLE Add-MSIXSharedContainer 'OfficeBundle' '*Word*','*Excel*','Acme.Helper_8wekyb3d8bbwe' -Merge Mixes wildcards and a literal PFN, merges into an existing OfficeBundle. .EXAMPLE Add-MSIXSharedContainer -Name 'JavaContainer' ` -Package 'Freecol_0cfjrh7p5ggd2','openjdkjre_0cfjrh7p5ggd2' ` -ExportXml 'C:\Deploy\JavaContainer.xml' Builds only the XML config and saves it to disk for later import on a target machine; nothing is registered locally. .NOTES https://learn.microsoft.com/en-us/powershell/module/appx/add-appsharedpackagecontainer https://www.nick-it.de Andreas Nick, 2026 #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [string] $Name, [Parameter(Mandatory = $true, Position = 1)] [string[]] $Package, [switch] $Force, [switch] $ForceApplicationShutdown, [switch] $Merge, [System.IO.FileInfo] $ExportXml ) process { # When -ExportXml is set we just build the XML and write it to disk, # so the underlying Appx cmdlet does not have to be present. $exportOnly = $PSBoundParameters.ContainsKey('ExportXml') if (-not $exportOnly) { if (-not (Get-Command Add-AppSharedPackageContainer -ErrorAction SilentlyContinue)) { Write-Error "Add-AppSharedPackageContainer is not available on this system. Requires Windows Server 2025 or Windows 11 24H2+." return } } elseif ($Force -or $ForceApplicationShutdown -or $Merge) { Write-Warning "-ExportXml is set; -Force / -ForceApplicationShutdown / -Merge are ignored (only relevant when registering the container)." } # Resolve every input entry to a list of PackageFamilyNames $pfnList = New-Object 'System.Collections.Generic.List[string]' foreach ($entry in $Package) { if ($entry -match '[\*\?]') { $matched = Get-AppxPackage -ErrorAction SilentlyContinue | Where-Object { $_.Name -like $entry -or $_.PackageFamilyName -like $entry } if ($null -eq $matched -or @($matched).Count -eq 0) { Write-Warning "No installed package matches pattern '$entry'" continue } foreach ($pkg in @($matched)) { Write-Verbose "Pattern '$entry' resolved to PFN '$($pkg.PackageFamilyName)' (Name=$($pkg.Name))" $pfnList.Add($pkg.PackageFamilyName) } } else { # Literal PFN — pass through as-is $pfnList.Add($entry) } } $uniquePfns = @($pfnList | Sort-Object -Unique) if ($uniquePfns.Count -lt 2) { Write-Error "A shared package container needs at least 2 distinct PackageFamilyNames. Got $($uniquePfns.Count) after pattern resolution and de-duplication." return } # Build the AppSharedPackageContainer XML $xml = New-Object System.Xml.XmlDocument $decl = $xml.CreateXmlDeclaration('1.0', 'utf-8', $null) $null = $xml.AppendChild($decl) $root = $xml.CreateElement('AppSharedPackageContainer') $null = $root.SetAttribute('Name', $Name) foreach ($pfn in $uniquePfns) { $elem = $xml.CreateElement('PackageFamily') $null = $elem.SetAttribute('Name', $pfn) $null = $root.AppendChild($elem) } $null = $xml.AppendChild($root) Write-Verbose ("Container '$Name' members:`r`n" + (($uniquePfns | ForEach-Object { ' ' + $_ }) -join "`r`n")) if ($exportOnly) { # Ensure parent directory exists, then save the XML where the caller asked $exportDir = Split-Path -Parent $ExportXml.FullName if ($exportDir -and -not (Test-Path $exportDir)) { $null = New-Item -ItemType Directory -Path $exportDir -Force } $xml.Save($ExportXml.FullName) Write-Verbose "Container XML exported to $($ExportXml.FullName)" return } $tempXml = Join-Path $env:Temp ("MSIXSharedContainer_" + [System.Guid]::NewGuid().ToString() + ".xml") $xml.Save($tempXml) Write-Verbose "Container XML written to $tempXml" try { $params = @{ Path = $tempXml } if ($Force) { $params.Force = $true } if ($ForceApplicationShutdown) { $params.ForceApplicationShutdown = $true } if ($Merge) { $params.Merge = $true } Add-AppSharedPackageContainer @params } finally { Remove-Item $tempXml -Force -ErrorAction SilentlyContinue } } } |