Public/New-EFBaseline.ps1

function New-EFBaseline {
    <#
    .SYNOPSIS
    Creates a validated starter EndpointForge checklist.
 
    .DESCRIPTION
    A checklist is a list of things you expect to be true, such as a Windows setting,
    restart state, update state, application, job, file, certificate, event, local Windows
    management namespace, local IP range, account relationship, or approved network service; scripts call it a baseline. This command
    copies a maintained starter template, validates it, and writes UTF-8 JSON plus its
    schema. It does not apply the checklist or change Windows. A PowerShell WhatIf preview
    creates no files, and existing files require Force.
 
    .PARAMETER Name
    A stable organization checklist name such as Contoso.Workstation.
 
    .PARAMETER Path
    The output JSON path. A directory path writes <Name>.json inside that directory.
 
    .PARAMETER Description
    A plain-language purpose and scope for the checklist.
 
    .PARAMETER Version
    The semantic version for the new checklist.
 
    .PARAMETER Template
    Starter includes firewall, UAC, and Windows Update controls;
    EnterpriseRecommended includes every built-in setting; AuditOnly includes only
    BitLocker, Secure Boot, and TPM checks. EverydayChecks creates edit-before-use,
    report-only examples for computer health, updates, applications, jobs, files, logs,
    certificates, local management information, local IP ranges, access, events, and approved network connections.
 
    .PARAMETER Force
    Replaces an existing checklist file.
 
    .EXAMPLE
    New-EFBaseline -Name Contoso.Workstation -Template Starter -Path .\baselines
 
    .EXAMPLE
    New-EFBaseline -Name Contoso.Audit -Template AuditOnly -Path .\Contoso.Audit.json -WhatIf
 
    .EXAMPLE
    New-EFBaseline -Name Contoso.Operations -Template EverydayChecks -Path .\checklists
 
    Creates editable everyday check examples. Replace every sample target before use.
 
    .OUTPUTS
    EndpointForge.BaselineCreationResult
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    [OutputType('EndpointForge.BaselineCreationResult')]
    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', 'EverydayChecks')]
        [string]$Template = 'Starter',

        [switch]$Force
    )

    if ([string]::IsNullOrWhiteSpace($Description)) {
        $Description = "$Name EndpointForge checklist. Review it before use."
    }

    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.")
    }

    if ($Template -eq 'EverydayChecks') {
        $examplePath = Join-Path $script:ModuleRoot 'examples\EverydayChecks.json'
        $sourceBaseline = Get-EFBaseline -Path $examplePath
        $controlIds = @($sourceBaseline.Controls.Id)
    }
    else {
        $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'
        FormatVersion = '1.0'
        Name        = $Name
        Version     = $Version
        Description = $Description
        Controls    = @($clonedControls)
    }
    Assert-EFBaseline -Baseline $baseline

    if (-not $PSCmdlet.ShouldProcess($targetPath, "Create $Template EndpointForge checklist")) {
        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    = @(
            $(if ($Template -eq 'EverydayChecks') {
                "Replace every sample application, job, path, text, event, certificate, management namespace, IP range, optional network connection name, account, host, port, and web address before use: $targetPath"
            } else {
                "Edit and review: $targetPath"
            })
            "Validate: Test-EFBaseline -Path '$targetPath'"
            "Plan: Get-EFRemediationPlan -Baseline '$targetPath'"
        )
    }
}