3

Suppose I have 2 files, A.ps1 and B.ps1, in this directory structure:

--  root
 |
 |-- A.ps1
 |
 |--  subfolder
   |
   |-- B.ps1

A.ps1:

Write-Host $PSScriptRoot

B.ps1:

. "\root\A.ps1"

Now, taking this fact into account:

Dot sourcing takes the script you've specified and immediately executes it as though it had been at that spot in your original script

THE PROBLEM: If I were to run B.ps1, I would expect the result to be \root\subfolder, but it's \root, why??

If A.ps1 is dot-sourced into B.ps1, shouldn't the contents of script A.ps1 run as if they were written directly inside of B.ps1? Meaning, shouldn't $PSScriptRoot run as if it was called from B.ps1, and thus evaluate to \root\subfolder? I've even tested this by wrapping A.ps1 in a function and calling that function in B.ps1 after dot-sourcing. Still yields the same result..

How does dot-sourcing really work?

Prid
  • 1,272
  • 16
  • 20
  • https://stackoverflow.com/questions/3667238/how-can-i-get-the-file-system-location-of-a-powershell-script – TheGameiswar Sep 24 '22 at 04:31
  • 2
    _How does dot-sourcing really work?_, [the docs are pretty clear on that](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators?view=powershell-7.2#dot-sourcing-operator-): Runs a script in the current scope so that any functions, aliases, and variables that the script creates are added to the current scope, overriding existing ones. `$PSScriptRoot` of `A.ps1` is `\root`, dot sourcing does not change that fact. – Santiago Squarzon Sep 24 '22 at 04:35
  • 3
    Also, the docs for ```$PSScriptRoot``` have this to say: “Contains the full path of the **executing** script's parent directory.” (see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables?view=powershell-7.2#psscriptroot). Even though ```A.ps1``` is executing in ```B.ps1```’s scope, it’s still ```A.ps1``` **executing**, so ```$PSScriptRoot``` contains ```A.ps1```’s parent folder path. – mclayton Sep 24 '22 at 05:18

1 Answers1

6

As commenters noted, $PSScriptRoot is independent of scope. From the docs (emphasis mine):

$PSScriptRoot
Contains the full path of the executing script's parent directory.

Dot-Sourcing doesn't literally "include" the script at the spot (contrary to the #include directive of C++, for instance), PowerShell still knows it is running a different script. The only thing that is different compared to calling the script (explicitly using the call operator & or running the script by entering its path), is the scoping.

There is a way to get the $PSScriptRoot of the calling script though. Turn the script to be dot-sourced into an advanced function cmdlet by using the CmdletBinding() and/or the Parameter() attribute. This makes the automatic variable $PSCmdlet available, which gives you (amongst other things), the $PSScriptRoot of the invoking script.

A.ps1

# CmdletBinding is an attribute of param(), so that is required as well
[CmdletBinding()] param()

$PSScriptRoot                         # Outputs directory that contains A.ps1
$PSCmdlet.MyInvocation.PSScriptRoot   # Outputs directory of the invoking script
zett42
  • 25,437
  • 3
  • 35
  • 72
  • 2
    Nice :-). As a personal style, I only really use dot-sourcing to define functions, and then call those functions in my main script - I don’t like the idea of a dot-sourced script actually executing code with side effects as it makes it harder to remember what is happening where (and once you get more than a handful of functions, turn it into a module)… – mclayton Sep 24 '22 at 08:03
  • 1
    @mclayton Agreed, side effects can be a nightmare. Another valid usecase is dot-sourcing of script blocks. In many cases I want the script block to be able to easily modify surrounding variables, so I dot-source it. – zett42 Sep 24 '22 at 08:07