Repository navigation
Getting Started
From zero to a verified evidence bundle and an incident report.
| Requirement | Why |
|---|---|
PowerShell 7.6+ (pwsh) |
the module targets Core only; Windows PowerShell 5.1 is not supported |
| Windows 10/11/Server 2016+ | collection via Get-WinEvent / WinRM |
| Elevated session | reading the Security log and auditpol
|
| Pester 5.4+ | only to run the test suite |
| PSScriptAnalyzer 1.25+ | only to run the static-analysis gate |
$PSVersionTable.PSVersion # must be 7.6 or later
$PSVersionTable.PSEdition # must be Coregit clone https://github.com/paulmann/Get-Windows-Security-Events-By-IP.git
cd Get-Windows-Security-Events-By-IP
./build/Install-Dev.ps1 -ForceInstall-Dev.ps1 copies src/WinSecHunter into your per-user module path and prints it.
$version = '2.1.0'
$zip = Join-Path $env:TEMP "WinSecHunter-$version-module.zip"
Invoke-WebRequest "https://github.com/paulmann/Get-Windows-Security-Events-By-IP/releases/download/v$version/WinSecHunter-$version-module.zip" -OutFile $zip -UseBasicParsing
Expand-Archive -LiteralPath $zip -DestinationPath "$env:USERPROFILE\Documents\PowerShell\Modules" -Force$dir = Join-Path $env:TEMP 'WinSecHunter-2.1.0'
Import-Module (Join-Path $dir 'WinSecHunter/2.1.0/WinSecHunter.psd1') -Force
(Get-Module WinSecHunter).ExportedFunctions.Count # 20Always pass a time window and -PassHostStatus, never use Read-Host/Get-Credential, and
keep API keys in environment variables. The full protocol is in
AGENTS.md.
Import-Module WinSecHunter -Force
(Get-Module WinSecHunter).Version # 2.1.0
(Get-Module WinSecHunter).ExportedFunctions.Count # 20
Show-WinSecHunterHelp # workflow map$events = Get-WinSecEventByIp -IpAddress 203.0.113.7 -LastDays 2 -PassHostStatusWhat happens:
- the catalog expands the default
IncidentResponseprofile into 6 channels; - each channel is queried separately with server-side XPath (time window, event IDs,
exact IP variants such as
::ffff:203.0.113.7); - every returned record is re-verified client-side against the IP;
-
-PassHostStatusemits oneWinSecHostResultper target withLogDetailsper channel.
If a channel is not installed (Sysmon on a stock server, RDP logs on a DC), it appears as
Skipped and the rest of the sweep continues. That is expected, not an error.
Important
"No events" is a conclusion, not an observation. Before writing it down, check retention
(Get-WinSecLogCoverage) and audit policy (Test-WinSecAuditPolicy).
$events | Measure-Object # count
$events | Group-Object EventId | Sort-Object Count -Descending | Select-Object -First 10
$events | Get-WinSecIpTimeline -AsciiChart -BucketSize ([timespan]::FromHours(1))$findings = $events | Test-WinSecBruteForce -WindowMinutes 30
$findings | Format-Table Pattern, Severity, TargetAccount, SourceIp, FailureCount, SuccessCount
$sessions = $events | Get-WinSecLogonSession -ActiveOnly # potential live footholds
$sessions | Format-Table Account, Computer, LogonType, SourceIp, StartUtc, IsAdminVerdicts: BruteForce, PasswordSpraying, CredentialStuffing, LockoutStorm,
BruteForceWithSuccess. When a success follows sustained failures from the same source,
the reporting layer escalates the case verdict to Credential compromise.
$case = New-WinSecCase -SubjectIps 203.0.113.7 -Title 'RDP brute force vs VPN tier' `
-ScopeHosts VPN01, VPN02
$case.AddNote('Customer reported lockouts 02:00β03:00 UTC.')
$case = Export-WinSecInvestigation -Case $case -Event $events -Finding $findings `
-Session $sessions -Format All -OutputDirectory D:\IR -PassThruThe bundle contains events, findings, sessions, coverage, attribution, timeline, IOCs, HTML
and Markdown reports, case metadata and manifest.json with SHA-256 for every artifact.
New-WinSecIncidentReport -Case $case -Event $events -Finding $findings -Session $sessions `
-Template Full -Format Both -OutputDirectory D:\IR -PassThru
New-WinSecAbuseReport -Case $case -Event $events -Finding $findings `
-ReporterEmail 'soc@contoso.com' -ReporterOrg 'Contoso SOC' -Format All -OutputDirectory D:\IRBoth write to D:\IR\<CaseId>-reports\, never into the bundle.
$s = Test-WinSecEvidenceIntegrity -Path D:\IR\WSC-20261021-XXXXXXXX -Summary
$s | Format-List IsIntact, Verified, Modified, Missing, Extra
$b = Import-WinSecCase -Path D:\IR\WSC-20261021-XXXXXXXX
$b.Events | Get-WinSecIpTimeline -AsciiChart$hosts = Get-Content .\hosts.txt
$status = Get-WinSecEventByIp 203.0.113.7 -ComputerName $hosts `
-Credential (Get-Credential) -ThrottleLimit 16 -LastHours 12 -PassHostStatus
$status | Format-Table Computer, Status, EventsFound, ErrorMessage
$status | ForEach-Object { $_.LogDetails } | Format-Table LogName, Status, EventsFoundPrerequisites on targets: WinRM enabled, firewall open, credentials with event-log read access. See Remote Collection.
$events = Get-WinSecEventByIp 198.51.100.9 -Path D:\Evidence\*.evtx -Profile All- Cmdlet Reference β every parameter and example.
- Reporting and Abuse β what goes into the reports.
- Evidence and Reporting β chain of custody in detail.
- Configuration β defaults, output directory, API keys.
WinSecHunter v2.1.0 Β· PolyForm Noncommercial 1.0.0 Β· derivative of Get-SecurityEventsByIP.ps1 by Mikhail Deynekin
Repository Β· Changelog Β· Security policy Β· Contributing
π Home
π― Problem Domain π Getting Started π§ Architecture
π Cmdlet Reference π Detection and Analysis π Remote Collection π°οΈ Threat Intel and Attribution π‘οΈ Audit Policy Guide
π Reporting and Abuse π§Ύ Evidence and Reporting π Reference Tables
π§ Configuration π©Ί Troubleshooting and FAQ π§βπ» Development πΊοΈ Roadmap