This commit is contained in:
Jeffrey
2026-05-20 16:29:41 +02:00
9 changed files with 312 additions and 91 deletions

View File

@@ -366,7 +366,7 @@
{
"FeatureId": "DisableTelemetry",
"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",
"RegistryKey": "Disable_Telemetry.reg",
"ApplyText": "Disabling telemetry, diagnostic data, activity history, app-launch tracking and targeted ads...",
@@ -601,28 +601,6 @@
"MinVersion": 22621,
"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",
"Label": "Hide Microsoft 365 Copilot ads in Settings Home",
@@ -878,12 +856,12 @@
{
"FeatureId": "DisableWidgets",
"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",
"Priority": 4,
"RegistryKey": "Disable_Widgets_Service.reg",
"ApplyText": "Disabling widgets on the taskbar & lock screen...",
"RegistryUndoKey": "Enable_Widgets_Service.reg",
"RegistryKey": null,
"ApplyText": null,
"RegistryUndoKey": null,
"MinVersion": null,
"MaxVersion": null
},

Binary file not shown.

View File

@@ -26,10 +26,6 @@ function ExecuteParameter {
# Also remove the app package for Copilot
RemoveApps 'Microsoft.Copilot'
}
'DisableWidgets' {
# Also remove the app packages for Widgets
RemoveApps 'Microsoft.StartExperiencesApp','MicrosoftWindows.Client.WebExperience','Microsoft.WidgetsPlatformRuntime'
}
}
return
}
@@ -86,6 +82,13 @@ function ExecuteParameter {
RemoveApps $appsList
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" {
Write-Host "> Enabling Windows Sandbox..."
EnableWindowsFeature "Containers-DisposableClientVM"
@@ -138,6 +141,8 @@ function ExecuteParameter {
# Executes all selected parameters/features
function ExecuteAllChanges {
$script:RegistryImportFailures = 0
# Build list of actionable parameters (skip control params and data-only params)
$actionableKeys = @()
foreach ($paramKey in $script:Params.Keys) {
@@ -216,4 +221,9 @@ function ExecuteAllChanges {
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
}
}

View File

@@ -8,33 +8,44 @@ function ImportRegistryFile {
Write-Host $message
$usesOfflineHive = $script:Params.ContainsKey("Sysprep") -or $script:Params.ContainsKey("User")
$regFilePath = if ($usesOfflineHive) {
"$script:RegfilesPath\Sysprep\$path"
$regFileDirectory = if ($usesOfflineHive) {
Join-Path $script:RegfilesPath "Sysprep"
}
else {
"$script:RegfilesPath\$path"
$script:RegfilesPath
}
$regFilePath = Join-Path $regFileDirectory $path
if (-not (Test-Path $regFilePath)) {
$errorMessage = "Unable to find registry file: $path ($regFilePath)"
$script:RegistryImportFailures++
Write-Host "Error: $errorMessage" -ForegroundColor Red
Write-Host ""
throw $errorMessage
}
# Reset exit code before running reg.exe for reliable success detection
$global:LASTEXITCODE = 0
$regResult = $null
$offlineHiveLoaded = $false
if ($usesOfflineHive) {
# Sysprep targets Default user, User targets the specified user
$hiveDatPath = if ($script:Params.ContainsKey("Sysprep")) {
GetUserDirectory -userName "Default" -fileName "NTUSER.DAT"
} else {
GetUserDirectory -userName $script:Params.Item("User") -fileName "NTUSER.DAT"
try {
if ($usesOfflineHive) {
# Sysprep targets Default user, User targets the specified user
$targetUserName = if ($script:Params.ContainsKey("Sysprep")) { "Default" } else { $script:Params.Item("User") }
$hiveDatPath = GetUserDirectory -userName $targetUserName -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 {
param($hivePath, $targetRegFilePath)
param($targetRegFilePath)
$result = @{
Output = @()
ExitCode = 0
@@ -43,13 +54,6 @@ function ImportRegistryFile {
try {
$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
$importExitCode = $LASTEXITCODE
@@ -66,52 +70,50 @@ function ImportRegistryFile {
$result.Error = $_.Exception.Message
$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
} -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
}
$regOutput = @($regResult.Output)
$hasSuccess = ($regResult.ExitCode -eq 0) -and -not $regResult.Error
if ($regOutput) {
foreach ($line in $regOutput) {
$lineText = if ($line -is [System.Management.Automation.ErrorRecord]) { $line.Exception.Message } else { $line.ToString() }
if ($lineText -and $lineText.Length -gt 0) {
if ($hasSuccess) {
Write-Host $lineText
}
else {
Write-Host $lineText -ForegroundColor Red
$regOutput = @($regResult.Output)
$hasSuccess = ($regResult.ExitCode -eq 0) -and -not $regResult.Error
if ($regOutput) {
foreach ($line in $regOutput) {
$lineText = if ($line -is [System.Management.Automation.ErrorRecord]) { $line.Exception.Message } else { $line.ToString() }
if ($lineText -and $lineText.Length -gt 0) {
if ($hasSuccess) {
Write-Host $lineText
}
else {
Write-Host $lineText -ForegroundColor Red
}
}
}
}
}
if (-not $hasSuccess) {
$details = if ($regResult.Error) { $regResult.Error } else { "Exit code: $($regResult.ExitCode)" }
$errorMessage = "Failed importing registry file '$path'. $details"
Write-Host $errorMessage -ForegroundColor Red
if (-not $hasSuccess) {
$details = if ($regResult.Error) { $regResult.Error } else { "Exit code: $($regResult.ExitCode)" }
Write-Warning "reg import failed for '$path'. Falling back to PowerShell registry writer. Details: $details"
Invoke-RegistryOperationsFromRegFile -RegFilePath $regFilePath
Write-Host "Fallback import succeeded for '$path'." -ForegroundColor Yellow
}
Write-Host ""
throw $errorMessage
}
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
Write-Host ""
if ($unloadExitCode -ne 0) {
Write-Warning "Failed to unload registry hive HKU\Default after importing '$path' (exit code: $unloadExitCode)"
}
}
}
}

View File

@@ -123,6 +123,8 @@ function Show-ApplyModal {
$applyWindow.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{
try {
ExecuteAllChanges
$registryImportFailureCount = [int]$script:RegistryImportFailures
# Restart explorer if requested
if ($RestartExplorer -and -not $script:CancelRequested) {
@@ -139,7 +141,7 @@ function Show-ApplyModal {
Write-Host ""
if ($script:CancelRequested) {
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!"
}
@@ -153,6 +155,11 @@ function Show-ApplyModal {
$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."
} 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 {
$script:ApplyCompletionTitleEl.Text = "Changes Applied"

View 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'."
}
}

View File

@@ -349,6 +349,7 @@ if (-not $script:WingetInstalled -and -not $Silent) {
. "$PSScriptRoot/Scripts/Helpers/GetUserDirectory.ps1"
. "$PSScriptRoot/Scripts/Helpers/GetUserName.ps1"
. "$PSScriptRoot/Scripts/Helpers/RegistryPathHelpers.ps1"
. "$PSScriptRoot/Scripts/Helpers/ApplyRegistryRegFile.ps1"
. "$PSScriptRoot/Scripts/Helpers/TestIfUserIsLoggedIn.ps1"
# Threading functions
@@ -401,7 +402,7 @@ else {
}
if ($script:Params.ContainsKey("Sysprep")) {
$defaultUserPath = GetUserDirectory -userName "Default"
GetUserDirectory -userName "Default" | Out-Null
# Exit script if run in Sysprep mode on Windows 10
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
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")) {
$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