Public/Remove-MSIXFileTypeAssociation.ps1

function Remove-MSIXFileTypeAssociation {
<#
.SYNOPSIS
    Removes windows.fileTypeAssociation (FTA) entries from an MSIX package.
.DESCRIPTION
    Selects FTAs by -Name or by -FileType and removes them. With -VerbId only that
    verb is removed and the association is kept. An Extensions node left empty is
    pruned. Works with Get-MSIXApplications and Get-MSIXFileTypeAssociation pipelines.
.PARAMETER MSIXFolderPath
    Expanded MSIX package folder. Pipeline by property name.
.PARAMETER AppId
    Limit removal to one Application Id. Binds from Get-MSIXApplications (Id) or
    Get-MSIXFileTypeAssociation (ApplicationId). Omit to act across all Applications.
.PARAMETER Name
    Remove the FTA with this name.
.PARAMETER FileType
    Remove FTAs whose supported file types include this extension (leading dot optional).
.PARAMETER VerbId
    Remove only this verb from the matched FTAs instead of the whole association.
.EXAMPLE
    Get-MSIXApplications -MSIXFolder $pkg | Remove-MSIXFileTypeAssociation -FileType .html
.EXAMPLE
    Get-MSIXFileTypeAssociation -MSIXFolder $pkg -Name putty | Remove-MSIXFileTypeAssociation
.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(ValueFromPipelineByPropertyName = $true, Position = 1)]
        [string] $Name,

        [string] $FileType,

        [string] $VerbId
    )

    process {
        if (-not $Name -and -not $FileType) {
            Write-Error "Specify -Name or -FileType to select what to remove."
            return
        }

        $manifestPath = Join-Path $MSIXFolderPath 'AppxManifest.xml'
        if (-not (Test-Path $manifestPath)) {
            Write-Error "AppxManifest.xml not found in: $($MSIXFolderPath.FullName)"
            return
        }

        $matchExt = $null
        if ($FileType) {
            $matchExt = $FileType.Trim()
            if (-not $matchExt.StartsWith('.')) { $matchExt = ".$matchExt" }
            $matchExt = $matchExt.ToLowerInvariant()
        }

        $manifest = New-Object System.Xml.XmlDocument
        $manifest.Load($manifestPath)

        $nsmgr = New-Object System.Xml.XmlNamespaceManager($manifest.NameTable)
        $AppXNamespaces.GetEnumerator() | ForEach-Object { $null = $nsmgr.AddNamespace($_.Key, $_.Value) }

        $removedAny = $false

        foreach ($app in $manifest.SelectNodes('//ns:Package/ns:Applications/ns:Application', $nsmgr)) {
            $thisId = $app.GetAttribute('Id')
            if ($AppId -and $thisId -ne $AppId) { continue }

            $extNode = $app.SelectSingleNode('ns:Extensions', $nsmgr)
            if ($null -eq $extNode) { continue }

            foreach ($ext in @($extNode.SelectNodes("uap3:Extension[@Category='windows.fileTypeAssociation']", $nsmgr))) {
                $fta = $ext.SelectSingleNode('uap3:FileTypeAssociation', $nsmgr)
                if ($null -eq $fta) { continue }

                $ftaName = $fta.GetAttribute('Name')
                if ($Name -and $ftaName -ne $Name.ToLowerInvariant()) { continue }

                if ($matchExt) {
                    $hasExt = $false
                    foreach ($ft in $fta.SelectNodes('uap:SupportedFileTypes/uap:FileType', $nsmgr)) {
                        if ($ft.InnerText.ToLowerInvariant() -eq $matchExt) { $hasExt = $true; break }
                    }
                    if (-not $hasExt) { continue }
                }

                if ($VerbId) {
                    $verbEl = $fta.SelectSingleNode("uap2:SupportedVerbs/uap3:Verb[@Id='$VerbId']", $nsmgr)
                    if ($null -eq $verbEl) { continue }
                    if (-not $PSCmdlet.ShouldProcess($thisId, "Remove verb '$VerbId' from FTA '$ftaName'")) { continue }
                    $svEl = $verbEl.ParentNode
                    $null = $svEl.RemoveChild($verbEl)
                    if ($svEl.SelectNodes('uap3:Verb', $nsmgr).Count -eq 0) { $null = $fta.RemoveChild($svEl) }
                    $removedAny = $true
                    Write-Verbose "Removed verb '$VerbId' from FTA '$ftaName' on '$thisId'."
                }
                else {
                    if (-not $PSCmdlet.ShouldProcess($thisId, "Remove FTA '$ftaName'")) { continue }
                    $null = $extNode.RemoveChild($ext)
                    $removedAny = $true
                    Write-Verbose "Removed FTA '$ftaName' from '$thisId'."
                }
            }

            # Drop an Extensions node that is now empty.
            if ($null -ne $extNode -and $extNode.SelectNodes('*').Count -eq 0) {
                $null = $app.RemoveChild($extNode)
            }
        }

        if ($removedAny) {
            $manifest.Save($manifestPath)
            Write-Verbose "Saved $manifestPath"
        }
        else {
            Write-Warning "No matching file type association found to remove."
        }
    }
}