Skip to content

Commit 971f02b

Browse files
committed
Added Docs and Test for UseFullyQualifiedCmdletNames rule
1 parent 69a2690 commit 971f02b

File tree

3 files changed

+316
-0
lines changed

3 files changed

+316
-0
lines changed
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
BeforeAll {
5+
$violationName = "PSUseFullyQualifiedCmdletNames"
6+
$testRootDirectory = Split-Path -Parent $PSScriptRoot
7+
Import-Module (Join-Path $testRootDirectory "PSScriptAnalyzerTestHelper.psm1")
8+
}
9+
10+
Describe "UseFullyQualifiedCmdletNames" {
11+
Context "When there are violations" {
12+
It "detects unqualified cmdlet calls" {
13+
$scriptDefinition = 'Get-Command'
14+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
15+
$violations.Count | Should -Be 1
16+
$violations[0].Message | Should -Match "The cmdlet 'Get-Command' should be replaced with the fully qualified cmdlet name 'Microsoft.PowerShell.Core\\Get-Command'"
17+
}
18+
19+
It "detects unqualified alias usage" {
20+
$scriptDefinition = 'gci C:\temp'
21+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
22+
$violations.Count | Should -Be 1
23+
$violations[0].Message | Should -Match "The alias 'gci' should be replaced with the fully qualified cmdlet name 'Microsoft.PowerShell.Management\\Get-ChildItem'"
24+
}
25+
26+
It "provides correct suggested corrections for cmdlets" {
27+
$scriptDefinition = 'Get-Command'
28+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
29+
$violations[0].SuggestedCorrections.Count | Should -Be 1
30+
$violations[0].SuggestedCorrections[0].Text | Should -Be 'Microsoft.PowerShell.Core\Get-Command'
31+
$violations[0].SuggestedCorrections[0].Description | Should -Be "Replace 'Get-Command' with 'Microsoft.PowerShell.Core\Get-Command'"
32+
}
33+
34+
It "provides correct suggested corrections for aliases" {
35+
$scriptDefinition = 'gci'
36+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
37+
$violations[0].SuggestedCorrections.Count | Should -Be 1
38+
$violations[0].SuggestedCorrections[0].Text | Should -Be 'Microsoft.PowerShell.Management\Get-ChildItem'
39+
$violations[0].SuggestedCorrections[0].Description | Should -Be "Replace 'gci' with 'Microsoft.PowerShell.Management\Get-ChildItem'"
40+
}
41+
42+
It "detects multiple violations in same script" {
43+
$scriptDefinition = @'
44+
Get-Command
45+
Write-Host "test"
46+
gci -Recurse
47+
'@
48+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
49+
$violations.Count | Should -Be 3
50+
$violations[0].Extent.Text | Should -Be "Get-Command"
51+
$violations[1].Extent.Text | Should -Be "Write-Host"
52+
$violations[2].Extent.Text | Should -Be "gci"
53+
}
54+
55+
It "detects violations in pipelines" {
56+
$scriptDefinition = 'Get-Process | Where-Object { $_.Name -eq "notepad" }'
57+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
58+
$violations.Count | Should -Be 2
59+
$violations[0].Extent.Text | Should -Be "Get-Process"
60+
$violations[1].Extent.Text | Should -Be "Where-Object"
61+
}
62+
63+
It "detects violations in script blocks" {
64+
$scriptDefinition = 'Invoke-Command -ScriptBlock { Get-Process }'
65+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
66+
$violations.Count | Should -Be 2
67+
($violations.Extent.Text -contains "Invoke-Command") | Should -Be $true
68+
($violations.Extent.Text -contains "Get-Process") | Should -Be $true
69+
}
70+
71+
It "detects violations with parameters" {
72+
$scriptDefinition = 'Get-ChildItem -Path C:\temp -Recurse'
73+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
74+
$violations.Count | Should -Be 1
75+
$violations[0].Extent.Text | Should -Be "Get-ChildItem"
76+
}
77+
78+
It "detects violations with splatting" {
79+
$scriptDefinition = @'
80+
$params = @{ Name = "notepad" }
81+
Get-Process @params
82+
'@
83+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
84+
$violations.Count | Should -Be 1
85+
$violations[0].Extent.Text | Should -Be "Get-Process"
86+
}
87+
}
88+
89+
Context "Violation Extent" {
90+
It "should return only the cmdlet extent, not parameters" {
91+
$scriptDefinition = 'Get-Command -Name Test'
92+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
93+
$violations[0].Extent.Text | Should -Be "Get-Command"
94+
}
95+
96+
It "should return only the alias extent, not parameters" {
97+
$scriptDefinition = 'gci -Recurse'
98+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
99+
$violations[0].Extent.Text | Should -Be "gci"
100+
}
101+
}
102+
103+
Context "When there are no violations" {
104+
It "ignores already qualified cmdlets" {
105+
$scriptDefinition = @'
106+
Microsoft.PowerShell.Core\Get-Command
107+
Microsoft.PowerShell.Utility\Write-Host "test"
108+
Microsoft.PowerShell.Management\Get-ChildItem -Path C:\temp
109+
'@
110+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
111+
$violations.Count | Should -Be 0
112+
}
113+
114+
It "ignores native commands" {
115+
$scriptDefinition = @'
116+
where.exe notepad
117+
cmd /c dir
118+
'@
119+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
120+
$violations.Count | Should -Be 0
121+
}
122+
123+
It "ignores variables" {
124+
$scriptDefinition = @'
125+
$GetCommand = "test"
126+
$variable = $true
127+
'@
128+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
129+
$violations.Count | Should -Be 0
130+
}
131+
132+
It "ignores string literals containing cmdlet names" {
133+
$scriptDefinition = @'
134+
$command = "Get-Command"
135+
"The Get-Command cmdlet is useful"
136+
'@
137+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
138+
$violations.Count | Should -Be 0
139+
}
140+
141+
It "handles mixed qualified and unqualified cmdlets" {
142+
$scriptDefinition = @'
143+
Microsoft.PowerShell.Core\Get-Command
144+
Get-Process
145+
'@
146+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
147+
$violations.Count | Should -Be 1
148+
$violations[0].Extent.Text | Should -Be "Get-Process"
149+
}
150+
}
151+
152+
Context "Different Module Contexts" {
153+
It "handles cmdlets from different modules" {
154+
$scriptDefinition = @'
155+
Get-Content "file.txt"
156+
ConvertTo-Json @{}
157+
Test-Connection "server"
158+
'@
159+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
160+
$violations.Count | Should -Be 3
161+
162+
$getContentViolation = $violations | Where-Object { $_.Extent.Text -eq "Get-Content" }
163+
$getContentViolation.SuggestedCorrections[0].Text | Should -Match "Get-Content$"
164+
165+
$convertToJsonViolation = $violations | Where-Object { $_.Extent.Text -eq "ConvertTo-Json" }
166+
$convertToJsonViolation.SuggestedCorrections[0].Text | Should -Match "ConvertTo-Json$"
167+
}
168+
169+
It "suggests different modules for different cmdlets" {
170+
$scriptDefinition = @'
171+
Get-Command
172+
Write-Host "test"
173+
Get-ChildItem
174+
'@
175+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
176+
$violations.Count | Should -Be 3
177+
178+
$getCmdViolation = $violations | Where-Object { $_.Extent.Text -eq "Get-Command" }
179+
$getCmdViolation.SuggestedCorrections[0].Text | Should -Be 'Microsoft.PowerShell.Core\Get-Command'
180+
181+
$writeHostViolation = $violations | Where-Object { $_.Extent.Text -eq "Write-Host" }
182+
$writeHostViolation.SuggestedCorrections[0].Text | Should -Be 'Microsoft.PowerShell.Utility\Write-Host'
183+
184+
$getChildItemViolation = $violations | Where-Object { $_.Extent.Text -eq "Get-ChildItem" }
185+
$getChildItemViolation.SuggestedCorrections[0].Text | Should -Be 'Microsoft.PowerShell.Management\Get-ChildItem'
186+
}
187+
}
188+
189+
Context "Severity and Rule Properties" {
190+
It "has Warning severity" {
191+
$scriptDefinition = 'Get-Command'
192+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
193+
$violations[0].Severity | Should -Be ([Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticSeverity]::Warning)
194+
}
195+
196+
It "has correct rule name" {
197+
$scriptDefinition = 'Get-Command'
198+
$violations = Invoke-ScriptAnalyzer -ScriptDefinition $scriptDefinition -IncludeRule $violationName
199+
$violations[0].RuleName | Should -Be $violationName
200+
}
201+
}
202+
}

