How to implement tasks in powershell based on a script parameter (i.e. msbuild, gradle)

Viewed 58

Is it possible to implement a task-style system in PowerShell so that you could do the following:

.\script.ps1 build # runs build() function
.\script.ps1 publish # runs publish() function

I was hoping to use PowerShell for building/publishing an application rather than requiring the user to install gradle/msbuild and wanted to avoid needing to update a switch statement for every function added.

Currently it is messy like this:

#!/usr/bin/env powershell

param(
    $action = 'build'
)

function build() {
    echo 'build';
}

function publish() {
    echo 'publish';
}

switch($action) {
    'build' { build; }
    'publish' { publish; }
}
1 Answers

You can use &, the call operator, to execute a command (such as a function) whose name (or, in the case of commands that are external executables, path) is stored in a variable.

However, as marsze points out, blindly invoking a function by name means that you could end up calling a command (of any form) that is unrelated to your script, which can be unintentional at best, and a security risk at worst.

Thankfully, PowerShell offers rich reflection capabilities and also exposes its own parsing API, so you can determine what functions are actually defined in the script itself, and only permit invocation of those.

Note that the code below also defines a list task, which lists the names of all tasks (functions) defined in the script.

#!/usr/bin/env powershell

param(
  [string] $task = 'build'
)

# -- Define a function for each task.
#    Note that functions must be defined *before* they are called in PowerShell.

function list {
  "Available tasks:"
  $functionNames
}

function build {
  'build'
}

function publish {
  'publish'
}

# -- Invoke the specified task by invoking the function of the same name.

# Get the list of the names of all functions defined in this script.
$functionNames = $MyInvocation.MyCommand.ScriptBlock.Ast.FindAll(
   { $args[0] -is [Management.Automation.Language.FunctionDefinitionAst] },
  $false
).Name

if ($task -in $functionNames) {

  # A known task - invoke it.
  & $task

}
else {

  # Report a script-terminating error, if the task name is uknown.
  throw "Unknown task: $task"
}
Related