177

I have a PowerShell script that does some stuff using the script’s current directory. So when inside that directory, running .\script.ps1 works correctly.

Now I want to call that script from a different directory without changing the referencing directory of the script. So I want to call ..\..\dir\script.ps1 and still want that script to behave as it was called from inside its directory.

How do I do that, or how do I modify a script so it can run from any directory?

poke
  • 369,085
  • 72
  • 557
  • 602

7 Answers7

241

Do you mean you want the script's own path so you can reference a file next to the script? Try this:

$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
Write-host "My directory is $dir"

You can get a lot of info from $MyInvocation and its properties.

If you want to reference a file in the current working directory, you can use Resolve-Path or Get-ChildItem:

$filepath = Resolve-Path "somefile.txt"

EDIT (based on comment from OP):

# temporarily change to the correct folder
Push-Location $dir

# do stuff, call ant, etc

# now back to previous directory
Pop-Location

There's probably other ways of achieving something similar using Invoke-Command as well.

Eternal21
  • 4,190
  • 2
  • 48
  • 63
JohnL
  • 3,922
  • 3
  • 22
  • 22
  • 1
    Hmm, okay, maybe I should have been a bit more clear about my script. Actually it is a script that calls `ant` with some parameters. So I have to call `ant` from that folder to ensure that it finds the configuration file correctly. Ideally I am looking for something to temporary change the execution directory locally within that script. – poke Jan 18 '11 at 13:28
  • 4
    Ah then in that case you will want `Push-Location` and `Pop-Location` – JohnL Jan 18 '11 at 13:41
  • 6
    careful about that, $MyInvocation is context sensitive, I usually do $ScriptPath = (Get-Variable MyInvocation -Scope Script).Value.MyCommand.Path which works from any sort of nesting or function –  Jun 25 '13 at 08:19
  • 1
    [As a oneliner](https://stackoverflow.com/a/62950925/492336) - `$MyInvocation.MyCommand.Path | Split-Path | Push-Location` – sashoalm Sep 08 '21 at 06:05
59

There are answers with big number of votes, but when I read your question, I thought you wanted to know the directory where the script is, not that where the script is running. You can get the information with powershell's auto variables

$PSScriptRoot     # the directory where the script exists, not the
                  # target directory the script is running in
$PSCommandPath    # the full path of the script

For example, I have a $profile script that finds a Visual Studio solution file and starts it. I wanted to store the full path, once a solution file is started. But I wanted to save the file where the original script exists. So I used $PsScriptRoot.

Andrew Chaa
  • 6,120
  • 2
  • 45
  • 33
42

If you're calling native apps, you need to worry about [Environment]::CurrentDirectory not about PowerShell's $PWD current directory. For various reasons, PowerShell does not set the process' current working directory when you Set-Location or Push-Location, so you need to make sure you do so if you're running applications (or cmdlets) that expect it to be set.

In a script, you can do this:

$CWD = [Environment]::CurrentDirectory

Push-Location $MyInvocation.MyCommand.Path
[Environment]::CurrentDirectory = $PWD
##  Your script code calling a native executable
Pop-Location

# Consider whether you really want to set it back:
# What if another runspace has set it in-between calls?
[Environment]::CurrentDirectory = $CWD

There's no foolproof alternative to this. Many of us put a line in our prompt function to set [Environment]::CurrentDirectory ... but that doesn't help you when you're changing the location within a script.

Two notes about the reason why this is not set by PowerShell automatically:

  1. PowerShell can be multi-threaded. You can have multiple Runspaces (see RunspacePool, and the PSThreadJob module) running simultaneously withinin a single process. Each runspace has it's own $PWD present working directory, but there's only one process, and only one Environment.
  2. Even when you're single-threaded, $PWD isn't always a legal CurrentDirectory (you might CD into the registry provider for instance).

If you want to put it into your prompt (which would only run in the main runspace, single-threaded), you need to use:

[Environment]::CurrentDirectory = Get-Location -PSProvider FileSystem
Jaykul
  • 15,370
  • 8
  • 61
  • 70
  • 4
    It doesn't change the process working directory because of path providers: you might be CD'ing into the registry, a SQL database or an IIS hive etc... – piers7 Nov 07 '13 at 08:42
  • 1
    Yes. I know, that was the point of that last "Final note". They could have (as I do in my prompt function) updated it to the current location of the FileSystem provider, like every other shell in the world. – Jaykul Nov 08 '13 at 16:18
  • 2
    Holy scripting gods, I'm SO DISAPPOINTED `.\_/.` — for this killed half of my day! People, seriously? Seriously?.. – ulidtko Dec 29 '14 at 14:56
  • Why not set `$location = Split-Path ((Get-Variable MyInvocation -Scope 0).Value).MyCommand.Path` then `Push-Location $location` and `[Environment]::CurrentDirectory = $location`? This worked well for me when calling a non-PowerShell exe. – dragon788 Aug 25 '16 at 17:37
  • That's what my answer does, @dragon788 just, with less code? – Jaykul Aug 26 '16 at 23:49
  • Yes, very similar but ensuring both directories are sent to the same location and reusing the variable. It's a little more DRY (don't repeat yourself). – dragon788 Aug 27 '16 at 00:34
  • The difference is you're creating a new variable `$location` (and also using convoluted logic to get at `$MyInvocation.MyCommand`) and *then* switching to it ... whereas I'm just switching to it, and then using the built-in `$pwd` variable. I don't repeat myself at all. The idea is you put the first two lines at the top, and the last two lines at the bottom to **UNDO** the change, because it would be rude to leave either location or current directory changed ... – Jaykul Aug 27 '16 at 00:48
  • 2
    It's mostly unecessary now, more modern versions of PowerShell set the working directory when they launch binary apps. – Jaykul May 24 '17 at 05:53
  • 1
    This method fails at restoring the original `[Environment]::CurrentDirectory` when that differs from `$PWD`. The correct thing to do would be to store it into a variable `$origEnvDir = [Environment]::CurrentDirectory` and then later restore it `[Environment]::CurrentDirectory = $origEnvDir`. – Strom May 29 '19 at 13:56
18

This would work fine.

Push-Location $PSScriptRoot

Write-Host CurrentDirectory $CurDir
ArunSK
  • 331
  • 2
  • 4
12

I often used the following code to import a module which sit under the same directory as the running script. It will first get the directory from which powershell is running

$currentPath=Split-Path ((Get-Variable MyInvocation -Scope 0).Value).MyCommand.Path

import-module "$currentPath\sqlps.ps1"

Daniel Wu
  • 5,853
  • 12
  • 42
  • 93
5

I made a one-liner out of @JohnL's solution:

$MyInvocation.MyCommand.Path | Split-Path | Push-Location
sashoalm
  • 75,001
  • 122
  • 434
  • 781
1

Well I was looking for solution for this for a while, without any scripts just from CLI. This is how I do it xD:

  • Navigate to folder from which you want to run script (important thing is that you have tab completions)

    ..\..\dir

  • Now surround location with double quotes, and inside them add cd, so we could invoke another instance of powershell.

    "cd ..\..\dir"

  • Add another command to run script separated by ;, with is a command separator in powershell

    "cd ..\..\dir\; script.ps1"

  • Finally Run it with another instance of powershell

    start powershell "cd..\..\dir\; script.ps1"

This will open new powershell window, go to ..\..\dir, run script.ps1 and close window.


Note that ";" just separates commands, like you typed them one by one, if first fails second will run and next after, and next after... If you wanna keep new powershell window open you add -noexit in passed command . Note that I first navigate to desired folder so I could use tab completions (you couldn't in double quotes).

start powershell "-noexit cd..\..\dir\; script.ps1"

Use double quotes "" so you could pass directories with spaces in names e.g.,

start powershell "-noexit cd '..\..\my dir'; script.ps1"

Community
  • 1
  • 1
IGRACH
  • 3,506
  • 6
  • 33
  • 48