diff --git a/.coderabbit.yaml b/.coderabbit.yaml index f484106..f0533aa 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -44,7 +44,7 @@ reviews: or parameters. Flag anything 7-only. - $PSScriptRoot is unreliable in param-default expressions under powershell.exe -File; defaults must be resolved in the script body. - - Every logging setting lives in LoggingBaseline.Settings.ps1 with a + - Every logging setting lives in WinLogKit.Settings.ps1 with a plain-language Purpose (and Risk where volume/stability matters). Flag settings hardcoded in the other scripts. - Shared helpers live in WinLogKit.Common.ps1 (host probes, registry diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 086961f..d1eb79f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,7 +8,7 @@ ## Checklist - [ ] `tests\Invoke-KitChecks.ps1` passes locally -- [ ] Settings changes made in `LoggingBaseline.Settings.ps1` only, with a +- [ ] Settings changes made in `WinLogKit.Settings.ps1` only, with a plain-language purpose (and risk note where it matters) - [ ] Generated files (presets, docs Reference page, packs) regenerated via their `tools\` generators, not edited by hand diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 99a9f47..24043e7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: Copy-Item *.ps1, PSScriptAnalyzerSettings.psd1, README.md, CHANGELOG.md, LICENSE $stage # Data-dependent scripts (coverage mapping, presets) must work from # the zip, not only from a git clone. - Copy-Item data, presets, tools, tests, addons $stage -Recurse + Copy-Item fleet, report, data, presets, tools, tests, addons $stage -Recurse Compress-Archive -Path $stage -DestinationPath "WinLogKit-$tag.zip" (Get-FileHash "WinLogKit-$tag.zip" -Algorithm SHA256).Hash.ToLower() + " WinLogKit-$tag.zip" | Set-Content "SHA256SUMS.txt" -Encoding ascii diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fa50ce..0c8f5af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,24 @@ All notable changes to WinLogKit. Versions follow [SemVer](https://semver.org/); releases are tagged `vX.Y.Z` and published with a zip + SHA256 checksum. -## Unreleased +## v1.0.0 - 2026-09-04 + +The v1.0 restructure ([ADR-001](https://github.com/spydisec/WinLogKit/pull/30)): +docs cut, one copy of the shared helpers, and a layout that shows a new +reader the three scripts they need. Version 1.0.0 because paths move; no +setting changed. ### Changed +- **Layout (breaking: paths).** The fleet generators + (`New-IntuneRemediationPack.ps1`, `New-GpoPack.ps1`, + `New-WefSubscription.ps1`, `Test-WefFilter.ps1`) now live in `fleet\`, + and the coverage report and WELA check (`Export-AttackCoverage.ps1`, + `Invoke-WELACheck.ps1`) in `report\`. The three host scripts stay at the + root with the settings table and the shared helpers. Output folders + (`Intune\`, `GPO\`, `WEF\`, `Results\`, `Evidence\`) stay at the kit + root wherever the script runs from. +- **`LoggingBaseline.Settings.ps1` is now `WinLogKit.Settings.ps1`.** Same + contents. If you carry a modified copy, rename it. - **`WinLogKit.Common.ps1`.** The helpers that Enable, Test, the WELA check, the coverage report and the fleet generators each carried their own copy of (admin check, host role and OS type, registry reads, the diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9560e84..815a30b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ is what tunes the presets. Open an issue with the *Field report* template. ## Ground rules for changes -- **Every setting lives in `LoggingBaseline.Settings.ps1`** with a +- **Every setting lives in `WinLogKit.Settings.ps1`** with a plain-language purpose and, where it matters, a risk note. Scripts, presets, packs and docs derive from it; never hard-code a setting anywhere else. diff --git a/Enable-LoggingBaseline.ps1 b/Enable-LoggingBaseline.ps1 index 74bc9c2..61a3cc4 100644 --- a/Enable-LoggingBaseline.ps1 +++ b/Enable-LoggingBaseline.ps1 @@ -99,7 +99,7 @@ $ErrorActionPreference = 'Stop' if ([string]::IsNullOrEmpty($BaselineDir)) { $BaselineDir = Join-Path $PSScriptRoot 'Baseline' } if ([string]::IsNullOrEmpty($LogDir)) { $LogDir = Join-Path $PSScriptRoot 'Logs' } -. (Join-Path $PSScriptRoot 'LoggingBaseline.Settings.ps1') +. (Join-Path $PSScriptRoot 'WinLogKit.Settings.ps1') . (Join-Path $PSScriptRoot 'WinLogKit.Common.ps1') # ---------------------------------------------------------------- helpers --- diff --git a/New-LoggingBaseline.ps1 b/New-LoggingBaseline.ps1 index e780515..078f181 100644 --- a/New-LoggingBaseline.ps1 +++ b/New-LoggingBaseline.ps1 @@ -91,7 +91,7 @@ Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' if ([string]::IsNullOrEmpty($OutFile)) { $OutFile = Join-Path $PSScriptRoot 'MyBaseline.csv' } -. (Join-Path $PSScriptRoot 'LoggingBaseline.Settings.ps1') +. (Join-Path $PSScriptRoot 'WinLogKit.Settings.ps1') . (Join-Path $PSScriptRoot 'WinLogKit.Common.ps1') if ((Test-Path $OutFile) -and -not $Force -and -not $Show) { diff --git a/PSScriptAnalyzerSettings.psd1 b/PSScriptAnalyzerSettings.psd1 index 9bdeb4d..6731faa 100644 --- a/PSScriptAnalyzerSettings.psd1 +++ b/PSScriptAnalyzerSettings.psd1 @@ -7,7 +7,7 @@ # The kit is an interactive console tool: Write-Host with colour IS the UI. 'PSAvoidUsingWriteHost' - # LoggingBaseline.Settings.ps1 defines script-scope tables consumed by the + # WinLogKit.Settings.ps1 defines script-scope tables consumed by the # other scripts after dot-sourcing; per-file analysis cannot see that. 'PSUseDeclaredVarsMoreThanAssignments' diff --git a/README.md b/README.md index 1220b70..2e74c05 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,9 @@ generator accept through `-BaselineFile`; `presets\` ships ready-made ones (ASD, Microsoft, and the kit's own `spydi_*` Minimal / Heavy pairs per role). +The three host scripts are at the kit root; fleet generators (Intune, GPO, +WEF) are in `fleet\` and the coverage report and WELA check in `report\`. + If scripts are blocked, `Set-ExecutionPolicy -Scope Process RemoteSigned` unblocks the current window without persisting anything; downloaded zips also need `Unblock-File`, and a policy enforced by Group Policy cannot be diff --git a/Test-LoggingBaseline.ps1 b/Test-LoggingBaseline.ps1 index 1d83cf3..fed4870 100644 --- a/Test-LoggingBaseline.ps1 +++ b/Test-LoggingBaseline.ps1 @@ -65,7 +65,7 @@ Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' if ([string]::IsNullOrEmpty($OutputDir)) { $OutputDir = Join-Path $PSScriptRoot 'Results' } -. (Join-Path $PSScriptRoot 'LoggingBaseline.Settings.ps1') +. (Join-Path $PSScriptRoot 'WinLogKit.Settings.ps1') . (Join-Path $PSScriptRoot 'WinLogKit.Common.ps1') # ---------------------------------------------------------------- helpers --- diff --git a/LoggingBaseline.Settings.ps1 b/WinLogKit.Settings.ps1 similarity index 99% rename from LoggingBaseline.Settings.ps1 rename to WinLogKit.Settings.ps1 index 3874d68..0a6e884 100644 --- a/LoggingBaseline.Settings.ps1 +++ b/WinLogKit.Settings.ps1 @@ -1,6 +1,6 @@ # ============================================================================= -# LoggingBaseline.Settings.ps1 -# Shared settings table for Enable-LoggingBaseline.ps1 and Test-LoggingBaseline.ps1 +# WinLogKit.Settings.ps1 +# The settings table every kit script dot-sources. # # This is the single source of truth for the kit. Both the enable script and # the verification script dot-source this file, so they can never disagree diff --git a/docs/baselines.md b/docs/baselines.md index ad3e629..a908a9f 100644 --- a/docs/baselines.md +++ b/docs/baselines.md @@ -7,7 +7,7 @@ ready-made lists we ship, how to build your own, and which one to pick. ## The model -The settings table (`LoggingBaseline.Settings.ps1`) is the single source of +The settings table (`WinLogKit.Settings.ps1`) is the single source of truth: every channel, audit subcategory, registry value and SMB audit setting, each with a plain-language purpose, a tier, a scope, behaviour category tags and - where it matters - a volume/stability risk note. @@ -114,7 +114,7 @@ Usage is identical to any baseline CSV: ```powershell .\New-LoggingBaseline.ps1 -Show -BaselineFile .\presets\role_Workstation.csv .\Enable-LoggingBaseline.ps1 -BaselineFile .\presets\role_MemberServer.csv -WhatIf -.\New-IntuneRemediationPack.ps1 -BaselineFile .\presets\role_Workstation.csv -OutDir .\Intune\Workstation +.\fleet\New-IntuneRemediationPack.ps1 -BaselineFile .\presets\role_Workstation.csv -OutDir .\Intune\Workstation ``` To customise a role, copy the CSV, flip `Selected` values in Excel, and keep @@ -208,7 +208,7 @@ Notes, stated plainly: kit's heaviest setting. - Selection CSVs carry item choices, not sizes: the Security log stays at the kit's 1 GB (ASD suggests 2 GB; raise it in - `LoggingBaseline.Settings.ps1` if you take that view). + `WinLogKit.Settings.ps1` if you take that view). - A **GPO pack** generated from a `spydi_Server_*` preset includes the DC-only subcategories. They are inert on member servers and collectors (those events only generate on DCs), but if your GPO hygiene prefers diff --git a/docs/commands.md b/docs/commands.md index d026ce3..ca65ed9 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -1,14 +1,30 @@ # Commands -Every script, what it does, and the flags you'll actually use. They all -read the same settings table (`LoggingBaseline.Settings.ps1`) and share one -helper file (`WinLogKit.Common.ps1`), so - given +Every script, what it does, and the flags you'll actually use. All of them +except `Test-WefFilter.ps1` (which needs only its sidecar CSV) read the +same settings table (`WinLogKit.Settings.ps1`) and share one helper file +(`WinLogKit.Common.ps1`), so - given the same selection, and regenerating artefacts after any settings change - what you apply, what you verify and what you deploy can't disagree. All of the kit's scripts run on PowerShell 7 and on stock Windows PowerShell 5.1 - use whichever your host has. (WELA is Yamato's tool with its own requirements; `Invoke-WELACheck.ps1` drives it either way.) +## Where the scripts live + +| Folder | Scripts | Run from | +|---|---|---| +| kit root | `New-`, `Enable-`, `Test-LoggingBaseline.ps1`, the settings table `WinLogKit.Settings.ps1`, the shared helpers `WinLogKit.Common.ps1` | the host you are configuring | +| `fleet\` | `New-IntuneRemediationPack.ps1`, `New-GpoPack.ps1`, `New-WefSubscription.ps1`, `Test-WefFilter.ps1` | an admin workstation (generators); the collector (`Test-WefFilter`) | +| `report\` | `Export-AttackCoverage.ps1`, `Invoke-WELACheck.ps1` | anywhere (coverage); the host (WELA) | +| `tools\` | regenerators for presets, the Reference page and the WEF event map | maintainers | + +The root, `fleet\` and `report\` scripts read the settings table and +helpers from the kit root and write their output (`Intune\`, `GPO\`, `WEF\`, +`Results\`, `Evidence\`) there too, wherever they live. Two things stand +alone by design: `Test-WefFilter.ps1` needs only its sidecar CSV, and the +generated Intune pack carries everything it needs to the endpoint. + ## Enable-LoggingBaseline.ps1 Applies the baseline. Idempotent - already-correct items are reported and @@ -65,7 +81,7 @@ which techniques it makes observable - and why the rest are not [Coverage](mapping.md). ```powershell -.\Export-AttackCoverage.ps1 [-IncludeHighVolume] [-IncludeOptional] [-BaselineFile ] +.\report\Export-AttackCoverage.ps1 [-IncludeHighVolume] [-IncludeOptional] [-BaselineFile ] ``` ## Invoke-WELACheck.ps1 @@ -76,7 +92,7 @@ timestamped evidence. Locates WELA in `.\WELA\` or an unzipped `WELA-\` folder; `-Download` fetches it from GitHub on request. ```powershell -.\Invoke-WELACheck.ps1 [-Download] [-WelaPath ] [-Baseline YamatoSecurity|ASD|Microsoft_Client|Microsoft_Server] +.\report\Invoke-WELACheck.ps1 [-Download] [-WelaPath ] [-Baseline YamatoSecurity|ASD|Microsoft_Client|Microsoft_Server] ``` WELA's own commands, for reference (v2.1.0, verified against source; all @@ -123,7 +139,7 @@ local event engine first. See [Collect - filtering with XPath](wec.md#filtering-with-xpath-matching-the-subscription-to-the-baseline). ```powershell -.\New-WefSubscription.ps1 [-BaselineFile ] [-Filter Channel|Baseline] [-Validate] [-SubscriptionId ] [-OutDir ] +.\fleet\New-WefSubscription.ps1 [-BaselineFile ] [-Filter Channel|Baseline] [-Validate] [-SubscriptionId ] [-OutDir ] ``` ## Test-WefFilter.ps1 @@ -136,7 +152,7 @@ checks the deployed subscription's query matches the generated XML on any unexpected ID or mismatch. ```powershell -.\Test-WefFilter.ps1 -ExpectedFile .\WEF\.expected-eventids.csv [-SubscriptionId ] [-Hours 24] +.\fleet\Test-WefFilter.ps1 -ExpectedFile .\WEF\.expected-eventids.csv [-SubscriptionId ] [-Hours 24] ``` ## New-GpoPack.ps1 diff --git a/docs/deployment.md b/docs/deployment.md index 0ccbaf7..9b420fe 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -11,7 +11,7 @@ Central collection (WEF / WEC) has its own page: [Collect](wec.md). ## Intune (workstations and cloud-managed servers) ```powershell -.\New-IntuneRemediationPack.ps1 [-BaselineFile ] [-IncludeHighVolume] [-IncludeOptional] +.\fleet\New-IntuneRemediationPack.ps1 [-BaselineFile ] [-IncludeHighVolume] [-IncludeOptional] ``` Produces a self-contained pair for Intune remediations: @@ -35,7 +35,7 @@ which does not belong in unattended remediation). ## GPO (domain-joined fleets) ```powershell -.\New-GpoPack.ps1 [-BaselineFile ] [-IncludeHighVolume] [-IncludeOptional] +.\fleet\New-GpoPack.ps1 [-BaselineFile ] [-IncludeHighVolume] [-IncludeOptional] ``` Produces: diff --git a/docs/getting-started.md b/docs/getting-started.md index 2cdfc1d..cf28a48 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -111,13 +111,13 @@ From an **elevated** Windows PowerShell prompt in the kit folder: .\Test-LoggingBaseline.ps1 # 3. Decide on the high volume tier with evidence, then apply it. -.\Export-AttackCoverage.ps1 # what Core makes observable -.\Export-AttackCoverage.ps1 -IncludeHighVolume # what HighVolume adds +.\report\Export-AttackCoverage.ps1 # what Core makes observable +.\report\Export-AttackCoverage.ps1 -IncludeHighVolume # what HighVolume adds .\Enable-LoggingBaseline.ps1 -IncludeHighVolume .\Test-LoggingBaseline.ps1 -IncludeHighVolume # 4. Independent second opinion (fetches WELA once, on request). -.\Invoke-WELACheck.ps1 -Download +.\report\Invoke-WELACheck.ps1 -Download # Escape hatch: restore everything captured at first run. .\Enable-LoggingBaseline.ps1 -Rollback @@ -143,6 +143,15 @@ stability risk, and `t` shows the whole tree at any point: Or start from a published reference: see [Baselines & Presets](baselines.md). +## Where the scripts live + +The three host scripts sit at the kit root. Fleet generators are in +`fleet\`, the coverage report and WELA check in `report\`; see +[Commands](commands.md#where-the-scripts-live). Wherever a script lives, it +reads the settings table from the root and writes its output there; the +one that needs neither is `Test-WefFilter.ps1`, which runs on the collector +from its sidecar CSV alone. + ## Where things land | Folder | Contents | diff --git a/docs/mapping.md b/docs/mapping.md index f229f4a..749006f 100644 --- a/docs/mapping.md +++ b/docs/mapping.md @@ -93,7 +93,7 @@ Reading it top to bottom: generated from them. - **One table**: every script - the builder, Enable, Test, the coverage report and all three fleet generators - dot-sources - `LoggingBaseline.Settings.ps1` (and the shared helpers in + `WinLogKit.Settings.ps1` (and the shared helpers in `WinLogKit.Common.ps1`), so applied config, deployed artefacts and verification can never disagree. - **Events out**: hosts write to the Windows Event Log service; @@ -158,9 +158,9 @@ Two data files in `data/attack/` (provenance and attribution in its README): logging), one sourced row per claim. ```powershell -.\Export-AttackCoverage.ps1 # Core tier -.\Export-AttackCoverage.ps1 -IncludeHighVolume -.\Export-AttackCoverage.ps1 -BaselineFile .\presets\role_Workstation.csv +.\report\Export-AttackCoverage.ps1 # Core tier +.\report\Export-AttackCoverage.ps1 -IncludeHighVolume +.\report\Export-AttackCoverage.ps1 -BaselineFile .\presets\role_Workstation.csv ``` Every technique verdict carries a reason: diff --git a/docs/reference.md b/docs/reference.md index 1b49011..d2424e4 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -21,7 +21,7 @@ Reading the columns: **S** = Microsoft Server, **Y** = Yamato (per the shipped reference presets; kit-added extras such as the Server 2025 SMB auditing and the NTLM audit values show no reference letter and are sourced in the - [settings table](https://github.com/spydisec/WinLogKit/blob/main/LoggingBaseline.Settings.ps1)). + [settings table](https://github.com/spydisec/WinLogKit/blob/main/WinLogKit.Settings.ps1)). - **Minimal / Heavy** - membership in `spydi_Server_Minimal` / `spydi_Server_Heavy` (the superset role presets; rows marked **(DC)** are deselected in the Workstation variants and inert off domain controllers). diff --git a/docs/wec.md b/docs/wec.md index b745285..54e5fac 100644 --- a/docs/wec.md +++ b/docs/wec.md @@ -30,7 +30,7 @@ worked example for Microsoft Sentinel is kept as an ## Generate the subscription ```powershell -.\New-WefSubscription.ps1 [-BaselineFile ] [-Filter Channel|Baseline] [-Validate] [-SubscriptionId ] +.\fleet\New-WefSubscription.ps1 [-BaselineFile ] [-Filter Channel|Baseline] [-Validate] [-SubscriptionId ] ``` Generates a source-initiated subscription XML with one query per selected diff --git a/New-GpoPack.ps1 b/fleet/New-GpoPack.ps1 similarity index 89% rename from New-GpoPack.ps1 rename to fleet/New-GpoPack.ps1 index 892cf7f..590a7be 100644 --- a/New-GpoPack.ps1 +++ b/fleet/New-GpoPack.ps1 @@ -42,14 +42,14 @@ switches decide (Core by default). .PARAMETER OutDir - Output folder. Default: .\GPO next to this script. + Output folder. Default: GPO\ at the kit root (the parent of fleet\). .EXAMPLE - .\New-GpoPack.ps1 -IncludeHighVolume + .\fleet\New-GpoPack.ps1 -IncludeHighVolume Core + HighVolume audit policy and registry artefacts. .EXAMPLE - .\New-GpoPack.ps1 -BaselineFile .\presets\Microsoft_Server.csv -OutDir .\GPO\MSServer + .\fleet\New-GpoPack.ps1 -BaselineFile .\presets\Microsoft_Server.csv -OutDir .\GPO\MSServer #> [CmdletBinding()] param( @@ -63,10 +63,13 @@ param( Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' -if ([string]::IsNullOrEmpty($OutDir)) { $OutDir = Join-Path $PSScriptRoot 'GPO' } +# This script lives in fleet\; the settings table, shared helpers, data +# and output folders are at the kit root. +$kitRoot = Split-Path $PSScriptRoot -Parent +if ([string]::IsNullOrEmpty($OutDir)) { $OutDir = Join-Path $kitRoot 'GPO' } -. (Join-Path $PSScriptRoot 'LoggingBaseline.Settings.ps1') -. (Join-Path $PSScriptRoot 'WinLogKit.Common.ps1') +. (Join-Path $kitRoot 'WinLogKit.Settings.ps1') +. (Join-Path $kitRoot 'WinLogKit.Common.ps1') $sel = Resolve-BaselineSelection -BaselineFile $BaselineFile -IncludeHighVolume $IncludeHighVolume -IncludeOptional $IncludeOptional @@ -140,12 +143,12 @@ if ($auditCount -lt $totalAudit) { Write-Host ("PARTIAL SELECTION: audit.csv covers {0} of {1} kit subcategories. Apply semantics for the others depend on the tool " -f $auditCount, $totalAudit) -ForegroundColor Yellow Write-Host 'and existing policy (LGPO /ac and GPO application may not preserve unlisted subcategories). After applying, ALWAYS verify' -ForegroundColor Yellow $verifyArgs = '' - if ($null -ne $sel.Map) { $verifyArgs = " -BaselineFile `"$BaselineFile`"" } + if ($null -ne $sel.Map) { $verifyArgs = " -BaselineFile `"$((Resolve-Path $BaselineFile).Path)`"" } else { if ($IncludeHighVolume) { $verifyArgs += ' -IncludeHighVolume' } if ($IncludeOptional) { $verifyArgs += ' -IncludeOptional' } } - Write-Host "the effective result: .\Test-LoggingBaseline.ps1$verifyArgs (it reads the live audit policy, not the file you applied)." -ForegroundColor Yellow + Write-Host "the effective result: & `"$(Join-Path $kitRoot 'Test-LoggingBaseline.ps1')`"$verifyArgs (it reads the live audit policy, not the file you applied)." -ForegroundColor Yellow } Write-Host 'Note: LGPO /t is additive - deselected registry values are NOT removed by a smaller pack. Use Enable-LoggingBaseline -Rollback or remove them deliberately.' -ForegroundColor Yellow exit 0 diff --git a/New-IntuneRemediationPack.ps1 b/fleet/New-IntuneRemediationPack.ps1 similarity index 95% rename from New-IntuneRemediationPack.ps1 rename to fleet/New-IntuneRemediationPack.ps1 index f33e0df..9f2da8d 100644 --- a/New-IntuneRemediationPack.ps1 +++ b/fleet/New-IntuneRemediationPack.ps1 @@ -34,7 +34,7 @@ Requires: Windows PowerShell 5.1+. No admin needed to generate. .PARAMETER OutDir - Where to write the pair. Default: .\Intune next to this script. + Where to write the pair. Default: Intune\ at the kit root (the parent of fleet\). .PARAMETER BaselineFile Optional selection CSV from New-LoggingBaseline.ps1. Only Selected = Y @@ -48,11 +48,11 @@ Without -BaselineFile: also embed Optional tier items. .EXAMPLE - .\New-IntuneRemediationPack.ps1 + .\fleet\New-IntuneRemediationPack.ps1 Recommended (Core) pack into .\Intune\. .EXAMPLE - .\New-IntuneRemediationPack.ps1 -BaselineFile .\WorkstationBaseline.csv -OutDir .\Intune\Workstation + .\fleet\New-IntuneRemediationPack.ps1 -BaselineFile .\WorkstationBaseline.csv -OutDir .\Intune\Workstation Pack for a role-specific baseline built with New-LoggingBaseline.ps1. #> [CmdletBinding()] @@ -67,10 +67,13 @@ param( Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' -if ([string]::IsNullOrEmpty($OutDir)) { $OutDir = Join-Path $PSScriptRoot 'Intune' } +# This script lives in fleet\; the settings table, shared helpers, data +# and output folders are at the kit root. +$kitRoot = Split-Path $PSScriptRoot -Parent +if ([string]::IsNullOrEmpty($OutDir)) { $OutDir = Join-Path $kitRoot 'Intune' } -. (Join-Path $PSScriptRoot 'LoggingBaseline.Settings.ps1') -. (Join-Path $PSScriptRoot 'WinLogKit.Common.ps1') +. (Join-Path $kitRoot 'WinLogKit.Settings.ps1') +. (Join-Path $kitRoot 'WinLogKit.Common.ps1') # ---------------------------------------------------------- item selection --- diff --git a/New-WefSubscription.ps1 b/fleet/New-WefSubscription.ps1 similarity index 95% rename from New-WefSubscription.ps1 rename to fleet/New-WefSubscription.ps1 index 15191a9..1430c80 100644 --- a/New-WefSubscription.ps1 +++ b/fleet/New-WefSubscription.ps1 @@ -62,7 +62,7 @@ Security events will silently fail. Transport defaults (ContentFormat, batching, heartbeat, source SDDL) live - in LoggingBaseline.Settings.ps1 ($BaselineWefDefaults); parameters here + in WinLogKit.Settings.ps1 ($BaselineWefDefaults); parameters here override them per run. Generation is read-only: no admin needed, nothing on the host changes. @@ -84,7 +84,7 @@ Subscription name shown in wecutil / Event Viewer. Default: WinLogKit-Baseline. .PARAMETER OutDir - Where the XML and sidecar are written. Default: .\WEF next to this script. + Where the XML and sidecar are written. Default: WEF\ at the kit root (the parent of fleet\). .PARAMETER ContentFormat Events (binary, locale-independent, smaller on the wire - default) or @@ -105,15 +105,15 @@ Computers and Network Service (Microsoft's documented default). .EXAMPLE - .\New-WefSubscription.ps1 + .\fleet\New-WefSubscription.ps1 Core-tier channels, whole-channel forwarding, into .\WEF\WinLogKit-Baseline.xml. .EXAMPLE - .\New-WefSubscription.ps1 -BaselineFile .\presets\spydi_Server_Minimal.csv -Filter Baseline -Validate + .\fleet\New-WefSubscription.ps1 -BaselineFile .\presets\spydi_Server_Minimal.csv -Filter Baseline -Validate Security filtered to exactly what that preset enables; every query parsed locally. .EXAMPLE - .\New-WefSubscription.ps1 -BaselineFile .\presets\ASD.csv -SubscriptionId ASD-Baseline + .\fleet\New-WefSubscription.ps1 -BaselineFile .\presets\ASD.csv -SubscriptionId ASD-Baseline Subscription covering exactly the channels the ASD preset selects. #> [CmdletBinding()] @@ -140,10 +140,13 @@ param( Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' -if ([string]::IsNullOrEmpty($OutDir)) { $OutDir = Join-Path $PSScriptRoot 'WEF' } +# This script lives in fleet\; the settings table, shared helpers, data +# and output folders are at the kit root. +$kitRoot = Split-Path $PSScriptRoot -Parent +if ([string]::IsNullOrEmpty($OutDir)) { $OutDir = Join-Path $kitRoot 'WEF' } -. (Join-Path $PSScriptRoot 'LoggingBaseline.Settings.ps1') -. (Join-Path $PSScriptRoot 'WinLogKit.Common.ps1') +. (Join-Path $kitRoot 'WinLogKit.Settings.ps1') +. (Join-Path $kitRoot 'WinLogKit.Common.ps1') $wefDefaults = $script:BaselineWefDefaults if ([string]::IsNullOrEmpty($ContentFormat)) { $ContentFormat = $wefDefaults.ContentFormat } @@ -202,7 +205,7 @@ if ($Filter -eq 'Baseline') { Write-Error 'Baseline filter mode found no selected audit subcategories in this selection, so the Security query would forward only the log-tamper events. Select subcategories in the baseline, or use -Filter Channel.' exit 1 } - $mapPath = Join-Path (Join-Path (Join-Path $PSScriptRoot 'data') 'wef') 'audit_subcategory_events.csv' + $mapPath = Join-Path (Join-Path (Join-Path $kitRoot 'data') 'wef') 'audit_subcategory_events.csv' if (-not (Test-Path $mapPath)) { Write-Error "Event map not found: $mapPath (regenerate with tools\Update-AuditSubcategoryEvents.ps1)"; exit 1 } $eventMap = Import-Csv $mapPath $idSet = New-Object 'System.Collections.Generic.SortedSet[int]' @@ -404,6 +407,6 @@ Write-Host " Server=http://:5985/wsman/SubscriptionManager/WE Write-Host ' (optionally HTTPS: Server=https://:5986/... - needs a server certificate on the collector)' Write-Host ' For the Security log: add NETWORK SERVICE to "Event Log Readers" on sources, or Security forwarding silently fails.' -ForegroundColor Yellow Write-Host '' -Write-Host "Prove the filter on the collector: .\Test-WefFilter.ps1 -ExpectedFile `"$sidecar`" -SubscriptionId $SubscriptionId" +Write-Host "Prove the filter on the collector: & `"$(Join-Path $PSScriptRoot 'Test-WefFilter.ps1')`" -ExpectedFile `"$sidecar`" -SubscriptionId $SubscriptionId" Write-Host 'SIEM handoff point: the ForwardedEvents log on the collector. Ingestion beyond that is out of kit scope.' exit 0 diff --git a/Test-WefFilter.ps1 b/fleet/Test-WefFilter.ps1 similarity index 98% rename from Test-WefFilter.ps1 rename to fleet/Test-WefFilter.ps1 index c54e9e3..4436d06 100644 --- a/Test-WefFilter.ps1 +++ b/fleet/Test-WefFilter.ps1 @@ -48,7 +48,7 @@ (.xml next to the sidecar). .EXAMPLE - .\Test-WefFilter.ps1 -ExpectedFile .\WEF\WinLogKit-Baseline.expected-eventids.csv -SubscriptionId WinLogKit-Baseline + .\fleet\Test-WefFilter.ps1 -ExpectedFile .\WEF\WinLogKit-Baseline.expected-eventids.csv -SubscriptionId WinLogKit-Baseline #> [CmdletBinding()] param( diff --git a/Export-AttackCoverage.ps1 b/report/Export-AttackCoverage.ps1 similarity index 94% rename from Export-AttackCoverage.ps1 rename to report/Export-AttackCoverage.ps1 index ce6e344..df6efab 100644 --- a/Export-AttackCoverage.ps1 +++ b/report/Export-AttackCoverage.ps1 @@ -40,13 +40,13 @@ Use the vendored OSSEM-DM snapshot instead of the native mapping. .PARAMETER OutDir - Where the CSVs go. Default: .\Results next to this script. + Where the CSVs go. Default: Results\ at the kit root (the parent of report\). .EXAMPLE - .\Export-AttackCoverage.ps1 -IncludeHighVolume + .\report\Export-AttackCoverage.ps1 -IncludeHighVolume .EXAMPLE - .\Export-AttackCoverage.ps1 -BaselineFile .\presets\role_Workstation.csv + .\report\Export-AttackCoverage.ps1 -BaselineFile .\presets\role_Workstation.csv #> [CmdletBinding()] param( @@ -61,10 +61,13 @@ param( Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' -if ([string]::IsNullOrEmpty($OutDir)) { $OutDir = Join-Path $PSScriptRoot 'Results' } +# This script lives in report\; the settings table, shared helpers, data +# and output folders are at the kit root. +$kitRoot = Split-Path $PSScriptRoot -Parent +if ([string]::IsNullOrEmpty($OutDir)) { $OutDir = Join-Path $kitRoot 'Results' } -. (Join-Path $PSScriptRoot 'LoggingBaseline.Settings.ps1') -. (Join-Path $PSScriptRoot 'WinLogKit.Common.ps1') +. (Join-Path $kitRoot 'WinLogKit.Settings.ps1') +. (Join-Path $kitRoot 'WinLogKit.Common.ps1') # ---------------------------------------------- resolve the selection sets --- @@ -106,7 +109,7 @@ $detail = New-Object System.Collections.Generic.List[object] if ($UseOssem) { # ------------------- legacy cross-check: OSSEM-DM snapshot join ---------- - $snapshot = Join-Path (Join-Path (Join-Path $PSScriptRoot 'data') 'ossem') 'techniques_to_events_windows.csv' + $snapshot = Join-Path (Join-Path (Join-Path $kitRoot 'data') 'ossem') 'techniques_to_events_windows.csv' if (-not (Test-Path $snapshot)) { Write-Error "OSSEM snapshot not found at $snapshot"; exit 1 } $subcatAlias = @{ 'PNP Activity' = 'Plug and Play'; 'Policy Change' = 'Audit Policy Change' } $subcatSelectedByName = @{}; $subcatKnownByName = @{} @@ -144,7 +147,7 @@ if ($UseOssem) { } else { # ------------------------ native mapping: ATT&CK v19.2 + kit event map --- # Nested Join-Path keeps these resolvable on non-Windows PowerShell too. - $attackDir = Join-Path (Join-Path $PSScriptRoot 'data') 'attack' + $attackDir = Join-Path (Join-Path $kitRoot 'data') 'attack' $analyticsCsv = Join-Path $attackDir 'windows_analytics.csv' $mapCsv = Join-Path $attackDir 'event_map.csv' foreach ($p in @($analyticsCsv, $mapCsv)) { diff --git a/Invoke-WELACheck.ps1 b/report/Invoke-WELACheck.ps1 similarity index 93% rename from Invoke-WELACheck.ps1 rename to report/Invoke-WELACheck.ps1 index f4782ae..65ce78d 100644 --- a/Invoke-WELACheck.ps1 +++ b/report/Invoke-WELACheck.ps1 @@ -40,19 +40,21 @@ .PARAMETER Download If WELA is not found, download WELA.ps1 and its two config files from - github.com/Yamato-Security/WELA (main branch) into .\WELA\ beside this - script. Off by default so nothing is fetched without an explicit decision. + github.com/Yamato-Security/WELA (main branch) into WELA\ at the kit root + (the parent of report\), which is also where an existing WELA\ or + WELA-\ folder is looked for. Off by default so nothing is fetched + without an explicit decision. .PARAMETER EvidenceDir Root folder for evidence. A timestamped subfolder is created per run. - Default: .\Evidence next to this script. + Default: Evidence\ at the kit root (the parent of report\). .EXAMPLE - .\Invoke-WELACheck.ps1 -Download + .\report\Invoke-WELACheck.ps1 -Download First run on an internet-connected test box: fetch WELA, run both audits. .EXAMPLE - .\Invoke-WELACheck.ps1 -WelaPath C:\Tools\WELA\WELA.ps1 -Baseline ASD + .\report\Invoke-WELACheck.ps1 -WelaPath C:\Tools\WELA\WELA.ps1 -Baseline ASD Air-gapped run against a pre-staged WELA copy, ASD baseline. #> [CmdletBinding()] @@ -68,9 +70,12 @@ param( Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' -if ([string]::IsNullOrEmpty($EvidenceDir)) { $EvidenceDir = Join-Path $PSScriptRoot 'Evidence' } +# This script lives in report\; the settings table, shared helpers, data +# and output folders are at the kit root. +$kitRoot = Split-Path $PSScriptRoot -Parent +if ([string]::IsNullOrEmpty($EvidenceDir)) { $EvidenceDir = Join-Path $kitRoot 'Evidence' } -. (Join-Path $PSScriptRoot 'WinLogKit.Common.ps1') +. (Join-Path $kitRoot 'WinLogKit.Common.ps1') if (-not (Test-IsAdmin)) { Write-Error 'Run as local Administrator - WELA audit-settings reads the audit policy via auditpol.' @@ -79,7 +84,7 @@ if (-not (Test-IsAdmin)) { # ------------------------------------------------------- locate / download --- -$welaDir = Join-Path $PSScriptRoot 'WELA' +$welaDir = Join-Path $kitRoot 'WELA' if ([string]::IsNullOrEmpty($WelaPath)) { # Search order: .\WELA\, any .\WELA-* folder (e.g. an unzipped WELA-2.1.0 # release, newest name first), then WELA.ps1 in the current directory. @@ -89,7 +94,7 @@ if ([string]::IsNullOrEmpty($WelaPath)) { $v = $null if ([version]::TryParse(($_.Name -replace '^WELA-', ''), [ref]$v)) { $v } else { [version]'0.0' } }; Descending = $true } - foreach ($d in (Get-ChildItem -Path $PSScriptRoot -Directory -Filter 'WELA-*' -ErrorAction SilentlyContinue | Sort-Object -Property $versionSort)) { + foreach ($d in (Get-ChildItem -Path $kitRoot -Directory -Filter 'WELA-*' -ErrorAction SilentlyContinue | Sort-Object -Property $versionSort)) { $candidates += Join-Path $d.FullName 'WELA.ps1' } $candidates += Join-Path (Get-Location).Path 'WELA.ps1' diff --git a/tests/Invoke-KitChecks.ps1 b/tests/Invoke-KitChecks.ps1 index 584326e..df36aa5 100644 --- a/tests/Invoke-KitChecks.ps1 +++ b/tests/Invoke-KitChecks.ps1 @@ -113,7 +113,7 @@ if ($notInCommon) { } # 2. Settings table consistency ----------------------------------------------- -. (Join-Path $KitRoot 'LoggingBaseline.Settings.ps1') +. (Join-Path $KitRoot 'WinLogKit.Settings.ps1') $bad = @() foreach ($grp in @($BaselineChannels, $BaselineAuditSubcategories, $BaselineRegistrySettings, $BaselineSmbAuditSettings)) { @@ -190,7 +190,7 @@ try { # 5. Intune pack generation: files parse, placeholders replaced, selection respected $packDir = Join-Path $tmp 'intune' - & (Join-Path $KitRoot 'New-IntuneRemediationPack.ps1') -OutDir $packDir | Out-Null + & (Join-Path $KitRoot 'fleet\New-IntuneRemediationPack.ps1') -OutDir $packDir | Out-Null foreach ($f in @('Detect-LoggingBaseline.ps1', 'Remediate-LoggingBaseline.ps1')) { $p = Join-Path $packDir $f if (-not (Test-Path $p)) { Fail "Intune pack missing $f"; continue } @@ -200,7 +200,7 @@ try { if ((Get-Content $p -Raw) -match '__(MODE|ITEMS|COUNT|SOURCE|FILENAME)__') { Fail "generated $f has unreplaced placeholders" } } $packDir2 = Join-Path $tmp 'intune-csv' - & (Join-Path $KitRoot 'New-IntuneRemediationPack.ps1') -OutDir $packDir2 -BaselineFile $csv1 | Out-Null + & (Join-Path $KitRoot 'fleet\New-IntuneRemediationPack.ps1') -OutDir $packDir2 -BaselineFile $csv1 | Out-Null $detect2 = Get-Content (Join-Path $packDir2 'Detect-LoggingBaseline.ps1') -Raw # The recommended CSV selects only Core, so no HighVolume item may be embedded. if ($detect2 -match 'EnableModuleLogging') { Fail 'Intune pack from Core-only CSV embedded a HighVolume item' } else { Pass 'Intune pack honours the baseline CSV selection' } @@ -231,7 +231,7 @@ try { # 7a. GPO pack: audit.csv row count matches selection; registry.txt has policy values $gpoTmp = Join-Path $tmp 'gpo' - & (Join-Path $KitRoot 'New-GpoPack.ps1') -OutDir $gpoTmp -IncludeHighVolume | Out-Null + & (Join-Path $KitRoot 'fleet\New-GpoPack.ps1') -OutDir $gpoTmp -IncludeHighVolume | Out-Null $auditRows = @(Import-Csv (Join-Path $gpoTmp 'audit.csv')) $expectedAudit = @($BaselineAuditSubcategories | Where-Object { $_.Tier -eq 'Core' -or $_.Tier -eq 'HighVolume' }).Count if ($auditRows.Count -eq $expectedAudit) { Pass "GPO audit.csv rows ($expectedAudit)" } else { Fail "GPO audit.csv has $($auditRows.Count) rows, expected $expectedAudit" } @@ -252,7 +252,7 @@ try { if ($badMap) { Fail "event_map.csv references unknown settings items: $($badMap -join ', ')" } else { Pass 'event map item ids valid against settings table' } $covTmp = Join-Path $tmp 'cov' - & (Join-Path $KitRoot 'Export-AttackCoverage.ps1') -OutDir $covTmp | Out-Null + & (Join-Path $KitRoot 'report\Export-AttackCoverage.ps1') -OutDir $covTmp | Out-Null $covDetail = Get-ChildItem $covTmp -Filter 'AttackCoverage_Detail_*.csv' | Select-Object -First 1 if ($null -eq $covDetail) { Fail 'coverage detail CSV not produced' } else { $covRows = Import-Csv $covDetail.FullName @@ -265,7 +265,7 @@ try { # 7. WEF subscription generation: valid XML, one query per selected channel $wefTmp = Join-Path $tmp 'wef' - & (Join-Path $KitRoot 'New-WefSubscription.ps1') -OutDir $wefTmp -BaselineFile (Join-Path $KitRoot 'presets\ASD.csv') -SubscriptionId 'CheckSub' | Out-Null + & (Join-Path $KitRoot 'fleet\New-WefSubscription.ps1') -OutDir $wefTmp -BaselineFile (Join-Path $KitRoot 'presets\ASD.csv') -SubscriptionId 'CheckSub' | Out-Null try { [xml]$wx = Get-Content (Join-Path $wefTmp 'CheckSub.xml') -Raw $qCount = [regex]::Matches($wx.Subscription.Query.'#cdata-section', '&1 | Out-String + $wefOut = & (Join-Path $KitRoot 'fleet\New-WefSubscription.ps1') -OutDir $wefB -BaselineFile (Join-Path $KitRoot 'presets\spydi_Server_Heavy.csv') -Filter Baseline -Validate -SubscriptionId 'CheckB' 2>&1 | Out-String if ($wefOut -match 'INVALID') { Fail "WEF Baseline filter: a generated query failed local validation: $wefOut" } try { [xml]$wb = Get-Content (Join-Path $wefB 'CheckB.xml') -Raw diff --git a/tools/Export-ReferenceTable.ps1 b/tools/Export-ReferenceTable.ps1 index 27c6724..0ea2657 100644 --- a/tools/Export-ReferenceTable.ps1 +++ b/tools/Export-ReferenceTable.ps1 @@ -7,7 +7,7 @@ .DESCRIPTION Everything is derived, never hand-written: - - items and sizes: LoggingBaseline.Settings.ps1 + - items and sizes: WinLogKit.Settings.ps1 - key events: the curated ATT&CK event map (authoritative for audit subcategories) plus event IDs mentioned in each item's Purpose text - reference membership: the ASD / Microsoft_Client / Microsoft_Server @@ -32,7 +32,7 @@ $ErrorActionPreference = 'Stop' $kitRoot = Split-Path $PSScriptRoot -Parent if ([string]::IsNullOrEmpty($OutFile)) { $OutFile = Join-Path (Join-Path $kitRoot 'docs') 'reference.md' } -. (Join-Path $kitRoot 'LoggingBaseline.Settings.ps1') +. (Join-Path $kitRoot 'WinLogKit.Settings.ps1') # ---- preset membership lookups ---------------------------------------------- @@ -176,7 +176,7 @@ Reading the columns: **S** = Microsoft Server, **Y** = Yamato (per the shipped reference presets; kit-added extras such as the Server 2025 SMB auditing and the NTLM audit values show no reference letter and are sourced in the - [settings table](https://github.com/spydisec/WinLogKit/blob/main/LoggingBaseline.Settings.ps1)). + [settings table](https://github.com/spydisec/WinLogKit/blob/main/WinLogKit.Settings.ps1)). - **Minimal / Heavy** - membership in `spydi_Server_Minimal` / `spydi_Server_Heavy` (the superset role presets; rows marked **(DC)** are deselected in the Workstation variants and inert off domain controllers). diff --git a/tools/New-PresetBaselines.ps1 b/tools/New-PresetBaselines.ps1 index a54ee88..8401529 100644 --- a/tools/New-PresetBaselines.ps1 +++ b/tools/New-PresetBaselines.ps1 @@ -1,7 +1,7 @@ <# .SYNOPSIS Regenerates the reference baseline presets in .\presets\ from the settings - table. Run after changing LoggingBaseline.Settings.ps1; CI fails if the + table. Run after changing WinLogKit.Settings.ps1; CI fails if the committed presets drift from what this script produces. .DESCRIPTION diff --git a/tools/Update-AuditSubcategoryEvents.ps1 b/tools/Update-AuditSubcategoryEvents.ps1 index 6c58a0d..3ccef3c 100644 --- a/tools/Update-AuditSubcategoryEvents.ps1 +++ b/tools/Update-AuditSubcategoryEvents.ps1 @@ -27,7 +27,7 @@ Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' $kitRoot = Split-Path $PSScriptRoot -Parent if ([string]::IsNullOrEmpty($OutFile)) { $OutFile = Join-Path (Join-Path (Join-Path $kitRoot 'data') 'wef') 'audit_subcategory_events.csv' } -. (Join-Path $kitRoot 'LoggingBaseline.Settings.ps1') +. (Join-Path $kitRoot 'WinLogKit.Settings.ps1') $base = 'https://learn.microsoft.com/windows/security/threat-protection/auditing/' # Explicit name -> page slug map. Microsoft's slugs are not derivable for