-
Notifications
You must be signed in to change notification settings - Fork 508
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
$PSScriptRoot
is not populated when running a code block (via F8)
#633
Comments
Thanks for reminding me! I need to get that fixed. |
Glad I wasn't going insane (more than usual) |
One could argue that since |
True, although if the code is being run from a file that exists, and it's a I feel I do understand the technicalities here, and yes I can see your argument. The bigger question in my mind is should the user know the difference? If so, how would VS Code convey that to the user, instead of errors and null values? |
Dot sourcing will evaluate
IMO yes, because running script with F8 i.e. in the global session is subtly different than executing the script file or even dot sourcing it. $PSScriptRoot is just on example. Other differences are Finally, maybe the F8 feature could be made smart and if you have selected all the text in a script, it would |
I'd be happy if it wasn't supported, but somehow I was warned that "Stuff may not do what you expect" if it saw those tokens in the text. |
We should probably do that if we detect $PSCommandPath, $MyInvocation or Get-PSCallStack in the text. I think those are corner-case enough to not try to "fix up" in the global session. Now that I think about it, perhaps the F8 mechanism could do the evaluation of |
Yeah, F8 could just insert the script's parent dir into the session as $PSScriptRoot right before running the snippet. I believe people used to complain that the ISE did not do this. Might be nice to make it work. However, I've been considering using VS Code's built in Run Selection in Terminal command instead of my own custom F8 implementation. This would mean that I wouldn't be able to do the $PSScriptRoot injection. However, I don't have a good reason to do that other than just removing "unnecessary" code. If the $PSScriptRoot injection is important enough (which it might be for interactive dev workflow) then I can still keep the current F8. Thoughts? |
ISE has the following property to get the Current File in the Editor: $psise.CurrentFile I don't know if you have ported it yet, but that makes it easy to inject into $PSScriptRoot when F8 is run. It always stays up-to-date with new editor tabs and even has path to where Untitled.ps1 would be saved. This is one of the most annoying things in ISE. It requires special handling and thinking when developing because you need $PSScriptRoot in production and $psise.CurrentFile in testing specific code blocks. A couple reasons for using F8 is because previous code in script is either dangerous to run multiple times (deleting files) or has performance impacts (large RESTful call or Get-ChildItem -Recurse). So I run a snippet and then test various things on that snippet and would like one "truth" of where the script is located. |
Hit this today, Really don't want to have to comment lots of code out to run that file. One might argue I need to factor my code correctly, but rightly or wrongly I expect people to be in this situation and want to F8 a script that tries to run files in relative folders. |
Fixing this is a little bit more difficult than it may appear. The big problem with this one is that it's basically impossible to set the That said, the engine creates the variable based on the Register-EditorCommand -Name TestingF8 -DisplayName 'Run selected text and preserve extent' -ScriptBlock {
[System.Diagnostics.DebuggerHidden()]
[System.Diagnostics.DebuggerStepThrough()]
[CmdletBinding()]
param()
end {
function __PSES__GetScriptBlockToInvoke {
$context = $psEditor.GetEditorContext()
$extent = $context.SelectedRange | ConvertTo-ScriptExtent
$newScript = [System.Text.StringBuilder]::new().
Append([char]' ', $extent.StartOffset - $extent.StartLineNumber - $extent.StartColumnNumber).
Append([char]"`n", $extent.StartLineNumber - 1).
Append([char]' ', $extent.StartColumnNumber - 1).
Append($extent.Text).
ToString()
try {
$errors = $null
return [System.Management.Automation.Language.Parser]::ParseInput(
<# input: #> $newScript,
<# fileName: #> $Context.CurrentFile.Path,
<# tokens: #> [ref] $null,
<# errors: #> [ref] $errors).
GetScriptBlock()
} catch [System.Management.Automation.PSInvalidOperationException] {
$exception = New-Object System.Management.Automation.ParseException($errors)
$PSCmdlet.ThrowTerminatingError(
(New-Object System.Management.Automation.ErrorRecord(
<# exception: #> $exception,
<# errorId: #> 'RunSelectionParseError',
<# errorCategory: #> 'ParserError',
<# targetObject: #> $newScript)))
}
}
try {
return . (__PSES__GetScriptBlockToInvoke)
} catch {
if ($PSItem -is [System.Management.Automation.ErrorRecord]) {
$PSCmdlet.ThrowTerminatingError($PSItem)
return
}
$PSCmdlet.ThrowTerminatingError(
(New-Object System.Management.Automation.ErrorRecord(
<# exception: #> $PSItem,
<# errorId: #> 'RunSelectionRuntimeException',
<# errorCategory: #> 'NotSpecified',
<# targetObject: #> $null)))
}
}
} |
Very clever solution! If we tried that, one thing we'd want to do is send the EditorContext along with the run selection request so that it doesn't have to be fetched from within PowerShell, saving another round trip. If the editor sends the EditorContext with the request we can take this approach, otherwise go with the original approach. |
I think this must be be related:
|
@TylerLeonhardt This should probably be retagged as an enhancement, since it's technically working as expected. The ISE-Compatibility tag should also be removed as ISE does the same thing with F8 (unless I'm missing something) so it's not a user experience compatibility thing. |
I can understand the challenges here, unless anyone has a suitable workaround I would love to see a solution. |
@simonsabin if I'm using psscriptroot in my code, I'll set a Breakpoint and use the debugging tool to run either the script itself or a pester test that calls the script, works just fine. |
@simonsabin Maybe? My original issue was with "application code" trying to load vendored DLLs. If the code has no concept of where it's running from then loading dependencies becomes very difficult.
@JustinGrote While this may be a workaround, it's not really feasible to do that for every time you run F8
I'm happy with that. It's somewhat trivial to create (I think) a runspace with no script file e.g.
Write-Host "Outside Block ScriptRoot = $PSScriptRoot"
Invoke-Command {
Write-Host "Inside Block ScriptRoot = $PSScriptRoot"
}
Invoke-Expression 'Write-Host "Inside IEX ScriptRoot = $PSScriptRoot"'
|
@glennsarti by application code I guess I means compiled code in a project like system. @JustinGrote Got to agree with @glennsarti that its not a feasible workaround for every time. Has anyone tried implementing @SeeminglyScience in VS Code |
Is changing the behavior of $PSScriptRoot a good idea? In the terminal, if you copy/paste something $PSScriptRoot is null. We are going to confuse people. |
If you run $PSScriptRoot in PowerShell console it's empty. F8 basically takes the code you highlighted and pastes it into console, so in this case $PSScriptRoot is empty. That's why the behavior is different. To fix it VSCode extension would need to overwrite the default behavior and overwrite $PSScriptRoot variable with folder path of the script the highlighted code was taken from. Not great, not terrible. $PSScriptRoot is simply special variable that is only usable in scripts, rather than in pasted content. If you take the content yourself and paste it into PowerShell console and it acts like it acts now - and it acts differently on F8 which basically is copy/paste into console - it may be look confusing. PS. I don't get the smell analogy - sorry |
@RandyInMarin for Powershell at least, there are several variables like $PSScriptRoot that are only present in the context of a script, not within an interactive session. https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables?view=powershell-7.2#myinvocation When you do F8, you are basically "cut pasting" into the terminal, so the current behavior is exactly what happens outside of vscode at a normal terminal. Like I said, an opt-in setting to populate these to ease development would make sense, if someone wants to attempt a PR I'm sure we could massage it to get accepted, but it definitely wouldn't be the default. |
Okay, I see that I should not test a block of script via F8 and expect the same behavior as F5. For a new, unsaved file or the console, I might have a hard time co-locating. By smell, I mean that perhaps there is a better alternative than $PSScriptRoot in code for locating a file in the same location, even if the location gets weird. Or perhaps importing co-located files is a bad practice in the first place. I've been trying to write code that works the same no matter what. Instead, perhaps I emulate environment variables in Visual Studio by testing the environment at the start of the main script. If it's VSCode or PSISE, set my environment. (Not the same as the environment. Perhaps an environment setting would simplify setting things like default preferences.) |
@RandyInMarin there isn't a great option for relative paths that works everywhere. You may want to consider consolidating your functions into a Powershell module, that way you just import the module and don't have to reference a lot of paths relative to the script. I'm going to close this as "working as expected" |
I also like David's early idea of replacing any custom F8 functionality with just Code's own APIs. I think deleting code is a good reason to do so. |
By Design, but not always as expected. |
@simonsabin for some reason in my brain I tracked this as a different issue, sorry. I think it's still open to, if we don't remove the "clever" F8 functionality, to allow an opt-in setting to reconstitute these variables as part of a F8 run, but it would have a big disclaimer that it would be best-effort. |
@JustinGrote Do we need to create a feature request? |
This issue can be used for tracking the enhancement. I've flagged it as Up for Grabs which means we will entertain PR attempts, right now with the pipeline rework the focus is on getting that to the point it can be promoted to stable so that we can have a regular stable release cadence again, so I wouldn't expect this anytime soon unless someone contributes it. |
I was wondering if this could be solved by the debug capability rather than built in F8. i.e. being able to specify something in launch.json |
{
"type": "PowerShell",
"request": "launch",
"name": "PS: 📄 with args",
"script": "${file}",
"args": [
"${command:SpecifyScriptArgs}"
],
"cwd": "${file}"
}, |
Thanks @JustinGrote.my point was that "script" only takes a filepath. one can't pass a chunk of text.
where run.ps1 is a script that can run the selected text. However you can't override psscriptroot. It looks like @SeeminglyScience solution would be a much better one. |
I had my "AHA" moment when I read this comment. Totally makes sense now why my $PSScriptRoot wasn't working. I was selectively running code segments. Thanks for the solution! |
I suppose what bothers me about $PSScriptRoot and $PSCommandPath is that they are not idempotent. They are populated if they are 1) part of a script and 2) run as a script. Even if it's not run as a script, the script still has a parent folder. It seems misleading to report it as not having a parent folder. Automatic variables that depend upon context this way are going to introduce complexity. I understand that it's complex to simplify the automatic variable. However, such complexity is done once and maintained by one team. Here's one of my current work arounds. Not sure how many more I'll have. Other developers will have their own variations. I think it would be nice to have just one version that just worked without thinking about it. I wonder how many people think about this.
|
What would the parent folder of When you use Run Selection, it's the same as copying and pasting the code to the command line. Testing of scripts proper including $PSScriptRoot you really should be using |
System Details
$PSVersionTable
:Issue Description
$PSScriptRoot
is not populated when running a code block (via F8)I am trying load DLLs that are in the same directory as the PowerShell script and use
$PSScriptRoot
to get the location for them.When running the code in PowerShell it's fine, but using VSCode, when running the code snippet via F8, the variable is not populated.
Repro
Write-Host "$PSScriptRoot\abc"
Expected result:
<full working path>\abc
e.g. If the script I was editing was in
C:\Source
I would expect the result to beC:\Source\abc
Actual result:
\abc
The text was updated successfully, but these errors were encountered: