Lab 04 — Windows Security Automation with PowerShell
Mission Information
Section titled “Mission Information”Difficulty: Intermediate
Estimated Time: 120–150 minutes
Primary Language: PowerShell
Security Domain: Windows Security / SOC / Endpoint Security / System Hardening
Environment: Windows lab system
Automation Type: Defensive Security Assessment
Mission
Section titled “Mission”Your task is to build a PowerShell-based Windows security assessment tool for a Windows system that you own or are explicitly authorized to administer.
The script will automatically collect:
SYSTEM INFORMATION
CURRENT USER CONTEXT
LOCAL USERS
LOCAL ADMINISTRATORS
RUNNING PROCESSES
WINDOWS SERVICES
NETWORK CONFIGURATION
LISTENING CONNECTIONS
WINDOWS EVENT LOGS
FAILED LOGINS
SUCCESSFUL LOGINS
MICROSOFT DEFENDER STATUS
WINDOWS FIREWALL STATUS
SCHEDULED TASKS
SELECTED SECURITY SETTINGSThe final workflow will be:
WINDOWS HOST ↓POWERSHELL ↓COLLECT ↓FILTER ↓NORMALIZE ↓ANALYZE ↓EXPORT ↓SECURITY REPORTWhy This Lab Matters
Section titled “Why This Lab Matters”Windows remains one of the most common enterprise operating systems.
Security professionals regularly investigate:
WORKSTATIONS
DOMAIN MEMBERS
APPLICATION SERVERS
ADMINISTRATIVE SERVERS
JUMP HOSTS
SECURITY WORKSTATIONS
CLOUD WINDOWS VMsDuring an assessment or incident, you often need answers such as:
Which Windows version is running?
Who is currently logged in?
Which local accounts exist?
Who belongs to Administrators?
Which processes are running?
Which services are active?
Which ports are listening?
What failed authentication events occurred?
Is Defender running?
Are firewall profiles enabled?
Which scheduled tasks exist?PowerShell makes these questions highly automatable.
Learning Objectives
Section titled “Learning Objectives”By completing this lab, you should be able to:
BUILD A POWERSHELL SCRIPT
USE FUNCTIONS
USE PARAMETERS
USE OBJECT PIPELINES
QUERY CIM
QUERY LOCAL USERS
QUERY LOCAL GROUPS
QUERY PROCESSES
QUERY SERVICES
QUERY TCP CONNECTIONS
QUERY WINDOWS EVENT LOGS
ANALYZE AUTHENTICATION EVENTS
CHECK DEFENDER
CHECK FIREWALL
CHECK SCHEDULED TASKS
EXPORT CSV
EXPORT JSON
GENERATE HTML REPORTS
IMPLEMENT LOGGING
HANDLE ERRORS
BUILD REUSABLE WINDOWS SECURITY AUTOMATIONFinal Architecture
Section titled “Final Architecture” WINDOWS HOST ↓ ┌─────────────┐ │ POWERSHELL │ └──────┬──────┘ ↓ ┌─────────────┼─────────────┐ ↓ ↓ ↓ USERS PROCESSES SERVICES ↓ ↓ ↓ LOCAL ADMINS NETWORK EVENT LOGS ↓ ↓ ↓ └─────────────┼─────────────┘ ↓ DEFENDER / FIREWALL ↓ SECURITY DATA ↓ ┌───────────┼───────────┐ ↓ ↓ ↓ CSV JSON HTMLAuthorization and Safety
Section titled “Authorization and Safety”Use this lab only on:
YOUR OWN WINDOWS SYSTEM
WINDOWS TRAINING VM
AUTHORIZED ENTERPRISE SYSTEM
CONTROLLED SECURITY LABThe lab focuses on:
READ-ONLY INVENTORY
LOG ANALYSIS
SECURITY CONFIGURATION REVIEW
REPORTINGDo not disable:
MICROSOFT DEFENDER
WINDOWS FIREWALL
AUDITING
LOGGING
SECURITY CONTROLSas part of this lab.
01 — Prepare the Windows Lab
Section titled “01 — Prepare the Windows Lab”Recommended systems:
Windows 10
Windows 11
Windows Server 2019+
Windows Server 2022+
Windows Server 2025Use:
Windows PowerShell 5.1or preferably:
PowerShell 7+where your cmdlets support it.
02 — Open PowerShell
Section titled “02 — Open PowerShell”Check:
$PSVersionTableReview:
PSVersion
PSEdition
OS
Platform03 — Create the Lab Workspace
Section titled “03 — Create the Lab Workspace”Create:
New-Item ` -ItemType Directory ` -Path "C:\Labs\WindowsSecurityAutomation" ` -ForceEnter:
Set-Location "C:\Labs\WindowsSecurityAutomation"Create:
New-Item ` -ItemType Directory ` -Path ".\Reports" ` -Force
New-Item ` -ItemType Directory ` -Path ".\Logs" ` -Force
New-Item ` -ItemType Directory ` -Path ".\Src" ` -ForceExpected:
WindowsSecurityAutomation|+-- Logs|+-- Reports|+-- Src04 — Create the PowerShell Script
Section titled “04 — Create the PowerShell Script”Create:
Src\Windows-Security-Audit.ps1Open it in:
VS Code
PowerShell ISE
Notepad05 — Add Script Metadata
Section titled “05 — Add Script Metadata”Start with:
<#.SYNOPSIS Windows Security Assessment Lab
.DESCRIPTION Collects defensive Windows security inventory and generates local reports.
.NOTES Use only on systems you own or are explicitly authorized to administer.#>06 — Add Script Version
Section titled “06 — Add Script Version”Add:
$ScriptVersion = "1.0.0"07 — Enable Strict Mode
Section titled “07 — Enable Strict Mode”Add:
Set-StrictMode -Version LatestThis helps detect:
UNDEFINED VARIABLES
PROPERTY ERRORS
SOME SCRIPTING MISTAKES08 — Configure Error Handling
Section titled “08 — Configure Error Handling”Add:
$ErrorActionPreference = "Stop"For individual commands where failure is expected or non-critical, you can override this carefully.
09 — Define Project Paths
Section titled “09 — Define Project Paths”Add:
$BaseDirectory = Split-Path ` -Parent ` (Split-Path -Parent $PSCommandPath)
$ReportDirectory = Join-Path ` $BaseDirectory ` "Reports"
$LogDirectory = Join-Path ` $BaseDirectory ` "Logs"10 — Ensure Directories Exist
Section titled “10 — Ensure Directories Exist”Add:
New-Item ` -ItemType Directory ` -Path $ReportDirectory ` -Force ` | Out-Null
New-Item ` -ItemType Directory ` -Path $LogDirectory ` -Force ` | Out-Null11 — Create Run ID
Section titled “11 — Create Run ID”Add:
$RunTimestamp = Get-Date ` -Format "yyyyMMdd-HHmmss"
$AssessmentId = "WIN-$RunTimestamp"12 — Define Output Files
Section titled “12 — Define Output Files”Add:
$LogFile = Join-Path ` $LogDirectory ` "windows-security-audit-$RunTimestamp.log"
$JsonReport = Join-Path ` $ReportDirectory ` "windows-security-report-$RunTimestamp.json"
$HtmlReport = Join-Path ` $ReportDirectory ` "windows-security-report-$RunTimestamp.html"13 — Create a Logging Function
Section titled “13 — Create a Logging Function”Add:
function Write-AuditLog {
param( [Parameter(Mandatory)] [string]$Message,
[ValidateSet( "INFO", "WARNING", "ERROR" )] [string]$Level = "INFO" )
$Timestamp = Get-Date ` -Format "yyyy-MM-ddTHH:mm:ssK"
$Line = "$Timestamp [$Level] $Message"
Write-Host $Line
Add-Content ` -Path $LogFile ` -Value $Line}14 — Test Logging
Section titled “14 — Test Logging”Temporarily:
Write-AuditLog ` -Message "Windows security audit test"Run:
.\Src\Windows-Security-Audit.ps1Confirm a log file appears.
Then remove the temporary test line.
15 — Create a Safe Collection Wrapper
Section titled “15 — Create a Safe Collection Wrapper”Security scripts should not completely fail because one optional cmdlet is unavailable.
Create:
function Invoke-SafeCollection {
param( [Parameter(Mandatory)] [string]$Name,
[Parameter(Mandatory)] [scriptblock]$ScriptBlock )
try {
Write-AuditLog ` -Message "Collecting: $Name"
& $ScriptBlock }
catch {
Write-AuditLog ` -Level "WARNING" ` -Message ( "Unable to collect $Name. " + $_.Exception.Message )
return $null }}16 — Collect System Information
Section titled “16 — Collect System Information”Create:
function Get-SystemSecurityInformation {
$OperatingSystem = Get-CimInstance ` -ClassName Win32_OperatingSystem
$ComputerSystem = Get-CimInstance ` -ClassName Win32_ComputerSystem
$Bios = Get-CimInstance ` -ClassName Win32_BIOS
[PSCustomObject]@{ ComputerName = $env:COMPUTERNAME Caption = $OperatingSystem.Caption Version = $OperatingSystem.Version BuildNumber = $OperatingSystem.BuildNumber Architecture = $OperatingSystem.OSArchitecture Manufacturer = $ComputerSystem.Manufacturer Model = $ComputerSystem.Model Domain = $ComputerSystem.Domain LastBootTime = $OperatingSystem.LastBootUpTime BiosVersion = $Bios.SMBIOSBIOSVersion }}17 — Test System Information
Section titled “17 — Test System Information”Run:
Get-SystemSecurityInformation | Format-List18 — Why System Context Matters
Section titled “18 — Why System Context Matters”Always determine:
COMPUTER NAME
WINDOWS EDITION
BUILD NUMBER
DOMAIN MEMBERSHIP
BOOT TIME
HARDWARE CONTEXTbefore interpreting security events.
19 — Current Identity
Section titled “19 — Current Identity”Create:
function Get-CurrentIdentityContext {
$Identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$Principal = New-Object ` System.Security.Principal.WindowsPrincipal( $Identity )
[PSCustomObject]@{ User = $Identity.Name AuthenticationType = $Identity.AuthenticationType IsAuthenticated = $Identity.IsAuthenticated IsAdministrator = $Principal.IsInRole( [System.Security.Principal.WindowsBuiltInRole]::Administrator ) }}20 — Why Current Identity Matters
Section titled “20 — Why Current Identity Matters”Ask:
WHO IS RUNNING THE SCRIPT?
IS THE SESSION ELEVATED?
WHICH DATA MAY BE INACCESSIBLE?21 — Collect Local Users
Section titled “21 — Collect Local Users”Create:
function Get-LocalUserInventory {
if ( Get-Command ` Get-LocalUser ` -ErrorAction SilentlyContinue ) {
Get-LocalUser | Select-Object ` Name, Enabled, LastLogon, PasswordRequired, PasswordExpires, UserMayChangePassword }
else {
Get-CimInstance ` -ClassName Win32_UserAccount ` -Filter "LocalAccount=True" | Select-Object ` Name, Disabled, Lockout, PasswordRequired }}22 — Security Questions for Local Accounts
Section titled “22 — Security Questions for Local Accounts”Review:
IS THE ACCOUNT REQUIRED?
IS IT ENABLED?
WHEN WAS IT LAST USED?
IS IT A SERVICE ACCOUNT?
DOES IT HAVE ADMIN ACCESS?
WHO OWNS IT?23 — Collect Local Administrators
Section titled “23 — Collect Local Administrators”Create:
function Get-LocalAdministratorInventory {
if ( Get-Command ` Get-LocalGroupMember ` -ErrorAction SilentlyContinue ) {
Get-LocalGroupMember ` -Group "Administrators" | Select-Object ` Name, ObjectClass, PrincipalSource }
else {
Get-CimInstance ` -ClassName Win32_GroupUser | Where-Object { $_.GroupComponent -match ` 'Name="Administrators"' } }}24 — Why Local Administrators Matter
Section titled “24 — Why Local Administrators Matter”Local administrative access provides powerful control over a system.
Every administrator should have:
BUSINESS REQUIREMENT
DOCUMENTED OWNER
APPROPRIATE APPROVAL
REGULAR REVIEW25 — Export Local Users
Section titled “25 — Export Local Users”Later you will use:
$LocalUsers | Export-Csv ` -Path ( Join-Path ` $ReportDirectory ` "local-users-$RunTimestamp.csv" ) ` -NoTypeInformation26 — Export Local Administrators
Section titled “26 — Export Local Administrators”$LocalAdministrators | Export-Csv ` -Path ( Join-Path ` $ReportDirectory ` "local-admins-$RunTimestamp.csv" ) ` -NoTypeInformation27 — Collect Processes
Section titled “27 — Collect Processes”Create:
function Get-ProcessInventory {
Get-Process | Select-Object ` Id, ProcessName, CPU, WorkingSet64, Path ` | Sort-Object ` WorkingSet64 ` -Descending}28 — Permission Note
Section titled “28 — Permission Note”For some processes:
Pathmay be inaccessible unless appropriate privileges are available.
This should not cause you to conclude:
NO PATH=MALICIOUS PROCESS29 — Process Review Questions
Section titled “29 — Process Review Questions”Ask:
IS THE PROCESS EXPECTED?
WHO INSTALLED IT?
WHERE DOES IT RUN FROM?
IS IT SIGNED?
WHAT ACCOUNT RUNS IT?
DOES IT HAVE NETWORK CONNECTIONS?30 — Process Command Lines
Section titled “30 — Process Command Lines”For richer context:
Get-CimInstance ` -ClassName Win32_Process | Select-Object ` ProcessId, Name, ExecutablePath, CommandLine31 — Sensitive Data Warning
Section titled “31 — Sensitive Data Warning”Command lines can contain:
TOKENS
PASSWORDS
CONNECTION STRINGS
API KEYSDo not unnecessarily publish or centrally store full command-line data.
32 — Collect Services
Section titled “32 — Collect Services”Create:
function Get-ServiceInventory {
Get-CimInstance ` -ClassName Win32_Service | Select-Object ` Name, DisplayName, State, StartMode, StartName, PathName}33 — Why Service Accounts Matter
Section titled “33 — Why Service Accounts Matter”The field:
StartNameindicates the identity under which the service runs.
Pay special attention to:
LOCAL SYSTEM
PRIVILEGED DOMAIN ACCOUNTS
CUSTOM SERVICE ACCOUNTS34 — Service Review Questions
Section titled “34 — Service Review Questions”Ask:
IS THE SERVICE REQUIRED?
WHO OWNS IT?
WHAT ACCOUNT RUNS IT?
WHERE IS THE EXECUTABLE?
DOES IT START AUTOMATICALLY?
IS THE SERVICE PATCHED?35 — Running Services
Section titled “35 — Running Services”Example:
Get-Service | Where-Object { $_.Status -eq "Running" } | Sort-Object DisplayName36 — Network Configuration
Section titled “36 — Network Configuration”Create:
function Get-NetworkConfiguration {
if ( Get-Command ` Get-NetIPConfiguration ` -ErrorAction SilentlyContinue ) {
Get-NetIPConfiguration | Select-Object ` InterfaceAlias, InterfaceDescription, IPv4Address, IPv6Address, IPv4DefaultGateway, DNSServer }
else {
Get-CimInstance ` Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled } | Select-Object ` Description, IPAddress, DefaultIPGateway, DNSServerSearchOrder }}37 — Collect TCP Connections
Section titled “37 — Collect TCP Connections”Create:
function Get-TcpConnectionInventory {
if ( Get-Command ` Get-NetTCPConnection ` -ErrorAction SilentlyContinue ) {
Get-NetTCPConnection | Select-Object ` LocalAddress, LocalPort, RemoteAddress, RemotePort, State, OwningProcess }
else {
netstat -ano }}38 — Listening Connections
Section titled “38 — Listening Connections”For systems supporting:
Get-NetTCPConnectionuse:
Get-NetTCPConnection ` -State Listen | Select-Object ` LocalAddress, LocalPort, OwningProcess39 — Correlate Port to Process
Section titled “39 — Correlate Port to Process”Example:
Get-NetTCPConnection ` -State Listen | ForEach-Object {
$Process = Get-Process ` -Id $_.OwningProcess ` -ErrorAction SilentlyContinue
[PSCustomObject]@{ Address = $_.LocalAddress Port = $_.LocalPort ProcessId = $_.OwningProcess Process = $Process.ProcessName } }40 — Why Listener Context Matters
Section titled “40 — Why Listener Context Matters”A port is much more useful when correlated with:
PROCESS
SERVICE
BIND ADDRESS
FIREWALL POLICY
BUSINESS PURPOSE41 — Listener Review Questions
Section titled “41 — Listener Review Questions”For each listener:
WHY IS THIS PORT OPEN?
IS IT EXPECTED?
IS IT BOUND TO LOCALHOST?
IS IT BOUND TO ALL INTERFACES?
WHICH PROCESS OWNS IT?
IS THE SERVICE AUTHENTICATED?
IS THE FIREWALL RESTRICTING ACCESS?42 — Windows Event Logs
Section titled “42 — Windows Event Logs”Windows records many security events in:
Security
System
Application
PowerShell
Microsoft-Windows-Windows Defender/Operational43 — Use Get-WinEvent
Section titled “43 — Use Get-WinEvent”Start with:
Get-WinEvent ` -LogName Security ` -MaxEvents 1044 — Important Authentication Event IDs
Section titled “44 — Important Authentication Event IDs”Useful Windows Security Event IDs include:
4624Successful Logon
4625Failed Logon
4634Logoff
4648Explicit Credentials Used
4672Special Privileges Assigned
4740Account Locked Out45 — Other Identity Events
Section titled “45 — Other Identity Events”Examples:
4720User Account Created
4726User Account Deleted
4732Member Added to Local Security Group
4733Member Removed from Local Security Group46 — Scheduled Task Events
Section titled “46 — Scheduled Task Events”Examples include:
4698Scheduled Task Created
4702Scheduled Task UpdatedInterpret availability according to your audit policy.
47 — Collect Failed Logins
Section titled “47 — Collect Failed Logins”Create:
function Get-FailedLoginEvents {
Get-WinEvent ` -FilterHashtable @{ LogName = "Security" Id = 4625 } ` -MaxEvents 100 ` -ErrorAction Stop | Select-Object ` TimeCreated, Id, MachineName, Message}48 — Export Failed Login Events
Section titled “48 — Export Failed Login Events”$FailedLogins | Export-Csv ` -Path ( Join-Path ` $ReportDirectory ` "failed-logins-$RunTimestamp.csv" ) ` -NoTypeInformation49 — Important Limitation
Section titled “49 — Important Limitation”The:
Messagefield is human-readable but not ideal for structured analytics.
A more advanced workflow should parse:
XML EVENT DATAinstead.
50 — Inspect Event XML
Section titled “50 — Inspect Event XML”Example:
$Event = Get-WinEvent ` -FilterHashtable @{ LogName = "Security" Id = 4625 } ` -MaxEvents 1
$Event.ToXml()51 — Why XML Is Better
Section titled “51 — Why XML Is Better”Structured event data allows you to extract:
TARGET USER
SOURCE IP
LOGON TYPE
WORKSTATION
STATUSwithout depending on localized human-readable messages.
52 — Build Event Data Parser
Section titled “52 — Build Event Data Parser”Create:
function Convert-WinEventToData {
param( [Parameter(Mandatory)] $Event )
$Xml = [xml]$Event.ToXml()
$Data = @{}
foreach ( $Item in $Xml.Event.EventData.Data ) {
$Data[ [string]$Item.Name ] = [string]$Item.'#text' }
return $Data}53 — Parse Failed Authentication Events
Section titled “53 — Parse Failed Authentication Events”Create:
function Get-StructuredFailedLogins {
$Events = Get-WinEvent ` -FilterHashtable @{ LogName = "Security" Id = 4625 } ` -MaxEvents 100
foreach ($Event in $Events) {
$Data = Convert-WinEventToData ` -Event $Event
[PSCustomObject]@{ TimeCreated = $Event.TimeCreated User = $Data["TargetUserName"] Domain = $Data["TargetDomainName"] SourceIp = $Data["IpAddress"] SourcePort = $Data["IpPort"] Workstation = $Data["WorkstationName"] LogonType = $Data["LogonType"] Status = $Data["Status"] SubStatus = $Data["SubStatus"] } }}54 — Why Structured Data Matters
Section titled “54 — Why Structured Data Matters”Now you can perform:
COUNT BY USER
COUNT BY SOURCE IP
COUNT BY LOGON TYPE
TIME-BASED ANALYSISwithout manually reading event messages.
55 — Count Failed Logins by User
Section titled “55 — Count Failed Logins by User”Example:
$StructuredFailedLogins | Group-Object User | Sort-Object Count -Descending | Select-Object ` Count, Name56 — Count Failed Logins by Source IP
Section titled “56 — Count Failed Logins by Source IP”$StructuredFailedLogins | Where-Object { $_.SourceIp -and $_.SourceIp -ne "-" } | Group-Object SourceIp | Sort-Object Count -Descending | Select-Object ` Count, Name57 — Analyst Interpretation
Section titled “57 — Analyst Interpretation”Repeated failures may result from:
BAD PASSWORD
STALE CREDENTIAL
SERVICE MISCONFIGURATION
USER ERROR
VPN ISSUE
AUTOMATED SYSTEM
ATTACK ACTIVITYAutomation identifies the:
PATTERNnot the final cause.
58 — Collect Successful Logins
Section titled “58 — Collect Successful Logins”Create:
function Get-SuccessfulLoginEvents {
Get-WinEvent ` -FilterHashtable @{ LogName = "Security" Id = 4624 } ` -MaxEvents 100 ` -ErrorAction Stop | Select-Object ` TimeCreated, Id, MachineName, Message}59 — Logon Types
Section titled “59 — Logon Types”Important examples include:
2Interactive
3Network
10Remote InteractiveThere are additional logon types.
Always interpret them in context.
60 — Account Lockout Events
Section titled “60 — Account Lockout Events”Create:
function Get-AccountLockoutEvents {
Get-WinEvent ` -FilterHashtable @{ LogName = "Security" Id = 4740 } ` -MaxEvents 50 ` -ErrorAction Stop | Select-Object ` TimeCreated, Id, Message}61 — Privileged Logon Events
Section titled “61 — Privileged Logon Events”Create:
function Get-PrivilegedLogonEvents {
Get-WinEvent ` -FilterHashtable @{ LogName = "Security" Id = 4672 } ` -MaxEvents 50 ` -ErrorAction Stop | Select-Object ` TimeCreated, Id, Message}62 — Account Creation Review
Section titled “62 — Account Creation Review”Create:
function Get-AccountCreationEvents {
Get-WinEvent ` -FilterHashtable @{ LogName = "Security" Id = 4720 } ` -MaxEvents 50 ` -ErrorAction Stop | Select-Object ` TimeCreated, Message}63 — Local Administrator Group Changes
Section titled “63 — Local Administrator Group Changes”Create:
function Get-GroupChangeEvents {
Get-WinEvent ` -FilterHashtable @{ LogName = "Security" Id = 4732,4733 } ` -MaxEvents 100 ` -ErrorAction Stop | Select-Object ` TimeCreated, Id, Message}64 — Security Timeline Thinking
Section titled “64 — Security Timeline Thinking”You may correlate:
ACCOUNT CREATED ↓ADDED TO ADMINISTRATORS ↓PRIVILEGED LOGONThis sequence can deserve investigation.
Do not automatically conclude malicious activity without context.
65 — Microsoft Defender Status
Section titled “65 — Microsoft Defender Status”Create:
function Get-DefenderStatus {
if ( Get-Command ` Get-MpComputerStatus ` -ErrorAction SilentlyContinue ) {
Get-MpComputerStatus | Select-Object ` AMServiceEnabled, AntivirusEnabled, AntispywareEnabled, BehaviorMonitorEnabled, IoavProtectionEnabled, NISEnabled, RealTimeProtectionEnabled, AntivirusSignatureLastUpdated, QuickScanAge, FullScanAge }
else {
[PSCustomObject]@{ Status = "Get-MpComputerStatus unavailable" } }}66 — What to Review
Section titled “66 — What to Review”Look for:
ANTIVIRUS ENABLED
REAL-TIME PROTECTION
BEHAVIOR MONITORING
SIGNATURE UPDATE TIME67 — Important Safety Rule
Section titled “67 — Important Safety Rule”Do not run commands that disable:
REAL-TIME PROTECTION
ANTIVIRUS
BEHAVIOR MONITORINGThe purpose of this lab is:
SECURITY VISIBILITYnot control bypass.
68 — Windows Firewall Status
Section titled “68 — Windows Firewall Status”Create:
function Get-FirewallStatus {
if ( Get-Command ` Get-NetFirewallProfile ` -ErrorAction SilentlyContinue ) {
Get-NetFirewallProfile | Select-Object ` Name, Enabled, DefaultInboundAction, DefaultOutboundAction, LogAllowed, LogBlocked, LogFileName }
else {
[PSCustomObject]@{ Status = "Get-NetFirewallProfile unavailable" } }}69 — Firewall Profiles
Section titled “69 — Firewall Profiles”Typical profiles:
DOMAIN
PRIVATE
PUBLICAsk:
IS EACH PROFILE ENABLED?
WHAT IS THE DEFAULT INBOUND POLICY?
IS BLOCKED TRAFFIC LOGGED?70 — Do Not Automatically Change Firewall Rules
Section titled “70 — Do Not Automatically Change Firewall Rules”This lab performs:
READ
ASSESS
REPORTnot:
AUTOMATIC FIREWALL MODIFICATION71 — Scheduled Tasks
Section titled “71 — Scheduled Tasks”Create:
function Get-ScheduledTaskInventory {
if ( Get-Command ` Get-ScheduledTask ` -ErrorAction SilentlyContinue ) {
Get-ScheduledTask | Select-Object ` TaskPath, TaskName, State, Author }
else {
schtasks.exe /Query /FO CSV /V }}72 — Why Scheduled Tasks Matter
Section titled “72 — Why Scheduled Tasks Matter”Scheduled tasks may be used for legitimate:
MAINTENANCE
BACKUPS
SOFTWARE UPDATES
MONITORINGEvery task should still have understandable:
OWNER
PURPOSE
COMMAND
TRIGGER
EXECUTION ACCOUNT73 — Service and Task Context
Section titled “73 — Service and Task Context”During security review, ask:
DOES A HIGH-PRIVILEGE SERVICEEXECUTE A FILE FROMA USER-WRITABLE LOCATION?Do not attempt to exploit such a condition in this defensive lab.
Document it for authorized remediation review.
74 — PowerShell Operational Logging
Section titled “74 — PowerShell Operational Logging”PowerShell itself has useful event logs.
Check:
Get-WinEvent ` -ListLog "*PowerShell*" | Select-Object ` LogName, RecordCount, IsEnabled75 — PowerShell Operational Events
Section titled “75 — PowerShell Operational Events”If available:
Get-WinEvent ` -LogName ` "Microsoft-Windows-PowerShell/Operational" ` -MaxEvents 5076 — Why PowerShell Logging Matters
Section titled “76 — Why PowerShell Logging Matters”It may provide visibility into:
SCRIPT EXECUTION
COMMAND ACTIVITY
ENGINE EVENTS
OPERATIONAL ERRORSdepending on configured logging policies.
77 — Audit Policy
Section titled “77 — Audit Policy”Collect:
auditpol.exe /get /category:*Store the output for review.
78 — Add Audit Policy Collection
Section titled “78 — Add Audit Policy Collection”Create:
function Get-AuditPolicyStatus {
& auditpol.exe ` /get ` /category:* ` 2>&1}79 — Why Audit Policy Matters
Section titled “79 — Why Audit Policy Matters”If relevant audit categories are disabled:
IMPORTANT EVENTSMAY NEVER BE RECORDEDA security analyst must understand both:
WHAT THE LOG SHOWSand:
WHAT THE SYSTEM WAS CONFIGURED TO LOG80 — Windows Update Context
Section titled “80 — Windows Update Context”For basic system context, you can record:
Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 2081 — Add Hotfix Inventory
Section titled “81 — Add Hotfix Inventory”Create:
function Get-HotfixInventory {
Get-HotFix | Sort-Object ` InstalledOn ` -Descending | Select-Object ` -First 20 ` HotFixID, Description, InstalledOn}82 — Patch Assessment Limitation
Section titled “82 — Patch Assessment Limitation”A list of installed hotfixes alone does not prove:
FULL PATCH COMPLIANCEEnterprise patch assessment should compare:
CURRENT STATE
EXPECTED BASELINE
VULNERABILITY DATA
VENDOR GUIDANCE83 — Disk Information
Section titled “83 — Disk Information”Create:
function Get-DiskInventory {
Get-CimInstance ` -ClassName Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 } | Select-Object ` DeviceID, VolumeName, @{ Name = "SizeGB" Expression = { [math]::Round( $_.Size / 1GB, 2 ) } }, @{ Name = "FreeGB" Expression = { [math]::Round( $_.FreeSpace / 1GB, 2 ) } }}84 — Why Disk Capacity Is Security Relevant
Section titled “84 — Why Disk Capacity Is Security Relevant”A full system drive can cause:
SECURITY LOG FAILURE
APPLICATION FAILURE
UPDATE FAILURE
MONITORING FAILURE85 — Build the Main Collection Object
Section titled “85 — Build the Main Collection Object”Create:
function Invoke-WindowsSecurityAssessment {
Write-AuditLog ` -Message "Assessment started"
$SystemInformation = Invoke-SafeCollection ` -Name "System Information" ` -ScriptBlock { Get-SystemSecurityInformation }
$Identity = Invoke-SafeCollection ` -Name "Current Identity" ` -ScriptBlock { Get-CurrentIdentityContext }
$LocalUsers = Invoke-SafeCollection ` -Name "Local Users" ` -ScriptBlock { @(Get-LocalUserInventory) }
$LocalAdministrators = Invoke-SafeCollection ` -Name "Local Administrators" ` -ScriptBlock { @(Get-LocalAdministratorInventory) }
$Processes = Invoke-SafeCollection ` -Name "Processes" ` -ScriptBlock { @(Get-ProcessInventory) }
$Services = Invoke-SafeCollection ` -Name "Services" ` -ScriptBlock { @(Get-ServiceInventory) }
$NetworkConfiguration = Invoke-SafeCollection ` -Name "Network Configuration" ` -ScriptBlock { @(Get-NetworkConfiguration) }
$TcpConnections = Invoke-SafeCollection ` -Name "TCP Connections" ` -ScriptBlock { @(Get-TcpConnectionInventory) }
$FailedLogins = Invoke-SafeCollection ` -Name "Failed Logins" ` -ScriptBlock { @(Get-StructuredFailedLogins) }
$Defender = Invoke-SafeCollection ` -Name "Microsoft Defender" ` -ScriptBlock { Get-DefenderStatus }
$Firewall = Invoke-SafeCollection ` -Name "Windows Firewall" ` -ScriptBlock { @(Get-FirewallStatus) }
$ScheduledTasks = Invoke-SafeCollection ` -Name "Scheduled Tasks" ` -ScriptBlock { @(Get-ScheduledTaskInventory) }
$Hotfixes = Invoke-SafeCollection ` -Name "Hotfixes" ` -ScriptBlock { @(Get-HotfixInventory) }
$Disks = Invoke-SafeCollection ` -Name "Disk Information" ` -ScriptBlock { @(Get-DiskInventory) }
[PSCustomObject]@{ AssessmentId = $AssessmentId CollectionTime = Get-Date ScriptVersion = $ScriptVersion System = $SystemInformation Identity = $Identity LocalUsers = $LocalUsers LocalAdministrators = $LocalAdministrators Processes = $Processes Services = $Services NetworkConfiguration = $NetworkConfiguration TcpConnections = $TcpConnections FailedLogins = $FailedLogins Defender = $Defender Firewall = $Firewall ScheduledTasks = $ScheduledTasks Hotfixes = $Hotfixes Disks = $Disks }}86 — Run the Assessment
Section titled “86 — Run the Assessment”At the bottom:
$Assessment = Invoke-WindowsSecurityAssessment87 — Export JSON
Section titled “87 — Export JSON”Add:
$Assessment | ConvertTo-Json ` -Depth 8 | Set-Content ` -Path $JsonReport ` -Encoding UTF888 — Why JSON?
Section titled “88 — Why JSON?”JSON provides:
STRUCTURED OUTPUT
MACHINE READABILITY
API INTEGRATION
FUTURE AUTOMATION
ARCHIVING89 — Export Major CSV Files
Section titled “89 — Export Major CSV Files”Add:
if ($Assessment.LocalUsers) {
$Assessment.LocalUsers | Export-Csv ` -Path ( Join-Path ` $ReportDirectory ` "local-users-$RunTimestamp.csv" ) ` -NoTypeInformation}Repeat for:
LOCAL ADMINS
SERVICES
PROCESSES
TCP CONNECTIONS
FAILED LOGINS90 — Export Local Administrators
Section titled “90 — Export Local Administrators”if ($Assessment.LocalAdministrators) {
$Assessment.LocalAdministrators | Export-Csv ` -Path ( Join-Path ` $ReportDirectory ` "local-admins-$RunTimestamp.csv" ) ` -NoTypeInformation}91 — Export Processes
Section titled “91 — Export Processes”if ($Assessment.Processes) {
$Assessment.Processes | Export-Csv ` -Path ( Join-Path ` $ReportDirectory ` "processes-$RunTimestamp.csv" ) ` -NoTypeInformation}92 — Export Services
Section titled “92 — Export Services”if ($Assessment.Services) {
$Assessment.Services | Export-Csv ` -Path ( Join-Path ` $ReportDirectory ` "services-$RunTimestamp.csv" ) ` -NoTypeInformation}93 — Export TCP Connections
Section titled “93 — Export TCP Connections”if ($Assessment.TcpConnections) {
$Assessment.TcpConnections | Export-Csv ` -Path ( Join-Path ` $ReportDirectory ` "tcp-connections-$RunTimestamp.csv" ) ` -NoTypeInformation}94 — Export Failed Login Data
Section titled “94 — Export Failed Login Data”if ($Assessment.FailedLogins) {
$Assessment.FailedLogins | Export-Csv ` -Path ( Join-Path ` $ReportDirectory ` "failed-logins-$RunTimestamp.csv" ) ` -NoTypeInformation}95 — Create an HTML Report
Section titled “95 — Create an HTML Report”Create:
function New-HtmlSecurityReport {
param( [Parameter(Mandatory)] $Assessment )
$Body = @()
$Body += "<h1>Windows Security Assessment</h1>"
$Body += "<p>Assessment ID: $($Assessment.AssessmentId)</p>"
$Body += "<p>Generated: $($Assessment.CollectionTime)</p>"
$Body += "<h2>System Information</h2>"
$Body += ( $Assessment.System | ConvertTo-Html ` -Fragment )
$Body += "<h2>Current Identity</h2>"
$Body += ( $Assessment.Identity | ConvertTo-Html ` -Fragment )
$Body += "<h2>Local Administrators</h2>"
$Body += ( $Assessment.LocalAdministrators | ConvertTo-Html ` -Fragment )
$Body += "<h2>Defender</h2>"
$Body += ( $Assessment.Defender | ConvertTo-Html ` -Fragment )
$Body += "<h2>Firewall Profiles</h2>"
$Body += ( $Assessment.Firewall | ConvertTo-Html ` -Fragment )
$Body += "<h2>Recent Failed Logins</h2>"
$Body += ( $Assessment.FailedLogins | Select-Object ` -First 25 | ConvertTo-Html ` -Fragment )
ConvertTo-Html ` -Title "Windows Security Assessment" ` -Body $Body | Set-Content ` -Path $HtmlReport ` -Encoding UTF8}96 — Generate the HTML Report
Section titled “96 — Generate the HTML Report”Run:
New-HtmlSecurityReport ` -Assessment $Assessment97 — Open the Report
Section titled “97 — Open the Report”Run:
Invoke-Item $HtmlReportYour browser should display the assessment.
98 — Add Completion Logging
Section titled “98 — Add Completion Logging”At the end:
Write-AuditLog ` -Message "Assessment completed"
Write-Host ""
Write-Host "JSON Report:"Write-Host $JsonReport
Write-Host ""
Write-Host "HTML Report:"Write-Host $HtmlReport99 — Expected Report Directory
Section titled “99 — Expected Report Directory”You should now have:
Reports/|+-- windows-security-report-<timestamp>.json|+-- windows-security-report-<timestamp>.html|+-- local-users-<timestamp>.csv|+-- local-admins-<timestamp>.csv|+-- processes-<timestamp>.csv|+-- services-<timestamp>.csv|+-- tcp-connections-<timestamp>.csv|+-- failed-logins-<timestamp>.csv100 — Analyst Review: Local Administrators
Section titled “100 — Analyst Review: Local Administrators”Open:
local-admins-*.csvAsk:
DO I RECOGNIZE EVERY ADMIN?
IS EACH ACCOUNT REQUIRED?
ARE DOMAIN GROUPS PRESENT?
IS ACCESS APPROVED?
IS ANY ACCOUNT STALE?101 — Analyst Review: Processes
Section titled “101 — Analyst Review: Processes”Look for:
UNEXPECTED PROCESS NAMES
UNEXPECTED EXECUTABLE PATHS
HIGH RESOURCE USAGE
UNUSUAL USER LOCATIONSDo not judge based solely on process name.
102 — Analyst Review: Services
Section titled “102 — Analyst Review: Services”Ask:
WHICH SERVICES RUN AUTOMATICALLY?
WHICH RUN WITH HIGH PRIVILEGE?
ARE SERVICE EXECUTABLE PATHS EXPECTED?
ARE CUSTOM SERVICE ACCOUNTS REQUIRED?103 — Analyst Review: Network
Section titled “103 — Analyst Review: Network”Look for:
LISTENING PORTS
REMOTE CONNECTIONS
UNKNOWN PROCESSES
UNEXPECTED BIND ADDRESSESThen correlate with:
PROCESS
SERVICE
FIREWALL
BUSINESS PURPOSE104 — Analyst Review: Authentication Failures
Section titled “104 — Analyst Review: Authentication Failures”Group:
$Assessment.FailedLogins | Group-Object User | Sort-Object Count -Descending | Select-Object ` Count, NameThen:
$Assessment.FailedLogins | Group-Object SourceIp | Sort-Object Count -Descending | Select-Object ` Count, Name105 — Failed Login Mental Model
Section titled “105 — Failed Login Mental Model”FAILED LOGIN ↓WHO? ↓FROM WHERE? ↓HOW MANY TIMES? ↓WHICH LOGON TYPE? ↓WAS THERE LATER SUCCESS? ↓WHAT HAPPENED NEXT?106 — Create a Simple Authentication Summary
Section titled “106 — Create a Simple Authentication Summary”Example:
$FailedByUser = $Assessment.FailedLogins | Group-Object User | Sort-Object Count -Descending
$FailedByIp = $Assessment.FailedLogins | Group-Object SourceIp | Sort-Object Count -Descending107 — Review Defender
Section titled “107 — Review Defender”Important checks:
AntivirusEnabled
RealTimeProtectionEnabled
BehaviorMonitorEnabled
AntivirusSignatureLastUpdated108 — Review Firewall
Section titled “108 — Review Firewall”Confirm:
DOMAIN PROFILE
PRIVATE PROFILE
PUBLIC PROFILEaccording to your lab baseline.
109 — Create a Training Finding Matrix
Section titled “109 — Create a Training Finding Matrix”| Finding | Example Condition | Review |
|---|---|---|
| Unexpected local admin | Unknown account in Administrators | High |
| Defender inactive | Expected control unavailable | High |
| Firewall profile disabled | Baseline expects enabled | High |
| Repeated failed logins | Multiple failures | Review |
| Unexpected listener | Unknown service/port | Review |
| Stale local account | Unused account remains enabled | Review |
These are lab review levels, not universal enterprise severity ratings.
110 — Add Assessment Findings
Section titled “110 — Add Assessment Findings”Create a basic function:
function Get-AssessmentFindings {
param( [Parameter(Mandatory)] $Assessment )
$Findings = @()
if ( $Assessment.Defender -and $Assessment.Defender.PSObject.Properties.Name -contains "RealTimeProtectionEnabled" -and -not $Assessment.Defender.RealTimeProtectionEnabled ) {
$Findings += [PSCustomObject]@{ Finding = "Defender real-time protection not enabled" ReviewLevel = "High" Recommendation = "Validate endpoint protection configuration." } }
foreach ( $Profile in @( $Assessment.Firewall ) ) {
if ( $null -ne $Profile.Enabled -and -not $Profile.Enabled ) {
$Findings += [PSCustomObject]@{ Finding = "Firewall profile disabled: $($Profile.Name)" ReviewLevel = "High" Recommendation = "Validate against approved firewall baseline." } } }
return $Findings}111 — Important Rule
Section titled “111 — Important Rule”Do not write automation that immediately changes:
DEFENDER
FIREWALL
LOCAL ADMINS
SERVICESbased solely on these findings.
Use:
DETECT ↓VALIDATE ↓REVIEW ↓APPROVE ↓REMEDIATE112 — Hash the Reports
Section titled “112 — Hash the Reports”Create:
Get-FileHash ` -Path $JsonReport ` -Algorithm SHA256Save:
Get-FileHash ` -Path $JsonReport ` -Algorithm SHA256 | Export-Csv ` -Path "$JsonReport.sha256.csv" ` -NoTypeInformation113 — Why Hash Reports?
Section titled “113 — Why Hash Reports?”A hash can provide:
REPORT INTEGRITY REFERENCEfor:
AUDIT
INCIDENT RESPONSE
EVIDENCE MANAGEMENT114 — Script Hash
Section titled “114 — Script Hash”Also record:
Get-FileHash ` -Path $PSCommandPath ` -Algorithm SHA256This documents which script file produced the report.
115 — Add Script Execution Metadata
Section titled “115 — Add Script Execution Metadata”Your final report should ideally include:
ASSESSMENT ID
COMPUTER NAME
COLLECTION USER
SCRIPT VERSION
COLLECTION TIME
SCRIPT HASH116 — Add Parameter Support
Section titled “116 — Add Parameter Support”A more professional script can start:
param( [string]$OutputDirectory)If not supplied:
if (-not $OutputDirectory) { $OutputDirectory = $ReportDirectory}117 — Add -IncludeEventLogs
Section titled “117 — Add -IncludeEventLogs”Example:
param( [switch]$IncludeEventLogs)Then collect high-volume event data only when requested.
118 — Why Parameters Matter
Section titled “118 — Why Parameters Matter”Parameters make automation:
REUSABLE
CONTROLLED
PREDICTABLEinstead of requiring source-code edits every time.
119 — Test Syntax
Section titled “119 — Test Syntax”Use PowerShell parsing:
$Errors = $null
[System.Management.Automation.Language.Parser]::ParseFile( ".\Src\Windows-Security-Audit.ps1", [ref]$null, [ref]$Errors)
$ErrorsNo errors should be returned.
120 — Test as Standard User
Section titled “120 — Test as Standard User”First run:
.\Src\Windows-Security-Audit.ps1without elevation.
Document which sections succeed.
121 — Test as Administrator
Section titled “121 — Test as Administrator”On your authorized lab machine, reopen PowerShell:
Run as AdministratorRun again.
Compare:
STANDARD USER OUTPUT
ADMINISTRATOR OUTPUT122 — Least Privilege Lesson
Section titled “122 — Least Privilege Lesson”Do not assume the script should always run as:
ADMINISTRATORAsk:
WHICH DATA REQUIRES ELEVATION?
CAN MOST COLLECTION RUNWITHOUT IT?123 — Test Missing Cmdlet Behavior
Section titled “123 — Test Missing Cmdlet Behavior”Temporarily call your safe wrapper with:
Invoke-SafeCollection ` -Name "Test Missing Command" ` -ScriptBlock { Get-NotARealCommand }Confirm the script:
LOGS THE FAILURE
CONTINUES SAFELYThen remove the test.
124 — Test Event Log Access
Section titled “124 — Test Event Log Access”Some systems may not provide Security log access to your current account.
The script should:
REPORT THE COLLECTION FAILUREinstead of pretending:
NO EVENTS EXIST125 — Test Empty Result Sets
Section titled “125 — Test Empty Result Sets”Examples:
NO 4625 EVENTS
NO ACCOUNT LOCKOUTS
NO CUSTOM LOCAL USERSThe report should still generate successfully.
126 — Test Large Event Volumes
Section titled “126 — Test Large Event Volumes”Keep:
-MaxEventsduring lab development.
Avoid loading an entire enterprise Security log when you only need recent records.
127 — Performance Principle
Section titled “127 — Performance Principle”Prefer:
Get-WinEvent -FilterHashtableover collecting every event and filtering afterward.
Filter as early as possible.
128 — Security Data Minimization
Section titled “128 — Security Data Minimization”Do not export everything simply because PowerShell can collect it.
Ask:
DO I NEED THIS DATA?
IS IT SENSITIVE?
WHO WILL RECEIVE THE REPORT?
HOW LONG WILL IT BE STORED?129 — Protect Reports
Section titled “129 — Protect Reports”Your generated reports may contain:
USERNAMES
ADMINISTRATIVE GROUPS
NETWORK INFORMATION
SERVICE DETAILS
PROCESS INFORMATION
SECURITY CONFIGURATIONTreat the output as sensitive security information.
130 — No Credential Collection
Section titled “130 — No Credential Collection”This assessment should never attempt to collect:
PASSWORDS
PASSWORD HASHES
AUTHENTICATION TOKENS
BROWSER COOKIES
PRIVATE KEYS131 — Do Not Disable Logging
Section titled “131 — Do Not Disable Logging”Do not reduce:
SECURITY AUDITING
POWERSHELL LOGGING
DEFENDER LOGGINGto make testing easier.
Visibility is part of the security control.
132 — PowerShell Execution Policy
Section titled “132 — PowerShell Execution Policy”Understand:
EXECUTION POLICYbut do not treat it as a complete security boundary.
For the lab, use your organization’s approved PowerShell execution configuration.
133 — Do Not Blindly Run Downloaded Scripts
Section titled “133 — Do Not Blindly Run Downloaded Scripts”Before executing PowerShell from another source:
READ IT
UNDERSTAND IT
VERIFY THE SOURCE
TEST IT IN A LAB
REVIEW REQUIRED PRIVILEGES134 — Optional Code Signing Concept
Section titled “134 — Optional Code Signing Concept”Production organizations may sign PowerShell scripts to help establish:
PUBLISHER IDENTITY
SCRIPT INTEGRITY
CHANGE CONTROL135 — Git Repository Structure
Section titled “135 — Git Repository Structure”Your project should eventually look like:
WindowsSecurityAutomation/|+-- Src/| +-- Windows-Security-Audit.ps1|+-- Reports/|+-- Logs/|+-- Tests/|+-- README.md|+-- architecture.md136 — Create README
Section titled “136 — Create README”Include:
PROJECT PURPOSE
AUTHORIZED USE
REQUIREMENTS
HOW TO RUN
COLLECTED DATA
OUTPUT FILES
SECURITY CONTROLS
LIMITATIONS
TROUBLESHOOTING
FUTURE IMPROVEMENTS137 — README Security Statement
Section titled “137 — README Security Statement”Use:
This project performs defensive Windowssecurity inventory and assessment.
It does not disable security controls,extract credentials, create persistence,or perform exploitation.
Run only against systems you own or areexplicitly authorized to administer.138 — Document Limitations
Section titled “138 — Document Limitations”Your tool does not provide:
FULL MALWARE ANALYSIS
MEMORY FORENSICS
FULL CIS BENCHMARK AUDIT
VULNERABILITY SCANNING
ACTIVE DIRECTORY FOREST ASSESSMENT
ENDPOINT DETECTION REPLACEMENT
EDR REPLACEMENT139 — Future Enhancement: Active Directory Context
Section titled “139 — Future Enhancement: Active Directory Context”For an authorized domain environment, future versions could collect:
DOMAIN MEMBERSHIP
DOMAIN USERS
DOMAIN GROUPS
PRIVILEGED GROUP MEMBERSHIP
COMPUTER INVENTORYKeep this separate from the basic local-host lab.
140 — Future Enhancement: Remote Collection
Section titled “140 — Future Enhancement: Remote Collection”PowerShell supports remote administration.
However, remote collection should require:
EXPLICIT AUTHORIZATION
APPROVED CREDENTIALS
APPROVED REMOTING CONFIGURATION
TARGET SCOPEThis lab does not require lateral movement or unauthorized remote access.
141 — Future Enhancement: Baseline Comparison
Section titled “141 — Future Enhancement: Baseline Comparison”A powerful defensive feature is:
CURRENT SYSTEM ↓COMPARE ↓KNOWN GOOD BASELINEExample:
EXPECTED ADMINSvsCURRENT ADMINS142 — Baseline Comparison Example
Section titled “142 — Baseline Comparison Example”Conceptually:
$ExpectedAdmins = @( "BUILTIN\Administrators", "CONTOSO\Endpoint Admins")
$CurrentAdmins = ( $Assessment.LocalAdministrators).Name
Compare-Object ` -ReferenceObject $ExpectedAdmins ` -DifferenceObject $CurrentAdminsUse only your own authorized baseline values.
143 — Future Enhancement: Change Detection
Section titled “143 — Future Enhancement: Change Detection”Run the audit daily and compare:
YESTERDAY
TODAYIdentify:
NEW LOCAL ADMIN
NEW SERVICE
NEW LISTENER
NEW SCHEDULED TASK
FIREWALL CHANGE
DEFENDER CHANGE144 — Security Drift Mental Model
Section titled “144 — Security Drift Mental Model”KNOWN GOOD STATE ↓TIME PASSES ↓SYSTEM CHANGES ↓CURRENT STATE ↓COMPARE ↓SECURITY DRIFT145 — Future Enhancement: Centralized Reporting
Section titled “145 — Future Enhancement: Centralized Reporting”Architecture:
WINDOWS HOSTS ↓POWERSHELL COLLECTION ↓JSON ↓CENTRAL PROCESSOR ↓SQL ↓DASHBOARDThis connects directly with the other Programming Labs.
146 — Common PowerShell Security Automation Mistakes
Section titled “146 — Common PowerShell Security Automation Mistakes”Avoid:
RUNNING EVERYTHING AS ADMIN
NO ERROR HANDLING
NO LOGGING
HARDCODED CREDENTIALS
EXPORTING SENSITIVE DATA NEEDLESSLY
USING MESSAGE TEXT WHEN STRUCTURED XML EXISTS
LOADING HUGE EVENT LOGS WITHOUT FILTERING
AUTOMATICALLY REMEDIATING FINDINGS
DISABLING SECURITY CONTROLS
NO BASELINE
NO REPORT VERSION
NO SCRIPT HASH
NO DOCUMENTATION147 — Mission Validation Checklist
Section titled “147 — Mission Validation Checklist”Confirm:
- PowerShell version reviewed
- Lab workspace created
- Script created
- Strict mode configured
- Error handling configured
- Logging implemented
- Assessment ID created
- System information collected
- Current identity collected
- Local users inventoried
- Local administrators inventoried
- Processes inventoried
- Services inventoried
- Network configuration collected
- TCP connections collected
- Listening ports reviewed
- Failed logins collected
- Structured event parsing tested
- Failed logins grouped by user
- Failed logins grouped by IP
- Successful logins understood
- Account lockouts reviewed
- Privileged logons understood
- Account creation events understood
- Group membership changes understood
- Defender status collected
- Firewall status collected
- Scheduled tasks inventoried
- Audit policy reviewed
- Hotfix information collected
- Disk information collected
- CSV reports exported
- JSON report exported
- HTML report generated
- Reports hashed
- Script tested as standard user
- Script tested with authorized elevation
- Missing-data behavior tested
- No credentials collected
- No security controls disabled
- Findings manually reviewed
Mission Review
Section titled “Mission Review”You started with individual PowerShell cmdlets:
Get-CimInstance
Get-LocalUser
Get-LocalGroupMember
Get-Process
Get-Service
Get-NetTCPConnection
Get-WinEvent
Get-MpComputerStatus
Get-NetFirewallProfile
Get-ScheduledTaskYou converted them into:
ONE REPEATABLEWINDOWS SECURITYASSESSMENT WORKFLOWWhat You Built
Section titled “What You Built”Your PowerShell tool now collects:
SYSTEM CONTEXT
LOCAL IDENTITIES
PRIVILEGED ACCESS
PROCESS INFORMATION
SERVICE INFORMATION
NETWORK INFORMATION
AUTHENTICATION EVENTS
DEFENDER STATUS
FIREWALL STATUS
TASK INFORMATION
PATCH CONTEXTand produces:
CSV
JSON
HTML
EXECUTION LOGSKey Security Lesson
Section titled “Key Security Lesson”The central lesson is:
WINDOWS SECURITYIS ABOUTCORRELATING OBJECTSA failed login becomes more valuable when connected to:
USER +SOURCE IP +LOGON TYPE +TIMESTAMP +ASSET +LATER ACTIVITYA listening port becomes more useful when connected to:
PROCESS +SERVICE +FIREWALL +BUSINESS PURPOSEAnd an administrator account becomes meaningful when connected to:
OWNER +APPROVAL +USAGE +BUSINESS NEEDFinal Mental Model
Section titled “Final Mental Model”Whenever you assess a Windows system, think:
WHAT SYSTEM IS THIS? ↓WHO AM I? ↓WHO CAN LOG IN? ↓WHO HAS ADMIN ACCESS? ↓WHAT PROCESSES ARE RUNNING? ↓WHAT SERVICES ARE RUNNING? ↓WHAT IS LISTENING? ↓WHAT AUTHENTICATION EVENTS OCCURRED? ↓ARE SECURITY CONTROLS ACTIVE? ↓WHAT REQUIRES HUMAN REVIEW?PowerShell’s greatest security advantage is:
WINDOWS DATA ↓STRUCTURED OBJECTS ↓PIPELINE ↓FILTER ↓CORRELATE ↓EXPORT ↓SECURITY DECISIONWhat’s Next?
Section titled “What’s Next?”➡️ Lab 05 — SQL Security Analytics Lab
The next lab moves from operating-system security automation into enterprise security data analysis.
You will build a synthetic security database containing:
USERS
ASSETS
LOGIN EVENTS
VULNERABILITIES
INCIDENTSThen use SQL to answer investigation questions such as:
WHO HAS THE MOST FAILED LOGINS?
WHICH SOURCE IP TARGETEDTHE MOST ACCOUNTS?
WHICH ADMINISTRATORSDO NOT HAVE MFA?
WHICH CRITICAL ASSETSHAVE OPEN CRITICAL FINDINGS?
WHICH ASSETSHAVE NO OWNER?
WHICH INCIDENTSREMAIN UNASSIGNED?The workflow will become:
SECURITY QUESTION ↓SQL ↓FILTER ↓JOIN ↓AGGREGATE ↓CORRELATE ↓ANALYST FINDING