2026-03-07 14:49:29 +01:00
|
|
|
function Show-ApplyModal {
|
|
|
|
|
param (
|
|
|
|
|
[Parameter(Mandatory=$false)]
|
|
|
|
|
[System.Windows.Window]$Owner = $null,
|
|
|
|
|
[Parameter(Mandatory=$false)]
|
|
|
|
|
[bool]$RestartExplorer = $false
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase | Out-Null
|
2026-03-07 15:53:58 +01:00
|
|
|
|
|
|
|
|
# P/Invoke helpers for forcing focus back after Explorer restart
|
|
|
|
|
if (-not ([System.Management.Automation.PSTypeName]'Win11Debloat.FocusHelper').Type) {
|
|
|
|
|
Add-Type -Namespace Win11Debloat -Name FocusHelper -MemberDefinition @'
|
|
|
|
|
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
|
|
|
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
|
|
|
|
|
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);
|
|
|
|
|
[DllImport("user32.dll")] public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
|
|
|
|
|
[DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId();
|
|
|
|
|
|
|
|
|
|
public static void ForceActivate(IntPtr hwnd) {
|
|
|
|
|
IntPtr fg = GetForegroundWindow();
|
|
|
|
|
uint fgThread = GetWindowThreadProcessId(fg, IntPtr.Zero);
|
|
|
|
|
uint myThread = GetCurrentThreadId();
|
|
|
|
|
if (fgThread != myThread) AttachThreadInput(myThread, fgThread, true);
|
|
|
|
|
SetForegroundWindow(hwnd);
|
|
|
|
|
if (fgThread != myThread) AttachThreadInput(myThread, fgThread, false);
|
|
|
|
|
}
|
|
|
|
|
'@
|
|
|
|
|
}
|
2026-03-07 14:49:29 +01:00
|
|
|
|
|
|
|
|
$usesDarkMode = GetSystemUsesDarkMode
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
SetWindowThemeResources -window $applyWindow -usesDarkMode $usesDarkMode
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
# Set up progress callback for ExecuteAllChanges
|
|
|
|
|
$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
|
|
|
|
|
DoEvents
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
}
|
|
|
|
|
DoEvents
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Run changes in background to keep UI responsive
|
|
|
|
|
$applyWindow.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{
|
|
|
|
|
try {
|
|
|
|
|
ExecuteAllChanges
|
|
|
|
|
|
|
|
|
|
# Restart explorer if requested
|
|
|
|
|
if ($RestartExplorer -and -not $script:CancelRequested) {
|
|
|
|
|
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]{
|
|
|
|
|
$hwnd = (New-Object System.Windows.Interop.WindowInteropHelper($applyWindow)).Handle
|
|
|
|
|
[Win11Debloat.FocusHelper]::ForceActivate($hwnd)
|
|
|
|
|
})
|
2026-03-07 14:49:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
|
|
|
|
if ($script:CancelRequested) {
|
|
|
|
|
Write-Host "Script execution was cancelled by the user. Some changes may not have been applied."
|
|
|
|
|
} else {
|
2026-03-07 15:17:03 +01:00
|
|
|
Write-Host "All changes have been applied successfully!"
|
2026-03-07 14:49:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Show completion state
|
|
|
|
|
$script:ApplyProgressBarEl.Value = 100
|
|
|
|
|
$script:ApplyInProgressPanel.Visibility = 'Collapsed'
|
|
|
|
|
$script:ApplyCompletionPanel.Visibility = 'Visible'
|
|
|
|
|
|
|
|
|
|
if ($script:CancelRequested) {
|
|
|
|
|
$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."
|
|
|
|
|
} else {
|
|
|
|
|
$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
|
|
|
|
|
if ($RestartExplorer) {
|
|
|
|
|
$rebootFeatures = @()
|
|
|
|
|
foreach ($paramKey in $script:Params.Keys) {
|
|
|
|
|
if ($script:Features.ContainsKey($paramKey) -and $script:Features[$paramKey].RequiresReboot -eq $true) {
|
|
|
|
|
$feature = $script:Features[$paramKey]
|
2026-04-01 21:33:24 +02:00
|
|
|
$rebootFeatures += $feature.Action
|
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
|
|
|
|
|
$tb.SetResourceReference([System.Windows.Controls.TextBlock]::ForegroundProperty, 'FgColor')
|
|
|
|
|
$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-03-22 22:07:51 +01: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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|