Skip to content

Commit ce9d92d

Browse files
committed
Fix disappearing output bug by resetting TranscribeOnly
What a bug! In PowerShell 5.1 our artificial pipeline for event handling (among other possible PowerShell tasks) could cause the output to just disappear. Turns out a bug in 5.1 with the transcript was being triggered. Scripts without `Out-Default` would get it appended with `-TranscribeOnly True` in order for the transcription to happen...but the `TranscribeOnly` field could erroneously not be set back to `false`! This would cause all output to just "disappear." It was fixed in PowerShell 7, but we still have to work around it for PowerShell 5.1 by resetting it.
1 parent 9e5bab5 commit ce9d92d

File tree

2 files changed

+43
-2
lines changed

2 files changed

+43
-2
lines changed

Diff for: src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs

+30-1
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,39 @@
77
using System.Collections.ObjectModel;
88
using System.Management.Automation;
99
using System.Management.Automation.Host;
10+
using System.Reflection;
1011
using System.Security;
1112
using Microsoft.Extensions.Logging;
13+
using Microsoft.PowerShell.EditorServices.Utility;
1214

1315
namespace Microsoft.PowerShell.EditorServices.Services.PowerShell.Host
1416
{
1517
internal class EditorServicesConsolePSHostUserInterface : PSHostUserInterface, IHostUISupportsMultipleChoiceSelection
1618
{
1719
private readonly PSHostUserInterface _underlyingHostUI;
1820

21+
private static readonly Action<PSHostUserInterface, bool> s_setTranscribeOnlyDelegate;
22+
1923
/// <summary>
2024
/// We use a ConcurrentDictionary because ConcurrentHashSet does not exist, hence the value
2125
/// is never actually used, and `WriteProgress` must be thread-safe.
2226
/// </summary>
2327
private readonly ConcurrentDictionary<(long, int), object> _currentProgressRecords = new();
2428

29+
static EditorServicesConsolePSHostUserInterface()
30+
{
31+
if (VersionUtils.IsPS5)
32+
{
33+
PropertyInfo transcribeOnlyProperty = typeof(PSHostUserInterface)
34+
.GetProperty("TranscribeOnly", BindingFlags.NonPublic | BindingFlags.Instance);
35+
36+
MethodInfo transcribeOnlySetMethod = transcribeOnlyProperty.GetSetMethod(nonPublic: true);
37+
38+
s_setTranscribeOnlyDelegate = (Action<PSHostUserInterface, bool>)Delegate.CreateDelegate(
39+
typeof(Action<PSHostUserInterface, bool>), transcribeOnlySetMethod);
40+
}
41+
}
42+
2543
public EditorServicesConsolePSHostUserInterface(
2644
ILoggerFactory loggerFactory,
2745
PSHostUserInterface underlyingHostUI)
@@ -70,7 +88,7 @@ public override void WriteProgress(long sourceId, ProgressRecord record)
7088
_underlyingHostUI.WriteProgress(sourceId, record);
7189
}
7290

73-
public void ResetProgress()
91+
internal void ResetProgress()
7492
{
7593
// Mark all processed progress records as completed.
7694
foreach ((long sourceId, int activityId) in _currentProgressRecords.Keys)
@@ -87,6 +105,17 @@ public void ResetProgress()
87105
// TODO: Maybe send the OSC sequence to turn off progress indicator.
88106
}
89107

108+
// This works around a bug in PowerShell 5.1 (that was later fixed) where a running
109+
// transcription could cause output to disappear since the `TranscribeOnly` property was
110+
// accidentally not reset to false.
111+
internal void DisableTranscribeOnly()
112+
{
113+
if (VersionUtils.IsPS5)
114+
{
115+
s_setTranscribeOnlyDelegate(_underlyingHostUI, false);
116+
}
117+
}
118+
90119
public override void WriteVerboseLine(string message) => _underlyingHostUI.WriteVerboseLine(message);
91120

92121
public override void WriteWarningLine(string message) => _underlyingHostUI.WriteWarningLine(message);

Diff for: src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs

+13-1
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,19 @@ public void InvokeDelegate(string representation, ExecutionOptions executionOpti
476476
public IReadOnlyList<TResult> InvokePSCommand<TResult>(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken)
477477
{
478478
SynchronousPowerShellTask<TResult> task = new(_logger, this, psCommand, executionOptions, cancellationToken);
479-
return task.ExecuteAndGetResult(cancellationToken);
479+
try
480+
{
481+
return task.ExecuteAndGetResult(cancellationToken);
482+
}
483+
finally
484+
{
485+
// At the end of each PowerShell command we need to reset PowerShell 5.1's
486+
// `TranscribeOnly` property to avoid a bug where output disappears.
487+
if (UI is EditorServicesConsolePSHostUserInterface ui)
488+
{
489+
ui.DisableTranscribeOnly();
490+
}
491+
}
480492
}
481493

482494
public void InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) => InvokePSCommand<PSObject>(psCommand, executionOptions, cancellationToken);

0 commit comments

Comments
 (0)