This function has been written dozens of times, in dozens of places, by dozens of bloggers, but I keep finding myself looking for it, so I thought I’d make myself a note. But I also discovered that there’s a problem with it.
If you’re writing a script in a module, it’s easy to use $PsScriptRoot to get the current script path. For others situations there’s:
1 2 3 4 5 6 |
# The wrong way! function Get-ScriptDirectory { $Invocation = (Get-Variable MyInvocation -Scope 1).Value Split-Path $Invocation.MyCommand.Path } |
This function, and ones like it, appear in loads of blog posts, and it generally solves the problem, but it doesn’t always work, though, as it contains a flaw.
The problem is -Scope 1. The this switch tells the script to look at the parent scope. This works as long as you have the Get-ScriptDirectory function at the top level of your script and you call it from that top level.
Here’s an example script demonstrating the problem:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
"In Script (scope 1): [$((Get-Variable MyInvocation -Scope 1).Value.MyCommand.Path)]" "In Script (scope 0): [$((Get-Variable MyInvocation -Scope 0).Value.MyCommand.Path)]" "In Script (scope script): [$((Get-Variable MyInvocation -Scope Script).Value.MyCommand.Path)]" function t() { "In t() (scope 1): [$((Get-Variable MyInvocation -Scope 1).Value.MyCommand.Path)]" } function t2() { "In t2() (scope script): [$((Get-Variable MyInvocation -Scope script).Value.MyCommand.Path)]" } function q() { "in q()" t } function q2() { "in q2()" t2 } t q t2 q2 |
Invoked as: PS> ./invoctest.ps1you get:
In Script (scope 1): []
In Script (scope 0): [F:tempinvoctest.ps1]
In Script (scope script): [F:tempinvoctest.ps1]
In t() (scope 1): [F:tempinvoctest.ps1]
in q()
In t() (scope 1): []
In t2() (scope script): [F:tempinvoctest.ps1]
in q2()
In t2() (scope script): [F:tempinvoctest.ps1]
Notice how when the call to t() is nested inside q() the function breaks, and you get nothing. The reason is that -Scope 1 is no longer looking at the script scope, but looking at the scope inside function t(). You’d need to have -Scope 2 if the function t() is invoked from inside q(). The higher the nesting level the higher the value to give to -Scope.
You could look at the call stack, if you wanted to, but my script above already includes the answer. You need to use the “script” option for -Scope. That will ensure you always get the correct invocation variable.
This even works when the script is dot-sourced, or run in a debugger, like in PowerGUI.
The corrected function is therefore:
1 2 3 4 5 |
function Get-ScriptDirectory { $Invocation = (Get-Variable MyInvocation -Scope script).Value Split-Path $Invocation.MyCommand.Path } |