7

If I have a .ps1 file with the following functions

function SomeFunction {}

function AnotherFunction {}

How can I get a list of all those functions and invoke them?

I'd like to do something like this:

$functionsFromFile = Get-ListOfFunctions -Path 'C:\someScript.ps1'
foreach($function in $functionsFromFile)
{
   $function.Run() #SomeFunction and AnotherFunction execute
}
3
  • 2
    If your .ps1 was a .psm1 instead, it'd be a module and getting the list of functions would be as easy as (Import-Module C:\someScript.psm1 -Passthru).ExportedFunctions.Values. (Invoke with &$_.ScriptBlock as per Martin's answer.) Commented Dec 6, 2016 at 19:31
  • @JeroenMostert Why is that so? (Could we convert it's name to a temp file and run it the same?) Commented Oct 4, 2020 at 9:20
  • 1
    A lot of new answers has been given since 2016, so you might want to reconsider the solution chosen to your question. Commented Jan 23, 2024 at 16:10

8 Answers 8

14

You could use the Get-ChildItem to retrieve all functions and store them into a variable. Then load the script into to runspace and retrieve all functions again and use the Where-Object cmdlet to filter all new functions by excluding all previously retrieved functions. Finally iterate over all new functions and invoke them:

$currentFunctions = Get-ChildItem function:
# dot source your script to load it to the current runspace
. "C:\someScript.ps1"
$scriptFunctions = Get-ChildItem function: | Where-Object { $currentFunctions -notcontains $_ }

$scriptFunctions | ForEach-Object {
      & $_
}
Sign up to request clarification or add additional context in comments.

4 Comments

Sometimes, you have to stop by and comment on a really nice idea. Nice trick :-)
If you only want the specific function for easy copy/paste, use: Get-ChildItem function:SomeFunction | ForEach-Object { "function $_ {", $_.ScriptBlock, "}`n" }.
Nicely done. Note that & $_ will do - no need to use .ScriptBlock
Well spotted. I changed it.
3

• SOLUTION 1.

If you need to use a function that is in another script file, you can import that function with the Import-Module cmdlet

Functions.ps1 Contains the full functions. This script needs to be imported by the main script.

function Write-TextColor
{
    Param(
        [parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [Object]
        $Info,

        [parameter(Position=1, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [System.ConsoleColor]
        $ForegroundColor =  [System.ConsoleColor]::White,

        [parameter(Position=2, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [Switch]
        $NoNewLine
        )

        Process{
            foreach ($value in $Info)
            {
                if($NoNewLine)
                {
                    Write-Host $value -ForegroundColor $ForegroundColor -NoNewline
                }
                else {
                    Write-Host $value -ForegroundColor $ForegroundColor
                }
            }            
        }
}
function Write-InfoBlue 
{
    Param(
        [parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [ValidateNotNullOrEmpty()]
        [Object]
        $Information,

        [parameter(Position=1, ValueFromPipeline=$true)]
        [Switch]
        $NoNewLine
        )

    Process{
        Write-TextColor $Information Blue $NoNewLine
    }
}

Main.ps1

Import-Module -Name "$($PSCommandPath | Split-Path)/Functions.ps1" -Force
Write-InfoBlue "Printed from imported function."

Console Output Solution1


• SOLUTION 2.

Functions.ps1 Contains the full functions. This script needs to be imported by the main script. Same as Solution1's Script.

Main.ps1
This script contains 3 functions.
1. Get-ScriptFunctionNames. It returns an array of String, each element is the name of function.
2. Get-ScriptFunctionDefinitions. It returns an array of String, each element is the complete function.
3. Get-AmalgamatedScriptFunctionDefinitions. It returns only one String, the result of joinning all elements returned by the function Get-ScriptFunctionDefinitions. All 3 need the same param, the path of the Powershell script file.

We will test the 3 functions on this file.

This script doesn't use Import-Module cmdlet.

function Get-ScriptFunctionNames {
    param (
        [parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [AllowEmptyString()]
        [AllowNull()]
        [System.String]
        $Path
    )
    Process{
        [System.Collections.Generic.List[String]]$FX_NAMES = New-Object System.Collections.Generic.List[String]

        if(!([System.String]::IsNullOrWhiteSpace($Path)))
        { 
            Select-String -Path "$Path" -Pattern "function" | 
            ForEach-Object {
                [System.Text.RegularExpressions.Regex] $regexp = New-Object Regex("(function)( +)([\w-]+)")
                [System.Text.RegularExpressions.Match] $match = $regexp.Match("$_")
                if($match.Success)
                {
                    $FX_NAMES.Add("$($match.Groups[3])")
                }   
            }    
        }
        return ,$FX_NAMES.ToArray()
    }
}
function Get-ScriptFunctionDefinitions {

    param (
        [parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [AllowEmptyString()]
        [AllowNull()]
        [System.String]
        $Path
    )
    Process{
        [System.Collections.Generic.List[String]]$FX_DEFS = New-Object System.Collections.Generic.List[String]
        if(!([System.String]::IsNullOrWhiteSpace($Path)))
        {
            Import-Module -Name "$Path" -Force 
        }
        $names = Get-ScriptFunctionNames -Path $Path
        Get-ChildItem "function:" | Where-Object { $_ -in $names } | ForEach-Object{
            $FX_DEFS.Add("function $($_.Name) { $($_.Definition) };")
        }
        return ,$FX_DEFS.ToArray()
    }
}

function Get-AmalgamatedScriptFunctionDefinitions {

    param (
        [parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [AllowEmptyString()]
        [AllowNull()]
        [System.String]
        $Path
    )
    Process{
        [System.String]$FX_DEFS = ""
        Get-ScriptFunctionDefinitions -Path $Path | 
        ForEach-Object {
            $FX_DEFS += "$_$([System.Environment]::NewLine)$([System.Environment]::NewLine)"
        }
        return $FX_DEFS
    }
}
Write-Host
[System.String[]]$FX_NAMES = Get-ScriptFunctionNames -Path "$($PSCommandPath | Split-Path)/Functions.ps1"
[System.String[]]$FX_DEFS = Get-ScriptFunctionDefinitions -Path "$($PSCommandPath | Split-Path)/Functions.ps1"
[System.String] $FX_ALL_DEFS = Get-AmalgamatedScriptFunctionDefinitions -Path "$($PSCommandPath | Split-Path)/Functions.ps1"

. ([System.Management.Automation.ScriptBlock]::Create($FX_ALL_DEFS)) #The functions in Functions.ps1 are created in the current script.
Write-InfoBlue "Printed from imported function."

Check: Dot Sourcing operator.
Dot Source
ScriptBlock class

Console Output Solution1

Adding following to Main.ps1 we can test the 3 functions.

Write-Host "• TEST 1" -ForegroundColor Magenta
$FX_NAMES | 
ForEach-Object {
    Write-Host $_
}
Write-Host

Write-Host "• TEST 2" -ForegroundColor Magenta
foreach($value in $FX_DEFS)
{
    Write-Host $value
    Write-Host "███" -ForegroundColor DarkGray
}
Write-Host

Write-Host "• TEST 3" -ForegroundColor Magenta
Write-Host $FX_ALL_DEFS

Console Output Solution2-1 Solution2-2 Solution2-3



3. EXTRA SOLUTION - SPECIAL CASE When obtaining the definitions of the functions it can be useful to use them to invoke commands on remote computers that do not contain the definition of the local functions, we simply obtain the definitions and pass them through parameters as follows.

If you need run powershell commands in remote computer, please install Powershell Core on remote computer.

Local File
PrintColorFunctions.ps1
Same content as Solution 1's script.

Local file Main.ps1

$FX_ALL_DEFS = Get-AmalgamatedScriptFunctionDefinitions -Path "$($PSCommandPath | Split-Path)/Functions.ps1"
$R_HOST = "192.168.211.1" 
$R_USERNAME = "root" 
$R_PORT = "2222" 
$R_SESSION = New-PSSession -HostName $R_USERNAME@$($R_HOST):$R_PORT #//Connected by OpenSSL, private key added to OpenSSH Session Agent. If you need login by password, remove the private key from OpenSSH Session Agent and write as follows user:pass@host:port $($R_USERNAME):$R_PASS@$($R_HOST):$R_PORT
Invoke-Command -ArgumentList $FX_ALL_DEFS,"Joma" -Session $R_SESSION -ScriptBlock{ #// -ArgumentList function definitions and a name.
    Param($fxs, $name) #// Param received by remote context.
    . ([System.Management.Automation.ScriptBlock]::Create($fxs)) #//Creating function definitions in remote script context.

    Clear-Host
    Write-Host "Running commands in my remote Linux Server" -ForegroundColor Green #//Powershell Core cmdlet
    #//We can use Write-InfoBlue on this script context.
    Write-InfoBlue ($(Get-Content /etc/*-release | Select-String -Pattern "^PRETTY_NAME=.*" ).ToString().Split("=")[1]) #//Created function + cmdlets combo
    Write-InfoBlue $(uname -a) #//Created function + Native remote command
    Write-InfoBlue $(whoami) #//Cmdlet + Native remote command
    printf "Hello! $name" #//Native remote command
    Write-InfoBlue "Local function executed in remote context"
}
Remove-PSSession -Session $R_SESSION

Console Output Solution3

3 Comments

Wow! This is one heck of an answer. No idea why this is not upvoted. It really does the print the content of each function, without other "crud". My only wish was that is was much shorter and easier to use. I was looking for something equivalent to the typeset -f command in Bash.
It also seem that pwsh is now doing some internal alphabetic sorting.
Re solution 1: No point in using Import-Module with .ps1 files - use dot-sourcing instead (e.g, . $PSScriptRoot/Functions.ps1). Re solution 2: No point in re-inventing the wheel: use a .psm1 file instead, and use (Import-Module $PSScriptRoot/Functions.psm1 -Force -PassThru).ExportedFunctions. Re solution 3: This is unrelated to the question at hand, given that any defined function's script block can be passed as a string to a remote session. /cc @not2qubit.
2

In fact when Powershell has a method to do it, why reinvent the wheel? We can simply do it by making it a Module:

cp .\scriptFile.ps1 .\scriptfile.psm1

Then import the module

import-module .\scriptfile.psm1

Now, get all the functions

get-command -module scriptfile

function SomeFunction
function AnotherFunction

1 Comment

That was a sneaky one :)
2

Here is how you can import functions from another file into the current scope without executing the file. Pretty useful when unit testing with Pester.

After doing the following you can just call the functions by their names.

function Get-Functions($filePath)
{
    $script = Get-Command $filePath
    return $script.ScriptBlock.AST.FindAll({ $args[0] -is [Management.Automation.Language.FunctionDefinitionAst] }, $false)
}

Get-Functions ScriptPath.ps1 | Invoke-Expression

This works by invoking the Powershell parser directly. No need for complex regex parsing or tokenization.

Answer was inspired by this thread: Is there a way to show all functions in a PowerShell script?

Comments

2

To build on the answer of @Nova you just need to get the Property Name as well to extract the function names as well.

$Script = Get-Command .\ScriptFile.ps1

$FunctionNames = $Script.ScriptBlock.AST.FindAll(
  { $args[0] -is [Management.Automation.Language.FunctionDefinitionAst] },
  $false
).Name

$FunctionNames

Comments

1

I needed to get the name of the functions from a multi-function script. This is what I came up with. Based on this maybe someone can provide a shorter version.

# Get text lines from file that contain 'function' 
$functionList = Select-String -Path $scriptName -Pattern "function"

# Iterate through all of the returned lines
foreach ($functionDef in $functionList)
{
    # Get index into string where function definition is and skip the space
    $funcIndex = ([string]$functionDef).LastIndexOf(" ") + 1

    # Get the function name from the end of the string
    $FunctionExport = ([string]$functionDef).Substring($funcIndex)

    Write-Output $FunctionExport
}

I came up with a shorter version to find and list the functions in a list of scripts. It is not perfect and will have an issue because the pattern is just the word "Function" and if this method will assume it has found a function anywhere it finds the keyword.

To iterate through the files and get a list I used the 'Get-ChildItem' function and passed a path and file filter specification using the recursive option.

That gets passed into the 'Select-String' through the pipeline and looks for the 'Function' keyword. It is not case-sensitive and will accept "Function" or "function". there is a case sensitive option if needed by adding the "-CaseSensitive".

The output is then iterated over to get the actual function names. The "Line" member is a string type and I used the "Substring" option starting at position 9, which is the length just passed the "function" identifier.

$scriptPath = "c:\\Project"
$scriptFilter = "*.ps1"

( Get-ChildItem -Path $scriptPath -Filter $scriptFilter -Recurse | Select-String -Pattern "function" ) | %{ $_.Line.Substring(9) }

Comments

1

This is a one-liner solution if anyone is still looking for it

Get-Content "C:\Path\To\File.ps1" | Select-String -Pattern "function\s+([^\s{]+)" | Foreach-Object { $_.Matches.Groups[1].Value }

Explanation:

  • Get-Content "C:\Path\To\File.ps1" reads the contents of the specified file, and outputs the content as an array of strings.
  • Select-String -Pattern "function\s+([^\s{]+)" searches the input text for matches to the specified regular expression pattern. This pattern matches the keyword function followed by one or more whitespace characters, followed by one or more non-whitespace characters until the opening curly brace symbol, and captures the matched non-whitespace characters in a group.
  • Foreach-Object { $_.Matches.Groups[1].Value } loops through each match found by Select-String and extracts the value of the first capture group (the function name) using the $_ variable, which represents the current object in the pipeline. The extracted function names are then printed to the console.

Comments

1

As I noted in another answer, another common way may be to use Get-Command and then filter the ScriptBlock.File to point to the current execution script $MyInvocation.MyCommand.Definition.

Get-Command returns a list of FunctionInfo objects, you can use this further to filter by function attributes, predefined or custom (eg. CmdLetBinding)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.