mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-05-20 20:56:18 +00:00
Compare commits
9 Commits
2026.05.10
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d9da4749b | ||
|
|
5cf9ac4082 | ||
|
|
924c192ca5 | ||
|
|
2a5cb986c9 | ||
|
|
66982ada28 | ||
|
|
489af33a8b | ||
|
|
51aa288dfd | ||
|
|
24a6f1bcf8 | ||
|
|
8ac664e45f |
@@ -366,7 +366,7 @@
|
|||||||
{
|
{
|
||||||
"FeatureId": "DisableTelemetry",
|
"FeatureId": "DisableTelemetry",
|
||||||
"Label": "Disable telemetry, tracking & targeted ads",
|
"Label": "Disable telemetry, tracking & targeted ads",
|
||||||
"ToolTip": "This setting disables telemetry, diagnostic data collection, activity history, app-launch tracking, targeted ads and more. It limits the data that is sent to Microsoft about your device and usage.",
|
"ToolTip": "This setting disables telemetry, diagnostic data collection, activity history, app-launch tracking, targeted ads and more. It limits the data that is sent to Microsoft about your device and usage. If you are a Windows Insider, updates may be blocked until optional diagnostic data collection is turned back on.",
|
||||||
"Category": "Privacy & Suggested Content",
|
"Category": "Privacy & Suggested Content",
|
||||||
"RegistryKey": "Disable_Telemetry.reg",
|
"RegistryKey": "Disable_Telemetry.reg",
|
||||||
"ApplyText": "Disabling telemetry, diagnostic data, activity history, app-launch tracking and targeted ads...",
|
"ApplyText": "Disabling telemetry, diagnostic data, activity history, app-launch tracking and targeted ads...",
|
||||||
@@ -601,28 +601,6 @@
|
|||||||
"MinVersion": 22621,
|
"MinVersion": 22621,
|
||||||
"MaxVersion": null
|
"MaxVersion": null
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"FeatureId": "DisableSearchHighlights",
|
|
||||||
"Label": "Disable Search Highlights in the taskbar search box",
|
|
||||||
"ToolTip": "This will turn off Search Highlights, which shows dynamically curated branded content and trending topics in the Windows search box on the taskbar.",
|
|
||||||
"Category": "Start Menu & Search",
|
|
||||||
"RegistryKey": "Disable_Search_Highlights.reg",
|
|
||||||
"ApplyText": "Disabling Search Highlights in the Windows search box...",
|
|
||||||
"RegistryUndoKey": "Enable_Search_Highlights.reg",
|
|
||||||
"MinVersion": 22621,
|
|
||||||
"MaxVersion": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"FeatureId": "DisableSearchHistory",
|
|
||||||
"Label": "Disable local Windows Search history",
|
|
||||||
"ToolTip": "This setting disables local search history in Windows Search. This does not affect web search history or the search history saved in Microsoft Edge.",
|
|
||||||
"Category": "Start Menu & Search",
|
|
||||||
"RegistryKey": "Disable_Search_History.reg",
|
|
||||||
"ApplyText": "Disabling search history...",
|
|
||||||
"RegistryUndoKey": "Enable_Search_History.reg",
|
|
||||||
"MinVersion": null,
|
|
||||||
"MaxVersion": null
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"FeatureId": "DisableSettings365Ads",
|
"FeatureId": "DisableSettings365Ads",
|
||||||
"Label": "Hide Microsoft 365 Copilot ads in Settings Home",
|
"Label": "Hide Microsoft 365 Copilot ads in Settings Home",
|
||||||
@@ -878,12 +856,12 @@
|
|||||||
{
|
{
|
||||||
"FeatureId": "DisableWidgets",
|
"FeatureId": "DisableWidgets",
|
||||||
"Label": "Disable widgets on the taskbar & lock screen",
|
"Label": "Disable widgets on the taskbar & lock screen",
|
||||||
"ToolTip": "This will disable the widgets features in Windows, including the widgets button on the taskbar and the widgets that can appear on the lock screen. This feature uses policies, which will lock down certain settings.",
|
"ToolTip": "This will disable the widgets features in Windows, including the widgets button on the taskbar and the widgets that can appear on the lock screen.",
|
||||||
"Category": "Taskbar",
|
"Category": "Taskbar",
|
||||||
"Priority": 4,
|
"Priority": 4,
|
||||||
"RegistryKey": "Disable_Widgets_Service.reg",
|
"RegistryKey": null,
|
||||||
"ApplyText": "Disabling widgets on the taskbar & lock screen...",
|
"ApplyText": null,
|
||||||
"RegistryUndoKey": "Enable_Widgets_Service.reg",
|
"RegistryUndoKey": null,
|
||||||
"MinVersion": null,
|
"MinVersion": null,
|
||||||
"MaxVersion": null
|
"MaxVersion": null
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -226,13 +226,20 @@ function Convert-RegistryValueToSnapshot {
|
|||||||
|
|
||||||
$valueKind = $RegistryKey.GetValueKind($ValueName)
|
$valueKind = $RegistryKey.GetValueKind($ValueName)
|
||||||
$value = $RegistryKey.GetValue($ValueName, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
|
$value = $RegistryKey.GetValue($ValueName, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
|
||||||
|
try {
|
||||||
$normalizedValue = switch ($valueKind) {
|
$normalizedValue = switch ($valueKind) {
|
||||||
([Microsoft.Win32.RegistryValueKind]::Binary) { @($value | ForEach-Object { [int]$_ }) }
|
([Microsoft.Win32.RegistryValueKind]::Binary) { @($value | ForEach-Object { [int]$_ }) }
|
||||||
([Microsoft.Win32.RegistryValueKind]::MultiString) { @($value) }
|
([Microsoft.Win32.RegistryValueKind]::MultiString) { @($value) }
|
||||||
([Microsoft.Win32.RegistryValueKind]::DWord) { [uint32]$value }
|
([Microsoft.Win32.RegistryValueKind]::DWord) { [BitConverter]::ToUInt32([BitConverter]::GetBytes([int32]$value), 0) }
|
||||||
([Microsoft.Win32.RegistryValueKind]::QWord) { [uint64]$value }
|
([Microsoft.Win32.RegistryValueKind]::QWord) { [BitConverter]::ToUInt64([BitConverter]::GetBytes([int64]$value), 0) }
|
||||||
default { if ($null -ne $value) { [string]$value } else { $null } }
|
default { if ($null -ne $value) { [string]$value } else { $null } }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$valueType = if ($null -ne $value) { $value.GetType().FullName } else { '<null>' }
|
||||||
|
$valueForLog = if ($null -eq $value) { '<null>' } elseif ($value -is [array]) { ($value -join ',') } else { [string]$value }
|
||||||
|
throw "Failed to normalize registry value for backup. Key='$($RegistryKey.Name)' Name='$ValueName' Kind='$valueKind' RawType='$valueType' RawValue='$valueForLog'. InnerError: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
|
||||||
return @{
|
return @{
|
||||||
Name = $ValueName
|
Name = $ValueName
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ function CreateSystemRestorePoint {
|
|||||||
# In GUI mode, skip the prompt and just try to enable it
|
# In GUI mode, skip the prompt and just try to enable it
|
||||||
if ($script:GuiWindow -or $Silent -or $( Read-Host -Prompt "System restore is disabled, would you like to enable it and create a restore point? (y/n)") -eq 'y') {
|
if ($script:GuiWindow -or $Silent -or $( Read-Host -Prompt "System restore is disabled, would you like to enable it and create a restore point? (y/n)") -eq 'y') {
|
||||||
try {
|
try {
|
||||||
$enableResult = Invoke-NonBlocking -TimeoutSeconds 20 -ScriptBlock {
|
$enableResult = Invoke-NonBlocking -TimeoutSeconds 90 -ScriptBlock {
|
||||||
try {
|
try {
|
||||||
Enable-ComputerRestore -Drive "$env:SystemDrive"
|
Enable-ComputerRestore -Drive "$env:SystemDrive"
|
||||||
return $null
|
return $null
|
||||||
@@ -33,7 +33,7 @@ function CreateSystemRestorePoint {
|
|||||||
|
|
||||||
if (-not $failed) {
|
if (-not $failed) {
|
||||||
try {
|
try {
|
||||||
$result = Invoke-NonBlocking -TimeoutSeconds 20 -ScriptBlock {
|
$result = Invoke-NonBlocking -TimeoutSeconds 90 -ScriptBlock {
|
||||||
try {
|
try {
|
||||||
$recentRestorePoints = Get-ComputerRestorePoint | Where-Object { (Get-Date) - [System.Management.ManagementDateTimeConverter]::ToDateTime($_.CreationTime) -le (New-TimeSpan -Hours 24) }
|
$recentRestorePoints = Get-ComputerRestorePoint | Where-Object { (Get-Date) - [System.Management.ManagementDateTimeConverter]::ToDateTime($_.CreationTime) -le (New-TimeSpan -Hours 24) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ function ExecuteParameter {
|
|||||||
# Also remove the app package for Copilot
|
# Also remove the app package for Copilot
|
||||||
RemoveApps 'Microsoft.Copilot'
|
RemoveApps 'Microsoft.Copilot'
|
||||||
}
|
}
|
||||||
'DisableWidgets' {
|
|
||||||
# Also remove the app packages for Widgets
|
|
||||||
RemoveApps 'Microsoft.StartExperiencesApp','MicrosoftWindows.Client.WebExperience','Microsoft.WidgetsPlatformRuntime'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -86,6 +82,13 @@ function ExecuteParameter {
|
|||||||
RemoveApps $appsList
|
RemoveApps $appsList
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
'DisableWidgets' {
|
||||||
|
Write-Host "> Disabling widgets on the taskbar & lock screen..."
|
||||||
|
# Stop widgets related processes before removing the app packages to prevent potential issues
|
||||||
|
Get-Process *Widget* | Stop-Process
|
||||||
|
|
||||||
|
RemoveApps 'Microsoft.StartExperiencesApp','MicrosoftWindows.Client.WebExperience','Microsoft.WidgetsPlatformRuntime'
|
||||||
|
}
|
||||||
"EnableWindowsSandbox" {
|
"EnableWindowsSandbox" {
|
||||||
Write-Host "> Enabling Windows Sandbox..."
|
Write-Host "> Enabling Windows Sandbox..."
|
||||||
EnableWindowsFeature "Containers-DisposableClientVM"
|
EnableWindowsFeature "Containers-DisposableClientVM"
|
||||||
@@ -138,6 +141,8 @@ function ExecuteParameter {
|
|||||||
|
|
||||||
# Executes all selected parameters/features
|
# Executes all selected parameters/features
|
||||||
function ExecuteAllChanges {
|
function ExecuteAllChanges {
|
||||||
|
$script:RegistryImportFailures = 0
|
||||||
|
|
||||||
# Build list of actionable parameters (skip control params and data-only params)
|
# Build list of actionable parameters (skip control params and data-only params)
|
||||||
$actionableKeys = @()
|
$actionableKeys = @()
|
||||||
foreach ($paramKey in $script:Params.Keys) {
|
foreach ($paramKey in $script:Params.Keys) {
|
||||||
@@ -166,20 +171,25 @@ function ExecuteAllChanges {
|
|||||||
if ($hasRegistryBackedFeature) {
|
if ($hasRegistryBackedFeature) {
|
||||||
$currentStep++
|
$currentStep++
|
||||||
if ($script:ApplyProgressCallback) {
|
if ($script:ApplyProgressCallback) {
|
||||||
& $script:ApplyProgressCallback $currentStep $totalSteps "Creating registry backup"
|
& $script:ApplyProgressCallback $currentStep $totalSteps "Creating registry backup..."
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "> Creating registry backup..."
|
Write-Host "> Creating registry backup..."
|
||||||
|
try {
|
||||||
New-RegistrySettingsBackup -ActionableKeys $actionableKeys | Out-Null
|
New-RegistrySettingsBackup -ActionableKeys $actionableKeys | Out-Null
|
||||||
}
|
}
|
||||||
|
catch {
|
||||||
|
throw "Registry backup failed before applying changes. $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Create restore point if requested (CLI only - GUI handles this separately)
|
# Create restore point if requested (CLI only - GUI handles this separately)
|
||||||
if ($script:Params.ContainsKey("CreateRestorePoint")) {
|
if ($script:Params.ContainsKey("CreateRestorePoint")) {
|
||||||
$currentStep++
|
$currentStep++
|
||||||
if ($script:ApplyProgressCallback) {
|
if ($script:ApplyProgressCallback) {
|
||||||
& $script:ApplyProgressCallback $currentStep $totalSteps "Creating system restore point"
|
& $script:ApplyProgressCallback $currentStep $totalSteps "Creating system restore point, this may take a moment..."
|
||||||
}
|
}
|
||||||
Write-Host "> Attempting to create a system restore point..."
|
Write-Host "> Creating a system restore point..."
|
||||||
CreateSystemRestorePoint
|
CreateSystemRestorePoint
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
}
|
}
|
||||||
@@ -211,4 +221,9 @@ function ExecuteAllChanges {
|
|||||||
|
|
||||||
ExecuteParameter -paramKey $paramKey
|
ExecuteParameter -paramKey $paramKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($script:RegistryImportFailures -gt 0) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "$($script:RegistryImportFailures) registry import change(s) failed. See output above for details." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,33 +8,44 @@ function ImportRegistryFile {
|
|||||||
Write-Host $message
|
Write-Host $message
|
||||||
|
|
||||||
$usesOfflineHive = $script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("User")
|
$usesOfflineHive = $script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("User")
|
||||||
$regFilePath = if ($usesOfflineHive) {
|
$regFileDirectory = if ($usesOfflineHive) {
|
||||||
"$script:RegfilesPath\Sysprep\$path"
|
Join-Path $script:RegfilesPath "Sysprep"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
"$script:RegfilesPath\$path"
|
$script:RegfilesPath
|
||||||
}
|
}
|
||||||
|
$regFilePath = Join-Path $regFileDirectory $path
|
||||||
|
|
||||||
if (-not (Test-Path $regFilePath)) {
|
if (-not (Test-Path $regFilePath)) {
|
||||||
$errorMessage = "Unable to find registry file: $path ($regFilePath)"
|
$errorMessage = "Unable to find registry file: $path ($regFilePath)"
|
||||||
|
$script:RegistryImportFailures++
|
||||||
Write-Host "Error: $errorMessage" -ForegroundColor Red
|
Write-Host "Error: $errorMessage" -ForegroundColor Red
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
throw $errorMessage
|
throw $errorMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
# Reset exit code before running reg.exe for reliable success detection
|
$regResult = $null
|
||||||
$global:LASTEXITCODE = 0
|
$offlineHiveLoaded = $false
|
||||||
|
|
||||||
|
try {
|
||||||
if ($usesOfflineHive) {
|
if ($usesOfflineHive) {
|
||||||
# Sysprep targets Default user, User targets the specified user
|
# Sysprep targets Default user, User targets the specified user
|
||||||
$hiveDatPath = if ($script:Params.ContainsKey("Sysprep")) {
|
$targetUserName = if ($script:Params.ContainsKey("Sysprep")) { "Default" } else { $script:Params.Item("User") }
|
||||||
GetUserDirectory -userName "Default" -fileName "NTUSER.DAT"
|
$hiveDatPath = GetUserDirectory -userName $targetUserName -fileName "NTUSER.DAT"
|
||||||
} else {
|
|
||||||
GetUserDirectory -userName $script:Params.Item("User") -fileName "NTUSER.DAT"
|
$global:LASTEXITCODE = 0
|
||||||
|
reg load "HKU\Default" $hiveDatPath | Out-Null
|
||||||
|
$loadExitCode = $LASTEXITCODE
|
||||||
|
|
||||||
|
if ($loadExitCode -ne 0) {
|
||||||
|
throw "Failed importing registry file '$path'. Offline hive load failed: Failed to load user hive at '$hiveDatPath' (exit code: $loadExitCode)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$offlineHiveLoaded = $true
|
||||||
}
|
}
|
||||||
|
|
||||||
$regResult = Invoke-NonBlocking -ScriptBlock {
|
$regResult = Invoke-NonBlocking -ScriptBlock {
|
||||||
param($hivePath, $targetRegFilePath)
|
param($targetRegFilePath)
|
||||||
$result = @{
|
$result = @{
|
||||||
Output = @()
|
Output = @()
|
||||||
ExitCode = 0
|
ExitCode = 0
|
||||||
@@ -43,13 +54,6 @@ function ImportRegistryFile {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$global:LASTEXITCODE = 0
|
$global:LASTEXITCODE = 0
|
||||||
reg load "HKU\Default" $hivePath | Out-Null
|
|
||||||
$loadExitCode = $LASTEXITCODE
|
|
||||||
|
|
||||||
if ($loadExitCode -ne 0) {
|
|
||||||
throw "Failed to load user hive at '$hivePath' (exit code: $loadExitCode)"
|
|
||||||
}
|
|
||||||
|
|
||||||
$output = reg import $targetRegFilePath 2>&1
|
$output = reg import $targetRegFilePath 2>&1
|
||||||
$importExitCode = $LASTEXITCODE
|
$importExitCode = $LASTEXITCODE
|
||||||
|
|
||||||
@@ -66,27 +70,9 @@ function ImportRegistryFile {
|
|||||||
$result.Error = $_.Exception.Message
|
$result.Error = $_.Exception.Message
|
||||||
$result.ExitCode = if ($LASTEXITCODE -ne 0) { $LASTEXITCODE } else { 1 }
|
$result.ExitCode = if ($LASTEXITCODE -ne 0) { $LASTEXITCODE } else { 1 }
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
$global:LASTEXITCODE = 0
|
|
||||||
reg unload "HKU\Default" | Out-Null
|
|
||||||
$unloadExitCode = $LASTEXITCODE
|
|
||||||
if ($unloadExitCode -ne 0 -and -not $result.Error) {
|
|
||||||
$result.Error = "Failed to unload registry hive HKU\Default (exit code: $unloadExitCode)"
|
|
||||||
$result.ExitCode = $unloadExitCode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result
|
return $result
|
||||||
} -ArgumentList @($hiveDatPath, $regFilePath)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$regResult = Invoke-NonBlocking -ScriptBlock {
|
|
||||||
param($targetRegFilePath)
|
|
||||||
$global:LASTEXITCODE = 0
|
|
||||||
$output = reg import $targetRegFilePath 2>&1
|
|
||||||
return @{ Output = @($output); ExitCode = $LASTEXITCODE; Error = $null }
|
|
||||||
} -ArgumentList $regFilePath
|
} -ArgumentList $regFilePath
|
||||||
}
|
|
||||||
|
|
||||||
$regOutput = @($regResult.Output)
|
$regOutput = @($regResult.Output)
|
||||||
$hasSuccess = ($regResult.ExitCode -eq 0) -and -not $regResult.Error
|
$hasSuccess = ($regResult.ExitCode -eq 0) -and -not $regResult.Error
|
||||||
@@ -107,11 +93,27 @@ function ImportRegistryFile {
|
|||||||
|
|
||||||
if (-not $hasSuccess) {
|
if (-not $hasSuccess) {
|
||||||
$details = if ($regResult.Error) { $regResult.Error } else { "Exit code: $($regResult.ExitCode)" }
|
$details = if ($regResult.Error) { $regResult.Error } else { "Exit code: $($regResult.ExitCode)" }
|
||||||
$errorMessage = "Failed importing registry file '$path'. $details"
|
Write-Warning "reg import failed for '$path'. Falling back to PowerShell registry writer. Details: $details"
|
||||||
Write-Host $errorMessage -ForegroundColor Red
|
Invoke-RegistryOperationsFromRegFile -RegFilePath $regFilePath
|
||||||
Write-Host ""
|
Write-Host "Fallback import succeeded for '$path'." -ForegroundColor Yellow
|
||||||
throw $errorMessage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
}
|
}
|
||||||
|
catch {
|
||||||
|
$script:RegistryImportFailures++
|
||||||
|
Write-Host $_.Exception.Message -ForegroundColor Red
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if ($offlineHiveLoaded) {
|
||||||
|
$global:LASTEXITCODE = 0
|
||||||
|
reg unload "HKU\Default" | Out-Null
|
||||||
|
$unloadExitCode = $LASTEXITCODE
|
||||||
|
|
||||||
|
if ($unloadExitCode -ne 0) {
|
||||||
|
Write-Warning "Failed to unload registry hive HKU\Default after importing '$path' (exit code: $unloadExitCode)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -157,8 +157,14 @@ function Convert-RegistryValueDataFromBackup {
|
|||||||
)
|
)
|
||||||
|
|
||||||
switch ($Kind) {
|
switch ($Kind) {
|
||||||
([Microsoft.Win32.RegistryValueKind]::DWord) { return [uint32]$Data }
|
([Microsoft.Win32.RegistryValueKind]::DWord) {
|
||||||
([Microsoft.Win32.RegistryValueKind]::QWord) { return [uint64]$Data }
|
$unsigned = [uint32]$Data
|
||||||
|
return [BitConverter]::ToInt32([BitConverter]::GetBytes($unsigned), 0)
|
||||||
|
}
|
||||||
|
([Microsoft.Win32.RegistryValueKind]::QWord) {
|
||||||
|
$unsigned = [uint64]$Data
|
||||||
|
return [BitConverter]::ToInt64([BitConverter]::GetBytes($unsigned), 0)
|
||||||
|
}
|
||||||
([Microsoft.Win32.RegistryValueKind]::MultiString) { return @($Data | ForEach-Object { [string]$_ }) }
|
([Microsoft.Win32.RegistryValueKind]::MultiString) { return @($Data | ForEach-Object { [string]$_ }) }
|
||||||
([Microsoft.Win32.RegistryValueKind]::Binary) {
|
([Microsoft.Win32.RegistryValueKind]::Binary) {
|
||||||
$bytes = Convert-BackupDataToByteArray -Data $Data
|
$bytes = Convert-BackupDataToByteArray -Data $Data
|
||||||
|
|||||||
@@ -124,6 +124,8 @@ function Show-ApplyModal {
|
|||||||
try {
|
try {
|
||||||
ExecuteAllChanges
|
ExecuteAllChanges
|
||||||
|
|
||||||
|
$registryImportFailureCount = [int]$script:RegistryImportFailures
|
||||||
|
|
||||||
# Restart explorer if requested
|
# Restart explorer if requested
|
||||||
if ($RestartExplorer -and -not $script:CancelRequested) {
|
if ($RestartExplorer -and -not $script:CancelRequested) {
|
||||||
RestartExplorer
|
RestartExplorer
|
||||||
@@ -139,7 +141,7 @@ function Show-ApplyModal {
|
|||||||
Write-Host ""
|
Write-Host ""
|
||||||
if ($script:CancelRequested) {
|
if ($script:CancelRequested) {
|
||||||
Write-Host "Script execution was cancelled by the user. Some changes may not have been applied."
|
Write-Host "Script execution was cancelled by the user. Some changes may not have been applied."
|
||||||
} else {
|
} elseif ($registryImportFailureCount -eq 0) {
|
||||||
Write-Host "All changes have been applied successfully!"
|
Write-Host "All changes have been applied successfully!"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +155,11 @@ function Show-ApplyModal {
|
|||||||
$script:ApplyCompletionIconEl.Foreground = [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#e8912d"))
|
$script:ApplyCompletionIconEl.Foreground = [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#e8912d"))
|
||||||
$script:ApplyCompletionTitleEl.Text = "Cancelled"
|
$script:ApplyCompletionTitleEl.Text = "Cancelled"
|
||||||
$script:ApplyCompletionMessageEl.Text = "Script execution was cancelled by the user."
|
$script:ApplyCompletionMessageEl.Text = "Script execution was cancelled by the user."
|
||||||
|
} elseif ($registryImportFailureCount -gt 0) {
|
||||||
|
$script:ApplyCompletionIconEl.Text = [char]0xE7BA
|
||||||
|
$script:ApplyCompletionIconEl.Foreground = [System.Windows.Media.SolidColorBrush]::new([System.Windows.Media.ColorConverter]::ConvertFromString("#e8912d"))
|
||||||
|
$script:ApplyCompletionTitleEl.Text = "Changes Applied with Errors"
|
||||||
|
$script:ApplyCompletionMessageEl.Text = "$registryImportFailureCount registry change(s) failed. See console for details."
|
||||||
} else {
|
} else {
|
||||||
$script:ApplyCompletionTitleEl.Text = "Changes Applied"
|
$script:ApplyCompletionTitleEl.Text = "Changes Applied"
|
||||||
|
|
||||||
|
|||||||
@@ -255,7 +255,7 @@ function Show-RestoreBackupDialog {
|
|||||||
|
|
||||||
$openDialog = New-Object Microsoft.Win32.OpenFileDialog
|
$openDialog = New-Object Microsoft.Win32.OpenFileDialog
|
||||||
$openDialog.Title = 'Select Registry Backup File'
|
$openDialog.Title = 'Select Registry Backup File'
|
||||||
$openDialog.Filter = 'Registry backup (*.json)|*.json|All files (*.*)|*.*'
|
$openDialog.Filter = 'Registry backup (*.json)|*.json'
|
||||||
$openDialog.DefaultExt = '.json'
|
$openDialog.DefaultExt = '.json'
|
||||||
$openDialog.InitialDirectory = $script:RegistryBackupsPath
|
$openDialog.InitialDirectory = $script:RegistryBackupsPath
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function Show-RestoreBackupWindow {
|
|||||||
|
|
||||||
Write-Host "User confirmed registry restore for $($backup.Target)."
|
Write-Host "User confirmed registry restore for $($backup.Target)."
|
||||||
Restore-RegistryBackupState -Backup $backup
|
Restore-RegistryBackupState -Backup $backup
|
||||||
$successMessage = 'Registry backup restored successfully.'
|
$successMessage = 'Registry backup restored successfully. Please restart your computer for all changes to take effect.'
|
||||||
}
|
}
|
||||||
elseif ($dialogResult.Result -eq 'RestoreStartMenu') {
|
elseif ($dialogResult.Result -eq 'RestoreStartMenu') {
|
||||||
$scope = $dialogResult.StartMenuScope
|
$scope = $dialogResult.StartMenuScope
|
||||||
|
|||||||
223
Scripts/Helpers/ApplyRegistryRegFile.ps1
Normal file
223
Scripts/Helpers/ApplyRegistryRegFile.ps1
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
function Get-NormalizedRegistryValueName {
|
||||||
|
param(
|
||||||
|
[AllowNull()]
|
||||||
|
$ValueName
|
||||||
|
)
|
||||||
|
|
||||||
|
if ([string]::IsNullOrEmpty([string]$ValueName)) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return [string]$ValueName
|
||||||
|
}
|
||||||
|
|
||||||
|
function Convert-RegOperationToValueKind {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
$Operation
|
||||||
|
)
|
||||||
|
|
||||||
|
$valueName = if ([string]::IsNullOrEmpty([string]$Operation.ValueName)) { '' } else { [string]$Operation.ValueName }
|
||||||
|
$valueType = [string]$Operation.ValueType
|
||||||
|
$operationKeyPath = [string]$Operation.KeyPath
|
||||||
|
|
||||||
|
switch ($valueType) {
|
||||||
|
'DWord' {
|
||||||
|
$unsigned = [uint32]$Operation.ValueData
|
||||||
|
$value = [BitConverter]::ToInt32([BitConverter]::GetBytes($unsigned), 0)
|
||||||
|
return @{ Name = $valueName; Kind = [Microsoft.Win32.RegistryValueKind]::DWord; Value = $value }
|
||||||
|
}
|
||||||
|
'String' {
|
||||||
|
return @{ Name = $valueName; Kind = [Microsoft.Win32.RegistryValueKind]::String; Value = [string]$Operation.ValueData }
|
||||||
|
}
|
||||||
|
'Binary' {
|
||||||
|
return @{ Name = $valueName; Kind = [Microsoft.Win32.RegistryValueKind]::Binary; Value = [byte[]]$Operation.ValueData }
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
throw "Unsupported value type '$valueType' while applying reg operation for '$operationKeyPath'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-RegistrySubKeyTreeIfExists {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[Microsoft.Win32.RegistryKey]$RootKey,
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$SubKeyPath
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$RootKey.DeleteSubKeyTree($SubKeyPath, $false)
|
||||||
|
}
|
||||||
|
catch [System.UnauthorizedAccessException], [System.Security.SecurityException] {
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Best-effort cleanup only; missing keys are fine.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-RegistryKeyForOperation {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$RegistryPath,
|
||||||
|
[switch]$CreateIfMissing,
|
||||||
|
[bool]$OpenKey = $true
|
||||||
|
)
|
||||||
|
|
||||||
|
$parts = Split-RegistryPath -path $RegistryPath
|
||||||
|
if (-not $parts) {
|
||||||
|
throw "Unsupported registry path: $RegistryPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
$rootKey = Get-RegistryRootKey -hiveName $parts.Hive
|
||||||
|
if (-not $rootKey) {
|
||||||
|
throw "Unsupported registry hive '$($parts.Hive)' in path '$RegistryPath'"
|
||||||
|
}
|
||||||
|
|
||||||
|
$subKeyPath = $parts.SubKey
|
||||||
|
if ([string]::IsNullOrWhiteSpace($subKeyPath)) {
|
||||||
|
return [PSCustomObject]@{ RootKey = $rootKey; SubKeyPath = $null; Key = $rootKey }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $OpenKey) {
|
||||||
|
return [PSCustomObject]@{ RootKey = $rootKey; SubKeyPath = $subKeyPath; Key = $null }
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = if ($CreateIfMissing) {
|
||||||
|
$rootKey.CreateSubKey($subKeyPath)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$rootKey.OpenSubKey($subKeyPath, $true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return [PSCustomObject]@{ RootKey = $rootKey; SubKeyPath = $subKeyPath; Key = $key }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-RegistryDeleteValueOperation {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
$Operation,
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
$KeyInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($null -eq $KeyInfo.Key) {
|
||||||
|
$valueName = Get-NormalizedRegistryValueName -ValueName $Operation.ValueName
|
||||||
|
$displayValueName = if ([string]::IsNullOrEmpty($valueName)) { '(Default)' } else { $valueName }
|
||||||
|
Write-Verbose "Unable to find or open key '$($Operation.KeyPath)' and value '$displayValueName'"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$valueName = Get-NormalizedRegistryValueName -ValueName $Operation.ValueName
|
||||||
|
$KeyInfo.Key.DeleteValue($valueName, $false)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
$KeyInfo.Key.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-RegistrySetValueOperation {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
$Operation,
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
$KeyInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($null -eq $KeyInfo.Key) {
|
||||||
|
throw [System.UnauthorizedAccessException]::new("Unable to open or create registry key '$($Operation.KeyPath)'")
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$setArgs = Convert-RegOperationToValueKind -Operation $Operation
|
||||||
|
$KeyInfo.Key.SetValue($setArgs.Name, $setArgs.Value, $setArgs.Kind)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
$KeyInfo.Key.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-RegistryOperationAccessDeniedWarning {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
$Operation,
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$ExceptionMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
$keyPath = [string]$Operation.KeyPath
|
||||||
|
$operationType = [string]$Operation.OperationType
|
||||||
|
|
||||||
|
if ($operationType -eq 'SetValue' -or $operationType -eq 'DeleteValue') {
|
||||||
|
$valueName = Get-NormalizedRegistryValueName -ValueName $Operation.ValueName
|
||||||
|
$displayValueName = if ([string]::IsNullOrEmpty($valueName)) { '(Default)' } else { $valueName }
|
||||||
|
Write-Warning "Skipping operation '$operationType' on key '$keyPath' value '$displayValueName' due to access restrictions: $ExceptionMessage"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Warning "Skipping operation '$operationType' on key '$keyPath' due to access restrictions: $ExceptionMessage"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-RegistryOperation {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
$Operation,
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$RegFilePath
|
||||||
|
)
|
||||||
|
|
||||||
|
$operationType = [string]$Operation.OperationType
|
||||||
|
$isSetValueOperation = $operationType -eq 'SetValue'
|
||||||
|
$isDeleteKeyOperation = $operationType -eq 'DeleteKey'
|
||||||
|
|
||||||
|
$keyInfo = Get-RegistryKeyForOperation -RegistryPath $Operation.KeyPath -CreateIfMissing:$isSetValueOperation -OpenKey:(-not $isDeleteKeyOperation)
|
||||||
|
|
||||||
|
switch ($operationType) {
|
||||||
|
'DeleteKey' {
|
||||||
|
if ($null -ne $keyInfo.SubKeyPath) {
|
||||||
|
Remove-RegistrySubKeyTreeIfExists -RootKey $keyInfo.RootKey -SubKeyPath $keyInfo.SubKeyPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'DeleteValue' {
|
||||||
|
Invoke-RegistryDeleteValueOperation -Operation $Operation -KeyInfo $keyInfo
|
||||||
|
}
|
||||||
|
'SetValue' {
|
||||||
|
Invoke-RegistrySetValueOperation -Operation $Operation -KeyInfo $keyInfo
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
throw "Unsupported reg operation type '$($Operation.OperationType)' in '$RegFilePath'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-RegistryOperationsFromRegFile {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$RegFilePath
|
||||||
|
)
|
||||||
|
|
||||||
|
$accessDeniedCount = 0
|
||||||
|
$operations = @(Get-RegFileOperations -regFilePath $RegFilePath)
|
||||||
|
$totalOperations = $operations.Count
|
||||||
|
|
||||||
|
foreach ($operation in $operations) {
|
||||||
|
try {
|
||||||
|
Invoke-RegistryOperation -Operation $operation -RegFilePath $RegFilePath
|
||||||
|
}
|
||||||
|
catch [System.UnauthorizedAccessException], [System.Security.SecurityException] {
|
||||||
|
$accessDeniedCount++
|
||||||
|
Write-RegistryOperationAccessDeniedWarning -Operation $operation -ExceptionMessage $_.Exception.Message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($totalOperations -gt 0 -and $accessDeniedCount -eq $totalOperations) {
|
||||||
|
throw "Registry fallback import could not apply any operations in '$RegFilePath' because all $accessDeniedCount operation(s) were blocked by access restrictions."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($accessDeniedCount -gt 0) {
|
||||||
|
Write-Warning "Registry fallback import completed with $accessDeniedCount access-restricted operation(s) skipped in '$RegFilePath'."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -141,7 +141,7 @@ if (-not $isAdmin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Define script-level variables & paths
|
# Define script-level variables & paths
|
||||||
$script:Version = "2026.05.10"
|
$script:Version = "2026.05.20"
|
||||||
$configPath = Join-Path $PSScriptRoot 'Config'
|
$configPath = Join-Path $PSScriptRoot 'Config'
|
||||||
$logsPath = Join-Path $PSScriptRoot 'Logs'
|
$logsPath = Join-Path $PSScriptRoot 'Logs'
|
||||||
$schemasPath = Join-Path $PSScriptRoot 'Schemas'
|
$schemasPath = Join-Path $PSScriptRoot 'Schemas'
|
||||||
@@ -349,6 +349,7 @@ if (-not $script:WingetInstalled -and -not $Silent) {
|
|||||||
. "$PSScriptRoot/Scripts/Helpers/GetUserDirectory.ps1"
|
. "$PSScriptRoot/Scripts/Helpers/GetUserDirectory.ps1"
|
||||||
. "$PSScriptRoot/Scripts/Helpers/GetUserName.ps1"
|
. "$PSScriptRoot/Scripts/Helpers/GetUserName.ps1"
|
||||||
. "$PSScriptRoot/Scripts/Helpers/RegistryPathHelpers.ps1"
|
. "$PSScriptRoot/Scripts/Helpers/RegistryPathHelpers.ps1"
|
||||||
|
. "$PSScriptRoot/Scripts/Helpers/ApplyRegistryRegFile.ps1"
|
||||||
. "$PSScriptRoot/Scripts/Helpers/TestIfUserIsLoggedIn.ps1"
|
. "$PSScriptRoot/Scripts/Helpers/TestIfUserIsLoggedIn.ps1"
|
||||||
|
|
||||||
# Threading functions
|
# Threading functions
|
||||||
@@ -401,7 +402,7 @@ else {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($script:Params.ContainsKey("Sysprep")) {
|
if ($script:Params.ContainsKey("Sysprep")) {
|
||||||
$defaultUserPath = GetUserDirectory -userName "Default"
|
GetUserDirectory -userName "Default" | Out-Null
|
||||||
|
|
||||||
# Exit script if run in Sysprep mode on Windows 10
|
# Exit script if run in Sysprep mode on Windows 10
|
||||||
if ($WinVersion -lt 22000) {
|
if ($WinVersion -lt 22000) {
|
||||||
@@ -412,10 +413,10 @@ if ($script:Params.ContainsKey("Sysprep")) {
|
|||||||
|
|
||||||
# Ensure that target user exists, if User or AppRemovalTarget parameter was provided
|
# Ensure that target user exists, if User or AppRemovalTarget parameter was provided
|
||||||
if ($script:Params.ContainsKey("User")) {
|
if ($script:Params.ContainsKey("User")) {
|
||||||
$userPath = GetUserDirectory -userName $script:Params.Item("User")
|
GetUserDirectory -userName $script:Params.Item("User") | Out-Null
|
||||||
}
|
}
|
||||||
if ($script:Params.ContainsKey("AppRemovalTarget")) {
|
if ($script:Params.ContainsKey("AppRemovalTarget")) {
|
||||||
$userPath = GetUserDirectory -userName $script:Params.Item("AppRemovalTarget")
|
GetUserDirectory -userName $script:Params.Item("AppRemovalTarget") | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove LastUsedSettings.json file if it exists and is empty
|
# Remove LastUsedSettings.json file if it exists and is empty
|
||||||
|
|||||||
Reference in New Issue
Block a user