Skip to content

P03-T05 - Workstation Hardening GPO

Objective

Create a separate workstation-hardening GPO, keep the day-zero baseline small, and prove the effective settings and service connectivity from a real domain workstation.

Validated on Saturday, July 18, 2026 against HQ-DC01 and HQ-CL01.

Inputs

Key Validated value Configurable parameter
GPO GPO-Workstations-Hardening GpoName
Link target OU=Workstations,OU=GNTECH,DC=corp,DC=gntech,DC=me WorkstationsOuDn
Transcript path C:\ProgramData\PSLogs TranscriptionPath

Target State

Control Required value
LLMNR Disabled (EnableMulticast=0)
PowerShell module logging Enabled
PowerShell transcription Enabled with invocation header
SmartScreen Enabled / Warn
Defender Antivirus, real-time protection, and IOAV remain enabled

The following remain outside this first hardening layer: ASR, BitLocker, LAPS, NTLM reduction, firewall redesign, and local administrator governance.

Prechecks

  • Complete P03-T01 through P03-T04.
  • Confirm GPO-Workstations-Baseline applies successfully.
  • Keep at least one real joined workstation available for effective-policy testing.
  • Run the script directly on HQ-DC01 from elevated PowerShell.

Current State Verification

Import-Module GroupPolicy
Get-GPO -Name 'GPO-Workstations-Hardening' -ErrorAction SilentlyContinue
Get-GPInheritance -Target 'OU=Workstations,OU=GNTECH,DC=corp,DC=gntech,DC=me'

Execution

Show full workstation-hardening GPO script
[CmdletBinding()]
param(
    [string]$WorkstationsOuDn = 'OU=Workstations,OU=GNTECH,DC=corp,DC=gntech,DC=me',
    [string]$GpoName = 'GPO-Workstations-Hardening',
    [string]$TranscriptionPath = 'C:\ProgramData\PSLogs',
    [switch]$ValidateOnly
)

$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'

function Assert-Elevated {
    $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($identity)
    if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
        throw 'Run this script from an elevated PowerShell session.'
    }
}

function Ensure-GpoLink {
    $links = @((Get-GPInheritance -Target $WorkstationsOuDn).GpoLinks | Where-Object DisplayName -eq $GpoName)
    if ($links.Count -eq 0) {
        New-GPLink -Name $GpoName -Target $WorkstationsOuDn -LinkEnabled Yes | Out-Null
        return 'Created'
    }
    if (-not $links[0].Enabled) {
        Set-GPLink -Name $GpoName -Target $WorkstationsOuDn -LinkEnabled Yes | Out-Null
        return 'Enabled'
    }
    return 'Compliant'
}

Assert-Elevated
Import-Module ActiveDirectory
Import-Module GroupPolicy
Get-ADOrganizationalUnit -Identity $WorkstationsOuDn -ErrorAction Stop | Out-Null

$settings = @(
    @{ Key='HKLM\Software\Policies\Microsoft\Windows NT\DNSClient'; Name='EnableMulticast'; Type='DWord'; Value=0 },
    @{ Key='HKLM\Software\Policies\Microsoft\Windows\PowerShell\ModuleLogging'; Name='EnableModuleLogging'; Type='DWord'; Value=1 },
    @{ Key='HKLM\Software\Policies\Microsoft\Windows\PowerShell\Transcription'; Name='EnableTranscripting'; Type='DWord'; Value=1 },
    @{ Key='HKLM\Software\Policies\Microsoft\Windows\PowerShell\Transcription'; Name='EnableInvocationHeader'; Type='DWord'; Value=1 },
    @{ Key='HKLM\Software\Policies\Microsoft\Windows\PowerShell\Transcription'; Name='OutputDirectory'; Type='String'; Value=$TranscriptionPath },
    @{ Key='HKLM\Software\Policies\Microsoft\Windows\System'; Name='EnableSmartScreen'; Type='DWord'; Value=1 },
    @{ Key='HKLM\Software\Policies\Microsoft\Windows\System'; Name='ShellSmartScreenLevel'; Type='String'; Value='Warn' },
    @{ Key='HKLM\Software\Policies\Microsoft\Windows Defender'; Name='DisableAntiSpyware'; Type='DWord'; Value=0 },
    @{ Key='HKLM\Software\Policies\Microsoft\Windows Defender\Real-Time Protection'; Name='DisableRealtimeMonitoring'; Type='DWord'; Value=0 },
    @{ Key='HKLM\Software\Policies\Microsoft\Windows Defender\Spynet'; Name='SpynetReporting'; Type='DWord'; Value=2 },
    @{ Key='HKLM\Software\Policies\Microsoft\Windows Defender\Spynet'; Name='SubmitSamplesConsent'; Type='DWord'; Value=1 }
)

