<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->
---
name: building-powershell-security-scripts
description: >-
  Build PowerShell-based security scripts for Windows endpoint hardening,
  Active Directory enumeration, event log analysis, and compliance auditing.
  Covers constrained language mode bypass detection, AMSI interaction,
  remoting via WinRM, and structured JSON/CSV output for SIEM ingestion.
domain: cybersecurity
subdomain: automation-scripting
tags:
  - powershell
  - windows-security
  - active-directory
  - event-log-analysis
  - endpoint-hardening
  - winrm
version: "1.0"
author: defconxt
license: AGPL-3.0
metadata:
  mitre-attack: ["T1059.001"]
---

# Building PowerShell Security Scripts

## Overview

PowerShell is the native automation language for Windows security operations.
This skill covers building production-quality security scripts that enumerate
AD objects, parse Windows event logs, audit local security policy, and
generate structured output for downstream consumption. Scripts follow
PowerShell best practices: approved verbs, parameter validation, pipeline
support, and `-WhatIf` for destructive actions.

Mode: `[MODE: BLUE]` — Defensive scripting with offensive awareness.

## Prerequisites

| Requirement | Details |
|---|---|
| PowerShell 5.1+ (Windows) or PowerShell 7+ (cross-platform) | Required |
| ActiveDirectory module (`Install-WindowsFeature RSAT-AD-PowerShell`) | Required |
| Remote Server Administration Tools for AD enumeration | Required |
| WinRM enabled for remote execution (`Enable-PSRemoting -Force`) | Required |

## Key Concepts

### Local Security Policy Audit

```powershell
function Get-SecurityPolicyAudit {
    [CmdletBinding()]
    param(
        [string]$ComputerName = $env:COMPUTERNAME
    )
    $result = @{
        Computer = $ComputerName
        Timestamp = (Get-Date -Format "o")
        Policies = @()
    }
    # Export local security policy
    $tempFile = [System.IO.Path]::GetTempFileName()
    secedit /export /cfg $tempFile /quiet
    $content = Get-Content $tempFile
    Remove-Item $tempFile -Force

    foreach ($line in $content) {
        if ($line -match '^(\w+)\s*=\s*(.+)$') {
            $result.Policies += @{
                Setting = $Matches[1]
                Value   = $Matches[2].Trim()
            }
        }
    }
    $result | ConvertTo-Json -Depth 5
}
```

### Active Directory Enumeration

```powershell
function Get-ADSecurityReport {
    [CmdletBinding()]
    param(
        [int]$InactiveDays = 90,
        [switch]$IncludeDisabled
    )

    $cutoff = (Get-Date).AddDays(-$InactiveDays)
    $staleUsers = Get-ADUser -Filter {
        LastLogonDate -lt $cutoff -and Enabled -eq $true
    } -Properties LastLogonDate, PasswordLastSet, MemberOf |
    Select-Object SamAccountName, LastLogonDate, PasswordLastSet,
        @{N='Groups';E={($_.MemberOf | ForEach-Object {
            ($_ -split ',')[0] -replace 'CN=',''
        }) -join ';'}}

    $adminUsers = Get-ADGroupMember -Identity "Domain Admins" -Recursive |
        Get-ADUser -Properties LastLogonDate, PasswordLastSet

    @{
        Timestamp     = (Get-Date -Format "o")
        StaleUsers    = @($staleUsers)
        DomainAdmins  = @($adminUsers | Select-Object SamAccountName, LastLogonDate)
        StaleCount    = $staleUsers.Count
        AdminCount    = $adminUsers.Count
    } | ConvertTo-Json -Depth 5
}
```

### Windows Event Log Parser

```powershell
function Search-SecurityEvents {
    [CmdletBinding()]
    param(
        [ValidateSet('Logon','FailedLogon','PrivEsc','ServiceInstall')]
        [string]$EventType = 'FailedLogon',
        [int]$Hours = 24,
        [string]$ComputerName = $env:COMPUTERNAME
    )

    $eventMap = @{
        Logon          = @{LogName='Security'; Id=4624}
        FailedLogon    = @{LogName='Security'; Id=4625}
        PrivEsc        = @{LogName='Security'; Id=4672}
        ServiceInstall = @{LogName='System';   Id=7045}
    }
    $filter = $eventMap[$EventType]
    $startTime = (Get-Date).AddHours(-$Hours)

    Get-WinEvent -FilterHashtable @{
        LogName   = $filter.LogName
        Id        = $filter.Id
        StartTime = $startTime
    } -ComputerName $ComputerName -ErrorAction SilentlyContinue |
    ForEach-Object {
        @{
            TimeCreated = $_.TimeCreated.ToString("o")
            EventId     = $_.Id
            Message     = $_.Message.Substring(0, [Math]::Min(200, $_.Message.Length))
            Computer    = $_.MachineName
        }
    } | ConvertTo-Json -Depth 3
}
```

## Workflow

### Step 1: Audit Local Security Policy

```bash
node scripts/agent.js --action policy-audit --target localhost
```

### Step 2: Enumerate Stale AD Accounts

```bash
node scripts/agent.js --action ad-stale-users --inactive-days 90
```

### Step 3: Search Failed Logon Events

```bash
node scripts/agent.js --action event-search --event-type FailedLogon --hours 24
```

## Verification

- [ ] Policy audit exports all secedit settings as structured JSON
- [ ] AD enumeration identifies stale users and privileged accounts
- [ ] Event log parser returns time-bounded results with truncated messages
- [ ] All scripts handle access-denied errors gracefully
- [ ] Exit code 0 on success, 1 on findings, 2 on error

## References

- [PowerShell Security Best Practices](https://learn.microsoft.com/en-us/powershell/scripting/security/)
- [MITRE ATT&CK T1059.001 — PowerShell](https://attack.mitre.org/techniques/T1059/001/)
- [CIS Microsoft Windows Benchmarks](https://www.cisecurity.org/benchmark/microsoft_windows_desktop)
- [Windows Security Event IDs](https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/plan/appendix-l--events-to-monitor)