docs/Rules/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ The PSScriptAnalyzer contains the following rule definitions.
6969
| [UseConsistentIndentation](./UseConsistentIndentation.md) | Warning | No | Yes |
7070
| [UseConsistentWhitespace](./UseConsistentWhitespace.md) | Warning | No | Yes |
7171
| [UseCorrectCasing](./UseCorrectCasing.md) | Information | No | Yes |
72+
| [UseFullyQualifiedCmdletNames](./UseFullyQualifiedCmdletNames.md) | Warning | Yes | |
7273
| [UseDeclaredVarsMoreThanAssignments](./UseDeclaredVarsMoreThanAssignments.md) | Warning | Yes | |
7374
| [UseLiteralInitializerForHashtable](./UseLiteralInitializerForHashtable.md) | Warning | Yes | |
7475
| [UseOutputTypeCorrectly](./UseOutputTypeCorrectly.md) | Information | Yes | |
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
---
2+
description: Use fully qualified module names when calling cmdlets and functions.
3+
ms.date: 08/20/2025
4+
ms.topic: reference
5+
title: UseFullyQualifiedCmdletNames
6+
---
7+
# UseFullyQualifiedCmdletNames
8+
9+
**Severity Level: Warning**
10+
11+
## Description
12+
13+
PowerShell cmdlets and functions can be called with or without their module names. Using fully qualified names (with the module prefix) improves script clarity, reduces ambiguity, and helps ensure that the correct cmdlet is executed, especially in environments where multiple modules might contain cmdlets with the same name.
14+
15+
This rule identifies cmdlet and function calls that are not fully qualified and suggests adding the appropriate module qualifier.
16+
17+
## How to Fix
18+
19+
Use the fully qualified cmdlet name in the format `ModuleName\CmdletName` instead of just `CmdletName`.
20+
21+
## Examples
22+
23+
### Wrong
24+
25+
```powershell
26+
# Unqualified cmdlet calls
27+
Get-Command
28+
Write-Host "Hello World"
29+
Get-ChildItem -Path C:\temp
30+
31+
# Unqualified alias usage
32+
gci C:\temp
33+
ls -Force
34+
```
35+
36+
### Correct
37+
38+
```powershell
39+
# Fully qualified cmdlet calls
40+
Microsoft.PowerShell.Core\Get-Command
41+
Microsoft.PowerShell.Utility\Write-Host "Hello World"
42+
Microsoft.PowerShell.Management\Get-ChildItem -Path C:\temp
43+
44+
# Fully qualified equivalents of aliases
45+
Microsoft.PowerShell.Management\Get-ChildItem C:\temp
46+
Microsoft.PowerShell.Management\Get-ChildItem -Force
47+
```
48+
49+
## Benefits
50+
51+
- **Clarity**: Makes it explicit which module provides each cmdlet
52+
- **Reliability**: Ensures the intended cmdlet is called, even if name conflicts exist
53+
- **Module Auto-Loading**: Triggers PowerShell's module auto-loading mechanism, automatically importing the required module if it's not already loaded
54+
- **Reduced Alias Conflicts**: Eliminates ambiguity that can arise from aliases that might conflict with cmdlets from different modules
55+
- **Maintenance**: Easier to understand dependencies and troubleshoot issues
56+
- **Best Practice**: Follows PowerShell best practices for production scripts
57+
- **Performance**: Can improve performance by avoiding the need for PowerShell to search through multiple modules to resolve cmdlet names
58+
59+
## When to Use
60+
61+
This rule is particularly valuable for:
62+
63+
- Production scripts and modules
64+
- Scripts shared across different environments
65+
- Code that might run with varying module configurations
66+
- Enterprise environments with custom or third-party modules
67+
68+
## Module Auto-Loading and Alias Considerations
69+
70+
### Auto-Loading Benefits
71+
72+
When you use fully qualified cmdlet names, PowerShell's module auto-loading feature provides several advantages:
73+
74+
- **Automatic Import**: If the specified module isn't already loaded, PowerShell will automatically import it when the cmdlet is called
75+
- **Explicit Dependencies**: The script clearly declares which modules it depends on without requiring manual `Import-Module` calls
76+
- **Version Control**: Helps ensure the correct module version is loaded, especially when multiple versions are installed
77+
78+
### Avoiding Alias Conflicts
79+
80+
Fully qualified names help prevent common issues with aliases:
81+
82+
- **Conflicting Aliases**: Different modules may define aliases with the same name but different behaviors
83+
- **Platform Differences**: Some aliases behave differently across PowerShell versions or operating systems
84+
- **Custom Aliases**: User-defined or organizational aliases won't interfere with script execution
85+
- **Predictable Behavior**: Scripts behave consistently regardless of the user's alias configuration
86+
87+
### Example of Conflict Resolution
88+
89+
```powershell
90+
# Without qualification - could resolve to different cmdlets depending on loaded modules
91+
Get-Item
92+
93+
# With qualification - always resolves to the specific cmdlet
94+
Microsoft.PowerShell.Management\Get-Item
95+
96+
# Alias that might conflict with custom definitions
97+
ls
98+
99+
# Fully qualified equivalent that avoids conflicts
100+
Microsoft.PowerShell.Management\Get-ChildItem
101+
```
102+
103+
## Notes
104+
105+
- The rule analyzes cmdlets, functions, and aliases that can be resolved to a module
106+
- Native commands (like `cmd.exe`) and user-defined functions without modules are not flagged
107+
- Already qualified cmdlet calls are not flagged
108+
- Variables and string literals containing cmdlet names are not flagged
109+
110+
## Related Rules
111+
112+
- [AvoidUsingCmdletAliases](./AvoidUsingCmdletAliases.md) - Recommends using full cmdlet names instead of aliases
113+
- [UseCorrectCasing](./UseCorrectCasing.md) - Ensures correct casing for cmdlet names

0 commit comments

Comments
 (0)