686

Whenever I need to reference a common module or script, I like to use paths relative to the current script file. That way, my script can always find other scripts in the library.

So, what is the best, standard way of determining the directory of the current script? Currently, I'm doing:

$MyDir = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.Definition)

I know in modules (.psm1) you can use $PSScriptRoot to get this information, but that doesn't get set in regular scripts (i.e. .ps1 files).

What's the canonical way to get the current PowerShell script file's location?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Aaron Jensen
  • 25,861
  • 15
  • 82
  • 91
  • 1
    possible duplicate of [How can i get the file system location of a powershell script?](http://stackoverflow.com/questions/3667238/how-can-i-get-the-file-system-location-of-a-powershell-script) – JohnC Apr 14 '14 at 11:09
  • 4
    Your solution $PSScriptRoot **IS** the right one, no need to read further. – Timo Nov 19 '20 at 08:58

14 Answers14

1129

PowerShell 3+

# This is an automatic variable set to the current file's/module's directory
$PSScriptRoot

PowerShell 2

Prior to PowerShell 3, there was not a better way than querying the MyInvocation.MyCommand.Definition property for general scripts. I had the following line at the top of essentially every PowerShell script I had:

$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 1
    What does `Split-Path` used for here? – CMCDragonkai Dec 09 '16 at 09:06
  • 12
    `Split-Path` is used with the `-Parent` parameter to return the current directory without the currently executing script's name. – goodman Dec 09 '16 at 14:55
  • 6
    Note: with PowerShell on Linux/macOS, your script must have a .ps1 extension for PSScriptRoot/MyInvocation etc to be populated. See bug report here: https://github.com/PowerShell/PowerShell/issues/4217 – Dave Wood Mar 11 '18 at 05:11
  • 8
    Quibble with a potentially interesting aside: The closer v2- approximation of `$PSScriptRoot` is (`Split-Path -Parent` applied to) `$MyInvocation.MyCommand.Path`, not `$MyInvocation.MyCommand.Definition`, though in the top-level scope of a script they behave the same (which is the only sensible place to call from for this purpose). When called inside a _function_ or _script block_, the former returns the empty string, whereas the latter returns the function body's / script block's _definition_ as a string (a piece of PowerShell source code). – mklement0 Sep 04 '19 at 14:24
  • Split-Path documentation says "The Parent parameter is the default split location parameter." and IME `-Parent` is indeed not necessary. – Tim Sparkles Mar 23 '21 at 01:24
75

If you are creating a V2 Module, you can use an automatic variable called $PSScriptRoot.

From PS > Help automatic_variable

$PSScriptRoot
       Contains the directory from which the script module is being executed.
       This variable allows scripts to use the module path to access other
       resources.
Zombo
  • 1
  • 62
  • 391
  • 407
Andy Schneider
  • 8,516
  • 6
  • 36
  • 52
  • 22
    This is what you need in PS 3.0: `$PSCommandPath Contains the full path and file name of the script that is being run. This variable is valid in all scripts.` – CodeMonkeyKing Mar 14 '13 at 06:02
  • 3
    Just tested $PSScriptRoot and working as expected. However, it would give you an empty string if you run it at command line. It would only give you result if used in a script and script is executed. That's what it is meant for ..... – Farrukh Waheed Sep 17 '13 at 04:22
  • 4
    I'm confused. This answer says to use PSScriptRoot for V2. Another answer says PSScriptRoot is for V3+, and to use something different for v2. –  May 09 '14 at 16:35
  • 7
    @user $PSScriptRoot in v2 is only for *modules*, if you're writing 'normal' scripts not in a module you need $MyInvocation.MyCommand.Definition, see top answer. – yzorg Jan 16 '15 at 15:01
  • Also, $MyInvocation.InvocationName works for *scripts* to return their location. – Daz C Feb 15 '16 at 17:55
  • @yzorg That's not true. It works in any type of PowerShell script if the interpreter is up to PowerShell 3.0. It works in all of my .ps1 files. Doesn't have to be defined as a module. – ZaxLofful Jul 28 '16 at 20:30
  • 1
    @Lofful I said in "v2" it was only defined for modules. You're saying it's defined outside modules in v3. I think we're saying the same thing. : ) – yzorg Aug 01 '16 at 13:59
  • @yzorg: Pssh, who uses v2. I missed that part when I was reading it. :) – ZaxLofful Aug 01 '16 at 18:36
41

For PowerShell 3.0

$PSCommandPath
    Contains the full path and file name of the script that is being run. 
    This variable is valid in all scripts.

The function is then:

function Get-ScriptDirectory {
    Split-Path -Parent $PSCommandPath
}
Zombo
  • 1
  • 62
  • 391
  • 407
CodeMonkeyKing
  • 4,556
  • 1
  • 32
  • 34
  • 22
    Even better, use $PSScriptRoot. It is the current file's/module's directory. – Aaron Jensen Apr 29 '14 at 17:57
  • 3
    This command includes the filename of the script, which threw me off until I realized that. When you are wanting the path, you probably don't want the script name in there too. At least, I can't think of a reason you would want that. $PSScriptRoot does not include the filename (gleaned from other answers). – YetAnotherRandomUser Jun 21 '17 at 11:26
  • 1
    $PSScriptRoot is empty from a regular PS1 script. $PSCommandPath works, though. Behavior of both is expected per the descriptions given in other posts. Can also just use [IO.Path]::GetDirectoryName($PSCommandPath) to get the script directory without the file name. – fizzled Apr 15 '20 at 15:36
23

For PowerShell 3+

function Get-ScriptDirectory {
    if ($psise) {
        Split-Path $psise.CurrentFile.FullPath
    }
    else {
        $global:PSScriptRoot
    }
}

I've placed this function in my profile. It works in ISE using F8/Run Selection too.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
nickkzl
  • 330
  • 2
  • 9
  • It should be `$script:PSScriptRoot` instead of `$global:PSScriptRoot` which only works in ISE but not in a plain PowerShell. – Burkart Jun 28 '22 at 13:25
19

Maybe I'm missing something here... but if you want the present working directory you can just use this: (Get-Location).Path for a string, or Get-Location for an object.

Unless you're referring to something like this, which I understand after reading the question again.

function Get-Script-Directory
{
    $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value
    return Split-Path $scriptInvocation.MyCommand.Path
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sean C.
  • 420
  • 2
  • 5
  • 22
    This gets the current location **where the user is running the script**. Not the location of the script file **itself**. – Aaron Jensen Mar 29 '11 at 00:07
  • 2
    `function Get-Script-Directory { $scriptInvocation = (Get-Variable MyInvocation -Scope 1).Value return Split-Path $scriptInvocation.MyCommand.Path } $hello = "hello" Write-Host (Get-Script-Directory) Write-Host $hello` Save that and run it from a different directory. You'll show the path to the script. – Sean C. Mar 29 '11 at 00:25
  • That's a good function, and does what I need, but how do I share it and use it in all my scripts? It's a chicken and egg problem: I'd like to use a function to find out my current location, but I need my location to load the function. – Aaron Jensen Mar 29 '11 at 02:26
  • 2
    NOTE: The invokation of this function must be at the top level of your script, if it is nested within another function, then you have to change the "-Scope" parameter to designate how deep in the call stack you are. – kenny Jun 01 '15 at 21:57
15

I use the automatic variable $ExecutionContext. It will work from PowerShell 2 and later.

 $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath('.\')

$ExecutionContext Contains an EngineIntrinsics object that represents the execution context of the Windows PowerShell host. You can use this variable to find the execution objects that are available to cmdlets.

Julian
  • 886
  • 10
  • 21
Viggos
  • 339
  • 3
  • 6
13

Very similar to already posted answers, but piping seems more PowerShell-like:

$PSCommandPath | Split-Path -Parent
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
CPAR
  • 131
  • 1
  • 5
  • Why do this when there is already an automatic variable for this `$PSScriptRoot`? `$PSCommandPath` & `$PSScriptRoot` were both introduced in PowerShell v3 so technically none of this helps the OP since they noted v2, but IS helpful for future people who stumble across this question like I did – gregg Mar 26 '22 at 15:00
12

It took me a while to develop something that took the accepted answer and turned it into a robust function.

I am not sure about others, but I work in an environment with machines on both PowerShell version 2 and 3, so I needed to handle both. The following function offers a graceful fallback:

Function Get-PSScriptRoot
{
    $ScriptRoot = ""

    Try
    {
        $ScriptRoot = Get-Variable -Name PSScriptRoot -ValueOnly -ErrorAction Stop
    }
    Catch
    {
        $ScriptRoot = Split-Path $script:MyInvocation.MyCommand.Path
    }

    Write-Output $ScriptRoot
}

It also means that the function refers to the Script scope rather than the parent's scope as outlined by Michael Sorens in one of his blog posts.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Bruno
  • 5,772
  • 1
  • 26
  • 43
  • Thanks! The "$script:" was what I needed to get this to work in Windows PowerShell ISE. – Kirk Liemohn Jan 11 '17 at 20:20
  • Thanks for this. This was the only one that works for me. I usually have to CD into the directory the script is in before things like Get-location work for me. I'd be interested to know why PowerShell doesn't automatically update the directory. – Zain Feb 25 '20 at 11:38
9

Using pieces from all of these answers and the comments, I put this together for anyone who sees this question in the future. It covers all of the situations listed in the other answers, and I've added another one I found as a fail-safe.

function Get-ScriptPath()
{
    # If using PowerShell ISE
    if ($psISE)
    {
        $ScriptPath = Split-Path -Parent -Path $psISE.CurrentFile.FullPath
    }
    # If using PowerShell 3.0 or greater
    elseif($PSVersionTable.PSVersion.Major -gt 3)
    {
        $ScriptPath = $PSScriptRoot
    }
    # If using PowerShell 2.0 or lower
    else
    {
        $ScriptPath = split-path -parent $MyInvocation.MyCommand.Path
    }

    # If still not found
    # I found this can happen if running an exe created using PS2EXE module
    if(-not $ScriptPath) {
        $ScriptPath = [System.AppDomain]::CurrentDomain.BaseDirectory.TrimEnd('\')
    }

    # Return result
    return $ScriptPath
}
Randy
  • 1,068
  • 2
  • 14
  • 32
  • Hello from future :), best answer I think, by the way I wonder that, it is relation with exe? can u add something $MyInvocation.MyCommand.CommandType -eq "ExternalScript", it is necesary or not ==> https://stackoverflow.com/questions/53134414/powershell-myinvocation-mycommand-path-returns-null-when-convert-script-in-exe – Ichigo Kurosaki Aug 20 '23 at 16:22
  • @IchigoKurosaki That's what the last if statement is for. I added that for exe files created using the PS2EXE module. Not sure if there is another way to convert to exe, but it works with PS2EXE. – Randy Aug 21 '23 at 21:30
8

I always use this little snippet which works for PowerShell and ISE the same way :

# Set active path to script-location:
$path = $MyInvocation.MyCommand.Path
if (!$path) {$path = $psISE.CurrentFile.Fullpath}
if ($path)  {$path = Split-Path $path -Parent}
Set-Location $path
Carsten
  • 1,612
  • 14
  • 21
  • Split-Path part looks good, but is quite slow. If you need it fast then go with RegEx like this: $path = [regex]::Match($path, '.+(?=\\)').value – Carsten Jul 06 '21 at 16:49
7

I needed to know the script name and where it is executing from.

Prefixing "$global:" to the MyInvocation structure returns the full path and script name when called from both the main script, and the main line of an imported .PSM1 library file. It also works from within a function in an imported library.

After much fiddling around, I settled on using $global:MyInvocation.InvocationName. It works reliably with CMD launch, Run With Powershell, and the ISE. Both local and UNC launches return the correct path.

Bruce Gavin
  • 349
  • 3
  • 9
  • 4
    **Split-Path -Path $($global:MyInvocation.MyCommand.Path)** worked perfect thanks. The other solutions returned the path of the calling application. – dynamiclynk May 06 '14 at 21:24
  • 1
    Trivial note: in ISE calling this function using F8/Run Selection will trigger a `ParameterArgumentValidationErrorNullNotAllowed` exception. – weir Oct 19 '15 at 14:15
3

I found that the older solutions posted here didn't work for me on PowerShell V5. I came up with this:

try {
    $scriptPath = $PSScriptRoot
    if (!$scriptPath)
    {
        if ($psISE)
        {
            $scriptPath = Split-Path -Parent -Path $psISE.CurrentFile.FullPath
        }
        else {
            Write-Host -ForegroundColor Red "Cannot resolve script file's path"
            exit 1
        }
    }
}
catch {
    Write-Host -ForegroundColor Red "Caught Exception: $($Error[0].Exception.Message)"
    exit 2
}

Write-Host "Path: $scriptPath"
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Quantium
  • 1,779
  • 1
  • 14
  • 14
2

You might also consider split-path -parent $psISE.CurrentFile.Fullpath if any of the other methods fail. In particular, if you run a file to load a bunch of functions and then execute those functions with-in the ISE shell (or if you run-selected), it seems the Get-Script-Directory function as above doesn't work.

Rudi Visser
  • 21,350
  • 5
  • 71
  • 97
  • 3
    `$PSCommandPath` will work in the ISE as long as you save the script first and execute the whole file. Otherwise, you're not actually executing a script; you're just "pasting" commands into the shell. – Zenexer Jul 25 '13 at 04:02
  • @Zenexer I think that was my goal at the time. Although if my goal didn't match up with the original one, this might not be too helpful except to the occasional Googlers... –  Jan 16 '15 at 16:36
2

If you want to load modules from a path relative to where the script runs, such as from a "lib" subfolder", you need to use one of the following:

$PSScriptRoot which works when invoked as a script, such as via the PowerShell command $psISE.CurrentFile.FullPath which works when you're running inside ISE

But if you're in neither, and just typing away within a PowerShell shell, you can use:

pwd.Path

You can could assign one of the three to a variable called $base depending on the environment you're running under, like so:

$base=$(if ($psISE) {Split-Path -Path $psISE.CurrentFile.FullPath} else {$(if ($global:PSScriptRoot.Length -gt 0) {$global:PSScriptRoot} else {$global:pwd.Path})})

Then in your scripts, you can use it like so:

Import-Module $base\lib\someConstants.psm1
Import-Module $base\lib\myCoolPsModule1.psm1
#etc.
zumalifeguard
  • 8,648
  • 5
  • 43
  • 56