mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-05-18 11:46:18 +00:00
Starting from this commit, Win11Debloat will automatically create a registry backup every time the script is run. This registry backup can be used to revert any registry changes made by the script.
258 lines
11 KiB
PowerShell
258 lines
11 KiB
PowerShell
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
|
|
|
|
# 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);
|
|
}
|
|
'@
|
|
}
|
|
|
|
$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')
|
|
$applyRebootPanel = $applyWindow.FindName('ApplyRebootPanel')
|
|
$applyRebootList = $applyWindow.FindName('ApplyRebootList')
|
|
$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
|
|
$script:ApplyModalInErrorState = $false
|
|
|
|
# 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
|
|
|
|
# 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)
|
|
})
|
|
}
|
|
|
|
Write-Host ""
|
|
if ($script:CancelRequested) {
|
|
Write-Host "Script execution was cancelled by the user. Some changes may not have been applied."
|
|
} else {
|
|
Write-Host "All changes have been applied successfully!"
|
|
}
|
|
|
|
# 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"
|
|
|
|
# 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]
|
|
$rebootFeatures += "$($feature.Label)"
|
|
}
|
|
}
|
|
|
|
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 {
|
|
$script:ApplyCompletionMessageEl.Text = "Your clean system is ready. Thanks for using Win11Debloat!"
|
|
}
|
|
}
|
|
}
|
|
$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)"
|
|
|
|
# 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()
|
|
$reportText.Text = 'Report a bug'
|
|
$reportText.VerticalAlignment = 'Center'
|
|
$reportText.FontSize = 14
|
|
$reportText.Margin = [System.Windows.Thickness]::new(0, 0, 0, 1)
|
|
|
|
$applyKofiBtn.Content = $reportText
|
|
|
|
[System.Windows.Automation.AutomationProperties]::SetName($applyKofiBtn, 'Report a bug')
|
|
|
|
$applyWindow.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Render, [action]{})
|
|
}
|
|
finally {
|
|
$script:ApplyProgressCallback = $null
|
|
$script:ApplySubStepCallback = $null
|
|
}
|
|
}) | Out-Null
|
|
|
|
# Button handlers
|
|
$applyCloseBtn.Add_Click({
|
|
$applyWindow.Close()
|
|
})
|
|
|
|
$applyKofiBtn.Add_Click({
|
|
if ($script:ApplyModalInErrorState) {
|
|
Start-Process "https://github.com/Raphire/Win11Debloat/issues/new"
|
|
} else {
|
|
Start-Process "https://ko-fi.com/raphire"
|
|
}
|
|
})
|
|
|
|
$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
|
|
try {
|
|
$applyWindow.ShowDialog() | Out-Null
|
|
}
|
|
finally {
|
|
# Hide overlay after dialog closes
|
|
if ($overlay) {
|
|
try {
|
|
$ownerWindow.Dispatcher.Invoke([action]{ $overlay.Visibility = 'Collapsed' })
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
}
|