Enable transcription of native commands on non-Windows (#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.
This commit is contained in:
Steve Lee 2017-09-28 17:15:37 -07:00 committed by Dongbo Wang
parent 6e77537181
commit 99e3fe586e
3 changed files with 71 additions and 28 deletions

View file

@ -185,6 +185,8 @@ namespace System.Management.Automation
//Create input writer for providing input to the process.
_inputWriter = new ProcessInputWriter(Command);
_isTranscribing = this.Command.Context.EngineHostInterface.UI.IsTranscribing;
}
/// <summary>
@ -373,8 +375,8 @@ namespace System.Management.Automation
/// </summary>
private BlockingCollection<ProcessOutputObject> _nativeProcessOutputQueue;
private bool _scrapeHostOutput;
private static bool? s_supportScreenScrape = null;
private bool _isTranscribing;
private Host.Coordinates _startPosition;
/// <summary>
@ -398,11 +400,13 @@ namespace System.Management.Automation
// redirecting anything. This is a bit tricky as we always run redirected so
// we have to see if the redirection is actually being done at the topmost level or not.
//Calculate if input and output are redirected.
// Calculate if input and output are redirected.
bool redirectOutput;
bool redirectError;
bool redirectInput;
_startPosition = new Host.Coordinates();
CalculateIORedirection(out redirectOutput, out redirectError, out redirectInput);
// Find out if it's the only command in the pipeline.
@ -416,9 +420,6 @@ namespace System.Management.Automation
throw new PipelineStoppedException();
}
_startPosition = new Host.Coordinates();
_scrapeHostOutput = false;
Exception exceptionToRethrow = null;
try
{
@ -432,19 +433,10 @@ namespace System.Management.Automation
// Also, store the Raw UI coordinates so that we can scrape the screen after
// if we are transcribing.
try
if (_isTranscribing && (true == s_supportScreenScrape))
{
if (this.Command.Context.EngineHostInterface.UI.IsTranscribing)
{
_scrapeHostOutput = true;
_startPosition = this.Command.Context.EngineHostInterface.UI.RawUI.CursorPosition;
_startPosition.X = 0;
}
}
catch (Host.HostException)
{
// The host doesn't support scraping via its RawUI interface
_scrapeHostOutput = false;
_startPosition = this.Command.Context.EngineHostInterface.UI.RawUI.CursorPosition;
_startPosition.X = 0;
}
}
@ -697,9 +689,8 @@ namespace System.Management.Automation
ConsumeAvailableNativeProcessOutput(blocking: true);
_nativeProcess.WaitForExit();
// Capture screen output if we are transcribing
if (this.Command.Context.EngineHostInterface.UI.IsTranscribing &&
_scrapeHostOutput)
// Capture screen output if we are transcribing and running stand alone
if (_isTranscribing && (true == s_supportScreenScrape) && _runStandAlone)
{
Host.Coordinates endPosition = this.Command.Context.EngineHostInterface.UI.RawUI.CursorPosition;
endPosition.X = this.Command.Context.EngineHostInterface.UI.RawUI.BufferSize.Width - 1;
@ -1287,6 +1278,33 @@ namespace System.Management.Automation
}
_runStandAlone = !redirectInput && !redirectOutput && !redirectError;
if (_runStandAlone)
{
if (null == s_supportScreenScrape)
{
try
{
_startPosition = this.Command.Context.EngineHostInterface.UI.RawUI.CursorPosition;
Host.BufferCell[,] bufferContents = this.Command.Context.EngineHostInterface.UI.RawUI.GetBufferContents(
new Host.Rectangle(_startPosition, _startPosition));
s_supportScreenScrape = true;
}
catch (Exception)
{
s_supportScreenScrape = false;
}
}
// if screen scraping isn't supported, we enable redirection so that the output is still transcribed
// as redirected output is always transcribed
if (_isTranscribing && (false == s_supportScreenScrape))
{
redirectOutput = true;
redirectError = true;
_runStandAlone = false;
}
}
}
private bool ValidateExtension(string path)

View file

@ -107,7 +107,7 @@ Describe "Start-Transcript, Stop-Transcript tests" -tags "CI" {
ValidateTranscription -scriptToExecute $script -outputFilePath $null -expectedError $expectedError
}
It "Transcription should remain active if other runspace in the host get closed" {
try{
try {
$ps = [powershell]::Create()
$ps.addscript("Start-Transcript -path $transcriptFilePath").Invoke()
$ps.addscript('$rs = [system.management.automation.runspaces.runspacefactory]::CreateRunspace()').Invoke()
@ -115,12 +115,11 @@ Describe "Start-Transcript, Stop-Transcript tests" -tags "CI" {
$ps.addscript('$rs.Dispose()').Invoke()
$ps.addscript('Write-Host "After Dispose"').Invoke()
$ps.addscript("Stop-Transcript").Invoke()
} finally {
if ($null -ne $ps) {
$ps.Dispose()
}
} finally {
if ($null -ne $ps) {
$ps.Dispose()
}
}
Test-Path $transcriptFilePath | Should be $true
$transcriptFilePath | Should contain "After Dispose"
@ -136,4 +135,15 @@ Describe "Start-Transcript, Stop-Transcript tests" -tags "CI" {
$transcriptFilePath | Should contain "PowerShell transcript end"
}
}
It "Transcription should record native command output" {
$script = {
Start-Transcript -Path $transcriptFilePath
hostname
Stop-Transcript }
& $script
Test-Path $transcriptFilePath | Should be $true
$machineName = [System.Environment]::MachineName
$transcriptFilePath | Should contain $machineName
}
}

View file

@ -17,6 +17,21 @@ Describe 'Basic Job Tests' -Tags 'CI' {
Receive-Job $job -wait | should be 1
}
It "Create job with native command" {
try {
$nativeJob = Start-job { powershell -c 1+1 }
$nativeJob | Wait-Job
$nativeJob.State | Should BeExactly "Completed"
$nativeJob.HasMoreData | Should Be $true
Receive-Job $nativeJob | Should BeExactly 2
Remove-Job $nativeJob
{ Get-Job $nativeJob -ErrorAction Stop } | ShouldBeErrorId "JobWithSpecifiedNameNotFound,Microsoft.PowerShell.Commands.GetJobCommand"
}
finally {
Remove-Job $nativeJob -Force -ErrorAction SilentlyContinue
}
}
AfterAll {
Remove-Job $job -Force
}