Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 75 additions & 47 deletions tools/run-with-console.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,78 +20,106 @@ param(
)

$TempBatFile = Join-Path ([System.IO.Path]::GetTempPath()) "run_bash_$(Get-Random).bat"
# conhost.exe adds VT100 escape codes which GitHub Actions doesn't handle well,
# so pipe output through a file.
$ConsoleOutputFile = Join-Path ([System.IO.Path]::GetTempPath()) "exit_code_$(Get-Random).txt"
# conhost.exe does not propagate the exit code, so write it to a file.
$ExitCodeFile = Join-Path ([System.IO.Path]::GetTempPath()) "exit_code_$(Get-Random).txt"
$CurrentWorkingDirectory = Get-Location
$BatchContent = @"
@echo off
cd "$CurrentWorkingDirectory"
$Command
$Command >"$ConsoleOutputFile" 2>&1
echo %ERRORLEVEL% > "$ExitCodeFile"
exit /b %ERRORLEVEL%
"@

try {
$BatchContent | Out-File -FilePath $TempBatFile -Encoding ASCII
$asyncConhost = @"
using System;
using System.Diagnostics;
using System.IO;

$ProcessStartInfo = New-Object System.Diagnostics.ProcessStartInfo
$ProcessStartInfo.FileName = "conhost.exe"
$ProcessStartInfo.Arguments = "`"$TempBatFile`""
$ProcessStartInfo.RedirectStandardOutput = $true
$ProcessStartInfo.RedirectStandardError = $true
$ProcessStartInfo.UseShellExecute = $false
$ProcessStartInfo.CreateNoWindow = $false
namespace AsyncConhost {
public static class exec {
public static int runCommand(string batFile, string consoleOutputFile, int timeoutInSeconds) {
using (var stdoutStream = Console.OpenStandardOutput())
using (var conhostOutputStream = File.Open(consoleOutputFile, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite)) {
Process process = new Process();
process.StartInfo.FileName = "conhost.exe";
process.StartInfo.Arguments = '"' + batFile + '"';
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;

$Process = New-Object System.Diagnostics.Process
$Process.StartInfo = $ProcessStartInfo
$OutputAction = {
if ($Event.SourceEventArgs.Data -ne $null) {
Write-Host $Event.SourceEventArgs.Data
}
}
$ErrorAction = {
if ($Event.SourceEventArgs.Data -ne $null) {
Write-Error $Event.SourceEventArgs.Data
}
}
$OutputEvent = Register-ObjectEvent -InputObject $Process -EventName OutputDataReceived -Action $OutputAction
$ErrorEvent = Register-ObjectEvent -InputObject $Process -EventName ErrorDataReceived -Action $ErrorAction
var outErrReceivedHandler = new DataReceivedEventHandler(
(sender, e) => Console.WriteLine(e.Data)
);
process.OutputDataReceived += outErrReceivedHandler;
process.ErrorDataReceived += outErrReceivedHandler;

try {
$Process.Start() | Out-Null
$Process.BeginOutputReadLine()
$Process.BeginErrorReadLine()
Write-Host "Started conhost process with PID: $($Process.Id)"
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
Console.WriteLine("Started conhost.exe " + batFile + " with PID " + process.Id);

$ProcessCompleted = $Process.WaitForExit($TimeoutMinutes * 60 * 1000)
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
while (!process.WaitForExit(100)) {
ForwardConsoleOutput(stdoutStream, conhostOutputStream);
if (stopWatch.ElapsedMilliseconds > 1000 * timeoutInSeconds) {
Console.WriteLine("Process exceeded timeout, force killing...");
// process.Kill(/*entireProcessTree =*/ true); should work.
process.Kill();
Console.WriteLine("Force killed process and its children");
return 42;
}
}
stopWatch.Stop();
ForwardConsoleOutput(stdoutStream, conhostOutputStream);
return process.ExitCode;
}
}

if (!$ProcessCompleted) {
Write-Error "Process exceeded timeout, force killing..."
$Process.Kill($true)
Write-Error "Force killed process and its children"
exit -1
private static void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine) {
Console.WriteLine(outLine.Data);
}

if (!(Test-Path $ExitCodeFile)) {
Write-Error "Exit code file not found"
exit 1

private static void ForwardConsoleOutput(Stream stdoutStream, FileStream consoleOutputStream) {
int bufSize = 65536;
byte[] bytes = new byte[bufSize];
while (true) {
int numBytesRead = consoleOutputStream.Read(bytes, 0, bufSize);
if (numBytesRead == 0) {
// End of file for now.
break;
}
stdoutStream.Write(bytes, 0, numBytesRead);
}
}

$ActualExitCode = [int](Get-Content $ExitCodeFile -Raw).Trim()
Write-Host "Process completed normally with exit code: $ActualExitCode"
exit $ActualExitCode
}
finally {
Unregister-Event -SourceIdentifier $OutputEvent.Name
Unregister-Event -SourceIdentifier $ErrorEvent.Name
$Process.Dispose()
}
"@

try {
$BatchContent | Out-File -FilePath $TempBatFile -Encoding ASCII

Add-Type -TypeDefinition $asyncConhost -Language CSharp
[AsyncConhost.exec]::runCommand($TempBatFile, $ConsoleOutputFile, $TimeoutMinutes * 60)
if (!(Test-Path $ExitCodeFile)) {
Write-Error "Exit code file not found"
exit 1
}
$ActualExitCode = [int](Get-Content $ExitCodeFile -Raw).Trim()
Write-Host "Process completed normally with exit code: $ActualExitCode"
exit $ActualExitCode
}
catch {
Write-Error "Error running process: $_"
exit 1
}
finally {
Remove-Item $TempBatFile -Force -ErrorAction SilentlyContinue
Remove-Item $ConsoleOutputFile -Force -ErrorAction SilentlyContinue
Remove-Item $ExitCodeFile -Force -ErrorAction SilentlyContinue
}