2026-03-27 20:33:24 +01:00
function Show-ImportExportConfigWindow {
param (
[ System.Windows.Window ] $Owner ,
[ bool ] $UsesDarkMode ,
[ string ] $Title ,
[ string ] $Prompt ,
[ string[] ] $Categories = @ ( 'Applications' , 'System Tweaks' , 'Deployment Settings' ) ,
[ string[] ] $DisabledCategories = @ ( )
)
# Show overlay on owner window
$overlay = $null
$overlayWasAlreadyVisible = $false
try {
$overlay = $Owner . FindName ( 'ModalOverlay' )
if ( $overlay ) {
$overlayWasAlreadyVisible = ( $overlay . Visibility -eq 'Visible' )
if ( -not $overlayWasAlreadyVisible ) {
$Owner . Dispatcher . Invoke ( [ action ] { $overlay . Visibility = 'Visible' } )
}
}
} catch { }
# Load XAML from schema file
$schemaPath = $script:ImportExportConfigSchema
if ( -not $schemaPath -or -not ( Test-Path $schemaPath ) ) {
Show-MessageBox -Message 'Import/Export window schema file could not be found.' -Title 'Error' -Button 'OK' -Icon 'Error' -Owner $Owner | Out-Null
if ( $overlay -and -not $overlayWasAlreadyVisible ) {
try { $Owner . Dispatcher . Invoke ( [ action ] { $overlay . Visibility = 'Collapsed' } ) } catch { }
}
return $null
}
$xaml = Get-Content -Path $schemaPath -Raw
$reader = [ System.Xml.XmlReader ] :: Create ( [ System.IO.StringReader ] :: new ( $xaml ) )
try {
$dlg = [ System.Windows.Markup.XamlReader ] :: Load ( $reader )
}
finally {
$reader . Close ( )
}
$dlg . Owner = $Owner
SetWindowThemeResources -window $dlg -usesDarkMode $UsesDarkMode
# Populate named elements
$dlg . Title = $Title
$dlg . FindName ( 'TitleText' ) . Text = $Title
$dlg . FindName ( 'PromptText' ) . Text = $Prompt
$titleBar = $dlg . FindName ( 'TitleBar' )
$titleBar . Add_MouseLeftButtonDown ( { $dlg . DragMove ( ) } )
# Add a themed checkbox per category
$checkboxPanel = $dlg . FindName ( 'CheckboxPanel' )
$checkboxes = @ { }
foreach ( $cat in $Categories ) {
$cb = New-Object System . Windows . Controls . CheckBox
$cb . Content = $cat
$cb . IsChecked = $true
$cb . Margin = [ System.Windows.Thickness ] :: new ( 0 , 0 , 0 , 8 )
$cb . FontSize = 14
$cb . Foreground = $dlg . FindResource ( 'FgColor' )
2026-03-27 20:36:16 +01:00
$cb . Style = $window . Resources [ " AppsPanelCheckBoxStyle " ]
2026-03-27 20:33:24 +01:00
if ( $DisabledCategories -contains $cat ) {
$cb . IsChecked = $false
$cb . IsEnabled = $false
$cb . Opacity = 0.65
$cb . ToolTip = 'No selected settings available in this category.'
}
$checkboxPanel . Children . Add ( $cb ) | Out-Null
$checkboxes [ $cat ] = $cb
}
$okBtn = $dlg . FindName ( 'OkButton' )
$cancelBtn = $dlg . FindName ( 'CancelButton' )
$okBtn . Add_Click ( { $dlg . Tag = 'OK' ; $dlg . Close ( ) } )
$cancelBtn . Add_Click ( { $dlg . Tag = 'Cancel' ; $dlg . Close ( ) } )
# Handle Escape key
$dlg . Add_KeyDown ( {
param ( $s , $e )
if ( $e . Key -eq 'Escape' ) { $dlg . Tag = 'Cancel' ; $dlg . Close ( ) }
} )
try {
$dlg . ShowDialog ( ) | Out-Null
}
finally {
# Hide overlay
if ( $overlay -and -not $overlayWasAlreadyVisible ) {
try { $Owner . Dispatcher . Invoke ( [ action ] { $overlay . Visibility = 'Collapsed' } ) } catch { }
}
}
if ( $dlg . Tag -ne 'OK' ) { return $null }
$selected = @ ( )
foreach ( $cat in $Categories ) {
if ( $checkboxes [ $cat ] . IsEnabled -and $checkboxes [ $cat ] . IsChecked ) { $selected + = $cat }
}
if ( $selected . Count -eq 0 ) { return $null }
return $selected
}
function Get-SelectedApplications {
param (
[ System.Windows.Controls.Panel ] $AppsPanel
)
$selectedApps = @ ( )
foreach ( $child in $AppsPanel . Children ) {
if ( $child -is [ System.Windows.Controls.CheckBox ] -and $child . IsChecked ) {
$selectedApps + = $child . Tag
}
}
return $selectedApps
}
function Get-SelectedTweakSettings {
param (
[ System.Windows.Window ] $Owner ,
[ hashtable ] $UiControlMappings
)
$tweakSettings = @ ( )
if ( -not $UiControlMappings ) {
return $tweakSettings
}
foreach ( $mappingKey in $UiControlMappings . Keys ) {
$control = $Owner . FindName ( $mappingKey )
if ( -not $control ) { continue }
$mapping = $UiControlMappings [ $mappingKey ]
if ( $control -is [ System.Windows.Controls.CheckBox ] -and $control . IsChecked ) {
if ( $mapping . Type -eq 'feature' ) {
$tweakSettings + = @ { Name = $mapping . FeatureId ; Value = $true }
}
}
elseif ( $control -is [ System.Windows.Controls.ComboBox ] -and $control . SelectedIndex -gt 0 ) {
if ( $mapping . Type -eq 'group' ) {
$selectedValue = $mapping . Values [ $control . SelectedIndex - 1 ]
foreach ( $fid in $selectedValue . FeatureIds ) {
$tweakSettings + = @ { Name = $fid ; Value = $true }
}
}
elseif ( $mapping . Type -eq 'feature' ) {
$tweakSettings + = @ { Name = $mapping . FeatureId ; Value = $true }
}
}
}
return $tweakSettings
}
function Get-DeploymentSettings {
param (
[ System.Windows.Window ] $Owner ,
[ System.Windows.Controls.ComboBox ] $UserSelectionCombo ,
[ System.Windows.Controls.TextBox ] $OtherUsernameTextBox
)
$deploySettings = @ (
@ { Name = 'UserSelectionIndex' ; Value = $UserSelectionCombo . SelectedIndex }
)
if ( $UserSelectionCombo . SelectedIndex -eq 1 ) {
$deploySettings + = @ { Name = 'OtherUsername' ; Value = $OtherUsernameTextBox . Text . Trim ( ) }
}
$appRemovalScopeCombo = $Owner . FindName ( 'AppRemovalScopeCombo' )
if ( $appRemovalScopeCombo ) {
$deploySettings + = @ { Name = 'AppRemovalScopeIndex' ; Value = $appRemovalScopeCombo . SelectedIndex }
}
$restorePointCheckBox = $Owner . FindName ( 'RestorePointCheckBox' )
if ( $restorePointCheckBox ) {
$deploySettings + = @ { Name = 'CreateRestorePoint' ; Value = [ bool ] $restorePointCheckBox . IsChecked }
}
$restartExplorerCheckBox = $Owner . FindName ( 'RestartExplorerCheckBox' )
if ( $restartExplorerCheckBox ) {
$deploySettings + = @ { Name = 'RestartExplorer' ; Value = [ bool ] $restartExplorerCheckBox . IsChecked }
}
return $deploySettings
}
function Get-AvailableImportExportCategories {
param (
$Config
)
$availableCategories = @ ( )
if ( $Config . Apps ) { $availableCategories + = 'Applications' }
if ( $Config . Tweaks ) { $availableCategories + = 'System Tweaks' }
if ( $Config . Deployment ) { $availableCategories + = 'Deployment Settings' }
return $availableCategories
}
function Apply-ImportedApplications {
param (
[ System.Windows.Controls.Panel ] $AppsPanel ,
[ string[] ] $AppIds
)
foreach ( $child in $AppsPanel . Children ) {
if ( $child -is [ System.Windows.Controls.CheckBox ] ) {
$child . IsChecked = ( $AppIds -contains $child . Tag )
}
}
}
function Apply-ImportedTweakSettings {
param (
[ System.Windows.Window ] $Owner ,
[ hashtable ] $UiControlMappings ,
[ array ] $TweakSettings
)
$settingsJson = [ PSCustomObject ] @ { Settings = @ ( $TweakSettings ) }
ApplySettingsToUiControls -window $Owner -settingsJson $settingsJson -uiControlMappings $UiControlMappings
}
function Apply-ImportedDeploymentSettings {
param (
[ System.Windows.Window ] $Owner ,
[ System.Windows.Controls.ComboBox ] $UserSelectionCombo ,
[ System.Windows.Controls.TextBox ] $OtherUsernameTextBox ,
[ array ] $DeploymentSettings
)
$lookup = @ { }
foreach ( $setting in $DeploymentSettings ) {
$lookup [ $setting . Name ] = $setting . Value
}
if ( $lookup . ContainsKey ( 'UserSelectionIndex' ) ) {
$UserSelectionCombo . SelectedIndex = [ int ] $lookup [ 'UserSelectionIndex' ]
}
if ( $lookup . ContainsKey ( 'OtherUsername' ) -and $UserSelectionCombo . SelectedIndex -eq 1 ) {
$OtherUsernameTextBox . Text = $lookup [ 'OtherUsername' ]
}
$appRemovalScopeCombo = $Owner . FindName ( 'AppRemovalScopeCombo' )
if ( $lookup . ContainsKey ( 'AppRemovalScopeIndex' ) -and $appRemovalScopeCombo ) {
$appRemovalScopeCombo . SelectedIndex = [ int ] $lookup [ 'AppRemovalScopeIndex' ]
}
$restorePointCheckBox = $Owner . FindName ( 'RestorePointCheckBox' )
if ( $lookup . ContainsKey ( 'CreateRestorePoint' ) -and $restorePointCheckBox ) {
$restorePointCheckBox . IsChecked = [ bool ] $lookup [ 'CreateRestorePoint' ]
}
$restartExplorerCheckBox = $Owner . FindName ( 'RestartExplorerCheckBox' )
if ( $lookup . ContainsKey ( 'RestartExplorer' ) -and $restartExplorerCheckBox ) {
$restartExplorerCheckBox . IsChecked = [ bool ] $lookup [ 'RestartExplorer' ]
}
}
function Export-Configuration {
param (
[ System.Windows.Window ] $Owner ,
[ bool ] $UsesDarkMode ,
[ System.Windows.Controls.Panel ] $AppsPanel ,
[ hashtable ] $UiControlMappings ,
[ System.Windows.Controls.ComboBox ] $UserSelectionCombo ,
[ System.Windows.Controls.TextBox ] $OtherUsernameTextBox
)
# Precompute exportable data so empty categories can be disabled in the picker.
$selectedApps = Get-SelectedApplications -AppsPanel $AppsPanel
$tweakSettings = Get-SelectedTweakSettings -Owner $Owner -UiControlMappings $UiControlMappings
$disabledCategories = @ ( )
if ( $selectedApps . Count -eq 0 ) { $disabledCategories + = 'Applications' }
if ( $tweakSettings . Count -eq 0 ) { $disabledCategories + = 'System Tweaks' }
$categories = Show-ImportExportConfigWindow -Owner $Owner -UsesDarkMode $UsesDarkMode -Title 'Export Configuration' -Prompt 'Select which settings to include in the export:' -DisabledCategories $disabledCategories
if ( -not $categories ) { return }
$config = @ { Version = '1.0' }
if ( $categories -contains 'Applications' ) {
$config [ 'Apps' ] = @ ( $selectedApps )
}
if ( $categories -contains 'System Tweaks' ) {
$config [ 'Tweaks' ] = @ ( $tweakSettings )
}
if ( $categories -contains 'Deployment Settings' ) {
$config [ 'Deployment' ] = @ ( Get-DeploymentSettings -Owner $Owner -UserSelectionCombo $UserSelectionCombo -OtherUsernameTextBox $OtherUsernameTextBox )
}
# Show native save-file dialog
$saveDialog = New-Object Microsoft . Win32 . SaveFileDialog
$saveDialog . Title = 'Export Configuration'
$saveDialog . Filter = 'JSON files (*.json)|*.json|All files (*.*)|*.*'
$saveDialog . DefaultExt = '.json'
$saveDialog . FileName = " Win11Debloat-Config- $( Get-Date -Format 'yyyyMMdd' ) .json "
if ( $saveDialog . ShowDialog ( $Owner ) -ne $true ) { return }
if ( SaveToFile -Config $config -FilePath $saveDialog . FileName ) {
Show-MessageBox -Message " Configuration exported successfully. " -Title 'Export Configuration' -Button 'OK' -Icon 'Information' | Out-Null
}
else {
Show-MessageBox -Message " Failed to export configuration " -Title 'Error' -Button 'OK' -Icon 'Error' | Out-Null
}
}
function Import-Configuration {
param (
[ System.Windows.Window ] $Owner ,
[ bool ] $UsesDarkMode ,
[ System.Windows.Controls.Panel ] $AppsPanel ,
[ hashtable ] $UiControlMappings ,
[ System.Windows.Controls.ComboBox ] $UserSelectionCombo ,
[ System.Windows.Controls.TextBox ] $OtherUsernameTextBox ,
[ scriptblock ] $OnAppsImported ,
[ scriptblock ] $OnImportCompleted
)
# Show native open-file dialog
$openDialog = New-Object Microsoft . Win32 . OpenFileDialog
$openDialog . Title = 'Import Configuration'
$openDialog . Filter = 'JSON files (*.json)|*.json|All files (*.*)|*.*'
$openDialog . DefaultExt = '.json'
if ( $openDialog . ShowDialog ( $Owner ) -ne $true ) { return }
$config = LoadJsonFile -filePath $openDialog . FileName -expectedVersion '1.0'
if ( -not $config ) {
Show-MessageBox -Message " Failed to read configuration file " -Title 'Error' -Button 'OK' -Icon 'Error' | Out-Null
return
}
if ( -not $config . Version ) {
Show-MessageBox -Message " Invalid configuration file format. " -Title 'Error' -Button 'OK' -Icon 'Error' | Out-Null
return
}
$availableCategories = Get-AvailableImportExportCategories -Config $config
if ( $availableCategories . Count -eq 0 ) {
Show-MessageBox -Message " The configuration file contains no importable data. " -Title 'Import Configuration' -Button 'OK' -Icon 'Information' | Out-Null
return
}
$categories = Show-ImportExportConfigWindow -Owner $Owner -UsesDarkMode $UsesDarkMode -Title 'Import Configuration' -Prompt 'Select which settings to import:' -Categories $availableCategories
if ( -not $categories ) { return }
if ( $categories -contains 'Applications' -and $config . Apps ) {
$appIds = @ (
$config . Apps |
Where-Object { $_ -is [ string ] } |
ForEach-Object { $_ . Trim ( ) } |
Where-Object { -not [ string ] :: IsNullOrWhiteSpace ( $_ ) }
)
Apply-ImportedApplications -AppsPanel $AppsPanel -AppIds $appIds
if ( $OnAppsImported ) {
& $OnAppsImported
}
}
if ( $categories -contains 'System Tweaks' -and $config . Tweaks ) {
Apply-ImportedTweakSettings -Owner $Owner -UiControlMappings $UiControlMappings -TweakSettings @ ( $config . Tweaks )
}
if ( $categories -contains 'Deployment Settings' -and $config . Deployment ) {
Apply-ImportedDeploymentSettings -Owner $Owner -UserSelectionCombo $UserSelectionCombo -OtherUsernameTextBox $OtherUsernameTextBox -DeploymentSettings @ ( $config . Deployment )
}
Show-MessageBox -Message " Configuration imported successfully. " -Title 'Import Configuration' -Button 'OK' -Icon 'Information' | Out-Null
if ( $OnImportCompleted ) {
& $OnImportCompleted $categories
}
}