Private/ConvertFrom-WinClatDump.ps1

Function ConvertFrom-WinClatDump {

    <#

    .SYNOPSIS
        Strictly parses 'netsh interface clat dump' output or an export file into WinCLAT settings.

    .DESCRIPTION
        Accepts only the structure that 'netsh interface clat dump' writes (DESIGN.md, sections 7.5 and 7.7): blank lines, comment lines beginning with '#', one 'pushd interface clat', exactly one 'set global' line, and one 'popd' after it. The 'set global' line may only contain permit, pref64fromdns, pref64fromra, and autoenable, each once, with the value enabled or disabled.

        Returns an object with Settings, a hashtable of Permit, Pref64FromDns, Pref64FromRA, and AutoEnable to [bool] for the settings present, and Problem, $null when the input is valid and otherwise the reason it was refused. Settings is empty when Problem is set.

        The content is never run. Import-WinClatConfiguration applies the parsed values through the same code as Set-WinClatConfiguration, so a file that contains any other netsh command is refused instead of executed.

        This function must also be available in the remote session. See DESIGN.md, section 4.3.

    .PARAMETER InputObject
        The lines to parse.

    .INPUTS
        None. This function does not accept pipeline input.

    .OUTPUTS
        System.Management.Automation.PSCustomObject with Settings and Problem properties.

    .EXAMPLE
        ConvertFrom-WinClatDump -InputObject (Get-Content -Path .\WinCLAT-CLIENT4-20261023-014419.txt)

        Returns Settings @{ Permit = $false; Pref64FromDns = $true; Pref64FromRA = $true; AutoEnable = $true } and Problem $null.

    .LINK
        https://learn.microsoft.com/windows-server/administration/windows-commands/netsh

    .NOTES
        Version: 1.0.0
        Creation Date: October 28, 2026
        Last Updated: October 28, 2026
        Author: Richard Hicks
        Organization: Richard M. Hicks Consulting, Inc.
        Contact: rich@richardhicks.com
        Website: https://www.richardhicks.com/

    #>


    [CmdletBinding()]
    [OutputType([PSCustomObject])]

    Param (

        [Parameter(Mandatory)]
        [AllowEmptyCollection()]
        [AllowEmptyString()]
        [AllowNull()]
        [string[]]$InputObject

    )

    $Names = @{ 'permit' = 'Permit'; 'pref64fromdns' = 'Pref64FromDns'; 'pref64fromra' = 'Pref64FromRA'; 'autoenable' = 'AutoEnable' }
    $Settings = @{}
    $Problem = $null
    $State = 'Start'
    $Number = 0

    ForEach ($Line in @($InputObject)) {

        $Number++
        $Text = "$Line".Trim()

        If ($Text -eq '' -or $Text.StartsWith('#')) {

            Continue

        }

        If ($Text -match '^pushd\s+interface\s+clat$' -and $State -eq 'Start') {

            $State = 'Pushed'
            Continue

        }

        If ($Text -match '^set\s+global\s+(.+)$' -and $State -eq 'Pushed') {

            ForEach ($Token in @($Matches[1] -split '\s+')) {

                If ($Token -notmatch '^(\w+)=(enabled|disabled)$' -or -not $Names.ContainsKey($Matches[1].ToLowerInvariant())) {

                    $Problem = "line $Number sets '$Token', which is not a WinCLAT global setting with the value enabled or disabled."
                    Break

                }

                $Name = $Names[$Matches[1].ToLowerInvariant()]

                If ($Settings.ContainsKey($Name)) {

                    $Problem = "line $Number sets $($Name.ToLowerInvariant()) more than once."
                    Break

                }

                $Settings[$Name] = $Matches[2].ToLowerInvariant() -eq 'enabled'

            }

            If ($Problem) {

                Break

            }

            $State = 'Set'
            Continue

        }

        If ($Text -match '^popd$' -and $State -eq 'Set') {

            $State = 'Popped'
            Continue

        }

        $Problem = "line $Number is not part of a WinCLAT configuration script: '$Text'."
        Break

    }

    If (-not $Problem -and $State -ne 'Popped') {

        $Problem = "it does not contain 'pushd interface clat', one 'set global' line, and 'popd', in that order."

    }

    If ($Problem) {

        $Settings = @{}

    }

    [PSCustomObject]@{

        Settings = $Settings
        Problem  = $Problem

    }

}