Skip to content

Development Guide

Ryan edited this page Mar 4, 2026 · 5 revisions

Development Guide

Comprehensive guide for developers contributing to the Windows Security Audit Script project.

Table of Contents


Getting Started

Prerequisites for Development

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 and Clone Repository

# 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 Development Tools

# 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, Pester

Development Environment

Visual Studio Code Setup

Install VS Code:

Required Extensions:

  1. PowerShell (Microsoft)

    • Syntax highlighting
    • IntelliSense
    • Debugging support
  2. 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

VS Code Settings

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"
}

PSScriptAnalyzer Configuration

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
        }
    }
}

Project Architecture

Directory Structure

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

Component Responsibilities

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

Data Flow

┌──────────────────────────────────────────────────────────────┐
│  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                            │
└──────────────────────────────────────────────────────────────┘

Result Object Structure

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"

Creating a New Module

Step 1: Choose Your Framework

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

Step 2: Research Official Documentation

  • Download official framework documentation
  • Identify applicable controls for Windows
  • Map controls to specific configurations
  • Document check rationale with citations

Step 3: Create Module File

# Create new module file
New-Item -Path ".\modules\Module-ISO27001.ps1" -ItemType File

Step 4: Module Template

Copy 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 $results

Step 5: Update Main Orchestrator

Add your module to the main script (Windows-Security-Audit.ps1):

  1. Add to module list in Get-AvailableModules function:
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
    }
}
  1. Add to ValidateSet in param block:
[ValidateSet("Core","CIS","MS","NIST","STIG","NSA","CISA","ISO27001","All")]
[string[]]$Modules = @("All"),
  1. Update the "All" modules array in Start-SecurityAudit:
$modulesToRun = if ($Modules -contains "All") {
    @("Core", "CIS", "MS", "NIST", "STIG", "NSA", "CISA", "ISO27001")
} else {
    $Modules
}

Adding New Checks

Check Development Process

  1. Identify what to check

    • Configuration setting
    • Registry value
    • Service status
    • File permission
    • Group Policy setting
  2. Determine expected value

    • What's the secure configuration?
    • What's the framework requirement?
    • What's the default?
  3. Research how to check it

    • PowerShell cmdlet available?
    • Registry path?
    • WMI query?
    • External tool needed?
  4. Implement the check with proper error handling

  5. Test on multiple systems

  6. Document with framework reference

Example: Adding a Registry Check

# 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: $_"
}

Example: Adding a Service Check

# 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: $_"
}

Example: Adding a WMI Check

# 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: $_"
}

Result Validation and Normalization

Overview

Version 6.0 introduces robust result validation and normalization to ensure data quality and consistency across all modules.

Validation Process

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
}

Normalization

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
}

Module Statistics

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
    }
}

Best Practices for Module Developers

  1. Always use the Add-Result helper function - it ensures proper structure
  2. Use exact Status values - Pass, Fail, Warning, Info, Error (case-sensitive)
  3. Include all required properties - Module, Category, Message, Status
  4. Provide meaningful Messages - Brief, actionable descriptions
  5. Include remediation when possible - Help users fix issues

Testing Your Changes

Manual Testing Checklist

# 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 XML

Test on Multiple Systems

Minimum test matrix:

OS Edition Result
Windows 10 22H2 Pro ✅ Passed
Windows 11 23H2 Enterprise ✅ Passed
Server 2019 Standard ✅ Passed
Server 2022 Datacenter ✅ Passed

Unit Testing with Pester

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"

Testing Remediation

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"

Remediation System

Overview

Version 6.0 includes a comprehensive remediation system that can fix security issues automatically or interactively.

Remediation Modes

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_Warning

Automatic Mode:

# Auto-remediate all failures (requires double confirmation)
.\Windows-Security-Audit.ps1 -RemediateIssues_Fail -AutoRemediate

Targeted Mode:

# Remediate specific issues from exported JSON
.\Windows-Security-Audit.ps1 -AutoRemediate -RemediationFile "selected-issues.json"

Remediation Workflow

  1. Run Audit:
   .\Windows-Security-Audit.ps1
  1. Review HTML Report - Inspect findings and select issues to fix

  2. Export Selected Issues - Use "Export Selected" button to create JSON

  3. Run Auto-Remediation:

   .\Windows-Security-Audit.ps1 -AutoRemediate -RemediationFile "Selected-Report.json"

Writing Remediation Commands

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
}
"@

Remediation Safety Features

  1. Double Confirmation - Auto-mode requires typing "YES" and "CONFIRM"
  2. Remediation Logging - All actions logged to JSON file
  3. Execution Statistics - Success/failure counts displayed
  4. Error Handling - Failed remediations logged, don't block others
  5. Restart Prompt - Optional restart after remediation

Code Style and Standards

PowerShell Conventions

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 + $value2

Comments:

# Single-line for brief explanations
$result = Get-Data  # Retrieves configuration

<#
Multi-line comments for:
- Function documentation
- Complex logic
- Section headers
#>

Error Handling

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

Status Level Guidelines

# 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"

Debugging Techniques

Debug Output

# 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

Verbose Output

# 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

VS Code Debugging

  1. Set breakpoint (F9) on line
  2. Press F5 to start debugging
  3. Step through code (F10/F11)
  4. Inspect variables in Debug pane

Common Issues

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 $results

Issue: Permission errors

# Verify running as administrator
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

# Some checks require admin

Issue: 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"    # Correct

Performance Optimization

Best Practices

1. 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.Setting2

3. Use -ErrorAction Appropriately

# Bad - Let errors write to console
Get-Service "NonExistent"

# Good - Suppress expected errors
Get-Service "NonExistent" -ErrorAction SilentlyContinue

4. 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

Measuring Performance

# Time your module
Measure-Command {
    & ".\modules\Module-YourModule.ps1"
}

# Target: < 1 minute for most modules
# Acceptable: < 3 minutes for comprehensive modules

Documentation Requirements

Module Header Documentation

<#
.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
#>

Check 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: $_"
}

Inline Comments

# 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
}

Wiki Documentation

When adding a new module, create wiki page with:

  • Module overview
  • Check categories
  • Framework reference
  • Usage examples
  • When to use
  • Sample output

Submission Checklist

Before submitting a pull request:

Code Quality

  • 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)

Testing

  • 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

Documentation

  • Module header documentation complete
  • Each check properly documented
  • Framework references included
  • Remediation commands provided and tested
  • Wiki page created/updated
  • CHANGELOG.md updated

Git

  • Committed to feature branch
  • Descriptive commit messages
  • Branch name follows convention (feature/module-name)
  • No merge conflicts with main
  • Squashed commits if needed

Pull Request

  • Clear title and description
  • Links to related issues
  • Screenshots if UI changes
  • Test results included
  • Reviewer assigned

Validation Checklist

  • 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

Getting Help

Questions during development:

Resources:


Version History

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!

Clone this wiki locally