How to get the current directory of the cmdlet being executed - powershell

This should be a simple task, but I have seen several attempts on how to get the path to the directory where the executed cmdlet is located with mixed success. For instance, when I execute C:\temp\myscripts\mycmdlet.ps1 which has a settings file at C:\temp\myscripts\settings.xml I would like to be able to store C:\temp\myscripts in a variable within mycmdlet.ps1.
This is one solution which works (although a bit cumbersome):
$invocation = (Get-Variable MyInvocation).Value
$directorypath = Split-Path $invocation.MyCommand.Path
$settingspath = $directorypath + '\settings.xml'
Another one suggested this solution which only works on our test environment:
$settingspath = '.\settings.xml'
I like the latter approach a lot and prefer it to having to parse the filepath as a parameter each time, but I can't get it to work on my development environment. What should I do? Does it have something to do with how PowerShell is configured?

Yes, that should work. But if you need to see the absolute path, this is all you need:
(Get-Item .).FullName

The reliable way to do this is just like you showed $MyInvocation.MyCommand.Path.
Using relative paths will be based on $pwd, in PowerShell, the current directory for an application, or the current working directory for a .NET API.
PowerShell v3+:
Use the automatic variable $PSScriptRoot.

The easiest method seems to be to use the following predefined variable:
$PSScriptRoot
about_Automatic_Variables and about_Scripts both state:
In PowerShell 2.0, this variable is valid only in script modules (.psm1). Beginning in PowerShell 3.0, it is valid in all scripts.
I use it like this:
$MyFileName = "data.txt"
$filebase = Join-Path $PSScriptRoot $MyFileName

You can also use:
(Resolve-Path .\).Path
The part in brackets returns a PathInfo object.
(Available since PowerShell 2.0.)

Try :
(Get-Location).path
or:
($pwd).path

