diff --git a/functions/private/Invoke-WinUtilTweaks.ps1 b/functions/private/Invoke-WinUtilTweaks.ps1
index 2576f7f9..406efec0 100644
--- a/functions/private/Invoke-WinUtilTweaks.ps1
+++ b/functions/private/Invoke-WinUtilTweaks.ps1
@@ -78,6 +78,7 @@ function Invoke-WinUtilTweaks {
$sync.configs.tweaks.$CheckBox.appx | ForEach-Object {
Remove-WinUtilAPPX -Name $psitem
}
+ Remove-WinUtilProvisionedAPPX -PackageList $sync.configs.tweaks.$CheckBox.appx
}
}
Write-WinUtilLog -Component "Tweaks" -Message "$action tweak completed: $CheckBox"
diff --git a/functions/private/Remove-WinUtilAPPX.ps1 b/functions/private/Remove-WinUtilAPPX.ps1
index 38759157..8efffea8 100644
--- a/functions/private/Remove-WinUtilAPPX.ps1
+++ b/functions/private/Remove-WinUtilAPPX.ps1
@@ -17,7 +17,20 @@ function Remove-WinUtilAPPX {
Write-Host "Removing $Name"
Write-WinUtilLog -Component "AppX" -Message "Removing AppX package pattern: $Name"
- Get-AppxPackage $Name -AllUsers | Remove-AppxPackage -AllUsers
- Get-AppxProvisionedPackage -Online | Where-Object DisplayName -like $Name | Remove-AppxProvisionedPackage -Online
+
+ # We explicitly loop through packages instead of using the pipeline because PowerShell 7 pipeline binding
+ # for Remove-AppxPackage fails silently, and Get-AppxPackage -AllUsers returns duplicate objects for each user profile.
+ $pkgs = Get-AppxPackage "*$Name*" -AllUsers | Sort-Object -Property PackageFullName -Unique
+ if ($null -ne $pkgs) {
+ foreach ($pkg in $pkgs) {
+ try {
+ Remove-AppxPackage -Package $pkg.PackageFullName -AllUsers -ErrorAction Stop
+ }
+ catch {
+ Write-WinUtilLog -Level "ERROR" -Component "AppX" -Message "Failed to remove AppX package $($pkg.PackageFullName): $($_.Exception.Message)"
+ }
+ }
+ }
+
Write-WinUtilLog -Component "AppX" -Message "AppX removal completed for package pattern: $Name"
}
diff --git a/functions/private/Remove-WinUtilProvisionedAPPX.ps1 b/functions/private/Remove-WinUtilProvisionedAPPX.ps1
new file mode 100644
index 00000000..98709fb4
--- /dev/null
+++ b/functions/private/Remove-WinUtilProvisionedAPPX.ps1
@@ -0,0 +1,61 @@
+function Remove-WinUtilProvisionedAPPX {
+ <#
+
+ .SYNOPSIS
+ Removes all AppX provisioned packages that match the given names
+
+ .PARAMETER PackageList
+ An array of names of the APPX packages to remove
+
+ .EXAMPLE
+ Remove-WinUtilProvisionedAPPX -PackageList @("Microsoft.Microsoft3DViewer", "Microsoft.WindowsCalculator")
+
+ #>
+ param (
+ [string[]]$PackageList
+ )
+
+ if ($null -eq $PackageList -or $PackageList.Count -eq 0) {
+ return
+ }
+
+ Write-Host "`nRemoving provisioned packages..."
+ Write-WinUtilLog -Component "AppX" -Message "Removing AppX provisioned packages: $($PackageList -join ', ')"
+
+ # DISM cmdlets like Get-AppxProvisionedPackage often fail with "Class not registered" or hang in PowerShell 7.
+ # We shell out to Windows PowerShell 5.1 (powershell.exe) to reliably remove the provisioned packages.
+ $ps5Command = {
+ $pkgs = $args
+ $provisionedPackages = Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue
+ $failures = [System.Collections.Generic.List[string]]::new()
+
+ foreach ($Package in $pkgs) {
+ $provs = $provisionedPackages |
+ Where-Object DisplayName -Like "*$Package*"
+
+ if ($null -ne $provs) {
+ foreach ($prov in $provs) {
+ try {
+ Remove-AppxProvisionedPackage -Online -PackageName $prov.PackageName -ErrorAction Stop | Out-Null
+ }
+ catch {
+ $failures.Add("Failed to remove provisioned AppX package $($prov.PackageName): $($_.Exception.Message)")
+ }
+ }
+ }
+ }
+
+ if ($failures.Count -gt 0) {
+ throw ($failures -join [Environment]::NewLine)
+ }
+ }
+
+ $removalOutput = powershell.exe -NoProfile -NonInteractive -Command $ps5Command -args $PackageList 2>&1
+ if ($LASTEXITCODE -ne 0 -or $null -ne $removalOutput) {
+ $failureDetails = ($removalOutput | Out-String).Trim()
+ Write-WinUtilLog -Level "ERROR" -Component "AppX" -Message "AppX provisioned package removal failed: $failureDetails"
+ return
+ }
+
+ Write-WinUtilLog -Component "AppX" -Message "AppX provisioned package removal completed."
+}
diff --git a/functions/public/Invoke-WPFAppxRemoval.ps1 b/functions/public/Invoke-WPFAppxRemoval.ps1
index 7facc196..ef4f13ea 100644
--- a/functions/public/Invoke-WPFAppxRemoval.ps1
+++ b/functions/public/Invoke-WPFAppxRemoval.ps1
@@ -13,11 +13,13 @@ function Invoke-WPFAppxRemoval {
$sync.ProcessRunning = $true
Write-WinUtilLog -Component "AppX" -Message "Starting AppX removal for $(@($selected).Count) selected package(s)."
+ $packageList = [System.Collections.Generic.List[string]]::new()
+
foreach ($key in $selected) {
if ($key -eq "WPFAppxMicrosoft_XboxGamingOverlay") {
# Making sure Game Bar isn't running
Write-WinUtilLog -Component "AppX" -Message "Stopping GameBarFTServer before removing Xbox Gaming Overlay."
- Stop-Process -Name GameBarFTServer
+ Stop-Process -Name GameBarFTServer -Force -Confirm:$false -ErrorAction SilentlyContinue
# This stops annoying ms-gamebar popup when launching games.
Write-WinUtilLog -Component "AppX" -Message "Disabling Game DVR capture before removing Xbox Gaming Overlay."
@@ -25,14 +27,14 @@ function Invoke-WPFAppxRemoval {
}
if ($key -eq "WPFAppxMicrosoft_WindowsNotepad") {
- # i hope your having fun reading this
Write-WinUtilLog -Component "AppX" -Message "Stopping dllhost before removing Notepad."
- Stop-Process -Name dllhost
+ Stop-Process -Name dllhost -Force -Confirm:$false -ErrorAction SilentlyContinue
}
Write-Host "Removing $($apps[$key].Content)"
Write-WinUtilLog -Component "AppX" -Message "Removing $($apps[$key].Content) ($($apps[$key].PackageId))."
- Get-AppxPackage -Name $apps[$key].PackageId -AllUsers | Remove-AppxPackage -AllUsers
+ Remove-WinUtilAPPX -Name $apps[$key].PackageId
+ $packageList.Add($apps[$key].PackageId)
if ($key -eq "WPFAppxMSTeams") {
# Uninstalls Microsoft Teams Meeting Add-in for Microsoft Office
@@ -41,6 +43,10 @@ function Invoke-WPFAppxRemoval {
}
}
+ if ($packageList.Count -gt 0) {
+ Remove-WinUtilProvisionedAPPX -PackageList $packageList.ToArray()
+ }
+
Write-Host "================================="
Write-Host "-- AppX Removal Finished ---"
Write-Host "================================="
diff --git a/functions/public/Invoke-WPFButton.ps1 b/functions/public/Invoke-WPFButton.ps1
index 9b76f8f7..8a13c378 100644
--- a/functions/public/Invoke-WPFButton.ps1
+++ b/functions/public/Invoke-WPFButton.ps1
@@ -65,6 +65,8 @@ function Invoke-WPFButton {
"WPFUpdatessecurity" {Invoke-WPFUpdatessecurity}
"WPFGetInstalled" {Invoke-WPFGetInstalled -CheckBox "winget"}
"WPFGetInstalledTweaks" {Invoke-WPFGetInstalled -CheckBox "tweaks"}
+ "WPFAppxRemoval" {Invoke-WPFTab "WPFTab6BT"}
+ "WPFBackToTweaks" {Invoke-WPFTab "WPFTab2BT"}
"WPFRemoveSelectedAppx" {Invoke-WPFAppxRemoval}
"WPFDefaultAppxSelection" {Invoke-WPFPresets "AppxDefault" -checkboxfilterpattern "WPFAppx*"}
"WPFSelectAllAppx" {
diff --git a/functions/public/Invoke-WPFTab.ps1 b/functions/public/Invoke-WPFTab.ps1
index 8640318f..43c7a06a 100644
--- a/functions/public/Invoke-WPFTab.ps1
+++ b/functions/public/Invoke-WPFTab.ps1
@@ -19,13 +19,12 @@ function Invoke-WPFTab {
$tabNumber = [int]($ClickedTab -replace "WPFTab","" -replace "BT","") - 1
$filter = Get-WinUtilVariables -Type ToggleButton | Where-Object {$psitem -like "WPFTab?BT"}
+ $sync.$tabNav.Items[$tabNumber].IsSelected = $true
($sync.GetEnumerator()).where{$psitem.Key -in $filter} | ForEach-Object {
if ($ClickedTab -ne $PSItem.name) {
$sync[$PSItem.Name].IsChecked = $false
} else {
$sync["$ClickedTab"].IsChecked = $true
- $tabNumber = [int]($ClickedTab-replace "WPFTab","" -replace "BT","") - 1
- $sync.$tabNav.Items[$tabNumber].IsSelected = $true
}
}
$sync.currentTab = $sync.$tabNav.Items[$tabNumber].Header
diff --git a/pester/appx.Tests.ps1 b/pester/appx.Tests.ps1
index c56c8061..3a341dff 100644
--- a/pester/appx.Tests.ps1
+++ b/pester/appx.Tests.ps1
@@ -5,8 +5,21 @@
BeforeAll {
$script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
. (Join-Path $script:repoRoot "functions\private\Remove-WinUtilAPPX.ps1")
+ . (Join-Path $script:repoRoot "functions\private\Remove-WinUtilProvisionedAPPX.ps1")
. (Join-Path $script:repoRoot "functions\public\Invoke-WPFAppxRemoval.ps1")
+ $tokens = $null
+ $parseErrors = $null
+ $provisionedSourcePath = Join-Path $script:repoRoot "functions\private\Remove-WinUtilProvisionedAPPX.ps1"
+ $provisionedSourceAst = [System.Management.Automation.Language.Parser]::ParseFile($provisionedSourcePath, [ref]$tokens, [ref]$parseErrors)
+ $ps5CommandAssignment = $provisionedSourceAst.Find({
+ param($node)
+ $node -is [System.Management.Automation.Language.AssignmentStatementAst] -and
+ $node.Left -is [System.Management.Automation.Language.VariableExpressionAst] -and
+ $node.Left.VariablePath.UserPath -eq "ps5Command"
+ }, $true)
+ $script:provisionedRemovalScriptBlock = $ps5CommandAssignment.Right.Expression.ScriptBlock.GetScriptBlock()
+
function Write-WinUtilLog {
param($Message, $Level, $Component)
}
@@ -23,18 +36,25 @@ BeforeAll {
param(
[Parameter(ValueFromPipeline = $true)]
$InputObject,
- [switch]$AllUsers
+ $Package,
+ [switch]$AllUsers,
+ $ErrorAction
)
process { }
}
+ function Remove-WinUtilProvisionedAPPX {
+ param($PackageList)
+ }
function Get-AppxProvisionedPackage {
- param([switch]$Online)
+ param([switch]$Online, $ErrorAction)
}
function Remove-AppxProvisionedPackage {
param(
[Parameter(ValueFromPipeline = $true)]
$InputObject,
- [switch]$Online
+ [switch]$Online,
+ $PackageName,
+ $ErrorAction
)
process { }
}
@@ -58,35 +78,90 @@ Describe "Remove-WinUtilAPPX" {
Mock Write-Host { }
Mock Write-WinUtilLog { }
Mock Get-AppxPackage {
- [pscustomobject]@{
- Name = $Name
- }
+ @(
+ [pscustomobject]@{
+ Name = $Name
+ PackageFullName = "$Name.FullName"
+ }
+ [pscustomobject]@{
+ Name = $Name
+ PackageFullName = "$Name.FullName"
+ }
+ )
}
Mock Remove-AppxPackage { }
+ }
+
+ It "removes matching installed AppX packages" {
+ Remove-WinUtilAPPX -Name "Microsoft.Xbox*"
+
+ Should -Invoke -CommandName Get-AppxPackage -Times 1 -Exactly -ParameterFilter {
+ $Name -eq "*Microsoft.Xbox**" -and $AllUsers -eq $true
+ }
+ Should -Invoke -CommandName Remove-AppxPackage -Times 1 -Exactly -ParameterFilter {
+ $Package -eq "*Microsoft.Xbox**.FullName" -and
+ $AllUsers -eq $true -and
+ $ErrorAction -eq "Stop"
+ }
+ }
+
+ It "logs installed AppX removal failures" {
+ Mock Remove-AppxPackage { throw "Removal failed" }
+
+ { Remove-WinUtilAPPX -Name "Example.Package" } | Should -Not -Throw
+
+ Should -Invoke -CommandName Write-WinUtilLog -Times 1 -Exactly -ParameterFilter {
+ $Level -eq "ERROR" -and
+ $Component -eq "AppX" -and
+ $Message -eq "Failed to remove AppX package *Example.Package*.FullName: Removal failed"
+ }
+ }
+}
+
+Describe "Remove-WinUtilProvisionedAPPX" {
+ BeforeEach {
Mock Get-AppxProvisionedPackage {
@(
- [pscustomobject]@{ DisplayName = "Microsoft.XboxGamingOverlay" }
- [pscustomobject]@{ DisplayName = "Microsoft.WindowsCalculator" }
+ [pscustomobject]@{ DisplayName = "Example.One"; PackageName = "Example.One_1.0" }
+ [pscustomobject]@{ DisplayName = "Example.Two"; PackageName = "Example.Two_1.0" }
)
}
Mock Remove-AppxProvisionedPackage { }
}
- It "removes matching installed and provisioned AppX packages" {
- Remove-WinUtilAPPX -Name "Microsoft.Xbox*"
+ It "queries provisioned packages once for all selected package names" {
+ & $script:provisionedRemovalScriptBlock "Example.One" "Example.Two"
- Should -Invoke -CommandName Get-AppxPackage -Times 1 -Exactly -ParameterFilter {
- $Name -eq "Microsoft.Xbox*" -and $AllUsers -eq $true
- }
- Should -Invoke -CommandName Remove-AppxPackage -Times 1 -Exactly -ParameterFilter {
- $InputObject.Name -eq "Microsoft.Xbox*" -and $AllUsers -eq $true
- }
Should -Invoke -CommandName Get-AppxProvisionedPackage -Times 1 -Exactly -ParameterFilter {
$Online -eq $true
}
Should -Invoke -CommandName Remove-AppxProvisionedPackage -Times 1 -Exactly -ParameterFilter {
- $InputObject.DisplayName -eq "Microsoft.XboxGamingOverlay" -and $Online -eq $true
+ $PackageName -eq "Example.One_1.0" -and $Online -eq $true
}
+ Should -Invoke -CommandName Remove-AppxProvisionedPackage -Times 1 -Exactly -ParameterFilter {
+ $PackageName -eq "Example.Two_1.0" -and $Online -eq $true
+ }
+ }
+
+ It "surfaces provisioned package removal failures from the child process" {
+ Mock Remove-AppxProvisionedPackage { throw "DISM failed" }
+
+ { & $script:provisionedRemovalScriptBlock "Example.One" } |
+ Should -Throw "*Failed to remove provisioned AppX package Example.One_1.0: DISM failed*"
+ Should -Invoke -CommandName Remove-AppxProvisionedPackage -Times 1 -Exactly -ParameterFilter {
+ $PackageName -eq "Example.One_1.0" -and
+ $Online -eq $true -and
+ $ErrorAction -eq "Stop"
+ }
+ }
+
+ It "handles child process failures before logging completion" {
+ $source = Get-Content -Path $provisionedSourcePath -Raw
+
+ $source | Should -Match '\$removalOutput = powershell\.exe .* 2>&1'
+ $source | Should -Match 'if \(\$LASTEXITCODE -ne 0 -or \$null -ne \$removalOutput\)'
+ $source | Should -Match 'Write-WinUtilLog -Level "ERROR" -Component "AppX" -Message "AppX provisioned package removal failed:'
+ $source | Should -Match '(?s)AppX provisioned package removal failed:.*return.*AppX provisioned package removal completed\.'
}
}
@@ -190,9 +265,11 @@ Describe "Invoke-WPFAppxRemoval runspace body" {
Mock Get-AppxPackage {
[pscustomobject]@{
Name = $Name
+ PackageFullName = "$Name.FullName"
}
}
Mock Remove-AppxPackage { }
+ Mock Remove-WinUtilProvisionedAPPX { }
Mock Get-Package {
[pscustomobject]@{
Name = "Microsoft Teams Meeting Add-in"
@@ -216,10 +293,13 @@ Describe "Invoke-WPFAppxRemoval runspace body" {
& $script:capturedAppxScriptBlock -selected $selected -apps $script:apps
Should -Invoke -CommandName Get-AppxPackage -Times 1 -Exactly -ParameterFilter {
- $Name -eq "Example.Package" -and $AllUsers -eq $true
+ $Name -eq "*Example.Package*" -and $AllUsers -eq $true
}
Should -Invoke -CommandName Remove-AppxPackage -Times 1 -Exactly -ParameterFilter {
- $InputObject.Name -eq "Example.Package" -and $AllUsers -eq $true
+ $Package -eq "*Example.Package*.FullName"
+ }
+ Should -Invoke -CommandName Remove-WinUtilProvisionedAPPX -Times 1 -Exactly -ParameterFilter {
+ $PackageList.Count -eq 1 -and $PackageList[0] -eq "Example.Package"
}
$script:sync.ProcessRunning | Should -BeFalse
}
@@ -239,7 +319,10 @@ Describe "Invoke-WPFAppxRemoval runspace body" {
& $script:capturedAppxScriptBlock -selected $selected -apps $script:apps
Should -Invoke -CommandName Stop-Process -Times 1 -Exactly -ParameterFilter {
- $Name -eq "GameBarFTServer"
+ $Name -eq "GameBarFTServer" -and
+ $Force -eq $true -and
+ $Confirm -eq $false -and
+ $ErrorAction -eq "SilentlyContinue"
}
Should -Invoke -CommandName Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\GameDVR" -and
@@ -247,7 +330,10 @@ Describe "Invoke-WPFAppxRemoval runspace body" {
$Value -eq 0
}
Should -Invoke -CommandName Stop-Process -Times 1 -Exactly -ParameterFilter {
- $Name -eq "dllhost"
+ $Name -eq "dllhost" -and
+ $Force -eq $true -and
+ $Confirm -eq $false -and
+ $ErrorAction -eq "SilentlyContinue"
}
Should -Invoke -CommandName Get-Package -Times 1 -Exactly -ParameterFilter {
$Name -eq "Microsoft Teams*"
@@ -256,6 +342,12 @@ Describe "Invoke-WPFAppxRemoval runspace body" {
$InputObject.Name -eq "Microsoft Teams Meeting Add-in" -and $Force -eq $true
}
Should -Invoke -CommandName Remove-AppxPackage -Times 3 -Exactly
+ Should -Invoke -CommandName Remove-WinUtilProvisionedAPPX -Times 1 -Exactly -ParameterFilter {
+ $PackageList.Count -eq 3 -and
+ $PackageList[0] -eq "Microsoft.XboxGamingOverlay" -and
+ $PackageList[1] -eq "Microsoft.WindowsNotepad" -and
+ $PackageList[2] -eq "MSTeams"
+ }
$script:sync.ProcessRunning | Should -BeFalse
}
}
diff --git a/pester/tweaks.Tests.ps1 b/pester/tweaks.Tests.ps1
index 7eda9d2d..bf6cf08a 100644
--- a/pester/tweaks.Tests.ps1
+++ b/pester/tweaks.Tests.ps1
@@ -19,6 +19,9 @@ BeforeAll {
function Remove-WinUtilAPPX {
param($Name)
}
+ function Remove-WinUtilProvisionedAPPX {
+ param($PackageList)
+ }
function Set-WinUtilDNS {
param($DNSProvider)
}
@@ -89,6 +92,7 @@ Describe "Invoke-WinUtilTweaks" {
Mock Set-WinUtilRegistry { }
Mock Invoke-WinUtilScript { }
Mock Remove-WinUtilAPPX { }
+ Mock Remove-WinUtilProvisionedAPPX { }
Mock Write-WinUtilLog { }
Mock Write-Warning { }
}
@@ -118,6 +122,9 @@ Describe "Invoke-WinUtilTweaks" {
Should -Invoke -CommandName Remove-WinUtilAPPX -Times 1 -Exactly -ParameterFilter {
$Name -eq "Microsoft.ExampleApp"
}
+ Should -Invoke -CommandName Remove-WinUtilProvisionedAPPX -Times 1 -Exactly -ParameterFilter {
+ $PackageList.Count -eq 1 -and $PackageList[0] -eq "Microsoft.ExampleApp"
+ }
}
It "uses original registry values and service startup types in undo mode" {
@@ -137,6 +144,7 @@ Describe "Invoke-WinUtilTweaks" {
$Name -eq "WPFTweaksExample" -and $ScriptBlock.ToString() -eq "Write-Output 'undo tweak'"
}
Should -Invoke -CommandName Remove-WinUtilAPPX -Times 0 -Exactly
+ Should -Invoke -CommandName Remove-WinUtilProvisionedAPPX -Times 0 -Exactly
}
It "keeps a user-changed service startup type by default" {
diff --git a/pester/xaml.Tests.ps1 b/pester/xaml.Tests.ps1
index 42aac74a..be312992 100644
--- a/pester/xaml.Tests.ps1
+++ b/pester/xaml.Tests.ps1
@@ -134,7 +134,6 @@ Describe "XAML document" {
"WPFTab3BT",
"WPFTab4BT",
"WPFTab5BT",
- "WPFTab6BT",
"SearchBar",
"SearchBarClearButton",
"appscategory",
@@ -148,6 +147,7 @@ Describe "XAML document" {
"WPFAdvanced",
"WPFClearTweaksSelection",
"WPFGetInstalledTweaks",
+ "WPFAppxRemoval",
"WPFTweaksbutton",
"WPFUndoall",
"WPFUpdatesdefault",
@@ -157,6 +157,7 @@ Describe "XAML document" {
"WPFGetInstalledAppx",
"WPFSelectAllAppx",
"WPFClearAppxSelection",
+ "WPFBackToTweaks",
"WPFRemoveSelectedAppx"
)
@@ -223,6 +224,27 @@ Describe "XAML document" {
}
}
}
+
+ It "opens AppX removal from Tweaks and provides a return path" {
+ $navPanel = $script:xaml.SelectSingleNode('//*[local-name()="StackPanel"][@Name="NavDockPanel"]')
+ $tweaksTab = $script:xaml.SelectSingleNode('//*[local-name()="TabItem"][@Name="WPFTab2"]')
+ $appxTab = $script:xaml.SelectSingleNode('//*[local-name()="TabItem"][@Name="WPFTab6"]')
+ $openButton = $tweaksTab.SelectSingleNode('.//*[local-name()="Button"][@Name="WPFAppxRemoval"]')
+ $buttonNames = @($openButton.ParentNode.SelectNodes('./*[local-name()="Button"]') | ForEach-Object { $_.GetAttribute("Name") })
+ $getInstalledIndex = [array]::IndexOf($buttonNames, "WPFGetInstalledTweaks")
+ $openAppxIndex = [array]::IndexOf($buttonNames, "WPFAppxRemoval")
+ $buttonSource = Get-Content -Path $script:buttonScriptPath -Raw
+ $tabSource = Get-Content -Path (Join-Path $script:functionRoot "public\Invoke-WPFTab.ps1") -Raw
+
+ $navPanel.SelectSingleNode('./*[local-name()="ToggleButton"][@Name="WPFTab6BT"]') | Should -BeNullOrEmpty
+ $openButton.GetAttribute("Content").Trim() | Should -Be "AppX Removal"
+ $openAppxIndex | Should -Be ($getInstalledIndex + 1)
+ $appxTab.SelectSingleNode('.//*[local-name()="Button"][@Name="WPFBackToTweaks"]') | Should -Not -BeNullOrEmpty
+ $appxTab.SelectSingleNode('.//*[local-name()="Button"][@Name="WPFRemoveSelectedAppx"]') | Should -Not -BeNullOrEmpty
+ $buttonSource | Should -Match '"WPFAppxRemoval"\s*\{Invoke-WPFTab "WPFTab6BT"\}'
+ $buttonSource | Should -Match '"WPFBackToTweaks"\s*\{Invoke-WPFTab "WPFTab2BT"\}'
+ $tabSource | Should -Match '\$sync\.\$tabNav\.Items\[\$tabNumber\]\.IsSelected = \$true'
+ }
}
Describe "XAML and sync wiring" {
diff --git a/xaml/inputXML.xaml b/xaml/inputXML.xaml
index f03bd00a..45b3d6ff 100644
--- a/xaml/inputXML.xaml
+++ b/xaml/inputXML.xaml
@@ -983,14 +983,6 @@
-
-
-
- AppX Removal
-
-
-
@@ -1237,6 +1229,7 @@
+
@@ -1717,6 +1710,7 @@
+