PowerShell is an indispensable management engine for Windows administrators, but its unrestricted access to underlying .NET runtime APIs, native Windows Win32 functions, and unmanaged memory makes it a frequent target for attackers deploying fileless malware and living-off-the-land techniques. To mitigate this threat surface, Microsoft introduced Constrained Language Mode (CLM). When active, Constrained Language Mode restricts PowerShell to basic commandlets and script blocks, completely blocking arbitrary .NET reflection, COM objects, and direct memory manipulations.
Understanding PowerShell Language Modes
PowerShell operates under four distinct language modes defined in the runtime engine:
- FullLanguage: The default mode. Unrestricted access to all PowerShell cmdlets, .NET types, reflection APIs, C# inline code (via
Add-Type), and Win32 interop. - ConstrainedLanguage: Restricts the execution environment to certified safe core cmdlets and primitive data types (such as strings, integers, and booleans). Arbitrary .NET object creation (
[System.Net.WebClient],[System.IO.File], etc.) is blocked. - RestrictedLanguage: Allows variable evaluation and basic string processing, but disallows command executions and loops (primarily used in configuration files).
- NoLanguage: Completely disables script execution and parsing.
Auditing Current Language Mode via PowerShell
To determine the active language mode of your current PowerShell session, check the execution context property:
$ExecutionContext.SessionState.LanguageMode
In standard configurations, this command returns FullLanguage.
Temporarily Testing Constrained Language Mode
You can simulate Constrained Language Mode within an active console session to test whether administrative scripts continue to execute properly:
# Downgrade current session to ConstrainedLanguage
$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"
# Verify updated state
$ExecutionContext.SessionState.LanguageMode
# Attempt to instantiate an unapproved .NET class (will fail)
[System.Math]::Sqrt(144)
New-Object -TypeName System.Net.WebClient
PowerShell immediately rejects the class instantiation, emitting an error: “Cannot create type. Only core types are supported in this language mode.”
Permanently Enforcing Constrained Language Mode via AppLocker
While setting an environment variable (__PSLockdownPolicy) is easily bypassed by malware, Microsoft designed Constrained Language Mode to integrate automatically with Windows AppLocker or Windows Defender Application Control (WDAC). When an AppLocker script enforcement rule is active, PowerShell automatically runs all interactive consoles and unsigned scripts in Constrained Language Mode:
- Open the Local Group Policy Editor (
gpedit.msc) or launchsecpol.msc(Local Security Policy). - Navigate to Security Settings > Application Control Policies > AppLocker.
- Click Configure rule enforcement.
- Check the box for Script Rules and set the mode to Enforce rules.
- In the left tree, right-click Script Rules and select Create Default Rules. This ensures built-in Windows scripts in
C:\WindowsandC:\Program Filescan continue running. - Open an elevated PowerShell console and enable the Application Identity service (required by AppLocker):
Set-Service -Name "AppIDSvc" -StartupType Automatic Start-Service -Name "AppIDSvc" - Refresh Group Policy:
gpupdate /force
Verifying Enforced Constrained Language Mode
Launch a fresh standard PowerShell prompt (as a regular user) and query the session state:
$ExecutionContext.SessionState.LanguageMode
The console now confirms ConstrainedLanguage. Any attempt by downloaded scripts or interactive keyboard inputs to execute unapproved .NET reflection, inject shellcode, or manipulate memory is permanently neutralised by the runtime engine.
Configuring PowerShell Constrained Language Mode through AppLocker provides a resilient, enterprise-grade defense against sophisticated script-based cyber threats in Windows 11.