Powershell: Using `params` with a Shebang?

80 views Asked by At

The params keyword lets you add parameters to a Powershell script, like so:

param (
  [switch] $UseDefaults = $false
)

But it has to be added to the first line of the script. A shebang allows us 'nix users to have an easier, more intuitive time starting the script (./script.ps1 instead of pwsh script.ts1) and that can be done by adding this to the start of the script:

#!/usr/bin/env pwsh

However, that also has to be added to the first line of the script. Hence, these two features don't seem to be able to work together. Is there a way to use both these features in one script?

1

There are 1 answers

2
mklement0 On BEST ANSWER

Comments and using statements are allowed to precede param() declarations, and since a shebang line is technically a comment, it works just fine; for example:

#!/usr/bin/env -S pwsh -noprofile

param(
  [string] $Foo
)

# Print all arguments that were passed
$PSBoundParameters

If you save the above to, say, sample.ps1, make it executable with chmod a+x sample.ps1, then invoke with ./sample.ps1 -Foo bar, you'll see:

Key Value
--- -----
Foo Bar

Important: As you note, in order for Unix-like platforms to recognize a shebang line as such, the file must not have a BOM. That is, be sure to save your files as UTF-8 without a BOM.[1]

Note:

  • The use of /usr/bin/env to launch pwsh assumes that the latter is in one of the directories listed in the PATH environment variable.

  • -noprofile suppresses loading of PowerShell's profile files, which - unfortunately - are loaded by default.

  • To support passing this extra argument via the shebang line, -S must be passed to env, for technical reasons (the system passes everything after the executable path as a single argument to that executable; -S makes env split that string into individual arguments).

  • There is no technical need to use filename extension .ps1 in naming your script; in fact, if you want your script to simply function as a general purpose CLI that can be called from any shell, you may choose to use no filename extension, e.g. to simply name your script sample.

    • Conversely, however, if you want to retain the ability to execute your script in-process from inside a PowerShell session, extension .ps1 is a must.

    • A caveat is that calling shebang line-based scripts from outside PowerShell or - also from inside PowerShell - a shebang line-based script without extension .ps1 invariably has syntax and data-type limitations (the latter also invariably run in a child process), due to the fact that given a shebang line results in invocation via the -File parameter of pwsh, the PowerShell (Core) CLI; notably:

  • Generally, it seems that shebang line-based PowerShell scripts haven't really caught on (yet?), possibly due to a combination of the following factors:

    • The relatively high startup cost of the pwsh CLI.
    • The aforementioned syntax and data-type limitations.
    • Various long-standing bugs that haven't been addressed; see this GitHub query; note that you'll have to click on the Closed link to also see still-relevant issues that have simply been closed due to inactivity.

Guidance re if and when to use shebang line-based PowerShell scripts:
  • Do NOT create shebang line-based scripts...

    • ... if you're primarily calling your scripts from inside PowerShell.

    • PowerShell runs .ps1 scripts in-process, which not only means faster execution, but also enables rich (non-serialized) data type-support based on .NET types.

    • PowerShell permits invocation of .ps1 scripts even without their filename extension, so that you can call ./sample.ps1 as just ./sample, for instance.

  • DO create shebang line-based scripts...

    • ... if you primarily use a different shell, say bash, but need to invoke PowerShell-based code on occasion, and want the convenience of invoking via, say, ./sample.ps1 rather than pwsh -noprofile sample.ps1

    • ... if you want to implement a general-purpose CLI whose implementation language just so happens to be PowerShell.

    • In either case:

      • The script needs to be designed with the syntax and data-type limitations of a -File-based PowerShell CLI invocation in mind.

      • Callers must be aware of PowerShell's parameter syntax, which generally differs from that of POSIX (single-letter option names such as -f) and GNU utilities (with multi-letter option names requiring --, e.g. --color).

        • That said, even though PowerShell-idiomatically parameters are multi-letter but only require - (e.g. -Path) in -File-based invocations (only), which includes shebang line-based invocations, -- is accepted too (e.g, --Path).

        • However, an important difference is that in PowerShell -? rather than --h or --help must be used to invoke command-line help.


[1] It is only Windows PowerShell, the legacy, Windows-only edition of PowerShell that requires a BOM in order to recognize a UTF-8 file as such.