How can I find the source path of an executing script?

PowershellPowershell 1.0

Powershell Problem Overview


I want to be able to tell what path my executing script was run from.
This will often not be $pwd.

I need to call other scripts that are in a folder structure relative to my script and while I could hard code the paths, that's both distasteful and a bit of a pain in the neck when trying to promote from "dev" to "test" to "production".

Powershell Solutions


Solution 1 - Powershell

The ubiquitous script originally posted by Jeffrey Snover of the PowerShell team (given in Skyler's answer) and the variations posted by Keith Cedirc, and EBGreen, all suffer from a serious drawback--whether the code reports what you expect depends on where you call it!

My code below overcomes this problem by simply referencing script scope instead of parent scope:

function Get-ScriptDirectory
{
    Split-Path $script:MyInvocation.MyCommand.Path
}

To illustrate the problem, I created a test vehicle that evalutes the target expression in four different ways. (The bracketed terms are the keys to the following result table.)

  1. inline code [inline]
  2. inline function, i.e. function in the main program [inline function]
  3. Dot-sourced function, i.e. the same function moved to a separate .ps1 file [dot source]
  4. Module function, i.e. the same function moved to a separate .psm1 file [module]

The last two columns show the result of using script scope (i.e. $script:) or with parent scope (with -scope 1). A result of "script" means that the invocation correctly reported the location of the script. The "module" result means the invocation reported the location of the module containing the function rather than the script that called the function; this indicates a drawback of both functions that you cannot put the function in a module.

Setting the module issue aside the remarkable observation from the table is that using the parent scope approach fails most of the time (in fact, twice as often as it succeeds).

table of input combinations

Finally, here is the test vehicle:

function DoubleNested()
{
	"=== DOUBLE NESTED ==="
	NestCall
}

function NestCall()
{
	"=== NESTED ==="
	"top level:"
	Split-Path $script:MyInvocation.MyCommand.Path
	#$foo = (Get-Variable MyInvocation -Scope 1).Value
	#Split-Path $foo.MyCommand.Path
	"immediate func call"
	Get-ScriptDirectory1
	"dot-source call"
	Get-ScriptDirectory2
	"module call"
	Get-ScriptDirectory3
}

function Get-ScriptDirectory1
{
	Split-Path $script:MyInvocation.MyCommand.Path
	# $Invocation = (Get-Variable MyInvocation -Scope 1).Value
	# Split-Path $Invocation.MyCommand.Path
}

. .\ScriptDirFinder.ps1
Import-Module ScriptDirFinder -force

"top level:"
Split-Path $script:MyInvocation.MyCommand.Path
#$foo = (Get-Variable MyInvocation -Scope 1).Value
#Split-Path $foo.MyCommand.Path

"immediate func call"
Get-ScriptDirectory1
"dot-source call"
Get-ScriptDirectory2
"module call"
Get-ScriptDirectory3

NestCall
DoubleNested

Contents of ScriptDirFinder.ps1:

function Get-ScriptDirectory2
{
	Split-Path $script:MyInvocation.MyCommand.Path
#	$Invocation = (Get-Variable MyInvocation -Scope 1).Value
#	Split-Path $Invocation.MyCommand.Path
}

Contents of ScriptDirFinder.psm1:

function Get-ScriptDirectory3
{
	Split-Path $script:MyInvocation.MyCommand.Path
#	$Invocation = (Get-Variable MyInvocation -Scope 1).Value
#	Split-Path $Invocation.MyCommand.Path
}

I am not familiar with what was introduced in PowerShell 2, but it could very well be that script scope did not exist in PowerShell 1, at the time Jeffrey Snover published his example.

I was surprised when, though I found his code example proliferated far and wide on the web, it failed immediately when I tried it! But that was because I used it differently than Snover's example (I called it not at script-top but from inside another function (my "nested twice" example).)

2011.09.12 Update

You can read about this with other tips and tricks on modules in my just-published article on Simple-Talk.com: Further Down the Rabbit Hole: PowerShell Modules and Encapsulation.

Solution 2 - Powershell

You tagged your question for Powershell version 1.0, however, if you have access to Powershell version 3.0 you know have $PSCommandPathand$PSScriptRootwhich makes getting the script path a little easier. Please refer to the "OTHER SCRIPT FEATURES" section on this page for more information.

Solution 3 - Powershell

We've been using code like this in most of our scripts for several years with no problems:

#--------------------------------------------------------------------
# Dot source support scripts
#--------------------------------------------------------------------
$ScriptPath = $MyInvocation.MyCommand.Path
$ScriptDir  = Split-Path -Parent $ScriptPath
. $ScriptDir\BuildVars.ps1
. $ScriptDir\LibraryBuildUtils.ps1
. $ScriptDir\BuildReportUtils.ps1

Solution 4 - Powershell

I ran into the same issue recently. The following article helped me solve the problem: http://blogs.msdn.com/powershell/archive/2007/06/19/get-scriptdirectory.aspx

If you're not interested in how it works, here's all the code you need per the article:

function Get-ScriptDirectory
{
$Invocation = (Get-Variable MyInvocation -Scope 1).Value
Split-Path $Invocation.MyCommand.Path
}

And then you get the path by simply doing:

$path = Get-ScriptDirectory

Solution 5 - Powershell

I think you can find the path of your running script using

$MyInvocation.MyCommand.Path

Hope it helps !

Cédric

Solution 6 - Powershell

This is one of those oddities (to my mind at least) in PS. I'm sure there is a perfectly good reason for it, but it still seems odd to me. So:

If you are in a script but not in a function then $myInvocation.InvocationName will give you the full path including the script name. If you are in a script and inside a function then $myInvocation.ScriptName will give you the same thing.

Solution 7 - Powershell

Thank you msorens! This really helped me with my custom module. In case anyone is interested in making their own, here is how mine is structured.

MyModule (folder)
 - MyModule.psd1 (help New-ModuleManifest)
 - MyScriptFile.ps1 (ps1 files are easy to test)

You then reference MyScriptFile.ps1 in MyModule.psd1. Referencing the .ps1 in the NestedModules array will place the functions in the module session state rather than the global session state. (How to Write a Module Manifest)

NestedModules = @('.\MyScriptFile.ps1','.\MyOtherScriptFile.ps1')

Content of MyScriptFile.ps1

function Get-ScriptDirectory {
    Split-Path $script:MyInvocation.MyCommand.Path
}

try {
    Export-ModuleMember -Function "*-*"
}
catch{}

The try/catch hides the error from Export-ModuleMember when running MyScriptFile.ps1

Copy the MyModule directory to one of the paths found here $env:PSModulePath

PS C:\>Import-Module MyModule
PS C:\>Get-Command -Module MyModule

CommandType     Name                                               ModuleName                                                                                                                                                
-----------     ----                                               ----------                                                                                                                                                
Function        Get-ScriptDirectory                                MyModule  

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKevin BuchanView Question on Stackoverflow
Solution 1 - PowershellMichael SorensView Answer on Stackoverflow
Solution 2 - PowershellMichael KelleyView Answer on Stackoverflow
Solution 3 - PowershellKeith HillView Answer on Stackoverflow
Solution 4 - PowershellSkylerView Answer on Stackoverflow
Solution 5 - PowershellCédric RupView Answer on Stackoverflow
Solution 6 - PowershellEBGreenView Answer on Stackoverflow
Solution 7 - PowershellCoding101View Answer on Stackoverflow