mirror of
https://github.com/NexusOne23/noid-privacy.git
synced 2026-02-07 12:11:53 +01:00
240 lines
9.6 KiB
PowerShell
240 lines
9.6 KiB
PowerShell
|
|
<#
|
||
|
|
.SYNOPSIS
|
||
|
|
Apply security template settings from parsed Security Baseline JSON
|
||
|
|
|
||
|
|
.DESCRIPTION
|
||
|
|
Converts JSON security template to INF format and applies via secedit.exe.
|
||
|
|
Handles:
|
||
|
|
- Password Policies
|
||
|
|
- Account Policies
|
||
|
|
- User Rights Assignments
|
||
|
|
- Security Options
|
||
|
|
- Event Log Settings
|
||
|
|
- Registry Values (security-related)
|
||
|
|
|
||
|
|
.PARAMETER SecurityTemplatePath
|
||
|
|
Path to SecurityTemplates.json
|
||
|
|
|
||
|
|
.PARAMETER DryRun
|
||
|
|
Preview changes without applying
|
||
|
|
|
||
|
|
.OUTPUTS
|
||
|
|
PSCustomObject with success status and errors
|
||
|
|
|
||
|
|
.NOTES
|
||
|
|
Requires Administrator privileges
|
||
|
|
Uses secedit.exe (built into Windows since Windows 2000)
|
||
|
|
#>
|
||
|
|
|
||
|
|
function Set-SecurityTemplate {
|
||
|
|
[CmdletBinding()]
|
||
|
|
param(
|
||
|
|
[Parameter(Mandatory = $true)]
|
||
|
|
[string]$SecurityTemplatePath,
|
||
|
|
|
||
|
|
[Parameter(Mandatory = $false)]
|
||
|
|
[switch]$DryRun
|
||
|
|
)
|
||
|
|
|
||
|
|
$result = [PSCustomObject]@{
|
||
|
|
Success = $false
|
||
|
|
SectionsApplied = 0
|
||
|
|
SettingsApplied = 0
|
||
|
|
Errors = @()
|
||
|
|
}
|
||
|
|
|
||
|
|
if (-not (Test-Path $SecurityTemplatePath)) {
|
||
|
|
$result.Errors += "Security template file not found: $SecurityTemplatePath"
|
||
|
|
return $result
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
$templates = Get-Content -Path $SecurityTemplatePath -Raw | ConvertFrom-Json
|
||
|
|
|
||
|
|
# Merge all GPO templates into one master INF
|
||
|
|
$infContent = @()
|
||
|
|
$infContent += "[Unicode]"
|
||
|
|
$infContent += "Unicode=yes"
|
||
|
|
$infContent += ""
|
||
|
|
$infContent += "[Version]"
|
||
|
|
$infContent += "signature=`"`$CHICAGO$`""
|
||
|
|
$infContent += "Revision=1"
|
||
|
|
$infContent += ""
|
||
|
|
|
||
|
|
# Track which sections we have
|
||
|
|
$sectionsProcessed = @{}
|
||
|
|
|
||
|
|
# Process each GPO's security template
|
||
|
|
foreach ($gpoName in ($templates.PSObject.Properties.Name)) {
|
||
|
|
$gpoTemplate = $templates.$gpoName
|
||
|
|
|
||
|
|
foreach ($sectionName in ($gpoTemplate.PSObject.Properties.Name)) {
|
||
|
|
# Skip metadata sections
|
||
|
|
if ($sectionName -in @("Unicode", "Version")) {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
$section = $gpoTemplate.$sectionName
|
||
|
|
|
||
|
|
if (-not $sectionsProcessed.ContainsKey($sectionName)) {
|
||
|
|
$sectionsProcessed[$sectionName] = @()
|
||
|
|
}
|
||
|
|
|
||
|
|
# Add settings from this section
|
||
|
|
foreach ($key in ($section.PSObject.Properties.Name)) {
|
||
|
|
$value = $section.$key
|
||
|
|
|
||
|
|
# Service General Setting requires special format per MS-GPSB 2.2.8
|
||
|
|
# Format: "ServiceName",StartupMode,""
|
||
|
|
# StartupMode: 2=Automatic, 3=Manual, 4=Disabled
|
||
|
|
if ($sectionName -eq 'Service General Setting') {
|
||
|
|
# Parse StartupType from value (e.g., "StartupType=Disabled")
|
||
|
|
$startupMode = 4 # Default: Disabled
|
||
|
|
if ($value -match 'StartupType=(\w+)') {
|
||
|
|
$startupType = $matches[1]
|
||
|
|
switch ($startupType) {
|
||
|
|
'Disabled' { $startupMode = 4 }
|
||
|
|
'Manual' { $startupMode = 3 }
|
||
|
|
'Automatic' { $startupMode = 2 }
|
||
|
|
default { $startupMode = 4 }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Microsoft INF format: "ServiceName",Mode,""
|
||
|
|
$settingLine = "`"$key`",$startupMode,`"`""
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# Standard format for other sections
|
||
|
|
# Values are used directly from JSON as they are already in correct MS INF format
|
||
|
|
# Examples:
|
||
|
|
# ScRemoveOption: 1,"1" (REG_SZ with string "1")
|
||
|
|
# RestrictRemoteSAM: 1,"O:BAG:BAD:(A;;RC;;;BA)" (REG_SZ with SDDL)
|
||
|
|
# EnableInstallerDetection: 4,1 (REG_DWORD with value 1)
|
||
|
|
|
||
|
|
# Format: Key = Value
|
||
|
|
$settingLine = "$key = $value"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Avoid duplicates
|
||
|
|
if ($sectionsProcessed[$sectionName] -notcontains $settingLine) {
|
||
|
|
$sectionsProcessed[$sectionName] += $settingLine
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Write all sections in Microsoft INF required order
|
||
|
|
# Order matters! secedit expects sections in specific sequence
|
||
|
|
$sectionOrder = @(
|
||
|
|
'System Access',
|
||
|
|
'Event Audit',
|
||
|
|
'Registry Values',
|
||
|
|
'Privilege Rights',
|
||
|
|
'Service General Setting'
|
||
|
|
)
|
||
|
|
|
||
|
|
foreach ($sectionName in $sectionOrder) {
|
||
|
|
if ($sectionsProcessed.ContainsKey($sectionName)) {
|
||
|
|
$infContent += "[$sectionName]"
|
||
|
|
$infContent += $sectionsProcessed[$sectionName]
|
||
|
|
$infContent += ""
|
||
|
|
|
||
|
|
$result.SectionsApplied++
|
||
|
|
$result.SettingsApplied += $sectionsProcessed[$sectionName].Count
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Write any remaining sections not in standard order (safety net)
|
||
|
|
foreach ($sectionName in $sectionsProcessed.Keys) {
|
||
|
|
if ($sectionName -notin $sectionOrder) {
|
||
|
|
$infContent += "[$sectionName]"
|
||
|
|
$infContent += $sectionsProcessed[$sectionName]
|
||
|
|
$infContent += ""
|
||
|
|
|
||
|
|
$result.SectionsApplied++
|
||
|
|
$result.SettingsApplied += $sectionsProcessed[$sectionName].Count
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Write-Log -Level DEBUG -Message "Generated security template: $($result.SectionsApplied) sections, $($result.SettingsApplied) settings" -Module "SecurityBaseline"
|
||
|
|
|
||
|
|
if ($DryRun) {
|
||
|
|
Write-Log -Level DEBUG -Message "[DRYRUN] Security template content:" -Module "SecurityBaseline"
|
||
|
|
$infContent | ForEach-Object { Write-Log -Level DEBUG -Message " $_" -Module "SecurityBaseline" }
|
||
|
|
$result.Success = $true
|
||
|
|
return $result
|
||
|
|
}
|
||
|
|
|
||
|
|
# Initialize temp file paths
|
||
|
|
$tempInf = $null
|
||
|
|
$dbFile = $null
|
||
|
|
$logFile = $null
|
||
|
|
|
||
|
|
try {
|
||
|
|
# Save to temporary INF file
|
||
|
|
$tempInf = Join-Path $env:TEMP "SecurityBaseline_$(Get-Date -Format 'yyyyMMddHHmmss').inf"
|
||
|
|
$infContent | Out-File -FilePath $tempInf -Encoding unicode -Force
|
||
|
|
|
||
|
|
Write-Log -Level DEBUG -Message "Applying security template via secedit.exe..." -Module "SecurityBaseline"
|
||
|
|
|
||
|
|
# Apply via secedit
|
||
|
|
$dbFile = Join-Path $env:TEMP "secedit_$(Get-Date -Format 'yyyyMMddHHmmss').sdb"
|
||
|
|
$logFile = Join-Path $env:TEMP "secedit_$(Get-Date -Format 'yyyyMMddHHmmss').log"
|
||
|
|
|
||
|
|
$seceditArgs = @(
|
||
|
|
"/configure",
|
||
|
|
"/db", "`"$dbFile`"",
|
||
|
|
"/cfg", "`"$tempInf`"",
|
||
|
|
"/log", "`"$logFile`"",
|
||
|
|
"/quiet"
|
||
|
|
)
|
||
|
|
|
||
|
|
$process = Start-Process -FilePath "secedit.exe" `
|
||
|
|
-ArgumentList $seceditArgs `
|
||
|
|
-Wait `
|
||
|
|
-NoNewWindow `
|
||
|
|
-PassThru
|
||
|
|
|
||
|
|
if ($process.ExitCode -eq 0) {
|
||
|
|
$result.Success = $true
|
||
|
|
Write-Log -Level DEBUG -Message "Security template applied successfully" -Module "SecurityBaseline"
|
||
|
|
}
|
||
|
|
if ($process.ExitCode -ne 0) {
|
||
|
|
$stderr = Get-Content $logFile -Raw -ErrorAction SilentlyContinue
|
||
|
|
$result.Errors += "secedit failed with exit code $($process.ExitCode): $stderr"
|
||
|
|
Write-Log -Level DEBUG -Message "secedit failed: $stderr" -Module "SecurityBaseline"
|
||
|
|
|
||
|
|
# On error: Save INF and log to Desktop for debugging
|
||
|
|
$debugInf = Join-Path ([Environment]::GetFolderPath("Desktop")) "SecurityBaseline_ERROR.inf"
|
||
|
|
$debugLog = Join-Path ([Environment]::GetFolderPath("Desktop")) "secedit_ERROR.log"
|
||
|
|
Copy-Item $tempInf $debugInf -Force -ErrorAction SilentlyContinue
|
||
|
|
Copy-Item $logFile $debugLog -Force -ErrorAction SilentlyContinue
|
||
|
|
Write-Log -Level DEBUG -Message "Error files saved to Desktop for debugging" -Module "SecurityBaseline"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch {
|
||
|
|
$result.Errors += "Security template application failed: $($_.Exception.Message)"
|
||
|
|
Write-Log -Level DEBUG -Message "Security template application failed: $_" -Module "SecurityBaseline"
|
||
|
|
}
|
||
|
|
finally {
|
||
|
|
# ALWAYS cleanup temp files (even on error)
|
||
|
|
if ($tempInf -and (Test-Path $tempInf)) {
|
||
|
|
Remove-Item $tempInf -Force -ErrorAction SilentlyContinue
|
||
|
|
}
|
||
|
|
if ($dbFile -and (Test-Path $dbFile)) {
|
||
|
|
Remove-Item $dbFile -Force -ErrorAction SilentlyContinue
|
||
|
|
}
|
||
|
|
if ($logFile -and (Test-Path $logFile)) {
|
||
|
|
Remove-Item $logFile -Force -ErrorAction SilentlyContinue
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch {
|
||
|
|
# Outer catch for JSON parsing or INF generation errors
|
||
|
|
$result.Errors += "Failed to process security template: $($_.Exception.Message)"
|
||
|
|
Write-Log -Level DEBUG -Message "Security template processing error: $_" -Module "SecurityBaseline"
|
||
|
|
}
|
||
|
|
|
||
|
|
return $result
|
||
|
|
}
|