-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWinLogKit.Common.ps1
More file actions
199 lines (181 loc) · 8.95 KB
/
Copy pathWinLogKit.Common.ps1
File metadata and controls
199 lines (181 loc) · 8.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# =============================================================================
# WinLogKit.Common.ps1
# Shared helpers, dot-sourced by the kit scripts right after the settings
# table. Everything here is read-only against the host: host probes, registry
# reads, the audit policy and SMB audit-state readers and the one selection model (tier switches
# or a selection CSV) that Enable, Test, the coverage report and the fleet
# generators all use. Registry writers stay in Enable-LoggingBaseline.ps1,
# the only script that writes.
#
# The Intune pack generator embeds its own copies of what the generated
# scripts need: those must stay self-contained.
#
# PowerShell 5.1 compatible. No external module dependencies.
# =============================================================================
Set-StrictMode -Version 2.0
# ------------------------------------------------------------ host probes ---
function Test-IsAdmin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
(New-Object Security.Principal.WindowsPrincipal $id).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Get-DomainRole {
# Win32_ComputerSystem.DomainRole: 0/1 standalone, 2/3 member, 4/5 domain controller
$role = (Get-CimInstance -ClassName Win32_ComputerSystem).DomainRole
if ($role -ge 4) { return 'DomainController' }
if ($role -ge 2) { return 'Member' }
return 'Standalone'
}
function Get-OsType {
# Win32_OperatingSystem.ProductType: 1 workstation, 2 domain controller, 3 server
$pt = (Get-CimInstance -ClassName Win32_OperatingSystem).ProductType
if ($pt -eq 1) { return 'Workstation' }
if ($pt -eq 2) { return 'Domain Controller' }
return 'Server'
}
# ---------------------------------------------------------- registry read ---
# Registry access uses the .NET API throughout, not *-ItemProperty, because
# one required value is literally named '*' and the ItemProperty cmdlets
# treat that as a wildcard.
function ConvertTo-NetRegPath { param([string]$Path) $Path -replace '^HKLM:\\', 'HKEY_LOCAL_MACHINE\' }
function Get-RegValue {
param([string]$Path, [string]$Name)
[Microsoft.Win32.Registry]::GetValue((ConvertTo-NetRegPath $Path), $Name, $null)
}
# ----------------------------------------------------------- audit policy ---
function Get-AuditPolicyByGuid {
# One auditpol call for everything; returns hashtable GUID -> inclusion setting text.
$map = @{}
$lines = auditpol /get /category:* /r
if ($LASTEXITCODE -ne 0) {
throw "auditpol /get /category:* /r failed with exit code $LASTEXITCODE (run elevated): $(($lines | Select-Object -First 2) -join ' ')"
}
$csv = $lines | Where-Object { $_ -match '\S' } | ConvertFrom-Csv
foreach ($row in $csv) {
$guid = ($row.'Subcategory GUID' -replace '[{}]', '').ToUpper()
$map[$guid] = $row.'Inclusion Setting'
}
return $map
}
# ------------------------------------------------------------ SMB auditing ---
# Current state of the Server 2025+ SMB signing/encryption audit settings.
# Returns a hashtable Id -> current bool; items missing from the hashtable are
# unsupported on this OS (the properties only exist on Server 2025 / Win11 24H2+).
function Get-SmbAuditState {
$state = @{}
$srv = $null; $cli = $null
try { $srv = Get-SmbServerConfiguration -ErrorAction Stop } catch { $srv = $null }
try { $cli = Get-SmbClientConfiguration -ErrorAction Stop } catch { $cli = $null }
foreach ($item in $script:BaselineSmbAuditSettings) {
$cfg = $srv
if ($item.Side -eq 'Client') { $cfg = $cli }
if ($null -ne $cfg -and ($cfg.PSObject.Properties.Name -contains $item.Id)) {
$state[$item.Id] = [bool]$cfg.($item.Id)
}
}
return $state
}
# -------------------------------------------------------------- selection ---
#
# A selection answers "is this item on?" for every item in the settings
# table. It comes from one of two places, and a baseline CSV always wins:
# - a selection CSV from New-LoggingBaseline.ps1 (or a preset): listed rows
# decide, unlisted items are off
# - the tier switches: Core is always on, HighVolume and Optional only when
# their switch is given
# Every "ITEMTYPE|ID" key the settings table defines, as a hashtable set.
function Get-BaselineItemKeySet {
$keys = @{}
foreach ($ch in $script:BaselineChannels) { $keys[("Channel|$($ch.Name)").ToUpper()] = $true }
foreach ($sub in $script:BaselineAuditSubcategories) { $keys[("AuditPolicy|$($sub.Guid)").ToUpper()] = $true }
foreach ($rs in $script:BaselineRegistrySettings) { $keys[("Registry|$($rs.Id)").ToUpper()] = $true }
foreach ($sa in $script:BaselineSmbAuditSettings) { $keys[("SmbAudit|$($sa.Id)").ToUpper()] = $true }
$keys[("Registry|$($script:BaselineAdcsAuditFilter.Id)").ToUpper()] = $true
return $keys
}
# Selection map from a selection CSV: "ITEMTYPE|ID" -> bool. The file is
# checked first: the wrong CSV (a Results export, say) must stop the run,
# not quietly select nothing. A CSV whose rows match nothing in the settings
# table is rejected for the same reason; rows for items this kit version
# does not know (an older CSV, a renamed setting) are warned about and
# ignored, since unlisted items are excluded anyway.
function Import-BaselineSelection {
param([string]$Path)
$rows = @(Import-Csv $Path)
if ($rows.Count -eq 0) {
Write-Error "Baseline file has no rows: $Path"
exit 1
}
$columns = @($rows[0].PSObject.Properties.Name)
$missing = @('ItemType', 'Id', 'Selected' | Where-Object { $columns -notcontains $_ })
if ($missing.Count -gt 0) {
Write-Error "Not a selection CSV (missing column(s): $($missing -join ', ')): $Path (build one with New-LoggingBaseline.ps1 or use a preset)"
exit 1
}
$map = @{}
$n = 0
foreach ($row in $rows) {
$n++
if ([string]::IsNullOrWhiteSpace($row.ItemType) -or [string]::IsNullOrWhiteSpace($row.Id)) {
Write-Error "Baseline file row $n has an empty ItemType or Id: $Path"
exit 1
}
$key = ("$($row.ItemType)|$($row.Id)").ToUpper()
if ($map.ContainsKey($key)) {
Write-Error "Baseline file lists $($row.ItemType) '$($row.Id)' more than once (row $n): $Path"
exit 1
}
$map[$key] = ("$($row.Selected)".Trim() -match '^(Y|YES|TRUE|1)$')
}
$known = Get-BaselineItemKeySet
$unknown = @($map.Keys | Where-Object { -not $known.ContainsKey($_) } | Sort-Object)
if ($unknown.Count -eq $map.Count) {
Write-Error "No row in the baseline file matches a setting in this kit (first: $($unknown[0])): $Path (build one with New-LoggingBaseline.ps1 or use a preset)"
exit 1
}
foreach ($u in $unknown) { Write-Warning "Baseline file lists an item this kit does not know, ignored: $u" }
return $map
}
function Test-TierSelected {
param([string]$Tier, [bool]$IncludeHighVolume, [bool]$IncludeOptional)
if ($Tier -eq 'Core') { return $true }
if ($Tier -eq 'HighVolume') { return $IncludeHighVolume }
if ($Tier -eq 'Optional') { return $IncludeOptional }
return $false
}
# Resolves a script's -BaselineFile / -IncludeHighVolume / -IncludeOptional
# parameters into one selection object; call it once at setup and pass the
# result to Test-ItemSelected. Stops the script (exit 1) when the file does
# not exist, which is what every caller did before this helper existed.
# Map hashtable "ITEMTYPE|ID" -> bool, or $null for tier mode
# IncludeHighVolume, IncludeOptional the tier switches (tier mode only)
# Description "baseline file X.csv" or "Core tier [+ HighVolume] [+ Optional]"
# BaselineFile the path as given, or ''
function Resolve-BaselineSelection {
param([string]$BaselineFile, [bool]$IncludeHighVolume, [bool]$IncludeOptional)
$map = $null
$description = "Core tier$(if ($IncludeHighVolume) {' + HighVolume'})$(if ($IncludeOptional) {' + Optional'})"
if (-not [string]::IsNullOrEmpty($BaselineFile)) {
if (-not (Test-Path $BaselineFile)) {
Write-Error "Baseline file not found: $BaselineFile (build one with New-LoggingBaseline.ps1 or use a preset)"
exit 1
}
$map = Import-BaselineSelection -Path $BaselineFile
$description = "baseline file $(Split-Path $BaselineFile -Leaf)"
}
return @{
Map = $map
IncludeHighVolume = $IncludeHighVolume
IncludeOptional = $IncludeOptional
Description = $description
BaselineFile = "$BaselineFile"
}
}
# The one predicate: is this item on under this selection?
function Test-ItemSelected {
param([hashtable]$Selection, [string]$ItemType, [string]$Id, [string]$Tier)
if ($null -ne $Selection.Map) {
$key = ("$ItemType|$Id").ToUpper()
return ($Selection.Map.ContainsKey($key) -and $Selection.Map[$key])
}
return (Test-TierSelected -Tier $Tier -IncludeHighVolume $Selection.IncludeHighVolume -IncludeOptional $Selection.IncludeOptional)
}