Last active
October 6, 2022 09:38
-
-
Save Jaykul/f10337411d545b15a84b06c6294b825e to your computer and use it in GitHub Desktop.
Extract a list of commands used by a script or script command. Will warn about commands it can't identify and put them in global `$MissingCommands` variable.
This file contains 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
#requires -Module Reflection | |
[CmdletBinding()] | |
param( | |
# The path to a script or name of a command | |
$Command, | |
# If you want to include private functions from a module, make sure it's imported, and pass the ModuleInfo here | |
[System.Management.Automation.PSModuleInfo]$ModuleScope = $( | |
Get-Module $Command -ListAvailable -ErrorAction SilentlyContinue | Get-Module -ErrorAction SilentlyContinue | |
), | |
[switch]$Recurse, | |
[switch]$Modules | |
) | |
function Resolve-Command { | |
[CmdletBinding()] | |
param( | |
[Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)] | |
[string]$Name | |
) | |
process { | |
Write-Debug "ENTER: Resolve-Command $Name" | |
while (($command = Get-Command $Name -EA SilentlyContinue).CommandType -eq "Alias") { | |
$Name = $command.Definition | |
} | |
$command | |
Write-Debug "EXIT: Resolve-Command $Name" | |
} | |
} | |
$FindDependencies = { | |
[CmdletBinding()] | |
param($Command) | |
Write-Progress "Searching Dependencies in $Command" | |
Write-Debug "FindDependencies $Command" | |
function Resolve-Command { | |
[CmdletBinding()] | |
param( | |
[Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)] | |
[string]$Name | |
) | |
process { | |
Write-Progress "Resolving Command $Name" | |
Write-Debug " ENTER: Resolve-Command $Name" | |
while (($command = Get-Command $Name -EA SilentlyContinue).CommandType -eq "Alias") { | |
$Name = $command.Definition | |
} | |
$command | |
Write-Debug " EXIT: Resolve-Command $Name" | |
} | |
} | |
function Get-ParseResults { | |
param( | |
# The script or file path to parse | |
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] | |
[Alias("Path", "PSPath")] | |
$Script | |
) | |
process { | |
Write-Progress "Parsing $Script" | |
Write-Debug " ENTER: Get-ParseResults $Script" | |
$ParseErrors = $null | |
$Tokens = $null | |
if ($Script | Test-Path -ErrorAction SilentlyContinue) { | |
Write-Debug " Parse $Script as Path" | |
$AST = [System.Management.Automation.Language.Parser]::ParseFile(($Script | Convert-Path), [ref]$Tokens, [ref]$ParseErrors) | |
} elseif ($Script -is [System.Management.Automation.FunctionInfo]) { | |
Write-Debug " Parse $Script as Function" | |
$String = "function $($Script.Name) { $($Script.Definition) }" | |
$AST = [System.Management.Automation.Language.Parser]::ParseInput($String, [ref]$Tokens, [ref]$ParseErrors) | |
} else { | |
Write-Debug " Parse $Script as String" | |
$AST = [System.Management.Automation.Language.Parser]::ParseInput([String]$Script, [ref]$Tokens, [ref]$ParseErrors) | |
} | |
Write-Debug " EXIT: Get-ParseResults $Script" | |
[PSCustomObject]@{ | |
PSTypeName = "System.Management.Automation.Language.ParseResults" | |
ParseErrors = $ParseErrors | |
Tokens = $Tokens | |
AST = $AST | |
} | |
} | |
} | |
Resolve-Command $Command | Get-ParseResults | & { | |
process { | |
Write-Progress "Searching for commands in ScriptBlock" | |
([System.Management.Automation.Language.Ast]$_.AST).FindAll( { | |
param($Ast) | |
Where-Object -Input $Ast -FilterScript { $_ -is "System.Management.Automation.Language.CommandAst" } | |
}, $true) | |
} | |
} | | |
# Errors will appear for commands you don't have available | |
Resolve-Command -Name { $_.CommandElements[0].Value } -ErrorAction SilentlyContinue -ErrorVariable +global:MissingCommands | |
} | |
$Commands = @( Resolve-Command $Command ) | |
$ScannedCommands = @() | |
$global:MissingCommands = @() | |
do { | |
foreach ($Command in $Commands | Where-Object { $_ -notin $ScannedCommands -and $_ -isnot [System.Management.Automation.CmdletInfo] }) { | |
$Commands += @( | |
if ($Command.Module) { | |
Write-Progress "Parsing $Command in module $($Command.Module)" | |
Write-Debug "Parsing $Command in module $($Command.Module)" | |
& $Command.Module $FindDependencies $Command | |
} else { | |
Write-Debug "Parsing $Command" | |
& $FindDependencies $Command | |
} | |
) | |
$ScannedCommands += $Command | |
} | |
} while ($Recurse -and ($Commands | Where-Object { $_ -notin $ScannedCommands -and $_ -isnot [System.Management.Automation.CmdletInfo] })) | |
if ($Modules) { | |
$Commands | | |
Sort-Object Source, Name -Unique | | |
Group-Object Source | | |
Select-Object @{N = "Module"; e = { $_.Name } }, @{N = "Used Commands"; E = { $_.Group } } | |
} else { | |
$Commands | Select-Object -Unique | |
} | |
if ($MissingCommands) { | |
Write-Warning "Missing source for some (probably internal) commands: $(($MissingCommands | Select-Object -Expand TargetObject -Unique) -join ', ')" | |
Write-Verbose ($MissingCommands | Sort-Object -Unique | Out-String) | |
$global:MissingCommands = $MissingCommands | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment