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?
# 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
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.
$PSCommandPath Contains the full path and file name of the script that is being run. This variable is valid in all scripts.
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
}
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.
$script:PSScriptRoot
instead of $global:PSScriptRoot
which only works in ISE but not in a plain PowerShell.
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
}
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.
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.
Very similar to already posted answers, but piping seems more PowerShell-like:
$PSCommandPath | Split-Path -Parent
$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
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.
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.
ParameterArgumentValidationErrorNullNotAllowed
exception.
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
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
}
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"
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.
$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.
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.
function func1()
{
$inv = (Get-Variable MyInvocation -Scope 1).Value
#$inv.MyCommand | Format-List *
$Path1 = Split-Path $inv.scriptname
Write-Host $Path1
}
function Main()
{
func1
}
Main
Success story sharing
Split-Path
used for here?Split-Path
is used with the-Parent
parameter to return the current directory without the currently executing script's name.$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).-Parent
is indeed not necessary.