File: src\Aspire.Tray\Common\WindowsTrayStartupSettings.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;
using System.Text;
 
namespace Aspire.Tray;
 
/// <summary>
/// Owns a per-user Run registration pointing at a stable GUI-only login bootstrap.
/// </summary>
internal sealed class WindowsTrayStartupSettings(
    TrayOptions options, bool nativeFrontend, string sourceExecutable, string bootstrapPath,
    ITrayStartupRegistrationStore store) : RegisteredTrayStartupSettings(store)
{
    private readonly TrayStartupBootstrap _bootstrap = new(sourceExecutable, bootstrapPath);
 
    protected override string? UnavailableReason
    {
        get
        {
            var reason = TrayStartupEntry.GetUnavailableReason(options, nativeFrontend, windows: true);
            if (reason is not null)
            {
                return reason;
            }
            if (bootstrapPath.Contains('%') || options.StartupCliPath!.Contains('%'))
            {
                return "Windows sign-in startup cannot use paths containing environment-variable delimiters.";
            }
            if (BuildRunCommand(bootstrapPath, options.StartupCliPath!).Length > 260)
            {
                return "The startup command exceeds the Windows Run key's 260-character limit.";
            }
            return _bootstrap.GetUnavailableReason();
        }
    }
 
    protected override string CreateRegistration() => BuildRunCommand(bootstrapPath, options.StartupCliPath!);
    protected override void PrepareRegistration() => _bootstrap.EnsureCreated();
 
    internal static string BuildRunCommand(string bootstrap, string cli)
    {
        var start = new ProcessStartInfo(bootstrap);
        foreach (var argument in new[] { "login-start", "--cli", cli })
        {
            start.ArgumentList.Add(argument);
        }
        return TrayLaunchCommand.BuildWindowsCommandLine(start);
    }
 
    protected override bool IsOwned(string registration)
    {
        var prefix = BuildRunCommand(bootstrapPath, "");
        prefix = prefix[..^2]; // Our canonical final argument is always quoted, including "".
        if (!registration.StartsWith(prefix, StringComparison.Ordinal))
        {
            return false;
        }
        var tail = registration.AsSpan(prefix.Length);
        if (tail.Length < 2 || tail[0] != '"' || tail[^1] != '"')
        {
            return false;
        }
        var cli = new StringBuilder();
        // Decode only the single canonical quoted argument emitted above. Windows paths
        // cannot contain quotes, but the canonical round-trip also rejects extra commands.
        for (var i = 1; i < tail.Length - 1; i++)
        {
            if (tail[i] == '"')
            {
                return false;
            }
            if (tail[i] == '\\')
            {
                var start = i;
                while (i < tail.Length - 1 && tail[i] == '\\')
                {
                    i++;
                }
                var count = i - start;
                if (i == tail.Length - 1)
                {
                    if (count % 2 != 0)
                    {
                        return false;
                    }
                    cli.Append('\\', count / 2);
                    break;
                }
                cli.Append('\\', count);
            }
            if (tail[i] == '"')
            {
                return false;
            }
            cli.Append(tail[i]);
        }
        return Path.IsPathFullyQualified(cli.ToString())
            && string.Equals(registration, BuildRunCommand(bootstrapPath, cli.ToString()), StringComparison.Ordinal);
    }
}