Skip to main content

Locking Down the Shell: AppLocker, WDAC & Surviving Constrained Language Mode

The "Why" Before the "How": The Weaponization of FullLanguage​

PowerShell is an administrator's most powerful tool—and an adversary's favorite post-exploitation execution framework. In default FullLanguage mode, PowerShell allows arbitrary .NET reflection, dynamic code generation, in-memory script execution, and unmanaged Win32 API memory allocation via Add-Type. Tools like PowerView, BloodHound, and Mimikatz rely on these advanced capabilities to inspect and compromise systems.

When organizations deploy AppLocker or Windows Defender Application Control (WDAC) in script enforcement mode, PowerShell automatically forces untrusted sessions into Constrained Language Mode (CLM):

AppLocker / WDAC Enforcement ➔ $ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"

Under Constrained Language Mode, arbitrary .NET types, Win32 API declarations, and COM object instantiations are immediately blocked. However, poorly engineered administrative automation often blows up in production under CLM.

To build true enterprise automation, our scripts must be designed, signed, and tested to execute cleanly under Constrained Language Mode.


1. What Breaks Under Constrained Language Mode?​

When CLM is engaged, PowerShell restricts the script execution engine to safe, core cmdlets and approved primitive data types:

CapabilityFullLanguage ModeConstrained Language Mode (CLM)
Cmdlets & Core FunctionsAllowedAllowed (Safe built-in cmdlets continue to operate)
Arbitrary .NET MethodsAllowed ([System.IO.File]::ReadAllText())BLOCKED: Cannot invoke arbitrary .NET types
Win32 API DeclarationsAllowed (Add-Type -TypeDefinition ...)BLOCKED: Dynamic compiler emission is strictly prohibited
COM Object CreationAllowed (New-Object -ComObject WScript.Shell)BLOCKED: Unsafe COM interfaces are denied
Dot-Sourcing Untrusted ScriptsAllowedBLOCKED: Scripts outside protected system paths run restricted

Demonstrating the CLM Block​

If a script attempts to invoke unapproved .NET reflection under CLM:

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

Resulting Error:

Cannot invoke method. Method invocation is only supported on core types in this language mode.
At line:1 char:1
+ [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodInvocationNotSupportedInConstrainedLanguage

2. Engineering Scripts That Survive CLM​

To ensure enterprise administrative tooling executes reliably across hardened endpoints, follow these core engineering principles:

1. Rely on Native Approved Cmdlets​

Instead of invoking [System.IO.File]::WriteAllText($Path, $Data), utilize standard pipeline cmdlets like Set-Content and Out-File. Native Microsoft cmdlets run in the trusted engine context and bypass arbitrary type restrictions.

2. Sign Enterprise Modules with an Internal PKI Certificate​

Scripts and modules placed in trusted system directories (C:\Program Files\WindowsPowerShell\Modules) or digitally signed by an enterprise Code Signing Certificate from our Active Directory Certificate Services (AD CS) root CA are trusted by AppLocker and execute in FullLanguage mode for authorized administrators.


3. Pester Testing: Proving Script Resilience Under CLM​

We integrate Pester unit tests into our build pipeline to verify that diagnostic scripts run cleanly without relying on forbidden .NET reflection:

Describe "Enterprise Diagnostics CLM Validation" {
Context "When executing under Constrained Language Mode" {
It "Executes successfully without throwing language restrictions" {
# Simulate Constrained Language Mode in a sub-process
$Result = powershell.exe -ExecutionPolicy Bypass -Command {
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
# Import module and invoke diagnostic command
Import-Module .\DarioCruz.SysAdmin.psd1
Get-HostNetworkDiagnostic -Target "10.1.1.1"
}
$LASTEXITCODE | Should -Be 0
}
}
}

4. Verification: Trust, but Verify​

Verify Current PowerShell Language Mode​

To determine whether an endpoint is enforcing CLM:

$ExecutionContext.SessionState.LanguageMode

Expected Output on Hardened Endpoint:

ConstrainedLanguage

(If the output returns FullLanguage, AppLocker or WDAC script enforcement is not active on the workstation).