mirror of
https://github.com/NexusOne23/noid-privacy.git
synced 2026-02-07 04:01:52 +01:00
Privacy Module: - Fixed 'Applied X settings' to show only registry settings (60/78/86) - Bloatware count no longer added to settings total - Consistent with module prompt (MSRecommended: 60, Strict: 78, Paranoid: 86) DNS Module: - Fixed DoH connectivity test for systems with REQUIRE mode active - Tests HTTPS endpoint (port 443) when classic DNS is blocked - Proper detection of existing DoH configuration Verified: Full Apply/Verify/Restore cycle - 633/633 settings (100%)
927 lines
42 KiB
PowerShell
927 lines
42 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Main orchestration engine for NoID Privacy Framework
|
|
|
|
.DESCRIPTION
|
|
Core framework that orchestrates module execution, manages configuration,
|
|
logging, validation, and rollback functionality.
|
|
|
|
.NOTES
|
|
Author: NexusOne23
|
|
Version: 2.2.0
|
|
Requires: PowerShell 5.1+
|
|
|
|
.EXAMPLE
|
|
.\Framework.ps1 -DryRun
|
|
Run in dry-run mode to preview changes
|
|
|
|
.EXAMPLE
|
|
.\Framework.ps1 -ModulesOnly SecurityBaseline,ASR
|
|
Run only specific modules
|
|
#>
|
|
|
|
# Note: This script is dot-sourced as a library, not called directly with parameters.
|
|
# All configuration comes from config.json via Initialize-Config.
|
|
|
|
# Script-level variables
|
|
$script:FrameworkVersion = "2.2.0"
|
|
$script:FrameworkRoot = Split-Path -Parent $PSScriptRoot
|
|
$script:ExecutionStartTime = Get-Date
|
|
|
|
# Import core and utility modules
|
|
$script:ModulesToLoad = @(
|
|
[PSCustomObject]@{ Path = "Core\Logger.ps1"; Name = "Logger" },
|
|
[PSCustomObject]@{ Path = "Core\Config.ps1"; Name = "Config" },
|
|
[PSCustomObject]@{ Path = "Core\Validator.ps1"; Name = "Validator" },
|
|
[PSCustomObject]@{ Path = "Core\Rollback.ps1"; Name = "Rollback" },
|
|
[PSCustomObject]@{ Path = "Core\NonInteractive.ps1"; Name = "NonInteractive" },
|
|
[PSCustomObject]@{ Path = "Utils\Registry.ps1"; Name = "Registry Utils" },
|
|
[PSCustomObject]@{ Path = "Utils\Service.ps1"; Name = "Service Utils" },
|
|
[PSCustomObject]@{ Path = "Utils\Hardware.ps1"; Name = "Hardware Utils" },
|
|
# NOTE: Utils\GPO.ps1 removed - v2.0 SecurityBaseline is self-contained, no LGPO.exe dependency
|
|
[PSCustomObject]@{ Path = "Utils\Localization.ps1"; Name = "Localization Utils" },
|
|
[PSCustomObject]@{ Path = "Utils\Compatibility.ps1"; Name = "Compatibility Utils" },
|
|
[PSCustomObject]@{ Path = "Utils\Dependencies.ps1"; Name = "Dependencies Utils" }
|
|
)
|
|
|
|
Write-Host "NoID Privacy Framework v$script:FrameworkVersion" -ForegroundColor Cyan
|
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
foreach ($moduleInfo in $script:ModulesToLoad) {
|
|
$modulePath = Join-Path $script:FrameworkRoot $moduleInfo.Path
|
|
|
|
if (Test-Path -Path $modulePath) {
|
|
try {
|
|
. $modulePath
|
|
Write-Host "[OK] Loaded: $($moduleInfo.Name)" -ForegroundColor Green
|
|
}
|
|
catch {
|
|
Write-Host "[ERROR] Failed to load: $($moduleInfo.Name)" -ForegroundColor Red
|
|
Write-Host "Error: $_" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
}
|
|
else {
|
|
Write-Host "[ERROR] Module not found: $($moduleInfo.Name) ($modulePath)" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
function Initialize-Framework {
|
|
<#
|
|
.SYNOPSIS
|
|
Initialize the framework and all subsystems
|
|
#>
|
|
[CmdletBinding()]
|
|
param()
|
|
|
|
Write-Host "Initializing framework..." -ForegroundColor Cyan
|
|
|
|
# Only initialize logger if not already initialized (NoIDPrivacy.ps1 may have already done it)
|
|
if ([string]::IsNullOrEmpty($global:LoggerConfig.LogFilePath)) {
|
|
$logLevel = if ($VerboseLogging) { [LogLevel]::DEBUG } else { [LogLevel]::INFO }
|
|
$logDirectory = Join-Path $script:FrameworkRoot "Logs"
|
|
Initialize-Logger -LogDirectory $logDirectory -MinimumLevel $logLevel
|
|
}
|
|
|
|
Write-Log -Level INFO -Message "NoID Privacy Framework v$script:FrameworkVersion starting" -Module "Framework"
|
|
Write-Log -Level INFO -Message "PowerShell version: $($PSVersionTable.PSVersion)" -Module "Framework"
|
|
|
|
# Load configuration
|
|
if ($ConfigPath) {
|
|
Initialize-Config -ConfigPath $ConfigPath
|
|
}
|
|
else {
|
|
Initialize-Config
|
|
}
|
|
|
|
# Initialize backup system
|
|
if (-not $SkipBackup) {
|
|
Initialize-BackupSystem
|
|
|
|
# Create system restore point
|
|
Write-Host "Creating system restore point..." -ForegroundColor Yellow
|
|
$restorePointCreated = New-SystemRestorePoint
|
|
|
|
if (-not $restorePointCreated) {
|
|
Write-Log -Level WARNING -Message "System restore point creation failed or unavailable" -Module "Framework"
|
|
}
|
|
}
|
|
else {
|
|
Write-Log -Level WARNING -Message "Backup system SKIPPED (not recommended)" -Module "Framework"
|
|
}
|
|
|
|
Write-Host ""
|
|
}
|
|
|
|
function Test-FrameworkPrerequisites {
|
|
<#
|
|
.SYNOPSIS
|
|
Validate all prerequisites before execution
|
|
|
|
.OUTPUTS
|
|
Boolean indicating if prerequisites are met
|
|
#>
|
|
[CmdletBinding()]
|
|
[OutputType([bool])]
|
|
param()
|
|
|
|
Write-Host "Validating system prerequisites..." -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
$prereqsPassed = Test-Prerequisites
|
|
|
|
if (-not $prereqsPassed.Success) {
|
|
Write-Log -Level ERROR -Message "Prerequisite validation failed" -Module "Framework"
|
|
Write-Host ""
|
|
Write-Host "PREREQUISITE CHECK FAILED" -ForegroundColor Red
|
|
Write-Host "Please resolve the issues above before continuing." -ForegroundColor Red
|
|
return $false
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "All basic prerequisite checks passed" -ForegroundColor Green
|
|
Write-Host ""
|
|
|
|
# Check if system is domain-joined (interactive warning only in CLI mode)
|
|
Write-Host "Checking domain status..." -ForegroundColor Cyan
|
|
if (Test-NonInteractiveMode) {
|
|
# GUI mode - just check, don't prompt
|
|
$null = Test-DomainJoined
|
|
}
|
|
else {
|
|
# CLI mode - show interactive warning
|
|
$null = Test-DomainJoined -Interactive
|
|
}
|
|
Write-Host ""
|
|
|
|
# Confirm system backup exists (interactive prompt only in CLI mode)
|
|
Write-Host "Verifying system backup..." -ForegroundColor Cyan
|
|
if (Test-NonInteractiveMode) {
|
|
# GUI mode - auto-confirm (backup is created by engine)
|
|
Write-Host "[GUI] Backup verification: Auto-confirmed" -ForegroundColor Cyan
|
|
$backupStatus = [PSCustomObject]@{ UserConfirmed = $true }
|
|
}
|
|
else {
|
|
# CLI mode - interactive prompt
|
|
$backupStatus = Confirm-SystemBackup
|
|
}
|
|
|
|
if (-not $backupStatus.UserConfirmed) {
|
|
Write-Log -Level ERROR -Message "System backup confirmation failed" -Module "Framework"
|
|
return $false
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "All prerequisite checks completed successfully" -ForegroundColor Green
|
|
Write-Host ""
|
|
|
|
return $true
|
|
}
|
|
|
|
function Get-ModulesToExecute {
|
|
<#
|
|
.SYNOPSIS
|
|
Determine which modules should be executed
|
|
|
|
.OUTPUTS
|
|
Array of module names to execute
|
|
#>
|
|
[CmdletBinding()]
|
|
[OutputType([string[]])]
|
|
param()
|
|
|
|
if ($ModulesOnly -and $ModulesOnly.Count -gt 0) {
|
|
Write-Log -Level INFO -Message "Running specific modules: $($ModulesOnly -join ', ')" -Module "Framework"
|
|
return $ModulesOnly
|
|
}
|
|
else {
|
|
$enabledModules = Get-EnabledModules
|
|
Write-Log -Level INFO -Message "Running all enabled modules: $($enabledModules -join ', ')" -Module "Framework"
|
|
return $enabledModules
|
|
}
|
|
}
|
|
|
|
# NOTE: Invoke-HardeningModule has been removed.
|
|
# Use Invoke-Hardening instead for module execution.
|
|
|
|
function Start-HardeningProcess {
|
|
<#
|
|
.SYNOPSIS
|
|
Main execution entry point
|
|
#>
|
|
[CmdletBinding()]
|
|
param()
|
|
|
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
Write-Host "STARTING HARDENING PROCESS" -ForegroundColor Cyan
|
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
if ($DryRun) {
|
|
Write-Host "MODE: DRY RUN (Preview only)" -ForegroundColor Yellow
|
|
Write-Log -Level WARNING -Message "Running in DRY RUN mode - no changes will be applied" -Module "Framework"
|
|
}
|
|
else {
|
|
Write-Host "MODE: APPLY CHANGES" -ForegroundColor Green
|
|
Write-Log -Level INFO -Message "Running in APPLY mode - changes will be applied" -Module "Framework"
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
# Get modules to execute
|
|
$modulesToRun = Get-ModulesToExecute
|
|
|
|
if ($modulesToRun.Count -eq 0) {
|
|
Write-Log -Level WARNING -Message "No modules enabled for execution" -Module "Framework"
|
|
Write-Host "No modules to execute. Check your configuration." -ForegroundColor Yellow
|
|
return
|
|
}
|
|
|
|
Write-Host "Modules to execute: $($modulesToRun.Count)" -ForegroundColor Cyan
|
|
foreach ($mod in $modulesToRun) {
|
|
Write-Host " - $mod" -ForegroundColor White
|
|
}
|
|
Write-Host ""
|
|
|
|
# Confirmation prompt (unless in dry run or NonInteractive mode)
|
|
if (-not $DryRun -and -not (Test-NonInteractiveMode)) {
|
|
Write-Host "WARNING: This will modify your system settings." -ForegroundColor Yellow
|
|
Write-Host "A backup and restore point have been created." -ForegroundColor Yellow
|
|
Write-Host ""
|
|
|
|
do {
|
|
$confirmation = Read-Host "Do you want to continue? [Y/N] (default: Y)"
|
|
if ([string]::IsNullOrWhiteSpace($confirmation)) { $confirmation = "Y" }
|
|
$confirmation = $confirmation.ToUpper()
|
|
|
|
if ($confirmation -notin @('Y', 'N')) {
|
|
Write-Host ""
|
|
Write-Host "Invalid input. Please enter Y or N." -ForegroundColor Red
|
|
Write-Host ""
|
|
}
|
|
} while ($confirmation -notin @('Y', 'N'))
|
|
|
|
if ($confirmation -ne "Y") {
|
|
Write-Log -Level INFO -Message "Execution cancelled by user" -Module "Framework"
|
|
Write-Host ""
|
|
Write-Host "Execution cancelled." -ForegroundColor Yellow
|
|
Write-Host ""
|
|
return
|
|
}
|
|
|
|
Write-Host ""
|
|
}
|
|
elseif (Test-NonInteractiveMode) {
|
|
Write-NonInteractiveDecision -Module "Framework" -Decision "Auto-confirming execution (GUI mode)"
|
|
}
|
|
|
|
# Execute modules using Invoke-Hardening
|
|
Write-Log -Level INFO -Message "Starting module execution: $($modulesToRun -join ', ')" -Module "Framework"
|
|
|
|
# Determine correct module parameter based on what user selected
|
|
if ($modulesToRun.Count -eq 1) {
|
|
$hardeningResult = Invoke-Hardening -Module $modulesToRun[0] -DryRun:$DryRun
|
|
}
|
|
else {
|
|
$hardeningResult = Invoke-Hardening -Module "All" -DryRun:$DryRun
|
|
}
|
|
|
|
# Summary (correctly use hardeningResult.ModuleResults)
|
|
Write-Host ""
|
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
Write-Host "EXECUTION SUMMARY" -ForegroundColor Cyan
|
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
$totalDuration = (Get-Date) - $script:ExecutionStartTime
|
|
|
|
# Correct calculation from ModuleResults
|
|
$totalModules = $hardeningResult.ModulesExecuted
|
|
$successCount = ($hardeningResult.ModuleResults | Where-Object { $_.Success }).Count
|
|
$failureCount = $totalModules - $successCount
|
|
|
|
Write-Host "Total modules executed: $totalModules" -ForegroundColor White
|
|
Write-Host "Successful: $successCount" -ForegroundColor Green
|
|
Write-Host "Failed: $failureCount" -ForegroundColor $(if ($failureCount -gt 0) { "Red" } else { "White" })
|
|
Write-Host "Total duration: $([math]::Round($totalDuration.TotalMinutes, 2)) minutes" -ForegroundColor White
|
|
|
|
if ($hardeningResult.Warnings.Count -gt 0) {
|
|
Write-Host "Warnings: $($hardeningResult.Warnings.Count)" -ForegroundColor Yellow
|
|
}
|
|
if ($hardeningResult.Errors.Count -gt 0) {
|
|
Write-Host "Errors: $($hardeningResult.Errors.Count)" -ForegroundColor Red
|
|
}
|
|
|
|
Write-Host ""
|
|
|
|
Write-Log -Level INFO -Message "Hardening process completed" -Module "Framework"
|
|
Write-Log -Level INFO -Message "Log file: $(Get-LogFilePath)" -Module "Framework"
|
|
|
|
Write-Host "Log file: $(Get-LogFilePath)" -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
# Reboot recommendation
|
|
if (-not $DryRun -and $successCount -gt 0) {
|
|
Write-Host "RECOMMENDATION: Restart your computer for all changes to take effect." -ForegroundColor Yellow
|
|
Write-Host ""
|
|
}
|
|
}
|
|
|
|
function Invoke-Hardening {
|
|
<#
|
|
.SYNOPSIS
|
|
Execute hardening module(s)
|
|
|
|
.PARAMETER Module
|
|
Module name to execute (SecurityBaseline, ASR, DNS, etc.) or "All"
|
|
|
|
.PARAMETER DryRun
|
|
Preview changes without applying them
|
|
|
|
.OUTPUTS
|
|
PSCustomObject with execution results
|
|
#>
|
|
[CmdletBinding()]
|
|
[OutputType([PSCustomObject])]
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateSet("SecurityBaseline", "ASR", "DNS", "Privacy", "AntiAI", "EdgeHardening", "AdvancedSecurity", "All")]
|
|
[string]$Module,
|
|
|
|
[Parameter(Mandatory = $false)]
|
|
[switch]$DryRun
|
|
)
|
|
|
|
$startTime = Get-Date
|
|
$results = [PSCustomObject]@{
|
|
Success = $true
|
|
ModulesExecuted = 0
|
|
Duration = $null
|
|
Errors = @()
|
|
Warnings = @()
|
|
ModuleResults = @()
|
|
}
|
|
|
|
# Get module list to execute
|
|
$modulesToExecute = @()
|
|
|
|
if ($Module -eq "All") {
|
|
# Get all enabled modules from config (sorted by priority)
|
|
$modulesToExecute = Get-EnabledModules
|
|
}
|
|
else {
|
|
# Single module - check if it's enabled in config
|
|
$moduleConfig = $script:Config.modules.$Module
|
|
|
|
if ($null -eq $moduleConfig) {
|
|
Write-Log -Level WARNING -Message "Module '$Module' not found in configuration" -Module "Framework"
|
|
$results.Warnings += "Module '$Module' not configured"
|
|
return $results
|
|
}
|
|
|
|
if ($moduleConfig.enabled -eq $false) {
|
|
Write-Log -Level INFO -Message "Module '$Module' is disabled in configuration - skipping" -Module "Framework"
|
|
Write-Host "Module '$Module' is disabled - skipping execution" -ForegroundColor Gray
|
|
return $results
|
|
}
|
|
|
|
$modulesToExecute = @($Module)
|
|
}
|
|
|
|
Write-Log -Level INFO -Message "Executing modules: $($modulesToExecute -join ', ')" -Module "Framework"
|
|
|
|
# Initialize backup system ONCE before all modules
|
|
if (-not $DryRun) {
|
|
try {
|
|
Initialize-BackupSystem
|
|
Write-Log -Level INFO -Message "Backup session initialized for all modules" -Module "Framework"
|
|
|
|
# Set session type from GUI config (for backup identification)
|
|
# SessionType is in options block when sent from GUI
|
|
if ($script:Config.options -and $script:Config.options.PSObject.Properties.Name -contains 'sessionType' -and $script:Config.options.sessionType) {
|
|
Set-SessionType -SessionType $script:Config.options.sessionType
|
|
Write-Log -Level DEBUG -Message "Session type from GUI config: $($script:Config.options.sessionType)" -Module "Framework"
|
|
}
|
|
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" }
|
|
Set-SessionType -SessionType $autoSessionType
|
|
Write-Log -Level DEBUG -Message "Session type auto-detected: $autoSessionType (based on $($modulesToExecute.Count) modules)" -Module "Framework"
|
|
}
|
|
|
|
# Create Pre-Framework Snapshot for ASR Rules (shared resource conflict prevention)
|
|
# Only when: Multi-module apply AND ASR module is in the list
|
|
# Why: SecurityBaseline sets 15 ASR rules, then ASR sets 19 rules
|
|
# We need to capture the ORIGINAL state before ANY module touches ASR
|
|
if ($modulesToExecute.Count -gt 1 -and $modulesToExecute -contains "ASR") {
|
|
Write-Log -Level INFO -Message "Creating Pre-Framework ASR snapshot (multi-module apply with ASR detected)" -Module "Framework"
|
|
|
|
try {
|
|
$sessionPath = $global:BackupBasePath
|
|
$mpPref = Get-MpPreference -ErrorAction SilentlyContinue
|
|
|
|
if (-not $mpPref) {
|
|
Write-Log -Level INFO -Message "Pre-Framework ASR snapshot skipped: Get-MpPreference returned no data (Defender/ASR not available)." -Module "Framework"
|
|
}
|
|
else {
|
|
$hasIdsProp = $mpPref.PSObject.Properties.Match('AttackSurfaceReductionRules_Ids').Count -gt 0
|
|
$hasActionsProp = $mpPref.PSObject.Properties.Match('AttackSurfaceReductionRules_Actions').Count -gt 0
|
|
|
|
if (-not $hasIdsProp -and -not $hasActionsProp) {
|
|
Write-Log -Level INFO -Message "Pre-Framework ASR snapshot skipped: ASR rule properties not present (third-party AV or Defender ASR disabled)." -Module "Framework"
|
|
}
|
|
else {
|
|
$ruleIds = @()
|
|
$ruleActions = @()
|
|
|
|
if ($hasIdsProp -and $mpPref.AttackSurfaceReductionRules_Ids) {
|
|
$ruleIds = @($mpPref.AttackSurfaceReductionRules_Ids)
|
|
}
|
|
|
|
if ($hasActionsProp -and $mpPref.AttackSurfaceReductionRules_Actions) {
|
|
$ruleActions = @($mpPref.AttackSurfaceReductionRules_Actions)
|
|
}
|
|
|
|
$ruleCount = $ruleIds.Count
|
|
|
|
$preFrameworkSnapshot = @{
|
|
ASR = @{
|
|
RuleIds = $ruleIds
|
|
RuleActions = $ruleActions
|
|
SnapshotDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
RuleCount = $ruleCount
|
|
}
|
|
AppliesTo = @("ASR") # Only apply this snapshot if ASR module is being restored
|
|
}
|
|
|
|
$snapshotPath = Join-Path $sessionPath "PreFramework_Snapshot.json"
|
|
$preFrameworkSnapshot | ConvertTo-Json -Depth 10 | Out-File $snapshotPath -Encoding UTF8
|
|
Write-Log -Level SUCCESS -Message "Pre-Framework snapshot saved: $ruleCount ASR rules captured (original system state)" -Module "Framework"
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Write-Log -Level WARNING -Message "Failed to create Pre-Framework snapshot (non-critical): $_" -Module "Framework"
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Write-ErrorLog -Message "Failed to initialize backup system" -Module "Framework" -ErrorRecord $_
|
|
$warnMsg = "Backup system initialization failed - proceeding without automatic backup"
|
|
$results.Warnings += $warnMsg
|
|
}
|
|
}
|
|
|
|
# Execute each module
|
|
foreach ($moduleName in $modulesToExecute) {
|
|
try {
|
|
Write-Log -Level INFO -Message "========================================" -Module "Framework"
|
|
Write-Log -Level INFO -Message "Module: $moduleName" -Module "Framework"
|
|
Write-Log -Level INFO -Message "========================================" -Module "Framework"
|
|
|
|
# Module Confirmation Prompt for interactive CLI runs.
|
|
# Skipped in DryRun and in NonInteractive/GUI mode.
|
|
if (-not $DryRun -and -not (Test-NonInteractiveMode)) {
|
|
Write-Host ""
|
|
Write-Host "========================================" -ForegroundColor Yellow
|
|
Write-Host " MODULE: $moduleName" -ForegroundColor Cyan
|
|
Write-Host "========================================" -ForegroundColor Yellow
|
|
Write-Host ""
|
|
|
|
# Module-specific description
|
|
switch ($moduleName) {
|
|
"SecurityBaseline" {
|
|
Write-Host "Microsoft Security Baseline for Windows 11 25H2" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " > Applies 425 hardening settings:" -ForegroundColor Gray
|
|
Write-Host " - 335 Registry policies (password, firewall, BitLocker)" -ForegroundColor Gray
|
|
Write-Host " - 67 Security template settings (user rights, audit)" -ForegroundColor Gray
|
|
Write-Host " - 23 Advanced audit policies" -ForegroundColor Gray
|
|
Write-Host " - VBS + Credential Guard + Memory Integrity" -ForegroundColor Gray
|
|
Write-Host ""
|
|
Write-Host " Impact: Enterprise-grade security, may break legacy software" -ForegroundColor Yellow
|
|
}
|
|
"ASR" {
|
|
Write-Host "Attack Surface Reduction Rules" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " > Applies 19 Microsoft Defender ASR rules:" -ForegroundColor Gray
|
|
Write-Host " - Block ransomware, exploits, malicious scripts" -ForegroundColor Gray
|
|
Write-Host " - Block credential theft (lsass.exe protection)" -ForegroundColor Gray
|
|
Write-Host " - Block Office macros, email executables" -ForegroundColor Gray
|
|
Write-Host " - Block untrusted USB execution, Safe Mode reboot" -ForegroundColor Gray
|
|
Write-Host ""
|
|
Write-Host " Note: You'll be asked about SCCM/Intune usage" -ForegroundColor Yellow
|
|
}
|
|
"DNS" {
|
|
Write-Host "Secure DNS with DNS-over-HTTPS" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " > Configures encrypted DNS:" -ForegroundColor Gray
|
|
Write-Host " - Choose provider: Quad9 (default), Cloudflare, or AdGuard" -ForegroundColor Gray
|
|
Write-Host " - Enable DoH encryption (HTTPS)" -ForegroundColor Gray
|
|
Write-Host " - Blocks DNS hijacking and snooping" -ForegroundColor Gray
|
|
Write-Host " - IPv4 + IPv6 configuration" -ForegroundColor Gray
|
|
Write-Host ""
|
|
Write-Host " Note: You'll choose provider and DoH mode interactively" -ForegroundColor Yellow
|
|
}
|
|
"Privacy" {
|
|
Write-Host "Telemetry & Privacy Hardening" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " > Applies privacy settings based on selected mode:" -ForegroundColor Gray
|
|
Write-Host " - Telemetry control (3 modes: MSRecommended/Strict/Paranoid)" -ForegroundColor Gray
|
|
Write-Host " - MSRecommended: 60 settings (default, max compatibility)" -ForegroundColor DarkGray
|
|
Write-Host " - Strict: 78 settings, Paranoid: 86 settings" -ForegroundColor DarkGray
|
|
Write-Host " - Disable ads, tips, personalization" -ForegroundColor Gray
|
|
Write-Host " - Remove bloatware (up to 24 apps, if present)" -ForegroundColor Gray
|
|
Write-Host " - OneDrive hardening (keeps sync functional)" -ForegroundColor Gray
|
|
Write-Host ""
|
|
Write-Host " Note: You'll choose privacy mode interactively" -ForegroundColor Yellow
|
|
}
|
|
"AntiAI" {
|
|
Write-Host "Disable Windows 11 AI Features" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " > Disables 15 features via 32 policies:" -ForegroundColor Gray
|
|
Write-Host " - Windows Recall + Export Block" -ForegroundColor Gray
|
|
Write-Host " - Windows Copilot (app + URI handlers + Edge sidebar)" -ForegroundColor Gray
|
|
Write-Host " - Click to Do, Explorer AI Actions" -ForegroundColor Gray
|
|
Write-Host " - Paint AI (3), Notepad AI, Settings Agent" -ForegroundColor Gray
|
|
Write-Host ""
|
|
Write-Host " Impact: All AI features disabled, reboot required" -ForegroundColor Yellow
|
|
}
|
|
"EdgeHardening" {
|
|
Write-Host "Microsoft Edge v139 Security Baseline" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " > Applies Edge security policies:" -ForegroundColor Gray
|
|
Write-Host " - Enhanced Security Mode, SmartScreen + PUA" -ForegroundColor Gray
|
|
Write-Host " - Site Isolation + SSL/TLS hardening" -ForegroundColor Gray
|
|
Write-Host " - Tracking Prevention + Privacy settings" -ForegroundColor Gray
|
|
Write-Host " - Extension blocklist (blocks all by default)" -ForegroundColor Gray
|
|
Write-Host ""
|
|
Write-Host " Impact: Maximum Edge security, extensions blocked by default" -ForegroundColor Yellow
|
|
}
|
|
"AdvancedSecurity" {
|
|
Write-Host "Advanced Security Hardening (Beyond MS Baseline)" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " > Applies 15 security features (50 individual settings):" -ForegroundColor Gray
|
|
Write-Host " - RDP hardening + optional complete disable" -ForegroundColor Gray
|
|
Write-Host " - WDigest credential protection" -ForegroundColor Gray
|
|
Write-Host " - Admin Shares disable (domain-aware)" -ForegroundColor Gray
|
|
Write-Host " - Risky ports/services block (LLMNR, NetBIOS, UPnP)" -ForegroundColor Gray
|
|
Write-Host " - Legacy TLS 1.0/1.1 disable, WPAD disable, PSv2 removal" -ForegroundColor Gray
|
|
Write-Host " - SRP .lnk protection (CVE-2025-9491)" -ForegroundColor Gray
|
|
Write-Host " - Windows Update (3 simple GUI settings)" -ForegroundColor Gray
|
|
Write-Host " - Wireless Display (Miracast) security" -ForegroundColor Gray
|
|
Write-Host ""
|
|
Write-Host " Note: You'll choose profile (Balanced/Enterprise/Maximum)" -ForegroundColor Yellow
|
|
}
|
|
default {
|
|
Write-Host "This module will apply changes to your system." -ForegroundColor White
|
|
Write-Host ""
|
|
}
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "Options:" -ForegroundColor White
|
|
Write-Host " [Y] Yes - Apply this module" -ForegroundColor Green
|
|
Write-Host " [N] No - Skip this module" -ForegroundColor Yellow
|
|
Write-Host " [A] Abort - Stop entire process" -ForegroundColor Red
|
|
Write-Host ""
|
|
|
|
do {
|
|
$response = Read-Host "Continue with ${moduleName}? [Y/N/A] (default: Y)"
|
|
if ([string]::IsNullOrWhiteSpace($response)) { $response = "Y" }
|
|
$response = $response.ToUpper()
|
|
} while ($response -notin @('Y', 'N', 'A', 'YES', 'NO', 'ABORT'))
|
|
|
|
if ($response -in @('A', 'ABORT')) {
|
|
Write-Log -Level WARNING -Message "User aborted execution at module: $moduleName" -Module "Framework"
|
|
Write-Host ""
|
|
Write-Host "Execution aborted by user" -ForegroundColor Red
|
|
Write-Host ""
|
|
$results.Warnings += "Execution aborted by user at module: $moduleName"
|
|
break
|
|
}
|
|
elseif ($response -in @('N', 'NO')) {
|
|
Write-Log -Level INFO -Message "User skipped module: $moduleName" -Module "Framework"
|
|
Write-Host ""
|
|
Write-Host "Skipping module: $moduleName" -ForegroundColor Yellow
|
|
Write-Host ""
|
|
continue
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "Proceeding with $moduleName..." -ForegroundColor Green
|
|
Write-Host ""
|
|
}
|
|
|
|
$moduleResult = $null
|
|
$modulePath = Join-Path $script:FrameworkRoot "Modules\$moduleName"
|
|
|
|
# Check if module exists
|
|
if (-not (Test-Path $modulePath)) {
|
|
$errMsg = "Module not found: $moduleName (Path: $modulePath)"
|
|
Write-Log -Level WARNING -Message $errMsg -Module "Framework"
|
|
$results.Warnings += $errMsg
|
|
continue
|
|
}
|
|
|
|
# Check module implementation status (FIX #2)
|
|
$moduleConfig = $script:Config.modules.$moduleName
|
|
if ($moduleConfig.PSObject.Properties.Name -contains 'status') {
|
|
if ($moduleConfig.status -ne 'IMPLEMENTED') {
|
|
Write-Log -Level WARNING -Message "Skipping module '$moduleName' - Status: $($moduleConfig.status) (not IMPLEMENTED)" -Module "Framework"
|
|
Write-Host " [SKIP] $moduleName - Not yet implemented" -ForegroundColor Yellow
|
|
continue
|
|
}
|
|
}
|
|
|
|
# Load and execute module based on name
|
|
switch ($moduleName) {
|
|
"SecurityBaseline" {
|
|
$manifestPath = Join-Path $modulePath "SecurityBaseline.psd1"
|
|
if (Test-Path $manifestPath) {
|
|
if (-not (Get-Module -Name $moduleName)) {
|
|
Import-Module $manifestPath -ErrorAction Stop
|
|
}
|
|
$moduleResult = Invoke-SecurityBaseline -DryRun:$DryRun
|
|
}
|
|
else {
|
|
throw "Module manifest not found: $manifestPath"
|
|
}
|
|
}
|
|
|
|
"ASR" {
|
|
$manifestPath = Join-Path $modulePath "ASR.psd1"
|
|
if (Test-Path $manifestPath) {
|
|
if (-not (Get-Module -Name $moduleName)) {
|
|
Import-Module $manifestPath -ErrorAction Stop
|
|
}
|
|
$moduleResult = Invoke-ASRRules -DryRun:$DryRun
|
|
}
|
|
else {
|
|
throw "Module manifest not found: $manifestPath"
|
|
}
|
|
}
|
|
|
|
"DNS" {
|
|
$manifestPath = Join-Path $modulePath "DNS.psd1"
|
|
if (Test-Path $manifestPath) {
|
|
if (-not (Get-Module -Name $moduleName)) {
|
|
Import-Module $manifestPath -ErrorAction Stop
|
|
}
|
|
|
|
# DNS module handles provider selection
|
|
# ONLY pass config values in NonInteractive mode (GUI)
|
|
# In interactive mode, let the module prompt the user!
|
|
if (Test-NonInteractiveMode) {
|
|
# GUI mode - use config values
|
|
$moduleResult = Invoke-DNSConfiguration -Provider $script:Config.modules.DNS.provider -DryRun:$DryRun
|
|
}
|
|
else {
|
|
# Interactive CLI mode - module will ask for provider and DoH mode
|
|
$moduleResult = Invoke-DNSConfiguration -DryRun:$DryRun
|
|
}
|
|
|
|
}
|
|
else {
|
|
throw "Module manifest not found: $manifestPath"
|
|
}
|
|
}
|
|
|
|
"Privacy" {
|
|
$manifestPath = Join-Path $modulePath "Privacy.psd1"
|
|
if (Test-Path $manifestPath) {
|
|
if (-not (Get-Module -Name $moduleName)) {
|
|
Import-Module $manifestPath -ErrorAction Stop
|
|
}
|
|
|
|
# Privacy module handles mode selection
|
|
# ONLY pass config values in NonInteractive mode (GUI)
|
|
if (Test-NonInteractiveMode) {
|
|
# GUI mode - use config values
|
|
$privacyArgs = @{ DryRun = $DryRun }
|
|
|
|
if ($script:Config.modules.Privacy.PSObject.Properties.Name -contains 'mode' -and $script:Config.modules.Privacy.mode) {
|
|
Write-Log -Level INFO -Message "Privacy mode: $($script:Config.modules.Privacy.mode)" -Module "Framework"
|
|
$privacyArgs["Mode"] = $script:Config.modules.Privacy.mode
|
|
}
|
|
|
|
if ($script:Config.modules.Privacy.PSObject.Properties.Name -contains 'removeBloatware') {
|
|
$rb = $script:Config.modules.Privacy.removeBloatware
|
|
if ($rb -is [string]) {
|
|
$rb = ($rb -eq "Y" -or $rb -eq "yes" -or $rb -eq "true" -or $rb -eq "1")
|
|
}
|
|
Write-Log -Level INFO -Message "Privacy removeBloatware: $rb" -Module "Framework"
|
|
$privacyArgs["RemoveBloatware"] = $rb
|
|
}
|
|
|
|
$moduleResult = Invoke-PrivacyHardening @privacyArgs
|
|
}
|
|
else {
|
|
# Interactive CLI mode - module will ask for mode selection
|
|
$moduleResult = Invoke-PrivacyHardening -DryRun:$DryRun
|
|
}
|
|
|
|
}
|
|
else {
|
|
throw "Module manifest not found: $manifestPath"
|
|
}
|
|
}
|
|
|
|
"AntiAI" {
|
|
$manifestPath = Join-Path $modulePath "AntiAI.psd1"
|
|
if (Test-Path $manifestPath) {
|
|
if (-not (Get-Module -Name $moduleName)) {
|
|
Import-Module $manifestPath -ErrorAction Stop
|
|
}
|
|
|
|
# AntiAI module applies maximum AI deactivation (no modes)
|
|
Write-Log -Level INFO -Message "Disabling all Windows 11 AI features (15 features, 32 policies)" -Module "Framework"
|
|
$moduleResult = Invoke-AntiAI -DryRun:$DryRun
|
|
|
|
}
|
|
else {
|
|
throw "Module manifest not found: $manifestPath"
|
|
}
|
|
}
|
|
|
|
"EdgeHardening" {
|
|
$manifestPath = Join-Path $modulePath "EdgeHardening.psd1"
|
|
if (Test-Path $manifestPath) {
|
|
if (-not (Get-Module -Name $moduleName)) {
|
|
Import-Module $manifestPath -ErrorAction Stop
|
|
}
|
|
|
|
# EdgeHardening applies Microsoft Edge security baseline
|
|
Write-Log -Level INFO -Message "Applying Microsoft Edge v139 Security Baseline (24 policies)" -Module "Framework"
|
|
$moduleResult = Invoke-EdgeHardening -DryRun:$DryRun
|
|
|
|
}
|
|
else {
|
|
throw "Module manifest not found: $manifestPath"
|
|
}
|
|
}
|
|
|
|
"AdvancedSecurity" {
|
|
$manifestPath = Join-Path $modulePath "AdvancedSecurity.psd1"
|
|
if (Test-Path $manifestPath) {
|
|
if (-not (Get-Module -Name $moduleName)) {
|
|
Import-Module $manifestPath -ErrorAction Stop
|
|
}
|
|
|
|
# AdvancedSecurity handles profile selection
|
|
# ONLY pass config values in NonInteractive mode (GUI)
|
|
if (Test-NonInteractiveMode) {
|
|
# GUI mode - use config values (securityProfile)
|
|
$secProfile = $script:Config.modules.AdvancedSecurity.securityProfile
|
|
if ($secProfile) {
|
|
Write-Log -Level INFO -Message "AdvancedSecurity profile: $secProfile" -Module "Framework"
|
|
$moduleResult = Invoke-AdvancedSecurity -SecurityProfile $secProfile -DryRun:$DryRun
|
|
}
|
|
else {
|
|
$moduleResult = Invoke-AdvancedSecurity -DryRun:$DryRun
|
|
}
|
|
}
|
|
else {
|
|
# Interactive CLI mode - module will ask for profile
|
|
$moduleResult = Invoke-AdvancedSecurity -DryRun:$DryRun
|
|
}
|
|
|
|
}
|
|
else {
|
|
throw "Module manifest not found: $manifestPath"
|
|
}
|
|
}
|
|
|
|
default {
|
|
$warnMsg = "Module '$moduleName' is not yet implemented"
|
|
Write-Log -Level WARNING -Message $warnMsg -Module "Framework"
|
|
$results.Warnings += $warnMsg
|
|
continue
|
|
}
|
|
}
|
|
|
|
# Store module result
|
|
if ($moduleResult) {
|
|
# If module returned an array, use the last element as the actual result
|
|
# (handles cases where helper functions inadvertently output to pipeline)
|
|
if ($moduleResult -is [array]) {
|
|
Write-Log -Level DEBUG -Message "Module '$moduleName' returned array ($($moduleResult.Count) items), using last element as result object" -Module "Framework"
|
|
$moduleResult = $moduleResult[-1]
|
|
}
|
|
|
|
$results.ModuleResults += $moduleResult
|
|
$results.ModulesExecuted++
|
|
|
|
# Handle different return types: Boolean or PSCustomObject
|
|
$success = $false
|
|
|
|
if ($moduleResult -is [bool]) {
|
|
# Module returned simple boolean (e.g., Privacy module)
|
|
$success = $moduleResult
|
|
}
|
|
elseif ($moduleResult -is [PSCustomObject]) {
|
|
# Module returned object with Success property (e.g., ASR, DNS modules)
|
|
$hasSuccess = $null -ne ($moduleResult.PSObject.Properties | Where-Object { $_.Name -eq 'Success' })
|
|
$success = if ($hasSuccess) { $moduleResult.Success } else { $false }
|
|
}
|
|
else {
|
|
# Unknown type - assume failure
|
|
Write-Log -Level WARNING -Message "Module '$moduleName' returned unexpected type: $($moduleResult.GetType().Name)" -Module "Framework"
|
|
}
|
|
|
|
if ($success) {
|
|
Write-Log -Level SUCCESS -Message "Module '$moduleName' completed successfully" -Module "Framework"
|
|
}
|
|
else {
|
|
Write-Log -Level WARNING -Message "Module '$moduleName' completed with errors" -Module "Framework"
|
|
|
|
# Only add errors if moduleResult is an object with Errors property
|
|
if ($moduleResult -is [PSCustomObject]) {
|
|
$hasErrors = $null -ne ($moduleResult.PSObject.Properties | Where-Object { $_.Name -eq 'Errors' })
|
|
if ($hasErrors -and $moduleResult.Errors.Count -gt 0) {
|
|
$results.Errors += $moduleResult.Errors
|
|
}
|
|
}
|
|
}
|
|
|
|
# Always collect warnings from modules (regardless of success)
|
|
# Warnings are informational (e.g., "rule set to AUDIT mode") - not errors
|
|
if ($moduleResult -is [PSCustomObject]) {
|
|
$hasWarnings = $null -ne ($moduleResult.PSObject.Properties | Where-Object { $_.Name -eq 'Warnings' })
|
|
if ($hasWarnings -and $moduleResult.Warnings.Count -gt 0) {
|
|
$results.Warnings += $moduleResult.Warnings
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Write-ErrorLog -Message "Failed to execute module '$moduleName'" -Module "Framework" -ErrorRecord $_
|
|
$errMsg = "Module '$moduleName' execution failed: $($_.Exception.Message)"
|
|
$results.Errors += $errMsg
|
|
$results.Success = $false
|
|
}
|
|
}
|
|
|
|
# Calculate duration
|
|
$results.Duration = (Get-Date) - $startTime
|
|
|
|
# Final success status
|
|
if ($results.Errors.Count -gt 0) {
|
|
$results.Success = $false
|
|
}
|
|
|
|
# Update session display name for backup identification (after all modules complete)
|
|
if (-not $DryRun) {
|
|
try {
|
|
Update-SessionDisplayName
|
|
}
|
|
catch {
|
|
Write-Log -Level WARNING -Message "Failed to update session display name: $_" -Module "Framework"
|
|
}
|
|
}
|
|
|
|
Write-Log -Level INFO -Message "Hardening execution completed - Modules: $($results.ModulesExecuted), Errors: $($results.Errors.Count), Warnings: $($results.Warnings.Count)" -Module "Framework"
|
|
|
|
return $results
|
|
}
|
|
|
|
# Note: Export-ModuleMember not used - this script is dot-sourced, not imported as module
|
|
|
|
# Main execution (only if script is run directly, not dot-sourced)
|
|
# This allows Framework.ps1 to be used standalone OR dot-sourced by NoIDPrivacy.ps1
|
|
if ($MyInvocation.InvocationName -ne '.' -and $MyInvocation.Line -notmatch '^\s*\.\s+') {
|
|
try {
|
|
Initialize-Framework
|
|
|
|
$prereqsPassed = Test-FrameworkPrerequisites
|
|
|
|
if ($prereqsPassed) {
|
|
Start-HardeningProcess
|
|
}
|
|
else {
|
|
Write-Host "Execution aborted due to failed prerequisites." -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host ""
|
|
Write-Host "CRITICAL ERROR" -ForegroundColor Red
|
|
Write-Host "An unexpected error occurred:" -ForegroundColor Red
|
|
Write-Host $_.Exception.Message -ForegroundColor Red
|
|
Write-Host ""
|
|
Write-Host "Stack trace:" -ForegroundColor Gray
|
|
Write-Host $_.ScriptStackTrace -ForegroundColor Gray
|
|
|
|
if ($null -ne (Get-Command Write-Log -ErrorAction SilentlyContinue)) {
|
|
Write-Log -Level ERROR -Message "Critical framework error" -Module "Framework" -Exception $_.Exception
|
|
}
|
|
|
|
exit 1
|
|
}
|
|
}
|