|
| 1 | +--- |
| 2 | +description: Use patterns compatible with Constrained Language Mode |
| 3 | +ms.date: 03/17/2026 |
| 4 | +ms.topic: reference |
| 5 | +title: UseConstrainedLanguageMode |
| 6 | +--- |
| 7 | +# UseConstrainedLanguageMode |
| 8 | + |
| 9 | +**Severity Level: Information** |
| 10 | + |
| 11 | +## Description |
| 12 | + |
| 13 | +This rule identifies PowerShell patterns that are restricted or not permitted in Constrained Language Mode (CLM). |
| 14 | + |
| 15 | +Constrained Language Mode is a PowerShell security feature that restricts: |
| 16 | +- .NET types that can be used |
| 17 | +- COM objects that can be instantiated |
| 18 | +- Commands that can be executed |
| 19 | +- Language features that can be used |
| 20 | + |
| 21 | +CLM is commonly used in: |
| 22 | +- Application Control environments (Application Control for Business, AppLocker) |
| 23 | +- Just Enough Administration (JEA) endpoints |
| 24 | +- Secure environments requiring additional PowerShell restrictions |
| 25 | + |
| 26 | +**Signed Script Behavior**: Digitally signed scripts from trusted publishers execute in Full Language Mode (FLM) even in CLM environments. The rule detects signature blocks (`# SIG # Begin signature block`) and adjusts checks accordingly - most restrictions don't apply to signed scripts, but certain checks (dot-sourcing, parameter types, manifest best practices) are always enforced. |
| 27 | + |
| 28 | +**Important**: The rule performs a simple text check for signature blocks and does NOT validate signature authenticity or certificate trust. Actual signature validation is performed by PowerShell at runtime. |
| 29 | + |
| 30 | +## Constrained Language Mode Restrictions |
| 31 | + |
| 32 | +### Unsigned Scripts (Full CLM Checking) |
| 33 | + |
| 34 | +The following are flagged for unsigned scripts: |
| 35 | + |
| 36 | +1. **Add-Type** - Code compilation not permitted |
| 37 | +2. **Disallowed COM Objects** - Only Scripting.Dictionary, Scripting.FileSystemObject, VBScript.RegExp allowed |
| 38 | +3. **Disallowed .NET Types** - Only ~70 allowed types (string, int, hashtable, pscredential, etc.) |
| 39 | +4. **Type Constraints** - On parameters and variables |
| 40 | +5. **Type Expressions** - Static type references like `[Type]::Method()` |
| 41 | +6. **Type Casts** - Converting to disallowed types |
| 42 | +7. **Member Invocations** - Methods/properties on disallowed types |
| 43 | +8. **PowerShell Classes** - `class` keyword not permitted |
| 44 | +9. **XAML/WPF** - Not permitted |
| 45 | +10. **Invoke-Expression** - Restricted |
| 46 | +11. **Dot-Sourcing** - May be restricted depending on the file being sourced |
| 47 | +12. **Module Manifest Wildcards** - Wildcard exports not recommended |
| 48 | +13. **Module Manifest .ps1 Files** - Script modules ending with .ps1 not allowed |
| 49 | + |
| 50 | +Always enforced, even for signed scripts |
| 51 | + |
| 52 | +### Signed Scripts (Selective Checking) |
| 53 | + |
| 54 | +For scripts with signature blocks, only these are checked: |
| 55 | +- Dot-sourcing |
| 56 | +- Parameter type constraints |
| 57 | +- Module manifest wildcards (.psd1 files) |
| 58 | +- Module manifest script modules (.psd1 files) |
| 59 | + |
| 60 | +## Configuration |
| 61 | + |
| 62 | +### Basic Configuration |
| 63 | + |
| 64 | +```powershell |
| 65 | +@{ |
| 66 | + Rules = @{ |
| 67 | + PSUseConstrainedLanguageMode = @{ |
| 68 | + Enable = $true |
| 69 | + } |
| 70 | + } |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +### Parameters |
| 75 | + |
| 76 | +#### Enable: bool (Default value is `$false`) |
| 77 | + |
| 78 | +Enable or disable the rule during ScriptAnalyzer invocation. This rule is disabled by default because not all scripts need CLM compatibility. |
| 79 | + |
| 80 | +#### IgnoreSignatures: bool (Default value is `$false`) |
| 81 | + |
| 82 | +Control signature detection behavior: |
| 83 | + |
| 84 | +- `$false` (default): Automatically detect signatures. Signed scripts get selective checking, unsigned get full checking. |
| 85 | +- `$true`: Bypass signature detection. ALL scripts get full CLM checking regardless of signature status. |
| 86 | + |
| 87 | +```powershell |
| 88 | +@{ |
| 89 | + Rules = @{ |
| 90 | + PSUseConstrainedLanguageMode = @{ |
| 91 | + Enable = $true |
| 92 | + IgnoreSignatures = $true # Enforce full CLM compliance for all scripts |
| 93 | + } |
| 94 | + } |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +**Use `IgnoreSignatures = $true` when:** |
| 99 | +- Auditing signed scripts for complete CLM compatibility |
| 100 | +- Preparing scripts for untrusted environments |
| 101 | +- Enforcing strict CLM compliance organization-wide |
| 102 | +- Development/testing to see all potential issues |
| 103 | + |
| 104 | +## How to Fix |
| 105 | + |
| 106 | +### Replace Add-Type |
| 107 | + |
| 108 | +Use allowed cmdlets or pre-compile assemblies. |
| 109 | + |
| 110 | +### Replace Disallowed COM Objects |
| 111 | + |
| 112 | +Use only allowed COM objects (Scripting.Dictionary, Scripting.FileSystemObject, VBScript.RegExp) or PowerShell cmdlets. |
| 113 | + |
| 114 | +### Replace Disallowed Types |
| 115 | + |
| 116 | +Use allowed type accelerators (`[string]`, `[int]`, `[hashtable]`, etc.) or allowed cmdlets instead of disallowed .NET types. |
| 117 | + |
| 118 | +### Replace PowerShell Classes |
| 119 | + |
| 120 | +Use `New-Object PSObject` with `Add-Member` or hashtables instead of classes. |
| 121 | + |
| 122 | +**Important**: `[PSCustomObject]@{}` syntax is NOT allowed in CLM because it uses type casting. |
| 123 | + |
| 124 | +### Avoid XAML |
| 125 | + |
| 126 | +Don't use WPF/XAML in CLM-compatible scripts. |
| 127 | + |
| 128 | +### Replace Invoke-Expression |
| 129 | + |
| 130 | +Use direct execution (`&`) or safer alternatives. |
| 131 | + |
| 132 | +### Replace Dot-Sourcing |
| 133 | + |
| 134 | +Use modules with Import-Module instead of dot-sourcing when possible. |
| 135 | + |
| 136 | +### Fix Module Manifests |
| 137 | + |
| 138 | +- Replace wildcard exports (`*`) with explicit lists |
| 139 | +- Use `.psm1` or `.dll` instead of `.ps1` for RootModule/NestedModules |
| 140 | + |
| 141 | +## Examples |
| 142 | + |
| 143 | +### Example 1: Add-Type |
| 144 | + |
| 145 | +#### Wrong |
| 146 | + |
| 147 | +```powershell |
| 148 | +Add-Type -TypeDefinition @" |
| 149 | + public class Helper { |
| 150 | + public static string DoWork() { return "Done"; } |
| 151 | + } |
| 152 | +"@ |
| 153 | +``` |
| 154 | + |
| 155 | +#### Correct |
| 156 | + |
| 157 | +```powershell |
| 158 | +# Code sign your module using Add-Type |
| 159 | +# Use allowed cmdlets instead |
| 160 | +# Or pre-compile and load the assembly |
| 161 | +``` |
| 162 | + |
| 163 | +### Example 2: COM Objects |
| 164 | + |
| 165 | +#### Wrong |
| 166 | + |
| 167 | +```powershell |
| 168 | +$excel = New-Object -ComObject Excel.Application |
| 169 | +``` |
| 170 | + |
| 171 | +#### Correct |
| 172 | + |
| 173 | +```powershell |
| 174 | +# Use allowed COM object |
| 175 | +$dict = New-Object -ComObject Scripting.Dictionary |
| 176 | +
|
| 177 | +# Or use PowerShell cmdlets |
| 178 | +Import-Excel -Path $file # From ImportExcel module |
| 179 | +``` |
| 180 | + |
| 181 | +### Example 3: Disallowed Types |
| 182 | + |
| 183 | +#### Wrong |
| 184 | + |
| 185 | +```powershell |
| 186 | +# Type constraint and member invocation flagged |
| 187 | +function Download-File { |
| 188 | + param([System.Net.WebClient]$Client) |
| 189 | + $Client.DownloadString($url) |
| 190 | +} |
| 191 | +
|
| 192 | +# Type cast and method call flagged |
| 193 | +[System.Net.WebClient]$client = New-Object System.Net.WebClient |
| 194 | +$data = $client.DownloadData($url) |
| 195 | +``` |
| 196 | + |
| 197 | +#### Correct |
| 198 | + |
| 199 | +```powershell |
| 200 | +# Use allowed cmdlets |
| 201 | +function Download-File { |
| 202 | + param([string]$Url) |
| 203 | + Invoke-WebRequest -Uri $Url |
| 204 | +} |
| 205 | +
|
| 206 | +# Use allowed types |
| 207 | +function Process-Text { |
| 208 | + param([string]$Text) |
| 209 | + $upper = $Text.ToUpper() # String methods are allowed |
| 210 | +} |
| 211 | +``` |
| 212 | + |
| 213 | +### Example 4: PowerShell Classes |
| 214 | + |
| 215 | +#### Wrong |
| 216 | + |
| 217 | +```powershell |
| 218 | +class MyClass { |
| 219 | + [string]$Name |
| 220 | + |
| 221 | + [string]GetInfo() { |
| 222 | + return $this.Name |
| 223 | + } |
| 224 | +} |
| 225 | +
|
| 226 | +# Also wrong - uses type cast |
| 227 | +$obj = [PSCustomObject]@{ |
| 228 | + Name = "Test" |
| 229 | +} |
| 230 | +``` |
| 231 | + |
| 232 | +#### Correct |
| 233 | + |
| 234 | +```powershell |
| 235 | +# Option 1: New-Object PSObject with Add-Member |
| 236 | +$obj = New-Object PSObject -Property @{ |
| 237 | + Name = "Test" |
| 238 | +} |
| 239 | +
|
| 240 | +$obj | Add-Member -MemberType ScriptMethod -Name GetInfo -Value { |
| 241 | + return $this.Name |
| 242 | +} |
| 243 | +
|
| 244 | +Add-Member -InputObject $obj -NotePropertyMembers @{"Number" = 42} |
| 245 | +
|
| 246 | +# Option 2: Hashtable |
| 247 | +$obj = @{ |
| 248 | + Name = "Test" |
| 249 | + Number = 42 |
| 250 | +} |
| 251 | +``` |
| 252 | + |
| 253 | +### Example 5: Module Manifests |
| 254 | + |
| 255 | +#### Wrong |
| 256 | + |
| 257 | +```powershell |
| 258 | +@{ |
| 259 | + ModuleVersion = '1.0.0' |
| 260 | + RootModule = 'MyModule.ps1' # .ps1 not recommended |
| 261 | + FunctionsToExport = '*' # Wildcard not recommended |
| 262 | + CmdletsToExport = '*' |
| 263 | +} |
| 264 | +``` |
| 265 | + |
| 266 | +#### Correct |
| 267 | + |
| 268 | +```powershell |
| 269 | +@{ |
| 270 | + ModuleVersion = '1.0.0' |
| 271 | + RootModule = 'MyModule.psm1' # Use .psm1 or .dll |
| 272 | + FunctionsToExport = @( # Explicit list |
| 273 | + 'Get-MyFunction' |
| 274 | + 'Set-MyFunction' |
| 275 | + ) |
| 276 | + CmdletsToExport = @() |
| 277 | +} |
| 278 | +``` |
| 279 | + |
| 280 | +### Example 6: Array Types |
| 281 | + |
| 282 | +#### Wrong |
| 283 | + |
| 284 | +```powershell |
| 285 | +# Disallowed type in array |
| 286 | +param([System.Net.WebClient[]]$Clients) |
| 287 | +``` |
| 288 | + |
| 289 | +#### Correct |
| 290 | + |
| 291 | +```powershell |
| 292 | +# Allowed types in arrays are fine |
| 293 | +param([string[]]$Names) |
| 294 | +param([int[]]$Numbers) |
| 295 | +param([hashtable[]]$Configuration) |
| 296 | +``` |
| 297 | + |
| 298 | +## Detailed Restrictions |
| 299 | + |
| 300 | +### 1. Add-Type |
| 301 | +`Add-Type` allows compiling arbitrary C# code and is not permitted in CLM. |
| 302 | + |
| 303 | +**Enforced For**: Unsigned scripts only |
| 304 | + |
| 305 | +### 2. COM Objects |
| 306 | +Only three COM objects are allowed: |
| 307 | +- `Scripting.Dictionary` |
| 308 | +- `Scripting.FileSystemObject` |
| 309 | +- `VBScript.RegExp` |
| 310 | + |
| 311 | +All others (Excel.Application, WScript.Shell, etc.) are flagged. |
| 312 | + |
| 313 | +**Enforced For**: Unsigned scripts only |
| 314 | + |
| 315 | +### 3. .NET Types |
| 316 | +Only ~70 allowed types including: |
| 317 | +- Primitives: `string`, `int`, `bool`, `byte`, `char`, `datetime`, `decimal`, `double`, etc. |
| 318 | +- Collections: `hashtable`, `array`, `arraylist` |
| 319 | +- PowerShell: `pscredential`, `psobject`, `securestring` |
| 320 | +- Utilities: `regex`, `guid`, `version`, `uri`, `xml` |
| 321 | +- Arrays: `string[]`, `int[][]`, etc. (array of any allowed type) |
| 322 | + |
| 323 | +The rule checks type usage in: |
| 324 | +- Parameter type constraints (**always enforced, even for signed scripts**) |
| 325 | +- Variable type constraints |
| 326 | +- New-Object -TypeName |
| 327 | +- Type expressions (`[Type]::Method()`) |
| 328 | +- Type casts (`[Type]$variable`) |
| 329 | +- Member invocations on typed variables |
| 330 | + |
| 331 | +**Enforced For**: Parameter constraints always; others unsigned only |
| 332 | + |
| 333 | +### 4. PowerShell Classes |
| 334 | +The `class` keyword is not permitted. Use `New-Object PSObject` with `Add-Member` or hashtables. |
| 335 | + |
| 336 | +**Note**: `[PSCustomObject]@{}` is also not allowed because it uses type casting. |
| 337 | + |
| 338 | +**Enforced For**: Unsigned scripts only |
| 339 | + |
| 340 | +### 5. XAML/WPF |
| 341 | +XAML and WPF are not permitted in CLM. |
| 342 | + |
| 343 | +**Enforced For**: Unsigned scripts only |
| 344 | + |
| 345 | +### 6. Invoke-Expression |
| 346 | +`Invoke-Expression` is restricted in CLM. |
| 347 | + |
| 348 | +**Enforced For**: Unsigned scripts only |
| 349 | + |
| 350 | +### 7. Dot-Sourcing |
| 351 | +Dot-sourcing (`. $PSScriptRoot\script.ps1`) may be restricted depending on source location. |
| 352 | + |
| 353 | +**Enforced For**: ALL scripts (unsigned and signed) |
| 354 | + |
| 355 | +### 8. Module Manifest Best Practices |
| 356 | + |
| 357 | +#### Wildcard Exports |
| 358 | +Don't use `*` in: `FunctionsToExport`, `CmdletsToExport`, `AliasesToExport`, `VariablesToExport` |
| 359 | + |
| 360 | +Use explicit lists for security and clarity. |
| 361 | + |
| 362 | +**Enforced For**: ALL .psd1 files (unsigned and signed) |
| 363 | + |
| 364 | +#### Script Module Files |
| 365 | +Don't use `.ps1` files in: `RootModule`, `ModuleToProcess`, `NestedModules` |
| 366 | + |
| 367 | +Use `.psm1` (script modules) or `.dll` (binary modules) for better performance and compatibility. |
| 368 | + |
| 369 | +**Enforced For**: ALL .psd1 files (unsigned and signed) |
| 370 | + |
| 371 | +## More Information |
| 372 | + |
| 373 | +- [About Language Modes](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_language_modes) |
| 374 | +- [PowerShell Constrained Language Mode](https://devblogs.microsoft.com/powershell/powershell-constrained-language-mode/) |
0 commit comments