Public/Add-MSIXloaderSearchPathOverride.ps1

function Add-MSIXloaderSearchPathOverride {
    <#
.SYNOPSIS
Adds a search path override to the LoaderSearchPathEntry element in the AppxManifest.xml file of an MSIX package.
 
.DESCRIPTION
The Add-MSIXloaderSearchPathOverride function adds a search path override to the LoaderSearchPathEntry element in the AppxManifest.xml file of an MSIX package. This allows the MSIX package to load dependencies from specified folder paths.
 
.PARAMETER MSIXFolderPath
Specifies the path to the folder containing the MSIX package.
 
.PARAMETER FolderPaths
Specifies an array of folder paths to be added as search paths in the LoaderSearchPathEntry element.
 
.EXAMPLE
Add-MSIXloaderSearchPathOverride -MSIXFolderPath "C:\MyMSIXPackage" -FolderPaths "libs", "plugins"
 
This example adds "libs" and "plugins" as search paths in the LoaderSearchPathEntry element of the AppxManifest.xml file located in the "C:\MyMSIXPackage" folder.
 
.EXAMPLE
Add-MSIXloaderSearchPathOverride -FolderPaths "Bin",'VFS\ProgramFilesX64\GIMP 3\bin','VFS\ProgramFilesX64\GIMP 2\bin' -MSIXFolderPath $MsixFolder
 
PROVEN WORKING EXAMPLE (GIMP): Use backslashes for VFS paths. Spaces in folder names
are written as-is (not URL-encoded). Multiple paths cover root-packaged and both
VFS variants (GIMP 2 and GIMP 3) in one call.
 
Author: Andreas Nick
Date: 01/10/2022
https://www.nick-it.de
#>


    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [System.IO.DirectoryInfo] $MSIXFolderPath,
        [Parameter(Mandatory = $true)]
        [String[]] $FolderPaths,
        [Switch] $SaveManifestCopy
    )

    if (Test-Path "$MSIXFolderPath\AppxManifest.xml") {
    
        # Load existing manifest
        [xml]$manifest = Get-Content "$MSIXFolderPath\AppxManifest.xml"
    
        Add-MSIXManifestNamespace -Manifest $manifest -Prefixes 'uap6'

        # Create namespace manager
        $nsmgr = New-Object System.Xml.XmlNamespaceManager($manifest.NameTable)
        $null = $nsmgr.AddNamespace("default", "http://schemas.microsoft.com/appx/manifest/foundation/windows10") 
        $null = $nsmgr.AddNamespace("uap6", "http://schemas.microsoft.com/appx/manifest/uap/windows10/6")
    
        # Check if LoaderSearchPathOverride exists
        if ($null -eq $manifest.SelectSingleNode("//uap6:LoaderSearchPathOverride", $nsmgr)) {
            
            $extensionsNode = $manifest.SelectSingleNode("//default:Package/default:Extensions", $nsmgr)
    
            if ($null -eq $extensionsNode) {
                # If Extensions node does not exist, create it
                $extensionsNode = $manifest.CreateElement("Extensions", $manifest.DocumentElement.NamespaceURI)

                # Insert the Extensions node before the Applications node (or at the right place in the hierarchy)
                $applicationsNode = $manifest.SelectSingleNode("//default:Package/default:Applications", $nsmgr)
                if ($null -ne $applicationsNode) {
                    [void]$manifest.DocumentElement.InsertAfter($extensionsNode, $applicationsNode)
                }
                else {
                    # In case Applications node is missing, just append Extensions at the root level
                    [void]$manifest.DocumentElement.AppendChild($extensionsNode)
                }
            }

            $extensionNode = $manifest.CreateElement("uap6:Extension", 'http://schemas.microsoft.com/appx/manifest/uap/windows10/6')
        
            # Set attributes and append nodes
            $categoryAttribute = $manifest.CreateAttribute("Category")
            $categoryAttribute.Value = 'windows.loaderSearchPathOverride'
            $null = $extensionNode.Attributes.Append($categoryAttribute)
        
            $loaderSearchPathOverrideNode = $manifest.CreateElement("uap6:LoaderSearchPathOverride", 'http://schemas.microsoft.com/appx/manifest/uap/windows10/6')
            foreach ($Item in $FolderPaths) {
                $entryNode = $manifest.CreateElement("uap6:LoaderSearchPathEntry", 'http://schemas.microsoft.com/appx/manifest/uap/windows10/6')
                $folderAttribute = $manifest.CreateAttribute("FolderPath")
                $folderAttribute.Value = ($Item -replace '/', '\')
                $null = $entryNode.Attributes.Append($folderAttribute)
                $null = $loaderSearchPathOverrideNode.AppendChild($entryNode)
            }
        
            # Append elements correctly
            $null = $extensionNode.AppendChild($loaderSearchPathOverrideNode)
            $null = $extensionsNode.AppendChild($extensionNode)
            #[void] $manifest.DocumentElement.AppendChild($extensionsNode)
        
            if ($SaveManifestCopy.IsPresent) {
                Write-Verbose "Creating backup of AppxManifest.xml"
                Rename-Item -Path "$MSIXFolderPath\AppxManifest.xml" -NewName "AppxManifest.xml.$(Get-Date -Format 'yyyymmdd-hhmmss')"
            }
        
            Write-Verbose "Saving modified AppxManifest.xml"
            $null = $manifest.Save("$MSIXFolderPath\AppxManifest.xml")
        }
        else {
            Write-Warning "Element Exists: Package.Extensions.Extension.LoaderSearchPathOverride.LoaderSearchPathEntry"
        }
    }
    else {
        Write-Warning "Cannot open path $($MSIXFolderPath)\AppxManifest.xml"
    }
}