Skip to content

Commit 3526267

Browse files
committed
Add documentation for PSUseConstrainedLanguageMode rule
1 parent 1abc86a commit 3526267

File tree

1 file changed

+374
-0
lines changed

1 file changed

+374
-0
lines changed
Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
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

Comments
 (0)