Public/Add-MSIXFixWinRAR.ps1
|
function Add-MSIXFixWinRAR { <# .SYNOPSIS Injects the Package Support Framework into a WinRAR 64-bit MSIX package. .DESCRIPTION Opens the WinRAR MSIX package and applies the PSF fixes .PARAMETER MsixFile Path to the WinRAR MSIX file to modify. .PARAMETER MSIXFolder Temporary extraction folder. Defaults to a unique path under %TEMP%. .PARAMETER OutputFilePath Path for the repackaged MSIX. Defaults to overwriting the source file. .PARAMETER Subject Publisher subject string (CN=...). When provided, Set-MSIXPublisher is called to align the manifest publisher with the signing certificate. .PARAMETER Force Overwrites existing files in the extraction folder without prompting. .PARAMETER KeepMSIXFolder Keeps the temporary extraction folder after packing. Useful for troubleshooting. .EXAMPLE Add-MSIXFixWinRAR -MsixFile "C:\Packages\WinRAR.msix" .EXAMPLE Add-MSIXFixWinRAR ` -MsixFile "C:\Packages\WinRAR.msix" ` -OutputFilePath "C:\Packages\WinRAR_PSF.msix" ` -Subject "CN=Contoso, O=Contoso, C=DE" ` -Verbose .NOTES Requires an active PSF framework set via Set-MSIXActivePSFFramework. https://www.nick-it.de Andreas Nick, 2026 #> [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [System.IO.FileInfo] $MsixFile, [System.IO.DirectoryInfo] $MSIXFolder = ($env:Temp + "\MSIX_TEMP_" + [System.Guid]::NewGuid().ToString()), [System.IO.FileInfo] $OutputFilePath = $null, [String] $Subject = "", [Switch] $Force, [Switch] $KeepMSIXFolder ) process { if ($null -eq $OutputFilePath) { $OutputFilePath = $MsixFile } try { $null = Open-MSIXPackage -MsixFile $MsixFile -Force:$Force -MSIXFolder $MSIXFolder if ($Subject -ne "") { Set-MSIXPublisher -MSIXFolder $MSIXFolder -PublisherSubject $Subject } # Remove unsupported desktop7 shortcuts and common installer leftovers Invoke-MSIXCleanup -MSIXFolder $MSIXFolder # WinRAR needs internetClient for update checks and online features Add-MSIXCapabilities -MSIXFolder $MSIXFolder -Capabilities 'internetClient' # Uninstall.exe has no purpose inside the MSIX container. $uninstallPath = Join-Path $MSIXFolder.FullName 'VFS\ProgramFilesX64\WinRAR\Uninstall.exe' if (Test-Path $uninstallPath) { Remove-Item $uninstallPath -Force Write-Verbose "Removed WinRAR artifact: Uninstall.exe" } # The MSIX Packaging Tool already captures all shell extension declarations # (comServer, desktop9 handlers, fileTypeAssociation) from the RarExtInstaller.exe # deployment into the main manifest. Importing from the sparse would replace the # correct DLL path with a different one and create duplicate extension entries. # Delete the sparse files so they cannot cause failed deployment at runtime. Import-MSIXSparseShellExtension -MSIXFolder $MSIXFolder ` -SparsePackagePath 'VFS\ProgramFilesX64\WinRAR\RarExtPackage.msix' ` -InstallerExePath 'VFS\ProgramFilesX64\WinRAR\RarExtInstaller.exe' ` -DeleteOnly # Search Path (WinRAR DLLs are in VFS\ProgramFilesX64\WinRAR) Add-MSIXloaderSearchPathOverride -MSIXFolderPath $MSIXFolder -FolderPaths "VFS\ProgramFilesX64\WinRAR" Add-MSIXInstalledLocationVirtualization -MSIXFolderPath $MSIXFolder # rarext.dll is registered as com:SurrogateServer and does not use MSIX APIs Remove-MSIXClassicShellExtension -MSIXFolder $MSIXFolder # Copy MsixContextMenuHandler.dll + JSON to the WinRAR VFS directory and $cmhLibsDir = Join-Path $Script:ScriptPath 'Libs\Fixes\WinRarContextMenuReplacement' $cmhDllSrc = Join-Path $cmhLibsDir 'MsixContextMenuHandler.dll' $cmhJsonSrc = Join-Path $cmhLibsDir 'MsixContextMenuHandler.json' $winrarVfsDir = Join-Path $MSIXFolder.FullName 'VFS\ProgramFilesX64\WinRAR' if ((Test-Path $cmhDllSrc) -and (Test-Path $cmhJsonSrc)) { # Two separate GUIDs: AppId identifies the COM application (surrogate host), # CLSID identifies the context menu handler class. $cmhAppId = [Guid]::NewGuid().ToString('D').ToLower() $cmhClsidNoBrace = [Guid]::NewGuid().ToString('D').ToLower() $cmhClsid = '{' + $cmhClsidNoBrace.ToUpper() + '}' Copy-Item $cmhDllSrc -Destination $winrarVfsDir -Force Write-Verbose "Copied MsixContextMenuHandler.dll to $winrarVfsDir" # Inject the generated CLSID into the JSON config so DllGetClassObject # responds to exactly the CLSID declared in the manifest. $cmhCfg = Get-Content $cmhJsonSrc -Raw | ConvertFrom-Json Add-Member -InputObject $cmhCfg -MemberType NoteProperty -Name 'clsid' -Value $cmhClsid -Force $dstJsonPath = Join-Path $winrarVfsDir 'MsixContextMenuHandler.json' $dstJsonText = $cmhCfg | ConvertTo-Json -Depth 10 [IO.File]::WriteAllText($dstJsonPath, $dstJsonText, [Text.Encoding]::UTF8) Write-Verbose "Wrote MsixContextMenuHandler.json (CLSID: $cmhClsid)" # Register the DLL and context menu handler in AppxManifest.xml. $cmhManifestPath = Join-Path $MSIXFolder.FullName 'AppxManifest.xml' $cmhManifest = New-Object xml $cmhManifest.Load($cmhManifestPath) $cmhNsMgr = New-Object System.Xml.XmlNamespaceManager($cmhManifest.NameTable) $AppXNamespaces.GetEnumerator() | ForEach-Object { $cmhNsMgr.AddNamespace($_.Key, $_.Value) } # Prefer the Application that runs WinRAR.exe; fall back to the first one. $cmhAppNode = $cmhManifest.SelectSingleNode( "//ns:Application[contains(@Executable,'WinRAR.exe')]", $cmhNsMgr) if ($null -eq $cmhAppNode) { $cmhAppNode = $cmhManifest.SelectSingleNode('//ns:Application', $cmhNsMgr) } if ($null -ne $cmhAppNode) { $cmhExtNode = $cmhAppNode.SelectSingleNode('ns:Extensions', $cmhNsMgr) if ($null -eq $cmhExtNode) { $cmhExtNode = $cmhManifest.CreateElement('Extensions', $AppXNamespaces['ns']) $null = $cmhAppNode.AppendChild($cmhExtNode) } Add-MSIXManifestNamespace -Manifest $cmhManifest -Prefixes 'com', 'desktop9' $nsCom = $AppXNamespaces['com'] $nsDesktop9 = $AppXNamespaces['desktop9'] # com:SurrogateServer is the schema-valid way to register a DLL COM server # in MSIX. com:Class/@Path names the DLL so DllHost can load it. $comExt = $cmhManifest.CreateElement('com', 'Extension', $nsCom) $null = $comExt.SetAttribute('Category', 'windows.comServer') $comServer = $cmhManifest.CreateElement('com', 'ComServer', $nsCom) $null = $comExt.AppendChild($comServer) $surrogate = $cmhManifest.CreateElement('com', 'SurrogateServer', $nsCom) $null = $surrogate.SetAttribute('AppId', $cmhAppId) $null = $comServer.AppendChild($surrogate) $cmhClassEl = $cmhManifest.CreateElement('com', 'Class', $nsCom) $null = $cmhClassEl.SetAttribute('Id', $cmhClsidNoBrace) $null = $cmhClassEl.SetAttribute('Path', 'VFS\ProgramFilesX64\WinRAR\MsixContextMenuHandler.dll') $null = $cmhClassEl.SetAttribute('ThreadingModel', 'STA') $null = $surrogate.AppendChild($cmhClassEl) $null = $cmhExtNode.AppendChild($comExt) # desktop9:ExtensionHandler entries — follow 7-Zip MSIX pattern: # Type="*" for all files, Type="Directory" and Type="Folder" for directories. $d9Ext = $cmhManifest.CreateElement('desktop9', 'Extension', $nsDesktop9) $null = $d9Ext.SetAttribute('Category', 'windows.fileExplorerClassicContextMenuHandler') $d9Handler = $cmhManifest.CreateElement('desktop9', 'FileExplorerClassicContextMenuHandler', $nsDesktop9) $null = $d9Ext.AppendChild($d9Handler) foreach ($handlerType in @('*', 'Directory', 'Folder')) { $d9Entry = $cmhManifest.CreateElement('desktop9', 'ExtensionHandler', $nsDesktop9) $null = $d9Entry.SetAttribute('Type', $handlerType) $null = $d9Entry.SetAttribute('Clsid', $cmhClsidNoBrace) $null = $d9Handler.AppendChild($d9Entry) } $null = $cmhExtNode.AppendChild($d9Ext) $cmhManifest.PreserveWhitespace = $false $cmhManifest.Save($cmhManifestPath) Write-Verbose "Registered MsixContextMenuHandler in AppxManifest.xml (CLSID: $cmhClsid)" } else { Write-Warning "No Application found in AppxManifest.xml - MsixContextMenuHandler not registered." } } else { Write-Warning "MsixContextMenuHandler files not found in: $cmhLibsDir - skipping context menu handler." } # Copy PSF binaries: VC++ Runtime DLLs, launcher, fixup DLLs, scripts Add-MSIXPsfFrameworkFiles -MSIXFolder $MSIXFolder # Redirect every regular application entry through PsfLauncher64. # Add-MSXIXPSFShim renames the Application Id and returns the new Id. $apps = Get-MSIXApplications -MSIXFolder $MSIXFolder if ($null -eq $apps -or $apps.Count -eq 0) { Write-Warning "No application entries found in AppxManifest.xml." } else { $shimmedIds = @{} foreach ($app in $apps) { Write-Verbose "Adding PSF shim for application: $($app.Id)" $newId = Add-MSXIXPSFShim -MSIXFolder $MSIXFolder -MISXAppID $app.Id -PSFArchitektur x64 $shimmedIds[$app.Id] = $newId } } # Add execution alias so WinRAR.exe can be invoked from Run dialog and command line. $winrarApp = $apps | Where-Object { $_.Executable -like '*WinRAR.exe' } | Select-Object -First 1 if ($null -ne $winrarApp) { $winrarNewId = $shimmedIds[$winrarApp.Id] Add-MSIXAppExecutionAlias -MSIXFolder $MSIXFolder ` -MISXAppID $winrarNewId ` -CommandlineAlias 'WinRAR.exe' ` -Executable 'VFS\ProgramFilesX64\WinRAR\WinRAR.exe' } Add-MSIXPSFDefaultRegLegacy -MSIXFolder $MSIXFolder Add-MSIXPSFMFRFixup -MSIXFolder $MSIXFolder -IlvAware $true Add-MSIXPSFDynamicLibraryFixup -MSIXFolder $MSIXFolder Close-MSIXPackage -MSIXFolder $MSIXFolder -MSIXFile $OutputFilePath -Force:$Force -KeepMSIXFolder:$KeepMSIXFolder "WinRAR PSF fix applied: $OutputFilePath" } catch { Write-Error "Error applying WinRAR PSF fix: $_" } } } |