mirror of
https://github.com/Raphire/Win11Debloat.git
synced 2026-08-23 08:02:07 +00:00
Add comprehensive test suite, fix minor issues, rename function and file names to match approved verbs (#708)
This commit is contained in:
+34
-4
@@ -195,10 +195,18 @@ function Get-RegistryKeySnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Converts an open registry key into a backup snapshot.
|
||||
|
||||
.DESCRIPTION
|
||||
Captures all values or selected value names, records missing selected values,
|
||||
and recursively captures subkeys when requested.
|
||||
#>
|
||||
function Convert-RegistryKeyToSnapshot {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Microsoft.Win32.RegistryKey]$RegistryKey,
|
||||
$RegistryKey,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$FullPath,
|
||||
[bool]$CaptureAllValues = $false,
|
||||
@@ -253,20 +261,34 @@ function Convert-RegistryKeyToSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Converts a registry value into a serializable backup snapshot.
|
||||
|
||||
.DESCRIPTION
|
||||
Preserves the value kind and normalizes supported data types for JSON
|
||||
serialization without expanding environment-string values. REG_NONE values
|
||||
are rejected.
|
||||
#>
|
||||
function Convert-RegistryValueToSnapshot {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Microsoft.Win32.RegistryKey]$RegistryKey,
|
||||
$RegistryKey,
|
||||
[Parameter(Mandatory)]
|
||||
[AllowEmptyString()]
|
||||
[string]$ValueName
|
||||
)
|
||||
|
||||
$valueKind = $RegistryKey.GetValueKind($ValueName)
|
||||
if ($valueKind -eq [Microsoft.Win32.RegistryValueKind]::None) {
|
||||
throw "REG_NONE registry values are not supported for backup. Key='$($RegistryKey.Name)' Name='$ValueName'"
|
||||
}
|
||||
|
||||
$value = $RegistryKey.GetValue($ValueName, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
|
||||
try {
|
||||
$normalizedValue = switch ($valueKind) {
|
||||
([Microsoft.Win32.RegistryValueKind]::Binary) { @($value | ForEach-Object { [int]$_ }) }
|
||||
# Prevent an empty byte sequence from being unrolled to $null by the switch pipeline.
|
||||
([Microsoft.Win32.RegistryValueKind]::Binary) { if ($null -eq $value) { ,@() } else { ,@($value | ForEach-Object { [int]$_ }) } }
|
||||
([Microsoft.Win32.RegistryValueKind]::MultiString) { @($value) }
|
||||
([Microsoft.Win32.RegistryValueKind]::DWord) { [BitConverter]::ToUInt32([BitConverter]::GetBytes([int32]$value), 0) }
|
||||
([Microsoft.Win32.RegistryValueKind]::QWord) { [BitConverter]::ToUInt64([BitConverter]::GetBytes([int64]$value), 0) }
|
||||
@@ -287,12 +309,20 @@ function Convert-RegistryValueToSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Describes the user profile targeted by a registry backup.
|
||||
|
||||
.DESCRIPTION
|
||||
Returns DefaultUserProfile for Sysprep, User:<name> for an explicit user,
|
||||
or CurrentUser:<name> otherwise.
|
||||
#>
|
||||
function Get-RegistryBackupTargetDescription {
|
||||
if ($script:Params.ContainsKey('Sysprep')) {
|
||||
return 'DefaultUserProfile'
|
||||
}
|
||||
|
||||
$resolvedUserName = [string](GetUserName)
|
||||
$resolvedUserName = [string](Get-UserName)
|
||||
|
||||
if ($script:Params.ContainsKey('User')) {
|
||||
return "User:$resolvedUserName"
|
||||
+1
-1
@@ -37,7 +37,7 @@ function New-RegistrySettingsBackup {
|
||||
$backupFilePath = Join-Path $backupDirectory $backupFileName
|
||||
|
||||
$backupConfig = Get-RegistryBackupPayload -SelectedFeatures $selectedFeatures -UndoFeatures $undoFeatures -CreatedAt $timestamp
|
||||
if (-not (SaveToFile -Config $backupConfig -FilePath $backupFilePath -MaxDepth 25)) {
|
||||
if (-not (Save-ToFile -Config $backupConfig -FilePath $backupFilePath -MaxDepth 25)) {
|
||||
throw "Failed to save registry backup to '$backupFilePath'"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
function CreateSystemRestorePoint {
|
||||
function Invoke-SystemRestorePoint {
|
||||
$SysRestore = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore" -Name "RPSessionInterval"
|
||||
$failed = $false
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ function Test-FeatureApplied {
|
||||
return (Test-StoreSearchSuggestionsDisabledForAllUsers)
|
||||
}
|
||||
|
||||
$storeDbPath = GetStoreAppsDatabasePathForUser -UserName (GetUserName)
|
||||
$storeDbPath = Get-StoreAppsDatabasePathForUser -UserName (Get-UserName)
|
||||
|
||||
return (Test-StoreSearchSuggestionsDisabled -StoreAppsDatabase $storeDbPath)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
# Import & execute regfile
|
||||
function ImportRegistryFile {
|
||||
function Import-RegistryFile {
|
||||
param (
|
||||
$message,
|
||||
$path
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
.DESCRIPTION
|
||||
Handles two categories of features:
|
||||
- Registry-backed: imports the .reg file via ImportRegistryFile, then runs
|
||||
- Registry-backed: imports the .reg file via Import-RegistryFile, then runs
|
||||
any post-import side effects (e.g., removing companion app packages).
|
||||
- Custom logic: app removal, Windows optional features, start menu
|
||||
replacement, and other special-case features.
|
||||
@@ -21,17 +21,17 @@ function Invoke-FeatureApply {
|
||||
|
||||
# ---- Registry-backed features: import .reg file, then handle side effects ----
|
||||
if ($feature.RegistryKey) {
|
||||
ImportRegistryFile "> $applyText..." $feature.RegistryKey
|
||||
Import-RegistryFile "> $applyText..." $feature.RegistryKey
|
||||
|
||||
# Post-import side effects for specific features
|
||||
switch ($FeatureId) {
|
||||
'DisableBing' {
|
||||
# Also remove the app package for Bing search
|
||||
RemoveApps @('Microsoft.BingSearch')
|
||||
Remove-SelectedApps @('Microsoft.BingSearch')
|
||||
}
|
||||
'DisableCopilot' {
|
||||
# Also remove the app packages for Copilot
|
||||
RemoveApps @('Microsoft.Copilot', 'XP9CXNGPPJ97XX')
|
||||
Remove-SelectedApps @('Microsoft.Copilot', 'XP9CXNGPPJ97XX')
|
||||
}
|
||||
'DisableTelemetry' {
|
||||
# Also disable telemetry scheduled tasks
|
||||
@@ -44,8 +44,8 @@ function Invoke-FeatureApply {
|
||||
# ---- Custom features (no registry backing, or special handling required) ----
|
||||
switch ($FeatureId) {
|
||||
'RemoveApps' {
|
||||
Write-Host "> $applyText for $(GetFriendlyTargetUserName)..."
|
||||
$appsList = GenerateAppsList
|
||||
Write-Host "> $applyText for $(Get-FriendlyTargetUserName)..."
|
||||
$appsList = Generate-AppsList
|
||||
|
||||
if ($appsList.Count -eq 0) {
|
||||
Write-Host "No valid apps were selected for removal" -ForegroundColor Yellow
|
||||
@@ -54,19 +54,19 @@ function Invoke-FeatureApply {
|
||||
}
|
||||
|
||||
Write-Host "$($appsList.Count) apps selected for removal"
|
||||
RemoveApps $appsList
|
||||
Remove-SelectedApps $appsList
|
||||
return
|
||||
}
|
||||
'RemoveGamingApps' {
|
||||
$appsList = @('Microsoft.GamingApp', 'Microsoft.XboxGameOverlay', 'Microsoft.XboxGamingOverlay')
|
||||
Write-Host "> $applyText..."
|
||||
RemoveApps $appsList
|
||||
Remove-SelectedApps $appsList
|
||||
return
|
||||
}
|
||||
'RemoveHPApps' {
|
||||
$appsList = @('AD2F1837.HPAIExperienceCenter', 'AD2F1837.HPJumpStarts', 'AD2F1837.HPPCHardwareDiagnosticsWindows', 'AD2F1837.HPPowerManager', 'AD2F1837.HPPrivacySettings', 'AD2F1837.HPSupportAssistant', 'AD2F1837.HPSureShieldAI', 'AD2F1837.HPSystemInformation', 'AD2F1837.HPQuickDrop', 'AD2F1837.HPWorkWell', 'AD2F1837.myHP', 'AD2F1837.HPDesktopSupportUtilities', 'AD2F1837.HPQuickTouch', 'AD2F1837.HPEasyClean', 'AD2F1837.HPConnectedMusic', 'AD2F1837.HPFileViewer', 'AD2F1837.HPRegistration', 'AD2F1837.HPWelcome', 'AD2F1837.HPConnectedPhotopoweredbySnapfish', 'AD2F1837.HPPrinterControl')
|
||||
Write-Host "> $applyText..."
|
||||
RemoveApps $appsList
|
||||
Remove-SelectedApps $appsList
|
||||
return
|
||||
}
|
||||
'DisableWidgets' {
|
||||
@@ -76,46 +76,46 @@ function Invoke-FeatureApply {
|
||||
Get-Process *Widget* -ErrorAction SilentlyContinue | Stop-Process
|
||||
}
|
||||
|
||||
RemoveApps @('Microsoft.StartExperiencesApp','MicrosoftWindows.Client.WebExperience','Microsoft.WidgetsPlatformRuntime')
|
||||
Remove-SelectedApps @('Microsoft.StartExperiencesApp','MicrosoftWindows.Client.WebExperience','Microsoft.WidgetsPlatformRuntime')
|
||||
return
|
||||
}
|
||||
'EnableWindowsSandbox' {
|
||||
Write-Host "> $applyText..."
|
||||
EnableWindowsFeature "Containers-DisposableClientVM"
|
||||
Enable-WindowsFeature "Containers-DisposableClientVM"
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'EnableWindowsSubsystemForLinux' {
|
||||
Write-Host "> $applyText..."
|
||||
EnableWindowsFeature "VirtualMachinePlatform"
|
||||
EnableWindowsFeature "Microsoft-Windows-Subsystem-Linux"
|
||||
Enable-WindowsFeature "VirtualMachinePlatform"
|
||||
Enable-WindowsFeature "Microsoft-Windows-Subsystem-Linux"
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'ClearStart' {
|
||||
Write-Host "> $applyText for user $(GetUserName)..."
|
||||
$startMenuBinFile = GetStartMenuBinPathForUser -UserName (GetUserName)
|
||||
Write-Host "> $applyText for user $(Get-UserName)..."
|
||||
$startMenuBinFile = Get-StartMenuBinPathForUser -UserName (Get-UserName)
|
||||
if (-not [string]::IsNullOrWhiteSpace($startMenuBinFile)) {
|
||||
ReplaceStartMenu -startMenuBinFile $startMenuBinFile
|
||||
Replace-StartMenu -startMenuBinFile $startMenuBinFile
|
||||
}
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'ReplaceStart' {
|
||||
Write-Host "> $applyText for user $(GetUserName)..."
|
||||
$startMenuBinFile = GetStartMenuBinPathForUser -UserName (GetUserName)
|
||||
Write-Host "> $applyText for user $(Get-UserName)..."
|
||||
$startMenuBinFile = Get-StartMenuBinPathForUser -UserName (Get-UserName)
|
||||
if (-not [string]::IsNullOrWhiteSpace($startMenuBinFile)) {
|
||||
ReplaceStartMenu -startMenuBinFile $startMenuBinFile -startMenuTemplate $script:Params.Item("ReplaceStart")
|
||||
Replace-StartMenu -startMenuBinFile $startMenuBinFile -startMenuTemplate $script:Params.Item("ReplaceStart")
|
||||
}
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'ClearStartAllUsers' {
|
||||
ReplaceStartMenuForAllUsers
|
||||
Replace-StartMenuForAllUsers
|
||||
return
|
||||
}
|
||||
'ReplaceStartAllUsers' {
|
||||
ReplaceStartMenuForAllUsers -startMenuTemplate $script:Params.Item("ReplaceStartAllUsers")
|
||||
Replace-StartMenuForAllUsers -startMenuTemplate $script:Params.Item("ReplaceStartAllUsers")
|
||||
return
|
||||
}
|
||||
'DisableStoreSearchSuggestions' {
|
||||
@@ -126,10 +126,10 @@ function Invoke-FeatureApply {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "> Disabling Microsoft Store search suggestions for user $(GetUserName)..."
|
||||
$storeDb = GetStoreAppsDatabasePathForUser -UserName (GetUserName)
|
||||
Write-Host "> Disabling Microsoft Store search suggestions for user $(Get-UserName)..."
|
||||
$storeDb = Get-StoreAppsDatabasePathForUser -UserName (Get-UserName)
|
||||
if ($storeDb) {
|
||||
DisableStoreSearchSuggestions -StoreAppsDatabase $storeDb
|
||||
Set-StoreSearchSuggestionsDisabled -StoreAppsDatabase $storeDb
|
||||
}
|
||||
Write-Host ""
|
||||
return
|
||||
@@ -145,7 +145,7 @@ function Invoke-FeatureApply {
|
||||
.DESCRIPTION
|
||||
Handles undo for features that require custom logic rather than a simple
|
||||
.reg file import. Features with a RegistryUndoKey are handled directly
|
||||
via ImportRegistryFile in Invoke-UndoFeatures.
|
||||
via Import-RegistryFile in Invoke-UndoFeatures.
|
||||
#>
|
||||
function Invoke-FeatureUndo {
|
||||
param(
|
||||
@@ -164,24 +164,24 @@ function Invoke-FeatureUndo {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "> Re-enabling Microsoft Store search suggestions for user $(GetUserName)..."
|
||||
$storeDb = GetStoreAppsDatabasePathForUser -UserName (GetUserName)
|
||||
Write-Host "> Re-enabling Microsoft Store search suggestions for user $(Get-UserName)..."
|
||||
$storeDb = Get-StoreAppsDatabasePathForUser -UserName (Get-UserName)
|
||||
if ($storeDb) {
|
||||
EnableStoreSearchSuggestions -StoreAppsDatabase $storeDb
|
||||
Set-StoreSearchSuggestionsEnabled -StoreAppsDatabase $storeDb
|
||||
}
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'EnableWindowsSandbox' {
|
||||
Write-Host "> $($feature.ApplyUndoText)..."
|
||||
DisableWindowsFeature 'Containers-DisposableClientVM'
|
||||
Disable-WindowsFeature 'Containers-DisposableClientVM'
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
'EnableWindowsSubsystemForLinux' {
|
||||
Write-Host "> $($feature.ApplyUndoText)..."
|
||||
DisableWindowsFeature 'Microsoft-Windows-Subsystem-Linux'
|
||||
DisableWindowsFeature 'VirtualMachinePlatform'
|
||||
Disable-WindowsFeature 'Microsoft-Windows-Subsystem-Linux'
|
||||
Disable-WindowsFeature 'VirtualMachinePlatform'
|
||||
Write-Host ""
|
||||
return
|
||||
}
|
||||
@@ -287,7 +287,7 @@ function Invoke-UndoFeatures {
|
||||
}
|
||||
|
||||
if ($f -and $f.RegistryUndoKey) {
|
||||
ImportRegistryFile "> $undoText" (Resolve-UndoRegFilePath $f.RegistryUndoKey)
|
||||
Import-RegistryFile "> $undoText" (Resolve-UndoRegFilePath $f.RegistryUndoKey)
|
||||
}
|
||||
|
||||
Invoke-FeatureUndo -FeatureId $featureId
|
||||
@@ -311,8 +311,10 @@ function Invoke-UndoFeatures {
|
||||
(used by the GUI modal). Cancellation is checked between each step.
|
||||
#>
|
||||
function Invoke-AllChanges {
|
||||
if ($script:CancelRequested) { return }
|
||||
|
||||
# Guard: prevent running as SYSTEM account without explicit target user
|
||||
$isSystem = ([Security.Principal.WindowsIdentity]::GetCurrent().User.Value -eq 'S-1-5-18')
|
||||
$isSystem = Test-RunningAsSystem
|
||||
if ($isSystem -and -not $script:Params.ContainsKey("User") -and -not $script:Params.ContainsKey("Sysprep")) {
|
||||
throw "Win11Debloat is running as the SYSTEM account. Use the '-User' or '-Sysprep' parameter to target a specific user."
|
||||
}
|
||||
@@ -355,6 +357,7 @@ function Invoke-AllChanges {
|
||||
# Phase 1: Registry backup
|
||||
# ================================================================
|
||||
if ($needsBackup) {
|
||||
if ($script:CancelRequested) { return }
|
||||
$step++
|
||||
if ($script:ApplyProgressCallback) {
|
||||
& $script:ApplyProgressCallback $step $totalSteps "Creating registry backup..."
|
||||
@@ -384,6 +387,7 @@ function Invoke-AllChanges {
|
||||
# Phase 2: System restore point
|
||||
# ================================================================
|
||||
if ($script:Params.ContainsKey("CreateRestorePoint")) {
|
||||
if ($script:CancelRequested) { return }
|
||||
$step++
|
||||
if ($script:ApplyProgressCallback) {
|
||||
& $script:ApplyProgressCallback $step $totalSteps "Creating system restore point, this may take a moment..."
|
||||
@@ -394,7 +398,7 @@ function Invoke-AllChanges {
|
||||
}
|
||||
else {
|
||||
Write-Host "> Creating a system restore point..."
|
||||
CreateSystemRestorePoint
|
||||
Invoke-SystemRestorePoint
|
||||
Write-Host ""
|
||||
}
|
||||
}
|
||||
@@ -407,6 +411,8 @@ function Invoke-AllChanges {
|
||||
$step += $applyIds.Count
|
||||
}
|
||||
|
||||
if ($script:CancelRequested) { return }
|
||||
|
||||
# ================================================================
|
||||
# Phase 4: Undo features
|
||||
# ================================================================
|
||||
@@ -423,3 +429,19 @@ function Invoke-AllChanges {
|
||||
Write-Host "$($script:RegistryImportFailures) registry import change(s) failed. See output above for details." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Tests whether Win11Debloat is running under the SYSTEM account.
|
||||
|
||||
.DESCRIPTION
|
||||
Compares the current Windows identity's security identifier (SID) with
|
||||
the well-known Local System SID (S-1-5-18).
|
||||
|
||||
.OUTPUTS
|
||||
System.Boolean
|
||||
Returns $true when the current process runs as SYSTEM; otherwise, $false.
|
||||
#>
|
||||
function Test-RunningAsSystem {
|
||||
return ([Security.Principal.WindowsIdentity]::GetCurrent().User.Value -eq 'S-1-5-18')
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
.DESCRIPTION
|
||||
Restarts the Explorer process to ensure all UI modifications take effect. Shows a warning if any of the applied features require a reboot to take full effect.
|
||||
#>
|
||||
function RestartExplorer {
|
||||
function Invoke-RestartExplorer {
|
||||
if ($script:Params.ContainsKey("WhatIf")) {
|
||||
Write-Host "[WhatIf] Restart the Windows Explorer process" -ForegroundColor Cyan
|
||||
return
|
||||
@@ -32,4 +32,4 @@ function RestartExplorer {
|
||||
else {
|
||||
Write-Host "Unable to restart Windows Explorer process, please manually reboot your PC to apply all changes" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
-57
@@ -248,6 +248,14 @@ function New-RegistryBackupAllowListPlanMap {
|
||||
return $planMap
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Converts registry value names into a case-insensitive set.
|
||||
|
||||
.DESCRIPTION
|
||||
Preserves empty names and prevents PowerShell from enumerating the returned
|
||||
HashSet.
|
||||
#>
|
||||
function ConvertTo-RegistryValueNameSet {
|
||||
param(
|
||||
[AllowEmptyCollection()]
|
||||
@@ -259,9 +267,18 @@ function ConvertTo-RegistryValueNameSet {
|
||||
$null = $valueNameSet.Add([string]$valueName)
|
||||
}
|
||||
|
||||
return $valueNameSet
|
||||
# Prevent PowerShell from enumerating the HashSet into an array or single string
|
||||
return ,$valueNameSet
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Validates a registry snapshot against the selected-feature allow list.
|
||||
|
||||
.DESCRIPTION
|
||||
Recursively validates snapshot paths, value names, value kinds, and value
|
||||
data, appending validation errors to the supplied list.
|
||||
#>
|
||||
function Test-RegistrySnapshotAgainstAllowList {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -301,7 +318,7 @@ function Test-RegistrySnapshotAgainstAllowList {
|
||||
$Errors.Add("Backup contains unsupported registry value kind '$kindName' for '$valueReference'.")
|
||||
}
|
||||
elseif (-not (Test-RegistryValueDataMatchesKind -KindName $kindName -Data $valueSnapshot.Data)) {
|
||||
$Errors.Add("Backup value '$valueReference' has Data that does not fit its Kind '$kindName'.")
|
||||
$Errors.Add("Backup contains invalid registry data for kind '$kindName' at '$valueReference'.")
|
||||
}
|
||||
}
|
||||
elseif (-not [string]::IsNullOrWhiteSpace($kindName)) {
|
||||
@@ -314,6 +331,64 @@ function Test-RegistrySnapshotAgainstAllowList {
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Tests whether backed-up registry data is valid for its declared value kind.
|
||||
|
||||
.DESCRIPTION
|
||||
Rejects corrupted or hand-edited backup data that cannot be restored safely,
|
||||
such as a DWord that overflows UInt32 or binary data containing an invalid byte.
|
||||
This validation runs before Restore-RegistryKeySnapshot mutates the live
|
||||
registry, preventing a failed conversion from leaving a partially restored key.
|
||||
|
||||
.PARAMETER KindName
|
||||
The declared registry value kind name, such as DWord, QWord, or Binary.
|
||||
|
||||
.PARAMETER Data
|
||||
The backed-up value data to validate against the declared kind.
|
||||
|
||||
.OUTPUTS
|
||||
System.Boolean
|
||||
#>
|
||||
function Test-RegistryValueDataMatchesKind {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$KindName,
|
||||
[AllowNull()]
|
||||
$Data
|
||||
)
|
||||
|
||||
$kind = [System.Enum]::Parse([Microsoft.Win32.RegistryValueKind], $KindName, $true)
|
||||
switch ($kind) {
|
||||
([Microsoft.Win32.RegistryValueKind]::DWord) {
|
||||
$parsed = [uint32]0
|
||||
return [uint32]::TryParse([string]$Data, [System.Globalization.NumberStyles]::Integer, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$parsed)
|
||||
}
|
||||
([Microsoft.Win32.RegistryValueKind]::QWord) {
|
||||
$parsed = [uint64]0
|
||||
return [uint64]::TryParse([string]$Data, [System.Globalization.NumberStyles]::Integer, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$parsed)
|
||||
}
|
||||
([Microsoft.Win32.RegistryValueKind]::Binary) {
|
||||
if ($null -eq $Data -or $Data -isnot [array]) { return $false }
|
||||
foreach ($item in @($Data)) {
|
||||
if ($item -isnot [ValueType] -and $item -isnot [string]) { return $false }
|
||||
$parsed = 0
|
||||
if (-not [int]::TryParse([string]$item, [ref]$parsed) -or $parsed -lt 0 -or $parsed -gt 255) {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
return $true
|
||||
}
|
||||
([Microsoft.Win32.RegistryValueKind]::MultiString) {
|
||||
foreach ($item in @($Data)) {
|
||||
if ($item -isnot [string]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
default { return ($null -eq $Data -or $Data -is [string]) }
|
||||
}
|
||||
}
|
||||
|
||||
function Test-RegistryValueAllowedByPlan {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -431,6 +506,14 @@ function Get-NormalizedRegistryPathKey {
|
||||
return "$normalizedHive\\$normalizedSubKey"
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Tests whether a registry value-kind name is supported in backups.
|
||||
|
||||
.DESCRIPTION
|
||||
Parses kind names case-insensitively and rejects empty, invalid, Unknown,
|
||||
and None values.
|
||||
#>
|
||||
function Test-RegistryValueKindNameSupported {
|
||||
param(
|
||||
[string]$KindName
|
||||
@@ -442,64 +525,10 @@ function Test-RegistryValueKindNameSupported {
|
||||
|
||||
try {
|
||||
$kind = [System.Enum]::Parse([Microsoft.Win32.RegistryValueKind], $KindName, $true)
|
||||
return $kind -ne [Microsoft.Win32.RegistryValueKind]::Unknown
|
||||
return $kind -notin @([Microsoft.Win32.RegistryValueKind]::Unknown, [Microsoft.Win32.RegistryValueKind]::None)
|
||||
}
|
||||
catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Checks whether a backed-up value's Data can be converted to its declared Kind.
|
||||
|
||||
.DESCRIPTION
|
||||
A corrupted or hand-edited backup can have Data that doesn't fit its declared
|
||||
Kind (e.g. Kind=DWord with Data=4294967296, which overflows uint32). Restore-
|
||||
RegistryValueSnapshot's Convert-RegistryValueDataFromBackup performs the same
|
||||
narrowing casts without a try/catch, and by the time it runs the live registry
|
||||
subtree has already been deleted (Restore-RegistryKeySnapshot deletes before
|
||||
rewriting) - so an invalid Data/Kind pairing must be rejected here, before any
|
||||
restore begins, not left to fail mid-restore. See #686.
|
||||
|
||||
.PARAMETER KindName
|
||||
The value's declared registry kind name (e.g. "DWord", "QWord", "String").
|
||||
|
||||
.PARAMETER Data
|
||||
The value's backed-up data to validate against KindName.
|
||||
#>
|
||||
function Test-RegistryValueDataMatchesKind {
|
||||
param(
|
||||
[string]$KindName,
|
||||
$Data
|
||||
)
|
||||
|
||||
$kind = [System.Enum]::Parse([Microsoft.Win32.RegistryValueKind], $KindName, $true)
|
||||
|
||||
switch ($kind) {
|
||||
([Microsoft.Win32.RegistryValueKind]::DWord) {
|
||||
try {
|
||||
[void][uint32]$Data
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
([Microsoft.Win32.RegistryValueKind]::QWord) {
|
||||
try {
|
||||
[void][uint64]$Data
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
default {
|
||||
# String/MultiString/Binary/None conversions in Convert-RegistryValueDataFromBackup
|
||||
# cannot throw for arbitrary Data - they stringify, array-map to strings, or fall
|
||||
# back to an empty byte array / null.
|
||||
return $true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,12 @@
|
||||
bundled with the script (Assets/Start/start2.bin).
|
||||
|
||||
.EXAMPLE
|
||||
ReplaceStartMenuForAllUsers
|
||||
Replace-StartMenuForAllUsers
|
||||
|
||||
.EXAMPLE
|
||||
ReplaceStartMenuForAllUsers -startMenuTemplate "C:\CustomLayout.bin"
|
||||
Replace-StartMenuForAllUsers -startMenuTemplate "C:\CustomLayout.bin"
|
||||
#>
|
||||
function ReplaceStartMenuForAllUsers {
|
||||
function Replace-StartMenuForAllUsers {
|
||||
param (
|
||||
[string]$startMenuTemplate = "$script:AssetsPath\Start\start2.bin"
|
||||
)
|
||||
@@ -34,16 +34,16 @@ function ReplaceStartMenuForAllUsers {
|
||||
}
|
||||
|
||||
# Get path to start menu file for all users
|
||||
$userPathString = GetUserDirectory -userName "*" -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState"
|
||||
$userPathString = Get-UserDirectory -userName "*" -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState"
|
||||
$usersStartMenuPaths = Get-ChildItem -Path $userPathString -ErrorAction SilentlyContinue
|
||||
|
||||
# Go through all users and replace the start menu file
|
||||
ForEach ($startMenuPath in $usersStartMenuPaths) {
|
||||
ReplaceStartMenu -startMenuBinFile "$($startMenuPath.Fullname)\start2.bin" -startMenuTemplate $startMenuTemplate
|
||||
Replace-StartMenu -startMenuBinFile "$($startMenuPath.Fullname)\start2.bin" -startMenuTemplate $startMenuTemplate
|
||||
}
|
||||
|
||||
# Also replace the start menu file for the default user profile
|
||||
$defaultStartMenuPath = GetUserDirectory -userName "Default" -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState" -exitIfPathNotFound $false
|
||||
$defaultStartMenuPath = Get-UserDirectory -userName "Default" -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState" -exitIfPathNotFound $false
|
||||
|
||||
if ($script:Params.ContainsKey("WhatIf")) {
|
||||
Write-Host "[WhatIf] Replace Start Menu for Default user profile with template $startMenuTemplate" -ForegroundColor Cyan
|
||||
@@ -57,7 +57,7 @@ function ReplaceStartMenuForAllUsers {
|
||||
}
|
||||
|
||||
# Copy template to default profile
|
||||
ReplaceStartMenu -startMenuBinFile "$($defaultStartMenuPath)\start2.bin" -startMenuTemplate $startMenuTemplate
|
||||
Replace-StartMenu -startMenuBinFile "$($defaultStartMenuPath)\start2.bin" -startMenuTemplate $startMenuTemplate
|
||||
Write-Host "Replaced start menu for the default user profile"
|
||||
Write-Host ""
|
||||
}
|
||||
@@ -83,12 +83,12 @@ function ReplaceStartMenuForAllUsers {
|
||||
bundled with the script (Assets/Start/start2.bin).
|
||||
|
||||
.EXAMPLE
|
||||
ReplaceStartMenu -startMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin"
|
||||
Replace-StartMenu -startMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin"
|
||||
|
||||
.EXAMPLE
|
||||
ReplaceStartMenu -startMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin" -startMenuTemplate "C:\CustomLayout.bin"
|
||||
Replace-StartMenu -startMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin" -startMenuTemplate "C:\CustomLayout.bin"
|
||||
#>
|
||||
function ReplaceStartMenu {
|
||||
function Replace-StartMenu {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string]$startMenuBinFile,
|
||||
@@ -106,7 +106,7 @@ function ReplaceStartMenu {
|
||||
return
|
||||
}
|
||||
|
||||
$userName = GetStartMenuUserNameFromPath -StartMenuBinFile $startMenuBinFile
|
||||
$userName = Get-StartMenuUserNameFromPath -StartMenuBinFile $startMenuBinFile
|
||||
|
||||
if ($script:Params.ContainsKey("WhatIf")) {
|
||||
Write-Host "[WhatIf] Replace Start Menu for user $userName with template $startMenuTemplate" -ForegroundColor Cyan
|
||||
@@ -147,12 +147,12 @@ function ReplaceStartMenu {
|
||||
The target username. Pass an empty string or omit to resolve for the current user.
|
||||
|
||||
.EXAMPLE
|
||||
GetStartMenuBinPathForUser -UserName "Jeff"
|
||||
Get-StartMenuBinPathForUser -UserName "Jeff"
|
||||
|
||||
.EXAMPLE
|
||||
GetStartMenuBinPathForUser -UserName "Default"
|
||||
Get-StartMenuBinPathForUser -UserName "Default"
|
||||
#>
|
||||
function GetStartMenuBinPathForUser {
|
||||
function Get-StartMenuBinPathForUser {
|
||||
param(
|
||||
[string]$UserName
|
||||
)
|
||||
@@ -161,7 +161,7 @@ function GetStartMenuBinPathForUser {
|
||||
return "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin"
|
||||
}
|
||||
|
||||
return (GetUserDirectory -userName $UserName -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin" -exitIfPathNotFound $false)
|
||||
return (Get-UserDirectory -userName $UserName -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin" -exitIfPathNotFound $false)
|
||||
}
|
||||
|
||||
<#
|
||||
@@ -177,9 +177,9 @@ function GetStartMenuBinPathForUser {
|
||||
The full path to a start2.bin file.
|
||||
|
||||
.EXAMPLE
|
||||
GetStartMenuUserNameFromPath -StartMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin"
|
||||
Get-StartMenuUserNameFromPath -StartMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin"
|
||||
#>
|
||||
function GetStartMenuUserNameFromPath {
|
||||
function Get-StartMenuUserNameFromPath {
|
||||
param(
|
||||
[string]$StartMenuBinFile
|
||||
)
|
||||
@@ -230,7 +230,7 @@ function Get-StartMenuBackupPath {
|
||||
return $null
|
||||
}
|
||||
else {
|
||||
$userPathString = GetUserDirectory -userName "*" -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState"
|
||||
$userPathString = Get-UserDirectory -userName "*" -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState"
|
||||
$usersStartMenuPaths = Get-ChildItem -Path $userPathString -ErrorAction SilentlyContinue
|
||||
foreach ($startMenuPath in $usersStartMenuPaths) {
|
||||
$latestBackup = Get-ChildItem -Path (Join-Path $startMenuPath.FullName 'Win11Debloat-StartBackup-*.bak') -ErrorAction SilentlyContinue |
|
||||
@@ -261,19 +261,19 @@ function Get-StartMenuBackupPath {
|
||||
finds the latest Win11Debloat-StartBackup-*.bak file.
|
||||
|
||||
.EXAMPLE
|
||||
RestoreStartMenuFromBackup -StartMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin"
|
||||
Restore-StartMenuFromBackup -StartMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin"
|
||||
|
||||
.EXAMPLE
|
||||
RestoreStartMenuFromBackup -StartMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin" -BackupFilePath "C:\Backups\Win11Debloat-StartBackup-20260101_120000.bak"
|
||||
Restore-StartMenuFromBackup -StartMenuBinFile "$env:LOCALAPPDATA\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState\start2.bin" -BackupFilePath "C:\Backups\Win11Debloat-StartBackup-20260101_120000.bak"
|
||||
#>
|
||||
function RestoreStartMenuFromBackup {
|
||||
function Restore-StartMenuFromBackup {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$StartMenuBinFile,
|
||||
[string]$BackupFilePath
|
||||
)
|
||||
|
||||
$userName = GetStartMenuUserNameFromPath -StartMenuBinFile $StartMenuBinFile
|
||||
$userName = Get-StartMenuUserNameFromPath -StartMenuBinFile $StartMenuBinFile
|
||||
$backupBinFile = if ([string]::IsNullOrWhiteSpace($BackupFilePath)) {
|
||||
# Auto-detect latest backup in the same folder as the start2.bin
|
||||
$startMenuDir = Split-Path $StartMenuBinFile -Parent
|
||||
@@ -342,19 +342,19 @@ function RestoreStartMenuFromBackup {
|
||||
|
||||
.DESCRIPTION
|
||||
Resolves the start2.bin path for the currently logged-in user, then
|
||||
delegates to RestoreStartMenuFromBackup.
|
||||
delegates to Restore-StartMenuFromBackup.
|
||||
|
||||
.PARAMETER BackupFilePath
|
||||
Path to the backup file to restore from. If omitted, automatically
|
||||
finds the latest Win11Debloat-StartBackup-*.bak file.
|
||||
|
||||
.EXAMPLE
|
||||
RestoreStartMenu
|
||||
Restore-StartMenu
|
||||
|
||||
.EXAMPLE
|
||||
RestoreStartMenu -BackupFilePath "C:\Backups\Win11Debloat-StartBackup-20260101_120000.bak"
|
||||
Restore-StartMenu -BackupFilePath "C:\Backups\Win11Debloat-StartBackup-20260101_120000.bak"
|
||||
#>
|
||||
function RestoreStartMenu {
|
||||
function Restore-StartMenu {
|
||||
param(
|
||||
[string]$BackupFilePath
|
||||
)
|
||||
@@ -364,7 +364,7 @@ function RestoreStartMenu {
|
||||
|
||||
Write-Host "Restoring start menu for user $targetUserName from backup..."
|
||||
|
||||
return RestoreStartMenuFromBackup -StartMenuBinFile $startMenuBinFile -BackupFilePath $BackupFilePath
|
||||
return Restore-StartMenuFromBackup -StartMenuBinFile $startMenuBinFile -BackupFilePath $BackupFilePath
|
||||
}
|
||||
|
||||
<#
|
||||
@@ -384,17 +384,17 @@ function RestoreStartMenu {
|
||||
LocalState folder.
|
||||
|
||||
.EXAMPLE
|
||||
RestoreStartMenuForAllUsers
|
||||
Restore-StartMenuForAllUsers
|
||||
|
||||
.EXAMPLE
|
||||
RestoreStartMenuForAllUsers -BackupFilePath "C:\Backups\Win11Debloat-StartBackup-20260101_120000.bak"
|
||||
Restore-StartMenuForAllUsers -BackupFilePath "C:\Backups\Win11Debloat-StartBackup-20260101_120000.bak"
|
||||
#>
|
||||
function RestoreStartMenuForAllUsers {
|
||||
function Restore-StartMenuForAllUsers {
|
||||
param(
|
||||
[string]$BackupFilePath
|
||||
)
|
||||
|
||||
$userPathString = GetUserDirectory -userName "*" -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState"
|
||||
$userPathString = Get-UserDirectory -userName "*" -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState"
|
||||
$usersStartMenuPaths = Get-ChildItem -Path $userPathString -ErrorAction SilentlyContinue
|
||||
$results = @()
|
||||
|
||||
@@ -402,10 +402,10 @@ function RestoreStartMenuForAllUsers {
|
||||
|
||||
foreach ($startMenuPath in $usersStartMenuPaths) {
|
||||
$startMenuBinFile = Join-Path $startMenuPath.FullName 'start2.bin'
|
||||
$results += RestoreStartMenuFromBackup -StartMenuBinFile $startMenuBinFile -BackupFilePath $BackupFilePath
|
||||
$results += Restore-StartMenuFromBackup -StartMenuBinFile $startMenuBinFile -BackupFilePath $BackupFilePath
|
||||
}
|
||||
|
||||
$defaultStartMenuPath = GetUserDirectory -userName "Default" -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState" -exitIfPathNotFound $false
|
||||
$defaultStartMenuPath = Get-UserDirectory -userName "Default" -fileName "AppData\Local\Packages\Microsoft.Windows.StartMenuExperienceHost_cw5n1h2txyewy\LocalState" -exitIfPathNotFound $false
|
||||
|
||||
if (Test-Path $defaultStartMenuPath) {
|
||||
$defaultStartMenuBinFile = Join-Path $defaultStartMenuPath 'start2.bin'
|
||||
+71
-31
@@ -1,3 +1,16 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Runs a script block against the registry hive for a backup target.
|
||||
|
||||
.PARAMETER Target
|
||||
A supported backup target: DefaultUserProfile or User:<user name>.
|
||||
|
||||
.PARAMETER ScriptBlock
|
||||
The operation to run after the target user hive is available.
|
||||
|
||||
.PARAMETER ArgumentObject
|
||||
Optional object passed to the script block.
|
||||
#>
|
||||
function Invoke-WithLoadedRestoreHive {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -24,6 +37,13 @@ function Invoke-WithLoadedRestoreHive {
|
||||
Invoke-WithTargetUserHive -TargetUserName $targetUserName -ScriptBlock $ScriptBlock -ArgumentObject $ArgumentObject
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Restores a registry key and its child keys from a backup snapshot.
|
||||
|
||||
.PARAMETER Snapshot
|
||||
The saved registry-key state, including existence, values, and subkeys.
|
||||
#>
|
||||
function Restore-RegistryKeySnapshot {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -74,10 +94,20 @@ function Restore-RegistryKeySnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Restores or removes a registry value from a backup snapshot.
|
||||
|
||||
.PARAMETER RegistryKey
|
||||
The open registry key that contains the value.
|
||||
|
||||
.PARAMETER Snapshot
|
||||
The saved registry-value state to apply.
|
||||
#>
|
||||
function Restore-RegistryValueSnapshot {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Microsoft.Win32.RegistryKey]$RegistryKey,
|
||||
$RegistryKey,
|
||||
[Parameter(Mandatory)]
|
||||
$Snapshot
|
||||
)
|
||||
@@ -101,21 +131,20 @@ function Restore-RegistryValueSnapshot {
|
||||
$RegistryKey.SetValue($valueName, $normalizedData, $valueKind)
|
||||
}
|
||||
catch {
|
||||
$retryBytes = Convert-BackupDataToByteArray -Data $Snapshot.Data
|
||||
if ($null -ne $retryBytes) {
|
||||
try {
|
||||
$RegistryKey.SetValue($valueName, $retryBytes, [Microsoft.Win32.RegistryValueKind]::Binary)
|
||||
return
|
||||
}
|
||||
catch {
|
||||
# Fall through to original error message for context.
|
||||
}
|
||||
}
|
||||
|
||||
throw "Failed setting registry value '$valueName' in '$($RegistryKey.Name)': $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Converts a backed-up registry value-kind name to its .NET enum value.
|
||||
|
||||
.PARAMETER KindName
|
||||
The registry value-kind name stored in the backup.
|
||||
|
||||
.OUTPUTS
|
||||
Microsoft.Win32.RegistryValueKind
|
||||
#>
|
||||
function Convert-RegistryValueKindFromBackup {
|
||||
param(
|
||||
[string]$KindName
|
||||
@@ -133,6 +162,16 @@ function Convert-RegistryValueKindFromBackup {
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Converts backed-up data to a value suitable for registry restoration.
|
||||
|
||||
.PARAMETER Kind
|
||||
The registry value kind that determines how the data is converted.
|
||||
|
||||
.PARAMETER Data
|
||||
The serialized value data from the backup.
|
||||
#>
|
||||
function Convert-RegistryValueDataFromBackup {
|
||||
param(
|
||||
[Microsoft.Win32.RegistryValueKind]$Kind,
|
||||
@@ -148,15 +187,20 @@ function Convert-RegistryValueDataFromBackup {
|
||||
$unsigned = [uint64]$Data
|
||||
return [BitConverter]::ToInt64([BitConverter]::GetBytes($unsigned), 0)
|
||||
}
|
||||
([Microsoft.Win32.RegistryValueKind]::MultiString) { return @($Data | ForEach-Object { [string]$_ }) }
|
||||
([Microsoft.Win32.RegistryValueKind]::MultiString) { return ,([string[]]@($Data | ForEach-Object { [string]$_ })) }
|
||||
([Microsoft.Win32.RegistryValueKind]::Binary) {
|
||||
if ($null -eq $Data) {
|
||||
return ,(New-Object byte[] 0)
|
||||
}
|
||||
|
||||
$bytes = Convert-BackupDataToByteArray -Data $Data
|
||||
if ($null -eq $bytes) {
|
||||
return (New-Object byte[] 0)
|
||||
throw 'Invalid binary registry data in backup. Expected byte values from 0 through 255.'
|
||||
}
|
||||
return $bytes
|
||||
# Keep the byte array intact instead of writing each byte to the
|
||||
# pipeline. RegistryKey.SetValue requires a byte[] for Binary.
|
||||
return ,$bytes
|
||||
}
|
||||
([Microsoft.Win32.RegistryValueKind]::None) { return $null }
|
||||
default {
|
||||
if ($null -ne $Data) {
|
||||
return [string]$Data
|
||||
@@ -167,6 +211,17 @@ function Convert-RegistryValueDataFromBackup {
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Converts serialized binary backup data to a byte array.
|
||||
|
||||
.PARAMETER Data
|
||||
A byte array or collection of integer byte values from the backup.
|
||||
|
||||
.OUTPUTS
|
||||
System.Byte[]
|
||||
Returns $null when the input contains invalid byte data.
|
||||
#>
|
||||
function Convert-BackupDataToByteArray {
|
||||
param(
|
||||
$Data
|
||||
@@ -207,18 +262,3 @@ function Convert-BackupDataToByteArray {
|
||||
|
||||
return ,$bytes
|
||||
}
|
||||
|
||||
function Remove-RegistrySubKeyTreeIfExists {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Microsoft.Win32.RegistryKey]$RootKey,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$SubKeyPath
|
||||
)
|
||||
|
||||
$existing = $RootKey.OpenSubKey($SubKeyPath, $false)
|
||||
if ($existing) {
|
||||
$existing.Close()
|
||||
$RootKey.DeleteSubKeyTree($SubKeyPath, $false)
|
||||
}
|
||||
}
|
||||
+10
-7
@@ -12,9 +12,9 @@
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject
|
||||
A normalized registry backup object produced by Normalize-RegistryBackup.
|
||||
A normalized registry backup object produced by ConvertTo-NormalizedRegistryBackup.
|
||||
#>
|
||||
function Load-RegistryBackupFromFile {
|
||||
function Import-RegistryBackup {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$FilePath
|
||||
@@ -31,7 +31,7 @@ function Load-RegistryBackupFromFile {
|
||||
throw "Failed to read backup file '$FilePath'. The file is not valid JSON."
|
||||
}
|
||||
|
||||
return Normalize-RegistryBackup -Backup $rawBackup
|
||||
return ConvertTo-NormalizedRegistryBackup -Backup $rawBackup
|
||||
}
|
||||
|
||||
<#
|
||||
@@ -52,7 +52,7 @@ function Load-RegistryBackupFromFile {
|
||||
ComputerName, Target, SelectedFeatures, SelectedUndoFeatures, and
|
||||
RegistryKeys properties.
|
||||
#>
|
||||
function Normalize-RegistryBackup {
|
||||
function ConvertTo-NormalizedRegistryBackup {
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
$Backup
|
||||
@@ -93,7 +93,10 @@ function Normalize-RegistryBackup {
|
||||
}
|
||||
elseif ($normalizedTarget -like 'CurrentUser:*') {
|
||||
$targetCurrentUserName = $normalizedTarget.Substring(12)
|
||||
if ([string]::IsNullOrWhiteSpace($targetCurrentUserName) -or
|
||||
if (Test-RunningAsSystem) {
|
||||
$errors.Add("Backup was made for '$targetCurrentUserName' and is user-scoped. Re-run as that user; SYSTEM cannot restore a CurrentUser backup.")
|
||||
}
|
||||
elseif ([string]::IsNullOrWhiteSpace($targetCurrentUserName) -or
|
||||
-not (Test-UserNameMatch -UserNameA $targetCurrentUserName -UserNameB $env:USERNAME)) {
|
||||
$errors.Add("Backup was made for '$targetCurrentUserName', this does not match current user '$env:USERNAME'.")
|
||||
}
|
||||
@@ -176,7 +179,7 @@ function Normalize-RegistryBackup {
|
||||
registry, loading the appropriate user hive when required.
|
||||
|
||||
.PARAMETER Backup
|
||||
A normalized backup object (as produced by Normalize-RegistryBackup) whose
|
||||
A normalized backup object (as produced by ConvertTo-NormalizedRegistryBackup) whose
|
||||
RegistryKeys snapshots should be restored.
|
||||
|
||||
.OUTPUTS
|
||||
@@ -190,7 +193,7 @@ function Restore-RegistryBackupState {
|
||||
$Backup
|
||||
)
|
||||
|
||||
$friendlyTarget = GetFriendlyRegistryBackupTarget -Target ([string]$Backup.Target)
|
||||
$friendlyTarget = Get-FriendlyRegistryBackupTarget -Target ([string]$Backup.Target)
|
||||
|
||||
if ($script:Params.ContainsKey("WhatIf")) {
|
||||
Write-Host "[WhatIf] Restore registry backup for $friendlyTarget" -ForegroundColor Cyan
|
||||
+19
-19
@@ -11,20 +11,20 @@
|
||||
.EXAMPLE
|
||||
DisableStoreSearchSuggestionsForAllUsers
|
||||
#>
|
||||
function DisableStoreSearchSuggestionsForAllUsers {
|
||||
function Set-StoreSearchSuggestionsDisabledForAllUsers {
|
||||
# Get path to Store app database for all users
|
||||
$userPathString = GetUserDirectory -userName "*" -fileName "AppData\Local\Packages"
|
||||
$userPathString = Get-UserDirectory -userName "*" -fileName "AppData\Local\Packages"
|
||||
$usersStoreDbPaths = Get-ChildItem -Path $userPathString -ErrorAction SilentlyContinue
|
||||
|
||||
# Go through all users and disable start search suggestions
|
||||
foreach ($storeDbPath in $usersStoreDbPaths) {
|
||||
DisableStoreSearchSuggestions -StoreAppsDatabase ($storeDbPath.FullName + "\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db")
|
||||
Set-StoreSearchSuggestionsDisabled -StoreAppsDatabase ($storeDbPath.FullName + "\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db")
|
||||
}
|
||||
|
||||
# Also disable start search suggestions for the default user profile
|
||||
$defaultStoreDbPath = GetStoreAppsDatabasePathForUser -UserName "Default"
|
||||
$defaultStoreDbPath = Get-StoreAppsDatabasePathForUser -UserName "Default"
|
||||
if ($defaultStoreDbPath) {
|
||||
DisableStoreSearchSuggestions -StoreAppsDatabase $defaultStoreDbPath
|
||||
Set-StoreSearchSuggestionsDisabled -StoreAppsDatabase $defaultStoreDbPath
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ function DisableStoreSearchSuggestionsForAllUsers {
|
||||
.EXAMPLE
|
||||
DisableStoreSearchSuggestions -StoreAppsDatabase "$env:LOCALAPPDATA\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db"
|
||||
#>
|
||||
function DisableStoreSearchSuggestions {
|
||||
function Set-StoreSearchSuggestionsDisabled {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string]$StoreAppsDatabase
|
||||
@@ -95,20 +95,20 @@ function DisableStoreSearchSuggestions {
|
||||
.EXAMPLE
|
||||
EnableStoreSearchSuggestionsForAllUsers
|
||||
#>
|
||||
function EnableStoreSearchSuggestionsForAllUsers {
|
||||
function Set-StoreSearchSuggestionsEnabledForAllUsers {
|
||||
# Get path to Store app database for all users
|
||||
$userPathString = GetUserDirectory -userName "*" -fileName "AppData\Local\Packages"
|
||||
$userPathString = Get-UserDirectory -userName "*" -fileName "AppData\Local\Packages"
|
||||
$usersStoreDbPaths = Get-ChildItem -Path $userPathString -ErrorAction SilentlyContinue
|
||||
|
||||
# Go through all users and re-enable start search suggestions
|
||||
foreach ($storeDbPath in $usersStoreDbPaths) {
|
||||
EnableStoreSearchSuggestions -StoreAppsDatabase ($storeDbPath.FullName + "\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db")
|
||||
Set-StoreSearchSuggestionsEnabled -StoreAppsDatabase ($storeDbPath.FullName + "\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db")
|
||||
}
|
||||
|
||||
# Also re-enable for the default user profile
|
||||
$defaultStoreDbPath = GetStoreAppsDatabasePathForUser -UserName "Default"
|
||||
$defaultStoreDbPath = Get-StoreAppsDatabasePathForUser -UserName "Default"
|
||||
if ($defaultStoreDbPath) {
|
||||
EnableStoreSearchSuggestions -StoreAppsDatabase $defaultStoreDbPath
|
||||
Set-StoreSearchSuggestionsEnabled -StoreAppsDatabase $defaultStoreDbPath
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ function EnableStoreSearchSuggestionsForAllUsers {
|
||||
.EXAMPLE
|
||||
EnableStoreSearchSuggestions -StoreAppsDatabase "$env:LOCALAPPDATA\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db"
|
||||
#>
|
||||
function EnableStoreSearchSuggestions {
|
||||
function Set-StoreSearchSuggestionsEnabled {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[string]$StoreAppsDatabase
|
||||
@@ -201,12 +201,12 @@ function EnableStoreSearchSuggestions {
|
||||
The target username. Pass an empty string or omit to resolve for the current user.
|
||||
|
||||
.EXAMPLE
|
||||
GetStoreAppsDatabasePathForUser -UserName "Jeff"
|
||||
Get-StoreAppsDatabasePathForUser -UserName "Jeff"
|
||||
|
||||
.EXAMPLE
|
||||
GetStoreAppsDatabasePathForUser -UserName "Default"
|
||||
Get-StoreAppsDatabasePathForUser -UserName "Default"
|
||||
#>
|
||||
function GetStoreAppsDatabasePathForUser {
|
||||
function Get-StoreAppsDatabasePathForUser {
|
||||
param(
|
||||
[string]$UserName
|
||||
)
|
||||
@@ -215,7 +215,7 @@ function GetStoreAppsDatabasePathForUser {
|
||||
return "$env:LOCALAPPDATA\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db"
|
||||
}
|
||||
|
||||
return (GetUserDirectory -userName $UserName -fileName "AppData\Local\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db" -exitIfPathNotFound $false)
|
||||
return (Get-UserDirectory -userName $UserName -fileName "AppData\Local\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db" -exitIfPathNotFound $false)
|
||||
}
|
||||
|
||||
<#
|
||||
@@ -287,13 +287,13 @@ function Test-StoreSearchSuggestionsDisabled {
|
||||
function Test-StoreSearchSuggestionsDisabledForAllUsers {
|
||||
$paths = @()
|
||||
|
||||
$userPathString = GetUserDirectory -userName "*" -fileName "AppData\Local\Packages"
|
||||
$userPathString = Get-UserDirectory -userName "*" -fileName "AppData\Local\Packages"
|
||||
$usersStoreDbPaths = Get-ChildItem -Path $userPathString -ErrorAction SilentlyContinue
|
||||
foreach ($storeDbPath in $usersStoreDbPaths) {
|
||||
$paths += ($storeDbPath.FullName + "\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalState\store.db")
|
||||
}
|
||||
|
||||
$defaultStoreDbPath = GetStoreAppsDatabasePathForUser -UserName "Default"
|
||||
$defaultStoreDbPath = Get-StoreAppsDatabasePathForUser -UserName "Default"
|
||||
if ($defaultStoreDbPath) {
|
||||
$paths += $defaultStoreDbPath
|
||||
}
|
||||
@@ -309,4 +309,4 @@ function Test-StoreSearchSuggestionsDisabledForAllUsers {
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
# Enables a Windows optional feature and pipes its output to the console
|
||||
function EnableWindowsFeature {
|
||||
function Enable-WindowsFeature {
|
||||
param (
|
||||
[string]$FeatureName
|
||||
)
|
||||
@@ -22,7 +22,7 @@ function EnableWindowsFeature {
|
||||
}
|
||||
|
||||
# Disables a Windows optional feature and pipes its output to the console
|
||||
function DisableWindowsFeature {
|
||||
function Disable-WindowsFeature {
|
||||
param (
|
||||
[string]$FeatureName
|
||||
)
|
||||
Reference in New Issue
Block a user