Path is often null. This function is safer.
function Get-ScriptDirectory
{
$Invocation = (Get-Variable MyInvocation -Scope 1).Value;
if($Invocation.PSScriptRoot)
{
$Invocation.PSScriptRoot;
}
Elseif($Invocation.MyCommand.Path)
{
Split-Path $Invocation.MyCommand.Path
}
else
{
$Invocation.InvocationName.Substring(0,$Invocation.InvocationName.LastIndexOf("\"));
}
}

Get-Location will return the current location:
$Currentlocation = Get-Location

I like the one-line solution :)
$scriptDir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent

Try this:
$WorkingDir = Convert-Path .

In Powershell 3 and above you can simply use
$PSScriptRoot

If you just need the name of the current directory, you could do something like this:
((Get-Location) | Get-Item).Name
Assuming you are working from C:\Temp\Location\MyWorkingDirectory>
Output
MyWorkingDirectory

Most answers don't work when debugging in the following IDEs:
PS-ISE (PowerShell ISE)
VS Code (Visual Studio Code)
Because in those the $PSScriptRoot is empty and Resolve-Path .\ (and similars) will result in incorrect paths.
Freakydinde's answer is the only one that resolves those situations, so I up-voted that, but I don't think the Set-Location in that answer is really what is desired. So I fixed that and made the code a little clearer:
$directorypath = if ($PSScriptRoot) { $PSScriptRoot } `
elseif ($psise) { split-path $psise.CurrentFile.FullPath } `
elseif ($psEditor) { split-path $psEditor.GetEditorContext().CurrentFile.Path }

For what it's worth, to be a single-line solution, the below is a working solution for me.
$currFolderName = (Get-Location).Path.Substring((Get-Location).Path.LastIndexOf("\")+1)
The 1 at the end is to ignore the /.
Thanks to the posts above using the Get-Location cmdlet.

this function will set the prompt location to script path, dealing with the differents way to get scriptpath between vscode, psise and pwd :
function Set-CurrentLocation
{
$currentPath = $PSScriptRoot # AzureDevOps, Powershell
if (!$currentPath) { $currentPath = Split-Path $pseditor.GetEditorContext().CurrentFile.Path -ErrorAction SilentlyContinue } # VSCode
if (!$currentPath) { $currentPath = Split-Path $psISE.CurrentFile.FullPath -ErrorAction SilentlyContinue } # PsISE
if ($currentPath) { Set-Location $currentPath }
}

You would think that using '.\' as the path means that it's the invocation path. But not all the time. Example, if you use it inside a job ScriptBlock. In which case, it might point to %profile%\Documents.

This is what I came up with. It's an array including multiple methods of finding a path, uses the current location, filters out null\empty results, and returns the first not-null value.
#((
($MyInvocation.MyCommand.Module.ModuleBase),
($PSScriptRoot),
(Split-Path -Parent -Path $MyInvocation.MyCommand.Definition -ErrorAction SilentlyContinue),
(Get-Location | Select-Object -ExpandProperty Path)
) | Where-Object { $_ })[0]

To only get the current folder name, you can also use:
(Split-Path -Path (Get-Location) -Leaf)

To expand on #Cradle 's answer: you could also write a multi-purpose function that will get you the same result per the OP's question:
Function Get-AbsolutePath {
[CmdletBinding()]
Param(
[parameter(
Mandatory=$false,
ValueFromPipeline=$true
)]
[String]$relativePath=".\"
)
if (Test-Path -Path $relativePath) {
return (Get-Item -Path $relativePath).FullName -replace "\\$", ""
} else {
Write-Error -Message "'$relativePath' is not a valid path" -ErrorId 1 -ErrorAction Stop
}
}

I had similar problems and it made me a lot of trouble since I am making programs written in PowerShell (full end user GUI applications) and I have a lot of files and resources I need to load from disk.
From my experience, using . to represent current directory is unreliable. It should represent current working directory, but it often does not.
It appears that PowerShell saves location from which PowerShell has been invoked inside ..
To be more precise, when PowerShell is first started, it starts, by default, inside your home user directory. That is usually directory of your user account, something like C:\USERS\YOUR USER NAME.
After that, PowerShell changes directory to either directory from which you invoked it, or to directory where script you are executing is located before either presenting you with PowerShell prompt or running the script. But that happens after PowerShell app itself originally starts inside your home user directory.
And . represents that initial directory inside which PowerShell started. So . only represents current directory in case if you invoked PowerShell from the wanted directory. If you later change directory in PowerShell code, change appears not to be reflected inside . in every case.
In some cases . represents current working directory, and in others directory from which PowerShell (itself, not the script) has been invoked, what can lead to inconsistent results.
For this reason I use invoker script. PowerShell script with single command inside:
POWERSHELL.
That will ensure that PowerShell is invoked from the wanted directory and thus make . represent current directory. But it only works if you do not change directory later in PowerShell code.
In case of a script, I use invoker script which is similar to last one I mentioned, except it contains a file option:
POWERSHELL -FILE DRIVE:\PATH\SCRIPT NAME.PS1.
That ensures that PowerShell is started inside current working directory.
Simply clicking on script invokes PowerShell from your home user directory no matter where script is located.
It results with current working directory being directory where script is located, but PowerShell invocation directory being C:\USERS\YOUR USER NAME, and with . returning one of these two directories depending on the situation, what is ridiculous.
But to avoid all this fuss and using invoker script, you can simply use either $PWD or $PSSCRIPTROOT instead of . to represent current directory depending on weather you wish to represent current working directory or directory from which script has been invoked.
And if you, for some reason, want to retrieve other of two directories which . returns, you can use $HOME.
I personally just have invoker script inside root directory of my apps I develop with PowerShell which invokes my main app script, and simply remember to never ever change current working directory inside my source code of my app, so I never have to worry about this, and I can use . to represent current directory and to support relative file addressing in my applications without any problems.
This should work in newer versions of PowerShell (newer than version 2).

Mine was a short, so unplug everything but USB from it and recompile

Related

Does PowerShell have a clean way of validating a path to a file to be created?

In Python there are simple one liners that can take a path like C:\somefolder\somesubfolder\file.csv, grab the C:\somefolder\somesubfolder\ part, if it exists, and tell you if it's valid. I have to account for a user potentially passing in an absolute path, a relative path, or no path at all and the file just writing to the same directory. Some potential user input:
..\an_invalid_folder\something.csv
C:\
an invalid folder\something.csv
something.csv
some_absolute_path\something.csv
In PowerShell I found this surprisingly cumbersome. I found things like Split-Path have strange behaviors - Ex: if you pass only C:\ and then run split path with the leaf option it will tell you C:\ is the leaf. Now, I understand why they do that but it makes solving the above a bit ugly. Below is what I came up with - does PowerShell not have something like the os.path library that more cleanly handles all these situations?
$ParentPath = $(Split-Path -Path $OutputFilePath -Parent)
if ($ParentPath -ne "") {
if (-not $(Test-Path -PathType Container $ParentPath)) {
Write-Error "The path you provided for $($OutputFilePath) is not valid." -ErrorAction Stop
}
}
if (Test-Path $(Split-Path -Path $OutputFilePath -Leaf) -PathType Container) {
Write-Error "You must provide a filename as part of the path. It looks like you only provided a folder in $($OutputFilePath)!" -ErrorAction Stop
}
Try { [io.file]::OpenWrite($outfile).close() }
Catch { Write-Error "It looks like you may not have permissions to $($OutputFilePath). We tried to open a file object there and the test failed." -ErrorAction Stop }
There is undoubtedly room for improvement in PowerShell's *-Path cmdlets, notably with respect to allowing the specified paths to (partially) not exist (yet) - for example, see the Convert-Path-related GitHub proposal #2993.
While direct use of the methods of the static .NET System.IO.Path class offers a way around that, a notable - and unavoidable - pitfall is that .NET's current directory usually differs from PowerShell's, which means:
You should generally always pass full paths to .NET methods (with existing paths, passing a path to Convert-Path first can ensure that). In your case, the [io.file]::OpenWrite() call may either fail or create the file elsewhere if given a relative (or file-name-only) path.
Additionally, .NET knows nothing of PowerShell-specific drives (created with New-PSDrive), so paths based on such drives must be converted to file-system-native paths - again, Convert-Path does that.
To get PowerShell's current directory as a native file-system path, use $PWD.ProviderPath (or, if there's a chance that the current location is that of a provider other than the file-system, such as the registry on Windows: (Get-Location -PSProvider FileSystem).ProviderPath).
Similarly, [System.IO.Path]::GetFullPath() specifically resolves a relative path relative to .NET's current directory, so it generally won't work as expected (unless you explicitly synchronize the current directories first, with [Environment]::CurrentDirectory = $PWD.ProviderPath).
In PowerShell (Core) 7+ only, you can work around the problem with the optional (.NET Core-only) 2nd parameter that allows passing in a reference directory:
[System.IO.Path]::GetFullPath('foo', $PWD.ProviderPath)
Here's a slightly simpler solution than yours, which partly relies on the underlying .NET exceptions to provide meaningful error messages:
try {
$null = New-Item -ErrorAction Stop -Type File $OutputFilePath
} catch {
Throw "`"$OutputFilePath`" is not a valid -OutFilePath argument: " + $(
if (Test-Path -PathType Container -LiteralPath $OutputFilePath) {
"Please specify a path to a *file*, not just a directory."
} else {
$_.Exception.Message
}
)
}
# Note: As with your approach, file $OutFilePath now exists as an empty,
# in a closed state.

Difference between $MyInvocation and $PSScriptRoot [duplicate]

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?
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
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.
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.
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
}
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
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.
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 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.
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
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.
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

Create new absolute path from absolute path + relative or absolute path

I am working on a build script using psake and I need to create an absolute path from the current working directory with an inputted path which could either be a relative or absolute path.
Suppose the current location is C:\MyProject\Build
$outputDirectory = Get-Location | Join-Path -ChildPath ".\output"
Gives C:\MyProject\Build\.\output, which isn't terrible, but I would like without the .\. I can solve that issue by using Path.GetFullPath.
The problem arises when I want to be able to provide absolute paths
$outputDirectory = Get-Location | Join-Path -ChildPath "\output"
Gives C:\MyProject\Build\output, where I need C:\output instead.
$outputDirectory = Get-Location | Join-Path -ChildPath "F:\output"
Gives C:\MyProject\Build\F:\output, where I need F:\output instead.
I tried using Resolve-Path, but this always complains about the path not existing.
I'm assuming Join-Path is not the cmdlet to use, but I have not been able find any resources on how to do what I want. Is there a simple one-line to accomplish what I need?
You could use GetFullPath(), but you would need to use a "hack" to make it use you current location as the current Directory(to resolve relative paths). Before using the fix, the .NET method's current directory is the working directory for the process, and not the location you have specified inside the PowerShell process. See Why don't .NET objects in PowerShell use the current directory?
#Hack to make .Net methods use the shells current directory instead of the working dir for the process
[System.Environment]::CurrentDirectory = (Get-Location)
".\output", "\output", "F:\output" | ForEach-Object {
[System.IO.Path]::GetFullPath($_)
}
Output:
C:\Users\Frode\output
C:\output
F:\output
Something like this should work for you:
#Hack to make .Net methods use the shells current directory instead of the working dir for the process
[System.Environment]::CurrentDirectory = (Get-Location)
$outputDirectory = [System.IO.Path]::GetFullPath(".\output")
I don't think there's a simple one-liner. But I assume you need the path created anyway, if it doesn't exist yet? So why not just test and create it?
cd C:\
$path = 'C:\Windows', 'C:\test1', '\Windows', '\test2', '.\Windows', '.\test3'
foreach ($p in $path) {
if (Test-Path $p) {
(Get-Item $p).FullName
} else {
(New-Item $p -ItemType Directory).FullName
}
}

How to Do MSBuild's GetDirectoryNameOfFileAbove in PowerShell?

In MSBuild, there's the GetDirectoryNameOfFileAbove property function.
How do I achieve the same with PowerShell?
Should better have compact syntax, because that's what you have to paste into every entry-point script to find its includes.
The idea of this question:
There's a large solution in source code control. Some of its parts are relatively autonomous.
It has a location for shared scripts and reusable functions, at a known folder under the root.
There're numerous entry-point scripts (files which you explicitly execute) scattered around the project, all of them including the shared scripts.
What's the convenient way for locating the shared scripts from the entry-point-script?
Relative paths turn out to work bad because they look like "../../../../../../scripts/smth", are hard to write and maintain.
Registering modules is not a good option because (a) you're getting this from SCC, not by installing (b) you usually have different versions in different disc locations, all at the same time and (c) this is an excess dependency on the environment when technically just local info is enough.
MSBuild way for doing this (since v4) is as follows: drop a marker file (say, root.here or whatever), get an absolute path to that folder with GetDirectoryNameOfFileAbove, et voila! You got the local root to build paths from.
Maybe it's not the right way to go with powershell, so I'd be grateful for such directions as well.
You can access the current folder thus:
$invoker= Split-Path -Parent $MyInvocation.MyCommand.Path
So the parent of that one is :
$parent=Split-Path -Parent $MyInvocation.MyCommand.Path|Split-Path -Parent
A quick and dirty solution looks like this:
function GetDirectoryNameOfFileAbove($markerfile)
{
$result = ""
$path = $MyInvocation.ScriptName
while(($path -ne "") -and ($path -ne $null) -and ($result -eq ""))
{
if(Test-Path $(Join-Path $path $markerfile)) {$result=$path}
$path = Split-Path $path
}
if($result -eq "") {throw "Could not find marker file $markerfile in parent folders."}
return $result
}
It could be compacted into a single line for planting into scripts, but it's still too C#-ish, and I think it might be shortened down with some PS pipes/LINQ style magic.
UPD: edited the script, it was found that $MyInvocation.MyCommand.Path is often NULL when script is called from cmdline with dotsourcing (with any context level), so the current hypothesis is ScriptName.

What's the best way to determine the location of the current PowerShell script?

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?
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
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.
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.
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
}
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
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.
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 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.
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
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.
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