-
Notifications
You must be signed in to change notification settings - Fork 0
Development Guide
Comprehensive guide for developers contributing to the Windows Security Audit Script project.
- Getting Started
- Development Environment
- Project Architecture
- Creating a New Module
- Adding New Checks
- Result Validation and Normalization
- Testing Your Changes
- Remediation System
- Code Style and Standards
- Debugging Techniques
- Performance Optimization
- Documentation Requirements
- Submission Checklist
Required:
- Windows 10/11 or Server 2016+ (for testing)
- PowerShell 5.1 or later
- Git for version control
- Administrator access for testing
Recommended:
- Visual Studio Code with PowerShell extension
- PSScriptAnalyzer for linting
- Pester for testing framework
- Multiple test systems (VMs recommended)
# Fork on GitHub first, then clone your fork
git clone https://github.com/YOUR-USERNAME/Windows-Security-Audit-Project.git
cd Windows-Security-Audit-Project
# Add upstream remote
git remote add upstream https://github.com/Sandler73/Windows-Security-Audit-Project.git
# Verify remotes
git remote -v# Install PSScriptAnalyzer
Install-Module -Name PSScriptAnalyzer -Scope CurrentUser -Force
# Install Pester (testing framework)
Install-Module -Name Pester -Scope CurrentUser -Force -SkipPublisherCheck
# Verify installations
Get-Module -ListAvailable PSScriptAnalyzer, PesterInstall VS Code:
- Download from https://code.visualstudio.com/
Required Extensions:
-
PowerShell (Microsoft)
- Syntax highlighting
- IntelliSense
- Debugging support
-
PowerShell Preview (Microsoft)
- Latest PowerShell features
Recommended Extensions: 3. GitLens - Enhanced Git integration 4. Better Comments - Highlight TODO, FIXME, etc. 5. Trailing Spaces - Highlight trailing whitespace 6. EditorConfig - Consistent formatting
Create .vscode/settings.json in project root:
{
"editor.tabSize": 4,
"editor.insertSpaces": true,
"editor.trimAutoWhitespace": true,
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"files.encoding": "utf8",
"powershell.codeFormatting.preset": "OTBS",
"powershell.scriptAnalysis.enable": true,
"powershell.scriptAnalysis.settingsPath": "PSScriptAnalyzerSettings.psd1"
}Create PSScriptAnalyzerSettings.psd1 in project root:
@{
Rules = @{
PSAvoidUsingCmdletAliases = @{
Enable = $true
}
PSAvoidTrailingWhitespace = @{
Enable = $true
}
PSPlaceOpenBrace = @{
Enable = $true
OnSameLine = $true
}
PSPlaceCloseBrace = @{
Enable = $true
NewLineAfter = $true
}
PSUseConsistentIndentation = @{
Enable = $true
IndentationSize = 4
}
PSUseConsistentWhitespace = @{
Enable = $true
}
}
}Windows-Security-Audit-Project/
├── Windows-Security-Audit.ps1 # Main orchestrator
├── modules/ # Security check modules
│ ├── module-core.ps1 # Foundational security (176 checks)
│ ├── module-cis.ps1 # CIS Benchmarks (222 checks)
│ ├── module-nist.ps1 # NIST 800-53/CSF (473 checks)
│ ├── module-stig.ps1 # DISA STIGs (184 checks)
│ ├── module-nsa.ps1 # NSA Guidance (172 checks)
│ ├── module-cisa.ps1 # CISA CPGs (230 checks)
│ ├── module-ms.ps1 # Microsoft Baselines (313 checks)
│ └── module-ms-defenderatp.ps1 # Defender for Endpoint (86 checks)
├── Reports/ # Generated reports (gitignored)
├── Tests/ # Test scripts (future)
│ └── Module.Tests.ps1
├── README.md
├── LICENSE
├── CONTRIBUTING.md
├── CHANGELOG.md
├── SECURITY.md
└── .gitignore
Main Orchestrator (Windows-Security-Audit.ps1):
- Parse command-line arguments
- Load and execute modules
- Validate and normalize results
- Aggregate results with statistics
- Handle remediation workflows
- Generate output files (HTML, JSON, CSV, XML)
- Display console summary
Security Modules (modules/Module-*.ps1):
- Self-contained checks for specific framework
- Return standardized result objects
- Handle errors gracefully
- Provide remediation guidance
- No direct dependencies between modules
┌──────────────────────────────────────────────────────────────┐
│ 1. Main Script Starts │
│ - Parse parameters │
│ - Initialize shared data │
│ - Check prerequisites │
└────────────────────┬─────────────────────────────────────────┘
│
┌────────────────────▼─────────────────────────────────────────┐
│ 2. For Each Selected Module │
│ - Verify module exists │
│ - Import and execute module │
│ - Pass shared data │
└────────────────────┬─────────────────────────────────────────┘
│
┌────────────────────▼─────────────────────────────────────────┐
│ 3. Module Executes Checks │
│ - Run security checks │
│ - Collect results in standard format │
│ - Return results array │
└────────────────────┬─────────────────────────────────────────┘
│
┌────────────────────▼─────────────────────────────────────────┐
│ 4. Result Validation & Normalization │
│ - Validate result structure │
│ - Normalize Status values │
│ - Repair malformed results │
│ - Track validation statistics │
└────────────────────┬─────────────────────────────────────────┘
│
┌────────────────────▼─────────────────────────────────────────┐
│ 5. Main Script Aggregates │
│ - Collect all validated results │
│ - Calculate statistics per module │
│ - Sort and organize results │
└────────────────────┬─────────────────────────────────────────┘
│
┌────────────────────▼─────────────────────────────────────────┐
│ 6. Remediation (Optional) │
│ - Filter results by status │
│ - Apply targeted or bulk remediation │
│ - Log remediation actions │
└────────────────────┬─────────────────────────────────────────┘
│
┌────────────────────▼─────────────────────────────────────────┐
│ 7. Report Generation │
│ - Generate HTML/CSV/JSON/XML │
│ - Include execution metadata │
│ - Open HTML report in browser │
└──────────────────────────────────────────────────────────────┘
Every check must return this standardized format:
[PSCustomObject]@{
Module = "STIG" # Module name
Category = "STIG - V-220929 (CAT I)" # Framework-specific category
Status = "Fail" # Pass|Fail|Warning|Info|Error
Message = "Guest account is ENABLED" # Brief description
Details = "STIG CAT I: Guest account..." # Full explanation
Remediation = "Disable-LocalUser -Name..." # Fix command/guidance
Timestamp = "2024-12-25 14:30:25" # ISO 8601 format
}Required Properties:
-
Module- Name of the module that generated the result -
Category- Specific control or check identifier -
Message- Brief, actionable finding description -
Status- Must be one of: Pass, Fail, Warning, Info, Error
Optional Properties:
-
Details- Extended explanation and context -
Remediation- PowerShell command or procedure to fix -
Timestamp- When the check was performed
Status Value Guidelines:
| Status | When to Use | Example |
|---|---|---|
| Pass | Configuration meets requirement | "UAC is enabled" |
| Fail | Security issue requiring immediate remediation | "Guest account is ENABLED" |
| Warning | Potential issue or non-critical deviation | "Password age is 90 days (recommend 60)" |
| Info | For awareness, no action needed | "RDP is enabled (verify authorized)" |
| Error | Check could not complete | "Failed to check BitLocker: Access denied" |
Identify a security framework or standard to implement:
- ISO 27001 controls
- PCI-DSS requirements
- HIPAA Security Rule
- SOC 2 controls
- CMMC framework
- Custom organizational standard
- Download official framework documentation
- Identify applicable controls for Windows
- Map controls to specific configurations
- Document check rationale with citations
# Create new module file
New-Item -Path ".\modules\Module-ISO27001.ps1" -ItemType FileCopy this template to your new module:
# Module-FrameworkName.ps1
# Brief Description
# Version: 6.0
# Based on: Framework Name and Version
<#
.SYNOPSIS
Brief summary of what this module checks.
.DESCRIPTION
Detailed description including:
- Framework overview
- Check categories covered
- Applicable systems
- Control mappings
.PARAMETER SharedData
Hashtable containing shared data from the main script
.NOTES
Author: Your Name
Version: 6.0
Based on: Framework Name Version X.X
Last Updated: YYYY-MM-DD
#>
param(
[Parameter(Mandatory=$false)]
[hashtable]$SharedData = @{}
)
$moduleName = "FrameworkName"
$results = @()
# Helper function to add results
function Add-Result {
param($Category, $Status, $Message, $Details = "", $Remediation = "")
$script:results += [PSCustomObject]@{
Module = $moduleName
Category = $Category
Status = $Status
Message = $Message
Details = $Details
Remediation = $Remediation
Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
}
}
Write-Host "`n[$moduleName] Starting checks..." -ForegroundColor Cyan
# ============================================================================
# Category 1: Description
# ============================================================================
Write-Host "[$moduleName] Checking Category 1..." -ForegroundColor Yellow
try {
# Your check logic here
$checkResult = Get-SomeConfiguration -ErrorAction Stop
if ($checkResult -eq $expectedValue) {
Add-Result -Category "$moduleName - Control 1.1" -Status "Pass" `
-Message "Configuration meets requirement" `
-Details "Framework Control 1.1: Detailed explanation of why this matters" `
-Remediation ""
} else {
Add-Result -Category "$moduleName - Control 1.1" -Status "Fail" `
-Message "Configuration does not meet requirement" `
-Details "Framework Control 1.1: Explanation and impact" `
-Remediation "Set-Configuration -Value ExpectedValue"
}
} catch {
Add-Result -Category "$moduleName - Control 1.1" -Status "Error" `
-Message "Failed to check configuration: $_" `
-Details "Framework Control 1.1: Check could not complete" `
-Remediation "Verify permissions and system state"
}
# ============================================================================
# Summary Statistics
# ============================================================================
$passCount = ($results | Where-Object { $_.Status -eq "Pass" }).Count
$failCount = ($results | Where-Object { $_.Status -eq "Fail" }).Count
$warningCount = ($results | Where-Object { $_.Status -eq "Warning" }).Count
$infoCount = ($results | Where-Object { $_.Status -eq "Info" }).Count
$errorCount = ($results | Where-Object { $_.Status -eq "Error" }).Count
$totalChecks = $results.Count
Write-Host "`n[$moduleName] Module completed:" -ForegroundColor Cyan
Write-Host " Total Checks: $totalChecks" -ForegroundColor White
Write-Host " Passed: $passCount" -ForegroundColor Green
Write-Host " Failed: $failCount" -ForegroundColor Red
Write-Host " Warnings: $warningCount" -ForegroundColor Yellow
Write-Host " Info: $infoCount" -ForegroundColor Cyan
Write-Host " Errors: $errorCount" -ForegroundColor Magenta
return $resultsAdd your module to the main script (Windows-Security-Audit.ps1):
- Add to module list in
Get-AvailableModulesfunction:
function Get-AvailableModules {
return @{
"Core" = "modules\module-core.ps1"
"CIS" = "modules\module-cis.ps1"
"MS" = "modules\module-ms.ps1"
"MS-DefenderATP" = "modules\module-ms-defenderatp.ps1"
"NIST" = "modules\module-nist.ps1"
"STIG" = "modules\module-stig.ps1"
"NSA" = "modules\module-nsa.ps1"
"CISA" = "modules\module-cisa.ps1"
"ISO27001" = "modules\Module-ISO27001.ps1" # Add your module
}
}- Add to ValidateSet in param block:
[ValidateSet("Core","CIS","MS","NIST","STIG","NSA","CISA","ISO27001","All")]
[string[]]$Modules = @("All"),- Update the "All" modules array in
Start-SecurityAudit:
$modulesToRun = if ($Modules -contains "All") {
@("Core", "CIS", "MS", "NIST", "STIG", "NSA", "CISA", "ISO27001")
} else {
$Modules
}-
Identify what to check
- Configuration setting
- Registry value
- Service status
- File permission
- Group Policy setting
-
Determine expected value
- What's the secure configuration?
- What's the framework requirement?
- What's the default?
-
Research how to check it
- PowerShell cmdlet available?
- Registry path?
- WMI query?
- External tool needed?
-
Implement the check with proper error handling
-
Test on multiple systems
-
Document with framework reference
# Check if screen saver is required
try {
$screenSaver = Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Control Panel\Desktop" `
-Name "ScreenSaveActive" `
-ErrorAction SilentlyContinue
if ($screenSaver -and $screenSaver.ScreenSaveActive -eq "1") {
Add-Result -Category "Framework - Control 5.3" -Status "Pass" `
-Message "Screen saver is required" `
-Details "Framework Control 5.3: Screen saver prevents shoulder surfing and provides automatic lock" `
-Remediation ""
} else {
Add-Result -Category "Framework - Control 5.3" -Status "Fail" `
-Message "Screen saver is not required" `
-Details "Framework Control 5.3: Configure screen saver to activate after inactivity" `
-Remediation "New-Item -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Control Panel\Desktop' -Force; Set-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Control Panel\Desktop' -Name ScreenSaveActive -Value 1"
}
} catch {
Add-Result -Category "Framework - Control 5.3" -Status "Error" `
-Message "Failed to check screen saver configuration: $_"
}# Check if Windows Update service is running
try {
$wuService = Get-Service -Name "wuauserv" -ErrorAction Stop
if ($wuService.Status -eq "Running") {
Add-Result -Category "Framework - Control 8.2" -Status "Pass" `
-Message "Windows Update service is running" `
-Details "Framework Control 8.2: Automatic updates ensure timely security patches"
} else {
Add-Result -Category "Framework - Control 8.2" -Status "Fail" `
-Message "Windows Update service is not running (Status: $($wuService.Status))" `
-Details "Framework Control 8.2: Windows Update must run for patch management" `
-Remediation "Start-Service wuauserv; Set-Service wuauserv -StartupType Automatic"
}
} catch {
Add-Result -Category "Framework - Control 8.2" -Status "Error" `
-Message "Failed to check Windows Update service: $_"
}# Check if system is domain-joined
try {
$computerSystem = Get-WmiObject -Class Win32_ComputerSystem -ErrorAction Stop
if ($computerSystem.PartOfDomain) {
Add-Result -Category "Framework - Control 2.1" -Status "Pass" `
-Message "System is domain-joined: $($computerSystem.Domain)" `
-Details "Framework Control 2.1: Centralized authentication via domain membership"
} else {
Add-Result -Category "Framework - Control 2.1" -Status "Warning" `
-Message "System is not domain-joined (Workgroup: $($computerSystem.Workgroup))" `
-Details "Framework Control 2.1: Standalone systems require additional local controls"
}
} catch {
Add-Result -Category "Framework - Control 2.1" -Status "Error" `
-Message "Failed to check domain membership: $_"
}Version 6.0 introduces robust result validation and normalization to ensure data quality and consistency across all modules.
The main orchestrator automatically validates every result returned by modules:
function Test-ResultObject {
param([PSCustomObject]$Result, [string]$ModuleName)
$isValid = $true
$issues = @()
# Required properties
$requiredProperties = @("Module", "Category", "Message", "Status")
foreach ($prop in $requiredProperties) {
if (-not $Result.PSObject.Properties[$prop]) {
$isValid = $false
$issues += "Missing: $prop"
}
}
# Validate Status value
if ($Result.Status -and $Result.Status -notin $script:ValidStatusValues) {
$isValid = $false
$issues += "Invalid Status: '$($Result.Status)'"
}
# Log validation issues
if (-not $isValid) {
$script:StatisticsLog.ValidationIssues += [PSCustomObject]@{
Module = $ModuleName
Issues = $issues -join "; "
Timestamp = Get-Date
}
}
return $isValid
}If a result fails validation, the system attempts to repair it:
function Repair-ResultObject {
param([PSCustomObject]$Result, [string]$ModuleName)
# Ensure Module property exists
if (-not $Result.Module) {
$Result | Add-Member -NotePropertyName "Module" -NotePropertyValue $ModuleName -Force
}
# Ensure Category exists
if (-not $Result.Category) {
$Result | Add-Member -NotePropertyName "Category" -NotePropertyValue "Uncategorized" -Force
}
# Normalize Status value (case-insensitive)
if ($Result.Status) {
$matchedStatus = $script:ValidStatusValues |
Where-Object { $_.ToLower() -eq $Result.Status.ToLower() } |
Select-Object -First 1
if ($matchedStatus) {
$Result.Status = $matchedStatus
}
}
# Add missing optional properties
if (-not $Result.Details) {
$Result | Add-Member -NotePropertyName "Details" -NotePropertyValue "" -Force
}
if (-not $Result.Remediation) {
$Result | Add-Member -NotePropertyName "Remediation" -NotePropertyValue "" -Force
}
return $Result
}The system tracks statistics for each module:
function Get-ModuleStatistics {
param([array]$Results)
return [PSCustomObject]@{
Total = $Results.Count
Pass = ($Results | Where-Object { $_.Status -eq "Pass" }).Count
Fail = ($Results | Where-Object { $_.Status -eq "Fail" }).Count
Warning = ($Results | Where-Object { $_.Status -eq "Warning" }).Count
Info = ($Results | Where-Object { $_.Status -eq "Info" }).Count
Error = ($Results | Where-Object { $_.Status -eq "Error" }).Count
}
}-
Always use the
Add-Resulthelper function - it ensures proper structure - Use exact Status values - Pass, Fail, Warning, Info, Error (case-sensitive)
- Include all required properties - Module, Category, Message, Status
- Provide meaningful Messages - Brief, actionable descriptions
- Include remediation when possible - Help users fix issues
# 1. Syntax check
Get-Content ".\modules\Module-YourModule.ps1" -Raw |
Invoke-Expression -ErrorAction Stop
# 2. PSScriptAnalyzer
Invoke-ScriptAnalyzer -Path ".\modules\Module-YourModule.ps1"
# 3. Test module independently
$results = & ".\modules\Module-YourModule.ps1"
$results | Format-Table
# 4. Validate result structure
$results | ForEach-Object {
Write-Host "Checking: $($_.Message)"
Write-Host " Module: $($_.Module)"
Write-Host " Status: $($_.Status)"
Write-Host " Has Category: $($null -ne $_.Category)"
Write-Host " Has Details: $($null -ne $_.Details)"
}
# 5. Test with main orchestrator
.\Windows-Security-Audit.ps1 -Modules YourModule
# 6. Verify all output formats
.\Windows-Security-Audit.ps1 -Modules YourModule -OutputFormat CSV
.\Windows-Security-Audit.ps1 -Modules YourModule -OutputFormat JSON
.\Windows-Security-Audit.ps1 -Modules YourModule -OutputFormat XMLMinimum test matrix:
| OS | Edition | Result |
|---|---|---|
| Windows 10 22H2 | Pro | ✅ Passed |
| Windows 11 23H2 | Enterprise | ✅ Passed |
| Server 2019 | Standard | ✅ Passed |
| Server 2022 | Datacenter | ✅ Passed |
Create Tests/Module-YourModule.Tests.ps1:
Describe "Module-YourModule" {
BeforeAll {
$results = & "$PSScriptRoot\..\modules\Module-YourModule.ps1"
}
It "Returns results array" {
$results | Should -Not -BeNullOrEmpty
$results | Should -BeOfType [PSCustomObject]
}
It "Each result has required properties" {
foreach ($result in $results) {
$result.Module | Should -Not -BeNullOrEmpty
$result.Category | Should -Not -BeNullOrEmpty
$result.Status | Should -BeIn @("Pass", "Fail", "Warning", "Info", "Error")
$result.Message | Should -Not -BeNullOrEmpty
$result.Timestamp | Should -Not -BeNullOrEmpty
}
}
It "Has at least 10 checks" {
$results.Count | Should -BeGreaterOrEqual 10
}
It "All Status values are valid" {
$validStatuses = @("Pass", "Fail", "Warning", "Info", "Error")
foreach ($result in $results) {
$result.Status | Should -BeIn $validStatuses
}
}
}
# Run tests
Invoke-Pester -Path ".\Tests\Module-YourModule.Tests.ps1"If your module includes remediation commands:
# Test remediation in safe environment
.\Windows-Security-Audit.ps1 -Modules YourModule -RemediateIssues_Fail
# Test auto-remediation workflow
.\Windows-Security-Audit.ps1 -Modules YourModule -RemediateIssues_Fail -AutoRemediate
# Test with remediation file
.\Windows-Security-Audit.ps1 -AutoRemediate -RemediationFile "test-remediation.json"Version 6.0 includes a comprehensive remediation system that can fix security issues automatically or interactively.
Interactive Mode:
# Prompt for each issue
.\Windows-Security-Audit.ps1 -RemediateIssues
# Remediate only failures
.\Windows-Security-Audit.ps1 -RemediateIssues_Fail
# Remediate only warnings
.\Windows-Security-Audit.ps1 -RemediateIssues_Warning
# Remediate specific types
.\Windows-Security-Audit.ps1 -RemediateIssues_Fail -RemediateIssues_WarningAutomatic Mode:
# Auto-remediate all failures (requires double confirmation)
.\Windows-Security-Audit.ps1 -RemediateIssues_Fail -AutoRemediateTargeted Mode:
# Remediate specific issues from exported JSON
.\Windows-Security-Audit.ps1 -AutoRemediate -RemediationFile "selected-issues.json"- Run Audit:
.\Windows-Security-Audit.ps1-
Review HTML Report - Inspect findings and select issues to fix
-
Export Selected Issues - Use "Export Selected" button to create JSON
-
Run Auto-Remediation:
.\Windows-Security-Audit.ps1 -AutoRemediate -RemediationFile "Selected-Report.json"Guidelines:
- Test remediation commands thoroughly before committing
- Use idempotent commands when possible
- Include error handling
- Document any prerequisites or side effects
- Consider system restart requirements
Example - Safe Remediation:
Add-Result -Category "Security" -Status "Fail" `
-Message "Screen saver timeout is too long" `
-Remediation "Set-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name ScreenSaveTimeOut -Value 900"Example - Complex Remediation:
Add-Result -Category "Security" -Status "Fail" `
-Message "BitLocker not enabled" `
-Remediation @"
if ((Get-Tpm).TpmReady) {
Enable-BitLocker -MountPoint C: -EncryptionMethod XtsAes256 -TpmProtector
} else {
Write-Host 'TPM not ready - enable in BIOS first' -ForegroundColor Yellow
}
"@- Double Confirmation - Auto-mode requires typing "YES" and "CONFIRM"
- Remediation Logging - All actions logged to JSON file
- Execution Statistics - Success/failure counts displayed
- Error Handling - Failed remediations logged, don't block others
- Restart Prompt - Optional restart after remediation
Naming:
# Functions: Verb-Noun
function Get-SecurityConfig { }
# Variables: camelCase
$userName = "admin"
$isCompliant = $true
# Constants: UPPER_CASE
$MAX_RETRIES = 3
# Parameters: PascalCase
param([string]$ModuleName)Formatting:
# Opening braces on same line
if ($condition) {
# Code
}
# 4-space indentation
function Test-Something {
if ($test) {
Write-Host "Testing"
}
}
# Space after commas
$array = @("one", "two", "three")
# Spaces around operators
$result = $value1 + $value2Comments:
# Single-line for brief explanations
$result = Get-Data # Retrieves configuration
<#
Multi-line comments for:
- Function documentation
- Complex logic
- Section headers
#>Always use try/catch:
# Good
try {
$value = Get-ItemProperty -Path "HKLM:\Path" -Name "Setting" -ErrorAction Stop
Add-Result -Status "Pass" -Message "Found"
} catch {
Add-Result -Status "Error" -Message "Failed: $_"
}
# Bad - no error handling
$value = Get-ItemProperty -Path "HKLM:\Path" -Name "Setting"Use appropriate ErrorAction:
# Stop on error (use in try/catch)
Get-Service -Name "Service" -ErrorAction Stop
# Continue silently (when failure is expected)
Get-Service -Name "Optional" -ErrorAction SilentlyContinue
# Inquire for user input
Remove-Item -Path "Important" -ErrorAction Inquire# Pass - Configuration meets requirement
Add-Result -Status "Pass" -Message "UAC is enabled"
# Fail - Security issue, must remediate
Add-Result -Status "Fail" -Message "Guest account is ENABLED"
# Warning - Potential issue or non-critical deviation
Add-Result -Status "Warning" -Message "Password age is 90 days (recommend 60)"
# Info - For awareness, no action needed
Add-Result -Status "Info" -Message "RDP is enabled (verify authorized)"
# Error - Check could not complete
Add-Result -Status "Error" -Message "Failed to check BitLocker: Access denied"# Add debug statements
Write-Debug "Checking registry path: HKLM:\Path"
Write-Debug "Retrieved value: $value"
# Run with -Debug
.\Windows-Security-Audit.ps1 -Debug -Modules YourModule# Add verbose statements
Write-Verbose "Processing check 1 of 50"
Write-Verbose "Current status: $status"
# Run with -Verbose
.\Windows-Security-Audit.ps1 -Verbose -Modules YourModule- Set breakpoint (F9) on line
- Press F5 to start debugging
- Step through code (F10/F11)
- Inspect variables in Debug pane
Issue: Module not found
# Check module path
Test-Path ".\modules\Module-YourModule.ps1"
# Verify current directory
Get-Location
# Run from correct directory
Set-Location "C:\Path\To\Project"Issue: Results not appearing
# Ensure module returns results
$results = & ".\modules\Module-YourModule.ps1"
$results.Count # Should be > 0
$results | Format-Table
# Check return statement at end
return $resultsIssue: Permission errors
# Verify running as administrator
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
# Some checks require adminIssue: Invalid Status values after validation
# Check for case sensitivity
$result.Status = "pass" # Wrong - will be normalized to "Pass"
$result.Status = "Pass" # Correct
# Check spelling
$result.Status = "Failed" # Wrong - will become "Error"
$result.Status = "Fail" # Correct1. Minimize WMI/CIM Calls
# Bad - Multiple calls
$os = Get-WmiObject Win32_OperatingSystem
$cs = Get-WmiObject Win32_ComputerSystem
$bios = Get-WmiObject Win32_BIOS
# Good - Single call with shared data
if (-not $SharedData.WMI) {
$SharedData.WMI = @{
OS = Get-WmiObject Win32_OperatingSystem
CS = Get-WmiObject Win32_ComputerSystem
BIOS = Get-WmiObject Win32_BIOS
}
}2. Cache Registry Reads
# Bad - Read same path multiple times
$val1 = Get-ItemProperty "HKLM:\Path" -Name "Setting1"
$val2 = Get-ItemProperty "HKLM:\Path" -Name "Setting2"
# Good - Read once
$regPath = Get-ItemProperty "HKLM:\Path"
$val1 = $regPath.Setting1
$val2 = $regPath.Setting23. Use -ErrorAction Appropriately
# Bad - Let errors write to console
Get-Service "NonExistent"
# Good - Suppress expected errors
Get-Service "NonExistent" -ErrorAction SilentlyContinue4. Batch Similar Operations
# Bad - Check services one at a time
foreach ($svc in $serviceNames) {
$service = Get-Service -Name $svc
}
# Good - Get all at once
$services = Get-Service -Name $serviceNames -ErrorAction SilentlyContinue# Time your module
Measure-Command {
& ".\modules\Module-YourModule.ps1"
}
# Target: < 1 minute for most modules
# Acceptable: < 3 minutes for comprehensive modules<#
.SYNOPSIS
One-line summary (< 80 chars)
.DESCRIPTION
Detailed description including:
- What framework/standard is implemented
- Major check categories
- Applicable systems
- Version of framework
.PARAMETER SharedData
Hashtable for shared data between modules
.OUTPUTS
Array of PSCustomObjects with check results
.EXAMPLE
& .\modules\Module-YourModule.ps1
Runs all checks in the module
.EXAMPLE
$results = & .\modules\Module-YourModule.ps1
$results | Where-Object { $_.Status -eq "Fail" }
Gets only failed checks
.NOTES
Author: Your Name
Version: 6.0
Based on: Framework Name Version 1.0
Last Updated: 2024-12-30
.LINK
https://framework-website.org/documentation
#>Each check should have:
# ============================================================================
# Framework Reference: Control ID - Brief Description
# ============================================================================
# Control 1.1: Specific requirement description
try {
# Check logic
Add-Result -Category "Framework - Control 1.1" -Status "Pass" `
-Message "Brief result message" `
-Details "Framework Control 1.1: Why this matters and what it protects against" `
-Remediation "Exact PowerShell command or GPO path to fix"
} catch {
Add-Result -Status "Error" -Message "What failed: $_"
}# Good - Explains why
# Use SilentlyContinue because service may not exist on all systems
$service = Get-Service "Optional" -ErrorAction SilentlyContinue
# Bad - States the obvious
# Get the service
$service = Get-Service "Optional"
# Good - Explains complex logic
# Calculate password age in days, accounting for accounts that have never logged on
$passwordAge = if ($user.PasswordLastSet) {
(Get-Date) - $user.PasswordLastSet
} else {
[TimeSpan]::MaxValue
}When adding a new module, create wiki page with:
- Module overview
- Check categories
- Framework reference
- Usage examples
- When to use
- Sample output
Before submitting a pull request:
- Passes PSScriptAnalyzer with no errors
- Follows project coding standards
- No hardcoded paths or credentials
- Proper error handling (try/catch)
- Meaningful variable names
- Comments explain complex logic
- Uses Add-Result helper function
- Status values are correct (Pass/Fail/Warning/Info/Error)
- Tested on Windows 10/11
- Tested on Windows Server (if applicable)
- Module runs independently
- Integrates with main orchestrator
- All output formats generate correctly (HTML/CSV/JSON/XML)
- No performance regression (< 3 minutes)
- Results pass validation (all required properties present)
- Module statistics display correctly
- Module header documentation complete
- Each check properly documented
- Framework references included
- Remediation commands provided and tested
- Wiki page created/updated
- CHANGELOG.md updated
- Committed to feature branch
- Descriptive commit messages
- Branch name follows convention (feature/module-name)
- No merge conflicts with main
- Squashed commits if needed
- Clear title and description
- Links to related issues
- Screenshots if UI changes
- Test results included
- Reviewer assigned
- All results have Module property
- All results have Category property
- All results have Message property
- All results have Status property
- Status values are one of: Pass, Fail, Warning, Info, Error
- Remediation commands are safe and tested
- No validation warnings in console output
Questions during development:
- GitHub Discussions - General questions
- GitHub Issues - Bugs or feature requests
Resources:
Version 6.0 (Current)
- Added result validation and normalization system
- Enhanced remediation with auto-mode and targeted fixing
- Improved HTML report with dark mode and export options
- Added XML export format for SIEM integration
- Enhanced module statistics tracking
- Better error handling and logging
Version 5.0
- Modular architecture introduced
- Support for multiple frameworks
- HTML report generation
- Basic remediation support
Ready to contribute? See CONTRIBUTING.md for submission process!