(I'm a beginner in many aspects, so I apologize if I misuse some terminology.)
If a function runs with a non-terminating error (e.g. {Write-Error "error"}, or simply {a}), $? will be False just after this line executes. But if the error is in the last line of the function, $? will be True in the calling script just after the function call. This is the intended behavior, I see reading Microsoft's article on automatic variables.
function f {
Write-Error "error"
$?
}
f
# f: error
# False
function f {
Write-Error "error"
}
f; $?
# f: error
# True
My question is, how does the builtin function prompt work around this? I'm curious how it works internally. $? is accurate at the beginning of the function, either true or false depending on the last command run. It still works as expected throughout the function, always reflecting the previous success/failure. And then it is also accurate for user input, though this is different from how other functions behave -- even though prompt ran successfully, $? is still false at user input.
The why is clear to me: $? would obviously be useless if prompt clobbered it, so prompt must have special treatment. I am seeking the how. Is it reset above in C#? Or is there a parent script calling prompt, maybe something like (over-simply):
… # Run $PROFILE etc.
prompt
while ($true) {
$? = readLine # Get input, run command, return bool for success/fail
$tmp = $?
prompt
$? = $tmp # Without this, $? would be clobbered
… # Exit conditions etc.
}
Am I close, or way off? I have looked through the Github repository on PowerShell for days, and I unfortunately am struggling to locate the particular implementation, or recognize it.
Supplementary questions: 1) Without saving the value in $?, can prompt possibly access the variable where the old value is stored (like $tmp above), that which will be reassigned to $? after it's called? Even if readonly? 2) I get the impression that prompt is called in the same execution context* that the user is in. Is this true? Or what particularly makes prompt a different function from other functions, other than it's called automatically? 3) Where on the PowerShell repo is this mechanism? Which particular line of code calls prompt, and which resets $??
* Here I'm uncertain about terminology. Between execution context, process, session, or environment, I'm not sure of the precise differences.
I encountered this question when customizing my PowerShell prompt and activating a Python virtual environment (venv). My custom prompt changes according to $?, similar to the green/red arrow on Oh My Zsh. The Python venv also changes the prompt, prefixing with the venv name in parentheses, but in doing so it clobbers $?, so my custom portion always shows success even when the previous command failed. I solved this by passing an argument to prompt, but it led to some deep curiosity and investigation.
PSReadLine.cs seems to be the right place to look to understand. It's difficult for me to not get lost, but I'm continuing to try. A bit of guidance would be so appreciated!
In PSReadLine.cs, method GetPrompt adds a command "prompt", then invokes with System.Management.Automation.PowerShell.Invoke<T>(). Invoke<T> is overloaded in the PowerShell class (PowerShell.cs) with the option to pass an argument for PSInvocationSettings, which has field AddToHistory:
/// <summary>
/// Boolean which tells if the command is added to the history of the
/// Runspace the command is executing in. By default this is false.
/// </summary>
public bool AddToHistory { get; set; }
GetPrompt passes no arguments to Invoke<T>, so AddToHistory takes the default value false. This comes in down the line in LocalPipeline.cs, where method InvokeHelper utilizes bool oldQuestionMarkValue to save the value of $? (or this.LocalRunspace.ExecutionContext.QuestionMarkVariableValue) before executing prompt, *if AddToHistory is false*. After execution, the value is restored (again checking AddToHistory).
// Preserve the last value of $? across non-interactive commands.
if (!AddToHistory)
{
oldQuestionMarkValue = this.LocalRunspace.ExecutionContext.QuestionMarkVariableValue;
// ...
}
// Execute command, etc.
if (!AddToHistory)
{
this.LocalRunspace.ExecutionContext.QuestionMarkVariableValue = oldQuestionMarkValue;
}
So, I've learned it's in the parameters to PowerShell.Invoke whether to restore $?. prompt is invoked with default PSInvocationSettings, so AddToHistory is false, and that is what distinguishes it from other functions. I'm sure there's a great deal more nuance, but I'll take this to describe the common case.
I'll also take it that oldQuestionMarkValue is inaccessible from prompt, and I didn't come across another way to get that old $? value -- I imagine it's impossible.