Created
December 11, 2025 13:40
-
-
Save mdowst/9d00ff37ea79dcbfb98e6de580cbedbe to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Search for cmdlet usage and parameter application in PowerShell modules and folders. | |
| .DESCRIPTION | |
| This script searches PowerShell script files (.ps1 and .psm1) for specific cmdlet usage | |
| and detects whether a specified parameter is being used. It uses AST (Abstract Syntax Tree) | |
| parsing for accurate detection and supports both direct parameter usage and splatting. | |
| The script also traces splatting variables back to their definitions to determine if | |
| parameters are set within the splatted hashtable. | |
| .PARAMETER CmdletName | |
| The name of the cmdlet to search for. Default is 'Invoke-WebRequest'. | |
| The script will also search for any aliases defined for this cmdlet. | |
| .PARAMETER Parameter | |
| The parameter to search for within the cmdlet usage. Default is '-UseBasicParsing'. | |
| This can include a value like '-UseBasicParsing=$true' to match specific assignments. | |
| .PARAMETER Modules | |
| Switch parameter to search all installed PowerShell modules. This is the default behavior | |
| when no parameter set is specified. | |
| .PARAMETER FolderPath | |
| Path to a specific folder to search for PowerShell scripts. Must be used with the Folder | |
| parameter set. | |
| .PARAMETER Recurse | |
| When used with FolderPath, recursively searches all subdirectories for script files. | |
| .EXAMPLE | |
| .\Search-CmdletParameterUsage.ps1 | |
| Searches for Invoke-WebRequest usage with -UseBasicParsing in all installed modules. | |
| .EXAMPLE | |
| .\Search-CmdletParameterUsage.ps1 -CmdletName 'Get-Content' -Parameter '-Encoding' -Modules | |
| Searches for Get-Content with -Encoding parameter in all modules. | |
| .EXAMPLE | |
| .\Search-CmdletParameterUsage.ps1 -CmdletName 'Invoke-WebRequest' -FolderPath 'C:\Scripts' -Recurse | |
| Searches a specific folder and its subdirectories for Invoke-WebRequest usage. | |
| .NOTES | |
| This script is useful for auditing scripts and modules to ensure compliance with | |
| best practices or to identify potential issues related to specific cmdlet parameters. | |
| For example, checking for the usage of -UseBasicParsing with Invoke-WebRequest | |
| to ensure compatibility with PowerShell 5.1 . | |
| REFERENCE: | |
| https://support.microsoft.com/en-us/topic/powershell-5-1-preventing-script-execution-from-web-content-7cb95559-655e-43fd-a8bd-ceef2406b705 | |
| .INPUTS | |
| None. This script does not accept pipeline input. | |
| .OUTPUTS | |
| PSCustomObject containing the following properties: | |
| - FileName: Name of the script file containing the cmdlet usage | |
| - FilePath: Full path to the script file | |
| - LineNumber: Line number where the cmdlet is used | |
| - CommandUsed: The actual cmdlet or alias name used | |
| - Line: The full text of the command line | |
| - ParameterValue: The value passed to the parameter, or $true/$false for switches, or $null if not found | |
| - ParameterFound: Boolean indicating if the parameter was found | |
| - UsesSplatting: Boolean indicating if splatting is used | |
| - SplattingVariables: Comma-separated list of splatting variable names | |
| #> | |
| [CmdletBinding(DefaultParameterSetName = 'Modules')] | |
| param( | |
| [Parameter()] | |
| [string]$CmdletName = 'Invoke-WebRequest', | |
| [Parameter()] | |
| [string]$Parameter = '-UseBasicParsing', | |
| [Parameter(ParameterSetName = 'Modules')] | |
| [switch]$Modules, | |
| [Parameter(ParameterSetName = 'Folder', Mandatory)] | |
| [string]$FolderPath, | |
| [Parameter(ParameterSetName = 'Folder')] | |
| [switch]$Recurse | |
| ) | |
| Function Search-FolderForCmdlet { | |
| [CmdletBinding()] | |
| Param ( | |
| [Parameter(Mandatory = $true)] | |
| [string]$CmdletName, | |
| [Parameter(Mandatory = $true)] | |
| [string]$FolderPath, | |
| [Parameter(Mandatory = $true)] | |
| [string]$Parameter, | |
| [switch]$Recurse | |
| ) | |
| # Ensure parameter starts with a dash | |
| if (-not $Parameter.StartsWith('-')) { | |
| $Parameter = "-$Parameter" | |
| } | |
| $Aliases = @(Get-Alias -Definition $CmdletName -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name ) | |
| if (Test-Path $FolderPath) { | |
| # Search for PowerShell files in the specified folder (exclude .psd1 for AST parsing) | |
| $scriptFiles = Get-ChildItem -Path $FolderPath -Filter '*.ps*' -Recurse:$Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Extension -in '.psm1', '.ps1' } | |
| foreach ($file in $scriptFiles) { | |
| try { | |
| # Parse the file using AST | |
| $ast = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref]$null, [ref]$null) | |
| # Find all command elements (cmdlets and aliases) | |
| $commandAsts = $ast.FindAll({ | |
| param($node) | |
| $node -is [System.Management.Automation.Language.CommandAst] | |
| }, $true) | |
| foreach ($commandAst in $commandAsts) { | |
| $commandName = $commandAst.GetCommandName() | |
| # Check if it matches the cmdlet name or alias | |
| if ($commandName -eq $CmdletName -or $commandName -in $Aliases) { | |
| $startLine = $commandAst.Extent.StartLineNumber | |
| $lineText = $commandAst.Extent.Text | |
| $parameterValue = $null | |
| $useSplatting = $false | |
| $splatVariables = @() | |
| # Extract parameter value from the command | |
| $paramName = $Parameter.TrimStart('-') | |
| $commandElements = $commandAst.CommandElements | |
| for ($i = 0; $i -lt $commandElements.Count; $i++) { | |
| $element = $commandElements[$i] | |
| if ($element -is [System.Management.Automation.Language.CommandParameterAst]) { | |
| if ($element.ParameterName -eq $paramName) { | |
| # Check if it's a switch or has a value | |
| if ($i + 1 -lt $commandElements.Count -and $commandElements[$i + 1] -isnot [System.Management.Automation.Language.CommandParameterAst]) { | |
| # Next element is the value | |
| $parameterValue = $commandElements[$i + 1].Extent.Text | |
| } | |
| else { | |
| # It's a switch parameter | |
| $parameterValue = '$true' | |
| } | |
| break | |
| } | |
| } | |
| } | |
| # Check for splatting usage (variables starting with @) | |
| $elementAsts = $commandAst.CommandElements | Where-Object { $_ -is [System.Management.Automation.Language.VariableExpressionAst] } | |
| foreach ($element in $elementAsts) { | |
| if ($element.Splatted) { | |
| $useSplatting = $true | |
| $splatVariables += $element.VariablePath.UserPath | |
| # Look for the splatting variable definition in the script | |
| $variableName = $element.VariablePath.UserPath | |
| $assignments = $ast.FindAll({ | |
| param($node) | |
| $node -is [System.Management.Automation.Language.AssignmentStatementAst] -and | |
| $node.Left -is [System.Management.Automation.Language.VariableExpressionAst] -and | |
| $node.Left.VariablePath.UserPath -eq $variableName | |
| }, $true) | |
| if ($assignments) { | |
| $assignmentText = $assignments[-1].Extent.Text | |
| # Extract parameter value from splatting hashtable | |
| if ($assignmentText -match "\s*$paramName\s*=\s*([^\r\n;]+)") { | |
| $parameterValue = $matches[1].Trim() | |
| } | |
| } | |
| } | |
| } | |
| # Create result object | |
| [PSCustomObject]@{ | |
| FileName = $file.Name | |
| FilePath = $file.FullName | |
| LineNumber = $startLine | |
| CommandUsed = $commandName | |
| Parameter = $Parameter | |
| ParameterFound = [string]::IsNullOrEmpty($parameterValue) -eq $false | |
| Line = $lineText | |
| ParameterValue = $parameterValue | |
| UsesSplatting = $useSplatting | |
| SplattingVariables = if ($splatVariables) { $splatVariables -join ', ' } else { '' } | |
| } | |
| } | |
| } | |
| } | |
| catch { | |
| # Skip files that can't be parsed | |
| continue | |
| } | |
| } | |
| } | |
| } | |
| $results = [Collections.Generic.List[object]]::new() | |
| # Determine search scope based on parameter set | |
| if ($PSCmdlet.ParameterSetName -eq 'Folder') { | |
| if (-not (Test-Path $FolderPath)) { | |
| Write-Error "FolderPath '$FolderPath' does not exist." | |
| exit 1 | |
| } | |
| Write-Host "Searching for '$CmdletName' in folder: $FolderPath" -ForegroundColor Cyan | |
| Search-FolderForCmdlet -CmdletName $CmdletName -FolderPath $FolderPath -Parameter $Parameter -Recurse:$Recurse | ForEach-Object { $results.Add($_) } | |
| } | |
| else { | |
| # Get all installed modules | |
| $AllModules = Get-Module -ListAvailable | |
| # Search each module folder for the specified cmdlet | |
| foreach ($module in $AllModules) { | |
| Write-Progress -Activity "Scanning modules" -Status "Processing $($module.Name)" -PercentComplete (($AllModules.IndexOf($module) / $AllModules.Count) * 100) | |
| Search-FolderForCmdlet -CmdletName $CmdletName -FolderPath $module.ModuleBase -Parameter $Parameter -Recurse | ForEach-Object { $results.Add($_) } | |
| } | |
| Write-Progress -Activity "Scanning modules" -Completed | |
| } | |
| Write-Host "" | |
| # Display results | |
| $results | Format-Table -AutoSize -Property FileName, LineNumber, CommandUsed, Parameter, ParameterFound, ParameterValue, UsesSplatting, SplattingVariables, Line, FilePath |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment