1

I have several PS1s with functions in them in the format "Invoke-T1234", "Invoke-T1235", etc. I'd like to create a function in a PS Module that allows the user to call one of these PS1s through an argument, e.g. "Invoke-Script -Script T1234" and it then runs "Invoke-T1234". Is it possible to pass in a variable to the function that calls the scripts so I don't have to create several "If" clauses?

E.g.

function Invoke-Script {
        param (
        [Parameter(Mandatory = $false)]       
            [string]$Script= ""
        )
    If ($Script){
    Invoke-$Script    
    }

Obviously this isn't working, as PowerShell is interpreting $Script literally instead of the value.

I know I can do

If ($Script -eq T1234){
Invoke-T1234}

But there are many PS1s, so I'd like to do this in the least amount of code possible.

Ryvik
  • 363
  • 1
  • 3
  • 14
  • Try `invoke-Expression "Invoke-$Script"`. It also may help to provide the full path – Abraham Zinala Oct 07 '21 at 12:26
  • 1
    @AbrahamZinala [`Invoke-Expression` (`iex`) should generally be avoided](https://stackoverflow.com/a/51252636/45375); definitely [don't use it to invoke an external program or PowerShell script](https://stackoverflow.com/a/57966347/45375). The simpler and safer alternative here is to use `& "Invoke-$Script"` – mklement0 Oct 07 '21 at 13:31

2 Answers2

2

You could use Get-Command to discover the target function based on the argument:

function Invoke-TScript
{
  param(
    [int]$ScriptID
  )

  $cmd = Get-Command "Invoke-T${ScriptID}" -ErrorAction SilentlyContinue
  if($cmd){
    & $cmd @args
  }
  else {
    Write-Error "No function named 'Invoke-T${ScriptID}' available"
  }
}

Splatting the automatic $args variable inside the function means any additional parameter arguments will be passed to the target script.

Now you can do:

Invoke-TScript -ScriptID 1234 -ScriptParam 'script argument'

which will then execute:

Invoke-T1234 -ScriptParam 'script argument'
Mathias R. Jessen
  • 157,619
  • 12
  • 148
  • 206
2

Using Get-Command to discover and test the existence of commands, as shown in Mathias R. Jessen's helpful answer, is definitely an option, but if you know the specific name of the command (function), as in your case, it is sufficient to use &, the call operator, which can invoke commands specified via a(n expandable) string, variable or expression:

# Use an expandable string to construct the target command name
# and invoke it with &
# If needed, pass arguments as you usually would; e.g.
#   & "Invoke-$Script" foo bar
& "Invoke-$Script"
  • You don't even strictly need quoting (the enclosing "...") here: & Invoke-$Script will do.

    • Because the arguments passed to & are parsed in argument(-parsing) mode, PowerShell generally treats unquoted arguments that include variable references (such as Invoke-$Script) implicitly like an expandable string, i.e. as if they had explicitly been enclosed in "...". However, there are pitfalls, so it's a good habit to form to use "..." explicitly - see this answer for background information.
  • Conversely, when quoting is used, use of & is required, even with verbatim strings, whereas unquoted, verbatim command names and paths do not require & (but may be used with it); e.g. c:\foo\script.ps1 works without & too, but
    & 'c:\foo\script file.ps1' requires &. See this answer for background information.

In the context of your code, with an error handler:

function Invoke-Script {
        param (
        [Parameter(Mandatory = $false)]       
            [string]$Script= ""
        )
    if ($Script) {
      try {
        & "Invoke-$Script"
      } catch {
        throw "Failed to call 'Invoke-$Script': $_"
      }
    }
}
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    I will add that this answer does **not** create a command injection vulnerability, even though it looks like it might. This is because the `&` operator will treat anything passed into the first argument as the literal executable/script names. For example, if you pass `"234; whoami"`, the script will error out instead of executing `Invoke-234` then `whoami`. This prevents anyone who may have access to the `$Script` parameter from just executing whatever they want, which is good. – spicy.dll Oct 07 '21 at 15:05
  • Thanks, @spicy.dll; just to make what is implied by your comment explicit: `&` with a dynamically constructed command name / path is the safe alternative to `Invoke-Expression` (`iex`), whose use is rarely justified and generally [should not be used to invoke an external program or PowerShell command](https://stackoverflow.com/a/57966347/45375). – mklement0 Oct 07 '21 at 15:29