Public/Uninstall-ADCSGoat.ps1

function Uninstall-ADCSGoat {
    <#
    .SYNOPSIS
        Removes an ADCSGoat deployment, restoring pre-change state ACL-last.

    .DESCRIPTION
        Drives the teardown contract settled in ADCSGoat issue #30 from the
        state file written at deploy time. Restores, in order: clone cns are
        removed from the selected CA's certificateTemplates (and the attribute
        returned to its pre-change member set), clone template objects and
        their companion OID objects are deleted, and finally the Enrollment
        Services security descriptor is restored byte-for-byte — reversing the
        ESC5 CA-object grant last. Any failure before the ACL step aborts and
        leaves the grant in place, reporting what remains.

        Performs no legacy detection; it restores only what the state file
        records.

    .PARAMETER StatePath
        Where the deploy state file lives. Defaults to ADCSGoat.State.xml next
        to the module root.

    .PARAMETER Server
        The domain controller to write to. Defaults to the logon server.

    .PARAMETER SkipLowPrivUser
        Leaves the low privileged lab user and its PAW RDP access in place.

    .PARAMETER LowPrivUserName
        The low privileged lab user to remove. Defaults to 'user'. It is deleted
        only when its description carries the ADCSGoat marker.

    .PARAMETER PawComputerName
        The workstation whose Remote Desktop Users group the lab user is removed
        from. Defaults to 'ADCSGoat-PAW'.

    .EXAMPLE
        Uninstall-ADCSGoat

        Removes the lab user, then tears down the deployment recorded in the
        default state file.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'State-driven restore; destructive steps ordered ACL-last so a failure never strands a partial access state.')]
    [CmdletBinding()]
    param (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$StatePath,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Server,

        [Parameter()]
        [switch]$SkipLowPrivUser,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$LowPrivUserName = 'user',

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$PawComputerName = 'ADCSGoat-PAW'
    )

    begin {
        if ([string]::IsNullOrEmpty($StatePath)) {
            $StatePath = Join-Path -Path $PSScriptRoot -ChildPath '..\ADCSGoat.State.xml'
        }
        $teardownParams = @{ StatePath = $StatePath }
        if ($PSBoundParameters.ContainsKey('Server')) { $teardownParams['Server'] = $Server }
    }

    process {
        if (-not $SkipLowPrivUser) {
            $userParams = @{}
            if ($PSBoundParameters.ContainsKey('Server')) { $userParams['Server'] = $Server }

            $serverPrefix = if ($Server) { "LDAP://$Server/" } else { 'LDAP://' }
            $rootDSE = New-Object System.DirectoryServices.DirectoryEntry("${serverPrefix}RootDSE")
            $netbiosName = Get-ADDomainNameFromNc -ConfigurationNC "$($rootDSE.configurationNamingContext)" @userParams

            # Group membership first: deleting the user first would leave an orphaned SID in the PAW group.
            try {
                Set-AGRdpUserAccess -ComputerName $PawComputerName -Member "$netbiosName\$LowPrivUserName" -Ensure Absent
            } catch {
                Write-Warning "Could not remove $netbiosName\$LowPrivUserName from Remote Desktop Users on ${PawComputerName}: $($_.Exception.Message)"
            }
            $null = Remove-AGLowPrivUser -UserName $LowPrivUserName @userParams
        }

        Invoke-AGDeployTeardown @teardownParams
    }
}