From da0f88358a50462900ecd827f4464f72ae336c7b Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 31 Jan 2025 00:59:49 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Add=20function=20`Get?= =?UTF-8?q?-ScriptCommand`=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This pull request introduces a new function `Get-ScriptCommand` to the PowerShell script and adds corresponding tests to ensure its functionality. The new function analyzes a specified PowerShell script to extract command invocations and optionally includes call operators. ### New Functionality: * [`src/functions/public/Scripts/Get-ScriptCommands.ps1`](diffhunk://#diff-c59ca712a6ccbcb05a5ae179a1d551bc55f9835feb3f4d73dad9e261e3d579b9R1-R56): Added a new function `Get-ScriptCommand` that retrieves commands used within a specified PowerShell script, including details such as command name, position, and file reference. It also provides an option to include call operators in the results. ### Tests: * [`tests/Module.Tests.ps1`](diffhunk://#diff-a94f0e90abaaff044e3cb0327ce3a63a10cd61551269a414e4dc40ad2af8a951R45-R56): Added a new test context for the `Get-ScriptCommands` function to verify that it retrieves script commands correctly, ensuring the results are not null or empty, are of the correct type, and contain expected command names. ## Type of change - [ ] 📖 [Docs] - [ ] 🪲 [Fix] - [x] 🩹 [Patch] - [ ] ⚠️ [Security fix] - [ ] 🚀 [Feature] - [ ] 🌟 [Breaking change] ## Checklist - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas --- .../public/Scripts/Get-ScriptCommand.ps1 | 60 +++++++++++++++++++ tests/Module.Tests.ps1 | 29 +++++++++ tests/src/Test-Function.ps1 | 31 ++++++++++ 3 files changed, 120 insertions(+) create mode 100644 src/functions/public/Scripts/Get-ScriptCommand.ps1 diff --git a/src/functions/public/Scripts/Get-ScriptCommand.ps1 b/src/functions/public/Scripts/Get-ScriptCommand.ps1 new file mode 100644 index 0000000..0b02044 --- /dev/null +++ b/src/functions/public/Scripts/Get-ScriptCommand.ps1 @@ -0,0 +1,60 @@ +function Get-ScriptCommand { + <# + .SYNOPSIS + Retrieves the commands used within a specified PowerShell script. + + .DESCRIPTION + Analyzes a given PowerShell script and extracts all command invocations. + Optionally includes call operators (& and .) in the results. + Returns details such as command name, position, and file reference. + + .EXAMPLE + Get-ScriptCommand -Path "C:\Scripts\example.ps1" + + Extracts and lists all commands found in the specified PowerShell script. + + .EXAMPLE + Get-ScriptCommand -Path "C:\Scripts\example.ps1" -IncludeCallOperators + + Extracts all commands, including those executed with call operators (& and .). + #> + [Alias('Get-ScriptCommands')] + [CmdletBinding()] + param ( + # The path to the PowerShell script file to be parsed. + [Parameter(Mandatory)] + [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] + [string] $Path, + + # Include call operators in the results, i.e. & and . + [Parameter()] + [switch] $IncludeCallOperators + ) + + # Extract function definitions + $ast = Get-ScriptAST -Path $Path + + # Gather CommandAsts + $commandAST = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.CommandAst] }, $true) + + if (-not $IncludeCallOperators) { + $commandAST = $commandAST | Where-Object { $_.InvocationOperator -notin 'Ampersand', 'Dot' } + } + + $commandAST | ForEach-Object { + $invocationOperator = switch ($_.InvocationOperator) { + 'Ampersand' { '&' } + 'Dot' { '.' } + } + $_.CommandElements[0].Extent | ForEach-Object { + [pscustomobject]@{ + Name = [string]::IsNullOrEmpty($invocationOperator) ? $_.Text : $invocationOperator + StartLineNumber = $_.StartLineNumber + StartColumnNumber = $_.StartColumnNumber + EndLineNumber = $_.EndLineNumber + EndColumnNumber = $_.EndColumnNumber + File = $_.File + } + } + } +} diff --git a/tests/Module.Tests.ps1 b/tests/Module.Tests.ps1 index e80904c..201d4cc 100644 --- a/tests/Module.Tests.ps1 +++ b/tests/Module.Tests.ps1 @@ -42,3 +42,32 @@ Describe 'Functions' { } } } + +Describe 'Scripts' { + Context "Function: 'Get-ScriptCommands'" { + It 'Get-ScriptCommands gets the script commands' { + $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' + $commands = Get-ScriptCommand -Path $path + $commands | Should -Not -BeNullOrEmpty + $commands | Should -BeOfType [pscustomobject] + $commands.Name | Should -Contain 'ForEach-Object' + $commands.Name | Should -Contain 'Get-Process' + $commands.Name | Should -Contain 'ipmo' + $commands.Name | Should -Contain 'Register-ArgumentCompleter' + $commands.Name | Should -Not -Contain '.' + $commands.Name | Should -Not -Contain '&' + } + It 'Get-ScriptCommands gets the script commands with call operators' { + $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' + $commands = Get-ScriptCommand -Path $path -IncludeCallOperators + $commands | Should -Not -BeNullOrEmpty + $commands | Should -BeOfType [pscustomobject] + $commands.Name | Should -Contain 'ForEach-Object' + $commands.Name | Should -Contain 'Get-Process' + $commands.Name | Should -Contain 'ipmo' + $commands.Name | Should -Contain 'Register-ArgumentCompleter' + $commands.Name | Should -Contain '.' + $commands.Name | Should -Contain '&' + } + } +} diff --git a/tests/src/Test-Function.ps1 b/tests/src/Test-Function.ps1 index c594565..9d5b761 100644 --- a/tests/src/Test-Function.ps1 +++ b/tests/src/Test-Function.ps1 @@ -10,6 +10,10 @@ #> [Alias('Test', 'TestFunc')] [Alias('Test-Func')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingCmdletAliases', '', Scope = 'Function', + Justification = 'This is a test :)' + )] [CmdletBinding()] param ( # Name of the person to greet. @@ -17,4 +21,31 @@ [string] $Name ) Write-Output "Hello, $Name!" + + Get-Process | Select-Object -First 5 + + $hash = @{ + 'Key1' = 'Value1' + 'Key2' = 'Value2' + } + + $hash.GetEnumerator() | ForEach-Object { + Write-Output "Key: $($_.Key), Value: $($_.Value)" + } + + . Get-Alias | ForEach-Object { + Write-Output "Alias: $($_.Name), Definition: $($_.Definition)" + } + + & { + Write-Output 'Hello, World!' + } + + ipmo Microsoft.PowerShell.Utility +} + +Register-ArgumentCompleter -CommandName Test-Function -ParameterName Name -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters + Get-Process | Where-Object { $_.Name -like "$wordToComplete*" } | Select-Object -ExpandProperty Name }