PowerShell Script Block Logging and Constrained Language Mode: The Complete Hardening Guide for Enterprise Windows

Retool's new app builder is where AI-generated code ships safely
Building apps with AI is easy. Getting them to production safely is another story.
PowerShell appears in roughly 70% of incident investigations because it is pre-installed, can be run from memory, bypasses many AV signatures, and gives attackers direct access to the Windows API. The three controls that matter most are Script Block Logging (what ran), Module Logging (which modules were loaded), and Constrained Language Mode (what can run at all). None of them require additional software. All three can be deployed via Group Policy in under an hour. The hardest part is not enabling the logs -- it is making sure your automation still works after CLM is enforced.
Enable Script Block Logging via Group Policy
Script Block Logging is controlled by a single Group Policy setting. Navigate to:
Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell
Enable: Turn on PowerShell Script Block Logging
Optionally enable: Log script block invocation start / stop events (generates Event IDs 4105/4106 -- high volume, usually omitted)
Also enable under the same node:
- Turn on Module Logging -- set the module names to
*to log all modules. This generates Event ID 4103. - Turn on PowerShell Transcription -- specify a network share as the output directory for full session transcripts. Set the directory with restricted write access (SYSTEM only) to prevent tampering.
Verify deployment:
# On a target machine, confirm the registry keys are present
Get-ItemProperty HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging
# ScriptBlockLogging should be 1
Get-ItemProperty HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging
# EnableModuleLogging should be 1
Verify logging is working:
# Generate a test event
Invoke-Expression 'Write-Host test'
# Check the event log
Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational' -MaxEvents 5 |
Where-Object Id -eq 4104 | Format-List TimeCreated, Message
Enable Constrained Language Mode with AppLocker
Constrained Language Mode is not a standalone setting. PowerShell automatically enters CLM when AppLocker or WDAC is enforced and the script is not in an allowed path.
AppLocker method (simpler, Windows Enterprise/Education required):
Create a default-deny AppLocker script rule policy that allows scripts only from trusted paths:
Allow: %WINDIR%\* (Publisher: Microsoft, Hash, or Path)
Allow: %PROGRAMFILES%\*
Deny: Everything else
Deploy via Group Policy: Computer Configuration > Windows Settings > Security Settings > Application Control Policies > AppLocker
Verify CLM is active after policy applies:
$ExecutionContext.SessionState.LanguageMode
# Should return: ConstrainedLanguage
Test that the restriction works:
# This should fail in CLM
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
# Error: This script contains constructs not permitted by the current language mode.
WDAC method (preferred, works without AppLocker license requirement):
Create a WDAC policy using the WDAC Policy Wizard or PowerShell that sets <ScriptEnforcement> to Enabled. WDAC-enforced CLM also covers PowerShell 2.0 downgrade attacks by preventing script execution on the legacy engine.
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
Handling Automation Breakage in Constrained Language Mode
CLM breaks scripts that use Add-Type, [System.Reflection.*], COM objects, or XAML. The most common legitimate casualties are:
- Custom scripts that call .NET libraries directly
- Third-party management tools that use PowerShell remoting with .NET interop
- Scheduled tasks that run unsigned scripts from user-writable paths
Resolution approach:
- Sign legitimate scripts with a code signing certificate from your internal CA:
# Sign a script
$cert = Get-ChildItem Cert:\LocalMachine\My -CodeSigningCert
Set-AuthenticodeSignature -FilePath 'C:\Scripts\backup.ps1' -Certificate $cert
-
Move automation scripts to AppLocker-trusted paths (WINDIR or PROGRAMFILES subtrees), and restrict write access to those paths to SYSTEM and Domain Admins only.
-
For third-party tools that cannot be signed: create a targeted AppLocker exception by publisher or hash for the specific binary that invokes PowerShell, rather than allowing the entire script.
-
Use JEA (Just Enough Administration) endpoints for legitimate administrative automation -- JEA sessions run with Full Language Mode for the authorized session configuration while remaining CLM for everything else.
Run-down test before production rollout:
# Audit mode first -- AppLocker can log without blocking
# Set AppLocker to Audit mode, collect logs for 2 weeks
Get-WinEvent -LogName 'Microsoft-Windows-AppLocker/MSI and Script' |
Where-Object LevelDisplayName -eq 'Warning' |
Select-Object TimeCreated, Message | Format-List
# Review blocked paths before switching to Enforce mode
Forwarding PowerShell Logs to Your SIEM
Script Block Logging events are written to the Microsoft-Windows-PowerShell/Operational channel. This channel is not forwarded by default in most Windows Event Forwarding configurations because it is not in the classic Windows event log set.
Add these channels to your WEF subscription or SIEM collector:
Microsoft-Windows-PowerShell/Operational(Event IDs 4103, 4104)Windows PowerShell(Event IDs 400, 403, 600, 800 -- legacy engine)Microsoft-Windows-WinRM/Operational(for remoting detection)
High-signal queries for your SIEM:
Detect encoded command execution:
Event ID 4104 AND Message CONTAINS "-enc" OR "-EncodedCommand"
Detect AMSI bypass attempts:
Event ID 4104 AND Message CONTAINS "amsiInitFailed" OR "AmsiScanBuffer" OR "Invoke-Mimikatz"
Detect PowerShell downloading payloads:
Event ID 4104 AND Message CONTAINS "Net.WebClient" OR "Invoke-WebRequest" OR "DownloadString" OR "DownloadFile"
Detect CLM bypass attempts:
Event ID 4104 AND Message CONTAINS "FullLanguage" OR "__PSLockDownPolicy"
PowerShell 2.0 Downgrade Attack and How to Block It
PowerShell 2.0, which ships disabled but installed on most Windows systems, does not support Script Block Logging, AMSI, or Constrained Language Mode. Attackers invoke it explicitly to bypass all three:
powershell -version 2 -command ...
Two steps to close this:
- Disable the PowerShell 2.0 Windows feature via Group Policy or DSC:
# Verify current state
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root
# Disable
Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root -NoRestart
Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2 -NoRestart
Deploy at scale via GPO: Computer Configuration > Preferences > Windows Settings > Files or via Intune Remediation script.
- Detect downgrade attempts even before you disable the feature:
# Event 400 in Windows PowerShell channel with EngineVersion=2.0
Event ID 400 AND Message CONTAINS "EngineVersion=2.0"
This event fires when PS 2.0 starts and is one of the highest-fidelity indicators of intentional evasion.
The bottom line
Script Block Logging, Module Logging, and Constrained Language Mode are all free, Group Policy-deployable controls that together eliminate most PowerShell-based post-exploitation techniques. The deployment order that minimizes breakage: enable logging first (no user impact), then audit AppLocker for two weeks, then enforce CLM in enforce mode. Sign or relocate automation scripts before enforcement. Disable PowerShell 2.0 to close the downgrade bypass.
Frequently asked questions
Does enabling Script Block Logging slow down PowerShell?
Minimally. Script block logging adds a small latency per execution as events are written to the event log. For interactive use and most automation, the impact is not measurable. For high-volume pipeline operations running thousands of script blocks per minute, you may see a few percent overhead. Module Logging at the wildcard level generates significantly more volume and can affect performance in script-heavy workloads -- consider scoping Module Logging to specific modules of interest rather than using '*' in high-throughput environments.
Can attackers turn off Script Block Logging?
Not without admin privileges, if the policy is set correctly. Script Block Logging is controlled by a registry key under HKLM (system hive), which requires elevation to modify. An attacker who already has local admin can disable it, but that level of access means they have other ways to evade detection. For non-admin sessions, the logging cannot be disabled. The real bypass risk is PowerShell 2.0 downgrade, which is why disabling the PS 2.0 Windows feature is part of the complete hardening set.
What is the difference between Script Block Logging and transcription?
Script Block Logging (Event ID 4104) records each individual script block in the Windows Event Log, including dynamically evaluated code. It captures deobfuscated content. Transcription writes a full session record including commands and output to a text file on disk or a network share. The two complement each other: Script Block Logging is easier to query at scale via SIEM; transcription provides full context including the output of commands, which is useful during an investigation but is much higher volume and requires storage management.
Does Constrained Language Mode block PowerShell completely?
No. CLM still allows standard pipeline operations, string manipulation, basic file I/O, and most administrative cmdlets that do not require .NET type loading or COM access. It blocks Add-Type, New-Object with dangerous classes, COM object instantiation, and direct Windows API calls via P/Invoke. Most legitimate IT automation does not require these capabilities. Scripts that do need them should be signed and placed in trusted paths, or run through JEA endpoints.
Does Script Block Logging capture obfuscated PowerShell?
Yes, that is the key feature. The logging happens after the PowerShell engine deobfuscates the script, not before. A Base64-encoded payload or a string-concatenated payload is logged in its decoded, executable form. Attackers who obfuscate to bypass AMSI signatures still get logged in plaintext by Script Block Logging. AMSI operates on the pre-execution input; Script Block Logging operates on the post-deobfuscation block that actually runs.
How does AMSI work with PowerShell and what attacker techniques try to bypass it?
AMSI (Antimalware Scan Interface) is a Windows API that allows security products to inspect content before it is executed. In PowerShell, AMSI intercepts script blocks before they run and passes the content to the installed antivirus engine for scanning. If the engine detects malicious content, AMSI returns a detection result that causes PowerShell to block execution. Attacker bypass techniques target AMSI itself: in-memory patching of the amsi.dll AmsiScanBuffer function to always return a clean result (requires admin or PowerShell reflection), obfuscation techniques that alter the script enough that AV signatures do not match (effective against signature-based detection, not behavior-based), and using COM objects or .NET assemblies to execute code outside the PowerShell script block AMSI intercepts. Detection: monitor for processes attempting to write to the amsi.dll memory space, alert on known AMSI bypass strings in Script Block Logs (even if the bypass failed), and look for PowerShell processes spawning child processes with unusual arguments immediately after an AMSI error.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.
Win a $2,495 Black Hat pass.
Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.
