Skip to content

Instantly share code, notes, and snippets.

@Jaykul
Last active October 6, 2022 09:38
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save Jaykul/f10337411d545b15a84b06c6294b825e to your computer and use it in GitHub Desktop.
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.
#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