Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 14f2be8

Browse files
Merge pull request #2023 from PowerShell/andschwa/fix-output-bug
Fix disappearing output in PowerShell 5.1
2 parents 3401a2d + 9d2a151 commit 14f2be8

File tree

10 files changed

+101
-31
lines changed

10 files changed

+101
-31
lines changed

‎src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,9 @@ private void LogHostInformation()
350350
private static string GetPSOutputEncoding()
351351
{
352352
using SMA.PowerShell pwsh = SMA.PowerShell.Create();
353-
return pwsh.AddScript("$OutputEncoding.EncodingName", useLocalScope: true).Invoke<string>()[0];
353+
return pwsh.AddScript(
354+
"[System.Diagnostics.DebuggerHidden()]param() $OutputEncoding.EncodingName",
355+
useLocalScope: true).Invoke<string>()[0];
354356
}
355357

356358
// TODO: Deduplicate this with VersionUtils.

‎src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ public async Task<string> SetVariableAsync(int variableContainerReferenceId, str
367367

368368
// Evaluate the expression to get back a PowerShell object from the expression string.
369369
// This may throw, in which case the exception is propagated to the caller
370-
PSCommand evaluateExpressionCommand = new PSCommand().AddScript(value);
370+
PSCommand evaluateExpressionCommand = new PSCommand().AddScript($"[System.Diagnostics.DebuggerHidden()]param() {value}");
371371
IReadOnlyList<object> expressionResults = await _executionService.ExecutePSCommandAsync<object>(evaluateExpressionCommand, CancellationToken.None).ConfigureAwait(false);
372372
if (expressionResults.Count == 0)
373373
{
@@ -500,7 +500,7 @@ public async Task<VariableDetails> EvaluateExpressionAsync(
500500
bool writeResultAsOutput,
501501
CancellationToken cancellationToken)
502502
{
503-
PSCommand command = new PSCommand().AddScript(expressionString);
503+
PSCommand command = new PSCommand().AddScript($"[System.Diagnostics.DebuggerHidden()]param() {expressionString}");
504504
IReadOnlyList<PSObject> results;
505505
try
506506
{
@@ -799,7 +799,7 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
799799

800800
// PSObject is used here instead of the specific type because we get deserialized
801801
// objects from remote sessions and want a common interface.
802-
PSCommand psCommand = new PSCommand().AddScript($"[Collections.ArrayList]{callStackVarName} = @(); {getPSCallStack}; {returnSerializedIfInRemoteRunspace}");
802+
PSCommand psCommand = new PSCommand().AddScript($"[System.Diagnostics.DebuggerHidden()]param() [Collections.ArrayList]{callStackVarName} = @(); {getPSCallStack}; {returnSerializedIfInRemoteRunspace}");
803803
IReadOnlyList<PSObject> results = await _executionService.ExecutePSCommandAsync<PSObject>(psCommand, CancellationToken.None).ConfigureAwait(false);
804804

805805
IEnumerable callStack = isRemoteRunspace

‎src/PowerShellEditorServices/Services/DebugAdapter/Handlers/DebugEvaluateHandler.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public async Task<EvaluateResponseBody> Handle(EvaluateRequestArguments request,
4848
if (isFromRepl)
4949
{
5050
await _executionService.ExecutePSCommandAsync(
51-
new PSCommand().AddScript(request.Expression),
51+
new PSCommand().AddScript($"[System.Diagnostics.DebuggerHidden()]param() {request.Expression}"),
5252
cancellationToken,
5353
new PowerShellExecutionOptions { WriteOutputToHost = true, ThrowOnError = false, AddToHistory = true }).HandleErrorsAsync(_logger).ConfigureAwait(false);
5454
}

‎src/PowerShellEditorServices/Services/PowerShell/Context/PowerShellVersionDetails.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public static PowerShellVersionDetails GetVersionDetails(ILogger logger, PowerSh
5555
try
5656
{
5757
Hashtable psVersionTable = pwsh
58-
.AddScript("$PSVersionTable", useLocalScope: true)
58+
.AddScript("[System.Diagnostics.DebuggerHidden()]param() $PSVersionTable", useLocalScope: true)
5959
.InvokeAndClear<Hashtable>()
6060
.FirstOrDefault();
6161

‎src/PowerShellEditorServices/Services/PowerShell/Handlers/ExpandAliasHandler.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public async Task<ExpandAliasResult> Handle(ExpandAliasParams request, Cancellat
3333
{
3434
const string script = @"
3535
function __Expand-Alias {
36-
36+
[System.Diagnostics.DebuggerHidden()]
3737
param($targetScript)
3838
3939
[ref]$errors=$null

‎src/PowerShellEditorServices/Services/PowerShell/Handlers/ShowHelpHandler.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ internal class ShowHelpHandler : IShowHelpHandler
2727

2828
public async Task<Unit> Handle(ShowHelpParams request, CancellationToken cancellationToken)
2929
{
30+
// TODO: Refactor to not rerun the function definition every time.
3031
const string CheckHelpScript = @"
32+
[System.Diagnostics.DebuggerHidden()]
3133
[CmdletBinding()]
3234
param (
3335
[String]$CommandName

‎src/PowerShellEditorServices/Services/PowerShell/Host/EditorServicesConsolePSHostUserInterface.cs‎

Lines changed: 30 additions & 1 deletion
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);

‎src/PowerShellEditorServices/Services/PowerShell/Host/PsesInternalHost.cs‎

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns
3535
{
3636
internal const string DefaultPrompt = "> ";
3737

38+
private static readonly PSCommand s_promptCommand = new PSCommand().AddCommand("prompt");
39+
3840
private static readonly PropertyInfo s_scriptDebuggerTriggerObjectProperty;
3941

4042
private readonly ILoggerFactory _loggerFactory;
@@ -474,7 +476,19 @@ public void InvokeDelegate(string representation, ExecutionOptions executionOpti
474476
public IReadOnlyList<TResult> InvokePSCommand<TResult>(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken)
475477
{
476478
SynchronousPowerShellTask<TResult> task = new(_logger, this, psCommand, executionOptions, cancellationToken);
477-
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+
}
478492
}
479493

480494
public void InvokePSCommand(PSCommand psCommand, PowerShellExecutionOptions executionOptions, CancellationToken cancellationToken) => InvokePSCommand<PSObject>(psCommand, executionOptions, cancellationToken);
@@ -1026,10 +1040,8 @@ internal string GetPrompt(CancellationToken cancellationToken)
10261040
string prompt = DefaultPrompt;
10271041
try
10281042
{
1029-
// TODO: Should we cache PSCommands like this as static members?
1030-
PSCommand command = new PSCommand().AddCommand("prompt");
10311043
IReadOnlyList<string> results = InvokePSCommand<string>(
1032-
command,
1044+
s_promptCommand,
10331045
executionOptions: new PowerShellExecutionOptions { ThrowOnError = false },
10341046
cancellationToken);
10351047

@@ -1207,7 +1219,18 @@ private Runspace CreateInitialRunspace(InitialSessionState initialSessionState)
12071219
return runspace;
12081220
}
12091221

1210-
// NOTE: This token is received from PSReadLine, and it _is_ the ReadKey cancellation token!
1222+
/// <summary>
1223+
/// This delegate is handed to PSReadLine and overrides similar logic within its `ReadKey`
1224+
/// method. Essentially we're replacing PowerShell's `OnIdle` handler since the PowerShell
1225+
/// engine isn't idle when we're sitting in PSReadLine's `ReadKey` loop. In our case we also
1226+
/// use this idle time to process queued tasks by executing those that can run in the
1227+
/// background, and canceling the foreground task if a queued tasks requires the foreground.
1228+
/// Finally, if and only if we have to, we run an artificial pipeline to force PowerShell's
1229+
/// own event processing.
1230+
/// </summary>
1231+
/// <param name="idleCancellationToken">
1232+
/// This token is received from PSReadLine, and it is the ReadKey cancellation token!
1233+
/// </param>
12111234
internal void OnPowerShellIdle(CancellationToken idleCancellationToken)
12121235
{
12131236
IReadOnlyList<PSEventSubscriber> eventSubscribers = _mainRunspaceEngineIntrinsics.Events.Subscribers;
@@ -1250,17 +1273,27 @@ internal void OnPowerShellIdle(CancellationToken idleCancellationToken)
12501273

12511274
// If we're executing a PowerShell task, we don't need to run an extra pipeline
12521275
// later for events.
1253-
runPipelineForEventProcessing = task is not ISynchronousPowerShellTask;
1276+
if (task is ISynchronousPowerShellTask)
1277+
{
1278+
// We don't ever want to set this to true here, just skip if it had
1279+
// previously been set true.
1280+
runPipelineForEventProcessing = false;
1281+
}
12541282
ExecuteTaskSynchronously(task, cancellationScope.CancellationToken);
12551283
}
12561284
}
12571285

12581286
// We didn't end up executing anything in the background,
12591287
// so we need to run a small artificial pipeline instead
1260-
// to force event processing
1288+
// to force event processing.
12611289
if (runPipelineForEventProcessing)
12621290
{
1263-
InvokePSCommand(new PSCommand().AddScript("0", useLocalScope: true), executionOptions: null, CancellationToken.None);
1291+
InvokePSCommand(
1292+
new PSCommand().AddScript(
1293+
"[System.Diagnostics.DebuggerHidden()]param() 0",
1294+
useLocalScope: true),
1295+
executionOptions: null,
1296+
CancellationToken.None);
12641297
}
12651298
}
12661299

‎src/PowerShellEditorServices/Services/PowerShell/Runspace/SessionDetails.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public static SessionDetails GetFromPowerShell(PowerShell pwsh)
2727
{
2828
Hashtable detailsObject = pwsh
2929
.AddScript(
30-
$"@{{ '{Property_ComputerName}' = if ([Environment]::MachineName) {{[Environment]::MachineName}} else {{'localhost'}}; '{Property_ProcessId}' = $PID; '{Property_InstanceId}' = $host.InstanceId }}",
30+
$"[System.Diagnostics.DebuggerHidden()]param() @{{ '{Property_ComputerName}' = if ([Environment]::MachineName) {{[Environment]::MachineName}} else {{'localhost'}}; '{Property_ProcessId}' = $PID; '{Property_InstanceId}' = $host.InstanceId }}",
3131
useLocalScope: true)
3232
.InvokeAndClear<Hashtable>()
3333
.FirstOrDefault();

‎src/PowerShellEditorServices/Services/Symbols/SymbolsService.cs‎

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,6 @@ public IEnumerable<SymbolReference> FindSymbolsInFile(ScriptFile scriptFile)
137137
// asserting we should use a giant nested ternary.
138138
private static string[] GetIdentifiers(string symbolName, SymbolType symbolType, CommandHelpers.AliasMap aliases)
139139
{
140-
if (symbolType is not SymbolType.Function)
141-
{
142-
return new[] { symbolName };
143-
}
144-
145140
if (!aliases.CmdletToAliases.TryGetValue(symbolName, out List<string> foundAliasList))
146141
{
147142
return new[] { symbolName };
@@ -165,22 +160,31 @@ public async Task<IEnumerable<SymbolReference>> ScanForReferencesOfSymbolAsync(
165160
return Enumerable.Empty<SymbolReference>();
166161
}
167162

168-
// TODO: Should we handle aliases at a lower level?
169-
CommandHelpers.AliasMap aliases = await CommandHelpers.GetAliasesAsync(
170-
_executionService,
171-
cancellationToken).ConfigureAwait(false);
163+
// We want to handle aliases for functions, but we only want to do the work of getting
164+
// the aliases when we must. We can't cache the alias list on first run else we won't
165+
// support newly defined aliases.
166+
string[] allIdentifiers;
167+
if (symbol.Type is SymbolType.Function)
168+
{
169+
CommandHelpers.AliasMap aliases = await CommandHelpers.GetAliasesAsync(
170+
_executionService,
171+
cancellationToken).ConfigureAwait(false);
172172

173-
string targetName = symbol.Id;
174-
if (symbol.Type is SymbolType.Function
175-
&& aliases.AliasToCmdlets.TryGetValue(symbol.Id, out string aliasDefinition))
173+
string targetName = symbol.Id;
174+
if (aliases.AliasToCmdlets.TryGetValue(symbol.Id, out string aliasDefinition))
175+
{
176+
targetName = aliasDefinition;
177+
}
178+
allIdentifiers = GetIdentifiers(targetName, symbol.Type, aliases);
179+
}
180+
else
176181
{
177-
targetName = aliasDefinition;
182+
allIdentifiers = new[]{symbol.Id};
178183
}
179184

180185
await ScanWorkspacePSFiles(cancellationToken).ConfigureAwait(false);
181186

182187
List<SymbolReference> symbols = new();
183-
string[] allIdentifiers = GetIdentifiers(targetName, symbol.Type, aliases);
184188

185189
foreach (ScriptFile file in _workspaceService.GetOpenedFiles())
186190
{

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /