Using relative file paths for your PowerShell script’s supporting files ensure it continues to work when the script and files get moved around. Here’s how.
Unpredictable File Paths
My first intensive use of PowerShell was for scripting software installations/configurations in SCCM packages. It seemed like every situation called for something above and beyond a straightforward MSI install.
I would develop and test the SCCM package on my local system with a sandbox VM prior to deploying in SCCM. The SCCM client would then download that package to a cached location on each individual client. From development to deployment, I couldn’t guarantee the absolute file path of my script and any supporting files would be constant. I needed my script to be able to figure out where the supporting files were in relation to itself.
The newer way: $PSScriptRoot
$PSScriptRoot is an Automatic Variable, which are built-in variables that contain information about the PowerShell environment itself. $PSScriptRoot contains the directory path of the script being executed currently. Originally $PSScriptRoot was only applicable to script modules (.psm1), but beginning with PowerShell 3.0, it works for all PowerShell script files.
From the console if I type $PSScriptRoot and press ENTER, it returns nothing. $PSScriptRoot only gets a value when a script is executing, and then is destroyed when the script terminates.
PS C:\Users\aaron> $PSScriptRoot PS C:\Users\aaron>
Let’s say I have a script and config file sitting in C:\TEMP\DemoScript. I would concatenate the value of $PSScriptRoot with a backslash and the config file name:
# DemoScript.ps1 $configFilePath = $PSScriptRoot + "\DemoFile.cfg" Write-Host $configFilePath
If I run DemoScript.ps1, you see $PSScriptRoot gets a value and correctly builds the config file path:
PS C:\Users\aaron> C:\TEMP\DemoScript\DemoScript.ps1 C:\TEMP\DemoScript\DemoFile.cfg
Now let’s move the DemoScript directory to C:\TEMP\NewRootFolder and run DemoScript.ps1 again. The config file path is built correctly to accommodate the file move.
PS C:\Users\aaron> C:\TEMP\NewRootFolder\DemoScript\DemoScript.ps1 C:\TEMP\NewRootFolder\DemoScript\DemoFile.cfg
This requires all supporting files be in the same folder as the script or a child folder.
The old way: $MyInvocation.MyCommand.Path
I started on the SCCM packages prior to PowerShell 3.0, when $PSScriptRoot didn’t exist. Instead, we parsed the script path from another Automatic Variable, $MyInvocation.MyCommand.Path, and used it in the same fashion as $PSScriptRoot.
# DemoScript.ps1 $scriptPath = split-path -parent $MyInvocation.MyCommand.Path $configFilePath = $scriptPath + "\DemoFile.cfg" Write-Host $configFilePath
PS C:\Users\aaron> C:\TEMP\NewRootFolder\DemoScript\DemoScript.ps1 C:\TEMP\NewRootFolder\DemoScript\DemoFile.cfg
It works, but it requires an extra line of code and isn’t as simple as $PSScriptRoot, but it is an option on any legacy systems you may have that don’t have PowerShell 3.0.
Make your script resilient with $PSScriptRoot
Use $PSScriptRoot to build absolute file paths from relative ones to make your script and supporting files portable and resilient. As part of this, it is a good practice to create a new folder to become that common root when you start developing a new script that will have supporting files.
Reference
- technet.microsoft.com
C2016 says
Hello Aaron,
First, I like your Beginner series! very informative. I have a question regarding copying security groups and distribution lists between users and security groups? Is there a mechanism to do this all at once? For example, If I want to copy all the group memberships of user A to security group B is that possible? or would I have to do it separately?
Aaron says
Glad you like the post. More to come. Regarding your question, are you saying you want to return the group memberships of a user, and then add those groups as members to another security group?
Ahmad yamout says
Hello dude I would like to appreciate your work . I have a question about how to get root folder directory that work for the latest powershell only 3+ , 4 and 5 .
Aaron Rothstein says
If you are working in 3.0 or later, use $PSScriptRoot. Did you have a more specific question I can help with?
Ross says
In your example, you show:
$configFilePath = $PSScriptRoot + “\DemoFile.cfg”
I expect you showed simple concatenation for ease of understanding, but any time you’re forming a path, I’d recommend using the Join-Path function:
$configFilePath = Join-Path $PSScriptRoot “DemoFile.cfg”
You won’t have to concern yourself with path delimiters (slash/backslash/wack/backwack).
Aaron Rothstein says
Ross – thank you for pointing out that useful cmdlet! There is an example of where I had been using concatenation for so long I didn’t think to look to see if a better option had been implemented. I will be sure to use this going forward!
Techie says
Hello Thanks for the article,
I would want to know if i write something like this in a script $srvlist = Import-Csv -Path ($PSScriptRoot + “\testvmlistupdated.csv”)
which is placed in c:/user/xxx/Desktop/temp, would that mean powershell will import the the file from the folder where the script being excicuted?
Aaron Rothstein says
Yes, that is correct. You will also see that your current working directory will change from whatever it was to the location of your script.
RAFAEL says
how i can goback one folder ? like this $PSScriptRoot\..\packages
my $PSScriptRoot result is :C:\vs\xxx\yyy\Installer\InstallApp\InstallApp\silent
but i want C:\vs\xxx\yyy\Installer\InstallApp\InstallApp\packages
Aaron Rothstein says
Hey Rafael,
To get the parent path, you can do the following:
(Get-Item $PSScriptRoot).Parent.FullName
In your example, that will return C:\vs\xxx\yyy\Installer\InstallApp\InstallApp, and you can then concatenate “\packages” to build your desired path.
Pramod Yadav says
This will help us refactor quite some scripts. Thank you for sharing!