We had some boiler plate code that we always put into our scripts to set strict mode and to compute $ScriptDir so the script can load other scripts relatively to its location. This boiler plate code is simple:
#requires –Version 2.0
Set-StrictMode –Version 2.0
$ScriptDir = Split-Path $MyInvocation.MyCommand.Path –Parent
However lets say you do this in all your scripts and one script (Parent.ps1) dot-sources another script (PoshLib.ps1) that does the same trick e.g.:
Parent.ps1:
$ScriptDir = Split-Path $MyInvocation.MyCommand.Path –Parent
"PARENT: Before dot-sourcing libary ScriptDir is $ScriptDir"
. $ScriptDir\Bin\PoshLib.ps1
"PARENT: After dot-sourcing libary ScriptDir is $ScriptDir"
PoshLib.ps1
$ScriptDir = Split-Path $MyInvocation.MyCommand.Path –Parent
"POSHLIB: ScriptDir is $ScriptDir"
Seems innocent enough but check out the output of running Parent.ps1:
PS C:\Users\Keith> C:\Users\Keith\Parent.ps1
PARENT: Before dot-sourcing libary ScriptDir is C:\Users\Keith
POSHLIB: ScriptDir is C:\Users\Keith\Bin
PARENT: After dot-sourcing libary ScriptDir is C:\Users\Keith\Bin
What happens here is that because we are “dot sourcing” PoshLib.ps1 into Parent.ps1, its definition of the $ScriptDir variable stomps the one created in Parent.ps1. Obviously this won’t do. You could try to create unique variable names for each script but that isn’t ideal and not very maintainable.
The best answer if you are on PowerShell 2.0 is to just use modules for your libraries. Modules can have “private” variables that don’t get exported so the variable “stomping” issue never arises. However, for various reasons, folks can’t always upgrade to PowerShell 2.0. So here is how you can fix this issue on PowerShell 1.0.
Essentially what you want to do is to dynamically evaluate the script’s location every time without having to use the longhand:
Split-Path $MyInvocation.MyCommand.Path –Parent
PowerShell allows us to create a shorthand for this using an anonymous scriptblock assigned to a variable like so:
Parent.ps1:
$ScriptDir = { Split-Path $MyInvocation.ScriptName –Parent }
"PARENT: Before dot-sourcing libary ScriptDir is $(&$ScriptDir)"
. "$(&$ScriptDir)\Bin\PoshLib.ps1"
"PARENT: After dot-sourcing libary ScriptDir is $(&$ScriptDir)"
PoshLib.ps1
$ScriptDir = { Split-Path $MyInvocation.ScriptName –Parent }
"POSHLIB: ScriptDir is $(&$ScriptDir)"
Note: that once we put the code within the scriptblock we need to switch from $MyInvocation.MyCommand.Path to $MyInvocation.ScriptName. These changes yield the desired results:
PS C:\Users\Keith> C:\Users\Keith\Parent.ps1
PARENT: Before dot-sourcing libary ScriptDir is C:\Users\Keith
POSHLIB: ScriptDir is C:\Users\Keith\Bin
PARENT: After dot-sourcing libary ScriptDir is C:\Users\Keith
If you are expecting variable collision or if you’re not sure if it will occur, modules should be used instead of . sourcing in my opinion…
Parent.ps1:
$ScriptDir = Split-Path $MyInvocation.MyCommand.Path –Parent
Write-Host “PARENT: Before importing libary ScriptDir is $ScriptDir”
Import-Module “$ScriptDir\Bin\PoshLib.psm1”
Write-Host “PARENT: After importing libary ScriptDir is $ScriptDir”
PoshLib.psm1
$ScriptDir = Split-Path $MyInvocation.MyCommand.Path –Parent
Write-Host “POSHLIB: ScriptDir is $ScriptDir”
Output:
PARENT: Before importing libary ScriptDir is C:\Users\andy\Desktop\temp
POSHLIB: ScriptDir is C:\Users\andy\Desktop\temp\Bin
PARENT: After importing libary ScriptDir is C:\Users\andy\Desktop\temp
I agree. In fact, I do recommend using Modules in this post – “The best answer if you are on PowerShell 2.0 is to just use modules …”. However there are cases where you have perhaps a lot of scripts that you can’t justify switching over to modules.
Ah I missed that paragraph, woops. I thought you were suggesting this approach for PS 2.0 because of the boiler plate at the beginning…
#requires –Version 2.0
Set-StrictMode –Version 2.0
Thanks.
No problem. It’s good to reiterate the benefit of Modules to overcome this sort of issue.
Alternatively, we can use a function instead of a script block:
function ScriptRoot { Split-Path $MyInvocation.ScriptName }
Yes, it does the same but there are a few differences:
* One may find notation (ScriptRoot) better than &$ScriptRoot (it depends, indeed);
* Even if strict mode is not enabled, misspelled function names yield errors (misspelled variable names do not).
Pingback: PowerShell $PSScriptRoot vs dot-sourcing | mnaoumov.NET