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). Two parameter sets select the approach: Disable (default): Adds desktop6:RegistryWriteVirtualization and/or desktop6:FileSystemWriteVirtualization with value "disabled". Disables ALL write virtualization for the selected target(s). Pattern used by WinRAR.ShellExtension. Works on Windows 10 1903+. Use -DisableRegistry and/or -DisableFileSystem. Selective: Adds virtualization:RegistryWriteVirtualization with ExcludedKeys and/or virtualization:FileSystemWriteVirtualization with ExcludedDirectories. Only the specified paths bypass virtualization; everything else remains virtualized. Windows 11+ only. Both approaches add rescap:Capability Name="unvirtualizedResources" to <Capabilities>. Namespaces: xmlns:desktop6 = http://schemas.microsoft.com/appx/manifest/desktop/windows10/6 xmlns:rescap = http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities xmlns:virtualization = http://schemas.microsoft.com/appx/manifest/virtualization/windows10 Note: when PsfFtaCom hosts a COM shell extension (IContextMenu DLL) inside the MSIX container, the DLL sees the virtual registry and VFS -- unvirtualizedResources is then NOT required. Use this cmdlet only when container processes genuinely need to write to the real system registry or filesystem (e.g. startup scripts, non-COM applications). .PARAMETER MSIXFolder Path to the expanded MSIX package folder. .PARAMETER DisableRegistry (Disable set) Adds desktop6:RegistryWriteVirtualization = disabled. All registry writes from container processes go to the real system registry. .PARAMETER DisableFileSystem (Disable set) Adds desktop6:FileSystemWriteVirtualization = disabled. All filesystem writes from container processes go to the real filesystem. .PARAMETER RegistryKeyPaths (Selective set) HKCU paths in the form 'HKEY_CURRENT_USER\SOFTWARE\...'. Only HKCU paths are valid; HKLM paths cannot be excluded from virtualization. Windows 11+ only. .PARAMETER DirectoryPaths (Selective set) AppData paths to exclude, e.g. '%USERPROFILE%\AppData\Roaming\MyApp'. Must be under %USERPROFILE%\AppData. Windows 11+ only. .EXAMPLE # Disable all registry write virtualization (WinRAR.ShellExtension pattern) Add-MSIXFlexibleVirtualization -MSIXFolder $MSIXFolder -DisableRegistry .EXAMPLE # Disable both registry and filesystem write virtualization Add-MSIXFlexibleVirtualization -MSIXFolder $MSIXFolder -DisableRegistry -DisableFileSystem .EXAMPLE # Selectively exclude specific HKCU registry paths (Windows 11+ only) Add-MSIXFlexibleVirtualization -MSIXFolder $MSIXFolder ` -RegistryKeyPaths @('HKEY_CURRENT_USER\SOFTWARE\WinRAR', 'HKEY_CURRENT_USER\SOFTWARE\WinRAR SFX') .EXAMPLE # Selectively exclude registry keys and filesystem paths (Windows 11+ only) Add-MSIXFlexibleVirtualization -MSIXFolder $MSIXFolder ` -RegistryKeyPaths @('HKEY_CURRENT_USER\SOFTWARE\MyApp') ` -DirectoryPaths @('%USERPROFILE%\AppData\Roaming\MyApp') .NOTES https://www.nick-it.de Andreas Nick, 2026 #> [CmdletBinding(DefaultParameterSetName = 'Disable')] param( [Parameter(Mandatory = $true, ParameterSetName = 'Disable', Position = 0)] [Parameter(Mandatory = $true, ParameterSetName = 'Selective', Position = 0)] [System.IO.DirectoryInfo] $MSIXFolder, [Parameter(ParameterSetName = 'Disable')] [Switch] $DisableRegistry, [Parameter(ParameterSetName = 'Disable')] [Switch] $DisableFileSystem, [Parameter(ParameterSetName = 'Selective')] [string[]] $RegistryKeyPaths = @(), [Parameter(ParameterSetName = 'Selective')] [string[]] $DirectoryPaths = @() ) $manifestPath = Join-Path $MSIXFolder 'AppxManifest.xml' if (-not (Test-Path $manifestPath)) { Write-Error "AppxManifest.xml not found in: $($MSIXFolder.FullName)" 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 is required by both approaches. 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') { $ignorable = ($ignorable + ' rescap').Trim() $packageEl.SetAttribute('IgnorableNamespaces', $ignorable) } $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) } if ($PSCmdlet.ParameterSetName -eq 'Disable') { if ($packageEl.GetAttribute('xmlns:desktop6') -eq '') { $null = $packageEl.SetAttribute('xmlns:desktop6', $nsD6) Write-Verbose "Added xmlns:desktop6 to Package element." } $ignorable = $packageEl.GetAttribute('IgnorableNamespaces') if ($ignorable -notmatch '\bdesktop6\b') { $ignorable = ($ignorable + ' desktop6').Trim() $packageEl.SetAttribute('IgnorableNamespaces', $ignorable) } if ($DisableRegistry) { if ($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) { if ($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." } } } else { # Selective approach: exclude only specific paths (Windows 11+). if ($packageEl.GetAttribute('xmlns:virtualization') -eq '') { $null = $packageEl.SetAttribute('xmlns:virtualization', $nsVirt) Write-Verbose "Added xmlns:virtualization to Package element." } $ignorable = $packageEl.GetAttribute('IgnorableNamespaces') if ($ignorable -notmatch '\bvirtualization\b') { $ignorable = ($ignorable + ' virtualization').Trim() $packageEl.SetAttribute('IgnorableNamespaces', $ignorable) } if ($RegistryKeyPaths.Count -gt 0) { if ($null -eq $propsEl.SelectSingleNode('virt:RegistryWriteVirtualization', $nsmgr)) { $virtEl = $xml.CreateElement('virtualization', 'RegistryWriteVirtualization', $nsVirt) $keysEl = $xml.CreateElement('virtualization', 'ExcludedKeys', $nsVirt) foreach ($keyPath in $RegistryKeyPaths) { $keyEl = $xml.CreateElement('virtualization', 'ExcludedKey', $nsVirt) $keyEl.InnerText = $keyPath $null = $keysEl.AppendChild($keyEl) Write-Verbose "Declared ExcludedKey: $keyPath" } $null = $virtEl.AppendChild($keysEl) $null = $propsEl.AppendChild($virtEl) Write-Verbose "Added virtualization:RegistryWriteVirtualization with $($RegistryKeyPaths.Count) ExcludedKey(s)." } } if ($DirectoryPaths.Count -gt 0) { if ($null -eq $propsEl.SelectSingleNode('virt:FileSystemWriteVirtualization', $nsmgr)) { $virtEl = $xml.CreateElement('virtualization', 'FileSystemWriteVirtualization', $nsVirt) $dirsEl = $xml.CreateElement('virtualization', 'ExcludedDirectories', $nsVirt) foreach ($dirPath in $DirectoryPaths) { $dirEl = $xml.CreateElement('virtualization', 'ExcludedDirectory', $nsVirt) $dirEl.InnerText = $dirPath $null = $dirsEl.AppendChild($dirEl) Write-Verbose "Declared ExcludedDirectory: $dirPath" } $null = $virtEl.AppendChild($dirsEl) $null = $propsEl.AppendChild($virtEl) Write-Verbose "Added virtualization:FileSystemWriteVirtualization with $($DirectoryPaths.Count) ExcludedDirectory/ies." } } } $xml.PreserveWhitespace = $false $xml.Save($manifestPath) Write-Verbose "Saved AppxManifest.xml with flexible virtualization declarations ($($PSCmdlet.ParameterSetName) mode)." } |