Public/Set-MSIXFileTypeAssociation.ps1
|
function Set-MSIXFileTypeAssociation { <# .SYNOPSIS Updates an existing windows.fileTypeAssociation (FTA) in an MSIX package. .DESCRIPTION Modifies an FTA that already exists (use Add-MSIXFileTypeAssociation to create one). -FileType REPLACES the registered extension list; -Logo/-InfoTip/-MultiSelectModel update those attributes; the verb parameters add or update the verb matched by -VerbId. Accepts FTA objects from Get-MSIXFileTypeAssociation via the pipeline. .PARAMETER MSIXFolderPath Expanded MSIX package folder. Pipeline by property name. .PARAMETER AppId Application Id. Binds from Get-MSIXApplications (Id) or Get-MSIXFileTypeAssociation (ApplicationId). .PARAMETER Name Name of the FTA to update. .PARAMETER FileType Replacement extension list (a leading dot is added if missing). .PARAMETER VerbId Verb to add or update. .PARAMETER VerbParameters Verb parameters. .PARAMETER VerbDisplayName Context-menu display text for the verb. .PARAMETER ExtendedVerb Show the verb only with Shift held down. .PARAMETER DefaultVerb Mark the verb as the default (uap7:Default). .PARAMETER Logo Package-relative path to a logo image (uap:Logo). .PARAMETER InfoTip Tooltip text (uap:InfoTip). .PARAMETER MultiSelectModel Activation model for multi-file selection: Player, Document or Single. .EXAMPLE Get-MSIXFileTypeAssociation -MSIXFolder $pkg -Name putty | Set-MSIXFileTypeAssociation -Logo 'Assets\putty.png' .NOTES Andreas Nick, 2026 #> [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [Alias('MSIXFolder')] [System.IO.DirectoryInfo] $MSIXFolderPath, [Parameter(ValueFromPipelineByPropertyName = $true)] [Alias('Id', 'ApplicationId')] [string] $AppId, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, Position = 1)] [string] $Name, [string[]] $FileType, [string] $VerbId, [string] $VerbParameters, [string] $VerbDisplayName, [switch] $ExtendedVerb, [switch] $DefaultVerb, [string] $Logo, [string] $InfoTip, [ValidateSet('Player', 'Document', 'Single')] [string] $MultiSelectModel ) process { $manifestPath = Join-Path $MSIXFolderPath 'AppxManifest.xml' if (-not (Test-Path $manifestPath)) { Write-Error "AppxManifest.xml not found in: $($MSIXFolderPath.FullName)" return } $manifest = New-Object System.Xml.XmlDocument $manifest.Load($manifestPath) $prefixes = @('uap', 'uap2', 'uap3') if ($DefaultVerb) { $prefixes += 'uap7' } Add-MSIXManifestNamespace -Manifest $manifest -Prefixes $prefixes $nsmgr = New-Object System.Xml.XmlNamespaceManager($manifest.NameTable) $AppXNamespaces.GetEnumerator() | ForEach-Object { $null = $nsmgr.AddNamespace($_.Key, $_.Value) } $uapUri = $AppXNamespaces['uap'] $uap2Uri = $AppXNamespaces['uap2'] $uap3Uri = $AppXNamespaces['uap3'] $uap7Uri = $AppXNamespaces['uap7'] # Locate the FTA (optionally scoped to a single Application). $appFilter = if ($AppId) { "[@Id='$AppId']" } else { '' } $ftaNode = $manifest.SelectSingleNode("//ns:Package/ns:Applications/ns:Application$appFilter/ns:Extensions/uap3:Extension[@Category='windows.fileTypeAssociation']/uap3:FileTypeAssociation[@Name='$Name']", $nsmgr) if ($null -eq $ftaNode) { $scope = if ($AppId) { "Application '$AppId'" } else { 'the package' } Write-Error "No file type association named '$Name' found in $scope. Use Add-MSIXFileTypeAssociation to create it." return } $ownerId = $ftaNode.SelectSingleNode('ancestor::ns:Application', $nsmgr).GetAttribute('Id') if (-not $PSCmdlet.ShouldProcess($ownerId, "Update file type association '$Name'")) { return } if ($PSBoundParameters.ContainsKey('MultiSelectModel')) { $null = $ftaNode.SetAttribute('MultiSelectModel', $MultiSelectModel) } # Replace the extension list when -FileType is supplied. if ($PSBoundParameters.ContainsKey('FileType')) { $sftEl = $ftaNode.SelectSingleNode('uap:SupportedFileTypes', $nsmgr) if ($null -ne $sftEl) { $null = $ftaNode.RemoveChild($sftEl) } $sftEl = $manifest.CreateElement('uap:SupportedFileTypes', $uapUri) foreach ($ext in $FileType) { $e = $ext.Trim() if (-not $e.StartsWith('.')) { $e = ".$e" } $ftEl = $manifest.CreateElement('uap:FileType', $uapUri) $ftEl.InnerText = $e.ToLowerInvariant() $null = $sftEl.AppendChild($ftEl) } # Keep schema order: SupportedFileTypes before SupportedVerbs. $svExisting = $ftaNode.SelectSingleNode('uap2:SupportedVerbs', $nsmgr) if ($null -ne $svExisting) { $null = $ftaNode.InsertBefore($sftEl, $svExisting) } else { $null = $ftaNode.AppendChild($sftEl) } } # Logo / InfoTip. if ($PSBoundParameters.ContainsKey('Logo')) { $logoEl = $ftaNode.SelectSingleNode('uap:Logo', $nsmgr) if ($null -eq $logoEl) { $logoEl = $manifest.CreateElement('uap:Logo', $uapUri) $null = $ftaNode.PrependChild($logoEl) } $logoEl.InnerText = $Logo } if ($PSBoundParameters.ContainsKey('InfoTip')) { $itEl = $ftaNode.SelectSingleNode('uap:InfoTip', $nsmgr) if ($null -eq $itEl) { $itEl = $manifest.CreateElement('uap:InfoTip', $uapUri) $sftRef = $ftaNode.SelectSingleNode('uap:SupportedFileTypes', $nsmgr) if ($null -ne $sftRef) { $null = $ftaNode.InsertBefore($itEl, $sftRef) } else { $null = $ftaNode.AppendChild($itEl) } } $itEl.InnerText = $InfoTip } # Add or update the verb (only when -VerbId is supplied). if ($VerbId) { $svEl = $ftaNode.SelectSingleNode('uap2:SupportedVerbs', $nsmgr) if ($null -eq $svEl) { $svEl = $manifest.CreateElement('uap2:SupportedVerbs', $uap2Uri) $null = $ftaNode.AppendChild($svEl) } $verbEl = $svEl.SelectSingleNode("uap3:Verb[@Id='$VerbId']", $nsmgr) if ($null -eq $verbEl) { $verbEl = $manifest.CreateElement('uap3:Verb', $uap3Uri) $null = $verbEl.SetAttribute('Id', $VerbId) $null = $svEl.AppendChild($verbEl) } if ($PSBoundParameters.ContainsKey('VerbParameters')) { $null = $verbEl.SetAttribute('Parameters', $VerbParameters) } if ($ExtendedVerb) { $null = $verbEl.SetAttribute('Extended', 'true') } if ($DefaultVerb) { $null = $verbEl.SetAttribute('Default', $uap7Uri, 'true') } $verbEl.InnerText = if ($VerbDisplayName) { $VerbDisplayName } else { $VerbId } } $manifest.Save($manifestPath) Write-Verbose "Saved $manifestPath" } } |