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:
| Capability | FullLanguage Mode | Constrained Language Mode (CLM) |
|---|---|---|
| Cmdlets & Core Functions | Allowed | Allowed (Safe built-in cmdlets continue to operate) |
| Arbitrary .NET Methods | Allowed ([System.IO.File]::ReadAllText()) | BLOCKED: Cannot invoke arbitrary .NET types |
| Win32 API Declarations | Allowed (Add-Type -TypeDefinition ...) | BLOCKED: Dynamic compiler emission is strictly prohibited |
| COM Object Creation | Allowed (New-Object -ComObject WScript.Shell) | BLOCKED: Unsafe COM interfaces are denied |
| Dot-Sourcing Untrusted Scripts | Allowed | BLOCKED: 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).