-
-
Save kilasuit/86a8c654f1e6127260527b99a05669fd 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