Powershell: Feature Request: What if $PSScriptRoot returned $PWD when used interactively?

Created on 8 Mar 2019  路  8Comments  路  Source: PowerShell/PowerShell

Summary of the new feature/enhancement

Sometimes when I'm writing a little script, I test out a bit of code by pasting into a console. And if I had a reference to $PSScriptRoot in that code, it blows up, usually looking for files in the root of my drive. E.g. Get-Content "$PSScriptRoot\Version.txt" turns into Get-Content "\Version.txt" (which in turn becomes an access for "C:\Version.txt", which of course does not exist).

The current behavior of $PSScriptRoot returning null/empty when there is no script running makes sense from the point of view that, "hey, there is no script, so... no script root". However, in practice I think it might be much more convenient for the value of $PSScriptRoot to be the current [PowerShell] directory ($PWD) when accessed interactively.

This certainly wouldn't solve all uses of $PSScriptRoot from the command line (what if you're not in the right directory?), but it would certainly improve a particular common case.

Is this a breaking change? I think it's in the "unlikely grey area" bucket--it seems unlikely that somebody would take a dependency on $PSScriptRoot being empty when accessed from the command line (though certainly not impossible, such as if someone tried to write a guard to detect being pasted into a console instead of being run from script).

Issue-Enhancement

Most helpful comment

We've also seen people have issues with this in VSCode with the PowerShell extension. This crops up when you select script that refers to $PSScriptRoot and press F8 to run the selected script in the console. In this case, the extensions "knows" the path for the script. If we could just set the $PSScriptRoot variable in the global session, then F8 would work better for folks.

All 8 comments

It seems we can not detect interactive input.

@iSazonov : well... what if any time $PSScriptRoot is going to return null, then just return $PWD instead?

Making it work interactively will make the name of this variable meaningless.

FWIW, $PSScriptRoot is the empty string, not $null, when used interactively. I don't know if in any possible future we would be working on a platform where the empty string is a valid absolute path.

BTW, don't join paths yourself. Use [System.IO.Path]::Combine instead.

This is an interesting idea, it's definitely tripped me up before.

I do think it's a risky change though - there are too many scenarios where $PSScriptRoot might not have a useful value, and I think it's likely real scripts might use that detail in some meaningful way.

Maybe what you really want is to replace $PSScriptRoot with $PWD when pasting? It seems simple to do as a custom PSReadLine key binding.

We've also seen people have issues with this in VSCode with the PowerShell extension. This crops up when you select script that refers to $PSScriptRoot and press F8 to run the selected script in the console. In this case, the extensions "knows" the path for the script. If we could just set the $PSScriptRoot variable in the global session, then F8 would work better for folks.

For PSRL you can override PSConsoleHostReadLine like this

function PSConsoleHostReadLineProxy {
    Microsoft.PowerShell.Core\Set-StrictMode -Off
    $command = [Microsoft.PowerShell.PSConsoleReadLine]::ReadLine($host.Runspace, $ExecutionContext)
    $ast = [System.Management.Automation.Language.Parser]::ParseInput(
        $command,
        [ref] $null,
        [ref] $null)

    $findVariablesDelegate = {
        param($a)
        end {
            $a -is [System.Management.Automation.Language.VariableExpressionAst] -and
            $a.VariablePath.UserPath -eq 'PSScriptRoot'
        }
    }

    $variables = $ast.FindAll($findVariablesDelegate, $true) |
        Microsoft.PowerShell.Utility\Sort-Object { $PSItem.Extent.StartOffset } -Descending

    $newPrompt = [System.Text.StringBuilder]::new($command)
    foreach ($variable in $variables) {
        $extent = $variable.Extent
        $null = $newPrompt.
            Remove($extent.StartOffset, $extent.EndOffset - $extent.StartOffset).
            Insert($extent.StartOffset, '$($PWD.ProviderPath)')
    }

    return $newPrompt.ToString()
}

Microsoft.PowerShell.Utility\New-Alias PSConsoleHostReadLine PSConsoleHostReadLineProxy -Force

Worth noting though that this won't work in the VSCode preview extension because it doesn't actually call PSConsoleHostReadLine. Maybe that should be revisited though.

Writing code with $PSScriptRoot is a best practice to avoid opaque dependencies on shell location. However, this makes testing snippets very frustrating, as they must be rewritten. This feature would be immensely valuable on a daily basis.

Was this page helpful?
0 / 5 - 0 ratings