| File: src\Aspire.Tray\Common\WindowsLoginBootstrap.cs | Web Access |
| Project: src\src\Aspire.Tray\Windows\Aspire.Tray.Windows.csproj (aspire-tray) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; namespace Aspire.Tray; /// <summary> /// Runs only the stable CLI's tray-start command, without a console or a native UI. /// </summary> internal static class WindowsLoginBootstrap { internal static ProcessStartInfo CreateStartInfo(string cliPath) { if (!Path.IsPathFullyQualified(cliPath) || !File.Exists(cliPath)) { throw new InvalidOperationException("The installed CLI for sign-in startup is missing. Update the startup setting from the installed Aspire CLI."); } if (!TrayStartupEntry.IsNativeExecutable(cliPath, windows: true, requireGui: false)) { throw new InvalidOperationException("Sign-in startup requires a native Windows Aspire CLI executable."); } return CliProcess.CreateStartInfo(cliPath, "tray", "start", "--non-interactive", "--nologo"); } internal static async Task RunAsync(string cliPath) { using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(60)); using var process = Process.Start(CreateStartInfo(cliPath)) ?? throw new InvalidOperationException("Windows could not start the installed Aspire CLI at sign-in."); process.StandardInput.Close(); var stdout = CliProcess.DrainAsync(process.StandardOutput, timeout.Token); var stderr = CliProcess.DrainAsync(process.StandardError, timeout.Token); try { await process.WaitForExitAsync(timeout.Token).ConfigureAwait(false); await Task.WhenAll(stdout, stderr).ConfigureAwait(false); if (process.ExitCode != 0) { throw new InvalidOperationException($"The sign-in tray start failed (CLI exit {process.ExitCode}). Start Aspire manually to inspect the installation."); } } catch (OperationCanceledException) when (timeout.IsCancellationRequested) { // Terminate only our CLI invocation, never its detached GUI or any AppHost. try { if (!process.HasExited) { process.Kill(); } } catch (InvalidOperationException) when (process.HasExited) { } using var termination = new CancellationTokenSource(TimeSpan.FromSeconds(5)); try { await process.WaitForExitAsync(termination.Token).ConfigureAwait(false); } catch (OperationCanceledException) when (termination.IsCancellationRequested) { throw new TimeoutException("The sign-in CLI invocation could not be stopped after its startup timeout."); } throw new TimeoutException("The sign-in tray start did not finish within 60 seconds."); } finally { await timeout.CancelAsync().ConfigureAwait(false); try { await Task.WhenAll(stdout, stderr).ConfigureAwait(false); } catch (OperationCanceledException) when (timeout.IsCancellationRequested) { } } } }