Skip to content

Commit 99e3fe5

Browse files
SteveL-MSFTdaxian-dbw
authored andcommitted
Enable transcription of native commands on non-Windows (PowerShell#4871)
Transcription was relying on reading the screen buffer to record output from native commands. This resulted in an unhandled exception calling an unimplemented API on non-Windows. The fix is to use redirected output/error if reading the screen buffer is not supported. We check whether screen scraping is supported or not only when the application is running standalone.
1 parent 6e77537 commit 99e3fe5

File tree

3 files changed

+71
-28
lines changed

3 files changed

+71
-28
lines changed

src/System.Management.Automation/engine/NativeCommandProcessor.cs

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ internal NativeCommandProcessor(ApplicationInfo applicationInfo, ExecutionContex
185185

186186
//Create input writer for providing input to the process.
187187
_inputWriter = new ProcessInputWriter(Command);
188+
189+
_isTranscribing = this.Command.Context.EngineHostInterface.UI.IsTranscribing;
188190
}
189191

190192
/// <summary>
@@ -373,8 +375,8 @@ internal override void ProcessRecord()
373375
/// </summary>
374376
private BlockingCollection<ProcessOutputObject> _nativeProcessOutputQueue;
375377

376-
private bool _scrapeHostOutput;
377-
378+
private static bool? s_supportScreenScrape = null;
379+
private bool _isTranscribing;
378380
private Host.Coordinates _startPosition;
379381

380382
/// <summary>
@@ -398,11 +400,13 @@ private void InitNativeProcess()
398400
// redirecting anything. This is a bit tricky as we always run redirected so
399401
// we have to see if the redirection is actually being done at the topmost level or not.
400402

401-
//Calculate if input and output are redirected.
403+
// Calculate if input and output are redirected.
402404
bool redirectOutput;
403405
bool redirectError;
404406
bool redirectInput;
405407

408+
_startPosition = new Host.Coordinates();
409+
406410
CalculateIORedirection(out redirectOutput, out redirectError, out redirectInput);
407411

408412
// Find out if it's the only command in the pipeline.
@@ -416,9 +420,6 @@ private void InitNativeProcess()
416420
throw new PipelineStoppedException();
417421
}
418422

419-
_startPosition = new Host.Coordinates();
420-
_scrapeHostOutput = false;
421-
422423
Exception exceptionToRethrow = null;
423424
try
424425
{
@@ -432,19 +433,10 @@ private void InitNativeProcess()
432433

433434
// Also, store the Raw UI coordinates so that we can scrape the screen after
434435
// if we are transcribing.
435-
try
436+
if (_isTranscribing && (true == s_supportScreenScrape))
436437
{
437-
if (this.Command.Context.EngineHostInterface.UI.IsTranscribing)
438-
{
439-
_scrapeHostOutput = true;
440-
_startPosition = this.Command.Context.EngineHostInterface.UI.RawUI.CursorPosition;
441-
_startPosition.X = 0;
442-
}
443-
}
444-
catch (Host.HostException)
445-
{
446-
// The host doesn't support scraping via its RawUI interface
447-
_scrapeHostOutput = false;
438+
_startPosition = this.Command.Context.EngineHostInterface.UI.RawUI.CursorPosition;
439+
_startPosition.X = 0;
448440
}
449441
}
450442

@@ -697,9 +689,8 @@ internal override void Complete()
697689
ConsumeAvailableNativeProcessOutput(blocking: true);
698690
_nativeProcess.WaitForExit();
699691

