Search code examples
powershell

How can I choose between $MyInvocation.ScriptName and $MyInvocation.PSCommandPath in PowerShell?


The following variables exist in PowerShell 5.1+, both of which seem to offer the full path to the 'invoking' script (i.e. the script that called this one, e.g. from a module, a function, or an external script):

$MyInvocation.ScriptName
$MyInvocation.PSCommandPath

I can find official documentation referencing both as options:

...[$MyInvocation.]ScriptName refers to the name of the script that called you (Yes – we probably should have chosen a better name)

[$MyInvocation.]PSCommandPath contains the full path and name of the script that called or invoked the current script.

Which one should I use? My current theories & observations:

Any other weird edge-cases or hidden 'gotchas' with either of these that I should be aware of? Or should I be aware of any situations where one should be preferred over the other?


Solution

  • tl;dr

    • For symmetry with the automatic variable of the same name, $PSCommandPath (which reflects the running script's full file path), it is better to use $MyInvocation.PSCommandPath (which reflects the path of the calling script, if any).

      • That said, its older equivalent, $MyInvocation.ScriptName, will continue to work, but I suggest avoiding it for its obscure name; on the plus side, however, it isn't plagued by the $null quirk you've discovered (see next).
    • Unfortunately, $MyInvocation.PSCommandPath latter has a quirk/bug in that when there is no calling script, it reports $null rather than the '' (the empty string) - see GitHub issue #23828.

      • You can avoid this quirk in a future-proof manner by testing for this case in a way that works equally with $null and ''; e.g.:
        • $noParentScript = -not $MyInvocation.PSCommandPath - that is, you can rely on PowerShell's to-Boolean coercion, which coerces both $null and '' to $false
        • $noParentScript = $MyInvocation.PSCommandPath -like '' - in the context of wildcard matching with -like, $null is coerced to ''

    Background information

    The automatic $PSCommandPath and $PSScriptRoot variables were introduced in PowerShell v3 (released in 2012), to reflect the running script's full file path and full directory path, respectively.

    Aside from being simpler than what you needed to do in v1 and v2 - $MyInvocation.MyCommand.Path and Split-Path -Parent $MyInvocation.MyCommand.Path - these variables work consistently, namely also in (script) modules.

    For symmetry with the newly introduced automatic variables in v3, $MyInvocation was given new properties with the same name, i.e. $MyInvocation.PSCommandPath and $MyInvocation.PSScriptRoot, which report the analogous information about the running script's caller, i.e. the invoking script.

    As such, these properties may not have values in a script invoked directly from an interactive session or via Invoke-Expression.

    Just like $PSCommandPath now (mostly) duplicates the older $MyInvocation.MyCommand.Path, so does $MyInvocation.PSCommandPath (mostly) with respect to the older - and unfortunately named - $MyInvocation.ScriptName

    In v3+ (v2 is fortunately rarely encountered anymore these days), it is therefore better to use the following:

    • to reflect on the running script: $PSCommandPath and $PSScriptRoot
    • to reflect on the calling script, if any: $MyInvocation.PSCommandPath and $MyInvocation.PSScriptRoot

    As you've discovered, there's a quirk in that the $MyInvocation.PSCommandPath reports $null rather than '' (the empty string) if there is no calling script, unlike $MyInvocation.PSScriptRoot (and unlike $MyInvocation.PSCommandPath's older equivalent, $MyInvocation.ScriptName).

    This should be considered a bug - a simple oversight in the implementation that makes for an awkward asymmetry. I've reported it in GitHub issue #23828, though I'm not holding my breath that it will be fixed, especially since there may be existing code that test for $null explicitly.