Public/Add-MSIXFlexibleVirtualization.ps1
|
function Add-MSIXFlexibleVirtualization { <# .SYNOPSIS Configures registry and file system write virtualization pass-through in AppxManifest.xml. .DESCRIPTION Adds manifest declarations so that container processes write registry keys and file system paths to the real system locations instead of the package's virtual hives (User.dat / VirtualFileSystem). One simple call per task - no arrays, no %ENV% strings. Three parameter sets: Disable (default, Windows 10 1903+): Disables ALL write virtualization for the selected target(s). Add-MSIXFlexibleVirtualization -MSIXFolder $pkg -DisableRegistry -DisableFileSystem Directory (Windows 10 Build 20348+ / Windows 11): Excludes ONE AppData sub-folder from file system virtualization. Add-MSIXFlexibleVirtualization -MSIXFolder $pkg -KnownFolder RoamingAppData -Folder 'Mozilla' Registry (Windows 10 Build 20348+ / Windows 11): Excludes ONE HKCU key from registry virtualization. Add-MSIXFlexibleVirtualization -MSIXFolder $pkg -RegistryKey 'HKEY_CURRENT_USER\SOFTWARE\Mozilla' Call the Directory/Registry forms repeatedly to exclude several paths; each call appends to the existing FileSystemWriteVirtualization / RegistryWriteVirtualization element. All forms add rescap:Capability Name="unvirtualizedResources" to <Capabilities>. .PARAMETER MSIXFolder Path to the expanded MSIX package folder. .PARAMETER DisableRegistry (Disable set) Adds desktop6:RegistryWriteVirtualization = disabled. .PARAMETER DisableFileSystem (Disable set) Adds desktop6:FileSystemWriteVirtualization = disabled. .PARAMETER KnownFolder (Directory set) The AppData known folder, e.g. RoamingAppData. Combined with -Folder to form $(KnownFolder:<KnownFolder>)\<Folder>. Tab-completes the allowed values. .PARAMETER Folder (Directory set) Sub-path under -KnownFolder, e.g. 'Mozilla' or 'Vendor\App'. .PARAMETER RegistryKey (Registry set) A single HKCU key. Accepts the full 'HKEY_CURRENT_USER\SOFTWARE\Mozilla', the 'HKCU\...' / 'HKCU:\...' shorthand, or a bare subkey ('SOFTWARE\Mozilla') - all are normalized to HKEY_CURRENT_USER. HKLM is rejected (only HKCU may be excluded). .EXAMPLE Add-MSIXFlexibleVirtualization -MSIXFolder $pkg -KnownFolder RoamingAppData -Folder 'Mozilla' .EXAMPLE Add-MSIXFlexibleVirtualization -MSIXFolder $pkg -RegistryKey 'HKEY_CURRENT_USER\SOFTWARE\Mozilla' .NOTES https://www.nick-it.de Andreas Nick, 2026 #> [CmdletBinding(DefaultParameterSetName = 'Disable')] param( [Parameter(Mandatory = $true, ParameterSetName = 'Disable', Position = 0)] [Parameter(Mandatory = $true, ParameterSetName = 'Directory', Position = 0)] [Parameter(Mandatory = $true, ParameterSetName = 'Registry', Position = 0)] [System.IO.DirectoryInfo] $MSIXFolder, [Parameter(ParameterSetName = 'Disable')] [Switch] $DisableRegistry, [Parameter(ParameterSetName = 'Disable')] [Switch] $DisableFileSystem, [Parameter(Mandatory = $true, ParameterSetName = 'Directory')] [ValidateSet( 'AccountPictures', 'AdminTools', 'AppDataDesktop', 'AppDataDocuments', 'AppDataFavorites', 'AppDataProgramData', 'ApplicationShortcuts', 'CDBurning', 'Cookies', 'GameTasks', 'History', 'ImplicitAppShortcuts', 'InternetCache', 'Libraries', 'LocalAppData', 'LocalAppDataLow', 'NetHood', 'OriginalImages', 'PrintHood', 'Programs', 'QuickLaunch', 'Recent', 'Ringtones', 'RoamingAppData', 'RoamedTileImages', 'RoamingTiles', 'SearchHistory', 'SearchTemplates', 'SendTo', 'SidebarParts', 'StartMenu', 'Startup', 'Templates', 'UserPinned', 'UserProgramFiles', 'UserProgramFilesCommon' )] [string] $KnownFolder, [Parameter(Mandatory = $true, ParameterSetName = 'Directory')] [string] $Folder, [Parameter(Mandatory = $true, ParameterSetName = 'Registry')] [string] $RegistryKey ) $manifestPath = Join-Path $MSIXFolder 'AppxManifest.xml' if (-not (Test-Path $manifestPath)) { Write-Error "AppxManifest.xml not found in: $($MSIXFolder.FullName)" return } if ($PSCmdlet.ParameterSetName -eq 'Registry') { # Friendly input: accept 'HKEY_CURRENT_USER\...', 'HKCU\...', 'HKCU:\...' or a bare # subkey ('SOFTWARE\Mozilla') and normalize to the manifest's required HKCU form. # Only HKCU is allowed (per the flexible-virtualization rules); HKLM is rejected. $RegistryKey = $RegistryKey.Trim() if ($RegistryKey -match '^(HKLM|HKEY_LOCAL_MACHINE)') { Write-Error "Only HKCU is allowed - HKLM keys cannot be excluded from virtualization." return } if ($RegistryKey -match '^HKCU:?\\') { $RegistryKey = 'HKEY_CURRENT_USER\' + ($RegistryKey -replace '^HKCU:?\\', '') } elseif ($RegistryKey -notmatch '^HKEY_CURRENT_USER\\') { $RegistryKey = 'HKEY_CURRENT_USER\' + $RegistryKey.TrimStart('\') } } if ($PSCmdlet.ParameterSetName -eq 'Directory' -and [string]::IsNullOrWhiteSpace($Folder)) { Write-Error "-Folder must not be empty." return } $xml = New-Object xml $xml.Load($manifestPath) $nsBase = 'http://schemas.microsoft.com/appx/manifest/foundation/windows10' $nsRescap = 'http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities' $nsD6 = 'http://schemas.microsoft.com/appx/manifest/desktop/windows10/6' $nsVirt = 'http://schemas.microsoft.com/appx/manifest/virtualization/windows10' $nsmgr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable) $null = $nsmgr.AddNamespace('ns', $nsBase) $null = $nsmgr.AddNamespace('rescap', $nsRescap) $null = $nsmgr.AddNamespace('d6', $nsD6) $null = $nsmgr.AddNamespace('virt', $nsVirt) $packageEl = $xml.DocumentElement # rescap + unvirtualizedResources are required by every form. if ($packageEl.GetAttribute('xmlns:rescap') -eq '') { $null = $packageEl.SetAttribute('xmlns:rescap', $nsRescap) Write-Verbose "Added xmlns:rescap to Package element." } $ignorable = $packageEl.GetAttribute('IgnorableNamespaces') if ($ignorable -notmatch '\brescap\b') { $packageEl.SetAttribute('IgnorableNamespaces', ($ignorable + ' rescap').Trim()) } $capsEl = $xml.SelectSingleNode('/ns:Package/ns:Capabilities', $nsmgr) if ($null -eq $capsEl) { $capsEl = $xml.CreateElement('Capabilities', $nsBase) $null = $packageEl.AppendChild($capsEl) } if ($null -eq $capsEl.SelectSingleNode("rescap:Capability[@Name='unvirtualizedResources']", $nsmgr)) { $capEl = $xml.CreateElement('rescap', 'Capability', $nsRescap) $null = $capEl.SetAttribute('Name', 'unvirtualizedResources') $null = $capsEl.AppendChild($capEl) Write-Verbose "Added rescap:Capability 'unvirtualizedResources'." } $propsEl = $xml.SelectSingleNode('/ns:Package/ns:Properties', $nsmgr) if ($null -eq $propsEl) { $propsEl = $xml.CreateElement('Properties', $nsBase) $null = $packageEl.PrependChild($propsEl) } switch ($PSCmdlet.ParameterSetName) { 'Disable' { if ($packageEl.GetAttribute('xmlns:desktop6') -eq '') { $null = $packageEl.SetAttribute('xmlns:desktop6', $nsD6) Write-Verbose "Added xmlns:desktop6 to Package element." } $ig = $packageEl.GetAttribute('IgnorableNamespaces') if ($ig -notmatch '\bdesktop6\b') { $packageEl.SetAttribute('IgnorableNamespaces', ($ig + ' desktop6').Trim()) } if ($DisableRegistry -and $null -eq $propsEl.SelectSingleNode('d6:RegistryWriteVirtualization', $nsmgr)) { $el = $xml.CreateElement('desktop6', 'RegistryWriteVirtualization', $nsD6) $el.InnerText = 'disabled' $null = $propsEl.AppendChild($el) Write-Verbose "Added desktop6:RegistryWriteVirtualization = disabled." } if ($DisableFileSystem -and $null -eq $propsEl.SelectSingleNode('d6:FileSystemWriteVirtualization', $nsmgr)) { $el = $xml.CreateElement('desktop6', 'FileSystemWriteVirtualization', $nsD6) $el.InnerText = 'disabled' $null = $propsEl.AppendChild($el) Write-Verbose "Added desktop6:FileSystemWriteVirtualization = disabled." } } default { # Directory / Registry: selective exclusion (append to existing element). if ($packageEl.GetAttribute('xmlns:virtualization') -eq '') { $null = $packageEl.SetAttribute('xmlns:virtualization', $nsVirt) Write-Verbose "Added xmlns:virtualization to Package element." } $ig = $packageEl.GetAttribute('IgnorableNamespaces') if ($ig -notmatch '\bvirtualization\b') { $packageEl.SetAttribute('IgnorableNamespaces', ($ig + ' virtualization').Trim()) } if ($PSCmdlet.ParameterSetName -eq 'Registry') { $rvEl = $propsEl.SelectSingleNode('virt:RegistryWriteVirtualization', $nsmgr) if ($null -eq $rvEl) { $rvEl = $xml.CreateElement('virtualization', 'RegistryWriteVirtualization', $nsVirt) $null = $propsEl.AppendChild($rvEl) } $keysEl = $rvEl.SelectSingleNode('virt:ExcludedKeys', $nsmgr) if ($null -eq $keysEl) { $keysEl = $xml.CreateElement('virtualization', 'ExcludedKeys', $nsVirt) $null = $rvEl.AppendChild($keysEl) } $exists = $false foreach ($k in $keysEl.SelectNodes('virt:ExcludedKey', $nsmgr)) { if ($k.InnerText -eq $RegistryKey) { $exists = $true; break } } if ($exists) { Write-Verbose "ExcludedKey already present: $RegistryKey" } else { $keyEl = $xml.CreateElement('virtualization', 'ExcludedKey', $nsVirt) $keyEl.InnerText = $RegistryKey $null = $keysEl.AppendChild($keyEl) Write-Verbose "Added ExcludedKey: $RegistryKey" } } else { $excludedValue = '$(KnownFolder:{0})\{1}' -f $KnownFolder, $Folder.Trim().TrimStart('\') $fvEl = $propsEl.SelectSingleNode('virt:FileSystemWriteVirtualization', $nsmgr) if ($null -eq $fvEl) { $fvEl = $xml.CreateElement('virtualization', 'FileSystemWriteVirtualization', $nsVirt) $null = $propsEl.AppendChild($fvEl) } $dirsEl = $fvEl.SelectSingleNode('virt:ExcludedDirectories', $nsmgr) if ($null -eq $dirsEl) { $dirsEl = $xml.CreateElement('virtualization', 'ExcludedDirectories', $nsVirt) $null = $fvEl.AppendChild($dirsEl) } $exists = $false foreach ($d in $dirsEl.SelectNodes('virt:ExcludedDirectory', $nsmgr)) { if ($d.InnerText -eq $excludedValue) { $exists = $true; break } } if ($exists) { Write-Verbose "ExcludedDirectory already present: $excludedValue" } else { $dirEl = $xml.CreateElement('virtualization', 'ExcludedDirectory', $nsVirt) $dirEl.InnerText = $excludedValue $null = $dirsEl.AppendChild($dirEl) Write-Verbose "Added ExcludedDirectory: $excludedValue" } } } } $xml.PreserveWhitespace = $false $xml.Save($manifestPath) Write-Verbose "Saved AppxManifest.xml ($($PSCmdlet.ParameterSetName) mode)." } |