2026-07-19 22:06:07 +02:00
<#
. SYNOPSIS
Displays the modal progress window while selected changes are applied.
.PARAMETER Owner
The optional window that owns the modal and its overlay.
.PARAMETER InvokeRestartExplorer
Indicates whether the modal should run the Explorer-restart flow after applying changes.
#>
2026-03-07 14:49:29 +01:00
function Show-ApplyModal {
param (
[ Parameter ( Mandatory = $false )]
[ System.Windows.Window ] $Owner = $null ,
[ Parameter ( Mandatory = $false )]
2026-07-19 22:06:07 +02:00
[ bool ] $InvokeRestartExplorer = $false
2026-03-07 14:49:29 +01:00
)
Add-Type -AssemblyName PresentationFramework , PresentationCore , WindowsBase | Out-Null
2026-07-19 22:06:07 +02:00
$usesDarkMode = Get-SystemUsesDarkMode
2026-03-07 14:49:29 +01:00
# Determine owner window
$ownerWindow = if ( $Owner ) { $Owner } else { $script:GuiWindow }
# Show overlay if owner window exists
$overlay = $null
if ( $ownerWindow ) {
try {
$overlay = $ownerWindow . FindName ( 'ModalOverlay' )
if ( $overlay ) {
$ownerWindow . Dispatcher . Invoke ([ action ]{ $overlay . Visibility = 'Visible' })
}
}
catch { }
}
# Load XAML from file
$xaml = Get-Content -Path $script:ApplyChangesWindowSchema -Raw
$reader = [ System.Xml.XmlReader ]:: Create ([ System.IO.StringReader ]:: new ( $xaml ))
try {
$applyWindow = [ System.Windows.Markup.XamlReader ]:: Load ( $reader )
}
finally {
$reader . Close ()
}
# Set owner to owner window if it exists
if ( $ownerWindow ) {
try {
$applyWindow . Owner = $ownerWindow
}
catch { }
}
# Apply theme resources
2026-07-19 22:06:07 +02:00
Set-WindowThemeResources -window $applyWindow -usesDarkMode $usesDarkMode
2026-03-07 14:49:29 +01:00
# Get UI elements
$script:ApplyInProgressPanel = $applyWindow . FindName ( 'ApplyInProgressPanel' )
$script:ApplyCompletionPanel = $applyWindow . FindName ( 'ApplyCompletionPanel' )
$script:ApplyStepNameEl = $applyWindow . FindName ( 'ApplyStepName' )
$script:ApplyStepCounterEl = $applyWindow . FindName ( 'ApplyStepCounter' )
$script:ApplyProgressBarEl = $applyWindow . FindName ( 'ApplyProgressBar' )
$script:ApplyCompletionTitleEl = $applyWindow . FindName ( 'ApplyCompletionTitle' )
$script:ApplyCompletionMessageEl = $applyWindow . FindName ( 'ApplyCompletionMessage' )
$script:ApplyCompletionIconEl = $applyWindow . FindName ( 'ApplyCompletionIcon' )
2026-03-07 15:53:58 +01:00
$applyRebootPanel = $applyWindow . FindName ( 'ApplyRebootPanel' )
$applyRebootList = $applyWindow . FindName ( 'ApplyRebootList' )
2026-03-07 14:49:29 +01:00
$applyCloseBtn = $applyWindow . FindName ( 'ApplyCloseBtn' )
$applyKofiBtn = $applyWindow . FindName ( 'ApplyKofiBtn' )
$applyCancelBtn = $applyWindow . FindName ( 'ApplyCancelBtn' )
# Initialize in-progress state
$script:ApplyInProgressPanel . Visibility = 'Visible'
$script:ApplyCompletionPanel . Visibility = 'Collapsed'
$script:ApplyStepNameEl . Text = "Preparing..."
$script:ApplyStepCounterEl . Text = "Preparing..."
$script:ApplyProgressBarEl . Value = 0
2026-03-08 22:31:09 +01:00
$script:ApplyModalInErrorState = $false
2026-03-07 14:49:29 +01:00
2026-06-22 21:43:53 +02:00
# Set up progress callback for Invoke-AllChanges
2026-03-07 14:49:29 +01:00
$script:ApplyProgressCallback = {
param ( $currentStep , $totalSteps , $stepName )
$script:ApplyStepNameEl . Text = $stepName
$script:ApplyStepCounterEl . Text = "Step $currentStep of $totalSteps "
# Store current step/total in Tag properties for sub-step interpolation
$script:ApplyStepCounterEl . Tag = $currentStep
$script:ApplyProgressBarEl . Tag = $totalSteps
# Show progress at the start of each step (empty at step 1, full after last step completes)
$pct = if ( $totalSteps -gt 0 ) { [ math ]:: Round ((( $currentStep - 1 ) / $totalSteps ) * 100 ) } else { 0 }
$script:ApplyProgressBarEl . Value = $pct
# Process pending window messages to keep UI responsive
2026-07-19 22:06:07 +02:00
Invoke-DoEvents
2026-03-07 14:49:29 +01:00
}
# Sub-step callback updates step name and interpolates progress bar within the current step
$script:ApplySubStepCallback = {
param ( $subStepName , $subIndex , $subCount )
$script:ApplyStepNameEl . Text = $subStepName
# Interpolate progress bar between previous step and current step
$currentStep = [ int ]( $script:ApplyStepCounterEl . Tag )
$totalSteps = [ int ]( $script:ApplyProgressBarEl . Tag )
if ( $totalSteps -gt 0 -and $subCount -gt 0 ) {
$baseProgress = ( $currentStep - 1 ) / $totalSteps
$stepFraction = ( $subIndex / $subCount ) / $totalSteps
$script:ApplyProgressBarEl . Value = [ math ]:: Round (( $baseProgress + $stepFraction ) * 100 )
}
2026-07-19 22:06:07 +02:00
Invoke-DoEvents
2026-03-07 14:49:29 +01:00
}
# Run changes in background to keep UI responsive
$applyWindow . Dispatcher . BeginInvoke ([ System.Windows.Threading.DispatcherPriority ]:: Background , [ action ]{
try {
2026-06-22 21:43:53 +02:00
Invoke-AllChanges
2026-05-20 16:29:06 +02:00
$registryImportFailureCount = [ int ] $script:RegistryImportFailures
2026-08-12 15:56:29 +02:00
$appRemovalFailureCount = [ int ] $script:AppRemovalFailures
$failureCount = $registryImportFailureCount + $appRemovalFailureCount
$appRemovalVerificationUnavailable = [ bool ] $script:AppRemovalVerificationUnavailable
2026-03-07 14:49:29 +01:00
# Restart explorer if requested
2026-07-19 22:06:07 +02:00
if ( $InvokeRestartExplorer -and -not $script:CancelRequested ) {
Invoke-RestartExplorer
2026-03-07 15:53:58 +01:00
# Wait for Explorer to finish relaunching, then reclaim focus.
Start-Sleep -Milliseconds 800
$applyWindow . Dispatcher . Invoke ([ action ]{
2026-06-22 02:07:26 +02:00
$applyWindow . Activate ()
2026-03-07 15:53:58 +01:00
})
2026-03-07 14:49:29 +01:00
}
Write-Host ""
# Show completion state
$script:ApplyProgressBarEl . Value = 100
$script:ApplyInProgressPanel . Visibility = 'Collapsed'
$script:ApplyCompletionPanel . Visibility = 'Visible'
if ( $script:CancelRequested ) {
2026-08-12 15:56:29 +02:00
Write-Warning "Script execution was cancelled by the user. Any remaining changes were not applied."
2026-03-07 14:49:29 +01:00
$script:ApplyCompletionIconEl . Text = [ char ] 0xE7BA
$script:ApplyCompletionIconEl . Foreground = [ System.Windows.Media.SolidColorBrush ]:: new ([ System.Windows.Media.ColorConverter ]:: ConvertFromString ( "#e8912d" ))
$script:ApplyCompletionTitleEl . Text = "Cancelled"
$script:ApplyCompletionMessageEl . Text = "Script execution was cancelled by the user."
2026-08-12 15:56:29 +02:00
} elseif ( $failureCount -gt 0 -or $appRemovalVerificationUnavailable ) {
if ( $failureCount -gt 0 ) {
Write-Host "Script completed with $failureCount error(s)."
}
2026-05-20 16:29:06 +02:00
$script:ApplyCompletionIconEl . Text = [ char ] 0xE7BA
$script:ApplyCompletionIconEl . Foreground = [ System.Windows.Media.SolidColorBrush ]:: new ([ System.Windows.Media.ColorConverter ]:: ConvertFromString ( "#e8912d" ))
2026-08-12 15:56:29 +02:00
if ( $failureCount -eq 0 -and $appRemovalVerificationUnavailable ) {
$script:ApplyCompletionTitleEl . Text = "Changes Applied"
$script:ApplyCompletionMessageEl . Text = "All changes were applied without errors, but Win11Debloat could not confirm that all selected apps were successfully uninstalled."
}
else {
$script:ApplyCompletionTitleEl . Text = "Changes Applied with Errors"
$failureMessages = @ ()
if ( $registryImportFailureCount -gt 0 ) { $failureMessages += " $registryImportFailureCount registry change(s) failed" }
if ( $appRemovalFailureCount -gt 0 ) { $failureMessages += " $appRemovalFailureCount app removal(s) failed" }
if ( $appRemovalVerificationUnavailable ) { $failureMessages += "Unable to verify if all apps were uninstalled successfully" }
$script:ApplyCompletionMessageEl . Text = " $( $failureMessages -join '; ' ) . See console for details."
}
2026-03-07 14:49:29 +01:00
} else {
2026-08-12 15:56:29 +02:00
Write-Host "All changes have been applied successfully!"
2026-03-07 14:49:29 +01:00
$script:ApplyCompletionTitleEl . Text = "Changes Applied"
2026-03-07 15:53:58 +01:00
# Show completion message with reboot instructions if any applied features require reboot
2026-07-19 22:06:07 +02:00
if ( $InvokeRestartExplorer ) {
2026-07-11 20:04:51 +02:00
$rebootFeatures = Get-RebootFeatureLabels
2026-03-07 15:53:58 +01:00
if ( $rebootFeatures . Count -gt 0 ) {
foreach ( $featureName in $rebootFeatures ) {
$tb = [ System.Windows.Controls.TextBlock ]:: new ()
$tb . Text = " $([ char ] 0x2022 ) $featureName "
$tb . FontSize = 12
2026-06-21 18:47:52 +02:00
$tb . SetResourceReference ([ System.Windows.Controls.TextBlock ]:: ForegroundProperty , "AppFgColor" )
2026-03-07 15:53:58 +01:00
$tb . Opacity = 0.85
$tb . Margin = [ System.Windows.Thickness ]:: new ( 0 , 2 , 0 , 0 )
$applyRebootList . Children . Add ( $tb ) | Out-Null
}
$applyRebootPanel . Visibility = 'Visible'
}
else {
2026-06-10 17:40:31 +02:00
$script:ApplyCompletionMessageEl . Text = "Your system is ready. Thanks for using Win11Debloat!"
2026-03-07 15:53:58 +01:00
}
}
2026-03-07 14:49:29 +01:00
}
$applyWindow . Dispatcher . Invoke ([ System.Windows.Threading.DispatcherPriority ]:: Render , [ action ]{})
}
catch {
Write-Host "Error: $( $_ . Exception . Message ) "
$script:ApplyInProgressPanel . Visibility = 'Collapsed'
$script:ApplyCompletionPanel . Visibility = 'Visible'
$script:ApplyCompletionIconEl . Text = [ char ] 0xEA39
$script:ApplyCompletionIconEl . Foreground = [ System.Windows.Media.SolidColorBrush ]:: new ([ System.Windows.Media.ColorConverter ]:: ConvertFromString ( "#c42b1c" ))
$script:ApplyCompletionTitleEl . Text = "Error"
$script:ApplyCompletionMessageEl . Text = "An error occurred while applying changes: $( $_ . Exception . Message ) "
2026-03-08 22:31:09 +01:00
# Set error state to change Kofi button to report link
$script:ApplyModalInErrorState = $true
# Update Kofi button to be a report issue button
$applyKofiBtn . Content = $null
$reportText = [ System.Windows.Controls.TextBlock ]:: new ()
2026-03-09 23:32:13 +01:00
$reportText . Text = 'Report a bug'
2026-03-08 22:31:09 +01:00
$reportText . VerticalAlignment = 'Center'
$reportText . FontSize = 14
$reportText . Margin = [ System.Windows.Thickness ]:: new ( 0 , 0 , 0 , 1 )
$applyKofiBtn . Content = $reportText
2026-03-09 23:32:13 +01:00
[ System.Windows.Automation.AutomationProperties ]:: SetName ( $applyKofiBtn , 'Report a bug' )
2026-03-08 22:31:09 +01:00
2026-03-07 14:49:29 +01:00
$applyWindow . Dispatcher . Invoke ([ System.Windows.Threading.DispatcherPriority ]:: Render , [ action ]{})
}
finally {
$script:ApplyProgressCallback = $null
$script:ApplySubStepCallback = $null
}
2026-03-15 22:58:06 +01:00
}) | Out-Null
2026-03-07 14:49:29 +01:00
# Button handlers
$applyCloseBtn . Add_Click ({
$applyWindow . Close ()
})
$applyKofiBtn . Add_Click ({
2026-03-08 22:31:09 +01:00
if ( $script:ApplyModalInErrorState ) {
Start-Process "https://github.com/Raphire/Win11Debloat/issues/new"
} else {
Start-Process "https://ko-fi.com/raphire"
}
2026-03-07 14:49:29 +01:00
})
$applyCancelBtn . Add_Click ({
if ( $script:ApplyCompletionPanel . Visibility -eq 'Visible' ) {
# Completion state - just close
$applyWindow . Close ()
} else {
# In-progress state - request cancellation
$script:CancelRequested = $true
}
})
# Show dialog
2026-03-27 20:33:24 +01:00
try {
$applyWindow . ShowDialog () | Out-Null
}
finally {
# Hide overlay after dialog closes
if ( $overlay ) {
try {
$ownerWindow . Dispatcher . Invoke ([ action ]{ $overlay . Visibility = 'Collapsed' })
}
catch { }
2026-03-07 14:49:29 +01:00
}
}
}