$gpo = Get-GPO -Name $GpoName -ErrorAction SilentlyContinue
if (-not $gpo -and $ValidateOnly) {
    Write-Output "GPO: Missing / Name=$GpoName"
    Write-Output 'Validation mode only. No GPO changes were applied.'
    return
}
if (-not $gpo) {
    $gpo = New-GPO -Name $GpoName -Comment 'Validated workstation hardening controls'
}

if ($ValidateOnly) {
    $link = @((Get-GPInheritance -Target $WorkstationsOuDn).GpoLinks | Where-Object DisplayName -eq $GpoName)
    Write-Output "GPO: Present / Id=$($gpo.Id)"
    Write-Output "Link: $(if ($link.Count -gt 0 -and $link[0].Enabled) { 'Enabled' } else { 'MissingOrDisabled' })"
    foreach ($setting in $settings) {
        $current = Get-GPRegistryValue -Name $GpoName -Key $setting.Key -ValueName $setting.Name -ErrorAction SilentlyContinue
        [pscustomobject]@{
            Key = $setting.Key
            ValueName = $setting.Name
            Expected = $setting.Value
            Current = if ($current) { $current.Value } else { $null }
            Compliant = [bool]($current -and $current.Value -eq $setting.Value)
        }
    }
    Write-Output 'Validation mode only. No GPO changes were applied.'
    return
}

$linkAction = Ensure-GpoLink
foreach ($setting in $settings) {
    Set-GPRegistryValue -Name $GpoName -Key $setting.Key -ValueName $setting.Name -Type $setting.Type -Value $setting.Value | Out-Null
}

Write-Output "GPO: $GpoName"
Write-Output "Link: $linkAction"
Write-Output "SettingsApplied: $($settings.Count)"
Write-Output 'Workstation hardening GPO completed successfully.'

Preview without changing AD:

.\Configure-HQ-WorkstationHardeningGPO.ps1 -ValidateOnly

Apply or reconcile the GPO and its link:

.\Configure-HQ-WorkstationHardeningGPO.ps1

The normal run is idempotent. The validated re-run reported an existing compliant link and reapplied all 11 desired registry settings.

Validation

On the workstation:

gpupdate /force
gpresult /scope computer /r

Get-ItemProperty 'HKLM:\Software\Policies\Microsoft\Windows NT\DNSClient'
Get-ItemProperty 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ModuleLogging'
Get-ItemProperty 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell\Transcription'
Get-ItemProperty 'HKLM:\Software\Policies\Microsoft\Windows\System'
Get-MpComputerStatus | Select-Object AMRunningMode,AntivirusEnabled,RealTimeProtectionEnabled,IoavProtectionEnabled

Test-NetConnection hq-dc01.corp.gntech.me -Port 389
Test-NetConnection hq-fs01.corp.gntech.me -Port 445
Test-NetConnection github.com -Port 443

Validated result on HQ-CL01:

  • both GPO-Workstations-Baseline and GPO-Workstations-Hardening applied
  • LLMNR 0, module logging 1, transcription 1, SmartScreen 1
  • Defender Normal, antivirus enabled, real-time protection enabled, IOAV enabled
  • LDAP 389, SMB 445, and external HTTPS 443 succeeded
  • domain membership, site discovery, DNS, SMB, and Internet access showed no regression

Evidence

  • server-side script -ValidateOnly showing all 11 settings compliant
  • gpresult /scope computer /r from HQ-CL01
  • effective registry and Defender status
  • successful LDAP, SMB, and HTTPS tests

Rollback

  • Disable or remove the link before deleting the GPO.
  • Prefer reverting one problematic registry setting and retesting over deleting the entire layer.
  • Do not merge emergency exceptions into GPO-Workstations-Baseline.