mirror of
https://github.com/NexusOne23/noid-privacy.git
synced 2026-02-19 09:48:14 +01:00
v2.2.0 - Complete Security Hardening Framework (632 Settings)
This commit is contained in:
commit
ba364813ed
195 changed files with 43788 additions and 0 deletions
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Support NoID Privacy development
|
||||
|
||||
buy_me_a_coffee: noidprivacy
|
||||
79
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
79
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
---
|
||||
name: 🐛 Bug Report
|
||||
about: Report a bug or unexpected behavior
|
||||
title: '[BUG] '
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 🐛 Bug Description
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
## 📋 Steps to Reproduce
|
||||
|
||||
1. Run command: `...`
|
||||
2. Configure module: `...`
|
||||
3. Execute script: `...`
|
||||
4. See error
|
||||
|
||||
## ✅ Expected Behavior
|
||||
|
||||
A clear description of what you expected to happen.
|
||||
|
||||
## ❌ Actual Behavior
|
||||
|
||||
A clear description of what actually happened.
|
||||
|
||||
## 💻 System Information
|
||||
|
||||
- **OS**: Windows 11 [e.g., 25H2 Build 26200]
|
||||
- **PowerShell Version**: [e.g., 5.1.26100.2161]
|
||||
- **CPU**: [e.g., AMD Ryzen 7 9800X3D]
|
||||
- **TPM**: [e.g., 2.0 Present]
|
||||
- **Third-Party AV**: [e.g., None, Windows Defender only]
|
||||
- **Script Version**: [e.g., v2.2.0]
|
||||
- **Execution Mode**: [Interactive / Direct / DryRun]
|
||||
|
||||
**Get System Info:**
|
||||
```powershell
|
||||
# Run this to get system info
|
||||
$PSVersionTable
|
||||
Get-ComputerInfo | Select-Object OsName, OsVersion, OsBuildNumber
|
||||
Get-Tpm | Select-Object TpmPresent, TpmReady
|
||||
```
|
||||
|
||||
## 📝 Log Files
|
||||
|
||||
Please attach or paste the relevant portion of the log file:
|
||||
|
||||
**Location**: `Logs\NoIDPrivacy_YYYYMMDD_HHMMSS.log`
|
||||
|
||||
```
|
||||
[Paste relevant log excerpt here]
|
||||
```
|
||||
|
||||
## 📸 Screenshots
|
||||
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## 🔍 Additional Context
|
||||
|
||||
Add any other context about the problem here:
|
||||
- Was this a fresh installation or re-run?
|
||||
- Did the script work previously?
|
||||
- Any recent system changes?
|
||||
- Running in VM or physical machine?
|
||||
|
||||
## ✔️ Checklist
|
||||
|
||||
- [ ] I have searched for similar issues
|
||||
- [ ] I have verified this is reproducible
|
||||
- [ ] I have included the log file
|
||||
- [ ] I have provided complete system information
|
||||
- [ ] I have tested on a clean Windows 11 25H2 installation (if possible)
|
||||
|
||||
## 🔒 Security Note
|
||||
|
||||
If this is a **security vulnerability**, please **DO NOT** create a public issue!
|
||||
Instead, report it privately via: https://github.com/NexusOne23/noid-privacy/security/advisories
|
||||
103
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
103
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
---
|
||||
name: ✨ Feature Request
|
||||
about: Suggest a new feature or enhancement
|
||||
title: '[FEATURE] '
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## 🚀 Feature Request
|
||||
|
||||
**Note:** For questions or discussions, please use [GitHub Discussions](https://github.com/NexusOne23/noid-privacy/discussions) instead!
|
||||
|
||||
## 🔍 Problem Statement
|
||||
|
||||
**Is your feature request related to a problem?**
|
||||
|
||||
Describe the problem this feature would solve. Example: "I'm frustrated when [...]"
|
||||
|
||||
## 💡 Proposed Solution
|
||||
|
||||
Describe the solution you'd like to see implemented.
|
||||
|
||||
## 🔄 Alternatives Considered
|
||||
|
||||
Describe any alternative solutions or features you've considered.
|
||||
|
||||
## 📊 Impact Assessment
|
||||
|
||||
Please assess the potential impact of this feature:
|
||||
|
||||
### Security Impact
|
||||
- [ ] Enhances security
|
||||
- [ ] No security impact
|
||||
- [ ] Potential security concerns (explain below)
|
||||
|
||||
**Details:**
|
||||
|
||||
### Privacy Impact
|
||||
- [ ] Enhances privacy
|
||||
- [ ] No privacy impact
|
||||
- [ ] Potential privacy concerns (explain below)
|
||||
|
||||
**Details:**
|
||||
|
||||
### Compatibility Impact
|
||||
- [ ] No breaking changes
|
||||
- [ ] Minor breaking changes
|
||||
- [ ] Major breaking changes (explain below)
|
||||
|
||||
**Details:**
|
||||
|
||||
### Usability Impact
|
||||
- [ ] Improves usability
|
||||
- [ ] No usability impact
|
||||
- [ ] May affect usability (explain below)
|
||||
|
||||
**Details:**
|
||||
|
||||
## 📝 Additional Context
|
||||
|
||||
Add any other context, examples, or mockups about the feature request here.
|
||||
|
||||
## 🎯 Use Cases
|
||||
|
||||
Describe specific use cases where this feature would be beneficial:
|
||||
|
||||
1. **Use case 1**: [Description]
|
||||
2. **Use case 2**: [Description]
|
||||
3. **Use case 3**: [Description]
|
||||
|
||||
## 📚 References
|
||||
|
||||
Link to any relevant documentation, standards, or similar implementations:
|
||||
|
||||
- [Example: Microsoft documentation]
|
||||
- [Example: CIS Benchmark requirement]
|
||||
- [Example: Similar project implementation]
|
||||
|
||||
## 🏷️ Module Target
|
||||
|
||||
Which module would this feature belong to?
|
||||
|
||||
- [ ] SecurityBaseline (MS Baseline settings)
|
||||
- [ ] ASR (Attack Surface Reduction)
|
||||
- [ ] DNS (Secure DNS)
|
||||
- [ ] Privacy (Telemetry, Bloatware)
|
||||
- [ ] AntiAI (AI Features Lockdown)
|
||||
- [ ] EdgeHardening (Microsoft Edge)
|
||||
- [ ] AdvancedSecurity (Beyond MS Baseline)
|
||||
- [ ] Core (Framework/Architecture)
|
||||
- [ ] New Module (describe below)
|
||||
|
||||
## ✔️ Checklist
|
||||
|
||||
- [ ] I have searched for similar feature requests
|
||||
- [ ] I have considered the impact on security and privacy
|
||||
- [ ] I have described the problem and proposed solution clearly
|
||||
- [ ] I have provided use cases and examples
|
||||
- [ ] This is NOT a security vulnerability (use Security Advisory instead)
|
||||
|
||||
## 💼 Commercial Licensing
|
||||
|
||||
If this feature is critical for your organization and you need it prioritized, consider our [commercial licensing options](https://github.com/NexusOne23/noid-privacy/discussions) with dedicated support and custom development.
|
||||
94
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
94
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# Pull Request
|
||||
|
||||
## 📝 Description
|
||||
|
||||
Please include a summary of the changes and the related issue. Explain the motivation and context.
|
||||
|
||||
Fixes #(issue number)
|
||||
|
||||
## 🎯 Type of Change
|
||||
|
||||
Please delete options that are not relevant:
|
||||
|
||||
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] ✨ New feature (non-breaking change which adds functionality)
|
||||
- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] 📚 Documentation update
|
||||
- [ ] 🔧 Code refactoring (no functional changes)
|
||||
- [ ] ⚡ Performance improvement
|
||||
- [ ] ✅ Test coverage improvement
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
Please describe the tests you ran to verify your changes:
|
||||
|
||||
- [ ] Tested on Windows 11 25H2
|
||||
- [ ] Tested on Windows 11 24H2
|
||||
- [ ] Tested in VM environment
|
||||
- [ ] Tested on physical hardware
|
||||
- [ ] Unit tests pass (`.\Tests\Run-Tests.ps1`)
|
||||
- [ ] Integration tests pass
|
||||
- [ ] Verification script passes (`.\Tools\Verify-Complete-Hardening.ps1`)
|
||||
|
||||
**Test Configuration:**
|
||||
- **OS Version**: Windows 11 25H2 Build 26200
|
||||
- **PowerShell Version**: 5.1.26100.xxxx
|
||||
- **Test Environment**: VM / Physical
|
||||
|
||||
## 📋 Checklist
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have made corresponding changes to the documentation
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] New and existing unit tests pass locally with my changes
|
||||
- [ ] Any dependent changes have been merged and published
|
||||
- [ ] I have updated the CHANGELOG.md
|
||||
- [ ] I have read and agree to the [Code of Conduct](../CODE_OF_CONDUCT.md)
|
||||
|
||||
## 🔒 Security Considerations
|
||||
|
||||
- [ ] This change does not introduce security vulnerabilities
|
||||
- [ ] This change has been reviewed for security implications
|
||||
- [ ] Sensitive data is handled properly (if applicable)
|
||||
- [ ] No hardcoded credentials or secrets
|
||||
|
||||
**Security Impact Details:**
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- [ ] README.md updated (if needed)
|
||||
- [ ] CHANGELOG.md updated
|
||||
- [ ] Docs/ folder updated (if needed)
|
||||
- [ ] Code comments added/updated
|
||||
|
||||
## 🔄 Backwards Compatibility
|
||||
|
||||
- [ ] This change is backwards compatible
|
||||
- [ ] This change includes migration path for existing users
|
||||
- [ ] Breaking changes are documented
|
||||
|
||||
**Compatibility Details:**
|
||||
|
||||
## 🎨 Screenshots (if applicable)
|
||||
|
||||
Add screenshots to help explain your changes (e.g., UI changes, verification report).
|
||||
|
||||
## 📝 Additional Notes
|
||||
|
||||
Add any additional notes for reviewers here.
|
||||
|
||||
---
|
||||
|
||||
## For Maintainers
|
||||
|
||||
**Review Checklist:**
|
||||
- [ ] Code quality meets project standards
|
||||
- [ ] Tests are comprehensive
|
||||
- [ ] Documentation is complete
|
||||
- [ ] Security implications reviewed
|
||||
- [ ] Backwards compatibility considered
|
||||
- [ ] CHANGELOG updated
|
||||
- [ ] Ready to merge
|
||||
226
.github/workflows/ci.yml
vendored
Normal file
226
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
name: CI - PowerShell Quality Checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
psscriptanalyzer:
|
||||
name: PSScriptAnalyzer
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run PSScriptAnalyzer
|
||||
shell: pwsh
|
||||
run: |
|
||||
Write-Host "Installing PSScriptAnalyzer..."
|
||||
Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser -SkipPublisherCheck -ErrorAction Stop
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Running PSScriptAnalyzer (Errors only)..."
|
||||
$results = Invoke-ScriptAnalyzer -Path . -Recurse -Severity Error
|
||||
|
||||
if ($results) {
|
||||
Write-Host ""
|
||||
Write-Host "=== PSScriptAnalyzer Errors Found ===" -ForegroundColor Red
|
||||
$results | Format-Table -AutoSize
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Error Count: $($results.Count)" -ForegroundColor Red
|
||||
Write-Host "Failing CI due to errors" -ForegroundColor Red
|
||||
exit 1
|
||||
} else {
|
||||
Write-Host ""
|
||||
Write-Host "No errors found! (Warnings are ignored)" -ForegroundColor Green
|
||||
}
|
||||
|
||||
test-powershell-51:
|
||||
name: Test on PowerShell 5.1
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test PowerShell Scripts (5.1)
|
||||
shell: powershell
|
||||
run: |
|
||||
Write-Host "PowerShell Version: $($PSVersionTable.PSVersion)" -ForegroundColor Cyan
|
||||
Write-Host "Testing script syntax..."
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$failed = $false
|
||||
Get-ChildItem -Path . -Filter "*.ps1" -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
Write-Host "Checking: $($_.Name)"
|
||||
try {
|
||||
$errors = $null
|
||||
$tokens = [System.Management.Automation.PSParser]::Tokenize((Get-Content $_.FullName -Raw), [ref]$errors)
|
||||
if ($errors.Count -gt 0) {
|
||||
Write-Host " [ERROR] $($errors[0].Message)" -ForegroundColor Red
|
||||
$failed = $true
|
||||
} else {
|
||||
Write-Host " [OK]" -ForegroundColor Green
|
||||
}
|
||||
} catch {
|
||||
Write-Host " [ERROR] $_" -ForegroundColor Red
|
||||
$failed = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($failed) {
|
||||
Write-Host ""
|
||||
Write-Host "Syntax check FAILED" -ForegroundColor Red
|
||||
exit 1
|
||||
} else {
|
||||
Write-Host ""
|
||||
Write-Host "All scripts have valid syntax!" -ForegroundColor Green
|
||||
}
|
||||
|
||||
test-powershell-7:
|
||||
name: Test on PowerShell 7.4
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test PowerShell Scripts (7.4)
|
||||
shell: pwsh
|
||||
run: |
|
||||
Write-Host "PowerShell Version: $($PSVersionTable.PSVersion)" -ForegroundColor Cyan
|
||||
Write-Host "Testing script syntax..."
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$failed = $false
|
||||
Get-ChildItem -Path . -Filter "*.ps1" -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
Write-Host "Checking: $($_.Name)"
|
||||
try {
|
||||
$errors = $null
|
||||
$tokens = $null
|
||||
$ast = [System.Management.Automation.Language.Parser]::ParseFile($_.FullName, [ref]$tokens, [ref]$errors)
|
||||
if ($errors.Count -gt 0) {
|
||||
Write-Host " [ERROR] $($errors[0].Message)" -ForegroundColor Red
|
||||
$failed = $true
|
||||
} else {
|
||||
Write-Host " [OK]" -ForegroundColor Green
|
||||
}
|
||||
} catch {
|
||||
Write-Host " [ERROR] $_" -ForegroundColor Red
|
||||
$failed = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($failed) {
|
||||
Write-Host ""
|
||||
Write-Host "Syntax check FAILED" -ForegroundColor Red
|
||||
exit 1
|
||||
} else {
|
||||
Write-Host ""
|
||||
Write-Host "All scripts have valid syntax!" -ForegroundColor Green
|
||||
}
|
||||
|
||||
validate-structure:
|
||||
name: Validate Project Structure
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check Required Files
|
||||
shell: pwsh
|
||||
run: |
|
||||
Write-Host "Checking project structure..." -ForegroundColor Cyan
|
||||
|
||||
$required = @(
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"CHANGELOG.md",
|
||||
"NoIDPrivacy-Interactive.ps1",
|
||||
"Core/Framework.ps1",
|
||||
"Modules",
|
||||
"Tools"
|
||||
)
|
||||
|
||||
$missing = @()
|
||||
foreach ($item in $required) {
|
||||
if (Test-Path $item) {
|
||||
Write-Host "[OK] $item" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[MISSING] $item" -ForegroundColor Red
|
||||
$missing += $item
|
||||
}
|
||||
}
|
||||
|
||||
if ($missing.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "Missing required files/folders!" -ForegroundColor Red
|
||||
exit 1
|
||||
} else {
|
||||
Write-Host ""
|
||||
Write-Host "All required files present!" -ForegroundColor Green
|
||||
}
|
||||
|
||||
- name: Check Module Structure
|
||||
shell: pwsh
|
||||
run: |
|
||||
Write-Host "`nValidating module structure..." -ForegroundColor Cyan
|
||||
|
||||
$modules = @(
|
||||
"SecurityBaseline",
|
||||
"ASR",
|
||||
"DNS",
|
||||
"Privacy",
|
||||
"AntiAI",
|
||||
"EdgeHardening",
|
||||
"AdvancedSecurity"
|
||||
)
|
||||
|
||||
$failed = $false
|
||||
foreach ($module in $modules) {
|
||||
$modulePath = "Modules/$module"
|
||||
if (Test-Path $modulePath) {
|
||||
Write-Host "[OK] Module: $module" -ForegroundColor Green
|
||||
|
||||
# Check for required module files
|
||||
$moduleFile = "$modulePath/$module.psm1"
|
||||
$manifestFile = "$modulePath/$module.psd1"
|
||||
|
||||
if (Test-Path $moduleFile) {
|
||||
Write-Host " [OK] $module.psm1" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [MISSING] $module.psm1" -ForegroundColor Red
|
||||
$failed = $true
|
||||
}
|
||||
|
||||
if (Test-Path $manifestFile) {
|
||||
Write-Host " [OK] $module.psd1" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " [MISSING] $module.psd1" -ForegroundColor Red
|
||||
$failed = $true
|
||||
}
|
||||
} else {
|
||||
Write-Host "[MISSING] Module: $module" -ForegroundColor Red
|
||||
$failed = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($failed) {
|
||||
Write-Host ""
|
||||
Write-Host "Module structure validation FAILED!" -ForegroundColor Red
|
||||
exit 1
|
||||
} else {
|
||||
Write-Host ""
|
||||
Write-Host "All modules are correctly structured!" -ForegroundColor Green
|
||||
}
|
||||
71
.github/workflows/pester-tests.yml
vendored
Normal file
71
.github/workflows/pester-tests.yml
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
name: Pester Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Pester
|
||||
shell: pwsh
|
||||
run: |
|
||||
Install-Module -Name Pester -Force -SkipPublisherCheck -Scope CurrentUser
|
||||
Import-Module Pester
|
||||
|
||||
- name: Run Unit Tests
|
||||
shell: pwsh
|
||||
run: |
|
||||
$config = New-PesterConfiguration
|
||||
$config.Run.Path = "Tests/Unit"
|
||||
$config.Run.PassThru = $true
|
||||
$config.Output.Verbosity = 'Detailed'
|
||||
$config.TestResult.Enabled = $true
|
||||
$config.TestResult.OutputPath = "TestResults-Unit.xml"
|
||||
$config.TestResult.OutputFormat = 'NUnitXml'
|
||||
|
||||
$results = Invoke-Pester -Configuration $config
|
||||
|
||||
if ($results.FailedCount -gt 0) {
|
||||
Write-Error "Unit tests failed: $($results.FailedCount) failures"
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Run Integration Tests (DryRun only)
|
||||
shell: pwsh
|
||||
run: |
|
||||
$config = New-PesterConfiguration
|
||||
$config.Run.Path = "Tests/Integration"
|
||||
$config.Run.PassThru = $true
|
||||
$config.Output.Verbosity = 'Detailed'
|
||||
$config.TestResult.Enabled = $true
|
||||
$config.TestResult.OutputPath = "TestResults-Integration.xml"
|
||||
$config.TestResult.OutputFormat = 'NUnitXml'
|
||||
|
||||
$results = Invoke-Pester -Configuration $config
|
||||
|
||||
if ($results.FailedCount -gt 0) {
|
||||
Write-Error "Integration tests failed: $($results.FailedCount) failures"
|
||||
exit 1
|
||||
}
|
||||
|
||||
- name: Upload Test Results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: test-results
|
||||
path: TestResults-*.xml
|
||||
|
||||
- name: Publish Test Results
|
||||
uses: EnricoMi/publish-unit-test-result-action/windows@v2
|
||||
if: always()
|
||||
with:
|
||||
files: TestResults-*.xml
|
||||
58
.gitignore
vendored
Normal file
58
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# Logs
|
||||
Logs/
|
||||
*.log
|
||||
|
||||
# Backups
|
||||
Backups/
|
||||
*.reg
|
||||
*.bak
|
||||
|
||||
# Reports
|
||||
Reports/
|
||||
*.html
|
||||
*.csv
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# PowerShell module cache
|
||||
*.psm1.tmp
|
||||
*.ps1.tmp
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.code-workspace
|
||||
|
||||
# Build artifacts
|
||||
Build/Output/
|
||||
Build/Release/
|
||||
*.msi
|
||||
*.exe
|
||||
|
||||
# Test results
|
||||
Tests/Results/
|
||||
*.trx
|
||||
*.coverage
|
||||
|
||||
# User configuration (don't commit personal settings)
|
||||
config.local.json
|
||||
|
||||
# Sensitive data
|
||||
*.key
|
||||
*.pem
|
||||
*.pfx
|
||||
secrets/
|
||||
|
||||
# Common build artifacts and IDE metadata
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
TestResults/
|
||||
.vs/
|
||||
|
||||
324
CHANGELOG.md
Normal file
324
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to NoID Privacy will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
---
|
||||
|
||||
## [2.2.0] - 2025-12-08
|
||||
|
||||
### 🚀 Enhanced Framework - 630+ Settings
|
||||
|
||||
**Major update with expanded AI lockdown, improved privacy coverage, and ASR quick-toggle fix.**
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Release Highlights
|
||||
|
||||
✅ **630+ Settings** - Expanded from 580+ (Privacy, AntiAI, EdgeHardening, AdvSec Wireless Display)
|
||||
✅ **NonInteractive Mode** - Full GUI integration via config.json
|
||||
✅ **Third-Party AV Support** - Automatic detection, graceful ASR skip
|
||||
✅ **AntiAI Enhanced** - 32 policies (was 24), Recall Export Block, Edge Copilot disabled
|
||||
✅ **Pre-Framework ASR Snapshot** - Preserves rule state before multi-module runs
|
||||
✅ **Smart Registry Backup** - JSON fallback for protected keys
|
||||
✅ **Critical Bugfixes** - ASR Quick-Toggle, NonInteractive strict-mode, DNS offline
|
||||
|
||||
### ✅ Added
|
||||
|
||||
**NonInteractive Mode (GUI Integration)**
|
||||
- Complete `config.json` support for automated execution
|
||||
- All 7 modules fully configurable without prompts when values are provided in `config.json`
|
||||
- Enables GUI-driven hardening in non-interactive mode (no Read-Host prompts)
|
||||
|
||||
**Pre-Framework ASR Snapshot**
|
||||
- Captures all 19 ASR rules before multi-module runs
|
||||
- Ensures original system state is preserved
|
||||
- Prevents ASR rule loss during complex operations
|
||||
|
||||
**AntiAI Module Enhancements (24 → 32 policies)**
|
||||
- Recall Export Block (prevents snapshot export)
|
||||
- Advanced Copilot Blocks (URI handlers, Edge sidebar)
|
||||
- Improved Edge Copilot sidebar disable (5 additional policies)
|
||||
- Hardware Copilot key remapped to Notepad
|
||||
- CapabilityAccessManager AI blocking
|
||||
|
||||
**AdvancedSecurity: Wireless Display / Miracast Hardening**
|
||||
- New Wireless Display security available in all AdvancedSecurity profiles (Balanced/Enterprise/Maximum)
|
||||
- Default: Block receiving projections and require PIN for incoming connections
|
||||
- Optional: Complete disable (blocks sending projections, mDNS discovery, ports 7236/7250, and Wi-Fi Direct adapters)
|
||||
|
||||
**AdvancedSecurity: Discovery Protocols Security (Maximum profile)**
|
||||
- Optional WS-Discovery + mDNS complete disable
|
||||
- Blocks automatic device discovery (printers, TVs, scanners)
|
||||
- Firewall rules for UDP 3702 (WS-Discovery) and UDP 5353 (mDNS)
|
||||
- Prevents network mapping and mDNS spoofing attacks
|
||||
|
||||
**AdvancedSecurity: IPv6 Disable (Maximum profile - mitm6 mitigation)**
|
||||
- Optional complete IPv6 disable (DisabledComponents = 0xFF)
|
||||
- Prevents mitm6 attacks (DHCPv6 spoofing → DNS takeover → NTLM relay)
|
||||
- Defense-in-depth (WPAD already disabled by framework)
|
||||
- Recommended for air-gapped/standalone systems
|
||||
|
||||
**Privacy Module Expansion (55+ → 77 settings)**
|
||||
- Cloud Clipboard toggle (user-configurable)
|
||||
- Enhanced compliance verification
|
||||
- Improved bloatware detection
|
||||
- Better OneDrive sync compatibility
|
||||
|
||||
**Third-Party Antivirus Detection**
|
||||
- Automatic detection of Kaspersky, Norton, Bitdefender, etc.
|
||||
- ASR module gracefully skipped when 3rd-party AV active
|
||||
- Clear user notification explaining why
|
||||
- All other modules continue normally (613 settings)
|
||||
|
||||
**Smart Registry Backup System**
|
||||
- JSON fallback for protected system keys
|
||||
- Handles access-denied scenarios gracefully
|
||||
- Empty marker files for non-existent keys
|
||||
- Improved restore reliability
|
||||
|
||||
**Documentation**
|
||||
- AV Compatibility section: "Designed for Microsoft Defender – Works with Any Antivirus"
|
||||
- Clear 632 vs 613 explanation for Defender vs. 3rd-party AV setups
|
||||
- Improved troubleshooting guides
|
||||
|
||||
### 🔨 Fixed
|
||||
|
||||
**ASR Quick-Toggle Bug (Critical)**
|
||||
- Fixed: Quick-toggling ASR rules caused 3 advanced rules to disappear
|
||||
- Affected rules: Safe Mode Reboot, Copied System Tools, Webshell Creation
|
||||
- Root cause: `Set-MpPreference` was called with single rule instead of full rule set
|
||||
- Fix: Now reads existing rules, updates target, writes complete set back
|
||||
|
||||
**NonInteractive Strict-Mode Error**
|
||||
- Fixed fatal error when dot-sourcing `NonInteractive.ps1` in GUI context
|
||||
- Safe check for `$global:NonInteractiveMode` variable
|
||||
|
||||
**Registry Backup Protected Keys**
|
||||
- Enhanced JSON fallback for protected system keys
|
||||
- Prevents backup failures on restricted registry paths
|
||||
- Creates marker files for rollback tracking
|
||||
|
||||
**DNS Offline Handling**
|
||||
- Graceful handling when system temporarily offline during DNS test
|
||||
- Configuration proceeds and activates when connection restored
|
||||
|
||||
**Module Progress Feedback**
|
||||
- Improved status messages during long operations
|
||||
- No more "stuck at 95%" feeling
|
||||
|
||||
### 📊 What Changed
|
||||
|
||||
| Component | v2.1.0 | v2.2.0 |
|
||||
|-----------|--------|--------|
|
||||
| Total Settings | 580+ | **632** |
|
||||
| AntiAI Policies | 24 | **32** |
|
||||
| Privacy Settings | 55+ | **77** |
|
||||
| NonInteractive Mode | ❌ | ✅ |
|
||||
| 3rd-Party AV Detection | ❌ | ✅ |
|
||||
| Pre-Framework ASR Snapshot | ❌ | ✅ |
|
||||
| Smart Registry Backup | Basic | **JSON Fallback** |
|
||||
|
||||
---
|
||||
|
||||
## [2.1.0] - 2025-11-23
|
||||
|
||||
### 🎉 Production Release - Complete Windows 11 Security Framework
|
||||
|
||||
**The first complete, production-ready release of NoID Privacy v2.x - 580+ settings, 7 modules, full BAVR pattern implementation.**
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Release Highlights
|
||||
|
||||
✅ **All 7 Modules Production-Ready** - Complete framework with 580+ security settings
|
||||
✅ **Zero-Day Protection** - CVE-2025-9491 mitigation (SRP .lnk protection)
|
||||
✅ **100% BAVR Coverage** - Every setting can be backed up, applied, verified, and restored
|
||||
✅ **Professional Code Quality** - All lint warnings resolved, comprehensive error handling
|
||||
✅ **Zero Tracking** - No cookies, no analytics, no telemetry (we practice what we preach)
|
||||
|
||||
### ✅ Added - Complete Framework
|
||||
|
||||
#### All 7 Security Modules
|
||||
|
||||
**SecurityBaseline** (425 settings) - Microsoft Security Baseline for Windows 11 25H2
|
||||
- 335 Registry policies (Computer + User Configuration)
|
||||
- 67 Security Template settings (Password Policy, Account Lockout, User Rights, Security Options)
|
||||
- 23 Advanced Audit policies (Complete security event logging)
|
||||
- Credential Guard, BitLocker policies, VBS & HVCI
|
||||
- No LGPO.exe dependency (100% native PowerShell)
|
||||
|
||||
**ASR** (19 rules) - Attack Surface Reduction
|
||||
- 18 rules in Block mode, 1 configurable (PSExec/WMI)
|
||||
- Blocks ransomware, macros, exploits, credential theft
|
||||
- Office/Adobe/Email protection
|
||||
- ConfigMgr detection for compatibility
|
||||
|
||||
**DNS** (5 checks) - Secure DNS with DoH encryption
|
||||
- 3 providers: Cloudflare (default), Quad9, AdGuard
|
||||
- REQUIRE mode (no unencrypted fallback) or ALLOW mode (VPN-friendly)
|
||||
- IPv4 + IPv6 dual-stack support
|
||||
- DNSSEC validation
|
||||
|
||||
**Privacy** (55+ settings) - Telemetry & Privacy Hardening
|
||||
- 3 operating modes: MSRecommended (default), Strict, Paranoid
|
||||
- Telemetry minimized to Security-Essential level
|
||||
- Bloatware removal with auto-restore via winget (policy-based on 25H2+ Ent/Edu)
|
||||
- OneDrive telemetry off (sync functional)
|
||||
- App permissions default-deny
|
||||
|
||||
**AntiAI** (24 policies) - AI Lockdown
|
||||
- Generative AI Master Switch (blocks ALL AI models system-wide)
|
||||
- Windows Recall (complete deactivation + component protection)
|
||||
- Windows Copilot (system-wide disabled + hardware key remapped)
|
||||
- Click to Do, Paint AI, Notepad AI, Settings Agent - all disabled
|
||||
|
||||
**EdgeHardening** (20 policies) - Microsoft Edge Security Baseline
|
||||
- SmartScreen enforced, Tracking Prevention strict
|
||||
- SSL/TLS hardening, Extension security
|
||||
- IE Mode restrictions
|
||||
- Native PowerShell implementation (no LGPO.exe)
|
||||
|
||||
**AdvancedSecurity** (44 settings) - Beyond Microsoft Baseline
|
||||
- **SRP .lnk Protection (CVE-2025-9491)** - Zero-day mitigation for ClickFix malware
|
||||
- **RDP Hardening** - Disabled by default, TLS + NLA enforced
|
||||
- **Legacy Protocol Blocking** - SMBv1, NetBIOS, LLMNR, WPAD, PowerShell v2
|
||||
- **TLS Hardening** - 1.0/1.1 OFF, 1.2/1.3 ON
|
||||
- **Windows Update** - 3 GUI-equivalent settings (interactive configuration)
|
||||
- **Finger Protocol** - Blocked (ClickFix malware protection)
|
||||
|
||||
#### Core Features
|
||||
|
||||
**Complete BAVR Pattern (Backup-Apply-Verify-Restore)**
|
||||
- All 580+ settings now fully verified in `Verify-Complete-Hardening.ps1`
|
||||
- EdgeHardening: 20 verification checks added
|
||||
- AdvancedSecurity: 42 verification checks added
|
||||
- 100% coverage achieved (was 89.4%)
|
||||
|
||||
**Bloatware Removal & Restore**
|
||||
- `REMOVED_APPS_LIST.txt` created in backup folder with reinstall instructions
|
||||
- `REMOVED_APPS_WINGET.json` metadata enables automatic reinstallation via `winget`
|
||||
- Session restore attempts auto-restore first, falls back to manual Microsoft Store reinstall
|
||||
- Policy-based removal for Windows 11 25H2+ Ent/Edu editions
|
||||
|
||||
**Documentation & Repository**
|
||||
- **FEATURES.md** - Complete settings reference
|
||||
- **SECURITY-ANALYSIS.md** - Home user impact analysis
|
||||
- **README.md** - Professional restructure with improved visual hierarchy
|
||||
- **CHANGELOG.md** - Comprehensive release history
|
||||
- **.gitignore** - Clean repository (ignores Logs/, Backups/, Reports/)
|
||||
|
||||
---
|
||||
|
||||
### 🔨 Fixed - Critical Bugfixes
|
||||
|
||||
**DNS Module Crash (CRITICAL)**
|
||||
- Fixed `System.Object[]` to `System.Int32` type conversion error in `Get-PhysicalAdapters`
|
||||
- Removed unary comma operator causing DNS configuration failure
|
||||
- Prevents complete DNS module failure on certain network configurations
|
||||
|
||||
**Bloatware Count Accuracy**
|
||||
- Corrected misleading console output showing "2 apps removed" instead of actual count
|
||||
- Fixed pipeline contamination from `Register-Backup` output in `Remove-Bloatware.ps1`
|
||||
- Now shows accurate count (e.g., "14 apps removed")
|
||||
|
||||
**Restore Logging System**
|
||||
- Implemented dedicated `RESTORE_Session_XXXXXX_timestamp.log` file
|
||||
- Captures all restore activities from A-Z with detailed logging
|
||||
- Fixed empty `Message` parameter validation errors in `Write-RestoreLog`
|
||||
|
||||
**User Selection Logs**
|
||||
- Moved user selection messages from INFO to DEBUG (cleaner console output)
|
||||
- Affects: Privacy mode selection, DNS provider selection, ASR mode selection
|
||||
- Console now shows only critical information, detailed logs in log file
|
||||
|
||||
**Code Quality & Linting**
|
||||
- Removed all unused variables (`$isAdmin` in `Invoke-AdvancedSecurity.ps1`)
|
||||
- Fixed PSScriptAnalyzer warnings across entire project
|
||||
- Resolved double backslash escaping in documentation paths
|
||||
|
||||
**Terminal Services GPO Cleanup**
|
||||
- Enhanced GPO cleanup with explicit value removal
|
||||
- Improved restore consistency for Terminal Services registry keys
|
||||
- Cosmetic variance only (no functional impact)
|
||||
|
||||
**Temporary File Leaks**
|
||||
- SecurityBaseline: Added `finally` blocks to prevent temp file pollution
|
||||
- Ensures cleanup of `secedit.exe` temp files even on errors
|
||||
- Prevents TEMP folder accumulation
|
||||
|
||||
---
|
||||
|
||||
### 📊 What Changed
|
||||
|
||||
**Framework Completion**
|
||||
- Status: **7/7 modules (100%)** - All production-ready
|
||||
- Total Settings: **580+** (was 521)
|
||||
- BAVR Coverage: **100%** (was 89.4%)
|
||||
- Verification: **EdgeHardening** (20 checks) + **AdvancedSecurity** (44 checks) added
|
||||
|
||||
**Module Structure**
|
||||
- All 7 modules now use consistent `/Config/` folder structure
|
||||
- ASR: `Data/` → `Config/`
|
||||
- EdgeHardening: `ParsedSettings/` → `Config/`
|
||||
|
||||
**Documentation Improvements**
|
||||
- README: Professional restructure, improved navigation
|
||||
- Added "Why NoID Privacy?" section (Security ↔ Privacy connection)
|
||||
- Added "Our Privacy Promise" section (Zero tracking)
|
||||
- Fixed all inconsistent list formatting (trailing spaces → proper bullets)
|
||||
|
||||
**Restore System**
|
||||
- Production tested with full apply-restore cycle verification
|
||||
- Restores to clean baseline state
|
||||
- AdvancedSecurity: 100% perfect restoration
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ Breaking Changes
|
||||
|
||||
**License Change**
|
||||
- **MIT (v1.x) → GPL v3.0 (v2.x+)**
|
||||
- Reason: Complete rewrite from scratch (100% new codebase)
|
||||
- Impact: Derivatives must comply with GPL v3.0 copyleft requirements
|
||||
- Note: v1.8.x releases remain under MIT license (unchanged)
|
||||
- **Dual-Licensing:** Commercial licenses available for closed-source use
|
||||
|
||||
---
|
||||
|
||||
### 📈 Before/After Comparison
|
||||
|
||||
**Before v2.1.0:**
|
||||
```
|
||||
Modules: 5/7 (71%)
|
||||
Settings: 521
|
||||
BAVR Coverage: 89.4%
|
||||
Restore Accuracy: Unknown
|
||||
Code Quality: Lint warnings present
|
||||
Temp File Cleanup: Partial
|
||||
```
|
||||
|
||||
**After v2.1.0:**
|
||||
```
|
||||
Modules: 7/7 (100%)
|
||||
Settings: 580+
|
||||
BAVR Coverage: 100%
|
||||
Restore: Verified (full cycle)
|
||||
Code Quality: PSScriptAnalyzer clean
|
||||
Temp File Cleanup: Complete
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
- **Full Documentation:** See [README.md](README.md) and [FEATURES.md](Docs/FEATURES.md)
|
||||
- **Security Analysis:** See [SECURITY-ANALYSIS.md](Docs/SECURITY-ANALYSIS.md)
|
||||
- **Bug Reports:** [GitHub Issues](https://github.com/NexusOne23/noid-privacy/issues)
|
||||
- **Discussions:** [GitHub Discussions](https://github.com/NexusOne23/noid-privacy/discussions)
|
||||
|
||||
---
|
||||
|
||||
**Made with 🛡️ for the Windows Security Community**
|
||||
135
CODE_OF_CONDUCT.md
Normal file
135
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official email address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement via:
|
||||
|
||||
- **GitHub Discussions**: https://github.com/NexusOne23/noid-privacy/discussions
|
||||
- **GitHub Issues**: For code-related conduct issues only
|
||||
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
783
CONTRIBUTING.md
Normal file
783
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,783 @@
|
|||
# 🛠️ Contributor Guide - Building New Modules
|
||||
|
||||
**How to build production-quality hardening modules for NoID Privacy**
|
||||
|
||||
This guide shows you how to create a new module using the **AdvancedSecurity** module as the reference implementation. Every principle, pattern, and structure you see here is based on real, production-tested code.
|
||||
|
||||
---
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
1. [Module Architecture](#module-architecture)
|
||||
2. [File Structure](#file-structure)
|
||||
3. [Core Integration Points](#core-integration-points)
|
||||
4. [Implementation Checklist](#implementation-checklist)
|
||||
5. [Best Practices](#best-practices)
|
||||
6. [Testing & Verification](#testing--verification)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Module Architecture
|
||||
|
||||
### Design Principles
|
||||
|
||||
All modules in NoID Privacy follow these principles:
|
||||
|
||||
1. **Separation of Concerns** - Public vs Private functions
|
||||
2. **Backup Before Modify** - Always backup before changes
|
||||
3. **Comprehensive Testing** - Test functions for compliance
|
||||
4. **Structured Logging** - Use Write-Log for everything
|
||||
5. **Error Handling** - Try/Catch everywhere
|
||||
6. **PowerShell Best Practices** - Modern CIM, explicit returns, validated parameters
|
||||
|
||||
### Module Types
|
||||
|
||||
| Type | Example | Auto-Enabled | Use Case |
|
||||
|------|---------|--------------|----------|
|
||||
| **Core Hardening** | SecurityBaseline, ASR | ✅ Yes | Standard security settings, always safe |
|
||||
| **Service-Based** | DNS, Privacy | ⚠️ Optional | User choice (provider, mode) |
|
||||
| **Advanced/Aggressive** | AdvancedSecurity | ❌ Opt-in only | Breaking changes, requires testing |
|
||||
|
||||
---
|
||||
|
||||
## 📁 File Structure
|
||||
|
||||
### Required Structure
|
||||
|
||||
```
|
||||
Modules/
|
||||
└── YourModule/
|
||||
├── YourModule.psd1 # Module Manifest (REQUIRED)
|
||||
├── YourModule.psm1 # Module Loader (REQUIRED)
|
||||
├── Config/ # Configuration files
|
||||
│ ├── Feature1.json # Feature-specific config
|
||||
│ └── Feature2.json
|
||||
├── Private/ # Internal functions (not exported)
|
||||
│ ├── Set-Feature1.ps1 # Implementation functions
|
||||
│ ├── Set-Feature2.ps1
|
||||
│ ├── Test-Feature1.ps1 # Compliance test functions
|
||||
│ ├── Test-Feature2.ps1
|
||||
│ ├── Backup-YourModuleSettings.ps1 # Comprehensive backup
|
||||
│ └── Restore-YourModuleSettings.ps1 # Comprehensive restore
|
||||
└── Public/ # Exported functions (user-facing)
|
||||
├── Invoke-YourModule.ps1 # Main entry point
|
||||
└── Test-YourModule.ps1 # Compliance test entry point
|
||||
```
|
||||
|
||||
### Example: AdvancedSecurity Module Structure
|
||||
|
||||
```
|
||||
Modules/AdvancedSecurity/
|
||||
├── AdvancedSecurity.psd1 # Manifest with version 2.2.0
|
||||
├── AdvancedSecurity.psm1 # Loads Private/*.ps1 and Public/*.ps1
|
||||
├── Config/
|
||||
│ ├── RDP.json # RDP hardening config
|
||||
│ ├── Credentials.json # WDigest config
|
||||
│ └── AdminShares.json # Admin shares config
|
||||
├── Private/
|
||||
│ ├── Enable-RdpNLA.ps1 # RDP hardening implementation
|
||||
│ ├── Set-WDigestProtection.ps1 # WDigest implementation
|
||||
│ ├── Disable-AdminShares.ps1 # Admin shares implementation
|
||||
│ ├── Disable-RiskyPorts.ps1 # Firewall ports
|
||||
│ ├── Stop-RiskyServices.ps1 # Services management
|
||||
│ ├── Disable-WPAD.ps1 # WPAD disable
|
||||
│ ├── Disable-LegacyTLS.ps1 # TLS 1.0/1.1 disable
|
||||
│ ├── Remove-PowerShellV2.ps1 # PSv2 removal
|
||||
│ ├── Test-RdpSecurity.ps1 # RDP compliance test
|
||||
│ ├── Test-WDigest.ps1 # WDigest compliance test
|
||||
│ ├── Test-AdminShares.ps1 # Admin shares test
|
||||
│ ├── Test-RiskyPorts.ps1 # Ports compliance test
|
||||
│ ├── Test-RiskyServices.ps1 # Services compliance test
|
||||
│ ├── Backup-AdvancedSecuritySettings.ps1 # Full backup
|
||||
│ └── Restore-AdvancedSecuritySettings.ps1 # Full restore
|
||||
└── Public/
|
||||
├── Invoke-AdvancedSecurity.ps1 # Main function with profiles
|
||||
└── Test-AdvancedSecurity.ps1 # Compliance aggregator
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Core Integration Points
|
||||
|
||||
### 1. Module Manifest (.psd1)
|
||||
|
||||
**Template:**
|
||||
```powershell
|
||||
@{
|
||||
RootModule = 'YourModule.psm1'
|
||||
ModuleVersion = '2.2.0'
|
||||
GUID = 'YOUR-GUID-HERE' # Generate with [guid]::NewGuid()
|
||||
Author = 'Your Name'
|
||||
CompanyName = 'NoID Privacy'
|
||||
Copyright = '(c) 2025. All rights reserved.'
|
||||
Description = 'Brief description of what your module does'
|
||||
|
||||
PowerShellVersion = '5.1'
|
||||
|
||||
FunctionsToExport = @(
|
||||
'Invoke-YourModule',
|
||||
'Test-YourModule'
|
||||
)
|
||||
|
||||
CmdletsToExport = @()
|
||||
VariablesToExport = @()
|
||||
AliasesToExport = @()
|
||||
|
||||
PrivateData = @{
|
||||
PSData = @{
|
||||
Tags = @('Security', 'Hardening', 'Windows11')
|
||||
ProjectUri = 'https://github.com/yourusername/noid-privacy'
|
||||
ReleaseNotes = @"
|
||||
v2.2.0 - Initial Release
|
||||
- Feature 1
|
||||
- Feature 2
|
||||
"@
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Real Example (AdvancedSecurity.psd1):**
|
||||
```powershell
|
||||
@{
|
||||
RootModule = 'AdvancedSecurity.psm1'
|
||||
ModuleVersion = '2.2.0'
|
||||
GUID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
|
||||
Author = 'NexusOne23'
|
||||
Description = 'Advanced Security hardening beyond Microsoft Security Baseline'
|
||||
|
||||
FunctionsToExport = @(
|
||||
'Invoke-AdvancedSecurity',
|
||||
'Test-AdvancedSecurity'
|
||||
)
|
||||
|
||||
PrivateData = @{
|
||||
PSData = @{
|
||||
Tags = @('Security', 'Hardening', 'RDP', 'TLS', 'Windows11')
|
||||
ReleaseNotes = @"
|
||||
v2.2.0 - Production Release
|
||||
- RDP NLA enforcement + optional complete disable
|
||||
- WDigest credential protection
|
||||
- Administrative shares disable (domain-aware)
|
||||
- Risky firewall ports closure (LLMNR, NetBIOS, UPnP/SSDP)
|
||||
- Risky network services stop
|
||||
- Legacy TLS 1.0/1.1 disable
|
||||
- WPAD auto-discovery disable
|
||||
- PowerShell v2 removal
|
||||
- Profile system (Balanced/Enterprise/Maximum)
|
||||
- Comprehensive backup/restore
|
||||
"@
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Module Loader (.psm1)
|
||||
|
||||
**Template:**
|
||||
```powershell
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Module loader for YourModule
|
||||
#>
|
||||
|
||||
# Get module root path
|
||||
$ModuleRoot = $PSScriptRoot
|
||||
|
||||
# Import Private functions (not exported)
|
||||
$PrivateFunctions = Get-ChildItem -Path "$ModuleRoot\Private\*.ps1" -ErrorAction SilentlyContinue
|
||||
|
||||
foreach ($function in $PrivateFunctions) {
|
||||
try {
|
||||
. $function.FullName
|
||||
Write-Verbose "Imported private function: $($function.BaseName)"
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to import private function $($function.FullName): $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Import Public functions (will be exported)
|
||||
$PublicFunctions = Get-ChildItem -Path "$ModuleRoot\Public\*.ps1" -ErrorAction SilentlyContinue
|
||||
|
||||
foreach ($function in $PublicFunctions) {
|
||||
try {
|
||||
. $function.FullName
|
||||
Write-Verbose "Imported public function: $($function.BaseName)"
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to import public function $($function.FullName): $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Export only Public functions
|
||||
$PublicFunctionNames = $PublicFunctions | ForEach-Object { $_.BaseName }
|
||||
Export-ModuleMember -Function $PublicFunctionNames
|
||||
|
||||
Write-Verbose "YourModule loaded successfully. Exported functions: $($PublicFunctionNames -join ', ')"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Logging Integration
|
||||
|
||||
**Always use Write-Log from Core/Logger.ps1:**
|
||||
|
||||
```powershell
|
||||
# Import at module level if not using framework
|
||||
. "$PSScriptRoot\..\..\Core\Logger.ps1"
|
||||
|
||||
# In your functions
|
||||
Write-Log -Level INFO -Message "Starting feature configuration..." -Module "YourModule"
|
||||
Write-Log -Level SUCCESS -Message "Feature configured successfully" -Module "YourModule"
|
||||
Write-Log -Level WARNING -Message "Non-critical issue detected" -Module "YourModule"
|
||||
Write-Log -Level ERROR -Message "Failed to apply setting" -Module "YourModule" -Exception $_.Exception
|
||||
Write-Log -Level DEBUG -Message "Registry key set: $regPath" -Module "YourModule"
|
||||
```
|
||||
|
||||
**Log Levels:**
|
||||
- `INFO` - General progress
|
||||
- `SUCCESS` - Operation completed
|
||||
- `WARNING` - Non-critical issues
|
||||
- `ERROR` - Failures
|
||||
- `DEBUG` - Detailed diagnostic info
|
||||
|
||||
---
|
||||
|
||||
### 4. Backup Integration
|
||||
|
||||
**Use Core/Rollback.ps1 functions:**
|
||||
|
||||
```powershell
|
||||
# Backup registry key
|
||||
Backup-RegistryKey -KeyPath "HKLM:\SYSTEM\CurrentControlSet\..." -BackupName "YourFeature"
|
||||
|
||||
# Backup service
|
||||
Backup-ServiceConfiguration -ServiceName "YourService"
|
||||
|
||||
# Register custom backup data
|
||||
$backupData = @{
|
||||
YourData = $someValue
|
||||
BackupDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
} | ConvertTo-Json
|
||||
|
||||
Register-Backup -Type "YourFeature_Settings" -Data $backupData -Name "YourFeatureName"
|
||||
```
|
||||
|
||||
**Real Example (from AdvancedSecurity):**
|
||||
```powershell
|
||||
function Backup-AdvancedSecuritySettings {
|
||||
try {
|
||||
# Start backup session
|
||||
$backupSession = Start-ModuleBackup -ModuleName "AdvancedSecurity"
|
||||
|
||||
$backupCount = 0
|
||||
|
||||
# 1. RDP Settings
|
||||
$rdpBackup = Backup-RegistryKey -KeyPath "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server" -BackupName "RDP_Settings"
|
||||
if ($rdpBackup) { $backupCount++ }
|
||||
|
||||
# 2. Services
|
||||
$services = @("SSDPSRV", "upnphost", "lmhosts")
|
||||
foreach ($svc in $services) {
|
||||
$svcBackup = Backup-ServiceConfiguration -ServiceName $svc
|
||||
if ($svcBackup) { $backupCount++ }
|
||||
}
|
||||
|
||||
# 3. Custom data (firewall rules snapshot)
|
||||
$firewallRules = Get-NetFirewallRule | Where-Object { ... }
|
||||
$firewallData = @{
|
||||
Rules = $firewallRules
|
||||
BackupDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
} | ConvertTo-Json -Depth 10
|
||||
|
||||
$firewallBackup = Register-Backup -Type "Firewall_Rules" -Data $firewallData -Name "RiskyPorts_Firewall"
|
||||
if ($firewallBackup) { $backupCount++ }
|
||||
|
||||
return $backupCount
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Backup failed: $_" -Module "YourModule"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Implementation Checklist
|
||||
|
||||
### Phase 1: Planning & Structure
|
||||
|
||||
- [ ] Define module name and purpose
|
||||
- [ ] Choose module type (Core/Service/Advanced)
|
||||
- [ ] Plan features and settings
|
||||
- [ ] Create folder structure
|
||||
- [ ] Generate GUID for manifest
|
||||
- [ ] Write module manifest (.psd1)
|
||||
- [ ] Write module loader (.psm1)
|
||||
|
||||
### Phase 2: Configuration
|
||||
|
||||
- [ ] Create Config/*.json files for each feature
|
||||
- [ ] Document settings with rationale
|
||||
- [ ] Include impact assessment
|
||||
- [ ] Add Microsoft documentation links
|
||||
- [ ] Define default values
|
||||
|
||||
### Phase 3: Implementation Functions (Private/)
|
||||
|
||||
For each feature:
|
||||
- [ ] Create Set-FeatureName.ps1
|
||||
- [ ] Add comprehensive help block
|
||||
- [ ] Implement backup integration
|
||||
- [ ] Add registry/service/file modifications
|
||||
- [ ] Include error handling (try/catch)
|
||||
- [ ] Add logging at every step
|
||||
- [ ] Return $true on success, $false on failure
|
||||
- [ ] Create Test-FeatureName.ps1
|
||||
- [ ] Check compliance
|
||||
- [ ] Return PSCustomObject with status
|
||||
- [ ] Include Details array for human-readable output
|
||||
|
||||
### Phase 4: Aggregation Functions (Private/)
|
||||
|
||||
- [ ] Create Backup-YourModuleSettings.ps1
|
||||
- [ ] Backup all features
|
||||
- [ ] Use Start-ModuleBackup for session tracking
|
||||
- [ ] Return backup count
|
||||
- [ ] Create Restore-YourModuleSettings.ps1
|
||||
- [ ] Restore from backup directory
|
||||
- [ ] Handle missing backups gracefully
|
||||
- [ ] Log all restore operations
|
||||
|
||||
### Phase 5: Public Interface (Public/)
|
||||
|
||||
- [ ] Create Invoke-YourModule.ps1
|
||||
- [ ] Add [CmdletBinding(SupportsShouldProcess=$true)]
|
||||
- [ ] Define parameters (profiles, modes, switches)
|
||||
- [ ] Check for admin rights
|
||||
- [ ] Initialize backup system
|
||||
- [ ] Call Backup-YourModuleSettings (unless -SkipBackup)
|
||||
- [ ] Call all Set-Feature functions
|
||||
- [ ] Track applied/failed features
|
||||
- [ ] Return structured PSCustomObject
|
||||
- [ ] Provide user-friendly console output
|
||||
- [ ] Create Test-YourModule.ps1
|
||||
- [ ] Call all Test-Feature functions
|
||||
- [ ] Aggregate results
|
||||
- [ ] Calculate compliance percentage
|
||||
- [ ] Return array of compliance objects
|
||||
|
||||
### Phase 6: Integration
|
||||
|
||||
- [ ] Update config.json with module entry
|
||||
- [ ] Set appropriate status (IMPLEMENTED/PLANNED)
|
||||
- [ ] Set enabled (true for auto, false for opt-in)
|
||||
- [ ] Add priority number
|
||||
- [ ] Include description
|
||||
- [ ] Update README.md
|
||||
- [ ] Add module to appropriate table
|
||||
- [ ] Document features
|
||||
- [ ] Provide usage examples
|
||||
- [ ] Explain opt-in if applicable
|
||||
- [ ] Update Verify-Complete-Hardening.ps1 (if auto-enabled)
|
||||
|
||||
### Phase 7: Testing
|
||||
|
||||
- [ ] Test on clean Windows 11 VM
|
||||
- [ ] Verify backup creation
|
||||
- [ ] Verify settings application
|
||||
- [ ] Test compliance checks
|
||||
- [ ] Verify restore functionality
|
||||
- [ ] Test error scenarios
|
||||
- [ ] Test -WhatIf mode
|
||||
- [ ] Document any issues
|
||||
|
||||
---
|
||||
|
||||
## 📚 Best Practices
|
||||
|
||||
### 1. Use Modern PowerShell
|
||||
|
||||
**✅ DO:**
|
||||
```powershell
|
||||
$computerSystem = Get-CimInstance -ClassName Win32_ComputerSystem
|
||||
$adapters = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "IPEnabled = TRUE"
|
||||
```
|
||||
|
||||
**❌ DON'T:**
|
||||
```powershell
|
||||
$computerSystem = Get-WmiObject Win32_ComputerSystem # Deprecated
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Explicit Error Handling
|
||||
|
||||
**✅ DO:**
|
||||
```powershell
|
||||
try {
|
||||
Set-ItemProperty -Path $regPath -Name $valueName -Value $value -Type DWord -Force -ErrorAction Stop
|
||||
Write-Log -Level SUCCESS -Message "Registry value set successfully" -Module "YourModule"
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to set registry value: $_" -Module "YourModule" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
```
|
||||
|
||||
**❌ DON'T:**
|
||||
```powershell
|
||||
Set-ItemProperty -Path $regPath -Name $valueName -Value $value # No error handling!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Structured Returns
|
||||
|
||||
**✅ DO (for action functions):**
|
||||
```powershell
|
||||
function Set-YourFeature {
|
||||
try {
|
||||
# ... implementation ...
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed: $_" -Module "YourModule"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**✅ DO (for test functions):**
|
||||
```powershell
|
||||
function Test-YourFeature {
|
||||
try {
|
||||
$result = [PSCustomObject]@{
|
||||
Feature = "Your Feature Name"
|
||||
Status = "Secure" # or "Insecure" or "Partially Secure"
|
||||
Details = @()
|
||||
Compliant = $true # or $false
|
||||
}
|
||||
|
||||
# ... check settings ...
|
||||
|
||||
if ($settingCorrect) {
|
||||
$result.Details += "Setting is correct"
|
||||
} else {
|
||||
$result.Status = "Insecure"
|
||||
$result.Compliant = $false
|
||||
$result.Details += "Setting is incorrect!"
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Test failed: $_" -Module "YourModule"
|
||||
return [PSCustomObject]@{
|
||||
Feature = "Your Feature Name"
|
||||
Status = "Error"
|
||||
Details = @("Failed to test: $_")
|
||||
Compliant = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**✅ DO (for main public function with scripting support):**
|
||||
```powershell
|
||||
function Invoke-YourModule {
|
||||
try {
|
||||
# ... apply all features ...
|
||||
|
||||
# Return structured object
|
||||
return [PSCustomObject]@{
|
||||
Success = $true
|
||||
FeaturesApplied = $appliedFeatures
|
||||
FeaturesFailed = $failedFeatures
|
||||
TotalFeatures = $appliedFeatures.Count + $failedFeatures.Count
|
||||
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return [PSCustomObject]@{
|
||||
Success = $false
|
||||
ErrorMessage = $_.Exception.Message
|
||||
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Validated Parameters
|
||||
|
||||
**✅ DO:**
|
||||
```powershell
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[ValidateSet('Mode1', 'Mode2', 'Mode3')]
|
||||
[string]$Mode = 'Mode1',
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$Force
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Comprehensive Help
|
||||
|
||||
**✅ DO:**
|
||||
```powershell
|
||||
function Set-YourFeature {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Brief one-line description
|
||||
|
||||
.DESCRIPTION
|
||||
Detailed description of what the function does.
|
||||
|
||||
Why this feature is important:
|
||||
- Security benefit 1
|
||||
- Security benefit 2
|
||||
|
||||
Potential impact:
|
||||
- Impact consideration 1
|
||||
- Impact consideration 2
|
||||
|
||||
.PARAMETER ParameterName
|
||||
Description of parameter
|
||||
|
||||
.EXAMPLE
|
||||
Set-YourFeature
|
||||
Basic usage
|
||||
|
||||
.EXAMPLE
|
||||
Set-YourFeature -Force
|
||||
Force mode example
|
||||
|
||||
.NOTES
|
||||
Additional important information
|
||||
References: Microsoft KB article, CVE, etc.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(...)
|
||||
|
||||
# Implementation
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. WhatIf Support for Destructive Operations
|
||||
|
||||
**✅ DO:**
|
||||
```powershell
|
||||
function Invoke-YourModule {
|
||||
[CmdletBinding(SupportsShouldProcess=$true)]
|
||||
param(...)
|
||||
|
||||
if ($PSCmdlet.ShouldProcess("YourModule", "Apply hardening")) {
|
||||
# Apply changes
|
||||
}
|
||||
else {
|
||||
Write-Host "WhatIf mode - no changes applied" -ForegroundColor Yellow
|
||||
return $true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```powershell
|
||||
Invoke-YourModule -WhatIf # Dry run
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing & Verification
|
||||
|
||||
### Pester Test Runner (Framework-Wide)
|
||||
|
||||
```powershell
|
||||
# 1) Test-Umgebung vorbereiten (einmalig)
|
||||
.\Tests\Setup-TestEnvironment.ps1
|
||||
|
||||
# 2) Alle Tests (Unit + Integration + Validation) ausführen
|
||||
.\Tests\Run-Tests.ps1
|
||||
|
||||
# 3) Nur bestimmte Testtypen ausführen
|
||||
.\Tests\Run-Tests.ps1 -TestType Unit
|
||||
.\Tests\Run-Tests.ps1 -TestType Integration
|
||||
.\Tests\Run-Tests.ps1 -TestType Validation
|
||||
|
||||
# 4) Mit Code Coverage
|
||||
.\Tests\Run-Tests.ps1 -TestType All -CodeCoverage
|
||||
|
||||
# Alternative: Einfacher Runner für alle Tests
|
||||
.\Tests\Run-AllTests.ps1
|
||||
|
||||
### Manual Testing Checklist
|
||||
|
||||
1. **Clean Windows 11 VM**
|
||||
```powershell
|
||||
# Check Windows version
|
||||
winver # Should be 24H2 or 25H2
|
||||
|
||||
# Check PowerShell version
|
||||
$PSVersionTable.PSVersion # Should be 5.1+
|
||||
```
|
||||
|
||||
2. **Import Module**
|
||||
```powershell
|
||||
Import-Module .\Modules\YourModule\YourModule.psd1 -Force -Verbose
|
||||
```
|
||||
|
||||
3. **Test Compliance (Before)**
|
||||
```powershell
|
||||
$before = Test-YourModule
|
||||
$before | Format-Table
|
||||
```
|
||||
|
||||
4. **Apply Hardening**
|
||||
```powershell
|
||||
$result = Invoke-YourModule
|
||||
$result
|
||||
```
|
||||
|
||||
5. **Test Compliance (After)**
|
||||
```powershell
|
||||
$after = Test-YourModule
|
||||
$after | Format-Table
|
||||
|
||||
# Check improvement
|
||||
$beforeCompliant = ($before | Where-Object { $_.Compliant }).Count
|
||||
$afterCompliant = ($after | Where-Object { $_.Compliant }).Count
|
||||
Write-Host "Before: $beforeCompliant compliant"
|
||||
Write-Host "After: $afterCompliant compliant"
|
||||
```
|
||||
|
||||
6. **Test Restore**
|
||||
```powershell
|
||||
Restore-YourModuleSettings
|
||||
|
||||
$restored = Test-YourModule
|
||||
$restored | Format-Table
|
||||
```
|
||||
|
||||
7. **Test WhatIf**
|
||||
```powershell
|
||||
Invoke-YourModule -WhatIf
|
||||
```
|
||||
|
||||
### Automated Testing Template
|
||||
|
||||
```powershell
|
||||
# Test-YourModule-Integration.ps1
|
||||
|
||||
Describe "YourModule Integration Tests" {
|
||||
BeforeAll {
|
||||
Import-Module ".\Modules\YourModule\YourModule.psd1" -Force
|
||||
}
|
||||
|
||||
Context "Module Loading" {
|
||||
It "Should export public functions" {
|
||||
$commands = Get-Command -Module YourModule
|
||||
$commands.Count | Should -Be 2
|
||||
$commands.Name | Should -Contain 'Invoke-YourModule'
|
||||
$commands.Name | Should -Contain 'Test-YourModule'
|
||||
}
|
||||
}
|
||||
|
||||
Context "Compliance Testing" {
|
||||
It "Should return compliance results" {
|
||||
$results = Test-YourModule
|
||||
$results | Should -Not -BeNullOrEmpty
|
||||
$results[0].PSObject.Properties.Name | Should -Contain 'Feature'
|
||||
$results[0].PSObject.Properties.Name | Should -Contain 'Status'
|
||||
$results[0].PSObject.Properties.Name | Should -Contain 'Compliant'
|
||||
}
|
||||
}
|
||||
|
||||
Context "Application" {
|
||||
It "Should apply hardening successfully" {
|
||||
$result = Invoke-YourModule
|
||||
$result.Success | Should -Be $true
|
||||
$result.FeaturesApplied.Count | Should -BeGreaterThan 0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Real-World Example: AdvancedSecurity Module
|
||||
|
||||
The **AdvancedSecurity** module is the gold standard reference implementation. Study these files:
|
||||
|
||||
### Key Files to Study
|
||||
|
||||
1. **Manifest & Loader**
|
||||
- `AdvancedSecurity.psd1` - Version, exports, metadata
|
||||
- `AdvancedSecurity.psm1` - Function loading pattern
|
||||
|
||||
2. **Feature Implementation**
|
||||
- `Private/Enable-RdpNLA.ps1` - Registry modification with backup
|
||||
- `Private/Disable-AdminShares.ps1` - Domain-aware safety checks
|
||||
- `Private/Stop-RiskyServices.ps1` - Service management with dependencies
|
||||
|
||||
3. **Testing**
|
||||
- `Private/Test-RdpSecurity.ps1` - Compliance check pattern
|
||||
- `Public/Test-AdvancedSecurity.ps1` - Test aggregation
|
||||
|
||||
4. **Public Interface**
|
||||
- `Public/Invoke-AdvancedSecurity.ps1` - Profile system, backup, structured returns
|
||||
|
||||
5. **Backup/Restore**
|
||||
- `Private/Backup-AdvancedSecuritySettings.ps1` - Comprehensive backup
|
||||
- `Private/Restore-AdvancedSecuritySettings.ps1` - Full restore logic
|
||||
|
||||
---
|
||||
|
||||
## 📝 Summary
|
||||
|
||||
### Key Takeaways
|
||||
|
||||
1. **Structure Matters** - Follow the Private/Public separation
|
||||
2. **Always Backup** - Before ANY modification
|
||||
3. **Log Everything** - Use Write-Log consistently
|
||||
4. **Error Handling** - Try/Catch everywhere
|
||||
5. **Explicit Returns** - $true/$false for actions, PSCustomObject for tests
|
||||
6. **Modern PowerShell** - CIM instead of WMI
|
||||
7. **User-Friendly** - Clear console output + structured data for scripts
|
||||
8. **Test Thoroughly** - Clean VM, before/after, restore
|
||||
|
||||
### Quick Start for New Module
|
||||
|
||||
```powershell
|
||||
# 1. Create structure
|
||||
mkdir "Modules\YourModule\Private"
|
||||
mkdir "Modules\YourModule\Public"
|
||||
mkdir "Modules\YourModule\Config"
|
||||
|
||||
# 2. Generate GUID
|
||||
[guid]::NewGuid()
|
||||
|
||||
# 3. Create manifest (use template above)
|
||||
# 4. Create loader (use template above)
|
||||
# 5. Implement Private functions (Set-*, Test-*, Backup-*, Restore-*)
|
||||
# 6. Implement Public functions (Invoke-*, Test-*)
|
||||
# 7. Update config.json
|
||||
# 8. Update README.md
|
||||
# 9. Test on clean VM
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Questions? Study AdvancedSecurity v2.2.0 - it's the reference implementation!** 🎯
|
||||
466
Core/Config.ps1
Normal file
466
Core/Config.ps1
Normal file
|
|
@ -0,0 +1,466 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Configuration management for NoID Privacy Framework
|
||||
|
||||
.DESCRIPTION
|
||||
Handles loading, saving, and validating configuration settings
|
||||
from JSON configuration files.
|
||||
|
||||
.NOTES
|
||||
Author: NexusOne23
|
||||
Version: 2.2.0
|
||||
Requires: PowerShell 5.1+
|
||||
#>
|
||||
|
||||
# Global configuration object
|
||||
$script:Config = $null
|
||||
|
||||
function Initialize-Config {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Initialize configuration system
|
||||
|
||||
.PARAMETER ConfigPath
|
||||
Path to configuration file (JSON)
|
||||
|
||||
.PARAMETER CreateDefault
|
||||
Create default configuration if file doesn't exist
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$ConfigPath = (Join-Path $PSScriptRoot "..\config.json"),
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[bool]$CreateDefault = $true
|
||||
)
|
||||
|
||||
# Check if config file exists
|
||||
if (-not (Test-Path -Path $ConfigPath)) {
|
||||
if ($CreateDefault) {
|
||||
Write-Log -Level INFO -Message "Configuration file not found, creating default" -Module "Config"
|
||||
New-DefaultConfig -Path $ConfigPath
|
||||
}
|
||||
else {
|
||||
throw "Configuration file not found: $ConfigPath"
|
||||
}
|
||||
}
|
||||
|
||||
# Load configuration
|
||||
try {
|
||||
$configContent = Get-Content -Path $ConfigPath -Raw -Encoding UTF8
|
||||
$script:Config = $configContent | ConvertFrom-Json
|
||||
|
||||
Write-Log -Level INFO -Message "Configuration loaded successfully" -Module "Config"
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to load configuration" -Module "Config" -Exception $_.Exception
|
||||
throw
|
||||
}
|
||||
|
||||
# Validate configuration
|
||||
if (-not (Test-ConfigValid)) {
|
||||
throw "Configuration validation failed"
|
||||
}
|
||||
}
|
||||
|
||||
function New-DefaultConfig {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Create default configuration file
|
||||
|
||||
.PARAMETER Path
|
||||
Path where configuration file should be created
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
$defaultConfig = @{
|
||||
version = "2.2.0"
|
||||
modules = @{
|
||||
SecurityBaseline = @{
|
||||
enabled = $true
|
||||
priority = 1
|
||||
status = "IMPLEMENTED"
|
||||
}
|
||||
ASR = @{
|
||||
enabled = $true
|
||||
priority = 2
|
||||
status = "IMPLEMENTED"
|
||||
}
|
||||
DNS = @{
|
||||
enabled = $true
|
||||
priority = 3
|
||||
provider = ""
|
||||
status = "IMPLEMENTED"
|
||||
}
|
||||
Privacy = @{
|
||||
enabled = $true
|
||||
priority = 4
|
||||
mode = ""
|
||||
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: 20 security policies including SmartScreen enforcement, site isolation, SSL/TLS hardening, extension blocklist, IE Mode restrictions, and Spectre mitigations. No LGPO.exe dependency."
|
||||
version = "2.2.0"
|
||||
baseline = "Edge v139"
|
||||
policies = 20
|
||||
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: RDP NLA/Disable, WDigest protection, Admin Shares disable, Risky ports/services, Legacy TLS disable, WPAD disable, PowerShell v2 removal, SRP .lnk protection, Windows Update (3 GUI settings), Finger Protocol block. Opt-in by design (use -SecurityProfile Balanced/Enterprise/Maximum)"
|
||||
version = "2.2.0"
|
||||
policies = 36
|
||||
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
|
||||
}
|
||||
profiles = @("Balanced", "Enterprise", "Maximum")
|
||||
}
|
||||
}
|
||||
options = @{
|
||||
dryRun = $false
|
||||
createBackup = $true
|
||||
verboseLogging = $true
|
||||
autoReboot = $false
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$defaultConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $Path -Encoding UTF8 | Out-Null
|
||||
Write-Log -Level SUCCESS -Message "Default configuration created: $Path" -Module "Config"
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to create default configuration" -Module "Config" -Exception $_.Exception
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
function Test-ConfigValid {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Validate configuration structure and values
|
||||
|
||||
.OUTPUTS
|
||||
Boolean indicating if configuration is valid
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([bool])]
|
||||
param()
|
||||
|
||||
if ($null -eq $script:Config) {
|
||||
Write-Log -Level ERROR -Message "Configuration is null" -Module "Config"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Check required properties
|
||||
$requiredProps = @('version', 'modules', 'options')
|
||||
foreach ($prop in $requiredProps) {
|
||||
if (-not (Get-Member -InputObject $script:Config -Name $prop -MemberType NoteProperty)) {
|
||||
Write-Log -Level ERROR -Message "Missing required property: $prop" -Module "Config"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Validate modules
|
||||
if ($null -eq $script:Config.modules) {
|
||||
Write-Log -Level ERROR -Message "Modules configuration is missing" -Module "Config"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Validate each module configuration
|
||||
foreach ($prop in $script:Config.modules.PSObject.Properties) {
|
||||
$moduleName = $prop.Name
|
||||
$moduleConfig = $prop.Value
|
||||
|
||||
# Check required module properties
|
||||
$requiredModuleProps = @('enabled', 'priority')
|
||||
foreach ($moduleProp in $requiredModuleProps) {
|
||||
if (-not (Get-Member -InputObject $moduleConfig -Name $moduleProp -MemberType NoteProperty)) {
|
||||
Write-Log -Level ERROR -Message "Module '$moduleName' missing required property: $moduleProp" -Module "Config"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
# Validate property types
|
||||
if ($moduleConfig.enabled -isnot [bool]) {
|
||||
Write-Log -Level ERROR -Message "Module '$moduleName' property 'enabled' must be boolean" -Module "Config"
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($moduleConfig.priority -isnot [int] -and $moduleConfig.priority -isnot [long]) {
|
||||
Write-Log -Level ERROR -Message "Module '$moduleName' property 'priority' must be integer" -Module "Config"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Module-specific validation
|
||||
if ($moduleName -eq "DNS") {
|
||||
# Validate DNS provider if specified
|
||||
if (Get-Member -InputObject $moduleConfig -Name 'provider' -MemberType NoteProperty) {
|
||||
$validProviders = @('Cloudflare', 'Quad9', 'AdGuard', '', 'KEEP')
|
||||
|
||||
# Empty string means interactive selection
|
||||
if ($moduleConfig.provider -eq '') {
|
||||
Write-Log -Level DEBUG -Message "DNS provider not specified - will prompt user for selection" -Module "Config"
|
||||
}
|
||||
elseif ($moduleConfig.provider -eq 'KEEP') {
|
||||
Write-Log -Level DEBUG -Message "DNS provider set to KEEP - will detect and preserve current provider" -Module "Config"
|
||||
}
|
||||
elseif ($validProviders -notcontains $moduleConfig.provider) {
|
||||
Write-Log -Level ERROR -Message "DNS module has invalid provider: '$($moduleConfig.provider)'. Valid providers: $($validProviders -join ', ')" -Module "Config"
|
||||
return $false
|
||||
}
|
||||
else {
|
||||
Write-Log -Level DEBUG -Message "DNS provider validated: $($moduleConfig.provider)" -Module "Config"
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Provider property missing - will prompt for selection
|
||||
Write-Log -Level DEBUG -Message "DNS provider not specified - will prompt user for selection" -Module "Config"
|
||||
}
|
||||
}
|
||||
|
||||
if ($moduleName -eq "Privacy") {
|
||||
# Validate Privacy mode if specified
|
||||
if (Get-Member -InputObject $moduleConfig -Name 'mode' -MemberType NoteProperty) {
|
||||
$validModes = @('MSRecommended', 'Strict', 'Paranoid', '')
|
||||
|
||||
# Empty string means interactive selection
|
||||
if ($moduleConfig.mode -eq '') {
|
||||
Write-Log -Level DEBUG -Message "Privacy mode not specified - will prompt user for selection" -Module "Config"
|
||||
}
|
||||
elseif ($validModes -notcontains $moduleConfig.mode) {
|
||||
Write-Log -Level ERROR -Message "Privacy module has invalid mode: '$($moduleConfig.mode)'. Valid modes: $($validModes -join ', ')" -Module "Config"
|
||||
return $false
|
||||
}
|
||||
else {
|
||||
Write-Log -Level DEBUG -Message "Privacy mode validated: $($moduleConfig.mode)" -Module "Config"
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Mode property missing - will prompt for selection
|
||||
Write-Log -Level DEBUG -Message "Privacy mode not specified - will prompt user for selection" -Module "Config"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Validate options
|
||||
if ($null -eq $script:Config.options) {
|
||||
Write-Log -Level ERROR -Message "Options configuration is missing" -Module "Config"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Check required option properties
|
||||
$requiredOptions = @('dryRun', 'createBackup', 'verboseLogging', 'autoReboot')
|
||||
foreach ($option in $requiredOptions) {
|
||||
if (-not (Get-Member -InputObject $script:Config.options -Name $option -MemberType NoteProperty)) {
|
||||
Write-Log -Level ERROR -Message "Missing required option: $option" -Module "Config"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log -Level INFO -Message "Configuration validation passed" -Module "Config"
|
||||
return $true
|
||||
}
|
||||
|
||||
function Get-Config {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get current configuration object
|
||||
|
||||
.OUTPUTS
|
||||
Configuration object
|
||||
#>
|
||||
return $script:Config
|
||||
}
|
||||
|
||||
function Get-ModuleConfig {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get configuration for specific module
|
||||
|
||||
.PARAMETER ModuleName
|
||||
Name of the module
|
||||
|
||||
.OUTPUTS
|
||||
Module configuration object or $null if not found
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ModuleName
|
||||
)
|
||||
|
||||
if ($null -eq $script:Config -or $null -eq $script:Config.modules) {
|
||||
Write-Log -Level WARNING -Message "Configuration not initialized" -Module "Config"
|
||||
return $null
|
||||
}
|
||||
|
||||
$moduleConfig = $script:Config.modules | Get-Member -Name $ModuleName -MemberType NoteProperty
|
||||
if ($null -eq $moduleConfig) {
|
||||
Write-Log -Level WARNING -Message "Module configuration not found: $ModuleName" -Module "Config"
|
||||
return $null
|
||||
}
|
||||
|
||||
return $script:Config.modules.$ModuleName
|
||||
}
|
||||
|
||||
function Test-ModuleAvailability {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Check if a module is actually implemented and available
|
||||
|
||||
.DESCRIPTION
|
||||
Checks if module directory exists and contains the required .psd1 manifest file
|
||||
|
||||
.PARAMETER ModuleName
|
||||
Name of the module to check
|
||||
|
||||
.OUTPUTS
|
||||
Boolean - True if module is implemented, False otherwise
|
||||
|
||||
.EXAMPLE
|
||||
Test-ModuleAvailability -ModuleName "SecurityBaseline"
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([bool])]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ModuleName
|
||||
)
|
||||
|
||||
# Get framework root (3 levels up from Config.ps1)
|
||||
$frameworkRoot = Split-Path $PSScriptRoot -Parent
|
||||
$modulePath = Join-Path $frameworkRoot "Modules\$ModuleName"
|
||||
$manifestPath = Join-Path $modulePath "$ModuleName.psd1"
|
||||
|
||||
# Check if module directory exists
|
||||
if (-not (Test-Path $modulePath)) {
|
||||
Write-Log -Level DEBUG -Message "Module directory not found: $modulePath" -Module "Config"
|
||||
return $false
|
||||
}
|
||||
|
||||
# Check if module manifest exists
|
||||
if (-not (Test-Path $manifestPath)) {
|
||||
Write-Log -Level DEBUG -Message "Module manifest not found: $manifestPath" -Module "Config"
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-Log -Level DEBUG -Message "Module $ModuleName is available" -Module "Config"
|
||||
return $true
|
||||
}
|
||||
|
||||
function Get-EnabledModules {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get list of enabled modules sorted by priority
|
||||
|
||||
.DESCRIPTION
|
||||
Returns only modules that are both enabled in config AND actually implemented
|
||||
|
||||
.OUTPUTS
|
||||
Array of enabled module names sorted by priority
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([string[]])]
|
||||
param()
|
||||
|
||||
if ($null -eq $script:Config -or $null -eq $script:Config.modules) {
|
||||
Write-Log -Level WARNING -Message "Configuration not initialized" -Module "Config"
|
||||
return @()
|
||||
}
|
||||
|
||||
$enabledModules = @()
|
||||
|
||||
foreach ($prop in $script:Config.modules.PSObject.Properties) {
|
||||
$moduleName = $prop.Name
|
||||
$moduleConfig = $prop.Value
|
||||
|
||||
if ($moduleConfig.enabled -eq $true) {
|
||||
# Check if module is actually implemented
|
||||
if (Test-ModuleAvailability -ModuleName $moduleName) {
|
||||
$enabledModules += [PSCustomObject]@{
|
||||
Name = $moduleName
|
||||
Priority = $moduleConfig.priority
|
||||
}
|
||||
}
|
||||
else {
|
||||
$status = if ($moduleConfig.PSObject.Properties.Name -contains 'status') { $moduleConfig.status } else { 'UNKNOWN' }
|
||||
Write-Log -Level WARNING -Message "Module '$moduleName' is enabled in config but not implemented (Status: $status)" -Module "Config"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Sort by priority
|
||||
$sorted = $enabledModules | Sort-Object -Property Priority
|
||||
|
||||
return $sorted | ForEach-Object { $_.Name }
|
||||
}
|
||||
|
||||
function Set-ModuleEnabled {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Enable or disable a module
|
||||
|
||||
.PARAMETER ModuleName
|
||||
Name of the module
|
||||
|
||||
.PARAMETER Enabled
|
||||
Enable (true) or disable (false) the module
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ModuleName,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[bool]$Enabled
|
||||
)
|
||||
|
||||
if ($null -eq $script:Config -or $null -eq $script:Config.modules) {
|
||||
throw "Configuration not initialized"
|
||||
}
|
||||
|
||||
$moduleConfig = $script:Config.modules | Get-Member -Name $ModuleName -MemberType NoteProperty
|
||||
if ($null -eq $moduleConfig) {
|
||||
throw "Module not found: $ModuleName"
|
||||
}
|
||||
|
||||
$script:Config.modules.$ModuleName.enabled = $Enabled
|
||||
Write-Log -Level INFO -Message "Module '$ModuleName' set to: $Enabled" -Module "Config"
|
||||
}
|
||||
|
||||
# Note: Export-ModuleMember not used - this script is dot-sourced, not imported as module
|
||||
927
Core/Framework.ps1
Normal file
927
Core/Framework.ps1
Normal file
|
|
@ -0,0 +1,927 @@
|
|||
<#
|
||||
.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: Cloudflare, Quad9, 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: 37 settings (default, max compatibility)" -ForegroundColor DarkGray
|
||||
Write-Host " - Strict/Paranoid: 37-40 settings + services disabled" -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 13 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 (13 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
|
||||
}
|
||||
}
|
||||
366
Core/Logger.ps1
Normal file
366
Core/Logger.ps1
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Unified logging system for NoID Privacy Framework
|
||||
|
||||
.DESCRIPTION
|
||||
Provides centralized logging functionality with multiple severity levels,
|
||||
file output, and optional console output with color coding.
|
||||
|
||||
.NOTES
|
||||
Author: NexusOne23
|
||||
Version: 2.2.0
|
||||
Requires: PowerShell 5.1+
|
||||
#>
|
||||
|
||||
# Log severity levels
|
||||
enum LogLevel {
|
||||
DEBUG = 0
|
||||
INFO = 1
|
||||
WARNING = 2
|
||||
ERROR = 3
|
||||
SUCCESS = 4
|
||||
}
|
||||
|
||||
# Global logger configuration - MUST be $global: for cross-module session sharing
|
||||
# Using $script: would create separate log files per Import-Module call!
|
||||
# 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
|
||||
TimestampFormat = "yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
}
|
||||
|
||||
function Initialize-Logger {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Initialize the logging system
|
||||
|
||||
.PARAMETER LogDirectory
|
||||
Directory path for log files
|
||||
|
||||
.PARAMETER MinimumLevel
|
||||
Minimum log level to record (DEBUG, INFO, WARNING, ERROR, SUCCESS)
|
||||
|
||||
.PARAMETER EnableConsole
|
||||
Enable console output
|
||||
|
||||
.PARAMETER EnableFile
|
||||
Enable file output
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$LogDirectory = (Join-Path $PSScriptRoot "..\Logs"),
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[LogLevel]$MinimumLevel = [LogLevel]::INFO,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[bool]$EnableConsole = $true,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[bool]$EnableFile = $true
|
||||
)
|
||||
|
||||
# Reuse existing session if already initialized
|
||||
if ($global:LoggerConfig.LogFilePath -and (Test-Path -Path $global:LoggerConfig.LogFilePath)) {
|
||||
Write-Host "[Logger] Reusing existing log session: $($global:LoggerConfig.LogFilePath)" -ForegroundColor DarkGray
|
||||
return
|
||||
}
|
||||
|
||||
# Create log directory if it doesn't exist
|
||||
if ($EnableFile) {
|
||||
if (-not (Test-Path -Path $LogDirectory)) {
|
||||
try {
|
||||
New-Item -ItemType Directory -Path $LogDirectory -Force -ErrorAction Stop | Out-Null
|
||||
}
|
||||
catch {
|
||||
Write-Host "[ERROR] Failed to create log directory: $LogDirectory" -ForegroundColor Red
|
||||
Write-Host "[ERROR] Exception: $_" -ForegroundColor Red
|
||||
$EnableFile = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Generate log file name with timestamp
|
||||
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
|
||||
$logFileName = "NoIDPrivacy_$timestamp.log"
|
||||
$global:LoggerConfig.LogFilePath = Join-Path $LogDirectory $logFileName
|
||||
$global:LoggerConfig.MinimumLevel = $MinimumLevel
|
||||
$global:LoggerConfig.EnableConsole = $EnableConsole
|
||||
$global:LoggerConfig.EnableFile = $EnableFile
|
||||
|
||||
# Test if we can write to the log file
|
||||
if ($EnableFile) {
|
||||
try {
|
||||
"# NoID Privacy Log File" | Out-File -FilePath $global:LoggerConfig.LogFilePath -Encoding UTF8 -ErrorAction Stop
|
||||
}
|
||||
catch {
|
||||
Write-Host "[ERROR] Failed to create log file: $($global:LoggerConfig.LogFilePath)" -ForegroundColor Red
|
||||
Write-Host "[ERROR] Exception: $_" -ForegroundColor Red
|
||||
$global:LoggerConfig.EnableFile = $false
|
||||
}
|
||||
}
|
||||
|
||||
# Write initial log entry
|
||||
Write-Log -Level INFO -Message "Logger initialized" -Module "Logger"
|
||||
Write-Log -Level INFO -Message "Log file: $($global:LoggerConfig.LogFilePath)" -Module "Logger"
|
||||
}
|
||||
|
||||
function Write-Log {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Write a log entry
|
||||
|
||||
.PARAMETER Level
|
||||
Log severity level
|
||||
|
||||
.PARAMETER Message
|
||||
Log message content
|
||||
|
||||
.PARAMETER Module
|
||||
Module or component name generating the log
|
||||
|
||||
.PARAMETER Exception
|
||||
Optional exception object for error logging
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[LogLevel]$Level,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Message,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Module = "Framework",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[System.Exception]$Exception = $null
|
||||
)
|
||||
|
||||
# Check if level meets minimum threshold
|
||||
if ($Level -lt $global:LoggerConfig.MinimumLevel) {
|
||||
return
|
||||
}
|
||||
|
||||
# Format timestamp
|
||||
$timestamp = Get-Date -Format $global:LoggerConfig.TimestampFormat
|
||||
|
||||
# Build log entry
|
||||
$logEntry = "[$timestamp] [$Level] [$Module] $Message"
|
||||
|
||||
# Add exception details if present
|
||||
if ($null -ne $Exception) {
|
||||
$logEntry += "`n Exception: $($Exception.Message)"
|
||||
$logEntry += "`n StackTrace: $($Exception.StackTrace)"
|
||||
}
|
||||
|
||||
# Write to file with robust retry logic
|
||||
if ($global:LoggerConfig.EnableFile -and $global:LoggerConfig.LogFilePath) {
|
||||
$maxRetries = 5
|
||||
$retryDelayMs = 100
|
||||
$writeSuccess = $false
|
||||
$lastError = $null
|
||||
|
||||
for ($i = 0; $i -lt $maxRetries; $i++) {
|
||||
try {
|
||||
Add-Content -Path $global:LoggerConfig.LogFilePath -Value $logEntry -Encoding UTF8 -ErrorAction Stop
|
||||
$writeSuccess = $true
|
||||
break
|
||||
}
|
||||
catch {
|
||||
$lastError = $_
|
||||
Start-Sleep -Milliseconds $retryDelayMs
|
||||
# Exponential backoff
|
||||
$retryDelayMs *= 2
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $writeSuccess) {
|
||||
# Write error to console with detailed info only after all retries failed
|
||||
Write-Host "[FILE WRITE ERROR] Failed to write to log file after $maxRetries attempts: $($global:LoggerConfig.LogFilePath)" -ForegroundColor Red
|
||||
Write-Host "[FILE WRITE ERROR] Last Exception: $lastError" -ForegroundColor Red
|
||||
# Disable file logging to prevent spam
|
||||
$global:LoggerConfig.EnableFile = $false
|
||||
}
|
||||
}
|
||||
|
||||
# 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]::WARNING) { "Yellow" }
|
||||
([LogLevel]::ERROR) { "Red" }
|
||||
([LogLevel]::SUCCESS) { "Green" }
|
||||
default { "White" }
|
||||
}
|
||||
|
||||
Write-Host $logEntry -ForegroundColor $consoleColor
|
||||
}
|
||||
}
|
||||
|
||||
function Get-LogFilePath {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get the current log file path
|
||||
|
||||
.OUTPUTS
|
||||
String containing the log file path
|
||||
#>
|
||||
return $global:LoggerConfig.LogFilePath
|
||||
}
|
||||
|
||||
function Get-ErrorContext {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extract detailed error context from PowerShell error record
|
||||
|
||||
.DESCRIPTION
|
||||
Provides comprehensive error information including message, location,
|
||||
line number, command, and stack trace for better debugging.
|
||||
|
||||
.PARAMETER ErrorRecord
|
||||
The error record to analyze (defaults to $_ in catch block)
|
||||
|
||||
.OUTPUTS
|
||||
Hashtable with error details
|
||||
|
||||
.EXAMPLE
|
||||
catch {
|
||||
$errorContext = Get-ErrorContext -ErrorRecord $_
|
||||
Write-Log -Level ERROR -Message $errorContext.Summary -Module "MyModule"
|
||||
}
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([hashtable])]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[System.Management.Automation.ErrorRecord]$ErrorRecord = $_
|
||||
)
|
||||
|
||||
$context = @{
|
||||
Message = ""
|
||||
Exception = ""
|
||||
Category = ""
|
||||
TargetObject = ""
|
||||
ScriptName = ""
|
||||
LineNumber = 0
|
||||
Command = ""
|
||||
StackTrace = ""
|
||||
Summary = ""
|
||||
}
|
||||
|
||||
if ($null -eq $ErrorRecord) {
|
||||
$context.Summary = "No error record available"
|
||||
return $context
|
||||
}
|
||||
|
||||
# Extract basic error information
|
||||
$context.Message = $ErrorRecord.Exception.Message
|
||||
$context.Exception = $ErrorRecord.Exception.GetType().FullName
|
||||
$context.Category = $ErrorRecord.CategoryInfo.Category.ToString()
|
||||
$context.TargetObject = if ($ErrorRecord.TargetObject) { $ErrorRecord.TargetObject.ToString() } else { "N/A" }
|
||||
|
||||
# Extract script location information
|
||||
if ($ErrorRecord.InvocationInfo) {
|
||||
$context.ScriptName = if ($ErrorRecord.InvocationInfo.ScriptName) {
|
||||
Split-Path -Leaf $ErrorRecord.InvocationInfo.ScriptName
|
||||
} else {
|
||||
"N/A"
|
||||
}
|
||||
$context.LineNumber = $ErrorRecord.InvocationInfo.ScriptLineNumber
|
||||
$context.Command = if ($ErrorRecord.InvocationInfo.MyCommand) {
|
||||
$ErrorRecord.InvocationInfo.MyCommand.Name
|
||||
} else {
|
||||
"N/A"
|
||||
}
|
||||
}
|
||||
|
||||
# Extract stack trace
|
||||
if ($ErrorRecord.ScriptStackTrace) {
|
||||
$context.StackTrace = $ErrorRecord.ScriptStackTrace
|
||||
}
|
||||
|
||||
# Build comprehensive summary
|
||||
$summary = "$($context.Message)"
|
||||
|
||||
if ($context.ScriptName -and $context.LineNumber -gt 0) {
|
||||
$summary += " [File: $($context.ScriptName), Line: $($context.LineNumber)]"
|
||||
}
|
||||
|
||||
if ($context.Command) {
|
||||
$summary += " [Command: $($context.Command)]"
|
||||
}
|
||||
|
||||
if ($context.Category -ne "NotSpecified") {
|
||||
$summary += " [Category: $($context.Category)]"
|
||||
}
|
||||
|
||||
$context.Summary = $summary
|
||||
|
||||
return $context
|
||||
}
|
||||
|
||||
function Write-ErrorLog {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Write a comprehensive error log entry with full context
|
||||
|
||||
.DESCRIPTION
|
||||
Convenience function that combines error context extraction
|
||||
and logging in one call. Provides detailed error information.
|
||||
|
||||
.PARAMETER Message
|
||||
Custom error message (will be prefixed to error details)
|
||||
|
||||
.PARAMETER Module
|
||||
Module or component name
|
||||
|
||||
.PARAMETER ErrorRecord
|
||||
The error record to log (defaults to $_ in catch block)
|
||||
|
||||
.PARAMETER IncludeStackTrace
|
||||
Include full stack trace in log (default: true)
|
||||
|
||||
.EXAMPLE
|
||||
catch {
|
||||
Write-ErrorLog -Message "Failed to apply security settings" -Module "SecurityBaseline" -ErrorRecord $_
|
||||
}
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Message,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Module = "Framework",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[System.Management.Automation.ErrorRecord]$ErrorRecord = $_,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[bool]$IncludeStackTrace = $true
|
||||
)
|
||||
|
||||
$errorContext = Get-ErrorContext -ErrorRecord $ErrorRecord
|
||||
|
||||
# Build comprehensive error message
|
||||
$fullMessage = "$Message - $($errorContext.Summary)"
|
||||
|
||||
# Log error with basic info
|
||||
Write-Log -Level ERROR -Message $fullMessage -Module $Module -Exception $ErrorRecord.Exception
|
||||
|
||||
# Log additional context if available
|
||||
if ($IncludeStackTrace -and $errorContext.StackTrace) {
|
||||
Write-Log -Level DEBUG -Message "Stack Trace: $($errorContext.StackTrace)" -Module $Module
|
||||
}
|
||||
}
|
||||
|
||||
# Note: Export-ModuleMember not used - this script is dot-sourced, not imported as module
|
||||
# All functions are automatically available when dot-sourced
|
||||
206
Core/NonInteractive.ps1
Normal file
206
Core/NonInteractive.ps1
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
#Requires -Version 5.1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
NonInteractive mode helper functions for NoID Privacy GUI integration
|
||||
|
||||
.DESCRIPTION
|
||||
Provides helper functions to check if running in NonInteractive mode (GUI)
|
||||
and to retrieve config values instead of prompting users.
|
||||
|
||||
Used by all modules to support both CLI (interactive) and GUI (non-interactive) modes.
|
||||
|
||||
.NOTES
|
||||
Author: NexusOne23
|
||||
Version: 2.2.0
|
||||
|
||||
Usage in modules:
|
||||
1. Call Test-NonInteractiveMode to check if prompts should be skipped
|
||||
2. Use Get-NonInteractiveValue to get config values with defaults
|
||||
#>
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test if running in NonInteractive mode (GUI)
|
||||
|
||||
.DESCRIPTION
|
||||
Checks if the global config has nonInteractive=true set.
|
||||
When true, all Read-Host prompts should be skipped and config values used instead.
|
||||
|
||||
.OUTPUTS
|
||||
[bool] True if nonInteractive mode is enabled
|
||||
|
||||
.EXAMPLE
|
||||
if (Test-NonInteractiveMode) {
|
||||
# Use config value
|
||||
$choice = Get-NonInteractiveValue -Module "DNS" -Key "provider" -Default "Quad9"
|
||||
} else {
|
||||
# Interactive prompt
|
||||
$choice = Read-Host "Select provider"
|
||||
}
|
||||
#>
|
||||
function Test-NonInteractiveMode {
|
||||
[CmdletBinding()]
|
||||
[OutputType([bool])]
|
||||
param()
|
||||
|
||||
# Check environment variable FIRST (set by GUI before process starts)
|
||||
if ($env:NOIDPRIVACY_NONINTERACTIVE -eq "true") {
|
||||
return $true
|
||||
}
|
||||
|
||||
# Check global config
|
||||
if ($script:Config -and $script:Config.options) {
|
||||
if ($script:Config.options.nonInteractive -eq $true) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
# Set global variable at load time if environment variable is set
|
||||
# This allows modules to check $global:NonInteractiveMode directly
|
||||
if ($env:NOIDPRIVACY_NONINTERACTIVE -eq "true") {
|
||||
# Only show banner once per session, even if this file is dot-sourced multiple times
|
||||
# Use Get-Variable to avoid strict-mode errors when the variable does not yet exist
|
||||
$niVar = Get-Variable -Name NonInteractiveMode -Scope Global -ErrorAction SilentlyContinue
|
||||
$niValue = if ($niVar) { [bool]$niVar.Value } else { $false }
|
||||
|
||||
if (-not $niValue) {
|
||||
Write-Host "[GUI] Non-Interactive mode detected (environment variable)" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
$global:NonInteractiveMode = $true
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get a value from config for NonInteractive mode
|
||||
|
||||
.DESCRIPTION
|
||||
Retrieves a module-specific config value when running in NonInteractive mode.
|
||||
Falls back to default if not found.
|
||||
|
||||
.PARAMETER Module
|
||||
The module name (SecurityBaseline, ASR, DNS, Privacy, AntiAI, EdgeHardening, AdvancedSecurity)
|
||||
|
||||
.PARAMETER Key
|
||||
The config key to retrieve
|
||||
|
||||
.PARAMETER Default
|
||||
Default value if key not found
|
||||
|
||||
.OUTPUTS
|
||||
The config value or default
|
||||
|
||||
.EXAMPLE
|
||||
$provider = Get-NonInteractiveValue -Module "DNS" -Key "provider" -Default "Quad9"
|
||||
#>
|
||||
function Get-NonInteractiveValue {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Module,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Key,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
$Default = $null
|
||||
)
|
||||
|
||||
try {
|
||||
$hasConfig = $null -ne $script:Config
|
||||
$hasModules = $hasConfig -and ($null -ne $script:Config.modules)
|
||||
$hasModule = $hasModules -and ($null -ne $script:Config.modules.$Module)
|
||||
|
||||
if ($hasModule) {
|
||||
$moduleConfig = $script:Config.modules.$Module
|
||||
$hasKey = $null -ne $moduleConfig.$Key
|
||||
|
||||
if ($hasKey) {
|
||||
$value = $moduleConfig.$Key
|
||||
Write-Log -Level DEBUG -Message "[NonInteractive] $Module.$Key = $value (from config)" -Module "Core"
|
||||
return $value
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "[NonInteractive] Failed to read $Module.$Key from config: $_" -Module "Core"
|
||||
}
|
||||
|
||||
Write-Log -Level DEBUG -Message "[NonInteractive] $Module.$Key = $Default (default)" -Module "Core"
|
||||
return $Default
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Check if auto-confirm is enabled
|
||||
|
||||
.DESCRIPTION
|
||||
Returns true if autoConfirm or nonInteractive is enabled.
|
||||
Used for Y/N confirmation prompts that should auto-confirm to Y.
|
||||
|
||||
.OUTPUTS
|
||||
[bool] True if should auto-confirm
|
||||
#>
|
||||
function Test-AutoConfirm {
|
||||
[CmdletBinding()]
|
||||
[OutputType([bool])]
|
||||
param()
|
||||
|
||||
if ($script:Config -and $script:Config.options) {
|
||||
if ($script:Config.options.autoConfirm -eq $true) {
|
||||
return $true
|
||||
}
|
||||
if ($script:Config.options.nonInteractive -eq $true) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Log a NonInteractive mode decision
|
||||
|
||||
.DESCRIPTION
|
||||
Helper to log when a decision was made automatically in NonInteractive mode.
|
||||
Outputs to both console and log file for transparency.
|
||||
|
||||
.PARAMETER Module
|
||||
The module name
|
||||
|
||||
.PARAMETER Decision
|
||||
Description of the decision made
|
||||
|
||||
.PARAMETER Value
|
||||
The value that was used
|
||||
#>
|
||||
function Write-NonInteractiveDecision {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Module,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Decision,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
$Value = $null
|
||||
)
|
||||
|
||||
$message = if ($null -ne $Value) {
|
||||
"[GUI] $Decision : $Value"
|
||||
} else {
|
||||
"[GUI] $Decision"
|
||||
}
|
||||
|
||||
Write-Host $message -ForegroundColor Cyan
|
||||
Write-Log -Level INFO -Message $message -Module $Module
|
||||
}
|
||||
|
||||
# Functions are available globally when dot-sourced by Framework.ps1
|
||||
# No Export-ModuleMember needed (script is not loaded as a module)
|
||||
2969
Core/Rollback.ps1
Normal file
2969
Core/Rollback.ps1
Normal file
File diff suppressed because it is too large
Load diff
426
Core/Validator.ps1
Normal file
426
Core/Validator.ps1
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
System validation for NoID Privacy Framework
|
||||
|
||||
.DESCRIPTION
|
||||
Provides pre-execution validation checks and post-execution verification
|
||||
to ensure system safety and compliance.
|
||||
|
||||
.NOTES
|
||||
Author: NexusOne23
|
||||
Version: 2.2.0
|
||||
Requires: PowerShell 5.1+
|
||||
#>
|
||||
|
||||
function Test-Prerequisites {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Validate all system prerequisites before hardening
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with validation results
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([PSCustomObject])]
|
||||
param()
|
||||
|
||||
Write-Log -Level INFO -Message "Starting prerequisite validation" -Module "Validator"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Success = $true
|
||||
Errors = @()
|
||||
Warnings = @()
|
||||
SystemInfo = $null
|
||||
}
|
||||
|
||||
# Check 1: Administrator privileges
|
||||
if (-not (Test-IsAdministrator)) {
|
||||
Write-Log -Level ERROR -Message "Administrator privileges required" -Module "Validator"
|
||||
$result.Success = $false
|
||||
$result.Errors += "Administrator privileges required"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level SUCCESS -Message "Administrator check: PASSED" -Module "Validator"
|
||||
}
|
||||
|
||||
# Check 2: Windows version
|
||||
$osInfo = Get-WindowsVersion
|
||||
if ($osInfo.IsSupported) {
|
||||
Write-Log -Level SUCCESS -Message "Windows version check: PASSED ($($osInfo.Version))" -Module "Validator"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level ERROR -Message "Unsupported Windows version: $($osInfo.Version)" -Module "Validator"
|
||||
$result.Success = $false
|
||||
$result.Errors += "Unsupported Windows version: $($osInfo.Version)"
|
||||
}
|
||||
|
||||
# Check 3: Disk space
|
||||
$diskSpace = Get-AvailableDiskSpace
|
||||
if ($diskSpace -gt 500MB) {
|
||||
Write-Log -Level SUCCESS -Message "Disk space check: PASSED ($([math]::Round($diskSpace/1MB, 2)) MB available)" -Module "Validator"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "Low disk space: $([math]::Round($diskSpace/1MB, 2)) MB" -Module "Validator"
|
||||
$result.Warnings += "Low disk space: $([math]::Round($diskSpace/1MB, 2)) MB"
|
||||
}
|
||||
|
||||
# Check 4: PowerShell version
|
||||
if ($PSVersionTable.PSVersion.Major -ge 5) {
|
||||
Write-Log -Level SUCCESS -Message "PowerShell version check: PASSED ($($PSVersionTable.PSVersion))" -Module "Validator"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level ERROR -Message "PowerShell 5.1 or higher required" -Module "Validator"
|
||||
$result.Success = $false
|
||||
$result.Errors += "PowerShell 5.1 or higher required (found: $($PSVersionTable.PSVersion))"
|
||||
}
|
||||
|
||||
# Get system info
|
||||
$result.SystemInfo = Get-SystemInfo
|
||||
|
||||
if ($result.Success) {
|
||||
Write-Log -Level SUCCESS -Message "All prerequisite checks passed" -Module "Validator"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level ERROR -Message "One or more prerequisite checks failed" -Module "Validator"
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
function Test-IsAdministrator {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Check if script is running with administrator privileges
|
||||
|
||||
.OUTPUTS
|
||||
Boolean indicating administrator status
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([bool])]
|
||||
param()
|
||||
|
||||
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
|
||||
return $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
}
|
||||
|
||||
function Get-WindowsVersion {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get Windows version information
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with version details and support status
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([PSCustomObject])]
|
||||
param()
|
||||
|
||||
$os = Get-CimInstance -ClassName Win32_OperatingSystem
|
||||
$buildNumber = [int]$os.BuildNumber
|
||||
|
||||
# Windows 11 build numbers
|
||||
# 22000 = 21H2, 22621 = 22H2, 22631 = 23H2, 26100 = 24H2, 26200 = 25H2
|
||||
$isWindows11 = $buildNumber -ge 22000
|
||||
$isSupported = $buildNumber -ge 26100 # 24H2 or newer
|
||||
|
||||
$versionName = switch ($buildNumber) {
|
||||
{ $_ -ge 26200 } { "Windows 11 25H2"; break }
|
||||
{ $_ -ge 26100 } { "Windows 11 24H2"; break }
|
||||
{ $_ -ge 22631 } { "Windows 11 23H2"; break }
|
||||
{ $_ -ge 22621 } { "Windows 11 22H2"; break }
|
||||
{ $_ -ge 22000 } { "Windows 11 21H2"; break }
|
||||
default { "Windows $($os.Version)" }
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
Version = $versionName
|
||||
BuildNumber = $buildNumber
|
||||
IsWindows11 = $isWindows11
|
||||
IsSupported = $isSupported
|
||||
Edition = $os.Caption
|
||||
Architecture = $os.OSArchitecture
|
||||
}
|
||||
}
|
||||
|
||||
function Get-AvailableDiskSpace {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get available disk space on system drive
|
||||
|
||||
.OUTPUTS
|
||||
Int64 representing available bytes
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([Int64])]
|
||||
param()
|
||||
|
||||
$systemDrive = $env:SystemDrive
|
||||
$drive = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='$systemDrive'"
|
||||
|
||||
return $drive.FreeSpace
|
||||
}
|
||||
|
||||
function Test-InternetConnectivity {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test internet connectivity
|
||||
|
||||
.OUTPUTS
|
||||
Boolean indicating connectivity status
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([bool])]
|
||||
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingComputerNameHardcoded', '')]
|
||||
param()
|
||||
|
||||
try {
|
||||
# Using Google DNS (8.8.8.8) - intentional for internet connectivity check
|
||||
$response = Test-Connection -ComputerName "8.8.8.8" -Count 1 -Quiet -ErrorAction Stop
|
||||
return $response
|
||||
}
|
||||
catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Test-TPMAvailable {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Check if TPM 2.0 is available
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with TPM information
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([PSCustomObject])]
|
||||
param()
|
||||
|
||||
try {
|
||||
$tpm = Get-Tpm -ErrorAction SilentlyContinue
|
||||
|
||||
if ($null -eq $tpm) {
|
||||
return [PSCustomObject]@{
|
||||
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
|
||||
Activated = $tpm.TpmActivated
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Unable to check TPM status: $_" -Module "Validator"
|
||||
return [PSCustomObject]@{
|
||||
Present = $false
|
||||
Version = "Unknown"
|
||||
Enabled = $false
|
||||
Activated = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Test-SecureBootEnabled {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Check if Secure Boot is enabled
|
||||
|
||||
.OUTPUTS
|
||||
Boolean indicating Secure Boot status
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([bool])]
|
||||
param()
|
||||
|
||||
try {
|
||||
$secureBoot = Confirm-SecureBootUEFI -ErrorAction Stop
|
||||
return $secureBoot
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Unable to check Secure Boot status (may not be UEFI): $_" -Module "Validator"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Test-VirtualizationEnabled {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Check if CPU virtualization is enabled
|
||||
|
||||
.OUTPUTS
|
||||
Boolean indicating virtualization status
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([bool])]
|
||||
param()
|
||||
|
||||
try {
|
||||
$cpu = Get-CimInstance -ClassName Win32_Processor
|
||||
|
||||
# Check for Intel VT-x or AMD-V
|
||||
$vmxEnabled = $cpu.VirtualizationFirmwareEnabled
|
||||
|
||||
return $vmxEnabled
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Unable to check virtualization status: $_" -Module "Validator"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Get-SystemInfo {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get comprehensive system information
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with detailed system information
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([PSCustomObject])]
|
||||
param()
|
||||
|
||||
$osInfo = Get-WindowsVersion
|
||||
$tpmInfo = Test-TPMAvailable
|
||||
$secureBoot = Test-SecureBootEnabled
|
||||
$virtualization = Test-VirtualizationEnabled
|
||||
$isAdmin = Test-IsAdministrator
|
||||
$diskSpace = Get-AvailableDiskSpace
|
||||
$internet = Test-InternetConnectivity
|
||||
|
||||
return [PSCustomObject]@{
|
||||
OS = $osInfo
|
||||
TPM = $tpmInfo
|
||||
SecureBoot = $secureBoot
|
||||
Virtualization = $virtualization
|
||||
IsAdministrator = $isAdmin
|
||||
DiskSpaceAvailable = $diskSpace
|
||||
InternetConnected = $internet
|
||||
PowerShellVersion = $PSVersionTable.PSVersion.ToString()
|
||||
}
|
||||
}
|
||||
|
||||
function Test-DomainJoined {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Check if system is joined to an Active Directory domain
|
||||
|
||||
.DESCRIPTION
|
||||
Detects if the system is domain-joined and warns about potential
|
||||
Group Policy conflicts with local hardening settings.
|
||||
|
||||
.PARAMETER Interactive
|
||||
If set, prompts user to confirm continuation on domain-joined systems
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with domain status information
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([PSCustomObject])]
|
||||
param(
|
||||
[switch]$Interactive
|
||||
)
|
||||
|
||||
try {
|
||||
$computerSystem = Get-CimInstance Win32_ComputerSystem -ErrorAction Stop
|
||||
$isDomainJoined = $computerSystem.PartOfDomain
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
IsDomainJoined = $isDomainJoined
|
||||
DomainName = if ($isDomainJoined) { $computerSystem.Domain } else { "N/A" }
|
||||
Workgroup = if (-not $isDomainJoined) { $computerSystem.Workgroup } else { "N/A" }
|
||||
UserConfirmed = $false
|
||||
}
|
||||
|
||||
if ($isDomainJoined) {
|
||||
Write-Log -Level WARNING -Message "System is domain-joined: $($computerSystem.Domain)" -Module "Validator"
|
||||
|
||||
if ($Interactive) {
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Yellow
|
||||
Write-Host " WARNING: DOMAIN-JOINED SYSTEM" -ForegroundColor Yellow
|
||||
Write-Host "========================================" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "This system is joined to domain: " -NoNewline -ForegroundColor White
|
||||
Write-Host "$($computerSystem.Domain)" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "IMPORTANT CONSIDERATIONS:" -ForegroundColor Red
|
||||
Write-Host " - Domain Group Policies will override local policies" -ForegroundColor Yellow
|
||||
Write-Host " - GPO refresh occurs every 90 minutes" -ForegroundColor Yellow
|
||||
Write-Host " - Some hardening may be reset automatically" -ForegroundColor Yellow
|
||||
Write-Host " - Coordinate with AD team before proceeding" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "RECOMMENDED FOR DOMAIN ENVIRONMENTS:" -ForegroundColor Cyan
|
||||
Write-Host " - Integrate these settings into Domain GPOs instead" -ForegroundColor White
|
||||
Write-Host " - Use this tool only for testing/standalone systems" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
$continue = Read-Host "Do you want to continue anyway? (yes/no)"
|
||||
|
||||
if ($continue -ne "yes") {
|
||||
Write-Log -Level INFO -Message "User cancelled due to domain-joined warning" -Module "Validator"
|
||||
Write-Host ""
|
||||
Write-Host "Operation cancelled by user." -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
exit 1
|
||||
}
|
||||
|
||||
$result.UserConfirmed = $true
|
||||
Write-Log -Level INFO -Message "User confirmed continuation on domain-joined system" -Module "Validator"
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Log -Level INFO -Message "System is standalone (workgroup: $($computerSystem.Workgroup))" -Module "Validator"
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to check domain status: $_" -Module "Validator" -Exception $_.Exception
|
||||
return [PSCustomObject]@{
|
||||
IsDomainJoined = $false
|
||||
DomainName = "Error"
|
||||
Workgroup = "Error"
|
||||
UserConfirmed = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Confirm-SystemBackup {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Non-interactive system backup recommendation
|
||||
|
||||
.DESCRIPTION
|
||||
Historically this function displayed an interactive prompt asking the
|
||||
user to confirm that a full system backup exists before proceeding.
|
||||
For modern CLI and GUI workflows this interaction is removed to avoid
|
||||
blocking automation. The function now simply logs that a backup is
|
||||
recommended and returns a confirmation object.
|
||||
|
||||
.PARAMETER Force
|
||||
Retained for backwards compatibility. No longer changes behaviour.
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with backup confirmation status
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([PSCustomObject])]
|
||||
param()
|
||||
|
||||
Write-Log -Level INFO -Message "Backup recommendation: non-interactive confirmation (no prompt shown)" -Module "Validator"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
UserConfirmed = $true
|
||||
BackupRecommended = $true
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
# Note: Export-ModuleMember not used - this script is dot-sourced, not imported as module
|
||||
742
Docs/FEATURES.md
Normal file
742
Docs/FEATURES.md
Normal file
|
|
@ -0,0 +1,742 @@
|
|||
# NoID Privacy - Complete Feature List
|
||||
|
||||
**Framework Version:** v2.2.0
|
||||
**Total Security Settings:** 632 (Paranoid mode)
|
||||
**Modules:** 7 (All Production-Ready)
|
||||
**Last Updated:** December 7, 2025
|
||||
|
||||
---
|
||||
|
||||
## 📊 Module Overview
|
||||
|
||||
| Module | Settings | Status | Description |
|
||||
|--------|----------|--------|-------------|
|
||||
| **SecurityBaseline** | 425 | ✅ v2.2.0 | Microsoft Security Baseline for Windows 11 v25H2 |
|
||||
| **ASR** | 19 | ✅ v2.2.0 | Attack Surface Reduction rules |
|
||||
| **DNS** | 5 | ✅ v2.2.0 | Secure DNS with DoH encryption |
|
||||
| **Privacy** | 77 | ✅ v2.2.0 | Telemetry control, Bloatware removal (53 Registry + 24 Bloatware) |
|
||||
| **AntiAI** | 32 | ✅ v2.2.0 | AI lockdown (13 features, 32 compliance checks) |
|
||||
| **EdgeHardening** | 24 | ✅ v2.2.0 | Microsoft Edge browser security (24 policies) |
|
||||
| **AdvancedSecurity** | 50 | ✅ v2.2.0 | Advanced hardening beyond MS Baseline (incl. Wireless Display, Discovery Protocols, IPv6) |
|
||||
| **TOTAL** | **632** | ✅ **100%** | **Complete Framework (Paranoid mode)** |
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Module 1: SecurityBaseline (425 Settings)
|
||||
|
||||
**Description:** Complete implementation of Microsoft's official Windows 11 v25H2 Security Baseline
|
||||
|
||||
### Components:
|
||||
|
||||
#### Registry Policies (335 settings)
|
||||
- Computer Configuration policies (330 settings)
|
||||
- User Configuration policies (5 settings)
|
||||
- Windows Defender Antivirus baseline
|
||||
- Windows Firewall configuration
|
||||
- BitLocker drive encryption settings
|
||||
- Internet Explorer 11 security zones
|
||||
|
||||
#### Security Template (67 settings)
|
||||
- **Password Policy:** MinimumPasswordLength (14), PasswordHistorySize (24), etc.
|
||||
- **Account Lockout:** LockoutBadCount (10), LockoutDuration (10 minutes)
|
||||
- **User Rights Assignment:** Administrative permissions and privileges
|
||||
- **Security Options:** Network access, authentication, object access
|
||||
- **Service Configuration:** Xbox services disabled for security
|
||||
|
||||
#### Audit Policies (23 subcategories)
|
||||
- Logon/Logoff events
|
||||
- Account Management
|
||||
- Policy Change tracking
|
||||
- Privilege Use monitoring
|
||||
- System events
|
||||
- Object Access auditing
|
||||
|
||||
### Key Features:
|
||||
- ✅ VBS (Virtualization Based Security)
|
||||
- ✅ Credential Guard
|
||||
- ✅ System Guard Secure Launch
|
||||
- ✅ Kernel CET Shadow Stacks (Win11 25H2)
|
||||
- ✅ Memory Integrity (HVCI)
|
||||
- ✅ Interactive BitLocker USB prompt (Home/Enterprise choice)
|
||||
|
||||
### Home User Adjustments:
|
||||
- **BitLocker USB:** Default = 0 (Home Mode - USB works normally)
|
||||
- **Password Policies:** Only affect local accounts (~5% of users)
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Module 2: ASR (19 Settings)
|
||||
|
||||
**Description:** All 19 Microsoft Defender Attack Surface Reduction rules
|
||||
|
||||
### What ASR Rules Block (and Why It's Important):
|
||||
|
||||
#### Email & Download Attacks
|
||||
1. **Block executable content from email** - Stops malware from .exe/.dll/.ps1 email attachments
|
||||
2. **Block JavaScript/VBScript from launching downloads** - Prevents drive-by downloads from malicious websites
|
||||
3. **Block execution of obfuscated scripts** - Detects and blocks heavily obfuscated PowerShell/JS scripts used by malware
|
||||
4. **Block untrusted/unsigned processes from USB** - Prevents USB-based malware execution (BadUSB attacks)
|
||||
|
||||
#### Office Exploits
|
||||
5. **Block Office from creating child processes** - Stops Word/Excel macros from spawning cmd.exe/powershell.exe
|
||||
6. **Block Office from creating executable content** - Prevents Office from writing .exe files to disk
|
||||
7. **Block Office from injecting code into other processes** - Stops process injection attacks
|
||||
8. **Block Win32 API calls from Office macros** - Prevents macros from calling dangerous Windows APIs
|
||||
9. **Block Adobe Reader from creating child processes** - Same protection for PDF exploits
|
||||
10. **Block Office communication apps (Outlook) child processes** - Stops email-based exploit chains
|
||||
|
||||
#### Credential Theft & Persistence
|
||||
11. **Block credential stealing from LSASS** - Protects against Mimikatz and similar tools
|
||||
12. **Block persistence through WMI** - Prevents malware from hiding in WMI event subscriptions
|
||||
13. **Block process creation from PSExec/WMI** - Stops lateral movement tools (configurable: Block or Audit)
|
||||
|
||||
#### Ransomware Protection
|
||||
14. **Use advanced ransomware protection** - AI-powered behavioral detection of ransomware
|
||||
15. **Block executable files unless they meet reputation criteria** - SmartScreen integration
|
||||
|
||||
#### Advanced Threats
|
||||
16. **Block abuse of exploited vulnerable signed drivers** - Prevents BYOVD (Bring Your Own Vulnerable Driver) attacks
|
||||
17. **Block webshell creation** - Stops IIS/Apache webshell deployment (Server-focused)
|
||||
18. **Block rebooting in Safe Mode** - Prevents ransomware from bypassing defenses
|
||||
19. **Block use of copied/impersonated system tools** - Detects renamed legitimate tools (rundll32.exe → run.exe)
|
||||
|
||||
### Interactive Prompt:
|
||||
- **PSExec/WMI Rule (d1e49aac):** Choose **Block** or **Audit**
|
||||
- Block: Maximum security (may break SCCM/remote admin tools)
|
||||
- Audit: Logs events only (good for enterprise compatibility testing)
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Module 3: DNS (5 Settings)
|
||||
|
||||
**Description:** Secure DNS with DNS-over-HTTPS encryption
|
||||
|
||||
### Providers (3 available):
|
||||
|
||||
#### Quad9 (Default - Security)
|
||||
- **IPv4:** 9.9.9.9, 149.112.112.112
|
||||
- **IPv6:** 2620:fe::fe, 2620:fe::9
|
||||
- **DoH:** https://dns.quad9.net/dns-query
|
||||
- **Ratings:** Speed 4/5, Privacy 5/5, Security 5/5, Filtering 4/5
|
||||
- **Best for:** Security-focused users, malware protection
|
||||
|
||||
#### Cloudflare (Speed)
|
||||
- **IPv4:** 1.1.1.1, 1.0.0.1
|
||||
- **IPv6:** 2606:4700:4700::1111, 2606:4700:4700::1001
|
||||
- **DoH:** https://cloudflare-dns.com/dns-query
|
||||
- **Ratings:** Speed 5/5, Privacy 4/5, Security 4/5, Filtering 2/5
|
||||
- **Best for:** Speed-focused users, fastest resolver
|
||||
|
||||
#### AdGuard (Ad-Blocking)
|
||||
- **IPv4:** 94.140.14.14, 94.140.15.15
|
||||
- **IPv6:** 2a10:50c0::ad1:ff, 2a10:50c0::ad2:ff
|
||||
- **DoH:** https://dns.adguard-dns.com/dns-query
|
||||
- **Ratings:** Speed 4/5, Privacy 4/5, Security 4/5, Filtering 5/5
|
||||
- **Best for:** Ad/tracker blocking at DNS level
|
||||
|
||||
### Features:
|
||||
- ✅ **DoH Encryption with 2 Interactive Modes:**
|
||||
- **[1] REQUIRE Mode (Default):** NO unencrypted fallback (AllowFallbackToUdp = $False)
|
||||
- Best for: Home networks, single-location systems
|
||||
- Maximum security - DNS queries always encrypted
|
||||
- **[2] ALLOW Mode:** Fallback to UDP allowed (AllowFallbackToUdp = $True)
|
||||
- Best for: VPN users, mobile devices, corporate networks, captive portals
|
||||
- Balanced security - falls back to unencrypted if DoH unavailable
|
||||
- **[3] Skip:** Keep current DNS settings unchanged
|
||||
- ✅ DNSSEC validation (server-side by all providers)
|
||||
- ✅ DHCP-aware backup/restore
|
||||
- ✅ Physical adapter auto-detection (excludes virtual/VPN adapters)
|
||||
- ✅ Connectivity validation before apply
|
||||
|
||||
---
|
||||
|
||||
## 🔇 Module 4: Privacy (77 Settings)
|
||||
|
||||
**Description:** Windows telemetry control, OneDrive/MS Store telemetry, and bloatware removal
|
||||
|
||||
### What's Actually Done:
|
||||
- ✅ **Windows Telemetry:** 3 modes (MSRecommended/Strict/Paranoid)
|
||||
- ✅ **OneDrive Telemetry:** Feedback & sync reports disabled
|
||||
- ✅ **OneDrive Sync:** Remains FUNCTIONAL (DisablePersonalSync = 0)
|
||||
- ✅ **MS Store Telemetry:** AutoDownload = 3 (auto-update apps, no upgrade prompts)
|
||||
- ✅ **Bloatware Removal:** 10-24+ apps removed (PolicyMethod for ENT/EDU, ClassicMethod for others)
|
||||
|
||||
### Operating Modes (Interactive Selection):
|
||||
|
||||
#### MSRecommended (Default - Fully Supported)
|
||||
- AllowTelemetry = 1 (Required)
|
||||
- Services NOT disabled (policies only)
|
||||
- AppPrivacy: Selective (Location/Radios Force Deny, Mic/Camera user decides)
|
||||
- **Best for:** Production, business environments
|
||||
|
||||
#### Strict (Maximum Privacy)
|
||||
- AllowTelemetry = 0 (Off)
|
||||
- Services: DiagTrack + dmwappushservice disabled
|
||||
- AppPrivacy: Force Deny Mic/Camera/Contacts/Calendar
|
||||
- **Warning:** Breaks Teams/Zoom, Windows Update error reporting
|
||||
- **Best for:** High-security, standalone systems
|
||||
|
||||
#### Paranoid (Hardcore - NOT Recommended)
|
||||
- Everything from Strict + WerSvc disabled
|
||||
- Tasks: CEIP/AppExperience/DiskDiag disabled
|
||||
- **Warning:** Breaks error analysis, support severely limited
|
||||
- **Best for:** Air-gapped, extreme privacy only
|
||||
|
||||
### ⚠️ Windows Insider Program Compatibility
|
||||
|
||||
**MSRecommended mode** sets `AllowTelemetry=1` via Group Policy, which blocks Windows Insider Program enrollment. The Insider Program requires "Optional diagnostic data" (AllowTelemetry=3) for initial enrollment.
|
||||
|
||||
**Workaround:** Temporarily remove the `AllowTelemetry` policy before Insider enrollment:
|
||||
```powershell
|
||||
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Name "AllowTelemetry"
|
||||
```
|
||||
|
||||
After enrollment, you can optionally re-apply Privacy hardening. Insider builds will continue to download even with `AllowTelemetry=1` restored.
|
||||
|
||||
**See:** [README Troubleshooting - Windows Insider Program Compatibility](../README.md#windows-insider-program-compatibility)
|
||||
|
||||
---
|
||||
|
||||
### Bloatware Removal:
|
||||
|
||||
**PolicyMethod (10 apps - ENT/EDU Win11 25H2+):**
|
||||
- BingNews, BingWeather, MicrosoftSolitaireCollection
|
||||
- MicrosoftStickyNotes, GamingApp, WindowsFeedbackHub
|
||||
- Xbox components (GamingOverlay, IdentityProvider, SpeechToTextOverlay, TCUI)
|
||||
|
||||
**ClassicMethod (24 apps - All other editions):**
|
||||
```
|
||||
Microsoft.BingNews, Microsoft.BingWeather
|
||||
Microsoft.MicrosoftSolitaireCollection, Microsoft.MicrosoftStickyNotes
|
||||
Microsoft.GamingApp, Microsoft.XboxApp
|
||||
Microsoft.XboxGamingOverlay, Microsoft.XboxIdentityProvider
|
||||
Microsoft.XboxSpeechToTextOverlay, Microsoft.Xbox.TCUI
|
||||
Microsoft.ZuneMusic, Microsoft.ZuneVideo
|
||||
Microsoft.WindowsFeedbackHub, Microsoft.GetHelp
|
||||
Microsoft.Getstarted, Microsoft.MixedReality.Portal
|
||||
Microsoft.People, Microsoft.YourPhone
|
||||
Clipchamp.Clipchamp, SpotifyAB.SpotifyMusic
|
||||
*CandyCrush*, Disney.*, Facebook.*, TikTok.TikTok
|
||||
```
|
||||
|
||||
### Protected Apps (18 kept):
|
||||
- **Core Apps:** WindowsStore, WindowsCalculator, Photos, Paint
|
||||
- **Productivity:** WindowsNotepad, WindowsTerminal, WindowsCamera, ScreenSketch, WindowsSoundRecorder
|
||||
- **System:** DesktopAppInstaller (winget), StorePurchaseApp
|
||||
- **Media Codecs:** HEIF, HEVC, WebP, VP9, WebMedia, AV1, MPEG2, RAW (8 extensions)
|
||||
|
||||
### OneDrive Settings:
|
||||
- Telemetry: Disabled
|
||||
- Sync: Functional (not broken)
|
||||
- Store: Enabled (app updates needed)
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Module 5: AntiAI (32 Policies)
|
||||
|
||||
**Description:** Disable 13 Windows AI features via 32 registry policies (v2.2.0)
|
||||
|
||||
### 13 AI Features Disabled:
|
||||
|
||||
| # | Feature | Policies | Description |
|
||||
|---|---------|----------|-------------|
|
||||
| 1 | **Generative AI Master Switch** | 2 | Blocks ALL apps from using on-device AI models |
|
||||
| 2 | **Windows Recall** | 8 | Screenshots, OCR, component removal + Enterprise Protection |
|
||||
| 3 | **Windows Copilot** | 6 | 4-layer disable: WindowsAI, WindowsCopilot, Taskbar, Explorer |
|
||||
| 4 | **Click to Do** | 2 | Screenshot AI analysis with action suggestions |
|
||||
| 5 | **Paint Cocreator** | 1 | Cloud-based text-to-image generation |
|
||||
| 6 | **Paint Generative Fill** | 1 | AI-powered image editing |
|
||||
| 7 | **Paint Image Creator** | 1 | DALL-E art generator |
|
||||
| 8 | **Notepad AI** | 1 | Write, Summarize, Rewrite features (GPT) |
|
||||
| 9 | **Settings Agent** | 1 | AI-powered Settings search |
|
||||
| 10 | **Recall Export Block** | 1 | Prevents export of Recall data |
|
||||
| 11 | **Edge Copilot Sidebar** | 3 | EdgeSidebarEnabled, ShowHubsSidebar, HubsSidebarEnabled |
|
||||
| 12 | **Edge Copilot Context** | 2 | CopilotPageContext, CopilotCDPPageContext |
|
||||
| 13 | **File Explorer AI Actions** | 1 | HideAIActionsMenu in Explorer context menu |
|
||||
|
||||
### Recall Enterprise Protection:
|
||||
- **App Deny List:** Browser, Terminal, Password managers, RDP never captured
|
||||
- **URI Deny List:** Banking (*.bank.*), Email (mail.*), Login pages (*password*, *login*)
|
||||
- **Storage Duration:** Maximum 30 days retention
|
||||
- **Storage Space:** Maximum 10 GB allocated
|
||||
|
||||
### Automatically Blocked (by Master Switch):
|
||||
- Photos Generative Erase / Background effects
|
||||
- Clipchamp Auto Compose
|
||||
- Snipping Tool AI-OCR / Quick Redact
|
||||
- All future generative AI apps
|
||||
|
||||
### 32 Registry Policies Applied:
|
||||
```
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy\LetAppsAccessSystemAIModels = 2
|
||||
HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\systemAIModels\Value = Deny
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI\AllowRecallEnablement = 0
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI\DisableAIDataAnalysis = 1
|
||||
HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI\DisableAIDataAnalysis = 1
|
||||
HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI\DisableRecallDataProviders = 1
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI\SetDenyAppListForRecall = [...]
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI\SetDenyUriListForRecall = [...]
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI\SetMaximumStorageDurationForRecallSnapshots = 30
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI\SetMaximumStorageSpaceForRecallSnapshots = 10
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI\TurnOffWindowsCopilot = 1
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsCopilot\TurnOffWindowsCopilot = 1
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsCopilot\ShowCopilotButton = 0
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer\DisableWindowsCopilot = 1
|
||||
HKCU:\Software\Policies\Microsoft\Windows\WindowsCopilot\TurnOffWindowsCopilot = 1
|
||||
HKCU:\Software\Policies\Microsoft\Windows\WindowsCopilot\ShowCopilotButton = 0
|
||||
HKCU:\Software\Policies\Microsoft\Windows\WindowsAI\SetCopilotHardwareKey = Notepad (redirect)
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI\DisableClickToDo = 1
|
||||
HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI\DisableClickToDo = 1
|
||||
HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\Paint\DisableCocreator = 1
|
||||
HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\Paint\DisableGenerativeFill = 1
|
||||
HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\Paint\DisableImageCreator = 1
|
||||
HKLM:\SOFTWARE\Policies\WindowsNotepad\DisableAIFeatures = 1
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI\DisableSettingsAgent = 1
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI\AllowRecallExport = 0
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Edge\EdgeSidebarEnabled = 0
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Edge\ShowHubsSidebar = 0
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Edge\HubsSidebarEnabled = 0
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Edge\CopilotPageContext = 0
|
||||
HKLM:\SOFTWARE\Policies\Microsoft\Edge\CopilotCDPPageContext = 0
|
||||
```
|
||||
|
||||
### Impact:
|
||||
- ✅ No AI data collection
|
||||
- ✅ No cloud processing of local data
|
||||
- ✅ Copilot completely hidden from taskbar and Start menu
|
||||
- ✅ Edge Copilot sidebar disabled
|
||||
- ✅ Traditional app experience restored
|
||||
- ✅ **Reboot required** for Recall component removal
|
||||
|
||||
### ⚠️ Known Limitations:
|
||||
Some UI elements in Paint and Photos apps may **still be visible** but non-functional due to lack of Microsoft-provided policies:
|
||||
- **Photos:** Generative Erase button, Background Blur/Remove options
|
||||
- **Paint:** Some AI feature UI elements
|
||||
|
||||
**Why?** Microsoft does NOT provide dedicated policies to hide these UI elements. Functionality is **blocked via systemAIModels API Master Switch** (LetAppsAccessSystemAIModels = 2), but UI removal requires Microsoft to add policies in future Windows updates.
|
||||
|
||||
**Result:** Buttons are visible but clicking them does nothing (API access blocked).
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Module 6: EdgeHardening (24 Settings)
|
||||
|
||||
**Description:** Microsoft Edge v139 Security Baseline
|
||||
|
||||
### Core Security:
|
||||
- EnhanceSecurityMode = 2 (Strict)
|
||||
- SmartScreenEnabled = 1
|
||||
- SmartScreenPuaEnabled = 1
|
||||
- PreventSmartScreenPromptOverride = 1
|
||||
- SitePerProcess = 1 (Site isolation)
|
||||
|
||||
### Privacy:
|
||||
- TrackingPrevention = 2 (Strict)
|
||||
- PersonalizationReportingEnabled = 0
|
||||
- DiagnosticData = 0
|
||||
- DoNotTrack = 1
|
||||
|
||||
### Security Mitigations:
|
||||
- SSL/TLS error override blocked
|
||||
- Extension blocklist (blocks all by default)
|
||||
- IE Mode restrictions
|
||||
- SharedArrayBuffer disabled (Spectre protection)
|
||||
- Application-bound encryption enabled
|
||||
|
||||
### Features:
|
||||
- ✅ Native PowerShell implementation (no LGPO.exe)
|
||||
- ✅ AllowExtensions parameter available
|
||||
- ✅ Full backup/restore support
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Module 7: AdvancedSecurity (50 Settings)
|
||||
|
||||
**Description:** Advanced hardening beyond Microsoft Security Baseline
|
||||
|
||||
### Profile-Based Execution:
|
||||
|
||||
| Feature | Balanced | Enterprise | Maximum |
|
||||
|---------|------|------------|-----------|
|
||||
| RDP NLA Enforcement | ✅ | ✅ | ✅ |
|
||||
| WDigest Protection | ✅ | ✅ | ✅ |
|
||||
| Risky Ports/Services | ✅ | ✅ | ✅ |
|
||||
| Legacy TLS Disable | ✅ | ✅ | ✅ |
|
||||
| WPAD Disable | ✅ | ✅ | ✅ |
|
||||
| PowerShell v2 Removal | ✅ | ✅ | ✅ |
|
||||
| Admin Shares Disable | ✅ | ⚠️ Domain Check | ✅ |
|
||||
| RDP Complete Disable | ⚠️ Optional | ❌ | ✅ |
|
||||
| UPnP/SSDP Block | ⚠️ Optional | ✅ | ✅ |
|
||||
| Wireless Display Hardening | ✅ | ✅ | ✅ |
|
||||
| Wireless Display Full Disable | ⚠️ Optional | ⚠️ Optional | ⚠️ Optional |
|
||||
| Discovery Protocols (WSD/mDNS) Disable | ❌ | ❌ | ⚠️ Optional |
|
||||
| Firewall Shields Up | ❌ | ❌ | ⚠️ Optional |
|
||||
| IPv6 Disable (mitm6 mitigation) | ❌ | ❌ | ⚠️ Optional |
|
||||
| SRP .lnk Protection | ✅ | ✅ | ✅ |
|
||||
| Windows Update Config | ✅ | ✅ | ✅ |
|
||||
| Finger Protocol Block | ✅ | ✅ | ✅ |
|
||||
|
||||
### Components:
|
||||
|
||||
#### 1. RDP Hardening (3 settings)
|
||||
- **NLA Enforcement:** UserAuthentication = 1, SecurityLayer = 2
|
||||
- **Optional Disable:** fDenyTSConnections = 1 (Maximum profile only, for air-gapped systems)
|
||||
- **Protection:** Prevents RDP brute-force attacks
|
||||
|
||||
#### 2. WDigest Credential Protection (1 setting)
|
||||
- **Registry:** UseLogonCredential = 0
|
||||
- **Protection:** Prevents LSASS memory credential theft (Mimikatz)
|
||||
- **Note:** Deprecated in Win11 24H2+ but kept for backwards compatibility
|
||||
|
||||
#### 3. Risky Ports Closure (15 firewall rules)
|
||||
- **LLMNR:** Port 5355 TCP/UDP (MITM attack prevention)
|
||||
- **NetBIOS:** Ports 137-138 TCP/UDP (name resolution hijacking)
|
||||
- **UPnP:** Ports 1900, 2869 TCP/UDP (NAT traversal exploits)
|
||||
|
||||
#### 4. Risky Services (3 services)
|
||||
- **SSDP Discovery:** Disabled (UPnP)
|
||||
- **UPnP Device Host:** Disabled
|
||||
- **TCP/IP NetBIOS Helper:** Disabled
|
||||
|
||||
#### 5. Administrative Shares (2 registry keys)
|
||||
- **AutoShareWks = 0:** Disables C$, ADMIN$
|
||||
- **AutoShareServer = 0:** Server shares
|
||||
- **Domain-Aware:** Auto-skipped for domain-joined systems unless -Force
|
||||
|
||||
#### 6. Legacy TLS Disable (8 registry keys)
|
||||
- **TLS 1.0:** Client + Server disabled
|
||||
- **TLS 1.1:** Client + Server disabled
|
||||
- **Protection:** BEAST, CRIME, POODLE attacks prevented
|
||||
|
||||
#### 7. WPAD Disable (3 registry keys)
|
||||
- **User + Machine:** AutoDetect = 0
|
||||
- **WinHTTP:** DisableWpad = 1
|
||||
- **Protection:** Proxy hijacking attacks prevented
|
||||
|
||||
#### 8. PowerShell v2 Removal (1 Windows Feature)
|
||||
- **Feature:** MicrosoftWindowsPowerShellV2Root
|
||||
- **Protection:** Prevents downgrade attacks (bypasses logging, AMSI, CLM)
|
||||
|
||||
#### 9. SRP .lnk Protection - CVE-2025-9491 (2 rules)
|
||||
- **Rule 1:** Block %LOCALAPPDATA%\Temp\*.lnk (Outlook attachments)
|
||||
- **Rule 2:** Block %USERPROFILE%\Downloads\*.lnk (Browser downloads)
|
||||
- **Protection:** Prevents zero-day LNK RCE exploitation
|
||||
- **Status:** CRITICAL - Actively exploited since 2017, no patch available
|
||||
|
||||
#### 10. Windows Update Configuration (3 Simple GUI Settings)
|
||||
|
||||
**Aligns with Windows Settings GUI toggles** – NO forced schedules, NO auto-reboot, and only the documented policy keys needed to drive the visible switches
|
||||
|
||||
**Settings Applied:**
|
||||
|
||||
**1. Get Latest Updates Immediately (ON, managed by policy)**
|
||||
- Registry: `HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate`
|
||||
- Keys:
|
||||
- `AllowOptionalContent = 1`
|
||||
- `SetAllowOptionalContent = 1`
|
||||
- Effect: Enables optional/content configuration updates so the toggle "Get the latest updates as soon as they're available" is effectively ON and enforced by policy
|
||||
- GUI Path: Settings > Windows Update > Advanced options > Get the latest updates as soon as they're available (will show as managed by your organization)
|
||||
|
||||
**2. Microsoft Update for Other Products (ON, user-toggleable)**
|
||||
- Registry: `HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings`
|
||||
- Key: `AllowMUUpdateService = 1`
|
||||
- Effect: Get updates for Office, drivers, and other Microsoft products when updating Windows
|
||||
- GUI Path: Settings > Windows Update > Advanced options > Receive updates for other Microsoft products (user can still toggle)
|
||||
|
||||
**3. Delivery Optimization - Downloads from Other Devices (OFF, managed by policy)**
|
||||
- Registry: `HKLM:\SOFTWARE\Policies\Microsoft\Windows\DeliveryOptimization`
|
||||
- Key: `DODownloadMode = 0`
|
||||
- Effect: HTTP only (Microsoft servers) – no peer-to-peer, no LAN sharing
|
||||
- GUI Path: Settings > Windows Update > Advanced options > Delivery Optimization > Allow downloads from other devices = OFF (managed by your organization)
|
||||
|
||||
**User Control & Transparency:**
|
||||
- ✅ NO forced installation schedules
|
||||
- ✅ NO auto-reboot policies
|
||||
- ✅ Microsoft Update toggle remains user-controlled in the GUI
|
||||
- ✅ Windows clearly indicates where policies manage settings ("Some settings are managed by your organization")
|
||||
|
||||
**Why This Approach?**
|
||||
- Follows Microsoft Best Practice - matches GUI behavior
|
||||
- User keeps control over installation timing
|
||||
- No unexpected reboots at 3 AM
|
||||
- Transparent - exactly what Windows Settings shows
|
||||
|
||||
#### 11. Finger Protocol Block (1 firewall rule)
|
||||
- **Port:** TCP 79 outbound
|
||||
- **Protection:** ClickFix malware campaign mitigation
|
||||
- **Attack:** Malware uses finger.exe to retrieve commands from attacker servers
|
||||
- **Impact:** Zero (Finger protocol obsolete since 1990s)
|
||||
|
||||
#### 12. Wireless Display Security (9 settings)
|
||||
|
||||
**Default Hardening (always applied, all profiles):**
|
||||
- **AllowProjectionToPC = 0:** Block receiving projections (PC can't be used as display)
|
||||
- **RequirePinForPairing = 2:** Always require PIN for pairing
|
||||
|
||||
**Optional Full Disable (user choice):**
|
||||
- **AllowProjectionFromPC = 0:** Block sending projections
|
||||
- **AllowMdnsAdvertisement = 0:** Don't advertise as receiver via mDNS
|
||||
- **AllowMdnsDiscovery = 0:** Don't discover displays via mDNS
|
||||
- **AllowProjectionFromPCOverInfrastructure = 0:** Block infrastructure projection
|
||||
- **AllowProjectionToPCOverInfrastructure = 0:** Block infrastructure receiving
|
||||
- **Firewall Rules:** Block Miracast ports 7236/7250 (TCP + UDP)
|
||||
|
||||
**Protection:**
|
||||
- Prevents rogue Miracast receiver attacks (screen capture by attackers in network)
|
||||
- Blocks WPS PIN brute-force on Miracast connections
|
||||
- Prevents mDNS spoofing for fake display discovery
|
||||
- Defense-in-depth for Miracast attack surface
|
||||
|
||||
**Impact:**
|
||||
- Default: Presentations to TV/projector still work (sending allowed)
|
||||
- Full Disable: Use HDMI/USB-C cables instead of Miracast
|
||||
|
||||
#### 13. Discovery Protocols Security (WS-Discovery + mDNS)
|
||||
|
||||
**Optional (Maximum profile - user choice):**
|
||||
- **mDNS Resolver:** Disabled via registry (EnableMDNS = 0)
|
||||
- **WS-Discovery Services:** FDResPub + SSDPSRV disabled
|
||||
- **Firewall Blocks:**
|
||||
- WS-Discovery ports: UDP 3702 (blocked inbound/outbound)
|
||||
- mDNS port: UDP 5353 (blocked inbound/outbound)
|
||||
|
||||
**Protection:**
|
||||
- Prevents network mapping via WS-Discovery
|
||||
- Blocks mDNS spoofing attacks (fake printers/devices)
|
||||
- Reduces lateral movement attack surface
|
||||
- Stops automatic device enumeration by attackers
|
||||
|
||||
**Impact:**
|
||||
- Automatic network printer/scanner discovery stops
|
||||
- Smart TV discovery via mDNS stops
|
||||
- Miracast discovery via mDNS stops (even if Feature 12 allows sending)
|
||||
- Manual IP configuration required for network devices
|
||||
|
||||
#### 14. Firewall Shields Up (Maximum profile only)
|
||||
|
||||
**Optional (Maximum profile):**
|
||||
- **Block All Inbound:** DefaultInboundAction = Block
|
||||
- **Block All Outbound:** DefaultOutboundAction = Block (with exceptions)
|
||||
- Applies to Domain, Private, and Public profiles
|
||||
|
||||
**Protection:**
|
||||
- Maximum network isolation
|
||||
- Blocks all unsolicited inbound connections
|
||||
- Prevents unauthorized outbound connections
|
||||
|
||||
**Impact:**
|
||||
- Only explicitly allowed traffic passes
|
||||
- Recommended for air-gapped or high-security systems
|
||||
|
||||
#### 15. IPv6 Disable (Maximum profile only - optional)
|
||||
|
||||
**Optional (Maximum profile - user choice):**
|
||||
- **DisabledComponents = 0xFF:** Completely disables IPv6 stack
|
||||
- Prevents all IPv6 traffic including DHCPv6 Solicitation
|
||||
|
||||
**Protection (mitm6 attack):**
|
||||
- Prevents DHCPv6 spoofing attacks
|
||||
- Blocks fake DHCPv6 server → DNS takeover
|
||||
- Prevents NTLM credential relay via IPv6
|
||||
- Defense-in-depth (WPAD already disabled)
|
||||
|
||||
**Impact:**
|
||||
- IPv6-only services/websites won't work
|
||||
- Exchange Server may have issues if using IPv6
|
||||
- Some Active Directory features may be affected
|
||||
- **REBOOT REQUIRED**
|
||||
|
||||
**Recommended for:**
|
||||
- Air-gapped systems
|
||||
- Standalone workstations (no Exchange/AD)
|
||||
- High-security environments where IPv6 is not needed
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Protection Coverage
|
||||
|
||||
### Zero-Day Vulnerabilities:
|
||||
|
||||
#### CVE-2025-9491 - Windows LNK RCE ✅ MITIGATED
|
||||
- **Status:** Unpatched (Microsoft: "does not meet servicing threshold")
|
||||
- **Exploited Since:** 2017 by APT groups
|
||||
- **Our Protection:** SRP rules block .lnk execution from Temp/Downloads
|
||||
- **Why ASR Fails:** .lnk files not classified as "executable content"
|
||||
- **Why SmartScreen Fails:** .lnk points to legitimate cmd.exe (trusted)
|
||||
|
||||
#### ClickFix Malware Campaign ✅ MITIGATED
|
||||
- **Attack Vector:** finger.exe abuse to retrieve malicious commands
|
||||
- **Our Protection:** Outbound TCP port 79 blocked
|
||||
- **Impact:** Zero (legacy protocol unused in 2025)
|
||||
|
||||
### Attack Surface Reduction:
|
||||
|
||||
| Attack Type | Protection |
|
||||
|-------------|-----------|
|
||||
| **Email Malware** | ASR: Block executables from email |
|
||||
| **USB Malware** | ASR: Block untrusted USB processes |
|
||||
| **Office Macros** | ASR: Block Win32 API calls |
|
||||
| **Credential Theft** | ASR: Block LSASS access + WDigest disabled |
|
||||
| **Ransomware** | ASR: Advanced ransomware protection |
|
||||
| **MITM Attacks** | DNS DoH + LLMNR/NetBIOS disabled |
|
||||
| **RDP Brute-Force** | NLA enforcement + optional disable |
|
||||
| **Proxy Hijacking** | WPAD disabled |
|
||||
| **TLS Exploits** | TLS 1.0/1.1 disabled (BEAST/CRIME) |
|
||||
| **PowerShell Downgrade** | PSv2 removed |
|
||||
| **DMA Attacks** | FireWire (IEEE 1394) blocked |
|
||||
|
||||
---
|
||||
|
||||
## 📋 Interactive Features
|
||||
|
||||
### User Prompts (13 Total):
|
||||
|
||||
#### SecurityBaseline (1 prompt):
|
||||
1. **BitLocker USB Policy** (Home/Enterprise)
|
||||
- Home Mode: USB works normally (no encryption enforcement)
|
||||
- Enterprise Mode: Require BitLocker encryption on USB drives
|
||||
|
||||
#### ASR (2 prompts):
|
||||
2. **PSExec/WMI rule mode** (Block/Audit)
|
||||
- Block: Maximum security (may break SCCM/remote admin)
|
||||
- Audit: Log only (compatibility testing)
|
||||
|
||||
3. **New Software rule mode** (Block/Audit)
|
||||
- Block: Block executables that don't meet prevalence criteria
|
||||
- Audit: Log only (recommended for new software installs)
|
||||
|
||||
#### DNS (2 prompts):
|
||||
4. **Provider selection** (Quad9/Cloudflare/AdGuard/Skip)
|
||||
- 3 DNS providers available with ratings
|
||||
- Skip option to keep current DNS
|
||||
|
||||
5. **DoH Mode selection** (REQUIRE/ALLOW/Skip)
|
||||
- REQUIRE: No unencrypted fallback (maximum security)
|
||||
- ALLOW: Fallback to UDP if needed (VPN/corporate/mobile)
|
||||
- Skip: Keep current DNS settings
|
||||
|
||||
#### Privacy (3 prompts):
|
||||
6. **Mode selection** (MSRecommended/Strict/Paranoid)
|
||||
- MSRecommended: Fully supported, production-safe
|
||||
- Strict: Maximum privacy (may break Teams/Zoom)
|
||||
- Paranoid: Extreme privacy (very limited support)
|
||||
|
||||
7. **Cloud Clipboard** (Enable/Disable) - *only in MSRecommended mode*
|
||||
- Disable: No cross-device clipboard sync (privacy)
|
||||
- Enable: Keep cloud clipboard functionality
|
||||
|
||||
8. **Bloatware Removal** (Yes/No)
|
||||
- Yes: Remove 10-24 pre-installed apps
|
||||
- No: Keep all apps installed
|
||||
|
||||
#### AdvancedSecurity (5 prompts):
|
||||
9. **Profile selection** (Balanced/Enterprise/Maximum)
|
||||
- Balanced: Safe defaults for home users
|
||||
- Enterprise: Domain-aware checks
|
||||
- Maximum: Maximum hardening
|
||||
|
||||
10. **RDP Disable** (Yes/No) - *Balanced profile only, Maximum always disables*
|
||||
- Yes: Completely disable Remote Desktop
|
||||
- No: Keep RDP enabled (with NLA hardening)
|
||||
|
||||
11. **UPnP/SSDP Block** (Yes/No) - *Balanced profile only, others always block*
|
||||
- Yes: Block UPnP/SSDP (may break DLNA streaming)
|
||||
- No: Keep UPnP enabled
|
||||
|
||||
12. **Wireless Display Disable** (Yes/No) - *all profiles*
|
||||
- Yes: Completely disable Miracast (use HDMI instead)
|
||||
- No: Keep Miracast hardened but usable
|
||||
|
||||
13. **Admin Shares Disable** (Yes/No) - *Domain-joined systems only*
|
||||
- Yes: Disable C$/ADMIN$ even on domain (may break IT tools)
|
||||
- No: Keep admin shares for IT management (SCCM, PDQ, etc.)
|
||||
|
||||
### Backup & Restore:
|
||||
|
||||
- ✅ Session-based backup system (Initialize-BackupSystem)
|
||||
- ✅ Full registry backup before changes
|
||||
- ✅ Service state backup
|
||||
- ✅ Feature state backup
|
||||
- ✅ DHCP settings backup (DNS module)
|
||||
- ✅ Restore capability for all modules
|
||||
|
||||
### Verification:
|
||||
|
||||
- ✅ Test-BaselineCompliance (SecurityBaseline)
|
||||
- ✅ Test-ASRCompliance (ASR)
|
||||
- ✅ Test-DNSConnectivity (DNS)
|
||||
- ✅ Test-AntiAI (AntiAI)
|
||||
- ✅ Test-PrivacyCompliance (Privacy)
|
||||
- ✅ Test-EdgeHardening (EdgeHardening)
|
||||
- ✅ Test-AdvancedSecurity (AdvancedSecurity)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Safety Features
|
||||
|
||||
### Pre-Flight Checks:
|
||||
- ✅ Administrator elevation required
|
||||
- ✅ OS version detection (Windows 11 24H2+)
|
||||
- ✅ Hardware capability detection (TPM, VBS)
|
||||
- ✅ Domain-joined system detection
|
||||
|
||||
### Execution Safety:
|
||||
- ✅ WhatIf mode (dry-run preview)
|
||||
- ✅ Profile-based execution (Balanced/Enterprise/Maximum)
|
||||
- ✅ Incremental backups
|
||||
- ✅ Error handling with graceful degradation
|
||||
- ✅ Comprehensive logging
|
||||
|
||||
### Rollback:
|
||||
- ✅ Restore-SecurityBaseline
|
||||
- ✅ Restore-DNSSettings
|
||||
- ✅ Restore-PrivacySettings
|
||||
- ✅ Restore-AdvancedSecuritySettings
|
||||
|
||||
---
|
||||
|
||||
## 📊 Home User Friendly
|
||||
|
||||
### Password Policies (Low Impact):
|
||||
- ✅ Only affect local accounts (~5% of home users)
|
||||
- ✅ 95%+ use Microsoft Accounts (managed online by Microsoft)
|
||||
- ✅ Policies: MinimumPasswordLength (14), PasswordHistory (24), Lockout (10)
|
||||
|
||||
### BitLocker USB (User Choice):
|
||||
- ✅ Default: Home Mode (USB works normally)
|
||||
- ✅ Option: Enterprise Mode (encryption enforcement)
|
||||
- ✅ Interactive prompt during SecurityBaseline
|
||||
|
||||
### FireWire Blocking:
|
||||
- ✅ Blocks IEEE 1394 devices (DMA attack prevention)
|
||||
- ✅ Impact: <1% of users (obsolete technology)
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Framework Status
|
||||
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
NoID Privacy v2.2.0
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Total Settings: 632 ✅
|
||||
Modules: 7/7 (100%) ✅
|
||||
Production Status: Ready ✅
|
||||
Verification: 100% ✅
|
||||
BACKUP-APPLY-VERIFY-RESTORE: Complete ✅
|
||||
|
||||
Zero-Day Protection: ✅ CVE-2025-9491 + ClickFix
|
||||
Microsoft Best Practices: 100% ✅
|
||||
Home User Friendly: ✅ Interactive prompts
|
||||
Enterprise Ready: ✅ Profile-based execution
|
||||
|
||||
Framework Completion: 🎉 100% COMPLETE
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** December 7, 2025
|
||||
**Framework Version:** v2.2.0
|
||||
86
Docs/LICENSE-HISTORY.md
Normal file
86
Docs/LICENSE-HISTORY.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# License History
|
||||
|
||||
This document tracks the license history of NoID Privacy.
|
||||
|
||||
---
|
||||
|
||||
## Current License (v2.x Series)
|
||||
|
||||
**GNU General Public License v3.0** (GPL-3.0)
|
||||
**Effective:** November 20, 2025
|
||||
**Applies to:** All v2.x releases (v2.0.0 and later)
|
||||
|
||||
See [LICENSE](LICENSE) for full text.
|
||||
|
||||
---
|
||||
|
||||
## Previous License (v1.0.0 - v1.8.3)
|
||||
|
||||
**MIT License**
|
||||
**Effective:** 2024 - November 13, 2025
|
||||
**Applies to:** All v1.x releases (v1.0.0 through v1.8.3)
|
||||
|
||||
### Why the License Changed
|
||||
|
||||
**Reason for Change:**
|
||||
- Version 2.0+ represents a **complete rewrite** from scratch
|
||||
- 100% new codebase with modular architecture
|
||||
- Zero code from v1.x carried forward
|
||||
- New author retains full copyright over v2.x codebase
|
||||
|
||||
**Legal Basis:**
|
||||
- As the sole copyright holder of v2.x code, relicensing is permitted
|
||||
- MIT License (v1.x) allows derivative works under different licenses
|
||||
- No external contributors' rights are affected (complete rewrite)
|
||||
|
||||
**Impact:**
|
||||
- **v1.8.3 and earlier:** Remain under MIT License (cannot be changed retroactively)
|
||||
- **v2.2.0 and later:** Licensed under GPL v3.0
|
||||
- Forks of v1.x can remain MIT-licensed
|
||||
- Forks of v2.x must comply with GPL v3.0
|
||||
|
||||
---
|
||||
|
||||
## MIT License Text (v1.x)
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024-2025 NexusOne23
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dual-Licensing (v2.x+)
|
||||
|
||||
Version 2.x+ is available under dual-licensing:
|
||||
|
||||
1. **GPL v3.0** (Open Source) - Free for open-source projects
|
||||
2. **Commercial License** - For closed-source commercial use
|
||||
|
||||
Contact via [GitHub Discussions](https://github.com/NexusOne23/noid-privacy/discussions) for commercial licensing inquiries.
|
||||
|
||||
---
|
||||
|
||||
## Questions?
|
||||
|
||||
If you have questions about licensing, please open a discussion on GitHub:
|
||||
https://github.com/NexusOne23/noid-privacy/discussions
|
||||
467
Docs/NONINTERACTIVE-MODE.md
Normal file
467
Docs/NONINTERACTIVE-MODE.md
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
# NonInteractive Mode - CI/CD & Automation Guide
|
||||
|
||||
## Overview
|
||||
|
||||
NoID Privacy supports fully automated, non-interactive execution for CI/CD pipelines, group policy deployment, and mass system hardening. This guide explains how to run the framework without any interactive prompts.
|
||||
|
||||
---
|
||||
|
||||
## Configuration-Based Execution
|
||||
|
||||
The framework automatically enters non-interactive mode when all required parameters are pre-configured in `config.json`.
|
||||
|
||||
### Required Configuration Keys
|
||||
|
||||
#### **1. DNS Module - Provider Selection**
|
||||
|
||||
```json
|
||||
{
|
||||
"modules": {
|
||||
"DNS": {
|
||||
"enabled": true,
|
||||
"priority": 3,
|
||||
"status": "IMPLEMENTED",
|
||||
"description": "Secure DNS with DoH",
|
||||
"provider": "Quad9"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Valid provider values:**
|
||||
- `"Quad9"` (default, security-focused, Swiss privacy)
|
||||
- `"Cloudflare"` (fastest resolver)
|
||||
- `"AdGuard"` (ad/tracker blocking)
|
||||
|
||||
**When provider is set:**
|
||||
- No interactive DNS provider selection prompt
|
||||
- Direct application of specified provider
|
||||
|
||||
---
|
||||
|
||||
#### **2. Privacy Module - Mode Selection**
|
||||
|
||||
```json
|
||||
{
|
||||
"modules": {
|
||||
"Privacy": {
|
||||
"enabled": true,
|
||||
"priority": 4,
|
||||
"status": "IMPLEMENTED",
|
||||
"description": "Privacy hardening",
|
||||
"mode": "MSRecommended"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Valid mode values:**
|
||||
- `"MSRecommended"` (default, fully supported, production-ready)
|
||||
- `"Strict"` (maximum privacy, breaks some apps)
|
||||
- `"Paranoid"` (hardcore, not recommended for production)
|
||||
|
||||
**When mode is set:**
|
||||
- No interactive privacy mode selection prompt
|
||||
- Direct application of specified mode with warnings logged
|
||||
|
||||
---
|
||||
|
||||
#### **3. Global Options - Automation Settings**
|
||||
|
||||
```json
|
||||
{
|
||||
"options": {
|
||||
"dryRun": false,
|
||||
"createBackup": true,
|
||||
"verboseLogging": false,
|
||||
"autoReboot": false,
|
||||
"nonInteractive": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key options:**
|
||||
- `nonInteractive`: Explicitly disable all prompts (optional, auto-detected)
|
||||
- `autoReboot`: Automatically restart after hardening (use with caution)
|
||||
- `createBackup`: Always create backups (highly recommended)
|
||||
|
||||
---
|
||||
|
||||
## Command-Line Execution
|
||||
|
||||
### **Basic Non-Interactive Execution**
|
||||
|
||||
```powershell
|
||||
# Run all enabled modules from config.json
|
||||
.\NoIDPrivacy.ps1 -Module All
|
||||
|
||||
# Run specific module with provider pre-configured
|
||||
.\NoIDPrivacy.ps1 -Module DNS
|
||||
|
||||
# Run with command-line overrides
|
||||
.\NoIDPrivacy.ps1 -Module Privacy -DryRun
|
||||
|
||||
# Run in verbose mode for logging
|
||||
.\NoIDPrivacy.ps1 -Module All -VerboseLogging
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **CI/CD Pipeline Example**
|
||||
|
||||
#### **Azure DevOps Pipeline**
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- task: PowerShell@2
|
||||
displayName: 'NoID Privacy Hardening'
|
||||
inputs:
|
||||
targetType: 'filePath'
|
||||
filePath: '$(System.DefaultWorkingDirectory)/NoIDPrivacy.ps1'
|
||||
arguments: '-Module All -VerboseLogging'
|
||||
errorActionPreference: 'stop'
|
||||
pwsh: false
|
||||
condition: succeededOrFailed()
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish Hardening Logs'
|
||||
inputs:
|
||||
PathtoPublish: 'Logs'
|
||||
ArtifactName: 'hardening-logs'
|
||||
```
|
||||
|
||||
#### **GitHub Actions Workflow**
|
||||
|
||||
```yaml
|
||||
name: Windows Hardening
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # Weekly on Sunday
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
harden:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Run NoID Privacy
|
||||
shell: powershell
|
||||
run: |
|
||||
.\NoIDPrivacy.ps1 -Module All -VerboseLogging
|
||||
|
||||
- name: Upload Logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: hardening-logs
|
||||
path: Logs/
|
||||
```
|
||||
|
||||
#### **Jenkins Pipeline**
|
||||
|
||||
```groovy
|
||||
pipeline {
|
||||
agent { label 'windows' }
|
||||
|
||||
stages {
|
||||
stage('Hardening') {
|
||||
steps {
|
||||
powershell '''
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force
|
||||
.\\NoIDPrivacy.ps1 -Module All -VerboseLogging
|
||||
'''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
archiveArtifacts artifacts: 'Logs/**/*', fingerprint: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Group Policy Deployment
|
||||
|
||||
### **Method 1: Startup Script**
|
||||
|
||||
1. Copy NoID Privacy to network share:
|
||||
```
|
||||
\\domain.local\NETLOGON\NoIDPrivacy\
|
||||
```
|
||||
|
||||
2. Create GPO startup script:
|
||||
```powershell
|
||||
# Startup-Hardening.ps1
|
||||
$scriptPath = "\\domain.local\NETLOGON\NoIDPrivacy\NoIDPrivacy.ps1"
|
||||
|
||||
if (Test-Path $scriptPath) {
|
||||
& $scriptPath -Module All -VerboseLogging
|
||||
}
|
||||
```
|
||||
|
||||
3. Link GPO to target OU
|
||||
4. Result logged to: `C:\NoIDPrivacy\Logs\`
|
||||
|
||||
---
|
||||
|
||||
### **Method 2: Scheduled Task (Recommended)**
|
||||
|
||||
Deploy via GPO Scheduled Task:
|
||||
|
||||
```xml
|
||||
<!-- Task XML for GPO deployment -->
|
||||
<Task>
|
||||
<Triggers>
|
||||
<CalendarTrigger>
|
||||
<StartBoundary>2025-01-01T03:00:00</StartBoundary>
|
||||
<ScheduleByWeek>
|
||||
<DaysOfWeek><Sunday /></DaysOfWeek>
|
||||
<WeeksInterval>1</WeeksInterval>
|
||||
</ScheduleByWeek>
|
||||
</CalendarTrigger>
|
||||
</Triggers>
|
||||
<Actions>
|
||||
<Exec>
|
||||
<Command>powershell.exe</Command>
|
||||
<Arguments>-ExecutionPolicy Bypass -File "\\domain.local\NETLOGON\NoIDPrivacy\NoIDPrivacy.ps1" -Module All</Arguments>
|
||||
</Exec>
|
||||
</Actions>
|
||||
</Task>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification Without Interaction
|
||||
|
||||
### **Silent Verification**
|
||||
|
||||
```powershell
|
||||
# Run verification and export structured JSON
|
||||
.\Tools\Verify-Complete-Hardening.ps1 -ExportPath "verification-result.json"
|
||||
|
||||
# Parse results programmatically
|
||||
$verification = Get-Content "verification-result.json" | ConvertFrom-Json
|
||||
|
||||
if ($verification.Failed -eq 0) {
|
||||
Write-Output "All settings verified successfully"
|
||||
exit 0
|
||||
} else {
|
||||
Write-Error "Verification failed: $($verification.Failed) settings did not match expected values"
|
||||
exit 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables (Alternative)
|
||||
|
||||
Instead of modifying `config.json`, use environment variables:
|
||||
|
||||
```powershell
|
||||
# Set environment variables
|
||||
$env:NOIDPRIVACY_DNS_PROVIDER = "Quad9"
|
||||
$env:NOIDPRIVACY_PRIVACY_MODE = "MSRecommended"
|
||||
$env:NOIDPRIVACY_NONINTERACTIVE = "true"
|
||||
|
||||
# Run framework
|
||||
.\NoIDPrivacy.ps1 -Module All
|
||||
```
|
||||
|
||||
**Note:** Environment variables require framework support and are currently a roadmap feature (not yet implemented).
|
||||
|
||||
---
|
||||
|
||||
## Return Codes
|
||||
|
||||
**Note:** Exit codes are currently not implemented. Error handling should be done via try/catch blocks and checking the log files.
|
||||
|
||||
### **Example: Error Handling in Scripts**
|
||||
|
||||
```powershell
|
||||
try {
|
||||
.\NoIDPrivacy.ps1 -Module All -ErrorAction Stop
|
||||
Write-Output "Hardening completed successfully"
|
||||
}
|
||||
catch {
|
||||
Write-Error "Hardening failed: $_"
|
||||
# Check logs for details
|
||||
$latestLog = Get-ChildItem "Logs" -Filter "NoIDPrivacy-*.log" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
Get-Content $latestLog.FullName | Select-String "ERROR"
|
||||
exit 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices for Automation
|
||||
|
||||
### **1. Always Use DryRun First**
|
||||
|
||||
```powershell
|
||||
# Test configuration without applying
|
||||
.\NoIDPrivacy.ps1 -Module All -DryRun -VerboseLogging
|
||||
|
||||
# Review logs before production run
|
||||
Get-Content "Logs\NoIDPrivacy-*.log" | Select-String "ERROR|WARNING"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **2. Centralized Logging**
|
||||
|
||||
Configure log aggregation for enterprise deployment:
|
||||
|
||||
```powershell
|
||||
# Example: Copy logs to central location
|
||||
$logPath = "C:\NoIDPrivacy\Logs"
|
||||
$centralPath = "\\fileserver\HardeningLogs\$env:COMPUTERNAME"
|
||||
|
||||
if (Test-Path $logPath) {
|
||||
Copy-Item -Path "$logPath\*" -Destination $centralPath -Recurse -Force
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **3. Rollback Plan**
|
||||
|
||||
Always maintain rollback capability:
|
||||
|
||||
```powershell
|
||||
# Before mass deployment, test rollback
|
||||
.\NoIDPrivacy.ps1 -Module DNS
|
||||
|
||||
# Restore from latest backup (uses Core\Rollback.ps1)
|
||||
.\Core\Rollback.ps1 -RestoreLatest
|
||||
|
||||
# Or restore specific module
|
||||
.\Modules\DNS\Public\Restore-DNSSettings.ps1
|
||||
|
||||
# Verify rollback worked
|
||||
.\Tools\Verify-Complete-Hardening.ps1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting Non-Interactive Mode
|
||||
|
||||
### **Issue: Still Showing Prompts**
|
||||
|
||||
**Cause:** Provider/mode not configured in `config.json`
|
||||
|
||||
**Solution:**
|
||||
```json
|
||||
{
|
||||
"modules": {
|
||||
"DNS": { "provider": "Quad9" },
|
||||
"Privacy": { "mode": "MSRecommended" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Issue: Script Fails Silently**
|
||||
|
||||
**Cause:** Error suppression in CI/CD
|
||||
|
||||
**Solution:**
|
||||
```powershell
|
||||
# Use verbose logging + error action
|
||||
.\NoIDPrivacy.ps1 -Module All -VerboseLogging -ErrorAction Stop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Issue: Insufficient Permissions**
|
||||
|
||||
**Cause:** Not running as Administrator
|
||||
|
||||
**Solution:**
|
||||
```powershell
|
||||
# For scheduled tasks, use SYSTEM account or admin user
|
||||
# For GPO, startup scripts run as SYSTEM automatically
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Example: Enterprise Deployment Script
|
||||
|
||||
```powershell
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Enterprise deployment wrapper for NoID Privacy
|
||||
|
||||
.DESCRIPTION
|
||||
Automated hardening with centralized logging and email reporting
|
||||
#>
|
||||
|
||||
param(
|
||||
[switch]$DryRun,
|
||||
[string]$EmailRecipient = "security@company.com"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
try {
|
||||
# Pre-flight checks
|
||||
if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw "Must run as Administrator"
|
||||
}
|
||||
|
||||
# Run hardening
|
||||
Write-Output "Starting NoID Privacy hardening..."
|
||||
$result = & "$scriptRoot\NoIDPrivacy.ps1" -Module All -DryRun:$DryRun -VerboseLogging
|
||||
|
||||
# Collect logs
|
||||
$logPath = "$scriptRoot\Logs"
|
||||
$latestLog = Get-ChildItem $logPath -Filter "NoIDPrivacy-*.log" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
|
||||
# Send report email
|
||||
$emailBody = @"
|
||||
NoID Privacy Hardening Report
|
||||
|
||||
Computer: $env:COMPUTERNAME
|
||||
Date: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
|
||||
Mode: $(if($DryRun){"DRY RUN"}else{"APPLY"})
|
||||
|
||||
Log file attached.
|
||||
"@
|
||||
|
||||
Send-MailMessage -To $EmailRecipient `
|
||||
-From "hardening@company.com" `
|
||||
-Subject "Hardening Report - $env:COMPUTERNAME" `
|
||||
-Body $emailBody `
|
||||
-Attachments $latestLog.FullName `
|
||||
-SmtpServer "smtp.company.com"
|
||||
|
||||
Write-Output "Hardening completed successfully"
|
||||
exit 0
|
||||
}
|
||||
catch {
|
||||
Write-Error "Hardening failed: $_"
|
||||
exit 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**For non-interactive execution:**
|
||||
|
||||
1. ✅ Configure `provider` and `mode` in `config.json`
|
||||
2. ✅ Use `-Module All` parameter
|
||||
3. ✅ Enable `-VerboseLogging` for CI/CD
|
||||
4. ✅ Always test with `-DryRun` first
|
||||
5. ✅ Implement centralized logging
|
||||
6. ✅ Plan rollback procedures
|
||||
|
||||
**The framework is fully automation-ready when configured correctly!**
|
||||
105
Docs/SECURITY-ANALYSIS.md
Normal file
105
Docs/SECURITY-ANALYSIS.md
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
# 🛡️ Security Impact Analysis for Home Users
|
||||
|
||||
**Understanding how Enterprise Security affects your Home PC**
|
||||
|
||||
This document explains the impact of applying the **Microsoft Security Baseline** (designed for Enterprise) to a standalone **Windows 11 Home/Pro** workstation.
|
||||
|
||||
> **Executive Summary:**
|
||||
> 98% of the settings improve security without visible impact. The remaining 2% (BitLocker, Password Policy) have been adjusted or documented to ensure usability for home users.
|
||||
|
||||
---
|
||||
|
||||
## 1. Password Policies
|
||||
|
||||
**Setting:** `MinimumPasswordLength = 14`, `PasswordHistory = 24`
|
||||
|
||||
### 🏠 Home User Impact: **Low / None**
|
||||
- **Microsoft Accounts:** These policies DO NOT affect your Microsoft Account (Outlook/Live/Hotmail) login. Microsoft manages those policies in the cloud.
|
||||
- **Local Accounts:** If you use a local "Offline" account, you will be forced to set a 14-character password next time you change it.
|
||||
- **PIN / FaceID:** Unaffected. You can still use Windows Hello PIN (4-6 digits) to sign in. The complex password is only for the underlying account.
|
||||
|
||||
**Recommendation:** Use a password manager generated password for your local account, and use PIN for daily login.
|
||||
|
||||
---
|
||||
|
||||
## 2. BitLocker USB Protection
|
||||
|
||||
**Setting:** `DenyWriteAccessOnFixedDrivesIfNotProtected` (Registry Policy)
|
||||
|
||||
### 🏠 Home User Impact: **High (if enabled)**
|
||||
- **Enterprise Default:** Windows blocks writing to ANY USB drive unless it is encrypted with BitLocker.
|
||||
- **NoID Privacy Default:** **DISABLED (Home Mode)**.
|
||||
- **Why?** Home users often share USB sticks with TVs, cars, or friends (Mac/Linux). Enforcing BitLocker makes the drive unreadable on non-Windows devices.
|
||||
|
||||
**Your Choice:**
|
||||
The tool asks you interactively:
|
||||
- **[N] No (Default):** USB drives work normally. Safe for home use.
|
||||
- **[Y] Yes:** Maximum security. USB drives are Read-Only until you encrypt them.
|
||||
|
||||
---
|
||||
|
||||
## 3. FireWire (IEEE 1394) Blocking
|
||||
|
||||
**Setting:** DMA Protection / Device Installation Restrictions
|
||||
|
||||
### 🏠 Home User Impact: **Near Zero**
|
||||
- **What is it?** An obsolete connection standard (pre-USB 3.0) used by old camcorders.
|
||||
- **Why block it?** Vulnerable to Direct Memory Access (DMA) attacks where an attacker plugs a device in and steals RAM content (passwords/keys) in seconds.
|
||||
- **Reality:** Most modern PCs don't even have FireWire ports.
|
||||
|
||||
**Workaround:**
|
||||
If you absolutely need to transfer video from a 2005 camcorder:
|
||||
```powershell
|
||||
# Run as Admin to temporarily allow
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DeviceInstall\Restrictions" -Name "DenyDeviceClasses" -Value 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Attack Surface Reduction (ASR)
|
||||
|
||||
**Setting:** 19 Defender Rules (Block Mode)
|
||||
|
||||
### 🏠 Home User Impact: **Low**
|
||||
- **Blocked:** running `.exe` files directly from an email attachment (Outlook).
|
||||
- **Solution:** Save the file to Downloads folder first, then run it. This simple friction stops 90% of malware.
|
||||
- **Blocked:** Office Macros downloading files.
|
||||
- **Solution:** Don't enable macros in documents from unknown sources.
|
||||
|
||||
**PSExec / WMI Rule:**
|
||||
- Enterprise admin tools used for remote management.
|
||||
- Home users don't use these. Blocking them stops malware lateral movement.
|
||||
- **Safe to Block.**
|
||||
|
||||
---
|
||||
|
||||
## 5. App Compatibility
|
||||
|
||||
### Known Issues
|
||||
- **Legacy Games (Pre-2010):** Some old games require **DirectPlay** or **SMBv1**.
|
||||
- *NoID Privacy* disables SMBv1 (WannaCry ransomware vector).
|
||||
- **Network Scanners:** Old Canon/HP printers might use SMBv1 for "Scan to Folder".
|
||||
- *Solution:* Use "Scan to Email" or update printer firmware.
|
||||
- **Cheater Software:** Some game hacks/trainers inject code into processes. ASR rules will block this.
|
||||
|
||||
### Troubleshooting
|
||||
If an app fails to launch:
|
||||
1. Check `Windows Security` > `Protection History`.
|
||||
2. It will show if an **ASR Rule** or **Controlled Folder Access** blocked it.
|
||||
|
||||
---
|
||||
|
||||
## 6. AI & Privacy
|
||||
|
||||
**Setting:** Recall & Copilot disabled, Telemetry minimized (Security-Essential level)
|
||||
|
||||
### 🏠 Home User Impact: **Positive**
|
||||
- **Performance:** Less background activity (indexing, analyzing).
|
||||
- **Privacy:** Screenshots (Recall) are not taken.
|
||||
- **Experience:** Start Menu and Taskbar are cleaner (no Copilot ads).
|
||||
- **Functionality:** Paint/Notepad AI features (Cocreator) will be disabled. If you pay for Copilot Pro, you might want to skip the **AntiAI** module.
|
||||
|
||||
---
|
||||
|
||||
**Conclusion:**
|
||||
NoID Privacy transforms a "leaky" Home edition into an "Enterprise Fortress" for everyday use, ohne die Fähigkeit zu verlieren, Spiele zu spielen oder normal zu surfen. Die wenigen Reibungspunkte (USB, Makros, alte Protokolle) sind bewusst gesetzte Sicherheitstore.
|
||||
697
LICENSE
Normal file
697
LICENSE
Normal file
|
|
@ -0,0 +1,697 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
||||
================================================================================
|
||||
|
||||
APPLICATION TO NoID Privacy:
|
||||
|
||||
NoID Privacy
|
||||
Copyright (C) 2025 NexusOne23
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing options (dual-licensing), please see Docs/LICENSE-HISTORY.md
|
||||
or contact via GitHub Discussions at https://github.com/NexusOne23/noid-privacy/discussions
|
||||
39
Modules/ASR/ASR.psd1
Normal file
39
Modules/ASR/ASR.psd1
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
@{
|
||||
RootModule = 'ASR.psm1'
|
||||
ModuleVersion = '2.2.0'
|
||||
GUID = 'b2c3d4e5-f6a7-8901-bcde-f23456789012'
|
||||
Author = 'NexusOne23'
|
||||
CompanyName = 'Open Source Project'
|
||||
Copyright = '(c) 2025 NexusOne23. Licensed under GPL-3.0.'
|
||||
Description = 'Attack Surface Reduction (ASR) - All 19 Microsoft Defender ASR rules in Block mode for maximum protection against modern threats'
|
||||
|
||||
PowerShellVersion = '5.1'
|
||||
|
||||
RequiredModules = @()
|
||||
|
||||
FunctionsToExport = @(
|
||||
'Invoke-ASRRules'
|
||||
)
|
||||
|
||||
CmdletsToExport = @()
|
||||
VariablesToExport = @()
|
||||
AliasesToExport = @()
|
||||
|
||||
PrivateData = @{
|
||||
PSData = @{
|
||||
Tags = @('Security', 'ASR', 'AttackSurfaceReduction', 'Defender', 'Windows11', 'Ransomware')
|
||||
LicenseUri = ''
|
||||
ProjectUri = ''
|
||||
ReleaseNotes = @"
|
||||
v2.2.0 - Production Release
|
||||
- All 19 ASR rules implementation
|
||||
- Hybrid approach: Registry backup + Set-MpPreference application
|
||||
- SCCM/Configuration Manager detection
|
||||
- Cloud protection verification
|
||||
- Exclusions management support
|
||||
- Full BACKUP/APPLY/VERIFY/RESTORE support
|
||||
- Security Baseline overlap detection and logging
|
||||
"@
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Modules/ASR/ASR.psm1
Normal file
52
Modules/ASR/ASR.psm1
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Attack Surface Reduction (ASR) Module
|
||||
|
||||
.DESCRIPTION
|
||||
Enables all 19 Microsoft Defender ASR rules in Block mode for comprehensive protection.
|
||||
|
||||
Hybrid implementation:
|
||||
- Registry for backup/verification
|
||||
- Set-MpPreference for clean application
|
||||
|
||||
.NOTES
|
||||
Author: NexusOne23
|
||||
Version: 2.2.0
|
||||
Requires: PowerShell 5.1+, Administrator privileges, Windows Defender
|
||||
#>
|
||||
|
||||
# Get the module root path
|
||||
$ModuleRoot = $PSScriptRoot
|
||||
|
||||
# Dot source all Private functions
|
||||
$PrivatePath = Join-Path $ModuleRoot "Private"
|
||||
if (Test-Path $PrivatePath) {
|
||||
Get-ChildItem -Path $PrivatePath -Filter "*.ps1" | ForEach-Object {
|
||||
try {
|
||||
. $_.FullName
|
||||
}
|
||||
catch {
|
||||
Write-Host "WARNING: Failed to import private function $($_.Name): $_" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Dot source all Public functions
|
||||
$PublicPath = Join-Path $ModuleRoot "Public"
|
||||
if (Test-Path $PublicPath) {
|
||||
Get-ChildItem -Path $PublicPath -Filter "*.ps1" | ForEach-Object {
|
||||
try {
|
||||
. $_.FullName
|
||||
}
|
||||
catch {
|
||||
Write-Host "WARNING: Failed to import public function $($_.Name): $_" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Export public functions + Test-ASRCompliance (needed for Invoke-ASRRules verification)
|
||||
Export-ModuleMember -Function @('Invoke-ASRRules', 'Test-ASRCompliance')
|
||||
|
||||
# Alias for naming consistency (non-breaking change)
|
||||
New-Alias -Name 'Invoke-ASR' -Value 'Invoke-ASRRules' -Force
|
||||
Export-ModuleMember -Alias 'Invoke-ASR'
|
||||
173
Modules/ASR/Config/ASR-Rules.json
Normal file
173
Modules/ASR/Config/ASR-Rules.json
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
[
|
||||
{
|
||||
"Name": "Block abuse of exploited vulnerable signed drivers",
|
||||
"GUID": "56a863a9-875e-4185-98a7-b882c64b5ce5",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": [],
|
||||
"Description": "Prevents applications from writing vulnerable signed drivers to disk"
|
||||
},
|
||||
{
|
||||
"Name": "Block Adobe Reader from creating child processes",
|
||||
"GUID": "7674ba52-37eb-4a4f-a9a1-f0f9a1619a2c",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": [],
|
||||
"Description": "Blocks Adobe Reader from creating processes to prevent malware spread"
|
||||
},
|
||||
{
|
||||
"Name": "Block all Office applications from creating child processes",
|
||||
"GUID": "d4f940ab-401b-4efc-aadc-ad5f3c50688a",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": [],
|
||||
"Description": "Blocks Office apps from creating child processes to prevent malware execution"
|
||||
},
|
||||
{
|
||||
"Name": "Block credential stealing from LSASS",
|
||||
"GUID": "9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": ["Produces high volume of events - safe to ignore most blocks", "Not required if LSA Protection enabled"],
|
||||
"Description": "Locks down LSASS to prevent credential theft (Mimikatz protection)"
|
||||
},
|
||||
{
|
||||
"Name": "Block executable content from email client and webmail",
|
||||
"GUID": "be9ba2d9-53ea-4cdc-84e5-9b1eeee46550",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": [],
|
||||
"Description": "Blocks executable files from being launched from Outlook/webmail"
|
||||
},
|
||||
{
|
||||
"Name": "Block executable files unless they meet prevalence, age, or trusted list",
|
||||
"GUID": "01443614-cd74-433a-b99e-2ecdc07bfc25",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Missing",
|
||||
"RequiresCloudProtection": true,
|
||||
"Warnings": ["Requires cloud-delivered protection", "May block legitimate software - test thoroughly"],
|
||||
"Description": "Blocks untrusted or unknown executable files based on reputation"
|
||||
},
|
||||
{
|
||||
"Name": "Block execution of potentially obfuscated scripts",
|
||||
"GUID": "5beb7efe-fd9a-4556-801d-275e5ffc04cc",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": true,
|
||||
"Warnings": ["Requires cloud-delivered protection"],
|
||||
"Description": "Detects and blocks suspicious properties in obfuscated scripts (JS/VBS/PS)"
|
||||
},
|
||||
{
|
||||
"Name": "Block JavaScript or VBScript from launching downloaded executable content",
|
||||
"GUID": "d3e037e1-3eb8-44c8-a917-57927947596d",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": [],
|
||||
"Description": "Prevents scripts from launching potentially malicious downloaded content"
|
||||
},
|
||||
{
|
||||
"Name": "Block Office applications from creating executable content",
|
||||
"GUID": "3b576869-a4ec-4529-8536-b80a7769e899",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": [],
|
||||
"Description": "Prevents Office from saving malicious components to disk for persistence"
|
||||
},
|
||||
{
|
||||
"Name": "Block Office applications from injecting code into other processes",
|
||||
"GUID": "75668c1f-73b5-4cf0-bb93-3ecf5cb7cc84",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": ["Requires restarting Office applications after configuration"],
|
||||
"Description": "Blocks code injection from Office apps into other processes"
|
||||
},
|
||||
{
|
||||
"Name": "Block Office communication application from creating child processes",
|
||||
"GUID": "26190899-1602-49e8-8b27-eb1d0a1ce869",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": [],
|
||||
"Description": "Prevents Outlook from creating child processes (social engineering protection)"
|
||||
},
|
||||
{
|
||||
"Name": "Block persistence through WMI event subscription",
|
||||
"GUID": "e6db77e5-3df2-4cf1-b95a-636979351e5b",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": ["If using SCCM (CcmExec.exe), audit for 60 days first"],
|
||||
"Description": "Prevents malware from abusing WMI to attain persistence"
|
||||
},
|
||||
{
|
||||
"Name": "Block process creations from PSExec and WMI commands",
|
||||
"GUID": "d1e49aac-8f56-4280-b9ba-993a6d77406c",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Audit",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": ["INCOMPATIBLE with SCCM/Configuration Manager", "Security Baseline uses Audit mode", "Only enable Block if NOT using SCCM"],
|
||||
"Description": "Blocks processes created through PsExec and WMI (lateral movement protection)"
|
||||
},
|
||||
{
|
||||
"Name": "Block rebooting machine in Safe Mode",
|
||||
"GUID": "33ddedf1-c6e0-47cb-833e-de6133960387",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Missing",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": ["New rule (2024) - not yet in TVM"],
|
||||
"Description": "Blocks commands to restart machines in Safe Mode (ransomware protection)"
|
||||
},
|
||||
{
|
||||
"Name": "Block untrusted and unsigned processes that run from USB",
|
||||
"GUID": "b2b3f03d-6a65-4f7b-a9c7-1c7ef74a9ba4",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": [],
|
||||
"Description": "Prevents unsigned/untrusted executables from running from USB drives"
|
||||
},
|
||||
{
|
||||
"Name": "Block use of copied or impersonated system tools",
|
||||
"GUID": "c0033c00-d16d-4114-a5a0-dc9b3a7d2ceb",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Missing",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": ["New rule (2024) - not yet in TVM"],
|
||||
"Description": "Blocks executables identified as copies/impostors of Windows system tools"
|
||||
},
|
||||
{
|
||||
"Name": "Block Webshell creation for Servers",
|
||||
"GUID": "a8f5898e-1dc8-49a9-9878-85004b8a61e6",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Missing",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": ["New rule (2024) - not yet in TVM", "Server-focused but safe on clients"],
|
||||
"Description": "Blocks web shell script creation on servers"
|
||||
},
|
||||
{
|
||||
"Name": "Block Win32 API calls from Office macros",
|
||||
"GUID": "92e97fa1-2edf-4476-bdd6-9dd0b4dddc7b",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": false,
|
||||
"Warnings": [],
|
||||
"Description": "Prevents VBA macros from calling Win32 APIs to launch malicious shellcode"
|
||||
},
|
||||
{
|
||||
"Name": "Use advanced protection against ransomware",
|
||||
"GUID": "c1db55ab-c21a-4637-bb3f-a12568109d35",
|
||||
"Action": 1,
|
||||
"BaselineStatus": "Block",
|
||||
"RequiresCloudProtection": true,
|
||||
"Warnings": ["Requires cloud-delivered protection"],
|
||||
"Description": "Extra layer of protection against ransomware using client and cloud heuristics"
|
||||
}
|
||||
]
|
||||
105
Modules/ASR/Private/Backup-ASRRegistry.ps1
Normal file
105
Modules/ASR/Private/Backup-ASRRegistry.ps1
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Backup current ASR registry settings
|
||||
|
||||
.DESCRIPTION
|
||||
Creates backup of ASR registry keys before modification
|
||||
|
||||
.PARAMETER BackupId
|
||||
Identifier for this backup
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with backup info
|
||||
#>
|
||||
|
||||
function Backup-ASRRegistry {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$BackupId = "ASR_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
|
||||
)
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Success = $true
|
||||
BackupPath = $null
|
||||
Errors = @()
|
||||
}
|
||||
|
||||
try {
|
||||
$asrPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender\Windows Defender Exploit Guard\ASR"
|
||||
|
||||
# BACKUP 1: Registry (for reference/verify)
|
||||
# CRITICAL FIX: Call Backup-RegistryKey unconditionally!
|
||||
# If key exists: Creates .reg backup
|
||||
# If key missing: Creates _EMPTY.json marker (Required for proper cleanup during restore)
|
||||
try {
|
||||
$regBackup = Backup-RegistryKey -KeyPath $asrPath -BackupName "ASR_Config"
|
||||
|
||||
if ($regBackup) {
|
||||
if ($regBackup -match "_EMPTY\.json$") {
|
||||
Write-Log -Level INFO -Message "ASR registry key does not exist - Created Empty Marker for cleanup" -Module "ASR"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level INFO -Message "ASR registry backed up with ID: $BackupId" -Module "ASR"
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Registry backup failed: $_" -Module "ASR"
|
||||
$result.Errors += "Registry backup failed: $_"
|
||||
}
|
||||
|
||||
# BACKUP 2: Get-MpPreference (CRITICAL for restore)
|
||||
# Registry-only restore doesn't work after Clear-ASRRules
|
||||
# We MUST save the active Defender configuration
|
||||
# IMPORTANT: We backup even if 0 rules are active (pre-hardening state)
|
||||
try {
|
||||
$mpPref = Get-MpPreference -ErrorAction Stop
|
||||
|
||||
$asrBackupData = @{
|
||||
BackupDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
BackupId = $BackupId
|
||||
Rules = @()
|
||||
}
|
||||
|
||||
# If rules exist, save them
|
||||
if ($mpPref.AttackSurfaceReductionRules_Ids -and $mpPref.AttackSurfaceReductionRules_Ids.Count -gt 0) {
|
||||
# Pair IDs with Actions
|
||||
for ($i = 0; $i -lt $mpPref.AttackSurfaceReductionRules_Ids.Count; $i++) {
|
||||
$asrBackupData.Rules += @{
|
||||
GUID = $mpPref.AttackSurfaceReductionRules_Ids[$i]
|
||||
Action = $mpPref.AttackSurfaceReductionRules_Actions[$i]
|
||||
}
|
||||
}
|
||||
Write-Log -Level INFO -Message "Backing up $($asrBackupData.Rules.Count) active ASR rules from Get-MpPreference" -Module "ASR"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level INFO -Message "No active ASR rules in Get-MpPreference - backing up empty state (pre-hardening)" -Module "ASR"
|
||||
}
|
||||
|
||||
# ALWAYS create the JSON file, even if Rules array is empty
|
||||
# This is critical for restore to know "system had 0 rules before hardening"
|
||||
$asrJson = $asrBackupData | ConvertTo-Json -Depth 5
|
||||
$backupFile = Register-Backup -Type "ASR" -Data $asrJson -Name "ASR_ActiveConfiguration"
|
||||
|
||||
if ($backupFile) {
|
||||
Write-Log -Level SUCCESS -Message "ASR MpPreference configuration backed up ($($asrBackupData.Rules.Count) rules)" -Module "ASR"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "Failed to register ASR MpPreference backup" -Module "ASR"
|
||||
$result.Errors += "MpPreference backup registration failed"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Get-MpPreference backup failed: $_" -Module "ASR"
|
||||
$result.Errors += "MpPreference backup failed: $_"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Success = $false
|
||||
$result.Errors += "Backup failed: $($_.Exception.Message)"
|
||||
Write-Log -Level ERROR -Message "ASR backup failed: $($_.Exception.Message)" -Module "ASR"
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
34
Modules/ASR/Private/Get-ASRRuleDefinitions.ps1
Normal file
34
Modules/ASR/Private/Get-ASRRuleDefinitions.ps1
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Load all 19 ASR rule definitions
|
||||
|
||||
.DESCRIPTION
|
||||
Loads ASR rules from JSON data file with all metadata
|
||||
|
||||
.OUTPUTS
|
||||
Array of ASR rule objects
|
||||
#>
|
||||
|
||||
function Get-ASRRuleDefinitions {
|
||||
[CmdletBinding()]
|
||||
[OutputType([Array])]
|
||||
param()
|
||||
|
||||
try {
|
||||
$configPath = Join-Path $PSScriptRoot "..\Config\ASR-Rules.json"
|
||||
|
||||
if (-not (Test-Path $configPath)) {
|
||||
throw "ASR rules configuration file not found: $configPath"
|
||||
}
|
||||
|
||||
$rules = Get-Content $configPath -Raw | ConvertFrom-Json
|
||||
|
||||
Write-Log -Level INFO -Message "Loaded $($rules.Count) ASR rule definitions" -Module "ASR"
|
||||
|
||||
return $rules
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to load ASR rules: $($_.Exception.Message)" -Module "ASR"
|
||||
throw
|
||||
}
|
||||
}
|
||||
28
Modules/ASR/Private/Restore-ASRSettings.ps1
Normal file
28
Modules/ASR/Private/Restore-ASRSettings.ps1
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Restore ASR settings from backup
|
||||
|
||||
.DESCRIPTION
|
||||
Restores ASR registry settings from a previous backup created by Backup-ASRRegistry
|
||||
|
||||
.PARAMETER BackupId
|
||||
Identifier of the backup to restore
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with restore results
|
||||
#>
|
||||
|
||||
function Restore-ASRSettings {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$BackupId
|
||||
)
|
||||
|
||||
# This helper is deprecated. ASR restore is handled centrally by the
|
||||
# framework rollback system (Restore-Session / Restore-AllBackups).
|
||||
# Keeping this function to avoid breaking existing scripts, but make
|
||||
# its behavior explicit and safe.
|
||||
Write-Log -Level WARNING -Message "Restore-ASRSettings is deprecated. Use the main rollback workflow (Restore-AllBackups or GUI Restore) instead." -Module "ASR"
|
||||
throw "Restore-ASRSettings is deprecated. Use the framework rollback (Core\Rollback.ps1) instead."
|
||||
}
|
||||
131
Modules/ASR/Private/Set-ASRViaPowerShell.ps1
Normal file
131
Modules/ASR/Private/Set-ASRViaPowerShell.ps1
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Apply ASR rules using Set-MpPreference (PowerShell)
|
||||
|
||||
.DESCRIPTION
|
||||
Uses Microsoft's recommended PowerShell cmdlet to apply ASR rules
|
||||
Cleaner and more validated than direct registry manipulation
|
||||
|
||||
.PARAMETER Rules
|
||||
Array of rule objects to apply
|
||||
|
||||
.PARAMETER DryRun
|
||||
Preview changes without applying
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with applied count and errors
|
||||
#>
|
||||
|
||||
function Set-ASRViaPowerShell {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Array]$Rules,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Applied = 0
|
||||
Errors = @()
|
||||
Warnings = @()
|
||||
}
|
||||
|
||||
try {
|
||||
# Build arrays for Set-MpPreference
|
||||
$ruleIds = @()
|
||||
$ruleActions = @()
|
||||
|
||||
foreach ($rule in $Rules) {
|
||||
$ruleIds += $rule.GUID
|
||||
$ruleActions += $rule.Action
|
||||
}
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Log -Level INFO -Message "[DRYRUN] Would apply $($ruleIds.Count) ASR rules via Set-MpPreference" -Module "ASR"
|
||||
$result.Applied = $ruleIds.Count
|
||||
return $result
|
||||
}
|
||||
|
||||
Write-Log -Level INFO -Message "Applying $($ruleIds.Count) ASR rules via Set-MpPreference..." -Module "ASR"
|
||||
|
||||
# Apply all rules at once
|
||||
Set-MpPreference -AttackSurfaceReductionRules_Ids $ruleIds `
|
||||
-AttackSurfaceReductionRules_Actions $ruleActions `
|
||||
-ErrorAction Stop | Out-Null
|
||||
|
||||
$result.Applied = $ruleIds.Count
|
||||
|
||||
# WORKAROUND: GPO Registry has higher priority than Set-MpPreference
|
||||
# We must set BOTH Set-MpPreference AND GPO Registry to ensure the rule is actually applied
|
||||
# This applies to user-configurable rules (PSExec/WMI and Prevalence)
|
||||
$asrRegistryPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender\Windows Defender Exploit Guard\ASR\Rules"
|
||||
|
||||
# User-configurable rules that need GPO Registry sync
|
||||
$userConfigurableRules = @(
|
||||
"d1e49aac-8f56-4280-b9ba-993a6d77406c", # PSExec/WMI (Management Tools)
|
||||
"01443614-cd74-433a-b99e-2ecdc07bfc25" # Prevalence (New/Unknown Software)
|
||||
)
|
||||
|
||||
foreach ($rule in $Rules) {
|
||||
# For user-configurable rules: Always sync to GPO Registry (Block OR Audit)
|
||||
if ($userConfigurableRules -contains $rule.GUID) {
|
||||
try {
|
||||
$currentValue = Get-ItemProperty -Path $asrRegistryPath -Name $rule.GUID -ErrorAction SilentlyContinue
|
||||
$needsUpdate = $false
|
||||
|
||||
if ($currentValue) {
|
||||
# Registry exists - check if value differs
|
||||
if ([int]$currentValue.($rule.GUID) -ne $rule.Action) {
|
||||
$needsUpdate = $true
|
||||
}
|
||||
} else {
|
||||
# Registry doesn't exist - need to create it
|
||||
$needsUpdate = $true
|
||||
}
|
||||
|
||||
if ($needsUpdate) {
|
||||
# Ensure path exists
|
||||
if (-not (Test-Path $asrRegistryPath)) {
|
||||
New-Item -Path $asrRegistryPath -Force | Out-Null
|
||||
}
|
||||
|
||||
# Set the registry value (using string type like Security Baseline does)
|
||||
Set-ItemProperty -Path $asrRegistryPath -Name $rule.GUID -Value $rule.Action.ToString() -Type String -Force -ErrorAction Stop | Out-Null
|
||||
|
||||
$modeName = switch ($rule.Action) { 1 { "Block" } 2 { "Audit" } default { "Unknown" } }
|
||||
Write-Log -Level INFO -Message "Synced $($rule.Name) to GPO Registry: $modeName mode" -Module "ASR"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Warnings += "Could not sync rule $($rule.Name) to GPO registry: $($_.Exception.Message)"
|
||||
Write-Log -Level WARNING -Message "Could not sync $($rule.Name) to GPO registry: $($_.Exception.Message)" -Module "ASR"
|
||||
}
|
||||
}
|
||||
# For non-configurable rules: Only override if user wants Block and Baseline had Audit
|
||||
elseif ($rule.Action -eq 1 -and $rule.BaselineStatus -eq "Audit") {
|
||||
try {
|
||||
$currentValue = Get-ItemProperty -Path $asrRegistryPath -Name $rule.GUID -ErrorAction SilentlyContinue
|
||||
|
||||
if ($currentValue -and [int]$currentValue.($rule.GUID) -ne 1) {
|
||||
Set-ItemProperty -Path $asrRegistryPath -Name $rule.GUID -Value "1" -Type String -Force -ErrorAction Stop | Out-Null
|
||||
Write-Log -Level INFO -Message "Force-applied $($rule.Name) to Block mode via registry (was Audit from Baseline)" -Module "ASR"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Warnings += "Could not force-apply rule $($rule.Name) via registry: $($_.Exception.Message)"
|
||||
Write-Log -Level WARNING -Message "Could not force-apply $($rule.Name) via registry: $($_.Exception.Message)" -Module "ASR"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log -Level INFO -Message "Successfully applied $($ruleIds.Count) ASR rules" -Module "ASR"
|
||||
}
|
||||
catch {
|
||||
$result.Errors += "Failed to apply ASR rules: $($_.Exception.Message)"
|
||||
Write-Log -Level ERROR -Message "Set-MpPreference failed: $($_.Exception.Message)" -Module "ASR"
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
117
Modules/ASR/Private/Test-ASRCompliance.ps1
Normal file
117
Modules/ASR/Private/Test-ASRCompliance.ps1
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Verify ASR rules are correctly applied
|
||||
|
||||
.DESCRIPTION
|
||||
Uses Get-MpPreference to verify all ASR rules are active with correct actions
|
||||
|
||||
.PARAMETER ExpectedRules
|
||||
Array of rule objects with GUID and Action properties
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with verification results
|
||||
#>
|
||||
|
||||
function Test-ASRCompliance {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[Array]$ExpectedRules
|
||||
)
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Passed = $true
|
||||
CheckedCount = 0
|
||||
FailedCount = 0
|
||||
FailedRules = @()
|
||||
}
|
||||
|
||||
try {
|
||||
# Get current ASR configuration from Defender
|
||||
$mpPref = Get-MpPreference -ErrorAction Stop
|
||||
|
||||
# Get configured ASR rule IDs and actions
|
||||
$configuredIds = $mpPref.AttackSurfaceReductionRules_Ids
|
||||
$configuredActions = $mpPref.AttackSurfaceReductionRules_Actions
|
||||
|
||||
if (-not $configuredIds -or $configuredIds.Count -eq 0) {
|
||||
$result.Passed = $false
|
||||
$result.FailedCount = $ExpectedRules.Count
|
||||
Write-Log -Level WARNING -Message "No ASR rules found in Defender configuration" -Module "ASR"
|
||||
return $result
|
||||
}
|
||||
|
||||
# Create hashtable for quick lookup
|
||||
$configuredRules = @{}
|
||||
for ($i = 0; $i -lt $configuredIds.Count; $i++) {
|
||||
$configuredRules[$configuredIds[$i]] = $configuredActions[$i]
|
||||
}
|
||||
|
||||
# Rules where both BLOCK (1) and AUDIT (2) are considered "Pass"
|
||||
# These are user-configurable rules where either mode is valid
|
||||
$flexibleRules = @(
|
||||
"d1e49aac-8f56-4280-b9ba-993a6d77406c", # PSExec/WMI (Management Tools)
|
||||
"01443614-cd74-433a-b99e-2ecdc07bfc25" # Prevalence (New/Unknown Software)
|
||||
)
|
||||
|
||||
# Verify each expected rule
|
||||
foreach ($rule in $ExpectedRules) {
|
||||
$result.CheckedCount++
|
||||
|
||||
if ($configuredRules.ContainsKey($rule.GUID)) {
|
||||
$actualAction = $configuredRules[$rule.GUID]
|
||||
|
||||
# Check if this is a flexible rule (Block or Audit both count as Pass)
|
||||
$isFlexibleRule = $flexibleRules -contains $rule.GUID
|
||||
$isActiveMode = $actualAction -in @(1, 2) # Block or Audit
|
||||
|
||||
# For flexible rules: Pass if Block OR Audit
|
||||
# For other rules: Pass only if exact match
|
||||
$rulePassed = if ($isFlexibleRule) { $isActiveMode } else { $actualAction -eq $rule.Action }
|
||||
|
||||
if (-not $rulePassed) {
|
||||
$result.FailedCount++
|
||||
$result.Passed = $false
|
||||
$result.FailedRules += $rule.GUID
|
||||
|
||||
$actionName = switch ($actualAction) {
|
||||
0 { "Disabled" }
|
||||
1 { "Block" }
|
||||
2 { "Audit" }
|
||||
6 { "Warn" }
|
||||
default { "Unknown($actualAction)" }
|
||||
}
|
||||
$expectedName = switch ($rule.Action) {
|
||||
0 { "Disabled" }
|
||||
1 { "Block" }
|
||||
2 { "Audit" }
|
||||
6 { "Warn" }
|
||||
default { "Unknown($($rule.Action))" }
|
||||
}
|
||||
|
||||
Write-Log -Level WARNING -Message "Rule '$($rule.Name)' has action $actionName, expected $expectedName" -Module "ASR"
|
||||
}
|
||||
}
|
||||
else {
|
||||
$result.FailedCount++
|
||||
$result.Passed = $false
|
||||
$result.FailedRules += $rule.GUID
|
||||
Write-Log -Level WARNING -Message "Rule '$($rule.Name)' not found in Defender configuration" -Module "ASR"
|
||||
}
|
||||
}
|
||||
|
||||
if ($result.Passed) {
|
||||
Write-Log -Level INFO -Message "ASR compliance check passed - all $($result.CheckedCount) rules verified" -Module "ASR"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "ASR compliance check found $($result.FailedCount) issues out of $($result.CheckedCount) rules" -Module "ASR"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Passed = $false
|
||||
$result.FailedCount = $ExpectedRules.Count
|
||||
Write-Log -Level ERROR -Message "Compliance check failed: $($_.Exception.Message)" -Module "ASR"
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
34
Modules/ASR/Private/Test-CloudProtection.ps1
Normal file
34
Modules/ASR/Private/Test-CloudProtection.ps1
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Verify cloud-delivered protection is enabled
|
||||
|
||||
.DESCRIPTION
|
||||
Some ASR rules require cloud protection to be enabled
|
||||
This function checks if it's active
|
||||
|
||||
.OUTPUTS
|
||||
Boolean - True if cloud protection is enabled
|
||||
#>
|
||||
|
||||
function Test-CloudProtection {
|
||||
[CmdletBinding()]
|
||||
[OutputType([bool])]
|
||||
param()
|
||||
|
||||
try {
|
||||
# Check via Get-MpPreference
|
||||
$mpPref = Get-MpPreference -ErrorAction Stop
|
||||
|
||||
if ($mpPref.MAPSReporting -eq 0) {
|
||||
Write-Log -Level WARNING -Message "Cloud-delivered protection (MAPS) is disabled" -Module "ASR"
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-Log -Level INFO -Message "Cloud-delivered protection is enabled (MAPS: $($mpPref.MAPSReporting))" -Module "ASR"
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Failed to check cloud protection status: $_" -Module "ASR"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
41
Modules/ASR/Private/Test-ConfigMgrPresence.ps1
Normal file
41
Modules/ASR/Private/Test-ConfigMgrPresence.ps1
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Detect SCCM/Configuration Manager presence
|
||||
|
||||
.DESCRIPTION
|
||||
Checks if Configuration Manager client (CcmExec.exe) is running
|
||||
This is critical because PSExec/WMI ASR rule conflicts with SCCM
|
||||
|
||||
.OUTPUTS
|
||||
Boolean - True if ConfigMgr detected
|
||||
#>
|
||||
|
||||
function Test-ConfigMgrPresence {
|
||||
[CmdletBinding()]
|
||||
[OutputType([bool])]
|
||||
param()
|
||||
|
||||
try {
|
||||
# Check for CCM service
|
||||
$ccmService = Get-Service -Name "CcmExec" -ErrorAction SilentlyContinue
|
||||
|
||||
if ($ccmService -and $ccmService.Status -eq "Running") {
|
||||
Write-Log -Level WARNING -Message "Configuration Manager (SCCM) client detected" -Module "ASR"
|
||||
return $true
|
||||
}
|
||||
|
||||
# Check for CCM process
|
||||
$ccmProcess = Get-Process -Name "CcmExec" -ErrorAction SilentlyContinue
|
||||
|
||||
if ($ccmProcess) {
|
||||
Write-Log -Level WARNING -Message "Configuration Manager process detected" -Module "ASR"
|
||||
return $true
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Failed to detect ConfigMgr: $_. Assuming not present." -Module "ASR"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
571
Modules/ASR/Public/Invoke-ASRRules.ps1
Normal file
571
Modules/ASR/Public/Invoke-ASRRules.ps1
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Apply all 19 Microsoft Defender ASR rules
|
||||
|
||||
.DESCRIPTION
|
||||
Enables all 19 Attack Surface Reduction rules in Block mode for comprehensive protection.
|
||||
|
||||
Rules Applied:
|
||||
- All 19 ASR rules in Block mode (Action = 1)
|
||||
- Includes 4 rules missing from Security Baseline
|
||||
- Upgrades 1 rule from Audit to Block (PSExec/WMI - with SCCM check)
|
||||
|
||||
Features:
|
||||
- SCCM/Configuration Manager detection (PSExec/WMI rule warning)
|
||||
- Cloud protection verification
|
||||
- BACKUP/APPLY/VERIFY/RESTORE pattern
|
||||
- DryRun mode for testing
|
||||
- Security Baseline overlap detection
|
||||
|
||||
.PARAMETER DryRun
|
||||
Preview changes without applying them
|
||||
|
||||
.PARAMETER SkipBackup
|
||||
Skip backup creation (not recommended)
|
||||
|
||||
.PARAMETER SkipVerify
|
||||
Skip post-application verification
|
||||
|
||||
.PARAMETER Force
|
||||
Apply even if validation warnings occur (SCCM, Cloud Protection)
|
||||
|
||||
.PARAMETER AllowPSExecWMI
|
||||
Force enable PSExec/WMI rule even if SCCM detected (use with caution)
|
||||
|
||||
.EXAMPLE
|
||||
Invoke-ASRRules
|
||||
Apply all 19 ASR rules with full backup and verification
|
||||
|
||||
.EXAMPLE
|
||||
Invoke-ASRRules -DryRun
|
||||
Preview what changes would be made
|
||||
|
||||
.EXAMPLE
|
||||
Invoke-ASRRules -AllowPSExecWMI -Force
|
||||
Force enable PSExec/WMI rule despite SCCM detection
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with results including success status, rules applied, and any errors
|
||||
#>
|
||||
|
||||
function Invoke-ASRRules {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$SkipBackup,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$SkipVerify,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$Force,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$AllowPSExecWMI
|
||||
)
|
||||
|
||||
begin {
|
||||
$moduleName = "ASR"
|
||||
$startTime = Get-Date
|
||||
|
||||
# Ensure core functions are available when the module is imported directly (outside Framework.ps1)
|
||||
if (-not (Get-Command Initialize-BackupSystem -ErrorAction SilentlyContinue)) {
|
||||
try {
|
||||
$frameworkRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
||||
$coreFiles = @(
|
||||
"Core\Logger.ps1",
|
||||
"Core\Config.ps1",
|
||||
"Core\Validator.ps1",
|
||||
"Core\Rollback.ps1",
|
||||
"Utils\Compatibility.ps1"
|
||||
)
|
||||
|
||||
foreach ($file in $coreFiles) {
|
||||
$corePath = Join-Path $frameworkRoot $file
|
||||
if (Test-Path $corePath) {
|
||||
. $corePath
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "ERROR: Failed to load core dependencies for ASR module: $_" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
# Initialize result object
|
||||
$result = [PSCustomObject]@{
|
||||
ModuleName = $moduleName
|
||||
Success = $false
|
||||
RulesApplied = 0
|
||||
Errors = @()
|
||||
Warnings = @()
|
||||
BackupCreated = $false
|
||||
VerificationPassed = $false
|
||||
ConfigMgrDetected = $false
|
||||
CloudProtectionEnabled = $false
|
||||
Duration = $null
|
||||
Details = @{
|
||||
TotalRules = 19
|
||||
BlockMode = 0
|
||||
AuditMode = 0
|
||||
DisabledMode = 0
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log -Level INFO -Message "Starting ASR rules application (all 19 rules)" -Module $moduleName
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Log -Level INFO -Message "DRY RUN MODE - No changes will be applied" -Module $moduleName
|
||||
}
|
||||
}
|
||||
|
||||
process {
|
||||
try {
|
||||
# Step 1: Prerequisites validation
|
||||
Write-Log -Level INFO -Message "Validating prerequisites..." -Module $moduleName
|
||||
|
||||
if (-not (Test-IsAdmin)) {
|
||||
throw "Administrator privileges required"
|
||||
}
|
||||
|
||||
if (-not (Test-WindowsVersion -MinimumBuild 22000)) {
|
||||
throw "Windows 11 or later required"
|
||||
}
|
||||
|
||||
# Check Windows Defender status and third-party AV
|
||||
$defenderService = Get-Service -Name "WinDefend" -ErrorAction SilentlyContinue
|
||||
$defenderRunning = $defenderService -and $defenderService.Status -eq "Running"
|
||||
|
||||
# Check for third-party antivirus (they disable Defender)
|
||||
$thirdPartyAV = $null
|
||||
try {
|
||||
$avProducts = Get-CimInstance -Namespace "root/SecurityCenter2" -ClassName "AntiVirusProduct" -ErrorAction SilentlyContinue
|
||||
$thirdPartyAV = $avProducts | Where-Object { $_.displayName -notmatch "Windows Defender|Microsoft Defender" } | Select-Object -First 1
|
||||
}
|
||||
catch {
|
||||
# SecurityCenter2 not available - continue with Defender check only
|
||||
$null = $null
|
||||
}
|
||||
|
||||
if (-not $defenderRunning) {
|
||||
if ($thirdPartyAV) {
|
||||
# Third-party AV detected - skip ASR gracefully (not an error!)
|
||||
$avName = $thirdPartyAV.displayName
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Yellow
|
||||
Write-Host " ASR Module Skipped" -ForegroundColor Yellow
|
||||
Write-Host "========================================" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Third-party antivirus detected: $avName" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "ASR rules require Windows Defender to be active." -ForegroundColor Yellow
|
||||
Write-Host "Your antivirus ($avName) has its own protection features." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "This is NOT an error - ASR will be skipped." -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
Write-Log -Level WARNING -Message "ASR skipped: Third-party AV detected ($avName). Defender disabled." -Module $moduleName
|
||||
|
||||
$result.Success = $true # Not an error - intentional skip
|
||||
$result.Warnings += "ASR skipped: Third-party antivirus detected ($avName). Your AV provides similar protection."
|
||||
$result.RulesApplied = 0
|
||||
|
||||
return $result
|
||||
}
|
||||
else {
|
||||
# No third-party AV but Defender not running - this IS a problem
|
||||
throw "Windows Defender service is not running and no third-party antivirus detected. ASR rules require Defender to be active."
|
||||
}
|
||||
}
|
||||
|
||||
# Load ASR rule definitions
|
||||
Write-Log -Level INFO -Message "Loading ASR rule definitions..." -Module $moduleName
|
||||
$asrRules = Get-ASRRuleDefinitions
|
||||
|
||||
# Step 2: Check for Remote Management Tools (SCCM/Intune/etc.)
|
||||
Write-Log -Level INFO -Message "Checking for remote management tools..." -Module $moduleName
|
||||
|
||||
# Automatic detection
|
||||
$configMgrDetected = Test-ConfigMgrPresence
|
||||
$result.ConfigMgrDetected = $configMgrDetected
|
||||
|
||||
# Check for management tools - NonInteractive or Interactive
|
||||
$usesManagementTools = $false
|
||||
|
||||
if (Test-NonInteractiveMode) {
|
||||
# NonInteractive mode (GUI) - use config value
|
||||
$usesManagementTools = Get-NonInteractiveValue -Module "ASR" -Key "usesManagementTools" -Default $false
|
||||
Write-NonInteractiveDecision -Module $moduleName -Decision "Management tools setting" -Value $(if ($usesManagementTools) { "Yes (1 AUDIT)" } else { "No (ALL BLOCK)" })
|
||||
}
|
||||
elseif (-not $Force -and -not $AllowPSExecWMI -and -not $DryRun) {
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Remote Management Tool Check" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
if ($configMgrDetected) {
|
||||
Write-Host "DETECTED: SCCM/Configuration Manager is currently installed" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
Write-Host "Do you use ANY of these remote management tools?" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " - Microsoft SCCM (Configuration Manager)" -ForegroundColor Gray
|
||||
Write-Host " - Microsoft Intune / Endpoint Manager" -ForegroundColor Gray
|
||||
Write-Host " - PDQ Deploy / PDQ Inventory" -ForegroundColor Gray
|
||||
Write-Host " - ManageEngine Desktop Central" -ForegroundColor Gray
|
||||
Write-Host " - Any other WMI/PSExec based management tools" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "These tools use PSExec and WMI for remote management." -ForegroundColor Yellow
|
||||
Write-Host "If you use them, one ASR rule must be set to AUDIT mode." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Options:" -ForegroundColor Cyan
|
||||
Write-Host " [Y] Yes - I use management tools" -ForegroundColor Yellow
|
||||
Write-Host " > 1 rule: AUDIT mode (PSExec/WMI only)" -ForegroundColor Gray
|
||||
Write-Host " > 18 rules: BLOCK mode (full protection)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " [N] No - I don't use any of these" -ForegroundColor Green
|
||||
Write-Host " > ALL 19 rules: BLOCK mode (maximum protection)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
do {
|
||||
$choice = Read-Host "Select option [Y/N] (default: N)"
|
||||
if ([string]::IsNullOrWhiteSpace($choice)) { $choice = "N" }
|
||||
$choice = $choice.ToUpper()
|
||||
|
||||
if ($choice -notin @('Y', 'N')) {
|
||||
Write-Host ""
|
||||
Write-Host "Invalid input. Please enter Y or N." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
}
|
||||
} while ($choice -notin @('Y', 'N'))
|
||||
|
||||
switch ($choice) {
|
||||
"Y" {
|
||||
$usesManagementTools = $true
|
||||
Write-Host ""
|
||||
Write-Host "1 rule set to AUDIT (PSExec/WMI), 18 rules set to BLOCK" -ForegroundColor Yellow
|
||||
Write-Log -Level INFO -Message "User confirmed use of management tools - 1 AUDIT + 18 BLOCK" -Module $moduleName
|
||||
}
|
||||
"N" {
|
||||
$usesManagementTools = $false
|
||||
Write-Host ""
|
||||
Write-Host "ALL 19 rules will be set to BLOCK mode" -ForegroundColor Green
|
||||
Write-Log -Level INFO -Message "User confirmed no management tools - ALL 19 BLOCK" -Module $moduleName
|
||||
}
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
elseif ($Force -and -not $AllowPSExecWMI) {
|
||||
# Force flag: Auto-detect or assume safe
|
||||
$usesManagementTools = $configMgrDetected
|
||||
Write-Log -Level INFO -Message "Force flag: Using detection result (ConfigMgr: $configMgrDetected)" -Module $moduleName
|
||||
}
|
||||
|
||||
# Apply PSExec/WMI rule mode based on user choice or detection
|
||||
if (($usesManagementTools -or $configMgrDetected) -and -not $AllowPSExecWMI) {
|
||||
$psexecRule = $asrRules | Where-Object { $_.GUID -eq "d1e49aac-8f56-4280-b9ba-993a6d77406c" }
|
||||
|
||||
# Set PSExec/WMI to Audit mode (user confirmed or detected)
|
||||
$psexecRule.Action = 2
|
||||
$result.Warnings += "Management tools detected/confirmed: PSExec/WMI rule set to Audit mode"
|
||||
Write-Log -Level INFO -Message "PSExec/WMI rule set to Audit mode (management tools in use)" -Module $moduleName
|
||||
}
|
||||
|
||||
# Step 2b: Prevalence rule (new/unknown software) - NonInteractive or Interactive
|
||||
if (-not $DryRun) {
|
||||
$prevalenceRule = $asrRules | Where-Object { $_.GUID -eq "01443614-cd74-433a-b99e-2ecdc07bfc25" }
|
||||
if ($prevalenceRule) {
|
||||
$allowNewSoftware = $false
|
||||
|
||||
if (Test-NonInteractiveMode) {
|
||||
# NonInteractive mode (GUI) - use config value
|
||||
$allowNewSoftware = Get-NonInteractiveValue -Module "ASR" -Key "allowNewSoftware" -Default $false
|
||||
|
||||
if ($allowNewSoftware) {
|
||||
$prevalenceRule.Action = 2
|
||||
$result.Warnings += "ASR prevalence rule set to AUDIT (less restrictive; see README for details)."
|
||||
} else {
|
||||
$prevalenceRule.Action = 1
|
||||
}
|
||||
Write-NonInteractiveDecision -Module $moduleName -Decision "New/Unknown software rule" -Value $(if ($allowNewSoftware) { "AUDIT (allow)" } else { "BLOCK (secure)" })
|
||||
}
|
||||
else {
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " ASR Rule: New / Unknown Software" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
Write-Host "Rule: Block executable files unless they meet prevalence, age, or trusted list" -ForegroundColor White
|
||||
Write-Host "GUID: $($prevalenceRule.GUID)" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
Write-Host "This rule blocks very new or unknown executables that" -ForegroundColor Yellow
|
||||
Write-Host "are not yet trusted by Microsoft's reputation systems." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Do you install NEW or UNTRUSTED software frequently?" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " - Games from independent developers" -ForegroundColor Gray
|
||||
Write-Host " - Beta software / Early access programs" -ForegroundColor Gray
|
||||
Write-Host " - Custom/in-house business applications" -ForegroundColor Gray
|
||||
Write-Host " - Open-source tools without Microsoft reputation" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "Options:" -ForegroundColor Cyan
|
||||
Write-Host " [Y] Yes - I need to install untrusted software" -ForegroundColor Yellow
|
||||
Write-Host " > AUDIT mode: Events logged, installs allowed" -ForegroundColor Gray
|
||||
Write-Host " > Developer/test mode (less secure)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " [N] No - I only install trusted software" -ForegroundColor Green
|
||||
Write-Host " > BLOCK mode: Maximum security (recommended)" -ForegroundColor Gray
|
||||
Write-Host " > New/unknown installers may be blocked" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
do {
|
||||
$prevalenceChoice = Read-Host "Select option [Y/N] (default: N)"
|
||||
if ([string]::IsNullOrWhiteSpace($prevalenceChoice)) { $prevalenceChoice = "N" }
|
||||
$prevalenceChoice = $prevalenceChoice.ToUpper()
|
||||
|
||||
if ($prevalenceChoice -notin @('Y', 'N')) {
|
||||
Write-Host ""
|
||||
Write-Host "Invalid input. Please enter Y or N." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
}
|
||||
} while ($prevalenceChoice -notin @('Y', 'N'))
|
||||
|
||||
switch ($prevalenceChoice) {
|
||||
"N" {
|
||||
$prevalenceRule.Action = 1
|
||||
Write-Host ""
|
||||
Write-Host "New/Unknown Software rule set to BLOCK mode (maximum security)" -ForegroundColor Green
|
||||
Write-Log -Level INFO -Message "Prevalence rule configured to BLOCK (recommended)" -Module $moduleName
|
||||
}
|
||||
"Y" {
|
||||
$prevalenceRule.Action = 2
|
||||
Write-Host ""
|
||||
Write-Host "New/Unknown Software rule set to AUDIT mode (developer/test)" -ForegroundColor Yellow
|
||||
$result.Warnings += "ASR prevalence rule set to AUDIT (less restrictive; see README for details)."
|
||||
Write-Log -Level INFO -Message "Prevalence rule configured to AUDIT (developer/compat mode)" -Module $moduleName
|
||||
}
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Step 3: Check cloud protection
|
||||
Write-Log -Level INFO -Message "Checking cloud-delivered protection..." -Module $moduleName
|
||||
|
||||
$cloudProtectionEnabled = Test-CloudProtection
|
||||
$result.CloudProtectionEnabled = $cloudProtectionEnabled
|
||||
|
||||
if (-not $cloudProtectionEnabled) {
|
||||
$cloudRules = $asrRules | Where-Object { $_.RequiresCloudProtection -eq $true }
|
||||
$result.Warnings += "Cloud protection disabled: $($cloudRules.Count) rules require it for optimal operation"
|
||||
Write-Log -Level WARNING -Message "$($cloudRules.Count) ASR rules require cloud protection for full functionality" -Module $moduleName
|
||||
|
||||
if (Test-NonInteractiveMode) {
|
||||
# NonInteractive mode (GUI) - use config value
|
||||
$continueWithoutCloud = Get-NonInteractiveValue -Module "ASR" -Key "continueWithoutCloud" -Default $true
|
||||
|
||||
if (-not $continueWithoutCloud) {
|
||||
Write-NonInteractiveDecision -Module $moduleName -Decision "Cloud protection required - aborting"
|
||||
throw "ASR application cancelled (cloud protection required, continueWithoutCloud=false)"
|
||||
}
|
||||
Write-NonInteractiveDecision -Module $moduleName -Decision "Continuing without cloud protection (limited functionality)"
|
||||
}
|
||||
elseif (-not $Force -and -not $DryRun) {
|
||||
# Interactive prompt for cloud protection
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Yellow
|
||||
Write-Host " Cloud Protection Not Enabled!" -ForegroundColor Yellow
|
||||
Write-Host "========================================" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "$($cloudRules.Count) ASR rules require cloud-delivered protection for optimal functionality:" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
foreach ($cloudRule in $cloudRules) {
|
||||
Write-Host " - $($cloudRule.Name)" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "These rules will work in limited capacity without cloud protection." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Options:" -ForegroundColor Cyan
|
||||
Write-Host " [C] Continue - Apply rules anyway (limited functionality)" -ForegroundColor Green
|
||||
Write-Host " [A] Abort - Cancel ASR rule application" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
do {
|
||||
$choice = Read-Host "Select option [C/A] (default: A)"
|
||||
if ([string]::IsNullOrWhiteSpace($choice)) { $choice = "A" }
|
||||
$choice = $choice.ToUpper()
|
||||
|
||||
if ($choice -notin @('C', 'A')) {
|
||||
Write-Host ""
|
||||
Write-Host "Invalid input. Please enter C or A." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
}
|
||||
} while ($choice -notin @('C', 'A'))
|
||||
|
||||
switch ($choice) {
|
||||
"C" {
|
||||
Write-Host ""
|
||||
Write-Host "Continuing with cloud protection disabled" -ForegroundColor Yellow
|
||||
Write-Log -Level INFO -Message "User chose to continue despite cloud protection disabled" -Module $moduleName
|
||||
}
|
||||
"A" {
|
||||
Write-Host ""
|
||||
Write-Host "ASR rule application cancelled by user" -ForegroundColor Yellow
|
||||
Write-Log -Level INFO -Message "ASR application cancelled due to cloud protection requirement" -Module $moduleName
|
||||
throw "ASR application cancelled by user due to cloud protection requirement"
|
||||
}
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
elseif ($Force) {
|
||||
# Force flag - continue silently
|
||||
Write-Log -Level INFO -Message "Continuing despite cloud protection disabled (Force flag)" -Module $moduleName
|
||||
}
|
||||
}
|
||||
|
||||
# Step 3a: Initialize and start module backup
|
||||
if (-not $SkipBackup -and -not $DryRun) {
|
||||
try {
|
||||
Initialize-BackupSystem
|
||||
$null = Start-ModuleBackup -ModuleName $moduleName
|
||||
Write-Log -Level INFO -Message "Session backup initialized" -Module $moduleName
|
||||
}
|
||||
catch {
|
||||
$result.Warnings += "Failed to initialize/start module backup: $_"
|
||||
Write-Log -Level WARNING -Message "Failed to initialize/start module backup: $_" -Module $moduleName
|
||||
}
|
||||
}
|
||||
|
||||
# Step 4: Create backup
|
||||
if (-not $SkipBackup -and -not $DryRun) {
|
||||
Write-Log -Level INFO -Message "Creating backup..." -Module $moduleName
|
||||
|
||||
$backupResult = Backup-ASRRegistry
|
||||
if ($backupResult.Errors.Count -gt 0) {
|
||||
foreach ($err in $backupResult.Errors) {
|
||||
$result.Warnings += $err
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Register backup in session manifest
|
||||
Complete-ModuleBackup -ItemsBackedUp 1 -Status "Success"
|
||||
$result.BackupCreated = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Step 5: Apply ASR rules via PowerShell
|
||||
# Note: Set-ASRViaPowerShell logs internally, no need to log here
|
||||
$applyResult = Set-ASRViaPowerShell -Rules $asrRules -DryRun:$DryRun
|
||||
|
||||
# In DryRun mode, no rules are actually applied, so keep RulesApplied = 0
|
||||
if (-not $DryRun) {
|
||||
$result.RulesApplied = $applyResult.Applied
|
||||
}
|
||||
|
||||
# Add errors and warnings individually to avoid nested arrays
|
||||
foreach ($err in $applyResult.Errors) {
|
||||
$result.Errors += $err
|
||||
}
|
||||
foreach ($warn in $applyResult.Warnings) {
|
||||
$result.Warnings += $warn
|
||||
}
|
||||
|
||||
# Count rule modes from actual system state
|
||||
$mpPref = Get-MpPreference
|
||||
$currentActions = $mpPref.AttackSurfaceReductionRules_Actions
|
||||
if ($currentActions) {
|
||||
$result.Details.BlockMode = ($currentActions | Where-Object { $_ -eq 1 }).Count
|
||||
$result.Details.AuditMode = ($currentActions | Where-Object { $_ -eq 2 }).Count
|
||||
$result.Details.DisabledMode = ($currentActions | Where-Object { $_ -eq 0 }).Count
|
||||
} else {
|
||||
# Fallback to array count
|
||||
$result.Details.BlockMode = ($asrRules | Where-Object { $_.Action -eq 1 }).Count
|
||||
$result.Details.AuditMode = ($asrRules | Where-Object { $_.Action -eq 2 }).Count
|
||||
$result.Details.DisabledMode = ($asrRules | Where-Object { $_.Action -eq 0 }).Count
|
||||
}
|
||||
|
||||
# Step 6: Verification
|
||||
if (-not $SkipVerify -and -not $DryRun) {
|
||||
Write-Log -Level INFO -Message "Verifying applied ASR rules..." -Module $moduleName
|
||||
|
||||
$verificationResult = Test-ASRCompliance -ExpectedRules $asrRules
|
||||
$result.VerificationPassed = $verificationResult.Passed
|
||||
|
||||
if (-not $verificationResult.Passed) {
|
||||
$result.Warnings += "Verification found $($verificationResult.FailedCount) rules not applied correctly"
|
||||
Write-Log -Level WARNING -Message "Verification found $($verificationResult.FailedCount) failed rules" -Module $moduleName
|
||||
}
|
||||
else {
|
||||
Write-Log -Level INFO -Message "Verification passed - all $($verificationResult.CheckedCount) rules confirmed" -Module $moduleName
|
||||
}
|
||||
}
|
||||
|
||||
# Log baseline overlap
|
||||
$baselineRules = $asrRules | Where-Object { $_.BaselineStatus -in @("Block", "Audit") }
|
||||
Write-Log -Level INFO -Message "Security Baseline overlap: $($baselineRules.Count) rules already in baseline" -Module $moduleName
|
||||
|
||||
$newRules = $asrRules | Where-Object { $_.BaselineStatus -eq "Missing" }
|
||||
if ($newRules.Count -gt 0) {
|
||||
Write-Log -Level INFO -Message "Added $($newRules.Count) rules not in Security Baseline:" -Module $moduleName
|
||||
foreach ($newRule in $newRules) {
|
||||
Write-Log -Level INFO -Message " + $($newRule.Name)" -Module $moduleName
|
||||
}
|
||||
}
|
||||
|
||||
$upgradedRules = $asrRules | Where-Object { $_.BaselineStatus -eq "Audit" -and $_.Action -eq 1 }
|
||||
if ($upgradedRules.Count -gt 0) {
|
||||
Write-Log -Level INFO -Message "Upgraded $($upgradedRules.Count) rules from Audit to Block:" -Module $moduleName
|
||||
foreach ($upgradedRule in $upgradedRules) {
|
||||
Write-Log -Level INFO -Message " [UPGRADE] $($upgradedRule.Name)" -Module $moduleName
|
||||
}
|
||||
}
|
||||
|
||||
# Mark as successful if no critical errors
|
||||
if ($result.Errors.Count -eq 0) {
|
||||
$result.Success = $true
|
||||
Write-Log -Level INFO -Message "ASR rules applied successfully" -Module $moduleName
|
||||
}
|
||||
else {
|
||||
Write-Log -Level ERROR -Message "ASR application completed with $($result.Errors.Count) errors" -Module $moduleName
|
||||
}
|
||||
|
||||
}
|
||||
catch {
|
||||
$result.Success = $false
|
||||
$result.Errors += $_.Exception.Message
|
||||
Write-Log -Level ERROR -Message "ASR application failed: $($_.Exception.Message)" -Module $moduleName
|
||||
}
|
||||
}
|
||||
|
||||
end {
|
||||
$result.Duration = (Get-Date) - $startTime
|
||||
|
||||
Write-Log -Level INFO -Message "ASR application completed in $($result.Duration.TotalSeconds) seconds" -Module $moduleName
|
||||
|
||||
$blockCount = $result.Details.BlockMode
|
||||
$auditCount = $result.Details.AuditMode
|
||||
Write-Log -Level INFO -Message "Rules applied: $($result.RulesApplied) ($blockCount Block, $auditCount Audit)" -Module $moduleName
|
||||
Write-Log -Level INFO -Message "Errors: $($result.Errors.Count), Warnings: $($result.Warnings.Count)" -Module $moduleName
|
||||
|
||||
# Log warning details for transparency
|
||||
if ($result.Warnings.Count -gt 0) {
|
||||
foreach ($warn in $result.Warnings) {
|
||||
Write-Log -Level INFO -Message " Warning: $warn" -Module $moduleName
|
||||
}
|
||||
}
|
||||
|
||||
# GUI parsing marker for settings count (19 ASR rules)
|
||||
Write-Log -Level SUCCESS -Message "Applied 19 settings" -Module "ASR"
|
||||
|
||||
return $result
|
||||
}
|
||||
}
|
||||
73
Modules/AdvancedSecurity/AdvancedSecurity.psd1
Normal file
73
Modules/AdvancedSecurity/AdvancedSecurity.psd1
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
@{
|
||||
# Module manifest for AdvancedSecurity
|
||||
|
||||
# Version
|
||||
ModuleVersion = '2.2.0'
|
||||
|
||||
# Unique ID
|
||||
GUID = 'e7f5a3d2-8c9b-4f1e-a6d3-9b2c8f4e5a1d'
|
||||
|
||||
# Author
|
||||
Author = 'NexusOne23'
|
||||
|
||||
# Company
|
||||
CompanyName = 'Open Source Project'
|
||||
|
||||
# Copyright
|
||||
Copyright = '(c) 2025 NexusOne23. Licensed under GPL-3.0.'
|
||||
|
||||
# Description
|
||||
Description = 'Advanced Security hardening beyond Microsoft Security Baseline: RDP hardening, WDigest protection, Admin Shares disable, Risky Ports/Services, Legacy TLS/WPAD/PSv2, SRP .lnk protection (CVE-2025-9491), Windows Update (3 simple GUI settings), Finger Protocol block, Wireless Display (Miracast) security. 38+ settings total with profile-based execution (Balanced/Enterprise/Maximum) and domain-safety checks plus full backup/restore.'
|
||||
|
||||
# Minimum PowerShell version
|
||||
PowerShellVersion = '5.1'
|
||||
|
||||
# Root module
|
||||
RootModule = 'AdvancedSecurity.psm1'
|
||||
|
||||
# Functions to export
|
||||
FunctionsToExport = @(
|
||||
'Invoke-AdvancedSecurity',
|
||||
'Test-AdvancedSecurity',
|
||||
'Restore-AdvancedSecuritySettings'
|
||||
)
|
||||
|
||||
# Cmdlets to export
|
||||
CmdletsToExport = @()
|
||||
|
||||
# Variables to export
|
||||
VariablesToExport = @()
|
||||
|
||||
# Aliases to export
|
||||
AliasesToExport = @()
|
||||
|
||||
# Private data
|
||||
PrivateData = @{
|
||||
PSData = @{
|
||||
Tags = @('Security', 'Hardening', 'Windows11', 'Advanced', 'RDP', 'Credentials', 'NetworkSecurity')
|
||||
LicenseUri = ''
|
||||
ProjectUri = ''
|
||||
ReleaseNotes = @'
|
||||
v2.2.0 (2025-12-08)
|
||||
- Production release of AdvancedSecurity module
|
||||
- 49 advanced hardening settings implemented (was 36)
|
||||
- NEW: Wireless Display (Miracast) security hardening
|
||||
- Default: Block receiving projections + require PIN (all profiles)
|
||||
- Optional: Complete disable (blocks sending, mDNS, ports 7236/7250)
|
||||
- Prevents screen interception attacks from network attackers
|
||||
- Profile-based execution (Balanced/Enterprise/Maximum)
|
||||
- RDP NLA enforcement + optional complete disable
|
||||
- WDigest credential protection (backwards compatible)
|
||||
- Administrative shares disable (domain-aware)
|
||||
- Risky firewall ports closure (LLMNR, NetBIOS, UPnP/SSDP)
|
||||
- Risky network services stop (SSDPSRV, upnphost, lmhosts)
|
||||
- Legacy TLS 1.0/1.1 disable
|
||||
- WPAD auto-discovery disable
|
||||
- PowerShell v2 removal
|
||||
- Full backup/restore capability
|
||||
- WhatIf mode and change log export
|
||||
- Compliance testing function
|
||||
'@
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Modules/AdvancedSecurity/AdvancedSecurity.psm1
Normal file
65
Modules/AdvancedSecurity/AdvancedSecurity.psm1
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# AdvancedSecurity Module Loader
|
||||
# Version: 2.2.0
|
||||
# Description: Advanced Security Hardening - Beyond Microsoft Security Baseline
|
||||
|
||||
# Get module path
|
||||
$ModulePath = $PSScriptRoot
|
||||
|
||||
# Load Private functions
|
||||
$PrivateFunctions = @(
|
||||
'Enable-RdpNLA',
|
||||
'Set-WDigestProtection',
|
||||
'Disable-AdminShares',
|
||||
'Disable-RiskyPorts',
|
||||
'Stop-RiskyServices',
|
||||
'Disable-WPAD',
|
||||
'Disable-LegacyTLS',
|
||||
'Remove-PowerShellV2',
|
||||
'Block-FingerProtocol',
|
||||
'Set-SRPRules',
|
||||
'Set-WindowsUpdate',
|
||||
'Set-WirelessDisplaySecurity',
|
||||
'Set-DiscoveryProtocolsSecurity',
|
||||
'Set-FirewallShieldsUp',
|
||||
'Set-IPv6Security',
|
||||
'Test-RdpSecurity',
|
||||
'Test-WDigest',
|
||||
'Test-RiskyPorts',
|
||||
'Test-RiskyServices',
|
||||
'Test-AdminShares',
|
||||
'Test-SRPCompliance',
|
||||
'Test-WindowsUpdate',
|
||||
'Test-LegacyTLS',
|
||||
'Test-WPAD',
|
||||
'Test-PowerShellV2',
|
||||
'Test-FingerProtocol',
|
||||
'Test-WirelessDisplaySecurity',
|
||||
'Test-DiscoveryProtocolsSecurity',
|
||||
'Test-FirewallShieldsUp',
|
||||
'Test-IPv6Security',
|
||||
'Backup-AdvancedSecuritySettings'
|
||||
)
|
||||
|
||||
foreach ($function in $PrivateFunctions) {
|
||||
$functionPath = Join-Path $ModulePath "Private\$function.ps1"
|
||||
if (Test-Path $functionPath) {
|
||||
. $functionPath
|
||||
}
|
||||
}
|
||||
|
||||
# Load Public functions
|
||||
$PublicFunctions = @(
|
||||
'Invoke-AdvancedSecurity',
|
||||
'Test-AdvancedSecurity',
|
||||
'Restore-AdvancedSecuritySettings'
|
||||
)
|
||||
|
||||
foreach ($function in $PublicFunctions) {
|
||||
$functionPath = Join-Path $ModulePath "Public\$function.ps1"
|
||||
if (Test-Path $functionPath) {
|
||||
. $functionPath
|
||||
}
|
||||
}
|
||||
|
||||
# Export only Public functions
|
||||
Export-ModuleMember -Function $PublicFunctions
|
||||
116
Modules/AdvancedSecurity/Config/AdminShares.json
Normal file
116
Modules/AdvancedSecurity/Config/AdminShares.json
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Administrative Shares Configuration",
|
||||
"description": "Configuration for disabling administrative shares (C$, ADMIN$, etc.) to prevent lateral movement",
|
||||
"version": "2.2.0",
|
||||
|
||||
"Administrative_Shares": {
|
||||
"description": "Disable automatic creation and remove existing administrative shares",
|
||||
"risk_level": "CRITICAL",
|
||||
"attack_vectors": [
|
||||
"Lateral movement in networks (WannaCry, NotPetya propagation)",
|
||||
"Remote file access by attackers with stolen credentials",
|
||||
"Pass-the-Hash attacks using admin shares",
|
||||
"Automated malware propagation"
|
||||
],
|
||||
|
||||
"shares_affected": {
|
||||
"C$": "Root of C: drive",
|
||||
"D$": "Root of D: drive (if exists)",
|
||||
"E$": "Root of E: drive (if exists)",
|
||||
"ADMIN$": "Windows directory (C:\\Windows)",
|
||||
"IPC$": "Named pipes - CANNOT be removed (required by Windows)"
|
||||
},
|
||||
|
||||
"registry_settings": {
|
||||
"path": "HKLM:\\SYSTEM\\CurrentControlSet\\Services\\LanmanServer\\Parameters",
|
||||
"AutoShareWks": {
|
||||
"description": "Disable automatic shares on Workstation (Home/Pro editions)",
|
||||
"value": 0,
|
||||
"type": "DWORD",
|
||||
"default": 1
|
||||
},
|
||||
"AutoShareServer": {
|
||||
"description": "Disable automatic shares on Server editions",
|
||||
"value": 0,
|
||||
"type": "DWORD",
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
|
||||
"firewall_protection": {
|
||||
"description": "Block SMB on Public network profile",
|
||||
"rule_name": "Block Admin Shares (NoID Privacy)",
|
||||
"direction": "Inbound",
|
||||
"protocol": "TCP",
|
||||
"local_port": 445,
|
||||
"profile": "Public",
|
||||
"action": "Block"
|
||||
},
|
||||
|
||||
"domain_safety": {
|
||||
"enabled": true,
|
||||
"description": "Automatically detect domain-joined systems and skip unless -Force",
|
||||
"check": "Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object PartOfDomain",
|
||||
"warnings": [
|
||||
"Group Policy management may be affected",
|
||||
"SCCM/Management tools may require admin shares",
|
||||
"Remote administration tools may stop working"
|
||||
],
|
||||
"force_required": true,
|
||||
"enterprise_recommendation": "Test in staging environment before deployment"
|
||||
}
|
||||
},
|
||||
|
||||
"Profiles": {
|
||||
"Balanced": {
|
||||
"enabled": true,
|
||||
"domain_check": true,
|
||||
"force_required": false
|
||||
},
|
||||
"Enterprise": {
|
||||
"enabled": "conditional",
|
||||
"domain_check": true,
|
||||
"force_required": true,
|
||||
"note": "Auto-disabled for domain-joined systems unless -Force"
|
||||
},
|
||||
"Maximum": {
|
||||
"enabled": true,
|
||||
"domain_check": false,
|
||||
"force_required": false,
|
||||
"note": "Always enabled for maximum security"
|
||||
}
|
||||
},
|
||||
|
||||
"Impact": {
|
||||
"positive": [
|
||||
"Prevents lateral movement in case of credential theft",
|
||||
"Stops automated ransomware propagation",
|
||||
"Blocks Pass-the-Hash attack vectors using admin shares"
|
||||
],
|
||||
"negative": [
|
||||
"Remote administration tools may not work",
|
||||
"Group Policy remote management affected",
|
||||
"Some enterprise monitoring tools may require admin shares",
|
||||
"SCCM and similar tools may need explicit shares"
|
||||
],
|
||||
"recommendations": {
|
||||
"home_users": "Recommended - high security benefit",
|
||||
"enterprise": "Requires testing - may break management tools",
|
||||
"workaround": "Create explicit shares for required management tools"
|
||||
}
|
||||
},
|
||||
|
||||
"Important_Notes": [
|
||||
"REQUIRES REBOOT to prevent share recreation",
|
||||
"Shares will NOT be recreated after reboot (if registry set)",
|
||||
"IPC$ cannot be disabled (required by Windows)",
|
||||
"File sharing via explicit shares still works",
|
||||
"Can be restored by setting AutoShareWks/AutoShareServer = 1 + reboot"
|
||||
],
|
||||
|
||||
"Compatibility": {
|
||||
"windows_versions": ["Windows 10", "Windows 11", "Windows Server 2016+"],
|
||||
"tested": "Windows 11 25H2 (Nov 16, 2025)"
|
||||
}
|
||||
}
|
||||
78
Modules/AdvancedSecurity/Config/Credentials.json
Normal file
78
Modules/AdvancedSecurity/Config/Credentials.json
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Credential Protection Configuration",
|
||||
"description": "Configuration for credential hardening including WDigest protection",
|
||||
"version": "2.2.0",
|
||||
|
||||
"WDigest_Protection": {
|
||||
"description": "Prevent WDigest from storing plaintext passwords in LSASS memory",
|
||||
"enabled": true,
|
||||
"deprecated_in": "Windows 11 24H2",
|
||||
"status": "Deprecated in Win11 24H2+ but kept for backwards compatibility and defense-in-depth",
|
||||
|
||||
"registry_path": "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest",
|
||||
"settings": {
|
||||
"UseLogonCredential": {
|
||||
"description": "Control whether WDigest stores credentials in memory",
|
||||
"value": 0,
|
||||
"type": "DWORD",
|
||||
"values": {
|
||||
"0": "Secure - Do NOT store plaintext credentials in memory",
|
||||
"1": "Insecure - Store plaintext credentials in memory (VULNERABLE!)"
|
||||
},
|
||||
"attack_prevention": [
|
||||
"Prevents Mimikatz from dumping plaintext passwords",
|
||||
"Prevents Windows Credential Editor (WCE) attacks",
|
||||
"Prevents other memory-dumping credential theft tools"
|
||||
],
|
||||
"impact": "None - Modern systems (Win 8.1+) already default to 0"
|
||||
}
|
||||
},
|
||||
|
||||
"default_behavior": {
|
||||
"Windows_7": 1,
|
||||
"Windows_8": 1,
|
||||
"Windows_8.1": 0,
|
||||
"Windows_10": 0,
|
||||
"Windows_11": 0,
|
||||
"Windows_11_24H2_plus": "Setting ignored (deprecated)"
|
||||
},
|
||||
|
||||
"rationale": {
|
||||
"why_set_if_deprecated": [
|
||||
"Protects older Windows versions (Win7/8/Server 2008/2012)",
|
||||
"Protects early Win10/11 builds that may not be fully patched",
|
||||
"Defense-in-depth: Explicit is better than implicit",
|
||||
"Ensures compatibility in mixed environments",
|
||||
"No negative impact on Win11 24H2+ (setting is ignored)"
|
||||
]
|
||||
},
|
||||
|
||||
"microsoft_advisory": {
|
||||
"kb_article": "KB2871997",
|
||||
"date": "May 2014",
|
||||
"title": "Update to improve credentials protection and management",
|
||||
"url": "https://support.microsoft.com/en-us/topic/microsoft-security-advisory-update-to-improve-credentials-protection-and-management-may-13-2014-93434251-04ac-b7f3-52aa-9f951c14b649",
|
||||
"baseline_removal": {
|
||||
"version": "Windows 11 25H2 Security Baseline",
|
||||
"reason": "Engineering teams deprecated this policy in Windows 11 24H2",
|
||||
"url": "https://techcommunity.microsoft.com/blog/microsoft-security-baselines/windows-11-version-25h2-security-baseline/4456231"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"Profiles": {
|
||||
"Balanced": true,
|
||||
"Enterprise": true,
|
||||
"Maximum": true
|
||||
},
|
||||
|
||||
"Compatibility": {
|
||||
"windows_versions": ["All Windows versions"],
|
||||
"notes": [
|
||||
"Setting is ignored on Windows 11 24H2+ (deprecated)",
|
||||
"No compatibility issues or breakage on any Windows version",
|
||||
"Recommended for all profiles for defense-in-depth"
|
||||
]
|
||||
}
|
||||
}
|
||||
20
Modules/AdvancedSecurity/Config/Firewall.json
Normal file
20
Modules/AdvancedSecurity/Config/Firewall.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"Description": "Firewall Shields Up - Block all incoming connections on Public network",
|
||||
"Purpose": "Extra protection in public WiFi networks (airports, cafes, hotels)",
|
||||
"Note": "This goes BEYOND Microsoft Security Baseline",
|
||||
|
||||
"ShieldsUp": {
|
||||
"description": "Block ALL incoming connections on Public profile, including allowed apps",
|
||||
"registry_path": "HKLM:\\SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\PublicProfile",
|
||||
"value_name": "DoNotAllowExceptions",
|
||||
"enabled_value": 1,
|
||||
"disabled_value": 0,
|
||||
"profiles": {
|
||||
"Balanced": false,
|
||||
"Enterprise": false,
|
||||
"Maximum": true
|
||||
},
|
||||
"warning": "When enabled, apps like Teams, Discord, Zoom cannot receive incoming calls on Public networks",
|
||||
"recommendation": "Enable only for maximum security (Maximum / air-gapped profile)"
|
||||
}
|
||||
}
|
||||
64
Modules/AdvancedSecurity/Config/RDP.json
Normal file
64
Modules/AdvancedSecurity/Config/RDP.json
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "RDP Hardening Configuration",
|
||||
"description": "Configuration for RDP (Remote Desktop Protocol) hardening including NLA enforcement and optional complete disable",
|
||||
"version": "2.2.0",
|
||||
|
||||
"NLA_Enforcement": {
|
||||
"description": "Network Level Authentication (NLA) enforcement settings",
|
||||
"enabled": true,
|
||||
"registry_path": "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp",
|
||||
"settings": {
|
||||
"UserAuthentication": {
|
||||
"description": "Require NLA (Network Level Authentication) before session establishment",
|
||||
"value": 1,
|
||||
"type": "DWORD",
|
||||
"attack_prevention": "Prevents brute-force attacks before login screen appears",
|
||||
"impact": "Minimal - NLA is Windows 7+ standard. May affect pre-Vista RDP clients."
|
||||
},
|
||||
"SecurityLayer": {
|
||||
"description": "Require SSL/TLS encryption for all RDP connections",
|
||||
"value": 2,
|
||||
"type": "DWORD",
|
||||
"attack_prevention": "Forces SSL/TLS encryption, prevents plaintext RDP traffic",
|
||||
"impact": "Minimal - SSL/TLS is standard since Windows Vista"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"Complete_Disable": {
|
||||
"description": "Complete RDP disable for air-gapped/high-security environments",
|
||||
"enabled_by_default": false,
|
||||
"profiles": {
|
||||
"Balanced": false,
|
||||
"Enterprise": false,
|
||||
"Maximum": "optional"
|
||||
},
|
||||
"registry_path": "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server",
|
||||
"settings": {
|
||||
"fDenyTSConnections": {
|
||||
"description": "Completely disable Remote Desktop",
|
||||
"value": 1,
|
||||
"type": "DWORD",
|
||||
"attack_prevention": "Complete RDP attack surface removal",
|
||||
"impact": "HIGH - Remote administration will not work. Windows automatically adjusts firewall rules."
|
||||
}
|
||||
},
|
||||
"requires": {
|
||||
"force_parameter": true,
|
||||
"domain_check": true,
|
||||
"warning": "This will completely disable RDP. Remote administration will not be possible."
|
||||
}
|
||||
},
|
||||
|
||||
"Compatibility": {
|
||||
"windows_versions": ["Windows 10", "Windows 11", "Windows Server 2016+"],
|
||||
"minimum_rdp_client": "Windows Vista+",
|
||||
"notes": [
|
||||
"NLA is standard since Windows Vista / Server 2008",
|
||||
"Pre-Vista clients will not be able to connect with NLA enforcement",
|
||||
"Complete disable affects all remote management via RDP",
|
||||
"Domain-joined systems should NOT disable RDP without explicit -Force"
|
||||
]
|
||||
}
|
||||
}
|
||||
85
Modules/AdvancedSecurity/Config/SRP-Rules.json
Normal file
85
Modules/AdvancedSecurity/Config/SRP-Rules.json
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"Description": "Software Restriction Policies (SRP) for CVE-2025-9491 Mitigation",
|
||||
"Documentation": "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-9491",
|
||||
"CVE": "CVE-2025-9491",
|
||||
"Threat": "Windows LNK Remote Code Execution",
|
||||
"Status": "Zero-Day, Actively Exploited since 2017",
|
||||
"Severity": "High (CVSS 7.0)",
|
||||
|
||||
"SRPConfiguration": {
|
||||
"DefaultLevel": 262144,
|
||||
"Description": "Unrestricted - Allow all programs except explicitly blocked",
|
||||
"TransparentEnabled": 1,
|
||||
"ExecutableTypes": [
|
||||
".ADE", ".ADP", ".BAS", ".BAT", ".CHM", ".CMD", ".COM", ".CPL", ".CRT",
|
||||
".EXE", ".HLP", ".HTA", ".INF", ".INS", ".ISP", ".LNK", ".MDB", ".MDE",
|
||||
".MSC", ".MSI", ".MSP", ".MST", ".OCX", ".PCD", ".PIF", ".REG", ".SCR",
|
||||
".SHS", ".URL", ".VB", ".WSC", ".WSF", ".WSH"
|
||||
]
|
||||
},
|
||||
|
||||
"PathRules": [
|
||||
{
|
||||
"Name": "Block LNK from Outlook Temp",
|
||||
"Path": "%LOCALAPPDATA%\\Temp\\*.lnk",
|
||||
"SecurityLevel": 0,
|
||||
"Description": "Blocks .lnk files from Outlook email attachments to prevent CVE-2025-9491 exploitation. Outlook saves attachments to %LOCALAPPDATA%\\Temp\\Content.Outlook\\ before execution.",
|
||||
"SaferFlags": 0,
|
||||
"Enabled": true,
|
||||
"AttackVector": "Email attachments (malicious.lnk via Outlook)",
|
||||
"Impact": "Prevents execution of .lnk files from email attachments. Legitimate shortcuts from Start Menu/Desktop/Taskbar still work (different paths)."
|
||||
},
|
||||
{
|
||||
"Name": "Block LNK from Downloads",
|
||||
"Path": "%USERPROFILE%\\Downloads\\*.lnk",
|
||||
"SecurityLevel": 0,
|
||||
"Description": "Blocks .lnk files from browser Downloads folder to prevent CVE-2025-9491 exploitation from web downloads.",
|
||||
"SaferFlags": 0,
|
||||
"Enabled": true,
|
||||
"AttackVector": "Browser downloads (malicious.lnk from web)",
|
||||
"Impact": "Prevents execution of .lnk files downloaded from internet. Move .lnk to another location to execute if needed."
|
||||
}
|
||||
],
|
||||
|
||||
"Windows11BugFix": {
|
||||
"Description": "Windows 11 has a bug where SRP is disabled by presence of certain keys in HKLM\\SYSTEM\\CurrentControlSet\\Control\\Srp\\Gp",
|
||||
"Action": "Remove RuleCount and LastWriteTime keys",
|
||||
"RegistryPath": "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Srp\\Gp",
|
||||
"KeysToRemove": ["RuleCount", "LastWriteTime"],
|
||||
"Reason": "These keys cause SRP to be ignored on Windows 11. Removing them re-enables SRP functionality."
|
||||
},
|
||||
|
||||
"RegistryPaths": {
|
||||
"PolicyRoot": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\Safer\\CodeIdentifiers",
|
||||
"PathRules": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\Safer\\CodeIdentifiers\\0\\Paths",
|
||||
"Win11BugFix": "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Srp\\Gp"
|
||||
},
|
||||
|
||||
"SecurityLevels": {
|
||||
"Disallowed": 0,
|
||||
"Unrestricted": 262144,
|
||||
"Description": "0 = Block execution, 262144 = Allow execution"
|
||||
},
|
||||
|
||||
"SafeScenarios": [
|
||||
"Start Menu shortcuts (C:\\ProgramData\\Microsoft\\Windows\\Start Menu)",
|
||||
"Desktop shortcuts (C:\\Users\\<user>\\Desktop)",
|
||||
"Taskbar shortcuts (pinned applications)",
|
||||
"Program Files shortcuts (C:\\Program Files)",
|
||||
"System shortcuts (C:\\Windows)"
|
||||
],
|
||||
|
||||
"BlockedScenarios": [
|
||||
"Outlook email attachments (%LOCALAPPDATA%\\Temp\\Content.Outlook)",
|
||||
"Browser downloads (%USERPROFILE%\\Downloads)",
|
||||
"Temporary Internet Files",
|
||||
"Other Temp locations matching patterns"
|
||||
],
|
||||
|
||||
"Testing": {
|
||||
"VerifyBlockedPath": "%USERPROFILE%\\Downloads\\test.lnk",
|
||||
"ExpectedResult": "Execution blocked with 'This program is blocked by group policy' message",
|
||||
"VerifySafePath": "%USERPROFILE%\\Desktop\\test.lnk",
|
||||
"ExpectedResult2": "Execution allowed (Desktop not in blocked path list)"
|
||||
}
|
||||
}
|
||||
57
Modules/AdvancedSecurity/Config/WindowsUpdate.json
Normal file
57
Modules/AdvancedSecurity/Config/WindowsUpdate.json
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"Description": "Simple Windows Update Configuration - MS Best Practice (GUI Settings Only)",
|
||||
"Documentation": "Matches Windows Settings > Windows Update > Advanced options",
|
||||
"Purpose": "Enable immediate updates from Microsoft using Windows built-in settings",
|
||||
|
||||
"Settings": {
|
||||
"1_ReceiveUpdatesImmediately": {
|
||||
"Name": "Get the latest updates as soon as they're available",
|
||||
"RegistryPath": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsUpdate",
|
||||
"Values": {
|
||||
"AllowOptionalContent": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "Policy: enable optional content & configuration updates immediately (grays out GUI toggle)"
|
||||
},
|
||||
"SetAllowOptionalContent": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "Policy: enforce AllowOptionalContent setting (managed by organization)"
|
||||
}
|
||||
},
|
||||
"GUIPath": "Settings > Windows Update > Advanced options > Get the latest updates as soon as they're available"
|
||||
},
|
||||
|
||||
"2_MicrosoftUpdate": {
|
||||
"Name": "Receive updates for other Microsoft products",
|
||||
"RegistryPath": "HKLM:\\SOFTWARE\\Microsoft\\WindowsUpdate\\UX\\Settings",
|
||||
"Values": {
|
||||
"AllowMUUpdateService": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "Get updates for Office, drivers, and other Microsoft products with Windows Update"
|
||||
}
|
||||
},
|
||||
"GUIPath": "Settings > Windows Update > Advanced options > Receive updates for other Microsoft products"
|
||||
},
|
||||
|
||||
"3_DeliveryOptimization": {
|
||||
"Name": "Downloads from other devices (DISABLED for privacy)",
|
||||
"RegistryPath": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\DeliveryOptimization",
|
||||
"Values": {
|
||||
"DODownloadMode": {
|
||||
"Type": "DWord",
|
||||
"Value": 0,
|
||||
"Description": "0 = HTTP only (Microsoft servers), no P2P, no LAN sharing"
|
||||
}
|
||||
},
|
||||
"GUIPath": "Settings > Windows Update > Advanced options > Delivery Optimization > Allow downloads from other devices = OFF"
|
||||
}
|
||||
},
|
||||
|
||||
"TotalRegistryKeys": 4,
|
||||
"MSBestPractice": "These are the EXACT settings shown in Windows Settings GUI - no hidden schedules, no auto-reboot config",
|
||||
"UserControl": "User keeps full control over installation timing via Windows Settings (except Setting 1 is enforced by policy if enabled)",
|
||||
"NoInteractivePrompt": "No mode selection needed - simple ON/ON/OFF configuration",
|
||||
"CRITICAL_NOTE": "Setting 1 uses Policies\\Microsoft\\Windows\\WindowsUpdate (AllowOptionalContent/SetAllowOptionalContent) and will appear as 'managed by organization'. Setting 2 MUST use UX\\Settings path (NOT Policies path) to avoid locking the Microsoft Update toggle."
|
||||
}
|
||||
|
|
@ -0,0 +1,380 @@
|
|||
function Backup-AdvancedSecuritySettings {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Create a comprehensive backup of all Advanced Security settings
|
||||
|
||||
.DESCRIPTION
|
||||
Backs up all registry keys, services, firewall rules, and Windows features
|
||||
that will be modified by the AdvancedSecurity module.
|
||||
|
||||
This is called automatically by Invoke-AdvancedSecurity before applying changes.
|
||||
|
||||
.EXAMPLE
|
||||
Backup-AdvancedSecuritySettings
|
||||
|
||||
.NOTES
|
||||
Uses the Core/Rollback.ps1 backup system
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
Write-Log -Level INFO -Message "Creating comprehensive backup of Advanced Security settings..." -Module "AdvancedSecurity"
|
||||
|
||||
$backupCount = 0
|
||||
|
||||
# Start module backup session
|
||||
$backupSession = Start-ModuleBackup -ModuleName "AdvancedSecurity"
|
||||
|
||||
if (-not $backupSession) {
|
||||
Write-Log -Level ERROR -Message "Failed to start backup session" -Module "AdvancedSecurity"
|
||||
return $false
|
||||
}
|
||||
|
||||
# 1. RDP Settings
|
||||
Write-Log -Level DEBUG -Message "Backing up RDP settings..." -Module "AdvancedSecurity"
|
||||
$rdpBackup = Backup-RegistryKey -KeyPath "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server" -BackupName "RDP_Settings"
|
||||
if ($rdpBackup) { $backupCount++ }
|
||||
|
||||
# CRITICAL: Create JSON backup for RDP (Rollback fallback)
|
||||
# .reg import often fails for RDP keys due to permissions, so we need values for PowerShell restore
|
||||
try {
|
||||
$rdpData = @{}
|
||||
|
||||
# System Settings
|
||||
$systemPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server"
|
||||
if (Test-Path $systemPath) {
|
||||
$val = Get-ItemProperty -Path $systemPath -Name "fDenyTSConnections" -ErrorAction SilentlyContinue
|
||||
if ($val) { $rdpData["System_fDenyTSConnections"] = $val.fDenyTSConnections }
|
||||
}
|
||||
|
||||
# Policy Settings
|
||||
$policyPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services"
|
||||
if (Test-Path $policyPath) {
|
||||
$val1 = Get-ItemProperty -Path $policyPath -Name "UserAuthentication" -ErrorAction SilentlyContinue
|
||||
if ($val1) { $rdpData["Policy_UserAuthentication"] = $val1.UserAuthentication }
|
||||
|
||||
$val2 = Get-ItemProperty -Path $policyPath -Name "SecurityLayer" -ErrorAction SilentlyContinue
|
||||
if ($val2) { $rdpData["Policy_SecurityLayer"] = $val2.SecurityLayer }
|
||||
}
|
||||
|
||||
if ($rdpData.Count -gt 0) {
|
||||
$rdpJson = $rdpData | ConvertTo-Json
|
||||
$rdpJsonBackup = Register-Backup -Type "AdvancedSecurity" -Data $rdpJson -Name "RDP_Hardening"
|
||||
if ($rdpJsonBackup) {
|
||||
Write-Log -Level DEBUG -Message "Created RDP JSON backup for rollback fallback" -Module "AdvancedSecurity"
|
||||
$backupCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Failed to create RDP JSON backup: $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# 2. WDigest Settings
|
||||
Write-Log -Level DEBUG -Message "Backing up WDigest settings..." -Module "AdvancedSecurity"
|
||||
$wdigestBackup = Backup-RegistryKey -KeyPath "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest" -BackupName "WDigest_Settings"
|
||||
if ($wdigestBackup) { $backupCount++ }
|
||||
|
||||
# Discovery Protocol Settings (mDNS resolver)
|
||||
Write-Log -Level DEBUG -Message "Backing up discovery protocol settings (mDNS)" -Module "AdvancedSecurity"
|
||||
$mdnsBackup = Backup-RegistryKey -KeyPath "HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters" -BackupName "DiscoveryProtocols_DnscacheParameters"
|
||||
if ($mdnsBackup) { $backupCount++ }
|
||||
|
||||
# 3. Admin Shares Settings
|
||||
Write-Log -Level DEBUG -Message "Backing up Admin Shares settings..." -Module "AdvancedSecurity"
|
||||
$adminSharesBackup = Backup-RegistryKey -KeyPath "HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters" -BackupName "AdminShares_Settings"
|
||||
if ($adminSharesBackup) { $backupCount++ }
|
||||
|
||||
# 4. TLS Settings
|
||||
Write-Log -Level DEBUG -Message "Backing up TLS settings..." -Module "AdvancedSecurity"
|
||||
$tlsVersions = @("TLS 1.0", "TLS 1.1")
|
||||
$components = @("Server", "Client")
|
||||
|
||||
foreach ($version in $tlsVersions) {
|
||||
foreach ($component in $components) {
|
||||
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\$version\$component"
|
||||
$tlsBackup = Backup-RegistryKey -KeyPath $regPath -BackupName "TLS_${version}_${component}".Replace(" ", "_").Replace(".", "")
|
||||
if ($tlsBackup) { $backupCount++ }
|
||||
}
|
||||
}
|
||||
|
||||
# 5. WPAD Settings (3 paths: WinHttp for official MS key, Wpad for legacy, Internet Settings for AutoDetect)
|
||||
Write-Log -Level DEBUG -Message "Backing up WPAD settings..." -Module "AdvancedSecurity"
|
||||
|
||||
$wpadPaths = @(
|
||||
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp", # Official MS DisableWpad key
|
||||
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Wpad", # Legacy WpadOverride
|
||||
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings" # AutoDetect
|
||||
)
|
||||
|
||||
foreach ($wpadPath in $wpadPaths) {
|
||||
$pathName = $wpadPath.Split('\')[-1]
|
||||
$wpadBackup = Backup-RegistryKey -KeyPath $wpadPath -BackupName "WPAD_${pathName}"
|
||||
if ($wpadBackup) { $backupCount++ }
|
||||
}
|
||||
|
||||
# CRITICAL: Create JSON backup for WPAD (Rollback fallback) - all paths combined
|
||||
try {
|
||||
$wpadData = @{}
|
||||
|
||||
foreach ($wpadPath in $wpadPaths) {
|
||||
if (Test-Path $wpadPath) {
|
||||
$wpadProps = Get-ItemProperty -Path $wpadPath -ErrorAction SilentlyContinue
|
||||
|
||||
# Capture all relevant properties in format expected by Rollback.ps1
|
||||
# Format: "FullPath\ValueName" = Value
|
||||
foreach ($prop in $wpadProps.PSObject.Properties) {
|
||||
if ($prop.Name -notin @('PSPath','PSParentPath','PSChildName','PSDrive','PSProvider')) {
|
||||
$fullKey = "$wpadPath\$($prop.Name)"
|
||||
$wpadData[$fullKey] = $prop.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($wpadData.Count -gt 0) {
|
||||
$wpadJson = $wpadData | ConvertTo-Json
|
||||
$wpadJsonBackup = Register-Backup -Type "AdvancedSecurity" -Data $wpadJson -Name "WPAD"
|
||||
if ($wpadJsonBackup) {
|
||||
Write-Log -Level DEBUG -Message "Created WPAD JSON backup for rollback fallback ($($wpadData.Count) values)" -Module "AdvancedSecurity"
|
||||
$backupCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Failed to create WPAD JSON backup: $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# 6. Services (including WiFi Direct for Wireless Display and WS-Discovery)
|
||||
Write-Log -Level DEBUG -Message "Backing up risky services state..." -Module "AdvancedSecurity"
|
||||
# Note: Computer Browser (Browser) is deprecated in Win10/11 - not included
|
||||
$services = @("SSDPSRV", "upnphost", "lmhosts", "WFDSConMgrSvc", "FDResPub", "fdPHost")
|
||||
|
||||
foreach ($svc in $services) {
|
||||
$svcBackup = Backup-ServiceConfiguration -ServiceName $svc
|
||||
if ($svcBackup) { $backupCount++ }
|
||||
}
|
||||
|
||||
# 7. PowerShell v2 Feature State
|
||||
Write-Log -Level DEBUG -Message "Backing up PowerShell v2 feature state..." -Module "AdvancedSecurity"
|
||||
|
||||
# Canonical detection: use Windows Optional Feature state
|
||||
$psv2Feature = $null
|
||||
try {
|
||||
$psv2Feature = Get-WindowsOptionalFeature -Online -FeatureName "MicrosoftWindowsPowerShellV2Root" -ErrorAction SilentlyContinue
|
||||
}
|
||||
catch {
|
||||
$psv2Feature = $null
|
||||
}
|
||||
|
||||
if (-not $psv2Feature -or $psv2Feature.State -ne 'Enabled') {
|
||||
# Feature not present or not enabled – nothing to back up
|
||||
Write-Log -Level INFO -Message "PowerShell v2 optional feature not enabled/present - skipping feature backup" -Module "AdvancedSecurity"
|
||||
}
|
||||
else {
|
||||
$psv2Data = @{
|
||||
FeatureName = $psv2Feature.FeatureName
|
||||
State = $psv2Feature.State
|
||||
DetectionMethod = "WindowsOptionalFeature"
|
||||
BackupDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
} | ConvertTo-Json
|
||||
|
||||
$psv2Backup = Register-Backup -Type "WindowsFeature" -Data $psv2Data -Name "PowerShellV2"
|
||||
if ($psv2Backup) { $backupCount++ }
|
||||
}
|
||||
|
||||
# 8. Firewall Rules Snapshot
|
||||
Write-Host ""
|
||||
Write-Host " ============================================" -ForegroundColor Cyan
|
||||
Write-Host " FIREWALL RULES BACKUP - PLEASE WAIT" -ForegroundColor Cyan
|
||||
Write-Host " ============================================" -ForegroundColor Cyan
|
||||
Write-Host " Creating snapshot for risky ports..." -ForegroundColor White
|
||||
Write-Host " Ports: 79, 137-139, 1900, 2869, 5355, 3702, 5353, 5357, 5358" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host " [!] This operation takes 60-120 seconds" -ForegroundColor Yellow
|
||||
Write-Host " System is working - do not interrupt!" -ForegroundColor Yellow
|
||||
Write-Host " ============================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Log -Level INFO -Message "Backing up firewall rules snapshot for risky ports (79, 137, 138, 139, 1900, 2869, 5355, 3702, 5353, 5357, 5358)..." -Module "AdvancedSecurity"
|
||||
$firewallRules = Get-NetFirewallRule | Where-Object {
|
||||
$portFilter = $_ | Get-NetFirewallPortFilter
|
||||
(($portFilter.LocalPort -in @(79, 137, 138, 139, 1900, 2869, 5355, 3702, 5353, 5357, 5358)) -or
|
||||
($portFilter.RemotePort -in @(79, 137, 138, 139, 1900, 2869, 5355, 3702, 5353, 5357, 5358))) -and
|
||||
($_.Direction -eq 'Inbound' -or $_.Direction -eq 'Outbound')
|
||||
} | Select-Object Name, DisplayName, Enabled, Direction, Action
|
||||
|
||||
$firewallData = @{
|
||||
Rules = $firewallRules
|
||||
BackupDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
RuleCount = $firewallRules.Count
|
||||
} | ConvertTo-Json -Depth 10
|
||||
|
||||
$firewallBackup = Register-Backup -Type "Firewall_Rules" -Data $firewallData -Name "RiskyPorts_Firewall"
|
||||
if ($firewallBackup) { $backupCount++ }
|
||||
|
||||
Write-Host " [OK] Firewall rules backup completed ($($firewallRules.Count) rules processed)" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# 9. SMB Shares Snapshot
|
||||
Write-Log -Level DEBUG -Message "Backing up SMB shares snapshot..." -Module "AdvancedSecurity"
|
||||
|
||||
# Check if LanmanServer service is running (required for Get-SmbShare)
|
||||
$serverService = Get-Service -Name "LanmanServer" -ErrorAction SilentlyContinue
|
||||
if (-not $serverService -or $serverService.Status -ne 'Running') {
|
||||
Write-Log -Level INFO -Message "LanmanServer service is not running - no SMB shares to backup" -Module "AdvancedSecurity"
|
||||
$adminShares = @()
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$adminShares = Get-SmbShare | Where-Object { $_.Name -match '^[A-Z]\$$|^ADMIN\$$' } |
|
||||
Select-Object Name, Path, Description
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level INFO -Message "Could not query SMB shares: $($_.Exception.Message)" -Module "AdvancedSecurity"
|
||||
$adminShares = @()
|
||||
}
|
||||
}
|
||||
|
||||
$sharesData = @{
|
||||
Shares = $adminShares
|
||||
BackupDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
ShareCount = $adminShares.Count
|
||||
} | ConvertTo-Json -Depth 10
|
||||
|
||||
$sharesBackup = Register-Backup -Type "SMB_Shares" -Data $sharesData -Name "AdminShares"
|
||||
if ($sharesBackup) { $backupCount++ }
|
||||
|
||||
$netbiosAdapters = @()
|
||||
try {
|
||||
$netbiosAdapters = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "IPEnabled = TRUE" -ErrorAction SilentlyContinue
|
||||
}
|
||||
catch {
|
||||
$netbiosAdapters = @()
|
||||
}
|
||||
if ($netbiosAdapters) {
|
||||
$netbiosSnapshot = @()
|
||||
foreach ($adapter in $netbiosAdapters) {
|
||||
$netbiosSnapshot += [PSCustomObject]@{
|
||||
Description = $adapter.Description
|
||||
Index = $adapter.Index
|
||||
TcpipNetbiosOptions = $adapter.TcpipNetbiosOptions
|
||||
}
|
||||
}
|
||||
if ($netbiosSnapshot.Count -gt 0) {
|
||||
$netbiosJson = $netbiosSnapshot | ConvertTo-Json -Depth 5
|
||||
$netbiosBackup = Register-Backup -Type "AdvancedSecurity" -Data $netbiosJson -Name "NetBIOS_Adapters"
|
||||
if ($netbiosBackup) { $backupCount++ }
|
||||
}
|
||||
}
|
||||
|
||||
# 10. Windows Update Settings (3 simple GUI settings)
|
||||
Write-Log -Level DEBUG -Message "Backing up Windows Update settings..." -Module "AdvancedSecurity"
|
||||
|
||||
# Setting 1: Get latest updates immediately
|
||||
$wuUXBackup = Backup-RegistryKey -KeyPath "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -BackupName "WindowsUpdate_UX_Settings"
|
||||
if ($wuUXBackup) { $backupCount++ }
|
||||
|
||||
# Setting 1 Policy: Windows Update optional content/config updates
|
||||
$wuPoliciesBackup = Backup-RegistryKey -KeyPath "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -BackupName "WindowsUpdate_Policies"
|
||||
if ($wuPoliciesBackup) { $backupCount++ }
|
||||
|
||||
# Setting 2: Microsoft Update for other products (moved to UX\Settings - same as Setting 1)
|
||||
# No separate backup needed - already backed up in WindowsUpdate_UX_Settings
|
||||
|
||||
# Setting 3: Delivery Optimization
|
||||
$wuDOBackup = Backup-RegistryKey -KeyPath "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DeliveryOptimization" -BackupName "WindowsUpdate_DeliveryOptimization"
|
||||
if ($wuDOBackup) { $backupCount++ }
|
||||
|
||||
# 11. SRP (Software Restriction Policies) Settings
|
||||
Write-Log -Level DEBUG -Message "Backing up SRP settings..." -Module "AdvancedSecurity"
|
||||
$srpBackup = Backup-RegistryKey -KeyPath "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers" -BackupName "SRP_Settings"
|
||||
if ($srpBackup) { $backupCount++ }
|
||||
|
||||
# 12. CRITICAL: Create comprehensive JSON Pre-State Snapshot (counter Registry tattooing)
|
||||
# This captures EXACT state of ALL AdvancedSecurity registry keys before hardening
|
||||
Write-Log -Level INFO -Message "Creating AdvancedSecurity registry pre-state snapshot (JSON)..." -Module "AdvancedSecurity"
|
||||
$preStateSnapshot = @()
|
||||
|
||||
# All registry keys that AdvancedSecurity modifies
|
||||
$allAdvSecKeys = @(
|
||||
"HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server",
|
||||
"HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp",
|
||||
"HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services",
|
||||
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest",
|
||||
"HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters",
|
||||
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server",
|
||||
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client",
|
||||
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Server",
|
||||
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client",
|
||||
"HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters",
|
||||
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp", # Official MS DisableWpad key
|
||||
"HKLM:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Wpad", # Legacy WpadOverride
|
||||
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings", # AutoDetect
|
||||
"HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate",
|
||||
"HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings",
|
||||
"HKLM:\SOFTWARE\Policies\Microsoft\Windows\DeliveryOptimization",
|
||||
"HKLM:\SOFTWARE\Policies\Microsoft\Windows\Safer\CodeIdentifiers",
|
||||
"HKLM:\SOFTWARE\Policies\Microsoft\Windows\Connect", # Wireless Display / Miracast
|
||||
"HKLM:\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\PublicProfile", # Firewall Shields Up
|
||||
"HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters" # IPv6 disable (mitm6 mitigation)
|
||||
)
|
||||
|
||||
foreach ($keyPath in $allAdvSecKeys) {
|
||||
if (Test-Path $keyPath) {
|
||||
try {
|
||||
# Get all properties for this key
|
||||
$properties = Get-ItemProperty -Path $keyPath -ErrorAction Stop
|
||||
$propertyNames = $properties.PSObject.Properties.Name | Where-Object {
|
||||
$_ -notin @('PSPath', 'PSParentPath', 'PSChildName', 'PSProvider', 'PSDrive')
|
||||
}
|
||||
|
||||
foreach ($propName in $propertyNames) {
|
||||
$propValue = $properties.$propName
|
||||
|
||||
# Get value type
|
||||
try {
|
||||
$propType = (Get-Item $keyPath).GetValueKind($propName)
|
||||
}
|
||||
catch {
|
||||
$propType = "String" # Default fallback
|
||||
}
|
||||
|
||||
$preStateSnapshot += [PSCustomObject]@{
|
||||
Path = $keyPath
|
||||
Name = $propName
|
||||
Value = $propValue
|
||||
Type = $propType.ToString()
|
||||
Exists = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Could not read properties from $keyPath : $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
# If key doesn't exist, we don't add it to snapshot (only existing values are tracked)
|
||||
}
|
||||
|
||||
# Save JSON snapshot
|
||||
try {
|
||||
$snapshotJson = $preStateSnapshot | ConvertTo-Json -Depth 5
|
||||
$result = Register-Backup -Type "AdvancedSecurity" -Data $snapshotJson -Name "AdvancedSecurity_PreState"
|
||||
if ($result) {
|
||||
$backupCount++
|
||||
Write-Log -Level SUCCESS -Message "AdvancedSecurity pre-state snapshot created ($($preStateSnapshot.Count) registry values)" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Failed to create AdvancedSecurity pre-state snapshot: $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
Write-Log -Level SUCCESS -Message "Advanced Security backup completed: $backupCount items backed up" -Module "AdvancedSecurity"
|
||||
|
||||
return $backupCount
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to backup Advanced Security settings: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
107
Modules/AdvancedSecurity/Private/Block-FingerProtocol.ps1
Normal file
107
Modules/AdvancedSecurity/Private/Block-FingerProtocol.ps1
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
function Block-FingerProtocol {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Blocks outbound connections to TCP Port 79 (Finger protocol) via Windows Firewall
|
||||
|
||||
.DESCRIPTION
|
||||
Creates a Windows Firewall rule to block all outbound connections to TCP port 79,
|
||||
preventing abuse of the finger.exe command in ClickFix malware campaigns.
|
||||
|
||||
THREAT: ClickFix attacks use finger.exe to retrieve commands from remote servers
|
||||
on port 79, which are then piped to cmd.exe for execution.
|
||||
|
||||
MITIGATION: Block outbound port 79 to prevent finger.exe from reaching C2 servers.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Preview changes without applying them
|
||||
|
||||
.EXAMPLE
|
||||
Block-FingerProtocol
|
||||
Blocks outbound finger protocol connections
|
||||
|
||||
.NOTES
|
||||
Author: NexusOne23
|
||||
Version: 2.2.0
|
||||
Requires: Administrator privileges
|
||||
|
||||
REFERENCES:
|
||||
- https://www.bleepingcomputer.com/news/security/decades-old-finger-protocol-abused-in-clickfix-malware-attacks/
|
||||
- https://redteamnews.com/threat-intelligence/clickfix-malware-campaigns-resurrect-decades-old-finger-protocol-for-command-retrieval/
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
try {
|
||||
$ruleName = "NoID Privacy - Block Finger Protocol (Port 79)"
|
||||
|
||||
Write-Log -Level INFO -Message "Checking for existing Finger protocol block rule..." -Module "AdvancedSecurity"
|
||||
|
||||
# Check if rule already exists
|
||||
$existingRule = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
|
||||
|
||||
if ($existingRule) {
|
||||
Write-Log -Level INFO -Message "Finger protocol block rule already exists" -Module "AdvancedSecurity"
|
||||
|
||||
# Show user that protection is already active
|
||||
Write-Host ""
|
||||
Write-Host "Finger Protocol Block: Already Protected" -ForegroundColor Green
|
||||
Write-Host " Rule: $ruleName" -ForegroundColor Gray
|
||||
Write-Host " Status: Active (Outbound TCP port 79 blocked)" -ForegroundColor Gray
|
||||
Write-Host " Protection: ClickFix malware using finger.exe" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Log -Level INFO -Message "[DRYRUN] Would create firewall rule to block outbound TCP port 79" -Module "AdvancedSecurity"
|
||||
return $true
|
||||
}
|
||||
|
||||
Write-Log -Level INFO -Message "Creating Windows Firewall rule to block outbound finger protocol (TCP 79)..." -Module "AdvancedSecurity"
|
||||
|
||||
# Create outbound firewall rule
|
||||
$ruleParams = @{
|
||||
DisplayName = $ruleName
|
||||
Description = "Blocks outbound connections to TCP port 79 (Finger protocol) to prevent ClickFix malware attacks. The finger.exe command is abused to retrieve malicious commands from remote servers."
|
||||
Direction = "Outbound"
|
||||
Action = "Block"
|
||||
Protocol = "TCP"
|
||||
RemotePort = 79
|
||||
Profile = "Any"
|
||||
Enabled = "True"
|
||||
ErrorAction = "Stop"
|
||||
}
|
||||
|
||||
New-NetFirewallRule @ruleParams | Out-Null
|
||||
|
||||
# Verify rule was created
|
||||
$verifyRule = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
|
||||
|
||||
if ($verifyRule) {
|
||||
Write-Log -Level SUCCESS -Message "Finger protocol (TCP port 79) outbound connections blocked" -Module "AdvancedSecurity"
|
||||
Write-Log -Level INFO -Message "ClickFix malware campaigns using finger.exe are now mitigated" -Module "AdvancedSecurity"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Firewall Rule Created:" -ForegroundColor Green
|
||||
Write-Host "Name: $ruleName" -ForegroundColor Gray
|
||||
Write-Host "Blocks: Outbound TCP port 79 (Finger protocol)" -ForegroundColor Gray
|
||||
Write-Host "Protection: ClickFix malware using finger.exe" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
return $true
|
||||
}
|
||||
else {
|
||||
Write-Log -Level ERROR -Message "Firewall rule creation failed - verification unsuccessful" -Module "AdvancedSecurity"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to create finger protocol block rule: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
219
Modules/AdvancedSecurity/Private/Disable-AdminShares.ps1
Normal file
219
Modules/AdvancedSecurity/Private/Disable-AdminShares.ps1
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
function Disable-AdminShares {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disable administrative shares (C$, ADMIN$, etc.) to prevent lateral movement
|
||||
|
||||
.DESCRIPTION
|
||||
Disables the automatic creation of administrative shares and removes existing shares.
|
||||
Administrative shares (C$, D$, ADMIN$) are used by attackers for:
|
||||
- Lateral movement (WannaCry, NotPetya propagation)
|
||||
- Remote file access with stolen credentials
|
||||
- Pass-the-Hash attacks
|
||||
- Automated malware propagation
|
||||
|
||||
CRITICAL: Includes domain-safety check. On domain-joined systems, admin shares
|
||||
are often required for Group Policy, SCCM, and remote management tools.
|
||||
|
||||
REQUIRES REBOOT to prevent share recreation.
|
||||
|
||||
.PARAMETER Force
|
||||
Force disable even on domain-joined systems (NOT RECOMMENDED for enterprise!)
|
||||
|
||||
.EXAMPLE
|
||||
Disable-AdminShares
|
||||
Disables admin shares with domain-safety check
|
||||
|
||||
.EXAMPLE
|
||||
Disable-AdminShares -Force
|
||||
Forces disable even on domain-joined systems (DANGEROUS!)
|
||||
|
||||
.NOTES
|
||||
Impact:
|
||||
- Home/Workgroup: Highly recommended
|
||||
- Enterprise Domain: May break management tools - TEST FIRST!
|
||||
- IPC$ cannot be removed (required by Windows)
|
||||
|
||||
Shares will NOT be recreated after reboot (if registry values set to 0).
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
try {
|
||||
Write-Log -Level INFO -Message "Configuring administrative shares disable..." -Module "AdvancedSecurity"
|
||||
|
||||
# CRITICAL: Check if system is domain-joined
|
||||
$computerSystem = Get-CimInstance -ClassName Win32_ComputerSystem
|
||||
|
||||
if ($computerSystem.PartOfDomain -and -not $Force) {
|
||||
Write-Log -Level WARNING -Message "Domain-joined system detected. Admin shares disable SKIPPED." -Module "AdvancedSecurity"
|
||||
Write-Log -Level WARNING -Message "Admin shares are often required for:" -Module "AdvancedSecurity"
|
||||
Write-Log -Level WARNING -Message " - Group Policy management" -Module "AdvancedSecurity"
|
||||
Write-Log -Level WARNING -Message " - SCCM/Management tools" -Module "AdvancedSecurity"
|
||||
Write-Log -Level WARNING -Message " - Remote administration" -Module "AdvancedSecurity"
|
||||
Write-Log -Level WARNING -Message "Use -Force to override (NOT RECOMMENDED!)" -Module "AdvancedSecurity"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "================================================" -ForegroundColor Yellow
|
||||
Write-Host " DOMAIN-JOINED SYSTEM DETECTED" -ForegroundColor Yellow
|
||||
Write-Host "================================================" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Administrative shares are often required for:" -ForegroundColor White
|
||||
Write-Host " - Group Policy remote management" -ForegroundColor Gray
|
||||
Write-Host " - SCCM and other management tools" -ForegroundColor Gray
|
||||
Write-Host " - Remote administration via WMI/PowerShell" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "Skipping admin shares disable to prevent breakage." -ForegroundColor Green
|
||||
Write-Host "Use -DisableAdminShares -Force to override (NOT RECOMMENDED)." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
|
||||
return $true # Not an error, just skipped
|
||||
}
|
||||
|
||||
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters"
|
||||
|
||||
# Check if LanmanServer service is running (required for Get-SmbShare)
|
||||
$serverService = Get-Service -Name "LanmanServer" -ErrorAction SilentlyContinue
|
||||
$serviceRunning = $serverService -and $serverService.Status -eq 'Running'
|
||||
|
||||
# Backup current shares and registry settings
|
||||
Write-Log -Level INFO -Message "Backing up current administrative shares..." -Module "AdvancedSecurity"
|
||||
|
||||
if (-not $serviceRunning) {
|
||||
# Server service not running - admin shares are already effectively disabled
|
||||
Write-Log -Level INFO -Message "LanmanServer service is not running - admin shares already disabled" -Module "AdvancedSecurity"
|
||||
$currentShares = @()
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$currentShares = Get-SmbShare | Where-Object { $_.Name -match '^[A-Z]\$$|^ADMIN\$$' } |
|
||||
Select-Object Name, Path, Description
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level INFO -Message "Could not query SMB shares: $($_.Exception.Message)" -Module "AdvancedSecurity"
|
||||
$currentShares = @()
|
||||
}
|
||||
}
|
||||
|
||||
$backupData = @{
|
||||
Shares = $currentShares
|
||||
AutoShareWks = (Get-ItemProperty -Path $regPath -Name "AutoShareWks" -ErrorAction SilentlyContinue).AutoShareWks
|
||||
AutoShareServer = (Get-ItemProperty -Path $regPath -Name "AutoShareServer" -ErrorAction SilentlyContinue).AutoShareServer
|
||||
DomainJoined = $computerSystem.PartOfDomain
|
||||
BackupDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
|
||||
# Register backup
|
||||
$backupJson = $backupData | ConvertTo-Json -Depth 10
|
||||
Register-Backup -Type "AdminShares_Settings" -Data $backupJson -Name "AdminShares_Disable"
|
||||
|
||||
Write-Log -Level INFO -Message "Backed up $($currentShares.Count) administrative shares" -Module "AdvancedSecurity"
|
||||
|
||||
# Disable automatic creation
|
||||
Write-Log -Level INFO -Message "Disabling automatic administrative share creation..." -Module "AdvancedSecurity"
|
||||
|
||||
if (-not (Test-Path $regPath)) {
|
||||
New-Item -Path $regPath -Force | Out-Null
|
||||
}
|
||||
|
||||
# Disable for Workstation (Home/Pro)
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "AutoShareWks" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "AutoShareWks" -Value 0 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "AutoShareWks" -Value 0 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level SUCCESS -Message "Disabled AutoShareWks (Workstation shares)" -Module "AdvancedSecurity"
|
||||
|
||||
# Disable for Server editions
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "AutoShareServer" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "AutoShareServer" -Value 0 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "AutoShareServer" -Value 0 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level SUCCESS -Message "Disabled AutoShareServer (Server edition shares)" -Module "AdvancedSecurity"
|
||||
|
||||
# Remove existing shares
|
||||
Write-Log -Level INFO -Message "Removing existing administrative shares..." -Module "AdvancedSecurity"
|
||||
|
||||
$removedCount = 0
|
||||
$skippedShares = @()
|
||||
|
||||
foreach ($share in $currentShares) {
|
||||
try {
|
||||
Remove-SmbShare -Name $share.Name -Force -ErrorAction Stop
|
||||
Write-Log -Level SUCCESS -Message "Removed share: $($share.Name) ($($share.Path))" -Module "AdvancedSecurity"
|
||||
$removedCount++
|
||||
}
|
||||
catch {
|
||||
# ADMIN$ and C$ cannot be removed while system is running (expected behavior)
|
||||
# They will NOT be recreated after reboot due to registry settings
|
||||
Write-Log -Level INFO -Message "Share $($share.Name) protected by system (will not be recreated after reboot)" -Module "AdvancedSecurity"
|
||||
$skippedShares += $share.Name
|
||||
}
|
||||
}
|
||||
|
||||
if ($skippedShares.Count -gt 0) {
|
||||
Write-Log -Level INFO -Message "System-protected shares: $($skippedShares -join ', ') - Will NOT be recreated after reboot" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
Write-Log -Level SUCCESS -Message "Removed $removedCount administrative shares, $($skippedShares.Count) protected by system" -Module "AdvancedSecurity"
|
||||
|
||||
# Add firewall protection for Public networks
|
||||
Write-Log -Level INFO -Message "Adding firewall protection for SMB on Public networks..." -Module "AdvancedSecurity"
|
||||
|
||||
$firewallRuleName = "Block Admin Shares - NoID Privacy"
|
||||
|
||||
# Check if rule already exists
|
||||
$existingRule = Get-NetFirewallRule -DisplayName $firewallRuleName -ErrorAction SilentlyContinue
|
||||
|
||||
if ($existingRule) {
|
||||
Write-Log -Level INFO -Message "Firewall rule already exists, updating..." -Module "AdvancedSecurity"
|
||||
Remove-NetFirewallRule -DisplayName $firewallRuleName -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Create new firewall rule
|
||||
New-NetFirewallRule -DisplayName $firewallRuleName `
|
||||
-Direction Inbound `
|
||||
-Protocol TCP `
|
||||
-LocalPort 445 `
|
||||
-Profile Public `
|
||||
-Action Block `
|
||||
-ErrorAction Stop | Out-Null
|
||||
|
||||
Write-Log -Level SUCCESS -Message "Firewall rule created: Block SMB (port 445) on Public networks" -Module "AdvancedSecurity"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "================================================" -ForegroundColor Green
|
||||
Write-Host " ADMINISTRATIVE SHARES DISABLED" -ForegroundColor Green
|
||||
Write-Host "================================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Registry settings:" -ForegroundColor White
|
||||
Write-Host " AutoShareWks: 0 (Disabled)" -ForegroundColor Gray
|
||||
Write-Host " AutoShareServer: 0 (Disabled)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "Removed shares: $removedCount" -ForegroundColor White
|
||||
if ($skippedShares.Count -gt 0) {
|
||||
Write-Host "Protected shares: $($skippedShares -join ', ') (cannot be removed while running)" -ForegroundColor Gray
|
||||
}
|
||||
Write-Host "Firewall: SMB blocked on Public networks" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "IMPORTANT: REBOOT REQUIRED" -ForegroundColor Yellow
|
||||
|
||||
$exampleShares = if ($skippedShares.Count -gt 0) { $skippedShares -join ', ' } else { 'C$, ADMIN$' }
|
||||
Write-Host "All admin shares (including $exampleShares) will NOT be recreated after reboot." -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Note: IPC$ cannot be removed (required by Windows)" -ForegroundColor Gray
|
||||
Write-Host "Note: Explicit file shares will still work" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to disable administrative shares: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
73
Modules/AdvancedSecurity/Private/Disable-LegacyTLS.ps1
Normal file
73
Modules/AdvancedSecurity/Private/Disable-LegacyTLS.ps1
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
function Disable-LegacyTLS {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disable legacy TLS 1.0 and TLS 1.1
|
||||
|
||||
.DESCRIPTION
|
||||
Disables TLS 1.0 and TLS 1.1 for both Client and Server to prevent
|
||||
BEAST, CRIME, and other attacks.
|
||||
|
||||
Attack Prevention: BEAST, CRIME, weak cipher suites
|
||||
|
||||
Impact: May break old internal web applications that haven't been updated
|
||||
|
||||
.EXAMPLE
|
||||
Disable-LegacyTLS
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
Write-Log -Level INFO -Message "Disabling legacy TLS 1.0 and TLS 1.1..." -Module "AdvancedSecurity"
|
||||
|
||||
$tlsVersions = @("TLS 1.0", "TLS 1.1")
|
||||
$components = @("Server", "Client")
|
||||
|
||||
$setCount = 0
|
||||
|
||||
foreach ($version in $tlsVersions) {
|
||||
foreach ($component in $components) {
|
||||
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\$version\$component"
|
||||
|
||||
# Create path if needed
|
||||
if (-not (Test-Path $regPath)) {
|
||||
New-Item -Path $regPath -Force | Out-Null
|
||||
}
|
||||
|
||||
# Disable TLS version
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "Enabled" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "Enabled" -Value 0 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "Enabled" -Value 0 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "DisabledByDefault" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "DisabledByDefault" -Value 1 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "DisabledByDefault" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
|
||||
Write-Log -Level SUCCESS -Message "Disabled $version $component" -Module "AdvancedSecurity"
|
||||
$setCount += 2
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log -Level SUCCESS -Message "Legacy TLS disabled ($setCount registry keys set)" -Module "AdvancedSecurity"
|
||||
Write-Host ""
|
||||
Write-Host "Legacy TLS Disabled:" -ForegroundColor Green
|
||||
Write-Host " TLS 1.0: Client + Server" -ForegroundColor Gray
|
||||
Write-Host " TLS 1.1: Client + Server" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "WARNING: Old web applications may not work!" -ForegroundColor Yellow
|
||||
Write-Host "Only TLS 1.2 and TLS 1.3 are now allowed." -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to disable legacy TLS: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
245
Modules/AdvancedSecurity/Private/Disable-RiskyPorts.ps1
Normal file
245
Modules/AdvancedSecurity/Private/Disable-RiskyPorts.ps1
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
function Disable-RiskyPorts {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disable risky firewall ports (LLMNR, NetBIOS, UPnP/SSDP)
|
||||
|
||||
.DESCRIPTION
|
||||
Closes firewall ports that are commonly exploited for MITM attacks,
|
||||
network enumeration, and credential theft:
|
||||
|
||||
- LLMNR (Port 5355) - HIGH RISK: Responder poisoning, credential theft
|
||||
- NetBIOS (Port 137-139) - MEDIUM RISK: Network enumeration
|
||||
- UPnP/SSDP (Port 1900, 2869) - MEDIUM RISK: Port forwarding vulnerabilities
|
||||
|
||||
Uses language-independent port-based filtering to avoid DisplayName issues.
|
||||
|
||||
.PARAMETER SkipUPnP
|
||||
Skip disabling UPnP/SSDP ports (for users who need DLNA/media streaming)
|
||||
|
||||
.EXAMPLE
|
||||
Disable-RiskyPorts
|
||||
Disables all risky firewall ports including UPnP
|
||||
|
||||
.EXAMPLE
|
||||
Disable-RiskyPorts -SkipUPnP
|
||||
Disables LLMNR and NetBIOS but keeps UPnP enabled
|
||||
|
||||
.NOTES
|
||||
Defense in Depth: Security Baseline disables protocols via registry,
|
||||
but firewall rules may still be active. This function closes the ports
|
||||
at the firewall level for additional protection.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$SkipUPnP
|
||||
)
|
||||
|
||||
try {
|
||||
Write-Log -Level INFO -Message "Disabling risky firewall ports..." -Module "AdvancedSecurity"
|
||||
|
||||
$disabledRules = 0
|
||||
$errors = @()
|
||||
|
||||
# PERFORMANCE: Get all firewall rules ONCE and cache port filters
|
||||
Write-Log -Level INFO -Message "Loading firewall rules for analysis..." -Module "AdvancedSecurity"
|
||||
$allRules = Get-NetFirewallRule | Where-Object { $_.Direction -eq 'Inbound' -and $_.Enabled -eq $true }
|
||||
|
||||
# Pre-fetch port filters to avoid repeated Get-NetFirewallPortFilter calls
|
||||
# NOTE: We cache both the rule and its ports so we can later filter ONLY
|
||||
# ALLOW rules for disabling. NoID block rules must remain enabled.
|
||||
$rulesWithPorts = @()
|
||||
foreach ($rule in $allRules) {
|
||||
$portFilter = $rule | Get-NetFirewallPortFilter -ErrorAction SilentlyContinue
|
||||
if ($portFilter) {
|
||||
$rulesWithPorts += [PSCustomObject]@{
|
||||
Rule = $rule
|
||||
LocalPort = $portFilter.LocalPort
|
||||
RemotePort = $portFilter.RemotePort
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log -Level INFO -Message "Analyzed $($rulesWithPorts.Count) firewall rules with port filters" -Module "AdvancedSecurity"
|
||||
|
||||
# Backup firewall rules
|
||||
Write-Log -Level INFO -Message "Backing up firewall rules..." -Module "AdvancedSecurity"
|
||||
$backupData = @{
|
||||
FirewallRules = $allRules | Select-Object Name, DisplayName, Enabled, Direction, Action
|
||||
BackupDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
$backupJson = $backupData | ConvertTo-Json -Depth 10
|
||||
Register-Backup -Type "Firewall_Rules" -Data $backupJson -Name "RiskyPorts_Firewall"
|
||||
|
||||
# 1. LLMNR (Port 5355 UDP) - HIGH RISK
|
||||
Write-Log -Level INFO -Message "Disabling LLMNR firewall rules (Port 5355)..." -Module "AdvancedSecurity"
|
||||
|
||||
try {
|
||||
# Filter from pre-loaded cache (ONLY ALLOW rules - keep NoID block rules enabled)
|
||||
$llmnrRules = $rulesWithPorts | Where-Object {
|
||||
($_.LocalPort -eq 5355 -or $_.RemotePort -eq 5355) -and $_.Rule.Action -eq 'Allow'
|
||||
} | Select-Object -ExpandProperty Rule
|
||||
|
||||
foreach ($rule in $llmnrRules) {
|
||||
Disable-NetFirewallRule -Name $rule.Name -ErrorAction Stop
|
||||
Write-Log -Level DEBUG -Message "Disabled LLMNR rule: $($rule.DisplayName)" -Module "AdvancedSecurity"
|
||||
$disabledRules++
|
||||
}
|
||||
|
||||
if ($llmnrRules.Count -eq 0) {
|
||||
Write-Log -Level INFO -Message "No active LLMNR rules found (already disabled or not present)" -Module "AdvancedSecurity"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level SUCCESS -Message "Disabled $($llmnrRules.Count) LLMNR firewall rules" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$errors += "LLMNR: $_"
|
||||
Write-Log -Level WARNING -Message "Failed to disable some LLMNR rules: $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# 2. NetBIOS (Port 137-139) - MEDIUM RISK
|
||||
Write-Log -Level INFO -Message "Disabling NetBIOS firewall rules (Port 137-139)..." -Module "AdvancedSecurity"
|
||||
|
||||
try {
|
||||
# Filter from pre-loaded cache (ONLY ALLOW rules - keep NoID block rules enabled)
|
||||
$netbiosRules = $rulesWithPorts | Where-Object {
|
||||
($_.LocalPort -in @(137, 138, 139)) -or ($_.RemotePort -in @(137, 138, 139))
|
||||
} | Where-Object { $_.Rule.Action -eq 'Allow' } | Select-Object -ExpandProperty Rule
|
||||
|
||||
foreach ($rule in $netbiosRules) {
|
||||
Disable-NetFirewallRule -Name $rule.Name -ErrorAction Stop
|
||||
Write-Log -Level DEBUG -Message "Disabled NetBIOS rule: $($rule.DisplayName)" -Module "AdvancedSecurity"
|
||||
$disabledRules++
|
||||
}
|
||||
|
||||
if ($netbiosRules.Count -eq 0) {
|
||||
Write-Log -Level INFO -Message "No active NetBIOS rules found (already disabled or not present)" -Module "AdvancedSecurity"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level SUCCESS -Message "Disabled $($netbiosRules.Count) NetBIOS firewall rules" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$errors += "NetBIOS: $_"
|
||||
Write-Log -Level WARNING -Message "Failed to disable some NetBIOS rules: $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# Also disable NetBIOS over TCP/IP on all network adapters
|
||||
Write-Log -Level INFO -Message "Disabling NetBIOS over TCP/IP on all adapters..." -Module "AdvancedSecurity"
|
||||
|
||||
try {
|
||||
$adapters = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "IPEnabled = TRUE"
|
||||
$adaptedCount = 0
|
||||
|
||||
foreach ($adapter in $adapters) {
|
||||
try {
|
||||
$result = Invoke-CimMethod -InputObject $adapter -MethodName SetTcpipNetbios -Arguments @{TcpipNetbiosOptions = 2 }
|
||||
if ($result.ReturnValue -eq 0) {
|
||||
Write-Log -Level DEBUG -Message "Disabled NetBIOS on adapter: $($adapter.Description)" -Module "AdvancedSecurity"
|
||||
$adaptedCount++
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Could not disable NetBIOS on adapter $($adapter.Description): $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log -Level SUCCESS -Message "Disabled NetBIOS over TCP/IP on $adaptedCount adapters" -Module "AdvancedSecurity"
|
||||
}
|
||||
catch {
|
||||
$errors += "NetBIOS TCP/IP: $_"
|
||||
Write-Log -Level WARNING -Message "Failed to disable NetBIOS over TCP/IP: $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# 3. UPnP/SSDP (Port 1900, 2869) - MEDIUM RISK (conditional)
|
||||
if (-not $SkipUPnP) {
|
||||
Write-Log -Level INFO -Message "Disabling UPnP/SSDP firewall rules (Port 1900, 2869)..." -Module "AdvancedSecurity"
|
||||
|
||||
try {
|
||||
# Filter from pre-loaded cache (ONLY ALLOW rules - keep NoID block rules enabled)
|
||||
$upnpRules = $rulesWithPorts | Where-Object {
|
||||
($_.LocalPort -in @(1900, 2869)) -or ($_.RemotePort -in @(1900, 2869))
|
||||
} | Where-Object { $_.Rule.Action -eq 'Allow' } | Select-Object -ExpandProperty Rule
|
||||
|
||||
foreach ($rule in $upnpRules) {
|
||||
Disable-NetFirewallRule -Name $rule.Name -ErrorAction Stop
|
||||
Write-Log -Level DEBUG -Message "Disabled UPnP/SSDP rule: $($rule.DisplayName)" -Module "AdvancedSecurity"
|
||||
$disabledRules++
|
||||
}
|
||||
|
||||
if ($upnpRules.Count -eq 0) {
|
||||
Write-Log -Level INFO -Message "No active UPnP/SSDP rules found (already disabled or not present)" -Module "AdvancedSecurity"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level SUCCESS -Message "Disabled $($upnpRules.Count) UPnP/SSDP firewall rules" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$errors += "UPnP/SSDP: $_"
|
||||
Write-Log -Level WARNING -Message "Failed to disable some UPnP/SSDP rules: $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# Ensure a dedicated inbound block rule exists for SSDP (UDP 1900)
|
||||
try {
|
||||
$ssdpRuleName = "NoID Privacy - Block SSDP (UDP 1900)"
|
||||
$existingSsdpRule = Get-NetFirewallRule -DisplayName $ssdpRuleName -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not $existingSsdpRule) {
|
||||
New-NetFirewallRule -DisplayName $ssdpRuleName `
|
||||
-Direction Inbound `
|
||||
-Action Block `
|
||||
-Enabled True `
|
||||
-Protocol UDP `
|
||||
-LocalPort 1900 `
|
||||
-Profile Any `
|
||||
-ErrorAction Stop | Out-Null
|
||||
Write-Log -Level SUCCESS -Message "Created SSDP block rule: $ssdpRuleName" -Module "AdvancedSecurity"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level INFO -Message "SSDP block rule already exists: $ssdpRuleName" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$errors += "SSDP BlockRule: $_"
|
||||
Write-Log -Level WARNING -Message "Failed to ensure SSDP block rule: $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Log -Level INFO -Message "UPnP/SSDP blocking skipped (user choice for DLNA compatibility)" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# Summary
|
||||
if ($errors.Count -eq 0) {
|
||||
if ($disabledRules -gt 0) {
|
||||
Write-Log -Level SUCCESS -Message "Disabled $disabledRules risky firewall rules" -Module "AdvancedSecurity"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level SUCCESS -Message "No risky firewall rules required changes (system already protected at firewall level)" -Module "AdvancedSecurity"
|
||||
}
|
||||
Write-Host ""
|
||||
Write-Host "Risky Firewall Ports Disabled: $disabledRules rules" -ForegroundColor Green
|
||||
if ($disabledRules -eq 0) {
|
||||
Write-Host " System already protected - no risky ALLOW rules were found for these ports:" -ForegroundColor Gray
|
||||
}
|
||||
Write-Host " - LLMNR (5355)" -ForegroundColor Gray
|
||||
Write-Host " - NetBIOS (137-139)" -ForegroundColor Gray
|
||||
if (-not $SkipUPnP) {
|
||||
Write-Host " - UPnP/SSDP (1900, 2869)" -ForegroundColor Gray
|
||||
}
|
||||
else {
|
||||
Write-Host " - UPnP/SSDP (1900, 2869) - SKIPPED" -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host ""
|
||||
return $true
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "Completed with $($errors.Count) errors. Disabled $disabledRules rules." -Module "AdvancedSecurity"
|
||||
return $true # Partial success is still success
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to disable risky ports: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
147
Modules/AdvancedSecurity/Private/Disable-WPAD.ps1
Normal file
147
Modules/AdvancedSecurity/Private/Disable-WPAD.ps1
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
function Disable-WPAD {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disable WPAD (Web Proxy Auto-Discovery) to prevent proxy hijacking
|
||||
|
||||
.DESCRIPTION
|
||||
Disables WPAD auto-discovery to prevent MITM attacks and proxy hijacking.
|
||||
Uses the official Microsoft-recommended registry key (DisableWpad) plus
|
||||
browser-level AutoDetect settings for third-party app compatibility.
|
||||
|
||||
Reference: https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/disable-http-proxy-auth-features
|
||||
|
||||
Attack Prevention: MITM attacks, proxy hijacking, credential theft
|
||||
|
||||
.EXAMPLE
|
||||
Disable-WPAD
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
Write-Log -Level INFO -Message "Disabling WPAD (Web Proxy Auto-Discovery)..." -Module "AdvancedSecurity"
|
||||
|
||||
# HKLM keys (machine-wide)
|
||||
# Key 1: Official Microsoft-recommended key (Windows 10 1809+ / Server 2019+)
|
||||
# Key 2: Legacy WpadOverride (for older compatibility)
|
||||
# Key 3: AutoDetect for HKLM (browser-level setting)
|
||||
$hklmKeys = @(
|
||||
@{
|
||||
Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp"
|
||||
Name = "DisableWpad"
|
||||
Value = 1
|
||||
Description = "Official MS key - disables WPAD for all WinHTTP API calls"
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Wpad"
|
||||
Name = "WpadOverride"
|
||||
Value = 1
|
||||
Description = "Legacy override key"
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings"
|
||||
Name = "AutoDetect"
|
||||
Value = 0
|
||||
Description = "Browser-level auto-detect (HKLM)"
|
||||
}
|
||||
)
|
||||
|
||||
# Backup HKLM keys
|
||||
$backupData = @{}
|
||||
foreach ($key in $hklmKeys) {
|
||||
if (Test-Path $key.Path) {
|
||||
$currentValue = (Get-ItemProperty -Path $key.Path -Name $key.Name -ErrorAction SilentlyContinue).($key.Name)
|
||||
$backupData["$($key.Path)\$($key.Name)"] = $currentValue
|
||||
}
|
||||
}
|
||||
|
||||
# Apply HKLM keys
|
||||
$setCount = 0
|
||||
foreach ($key in $hklmKeys) {
|
||||
if (-not (Test-Path $key.Path)) {
|
||||
New-Item -Path $key.Path -Force | Out-Null
|
||||
}
|
||||
|
||||
$existing = Get-ItemProperty -Path $key.Path -Name $key.Name -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing -and $null -ne $existing.($key.Name)) {
|
||||
Set-ItemProperty -Path $key.Path -Name $key.Name -Value $key.Value -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $key.Path -Name $key.Name -Value $key.Value -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
$setCount++
|
||||
}
|
||||
|
||||
# HKCU key - must be set for ALL user profiles, not just current elevated admin
|
||||
# When running as admin, HKCU points to admin's profile, not the logged-in user
|
||||
# Solution: Iterate through all user profiles via HKU (HKEY_USERS)
|
||||
$hkuPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings"
|
||||
$hkuName = "AutoDetect"
|
||||
$hkuValue = 0
|
||||
|
||||
# Mount HKU if not already available
|
||||
if (-not (Test-Path "HKU:")) {
|
||||
New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS -ErrorAction SilentlyContinue | Out-Null
|
||||
}
|
||||
|
||||
# Get all user SIDs (excluding system accounts)
|
||||
$userSIDs = Get-ChildItem -Path "HKU:\" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.PSChildName -match '^S-1-5-21-' -and $_.PSChildName -notmatch '_Classes$' } |
|
||||
Select-Object -ExpandProperty PSChildName
|
||||
|
||||
foreach ($sid in $userSIDs) {
|
||||
$userKeyPath = "HKU:\$sid\$hkuPath"
|
||||
try {
|
||||
# Backup
|
||||
if (Test-Path $userKeyPath) {
|
||||
$currentValue = (Get-ItemProperty -Path $userKeyPath -Name $hkuName -ErrorAction SilentlyContinue).($hkuName)
|
||||
$backupData["HKU\$sid\$hkuPath\$hkuName"] = $currentValue
|
||||
}
|
||||
|
||||
# Create path if not exists
|
||||
if (-not (Test-Path $userKeyPath)) {
|
||||
New-Item -Path $userKeyPath -Force | Out-Null
|
||||
}
|
||||
|
||||
# Always use Set-ItemProperty with -Type to ensure correct value type
|
||||
# Remove existing value first to avoid type conflicts
|
||||
Remove-ItemProperty -Path $userKeyPath -Name $hkuName -ErrorAction SilentlyContinue
|
||||
New-ItemProperty -Path $userKeyPath -Name $hkuName -Value $hkuValue -PropertyType DWord -Force | Out-Null
|
||||
|
||||
# Verify the value was set correctly
|
||||
$verifyVal = (Get-ItemProperty -Path $userKeyPath -Name $hkuName -ErrorAction SilentlyContinue).($hkuName)
|
||||
if ($verifyVal -eq $hkuValue) {
|
||||
$setCount++
|
||||
Write-Log -Level DEBUG -Message "WPAD AutoDetect set for SID $sid (verified: $verifyVal)" -Module "AdvancedSecurity"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "WPAD AutoDetect verification failed for SID $sid (expected $hkuValue, got $verifyVal)" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Could not set WPAD for SID $sid : $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
|
||||
# Also set for .DEFAULT (applies to new users)
|
||||
$defaultPath = "HKU:\.DEFAULT\$hkuPath"
|
||||
try {
|
||||
if (-not (Test-Path $defaultPath)) {
|
||||
New-Item -Path $defaultPath -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
}
|
||||
New-ItemProperty -Path $defaultPath -Name $hkuName -Value $hkuValue -PropertyType DWord -Force -ErrorAction SilentlyContinue | Out-Null
|
||||
$setCount++
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Could not set WPAD for .DEFAULT: $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
Register-Backup -Type "WPAD_Settings" -Data ($backupData | ConvertTo-Json) -Name "WPAD"
|
||||
|
||||
Write-Log -Level SUCCESS -Message "WPAD disabled ($setCount registry keys set across all user profiles)" -Module "AdvancedSecurity"
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to disable WPAD: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
172
Modules/AdvancedSecurity/Private/Enable-RdpNLA.ps1
Normal file
172
Modules/AdvancedSecurity/Private/Enable-RdpNLA.ps1
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
function Enable-RdpNLA {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Enforce Network Level Authentication (NLA) for Remote Desktop Protocol
|
||||
|
||||
.DESCRIPTION
|
||||
HYBRID ENFORCEMENT APPROACH (Best of Security + Usability):
|
||||
|
||||
LEVEL 1 - ENFORCED VIA POLICIES (admin cannot disable):
|
||||
- NLA (Network Level Authentication) = REQUIRED
|
||||
- SSL/TLS encryption = REQUIRED
|
||||
Path: HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services
|
||||
|
||||
LEVEL 2 - USER CONTROL VIA SYSTEM (admin can change in Settings):
|
||||
- RDP Enable/Disable = User choice
|
||||
Path: HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server
|
||||
|
||||
Attack Prevention:
|
||||
- Prevents brute-force attacks before login screen appears
|
||||
- Forces SSL/TLS encryption for RDP traffic (cannot be disabled)
|
||||
- Requires authentication at network level (cannot be disabled)
|
||||
|
||||
.PARAMETER DisableRDP
|
||||
Optionally completely disable RDP (for air-gapped systems)
|
||||
|
||||
.PARAMETER Force
|
||||
Force RDP disable even on domain-joined systems (NOT RECOMMENDED)
|
||||
|
||||
.EXAMPLE
|
||||
Enable-RdpNLA
|
||||
Enforces NLA and SSL/TLS for RDP
|
||||
|
||||
.EXAMPLE
|
||||
Enable-RdpNLA -DisableRDP -Force
|
||||
Completely disables RDP (air-gapped mode)
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DisableRDP,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
try {
|
||||
Write-Log -Level INFO -Message "Configuring RDP hardening (Hybrid Enforcement)..." -Module "AdvancedSecurity"
|
||||
|
||||
# POLICIES PATH (enforced - admin cannot change via GUI)
|
||||
$policyPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services"
|
||||
|
||||
# SYSTEM PATH (user control - admin can change via Settings)
|
||||
$systemPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server"
|
||||
|
||||
# Backup current settings from BOTH paths
|
||||
$backupData = @{
|
||||
Policy_UserAuthentication = $null
|
||||
Policy_SecurityLayer = $null
|
||||
System_fDenyTSConnections = $null
|
||||
}
|
||||
|
||||
# Backup Policies path (if exists)
|
||||
if (Test-Path $policyPath) {
|
||||
$backupData.Policy_UserAuthentication = (Get-ItemProperty -Path $policyPath -Name "UserAuthentication" -ErrorAction SilentlyContinue).UserAuthentication
|
||||
$backupData.Policy_SecurityLayer = (Get-ItemProperty -Path $policyPath -Name "SecurityLayer" -ErrorAction SilentlyContinue).SecurityLayer
|
||||
}
|
||||
|
||||
# Backup System path (if exists)
|
||||
if (Test-Path $systemPath) {
|
||||
$backupData.System_fDenyTSConnections = (Get-ItemProperty -Path $systemPath -Name "fDenyTSConnections" -ErrorAction SilentlyContinue).fDenyTSConnections
|
||||
}
|
||||
|
||||
# Register backup
|
||||
$backupJson = $backupData | ConvertTo-Json -Depth 10
|
||||
Register-Backup -Type "RDP_Settings" -Data $backupJson -Name "RDP_Hardening"
|
||||
|
||||
# ========================================
|
||||
# LEVEL 1: ENFORCE NLA + SSL/TLS (Policies)
|
||||
# ========================================
|
||||
Write-Log -Level INFO -Message "LEVEL 1: Enforcing NLA + SSL/TLS via Policies (admin cannot disable)..." -Module "AdvancedSecurity"
|
||||
|
||||
# Create Policies path if not exists
|
||||
if (-not (Test-Path $policyPath)) {
|
||||
New-Item -Path $policyPath -Force | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Created Policies registry path" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# ENFORCE NLA (cannot be disabled by admin via GUI)
|
||||
$existing = Get-ItemProperty -Path $policyPath -Name "UserAuthentication" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $policyPath -Name "UserAuthentication" -Value 1 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $policyPath -Name "UserAuthentication" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level SUCCESS -Message "NLA ENFORCED via Policies (UserAuthentication = 1)" -Module "AdvancedSecurity"
|
||||
|
||||
# ENFORCE SSL/TLS (cannot be disabled by admin via GUI)
|
||||
$existing = Get-ItemProperty -Path $policyPath -Name "SecurityLayer" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $policyPath -Name "SecurityLayer" -Value 2 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $policyPath -Name "SecurityLayer" -Value 2 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level SUCCESS -Message "SSL/TLS ENFORCED via Policies (SecurityLayer = 2)" -Module "AdvancedSecurity"
|
||||
|
||||
# ========================================
|
||||
# LEVEL 2: RDP ENABLE/DISABLE (System - User Control)
|
||||
# ========================================
|
||||
Write-Log -Level INFO -Message "LEVEL 2: Setting RDP enable/disable (user CAN change in Settings)..." -Module "AdvancedSecurity"
|
||||
|
||||
# Create System path if not exists
|
||||
if (-not (Test-Path $systemPath)) {
|
||||
New-Item -Path $systemPath -Force | Out-Null
|
||||
}
|
||||
|
||||
if ($DisableRDP) {
|
||||
# Check if domain-joined
|
||||
$computerSystem = Get-CimInstance -ClassName Win32_ComputerSystem
|
||||
|
||||
if ($computerSystem.PartOfDomain -and -not $Force) {
|
||||
Write-Log -Level WARNING -Message "Domain-joined system detected. RDP disable skipped." -Module "AdvancedSecurity"
|
||||
Write-Log -Level WARNING -Message "Use -Force to override (NOT RECOMMENDED for enterprise!)" -Module "AdvancedSecurity"
|
||||
Write-Host ""
|
||||
Write-Host "WARNING: Domain-joined system detected!" -ForegroundColor Yellow
|
||||
Write-Host "Skipping RDP complete disable (may be required for management)." -ForegroundColor Yellow
|
||||
Write-Host "Use -DisableRDP -Force to override (NOT RECOMMENDED)." -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
else {
|
||||
# Set RDP DISABLED as default (user CAN re-enable)
|
||||
$existing = Get-ItemProperty -Path $systemPath -Name "fDenyTSConnections" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $systemPath -Name "fDenyTSConnections" -Value 1 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $systemPath -Name "fDenyTSConnections" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level SUCCESS -Message "RDP DISABLED by default (user CAN re-enable via Settings)" -Module "AdvancedSecurity"
|
||||
Write-Log -Level INFO -Message "Windows will automatically adjust RDP firewall rules" -Module "AdvancedSecurity"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "RDP Status: DISABLED by default" -ForegroundColor Yellow
|
||||
Write-Host " You CAN re-enable RDP anytime via:" -ForegroundColor White
|
||||
Write-Host " -> Settings > System > Remote Desktop > Enable Remote Desktop" -ForegroundColor Gray
|
||||
Write-Host " [OK] NLA + SSL/TLS will remain ENFORCED (secure!)" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Set RDP ENABLED (with NLA+SSL enforced)
|
||||
$existing = Get-ItemProperty -Path $systemPath -Name "fDenyTSConnections" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $systemPath -Name "fDenyTSConnections" -Value 0 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $systemPath -Name "fDenyTSConnections" -Value 0 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level SUCCESS -Message "RDP ENABLED with enforced NLA+SSL (user CAN disable via Settings)" -Module "AdvancedSecurity"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "RDP Status: ENABLED with enforced security" -ForegroundColor Green
|
||||
Write-Host " [ENFORCED] NLA (Network Level Authentication)" -ForegroundColor Green
|
||||
Write-Host " [ENFORCED] SSL/TLS encryption" -ForegroundColor Green
|
||||
Write-Host " You CAN disable RDP anytime via Settings if not needed" -ForegroundColor White
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to configure RDP hardening: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
98
Modules/AdvancedSecurity/Private/Remove-PowerShellV2.ps1
Normal file
98
Modules/AdvancedSecurity/Private/Remove-PowerShellV2.ps1
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
function Remove-PowerShellV2 {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Remove PowerShell v2 to prevent downgrade attacks
|
||||
|
||||
.DESCRIPTION
|
||||
Removes the PowerShell v2 Windows Feature to prevent downgrade attacks.
|
||||
PowerShell v2 bypasses logging, AMSI, and Constrained Language Mode.
|
||||
|
||||
Attack Prevention: Downgrade attacks, script logging bypass, AMSI bypass
|
||||
|
||||
Impact: Legacy scripts using -Version 2 will not work
|
||||
|
||||
.EXAMPLE
|
||||
Remove-PowerShellV2
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
Write-Log -Level INFO -Message "Checking PowerShell v2 optional feature state..." -Module "AdvancedSecurity"
|
||||
|
||||
# Canonical detection: use Windows Optional Feature state
|
||||
$psv2Feature = $null
|
||||
try {
|
||||
$psv2Feature = Get-WindowsOptionalFeature -Online -FeatureName "MicrosoftWindowsPowerShellV2Root" -ErrorAction SilentlyContinue
|
||||
}
|
||||
catch {
|
||||
$psv2Feature = $null
|
||||
}
|
||||
|
||||
if (-not $psv2Feature) {
|
||||
# Feature is not available on this OS (e.g. removed in newer Windows 11 builds)
|
||||
Write-Log -Level INFO -Message "PowerShell v2 optional feature not available on this OS (nothing to remove)" -Module "AdvancedSecurity"
|
||||
return [PSCustomObject]@{
|
||||
Success = $true
|
||||
Changed = $false
|
||||
}
|
||||
}
|
||||
|
||||
if ($psv2Feature.State -ne 'Enabled') {
|
||||
# Feature exists but is not enabled/installed
|
||||
Write-Log -Level SUCCESS -Message "PowerShell v2 feature state: $($psv2Feature.State) - no removal required" -Module "AdvancedSecurity"
|
||||
return [PSCustomObject]@{
|
||||
Success = $true
|
||||
Changed = $false
|
||||
}
|
||||
}
|
||||
|
||||
# PSv2 feature is enabled - proceed with backup and removal
|
||||
Write-Log -Level DEBUG -Message "PowerShell v2 feature is ENABLED - preparing backup and removal via DISM..." -Module "AdvancedSecurity"
|
||||
|
||||
# Backup current state
|
||||
$backupData = @{
|
||||
FeatureName = $psv2Feature.FeatureName
|
||||
State = $psv2Feature.State
|
||||
BackupDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
Register-Backup -Type "WindowsFeature" -Data ($backupData | ConvertTo-Json) -Name "PowerShellV2"
|
||||
|
||||
# Remove PowerShell v2
|
||||
Write-Log -Level WARNING -Message "Removing PowerShell v2 (this may take a moment)..." -Module "AdvancedSecurity"
|
||||
Write-Host ""
|
||||
Write-Host "Removing PowerShell v2..." -ForegroundColor Yellow
|
||||
Write-Host "This may take up to 60 seconds..." -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
$result = Disable-WindowsOptionalFeature -Online -FeatureName "MicrosoftWindowsPowerShellV2Root" -NoRestart -ErrorAction Stop
|
||||
|
||||
if ($result.RestartNeeded) {
|
||||
Write-Log -Level WARNING -Message "PowerShell v2 removed - REBOOT REQUIRED to complete" -Module "AdvancedSecurity"
|
||||
Write-Host ""
|
||||
Write-Host "PowerShell v2 Removed!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "IMPORTANT: REBOOT REQUIRED" -ForegroundColor Yellow
|
||||
Write-Host "Changes will take effect after reboot." -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
}
|
||||
else {
|
||||
Write-Log -Level SUCCESS -Message "PowerShell v2 removed successfully" -Module "AdvancedSecurity"
|
||||
Write-Host ""
|
||||
Write-Host "PowerShell v2 Removed!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
Success = $true
|
||||
Changed = $true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to remove PowerShell v2: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return [PSCustomObject]@{
|
||||
Success = $false
|
||||
Changed = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
function Set-DiscoveryProtocolsSecurity {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Completely hardens discovery protocols (WS-Discovery + mDNS) for air-gapped systems.
|
||||
|
||||
.DESCRIPTION
|
||||
This function is designed for the AdvancedSecurity **Maximum** profile.
|
||||
|
||||
It applies the following changes:
|
||||
- Disables OS-level mDNS client resolution
|
||||
- Stops and disables WS-Discovery related services
|
||||
- Adds explicit Windows Firewall BLOCK rules for WS-Discovery and mDNS ports
|
||||
|
||||
Protocols/ports affected:
|
||||
- WS-Discovery: UDP 3702, TCP 5357/5358
|
||||
- mDNS: UDP 5353
|
||||
|
||||
NOTE: Backup for services, registry and firewall rules is handled centrally by
|
||||
Backup-AdvancedSecuritySettings and the Core rollback system.
|
||||
|
||||
.PARAMETER DisableCompletely
|
||||
When present, applies full discovery protocol hardening. Currently this
|
||||
function is only called with -DisableCompletely in Maximum profile.
|
||||
|
||||
.EXAMPLE
|
||||
Set-DiscoveryProtocolsSecurity -DisableCompletely
|
||||
# Completely disables WS-Discovery and mDNS on this host.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$DisableCompletely
|
||||
)
|
||||
|
||||
try {
|
||||
Write-Log -Level INFO -Message "Applying discovery protocol security (WS-Discovery + mDNS)... DisableCompletely: $DisableCompletely" -Module "AdvancedSecurity"
|
||||
|
||||
if (-not $DisableCompletely) {
|
||||
Write-Log -Level INFO -Message "Set-DiscoveryProtocolsSecurity called without -DisableCompletely. No changes applied." -Module "AdvancedSecurity"
|
||||
return $true
|
||||
}
|
||||
|
||||
$changesApplied = 0
|
||||
|
||||
# =============================
|
||||
# 1) Disable mDNS via DNS Client parameters
|
||||
# =============================
|
||||
$dnsParamsPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters"
|
||||
|
||||
if (-not (Test-Path $dnsParamsPath)) {
|
||||
New-Item -Path $dnsParamsPath -Force | Out-Null
|
||||
Write-Log -Level INFO -Message "Created registry key: $dnsParamsPath" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
$mdnsProps = Get-ItemProperty -Path $dnsParamsPath -ErrorAction SilentlyContinue
|
||||
$currentEnableMdns = if ($mdnsProps) { $mdnsProps.EnableMDNS } else { $null }
|
||||
|
||||
if ($currentEnableMdns -ne 0) {
|
||||
New-ItemProperty -Path $dnsParamsPath -Name "EnableMDNS" -Value 0 -PropertyType DWord -Force | Out-Null
|
||||
Write-Log -Level INFO -Message "Set EnableMDNS = 0 (Disable OS mDNS resolver)" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
|
||||
# =============================
|
||||
# 2) Stop and disable WS-Discovery related services
|
||||
# =============================
|
||||
$wsdServices = @(
|
||||
@{ Name = "FDResPub"; DisplayName = "Function Discovery Resource Publication" },
|
||||
@{ Name = "fdPHost"; DisplayName = "Function Discovery Provider Host" }
|
||||
)
|
||||
|
||||
foreach ($svc in $wsdServices) {
|
||||
$service = Get-Service -Name $svc.Name -ErrorAction SilentlyContinue
|
||||
if (-not $service) {
|
||||
Write-Log -Level INFO -Message "Service $($svc.Name) not found (may not be installed)" -Module "AdvancedSecurity"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($service.Status -eq 'Running') {
|
||||
try {
|
||||
Stop-Service -Name $svc.Name -Force -ErrorAction Stop
|
||||
Write-Log -Level INFO -Message "Stopped service: $($svc.Name) ($($svc.DisplayName))" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Failed to stop service $($svc.Name): $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
|
||||
if ($service.StartType -ne 'Disabled') {
|
||||
try {
|
||||
Set-Service -Name $svc.Name -StartupType Disabled -ErrorAction Stop
|
||||
Write-Log -Level INFO -Message "Set service $($svc.Name) StartupType = Disabled" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Failed to set StartupType=Disabled for $($svc.Name): $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# =============================
|
||||
# 3) Add firewall BLOCK rules for WS-Discovery and mDNS
|
||||
# =============================
|
||||
$firewallRules = @(
|
||||
@{ Name = "NoID-Block-WSD-UDP-3702"; DisplayName = "NoID Privacy - Block WS-Discovery UDP 3702"; Protocol = "UDP"; LocalPort = 3702 },
|
||||
@{ Name = "NoID-Block-WSD-TCP-5357"; DisplayName = "NoID Privacy - Block WS-Discovery HTTP TCP 5357"; Protocol = "TCP"; LocalPort = 5357 },
|
||||
@{ Name = "NoID-Block-WSD-TCP-5358"; DisplayName = "NoID Privacy - Block WS-Discovery HTTPS TCP 5358"; Protocol = "TCP"; LocalPort = 5358 },
|
||||
@{ Name = "NoID-Block-mDNS-UDP-5353"; DisplayName = "NoID Privacy - Block mDNS UDP 5353"; Protocol = "UDP"; LocalPort = 5353 }
|
||||
)
|
||||
|
||||
foreach ($rule in $firewallRules) {
|
||||
try {
|
||||
$existing = Get-NetFirewallRule -Name $rule.Name -ErrorAction SilentlyContinue
|
||||
if (-not $existing) {
|
||||
New-NetFirewallRule -Name $rule.Name `
|
||||
-DisplayName $rule.DisplayName `
|
||||
-Direction Inbound `
|
||||
-Protocol $rule.Protocol `
|
||||
-LocalPort $rule.LocalPort `
|
||||
-Action Block `
|
||||
-Profile Any `
|
||||
-Enabled True | Out-Null
|
||||
Write-Log -Level INFO -Message "Created firewall rule: $($rule.DisplayName)" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
else {
|
||||
# Ensure rule is enabled and blocking
|
||||
Set-NetFirewallRule -Name $rule.Name -Enabled True -Action Block -ErrorAction SilentlyContinue
|
||||
Write-Log -Level DEBUG -Message "Firewall rule already exists and was enforced: $($rule.DisplayName)" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Failed to ensure firewall rule $($rule.DisplayName): $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
|
||||
if ($changesApplied -eq 0) {
|
||||
Write-Log -Level SUCCESS -Message "Discovery protocol security already configured (no changes needed)" -Module "AdvancedSecurity"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level SUCCESS -Message "Discovery protocol security applied ($changesApplied changes)" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to apply discovery protocol security (WS-Discovery/mDNS): $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
66
Modules/AdvancedSecurity/Private/Set-FirewallShieldsUp.ps1
Normal file
66
Modules/AdvancedSecurity/Private/Set-FirewallShieldsUp.ps1
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
function Set-FirewallShieldsUp {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Enable "Shields Up" mode - Block ALL incoming connections on Public network
|
||||
|
||||
.DESCRIPTION
|
||||
Sets DoNotAllowExceptions=1 for PublicProfile firewall.
|
||||
This blocks ALL incoming connections, even from allowed apps.
|
||||
Goes BEYOND Microsoft Security Baseline.
|
||||
|
||||
.PARAMETER Enable
|
||||
Enable Shields Up mode (block all incoming on Public)
|
||||
|
||||
.PARAMETER Disable
|
||||
Disable Shields Up mode (allow configured exceptions)
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Enable,
|
||||
[switch]$Disable
|
||||
)
|
||||
|
||||
$moduleName = "AdvancedSecurity"
|
||||
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\PublicProfile"
|
||||
$valueName = "DoNotAllowExceptions"
|
||||
|
||||
try {
|
||||
if ($Enable) {
|
||||
Write-Log -Level INFO -Message "Enabling Firewall Shields Up mode (Public profile)..." -Module $moduleName
|
||||
|
||||
# Ensure path exists
|
||||
if (!(Test-Path $regPath)) {
|
||||
New-Item -Path $regPath -Force | Out-Null
|
||||
}
|
||||
|
||||
# Set DoNotAllowExceptions = 1
|
||||
Set-ItemProperty -Path $regPath -Name $valueName -Value 1 -Type DWord -Force
|
||||
|
||||
Write-Log -Level SUCCESS -Message "Firewall Shields Up ENABLED - All incoming connections blocked on Public network" -Module $moduleName
|
||||
Write-Host ""
|
||||
Write-Host " SHIELDS UP: Public network now blocks ALL incoming connections" -ForegroundColor Green
|
||||
Write-Host " This includes allowed apps (Teams, Discord, etc. cannot receive calls)" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
return $true
|
||||
}
|
||||
elseif ($Disable) {
|
||||
Write-Log -Level INFO -Message "Disabling Firewall Shields Up mode..." -Module $moduleName
|
||||
|
||||
if (Test-Path $regPath) {
|
||||
Set-ItemProperty -Path $regPath -Name $valueName -Value 0 -Type DWord -Force
|
||||
}
|
||||
|
||||
Write-Log -Level SUCCESS -Message "Firewall Shields Up disabled - Normal firewall exceptions apply" -Module $moduleName
|
||||
return $true
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "No action specified for Set-FirewallShieldsUp" -Module $moduleName
|
||||
return $false
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to set Firewall Shields Up: $_" -Module $moduleName
|
||||
return $false
|
||||
}
|
||||
}
|
||||
102
Modules/AdvancedSecurity/Private/Set-IPv6Security.ps1
Normal file
102
Modules/AdvancedSecurity/Private/Set-IPv6Security.ps1
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
function Set-IPv6Security {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disable IPv6 to prevent DHCPv6/Router Solicitation attacks (mitm6)
|
||||
|
||||
.DESCRIPTION
|
||||
Disables IPv6 via registry to prevent:
|
||||
- mitm6 attacks (DHCPv6 spoofing → DNS takeover → NTLM relay)
|
||||
- IPv6 Router Advertisement spoofing
|
||||
- DHCPv6 poisoning attacks
|
||||
|
||||
This is the recommended mitigation per Fox-IT security research.
|
||||
|
||||
WARNING: May break Exchange Server and some Active Directory features.
|
||||
Only recommended for air-gapped or high-security systems.
|
||||
|
||||
.PARAMETER DisableCompletely
|
||||
If true, completely disables IPv6 (DisabledComponents = 0xFF)
|
||||
|
||||
.EXAMPLE
|
||||
Set-IPv6Security -DisableCompletely
|
||||
|
||||
.NOTES
|
||||
Registry: HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\DisabledComponents
|
||||
Value 0xFF = Disable all IPv6 components
|
||||
|
||||
REBOOT REQUIRED for changes to take effect.
|
||||
|
||||
References:
|
||||
- https://blog.fox-it.com/2018/01/11/mitm6-compromising-ipv4-networks-via-ipv6/
|
||||
- https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/configure-ipv6-in-windows
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$DisableCompletely
|
||||
)
|
||||
|
||||
try {
|
||||
if (-not $DisableCompletely) {
|
||||
Write-Log -Level INFO -Message "IPv6 disable not requested - keeping default configuration" -Module "AdvancedSecurity"
|
||||
return $true
|
||||
}
|
||||
|
||||
Write-Log -Level INFO -Message "Disabling IPv6 (mitm6 attack mitigation)..." -Module "AdvancedSecurity"
|
||||
|
||||
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"
|
||||
|
||||
# Backup current value
|
||||
$currentValue = Get-ItemProperty -Path $regPath -Name "DisabledComponents" -ErrorAction SilentlyContinue
|
||||
$backupData = @{
|
||||
Path = $regPath
|
||||
Name = "DisabledComponents"
|
||||
PreviousValue = if ($currentValue) { $currentValue.DisabledComponents } else { "_NOT_SET" }
|
||||
NewValue = 255
|
||||
BackupDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
$backupJson = $backupData | ConvertTo-Json -Depth 10
|
||||
Register-Backup -Type "Registry" -Data $backupJson -Name "IPv6_DisabledComponents"
|
||||
|
||||
# Ensure registry path exists
|
||||
if (-not (Test-Path $regPath)) {
|
||||
New-Item -Path $regPath -Force | Out-Null
|
||||
}
|
||||
|
||||
# Set DisabledComponents to 0xFF (255) = Disable all IPv6 components
|
||||
Set-ItemProperty -Path $regPath -Name "DisabledComponents" -Value 255 -Type DWord -Force
|
||||
|
||||
Write-Log -Level SUCCESS -Message "IPv6 disabled (DisabledComponents = 0xFF)" -Module "AdvancedSecurity"
|
||||
|
||||
# Verify
|
||||
$verifyValue = (Get-ItemProperty -Path $regPath -Name "DisabledComponents" -ErrorAction SilentlyContinue).DisabledComponents
|
||||
if ($verifyValue -eq 255) {
|
||||
Write-Log -Level SUCCESS -Message "IPv6 disable verified - REBOOT REQUIRED" -Module "AdvancedSecurity"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "================================================" -ForegroundColor Yellow
|
||||
Write-Host " IPv6 DISABLED (mitm6 Attack Mitigation)" -ForegroundColor Yellow
|
||||
Write-Host "================================================" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Registry: DisabledComponents = 0xFF (255)" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Protection against:" -ForegroundColor Cyan
|
||||
Write-Host " - DHCPv6 spoofing (mitm6 tool)" -ForegroundColor Gray
|
||||
Write-Host " - IPv6 Router Advertisement attacks" -ForegroundColor Gray
|
||||
Write-Host " - DNS takeover via fake DHCPv6 server" -ForegroundColor Gray
|
||||
Write-Host " - NTLM credential relay attacks" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "REBOOT REQUIRED for changes to take effect!" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
|
||||
return $true
|
||||
}
|
||||
else {
|
||||
Write-Log -Level ERROR -Message "IPv6 disable verification failed" -Module "AdvancedSecurity"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to disable IPv6: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
192
Modules/AdvancedSecurity/Private/Set-SRPRules.ps1
Normal file
192
Modules/AdvancedSecurity/Private/Set-SRPRules.ps1
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
function Set-SRPRules {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Configures Software Restriction Policies (SRP) to block .lnk execution from Temp/Downloads
|
||||
|
||||
.DESCRIPTION
|
||||
Implements SRP rules to mitigate CVE-2025-9491 (Windows LNK Remote Code Execution).
|
||||
|
||||
CRITICAL ZERO-DAY MITIGATION:
|
||||
- CVE-2025-9491: Actively exploited since 2017
|
||||
- No patch available (Microsoft: "does not meet servicing threshold")
|
||||
- ASR and SmartScreen do NOT protect against this
|
||||
|
||||
SRP Rules Created:
|
||||
1. Block *.lnk from %LOCALAPPDATA%\Temp\* (Outlook attachments)
|
||||
2. Block *.lnk from %USERPROFILE%\Downloads\* (Browser downloads)
|
||||
|
||||
Windows 11 Bug Fix:
|
||||
- Removes buggy registry keys that disable SRP on Win11
|
||||
|
||||
.PARAMETER DryRun
|
||||
Preview changes without applying them
|
||||
|
||||
.EXAMPLE
|
||||
Set-SRPRules
|
||||
Applies SRP rules to block malicious .lnk execution
|
||||
|
||||
.NOTES
|
||||
Author: NexusOne23
|
||||
Version: 2.2.0
|
||||
Requires: Administrator privileges
|
||||
|
||||
REFERENCES:
|
||||
- CVE-2025-9491: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-9491
|
||||
- CISA KEV: https://www.cisa.gov/known-exploited-vulnerabilities-catalog
|
||||
- SRP Documentation: https://docs.microsoft.com/windows/security/threat-protection/windows-defender-application-control/applocker/software-restriction-policies
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
try {
|
||||
$configPath = Join-Path $PSScriptRoot "..\Config\SRP-Rules.json"
|
||||
|
||||
if (-not (Test-Path $configPath)) {
|
||||
Write-Log -Level ERROR -Message "SRP-Rules.json not found: $configPath" -Module "AdvancedSecurity"
|
||||
return $false
|
||||
}
|
||||
|
||||
$config = Get-Content $configPath -Raw | ConvertFrom-Json
|
||||
|
||||
Write-Log -Level INFO -Message "Configuring Software Restriction Policies (SRP) for CVE-2025-9491..." -Module "AdvancedSecurity"
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Log -Level INFO -Message "[DRYRUN] Would configure SRP with following rules:" -Module "AdvancedSecurity"
|
||||
foreach ($rule in $config.PathRules) {
|
||||
Write-Log -Level INFO -Message "[DRYRUN] - $($rule.Name): $($rule.Path)" -Module "AdvancedSecurity"
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
# Step 1: Create SRP Policy Root
|
||||
$policyRoot = $config.RegistryPaths.PolicyRoot
|
||||
|
||||
if (-not (Test-Path $policyRoot)) {
|
||||
Write-Log -Level INFO -Message "Creating SRP policy root: $policyRoot" -Module "AdvancedSecurity"
|
||||
New-Item -Path $policyRoot -Force | Out-Null
|
||||
}
|
||||
|
||||
# Step 2: Set Default Level (Unrestricted)
|
||||
Write-Log -Level INFO -Message "Setting SRP default level to Unrestricted (262144)" -Module "AdvancedSecurity"
|
||||
|
||||
$existingDefaultLevel = Get-ItemProperty -Path $policyRoot -Name "DefaultLevel" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existingDefaultLevel) {
|
||||
Set-ItemProperty -Path $policyRoot -Name "DefaultLevel" -Value $config.SRPConfiguration.DefaultLevel -Force | Out-Null
|
||||
}
|
||||
else {
|
||||
New-ItemProperty -Path $policyRoot -Name "DefaultLevel" -Value $config.SRPConfiguration.DefaultLevel -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
|
||||
# Step 3: Enable Transparent Enforcement
|
||||
$existingTransparent = Get-ItemProperty -Path $policyRoot -Name "TransparentEnabled" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existingTransparent) {
|
||||
Set-ItemProperty -Path $policyRoot -Name "TransparentEnabled" -Value $config.SRPConfiguration.TransparentEnabled -Force | Out-Null
|
||||
}
|
||||
else {
|
||||
New-ItemProperty -Path $policyRoot -Name "TransparentEnabled" -Value $config.SRPConfiguration.TransparentEnabled -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
|
||||
# Step 4: Create Path Rules
|
||||
$pathRulesRoot = $config.RegistryPaths.PathRules
|
||||
|
||||
if (-not (Test-Path $pathRulesRoot)) {
|
||||
Write-Log -Level INFO -Message "Creating SRP path rules root: $pathRulesRoot" -Module "AdvancedSecurity"
|
||||
New-Item -Path $pathRulesRoot -Force | Out-Null
|
||||
}
|
||||
|
||||
$rulesCreated = 0
|
||||
|
||||
foreach ($rule in $config.PathRules) {
|
||||
if (-not $rule.Enabled) {
|
||||
Write-Log -Level INFO -Message "Skipping disabled rule: $($rule.Name)" -Module "AdvancedSecurity"
|
||||
continue
|
||||
}
|
||||
|
||||
# Generate GUID for rule
|
||||
$ruleGuid = "{$([guid]::NewGuid().ToString())}"
|
||||
$rulePath = Join-Path $pathRulesRoot $ruleGuid
|
||||
|
||||
Write-Log -Level INFO -Message "Creating SRP rule: $($rule.Name)" -Module "AdvancedSecurity"
|
||||
|
||||
# Create rule key
|
||||
if (-not (Test-Path $rulePath)) {
|
||||
New-Item -Path $rulePath -Force | Out-Null
|
||||
}
|
||||
|
||||
# Set ItemData (path pattern)
|
||||
$existingItemData = Get-ItemProperty -Path $rulePath -Name "ItemData" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existingItemData) {
|
||||
Set-ItemProperty -Path $rulePath -Name "ItemData" -Value $rule.Path -Force | Out-Null
|
||||
}
|
||||
else {
|
||||
New-ItemProperty -Path $rulePath -Name "ItemData" -Value $rule.Path -PropertyType ExpandString -Force | Out-Null
|
||||
}
|
||||
|
||||
# Set Description
|
||||
$existingDescription = Get-ItemProperty -Path $rulePath -Name "Description" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existingDescription) {
|
||||
Set-ItemProperty -Path $rulePath -Name "Description" -Value $rule.Description -Force | Out-Null
|
||||
}
|
||||
else {
|
||||
New-ItemProperty -Path $rulePath -Name "Description" -Value $rule.Description -PropertyType String -Force | Out-Null
|
||||
}
|
||||
|
||||
# Set SaferFlags
|
||||
$existingSaferFlags = Get-ItemProperty -Path $rulePath -Name "SaferFlags" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existingSaferFlags) {
|
||||
Set-ItemProperty -Path $rulePath -Name "SaferFlags" -Value $rule.SaferFlags -Force | Out-Null
|
||||
}
|
||||
else {
|
||||
New-ItemProperty -Path $rulePath -Name "SaferFlags" -Value $rule.SaferFlags -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
|
||||
$rulesCreated++
|
||||
Write-Log -Level SUCCESS -Message "SRP rule created: $($rule.Name) -> $($rule.Path)" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# Step 5: Windows 11 Bug Fix
|
||||
$bugFixPath = $config.RegistryPaths.Win11BugFix
|
||||
|
||||
if (Test-Path $bugFixPath) {
|
||||
Write-Log -Level INFO -Message "Applying Windows 11 SRP bug fix..." -Module "AdvancedSecurity"
|
||||
|
||||
foreach ($keyName in $config.Windows11BugFix.KeysToRemove) {
|
||||
$keyExists = Get-ItemProperty -Path $bugFixPath -Name $keyName -ErrorAction SilentlyContinue
|
||||
|
||||
if ($null -ne $keyExists) {
|
||||
Remove-ItemProperty -Path $bugFixPath -Name $keyName -Force -ErrorAction SilentlyContinue
|
||||
Write-Log -Level SUCCESS -Message "Removed buggy key: $keyName (Windows 11 SRP fix)" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log -Level SUCCESS -Message "SRP configuration completed: $rulesCreated rules created" -Module "AdvancedSecurity"
|
||||
Write-Log -Level INFO -Message "CVE-2025-9491 mitigation active - .lnk files from Temp/Downloads now blocked" -Module "AdvancedSecurity"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "================================================" -ForegroundColor Green
|
||||
Write-Host " SRP RULES CONFIGURED (CVE-2025-9491)" -ForegroundColor Green
|
||||
Write-Host "================================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Zero-Day Protection: Windows LNK RCE (ACTIVELY EXPLOITED)" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
Write-Host "Rules Created: $rulesCreated" -ForegroundColor Cyan
|
||||
Write-Host "Protected Paths:" -ForegroundColor White
|
||||
Write-Host " - Outlook Temp (%LOCALAPPDATA%\Temp\*.lnk)" -ForegroundColor Gray
|
||||
Write-Host " - Downloads (%USERPROFILE%\Downloads\*.lnk)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "Status: ACTIVE (malicious .lnk files blocked)" -ForegroundColor Green
|
||||
Write-Host "CVE-2025-9491: MITIGATED" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to configure SRP rules: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
113
Modules/AdvancedSecurity/Private/Set-WDigestProtection.ps1
Normal file
113
Modules/AdvancedSecurity/Private/Set-WDigestProtection.ps1
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
function Set-WDigestProtection {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disable WDigest credential caching to prevent plaintext password storage in LSASS
|
||||
|
||||
.DESCRIPTION
|
||||
Configures WDigest to NOT store plaintext credentials in LSASS memory.
|
||||
Prevents Mimikatz, Windows Credential Editor (WCE), and other memory-dumping
|
||||
tools from extracting plaintext passwords.
|
||||
|
||||
Status: This setting is DEPRECATED in Windows 11 24H2+ (default is already secure),
|
||||
but we set it explicitly for:
|
||||
- Backwards compatibility with older Windows versions
|
||||
- Defense-in-depth (explicit is better than implicit)
|
||||
- Mixed environments with Win7/8/Server 2008/2012
|
||||
|
||||
No negative impact on modern systems (setting is ignored on Win11 24H2+).
|
||||
|
||||
.EXAMPLE
|
||||
Set-WDigestProtection
|
||||
Sets UseLogonCredential = 0 to prevent plaintext credential storage
|
||||
|
||||
.NOTES
|
||||
Microsoft Security Advisory: KB2871997 (May 2014)
|
||||
Deprecated in Windows 11 24H2 Security Baseline (September 2024)
|
||||
|
||||
Default behavior:
|
||||
- Windows 7/8/Server 2008/2012: UseLogonCredential = 1 (INSECURE!)
|
||||
- Windows 8.1+: UseLogonCredential = 0 (Secure)
|
||||
- Windows 11 24H2+: Setting ignored (hardcoded secure)
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
Write-Log -Level INFO -Message "Configuring WDigest credential protection..." -Module "AdvancedSecurity"
|
||||
|
||||
$wdigestRegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest"
|
||||
|
||||
# Check Windows version for informational logging
|
||||
$osVersion = [System.Environment]::OSVersion.Version
|
||||
$isWin11 = $osVersion.Major -ge 10 -and $osVersion.Build -ge 22000
|
||||
|
||||
if ($isWin11 -and $osVersion.Build -ge 26100) {
|
||||
# Windows 11 24H2+ (Build 26100+)
|
||||
Write-Log -Level INFO -Message "Windows 11 24H2+ detected - WDigest setting is deprecated but will be set for backwards compatibility" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# Backup current setting
|
||||
$currentValue = $null
|
||||
if (Test-Path $wdigestRegPath) {
|
||||
$currentValue = (Get-ItemProperty -Path $wdigestRegPath -Name "UseLogonCredential" -ErrorAction SilentlyContinue).UseLogonCredential
|
||||
}
|
||||
|
||||
$backupData = @{
|
||||
OriginalValue = $currentValue
|
||||
RegistryPath = $wdigestRegPath
|
||||
SettingName = "UseLogonCredential"
|
||||
BackupDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
|
||||
# Register backup
|
||||
$backupJson = $backupData | ConvertTo-Json -Depth 10
|
||||
Register-Backup -Type "WDigest_Settings" -Data $backupJson -Name "WDigest_Protection"
|
||||
|
||||
# Create registry path if it doesn't exist
|
||||
if (-not (Test-Path $wdigestRegPath)) {
|
||||
Write-Log -Level INFO -Message "Creating WDigest registry path..." -Module "AdvancedSecurity"
|
||||
New-Item -Path $wdigestRegPath -Force | Out-Null
|
||||
}
|
||||
|
||||
# Set UseLogonCredential = 0 (Secure - no plaintext in memory)
|
||||
$existing = Get-ItemProperty -Path $wdigestRegPath -Name "UseLogonCredential" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $wdigestRegPath -Name "UseLogonCredential" -Value 0 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $wdigestRegPath -Name "UseLogonCredential" -Value 0 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
|
||||
# Verify
|
||||
$newValue = (Get-ItemProperty -Path $wdigestRegPath -Name "UseLogonCredential").UseLogonCredential
|
||||
|
||||
if ($newValue -eq 0) {
|
||||
if ($currentValue -eq 1) {
|
||||
Write-Log -Level SUCCESS -Message "WDigest credential protection enabled (UseLogonCredential = 0)" -Module "AdvancedSecurity"
|
||||
Write-Log -Level WARNING -Message "Previous value was 1 (INSECURE) - system was vulnerable to plaintext credential dumps!" -Module "AdvancedSecurity"
|
||||
Write-Host ""
|
||||
Write-Host "SECURITY IMPROVEMENT: WDigest was storing plaintext credentials!" -ForegroundColor Yellow
|
||||
Write-Host "This has now been FIXED. Plaintext credential storage is disabled." -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
elseif ($null -eq $currentValue) {
|
||||
Write-Log -Level SUCCESS -Message "WDigest credential protection configured (UseLogonCredential = 0)" -Module "AdvancedSecurity"
|
||||
Write-Log -Level INFO -Message "WDigest setting was not previously configured (default varies by OS version)" -Module "AdvancedSecurity"
|
||||
}
|
||||
else {
|
||||
# currentValue was already 0
|
||||
Write-Log -Level SUCCESS -Message "WDigest credential protection verified (UseLogonCredential = 0)" -Module "AdvancedSecurity"
|
||||
Write-Log -Level INFO -Message "Setting was already correct (no change needed)" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
else {
|
||||
Write-Log -Level ERROR -Message "Failed to verify WDigest setting (expected 0, got $newValue)" -Module "AdvancedSecurity"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to configure WDigest protection: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
110
Modules/AdvancedSecurity/Private/Set-WindowsUpdate.ps1
Normal file
110
Modules/AdvancedSecurity/Private/Set-WindowsUpdate.ps1
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
function Set-WindowsUpdate {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Configures Windows Update using simple GUI-equivalent settings
|
||||
|
||||
.DESCRIPTION
|
||||
Applies 3 simple Windows Update settings that align with the Windows Settings GUI:
|
||||
1. Get the latest updates as soon as they're available (ON, enforced via policy)
|
||||
2. Receive updates for other Microsoft products (ON, user-toggleable)
|
||||
3. Delivery Optimization - Downloads from other devices (OFF, enforced via policy)
|
||||
|
||||
NO forced schedules and NO auto-reboot policies are configured.
|
||||
Installation timing remains user-controlled via the Windows Update GUI; where
|
||||
policies are used, Windows clearly indicates that "Some settings are managed
|
||||
by your organization".
|
||||
|
||||
.PARAMETER DryRun
|
||||
Preview changes without applying them
|
||||
|
||||
.EXAMPLE
|
||||
Set-WindowsUpdate
|
||||
|
||||
.NOTES
|
||||
Author: NexusOne23
|
||||
Version: 2.2.0
|
||||
Requires: Administrator privileges
|
||||
Based on: Windows Settings > Windows Update > Advanced options
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
try {
|
||||
$configPath = Join-Path $PSScriptRoot "..\Config\WindowsUpdate.json"
|
||||
|
||||
if (-not (Test-Path $configPath)) {
|
||||
Write-Log -Level ERROR -Message "WindowsUpdate.json not found: $configPath" -Module "AdvancedSecurity"
|
||||
return $false
|
||||
}
|
||||
|
||||
$config = Get-Content $configPath -Raw | ConvertFrom-Json
|
||||
|
||||
Write-Log -Level INFO -Message "Configuring Windows Update (3 simple GUI settings)..." -Module "AdvancedSecurity"
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Log -Level INFO -Message "[DRYRUN] Would configure 3 Windows Update settings" -Module "AdvancedSecurity"
|
||||
return $true
|
||||
}
|
||||
|
||||
$settingsApplied = 0
|
||||
|
||||
# Loop through all 3 settings from config
|
||||
foreach ($settingKey in $config.Settings.PSObject.Properties.Name) {
|
||||
$setting = $config.Settings.$settingKey
|
||||
$regPath = $setting.RegistryPath
|
||||
|
||||
# Ensure registry path exists
|
||||
if (-not (Test-Path $regPath)) {
|
||||
Write-Log -Level DEBUG -Message "Creating registry path: $regPath" -Module "AdvancedSecurity"
|
||||
New-Item -Path $regPath -Force | Out-Null
|
||||
}
|
||||
|
||||
# Apply each value in this setting
|
||||
foreach ($valueName in $setting.Values.PSObject.Properties.Name) {
|
||||
$valueData = $setting.Values.$valueName
|
||||
|
||||
# Always use New-ItemProperty with -Force to ensure correct type and value
|
||||
# -Force will overwrite existing keys
|
||||
New-ItemProperty -Path $regPath -Name $valueName -Value $valueData.Value -PropertyType DWord -Force | Out-Null
|
||||
|
||||
Write-Log -Level SUCCESS -Message "$($setting.Name): $valueName = $($valueData.Value)" -Module "AdvancedSecurity"
|
||||
$settingsApplied++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log -Level SUCCESS -Message "Windows Update configured: $settingsApplied registry keys set" -Module "AdvancedSecurity"
|
||||
|
||||
# Restart Windows Update service to apply changes immediately
|
||||
Write-Log -Level INFO -Message "Restarting Windows Update service to apply changes..." -Module "AdvancedSecurity"
|
||||
try {
|
||||
Restart-Service -Name wuauserv -Force -ErrorAction Stop | Out-Null
|
||||
Write-Log -Level SUCCESS -Message "Windows Update service restarted successfully" -Module "AdvancedSecurity"
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Could not restart Windows Update service: $($_.Exception.Message)" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "================================================" -ForegroundColor Green
|
||||
Write-Host " Windows Update Configured (3 Settings)" -ForegroundColor Green
|
||||
Write-Host "================================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "[1] Get latest updates immediately: ON (Policy)" -ForegroundColor Gray
|
||||
Write-Host "[2] Microsoft Update (Office, etc.): ON (User can toggle)" -ForegroundColor Gray
|
||||
Write-Host "[3] P2P Delivery Optimization: OFF (Policy)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "Installation timing remains user-controlled (no forced schedules, no auto-reboot policies)." -ForegroundColor White
|
||||
Write-Host "Windows will indicate where settings are managed by policy in the GUI." -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to configure Windows Update: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
200
Modules/AdvancedSecurity/Private/Set-WirelessDisplaySecurity.ps1
Normal file
200
Modules/AdvancedSecurity/Private/Set-WirelessDisplaySecurity.ps1
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
function Set-WirelessDisplaySecurity {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Hardens Wireless Display (Miracast) settings to prevent screen interception attacks.
|
||||
|
||||
.DESCRIPTION
|
||||
Configures Windows Wireless Display policies to prevent attackers from:
|
||||
- Setting up rogue Miracast receivers to capture your screen
|
||||
- Using your PC as an unauthorized display receiver
|
||||
- Intercepting screen content via mDNS spoofing
|
||||
|
||||
Default (always applied): Blocks receiving projections, requires PIN for pairing
|
||||
Full disable: Also blocks sending projections and mDNS discovery
|
||||
|
||||
.PARAMETER DisableCompletely
|
||||
If specified, completely disables all Wireless Display functionality.
|
||||
Default: Only hardens (blocks receiving, requires PIN) but allows sending.
|
||||
|
||||
.EXAMPLE
|
||||
Set-WirelessDisplaySecurity
|
||||
# Applies default hardening (blocks receiving, requires PIN)
|
||||
|
||||
.EXAMPLE
|
||||
Set-WirelessDisplaySecurity -DisableCompletely
|
||||
# Completely disables all Wireless Display functionality
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$DisableCompletely
|
||||
)
|
||||
|
||||
try {
|
||||
Write-Log -Level INFO -Message "Applying Wireless Display security hardening (DisableCompletely: $DisableCompletely)..." -Module "AdvancedSecurity"
|
||||
|
||||
$changesApplied = 0
|
||||
|
||||
# Registry path for Wireless Display policies
|
||||
$connectPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Connect"
|
||||
|
||||
# Create key if it doesn't exist
|
||||
if (-not (Test-Path $connectPath)) {
|
||||
New-Item -Path $connectPath -Force | Out-Null
|
||||
Write-Log -Level INFO -Message "Created registry key: $connectPath" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# ALWAYS APPLIED (Default hardening for all profiles)
|
||||
# ============================================
|
||||
|
||||
# 1. AllowProjectionToPC = 0 (Block receiving projections - prevents rogue receiver attacks)
|
||||
$currentValue = Get-ItemProperty -Path $connectPath -Name "AllowProjectionToPC" -ErrorAction SilentlyContinue
|
||||
if ($null -eq $currentValue -or $currentValue.AllowProjectionToPC -ne 0) {
|
||||
Set-ItemProperty -Path $connectPath -Name "AllowProjectionToPC" -Value 0 -Type DWord -Force
|
||||
Write-Log -Level INFO -Message "Set AllowProjectionToPC = 0 (Block receiving)" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
|
||||
# 2. RequirePinForPairing = 2 (Always require PIN - prevents unauthorized pairing)
|
||||
$currentValue = Get-ItemProperty -Path $connectPath -Name "RequirePinForPairing" -ErrorAction SilentlyContinue
|
||||
if ($null -eq $currentValue -or $currentValue.RequirePinForPairing -ne 2) {
|
||||
Set-ItemProperty -Path $connectPath -Name "RequirePinForPairing" -Value 2 -Type DWord -Force
|
||||
Write-Log -Level INFO -Message "Set RequirePinForPairing = 2 (Always require PIN)" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# OPTIONAL: Complete disable (user choice)
|
||||
# ============================================
|
||||
|
||||
if ($DisableCompletely) {
|
||||
Write-Log -Level INFO -Message "Applying complete Wireless Display disable..." -Module "AdvancedSecurity"
|
||||
|
||||
# 3. AllowProjectionFromPC = 0 (Block sending projections)
|
||||
$currentValue = Get-ItemProperty -Path $connectPath -Name "AllowProjectionFromPC" -ErrorAction SilentlyContinue
|
||||
if ($null -eq $currentValue -or $currentValue.AllowProjectionFromPC -ne 0) {
|
||||
Set-ItemProperty -Path $connectPath -Name "AllowProjectionFromPC" -Value 0 -Type DWord -Force
|
||||
Write-Log -Level INFO -Message "Set AllowProjectionFromPC = 0 (Block sending)" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
|
||||
# 4. AllowMdnsAdvertisement = 0 (Don't advertise as receiver)
|
||||
$currentValue = Get-ItemProperty -Path $connectPath -Name "AllowMdnsAdvertisement" -ErrorAction SilentlyContinue
|
||||
if ($null -eq $currentValue -or $currentValue.AllowMdnsAdvertisement -ne 0) {
|
||||
Set-ItemProperty -Path $connectPath -Name "AllowMdnsAdvertisement" -Value 0 -Type DWord -Force
|
||||
Write-Log -Level INFO -Message "Set AllowMdnsAdvertisement = 0 (No mDNS ads)" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
|
||||
# 5. AllowMdnsDiscovery = 0 (Don't discover receivers via mDNS)
|
||||
$currentValue = Get-ItemProperty -Path $connectPath -Name "AllowMdnsDiscovery" -ErrorAction SilentlyContinue
|
||||
if ($null -eq $currentValue -or $currentValue.AllowMdnsDiscovery -ne 0) {
|
||||
Set-ItemProperty -Path $connectPath -Name "AllowMdnsDiscovery" -Value 0 -Type DWord -Force
|
||||
Write-Log -Level INFO -Message "Set AllowMdnsDiscovery = 0 (No mDNS discovery)" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
|
||||
# 6. AllowProjectionFromPCOverInfrastructure = 0 (Block infrastructure projection)
|
||||
$currentValue = Get-ItemProperty -Path $connectPath -Name "AllowProjectionFromPCOverInfrastructure" -ErrorAction SilentlyContinue
|
||||
if ($null -eq $currentValue -or $currentValue.AllowProjectionFromPCOverInfrastructure -ne 0) {
|
||||
Set-ItemProperty -Path $connectPath -Name "AllowProjectionFromPCOverInfrastructure" -Value 0 -Type DWord -Force
|
||||
Write-Log -Level INFO -Message "Set AllowProjectionFromPCOverInfrastructure = 0" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
|
||||
# 7. AllowProjectionToPCOverInfrastructure = 0 (Block infrastructure receiving)
|
||||
$currentValue = Get-ItemProperty -Path $connectPath -Name "AllowProjectionToPCOverInfrastructure" -ErrorAction SilentlyContinue
|
||||
if ($null -eq $currentValue -or $currentValue.AllowProjectionToPCOverInfrastructure -ne 0) {
|
||||
Set-ItemProperty -Path $connectPath -Name "AllowProjectionToPCOverInfrastructure" -Value 0 -Type DWord -Force
|
||||
Write-Log -Level INFO -Message "Set AllowProjectionToPCOverInfrastructure = 0" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
|
||||
# 8. Block Miracast ports via Windows Firewall (7236, 7250)
|
||||
$firewallRules = @(
|
||||
@{
|
||||
Name = "NoID-Block-Miracast-TCP-7236"
|
||||
DisplayName = "NoID Privacy - Block Miracast TCP 7236"
|
||||
Direction = "Inbound"
|
||||
Protocol = "TCP"
|
||||
LocalPort = 7236
|
||||
},
|
||||
@{
|
||||
Name = "NoID-Block-Miracast-TCP-7250"
|
||||
DisplayName = "NoID Privacy - Block Miracast TCP 7250"
|
||||
Direction = "Inbound"
|
||||
Protocol = "TCP"
|
||||
LocalPort = 7250
|
||||
},
|
||||
@{
|
||||
Name = "NoID-Block-Miracast-UDP-7236"
|
||||
DisplayName = "NoID Privacy - Block Miracast UDP 7236"
|
||||
Direction = "Inbound"
|
||||
Protocol = "UDP"
|
||||
LocalPort = 7236
|
||||
},
|
||||
@{
|
||||
Name = "NoID-Block-Miracast-UDP-7250"
|
||||
DisplayName = "NoID Privacy - Block Miracast UDP 7250"
|
||||
Direction = "Inbound"
|
||||
Protocol = "UDP"
|
||||
LocalPort = 7250
|
||||
}
|
||||
)
|
||||
|
||||
foreach ($rule in $firewallRules) {
|
||||
$existingRule = Get-NetFirewallRule -Name $rule.Name -ErrorAction SilentlyContinue
|
||||
if (-not $existingRule) {
|
||||
New-NetFirewallRule -Name $rule.Name `
|
||||
-DisplayName $rule.DisplayName `
|
||||
-Direction $rule.Direction `
|
||||
-Protocol $rule.Protocol `
|
||||
-LocalPort $rule.LocalPort `
|
||||
-Action Block `
|
||||
-Profile Any `
|
||||
-Enabled True | Out-Null
|
||||
Write-Log -Level INFO -Message "Created firewall rule: $($rule.DisplayName)" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
}
|
||||
|
||||
# 9. Disable WiFi Direct Service (WFDSConMgrSvc) - CRITICAL for complete Miracast block
|
||||
# Registry policies alone don't block WiFi Direct P2P discovery!
|
||||
$wfdService = Get-Service -Name "WFDSConMgrSvc" -ErrorAction SilentlyContinue
|
||||
if ($wfdService) {
|
||||
if ($wfdService.Status -eq 'Running') {
|
||||
Stop-Service -Name "WFDSConMgrSvc" -Force -ErrorAction SilentlyContinue
|
||||
Write-Log -Level INFO -Message "Stopped WiFi Direct Service (WFDSConMgrSvc)" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
if ($wfdService.StartType -ne 'Disabled') {
|
||||
Set-Service -Name "WFDSConMgrSvc" -StartupType Disabled -ErrorAction SilentlyContinue
|
||||
Write-Log -Level INFO -Message "Disabled WiFi Direct Service (WFDSConMgrSvc) - survives reboot" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
}
|
||||
|
||||
# 10. Disable WiFi Direct Virtual Adapters (immediate effect)
|
||||
$wfdAdapters = Get-NetAdapter -InterfaceDescription "Microsoft Wi-Fi Direct Virtual*" -IncludeHidden -ErrorAction SilentlyContinue
|
||||
if ($wfdAdapters) {
|
||||
$wfdAdapters | Where-Object { $_.Status -ne 'Disabled' } | ForEach-Object {
|
||||
Disable-NetAdapter -Name $_.Name -Confirm:$false -ErrorAction SilentlyContinue
|
||||
Write-Log -Level INFO -Message "Disabled WiFi Direct adapter: $($_.Name)" -Module "AdvancedSecurity"
|
||||
$changesApplied++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($changesApplied -eq 0) {
|
||||
Write-Log -Level SUCCESS -Message "Wireless Display security already configured (no changes needed)" -Module "AdvancedSecurity"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level SUCCESS -Message "Wireless Display security applied ($changesApplied changes)" -Module "AdvancedSecurity"
|
||||
}
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to apply Wireless Display security: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
216
Modules/AdvancedSecurity/Private/Stop-RiskyServices.ps1
Normal file
216
Modules/AdvancedSecurity/Private/Stop-RiskyServices.ps1
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
function Stop-RiskyServices {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Stop and disable risky network services
|
||||
|
||||
.DESCRIPTION
|
||||
Stops and disables network services that pose security risks:
|
||||
|
||||
- SSDPSRV (SSDP Discovery) - Port 1900 UDP
|
||||
- upnphost (UPnP Device Host) - Port 2869 TCP
|
||||
- lmhosts (TCP/IP NetBIOS Helper) - Port 139 TCP
|
||||
|
||||
Defense in Depth: Firewall blocks external access, but services
|
||||
still run and listen locally. Stopping services completely closes ports.
|
||||
|
||||
Service Dependencies:
|
||||
upnphost depends on SSDPSRV, so upnphost must be stopped FIRST.
|
||||
|
||||
.EXAMPLE
|
||||
Stop-RiskyServices
|
||||
Stops all risky network services
|
||||
|
||||
.NOTES
|
||||
Impact:
|
||||
- Smart home device auto-discovery may not work
|
||||
- DLNA/casting features may require manual configuration
|
||||
- NetBIOS name resolution disabled (already disabled via registry)
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
Write-Log -Level INFO -Message "Stopping risky network services..." -Module "AdvancedSecurity"
|
||||
|
||||
$services = @(
|
||||
@{
|
||||
Name = "upnphost"
|
||||
DisplayName = "UPnP Device Host"
|
||||
Port = 2869
|
||||
Protocol = "TCP"
|
||||
Risk = "MEDIUM"
|
||||
Impact = "DLNA/casting features may require manual configuration"
|
||||
},
|
||||
@{
|
||||
Name = "SSDPSRV"
|
||||
DisplayName = "SSDP Discovery"
|
||||
Port = 1900
|
||||
Protocol = "UDP"
|
||||
Risk = "MEDIUM"
|
||||
Impact = "Smart home device auto-discovery may not work"
|
||||
},
|
||||
@{
|
||||
Name = "lmhosts"
|
||||
DisplayName = "TCP/IP NetBIOS Helper"
|
||||
Port = 139
|
||||
Protocol = "TCP"
|
||||
Risk = "MEDIUM"
|
||||
Impact = "NetBIOS name resolution disabled"
|
||||
}
|
||||
# Note: Computer Browser (Browser) service is DEPRECATED in Win10/11
|
||||
# It's tied to SMB1 which is not installed by default
|
||||
# Removing from list to avoid errors on modern systems
|
||||
)
|
||||
|
||||
# Backup service states
|
||||
Write-Log -Level INFO -Message "Backing up service states..." -Module "AdvancedSecurity"
|
||||
|
||||
$serviceBackup = @{}
|
||||
foreach ($svc in $services) {
|
||||
$service = Get-Service -Name $svc.Name -ErrorAction SilentlyContinue
|
||||
if ($service) {
|
||||
$serviceBackup[$svc.Name] = @{
|
||||
Status = $service.Status.ToString()
|
||||
StartType = $service.StartType.ToString()
|
||||
DisplayName = $service.DisplayName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$backupData = @{
|
||||
Services = $serviceBackup
|
||||
BackupDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
}
|
||||
$backupJson = $backupData | ConvertTo-Json -Depth 10
|
||||
Register-Backup -Type "Services_State" -Data $backupJson -Name "RiskyServices"
|
||||
|
||||
Write-Log -Level SUCCESS -Message "Backed up state of $($serviceBackup.Count) services" -Module "AdvancedSecurity"
|
||||
|
||||
# Stop and disable services
|
||||
$stoppedCount = 0
|
||||
$errors = @()
|
||||
|
||||
# CRITICAL: Stop upnphost FIRST (it depends on SSDPSRV)
|
||||
foreach ($svc in $services) {
|
||||
Write-Log -Level INFO -Message "Processing service: $($svc.DisplayName) ($($svc.Name))..." -Module "AdvancedSecurity"
|
||||
|
||||
$service = Get-Service -Name $svc.Name -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not $service) {
|
||||
Write-Log -Level INFO -Message "Service $($svc.Name) not found (may not be installed)" -Module "AdvancedSecurity"
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
# Stop service if running
|
||||
if ($service.Status -eq 'Running') {
|
||||
Write-Log -Level INFO -Message "Stopping $($svc.Name)..." -Module "AdvancedSecurity"
|
||||
Stop-Service -Name $svc.Name -Force -ErrorAction Stop
|
||||
Write-Log -Level SUCCESS -Message "Stopped $($svc.Name)" -Module "AdvancedSecurity"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level INFO -Message "$($svc.Name) already stopped" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# Disable service
|
||||
Write-Log -Level INFO -Message "Disabling $($svc.Name)..." -Module "AdvancedSecurity"
|
||||
Set-Service -Name $svc.Name -StartupType Disabled -ErrorAction Stop
|
||||
Write-Log -Level SUCCESS -Message "Disabled $($svc.Name) (StartupType = Disabled)" -Module "AdvancedSecurity"
|
||||
|
||||
$stoppedCount++
|
||||
}
|
||||
catch {
|
||||
$errors += "$($svc.Name): $_"
|
||||
Write-Log -Level WARNING -Message "Failed to stop/disable $($svc.Name): $_" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
|
||||
# Verify ports are closed
|
||||
Write-Log -Level INFO -Message "Verifying ports are closed..." -Module "AdvancedSecurity"
|
||||
|
||||
Start-Sleep -Seconds 2 # Give services time to fully stop
|
||||
|
||||
$portsClosed = @()
|
||||
$portsStillOpen = @()
|
||||
|
||||
# Check TCP ports
|
||||
foreach ($port in @(139, 2869)) {
|
||||
$listener = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue
|
||||
if (-not $listener) {
|
||||
$portsClosed += "TCP $port"
|
||||
Write-Log -Level SUCCESS -Message "Port TCP $port is CLOSED" -Module "AdvancedSecurity"
|
||||
}
|
||||
else {
|
||||
$portsStillOpen += "TCP $port"
|
||||
Write-Log -Level WARNING -Message "Port TCP $port is still LISTENING!" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
|
||||
# Check UDP port 1900
|
||||
$udpListener = Get-NetUDPEndpoint -LocalPort 1900 -ErrorAction SilentlyContinue
|
||||
if (-not $udpListener) {
|
||||
$portsClosed += "UDP 1900"
|
||||
Write-Log -Level SUCCESS -Message "Port UDP 1900 is CLOSED" -Module "AdvancedSecurity"
|
||||
}
|
||||
else {
|
||||
$portsStillOpen += "UDP 1900"
|
||||
Write-Log -Level WARNING -Message "Port UDP 1900 is still LISTENING locally. SSDP service is disabled and blocked by firewall; this listener is a known Windows behavior and not reachable from external networks." -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# Summary
|
||||
Write-Host ""
|
||||
Write-Host "================================================" -ForegroundColor Green
|
||||
Write-Host " RISKY SERVICES STOPPED" -ForegroundColor Green
|
||||
Write-Host "================================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Services stopped: $stoppedCount" -ForegroundColor White
|
||||
foreach ($svc in $services) {
|
||||
$service = Get-Service -Name $svc.Name -ErrorAction SilentlyContinue
|
||||
if ($service) {
|
||||
$status = if ($service.Status -eq 'Stopped') { "STOPPED" } else { $service.Status }
|
||||
$startType = $service.StartType
|
||||
Write-Host " $($svc.DisplayName): $status (StartType: $startType)" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
Write-Host ""
|
||||
Write-Host "Ports closed: $($portsClosed.Count)" -ForegroundColor White
|
||||
foreach ($port in $portsClosed) {
|
||||
Write-Host " $port" -ForegroundColor Green
|
||||
}
|
||||
|
||||
if ($portsStillOpen.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "Ports still open: $($portsStillOpen.Count)" -ForegroundColor Yellow
|
||||
foreach ($port in $portsStillOpen) {
|
||||
Write-Host " $port" -ForegroundColor Yellow
|
||||
}
|
||||
if ($portsStillOpen -contains "UDP 1900") {
|
||||
Write-Host ""
|
||||
Write-Host "Note: UDP 1900 may still show a local listener, but SSDP is disabled and blocked by firewall. This is a known Windows behavior and not remotely reachable." -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "Errors: $($errors.Count)" -ForegroundColor Red
|
||||
foreach ($errorMsg in $errors) {
|
||||
Write-Host " $errorMsg" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
if ($errors.Count -eq 0) {
|
||||
Write-Log -Level SUCCESS -Message "All risky services stopped and disabled successfully" -Module "AdvancedSecurity"
|
||||
return $true
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "Completed with $($errors.Count) errors" -Module "AdvancedSecurity"
|
||||
return $true # Partial success
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to stop risky services: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
100
Modules/AdvancedSecurity/Private/Test-AdminShares.ps1
Normal file
100
Modules/AdvancedSecurity/Private/Test-AdminShares.ps1
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
function Test-AdminShares {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test administrative shares compliance
|
||||
|
||||
.DESCRIPTION
|
||||
Checks if administrative shares (C$, ADMIN$, etc.) are disabled
|
||||
|
||||
.EXAMPLE
|
||||
Test-AdminShares
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Feature = "Admin Shares"
|
||||
Status = "Unknown"
|
||||
Details = @()
|
||||
AutoShareWks = $null
|
||||
AutoShareServer = $null
|
||||
ActiveShares = @()
|
||||
Compliant = $false
|
||||
}
|
||||
|
||||
# Check registry settings
|
||||
if (Test-Path $regPath) {
|
||||
$result.AutoShareWks = (Get-ItemProperty -Path $regPath -Name "AutoShareWks" -ErrorAction SilentlyContinue).AutoShareWks
|
||||
$result.AutoShareServer = (Get-ItemProperty -Path $regPath -Name "AutoShareServer" -ErrorAction SilentlyContinue).AutoShareServer
|
||||
|
||||
if ($result.AutoShareWks -eq 0 -and $result.AutoShareServer -eq 0) {
|
||||
$result.Details += "Registry: AutoShareWks = 0, AutoShareServer = 0 (Disabled)"
|
||||
}
|
||||
else {
|
||||
$result.Details += "Registry: AutoShareWks = $($result.AutoShareWks), AutoShareServer = $($result.AutoShareServer)"
|
||||
}
|
||||
}
|
||||
|
||||
# Check for active admin shares (requires LanmanServer service)
|
||||
$serverService = Get-Service -Name "LanmanServer" -ErrorAction SilentlyContinue
|
||||
if (-not $serverService -or $serverService.Status -ne 'Running') {
|
||||
# Server service is stopped/disabled - admin shares are effectively disabled
|
||||
$result.Details += "LanmanServer service is not running (admin shares cannot exist)"
|
||||
$adminShares = @()
|
||||
}
|
||||
else {
|
||||
try {
|
||||
$adminShares = Get-SmbShare | Where-Object { $_.Name -match '^[A-Z]\$$|^ADMIN\$$' }
|
||||
}
|
||||
catch {
|
||||
# Get-SmbShare failed - treat as no shares
|
||||
$result.Details += "Could not query SMB shares: $($_.Exception.Message)"
|
||||
$adminShares = @()
|
||||
}
|
||||
}
|
||||
$result.ActiveShares = $adminShares | Select-Object -ExpandProperty Name
|
||||
|
||||
if ($adminShares.Count -eq 0) {
|
||||
$result.Details += "No administrative shares found (C$, ADMIN$ removed)"
|
||||
|
||||
if ($result.AutoShareWks -eq 0 -and $result.AutoShareServer -eq 0) {
|
||||
$result.Status = "Secure"
|
||||
$result.Compliant = $true
|
||||
}
|
||||
else {
|
||||
$result.Status = "Partially Secure"
|
||||
$result.Compliant = $false
|
||||
$result.Details += "WARNING: Shares removed but AutoShare registry not set (will recreate on reboot!)"
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Shares are present, check if Registry is configured to disable them
|
||||
if ($result.AutoShareWks -eq 0 -and $result.AutoShareServer -eq 0) {
|
||||
# Config is correct, just needs a reboot
|
||||
$result.Status = "Pending Reboot"
|
||||
$result.Compliant = $true
|
||||
$result.Details += "Active admin shares: $($adminShares.Name -join ', ') (Will be removed after reboot)"
|
||||
}
|
||||
else {
|
||||
# Config is NOT correct
|
||||
$result.Status = "Insecure"
|
||||
$result.Compliant = $false
|
||||
$result.Details += "Active admin shares: $($adminShares.Name -join ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to test admin shares: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return [PSCustomObject]@{
|
||||
Feature = "Admin Shares"
|
||||
Status = "Error"
|
||||
Details = @("Failed to test: $_")
|
||||
Compliant = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
function Test-DiscoveryProtocolsSecurity {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Tests WS-Discovery and mDNS hardening state.
|
||||
|
||||
.DESCRIPTION
|
||||
Verifies that the following conditions are met:
|
||||
- OS-level mDNS resolver disabled (EnableMDNS = 0)
|
||||
- FDResPub and fdPHost services disabled and not running
|
||||
- NoID firewall BLOCK rules for WS-Discovery and mDNS exist and are enabled
|
||||
|
||||
Returns a PSCustomObject with detailed fields and an overall Compliant flag.
|
||||
|
||||
.EXAMPLE
|
||||
Test-DiscoveryProtocolsSecurity
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
EnableMDNS = $null
|
||||
FDResPubDisabled = $false
|
||||
FdPHostDisabled = $false
|
||||
FirewallRulesPresent = $false
|
||||
FirewallRulesEnabled = $false
|
||||
Udp3702ListenersClosed = $null
|
||||
Udp5353ListenersClosed = $null
|
||||
Tcp5357ListenersClosed = $null
|
||||
Tcp5358ListenersClosed = $null
|
||||
Compliant = $false
|
||||
}
|
||||
|
||||
try {
|
||||
# 1) Check mDNS registry flag
|
||||
$dnsParamsPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters"
|
||||
if (Test-Path $dnsParamsPath) {
|
||||
$props = Get-ItemProperty -Path $dnsParamsPath -ErrorAction SilentlyContinue
|
||||
if ($props.PSObject.Properties.Name -contains 'EnableMDNS') {
|
||||
$result.EnableMDNS = $props.EnableMDNS
|
||||
}
|
||||
}
|
||||
|
||||
# 2) Check services
|
||||
$fdResPub = Get-Service -Name "FDResPub" -ErrorAction SilentlyContinue
|
||||
if ($fdResPub) {
|
||||
$result.FDResPubDisabled = ($fdResPub.StartType -eq 'Disabled' -and $fdResPub.Status -ne 'Running')
|
||||
}
|
||||
|
||||
$fdPHost = Get-Service -Name "fdPHost" -ErrorAction SilentlyContinue
|
||||
if ($fdPHost) {
|
||||
$result.FdPHostDisabled = ($fdPHost.StartType -eq 'Disabled' -and $fdPHost.Status -ne 'Running')
|
||||
}
|
||||
|
||||
# 3) Check firewall rules
|
||||
$ruleNames = @(
|
||||
"NoID-Block-WSD-UDP-3702",
|
||||
"NoID-Block-WSD-TCP-5357",
|
||||
"NoID-Block-WSD-TCP-5358",
|
||||
"NoID-Block-mDNS-UDP-5353"
|
||||
)
|
||||
|
||||
$rules = @()
|
||||
foreach ($name in $ruleNames) {
|
||||
$r = Get-NetFirewallRule -Name $name -ErrorAction SilentlyContinue
|
||||
if ($r) {
|
||||
$rules += $r
|
||||
}
|
||||
}
|
||||
|
||||
if ($rules.Count -gt 0) {
|
||||
$result.FirewallRulesPresent = ($rules.Count -eq $ruleNames.Count)
|
||||
$result.FirewallRulesEnabled = ($rules | Where-Object { $_.Enabled -eq 'True' -and $_.Action -eq 'Block' }).Count -eq $ruleNames.Count
|
||||
}
|
||||
|
||||
# 4) Optional: check that ports are not listening
|
||||
try {
|
||||
$udp3702 = Get-NetUDPEndpoint -LocalPort 3702 -ErrorAction SilentlyContinue
|
||||
$result.Udp3702ListenersClosed = (-not $udp3702)
|
||||
}
|
||||
catch {
|
||||
$result.Udp3702ListenersClosed = $null
|
||||
}
|
||||
|
||||
try {
|
||||
$udp5353 = Get-NetUDPEndpoint -LocalPort 5353 -ErrorAction SilentlyContinue
|
||||
$result.Udp5353ListenersClosed = (-not $udp5353)
|
||||
}
|
||||
catch {
|
||||
$result.Udp5353ListenersClosed = $null
|
||||
}
|
||||
|
||||
try {
|
||||
$tcp5357 = Get-NetTCPConnection -LocalPort 5357 -State Listen -ErrorAction SilentlyContinue
|
||||
$result.Tcp5357ListenersClosed = (-not $tcp5357)
|
||||
}
|
||||
catch {
|
||||
$result.Tcp5357ListenersClosed = $null
|
||||
}
|
||||
|
||||
try {
|
||||
$tcp5358 = Get-NetTCPConnection -LocalPort 5358 -State Listen -ErrorAction SilentlyContinue
|
||||
$result.Tcp5358ListenersClosed = (-not $tcp5358)
|
||||
}
|
||||
catch {
|
||||
$result.Tcp5358ListenersClosed = $null
|
||||
}
|
||||
|
||||
# Overall compliance: mDNS disabled, services disabled, firewall rules present+enabled
|
||||
$mdnsOk = ($result.EnableMDNS -eq 0)
|
||||
$servicesOk = $result.FDResPubDisabled -and $result.FdPHostDisabled
|
||||
$firewallOk = $result.FirewallRulesPresent -and $result.FirewallRulesEnabled
|
||||
|
||||
$result.Compliant = $mdnsOk -and $servicesOk -and $firewallOk
|
||||
|
||||
return $result
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to test discovery protocol security (WS-Discovery/mDNS): $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $result
|
||||
}
|
||||
}
|
||||
57
Modules/AdvancedSecurity/Private/Test-FingerProtocol.ps1
Normal file
57
Modules/AdvancedSecurity/Private/Test-FingerProtocol.ps1
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
function Test-FingerProtocol {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test if Finger Protocol (TCP 79) is blocked
|
||||
|
||||
.DESCRIPTION
|
||||
Verifies that the Windows Firewall rule blocking outbound TCP port 79
|
||||
is present and enabled. This prevents ClickFix malware attacks that
|
||||
abuse finger.exe to retrieve commands from C2 servers.
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with compliance result
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
$ruleName = "NoID Privacy - Block Finger Protocol (Port 79)"
|
||||
|
||||
# Check if firewall rule exists and is enabled
|
||||
$rule = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue
|
||||
|
||||
if ($rule) {
|
||||
$isEnabled = $rule.Enabled -eq 'True'
|
||||
$isBlocking = $rule.Action -eq 'Block'
|
||||
$isOutbound = $rule.Direction -eq 'Outbound'
|
||||
|
||||
$compliant = $isEnabled -and $isBlocking -and $isOutbound
|
||||
|
||||
if ($compliant) {
|
||||
$status = "Finger Protocol blocked (TCP 79 outbound)"
|
||||
}
|
||||
else {
|
||||
$status = "Rule exists but misconfigured (Enabled: $isEnabled, Block: $isBlocking, Outbound: $isOutbound)"
|
||||
}
|
||||
}
|
||||
else {
|
||||
$compliant = $false
|
||||
$status = "Firewall rule not found"
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
Feature = "Finger Protocol Block"
|
||||
Compliant = $compliant
|
||||
Status = $status
|
||||
Details = if ($rule) { "Rule: $ruleName" } else { "ClickFix malware protection not active" }
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return [PSCustomObject]@{
|
||||
Feature = "Finger Protocol Block"
|
||||
Compliant = $false
|
||||
Status = "Error checking: $($_.Exception.Message)"
|
||||
Details = $null
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Modules/AdvancedSecurity/Private/Test-FirewallShieldsUp.ps1
Normal file
39
Modules/AdvancedSecurity/Private/Test-FirewallShieldsUp.ps1
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
function Test-FirewallShieldsUp {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test if Firewall Shields Up mode is enabled
|
||||
|
||||
.DESCRIPTION
|
||||
Checks DoNotAllowExceptions value for PublicProfile firewall.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\PublicProfile"
|
||||
$valueName = "DoNotAllowExceptions"
|
||||
|
||||
try {
|
||||
$value = Get-ItemProperty -Path $regPath -Name $valueName -ErrorAction SilentlyContinue
|
||||
|
||||
if ($null -eq $value -or $value.$valueName -ne 1) {
|
||||
return @{
|
||||
Pass = $false
|
||||
Message = "Shields Up NOT enabled (Public network allows configured exceptions)"
|
||||
CurrentValue = if ($null -eq $value) { "Not Set" } else { $value.$valueName }
|
||||
}
|
||||
}
|
||||
|
||||
return @{
|
||||
Pass = $true
|
||||
Message = "Shields Up ENABLED (Public network blocks ALL incoming)"
|
||||
CurrentValue = 1
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return @{
|
||||
Pass = $false
|
||||
Message = "Error checking Shields Up: $_"
|
||||
CurrentValue = "Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Modules/AdvancedSecurity/Private/Test-IPv6Security.ps1
Normal file
61
Modules/AdvancedSecurity/Private/Test-IPv6Security.ps1
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
function Test-IPv6Security {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test IPv6 disable status (mitm6 attack mitigation)
|
||||
|
||||
.DESCRIPTION
|
||||
Checks if IPv6 is completely disabled via DisabledComponents registry value.
|
||||
This is an OPTIONAL setting only available in Maximum profile.
|
||||
|
||||
DisabledComponents = 0xFF (255) means IPv6 is completely disabled.
|
||||
|
||||
.EXAMPLE
|
||||
Test-IPv6Security
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters"
|
||||
$value = Get-ItemProperty -Path $regPath -Name "DisabledComponents" -ErrorAction SilentlyContinue
|
||||
|
||||
if ($value -and $value.DisabledComponents -eq 255) {
|
||||
return [PSCustomObject]@{
|
||||
Feature = "IPv6 Disable (mitm6 mitigation)"
|
||||
Pass = $true
|
||||
Compliant = $true
|
||||
Message = "IPv6 DISABLED (DisabledComponents = 0xFF) - mitm6 protected"
|
||||
Details = "IPv6 completely disabled - DHCPv6 spoofing attacks blocked"
|
||||
}
|
||||
}
|
||||
elseif ($value -and $value.DisabledComponents -gt 0) {
|
||||
return [PSCustomObject]@{
|
||||
Feature = "IPv6 Disable (mitm6 mitigation)"
|
||||
Pass = $true
|
||||
Compliant = $true
|
||||
Message = "IPv6 PARTIALLY disabled (DisabledComponents = $($value.DisabledComponents))"
|
||||
Details = "IPv6 partially disabled - some mitm6 protection"
|
||||
}
|
||||
}
|
||||
else {
|
||||
# IPv6 is enabled - this is OPTIONAL, so still "pass" but note it's not configured
|
||||
return [PSCustomObject]@{
|
||||
Feature = "IPv6 Disable (mitm6 mitigation)"
|
||||
Pass = $true # Optional feature - not a failure
|
||||
Compliant = $true # Optional feature
|
||||
Message = "IPv6 ENABLED (Optional - not configured)"
|
||||
Details = "IPv6 enabled (default) - WPAD disabled provides partial mitm6 protection"
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to test IPv6 security: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return [PSCustomObject]@{
|
||||
Feature = "IPv6 Disable (mitm6 mitigation)"
|
||||
Pass = $true # Don't fail on error for optional feature
|
||||
Compliant = $true
|
||||
Message = "Error checking IPv6 status"
|
||||
Details = "Could not determine IPv6 status: $_"
|
||||
}
|
||||
}
|
||||
}
|
||||
75
Modules/AdvancedSecurity/Private/Test-LegacyTLS.ps1
Normal file
75
Modules/AdvancedSecurity/Private/Test-LegacyTLS.ps1
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
function Test-LegacyTLS {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test Legacy TLS configuration compliance
|
||||
|
||||
.DESCRIPTION
|
||||
Verifies that TLS 1.0 and TLS 1.1 are disabled for both Client and Server.
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with compliance details
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
$result = [PSCustomObject]@{
|
||||
Feature = "Legacy TLS (1.0/1.1)"
|
||||
Status = "Unknown"
|
||||
Details = @()
|
||||
Compliant = $true
|
||||
}
|
||||
|
||||
$tlsVersions = @("TLS 1.0", "TLS 1.1")
|
||||
$components = @("Server", "Client")
|
||||
$nonCompliantCount = 0
|
||||
|
||||
foreach ($version in $tlsVersions) {
|
||||
foreach ($component in $components) {
|
||||
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\$version\$component"
|
||||
|
||||
if (Test-Path $regPath) {
|
||||
$enabled = (Get-ItemProperty -Path $regPath -Name "Enabled" -ErrorAction SilentlyContinue).Enabled
|
||||
$disabledByDefault = (Get-ItemProperty -Path $regPath -Name "DisabledByDefault" -ErrorAction SilentlyContinue).DisabledByDefault
|
||||
|
||||
if ($enabled -eq 0) {
|
||||
# Compliant
|
||||
}
|
||||
elseif ($null -eq $enabled -and $disabledByDefault -eq 1) {
|
||||
# Compliant (implicitly disabled)
|
||||
}
|
||||
else {
|
||||
$result.Details += "$version $component is NOT disabled (Enabled=$enabled)"
|
||||
$nonCompliantCount++
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Key missing usually means default (Enabled on old OS, Disabled on very new OS)
|
||||
# For hardening, we expect explicit disable keys
|
||||
$result.Details += "$version $component registry keys missing"
|
||||
$nonCompliantCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($nonCompliantCount -eq 0) {
|
||||
$result.Status = "Secure (Disabled)"
|
||||
$result.Compliant = $true
|
||||
}
|
||||
else {
|
||||
$result.Status = "Insecure ($nonCompliantCount issues)"
|
||||
$result.Compliant = $false
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to test Legacy TLS: $_" -Module "AdvancedSecurity"
|
||||
return [PSCustomObject]@{
|
||||
Feature = "Legacy TLS (1.0/1.1)"
|
||||
Status = "Error"
|
||||
Details = @("Failed to test: $_")
|
||||
Compliant = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Modules/AdvancedSecurity/Private/Test-PowerShellV2.ps1
Normal file
61
Modules/AdvancedSecurity/Private/Test-PowerShellV2.ps1
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
function Test-PowerShellV2 {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test PowerShell v2 status
|
||||
|
||||
.DESCRIPTION
|
||||
Verifies that the PowerShell v2 feature is disabled or not present.
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with compliance details
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
$result = [PSCustomObject]@{
|
||||
Feature = "PowerShell v2 (Downgrade Attack)"
|
||||
Status = "Unknown"
|
||||
Details = @()
|
||||
Compliant = $true
|
||||
}
|
||||
|
||||
$psv2Feature = $null
|
||||
try {
|
||||
$psv2Feature = Get-WindowsOptionalFeature -Online -FeatureName "MicrosoftWindowsPowerShellV2Root" -ErrorAction SilentlyContinue
|
||||
}
|
||||
catch {
|
||||
$psv2Feature = $null
|
||||
}
|
||||
|
||||
if (-not $psv2Feature) {
|
||||
# Feature not present on OS - Secure by default
|
||||
$result.Status = "Secure (Not Present)"
|
||||
$result.Compliant = $true
|
||||
$result.Details += "Feature 'MicrosoftWindowsPowerShellV2Root' not found on this OS"
|
||||
}
|
||||
elseif ($psv2Feature.State -ne 'Enabled') {
|
||||
# Feature present but disabled - Secure
|
||||
$result.Status = "Secure (Disabled)"
|
||||
$result.Compliant = $true
|
||||
$result.Details += "Feature state: $($psv2Feature.State)"
|
||||
}
|
||||
else {
|
||||
# Feature Enabled - Insecure
|
||||
$result.Status = "Insecure (Enabled)"
|
||||
$result.Compliant = $false
|
||||
$result.Details += "PowerShell v2 is enabled (allows downgrade attacks)"
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to test PowerShell v2: $_" -Module "AdvancedSecurity"
|
||||
return [PSCustomObject]@{
|
||||
Feature = "PowerShell v2"
|
||||
Status = "Error"
|
||||
Details = @("Failed to test: $_")
|
||||
Compliant = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
96
Modules/AdvancedSecurity/Private/Test-RdpSecurity.ps1
Normal file
96
Modules/AdvancedSecurity/Private/Test-RdpSecurity.ps1
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
function Test-RdpSecurity {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test RDP security hardening compliance
|
||||
|
||||
.DESCRIPTION
|
||||
Verifies that RDP is properly hardened:
|
||||
- NLA (Network Level Authentication) is enforced
|
||||
- SSL/TLS encryption is required
|
||||
- Optionally checks if RDP is completely disabled
|
||||
|
||||
.EXAMPLE
|
||||
Test-RdpSecurity
|
||||
Returns compliance status for RDP hardening
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with compliance details
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
$rdpRegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp"
|
||||
$rdpServerPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Feature = "RDP Security"
|
||||
Status = "Unknown"
|
||||
Details = @()
|
||||
NLA_Enabled = $false
|
||||
SSL_TLS_Enabled = $false
|
||||
RDP_Disabled = $false
|
||||
Compliant = $false
|
||||
}
|
||||
|
||||
# Check NLA
|
||||
if (Test-Path $rdpRegPath) {
|
||||
$userAuth = (Get-ItemProperty -Path $rdpRegPath -Name "UserAuthentication" -ErrorAction SilentlyContinue).UserAuthentication
|
||||
$secLayer = (Get-ItemProperty -Path $rdpRegPath -Name "SecurityLayer" -ErrorAction SilentlyContinue).SecurityLayer
|
||||
|
||||
if ($userAuth -eq 1) {
|
||||
$result.NLA_Enabled = $true
|
||||
$result.Details += "NLA enforced (UserAuthentication = 1)"
|
||||
}
|
||||
else {
|
||||
$result.Details += "NLA NOT enforced (UserAuthentication = $userAuth)"
|
||||
}
|
||||
|
||||
if ($secLayer -eq 2) {
|
||||
$result.SSL_TLS_Enabled = $true
|
||||
$result.Details += "SSL/TLS enforced (SecurityLayer = 2)"
|
||||
}
|
||||
else {
|
||||
$result.Details += "SSL/TLS NOT enforced (SecurityLayer = $secLayer)"
|
||||
}
|
||||
}
|
||||
else {
|
||||
$result.Details += "RDP registry path not found"
|
||||
}
|
||||
|
||||
# Check if RDP is completely disabled
|
||||
if (Test-Path $rdpServerPath) {
|
||||
$rdpDisabled = (Get-ItemProperty -Path $rdpServerPath -Name "fDenyTSConnections" -ErrorAction SilentlyContinue).fDenyTSConnections
|
||||
|
||||
if ($rdpDisabled -eq 1) {
|
||||
$result.RDP_Disabled = $true
|
||||
$result.Details += "RDP completely disabled (fDenyTSConnections = 1)"
|
||||
}
|
||||
}
|
||||
|
||||
# Determine compliance
|
||||
if ($result.RDP_Disabled) {
|
||||
$result.Status = "Secure (RDP Disabled)"
|
||||
$result.Compliant = $true
|
||||
}
|
||||
elseif ($result.NLA_Enabled -and $result.SSL_TLS_Enabled) {
|
||||
$result.Status = "Secure (NLA + SSL/TLS)"
|
||||
$result.Compliant = $true
|
||||
}
|
||||
else {
|
||||
$result.Status = "Insecure"
|
||||
$result.Compliant = $false
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to test RDP security: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return [PSCustomObject]@{
|
||||
Feature = "RDP Security"
|
||||
Status = "Error"
|
||||
Details = @("Failed to test: $_")
|
||||
Compliant = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
134
Modules/AdvancedSecurity/Private/Test-RiskyPorts.ps1
Normal file
134
Modules/AdvancedSecurity/Private/Test-RiskyPorts.ps1
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
function Test-RiskyPorts {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test risky firewall ports compliance
|
||||
|
||||
.DESCRIPTION
|
||||
Checks if risky firewall ports (LLMNR, NetBIOS, UPnP/SSDP) are closed
|
||||
|
||||
.EXAMPLE
|
||||
Test-RiskyPorts
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
$result = [PSCustomObject]@{
|
||||
Feature = "Risky Firewall Ports"
|
||||
Status = "Unknown"
|
||||
Details = @()
|
||||
OpenPorts = @()
|
||||
DisabledRules = 0
|
||||
EnabledRules = 0
|
||||
Compliant = $false
|
||||
}
|
||||
|
||||
$riskyPorts = @(5355, 137, 138, 139, 1900, 2869)
|
||||
|
||||
# Detect NoID SSDP firewall block rule for UDP 1900
|
||||
$ssdpRuleName = "NoID Privacy - Block SSDP (UDP 1900)"
|
||||
$ssdpBlockRule = Get-NetFirewallRule -DisplayName $ssdpRuleName -ErrorAction SilentlyContinue
|
||||
$ssdpBlockActive = $false
|
||||
if ($ssdpBlockRule -and $ssdpBlockRule.Enabled -eq 'True' -and $ssdpBlockRule.Action -eq 'Block') {
|
||||
$ssdpBlockActive = $true
|
||||
}
|
||||
|
||||
# PERFORMANCE FIX: Batch query instead of per-rule queries
|
||||
# Old approach: Get-NetFirewallRule | ForEach { Get-NetFirewallPortFilter } = 300 queries × 200ms = 60s!
|
||||
# New approach: Get all port filters once, then filter = 2-3s total
|
||||
|
||||
# Get all inbound firewall rules (pre-filter by direction)
|
||||
$inboundRules = Get-NetFirewallRule -Direction Inbound -ErrorAction SilentlyContinue
|
||||
|
||||
# Get all port filters in one batch query
|
||||
$allPortFilters = @{}
|
||||
Get-NetFirewallPortFilter -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
$allPortFilters[$_.InstanceID] = $_
|
||||
}
|
||||
|
||||
# Now filter rules by risky ports (fast lookup)
|
||||
$riskyRules = $inboundRules | Where-Object {
|
||||
$portFilter = $allPortFilters[$_.InstanceID]
|
||||
if ($portFilter) {
|
||||
($portFilter.LocalPort -in $riskyPorts) -or ($portFilter.RemotePort -in $riskyPorts)
|
||||
}
|
||||
else {
|
||||
$false
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($rule in $riskyRules) {
|
||||
if ($rule.Enabled -eq $true) {
|
||||
$portFilter = $allPortFilters[$rule.InstanceID]
|
||||
|
||||
if ($rule.Action -eq 'Allow') {
|
||||
$result.EnabledRules++
|
||||
$result.Details += "WARNING: Allow rule '$($rule.DisplayName)' is ENABLED (Port: $($portFilter.LocalPort))"
|
||||
}
|
||||
else {
|
||||
$result.Details += "INFO: Block rule '$($rule.DisplayName)' is ENABLED (Port: $($portFilter.LocalPort))"
|
||||
}
|
||||
}
|
||||
else {
|
||||
$result.DisabledRules++
|
||||
}
|
||||
}
|
||||
|
||||
# Check actual port listeners
|
||||
foreach ($port in $riskyPorts) {
|
||||
if ($port -in @(137, 138, 139, 2869)) {
|
||||
# TCP ports
|
||||
$listener = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue
|
||||
if ($listener) {
|
||||
$result.OpenPorts += "TCP $port"
|
||||
$result.Details += "OPEN: TCP port $port is LISTENING!"
|
||||
}
|
||||
}
|
||||
else {
|
||||
# UDP ports (5355, 1900)
|
||||
$listener = Get-NetUDPEndpoint -LocalPort $port -ErrorAction SilentlyContinue
|
||||
if ($listener) {
|
||||
$result.OpenPorts += "UDP $port"
|
||||
$result.Details += "OPEN: UDP port $port is LISTENING!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Determine compliance
|
||||
$udp1900Open = $result.OpenPorts -contains "UDP 1900"
|
||||
$otherOpenPorts = $result.OpenPorts | Where-Object { $_ -ne "UDP 1900" }
|
||||
|
||||
if ($result.OpenPorts.Count -eq 0 -and $result.EnabledRules -eq 0) {
|
||||
# Ideal case: no listeners and no allow rules
|
||||
$result.Status = "Secure"
|
||||
$result.Compliant = $true
|
||||
$result.Details += "All risky ports closed and firewall rules disabled"
|
||||
}
|
||||
elseif ($udp1900Open -and -not $otherOpenPorts -and $result.EnabledRules -eq 0 -and $ssdpBlockActive) {
|
||||
# Only open endpoint is UDP 1900, but protected by NoID block rule (inbound)
|
||||
$result.Status = "Secure (blocked by firewall)"
|
||||
$result.Compliant = $true
|
||||
$result.Details += "UDP 1900 is listening locally but inbound traffic is blocked by '$ssdpRuleName'"
|
||||
}
|
||||
elseif ($result.OpenPorts.Count -eq 0 -and $result.EnabledRules -gt 0) {
|
||||
$result.Status = "Partially Secure"
|
||||
$result.Compliant = $false
|
||||
$result.Details += "Ports closed but $($result.EnabledRules) firewall rules still enabled"
|
||||
}
|
||||
else {
|
||||
$result.Status = "Insecure"
|
||||
$result.Compliant = $false
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to test risky ports: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return [PSCustomObject]@{
|
||||
Feature = "Risky Firewall Ports"
|
||||
Status = "Error"
|
||||
Details = @("Failed to test: $_")
|
||||
Compliant = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
89
Modules/AdvancedSecurity/Private/Test-RiskyServices.ps1
Normal file
89
Modules/AdvancedSecurity/Private/Test-RiskyServices.ps1
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
function Test-RiskyServices {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test risky network services compliance
|
||||
|
||||
.DESCRIPTION
|
||||
Checks if risky network services (SSDPSRV, upnphost, lmhosts) are stopped and disabled
|
||||
|
||||
.EXAMPLE
|
||||
Test-RiskyServices
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
$result = [PSCustomObject]@{
|
||||
Feature = "Risky Network Services"
|
||||
Status = "Unknown"
|
||||
Details = @()
|
||||
RunningServices = @()
|
||||
StoppedServices = @()
|
||||
Compliant = $false
|
||||
}
|
||||
|
||||
# Note: Computer Browser (Browser) is deprecated in Win10/11 - not included
|
||||
$services = @("SSDPSRV", "upnphost", "lmhosts")
|
||||
|
||||
foreach ($svcName in $services) {
|
||||
$service = Get-Service -Name $svcName -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not $service) {
|
||||
$result.Details += "$svcName - Not found (service may not be installed)"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($service.Status -eq 'Running') {
|
||||
$result.RunningServices += $svcName
|
||||
$result.Details += "WARNING - $svcName is RUNNING (StartType: $($service.StartType))"
|
||||
}
|
||||
else {
|
||||
$result.StoppedServices += $svcName
|
||||
|
||||
if ($service.StartType -eq 'Disabled') {
|
||||
$result.Details += "${svcName}: Stopped and Disabled"
|
||||
}
|
||||
else {
|
||||
$result.Details += "WARNING: ${svcName} is stopped but StartType is $($service.StartType) (should be Disabled)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Determine compliance
|
||||
if ($result.RunningServices.Count -eq 0) {
|
||||
$stoppedAndDisabled = $true
|
||||
|
||||
foreach ($svcName in $services) {
|
||||
$service = Get-Service -Name $svcName -ErrorAction SilentlyContinue
|
||||
if ($service -and $service.StartType -ne 'Disabled') {
|
||||
$stoppedAndDisabled = $false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ($stoppedAndDisabled) {
|
||||
$result.Status = "Secure"
|
||||
$result.Compliant = $true
|
||||
}
|
||||
else {
|
||||
$result.Status = "Partially Secure"
|
||||
$result.Compliant = $false
|
||||
}
|
||||
}
|
||||
else {
|
||||
$result.Status = "Insecure"
|
||||
$result.Compliant = $false
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to test risky services: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return [PSCustomObject]@{
|
||||
Feature = "Risky Network Services"
|
||||
Status = "Error"
|
||||
Details = @("Failed to test: $_")
|
||||
Compliant = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
126
Modules/AdvancedSecurity/Private/Test-SRPCompliance.ps1
Normal file
126
Modules/AdvancedSecurity/Private/Test-SRPCompliance.ps1
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
function Test-SRPCompliance {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Verifies Software Restriction Policies (SRP) configuration for CVE-2025-9491
|
||||
|
||||
.DESCRIPTION
|
||||
Tests whether SRP rules are correctly configured to block .lnk execution from Temp/Downloads.
|
||||
Returns compliance status for CVE-2025-9491 mitigation.
|
||||
|
||||
.EXAMPLE
|
||||
Test-SRPCompliance
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with compliance results
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
$configPath = Join-Path $PSScriptRoot "..\Config\SRP-Rules.json"
|
||||
|
||||
if (-not (Test-Path $configPath)) {
|
||||
return [PSCustomObject]@{
|
||||
Feature = "SRP Configuration"
|
||||
Status = "Not Configured"
|
||||
Compliant = $false
|
||||
Details = "SRP-Rules.json not found"
|
||||
}
|
||||
}
|
||||
|
||||
$config = Get-Content $configPath -Raw | ConvertFrom-Json
|
||||
$policyRoot = $config.RegistryPaths.PolicyRoot
|
||||
|
||||
# Check if SRP policy exists
|
||||
if (-not (Test-Path $policyRoot)) {
|
||||
Write-Log -Level WARNING -Message "SRP Check Failed: Policy root not found ($policyRoot)" -Module "AdvancedSecurity"
|
||||
return [PSCustomObject]@{
|
||||
Feature = "SRP CVE-2025-9491"
|
||||
Status = "Not Configured"
|
||||
Compliant = $false
|
||||
Details = "SRP policy root not found"
|
||||
}
|
||||
}
|
||||
|
||||
# Check Default Level
|
||||
$defaultLevel = Get-ItemProperty -Path $policyRoot -Name "DefaultLevel" -ErrorAction SilentlyContinue
|
||||
if ($null -eq $defaultLevel -or $defaultLevel.DefaultLevel -ne 262144) {
|
||||
Write-Log -Level WARNING -Message "SRP Check Failed: DefaultLevel is not Unrestricted (262144)" -Module "AdvancedSecurity"
|
||||
return [PSCustomObject]@{
|
||||
Feature = "SRP CVE-2025-9491"
|
||||
Status = "Misconfigured"
|
||||
Compliant = $false
|
||||
Details = "Default level not set to Unrestricted (262144)"
|
||||
}
|
||||
}
|
||||
|
||||
# Check Path Rules
|
||||
$pathRulesRoot = $config.RegistryPaths.PathRules
|
||||
|
||||
if (-not (Test-Path $pathRulesRoot)) {
|
||||
Write-Log -Level WARNING -Message "SRP Check Failed: PathRules root not found ($pathRulesRoot)" -Module "AdvancedSecurity"
|
||||
return [PSCustomObject]@{
|
||||
Feature = "SRP CVE-2025-9491"
|
||||
Status = "Incomplete"
|
||||
Compliant = $false
|
||||
Details = "Path rules not configured"
|
||||
}
|
||||
}
|
||||
|
||||
# Count configured rules
|
||||
$configuredRules = Get-ChildItem -Path $pathRulesRoot -ErrorAction SilentlyContinue
|
||||
$ruleCount = if ($configuredRules) { $configuredRules.Count } else { 0 }
|
||||
|
||||
# Check for Windows 11 bug
|
||||
$bugFixPath = $config.RegistryPaths.Win11BugFix
|
||||
$hasBuggyKeys = $false
|
||||
|
||||
if (Test-Path $bugFixPath) {
|
||||
foreach ($keyName in $config.Windows11BugFix.KeysToRemove) {
|
||||
$keyExists = Get-ItemProperty -Path $bugFixPath -Name $keyName -ErrorAction SilentlyContinue
|
||||
if ($null -ne $keyExists) {
|
||||
$hasBuggyKeys = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasBuggyKeys) {
|
||||
Write-Log -Level WARNING -Message "SRP Check Failed: Windows 11 buggy keys present (RuleCount/LastWriteTime)" -Module "AdvancedSecurity"
|
||||
return [PSCustomObject]@{
|
||||
Feature = "SRP CVE-2025-9491"
|
||||
Status = "Windows 11 Bug Detected"
|
||||
Compliant = $false
|
||||
Details = "Buggy registry keys present (RuleCount/LastWriteTime) - SRP may not work"
|
||||
}
|
||||
}
|
||||
|
||||
# All checks passed
|
||||
if ($ruleCount -ge 2) {
|
||||
return [PSCustomObject]@{
|
||||
Feature = "SRP CVE-2025-9491"
|
||||
Status = "Protected"
|
||||
Compliant = $true
|
||||
Details = "$ruleCount path rules configured, Windows 11 bug fix applied"
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "SRP Check Failed: Insufficient rules found ($ruleCount, expected 2+)" -Module "AdvancedSecurity"
|
||||
return [PSCustomObject]@{
|
||||
Feature = "SRP CVE-2025-9491"
|
||||
Status = "Incomplete"
|
||||
Compliant = $false
|
||||
Details = "Only $ruleCount path rules found (expected 2+)"
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return [PSCustomObject]@{
|
||||
Feature = "SRP CVE-2025-9491"
|
||||
Status = "Error"
|
||||
Compliant = $false
|
||||
Details = "Test failed: $_"
|
||||
}
|
||||
}
|
||||
}
|
||||
130
Modules/AdvancedSecurity/Private/Test-WDigest.ps1
Normal file
130
Modules/AdvancedSecurity/Private/Test-WDigest.ps1
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
function Test-WDigest {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test WDigest credential protection compliance
|
||||
|
||||
.DESCRIPTION
|
||||
Verifies that WDigest is configured to NOT store plaintext credentials in LSASS memory.
|
||||
Checks the UseLogonCredential registry value.
|
||||
|
||||
Expected: UseLogonCredential = 0 (Secure)
|
||||
Insecure: UseLogonCredential = 1 (Plaintext credentials in memory!)
|
||||
|
||||
.EXAMPLE
|
||||
Test-WDigest
|
||||
Returns compliance status for WDigest protection
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with compliance details
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
$wdigestRegPath = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Feature = "WDigest Protection"
|
||||
Status = "Unknown"
|
||||
Details = @()
|
||||
UseLogonCredential = $null
|
||||
Compliant = $false
|
||||
Windows_Version = ""
|
||||
Deprecated = $false
|
||||
}
|
||||
|
||||
# Get Windows version
|
||||
$osVersion = [System.Environment]::OSVersion.Version
|
||||
$isWin11 = $osVersion.Major -ge 10 -and $osVersion.Build -ge 22000
|
||||
$isWin11_24H2Plus = $isWin11 -and $osVersion.Build -ge 26100
|
||||
|
||||
if ($isWin11) {
|
||||
$result.Windows_Version = "Windows 11 (Build $($osVersion.Build))"
|
||||
if ($isWin11_24H2Plus) {
|
||||
$result.Deprecated = $true
|
||||
$result.Details += "Windows 11 24H2+ detected - WDigest setting is deprecated"
|
||||
}
|
||||
}
|
||||
elseif ($osVersion.Major -eq 10) {
|
||||
$result.Windows_Version = "Windows 10 (Build $($osVersion.Build))"
|
||||
}
|
||||
else {
|
||||
$result.Windows_Version = "Windows $($osVersion.Major).$($osVersion.Minor) (Build $($osVersion.Build))"
|
||||
}
|
||||
|
||||
# Check registry value
|
||||
if (Test-Path $wdigestRegPath) {
|
||||
$useLogonCred = (Get-ItemProperty -Path $wdigestRegPath -Name "UseLogonCredential" -ErrorAction SilentlyContinue).UseLogonCredential
|
||||
|
||||
if ($null -ne $useLogonCred) {
|
||||
$result.UseLogonCredential = $useLogonCred
|
||||
|
||||
if ($useLogonCred -eq 0) {
|
||||
$result.Status = "Secure"
|
||||
$result.Compliant = $true
|
||||
$result.Details += "UseLogonCredential = 0 (Plaintext credentials NOT stored)"
|
||||
|
||||
if ($result.Deprecated) {
|
||||
$result.Details += "Note: Setting is deprecated but explicitly configured for backwards compatibility"
|
||||
}
|
||||
}
|
||||
elseif ($useLogonCred -eq 1) {
|
||||
$result.Status = "INSECURE!"
|
||||
$result.Compliant = $false
|
||||
$result.Details += "WARNING: UseLogonCredential = 1 (Plaintext credentials IN MEMORY!)"
|
||||
$result.Details += "VULNERABLE to Mimikatz, WCE, and other credential dumping tools!"
|
||||
}
|
||||
else {
|
||||
$result.Status = "Unknown Value"
|
||||
$result.Compliant = $false
|
||||
$result.Details += "UseLogonCredential = $useLogonCred (Unknown value)"
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Value not set - default depends on OS version
|
||||
if ($osVersion.Major -eq 6 -and $osVersion.Minor -le 2) {
|
||||
# Windows 7/8 - default is 1 (INSECURE!)
|
||||
$result.Status = "Insecure (Default)"
|
||||
$result.Compliant = $false
|
||||
$result.Details += "UseLogonCredential not set - Windows 7/8 default is 1 (INSECURE!)"
|
||||
}
|
||||
else {
|
||||
# Windows 8.1+ - default is 0 (Secure)
|
||||
$result.Status = "Secure (Default)"
|
||||
$result.Compliant = $true
|
||||
$result.Details += "UseLogonCredential not set - Windows 8.1+ default is 0 (Secure)"
|
||||
|
||||
if ($result.Deprecated) {
|
||||
$result.Details += "Windows 11 24H2+: Setting is hardcoded secure (deprecated)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Registry path doesn't exist
|
||||
if ($osVersion.Major -eq 6 -and $osVersion.Minor -le 2) {
|
||||
# Windows 7/8
|
||||
$result.Status = "Insecure (No Config)"
|
||||
$result.Compliant = $false
|
||||
$result.Details += "WDigest registry path not found - Windows 7/8 default is INSECURE!"
|
||||
}
|
||||
else {
|
||||
# Windows 8.1+
|
||||
$result.Status = "Secure (Default)"
|
||||
$result.Compliant = $true
|
||||
$result.Details += "WDigest registry path not found - Windows 8.1+ default is secure"
|
||||
}
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to test WDigest protection: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return [PSCustomObject]@{
|
||||
Feature = "WDigest Protection"
|
||||
Status = "Error"
|
||||
Details = @("Failed to test: $_")
|
||||
Compliant = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Modules/AdvancedSecurity/Private/Test-WPAD.ps1
Normal file
88
Modules/AdvancedSecurity/Private/Test-WPAD.ps1
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
function Test-WPAD {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test WPAD configuration compliance
|
||||
|
||||
.DESCRIPTION
|
||||
Verifies that Web Proxy Auto-Discovery (WPAD) is disabled using the official
|
||||
Microsoft-recommended key plus legacy keys for compatibility.
|
||||
|
||||
Reference: https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/disable-http-proxy-auth-features
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with compliance details
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
$result = [PSCustomObject]@{
|
||||
Feature = "WPAD (Proxy Auto-Discovery)"
|
||||
Status = "Unknown"
|
||||
Details = @()
|
||||
Compliant = $true
|
||||
}
|
||||
|
||||
$wpadKeys = @(
|
||||
@{
|
||||
Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp"
|
||||
Name = "DisableWpad"
|
||||
Expected = 1
|
||||
Description = "Official MS key (Win10 1809+)"
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Wpad"
|
||||
Name = "WpadOverride"
|
||||
Expected = 1
|
||||
Description = "Legacy override key"
|
||||
},
|
||||
@{
|
||||
Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings"
|
||||
Name = "AutoDetect"
|
||||
Expected = 0
|
||||
Description = "Browser-level HKLM"
|
||||
}
|
||||
)
|
||||
|
||||
$nonCompliantCount = 0
|
||||
|
||||
foreach ($key in $wpadKeys) {
|
||||
if (Test-Path $key.Path) {
|
||||
$val = (Get-ItemProperty -Path $key.Path -Name $key.Name -ErrorAction SilentlyContinue).($key.Name)
|
||||
|
||||
if ($val -eq $key.Expected) {
|
||||
# Compliant
|
||||
}
|
||||
else {
|
||||
$result.Details += "$($key.Name) is NOT set to $($key.Expected) (Current: $val)"
|
||||
$nonCompliantCount++
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Key missing
|
||||
$result.Details += "Registry key missing: $($key.Path)"
|
||||
$nonCompliantCount++
|
||||
}
|
||||
}
|
||||
|
||||
if ($nonCompliantCount -eq 0) {
|
||||
$result.Status = "Secure (Disabled)"
|
||||
$result.Compliant = $true
|
||||
}
|
||||
else {
|
||||
$result.Status = "Insecure ($nonCompliantCount issues)"
|
||||
$result.Compliant = $false
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to test WPAD: $_" -Module "AdvancedSecurity"
|
||||
return [PSCustomObject]@{
|
||||
Feature = "WPAD (Proxy Auto-Discovery)"
|
||||
Status = "Error"
|
||||
Details = @("Failed to test: $_")
|
||||
Compliant = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Modules/AdvancedSecurity/Private/Test-WindowsUpdate.ps1
Normal file
91
Modules/AdvancedSecurity/Private/Test-WindowsUpdate.ps1
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
function Test-WindowsUpdate {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Verifies Windows Update configuration (3 simple GUI settings)
|
||||
|
||||
.DESCRIPTION
|
||||
Tests whether the 3 Windows Update GUI settings are properly configured:
|
||||
1. Get latest updates immediately
|
||||
2. Microsoft Update for other products
|
||||
3. Delivery Optimization disabled
|
||||
|
||||
.EXAMPLE
|
||||
Test-WindowsUpdate
|
||||
|
||||
.OUTPUTS
|
||||
PSCustomObject with compliance results
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
$configPath = Join-Path $PSScriptRoot "..\Config\WindowsUpdate.json"
|
||||
|
||||
if (-not (Test-Path $configPath)) {
|
||||
return [PSCustomObject]@{
|
||||
Feature = "Windows Update"
|
||||
Status = "Not Configured"
|
||||
Compliant = $false
|
||||
Details = "WindowsUpdate.json not found"
|
||||
}
|
||||
}
|
||||
|
||||
$config = Get-Content $configPath -Raw | ConvertFrom-Json
|
||||
|
||||
$settingsConfigured = 0
|
||||
$settingsTotal = 0
|
||||
$details = @()
|
||||
|
||||
# Check all 3 settings from config
|
||||
foreach ($settingKey in $config.Settings.PSObject.Properties.Name) {
|
||||
$setting = $config.Settings.$settingKey
|
||||
$regPath = $setting.RegistryPath
|
||||
|
||||
foreach ($valueName in $setting.Values.PSObject.Properties.Name) {
|
||||
$valueData = $setting.Values.$valueName
|
||||
$settingsTotal++
|
||||
|
||||
if (Test-Path $regPath) {
|
||||
$actual = Get-ItemProperty -Path $regPath -Name $valueName -ErrorAction SilentlyContinue
|
||||
|
||||
if ($null -ne $actual -and $actual.$valueName -eq $valueData.Value) {
|
||||
$settingsConfigured++
|
||||
$details += "$($setting.Name): OK"
|
||||
}
|
||||
else {
|
||||
$details += "$($setting.Name): NOT SET"
|
||||
Write-Log -Level WARNING -Message "Windows Update Check Failed: $($setting.Name)" -Module "AdvancedSecurity"
|
||||
if ($null -eq $actual) {
|
||||
Write-Log -Level WARNING -Message " - Value '$valueName' not found in $regPath" -Module "AdvancedSecurity"
|
||||
} else {
|
||||
Write-Log -Level WARNING -Message " - Value '$valueName' mismatch. Expected: $($valueData.Value), Actual: $($actual.$valueName)" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$details += "$($setting.Name): NOT SET (reg path missing)"
|
||||
Write-Log -Level WARNING -Message "Windows Update Check Failed: $($setting.Name)" -Module "AdvancedSecurity"
|
||||
Write-Log -Level WARNING -Message " - Registry Path Missing: $regPath" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$compliant = ($settingsConfigured -eq $settingsTotal)
|
||||
|
||||
return [PSCustomObject]@{
|
||||
Feature = "Windows Update"
|
||||
Status = if ($compliant) { "Configured" } else { "Incomplete" }
|
||||
Compliant = $compliant
|
||||
Details = "$settingsConfigured/$settingsTotal settings OK. $(if ($details) { $details -join ', ' })"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return [PSCustomObject]@{
|
||||
Feature = "Windows Update"
|
||||
Status = "Error"
|
||||
Compliant = $false
|
||||
Details = "Test failed: $_"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
function Test-WirelessDisplaySecurity {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Tests Wireless Display (Miracast) security configuration.
|
||||
|
||||
.DESCRIPTION
|
||||
Verifies that Wireless Display policies are configured securely:
|
||||
- AllowProjectionToPC = 0 (blocking receiving)
|
||||
- RequirePinForPairing = 2 (always require PIN)
|
||||
- Optionally: Complete disable of all Wireless Display
|
||||
|
||||
.EXAMPLE
|
||||
Test-WirelessDisplaySecurity
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
$connectPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Connect"
|
||||
|
||||
$results = @{
|
||||
AllowProjectionToPC = $null
|
||||
RequirePinForPairing = $null
|
||||
AllowProjectionFromPC = $null
|
||||
AllowMdnsAdvertisement = $null
|
||||
AllowMdnsDiscovery = $null
|
||||
WiFiDirectServiceDisabled = $null
|
||||
Compliant = $false
|
||||
FullyDisabled = $false
|
||||
}
|
||||
|
||||
if (Test-Path $connectPath) {
|
||||
$props = Get-ItemProperty -Path $connectPath -ErrorAction SilentlyContinue
|
||||
|
||||
# Check basic hardening (always required)
|
||||
$results.AllowProjectionToPC = $props.AllowProjectionToPC
|
||||
$results.RequirePinForPairing = $props.RequirePinForPairing
|
||||
|
||||
# Check optional complete disable
|
||||
$results.AllowProjectionFromPC = $props.AllowProjectionFromPC
|
||||
$results.AllowMdnsAdvertisement = $props.AllowMdnsAdvertisement
|
||||
$results.AllowMdnsDiscovery = $props.AllowMdnsDiscovery
|
||||
|
||||
# Check WiFi Direct Service status (CRITICAL for complete block)
|
||||
$wfdService = Get-Service -Name "WFDSConMgrSvc" -ErrorAction SilentlyContinue
|
||||
$results.WiFiDirectServiceDisabled = ($null -eq $wfdService) -or ($wfdService.StartType -eq 'Disabled')
|
||||
|
||||
# Basic compliance: receiving blocked + PIN required
|
||||
$results.Compliant = ($results.AllowProjectionToPC -eq 0) -and ($results.RequirePinForPairing -eq 2)
|
||||
|
||||
# Fully disabled: all settings at 0/2 AND WiFi Direct service disabled
|
||||
$results.FullyDisabled = $results.Compliant -and
|
||||
($results.AllowProjectionFromPC -eq 0) -and
|
||||
($results.AllowMdnsAdvertisement -eq 0) -and
|
||||
($results.AllowMdnsDiscovery -eq 0) -and
|
||||
$results.WiFiDirectServiceDisabled
|
||||
}
|
||||
else {
|
||||
# Key doesn't exist = not hardened
|
||||
$results.Compliant = $false
|
||||
$results.FullyDisabled = $false
|
||||
}
|
||||
|
||||
return [PSCustomObject]$results
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to test Wireless Display security: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $null
|
||||
}
|
||||
}
|
||||
1141
Modules/AdvancedSecurity/Public/Invoke-AdvancedSecurity.ps1
Normal file
1141
Modules/AdvancedSecurity/Public/Invoke-AdvancedSecurity.ps1
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,312 @@
|
|||
function Restore-AdvancedSecuritySettings {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Restore Advanced Security settings from backup
|
||||
|
||||
.DESCRIPTION
|
||||
Restores custom Advanced Security settings that are not handled by the generic
|
||||
registry/service restore logic. This includes:
|
||||
- Firewall Rules (Risky Ports)
|
||||
- Windows Features (PowerShell v2)
|
||||
- SMB Shares (Admin Shares)
|
||||
|
||||
.PARAMETER BackupFilePath
|
||||
Path to the JSON backup file
|
||||
|
||||
.OUTPUTS
|
||||
Boolean indicating success
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([bool])]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$BackupFilePath
|
||||
)
|
||||
|
||||
if (-not (Test-Path $BackupFilePath)) {
|
||||
Write-Log -Level ERROR -Message "Backup file not found: $BackupFilePath" -Module "AdvancedSecurity"
|
||||
return $false
|
||||
}
|
||||
|
||||
try {
|
||||
$filename = Split-Path $BackupFilePath -Leaf
|
||||
Write-Log -Level INFO -Message "Processing Advanced Security backup: $filename" -Module "AdvancedSecurity"
|
||||
|
||||
# Skip Empty Marker files - these are already processed by generic Empty Marker logic in Core/Rollback.ps1
|
||||
if ($filename -match "_EMPTY\.json$") {
|
||||
Write-Log -Level DEBUG -Message "Skipping Empty Marker file (already processed): $filename" -Module "AdvancedSecurity"
|
||||
return $true # Success - nothing to do here
|
||||
}
|
||||
|
||||
# Load backup data
|
||||
$backupData = Get-Content -Path $BackupFilePath -Raw | ConvertFrom-Json
|
||||
|
||||
# Determine backup type based on filename or content
|
||||
if ($filename -match "RiskyPorts_Firewall") {
|
||||
return Restore-FirewallRules -BackupData $backupData
|
||||
}
|
||||
elseif ($filename -match "PowerShellV2") {
|
||||
return Restore-PowerShellV2 -BackupData $backupData
|
||||
}
|
||||
elseif ($filename -match "AdminShares") {
|
||||
return Restore-AdminShares -BackupData $backupData
|
||||
}
|
||||
elseif ($filename -match "NetBIOS_Adapters") {
|
||||
return Restore-NetBIOSAdapters -BackupData $backupData
|
||||
}
|
||||
elseif ($filename -match "RDP_Hardening") {
|
||||
# RDP settings are already restored via the Smart JSON-Fallback mechanism in Rollback.ps1
|
||||
# This JSON backup serves as a fallback and doesn't require separate restore logic
|
||||
Write-Log -Level DEBUG -Message "RDP_Hardening.json acknowledged (already handled by Smart JSON-Fallback)" -Module "AdvancedSecurity"
|
||||
return $true
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "Unknown Advanced Security backup type: $filename" -Module "AdvancedSecurity"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to restore Advanced Security settings: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Restore-FirewallRules {
|
||||
param($BackupData)
|
||||
|
||||
Write-Log -Level INFO -Message "Restoring firewall rules..." -Module "AdvancedSecurity"
|
||||
|
||||
try {
|
||||
# 1. Remove rules created by hardening (identified by Group or Name pattern)
|
||||
# The hardening module creates rules with specific names/groups.
|
||||
# Since we don't have the exact names of created rules stored in a "CreatedRules" list here,
|
||||
# we rely on the fact that we are restoring the *previous* state.
|
||||
|
||||
# However, for firewall rules, "restoring" usually means:
|
||||
# 1. Deleting the BLOCK rules we added
|
||||
# 2. Re-enabling any rules we disabled (if any)
|
||||
|
||||
# The backup contains a SNAPSHOT of rules matching the risky ports.
|
||||
# We should restore their state (Enabled/Disabled, Action).
|
||||
|
||||
if ($BackupData.Rules) {
|
||||
foreach ($rule in $BackupData.Rules) {
|
||||
# Check if rule exists
|
||||
$currentRule = Get-NetFirewallRule -Name $rule.Name -ErrorAction SilentlyContinue
|
||||
|
||||
if ($currentRule) {
|
||||
# Restore state
|
||||
Set-NetFirewallRule -Name $rule.Name `
|
||||
-Enabled $rule.Enabled `
|
||||
-Action $rule.Action `
|
||||
-ErrorAction SilentlyContinue
|
||||
|
||||
Write-Log -Level DEBUG -Message "Restored rule state: $($rule.Name)" -Module "AdvancedSecurity"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Also remove the specific block rules added by AdvancedSecurity
|
||||
# These include:
|
||||
# - Block Risky Port * (legacy patterns)
|
||||
# - NoID Privacy - Block Finger Protocol (Port 79)
|
||||
# - NoID Privacy - Block SSDP (UDP 1900)
|
||||
# - Block Admin Shares - NoID Privacy (TCP 445 on Public profile)
|
||||
$blockRules = Get-NetFirewallRule -DisplayName "Block Risky Port *" -ErrorAction SilentlyContinue
|
||||
if ($blockRules) {
|
||||
Remove-NetFirewallRule -InputObject $blockRules -ErrorAction SilentlyContinue
|
||||
Write-Log -Level INFO -Message "Removed $($blockRules.Count) hardening block rules" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# Remove Finger Protocol rule (corrected name with NoID prefix)
|
||||
Remove-NetFirewallRule -DisplayName "NoID Privacy - Block Finger Protocol (Port 79)" -ErrorAction SilentlyContinue
|
||||
|
||||
# Remove SSDP block rule (UDP 1900)
|
||||
Remove-NetFirewallRule -DisplayName "NoID Privacy - Block SSDP (UDP 1900)" -ErrorAction SilentlyContinue
|
||||
|
||||
# Remove WS-Discovery and mDNS block rules (Maximum profile discovery hardening)
|
||||
Remove-NetFirewallRule -Name "NoID-Block-WSD-UDP-3702" -ErrorAction SilentlyContinue
|
||||
Remove-NetFirewallRule -Name "NoID-Block-WSD-TCP-5357" -ErrorAction SilentlyContinue
|
||||
Remove-NetFirewallRule -Name "NoID-Block-WSD-TCP-5358" -ErrorAction SilentlyContinue
|
||||
Remove-NetFirewallRule -Name "NoID-Block-mDNS-UDP-5353" -ErrorAction SilentlyContinue
|
||||
|
||||
# Remove Admin Shares SMB block rule (TCP 445 on Public profile)
|
||||
Remove-NetFirewallRule -DisplayName "Block Admin Shares - NoID Privacy" -ErrorAction SilentlyContinue
|
||||
|
||||
# Remove Miracast/Wireless Display block rules (Ports 7236, 7250)
|
||||
Remove-NetFirewallRule -DisplayName "NoID Privacy - Block Miracast TCP 7236" -ErrorAction SilentlyContinue
|
||||
Remove-NetFirewallRule -DisplayName "NoID Privacy - Block Miracast TCP 7250" -ErrorAction SilentlyContinue
|
||||
Remove-NetFirewallRule -DisplayName "NoID Privacy - Block Miracast UDP 7236" -ErrorAction SilentlyContinue
|
||||
Remove-NetFirewallRule -DisplayName "NoID Privacy - Block Miracast UDP 7250" -ErrorAction SilentlyContinue
|
||||
|
||||
# Re-enable WiFi Direct Service (WFDSConMgrSvc) for Miracast functionality
|
||||
$wfdService = Get-Service -Name "WFDSConMgrSvc" -ErrorAction SilentlyContinue
|
||||
if ($wfdService -and $wfdService.StartType -eq 'Disabled') {
|
||||
Set-Service -Name "WFDSConMgrSvc" -StartupType Manual -ErrorAction SilentlyContinue
|
||||
Write-Log -Level INFO -Message "Re-enabled WiFi Direct Service (WFDSConMgrSvc)" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
# Re-enable WiFi Direct Virtual Adapters
|
||||
Get-NetAdapter -InterfaceDescription "Microsoft Wi-Fi Direct Virtual*" -IncludeHidden -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.Status -eq 'Disabled' } |
|
||||
ForEach-Object {
|
||||
Enable-NetAdapter -Name $_.Name -Confirm:$false -ErrorAction SilentlyContinue
|
||||
Write-Log -Level INFO -Message "Re-enabled WiFi Direct adapter: $($_.Name)" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to restore firewall rules: $_" -Module "AdvancedSecurity"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Restore-PowerShellV2 {
|
||||
param($BackupData)
|
||||
|
||||
Write-Log -Level INFO -Message "Restoring PowerShell v2 state..." -Module "AdvancedSecurity"
|
||||
|
||||
try {
|
||||
$shouldEnable = ($BackupData.State -eq "Enabled")
|
||||
|
||||
# Check current state
|
||||
$psv2RegPath = "HKLM:\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine"
|
||||
$psv2EngineVersion = (Get-ItemProperty -Path $psv2RegPath -Name "PowerShellVersion" -ErrorAction SilentlyContinue).PowerShellVersion
|
||||
$isEnabled = ($null -ne $psv2EngineVersion -and $psv2EngineVersion -like "2.*")
|
||||
|
||||
if ($shouldEnable -and -not $isEnabled) {
|
||||
Write-Log -Level INFO -Message "Re-enabling PowerShell v2 (via DISM)..." -Module "AdvancedSecurity"
|
||||
Enable-WindowsOptionalFeature -Online -FeatureName "MicrosoftWindowsPowerShellV2Root" -NoRestart -ErrorAction Stop | Out-Null
|
||||
}
|
||||
elseif (-not $shouldEnable -and $isEnabled) {
|
||||
Write-Log -Level INFO -Message "Disabling PowerShell v2 (via DISM)..." -Module "AdvancedSecurity"
|
||||
Disable-WindowsOptionalFeature -Online -FeatureName "MicrosoftWindowsPowerShellV2Root" -NoRestart -ErrorAction Stop | Out-Null
|
||||
}
|
||||
else {
|
||||
Write-Log -Level INFO -Message "PowerShell v2 state already matches backup ($($BackupData.State))" -Module "AdvancedSecurity"
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to restore PowerShell v2: $_" -Module "AdvancedSecurity"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Restore-AdminShares {
|
||||
# Note: No parameters needed - registry restore happens separately via Core\Rollback.ps1
|
||||
|
||||
Write-Log -Level INFO -Message "Restoring Admin Shares..." -Module "AdvancedSecurity"
|
||||
|
||||
try {
|
||||
# The backup contains a list of shares that existed.
|
||||
# If we disabled them, they might be gone or the AutoShareServer/Wks registry keys were changed.
|
||||
# Registry keys are handled by the generic Registry restore!
|
||||
# So we mainly need to verify if we need to manually recreate shares or if registry restore + reboot is enough.
|
||||
|
||||
# Changing AutoShareServer/AutoShareWks requires a reboot to take effect.
|
||||
# So simply restoring the registry keys (which happens before this) should be sufficient for the next boot.
|
||||
|
||||
# However, we can try to force re-creation if possible, but usually LanmanServer needs restart.
|
||||
Write-Log -Level INFO -Message "Admin Shares settings restored via Registry. A reboot is required to fully restore shares." -Module "AdvancedSecurity"
|
||||
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to restore Admin Shares: $_" -Module "AdvancedSecurity"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Restore-NetBIOSAdapters {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Restore NetBIOS over TCP/IP settings on network adapters
|
||||
|
||||
.DESCRIPTION
|
||||
Restores the TcpipNetbiosOptions setting on each network adapter
|
||||
to its pre-hardening state.
|
||||
|
||||
TcpipNetbiosOptions values:
|
||||
- 0 = Default (use DHCP option)
|
||||
- 1 = Enable NetBIOS over TCP/IP
|
||||
- 2 = Disable NetBIOS over TCP/IP (set by hardening)
|
||||
|
||||
.PARAMETER BackupData
|
||||
JSON backup data containing adapter descriptions and their original TcpipNetbiosOptions
|
||||
#>
|
||||
param($BackupData)
|
||||
|
||||
Write-Log -Level INFO -Message "Restoring NetBIOS over TCP/IP settings on network adapters..." -Module "AdvancedSecurity"
|
||||
|
||||
try {
|
||||
# BackupData can be an array directly or have a nested structure
|
||||
$adaptersToRestore = if ($BackupData -is [Array]) { $BackupData } else { @($BackupData) }
|
||||
|
||||
if ($adaptersToRestore.Count -eq 0) {
|
||||
Write-Log -Level INFO -Message "No NetBIOS adapter settings to restore" -Module "AdvancedSecurity"
|
||||
return $true
|
||||
}
|
||||
|
||||
$restoredCount = 0
|
||||
$failedCount = 0
|
||||
|
||||
# Get current adapters
|
||||
$currentAdapters = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "IPEnabled = TRUE" -ErrorAction SilentlyContinue
|
||||
|
||||
foreach ($backupAdapter in $adaptersToRestore) {
|
||||
try {
|
||||
# Find matching adapter by Index (most reliable) or Description
|
||||
$targetAdapter = $currentAdapters | Where-Object {
|
||||
$_.Index -eq $backupAdapter.Index -or
|
||||
$_.Description -eq $backupAdapter.Description
|
||||
} | Select-Object -First 1
|
||||
|
||||
if ($targetAdapter) {
|
||||
$originalSetting = $backupAdapter.TcpipNetbiosOptions
|
||||
|
||||
# Only restore if different from current
|
||||
if ($targetAdapter.TcpipNetbiosOptions -ne $originalSetting) {
|
||||
$result = Invoke-CimMethod -InputObject $targetAdapter -MethodName SetTcpipNetbios -Arguments @{TcpipNetbiosOptions = $originalSetting }
|
||||
|
||||
if ($result.ReturnValue -eq 0) {
|
||||
Write-Log -Level DEBUG -Message "Restored NetBIOS setting on adapter '$($targetAdapter.Description)' to $originalSetting" -Module "AdvancedSecurity"
|
||||
$restoredCount++
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "SetTcpipNetbios returned $($result.ReturnValue) for adapter '$($targetAdapter.Description)'" -Module "AdvancedSecurity"
|
||||
$failedCount++
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Log -Level DEBUG -Message "NetBIOS setting on adapter '$($targetAdapter.Description)' already matches backup ($originalSetting)" -Module "AdvancedSecurity"
|
||||
$restoredCount++
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "Adapter not found for restore: Index=$($backupAdapter.Index), Description='$($backupAdapter.Description)'" -Module "AdvancedSecurity"
|
||||
$failedCount++
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Failed to restore NetBIOS on adapter '$($backupAdapter.Description)': $_" -Module "AdvancedSecurity"
|
||||
$failedCount++
|
||||
}
|
||||
}
|
||||
|
||||
if ($failedCount -eq 0) {
|
||||
Write-Log -Level SUCCESS -Message "NetBIOS settings restored on $restoredCount adapter(s)" -Module "AdvancedSecurity"
|
||||
return $true
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "NetBIOS restore completed with issues: $restoredCount succeeded, $failedCount failed" -Module "AdvancedSecurity"
|
||||
return $true # Still return true - partial success is acceptable
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to restore NetBIOS adapter settings: $_" -Module "AdvancedSecurity"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
188
Modules/AdvancedSecurity/Public/Test-AdvancedSecurity.ps1
Normal file
188
Modules/AdvancedSecurity/Public/Test-AdvancedSecurity.ps1
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
function Test-AdvancedSecurity {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Test Advanced Security compliance
|
||||
|
||||
.DESCRIPTION
|
||||
Runs all compliance tests for Advanced Security hardening and returns
|
||||
a comprehensive report of the current security posture.
|
||||
|
||||
Tests include:
|
||||
- RDP Security (NLA enforcement, SSL/TLS, disable status)
|
||||
- WDigest Protection (credential caching disabled)
|
||||
- Administrative Shares (disabled and removed)
|
||||
- Risky Firewall Ports (LLMNR, NetBIOS, UPnP/SSDP closed)
|
||||
- Risky Network Services (SSDPSRV, upnphost, lmhosts stopped)
|
||||
- Discovery Protocols (WS-Discovery, mDNS)
|
||||
|
||||
.EXAMPLE
|
||||
Test-AdvancedSecurity
|
||||
Runs all compliance tests and displays results
|
||||
|
||||
.EXAMPLE
|
||||
$results = Test-AdvancedSecurity
|
||||
$results | Format-Table
|
||||
|
||||
.OUTPUTS
|
||||
Array of PSCustomObjects with compliance results
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
try {
|
||||
Write-Host ""
|
||||
Write-Host "============================================" -ForegroundColor Cyan
|
||||
Write-Host " ADVANCED SECURITY COMPLIANCE TEST" -ForegroundColor Cyan
|
||||
Write-Host "============================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$results = @()
|
||||
|
||||
# 1. RDP Security
|
||||
Write-Host "Testing RDP Security..." -ForegroundColor Gray
|
||||
$rdpTest = Test-RdpSecurity
|
||||
$results += $rdpTest
|
||||
|
||||
# 2. WDigest Protection
|
||||
Write-Host "Testing WDigest Protection..." -ForegroundColor Gray
|
||||
$wdigestTest = Test-WDigest
|
||||
$results += $wdigestTest
|
||||
|
||||
# 3. Admin Shares
|
||||
Write-Host "Testing Administrative Shares..." -ForegroundColor Gray
|
||||
$adminSharesTest = Test-AdminShares
|
||||
$results += $adminSharesTest
|
||||
|
||||
# 4. Legacy TLS
|
||||
Write-Host "Testing Legacy TLS (1.0/1.1)..." -ForegroundColor Gray
|
||||
$tlsTest = Test-LegacyTLS
|
||||
$results += $tlsTest
|
||||
|
||||
# 5. WPAD
|
||||
Write-Host "Testing WPAD Configuration..." -ForegroundColor Gray
|
||||
$wpadTest = Test-WPAD
|
||||
$results += $wpadTest
|
||||
|
||||
# 6. PowerShell v2
|
||||
Write-Host "Testing PowerShell v2 Status..." -ForegroundColor Gray
|
||||
$psv2Test = Test-PowerShellV2
|
||||
$results += $psv2Test
|
||||
|
||||
# 7. Risky Ports
|
||||
Write-Host "Testing Risky Firewall Ports..." -ForegroundColor Gray
|
||||
$riskyPortsTest = Test-RiskyPorts
|
||||
$results += $riskyPortsTest
|
||||
|
||||
# 8. Risky Services
|
||||
Write-Host "Testing Risky Network Services..." -ForegroundColor Gray
|
||||
$riskyServicesTest = Test-RiskyServices
|
||||
$results += $riskyServicesTest
|
||||
|
||||
# 9. SRP Configuration (CVE-2025-9491)
|
||||
Write-Host "Testing SRP Configuration (CVE-2025-9491)..." -ForegroundColor Gray
|
||||
$srpTest = Test-SRPCompliance
|
||||
$results += $srpTest
|
||||
|
||||
# 10. Windows Update Configuration
|
||||
Write-Host "Testing Windows Update Configuration..." -ForegroundColor Gray
|
||||
$wuTest = Test-WindowsUpdate
|
||||
$results += $wuTest
|
||||
|
||||
# 11. Finger Protocol Block
|
||||
Write-Host "Testing Finger Protocol Block..." -ForegroundColor Gray
|
||||
$fingerTest = Test-FingerProtocol
|
||||
$results += $fingerTest
|
||||
|
||||
# 12. Wireless Display Security
|
||||
Write-Host "Testing Wireless Display Security..." -ForegroundColor Gray
|
||||
$wirelessDisplayTest = Test-WirelessDisplaySecurity
|
||||
if ($wirelessDisplayTest) {
|
||||
$results += [PSCustomObject]@{
|
||||
Feature = "Wireless Display Security"
|
||||
Compliant = $wirelessDisplayTest.Compliant
|
||||
Details = if ($wirelessDisplayTest.FullyDisabled) { "Fully Disabled" }
|
||||
elseif ($wirelessDisplayTest.Compliant) { "Hardened (receiving blocked, PIN required)" }
|
||||
else { "NOT HARDENED - screen interception possible!" }
|
||||
}
|
||||
}
|
||||
|
||||
# 13. Discovery Protocols (WS-Discovery + mDNS) - Maximum profile only
|
||||
Write-Host "Testing Discovery Protocols (WS-Discovery + mDNS)..." -ForegroundColor Gray
|
||||
$discoveryTest = Test-DiscoveryProtocolsSecurity
|
||||
if ($discoveryTest) {
|
||||
$results += [PSCustomObject]@{
|
||||
Feature = "Discovery Protocols (WS-Discovery + mDNS)"
|
||||
Status = if ($discoveryTest.Compliant) { "Secure" } else { "Insecure" }
|
||||
Details = "mDNS=" + $(if ($discoveryTest.EnableMDNS -eq 0) { "Disabled" } else { "Enabled/Not Set" }) +
|
||||
"; Services: FDResPub=" + $discoveryTest.FDResPubDisabled + ", fdPHost=" + $discoveryTest.FdPHostDisabled +
|
||||
"; FirewallRulesEnabled=" + $discoveryTest.FirewallRulesEnabled
|
||||
Compliant = $discoveryTest.Compliant
|
||||
}
|
||||
}
|
||||
|
||||
# 14. Firewall Shields Up (optional - Maximum profile only)
|
||||
Write-Host "Testing Firewall Shields Up (Public)..." -ForegroundColor Gray
|
||||
$shieldsUpTest = Test-FirewallShieldsUp
|
||||
# Always pass - this is an optional hardening only for the Maximum (air-gapped) profile
|
||||
$results += [PSCustomObject]@{
|
||||
Feature = "Firewall Shields Up (Public)"
|
||||
Compliant = $shieldsUpTest.Pass
|
||||
Details = $shieldsUpTest.Message
|
||||
}
|
||||
|
||||
# 15. IPv6 Disable (optional - Maximum profile only, mitm6 mitigation)
|
||||
Write-Host "Testing IPv6 Security (mitm6 mitigation)..." -ForegroundColor Gray
|
||||
$ipv6Test = Test-IPv6Security
|
||||
# Always pass - this is an optional hardening only for the Maximum profile
|
||||
$results += [PSCustomObject]@{
|
||||
Feature = "IPv6 Disable (mitm6 mitigation)"
|
||||
Compliant = $ipv6Test.Pass
|
||||
Details = $ipv6Test.Message
|
||||
}
|
||||
|
||||
# Summary
|
||||
Write-Host ""
|
||||
Write-Host "============================================" -ForegroundColor Cyan
|
||||
Write-Host " COMPLIANCE SUMMARY" -ForegroundColor Cyan
|
||||
Write-Host "============================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
$compliantCount = ($results | Where-Object { $_.Compliant -eq $true }).Count
|
||||
$totalTests = $results.Count
|
||||
$compliancePercent = [math]::Round(($compliantCount / $totalTests) * 100, 1)
|
||||
|
||||
Write-Host "Total Tests: $totalTests" -ForegroundColor White
|
||||
Write-Host "Compliant: $compliantCount" -ForegroundColor Green
|
||||
Write-Host "Non-Compliant: $($totalTests - $compliantCount)" -ForegroundColor Red
|
||||
Write-Host "Compliance: $compliancePercent%" -ForegroundColor $(if ($compliancePercent -ge 80) { 'Green' } elseif ($compliancePercent -ge 50) { 'Yellow' } else { 'Red' })
|
||||
Write-Host ""
|
||||
|
||||
# Detailed results table
|
||||
Write-Host "DETAILED RESULTS:" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
$tableFormat = @{Expression = { $_.Feature }; Label = "Feature"; Width = 30 },
|
||||
@{Expression = { $_.Status }; Label = "Status"; Width = 20 },
|
||||
@{Expression = { if ($_.Compliant) { "[X]" }else { "[ ]" } }; Label = "Compliant"; Width = 10 }
|
||||
|
||||
$results | Format-Table $tableFormat -AutoSize
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Return structured object with metadata for programmatic use
|
||||
return [PSCustomObject]@{
|
||||
Results = $results
|
||||
TotalChecks = $totalTests
|
||||
CompliantCount = $compliantCount
|
||||
Compliance = $compliancePercent
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level ERROR -Message "Failed to run compliance tests: $_" -Module "AdvancedSecurity" -Exception $_.Exception
|
||||
Write-Host ""
|
||||
Write-Host "ERROR: Failed to run compliance tests" -ForegroundColor Red
|
||||
Write-Host $_.Exception.Message -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
return $null
|
||||
}
|
||||
}
|
||||
35
Modules/AntiAI/AntiAI.psd1
Normal file
35
Modules/AntiAI/AntiAI.psd1
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
@{
|
||||
RootModule = 'AntiAI.psm1'
|
||||
ModuleVersion = '2.2.0'
|
||||
GUID = 'f8e9d7c6-5b4a-3c2d-1e0f-9a8b7c6d5e4f'
|
||||
Author = 'NexusOne23'
|
||||
CompanyName = 'Open Source Project'
|
||||
Copyright = '(c) 2025 NexusOne23. Licensed under GPL-3.0.'
|
||||
Description = 'Comprehensive Windows 11 AI deactivation - Disables all 8+ AI features using official Microsoft policies (Recall, Copilot, Paint AI, Notepad AI, Click to Do, Settings Agent). Maximum compliance mode with enterprise-grade Recall protection.'
|
||||
PowerShellVersion = '5.1'
|
||||
|
||||
FunctionsToExport = @(
|
||||
'Invoke-AntiAI'
|
||||
)
|
||||
|
||||
PrivateData = @{
|
||||
PSData = @{
|
||||
Tags = @('Windows11', 'AI', 'Privacy', 'Security', 'Recall', 'Copilot', 'AntiAI')
|
||||
ProjectUri = 'https://github.com/yourusername/NoIDPrivacy'
|
||||
ReleaseNotes = @'
|
||||
v1.0.0 - Initial Release
|
||||
- Disables 8+ Windows 11 AI features using official Microsoft policies
|
||||
- Master switch: Blocks all generative AI models (Paint, Notepad, Photos, Clipchamp, Snipping Tool)
|
||||
- Windows Recall: Complete deactivation (component removal + snapshots + data providers)
|
||||
- Windows Recall: Enterprise protection (app/URI deny lists, storage limits)
|
||||
- Windows Copilot: System-wide deactivation + hardware key remapping
|
||||
- Click to Do: Screenshot analysis disabled
|
||||
- Paint AI: Cocreator, Generative Fill, Image Creator disabled
|
||||
- Notepad AI: Write, Summarize, Rewrite features disabled
|
||||
- Settings Agent: AI-powered search in Settings disabled
|
||||
- Full backup/restore capability
|
||||
- Comprehensive compliance verification
|
||||
'@
|
||||
}
|
||||
}
|
||||
}
|
||||
60
Modules/AntiAI/AntiAI.psm1
Normal file
60
Modules/AntiAI/AntiAI.psm1
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#Requires -Version 5.1
|
||||
#Requires -RunAsAdministrator
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
AntiAI Module Loader
|
||||
|
||||
.DESCRIPTION
|
||||
Disables all Windows 11 AI features using official Microsoft policies.
|
||||
Includes Recall, Copilot, Paint AI, Notepad AI, Click to Do, Settings Agent, and Explorer AI Actions.
|
||||
|
||||
.NOTES
|
||||
Module: AntiAI
|
||||
Version: 2.2.0
|
||||
Author: NoID Privacy
|
||||
#>
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
# Get module root path
|
||||
$script:ModuleRoot = $PSScriptRoot
|
||||
|
||||
# Import private functions
|
||||
$privateFunctions = @(
|
||||
'Backup-AntiAISettings'
|
||||
'Restore-AntiAISettings'
|
||||
'Test-AntiAICompliance'
|
||||
'Set-SystemAIModels'
|
||||
'Disable-Recall'
|
||||
'Set-RecallProtection'
|
||||
'Disable-Copilot'
|
||||
'Disable-CopilotAdvanced' # NEW v2.2.0: URI handlers, Edge sidebar, Recall export
|
||||
'Disable-ClickToDo'
|
||||
'Disable-SettingsAgent'
|
||||
'Disable-ExplorerAI' # NEW: File Explorer AI Actions menu
|
||||
'Disable-NotepadAI'
|
||||
'Disable-PaintAI'
|
||||
)
|
||||
|
||||
foreach ($function in $privateFunctions) {
|
||||
$functionPath = Join-Path $ModuleRoot "Private\$function.ps1"
|
||||
if (Test-Path $functionPath) {
|
||||
. $functionPath
|
||||
}
|
||||
}
|
||||
|
||||
# Import public functions
|
||||
$publicFunctions = @(
|
||||
'Invoke-AntiAI'
|
||||
)
|
||||
|
||||
foreach ($function in $publicFunctions) {
|
||||
$functionPath = Join-Path $ModuleRoot "Public\$function.ps1"
|
||||
if (Test-Path $functionPath) {
|
||||
. $functionPath
|
||||
}
|
||||
}
|
||||
|
||||
# Export public functions + Test-AntiAICompliance (needed for Invoke-AntiAI verification)
|
||||
Export-ModuleMember -Function @($publicFunctions + 'Test-AntiAICompliance')
|
||||
419
Modules/AntiAI/Config/AntiAI-Settings.json
Normal file
419
Modules/AntiAI/Config/AntiAI-Settings.json
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
{
|
||||
"ModuleName": "AntiAI",
|
||||
"Version": "1.0.0",
|
||||
"Description": "Maximum AI deactivation - Disables all 13 Windows 11 AI features using official Microsoft policies",
|
||||
"Mode": "Maximum Compliance (Enterprise-Grade)",
|
||||
"TotalFeatures": 13,
|
||||
"TotalPolicies": 32,
|
||||
|
||||
"Features": {
|
||||
"1_GenerativeAI_Master": {
|
||||
"Name": "Generative AI Master Switch",
|
||||
"Description": "Blocks ALL apps from using Windows on-device generative AI models AND app-level generative AI access",
|
||||
"Impact": "Disables generative AI in Notepad, Paint, Photos, Clipchamp, Snipping Tool, and all future apps. Also blocks app access to generative AI features.",
|
||||
"CloudBased": false,
|
||||
"Registry": {
|
||||
"HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\AppPrivacy": {
|
||||
"LetAppsAccessSystemAIModels": {
|
||||
"Type": "DWord",
|
||||
"Value": 2,
|
||||
"Description": "Force Deny - No app can access on-device generative AI models (0=User decides, 1=Force Allow, 2=Force Deny)"
|
||||
},
|
||||
"LetAppsAccessGenerativeAI": {
|
||||
"Type": "DWord",
|
||||
"Value": 2,
|
||||
"Description": "Force Deny - Block app access to generative AI features (Text & Image Generation in Settings)"
|
||||
}
|
||||
},
|
||||
"HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\CapabilityAccessManager\\ConsentStore\\systemAIModels": {
|
||||
"Value": {
|
||||
"Type": "String",
|
||||
"Value": "Deny",
|
||||
"Description": "CapabilityAccessManager Workaround - Blocks Paint Generative Erase/Background Removal"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"2_Windows_Recall": {
|
||||
"Name": "Windows Recall (Complete Deactivation + Enterprise Protection)",
|
||||
"Description": "Takes continuous screenshots of EVERYTHING on your screen (passwords, banking, private messages) and stores them locally for AI-powered search. EXTREME privacy risk!",
|
||||
"Impact": "Component completely removed from system, all snapshots deleted, background data providers disabled, apps/URLs protected",
|
||||
"CloudBased": false,
|
||||
"RequiresReboot": true,
|
||||
"Registry": {
|
||||
"HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsAI": {
|
||||
"AllowRecallEnablement": {
|
||||
"Type": "DWord",
|
||||
"Value": 0,
|
||||
"Description": "REMOVE Recall component from system + delete all existing snapshots (requires reboot)"
|
||||
},
|
||||
"DisableAIDataAnalysis": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "PREVENT saving new snapshots for Recall (Device-scope)"
|
||||
}
|
||||
},
|
||||
"HKCU:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsAI": {
|
||||
"DisableAIDataAnalysis": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "PREVENT saving new snapshots for Recall (User-scope)"
|
||||
},
|
||||
"DisableRecallDataProviders": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "DISABLE Recall background data providers (Enterprise/Education only)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnterpriseProtection": {
|
||||
"HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsAI": {
|
||||
"SetDenyAppListForRecall": {
|
||||
"Type": "MultiString",
|
||||
"Value": [
|
||||
"Microsoft.MicrosoftEdge_8wekyb3d8bbwe!App",
|
||||
"Microsoft.WindowsTerminal_8wekyb3d8bbwe!App",
|
||||
"KeePassXC_8wekyb3d8bbwe!KeePassXC",
|
||||
"Microsoft.RemoteDesktop_8wekyb3d8bbwe!App"
|
||||
],
|
||||
"Description": "Apps NEVER captured in snapshots (Browser for Banking, Terminal for Passwords, KeePass, RDP)"
|
||||
},
|
||||
"SetDenyUriListForRecall": {
|
||||
"Type": "MultiString",
|
||||
"Value": [
|
||||
"*.bank.*",
|
||||
"*.paypal.*",
|
||||
"*.bankofamerica.*",
|
||||
"mail.*",
|
||||
"webmail.*",
|
||||
"*password*",
|
||||
"*login*"
|
||||
],
|
||||
"Description": "Websites/URLs NEVER captured in snapshots (Banking, Email, Login pages)"
|
||||
},
|
||||
"SetMaximumStorageDurationForRecallSnapshots": {
|
||||
"Type": "DWord",
|
||||
"Value": 30,
|
||||
"Description": "Maximum snapshot retention: 30 days (Choices: 30/60/90/180 days, 0=OS default)"
|
||||
},
|
||||
"SetMaximumStorageSpaceForRecallSnapshots": {
|
||||
"Type": "DWord",
|
||||
"Value": 10,
|
||||
"Description": "Maximum snapshot storage: 10 GB (Choices: 10/25/50/75/100/150 GB, 0=OS default)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"3_Windows_Copilot": {
|
||||
"Name": "Windows Copilot (System-Wide AI Assistant - 4-Layer Defense)",
|
||||
"Description": "Microsoft's AI assistant integrated into Windows (chat, suggestions, proactive recommendations)",
|
||||
"Impact": "Copilot completely disabled in UI, taskbar, and search. Hardware Copilot key remapped to Notepad.",
|
||||
"CloudBased": true,
|
||||
"Registry": {
|
||||
"HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsAI": {
|
||||
"TurnOffWindowsCopilot": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "DISABLE Copilot Layer 1 (WindowsAI HKLM)"
|
||||
}
|
||||
},
|
||||
"HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsCopilot": {
|
||||
"TurnOffWindowsCopilot": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "DISABLE Copilot Layer 2 (WindowsCopilot HKLM)"
|
||||
},
|
||||
"ShowCopilotButton": {
|
||||
"Type": "DWord",
|
||||
"Value": 0,
|
||||
"Description": "HIDE Copilot Layer 3 (Taskbar Button Hidden HKLM)"
|
||||
}
|
||||
},
|
||||
"HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\Explorer": {
|
||||
"DisableWindowsCopilot": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "DISABLE Copilot Layer 4 (Explorer Integration HKLM)"
|
||||
}
|
||||
},
|
||||
"HKCU:\\Software\\Policies\\Microsoft\\Windows\\WindowsCopilot": {
|
||||
"TurnOffWindowsCopilot": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "DISABLE Windows Copilot User-scope (HKCU)"
|
||||
},
|
||||
"ShowCopilotButton": {
|
||||
"Type": "DWord",
|
||||
"Value": 0,
|
||||
"Description": "HIDE Copilot Button User-scope (HKCU)"
|
||||
}
|
||||
},
|
||||
"HKCU:\\Software\\Policies\\Microsoft\\Windows\\WindowsAI": {
|
||||
"SetCopilotHardwareKey": {
|
||||
"Type": "String",
|
||||
"Value": "Microsoft.WindowsNotepad_8wekyb3d8bbwe!App",
|
||||
"Description": "REMAP hardware Copilot key to open Notepad instead"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"4_Click_to_Do": {
|
||||
"Name": "Click to Do (Screenshot AI Analysis)",
|
||||
"Description": "Takes on-demand screenshots and analyzes them with AI to suggest actions (copy text, search, call numbers)",
|
||||
"Impact": "Screenshot analysis feature completely disabled",
|
||||
"CloudBased": false,
|
||||
"Registry": {
|
||||
"HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsAI": {
|
||||
"DisableClickToDo": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "DISABLE Click to Do (no screenshot AI analysis, no action suggestions)"
|
||||
}
|
||||
},
|
||||
"HKCU:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsAI": {
|
||||
"DisableClickToDo": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "DISABLE Click to Do (User-scope)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"5_Paint_Cocreator": {
|
||||
"Name": "Paint Cocreator (Cloud-Based Image Generation)",
|
||||
"Description": "Text-to-image generation using cloud AI (type description, AI creates artwork)",
|
||||
"Impact": "Cocreator feature completely removed from Paint app",
|
||||
"CloudBased": true,
|
||||
"Registry": {
|
||||
"HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Paint": {
|
||||
"DisableCocreator": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "DISABLE Paint Cocreator (no AI-generated images from text prompts)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"6_Paint_Generative_Fill": {
|
||||
"Name": "Paint Generative Fill (Cloud-Based AI Editing)",
|
||||
"Description": "AI-powered image editing (fill selected areas with AI-generated content)",
|
||||
"Impact": "Generative Fill feature completely removed from Paint app",
|
||||
"CloudBased": true,
|
||||
"Registry": {
|
||||
"HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Paint": {
|
||||
"DisableGenerativeFill": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "DISABLE Paint Generative Fill (no AI-powered content-aware fill)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"7_Paint_Image_Creator": {
|
||||
"Name": "Paint Image Creator (Cloud-Based AI Art)",
|
||||
"Description": "DALL-E powered AI art generator integrated into Paint",
|
||||
"Impact": "Image Creator feature completely removed from Paint app",
|
||||
"CloudBased": true,
|
||||
"Registry": {
|
||||
"HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Paint": {
|
||||
"DisableImageCreator": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "DISABLE Paint Image Creator (no DALL-E AI art generation)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"8_Notepad_AI": {
|
||||
"Name": "Notepad AI (Copilot Integration - Write, Summarize, Rewrite)",
|
||||
"Description": "GPT-powered AI features in Notepad (generate text, summarize content, rewrite paragraphs)",
|
||||
"Impact": "All AI features completely removed from Notepad app",
|
||||
"CloudBased": true,
|
||||
"RequiresADMX": false,
|
||||
"Note": "ADMX file (WindowsNotepad.admx) is NOT required - registry policy works without it. ADMX only provides GUI visibility in gpedit.msc.",
|
||||
"Registry": {
|
||||
"HKLM:\\SOFTWARE\\Policies\\WindowsNotepad": {
|
||||
"DisableAIFeatures": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "DISABLE all AI features in Notepad (Write, Summarize, Rewrite, Explain) - Microsoft official registry value name"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"9_Settings_Agent": {
|
||||
"Name": "Settings Agent (AI-Powered Settings Search)",
|
||||
"Description": "AI-enhanced natural language search in Windows Settings (understands questions like 'How do I change wallpaper?')",
|
||||
"Impact": "AI search disabled, only classic keyword search remains",
|
||||
"CloudBased": false,
|
||||
"Registry": {
|
||||
"HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsAI": {
|
||||
"DisableSettingsAgent": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "DISABLE Settings AI agent (fallback to classic search without natural language understanding)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"10_Recall_Export_Block": {
|
||||
"Name": "Recall Export Prevention (NEW - KB5055627)",
|
||||
"Description": "Prevents users from exporting Recall snapshots to share with apps/websites (EEA compliance feature)",
|
||||
"Impact": "Export functionality completely disabled, prevents data exfiltration",
|
||||
"CloudBased": false,
|
||||
"Registry": {
|
||||
"HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsAI": {
|
||||
"AllowRecallExport": {
|
||||
"Type": "DWord",
|
||||
"Value": 0,
|
||||
"Description": "PREVENT Recall snapshot export (0=Disabled, 1=Allowed)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"11_Copilot_URI_Handlers": {
|
||||
"Name": "Copilot URI Protocol Handlers (Deep Link Block)",
|
||||
"Description": "Blocks ms-copilot: and ms-edge-copilot: URI handlers that bypass policy restrictions",
|
||||
"Impact": "Prevents launching Copilot via deep links, Start menu search, or third-party apps",
|
||||
"CloudBased": false,
|
||||
"URIHandlers": [
|
||||
"ms-copilot",
|
||||
"ms-edge-copilot"
|
||||
],
|
||||
"Note": "These handlers are in HKEY_CLASSES_ROOT and route Copilot requests to Edge"
|
||||
},
|
||||
|
||||
"12_Edge_Copilot_Sidebar": {
|
||||
"Name": "Microsoft Edge Copilot Sidebar (Browser AI)",
|
||||
"Description": "Disables Copilot integration in Edge browser sidebar",
|
||||
"Impact": "Edge sidebar and Copilot features completely disabled",
|
||||
"CloudBased": true,
|
||||
"Registry": {
|
||||
"HKLM:\\SOFTWARE\\Policies\\Microsoft\\Edge": {
|
||||
"EdgeSidebarEnabled": {
|
||||
"Type": "DWord",
|
||||
"Value": 0,
|
||||
"Description": "DISABLE Edge sidebar completely"
|
||||
},
|
||||
"ShowHubsSidebar": {
|
||||
"Type": "DWord",
|
||||
"Value": 0,
|
||||
"Description": "HIDE sidebar panel"
|
||||
},
|
||||
"HubsSidebarEnabled": {
|
||||
"Type": "DWord",
|
||||
"Value": 0,
|
||||
"Description": "DISABLE Hubs sidebar"
|
||||
},
|
||||
"CopilotPageContext": {
|
||||
"Type": "DWord",
|
||||
"Value": 0,
|
||||
"Description": "PREVENT Copilot from accessing page content"
|
||||
},
|
||||
"CopilotCDPPageContext": {
|
||||
"Type": "DWord",
|
||||
"Value": 0,
|
||||
"Description": "PREVENT Copilot CDP page context"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"13_Region_Policy_Override": {
|
||||
"Name": "IntegratedServicesRegionPolicySet.json Override",
|
||||
"Description": "Modifies Windows regional policy file to disable Copilot globally (bypasses region restrictions)",
|
||||
"Impact": "Copilot disabled at OS level regardless of region setting",
|
||||
"CloudBased": false,
|
||||
"FilePath": "C:\\Windows\\System32\\IntegratedServicesRegionPolicySet.json",
|
||||
"Note": "Requires TakeOwnership of system file - changes 'Windows CoPilot' policy to disabled"
|
||||
},
|
||||
|
||||
"14_Copilot_Network_Block": {
|
||||
"Name": "Copilot Network Block (Hosts File)",
|
||||
"Description": "Blocks Copilot cloud endpoints via hosts file redirect",
|
||||
"Impact": "Web-based Copilot completely unreachable",
|
||||
"CloudBased": true,
|
||||
"HostsEntries": [
|
||||
"copilot.microsoft.com",
|
||||
"www.bing.com/copilot",
|
||||
"edgeservices.bing.com"
|
||||
],
|
||||
"Note": "Optional aggressive blocking - may affect legitimate Bing searches"
|
||||
},
|
||||
|
||||
"15_File_Explorer_AI_Actions": {
|
||||
"Name": "File Explorer AI Actions Menu",
|
||||
"Description": "Hides 'AI Actions' entry from File Explorer right-click context menu (image editing, text summarization, etc.)",
|
||||
"Impact": "AI Actions menu entry removed from Explorer context menu",
|
||||
"CloudBased": false,
|
||||
"Registry": {
|
||||
"HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\Explorer": {
|
||||
"HideAIActionsMenu": {
|
||||
"Type": "DWord",
|
||||
"Value": 1,
|
||||
"Description": "HIDE AI Actions from File Explorer context menu"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"AutomaticallyBlockedByMasterSwitch": {
|
||||
"Description": "These AI features are automatically blocked by the Generative AI Master Switch (no dedicated policies exist)",
|
||||
"Features": [
|
||||
{
|
||||
"Name": "Photos Generative Erase",
|
||||
"Description": "AI-powered object removal from photos"
|
||||
},
|
||||
{
|
||||
"Name": "Photos Background Blur/Remove",
|
||||
"Description": "AI background effects in Photos app"
|
||||
},
|
||||
{
|
||||
"Name": "Photos Auto-Categorization",
|
||||
"Description": "AI-powered photo organization (Receipts, IDs, Screenshots, Notes)"
|
||||
},
|
||||
{
|
||||
"Name": "Snipping Tool AI-OCR",
|
||||
"Description": "Text extraction and actions from screenshots"
|
||||
},
|
||||
{
|
||||
"Name": "Snipping Tool Quick Redact",
|
||||
"Description": "AI-powered sensitive data redaction"
|
||||
},
|
||||
{
|
||||
"Name": "Clipchamp Auto Compose",
|
||||
"Description": "AI-powered automatic video editing"
|
||||
},
|
||||
{
|
||||
"Name": "All Future Generative AI Apps",
|
||||
"Description": "Any app that uses Windows generative AI models"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"Summary": {
|
||||
"TotalAIFeaturesDisabled": "13 features (10 dedicated + 3 advanced blocks + unlimited via master switch)",
|
||||
"TotalPoliciesApplied": 32,
|
||||
"RegistryKeysModified": 32,
|
||||
"URIHandlersBlocked": 2,
|
||||
"RequiresReboot": "Yes (for Recall component removal)",
|
||||
"RequiresADMX": "No (all policies work via registry, no ADMX needed)",
|
||||
"CloudAIBlocked": "All documented Windows 11 25H2 cloud-based AI features",
|
||||
"OnDeviceAIBlocked": "All on-device generative AI models via systemAIModels API",
|
||||
"EdgeAIBlocked": "Copilot sidebar, page context, all AI integrations",
|
||||
"DeepLinksBlocked": "ms-copilot: and ms-edge-copilot: URI protocols",
|
||||
"EnterpriseCompliance": "Maximum (Recall app/URI protection, storage limits, export block)",
|
||||
"MSBestPractice": "Based on OFFICIAL Microsoft registry policies ONLY (no community workarounds)"
|
||||
}
|
||||
}
|
||||
97
Modules/AntiAI/Private/Disable-ClickToDo.ps1
Normal file
97
Modules/AntiAI/Private/Disable-ClickToDo.ps1
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#Requires -Version 5.1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disables Click to Do (screenshot AI analysis).
|
||||
|
||||
.DESCRIPTION
|
||||
Applies DisableClickToDo = 1 policy (Device and User scope).
|
||||
|
||||
Click to Do takes on-demand screenshots and analyzes them with AI to suggest actions:
|
||||
- Extract and copy text
|
||||
- Search for selected content
|
||||
- Call detected phone numbers
|
||||
- Email detected addresses
|
||||
|
||||
Disabling prevents all screenshot AI analysis and action suggestions.
|
||||
|
||||
.EXAMPLE
|
||||
Disable-ClickToDo
|
||||
#>
|
||||
function Disable-ClickToDo {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
Write-Log -Level DEBUG -Message "Disabling Click to Do (screenshot AI analysis)" -Module "AntiAI"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Success = $false
|
||||
Applied = 0
|
||||
Errors = @()
|
||||
}
|
||||
|
||||
try {
|
||||
if ($DryRun) {
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Would disable Click to Do (DisableClickToDo=1)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
$result.Success = $true
|
||||
return $result
|
||||
}
|
||||
|
||||
# Device-scope (HKLM)
|
||||
$devicePath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"
|
||||
if (-not (Test-Path $devicePath)) {
|
||||
New-Item -Path $devicePath -Force | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Created registry path: $devicePath" -Module "AntiAI"
|
||||
}
|
||||
|
||||
$existing = Get-ItemProperty -Path $devicePath -Name "DisableClickToDo" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $devicePath -Name "DisableClickToDo" -Value 1 -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $devicePath -Name "DisableClickToDo" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set DisableClickToDo = 1 (Device-scope)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# User-scope (HKCU)
|
||||
$userPath = "HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"
|
||||
if (-not (Test-Path $userPath)) {
|
||||
New-Item -Path $userPath -Force | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Created registry path: $userPath" -Module "AntiAI"
|
||||
}
|
||||
|
||||
$existing = Get-ItemProperty -Path $userPath -Name "DisableClickToDo" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $userPath -Name "DisableClickToDo" -Value 1 -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $userPath -Name "DisableClickToDo" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set DisableClickToDo = 1 (User-scope)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Verify
|
||||
$deviceValues = Get-ItemProperty -Path $devicePath -ErrorAction SilentlyContinue
|
||||
$userValues = Get-ItemProperty -Path $userPath -ErrorAction SilentlyContinue
|
||||
|
||||
$verified = ($deviceValues.DisableClickToDo -eq 1) -and
|
||||
($userValues.DisableClickToDo -eq 1)
|
||||
|
||||
if ($verified) {
|
||||
Write-Log -Level DEBUG -Message "Verification SUCCESS: Click to Do disabled" -Module "AntiAI"
|
||||
$result.Success = $true
|
||||
}
|
||||
else {
|
||||
$result.Errors += "Verification FAILED: Click to Do policy not applied correctly"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Errors += "Failed to disable Click to Do: $($_.Exception.Message)"
|
||||
Write-Error $result.Errors[-1]
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
250
Modules/AntiAI/Private/Disable-Copilot.ps1
Normal file
250
Modules/AntiAI/Private/Disable-Copilot.ps1
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
#Requires -Version 5.1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disables Windows Copilot system-wide (App Removal + Multi-Layer Policies).
|
||||
|
||||
.DESCRIPTION
|
||||
Complete Copilot removal for Windows 11 24H2/25H2+:
|
||||
|
||||
LAYER 0: APP REMOVAL (NEW - Windows 11 24H2/25H2+)
|
||||
- Removes Copilot AppX packages (current user, all users, provisioned)
|
||||
- Prevents Copilot integration in Paint, Office, and other apps
|
||||
- Microsoft Official: TurnOffWindowsCopilot policy is DEPRECATED in 24H2+
|
||||
- Reference: https://learn.microsoft.com/en-us/windows/client-management/manage-windows-copilot
|
||||
|
||||
LEGACY LAYERS (for older Windows 11 versions):
|
||||
- Layer 1-4: Registry policies (WindowsAI, WindowsCopilot, Explorer)
|
||||
- Layer 5: Hardware key remap to Notepad
|
||||
|
||||
Multi-layer approach ensures maximum compatibility across all Windows 11 versions.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Simulates the operation without making changes.
|
||||
|
||||
.EXAMPLE
|
||||
Disable-Copilot
|
||||
|
||||
.NOTES
|
||||
Requires Administrator privileges.
|
||||
Best Practice: Run with -Verbose to see detailed operation log.
|
||||
#>
|
||||
function Disable-Copilot {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
Write-Log -Level DEBUG -Message "Disabling Windows Copilot (multi-layer defense + app removal)" -Module "AntiAI"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Success = $false
|
||||
Applied = 0
|
||||
Errors = @()
|
||||
CopilotAppRemoved = $false
|
||||
}
|
||||
|
||||
try {
|
||||
if ($DryRun) {
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Would disable Copilot (app removal + policies + hardware key)" -Module "AntiAI"
|
||||
$result.Applied += 8 # 1 app removal + 2 HKLM policies + 1 ShowButton + 1 Explorer + 2 HKCU + 1 HW key
|
||||
$result.CopilotAppRemoved = $true
|
||||
$result.Success = $true
|
||||
return $result
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# LAYER 0: REMOVE COPILOT APP (Windows 11 24H2/25H2+)
|
||||
# ============================================================================
|
||||
# Microsoft official: TurnOffWindowsCopilot policy is DEPRECATED in 24H2+
|
||||
# New method: Uninstall Copilot app completely (prevents in-app integration)
|
||||
# Reference: https://learn.microsoft.com/en-us/windows/client-management/manage-windows-copilot
|
||||
|
||||
Write-Log -Level DEBUG -Message "Layer 0: Removing Copilot app packages..." -Module "AntiAI"
|
||||
|
||||
# Step 1: Remove for current user
|
||||
$copilotPackages = Get-AppxPackage -Name "*Copilot*" -ErrorAction SilentlyContinue
|
||||
if ($copilotPackages) {
|
||||
foreach ($package in $copilotPackages) {
|
||||
try {
|
||||
Remove-AppxPackage -Package $package.PackageFullName -ErrorAction Stop
|
||||
Write-Log -Level DEBUG -Message "Removed Copilot package: $($package.Name)" -Module "AntiAI"
|
||||
$result.CopilotAppRemoved = $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Could not remove package $($package.Name): $($_.Exception.Message)" -Module "AntiAI"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Step 2: Remove for all users (requires admin)
|
||||
$copilotAllUsers = Get-AppxPackage -AllUsers -Name "*Copilot*" -ErrorAction SilentlyContinue
|
||||
if ($copilotAllUsers) {
|
||||
foreach ($package in $copilotAllUsers) {
|
||||
try {
|
||||
Remove-AppxPackage -Package $package.PackageFullName -AllUsers -ErrorAction Stop
|
||||
Write-Log -Level DEBUG -Message "Removed Copilot package (all users): $($package.Name)" -Module "AntiAI"
|
||||
$result.CopilotAppRemoved = $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Could not remove package for all users: $($_.Exception.Message)" -Module "AntiAI"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Step 3: Remove provisioned packages (prevents reinstall for new users)
|
||||
$provisionedCopilot = Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.PackageName -like "*Copilot*" }
|
||||
if ($provisionedCopilot) {
|
||||
foreach ($package in $provisionedCopilot) {
|
||||
try {
|
||||
Remove-AppxProvisionedPackage -Online -PackageName $package.PackageName -ErrorAction Stop | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Removed provisioned Copilot package: $($package.PackageName)" -Module "AntiAI"
|
||||
$result.CopilotAppRemoved = $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Could not remove provisioned package: $($_.Exception.Message)" -Module "AntiAI"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($result.CopilotAppRemoved) {
|
||||
Write-Log -Level DEBUG -Message "Layer 0: Copilot app packages removed successfully" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
}
|
||||
else {
|
||||
Write-Log -Level DEBUG -Message "Layer 0: No Copilot app packages found (already removed or not installed)" -Module "AntiAI"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# LEGACY LAYERS: Registry policies (still needed for older Windows 11 versions)
|
||||
# ============================================================================
|
||||
|
||||
# MULTI-LAYER COPILOT BLOCKING (SecurityBaseline Best Practice)
|
||||
|
||||
# Layer 1: WindowsAI\TurnOffWindowsCopilot (HKLM - machine-wide)
|
||||
$aiPolicyPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"
|
||||
if (-not (Test-Path $aiPolicyPath)) {
|
||||
New-Item -Path $aiPolicyPath -Force | Out-Null
|
||||
}
|
||||
|
||||
$existing = Get-ItemProperty -Path $aiPolicyPath -Name "TurnOffWindowsCopilot" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $aiPolicyPath -Name "TurnOffWindowsCopilot" -Value 1 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $aiPolicyPath -Name "TurnOffWindowsCopilot" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Layer 1: WindowsAI\TurnOffWindowsCopilot (HKLM) = 1" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Layer 2: WindowsCopilot\TurnOffWindowsCopilot (HKLM - legacy path)
|
||||
$copilotPathHKLM = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsCopilot"
|
||||
if (-not (Test-Path $copilotPathHKLM)) {
|
||||
New-Item -Path $copilotPathHKLM -Force | Out-Null
|
||||
}
|
||||
|
||||
$existing = Get-ItemProperty -Path $copilotPathHKLM -Name "TurnOffWindowsCopilot" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $copilotPathHKLM -Name "TurnOffWindowsCopilot" -Value 1 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $copilotPathHKLM -Name "TurnOffWindowsCopilot" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Layer 2: WindowsCopilot\TurnOffWindowsCopilot (HKLM) = 1" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Layer 3: ShowCopilotButton = 0 (Hide taskbar button)
|
||||
$existing = Get-ItemProperty -Path $copilotPathHKLM -Name "ShowCopilotButton" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $copilotPathHKLM -Name "ShowCopilotButton" -Value 0 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $copilotPathHKLM -Name "ShowCopilotButton" -Value 0 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Layer 3: ShowCopilotButton (HKLM) = 0" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Layer 4: Explorer\DisableWindowsCopilot (Block Explorer integration)
|
||||
$explorerPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer"
|
||||
if (-not (Test-Path $explorerPath)) {
|
||||
New-Item -Path $explorerPath -Force | Out-Null
|
||||
}
|
||||
|
||||
$existing = Get-ItemProperty -Path $explorerPath -Name "DisableWindowsCopilot" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $explorerPath -Name "DisableWindowsCopilot" -Value 1 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $explorerPath -Name "DisableWindowsCopilot" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Layer 4: Explorer\DisableWindowsCopilot (HKLM) = 1" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# User-scope policies (HKCU - additional protection)
|
||||
$copilotPathHKCU = "HKCU:\Software\Policies\Microsoft\Windows\WindowsCopilot"
|
||||
if (-not (Test-Path $copilotPathHKCU)) {
|
||||
New-Item -Path $copilotPathHKCU -Force | Out-Null
|
||||
}
|
||||
|
||||
$existing = Get-ItemProperty -Path $copilotPathHKCU -Name "TurnOffWindowsCopilot" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $copilotPathHKCU -Name "TurnOffWindowsCopilot" -Value 1 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $copilotPathHKCU -Name "TurnOffWindowsCopilot" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "User-scope: WindowsCopilot\TurnOffWindowsCopilot (HKCU) = 1" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
$existing = Get-ItemProperty -Path $copilotPathHKCU -Name "ShowCopilotButton" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $copilotPathHKCU -Name "ShowCopilotButton" -Value 0 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $copilotPathHKCU -Name "ShowCopilotButton" -Value 0 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "User-scope: ShowCopilotButton (HKCU) = 0" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Layer 5: Remap hardware Copilot key to Notepad (neutralize dedicated key)
|
||||
$aiPathHKCU = "HKCU:\Software\Policies\Microsoft\Windows\WindowsAI"
|
||||
if (-not (Test-Path $aiPathHKCU)) {
|
||||
New-Item -Path $aiPathHKCU -Force | Out-Null
|
||||
}
|
||||
|
||||
$notepadAUMID = "Microsoft.WindowsNotepad_8wekyb3d8bbwe!App"
|
||||
$existing = Get-ItemProperty -Path $aiPathHKCU -Name "SetCopilotHardwareKey" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $aiPathHKCU -Name "SetCopilotHardwareKey" -Value $notepadAUMID -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $aiPathHKCU -Name "SetCopilotHardwareKey" -Value $notepadAUMID -PropertyType String -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Layer 5: Hardware Copilot key remapped to Notepad" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Verify all layers
|
||||
$aiHKLM = Get-ItemProperty -Path $aiPolicyPath -ErrorAction SilentlyContinue
|
||||
$copilotHKLM = Get-ItemProperty -Path $copilotPathHKLM -ErrorAction SilentlyContinue
|
||||
$explorerHKLM = Get-ItemProperty -Path $explorerPath -ErrorAction SilentlyContinue
|
||||
$copilotHKCU = Get-ItemProperty -Path $copilotPathHKCU -ErrorAction SilentlyContinue
|
||||
$aiHKCU = Get-ItemProperty -Path $aiPathHKCU -ErrorAction SilentlyContinue
|
||||
|
||||
$verified = ($aiHKLM.TurnOffWindowsCopilot -eq 1) -and
|
||||
($copilotHKLM.TurnOffWindowsCopilot -eq 1) -and
|
||||
($copilotHKLM.ShowCopilotButton -eq 0) -and
|
||||
($explorerHKLM.DisableWindowsCopilot -eq 1) -and
|
||||
($copilotHKCU.TurnOffWindowsCopilot -eq 1) -and
|
||||
($copilotHKCU.ShowCopilotButton -eq 0) -and
|
||||
($aiHKCU.SetCopilotHardwareKey -eq $notepadAUMID)
|
||||
|
||||
if ($verified) {
|
||||
Write-Log -Level DEBUG -Message "Verification SUCCESS: All Copilot policies configured (MS official keys only)" -Module "AntiAI"
|
||||
$result.Success = $true
|
||||
}
|
||||
else {
|
||||
$result.Errors += "Verification FAILED: Not all Copilot policies applied correctly"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Errors += "Failed to disable Copilot: $($_.Exception.Message)"
|
||||
Write-Error $result.Errors[-1]
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
206
Modules/AntiAI/Private/Disable-CopilotAdvanced.ps1
Normal file
206
Modules/AntiAI/Private/Disable-CopilotAdvanced.ps1
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
#Requires -Version 5.1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Advanced Copilot blocking - URI handlers, Edge sidebar, region policy, network block.
|
||||
|
||||
.DESCRIPTION
|
||||
Multi-layer advanced Copilot blocking for Windows 11 24H2/25H2+:
|
||||
|
||||
LAYER 1: RECALL EXPORT BLOCK (KB5055627)
|
||||
- AllowRecallExport = 0 (prevents snapshot export)
|
||||
|
||||
LAYER 2: URI PROTOCOL HANDLERS
|
||||
- Blocks ms-copilot: and ms-edge-copilot: deep links
|
||||
- Prevents Start menu search and third-party app launching
|
||||
|
||||
LAYER 3: EDGE COPILOT SIDEBAR
|
||||
- Disables sidebar completely
|
||||
- Blocks page context access
|
||||
- 5 registry policies
|
||||
|
||||
LAYER 4: REGION POLICY OVERRIDE (Optional)
|
||||
- Modifies IntegratedServicesRegionPolicySet.json
|
||||
- Disables Copilot at OS level regardless of region
|
||||
|
||||
LAYER 5: NETWORK BLOCK (Optional)
|
||||
- Hosts file redirect for copilot endpoints
|
||||
|
||||
.PARAMETER DryRun
|
||||
Simulates the operation without making changes.
|
||||
|
||||
.PARAMETER SkipNetworkBlock
|
||||
Skip hosts file modification (less aggressive).
|
||||
|
||||
.PARAMETER SkipRegionPolicy
|
||||
Skip IntegratedServicesRegionPolicySet.json modification.
|
||||
|
||||
.EXAMPLE
|
||||
Disable-CopilotAdvanced
|
||||
|
||||
.NOTES
|
||||
Requires Administrator privileges.
|
||||
Part of NoID Privacy AntiAI Module v2.2.0
|
||||
#>
|
||||
function Disable-CopilotAdvanced {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
Write-Log -Level DEBUG -Message "Disabling Copilot (Advanced Layers)" -Module "AntiAI"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Success = $false
|
||||
Applied = 0
|
||||
Errors = @()
|
||||
RecallExportBlocked = $false
|
||||
URIHandlersBlocked = $false
|
||||
EdgeSidebarDisabled = $false
|
||||
}
|
||||
|
||||
try {
|
||||
if ($DryRun) {
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Would apply advanced Copilot blocks" -Module "AntiAI"
|
||||
$result.Applied = 3 # 3 official MS features: RecallExport, URIHandlers, EdgeSidebar
|
||||
$result.Success = $true
|
||||
return $result
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# LAYER 1: RECALL EXPORT BLOCK (KB5055627 - NEW)
|
||||
# ============================================================================
|
||||
Write-Log -Level DEBUG -Message "Layer 1: Blocking Recall Export..." -Module "AntiAI"
|
||||
|
||||
$aiPolicyPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"
|
||||
if (-not (Test-Path $aiPolicyPath)) {
|
||||
New-Item -Path $aiPolicyPath -Force | Out-Null
|
||||
}
|
||||
|
||||
try {
|
||||
$existing = Get-ItemProperty -Path $aiPolicyPath -Name "AllowRecallExport" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $aiPolicyPath -Name "AllowRecallExport" -Value 0 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $aiPolicyPath -Name "AllowRecallExport" -Value 0 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "AllowRecallExport = 0 (export disabled)" -Module "AntiAI"
|
||||
$result.RecallExportBlocked = $true
|
||||
$result.Applied++
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Failed to set AllowRecallExport: $_" -Module "AntiAI"
|
||||
$result.Errors += "AllowRecallExport: $_"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# LAYER 2: URI PROTOCOL HANDLERS (ms-copilot:, ms-edge-copilot:)
|
||||
# ============================================================================
|
||||
Write-Log -Level DEBUG -Message "Layer 2: Blocking URI handlers..." -Module "AntiAI"
|
||||
|
||||
$uriHandlers = @("ms-copilot", "ms-edge-copilot")
|
||||
$uriBlocked = 0
|
||||
|
||||
foreach ($handler in $uriHandlers) {
|
||||
$handlerPath = "Registry::HKEY_CLASSES_ROOT\$handler"
|
||||
|
||||
try {
|
||||
if (Test-Path $handlerPath) {
|
||||
# Rename the key to disable it (preserves for restore)
|
||||
$backupPath = "Registry::HKEY_CLASSES_ROOT\${handler}_DISABLED_BY_NOID"
|
||||
|
||||
# Check if already disabled
|
||||
if (-not (Test-Path $backupPath)) {
|
||||
# Delete the original handler (blocks the protocol)
|
||||
Remove-Item -Path $handlerPath -Recurse -Force -ErrorAction Stop
|
||||
|
||||
# Create marker for restore
|
||||
New-Item -Path $backupPath -Force | Out-Null
|
||||
New-ItemProperty -Path $backupPath -Name "OriginallyExisted" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
New-ItemProperty -Path $backupPath -Name "DisabledBy" -Value "NoID Privacy AntiAI" -PropertyType String -Force | Out-Null
|
||||
New-ItemProperty -Path $backupPath -Name "DisabledAt" -Value (Get-Date -Format "o") -PropertyType String -Force | Out-Null
|
||||
|
||||
Write-Log -Level DEBUG -Message "Blocked URI handler: $handler" -Module "AntiAI"
|
||||
$uriBlocked++
|
||||
}
|
||||
else {
|
||||
Write-Log -Level DEBUG -Message "URI handler already blocked: $handler" -Module "AntiAI"
|
||||
$uriBlocked++
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Log -Level DEBUG -Message "URI handler not found (already removed): $handler" -Module "AntiAI"
|
||||
$uriBlocked++
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Failed to block URI handler $handler : $_" -Module "AntiAI"
|
||||
$result.Errors += "URI $handler : $_"
|
||||
}
|
||||
}
|
||||
|
||||
if ($uriBlocked -gt 0) {
|
||||
$result.URIHandlersBlocked = $true
|
||||
$result.Applied++
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# LAYER 3: EDGE COPILOT SIDEBAR
|
||||
# ============================================================================
|
||||
Write-Log -Level DEBUG -Message "Layer 3: Disabling Edge Copilot Sidebar..." -Module "AntiAI"
|
||||
|
||||
$edgePolicyPath = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"
|
||||
if (-not (Test-Path $edgePolicyPath)) {
|
||||
New-Item -Path $edgePolicyPath -Force | Out-Null
|
||||
}
|
||||
|
||||
$edgePolicies = @(
|
||||
@{ Name = "EdgeSidebarEnabled"; Value = 0; Desc = "Edge sidebar" },
|
||||
@{ Name = "ShowHubsSidebar"; Value = 0; Desc = "Hubs sidebar visibility" },
|
||||
@{ Name = "HubsSidebarEnabled"; Value = 0; Desc = "Hubs sidebar" },
|
||||
@{ Name = "CopilotPageContext"; Value = 0; Desc = "Copilot page context" },
|
||||
@{ Name = "CopilotCDPPageContext"; Value = 0; Desc = "Copilot CDP context" }
|
||||
)
|
||||
|
||||
$edgeApplied = 0
|
||||
foreach ($policy in $edgePolicies) {
|
||||
try {
|
||||
$existing = Get-ItemProperty -Path $edgePolicyPath -Name $policy.Name -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $edgePolicyPath -Name $policy.Name -Value $policy.Value -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $edgePolicyPath -Name $policy.Name -Value $policy.Value -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Edge: $($policy.Name) = $($policy.Value)" -Module "AntiAI"
|
||||
$edgeApplied++
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Failed to set Edge policy $($policy.Name): $_" -Module "AntiAI"
|
||||
}
|
||||
}
|
||||
|
||||
if ($edgeApplied -eq $edgePolicies.Count) {
|
||||
$result.EdgeSidebarDisabled = $true
|
||||
$result.Applied++
|
||||
}
|
||||
|
||||
# NOTE: Layer 4 (RegionPolicy) and Layer 5 (NetworkBlock) REMOVED
|
||||
# Reason: NOT Microsoft Best Practice
|
||||
# - IntegratedServicesRegionPolicySet.json: Community workaround, can break with updates
|
||||
# - Hosts file blocking: "Not officially supported" per Microsoft Q&A
|
||||
# We only use official Registry Policies as per MS documentation
|
||||
|
||||
# Determine overall success
|
||||
$result.Success = ($result.RecallExportBlocked -or $result.URIHandlersBlocked -or
|
||||
$result.EdgeSidebarDisabled) -and ($result.Errors.Count -eq 0)
|
||||
|
||||
Write-Log -Level DEBUG -Message "Advanced Copilot blocks applied: $($result.Applied)" -Module "AntiAI"
|
||||
}
|
||||
catch {
|
||||
$result.Errors += "Critical error: $($_.Exception.Message)"
|
||||
Write-Error $result.Errors[-1]
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
76
Modules/AntiAI/Private/Disable-ExplorerAI.ps1
Normal file
76
Modules/AntiAI/Private/Disable-ExplorerAI.ps1
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#Requires -Version 5.1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disables AI Actions in File Explorer context menu.
|
||||
|
||||
.DESCRIPTION
|
||||
Applies HideAIActionsMenu = 1 policy.
|
||||
|
||||
File Explorer AI Actions provides AI-powered features in the right-click menu:
|
||||
- Image editing with AI (background removal, effects)
|
||||
- Text summarization
|
||||
- AI-powered file actions
|
||||
|
||||
Disabling removes the "AI Actions" entry from the File Explorer context menu.
|
||||
|
||||
.EXAMPLE
|
||||
Disable-ExplorerAI
|
||||
#>
|
||||
function Disable-ExplorerAI {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
Write-Log -Level DEBUG -Message "Disabling File Explorer AI Actions Menu" -Module "AntiAI"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Success = $false
|
||||
Applied = 0
|
||||
Errors = @()
|
||||
}
|
||||
|
||||
try {
|
||||
if ($DryRun) {
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Would disable Explorer AI Actions (HideAIActionsMenu=1)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
$result.Success = $true
|
||||
return $result
|
||||
}
|
||||
|
||||
$regPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer"
|
||||
|
||||
if (-not (Test-Path $regPath)) {
|
||||
New-Item -Path $regPath -Force | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Created registry path: $regPath" -Module "AntiAI"
|
||||
}
|
||||
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "HideAIActionsMenu" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "HideAIActionsMenu" -Value 1 -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "HideAIActionsMenu" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set HideAIActionsMenu = 1 (AI Actions hidden from Explorer context menu)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Verify
|
||||
$values = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue
|
||||
|
||||
if ($values.HideAIActionsMenu -eq 1) {
|
||||
Write-Log -Level DEBUG -Message "Verification SUCCESS: Explorer AI Actions disabled" -Module "AntiAI"
|
||||
$result.Success = $true
|
||||
}
|
||||
else {
|
||||
$result.Errors += "Verification FAILED: Explorer AI Actions policy not applied"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Errors += "Failed to disable Explorer AI Actions: $($_.Exception.Message)"
|
||||
Write-Error $result.Errors[-1]
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
87
Modules/AntiAI/Private/Disable-NotepadAI.ps1
Normal file
87
Modules/AntiAI/Private/Disable-NotepadAI.ps1
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#Requires -Version 5.1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disables all AI features in Notepad (Write, Summarize, Rewrite, Explain).
|
||||
|
||||
.DESCRIPTION
|
||||
Applies DisableAIFeatures = 1 policy (Microsoft official registry value name).
|
||||
|
||||
Notepad AI features (GPT-powered):
|
||||
- Write: Generate text from prompts
|
||||
- Summarize: Condense long text into key points
|
||||
- Rewrite: Rephrase text in different styles (formal, casual, professional)
|
||||
- Explain: Clarify complex text
|
||||
|
||||
All features are cloud-based and require Copilot integration.
|
||||
|
||||
WARNING: Requires WindowsNotepad.admx for Group Policy (not required for direct registry).
|
||||
ADMX Download: https://download.microsoft.com/download/72ea16a9-4cc9-4032-945d-3a56a483d034/WindowsNotepadAdminTemplates.cab
|
||||
|
||||
.EXAMPLE
|
||||
Disable-NotepadAI
|
||||
#>
|
||||
function Disable-NotepadAI {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
Write-Log -Level DEBUG -Message "Disabling Notepad AI features (Write, Summarize, Rewrite, Explain)" -Module "AntiAI"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Success = $false
|
||||
Applied = 0
|
||||
Errors = @()
|
||||
Warnings = @()
|
||||
}
|
||||
|
||||
try {
|
||||
if ($DryRun) {
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Would disable Notepad AI (DisableAIFeatures=1)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
$result.Success = $true
|
||||
return $result
|
||||
}
|
||||
|
||||
$regPath = "HKLM:\SOFTWARE\Policies\WindowsNotepad"
|
||||
|
||||
if (-not (Test-Path $regPath)) {
|
||||
New-Item -Path $regPath -Force | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Created registry path: $regPath" -Module "AntiAI"
|
||||
}
|
||||
|
||||
# CRITICAL: Value name is "DisableAIFeatures" (NOT "DisableAIFeaturesInNotepad")
|
||||
# Microsoft official registry value name from WindowsNotepad ADMX
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "DisableAIFeatures" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "DisableAIFeatures" -Value 1 -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "DisableAIFeatures" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set DisableAIFeatures = 1 (All AI features disabled - Write/Summarize/Rewrite/Explain)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Note: WindowsNotepad.admx is NOT required - registry policy is fully effective without it
|
||||
# ADMX only provides GUI visibility in gpedit.msc, which is irrelevant for scripted deployment
|
||||
Write-Log -Level DEBUG -Message "Notepad AI disabled via registry policy (no ADMX required)" -Module "AntiAI"
|
||||
|
||||
# Verify with correct value name
|
||||
$values = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue
|
||||
|
||||
if ($values.DisableAIFeatures -eq 1) {
|
||||
Write-Log -Level DEBUG -Message "Verification SUCCESS: Notepad AI disabled (DisableAIFeatures=1)" -Module "AntiAI"
|
||||
$result.Success = $true
|
||||
}
|
||||
else {
|
||||
$result.Errors += "Verification FAILED: Notepad AI policy not applied (DisableAIFeatures not set)"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Errors += "Failed to disable Notepad AI: $($_.Exception.Message)"
|
||||
Write-Error $result.Errors[-1]
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
104
Modules/AntiAI/Private/Disable-PaintAI.ps1
Normal file
104
Modules/AntiAI/Private/Disable-PaintAI.ps1
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#Requires -Version 5.1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disables all AI features in Paint (Cocreator, Generative Fill, Image Creator).
|
||||
|
||||
.DESCRIPTION
|
||||
Applies 3 Paint AI policies:
|
||||
1. DisableCocreator = 1 (Text-to-image generation)
|
||||
2. DisableGenerativeFill = 1 (AI-powered content-aware fill)
|
||||
3. DisableImageCreator = 1 (DALL-E art generator)
|
||||
|
||||
Paint AI features (cloud-based):
|
||||
- Cocreator: Type description, AI generates artwork (e.g., "sunset over mountains")
|
||||
- Generative Fill: Select area, AI fills with contextual content
|
||||
- Image Creator: DALL-E powered AI art generation
|
||||
|
||||
All features require internet connection and send data to Microsoft cloud.
|
||||
|
||||
.EXAMPLE
|
||||
Disable-PaintAI
|
||||
#>
|
||||
function Disable-PaintAI {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
Write-Log -Level DEBUG -Message "Disabling Paint AI features (Cocreator, Generative Fill, Image Creator)" -Module "AntiAI"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Success = $false
|
||||
Applied = 0
|
||||
Errors = @()
|
||||
}
|
||||
|
||||
try {
|
||||
if ($DryRun) {
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Would disable Paint AI (Cocreator, GenerativeFill, ImageCreator)" -Module "AntiAI"
|
||||
$result.Applied += 3
|
||||
$result.Success = $true
|
||||
return $result
|
||||
}
|
||||
|
||||
$regPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Paint"
|
||||
|
||||
if (-not (Test-Path $regPath)) {
|
||||
New-Item -Path $regPath -Force | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Created registry path: $regPath" -Module "AntiAI"
|
||||
}
|
||||
|
||||
# 1. Disable Cocreator (text-to-image)
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "DisableCocreator" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "DisableCocreator" -Value 1 -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "DisableCocreator" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set DisableCocreator = 1 (Text-to-image generation disabled)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# 2. Disable Generative Fill (AI content-aware fill)
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "DisableGenerativeFill" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "DisableGenerativeFill" -Value 1 -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "DisableGenerativeFill" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set DisableGenerativeFill = 1 (AI content-aware fill disabled)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# 3. Disable Image Creator (DALL-E art generator)
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "DisableImageCreator" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "DisableImageCreator" -Value 1 -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "DisableImageCreator" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set DisableImageCreator = 1 (DALL-E art generation disabled)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Verify
|
||||
$values = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue
|
||||
|
||||
$verified = ($values.DisableCocreator -eq 1) -and
|
||||
($values.DisableGenerativeFill -eq 1) -and
|
||||
($values.DisableImageCreator -eq 1)
|
||||
|
||||
if ($verified) {
|
||||
Write-Log -Level DEBUG -Message "Verification SUCCESS: All Paint AI features disabled" -Module "AntiAI"
|
||||
$result.Success = $true
|
||||
}
|
||||
else {
|
||||
$result.Errors += "Verification FAILED: Not all Paint AI policies were applied"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Errors += "Failed to disable Paint AI: $($_.Exception.Message)"
|
||||
Write-Error $result.Errors[-1]
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
120
Modules/AntiAI/Private/Disable-Recall.ps1
Normal file
120
Modules/AntiAI/Private/Disable-Recall.ps1
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
#Requires -Version 5.1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disables Windows Recall completely (component removal + snapshots + data providers).
|
||||
|
||||
.DESCRIPTION
|
||||
Applies 3 core Recall policies:
|
||||
1. AllowRecallEnablement = 0 (Removes Recall component, deletes existing snapshots, requires reboot)
|
||||
2. DisableAIDataAnalysis = 1 (Prevents new snapshots - Device and User scope)
|
||||
3. DisableRecallDataProviders = 1 (Disables background data providers - Enterprise/Education)
|
||||
|
||||
WARNING: Requires system reboot for Recall component removal to take effect!
|
||||
|
||||
.EXAMPLE
|
||||
Disable-Recall
|
||||
#>
|
||||
function Disable-Recall {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
Write-Log -Level DEBUG -Message "Disabling Windows Recall (component + snapshots + providers)" -Module "AntiAI"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Success = $false
|
||||
Applied = 0
|
||||
Errors = @()
|
||||
RequiresReboot = $true
|
||||
}
|
||||
|
||||
try {
|
||||
if ($DryRun) {
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Would disable Recall (AllowRecallEnablement=0, DisableAIDataAnalysis=1)" -Module "AntiAI"
|
||||
$result.Success = $true
|
||||
return $result
|
||||
}
|
||||
|
||||
# Device-scope policies (HKLM)
|
||||
$devicePath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"
|
||||
if (-not (Test-Path $devicePath)) {
|
||||
New-Item -Path $devicePath -Force | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Created registry path: $devicePath" -Module "AntiAI"
|
||||
}
|
||||
|
||||
# 1. Remove Recall component (deletes bits + existing snapshots)
|
||||
$existing = Get-ItemProperty -Path $devicePath -Name "AllowRecallEnablement" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $devicePath -Name "AllowRecallEnablement" -Value 0 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $devicePath -Name "AllowRecallEnablement" -Value 0 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set AllowRecallEnablement = 0 (Recall component will be removed on reboot)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# 2. Disable AI data analysis (Device-scope)
|
||||
$existing = Get-ItemProperty -Path $devicePath -Name "DisableAIDataAnalysis" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $devicePath -Name "DisableAIDataAnalysis" -Value 1 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $devicePath -Name "DisableAIDataAnalysis" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set DisableAIDataAnalysis = 1 (Device-scope - no new snapshots)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# User-scope policies (HKCU)
|
||||
$userPath = "HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"
|
||||
if (-not (Test-Path $userPath)) {
|
||||
New-Item -Path $userPath -Force | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Created registry path: $userPath" -Module "AntiAI"
|
||||
}
|
||||
|
||||
# 3. Disable AI data analysis (User-scope)
|
||||
$existing = Get-ItemProperty -Path $userPath -Name "DisableAIDataAnalysis" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $userPath -Name "DisableAIDataAnalysis" -Value 1 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $userPath -Name "DisableAIDataAnalysis" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set DisableAIDataAnalysis = 1 (User-scope - no new snapshots)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# 4. Disable Recall data providers (Enterprise/Education only, User-scope)
|
||||
$existing = Get-ItemProperty -Path $userPath -Name "DisableRecallDataProviders" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $userPath -Name "DisableRecallDataProviders" -Value 1 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $userPath -Name "DisableRecallDataProviders" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set DisableRecallDataProviders = 1 (Background data providers disabled)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Verify
|
||||
$deviceValues = Get-ItemProperty -Path $devicePath -ErrorAction SilentlyContinue
|
||||
$userValues = Get-ItemProperty -Path $userPath -ErrorAction SilentlyContinue
|
||||
|
||||
$verified = ($deviceValues.AllowRecallEnablement -eq 0) -and
|
||||
($deviceValues.DisableAIDataAnalysis -eq 1) -and
|
||||
($userValues.DisableAIDataAnalysis -eq 1) -and
|
||||
($userValues.DisableRecallDataProviders -eq 1)
|
||||
|
||||
if ($verified) {
|
||||
Write-Log -Level DEBUG -Message "Verification SUCCESS: All Recall policies applied" -Module "AntiAI"
|
||||
Write-Host "" # Ensure warning appears on new line
|
||||
Write-Warning "REBOOT REQUIRED to remove Recall component and delete existing snapshots!"
|
||||
$result.Success = $true
|
||||
}
|
||||
else {
|
||||
$result.Errors += "Verification FAILED: Not all Recall policies were applied correctly"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Errors += "Failed to disable Recall: $($_.Exception.Message)"
|
||||
Write-Error $result.Errors[-1]
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
77
Modules/AntiAI/Private/Disable-SettingsAgent.ps1
Normal file
77
Modules/AntiAI/Private/Disable-SettingsAgent.ps1
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#Requires -Version 5.1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disables Settings Agent (AI-powered Settings search).
|
||||
|
||||
.DESCRIPTION
|
||||
Applies DisableSettingsAgent = 1 policy.
|
||||
|
||||
Settings Agent provides AI-enhanced natural language search in Windows Settings.
|
||||
Examples of AI features:
|
||||
- Understanding questions: "How do I change my wallpaper?"
|
||||
- Contextual suggestions: "Change background" -> Desktop personalization
|
||||
- Intelligent search results with natural language processing
|
||||
|
||||
Disabling falls back to classic keyword search without AI understanding.
|
||||
|
||||
.EXAMPLE
|
||||
Disable-SettingsAgent
|
||||
#>
|
||||
function Disable-SettingsAgent {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
Write-Log -Level DEBUG -Message "Disabling Settings Agent (AI-powered search)" -Module "AntiAI"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Success = $false
|
||||
Applied = 0
|
||||
Errors = @()
|
||||
}
|
||||
|
||||
try {
|
||||
if ($DryRun) {
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Would disable Settings Agent (DisableAISettingsAgent=1)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
$result.Success = $true
|
||||
return $result
|
||||
}
|
||||
|
||||
$regPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"
|
||||
|
||||
if (-not (Test-Path $regPath)) {
|
||||
New-Item -Path $regPath -Force | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Created registry path: $regPath" -Module "AntiAI"
|
||||
}
|
||||
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "DisableSettingsAgent" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "DisableSettingsAgent" -Value 1 -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "DisableSettingsAgent" -Value 1 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set DisableSettingsAgent = 1 (AI search disabled, fallback to classic)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Verify
|
||||
$values = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue
|
||||
|
||||
if ($values.DisableSettingsAgent -eq 1) {
|
||||
Write-Log -Level DEBUG -Message "Verification SUCCESS: Settings Agent disabled" -Module "AntiAI"
|
||||
$result.Success = $true
|
||||
}
|
||||
else {
|
||||
$result.Errors += "Verification FAILED: Settings Agent policy not applied"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Errors += "Failed to disable Settings Agent: $($_.Exception.Message)"
|
||||
Write-Error $result.Errors[-1]
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
130
Modules/AntiAI/Private/Set-RecallProtection.ps1
Normal file
130
Modules/AntiAI/Private/Set-RecallProtection.ps1
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#Requires -Version 5.1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Applies enterprise-grade Recall protection (app/URI deny lists, storage limits).
|
||||
|
||||
.DESCRIPTION
|
||||
Configures 4 additional Recall policies for maximum data protection:
|
||||
1. SetDenyAppListForRecall - Apps never captured in snapshots (Browser, Terminal, Password managers, RDP)
|
||||
2. SetDenyUriListForRecall - Websites/URLs never captured (Banking, Email, Login pages)
|
||||
3. SetMaximumStorageDurationForRecallSnapshots - Max retention: 30 days
|
||||
4. SetMaximumStorageSpaceForRecallSnapshots - Max storage: 10 GB
|
||||
|
||||
Note: These are additional protection layers BEYOND core Recall disable policies.
|
||||
Even though Recall is disabled, these provide defense-in-depth.
|
||||
|
||||
.EXAMPLE
|
||||
Set-RecallProtection
|
||||
#>
|
||||
function Set-RecallProtection {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
Write-Log -Level DEBUG -Message "Applying Recall enterprise protection (deny lists + storage limits)" -Module "AntiAI"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Success = $false
|
||||
Applied = 0
|
||||
Errors = @()
|
||||
}
|
||||
|
||||
try {
|
||||
if ($DryRun) {
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Would set Recall protection (Deny lists + Storage limits)" -Module "AntiAI"
|
||||
$result.Success = $true
|
||||
return $result
|
||||
}
|
||||
|
||||
$regPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"
|
||||
|
||||
if (-not (Test-Path $regPath)) {
|
||||
New-Item -Path $regPath -Force | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Created registry path: $regPath" -Module "AntiAI"
|
||||
}
|
||||
|
||||
# 1. App Deny List - Critical apps never captured in snapshots
|
||||
$denyApps = @(
|
||||
"Microsoft.MicrosoftEdge_8wekyb3d8bbwe!App", # Edge Browser (Banking, passwords)
|
||||
"Microsoft.WindowsTerminal_8wekyb3d8bbwe!App", # Terminal (CLI passwords, keys)
|
||||
"KeePassXC_8wekyb3d8bbwe!KeePassXC", # Password Manager
|
||||
"Microsoft.RemoteDesktop_8wekyb3d8bbwe!App" # RDP (remote system access)
|
||||
)
|
||||
|
||||
# Store as proper MultiString (string array) so policies are visible to compliance checks
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "SetDenyAppListForRecall" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "SetDenyAppListForRecall" -Value $denyApps -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "SetDenyAppListForRecall" -Value $denyApps -PropertyType MultiString -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set App Deny List: $($denyApps.Count) critical apps protected" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# 2. URI Deny List - Critical websites never captured in snapshots
|
||||
$denyUris = @(
|
||||
"*.bank.*", # All banking sites
|
||||
"*.paypal.*", # Payment processor
|
||||
"*.bankofamerica.*", # Major bank
|
||||
"mail.*", # Email sites
|
||||
"webmail.*", # Webmail sites
|
||||
"*password*", # Any password-related pages
|
||||
"*login*" # Any login pages
|
||||
)
|
||||
|
||||
# Store as MultiString using string array
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "SetDenyUriListForRecall" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "SetDenyUriListForRecall" -Value $denyUris -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "SetDenyUriListForRecall" -Value $denyUris -PropertyType MultiString -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set URI Deny List: $($denyUris.Count) URL patterns protected" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# 3. Storage Duration Limit - Max 30 days retention
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "SetMaximumStorageDurationForRecallSnapshots" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "SetMaximumStorageDurationForRecallSnapshots" -Value 30 -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "SetMaximumStorageDurationForRecallSnapshots" -Value 30 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set max snapshot retention: 30 days" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# 4. Storage Space Limit - Max 10 GB
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "SetMaximumStorageSpaceForRecallSnapshots" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "SetMaximumStorageSpaceForRecallSnapshots" -Value 10 -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "SetMaximumStorageSpaceForRecallSnapshots" -Value 10 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set max snapshot storage: 10 GB" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Verify
|
||||
$values = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue
|
||||
|
||||
$verified = ($null -ne $values.SetDenyAppListForRecall) -and
|
||||
($null -ne $values.SetDenyUriListForRecall) -and
|
||||
($values.SetMaximumStorageDurationForRecallSnapshots -eq 30) -and
|
||||
($values.SetMaximumStorageSpaceForRecallSnapshots -eq 10)
|
||||
|
||||
if ($verified) {
|
||||
Write-Log -Level DEBUG -Message "Verification SUCCESS: All Recall protection policies applied" -Module "AntiAI"
|
||||
$result.Success = $true
|
||||
}
|
||||
else {
|
||||
$result.Errors += "Verification FAILED: Not all Recall protection policies were applied"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Errors += "Failed to apply Recall protection: $($_.Exception.Message)"
|
||||
Write-Error $result.Errors[-1]
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
118
Modules/AntiAI/Private/Set-SystemAIModels.ps1
Normal file
118
Modules/AntiAI/Private/Set-SystemAIModels.ps1
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#Requires -Version 5.1
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Sets the Generative AI Master Switch to block all apps from using AI models.
|
||||
|
||||
.DESCRIPTION
|
||||
Configures LetAppsAccessSystemAIModels = 2 (Force Deny) to prevent ALL apps from
|
||||
accessing Windows on-device generative AI models (text and image generation).
|
||||
|
||||
This master switch automatically blocks:
|
||||
- Notepad AI (Write, Summarize, Rewrite)
|
||||
- Paint AI (Cocreator, Generative Fill unless specifically disabled)
|
||||
- Photos AI (Generative Erase, Background effects, Auto-categorization)
|
||||
- Clipchamp AI (Auto Compose)
|
||||
- Snipping Tool AI (OCR, Quick Redact)
|
||||
- All future apps that use generative AI
|
||||
|
||||
.EXAMPLE
|
||||
Set-SystemAIModels
|
||||
#>
|
||||
function Set-SystemAIModels {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
Write-Log -Level DEBUG -Message "Setting Generative AI Master Switch (Force Deny all apps)" -Module "AntiAI"
|
||||
|
||||
$result = [PSCustomObject]@{
|
||||
Success = $false
|
||||
Applied = 0
|
||||
Errors = @()
|
||||
}
|
||||
|
||||
try {
|
||||
# 1. Set AppPrivacy Master Switch
|
||||
$regPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy"
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Would set $regPath\LetAppsAccessSystemAIModels = 2" -Module "AntiAI"
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Would set $regPath\LetAppsAccessGenerativeAI = 2" -Module "AntiAI"
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Would set CapabilityAccessManager\systemAIModels = Deny" -Module "AntiAI"
|
||||
$result.Success = $true
|
||||
return $result
|
||||
}
|
||||
|
||||
# Ensure registry path exists
|
||||
if (-not (Test-Path $regPath)) {
|
||||
New-Item -Path $regPath -Force | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Created registry path: $regPath" -Module "AntiAI"
|
||||
}
|
||||
|
||||
# Set master switch: 2 = Force Deny (no app can access generative AI)
|
||||
$existing = Get-ItemProperty -Path $regPath -Name "LetAppsAccessSystemAIModels" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $regPath -Name "LetAppsAccessSystemAIModels" -Value 2 -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "LetAppsAccessSystemAIModels" -Value 2 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set LetAppsAccessSystemAIModels = 2 (Force Deny)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Set app-level Generative AI access: 2 = Force Deny (Text & Image Generation in Settings)
|
||||
$existing2 = Get-ItemProperty -Path $regPath -Name "LetAppsAccessGenerativeAI" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing2) {
|
||||
Set-ItemProperty -Path $regPath -Name "LetAppsAccessGenerativeAI" -Value 2 -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $regPath -Name "LetAppsAccessGenerativeAI" -Value 2 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set LetAppsAccessGenerativeAI = 2 (Force Deny)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Verify AppPrivacy switches
|
||||
$value = Get-ItemProperty -Path $regPath -Name "LetAppsAccessSystemAIModels" -ErrorAction SilentlyContinue
|
||||
$value2 = Get-ItemProperty -Path $regPath -Name "LetAppsAccessGenerativeAI" -ErrorAction SilentlyContinue
|
||||
if ($value.LetAppsAccessSystemAIModels -eq 2 -and $value2.LetAppsAccessGenerativeAI -eq 2) {
|
||||
Write-Log -Level DEBUG -Message "Verification SUCCESS: Both AppPrivacy AI switches are Force Deny" -Module "AntiAI"
|
||||
}
|
||||
else {
|
||||
$result.Errors += "Verification FAILED: AppPrivacy AI switches not set correctly"
|
||||
}
|
||||
|
||||
# 2. Set CapabilityAccessManager Deny (additional workaround for Paint Generative Erase/Background Removal)
|
||||
$capabilityPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\systemAIModels"
|
||||
|
||||
if (-not (Test-Path $capabilityPath)) {
|
||||
New-Item -Path $capabilityPath -Force | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Created registry path: $capabilityPath" -Module "AntiAI"
|
||||
}
|
||||
|
||||
$existing = Get-ItemProperty -Path $capabilityPath -Name "Value" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $capabilityPath -Name "Value" -Value "Deny" -Force
|
||||
} else {
|
||||
New-ItemProperty -Path $capabilityPath -Name "Value" -Value "Deny" -PropertyType String -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Set CapabilityAccessManager\systemAIModels = Deny (workaround for undocumented AI features)" -Module "AntiAI"
|
||||
$result.Applied++
|
||||
|
||||
# Verify CapabilityAccessManager
|
||||
$capValue = Get-ItemProperty -Path $capabilityPath -Name "Value" -ErrorAction SilentlyContinue
|
||||
if ($capValue.Value -eq "Deny") {
|
||||
Write-Log -Level DEBUG -Message "Verification SUCCESS: CapabilityAccessManager is Deny" -Module "AntiAI"
|
||||
$result.Success = $true
|
||||
}
|
||||
else {
|
||||
$result.Errors += "Verification FAILED: CapabilityAccessManager not set correctly"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$result.Errors += "Failed to set Generative AI Master Switch: $($_.Exception.Message)"
|
||||
Write-Error $result.Errors[-1]
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
652
Modules/AntiAI/Private/Test-AntiAICompliance.ps1
Normal file
652
Modules/AntiAI/Private/Test-AntiAICompliance.ps1
Normal file
|
|
@ -0,0 +1,652 @@
|
|||
#Requires -Version 5.1
|
||||
#Requires -RunAsAdministrator
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Verifies that all AntiAI policies are correctly applied.
|
||||
|
||||
.DESCRIPTION
|
||||
REGISTRY COMPLIANCE VERIFICATION (Self-Check + MS Policy Validation)
|
||||
|
||||
This script performs TWO types of checks:
|
||||
|
||||
A) SELF-CHECK (Primary):
|
||||
Verifies that AntiAI module successfully set all intended registry keys:
|
||||
- Generative AI Master Switch (LetAppsAccessSystemAIModels)
|
||||
- Recall Core (AllowRecallEnablement, DisableAIDataAnalysis x2, DisableRecallDataProviders)
|
||||
- Recall Protection (App/URI Deny Lists, Storage Duration/Space)
|
||||
- Copilot (4-layer defense: WindowsAI, WindowsCopilot, ShowCopilotButton, Explorer, User-scope, Hardware Key)
|
||||
- Click to Do (DisableClickToDo x2)
|
||||
- Paint AI (DisableCocreator, DisableGenerativeFill, DisableImageCreator)
|
||||
- Notepad AI (DisableAIFeatures)
|
||||
- Settings Agent (DisableSettingsAgent)
|
||||
|
||||
B) MS POLICY VALIDATION (Secondary):
|
||||
Checks for additional Microsoft-official registry keys that AntiAI module does NOT set,
|
||||
but which could indicate incomplete deactivation or MS policy changes:
|
||||
- PolicyManager paths (alternative policy enforcement)
|
||||
- Additional WindowsAI keys introduced in newer Windows builds
|
||||
- Alternative Copilot/Recall paths
|
||||
|
||||
IMPORTANT LIMITATIONS:
|
||||
- This is a REGISTRY-ONLY check. It does NOT verify if AI features are functionally disabled.
|
||||
- "PASS" means "registry keys are set correctly" NOT "AI features are 100% inactive".
|
||||
- Microsoft may add new AI features or change registry paths in future Windows updates.
|
||||
- Some AI features may still work via cloud APIs even with correct registry settings.
|
||||
|
||||
For functional verification, test AI features manually after applying policies.
|
||||
|
||||
.EXAMPLE
|
||||
.\Test-AntiAICompliance.ps1
|
||||
Runs full compliance check and displays results.
|
||||
|
||||
.NOTES
|
||||
Author: NoID Privacy
|
||||
Version: 2.2.0 (Extended validation)
|
||||
Requires: Windows 11 24H2+, Administrator privileges
|
||||
#>
|
||||
|
||||
# Helper function to check registry value (must be outside main function)
|
||||
function Test-RegistryValue {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string]$Name,
|
||||
$ExpectedValue,
|
||||
[string]$Description
|
||||
)
|
||||
|
||||
$check = @{
|
||||
Description = $Description
|
||||
Path = "$Path\$Name"
|
||||
Expected = $ExpectedValue
|
||||
Actual = $null
|
||||
Status = "FAIL"
|
||||
}
|
||||
|
||||
try {
|
||||
if (Test-Path $Path) {
|
||||
$value = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue
|
||||
if ($null -ne $value) {
|
||||
$check.Actual = $value.$Name
|
||||
|
||||
# Handle different value types
|
||||
if ($ExpectedValue -is [array]) {
|
||||
# MultiString comparison - verify arrays match
|
||||
if ($check.Actual -is [array]) {
|
||||
# Check if arrays have same length and all items match
|
||||
if ($check.Actual.Count -eq $ExpectedValue.Count) {
|
||||
$allMatch = $true
|
||||
foreach ($expectedItem in $ExpectedValue) {
|
||||
if ($check.Actual -notcontains $expectedItem) {
|
||||
$allMatch = $false
|
||||
break
|
||||
}
|
||||
}
|
||||
$check.Status = if ($allMatch) { "PASS" } else { "FAIL" }
|
||||
}
|
||||
else {
|
||||
# Different array lengths - still OK if all expected items are present
|
||||
# (allows for extra items set by policy)
|
||||
$allPresent = $true
|
||||
foreach ($expectedItem in $ExpectedValue) {
|
||||
if ($check.Actual -notcontains $expectedItem) {
|
||||
$allPresent = $false
|
||||
break
|
||||
}
|
||||
}
|
||||
$check.Status = if ($allPresent) { "PASS" } else { "FAIL" }
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Expected array but got single value or nothing
|
||||
$check.Status = "FAIL"
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Exact value comparison
|
||||
$check.Status = if ($check.Actual -eq $ExpectedValue) { "PASS" } else { "FAIL" }
|
||||
}
|
||||
}
|
||||
else {
|
||||
$check.Actual = "NOT SET"
|
||||
}
|
||||
}
|
||||
else {
|
||||
$check.Actual = "PATH MISSING"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$check.Actual = "ERROR: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
return $check
|
||||
}
|
||||
|
||||
function Test-AntiAICompliance {
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
$startTime = Get-Date
|
||||
|
||||
Write-Host "`n========================================" -ForegroundColor Cyan
|
||||
Write-Host " ANTIAI COMPLIANCE VERIFICATION v2.2" -ForegroundColor Cyan
|
||||
Write-Host " Registry-Based Policy Check" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Checking 32 AI Deactivation Policies" -ForegroundColor Cyan
|
||||
Write-Host " + Advanced Copilot Blocks + MS Validation" -ForegroundColor DarkGray
|
||||
Write-Host "========================================`n" -ForegroundColor Cyan
|
||||
|
||||
# Initialize results (TotalPolicies calculated dynamically)
|
||||
$results = @{
|
||||
Passed = 0
|
||||
Failed = 0
|
||||
Warnings = 0
|
||||
Details = @()
|
||||
MSConflicts = 0
|
||||
MSAligned = 0
|
||||
}
|
||||
|
||||
Write-Host "[1/13] Checking Generative AI Master Switch..." -ForegroundColor Yellow
|
||||
$check = Test-RegistryValue `
|
||||
-Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" `
|
||||
-Name "LetAppsAccessSystemAIModels" `
|
||||
-ExpectedValue 2 `
|
||||
-Description "Generative AI Master (Force Deny all apps)"
|
||||
$results.Details += $check
|
||||
if ($check.Status -eq "PASS") {
|
||||
Write-Host " PASS: Master switch blocks all generative AI" -ForegroundColor Green
|
||||
$results.Passed++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAIL: Expected 2 (Force Deny), got $($check.Actual)" -ForegroundColor Red
|
||||
$results.Failed++
|
||||
}
|
||||
|
||||
# Additional check for LetAppsAccessGenerativeAI (Text & Image Generation in Settings)
|
||||
$genAICheck = Test-RegistryValue `
|
||||
-Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" `
|
||||
-Name "LetAppsAccessGenerativeAI" `
|
||||
-ExpectedValue 2 `
|
||||
-Description "Generative AI App Access (Force Deny)"
|
||||
$results.Details += $genAICheck
|
||||
if ($genAICheck.Status -eq "PASS") {
|
||||
Write-Host " PASS: App access to generative AI blocked" -ForegroundColor Green
|
||||
$results.Passed++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAIL: LetAppsAccessGenerativeAI not set (may allow AI features)" -ForegroundColor Red
|
||||
$results.Failed++
|
||||
}
|
||||
|
||||
# Additional CapabilityAccessManager check (workaround for Paint Generative Erase/Background Removal)
|
||||
$capCheck = Test-RegistryValue `
|
||||
-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\systemAIModels" `
|
||||
-Name "Value" `
|
||||
-ExpectedValue "Deny" `
|
||||
-Description "CapabilityAccessManager systemAIModels (Workaround)"
|
||||
$results.Details += $capCheck
|
||||
if ($capCheck.Status -eq "PASS") {
|
||||
Write-Host " PASS: CapabilityAccessManager blocks AI capabilities" -ForegroundColor Green
|
||||
$results.Passed++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAIL: CapabilityAccessManager not set (may allow Paint Generative Erase/Background Removal)" -ForegroundColor Red
|
||||
$results.Failed++
|
||||
}
|
||||
|
||||
Write-Host "`n[2/13] Checking Recall Core Policies..." -ForegroundColor Yellow
|
||||
$recallChecks = @(
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" -Name "AllowRecallEnablement" -ExpectedValue 0 -Description "Recall Component Removal"),
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" -Name "DisableAIDataAnalysis" -ExpectedValue 1 -Description "Recall Snapshots Disabled (Device)"),
|
||||
(Test-RegistryValue -Path "HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" -Name "DisableAIDataAnalysis" -ExpectedValue 1 -Description "Recall Snapshots Disabled (User)"),
|
||||
(Test-RegistryValue -Path "HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" -Name "DisableRecallDataProviders" -ExpectedValue 1 -Description "Recall Data Providers Disabled")
|
||||
)
|
||||
foreach ($check in $recallChecks) {
|
||||
$results.Details += $check
|
||||
if ($check.Status -eq "PASS") {
|
||||
Write-Host " PASS: $($check.Description)" -ForegroundColor Green
|
||||
$results.Passed++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAIL: $($check.Description) - $($check.Actual)" -ForegroundColor Red
|
||||
$results.Failed++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n[3/13] Checking Recall Enterprise Protection..." -ForegroundColor Yellow
|
||||
# Expected deny lists (must match Set-RecallProtection.ps1)
|
||||
$expectedDenyApps = @(
|
||||
"Microsoft.MicrosoftEdge_8wekyb3d8bbwe!App",
|
||||
"Microsoft.WindowsTerminal_8wekyb3d8bbwe!App",
|
||||
"KeePassXC_8wekyb3d8bbwe!KeePassXC",
|
||||
"Microsoft.RemoteDesktop_8wekyb3d8bbwe!App"
|
||||
)
|
||||
$expectedDenyUris = @(
|
||||
"*.bank.*",
|
||||
"*.paypal.*",
|
||||
"*.bankofamerica.*",
|
||||
"mail.*",
|
||||
"webmail.*",
|
||||
"*password*",
|
||||
"*login*"
|
||||
)
|
||||
|
||||
$protectionChecks = @(
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" -Name "SetDenyAppListForRecall" -ExpectedValue $expectedDenyApps -Description "App Deny List"),
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" -Name "SetDenyUriListForRecall" -ExpectedValue $expectedDenyUris -Description "URI Deny List"),
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" -Name "SetMaximumStorageDurationForRecallSnapshots" -ExpectedValue 30 -Description "Max Retention: 30 days"),
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" -Name "SetMaximumStorageSpaceForRecallSnapshots" -ExpectedValue 10 -Description "Max Storage: 10 GB")
|
||||
)
|
||||
foreach ($check in $protectionChecks) {
|
||||
$results.Details += $check
|
||||
if ($check.Status -eq "PASS") {
|
||||
Write-Host " PASS: $($check.Description)" -ForegroundColor Green
|
||||
$results.Passed++
|
||||
}
|
||||
else {
|
||||
Write-Host " WARN: $($check.Description) - $($check.Actual)" -ForegroundColor Yellow
|
||||
$results.Warnings++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n[4/13] Checking Windows Copilot (4-layer defense)..." -ForegroundColor Yellow
|
||||
$copilotChecks = @(
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" -Name "TurnOffWindowsCopilot" -ExpectedValue 1 -Description "Copilot Layer 1 (WindowsAI HKLM)"),
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsCopilot" -Name "TurnOffWindowsCopilot" -ExpectedValue 1 -Description "Copilot Layer 2 (WindowsCopilot HKLM)"),
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsCopilot" -Name "ShowCopilotButton" -ExpectedValue 0 -Description "Copilot Layer 3 (Taskbar Button Hidden)"),
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" -Name "DisableWindowsCopilot" -ExpectedValue 1 -Description "Copilot Layer 4 (Explorer Integration)"),
|
||||
(Test-RegistryValue -Path "HKCU:\Software\Policies\Microsoft\Windows\WindowsCopilot" -Name "TurnOffWindowsCopilot" -ExpectedValue 1 -Description "Copilot User-scope (HKCU)"),
|
||||
(Test-RegistryValue -Path "HKCU:\Software\Policies\Microsoft\Windows\WindowsCopilot" -Name "ShowCopilotButton" -ExpectedValue 0 -Description "Copilot Button Hidden (User)"),
|
||||
(Test-RegistryValue -Path "HKCU:\Software\Policies\Microsoft\Windows\WindowsAI" -Name "SetCopilotHardwareKey" -ExpectedValue "Microsoft.WindowsNotepad_8wekyb3d8bbwe!App" -Description "Hardware Key Remapped to Notepad")
|
||||
)
|
||||
foreach ($check in $copilotChecks) {
|
||||
$results.Details += $check
|
||||
if ($check.Status -eq "PASS") {
|
||||
Write-Host " PASS: $($check.Description)" -ForegroundColor Green
|
||||
$results.Passed++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAIL: $($check.Description) - $($check.Actual)" -ForegroundColor Red
|
||||
$results.Failed++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n[5/13] Checking Click to Do..." -ForegroundColor Yellow
|
||||
$clickChecks = @(
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" -Name "DisableClickToDo" -ExpectedValue 1 -Description "Click to Do Disabled (Device)"),
|
||||
(Test-RegistryValue -Path "HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" -Name "DisableClickToDo" -ExpectedValue 1 -Description "Click to Do Disabled (User)")
|
||||
)
|
||||
foreach ($check in $clickChecks) {
|
||||
$results.Details += $check
|
||||
if ($check.Status -eq "PASS") {
|
||||
Write-Host " PASS: $($check.Description)" -ForegroundColor Green
|
||||
$results.Passed++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAIL: $($check.Description) - $($check.Actual)" -ForegroundColor Red
|
||||
$results.Failed++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n[6/13] Checking Paint AI..." -ForegroundColor Yellow
|
||||
$paintChecks = @(
|
||||
(Test-RegistryValue -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\Paint" -Name "DisableCocreator" -ExpectedValue 1 -Description "Paint Cocreator Disabled"),
|
||||
(Test-RegistryValue -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\Paint" -Name "DisableGenerativeFill" -ExpectedValue 1 -Description "Paint Generative Fill Disabled"),
|
||||
(Test-RegistryValue -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\Paint" -Name "DisableImageCreator" -ExpectedValue 1 -Description "Paint Image Creator Disabled")
|
||||
)
|
||||
foreach ($check in $paintChecks) {
|
||||
$results.Details += $check
|
||||
if ($check.Status -eq "PASS") {
|
||||
Write-Host " PASS: $($check.Description)" -ForegroundColor Green
|
||||
$results.Passed++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAIL: $($check.Description) - $($check.Actual)" -ForegroundColor Red
|
||||
$results.Failed++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n[7/13] Checking Notepad AI..." -ForegroundColor Yellow
|
||||
$check = Test-RegistryValue `
|
||||
-Path "HKLM:\SOFTWARE\Policies\WindowsNotepad" `
|
||||
-Name "DisableAIFeatures" `
|
||||
-ExpectedValue 1 `
|
||||
-Description "Notepad AI Disabled (Write/Summarize/Rewrite)"
|
||||
$results.Details += $check
|
||||
if ($check.Status -eq "PASS") {
|
||||
Write-Host " PASS: Notepad AI completely disabled" -ForegroundColor Green
|
||||
$results.Passed++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAIL: Expected 1, got $($check.Actual)" -ForegroundColor Red
|
||||
$results.Failed++
|
||||
}
|
||||
|
||||
Write-Host "`n[8/13] Checking Settings Agent..." -ForegroundColor Yellow
|
||||
$check = Test-RegistryValue `
|
||||
-Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" `
|
||||
-Name "DisableSettingsAgent" `
|
||||
-ExpectedValue 1 `
|
||||
-Description "Settings AI Agent Disabled"
|
||||
$results.Details += $check
|
||||
if ($check.Status -eq "PASS") {
|
||||
Write-Host " PASS: Settings Agent disabled (classic search only)" -ForegroundColor Green
|
||||
$results.Passed++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAIL: Expected 1, got $($check.Actual)" -ForegroundColor Red
|
||||
$results.Failed++
|
||||
}
|
||||
|
||||
Write-Host "`n[9/13] Checking Explorer AI Actions..." -ForegroundColor Yellow
|
||||
$check = Test-RegistryValue `
|
||||
-Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" `
|
||||
-Name "HideAIActionsMenu" `
|
||||
-ExpectedValue 1 `
|
||||
-Description "Explorer AI Actions Hidden"
|
||||
$results.Details += $check
|
||||
if ($check.Status -eq "PASS") {
|
||||
Write-Host " PASS: Explorer AI Actions menu hidden" -ForegroundColor Green
|
||||
$results.Passed++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAIL: Expected 1, got $($check.Actual)" -ForegroundColor Red
|
||||
$results.Failed++
|
||||
}
|
||||
|
||||
Write-Host "`n[10/13] Checking Recall Export Block (NEW)..." -ForegroundColor Yellow
|
||||
$check = Test-RegistryValue `
|
||||
-Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" `
|
||||
-Name "AllowRecallExport" `
|
||||
-ExpectedValue 0 `
|
||||
-Description "Recall Export Disabled"
|
||||
$results.Details += $check
|
||||
if ($check.Status -eq "PASS") {
|
||||
Write-Host " PASS: Recall snapshot export blocked" -ForegroundColor Green
|
||||
$results.Passed++
|
||||
}
|
||||
else {
|
||||
Write-Host " WARN: Recall export may be allowed (optional policy)" -ForegroundColor Yellow
|
||||
$results.Warnings++
|
||||
}
|
||||
|
||||
Write-Host "`n[11/13] Checking Edge Copilot Sidebar..." -ForegroundColor Yellow
|
||||
$edgeCopilotChecks = @(
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Edge" -Name "EdgeSidebarEnabled" -ExpectedValue 0 -Description "Edge Sidebar Disabled"),
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Edge" -Name "ShowHubsSidebar" -ExpectedValue 0 -Description "Hubs Sidebar Hidden"),
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Edge" -Name "HubsSidebarEnabled" -ExpectedValue 0 -Description "Hubs Sidebar Disabled"),
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Edge" -Name "CopilotPageContext" -ExpectedValue 0 -Description "Copilot Page Context Blocked"),
|
||||
(Test-RegistryValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\Edge" -Name "CopilotCDPPageContext" -ExpectedValue 0 -Description "Copilot CDP Context Blocked")
|
||||
)
|
||||
foreach ($check in $edgeCopilotChecks) {
|
||||
$results.Details += $check
|
||||
if ($check.Status -eq "PASS") {
|
||||
Write-Host " PASS: $($check.Description)" -ForegroundColor Green
|
||||
$results.Passed++
|
||||
}
|
||||
else {
|
||||
Write-Host " WARN: $($check.Description) - $($check.Actual)" -ForegroundColor Yellow
|
||||
$results.Warnings++
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n[12/13] Checking Recall Component Status..." -ForegroundColor Yellow
|
||||
|
||||
# Check for Recall component status (Windows Optional Feature)
|
||||
try {
|
||||
$recallFeature = Get-WindowsOptionalFeature -Online -FeatureName "Recall" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $recallFeature) {
|
||||
if ($recallFeature.State -eq "Disabled") {
|
||||
Write-Host " PASS: Recall component is disabled" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host " INFO: Recall component present but configured to be removed (reboot required)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host " PASS: Recall component not present on this system" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host " INFO: Cannot query Recall feature status" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
Write-Host "`n[13/13] Checking MS Policy Validation (Conflict Scanner)..." -ForegroundColor Yellow
|
||||
|
||||
$msConflicts = 0
|
||||
$msInfo = 0
|
||||
|
||||
try {
|
||||
# PolicyManager paths (alternative policy enforcement used by Intune/MDM)
|
||||
$policyManagerChecks = @(
|
||||
@{ Path = "HKLM:\SOFTWARE\Microsoft\PolicyManager\current\device\WindowsAI"; Name = "DisableAIDataAnalysis"; Desc = "Recall PolicyManager (MDM Current)" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Microsoft\PolicyManager\default\WindowsAI"; Name = "DisableAIDataAnalysis"; Desc = "Recall PolicyManager (MDM Default)" }
|
||||
)
|
||||
|
||||
foreach ($check in $policyManagerChecks) {
|
||||
try {
|
||||
if (Test-Path $check.Path) {
|
||||
$prop = Get-ItemProperty -Path $check.Path -ErrorAction SilentlyContinue
|
||||
if ($prop -and ($prop.PSObject.Properties.Name -contains $check.Name)) {
|
||||
$value = $prop.($check.Name)
|
||||
if ($null -ne $value) {
|
||||
if ($value -eq 1) {
|
||||
Write-Host " INFO: $($check.Desc) = 1 (aligned with AntiAI)" -ForegroundColor DarkGray
|
||||
$msInfo++
|
||||
}
|
||||
else {
|
||||
Write-Host " WARN: $($check.Desc) = $value (may conflict with AntiAI!)" -ForegroundColor Yellow
|
||||
$msConflicts++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
# Silently ignore if property doesn't exist or path is inaccessible
|
||||
$null = $null
|
||||
}
|
||||
}
|
||||
|
||||
# Check for alternative Copilot/Explorer keys (conflict detection)
|
||||
$additionalMSKeys = @(
|
||||
@{ Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer"; Name = "NoCopilotButton"; ExpectedValue = 1; Desc = "Explorer Copilot Button" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"; Name = "CopilotPageEnabled"; ExpectedValue = 0; Desc = "Edge Copilot Integration" }
|
||||
)
|
||||
|
||||
foreach ($check in $additionalMSKeys) {
|
||||
try {
|
||||
if (Test-Path $check.Path) {
|
||||
$prop = Get-ItemProperty -Path $check.Path -ErrorAction SilentlyContinue
|
||||
if ($prop -and ($prop.PSObject.Properties.Name -contains $check.Name)) {
|
||||
$value = $prop.($check.Name)
|
||||
if ($null -ne $value) {
|
||||
if ($value -eq $check.ExpectedValue) {
|
||||
Write-Host " INFO: $($check.Desc) = $value (aligned with AntiAI)" -ForegroundColor DarkGray
|
||||
$msInfo++
|
||||
}
|
||||
else {
|
||||
Write-Host " WARN: $($check.Desc) = $value (may conflict with AntiAI, expected $($check.ExpectedValue))" -ForegroundColor Yellow
|
||||
$msConflicts++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
# Silently ignore if property doesn't exist or path is inaccessible
|
||||
$null = $null
|
||||
}
|
||||
}
|
||||
|
||||
if ($msConflicts -eq 0 -and $msInfo -eq 0) {
|
||||
Write-Host " No alternative MS policies detected (clean configuration)" -ForegroundColor DarkGray
|
||||
}
|
||||
elseif ($msConflicts -gt 0) {
|
||||
Write-Host " CONFLICTS DETECTED: $msConflicts MS policy conflict(s) found!" -ForegroundColor Yellow
|
||||
}
|
||||
else {
|
||||
Write-Host " $msInfo additional MS policy/policies aligned with AntiAI" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
Write-Host " NOTE: MS Policy Validation scans for conflicts with AntiAI configuration." -ForegroundColor DarkGray
|
||||
Write-Host " Missing keys are OK - conflicts are reported as warnings." -ForegroundColor DarkGray
|
||||
}
|
||||
catch {
|
||||
Write-Host " WARNING: MS Policy Validation encountered an error: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
Write-Host " Continuing with self-check results only..." -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
# Store MS validation results
|
||||
$results.MSConflicts = $msConflicts
|
||||
$results.MSAligned = $msInfo
|
||||
|
||||
# Calculate final results
|
||||
$endTime = Get-Date
|
||||
$duration = ($endTime - $startTime).TotalSeconds
|
||||
|
||||
# TotalPolicies = Passed + Failed (Warnings are informational only)
|
||||
$totalPolicies = $results.Passed + $results.Failed
|
||||
|
||||
if ($totalPolicies -gt 0) {
|
||||
$successRate = [math]::Round(($results.Passed / $totalPolicies) * 100, 1)
|
||||
}
|
||||
else {
|
||||
$successRate = 0
|
||||
}
|
||||
|
||||
# Derive overall status for programmatic use
|
||||
if ($results.Failed -eq 0 -and $results.Passed -gt 0) {
|
||||
# All checks passed (warnings are OK)
|
||||
$overallStatus = "PASS"
|
||||
}
|
||||
elseif ($results.Passed -eq 0 -and $results.Failed -gt 0) {
|
||||
# All checks failed - likely AntiAI module was never run
|
||||
$overallStatus = "NOT_APPLIED"
|
||||
}
|
||||
elseif ($results.Failed -gt 0) {
|
||||
# Some checks failed
|
||||
$overallStatus = "FAIL"
|
||||
}
|
||||
else {
|
||||
# Edge case: no checks run
|
||||
$overallStatus = "NOT_APPLIED"
|
||||
}
|
||||
|
||||
$results["OverallStatus"] = $overallStatus
|
||||
$results["TotalPolicies"] = $totalPolicies
|
||||
$results["TotalChecks"] = $totalPolicies
|
||||
$results["FailedChecks"] = $results.Failed
|
||||
$results["DurationSeconds"] = [math]::Round($duration, 2)
|
||||
|
||||
# Set exit code for programmatic use
|
||||
# 0 = All checks passed, no MS conflicts
|
||||
# 1 = Self-check failed (AntiAI policies not set correctly)
|
||||
# 2 = Self-check passed but MS conflicts detected
|
||||
$exitCode = 0
|
||||
if ($results.Failed -gt 0) {
|
||||
$exitCode = 1
|
||||
}
|
||||
elseif ($results.MSConflicts -gt 0) {
|
||||
$exitCode = 2
|
||||
}
|
||||
$results["ExitCode"] = $exitCode
|
||||
|
||||
# Display summary
|
||||
Write-Host "`n========================================" -ForegroundColor Cyan
|
||||
Write-Host " COMPLIANCE SUMMARY" -ForegroundColor Cyan
|
||||
Write-Host "========================================`n" -ForegroundColor Cyan
|
||||
|
||||
# Self-Check Results
|
||||
Write-Host "Self-Check (AntiAI Policies):" -ForegroundColor Cyan
|
||||
Write-Host " Total Policies: $totalPolicies" -ForegroundColor White
|
||||
Write-Host " Passed: " -NoNewline
|
||||
Write-Host "$($results.Passed)" -ForegroundColor Green
|
||||
Write-Host " Failed: " -NoNewline
|
||||
if ($results.Failed -eq 0) {
|
||||
Write-Host "$($results.Failed)" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host "$($results.Failed)" -ForegroundColor Red
|
||||
}
|
||||
Write-Host " Warnings: " -NoNewline
|
||||
if ($results.Warnings -eq 0) {
|
||||
Write-Host "$($results.Warnings)" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host "$($results.Warnings)" -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host " Success Rate: " -NoNewline
|
||||
if ($successRate -eq 100) {
|
||||
Write-Host "$successRate%" -ForegroundColor Green
|
||||
}
|
||||
elseif ($successRate -ge 80) {
|
||||
Write-Host "$successRate%" -ForegroundColor Yellow
|
||||
}
|
||||
else {
|
||||
Write-Host "$successRate%" -ForegroundColor Red
|
||||
}
|
||||
|
||||
# MS Policy Validation Results
|
||||
Write-Host "`nMS Policy Validation:" -ForegroundColor Cyan
|
||||
Write-Host " Conflicts: " -NoNewline
|
||||
if ($results.MSConflicts -eq 0) {
|
||||
Write-Host "$($results.MSConflicts)" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host "$($results.MSConflicts)" -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host " Aligned: $($results.MSAligned)" -ForegroundColor White
|
||||
Write-Host " Status: " -NoNewline
|
||||
if ($results.MSConflicts -eq 0) {
|
||||
Write-Host "NO CONFLICTS" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host "CONFLICTS DETECTED" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host "`nExecution:" -ForegroundColor Cyan
|
||||
Write-Host " Duration: $([math]::Round($duration, 2)) seconds" -ForegroundColor White
|
||||
|
||||
Write-Host "`nOverall Status: " -NoNewline
|
||||
switch ($overallStatus) {
|
||||
"PASS" {
|
||||
if ($results.MSConflicts -eq 0) {
|
||||
Write-Host "COMPLIANT - All checks passed, no conflicts (Exit Code: 0)" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host "COMPLIANT - Registry OK, but MS conflicts detected (Exit Code: 2)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
"NOT_APPLIED" {
|
||||
Write-Host "NOT APPLIED - AntiAI module has not been run yet (Exit Code: 1)" -ForegroundColor Yellow
|
||||
}
|
||||
default {
|
||||
Write-Host "NON-COMPLIANT - Action required (Exit Code: 1)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n========================================`n" -ForegroundColor Cyan
|
||||
|
||||
# IMPORTANT DISCLAIMER
|
||||
if ($overallStatus -eq "PASS") {
|
||||
Write-Host " IMPORTANT: This check verifies REGISTRY COMPLIANCE ONLY." -ForegroundColor Yellow
|
||||
Write-Host " It does NOT guarantee that AI features are functionally disabled." -ForegroundColor Yellow
|
||||
Write-Host "" -ForegroundColor Yellow
|
||||
Write-Host " Reasons why AI features might still work:" -ForegroundColor DarkGray
|
||||
Write-Host " - Microsoft may use alternative/undocumented registry paths" -ForegroundColor DarkGray
|
||||
Write-Host " - Cloud-based AI features bypass local policies" -ForegroundColor DarkGray
|
||||
Write-Host " - Newer Windows builds may introduce new AI keys/features" -ForegroundColor DarkGray
|
||||
Write-Host " - Apps may have hardcoded AI functionality" -ForegroundColor DarkGray
|
||||
Write-Host "" -ForegroundColor Yellow
|
||||
Write-Host " RECOMMENDATION: Manually test AI features after applying policies:" -ForegroundColor Yellow
|
||||
Write-Host " - Open Notepad -> Check for AI/Copilot button" -ForegroundColor DarkGray
|
||||
Write-Host " - Open Paint -> Check for Cocreator/Generative Fill" -ForegroundColor DarkGray
|
||||
Write-Host " - Press Win+C -> Should NOT open Copilot" -ForegroundColor DarkGray
|
||||
Write-Host " - Snipping Tool -> Check for AI OCR/Redact features" -ForegroundColor DarkGray
|
||||
Write-Host "`n========================================`n" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
# Return results object
|
||||
return $results
|
||||
}
|
||||
508
Modules/AntiAI/Public/Invoke-AntiAI.ps1
Normal file
508
Modules/AntiAI/Public/Invoke-AntiAI.ps1
Normal file
|
|
@ -0,0 +1,508 @@
|
|||
#Requires -Version 5.1
|
||||
#Requires -RunAsAdministrator
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Disables all Windows 11 AI features using official Microsoft policies.
|
||||
|
||||
.DESCRIPTION
|
||||
Maximum AI deactivation module - Disables 8+ Windows 11 AI features:
|
||||
|
||||
DEACTIVATED AI FEATURES:
|
||||
1. Generative AI Master Switch - Blocks ALL apps from using on-device AI models
|
||||
2. Windows Recall - Screenshots everything (EXTREME privacy risk!) - Component removed
|
||||
3. Windows Copilot - System AI assistant (chat, proactive suggestions)
|
||||
4. Click to Do - Screenshot AI analysis with action suggestions
|
||||
5. Paint Cocreator - Cloud-based text-to-image generation
|
||||
6. Paint Generative Fill - AI-powered image editing
|
||||
7. Paint Image Creator - DALL-E art generator
|
||||
8. Notepad AI - Write, Summarize, Rewrite features (GPT)
|
||||
9. Settings Agent - AI-powered Settings search
|
||||
|
||||
AUTOMATICALLY BLOCKED (by Master Switch):
|
||||
- Photos Generative Erase / Background effects
|
||||
- Clipchamp Auto Compose
|
||||
- Snipping Tool AI-OCR / Quick Redact
|
||||
- All future generative AI apps
|
||||
|
||||
RECALL ENTERPRISE PROTECTION (Maximum Compliance):
|
||||
- App Deny List: Browser, Terminal, Password managers, RDP never captured
|
||||
- URI Deny List: Banking, Email, Login pages never captured
|
||||
- Storage Duration: Maximum 30 days retention
|
||||
- Storage Space: Maximum 10 GB allocated
|
||||
|
||||
Uses only official Microsoft policies (WindowsAI CSP, AppPrivacy, Paint, Notepad).
|
||||
No registry hacks, 100% MS Best Practice compliant.
|
||||
|
||||
WARNING: Recall component removal requires reboot!
|
||||
|
||||
.PARAMETER SkipBackup
|
||||
Skip backup creation (NOT RECOMMENDED - use only for testing)
|
||||
|
||||
.PARAMETER DryRun
|
||||
Preview actions without applying changes
|
||||
|
||||
.EXAMPLE
|
||||
Invoke-AntiAI
|
||||
Disables all AI features with automatic backup.
|
||||
|
||||
.EXAMPLE
|
||||
Invoke-AntiAI -DryRun
|
||||
Preview actions without applying changes.
|
||||
|
||||
.NOTES
|
||||
Author: NoID Privacy
|
||||
Version: 2.2.0
|
||||
Requires: Windows 11 24H2 or later, Administrator privileges
|
||||
Impact: All AI features completely disabled, reboot required
|
||||
#>
|
||||
function Invoke-AntiAI {
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$DryRun,
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$SkipBackup
|
||||
)
|
||||
|
||||
$startTime = Get-Date
|
||||
|
||||
Write-Host "" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " ANTI-AI MODULE v2.2.0" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Disables 13 AI features (32 policies):" -ForegroundColor White
|
||||
Write-Host " - Generative AI Master Switch (blocks ALL AI models)" -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, Paint AI (3), Notepad AI, Settings Agent" -ForegroundColor Gray
|
||||
Write-Host " - Explorer AI Actions Menu" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
Write-Host "Uses 32 registry policies (+ URI handlers blocked separately)" -ForegroundColor Gray
|
||||
Write-Host "REBOOT REQUIRED for Recall component removal" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Host "[DRY RUN MODE - Preview only, no changes]" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Initialize result tracking (PSCustomObject for Framework compatibility)
|
||||
$result = [PSCustomObject]@{
|
||||
Success = $false
|
||||
TotalFeatures = 13 # 10 Original + 3 Advanced (RecallExport, URIHandlers, EdgeSidebar)
|
||||
Applied = 0
|
||||
Failed = 0
|
||||
Warnings = @()
|
||||
Errors = @()
|
||||
RequiresReboot = $false
|
||||
VerificationPassed = $null
|
||||
StartTime = $startTime
|
||||
EndTime = $null
|
||||
Duration = $null
|
||||
}
|
||||
|
||||
# BAVR Pattern: Backup, Apply (9 features), Verify, Complete
|
||||
# No step counting during apply - clean sequential output
|
||||
|
||||
try {
|
||||
# Core/Rollback.ps1 is loaded by Framework.ps1 - DO NOT load again here
|
||||
# Loading it twice would reset $script:BackupBasePath and break the backup system!
|
||||
|
||||
# Initialize Session-based backup system
|
||||
$moduleBackupPath = $null
|
||||
|
||||
# PHASE 1: BACKUP
|
||||
Write-Host "[1/4] BACKUP - Creating restore point..." -ForegroundColor Cyan
|
||||
|
||||
if (-not $SkipBackup -and -not $DryRun) {
|
||||
try {
|
||||
Initialize-BackupSystem
|
||||
$moduleBackupPath = Start-ModuleBackup -ModuleName "AntiAI"
|
||||
Write-Host " Backup initialized: $moduleBackupPath" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
}
|
||||
catch {
|
||||
Write-Host " WARNING: Backup failed - continuing without backup (RISKY!)" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
$result.Warnings += "Backup initialization failed: $_"
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($DryRun) {
|
||||
Write-Host " Skipped (DryRun mode)" -ForegroundColor Gray
|
||||
}
|
||||
else {
|
||||
Write-Host " Skipped (SkipBackup flag)" -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Capture AntiAI pre-state for precise restore (32 policies)
|
||||
if ($moduleBackupPath -and -not $DryRun) {
|
||||
try {
|
||||
$antiAIPreState = @()
|
||||
|
||||
$antiAIPreTargets = @(
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy"; Name = "LetAppsAccessSystemAIModels"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy"; Name = "LetAppsAccessGenerativeAI"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\systemAIModels"; Name = "Value"; Type = "String" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"; Name = "AllowRecallEnablement"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"; Name = "DisableAIDataAnalysis"; Type = "DWord" },
|
||||
@{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"; Name = "DisableAIDataAnalysis"; Type = "DWord" },
|
||||
@{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"; Name = "DisableRecallDataProviders"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"; Name = "SetDenyAppListForRecall"; Type = "MultiString" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"; Name = "SetDenyUriListForRecall"; Type = "MultiString" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"; Name = "SetMaximumStorageDurationForRecallSnapshots"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"; Name = "SetMaximumStorageSpaceForRecallSnapshots"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"; Name = "TurnOffWindowsCopilot"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsCopilot"; Name = "TurnOffWindowsCopilot"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsCopilot"; Name = "ShowCopilotButton"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer"; Name = "DisableWindowsCopilot"; Type = "DWord" },
|
||||
@{ Path = "HKCU:\Software\Policies\Microsoft\Windows\WindowsCopilot"; Name = "TurnOffWindowsCopilot"; Type = "DWord" },
|
||||
@{ Path = "HKCU:\Software\Policies\Microsoft\Windows\WindowsCopilot"; Name = "ShowCopilotButton"; Type = "DWord" },
|
||||
@{ Path = "HKCU:\Software\Policies\Microsoft\Windows\WindowsAI"; Name = "SetCopilotHardwareKey"; Type = "String" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"; Name = "DisableClickToDo"; Type = "DWord" },
|
||||
@{ Path = "HKCU:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"; Name = "DisableClickToDo"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\Paint"; Name = "DisableCocreator"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\Paint"; Name = "DisableGenerativeFill"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\Paint"; Name = "DisableImageCreator"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\WindowsNotepad"; Name = "DisableAIFeatures"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"; Name = "DisableSettingsAgent"; Type = "DWord" },
|
||||
# NEW v2.2.0: Advanced Copilot Blocking
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI"; Name = "AllowRecallExport"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"; Name = "EdgeSidebarEnabled"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"; Name = "ShowHubsSidebar"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"; Name = "HubsSidebarEnabled"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"; Name = "CopilotPageContext"; Type = "DWord" },
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"; Name = "CopilotCDPPageContext"; Type = "DWord" },
|
||||
# NEW: File Explorer AI Actions Menu
|
||||
@{ Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer"; Name = "HideAIActionsMenu"; Type = "DWord" }
|
||||
)
|
||||
|
||||
foreach ($t in $antiAIPreTargets) {
|
||||
$entry = [PSCustomObject]@{
|
||||
Path = $t.Path
|
||||
Name = $t.Name
|
||||
Type = $t.Type
|
||||
Exists = $false
|
||||
Value = $null
|
||||
}
|
||||
|
||||
try {
|
||||
if (Test-Path $t.Path) {
|
||||
$prop = Get-ItemProperty -Path $t.Path -Name $t.Name -ErrorAction SilentlyContinue
|
||||
if ($null -ne $prop -and $prop.PSObject.Properties.Name -contains $t.Name) {
|
||||
$entry.Exists = $true
|
||||
$entry.Value = $prop.$($t.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
# Ignore read errors, entry remains Exists = $false
|
||||
$null = $null
|
||||
}
|
||||
|
||||
$antiAIPreState += $entry
|
||||
}
|
||||
|
||||
$preStatePath = Join-Path $moduleBackupPath "AntiAI_PreState.json"
|
||||
$antiAIPreState | ConvertTo-Json -Depth 5 | Out-File -FilePath $preStatePath -Encoding UTF8 -Force
|
||||
Write-Log -Level DEBUG -Message "AntiAI pre-state snapshot saved: $preStatePath" -Module "AntiAI"
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Failed to capture AntiAI pre-state snapshot: $_" -Module "AntiAI"
|
||||
}
|
||||
}
|
||||
|
||||
# PHASE 2: APPLY
|
||||
Write-Host "[2/4] APPLY - Disabling AI features..." -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Feature 1: Generative AI Master Switch
|
||||
Write-Host " Generative AI Master Switch..." -ForegroundColor White -NoNewline
|
||||
$masterResult = Set-SystemAIModels -DryRun:$DryRun
|
||||
if ($masterResult.Success) {
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
$result.Applied++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAILED" -ForegroundColor Red
|
||||
$result.Failed++
|
||||
$result.Errors += $masterResult.Errors
|
||||
}
|
||||
|
||||
# Feature 2: Windows Recall (Core + Protection)
|
||||
Write-Host " Windows Recall (component removal)..." -ForegroundColor White -NoNewline
|
||||
$recallResult = Disable-Recall -DryRun:$DryRun
|
||||
if ($recallResult.Success) {
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
$result.Applied++
|
||||
$result.RequiresReboot = $true
|
||||
}
|
||||
else {
|
||||
Write-Host " FAILED" -ForegroundColor Red
|
||||
$result.Failed++
|
||||
$result.Errors += $recallResult.Errors
|
||||
}
|
||||
|
||||
Write-Host " Recall Enterprise Protection..." -ForegroundColor White -NoNewline
|
||||
$protectionResult = Set-RecallProtection -DryRun:$DryRun
|
||||
if ($protectionResult.Success) {
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host " WARNING" -ForegroundColor Yellow
|
||||
$result.Warnings += "Recall protection incomplete but core disable succeeded"
|
||||
}
|
||||
|
||||
# Feature 3: Windows Copilot
|
||||
if ($moduleBackupPath -and -not $DryRun) {
|
||||
# CRITICAL: Create JSON backup for Explorer Advanced HKLM (Protected Key)
|
||||
# .reg import often fails for this key due to permissions/ownership
|
||||
try {
|
||||
$expPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
|
||||
if (Test-Path $expPath) {
|
||||
$expVal = Get-ItemProperty -Path $expPath -Name "ShowCopilotButton" -ErrorAction SilentlyContinue
|
||||
if ($expVal) {
|
||||
$expData = @{ "ShowCopilotButton" = $expVal.ShowCopilotButton }
|
||||
$expJson = $expData | ConvertTo-Json
|
||||
Register-Backup -Type "AntiAI" -Data $expJson -Name "Explorer_Advanced_Device_JSON" | Out-Null
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Write-Host " WARNING: Failed to create JSON backup for Explorer Advanced: $_" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host " Windows Copilot..." -ForegroundColor White -NoNewline
|
||||
$copilotResult = Disable-Copilot -DryRun:$DryRun
|
||||
if ($copilotResult.Success) {
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
$result.Applied++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAILED" -ForegroundColor Red
|
||||
$result.Failed++
|
||||
$result.Errors += $copilotResult.Errors
|
||||
}
|
||||
|
||||
# Feature 4: Click to Do
|
||||
Write-Host " Click to Do..." -ForegroundColor White -NoNewline
|
||||
$clickResult = Disable-ClickToDo -DryRun:$DryRun
|
||||
if ($clickResult.Success) {
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
$result.Applied++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAILED" -ForegroundColor Red
|
||||
$result.Failed++
|
||||
$result.Errors += $clickResult.Errors
|
||||
}
|
||||
|
||||
# Feature 5-7: Paint AI (3 features)
|
||||
Write-Host " Paint AI (Cocreator, Fill, Creator)..." -ForegroundColor White -NoNewline
|
||||
$paintResult = Disable-PaintAI -DryRun:$DryRun
|
||||
if ($paintResult.Success) {
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
$result.Applied += 3
|
||||
}
|
||||
else {
|
||||
Write-Host " FAILED" -ForegroundColor Red
|
||||
$result.Failed += 3
|
||||
$result.Errors += $paintResult.Errors
|
||||
}
|
||||
|
||||
# Feature 8: Notepad AI
|
||||
Write-Host " Notepad AI..." -ForegroundColor White -NoNewline
|
||||
$notepadResult = Disable-NotepadAI -DryRun:$DryRun
|
||||
if ($notepadResult.Success) {
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
$result.Applied++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAILED" -ForegroundColor Red
|
||||
$result.Failed++
|
||||
$result.Errors += $notepadResult.Errors
|
||||
}
|
||||
|
||||
# Feature 9: Settings Agent
|
||||
Write-Host " Settings Agent..." -ForegroundColor White -NoNewline
|
||||
$settingsResult = Disable-SettingsAgent -DryRun:$DryRun
|
||||
if ($settingsResult.Success) {
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
$result.Applied++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAILED" -ForegroundColor Red
|
||||
$result.Failed++
|
||||
$result.Errors += $settingsResult.Errors
|
||||
}
|
||||
|
||||
# Feature 10: Explorer AI Actions Menu
|
||||
Write-Host " Explorer AI Actions..." -ForegroundColor White -NoNewline
|
||||
$explorerResult = Disable-ExplorerAI -DryRun:$DryRun
|
||||
if ($explorerResult.Success) {
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
$result.Applied++
|
||||
}
|
||||
else {
|
||||
Write-Host " FAILED" -ForegroundColor Red
|
||||
$result.Failed++
|
||||
$result.Errors += $explorerResult.Errors
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# ADVANCED COPILOT BLOCKING (NEW v2.2.0)
|
||||
# ============================================================================
|
||||
Write-Host ""
|
||||
Write-Host " [Advanced Copilot Blocks]" -ForegroundColor Cyan
|
||||
|
||||
# Feature 11-15: Advanced Copilot Blocking
|
||||
Write-Host " Advanced Copilot Blocks..." -ForegroundColor White -NoNewline
|
||||
$advancedResult = Disable-CopilotAdvanced -DryRun:$DryRun
|
||||
|
||||
if ($advancedResult.Success) {
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
|
||||
# Count actual features applied (not layers)
|
||||
$advancedFeaturesApplied = 0
|
||||
if ($advancedResult.RecallExportBlocked) {
|
||||
Write-Host " - Recall Export blocked" -ForegroundColor Gray
|
||||
$advancedFeaturesApplied++
|
||||
}
|
||||
if ($advancedResult.URIHandlersBlocked) {
|
||||
Write-Host " - URI handlers (ms-copilot:) blocked" -ForegroundColor Gray
|
||||
$advancedFeaturesApplied++
|
||||
}
|
||||
if ($advancedResult.EdgeSidebarDisabled) {
|
||||
Write-Host " - Edge Copilot sidebar disabled" -ForegroundColor Gray
|
||||
$advancedFeaturesApplied++
|
||||
}
|
||||
$result.Applied += $advancedFeaturesApplied
|
||||
}
|
||||
else {
|
||||
Write-Host " PARTIAL" -ForegroundColor Yellow
|
||||
$result.Warnings += "Some advanced Copilot blocks may have failed"
|
||||
if ($advancedResult.Errors.Count -gt 0) {
|
||||
$result.Errors += $advancedResult.Errors
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Register backup in session manifest
|
||||
# Note: Only Explorer Advanced JSON backup + PreState snapshot are created
|
||||
# PreState snapshot covers all 32 policies precisely
|
||||
if ($moduleBackupPath) {
|
||||
Complete-ModuleBackup -ItemsBackedUp 2 -Status "Success"
|
||||
}
|
||||
|
||||
# PHASE 3: VERIFY
|
||||
Write-Host "[3/4] VERIFY - Checking compliance..." -ForegroundColor Cyan
|
||||
|
||||
if (-not $DryRun -and $result.Failed -eq 0) {
|
||||
try {
|
||||
$complianceResult = Test-AntiAICompliance
|
||||
|
||||
if ($complianceResult.OverallStatus -eq "PASS") {
|
||||
Write-Host " All $($complianceResult.TotalChecks) compliance checks passed" -ForegroundColor Green
|
||||
$result.VerificationPassed = $true
|
||||
}
|
||||
else {
|
||||
Write-Host " WARNING: $($complianceResult.FailedChecks)/$($complianceResult.TotalChecks) checks failed" -ForegroundColor Yellow
|
||||
$result.VerificationPassed = $false
|
||||
$result.Warnings += "Some compliance checks failed - policies may not be fully effective"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host " WARNING: Verification failed - $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
$result.Warnings += "Compliance verification skipped due to error"
|
||||
$result.VerificationPassed = $null
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($DryRun) {
|
||||
Write-Host " Skipped (DryRun mode)" -ForegroundColor Gray
|
||||
}
|
||||
else {
|
||||
Write-Host " Skipped (errors occurred)" -ForegroundColor Yellow
|
||||
}
|
||||
$result.VerificationPassed = $null
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# Calculate final status
|
||||
$result.Success = ($result.Failed -eq 0)
|
||||
$result.EndTime = Get-Date
|
||||
$result.Duration = ($result.EndTime - $result.StartTime).TotalSeconds
|
||||
|
||||
# PHASE 4: COMPLETE
|
||||
Write-Host "[4/4] COMPLETE - AI hardening finished!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
Write-Host "Status: " -NoNewline
|
||||
if ($result.Success) {
|
||||
Write-Host "SUCCESS - All AI features disabled!" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host "COMPLETED WITH ERRORS" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host "Features: $($result.Applied)/$($result.TotalFeatures) disabled" -ForegroundColor $(if ($result.Failed -eq 0) { 'Green' } else { 'Yellow' })
|
||||
Write-Host "Errors: $($result.Failed)" -ForegroundColor $(if ($result.Failed -eq 0) { 'Green' } else { 'Red' })
|
||||
Write-Host "Warnings: $($result.Warnings.Count)" -ForegroundColor $(if ($result.Warnings.Count -eq 0) { 'Green' } else { 'Yellow' })
|
||||
|
||||
if ($null -ne $result.VerificationPassed) {
|
||||
Write-Host "Verification: " -NoNewline
|
||||
if ($result.VerificationPassed) {
|
||||
Write-Host "PASSED - All policies verified" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host "FAILED - Some policies not verified" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Duration: $([math]::Round($result.Duration, 2)) seconds" -ForegroundColor Cyan
|
||||
|
||||
if ($moduleBackupPath) {
|
||||
Write-Host "Backup: $moduleBackupPath" -ForegroundColor Cyan
|
||||
Write-Host "Items Backed: 2 items (PreState snapshot + Explorer JSON)" -ForegroundColor Cyan
|
||||
}
|
||||
elseif (-not $SkipBackup -and -not $DryRun) {
|
||||
Write-Host "Backup: FAILED" -ForegroundColor Red
|
||||
}
|
||||
else {
|
||||
Write-Host "Backup: SKIPPED" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
if ($result.RequiresReboot) {
|
||||
Write-Host "`nREBOOT REQUIRED: " -NoNewline -ForegroundColor Red
|
||||
Write-Host "Recall component removal needs system restart!" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
if ($result.Errors.Count -gt 0) {
|
||||
Write-Host ""
|
||||
Write-Host "Errors:" -ForegroundColor Red
|
||||
foreach ($err in $result.Errors) {
|
||||
Write-Host " - $err" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# GUI parsing marker for settings count (32 registry policies)
|
||||
Write-Log -Level SUCCESS -Message "Applied 32 settings" -Module "AntiAI"
|
||||
|
||||
# Return result object as PSCustomObject (Framework expects this type)
|
||||
return [PSCustomObject]$result
|
||||
}
|
||||
catch {
|
||||
$result.Success = $false
|
||||
$result.Errors += "Critical error: $($_.Exception.Message)"
|
||||
Write-Error "AntiAI module failed: $($_.Exception.Message)"
|
||||
return [PSCustomObject]$result
|
||||
}
|
||||
}
|
||||
38
Modules/DNS/Config/DNS.json
Normal file
38
Modules/DNS/Config/DNS.json
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"DoHPolicy": {
|
||||
"Mode": "REQUIRE",
|
||||
"Description": "DNS-over-HTTPS enforcement mode",
|
||||
"Options": {
|
||||
"REQUIRE": {
|
||||
"Value": 3,
|
||||
"Description": "REQUIRE DoH (no unencrypted fallback) - Maximum security",
|
||||
"BestFor": "Home users, single-network systems, maximum privacy",
|
||||
"Warning": "May cause connectivity issues in corporate networks, captive portals, or mobile hotspots"
|
||||
},
|
||||
"ALLOW": {
|
||||
"Value": 2,
|
||||
"Description": "ALLOW DoH (fallback to UDP if DoH fails) - Balanced",
|
||||
"BestFor": "VPN users, mobile devices, multi-network systems, enterprise environments",
|
||||
"Warning": "Less secure - DNS queries may fall back to unencrypted UDP"
|
||||
},
|
||||
"PROHIBIT": {
|
||||
"Value": 1,
|
||||
"Description": "PROHIBIT DoH (disable encrypted DNS) - Not recommended",
|
||||
"BestFor": "Legacy systems, specific enterprise requirements only",
|
||||
"Warning": "All DNS queries will be unencrypted"
|
||||
}
|
||||
},
|
||||
"Recommendation": "Use REQUIRE for home networks, ALLOW for VPN/mobile/enterprise"
|
||||
},
|
||||
"AllowFallbackToUdp": {
|
||||
"REQUIRE": false,
|
||||
"ALLOW": true,
|
||||
"PROHIBIT": true
|
||||
},
|
||||
"Notes": [
|
||||
"REQUIRE mode (default): Best privacy, but may break in corporate/captive portal networks or with VPNs",
|
||||
"ALLOW mode: Good balance for VPN users, mobile users and enterprise environments",
|
||||
"Users with VPNs or on multiple networks should choose ALLOW mode",
|
||||
"PROHIBIT mode is NOT recommended - only for specific legacy requirements"
|
||||
]
|
||||
}
|
||||
140
Modules/DNS/Config/Providers.json
Normal file
140
Modules/DNS/Config/Providers.json
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"providers": {
|
||||
"cloudflare": {
|
||||
"name": "Cloudflare",
|
||||
"description": "Fastest global DNS resolver with strong privacy",
|
||||
"ipv4": {
|
||||
"primary": "1.1.1.1",
|
||||
"secondary": "1.0.0.1"
|
||||
},
|
||||
"ipv6": {
|
||||
"primary": "2606:4700:4700::1111",
|
||||
"secondary": "2606:4700:4700::1001"
|
||||
},
|
||||
"doh": {
|
||||
"template": "https://cloudflare-dns.com/dns-query",
|
||||
"supported": true
|
||||
},
|
||||
"ratings": {
|
||||
"speed": 5,
|
||||
"privacy": 4,
|
||||
"security": 4,
|
||||
"filtering": 2
|
||||
},
|
||||
"features": [
|
||||
"Minimal logging (25h retention, anonymized IPs)",
|
||||
"DNSSEC validation (server-side)",
|
||||
"Fastest anycast network globally (150+ locations)",
|
||||
"KPMG-audited privacy practices",
|
||||
"Malware blocking available (1.1.1.2 variant)",
|
||||
"Adult content blocking available (1.1.1.3 variant)"
|
||||
],
|
||||
"jurisdiction": "USA (GDPR-compliant, strong privacy commitments)",
|
||||
"best_for": "Maximum speed with audited privacy",
|
||||
"documentation": "https://developers.cloudflare.com/1.1.1.1/"
|
||||
},
|
||||
"quad9": {
|
||||
"name": "Quad9",
|
||||
"description": "Non-profit DNS with Swiss privacy and threat blocking",
|
||||
"ipv4": {
|
||||
"primary": "9.9.9.9",
|
||||
"secondary": "149.112.112.112"
|
||||
},
|
||||
"ipv6": {
|
||||
"primary": "2620:fe::fe",
|
||||
"secondary": "2620:fe::9"
|
||||
},
|
||||
"doh": {
|
||||
"template": "https://dns.quad9.net/dns-query",
|
||||
"supported": true
|
||||
},
|
||||
"ratings": {
|
||||
"speed": 4,
|
||||
"privacy": 5,
|
||||
"security": 5,
|
||||
"filtering": 4
|
||||
},
|
||||
"features": [
|
||||
"Zero logging (no IP or query data stored)",
|
||||
"Swiss Data Protection Act enforcement",
|
||||
"Threat intelligence from 20+ sources (97% blocking rate)",
|
||||
"DNSSEC validation (server-side)",
|
||||
"Non-profit, no data monetization",
|
||||
"150+ locations in 90 countries"
|
||||
],
|
||||
"jurisdiction": "Switzerland (Zuerich) - Strongest privacy laws",
|
||||
"best_for": "Maximum privacy and security under Swiss law",
|
||||
"documentation": "https://quad9.net/"
|
||||
},
|
||||
"adguard": {
|
||||
"name": "AdGuard DNS",
|
||||
"description": "EU-based DNS with comprehensive ad and tracker blocking",
|
||||
"ipv4": {
|
||||
"primary": "94.140.14.14",
|
||||
"secondary": "94.140.15.15"
|
||||
},
|
||||
"ipv6": {
|
||||
"primary": "2a10:50c0::ad1:ff",
|
||||
"secondary": "2a10:50c0::ad2:ff"
|
||||
},
|
||||
"doh": {
|
||||
"template": "https://dns.adguard-dns.com/dns-query",
|
||||
"supported": true
|
||||
},
|
||||
"ratings": {
|
||||
"speed": 4,
|
||||
"privacy": 4,
|
||||
"security": 4,
|
||||
"filtering": 5
|
||||
},
|
||||
"features": [
|
||||
"Comprehensive ad and tracker blocking",
|
||||
"Analytics and telemetry blocking",
|
||||
"DNSSEC validation (server-side)",
|
||||
"GDPR compliant (EU jurisdiction)",
|
||||
"No-logging policy (not independently audited)",
|
||||
"Family-friendly filtering options available"
|
||||
],
|
||||
"jurisdiction": "Cyprus (Limassol) - EU, GDPR compliance",
|
||||
"best_for": "Maximum ad/tracker blocking with EU privacy",
|
||||
"documentation": "https://adguard-dns.io/"
|
||||
}
|
||||
},
|
||||
"default_provider": "quad9",
|
||||
"rating_descriptions": {
|
||||
"speed": {
|
||||
"5": "Exceptional - Fastest global performance",
|
||||
"4": "Excellent - Very fast response times",
|
||||
"3": "Good - Adequate performance",
|
||||
"2": "Fair - Slower than average",
|
||||
"1": "Poor - Slow response times"
|
||||
},
|
||||
"privacy": {
|
||||
"5": "Exceptional - Zero logging, strong jurisdiction, independently audited",
|
||||
"4": "Excellent - Minimal logging, good privacy policies",
|
||||
"3": "Good - Some logging, reasonable policies",
|
||||
"2": "Fair - Extensive logging",
|
||||
"1": "Poor - Privacy concerns"
|
||||
},
|
||||
"security": {
|
||||
"5": "Exceptional - Multiple threat feeds, DNSSEC, automatic blocking",
|
||||
"4": "Excellent - DNSSEC validation, basic threat protection",
|
||||
"3": "Good - DNSSEC only",
|
||||
"2": "Fair - Limited security features",
|
||||
"1": "Poor - No security features"
|
||||
},
|
||||
"filtering": {
|
||||
"5": "Exceptional - Comprehensive ad, tracker, and malware blocking",
|
||||
"4": "Excellent - Malware and phishing blocking",
|
||||
"3": "Good - Basic filtering available",
|
||||
"2": "Fair - Limited filtering options",
|
||||
"1": "Poor - No filtering"
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"dnssec": "All providers perform server-side DNSSEC validation. No client-side NRPT configuration required.",
|
||||
"doh": "DNS over HTTPS (DoH) encrypts DNS queries for privacy. Fallback to unencrypted DNS is disabled for security.",
|
||||
"ipv6": "IPv6 addresses are always configured alongside IPv4. Windows will use IPv6 when available."
|
||||
}
|
||||
}
|
||||
38
Modules/DNS/DNS.psd1
Normal file
38
Modules/DNS/DNS.psd1
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
@{
|
||||
# Module manifest for DNS module
|
||||
|
||||
RootModule = 'DNS.psm1'
|
||||
ModuleVersion = '2.2.0'
|
||||
GUID = 'a8f7b3c9-4e5d-4a2b-9c1d-8f3e5a7b9c2d'
|
||||
Author = 'NexusOne23'
|
||||
CompanyName = 'Open Source Project'
|
||||
Copyright = '(c) 2025 NexusOne23. Licensed under GPL-3.0.'
|
||||
Description = 'Secure DNS configuration module with DoH support for Cloudflare, Quad9, and AdGuard DNS providers'
|
||||
|
||||
PowerShellVersion = '5.1'
|
||||
|
||||
# Functions to export from this module
|
||||
FunctionsToExport = @(
|
||||
'Invoke-DNSConfiguration',
|
||||
'Get-DNSStatus',
|
||||
'Restore-DNSSettings'
|
||||
)
|
||||
|
||||
# Cmdlets to export from this module
|
||||
CmdletsToExport = @()
|
||||
|
||||
# Variables to export from this module
|
||||
VariablesToExport = @()
|
||||
|
||||
# Aliases to export from this module
|
||||
AliasesToExport = @()
|
||||
|
||||
PrivateData = @{
|
||||
PSData = @{
|
||||
Tags = @('DNS', 'DoH', 'Security', 'Privacy', 'Cloudflare', 'Quad9', 'AdGuard')
|
||||
LicenseUri = ''
|
||||
ProjectUri = ''
|
||||
ReleaseNotes = 'Initial release with DoH support for 3 major DNS providers'
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Modules/DNS/DNS.psm1
Normal file
43
Modules/DNS/DNS.psm1
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#Requires -Version 5.1
|
||||
#Requires -RunAsAdministrator
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
DNS Configuration Module for NoID Privacy
|
||||
|
||||
.DESCRIPTION
|
||||
Provides secure DNS configuration with DNS over HTTPS (DoH) support.
|
||||
Supports Cloudflare, Quad9, and AdGuard DNS providers with automatic
|
||||
backup and restore capabilities.
|
||||
|
||||
.NOTES
|
||||
Author: NoID Privacy
|
||||
Version: 2.2.0
|
||||
Requires: PowerShell 5.1+, Administrator privileges
|
||||
#>
|
||||
|
||||
# Module-level variables
|
||||
$script:ModuleName = "DNS"
|
||||
$script:ModuleRoot = $PSScriptRoot
|
||||
$PrivatePath = "$PSScriptRoot\Private"
|
||||
|
||||
# Get module functions
|
||||
$Private = @(Get-ChildItem -Path $PrivatePath -Filter "*.ps1" -ErrorAction SilentlyContinue)
|
||||
$Public = @(Get-ChildItem -Path "$PSScriptRoot\Public" -Filter "*.ps1" -ErrorAction SilentlyContinue)
|
||||
|
||||
# Dot source the functions
|
||||
foreach ($import in @($Private + $Public)) {
|
||||
try {
|
||||
. $import.FullName
|
||||
}
|
||||
catch {
|
||||
Write-Host "ERROR: Failed to import function $($import.FullName): $_" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
# Export public functions
|
||||
Export-ModuleMember -Function $Public.BaseName
|
||||
|
||||
# Alias for naming consistency (non-breaking change)
|
||||
New-Alias -Name 'Invoke-DNS' -Value 'Invoke-DNSConfiguration' -Force
|
||||
Export-ModuleMember -Alias 'Invoke-DNS'
|
||||
300
Modules/DNS/Private/Backup-DNSSettings.ps1
Normal file
300
Modules/DNS/Private/Backup-DNSSettings.ps1
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
function Backup-DNSSettings {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Backup current DNS settings for all physical network adapters
|
||||
|
||||
.DESCRIPTION
|
||||
Creates a comprehensive backup of DNS configuration including:
|
||||
- Current DNS server addresses (IPv4 and IPv6)
|
||||
- DHCP status (was DNS obtained from DHCP?)
|
||||
- DoH configuration
|
||||
- Adapter interface information
|
||||
|
||||
Backup is stored using the framework's rollback system.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Show what would be backed up without actually creating backup
|
||||
|
||||
.EXAMPLE
|
||||
Backup-DNSSettings
|
||||
Creates backup of current DNS settings
|
||||
|
||||
.OUTPUTS
|
||||
System.String - Path to backup file or $null if failed
|
||||
|
||||
.NOTES
|
||||
DHCP awareness is critical for correct rollback behavior
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter()]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
try {
|
||||
Write-Log -Level INFO -Message "Backing up DNS settings..." -Module $script:ModuleName
|
||||
|
||||
# Get all physical adapters
|
||||
$adapters = @(Get-PhysicalAdapters) # Force array
|
||||
|
||||
if ($adapters.Count -eq 0) {
|
||||
Write-Log -Level WARNING -Message "No physical adapters found to backup" -Module $script:ModuleName
|
||||
return $null
|
||||
}
|
||||
|
||||
Write-Log -Level DEBUG -Message "Found $($adapters.Count) adapter(s) to backup" -Module $script:ModuleName
|
||||
|
||||
# Get netsh global DoH state
|
||||
$netshGlobalDoh = $null
|
||||
try {
|
||||
$netshResult = netsh dnsclient show global 2>&1 | Out-String
|
||||
if ($netshResult -match "DoH\s*:\s*(\w+)") {
|
||||
$netshGlobalDoh = $matches[1]
|
||||
Write-Log -Level DEBUG -Message "netsh global DoH state: $netshGlobalDoh" -Module $script:ModuleName
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Could not retrieve netsh global DoH state: $_" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
# Get all netsh DoH encryption entries
|
||||
$netshDohEntries = @()
|
||||
try {
|
||||
$netshEncryption = netsh dnsclient show encryption 2>&1 | Out-String
|
||||
# Parse netsh output for DoH servers
|
||||
# Format: "Server: X.X.X.X | Template: https://... | Auto-upgrade: yes | UDP fallback: no"
|
||||
$lines = $netshEncryption -split "`n"
|
||||
foreach ($line in $lines) {
|
||||
if ($line -match "Server:\s*(\S+)") {
|
||||
$server = $matches[1]
|
||||
$template = $null
|
||||
$autoupgrade = $null
|
||||
$udpfallback = $null
|
||||
|
||||
if ($netshEncryption -match "Server:\s*$([regex]::Escape($server)).*?Template:\s*(\S+)") {
|
||||
$template = $matches[1]
|
||||
}
|
||||
if ($netshEncryption -match "Server:\s*$([regex]::Escape($server)).*?Auto-upgrade:\s*(\w+)") {
|
||||
$autoupgrade = $matches[1]
|
||||
}
|
||||
if ($netshEncryption -match "Server:\s*$([regex]::Escape($server)).*?UDP fallback:\s*(\w+)") {
|
||||
$udpfallback = $matches[1]
|
||||
}
|
||||
|
||||
if ($template) {
|
||||
$netshDohEntries += @{
|
||||
Server = $server
|
||||
Template = $template
|
||||
AutoUpgrade = $autoupgrade
|
||||
UdpFallback = $udpfallback
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Found netsh DoH entry: $server" -Module $script:ModuleName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Could not retrieve netsh DoH entries: $_" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
$dohEntries = @()
|
||||
try {
|
||||
$allDoh = Get-DnsClientDohServerAddress -ErrorAction SilentlyContinue
|
||||
if ($allDoh) {
|
||||
foreach ($entry in $allDoh) {
|
||||
if ($entry.ServerAddress -and $entry.DohTemplate) {
|
||||
$dohEntries += @{
|
||||
ServerAddress = $entry.ServerAddress
|
||||
DohTemplate = $entry.DohTemplate
|
||||
AllowFallbackToUdp = $entry.AllowFallbackToUdp
|
||||
AutoUpgrade = $entry.AutoUpgrade
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Log -Level DEBUG -Message "Backed up $($dohEntries.Count) DoH entries from Get-DnsClientDohServerAddress" -Module $script:ModuleName
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Could not retrieve DoH entries via Get-DnsClientDohServerAddress: $_" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
# Get DoH Policy Registry settings
|
||||
$dohPolicySettings = @{}
|
||||
try {
|
||||
$dnsClientPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient"
|
||||
if (Test-Path $dnsClientPath) {
|
||||
$dohPolicy = (Get-ItemProperty -Path $dnsClientPath -Name "DoHPolicy" -ErrorAction SilentlyContinue).DoHPolicy
|
||||
if ($null -ne $dohPolicy) {
|
||||
$dohPolicySettings['DoHPolicy'] = $dohPolicy
|
||||
}
|
||||
}
|
||||
|
||||
$dnsParamsPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters"
|
||||
if (Test-Path $dnsParamsPath) {
|
||||
$enableAutoDoh = (Get-ItemProperty -Path $dnsParamsPath -Name "EnableAutoDoh" -ErrorAction SilentlyContinue).EnableAutoDoh
|
||||
if ($null -ne $enableAutoDoh) {
|
||||
$dohPolicySettings['EnableAutoDoh'] = $enableAutoDoh
|
||||
}
|
||||
|
||||
# NOTE: Global DohFlags no longer used (we use per-adapter DohFlags instead)
|
||||
# Kept for backward compatibility with old backups, but not written anymore
|
||||
}
|
||||
|
||||
Write-Log -Level DEBUG -Message "Backed up DoH policy settings: $($dohPolicySettings.Count) keys" -Module $script:ModuleName
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Could not retrieve DoH policy settings: $_" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
$backupData = @{
|
||||
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
ComputerName = $env:COMPUTERNAME
|
||||
NetshGlobalDoh = $netshGlobalDoh
|
||||
NetshDohEntries = $netshDohEntries
|
||||
DohEntries = $dohEntries
|
||||
DohPolicySettings = $dohPolicySettings
|
||||
Adapters = @()
|
||||
}
|
||||
|
||||
foreach ($adapter in $adapters) {
|
||||
Write-Log -Level DEBUG -Message "Backing up adapter: $($adapter.Name)" -Module $script:ModuleName
|
||||
|
||||
# Get current DNS configuration
|
||||
$dnsConfig = Get-DnsClientServerAddress -InterfaceIndex $adapter.InterfaceIndex -ErrorAction SilentlyContinue
|
||||
|
||||
# Collect DNS addresses first
|
||||
$ipv4Addresses = @()
|
||||
$ipv6Addresses = @()
|
||||
|
||||
foreach ($config in $dnsConfig) {
|
||||
if ($config.AddressFamily -eq 2) { # IPv4
|
||||
if ($config.ServerAddresses.Count -gt 0) {
|
||||
$ipv4Addresses = $config.ServerAddresses
|
||||
}
|
||||
}
|
||||
elseif ($config.AddressFamily -eq 23) { # IPv6
|
||||
if ($config.ServerAddresses.Count -gt 0 -and
|
||||
$config.ServerAddresses -notcontains "fec0:0:0:ffff::1" -and
|
||||
$config.ServerAddresses -notcontains "fec0:0:0:ffff::2" -and
|
||||
$config.ServerAddresses -notcontains "fec0:0:0:ffff::3") {
|
||||
# Only if not DHCP placeholder addresses
|
||||
$ipv6Addresses = $config.ServerAddresses
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# CRITICAL FIX: Determine DHCP status AFTER collecting all addresses
|
||||
# DNS is from DHCP only if NO addresses are configured (neither IPv4 nor IPv6)
|
||||
$isDHCP = ($ipv4Addresses.Count -eq 0) -and ($ipv6Addresses.Count -eq 0)
|
||||
|
||||
# Get DoH configuration for this adapter's DNS servers
|
||||
$dohConfig = @()
|
||||
try {
|
||||
$allDohServers = Get-DnsClientDohServerAddress -ErrorAction SilentlyContinue
|
||||
if ($allDohServers) {
|
||||
foreach ($dohServer in $allDohServers) {
|
||||
if ($ipv4Addresses -contains $dohServer.ServerAddress -or
|
||||
$ipv6Addresses -contains $dohServer.ServerAddress) {
|
||||
$dohConfig += @{
|
||||
ServerAddress = $dohServer.ServerAddress
|
||||
DohTemplate = $dohServer.DohTemplate
|
||||
AllowFallbackToUdp = $dohServer.AllowFallbackToUdp
|
||||
AutoUpgrade = $dohServer.AutoUpgrade
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Could not retrieve DoH configuration: $_" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
# Get DohFlags registry settings for this adapter
|
||||
$dohFlags = @{}
|
||||
try {
|
||||
$interfaceGuid = $adapter.InterfaceGuid
|
||||
$dohFlagsBasePath = "HKLM:\System\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters\$interfaceGuid\DohInterfaceSettings\Doh"
|
||||
|
||||
if (Test-Path $dohFlagsBasePath) {
|
||||
# Check each DNS server IP
|
||||
$allDnsIPs = $ipv4Addresses + $ipv6Addresses
|
||||
foreach ($dnsIP in $allDnsIPs) {
|
||||
$dohFlagsPath = "$dohFlagsBasePath\$dnsIP"
|
||||
if (Test-Path $dohFlagsPath) {
|
||||
$flagValue = (Get-ItemProperty -Path $dohFlagsPath -Name "DohFlags" -ErrorAction SilentlyContinue).DohFlags
|
||||
if ($null -ne $flagValue) {
|
||||
$dohFlags[$dnsIP] = $flagValue
|
||||
Write-Log -Level DEBUG -Message "Found DohFlags for $dnsIP : $flagValue" -Module $script:ModuleName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Could not retrieve DohFlags: $_" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
# Get DHCP DNS Override setting for this adapter
|
||||
$dhcpOverrideDisabled = $null
|
||||
try {
|
||||
$dnsClient = Get-DnsClient -InterfaceIndex $adapter.InterfaceIndex -ErrorAction SilentlyContinue
|
||||
if ($dnsClient) {
|
||||
$dhcpOverrideDisabled = (-not $dnsClient.RegisterThisConnectionsAddress)
|
||||
Write-Log -Level DEBUG -Message "DHCP Override disabled: $dhcpOverrideDisabled" -Module $script:ModuleName
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Could not retrieve DHCP override setting: $_" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
$adapterBackup = @{
|
||||
InterfaceIndex = $adapter.InterfaceIndex
|
||||
InterfaceAlias = $adapter.Name
|
||||
InterfaceDescription = $adapter.InterfaceDescription
|
||||
InterfaceGuid = $adapter.InterfaceGuid
|
||||
Status = $adapter.Status
|
||||
IsDHCP = $isDHCP
|
||||
IPv4Addresses = $ipv4Addresses
|
||||
IPv6Addresses = $ipv6Addresses
|
||||
DoHConfiguration = $dohConfig
|
||||
DohFlags = $dohFlags
|
||||
DhcpOverrideDisabled = $dhcpOverrideDisabled
|
||||
}
|
||||
|
||||
$backupData.Adapters += $adapterBackup
|
||||
|
||||
$statusText = if ($isDHCP) { "DHCP" } else { "Static" }
|
||||
$dnsText = if ($isDHCP) { "from DHCP" } else { "$($ipv4Addresses.Count) IPv4, $($ipv6Addresses.Count) IPv6" }
|
||||
Write-Log -Level INFO -Message " - $($adapter.Name): $statusText ($dnsText)" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Log -Level INFO -Message "[DRYRUN] Would backup DNS settings for $($adapters.Count) adapter(s)" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] - netsh global DoH state" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] - netsh DoH encryption entries" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] - DoH Policy Registry (DoHPolicy, EnableAutoDoh, DohFlags)" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] - Per-adapter DohFlags registry" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] - DHCP DNS override settings" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] - DNS server addresses" -Module $script:ModuleName
|
||||
return "DRYRUN"
|
||||
}
|
||||
|
||||
# Convert to JSON and save using rollback system
|
||||
$backupJson = $backupData | ConvertTo-Json -Depth 10
|
||||
$backupFile = Register-Backup -Type "DNS" -Data $backupJson
|
||||
|
||||
if ($backupFile) {
|
||||
Write-Log -Level SUCCESS -Message "DNS settings backed up successfully" -Module $script:ModuleName
|
||||
return $backupFile
|
||||
}
|
||||
else {
|
||||
Write-Log -Level ERROR -Message "Failed to register DNS backup" -Module $script:ModuleName
|
||||
return $null
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-ErrorLog -Message "Failed to backup DNS settings" -Module $script:ModuleName -ErrorRecord $_
|
||||
return $null
|
||||
}
|
||||
}
|
||||
65
Modules/DNS/Private/Disable-DHCPDnsOverride.ps1
Normal file
65
Modules/DNS/Private/Disable-DHCPDnsOverride.ps1
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
function Disable-DHCPDnsOverride {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Prevent DHCP from overriding manually configured DNS servers
|
||||
|
||||
.DESCRIPTION
|
||||
Sets adapter to NOT register its DNS address and ignore DHCP-provided DNS servers.
|
||||
This ensures your static DNS configuration (e.g., Cloudflare with DoH) cannot be overridden.
|
||||
|
||||
.PARAMETER InterfaceIndex
|
||||
Network adapter interface index
|
||||
|
||||
.PARAMETER DryRun
|
||||
Show what would be configured without applying changes
|
||||
|
||||
.EXAMPLE
|
||||
Disable-DHCPDnsOverride -InterfaceIndex 12
|
||||
|
||||
.NOTES
|
||||
Uses Set-DnsClient cmdlet (PowerShell Best Practice)
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[int]$InterfaceIndex,
|
||||
|
||||
[Parameter()]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
try {
|
||||
$adapter = Get-NetAdapter -InterfaceIndex $InterfaceIndex -ErrorAction Stop
|
||||
$adapterName = $adapter.Name
|
||||
|
||||
Write-Log -Level DEBUG -Message "Preventing DHCP DNS override on adapter: $adapterName" -Module $script:ModuleName
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Log -Level INFO -Message "[DRYRUN] Would disable DHCP DNS override on $adapterName" -Module $script:ModuleName
|
||||
return $true
|
||||
}
|
||||
|
||||
# Set RegisterThisConnectionsAddress = $false to prevent DHCP from overriding DNS
|
||||
Set-DnsClient -InterfaceIndex $InterfaceIndex `
|
||||
-RegisterThisConnectionsAddress $false `
|
||||
-ErrorAction Stop
|
||||
|
||||
Write-Log -Level SUCCESS -Message "DHCP DNS override disabled on $adapterName" -Module $script:ModuleName
|
||||
|
||||
# Verify
|
||||
$dnsClient = Get-DnsClient -InterfaceIndex $InterfaceIndex -ErrorAction SilentlyContinue
|
||||
if ($dnsClient.RegisterThisConnectionsAddress -eq $false) {
|
||||
Write-Log -Level DEBUG -Message "Verification passed: DHCP cannot override DNS" -Module $script:ModuleName
|
||||
return $true
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "Verification failed: DHCP override not disabled" -Module $script:ModuleName
|
||||
return $false
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-ErrorLog -Message "Failed to disable DHCP DNS override on adapter $InterfaceIndex" -Module $script:ModuleName -ErrorRecord $_
|
||||
return $false
|
||||
}
|
||||
}
|
||||
119
Modules/DNS/Private/Enable-DoH.ps1
Normal file
119
Modules/DNS/Private/Enable-DoH.ps1
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
function Enable-DoH {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Enable DNS over HTTPS (DoH) for specified DNS servers
|
||||
|
||||
.DESCRIPTION
|
||||
Configures DNS over HTTPS encryption for privacy and security.
|
||||
Uses Microsoft Best Practice: Add-DnsClientDohServerAddress cmdlet.
|
||||
|
||||
CRITICAL SECURITY SETTINGS:
|
||||
- AllowFallbackToUdp = $False (prevents fallback to unencrypted DNS)
|
||||
- AutoUpgrade = $True (automatically uses DoH when available)
|
||||
|
||||
.PARAMETER ServerAddress
|
||||
DNS server IP address (IPv4 or IPv6)
|
||||
|
||||
.PARAMETER DohTemplate
|
||||
HTTPS URL template for DoH queries
|
||||
|
||||
.PARAMETER DryRun
|
||||
Show what would be configured without applying changes
|
||||
|
||||
.EXAMPLE
|
||||
Enable-DoH -ServerAddress "1.1.1.1" -DohTemplate "https://cloudflare-dns.com/dns-query"
|
||||
|
||||
.OUTPUTS
|
||||
System.Boolean - $true if successful, $false otherwise
|
||||
|
||||
.NOTES
|
||||
Requires Windows 11 or Windows Server 2022+ for native DoH support
|
||||
Fallback to unencrypted DNS is DISABLED for security
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ServerAddress,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$DohTemplate,
|
||||
|
||||
[Parameter()]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
try {
|
||||
Write-Log -Level DEBUG -Message "Configuring DoH for $ServerAddress" -Module $script:ModuleName
|
||||
|
||||
# Determine AllowFallbackToUdp based on DoH mode
|
||||
$allowFallback = if ($script:DoHMode -eq "ALLOW") { $True } else { $False }
|
||||
$fallbackText = if ($allowFallback) { "True (fallback allowed)" } else { "False (no fallback)" }
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Log -Level INFO -Message "[DRYRUN] Would enable DoH for $ServerAddress" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Template: $DohTemplate" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] AllowFallbackToUdp: $fallbackText" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] AutoUpgrade: True" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] Method: PowerShell cmdlet + netsh (dual registration)" -Module $script:ModuleName
|
||||
return $true
|
||||
}
|
||||
|
||||
# Register DoH for this DNS server (overwrites existing if present)
|
||||
Write-Log -Level DEBUG -Message "Registering DoH server: $ServerAddress" -Module "DNS"
|
||||
|
||||
# METHOD 1: PowerShell cmdlet (modern API)
|
||||
try {
|
||||
Add-DnsClientDohServerAddress -ServerAddress $ServerAddress `
|
||||
-DohTemplate $DohTemplate `
|
||||
-AllowFallbackToUdp $allowFallback `
|
||||
-AutoUpgrade $True `
|
||||
-ErrorAction Stop
|
||||
Write-Log -Level DEBUG -Message "PowerShell cmdlet registration successful" -Module "DNS"
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "PowerShell cmdlet failed (expected on some builds): $_" -Module "DNS"
|
||||
}
|
||||
|
||||
# METHOD 2: netsh (critical for actual enforcement - what v1.0 uses!)
|
||||
$udpFallbackMode = if ($allowFallback) { "yes" } else { "no" }
|
||||
try {
|
||||
$netshResult = netsh dnsclient add encryption `
|
||||
server=$ServerAddress `
|
||||
dohtemplate=$DohTemplate `
|
||||
autoupgrade=yes `
|
||||
udpfallback=$udpFallbackMode 2>&1
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Log -Level DEBUG -Message "netsh registration successful for $ServerAddress" -Module "DNS"
|
||||
}
|
||||
else {
|
||||
Write-Log -Level DEBUG -Message "netsh returned exit code $LASTEXITCODE : $netshResult" -Module "DNS"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "netsh registration failed: $_" -Module "DNS"
|
||||
}
|
||||
|
||||
Write-Log -Level DEBUG -Message "Successfully registered DoH for $ServerAddress" -Module "DNS"
|
||||
|
||||
Write-Log -Level SUCCESS -Message "DoH enabled for $ServerAddress" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message " Template: $DohTemplate" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message " Fallback: $(if ($allowFallback) { 'ENABLED (ALLOW mode)' } else { 'DISABLED (REQUIRE mode)' })" -Module $script:ModuleName
|
||||
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
# DoH might not be supported on older Windows versions
|
||||
$errorMessage = $_.Exception.Message
|
||||
|
||||
if ($errorMessage -like "*not recognized*" -or $errorMessage -like "*does not exist*") {
|
||||
Write-Log -Level WARNING -Message "DoH not supported on this Windows version (requires Windows 11 or Server 2022+)" -Module $script:ModuleName
|
||||
Write-Log -Level INFO -Message "DNS will work without encryption - consider upgrading Windows for DoH support" -Module $script:ModuleName
|
||||
return $false
|
||||
}
|
||||
|
||||
Write-ErrorLog -Message "Failed to enable DoH for $ServerAddress" -Module $script:ModuleName -ErrorRecord $_
|
||||
return $false
|
||||
}
|
||||
}
|
||||
150
Modules/DNS/Private/Get-PhysicalAdapters.ps1
Normal file
150
Modules/DNS/Private/Get-PhysicalAdapters.ps1
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
function Get-PhysicalAdapters {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Get physical network adapters (LAN/WLAN) excluding virtual adapters
|
||||
|
||||
.DESCRIPTION
|
||||
Retrieves physical network adapters using multi-layer filtering to exclude:
|
||||
- Virtual adapters (Hyper-V, VMware, VirtualBox)
|
||||
- VPN adapters (TAP, OpenVPN, WireGuard, Cisco, etc.)
|
||||
- Tunnel adapters (Teredo, 6to4, ISATAP)
|
||||
- Loopback adapters
|
||||
|
||||
Uses Microsoft Best Practice: Get-NetAdapter with -Physical switch
|
||||
and additional filtering based on InterfaceDescription patterns.
|
||||
|
||||
.PARAMETER IncludeDisabled
|
||||
Include disabled adapters in results
|
||||
|
||||
.EXAMPLE
|
||||
Get-PhysicalAdapters
|
||||
Returns all active physical network adapters
|
||||
|
||||
.EXAMPLE
|
||||
Get-PhysicalAdapters -IncludeDisabled
|
||||
Returns all physical adapters including disabled ones
|
||||
|
||||
.OUTPUTS
|
||||
Microsoft.Management.Infrastructure.CimInstance#ROOT/StandardCimv2/MSFT_NetAdapter
|
||||
|
||||
.NOTES
|
||||
Uses Get-NetAdapter -Physical for primary filtering (Microsoft Best Practice)
|
||||
Additional filtering excludes known virtual adapter patterns
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter()]
|
||||
[switch]$IncludeDisabled
|
||||
)
|
||||
|
||||
try {
|
||||
Write-Log -Level DEBUG -Message "Retrieving physical network adapters..." -Module $script:ModuleName
|
||||
|
||||
# Layer 1: Get physical adapters only (Microsoft Best Practice)
|
||||
$adapters = Get-NetAdapter -Physical -ErrorAction Stop
|
||||
|
||||
# Layer 2: Filter by status if required
|
||||
if (-not $IncludeDisabled) {
|
||||
# Allow 'Up' (Connected) and 'Disconnected' (Cable unplugged)
|
||||
# Only filter out 'Disabled' (Administratively down) or 'Not Present'
|
||||
$adapters = $adapters | Where-Object { $_.Status -eq "Up" -or $_.Status -eq "Disconnected" }
|
||||
Write-Log -Level DEBUG -Message "Filtering to active/disconnected adapters (excluding disabled)" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
# Layer 3: Exclude virtual adapter patterns (COMPREHENSIVE!)
|
||||
# NOTE: We distinguish between HOST-side and GUEST-side virtual adapters:
|
||||
# - HOST-side (vEthernet, VMware Network Adapter VMnet*) → EXCLUDE
|
||||
# - GUEST-side (Microsoft Hyper-V Network Adapter in VM) → KEEP!
|
||||
$virtualPatterns = @(
|
||||
# Host-side virtualization adapters (NOT guest adapters!)
|
||||
'*vEthernet*', # Hyper-V HOST virtual switch
|
||||
'*VMware Network Adapter*', # VMware HOST adapters (VMnet1, VMnet8)
|
||||
'*VirtualBox Host-Only*', # VirtualBox HOST-only adapter
|
||||
'*Virtual*Adapter*', # Generic virtual adapters
|
||||
'*Container*', '*WSL*', '*Docker*',
|
||||
# Generic VPN protocols
|
||||
'*VPN*', '*OpenVPN*', '*WireGuard*', '*TAP*',
|
||||
'*L2TP*', '*IKEv2*', '*RAS*', '*PPTP*',
|
||||
# Consumer VPN vendors
|
||||
'*NordVPN*', '*NordLynx*', '*ExpressVPN*', '*ProtonVPN*', '*Mullvad*',
|
||||
# Enterprise VPN vendors
|
||||
'*Cisco*', '*Pulse*', '*FortiClient*',
|
||||
'*Palo Alto*', '*PANGP*', # Palo Alto GlobalProtect (no "VPN" in name!)
|
||||
'*F5*', '*Checkpoint*', '*Check Point*', '*Sonicwall*', '*Juniper*',
|
||||
# Tunnel adapters
|
||||
'*Tunnel*', '*Teredo*', '*6to4*', '*ISATAP*', '*Loopback*'
|
||||
)
|
||||
|
||||
# Layer 4: Check for active Windows VPN connections
|
||||
$activeVpnConnections = @()
|
||||
try {
|
||||
$vpnConns = Get-VpnConnection -ErrorAction SilentlyContinue
|
||||
$activeVpnConnections = $vpnConns | Where-Object { $_.ConnectionStatus -eq 'Connected' }
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message "Get-VpnConnection not available or failed: $_" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
$filteredAdapters = @($adapters | Where-Object {
|
||||
$description = $_.InterfaceDescription
|
||||
$name = $_.Name
|
||||
$skipAdapter = $false
|
||||
$skipReason = ""
|
||||
|
||||
# Check if adapter matches any virtual pattern
|
||||
foreach ($pattern in $virtualPatterns) {
|
||||
if ($description -like $pattern -or $name -like $pattern) {
|
||||
$skipAdapter = $true
|
||||
$skipReason = "Pattern match: $pattern"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# Check if InterfaceType is Tunnel (131)
|
||||
if (-not $skipAdapter -and $_.InterfaceType -eq 131) {
|
||||
$skipAdapter = $true
|
||||
$skipReason = "InterfaceType = 131 (Tunnel)"
|
||||
}
|
||||
|
||||
# Check MediaType for Tunnel
|
||||
if (-not $skipAdapter -and $_.MediaType -match "Tunnel") {
|
||||
$skipAdapter = $true
|
||||
$skipReason = "MediaType contains 'Tunnel'"
|
||||
}
|
||||
|
||||
# Check for native Windows VPN connection
|
||||
if (-not $skipAdapter -and $activeVpnConnections) {
|
||||
$currentAdapterAlias = $_.InterfaceAlias
|
||||
$matchingVpn = $activeVpnConnections | Where-Object { $_.InterfaceAlias -eq $currentAdapterAlias }
|
||||
if ($matchingVpn) {
|
||||
$skipAdapter = $true
|
||||
$skipReason = "Native Windows VPN active: $($matchingVpn.Name)"
|
||||
}
|
||||
}
|
||||
|
||||
if ($skipAdapter) {
|
||||
Write-Log -Level DEBUG -Message "Excluding adapter: $name - $skipReason" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
-not $skipAdapter
|
||||
}) # Close @( array wrapper
|
||||
|
||||
if ($filteredAdapters.Count -eq 0) {
|
||||
Write-Log -Level WARNING -Message "No physical network adapters found" -Module $script:ModuleName
|
||||
return @()
|
||||
}
|
||||
|
||||
Write-Log -Level DEBUG -Message "Found $($filteredAdapters.Count) physical network adapter(s)" -Module $script:ModuleName
|
||||
|
||||
foreach ($adapter in $filteredAdapters) {
|
||||
Write-Log -Level DEBUG -Message " - $($adapter.Name) ($($adapter.InterfaceDescription)) [Status: $($adapter.Status)]" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
return $filteredAdapters # Already wrapped as array in line 83
|
||||
}
|
||||
catch {
|
||||
Write-ErrorLog -Message "Failed to retrieve physical network adapters" -Module $script:ModuleName -ErrorRecord $_
|
||||
return @()
|
||||
}
|
||||
}
|
||||
106
Modules/DNS/Private/Reset-DnsState.ps1
Normal file
106
Modules/DNS/Private/Reset-DnsState.ps1
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
function Reset-DnsState {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Cleans up ALL DoH entries from ALL known providers
|
||||
.DESCRIPTION
|
||||
Deletes all DoH registrations (Cloudflare, AdGuard, NextDNS, Quad9)
|
||||
and removes per-adapter DoH registry keys to ensure clean state.
|
||||
|
||||
CRITICAL: This prevents stale DoH entries from previous providers
|
||||
from interfering with new provider settings.
|
||||
.PARAMETER KeepAdapterDns
|
||||
If specified, keeps current DNS server addresses on adapters.
|
||||
Otherwise resets adapters to automatic DHCP DNS.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$KeepAdapterDns
|
||||
)
|
||||
|
||||
Write-Log -Level DEBUG -Message "Cleaning up DNS state (all providers)..." -Module "DNS"
|
||||
|
||||
# 1. Clear network caches FIRST (remove stale mappings before any changes)
|
||||
Write-Log -Level DEBUG -Message "Clearing DNS, ARP and NetBIOS caches..." -Module "DNS"
|
||||
|
||||
# DNS cache
|
||||
ipconfig /flushdns 2>$null | Out-Null
|
||||
|
||||
# ARP cache (use netsh - more reliable than arp -d * on international Windows)
|
||||
netsh interface ip delete arpcache 2>$null | Out-Null
|
||||
|
||||
# NetBIOS name cache
|
||||
nbtstat -R 2>$null | Out-Null
|
||||
|
||||
# 2. Delete ALL known DoH server registrations
|
||||
$allKnownIps = @(
|
||||
# Cloudflare (Standard)
|
||||
'1.1.1.1', '1.0.0.1', '2606:4700:4700::1111', '2606:4700:4700::1001',
|
||||
# Cloudflare (Family - Malware blocking)
|
||||
'1.1.1.2', '1.0.0.2', '2606:4700:4700::1112', '2606:4700:4700::1002',
|
||||
# Cloudflare (Family - Malware + Adult blocking)
|
||||
'1.1.1.3', '1.0.0.3', '2606:4700:4700::1113', '2606:4700:4700::1003',
|
||||
# AdGuard
|
||||
'94.140.14.14', '94.140.15.15', '2a10:50c0::ad1:ff', '2a10:50c0::ad2:ff',
|
||||
# NextDNS
|
||||
'45.90.28.0', '45.90.30.0', '2a07:a8c0::', '2a07:a8c1::',
|
||||
# Quad9
|
||||
'9.9.9.9', '149.112.112.112', '2620:fe::fe', '2620:fe::9'
|
||||
) | Select-Object -Unique
|
||||
|
||||
foreach ($ip in $allKnownIps) {
|
||||
if ([string]::IsNullOrWhiteSpace($ip)) { continue }
|
||||
try {
|
||||
netsh dnsclient delete encryption server=$ip 2>$null | Out-Null
|
||||
Write-Log -Level DEBUG -Message " Deleted DoH entry: $ip" -Module "DNS"
|
||||
}
|
||||
catch {
|
||||
# Ignore - entry might not exist
|
||||
$null = $null
|
||||
}
|
||||
}
|
||||
|
||||
# 3. Clean per-adapter DoH registry keys (all GUIDs)
|
||||
# CRITICAL: We clean these because Enable-DoH + manual DohFlags setting will recreate them
|
||||
$basePath = 'HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters'
|
||||
if (Test-Path $basePath) {
|
||||
Get-ChildItem $basePath -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
$adapterPath = $_.PSPath
|
||||
|
||||
# Remove DohInterfaceSettings (contains both Doh and Doh6 branches)
|
||||
if (Test-Path "$adapterPath\DohInterfaceSettings") {
|
||||
Remove-Item "$adapterPath\DohInterfaceSettings" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Write-Log -Level DEBUG -Message " Cleaned DoH registry: $($_.PSChildName)" -Module "DNS"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 4. Optional: Reset adapters to automatic DHCP DNS
|
||||
$adapterWasReset = $false
|
||||
if (-not $KeepAdapterDns) {
|
||||
Write-Log -Level DEBUG -Message "Resetting adapters to automatic DNS..." -Module "DNS"
|
||||
$adaptersToReset = Get-DnsClient -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.InterfaceOperationalStatus -eq 'Up' }
|
||||
|
||||
foreach ($adapter in $adaptersToReset) {
|
||||
try {
|
||||
Set-DnsClientServerAddress -InterfaceAlias $adapter.InterfaceAlias `
|
||||
-ResetServerAddresses -ErrorAction Stop
|
||||
Write-Log -Level DEBUG -Message " Reset: $($adapter.InterfaceAlias)" -Module "DNS"
|
||||
$adapterWasReset = $true
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level DEBUG -Message " Failed to reset: $($adapter.InterfaceAlias)" -Module "DNS"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 5. Clear caches AGAIN if adapter was reset (DHCP may have created new cache entries)
|
||||
if ($adapterWasReset) {
|
||||
Write-Log -Level DEBUG -Message "Clearing caches again after adapter reset..." -Module "DNS"
|
||||
ipconfig /flushdns 2>$null | Out-Null
|
||||
netsh interface ip delete arpcache 2>$null | Out-Null
|
||||
nbtstat -R 2>$null | Out-Null
|
||||
}
|
||||
|
||||
Write-Log -Level DEBUG -Message "DNS state cleanup complete" -Module $script:ModuleName
|
||||
}
|
||||
183
Modules/DNS/Private/Set-DNSServers.ps1
Normal file
183
Modules/DNS/Private/Set-DNSServers.ps1
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
function Set-DNSServers {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Set DNS server addresses on network adapter
|
||||
|
||||
.DESCRIPTION
|
||||
Configures DNS server addresses (IPv4 and IPv6) on specified network adapter.
|
||||
Uses Microsoft Best Practice: Set-DnsClientServerAddress with -Validate parameter.
|
||||
|
||||
Always configures both IPv4 and IPv6 addresses. Windows will use IPv6 when available,
|
||||
and fall back to IPv4 otherwise.
|
||||
|
||||
.PARAMETER InterfaceIndex
|
||||
Network adapter interface index
|
||||
|
||||
.PARAMETER IPv4Primary
|
||||
Primary IPv4 DNS server address
|
||||
|
||||
.PARAMETER IPv4Secondary
|
||||
Secondary IPv4 DNS server address
|
||||
|
||||
.PARAMETER IPv6Primary
|
||||
Primary IPv6 DNS server address
|
||||
|
||||
.PARAMETER IPv6Secondary
|
||||
Secondary IPv6 DNS server address
|
||||
|
||||
.PARAMETER Validate
|
||||
Validate DNS servers are reachable before applying (recommended)
|
||||
|
||||
.PARAMETER DryRun
|
||||
Show what would be configured without applying changes
|
||||
|
||||
.EXAMPLE
|
||||
Set-DNSServers -InterfaceIndex 12 -IPv4Primary "1.1.1.1" -IPv4Secondary "1.0.0.1" `
|
||||
-IPv6Primary "2606:4700:4700::1111" -IPv6Secondary "2606:4700:4700::1001" -Validate
|
||||
|
||||
.OUTPUTS
|
||||
System.Boolean - $true if successful, $false otherwise
|
||||
|
||||
.NOTES
|
||||
Uses Set-DnsClientServerAddress cmdlet (PowerShell Best Practice)
|
||||
NEVER uses netsh (deprecated legacy method)
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[int]$InterfaceIndex,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$IPv4Primary,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$IPv4Secondary,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$IPv6Primary,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$IPv6Secondary,
|
||||
|
||||
[Parameter()]
|
||||
[switch]$Validate,
|
||||
|
||||
[Parameter()]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
try {
|
||||
$adapter = Get-NetAdapter -InterfaceIndex $InterfaceIndex -ErrorAction Stop
|
||||
$adapterName = $adapter.Name
|
||||
|
||||
Write-Log -Level INFO -Message "Configuring DNS servers on adapter: $adapterName" -Module $script:ModuleName
|
||||
|
||||
# Prepare IPv4 addresses array
|
||||
$ipv4Addresses = @($IPv4Primary, $IPv4Secondary)
|
||||
|
||||
# Prepare IPv6 addresses array
|
||||
$ipv6Addresses = @($IPv6Primary, $IPv6Secondary)
|
||||
|
||||
Write-Log -Level DEBUG -Message " IPv4: $($ipv4Addresses -join ', ')" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message " IPv6: $($ipv6Addresses -join ', ')" -Module $script:ModuleName
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Log -Level INFO -Message "[DRYRUN] Would configure DNS servers on $adapterName" -Module $script:ModuleName
|
||||
return $true
|
||||
}
|
||||
|
||||
# Configure IPv4 DNS servers with retry logic (fixes 0x80004005 errors)
|
||||
Write-Log -Level DEBUG -Message "Setting IPv4 DNS servers..." -Module $script:ModuleName
|
||||
|
||||
$ipv4Params = @{
|
||||
InterfaceIndex = $InterfaceIndex
|
||||
ServerAddresses = $ipv4Addresses
|
||||
ErrorAction = 'Stop'
|
||||
}
|
||||
|
||||
if ($Validate) {
|
||||
$ipv4Params['Validate'] = $true
|
||||
Write-Log -Level DEBUG -Message "Validation enabled for IPv4 DNS servers" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
# Retry logic with fast retries (adapter stabilization or offline detection)
|
||||
$maxRetries = 3
|
||||
$retryDelay = 1 # Fast 1-second retries (no exponential backoff needed)
|
||||
|
||||
for ($attempt = 1; $attempt -le $maxRetries; $attempt++) {
|
||||
try {
|
||||
Set-DnsClientServerAddress @ipv4Params
|
||||
Write-Log -Level SUCCESS -Message "IPv4 DNS servers configured: $($ipv4Addresses -join ', ')" -Module $script:ModuleName
|
||||
break
|
||||
}
|
||||
catch {
|
||||
if ($attempt -lt $maxRetries) {
|
||||
Write-Log -Level DEBUG -Message "Attempt $attempt failed, retrying... ($($_.Exception.Message))" -Module $script:ModuleName
|
||||
Start-Sleep -Seconds $retryDelay
|
||||
}
|
||||
else {
|
||||
# Fallback to netsh if CIM fails (General Error fix - often happens when offline)
|
||||
Write-Log -Level DEBUG -Message "PowerShell cmdlet failed, using netsh fallback..." -Module $script:ModuleName
|
||||
|
||||
try {
|
||||
# Use netsh for IPv4 configuration
|
||||
$netshResult = & netsh interface ip set dns name="$adapterName" source=static address=$IPv4Primary validate=no 2>&1
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
# Add secondary DNS
|
||||
$null = & netsh interface ip add dns name="$adapterName" address=$IPv4Secondary index=2 validate=no 2>&1
|
||||
|
||||
Write-Log -Level SUCCESS -Message "IPv4 DNS configured via netsh fallback: $($ipv4Addresses -join ', ')" -Module $script:ModuleName
|
||||
break # Success, exit retry loop
|
||||
}
|
||||
else {
|
||||
throw "Netsh fallback also failed: $netshResult"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
throw "All DNS configuration methods failed: $_"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Configure IPv6 DNS servers
|
||||
# Note: IPv6 configuration uses the same cmdlet with IPv6 addresses
|
||||
Write-Log -Level DEBUG -Message "Setting IPv6 DNS servers..." -Module $script:ModuleName
|
||||
|
||||
# For IPv6, we need to configure it separately
|
||||
# Get the IPv6 interface
|
||||
$ipv6Interface = Get-NetAdapter -InterfaceIndex $InterfaceIndex |
|
||||
Get-NetAdapterBinding -ComponentID ms_tcpip6 -ErrorAction SilentlyContinue
|
||||
|
||||
if ($ipv6Interface -and $ipv6Interface.Enabled) {
|
||||
try {
|
||||
# Set IPv6 DNS using netsh as PowerShell cmdlet doesn't support dual-stack properly
|
||||
# NOTE: This is one of the few cases where netsh is still needed for IPv6
|
||||
$primaryResult = & netsh interface ipv6 set dnsservers name="$adapterName" source=static address=$IPv6Primary validate=no 2>&1
|
||||
$secondaryResult = & netsh interface ipv6 add dnsservers name="$adapterName" address=$IPv6Secondary index=2 validate=no 2>&1
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Log -Level SUCCESS -Message "IPv6 DNS servers configured: $($ipv6Addresses -join ', ')" -Module $script:ModuleName
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "IPv6 DNS configuration had issues (non-fatal): $primaryResult $secondaryResult" -Module $script:ModuleName
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Could not configure IPv6 DNS (non-fatal): $_" -Module $script:ModuleName
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Log -Level INFO -Message "IPv6 binding is disabled on this adapter - skipping IPv6 DNS server assignment (IPv4 + DoH templates will still be used)" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
# Configuration complete - Windows cmdlets verify automatically
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-ErrorLog -Message "Failed to set DNS servers on interface $InterfaceIndex" -Module $script:ModuleName -ErrorRecord $_
|
||||
return $false
|
||||
}
|
||||
}
|
||||
113
Modules/DNS/Private/Set-DoHPolicy.ps1
Normal file
113
Modules/DNS/Private/Set-DoHPolicy.ps1
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
function Set-DoHPolicy {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Enforce DNS-over-HTTPS (DoH) system-wide according to the selected mode
|
||||
|
||||
.DESCRIPTION
|
||||
Sets Windows registry keys to enforce DoH policy based on $script:DoHMode:
|
||||
- DoHPolicy = 3 (REQUIRE DoH - mandatory encryption, no fallback)
|
||||
- DoHPolicy = 2 (ALLOW DoH - encryption preferred, fallback to UDP allowed)
|
||||
- EnableAutoDoh = 2 (Enable automatic DoH upgrade)
|
||||
- netsh global doh = yes
|
||||
|
||||
DoHPolicy values: 0=Default, 1=Prohibit, 2=Allow, 3=Require
|
||||
|
||||
In REQUIRE mode this prevents Windows from silently falling back to
|
||||
unencrypted DNS on port 53. In ALLOW mode, encrypted DoH is still used
|
||||
for supported servers, but fallback to classic DNS is permitted for
|
||||
VPN/mobile/enterprise scenarios.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Show what would be configured without applying changes
|
||||
|
||||
.EXAMPLE
|
||||
Set-DoHPolicy
|
||||
|
||||
.NOTES
|
||||
Requires Administrator privileges
|
||||
Based on Microsoft DNS Client documentation
|
||||
#>
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter()]
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
try {
|
||||
# Determine DoH mode (REQUIRE or ALLOW)
|
||||
$dohModeValue = if ($script:DoHMode -eq "ALLOW") { 2 } else { 3 }
|
||||
$dohModeText = if ($script:DoHMode -eq "ALLOW") { "ALLOW (with fallback)" } else { "REQUIRE (no fallback)" }
|
||||
|
||||
Write-Log -Level INFO -Message "Enforcing DoH policy ($dohModeText)" -Module $script:ModuleName
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Log -Level INFO -Message "[DRYRUN] Would set DoH policy to $dohModeText" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] DoHPolicy = $dohModeValue ($($script:DoHMode))" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] EnableAutoDoh = 2 (enforce)" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] DohFlags = 1 (use DoH)" -Module $script:ModuleName
|
||||
Write-Log -Level DEBUG -Message "[DRYRUN] netsh global doh = yes" -Module $script:ModuleName
|
||||
return $true
|
||||
}
|
||||
|
||||
# Registry path for DNS Client settings
|
||||
$dnsClientPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient"
|
||||
$dnsParamsPath = "HKLM:\SYSTEM\CurrentControlSet\Services\Dnscache\Parameters"
|
||||
|
||||
# Ensure policy path exists
|
||||
if (-not (Test-Path $dnsClientPath)) {
|
||||
New-Item -Path $dnsClientPath -Force | Out-Null
|
||||
Write-Log -Level DEBUG -Message "Created registry path: $dnsClientPath" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
# CRITICAL ORDER: netsh FIRST (may reset registry values), then Registry entries AFTER
|
||||
# This ensures Registry values persist and are not overwritten by netsh
|
||||
|
||||
# 1. FIRST: Activate DoH globally via netsh (this may reset EnableAutoDoh!)
|
||||
try {
|
||||
netsh dnsclient set global doh=yes 2>&1 | Out-Null
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Log -Level DEBUG -Message "netsh global DoH activated" -Module $script:ModuleName
|
||||
}
|
||||
else {
|
||||
Write-Log -Level WARNING -Message "netsh global DoH returned exit code $LASTEXITCODE" -Module $script:ModuleName
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level WARNING -Message "Could not activate global DoH via netsh: $_" -Module $script:ModuleName
|
||||
}
|
||||
|
||||
# 2. SECOND: EnableAutoDoh = 2 (Enable automatic DoH) - AFTER netsh!
|
||||
if (-not (Test-Path $dnsParamsPath)) {
|
||||
New-Item -Path $dnsParamsPath -Force | Out-Null
|
||||
}
|
||||
$existing = Get-ItemProperty -Path $dnsParamsPath -Name "EnableAutoDoh" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $dnsParamsPath -Name "EnableAutoDoh" -Value 2 -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $dnsParamsPath -Name "EnableAutoDoh" -Value 2 -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level SUCCESS -Message "Set EnableAutoDoh = 2 (Automatic DoH enabled)" -Module $script:ModuleName
|
||||
|
||||
# 3. THIRD: DoHPolicy = 2 (ALLOW) or 3 (REQUIRE) - LAST for highest priority
|
||||
# Values: 0=Default, 1=Prohibit, 2=Allow, 3=Require
|
||||
$existing = Get-ItemProperty -Path $dnsClientPath -Name "DoHPolicy" -ErrorAction SilentlyContinue
|
||||
if ($null -ne $existing) {
|
||||
Set-ItemProperty -Path $dnsClientPath -Name "DoHPolicy" -Value $dohModeValue -Force | Out-Null
|
||||
} else {
|
||||
New-ItemProperty -Path $dnsClientPath -Name "DoHPolicy" -Value $dohModeValue -PropertyType DWord -Force | Out-Null
|
||||
}
|
||||
Write-Log -Level SUCCESS -Message "Set DoHPolicy = $dohModeValue ($dohModeText)" -Module $script:ModuleName
|
||||
|
||||
# NOTE: Global DohFlags removed - we use per-adapter DohFlags instead (set in Invoke-DNSConfiguration)
|
||||
# Per-adapter DohFlags are more reliable and prevent conflicts
|
||||
|
||||
Write-Log -Level SUCCESS -Message "DoH policy verified: $dohModeText" -Module $script:ModuleName
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-ErrorLog -Message "Failed to set DoH policy" -Module $script:ModuleName -ErrorRecord $_
|
||||
return $false
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue