Skip to content

Added PSUseFullyQualifiedCmdletNames rule with fix capabilities #2122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

genXdev
Copy link

@genXdev genXdev commented Aug 19, 2025

PR Summary

Add new diagnostic rule PSUseFullyQualifiedCmdletNames to replace aliases and unqualified cmdlet names with fully qualified versions (e.g., ModuleName\CmdletName).

This rule addresses a common pain point in PowerShell scripting where cmdlet names without module prefixes can lead to ambiguity, especially in environments with multiple modules exporting similarly named cmdlets. By enforcing fully qualified names, the rule provides the following benefits:

  • Automatic module loading: PowerShell will auto-load the specified module if it's not already loaded when the qualified cmdlet is invoked.
  • Enhanced safety: Prevents accidental execution of the wrong cmdlet due to name conflicts across modules, reducing runtime errors.
  • Improved readability and maintainability: Explicit module references make the code's intent clearer, aiding in code reviews and long-term upkeep.
  • Better portability: Scripts become more reliable across different PowerShell environments or versions where module availability might vary.
  • Reduced ambiguity: Helps avoid issues with alias resolution or unqualified names that might resolve differently based on session state.
  • Facilitates advanced editor features: Supports better IntelliSense, auto-completion, and static analysis in tools like VS Code.

This change directly relates to PowerShell/PSScriptAnalyzer#2123, where the PowerShell extension for VS Code has been reported to unexpectedly remove module prefixes (e.g., converting MicrosoftTeams\Get-CsLisCivicAddress to Get-CsLisCivicAddress) during code formatting, potentially introducing ambiguities and runtime issues in scripts. This new rule enables users to automatically add or restore fully qualified cmdlet names during analysis or formatting, helping to repair the damage caused by such removals and promoting safer, more explicit scripting practices.

Implemented in Rules/UseFullyQualifiedCmdletNames.cs, with accompanying tests in the test suite to verify replacement logic for aliases (e.g., lsMicrosoft.PowerShell.Management\Get-ChildItem) and unqualified cmdlets.

PR Checklist

@Copilot Copilot AI review requested due to automatic review settings August 19, 2025 04:07
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new PowerShell Script Analyzer rule PSUseFullQualifiedCmdletNames that enforces the use of fully qualified cmdlet names (e.g., ModuleName\CmdletName) instead of aliases or unqualified names to improve script reliability and prevent ambiguity.

  • Implements diagnostic rule with automatic fix capabilities for replacing aliases and unqualified cmdlets
  • Adds caching mechanism for command resolution to improve performance
  • Provides localized error messages and correction descriptions

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
Rules/UseFullyQualifiedCmdletNames.cs Core implementation of the new diagnostic rule with command resolution and fix suggestions
Rules/Strings.resx Localized string resources for error messages and rule descriptions

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.


var extent = commandAst.CommandElements[0].Extent;

bool isAlias = commandName != fullyQualifiedName.Split('\\')[1];
Copy link
Preview

Copilot AI Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for determining if a command is an alias is incorrect. This will incorrectly identify unqualified cmdlets as aliases when the command name matches the actual cmdlet name. Consider checking the resolved command type instead: bool isAlias = resolvedCommand.CommandType == CommandTypes.Alias;

Suggested change
bool isAlias = commandName != fullyQualifiedName.Split('\\')[1];

Copilot uses AI. Check for mistakes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not following..


var extent = commandAst.CommandElements[0].Extent;

bool isAlias = commandName != fullyQualifiedName.Split('\\')[1];
Copy link
Preview

Copilot AI Aug 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Split('\\')[1] operation is performed for every command analysis. Since the actual cmdlet name is already available from the resolution logic above (line 99), consider storing it in a variable to avoid redundant string operations.

Suggested change
bool isAlias = commandName != fullyQualifiedName.Split('\\')[1];
else
{
// Extract actualCmdletName from the cached fullyQualifiedName
int idx = fullyQualifiedName.IndexOf('\\');
actualCmdletName = (idx >= 0 && idx < fullyQualifiedName.Length - 1)
? fullyQualifiedName.Substring(idx + 1)
: fullyQualifiedName;
}
var extent = commandAst.CommandElements[0].Extent;
bool isAlias = commandName != actualCmdletName;

Copilot uses AI. Check for mistakes.

@genXdev
Copy link
Author

genXdev commented Aug 19, 2025

@microsoft-github-policy-service agree

I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.

@microsoft-github-policy-service agree company="Microsoft"

@genXdev
Copy link
Author

genXdev commented Aug 19, 2025

@genXdev please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"

Contributor License Agreement

@microsoft-github-policy-service agree

I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.

@microsoft-github-policy-service agree company="Microsoft"

@liamjpeters
Copy link
Contributor

👋 Hey @genXdev, could you please add your tests and docs to the PR for your new rule? If you're still working on it, please title the PR as WIP and mark as draft.

My initial thoughts on this:

  • This shouldn't be a default rule. I would expect most people would find this behaviour undesirable - it makes even simple scripts incredibly verbose. My suggestion would be to make it a configurable rule, disabled by default. This allows those that want this behaviour to opt into it and still benefit from your hard work. See something like UseSingularNouns as an example for making a rule configurable and offering settings.

  • If you made the rule configurable, you could add a setting for something like IgnoredModules \ IgnoredNamespaces. For commands in those modules (i.e. Microsoft.PowerShell.Management and Microsoft.PowerShell.Utility) the rule would not expand them into their fully qualified names. This could help with some of the verbosity while still retaining the value you're after.

  • Looking through the context of the issue raised in the VSCode-PowerShell project, I'm not sure that this rule resolves that original issue (not that that's your stated goal). I think that, depending on order of rule execution/formatting, if your rule runs first to expand the command names to their fully qualified names - then the faulty rule runs to "correct" them - your hard work is undone. If it runs afterward it's simply masking that erroneous behaviour which will still be happening.

  • The rule name is inconsistent in the PR title (...FullQualified... vs ...FullyQualified...).

@genXdev
Copy link
Author

genXdev commented Aug 19, 2025

👋 Hey @genXdev, could you please add your tests and docs to the PR for your new rule? If you're still working on it, please title the PR as WIP and mark as draft.

I have committed them;
971f02b

My initial thoughts on this:

  • This shouldn't be a default rule. I would expect most people would find this behaviour undesirable - it makes even simple scripts incredibly verbose. My suggestion would be to make it a configurable rule, disabled by default. This allows those that want this behaviour to opt into it and still benefit from your hard work. See something like UseSingularNouns as an example for making a rule configurable and offering settings.

Whatever you think is best.

  • If you made the rule configurable, you could add a setting for something like IgnoredModules \ IgnoredNamespaces. For commands in those modules (i.e. Microsoft.PowerShell.Management and Microsoft.PowerShell.Utility) the rule would not expand them into their fully qualified names. This could help with some of the verbosity while still retaining the value you're after.

If this still desirable, I'll add them, let me know.

  • Looking through the context of the issue raised in the VSCode-PowerShell project, I'm not sure that this rule resolves that original issue (not that that's your stated goal). I think that, depending on order of rule execution/formatting, if your rule runs first to expand the command names to their fully qualified names - then the faulty rule runs to "correct" them - your hard work is undone. If it runs afterward it's simply masking that erroneous behaviour which will still be happening.

Haven't tested that, for fixing damage, I only enabled my new rule with -Fix

Maybe not the place to mention it, but analyzing 100+ script files at once, I sometimes see 'Collection modified' concurrency exceptions.
I think it is because of SSADictionary, VariablesDictionary, VariableAnalysisDetails in .\Engine\VariableAnalysisBase.cs

@genXdev genXdev changed the title Added PSUseFullQualifiedCmdletNames rule with fix capabilities Added PSUseFullyQualifiedCmdletNames rule with fix capabilities Aug 19, 2025
@genXdev
Copy link
Author

genXdev commented Aug 21, 2025

Also added 'IgnoredModules' parameter and updated tests and docs accordingly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants