-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Out-Default incorrectly staying in transcript state #3392
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
Conversation
PetSerAl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It ends up Out-Default -Transcript just set TranscribeOnly = true on start (BeginProcessing) and TranscribeOnly = false on end (Dispose).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You ends up restoring TranscribeOnly even if you not capture it before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You never set _transcriptStateChanged back to false. So Out-Default stop capturing TranscribeOnly after first call with -Transcript switch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out-Default lifetime only exists in that pipeline and typically should be the last thing in the pipeline as nothing is emitted if used with -Transcript. Is there a case that's broken?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not going to say that nested or intersected Out-Default -Transcript are useful scenario, but if you are not support it, then (taking in account that OutDefaultCommand is the only place where TranscribeOnly modified) the only value that _savedTranscribeOnly will have is false. So, why bother and define variable, which will be always false?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You define _transcriptStateChanged as static, so it affect entire AppDomain. That might be not good when multiple PowerShell Runspaces share same AppDomain.
& { [PowerShell]::Create().AddCommand('Out-Default').AddParameter('Transcript').Invoke() } | Out-Default -Transcript|
@PetSerAl I appreciate the feedback. I rethought about how BeginProcessing, ProcessRecord, and EndProcessing work and I think I have a solution that remedies your concerns |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just asking - but should TranscribeOnly be a counter that is incremented each time it's true, and decremented in Dispose?
I'm not sure this is the correct semantic, but it seems right - like it shouldn't be possible to turn it off as long as somebody wants it on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only learned that Out-Default had a -Transcript switch when this issues was opened and I started looking into it (documentation and tests are also lacking).
Best as I can tell, although you can have nested Start-Transcript, Out-Default is only used at the end of a pipeline, so the test case of Out-Default -Transcript | Out-Default -Transcript I don't think represents a real use case as the intent of -Transcript (as far as I can tell) is to suppress output to the pipeline and only send it to the transcript. However, it got the console stuck in transcript mode, so I thought it was worth fixing (the other issue with exception being raised is a real use case).
So I don't think we need a counter here as it should really just toggle from true and false when this cmdlet is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as the intent of -Transcript (as far as I can tell) is to suppress output to the pipeline
Sadly it is exact opposite of how Out-Default -Transcript is implemented. By default Out-Default does not output anything, but display input on host. With -Transcript it pass thru the input, but making following Out-Default/Out-Host to not display their input:
1..10 | Out-Default -Transcript | Out-HostBut by replacing Out-Host with Write-Host you can see, that output is really here:
1..10 | Out-Default -Transcript | Write-HostThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the code, the only thing TranscribeOnly does is not write to the console so I mispoke when I said pipeline (should have said host).
|
@SteveL-MSFT I can not think of any problematic cases now, at least |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you really need explicitly state IDisposable here? FrontEndCommandBase already implement IDisposable and it is not like you need to reimplement IDisposable.Dispose() here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was a leftover from before I realized FrontEndCommandBase implements IDisposable. Will remove.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are not you supposed to call base.InternalDispose() at some point of overriding method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're correct. Will fix.
…uent use was caching the already changed state Out-Default needed to implement IDispose pattern so transcription state reverts back on exception in pipeline
addressed feedback from code review on using static
added missing call to base.InternalDispose()
|
@SteveL-MSFT I do not see any obvious wrong things. I still can construct the case where host stuck in begin{
$sp = {Out-Default -Transcript}.GetSteppablePipeline()
$sp.Begin($false)
$sp.Process()
}
end{
$sp.End()
$sp.Dispose()
}Possible, the only way to prevent this is to implement |
|
I'll rework it to use a counter. Thanks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are doing this in process block, once for each input item, not once per Out-Default invocation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving back to BeginProcessing()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are not checking if you actually increment TranscribeOnlyCount before. That lead to situation where every call to Out-Default reduce counter. Including ones without -Transcript. Or where begin block of Out-Default does not have opportunity to execute:
& {begin{throw}} | Out-Default -TranscriptThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe wrap this with try/finally, so you have opportunity to clean up even if base.InternalDispose() throw exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good
… -Trascript stays in TranscibeOnly Add try..finally in InternalDispose to ensure TranscribeOnlyCount gets decremented
|
To think about that. Should not it also properly revert $sp = {Out-Default -Transcript}.GetSteppablePipeline()
$sp.Begin($false)
$sp = $null
[GC]::Collect()I have idea about implementation. Can you look at it? |
|
@PetSerAl I'll look at this today |
|
@PetSerAl I stepped through the debugger and BeginProcessing is always called and ProcessRecord is called where InputObject is empty. Finally, Dispose() is called. In both your samples, once the script is complete, Host.UI.TranscribeOnlyCount = 0. |
|
@SteveL-MSFT Not sure, what exact code you are referencing as my samples, but I use following code for test: PS> Add-Type @‘
>>> using System;
>>> using System.Management.Automation;
>>> [Cmdlet("Test", "Methods")]
>>> public class TestMethods : PSCmdlet, IDisposable {
>>> public TestMethods() {
>>> Console.WriteLine("Constructor");
>>> }
>>> ~TestMethods() {
>>> Console.WriteLine("Destructor");
>>> }
>>> protected override void BeginProcessing() {
>>> Console.WriteLine("Begin");
>>> }
>>> protected override void ProcessRecord() {
>>> Console.WriteLine("Process");
>>> }
>>> protected override void EndProcessing() {
>>> Console.WriteLine("End");
>>> }
>>> protected override void StopProcessing() {
>>> Console.WriteLine("Stop");
>>> }
>>> public void Dispose() {
>>> Console.WriteLine("Dispose");
>>> }
>>> }
>>> ’@ -PassThru | Select-Object -First 1 -ExpandProperty Assembly | Import-Module
PS> $sp = {Test-Methods}.GetSteppablePipeline();
Constructor
PS> $sp.Begin($false)
Begin
PS> $sp = $null
PS> [GC]::Collect()
Destructor
PS>As you can see, |
|
@PetSerAl, by samples, I mean: begin{
$sp = {Out-Default -Transcript}.GetSteppablePipeline()
$sp.Begin($false)
$sp.Process()
}
end{
$sp.End()
$sp.Dispose()
}and $sp = {Out-Default -Transcript}.GetSteppablePipeline()
$sp.Begin($false)
$sp = $null
[GC]::Collect()In both of these cases, the |
|
@SteveL-MSFT The first sample got fixed, when you implement PS> $sp = {Out-Default -Transcript}.GetSteppablePipeline()
PS> $sp.Begin($false)
PS> $sp = $null
PS> [GC]::Collect()
PS> 1..10
PS> [System.Management.Automation.Host.PSHostUserInterface].GetProperty('TranscribeOnlyCount', [System.Reflection.BindingFlags]'Instance, NonPublic').GetValue($Host.UI) | Write-Host
1
PS> |
|
@PetSerAl your proposed change looks good to me, can you submit that as a PR and include the tests I added to this PR? I'll close this one |
|
@SteveL-MSFT OK. I can do that. |
|
@PetSerAl thanks for your help on this |
Out-Default was incorrectly caching transcription state as the subsequent use was caching the already changed state
Out-Default needed to implement IDispose pattern so transcription state reverts back on exception in pipeline
Addresses #3390