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:
parent
6e77537181
commit
99e3fe586e
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue