Public/New-EFBaseline.ps1
|
function New-EFBaseline { <# .SYNOPSIS Creates a validated EndpointForge baseline JSON file. .DESCRIPTION Creates a safe starting baseline from a maintained template, validates it with the same runtime contract used by compliance commands, and writes UTF-8 without a BOM. The command supports WhatIf and never overwrites unless Force is specified. .PARAMETER Name A stable organization baseline name such as Contoso.Workstation. .PARAMETER Path The output JSON path. A directory path writes <Name>.json inside that directory. .PARAMETER Description A human-readable purpose and scope for the baseline. .PARAMETER Version The semantic version for the new baseline. .PARAMETER Template Starter includes firewall, UAC, and Windows Update controls; EnterpriseRecommended includes every built-in control; AuditOnly includes only BitLocker, Secure Boot, and TPM audit controls. .PARAMETER Force Replaces an existing baseline file. .EXAMPLE New-EFBaseline -Name Contoso.Workstation -Template Starter -Path .\baselines .EXAMPLE New-EFBaseline -Name Contoso.Audit -Template AuditOnly -Path .\Contoso.Audit.json -WhatIf .OUTPUTS EndpointForge.BaselineCreationResult #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')] param( [Parameter(Mandatory)] [ValidatePattern('^[A-Za-z0-9][A-Za-z0-9._-]+$')] [string]$Name, [string]$Path, [ValidateNotNullOrEmpty()] [string]$Description, [ValidatePattern('^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$')] [string]$Version = '1.0.0', [ValidateSet('Starter', 'EnterpriseRecommended', 'AuditOnly')] [string]$Template = 'Starter', [switch]$Force ) if ([string]::IsNullOrWhiteSpace($Description)) { $Description = "$Name endpoint compliance baseline." } if ([string]::IsNullOrWhiteSpace($Path)) { $targetPath = Join-Path (Get-Location).Path "$Name.json" } else { $unresolvedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path) $treatAsDirectory = (Test-Path -LiteralPath $unresolvedPath -PathType Container) -or [string]::IsNullOrWhiteSpace([IO.Path]::GetExtension($unresolvedPath)) $targetPath = if ($treatAsDirectory) { Join-Path $unresolvedPath "$Name.json" } else { $unresolvedPath } } if ([IO.Path]::GetExtension($targetPath) -ne '.json') { throw [System.ArgumentException]::new("Baseline path '$targetPath' must use the .json extension.") } if ((Test-Path -LiteralPath $targetPath) -and -not $Force) { throw [System.IO.IOException]::new("Baseline '$targetPath' already exists. Use -Force to replace it.") } $sourceBaseline = Get-EFBaseline -Name EnterpriseRecommended $controlIds = switch ($Template) { 'Starter' { @('EF-FW-DOMAIN', 'EF-FW-PRIVATE', 'EF-FW-PUBLIC', 'EF-UAC-ENABLED', 'EF-WUA-NOT-DISABLED') } 'AuditOnly' { @('EF-BITLOCKER-OS', 'EF-SECUREBOOT', 'EF-TPM-READY') } default { @($sourceBaseline.Controls.Id) } } $selectedControls = @($sourceBaseline.Controls | Where-Object Id -in $controlIds) $clonedControls = $selectedControls | ConvertTo-Json -Depth 12 | ConvertFrom-Json $baseline = [pscustomobject][ordered]@{ '$schema' = './EndpointForge.Baseline.schema.json' Name = $Name Version = $Version Description = $Description Controls = @($clonedControls) } Assert-EFBaseline -Baseline $baseline if (-not $PSCmdlet.ShouldProcess($targetPath, "Create $Template EndpointForge baseline")) { return } $parent = Split-Path -Parent $targetPath if (-not [string]::IsNullOrWhiteSpace($parent) -and -not (Test-Path -LiteralPath $parent)) { $null = New-Item -ItemType Directory -Path $parent -Force -ErrorAction Stop } $schemaPath = Join-Path $parent 'EndpointForge.Baseline.schema.json' if (-not (Test-Path -LiteralPath $schemaPath) -or $Force) { Copy-Item -LiteralPath (Join-Path $script:ModuleRoot 'Data\Baseline.schema.json') ` -Destination $schemaPath -Force:$Force -ErrorAction Stop } $json = ConvertTo-EFSerializableValue -InputObject $baseline | ConvertTo-Json -Depth 20 [IO.File]::WriteAllText($targetPath, $json, [Text.UTF8Encoding]::new($false)) $createdBaseline = Get-EFBaseline -Path $targetPath [pscustomobject]@{ PSTypeName = 'EndpointForge.BaselineCreationResult' Name = $Name Version = $Version Template = $Template Path = $targetPath SchemaPath = $schemaPath ControlCount = @($createdBaseline.Controls).Count Baseline = $createdBaseline NextSteps = @( "Edit and review: $targetPath" "Validate: Test-EFBaseline -Path '$targetPath'" "Plan: Get-EFRemediationPlan -Baseline '$targetPath'" ) } } |