Public/Remove-MSIXClassicShellExtension.ps1
|
function Remove-MSIXClassicShellExtension { <# .SYNOPSIS Removes classic COM-based shell extension declarations from AppxManifest.xml. .DESCRIPTION Scans AppxManifest.xml in an expanded MSIX package folder and removes Extension elements whose Category matches one of the classic COM shell extension categories: windows.comServer - COM server registration windows.fileExplorerContextMenus - desktop4/5 CLSID verb wiring windows.fileExplorerClassicContextMenuHandler - desktop9 classic handler (references CLSID) windows.fileExplorerClassicDragDropContextMenuHandler - desktop9 drag-drop handler All four categories must be removed together. MakeAppx validates that every CLSID referenced in fileExplorerClassicContextMenuHandler has a matching windows.comServer registration in the same package. Removing comServer without removing the handlers leaves dangling CLSID references and causes a manifest validation error. Modern shell extension declarations are intentionally left untouched: windows.fileTypeAssociation - modern verb-based file type associations (uap3) Use this function after Import-MSIXSparseShellExtension when the COM-based classic context menu entries are visible in Explorer but do not execute correctly (because the MSIX sandbox prevents the COM surrogate from locating the host executable or accessing required registry keys outside the package's virtual hive). The modern file type association verbs (uap3) work without COM and continue to provide the right-click "Add to..." entry in the Windows 11 context menu. .PARAMETER MSIXFolder Path to the expanded MSIX package folder (must contain AppxManifest.xml). .PARAMETER Categories One or more Extension Category values to remove. Defaults to 'windows.comServer' and 'windows.fileExplorerContextMenus'. Override to target a different set of categories. .EXAMPLE Remove-MSIXClassicShellExtension -MSIXFolder "C:\MSIXTemp\WinRAR" .EXAMPLE # Remove only the COM server registration, keep the context menu wiring Remove-MSIXClassicShellExtension -MSIXFolder "C:\MSIXTemp\WinRAR" ` -Categories 'windows.comServer' .NOTES https://www.nick-it.de Andreas Nick, 2026 #> [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] [System.IO.DirectoryInfo] $MSIXFolder, [string[]] $Categories = @( 'windows.comServer', 'windows.fileExplorerContextMenus', 'windows.fileExplorerClassicContextMenuHandler', 'windows.fileExplorerClassicDragDropContextMenuHandler' ) ) process { $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 $manifest.Load($manifestPath) $extensions = @($manifest.SelectNodes("//*[local-name()='Extension']")) $removed = 0 foreach ($ext in $extensions) { $category = $ext.GetAttribute('Category') if ($Categories -notcontains $category) { continue } if ($PSCmdlet.ShouldProcess("Extension Category='$category'", 'Remove')) { $null = $ext.ParentNode.RemoveChild($ext) Write-Verbose "Removed Extension Category='$category'." $removed++ } } if ($removed -eq 0) { Write-Verbose "No matching Extension elements found - nothing removed." return } # Remove Extensions container elements that have no element children left. # MakeAppx rejects an Extensions element that contains only whitespace text nodes. # Use XPath not(*) to match elements with no element children regardless of whitespace. $emptyExtensionsNodes = @($manifest.SelectNodes("//*[local-name()='Extensions' and not(*)]")) foreach ($node in $emptyExtensionsNodes) { $null = $node.ParentNode.RemoveChild($node) Write-Verbose "Removed empty Extensions element." } $manifest.PreserveWhitespace = $false $manifest.Save($manifestPath) Write-Verbose "Removed $removed Extension element(s) from AppxManifest.xml." } } |