release: v2.2.3 - Fix Restore Mode module selection crash

CHANGELOG:
- Fixed: Restore Mode manual module selection crash (Critical)
- Root cause: .Split(',', ';', ' ') triggered wrong .NET overload
- Fix: Replaced with native PowerShell -split '[,; ]' operator
- Reported by: KatCat2

VERSION BUMP: 2.2.2 -> 2.2.3
- Updated 48 files with new version number
- CHANGELOG.md: Added v2.2.3 release notes
- README.md: Updated badge, module table, project status
This commit is contained in:
NexusOne23 2026-01-07 18:46:14 +01:00
parent 8435dbe97b
commit da9f937ee8
48 changed files with 628 additions and 589 deletions

View file

@ -8,7 +8,7 @@
.NOTES
Author: NexusOne23
Version: 2.2.2
Version: 2.2.3
Requires: PowerShell 5.1+
#>
@ -79,104 +79,104 @@ function New-DefaultConfig {
)
$defaultConfig = @{
version = "2.2.2"
version = "2.2.3"
modules = @{
SecurityBaseline = @{
enabled = $true
priority = 1
status = "IMPLEMENTED"
enabled = $true
priority = 1
status = "IMPLEMENTED"
bitLockerUSBEnforcement = $false
}
ASR = @{
enabled = $true
priority = 2
status = "IMPLEMENTED"
usesManagementTools = $false
allowNewSoftware = $false
ASR = @{
enabled = $true
priority = 2
status = "IMPLEMENTED"
usesManagementTools = $false
allowNewSoftware = $false
continueWithoutCloud = $true
}
DNS = @{
enabled = $true
DNS = @{
enabled = $true
priority = 3
status = "IMPLEMENTED"
status = "IMPLEMENTED"
provider = "Quad9"
dohMode = "REQUIRE"
dohMode = "REQUIRE"
}
Privacy = @{
enabled = $true
priority = 4
status = "IMPLEMENTED"
mode = "MSRecommended"
Privacy = @{
enabled = $true
priority = 4
status = "IMPLEMENTED"
mode = "MSRecommended"
disableCloudClipboard = $true
removeBloatware = $true
removeBloatware = $true
}
AntiAI = @{
enabled = $true
priority = 5
status = "IMPLEMENTED"
AntiAI = @{
enabled = $true
priority = 5
status = "IMPLEMENTED"
description = "Disable all Windows 11 AI features (Recall, Copilot, Paint AI, etc.)"
}
EdgeHardening = @{
enabled = $true
priority = 6
status = "IMPLEMENTED"
description = "Microsoft Edge v139 Security Baseline: 24 security policies"
EdgeHardening = @{
enabled = $true
priority = 6
status = "IMPLEMENTED"
description = "Microsoft Edge v139 Security Baseline: 24 security policies"
allowExtensions = $true
version = "2.2.2"
baseline = "Edge v139"
policies = 24
features = @{
smartscreen_enforcement = $true
site_isolation = $true
ssl_error_blocking = $true
extension_blocklist = $true
ie_mode_restrictions = $true
spectre_mitigations = $true
application_encryption = $true
version = "2.2.3"
baseline = "Edge v139"
policies = 24
features = @{
smartscreen_enforcement = $true
site_isolation = $true
ssl_error_blocking = $true
extension_blocklist = $true
ie_mode_restrictions = $true
spectre_mitigations = $true
application_encryption = $true
auth_scheme_restrictions = $true
}
}
AdvancedSecurity = @{
enabled = $true
priority = 7
status = "IMPLEMENTED"
description = "Advanced Security hardening beyond MS Baseline"
securityProfile = "Balanced"
disableRDP = $true
forceAdminShares = $false
disableUPnP = $true
disableWirelessDisplay = $false
enabled = $true
priority = 7
status = "IMPLEMENTED"
description = "Advanced Security hardening beyond MS Baseline"
securityProfile = "Balanced"
disableRDP = $true
forceAdminShares = $false
disableUPnP = $true
disableWirelessDisplay = $false
disableDiscoveryProtocols = $true
disableIPv6 = $false
version = "2.2.2"
policies = 50
features = @{
rdp_hardening = $true
wdigest_protection = $true
admin_shares_disable = $true
risky_ports_closure = $true
risky_services_stop = $true
legacy_tls_disable = $true
wpad_disable = $true
powershell_v2_removal = $true
srp_lnk_protection = $true
windows_update_config = $true
finger_protocol_block = $true
wireless_display_security = $true
disableIPv6 = $false
version = "2.2.3"
policies = 50
features = @{
rdp_hardening = $true
wdigest_protection = $true
admin_shares_disable = $true
risky_ports_closure = $true
risky_services_stop = $true
legacy_tls_disable = $true
wpad_disable = $true
powershell_v2_removal = $true
srp_lnk_protection = $true
windows_update_config = $true
finger_protocol_block = $true
wireless_display_security = $true
discovery_protocols_security = $true
firewall_shields_up = $true
ipv6_disable = $true
firewall_shields_up = $true
ipv6_disable = $true
}
profiles = @("Balanced", "Enterprise", "Maximum")
profiles = @("Balanced", "Enterprise", "Maximum")
}
}
options = @{
dryRun = $false
createBackup = $true
dryRun = $false
createBackup = $true
verboseLogging = $true
autoReboot = $false
autoReboot = $false
nonInteractive = $false
autoConfirm = $false
autoConfirm = $false
}
}
@ -434,7 +434,7 @@ function Get-EnabledModules {
# Check if module is actually implemented
if (Test-ModuleAvailability -ModuleName $moduleName) {
$enabledModules += [PSCustomObject]@{
Name = $moduleName
Name = $moduleName
Priority = $moduleConfig.priority
}
}

View file

@ -8,7 +8,7 @@
.NOTES
Author: NexusOne23
Version: 2.2.2
Version: 2.2.3
Requires: PowerShell 5.1+
.EXAMPLE
@ -24,7 +24,7 @@
# All configuration comes from config.json via Initialize-Config.
# Script-level variables
$script:FrameworkVersion = "2.2.2"
$script:FrameworkVersion = "2.2.3"
$script:FrameworkRoot = Split-Path -Parent $PSScriptRoot
$script:ExecutionStartTime = Get-Date
@ -409,8 +409,8 @@ function Invoke-Hardening {
else {
# CLI mode: Auto-detect session type based on module count
$autoSessionType = if ($modulesToExecute.Count -ge 7) { "wizard" }
elseif ($modulesToExecute.Count -eq 1) { "advanced" }
else { "manual" }
elseif ($modulesToExecute.Count -eq 1) { "advanced" }
else { "manual" }
Set-SessionType -SessionType $autoSessionType
Write-Log -Level DEBUG -Message "Session type auto-detected: $autoSessionType (based on $($modulesToExecute.Count) modules)" -Module "Framework"
}
@ -451,7 +451,7 @@ function Invoke-Hardening {
$ruleCount = $ruleIds.Count
$preFrameworkSnapshot = @{
ASR = @{
ASR = @{
RuleIds = $ruleIds
RuleActions = $ruleActions
SnapshotDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

View file

@ -8,7 +8,7 @@
.NOTES
Author: NexusOne23
Version: 2.2.2
Version: 2.2.3
Requires: PowerShell 5.1+
#>
@ -26,10 +26,10 @@ enum LogLevel {
# NOTE: Must use Get-Variable to check existence (direct access fails in Strict Mode)
if (-not (Get-Variable -Name 'LoggerConfig' -Scope Global -ErrorAction SilentlyContinue)) {
$global:LoggerConfig = @{
LogFilePath = ""
MinimumLevel = [LogLevel]::INFO
EnableConsole = $true
EnableFile = $true
LogFilePath = ""
MinimumLevel = [LogLevel]::INFO
EnableConsole = $true
EnableFile = $true
TimestampFormat = "yyyy-MM-dd HH:mm:ss"
}
}
@ -193,10 +193,10 @@ function Write-Log {
# Write to console with color coding (suppress DEBUG-level on console)
if ($global:LoggerConfig.EnableConsole -and $Level -ge [LogLevel]::INFO) {
$consoleColor = switch ($Level) {
([LogLevel]::DEBUG) { "Gray" }
([LogLevel]::INFO) { "White" }
([LogLevel]::DEBUG) { "Gray" }
([LogLevel]::INFO) { "White" }
([LogLevel]::WARNING) { "Yellow" }
([LogLevel]::ERROR) { "Red" }
([LogLevel]::ERROR) { "Red" }
([LogLevel]::SUCCESS) { "Green" }
default { "White" }
}
@ -245,15 +245,15 @@ function Get-ErrorContext {
)
$context = @{
Message = ""
Exception = ""
Category = ""
Message = ""
Exception = ""
Category = ""
TargetObject = ""
ScriptName = ""
LineNumber = 0
Command = ""
StackTrace = ""
Summary = ""
ScriptName = ""
LineNumber = 0
Command = ""
StackTrace = ""
Summary = ""
}
if ($null -eq $ErrorRecord) {
@ -271,13 +271,15 @@ function Get-ErrorContext {
if ($ErrorRecord.InvocationInfo) {
$context.ScriptName = if ($ErrorRecord.InvocationInfo.ScriptName) {
Split-Path -Leaf $ErrorRecord.InvocationInfo.ScriptName
} else {
}
else {
"N/A"
}
$context.LineNumber = $ErrorRecord.InvocationInfo.ScriptLineNumber
$context.Command = if ($ErrorRecord.InvocationInfo.MyCommand) {
$ErrorRecord.InvocationInfo.MyCommand.Name
} else {
}
else {
"N/A"
}
}

View file

@ -12,7 +12,7 @@
.NOTES
Author: NexusOne23
Version: 2.2.2
Version: 2.2.3
Usage in modules:
1. Call Test-NonInteractiveMode to check if prompts should be skipped
@ -194,7 +194,8 @@ function Write-NonInteractiveDecision {
$message = if ($null -ne $Value) {
"[GUI] $Decision : $Value"
} else {
}
else {
"[GUI] $Decision"
}

View file

@ -8,7 +8,7 @@
.NOTES
Author: NexusOne23
Version: 2.2.2
Version: 2.2.3
Requires: PowerShell 5.1+
#>
@ -64,7 +64,7 @@ function Initialize-BackupSystem {
displayName = "" # Auto-generated based on modules
sessionType = "unknown" # wizard | advanced | manual
timestamp = Get-Date -Format "o"
frameworkVersion = "2.2.2"
frameworkVersion = "2.2.3"
modules = @()
totalItems = 0
restorable = $true
@ -116,13 +116,13 @@ function Update-SessionDisplayName {
# Calculate ACTUAL settings count (not backup items!)
# Each module applies a specific number of settings (Paranoid mode = max):
$settingsPerModule = @{
"SecurityBaseline" = 425 # 335 Registry + 67 Security Template + 23 Audit
"ASR" = 19 # 19 ASR Rules
"DNS" = 5 # 5 DNS Settings
"Privacy" = 78 # 54 Registry (MSRecommended) + 24 Bloatware
"AntiAI" = 32 # 32 Registry Policies (15 features)
"EdgeHardening" = 24 # 24 Edge Policies (22-23 applied depending on extensions)
"AdvancedSecurity" = 50 # 50 Advanced Settings (15 features incl. Discovery Protocols + IPv6)
"SecurityBaseline" = 425 # 335 Registry + 67 Security Template + 23 Audit
"ASR" = 19 # 19 ASR Rules
"DNS" = 5 # 5 DNS Settings
"Privacy" = 78 # 54 Registry (MSRecommended) + 24 Bloatware
"AntiAI" = 32 # 32 Registry Policies (15 features)
"EdgeHardening" = 24 # 24 Edge Policies (22-23 applied depending on extensions)
"AdvancedSecurity" = 50 # 50 Advanced Settings (15 features incl. Discovery Protocols + IPv6)
}
$totalSettings = 0
@ -382,10 +382,10 @@ function Backup-RegistryKey {
try {
$emptyMarker = @{
KeyPath = $KeyPath
KeyPath = $KeyPath
BackupDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
State = "NotExisted"
Message = "Registry key did not exist before hardening - must be deleted during restore"
State = "NotExisted"
Message = "Registry key did not exist before hardening - must be deleted during restore"
} | ConvertTo-Json
$markerFile = Join-Path $backupFolder "$safeBackupName`_EMPTY.json"
@ -935,10 +935,10 @@ function Restore-FromBackup {
try {
# Convert reg.exe path to PowerShell path
$psKeyPath = $keyPathToRestore -replace 'HKEY_LOCAL_MACHINE', 'HKLM:' `
-replace 'HKEY_CURRENT_USER', 'HKCU:' `
-replace 'HKEY_CLASSES_ROOT', 'HKCR:' `
-replace 'HKEY_USERS', 'HKU:' `
-replace 'HKEY_CURRENT_CONFIG', 'HKCC:'
-replace 'HKEY_CURRENT_USER', 'HKCU:' `
-replace 'HKEY_CLASSES_ROOT', 'HKCR:' `
-replace 'HKEY_USERS', 'HKU:' `
-replace 'HKEY_CURRENT_CONFIG', 'HKCC:'
if (Test-Path $psKeyPath) {
Write-Log -Level INFO -Message "Deleting existing protected key: $psKeyPath before re-import." -Module "Rollback"
@ -1106,7 +1106,8 @@ function Invoke-RestoreRebootPrompt {
Write-Host ""
if ($NoReboot) {
Write-Host "[!] NoReboot specified - reboot prompt skipped" -ForegroundColor Yellow
} else {
}
else {
Write-Host "[!] Running in NonInteractive mode - reboot prompt skipped" -ForegroundColor Yellow
}
Write-Host " Please reboot manually to complete the restore." -ForegroundColor Gray
@ -1421,7 +1422,8 @@ function Restore-Session {
Write-RestoreLog -Level INFO -Message "Session Path: $SessionPath"
if ($ModuleNames) {
Write-RestoreLog -Level INFO -Message "Specific Modules: $($ModuleNames -join ', ')"
} else {
}
else {
Write-RestoreLog -Level INFO -Message "Restoring: ALL modules"
}
Write-RestoreLog -Level INFO -Message "========================================"
@ -1849,7 +1851,7 @@ function Restore-Session {
for ($i = 0; $i -lt $preFramework.ASR.RuleIds.Count; $i++) {
if ($preFramework.ASR.RuleActions[$i] -ne 0) {
$asrRulesToRestore += @{
GUID = $preFramework.ASR.RuleIds[$i]
GUID = $preFramework.ASR.RuleIds[$i]
Action = $preFramework.ASR.RuleActions[$i]
}
}
@ -1892,8 +1894,8 @@ function Restore-Session {
$ruleActions = $asrRulesToRestore | ForEach-Object { $_.Action }
Set-MpPreference -AttackSurfaceReductionRules_Ids $ruleIds `
-AttackSurfaceReductionRules_Actions $ruleActions `
-ErrorAction Stop
-AttackSurfaceReductionRules_Actions $ruleActions `
-ErrorAction Stop
$sourceDesc = if ($usePreFramework) { "PreFramework snapshot (TRUE pre-hardening)" } else { "ASR_ActiveConfiguration.json" }
Write-Log -Level SUCCESS -Message "ASR rules restored via Set-MpPreference ($($asrRulesToRestore.Count) active rules from $sourceDesc)" -Module "Rollback"
@ -1961,7 +1963,8 @@ function Restore-Session {
Write-Log -Level SUCCESS -Message "Explorer Advanced settings restored via PowerShell" -Module "Rollback"
}
}
} catch {
}
catch {
Write-Log -Level WARNING -Message "PowerShell-based Explorer restore failed: $($_.Exception.Message)" -Module "Rollback"
}
}
@ -1985,10 +1988,10 @@ function Restore-Session {
}
$regType = switch ($entry.Type) {
"DWord" { "DWord" }
"String" { "String" }
"DWord" { "DWord" }
"String" { "String" }
"MultiString" { "MultiString" }
default { "String" }
default { "String" }
}
$existing = Get-ItemProperty -Path $keyPath -Name $entry.Name -ErrorAction SilentlyContinue
@ -2227,13 +2230,13 @@ function Restore-Session {
}
$regType = switch ($entry.Type) {
"DWord" { "DWord" }
"String" { "String" }
"DWord" { "DWord" }
"String" { "String" }
"MultiString" { "MultiString" }
"ExpandString" { "ExpandString" }
"Binary" { "Binary" }
"QWord" { "QWord" }
default { "String" }
"Binary" { "Binary" }
"QWord" { "QWord" }
default { "String" }
}
New-ItemProperty -Path $entry.Path -Name $entry.Name -Value $entry.Value -PropertyType $regType -Force -ErrorAction Stop | Out-Null
@ -2295,7 +2298,7 @@ function Restore-Session {
"HKCU:\Software\Microsoft\Windows\CurrentVersion\SystemSettings\AccountNotifications",
"HKCU:\Software\Microsoft\Windows\CurrentVersion\UserProfileEngagement",
"HKCU:\SOFTWARE\Microsoft\Personalization\Settings",
# NEW: Input Personalization Settings (v2.2.2 - FIX missing HKCU restore)
# NEW: Input Personalization Settings (v2.2.3 - FIX missing HKCU restore)
"HKCU:\SOFTWARE\Microsoft\InputPersonalization",
"HKCU:\SOFTWARE\Microsoft\InputPersonalization\TrainedDataStore",
"HKCU:\Software\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\appDiagnostics"
@ -2312,7 +2315,8 @@ function Restore-Session {
Remove-ItemProperty -Path $keyPath -Name $prop -ErrorAction SilentlyContinue
}
}
} catch {
}
catch {
Write-Log -Level DEBUG -Message "Could not clear $keyPath : $_" -Module "Rollback"
}
}
@ -2329,12 +2333,12 @@ function Restore-Session {
}
$regType = switch ($entry.Type) {
"DWord" { "DWord" }
"String" { "String" }
"DWord" { "DWord" }
"String" { "String" }
"MultiString" { "MultiString" }
"ExpandString" { "ExpandString" }
"Binary" { "Binary" }
default { "String" }
"Binary" { "Binary" }
default { "String" }
}
New-ItemProperty -Path $entry.Path -Name $entry.Name -Value $entry.Value -PropertyType $regType -Force -ErrorAction Stop | Out-Null
@ -2512,13 +2516,13 @@ function Restore-Session {
}
$regType = switch ($entry.Type) {
"DWord" { "DWord" }
"String" { "String" }
"DWord" { "DWord" }
"String" { "String" }
"MultiString" { "MultiString" }
"ExpandString" { "ExpandString" }
"Binary" { "Binary" }
"QWord" { "QWord" }
default { "String" }
"Binary" { "Binary" }
"QWord" { "QWord" }
default { "String" }
}
New-ItemProperty -Path $entry.Path -Name $entry.Name -Value $entry.Value -PropertyType $regType -Force -ErrorAction Stop | Out-Null
@ -2726,7 +2730,8 @@ function Restore-Session {
Write-Host " All security settings have been reverted to backup state" -ForegroundColor White
Write-Host " Modules restored: $($reversedModules.Count) | Total items: $($manifest.totalItems)" -ForegroundColor Gray
Write-Host ""
} else {
}
else {
Write-Host ""
Write-Host " RESTORE COMPLETED WITH ISSUES " -ForegroundColor Yellow
Write-Host ""

View file

@ -8,7 +8,7 @@
.NOTES
Author: NexusOne23
Version: 2.2.2
Version: 2.2.3
Requires: PowerShell 5.1+
#>
@ -27,9 +27,9 @@ function Test-Prerequisites {
Write-Log -Level INFO -Message "Starting prerequisite validation" -Module "Validator"
$result = [PSCustomObject]@{
Success = $true
Errors = @()
Warnings = @()
Success = $true
Errors = @()
Warnings = @()
SystemInfo = $null
}
@ -133,11 +133,11 @@ function Get-WindowsVersion {
}
return [PSCustomObject]@{
Version = $versionName
BuildNumber = $buildNumber
IsWindows11 = $isWindows11
IsSupported = $isSupported
Edition = $os.Caption
Version = $versionName
BuildNumber = $buildNumber
IsWindows11 = $isWindows11
IsSupported = $isSupported
Edition = $os.Caption
Architecture = $os.OSArchitecture
}
}
@ -200,26 +200,26 @@ function Test-TPMAvailable {
if ($null -eq $tpm) {
return [PSCustomObject]@{
Present = $false
Version = "N/A"
Enabled = $false
Present = $false
Version = "N/A"
Enabled = $false
Activated = $false
}
}
return [PSCustomObject]@{
Present = $tpm.TpmPresent
Version = if ($tpm.ManufacturerVersion) { $tpm.ManufacturerVersion } else { "2.0" }
Enabled = $tpm.TpmEnabled
Present = $tpm.TpmPresent
Version = if ($tpm.ManufacturerVersion) { $tpm.ManufacturerVersion } else { "2.0" }
Enabled = $tpm.TpmEnabled
Activated = $tpm.TpmActivated
}
}
catch {
Write-Log -Level WARNING -Message "Unable to check TPM status: $_" -Module "Validator"
return [PSCustomObject]@{
Present = $false
Version = "Unknown"
Enabled = $false
Present = $false
Version = "Unknown"
Enabled = $false
Activated = $false
}
}
@ -294,14 +294,14 @@ function Get-SystemInfo {
$internet = Test-InternetConnectivity
return [PSCustomObject]@{
OS = $osInfo
TPM = $tpmInfo
SecureBoot = $secureBoot
Virtualization = $virtualization
IsAdministrator = $isAdmin
OS = $osInfo
TPM = $tpmInfo
SecureBoot = $secureBoot
Virtualization = $virtualization
IsAdministrator = $isAdmin
DiskSpaceAvailable = $diskSpace
InternetConnected = $internet
PowerShellVersion = $PSVersionTable.PSVersion.ToString()
InternetConnected = $internet
PowerShellVersion = $PSVersionTable.PSVersion.ToString()
}
}
@ -332,9 +332,9 @@ function Test-DomainJoined {
$result = [PSCustomObject]@{
IsDomainJoined = $isDomainJoined
DomainName = if ($isDomainJoined) { $computerSystem.Domain } else { "N/A" }
Workgroup = if (-not $isDomainJoined) { $computerSystem.Workgroup } else { "N/A" }
UserConfirmed = $false
DomainName = if ($isDomainJoined) { $computerSystem.Domain } else { "N/A" }
Workgroup = if (-not $isDomainJoined) { $computerSystem.Workgroup } else { "N/A" }
UserConfirmed = $false
}
if ($isDomainJoined) {
@ -384,9 +384,9 @@ function Test-DomainJoined {
Write-Log -Level ERROR -Message "Failed to check domain status: $_" -Module "Validator" -Exception $_.Exception
return [PSCustomObject]@{
IsDomainJoined = $false
DomainName = "Error"
Workgroup = "Error"
UserConfirmed = $false
DomainName = "Error"
Workgroup = "Error"
UserConfirmed = $false
}
}
}
@ -416,7 +416,7 @@ function Confirm-SystemBackup {
Write-Log -Level INFO -Message "Backup recommendation: non-interactive confirmation (no prompt shown)" -Module "Validator"
$result = [PSCustomObject]@{
UserConfirmed = $true
UserConfirmed = $true
BackupRecommended = $true
}