700-
// Capture screen output if we are transcribing
701-
if (this.Command.Context.EngineHostInterface.UI.IsTranscribing &&
702-
_scrapeHostOutput)
692+
// Capture screen output if we are transcribing and running stand alone
693+
if (_isTranscribing && (true == s_supportScreenScrape) && _runStandAlone)
703694
{
704695
Host.Coordinates endPosition = this.Command.Context.EngineHostInterface.UI.RawUI.CursorPosition;
705696
endPosition.X = this.Command.Context.EngineHostInterface.UI.RawUI.BufferSize.Width - 1;
@@ -1287,6 +1278,33 @@ private void CalculateIORedirection(out bool redirectOutput, out bool redirectEr
12871278
}
12881279

12891280
_runStandAlone = !redirectInput && !redirectOutput && !redirectError;
1281+
1282+
if (_runStandAlone)
1283+
{
1284+
if (null == s_supportScreenScrape)
1285+
{
1286+
try
1287+
{
1288+
_startPosition = this.Command.Context.EngineHostInterface.UI.RawUI.CursorPosition;
1289+
Host.BufferCell[,] bufferContents = this.Command.Context.EngineHostInterface.UI.RawUI.GetBufferContents(
1290+
new Host.Rectangle(_startPosition, _startPosition));
1291+
s_supportScreenScrape = true;
1292+
}
1293+
catch (Exception)
1294+
{
1295+
s_supportScreenScrape = false;
1296+
}
1297+
}
1298+
1299+
// if screen scraping isn't supported, we enable redirection so that the output is still transcribed
1300+
// as redirected output is always transcribed
1301+
if (_isTranscribing && (false == s_supportScreenScrape))
1302+
{
1303+
redirectOutput = true;
1304+
redirectError = true;
1305+
_runStandAlone = false;
1306+
}
1307+
}
12901308
}
12911309

12921310
private bool ValidateExtension(string path)

test/powershell/Modules/Microsoft.Powershell.Host/Start-Transcript.Tests.ps1

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,20 +107,19 @@ Describe "Start-Transcript, Stop-Transcript tests" -tags "CI" {
107107
ValidateTranscription -scriptToExecute $script -outputFilePath $null -expectedError $expectedError
108108
}
109109
It "Transcription should remain active if other runspace in the host get closed" {
110-
try{
110+
try {
111111
$ps = [powershell]::Create()
112112
$ps.addscript("Start-Transcript -path $transcriptFilePath").Invoke()
113113
$ps.addscript('$rs = [system.management.automation.runspaces.runspacefactory]::CreateRunspace()').Invoke()
114114
$ps.addscript('$rs.open()').Invoke()
115115
$ps.addscript('$rs.Dispose()').Invoke()
116116
$ps.addscript('Write-Host "After Dispose"').Invoke()
117117
$ps.addscript("Stop-Transcript").Invoke()
118-
} finally {
119-
if ($null -ne $ps) {
120-
$ps.Dispose()
121-
}
118+
} finally {
119+
if ($null -ne $ps) {
120+
$ps.Dispose()
122121
}
123-
122+
}
124123

125124
Test-Path $transcriptFilePath | Should be $true
126125
$transcriptFilePath | Should contain "After Dispose"
@@ -136,4 +135,15 @@ Describe "Start-Transcript, Stop-Transcript tests" -tags "CI" {
136135
$transcriptFilePath | Should contain "PowerShell transcript end"
137136
}
138137

139-
}
138+
It "Transcription should record native command output" {
139+
$script = {
140+
Start-Transcript -Path $transcriptFilePath
141+
hostname
142+
Stop-Transcript }
143+
& $script
144+
Test-Path $transcriptFilePath | Should be $true
145+
146+
$machineName = [System.Environment]::MachineName
147+
$transcriptFilePath | Should contain $machineName
148+
}
149+
}

test/powershell/engine/Job/Jobs.Tests.ps1

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ Describe 'Basic Job Tests' -Tags 'CI' {
1717
Receive-Job $job -wait | should be 1
1818
}
1919

20+
It "Create job with native command" {
21+
try {
22+
$nativeJob = Start-job { powershell -c 1+1 }
23+
$nativeJob | Wait-Job
24+
$nativeJob.State | Should BeExactly "Completed"
25+
$nativeJob.HasMoreData | Should Be $true
26+
Receive-Job $nativeJob | Should BeExactly 2
27+
Remove-Job $nativeJob
28+
{ Get-Job $nativeJob -ErrorAction Stop } | ShouldBeErrorId "JobWithSpecifiedNameNotFound,Microsoft.PowerShell.Commands.GetJobCommand"
29+
}
30+
finally {
31+
Remove-Job $nativeJob -Force -ErrorAction SilentlyContinue
32+
}
33+
}
34+
2035
AfterAll {
2136
Remove-Job $job -Force
2237
}

0 commit comments

Comments
 (0)