Out-Default incorrectly staying in transcript state#3392
Out-Default incorrectly staying in transcript state#3392SteveL-MSFT wants to merge 6 commits intoPowerShell:masterfrom
Conversation
PetSerAl
left a comment
There was a problem hiding this comment.
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.
You ends up restoring TranscribeOnly even if you not capture it before.
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
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.
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.
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.
That was a leftover from before I realized FrontEndCommandBase implements IDisposable. Will remove.
There was a problem hiding this comment.
Are not you supposed to call base.InternalDispose() at some point of overriding method?
There was a problem hiding this comment.
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.
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.
Moving back to BeginProcessing()
There was a problem hiding this comment.
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.
Maybe wrap this with try/finally, so you have opportunity to clean up even if base.InternalDispose() throw exception.
… -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