Thank you for your interest in contributing to WinFIRE β the Windows Forensic Investigation & Response Engine. This guide will help you get started.
Repository: https://github.com/Masriyan/WinFire/
- Ways to Contribute
- Development Setup
- Architecture in 60 Seconds
- Code Guidelines
- Module Template
- Testing
- Pull Request Process
- Code of Conduct
- Search existing issues first
- Create a new issue with:
- Title: Clear, descriptive summary
- Environment: Windows version, PowerShell version, WinFIRE version (
$script:Version) - Steps to Reproduce: Detailed reproduction steps and the exact flags used
- Expected vs Actual: What you expected vs what happened
- Logs: Relevant lines from the case folder's
winfire.log
- Open an issue with the
enhancementlabel - Describe:
- Forensic Value: Why is this artifact / detection important?
- Use Case: When would an investigator need this?
- Data Source: Where does the data come from (API, binary, registry, EVTXβ¦)?
- Findings: What
severity/ MITRE ATT&CK techniques would it emit?
- Fix typos or clarify existing documentation
- Add usage examples or forensic context to
usage-sample.md - Translate documentation to other languages
- Windows 10/11 or Windows Server 2016+ (modules can be exercised cross-platform under
PowerShell 7 with
WINFIRE_ALLOW_NONWINDOWS=1; Windows-only probes simply collect nothing) - PowerShell 5.1+ (Windows PowerShell) or PowerShell 7
- Pester and PSScriptAnalyzer for tests/lint
- Git for version control
- Administrator privileges for testing full coverage
Install-Module Pester, PSScriptAnalyzer -Scope CurrentUser# 1. Fork the repository on GitHub
# 2. Clone your fork
git clone https://github.com/YOUR-USERNAME/WinFire.git
cd WinFire
# 3. Add upstream remote
git remote add upstream https://github.com/Masriyan/WinFire.git
# 4. Create a feature branch
git checkout -b feature/your-feature-name
# 5. Keep your fork updated
git fetch upstream
git merge upstream/mainWinFIRE.ps1 is a thin orchestrator. It owns a module registry ($script:Modules) that is
the single source of truth for dispatch, -ListModules, and the docs. Each entry names a
modules\*.psm1 file, the phase, the tools it needs, a Selected scriptblock (which flags
turn it on), and an Invoke scriptblock (its entrypoint).
Every module receives the shared Context (Initialize-WinFIRESession) and returns zero or
more normalized finding objects built with New-WinFIREFinding. The orchestrator collects
them, and Report.psm1 + Mitre.psm1 render report.html + findings.json.
Core helpers you should reuse (all in modules/Core.psm1):
| Helper | Purpose |
|---|---|
Write-WinFIRELog -Level INFO -Message β¦ |
Console + winfire.log logging (-Quiet aware) |
Invoke-WinFIRESafeOp -Name β¦ -Operation { β¦ } |
Non-terminating wrapper: logs, times, returns $null on failure |
New-WinFIREFinding -Module β¦ -Severity β¦ -Title β¦ |
The one normalized finding shape consumed by reporting |
Save-WinFIREData -Context $ctx -Name β¦ |
Persist raw objects to raw\ as JSON (+CSV when tabular) |
Resolve-WinFIRETool -Context $ctx -Name yara |
Locate a bundled binary; returns $null so the module can degrade gracefully |
Get-WinFIREFileHash, Invoke-WinFIREParallel, Test-WinFIREAdmin |
Hashing, PS5.1-compatible runspace fan-out, privilege checks |
| Element | Convention | Example |
|---|---|---|
| Module entrypoint | Invoke-WinFIRE<Name> |
Invoke-WinFIRELiveTriage |
| Helper functions | Verb-WinFIRE* |
Get-WinFIREHiddenFiles |
| Variables | $camelCase |
$processData |
| Script Variables | $script:PascalCase |
$script:Version |
| Parameters | PascalCase |
[string]$OutputPath |
# β
Wrap probes in the safe-op wrapper β a single broken probe must never abort a run
$data = Invoke-WinFIRESafeOp -Name "Collect services" -Quiet:$ctx.Quiet -Operation {
Get-CimInstance Win32_Service | Select-Object Name, PathName, StartName, State
}
# β
Log via the standard logger (respects -Quiet, always writes winfire.log)
Write-WinFIRELog -Level INFO -Message "Parsed $($data.Count) services."
# β
Persist raw artifacts to the case 'raw\' folder
Save-WinFIREData -Context $ctx -Name 'services' -Data $data | Out-Null
# β
Emit normalized findings β this is what the report renders
New-WinFIREFinding -Module 'LiveTriage' -Severity 'Medium' `
-Title 'Unsigned service from a user-writable path' `
-Target $svc.PathName -Mitre @('T1543.003') `
-Detail 'Service binary is unsigned and lives under a writable directory.' `
-Evidence @{ Name = $svc.Name; Path = $svc.PathName }- Handle null/empty data gracefully; never assume a probe returned rows
- Degrade gracefully when a tool/privilege is absent β return an Info finding, don't throw
- Use
-ErrorAction SilentlyContinuewhere appropriate; letInvoke-WinFIRESafeOpcatch the rest - Document each function with a
<# .SYNOPSIS #>/ comment block - Add
[CmdletBinding()]to all functions (required forSet-StrictMode -Version Latest) - Keep lines under 120 characters
- Use the
$script:Versionconstant instead of hardcoding version strings - Use ASCII-only characters in string literals (no Unicode box-drawing) β the codebase runs on PS 5.1
- Place
$nullon the left side of equality comparisons (if ($null -eq $x)) - Avoid PowerShell automatic variable names (
$profile,$event,$host, etc.) - No hardcoded paths β derive from
$ctx/$PSScriptRoot/ environment variables - No
Write-Hostoutside the logger; route everything throughWrite-WinFIRELog
Use this template when adding a new capability module under modules\:
# modules/MyFeature.psm1
function Invoke-WinFIREMyFeature {
<#
.SYNOPSIS
One-line description of what this module hunts for / collects.
.DESCRIPTION
Detailed forensic value, data source, and the privilege/tooling it needs.
#>
[CmdletBinding()]
param([Parameter(Mandatory)]$Context)
$findings = New-Object System.Collections.Generic.List[object]
# Optional: locate a bundled binary; degrade gracefully if absent.
# $tool = Resolve-WinFIRETool -Context $Context -Name 'yara'
# if (-not $tool) {
# $findings.Add((New-WinFIREFinding -Module 'MyFeature' -Severity 'Info' `
# -Title 'MyFeature engine unavailable' -Detail 'bin\yara64.exe not found; skipped.'))
# return $findings
# }
$data = Invoke-WinFIRESafeOp -Name 'MyFeature: collect' -Quiet:$Context.Quiet -Operation {
# β¦your collection / analysis logic hereβ¦
}
if ($data) {
Save-WinFIREData -Context $Context -Name 'myfeature' -Data $data | Out-Null
foreach ($item in $data) {
if ($item.IsSuspicious) {
$findings.Add((New-WinFIREFinding -Module 'MyFeature' -Severity 'Medium' `
-Title 'Suspicious MyFeature artifact' -Target $item.Path `
-Mitre @('T1059') -Evidence @{ Detail = $item.Why }))
}
}
}
return $findings
}
Export-ModuleMember -Function Invoke-WinFIREMyFeatureThen register it in $script:Modules inside WinFIRE.ps1:
[pscustomobject]@{
Name = 'MyFeature'; File = 'MyFeature.psm1'; Phase = 3; Status = 'Ready'
Desc = 'Short description shown by -ListModules'
Tools = @() # tool names from config/tools.json this module needs
Selected = { $MyFeature -or $Full } # which flags enable it
Invoke = { param($ctx) Invoke-WinFIREMyFeature -Context $ctx }
}# Static analysis (uses tests/PSScriptAnalyzerSettings.psd1)
Invoke-ScriptAnalyzer -Recurse .
# Pester unit tests β pure helpers run cross-platform (no Windows required)
Invoke-Pester .\testsAdd Pester cases to tests\WinFIRE.Tests.ps1 for any new pure helper (parsing, scoring,
classification). Windows-only probes that can't run under CI should be guarded so the suite
still passes everywhere.
.\WinFIRE.ps1 -ListModules # registry prints, every module visible
.\WinFIRE.ps1 -Quick -CaseNumber TEST-01 # LiveTriage + registry/event-log YARA
.\WinFIRE.ps1 -Full -CaseNumber TEST-02 # every available module
.\WinFIRE.ps1 -LiveTriage -Quiet -CaseNumber TEST-03- β
A case folder
WinFIRE_<HOST>_<stamp>\is created - β
report.htmlandfindings.jsonare written and open cleanly - β
New findings carry a sensible
Severityand MITRE technique(s) - β
No unhandled exceptions in
winfire.log - β The run still completes when the module's tool/privilege is missing (graceful Info finding)
- Update
CHANGELOG.mdwith your changes (and bump$script:Versionif releasing) - Register any new module in
$script:Modules - Add/extend Pester tests in
tests\WinFIRE.Tests.ps1 - Update
README.md/usage-sample.mdfor user-visible features - Run
Invoke-ScriptAnalyzer -Recurse .andInvoke-Pester .\testsclean
## Description
Brief description of changes.
## Type of Change
- [ ] Bug fix
- [ ] New module / capability
- [ ] Documentation update
- [ ] Performance improvement
## Testing Done
- [ ] `Invoke-Pester .\tests` passes
- [ ] `Invoke-ScriptAnalyzer -Recurse .` clean
- [ ] Smoke-tested on Windows 10/11 (`-Quick` / `-Full`)
## Checklist
- [ ] Code follows project style and degrades gracefully
- [ ] CHANGELOG.md updated
- [ ] Documentation updated- Maintainers will review within 1-2 weeks
- Address any requested changes
- Once approved, the PR will be merged
- β Be respectful and inclusive
- β Focus on constructive feedback
- β Help others learn
- β Accept responsibility for mistakes
- β No harassment or discrimination
- β No personal attacks
Violations may result in:
- Warning
- Temporary ban
- Permanent ban
Report issues to: sudo3rs@protonmail.com
- GitHub Issues: https://github.com/Masriyan/WinFire/issues
- Discussions: https://github.com/Masriyan/WinFire/discussions
- Email: sudo3rs@protonmail.com
Thank you for contributing to WinFIRE! π₯