76 lines
3.7 KiB
C#
76 lines
3.7 KiB
C#
// Compiled as a Windows GUI executable so Task Scheduler never allocates a console.
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
internal static class WindowsRunnerLauncher
|
|
{
|
|
private static int Main(string[] args)
|
|
{
|
|
string root = AppDomain.CurrentDomain.BaseDirectory;
|
|
var owner = Process.GetCurrentProcess();
|
|
string log = Path.Combine(root, "launcher-" + DateTime.UtcNow.ToString("yyyyMMddTHHmmssfffZ") + "-" + owner.Id + "-" + Guid.NewGuid().ToString("N"));
|
|
try
|
|
{
|
|
File.WriteAllText(Path.Combine(root, "process.json"), "{\"Pid\":" + owner.Id + ",\"Started\":" + owner.StartTime.ToUniversalTime().Ticks + "}");
|
|
if (args.Length != 1 || !Path.IsPathRooted(args[0]))
|
|
throw new ArgumentException("Expected the absolute PowerShell executable path.");
|
|
using (var output = File.Create(log + ".out.log"))
|
|
using (var errors = File.Create(log + ".err.log"))
|
|
using (var child = new Process())
|
|
{
|
|
child.StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = args[0],
|
|
Arguments = "-NoProfile -NonInteractive -File \"" + Path.Combine(root, "run.ps1") + "\"",
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true
|
|
};
|
|
child.Start();
|
|
Task stdout = child.StandardOutput.BaseStream.CopyToAsync(output);
|
|
Task stderr = child.StandardError.BaseStream.CopyToAsync(errors);
|
|
child.WaitForExit();
|
|
// Descendants can retain inherited pipe writers after the direct child exits.
|
|
if (!Task.WaitAll(new[] { stdout, stderr }, TimeSpan.FromSeconds(5)))
|
|
{
|
|
File.WriteAllText(log + ".drain.log", "Output drain timed out after child exit " + child.ExitCode + "; launcher logs may be incomplete. Periodic recovery remains governed by the restart budget.");
|
|
// Process exit releases pipe reads still held by background copy tasks.
|
|
return 5;
|
|
}
|
|
return child.ExitCode;
|
|
}
|
|
}
|
|
catch (Exception error)
|
|
{
|
|
// Launcher exceptions disable periodic starts, including failures before run.ps1 can consume its budget.
|
|
int result = 2;
|
|
try { DisableTask(root); }
|
|
catch (Exception disableError) { error = new AggregateException(error, disableError); result = 4; }
|
|
try { File.AppendAllText(log + ".err.log", error.ToString() + Environment.NewLine); }
|
|
catch (Exception)
|
|
{
|
|
// No console is available. Task Scheduler result 3 reports that diagnostics could not be written.
|
|
return result == 4 ? 4 : 3;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private static void DisableTask(string root)
|
|
{
|
|
// Keep task identity aligned with windows-runner-task.ps1 and run-windows-runner-task.ps1.
|
|
string runner = Directory.GetParent(root.TrimEnd(Path.DirectorySeparatorChar)).FullName.TrimEnd('\\', '/');
|
|
string taskName;
|
|
using (var hash = SHA256.Create())
|
|
taskName = "DSH-Packaging-" + BitConverter.ToString(hash.ComputeHash(Encoding.UTF8.GetBytes(runner.ToUpperInvariant()))).Replace("-", "").Substring(0, 16);
|
|
dynamic scheduler = Activator.CreateInstance(Type.GetTypeFromProgID("Schedule.Service"));
|
|
scheduler.Connect();
|
|
scheduler.GetFolder("\\").GetTask(taskName).Enabled = false;
|
|
}
|
|
}
|