File: Installer\Windows\InstallClientElevationContext.cs
Web Access
Project: ..\..\..\src\Cli\dotnet\dotnet.csproj (dotnet)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using System.Diagnostics;
using System.IO.Pipes;
using System.Reflection;
using System.Runtime.Versioning;
 
namespace Microsoft.DotNet.Cli.Installer.Windows;
 
/// <summary>
/// Encapsulates information about elevation to support workload installations.
/// </summary>
[SupportedOSPlatform("windows")]
internal sealed class InstallClientElevationContext(ISynchronizingLogger logger) : InstallElevationContextBase
{
    private readonly ISynchronizingLogger _log = logger;
 
    private Process _serverProcess;
 
    public override bool IsClient => true;
 
    /// <summary>
    /// Starts the elevated install server.
    /// </summary>
    public override void Elevate()
    {
        if (!IsElevated && !HasElevated)
        {
            // Use the path of the current host, otherwise we risk resolving against the wrong SDK version.
            // To trigger UAC, UseShellExecute must be true and Verb must be "runas".
            ProcessStartInfo startInfo = new($@"""{Environment.ProcessPath}""",
                    $@"""{Assembly.GetExecutingAssembly().Location}"" workload elevate")
            {
                Verb = "runas",
                UseShellExecute = true,
                CreateNoWindow = true,
                WindowStyle = ProcessWindowStyle.Hidden,
            };
 
            _log?.LogMessage($"Attempting to start the elevated command instance. {startInfo.FileName} {startInfo.Arguments}.");
 
            _serverProcess = new Process
            {
                StartInfo = startInfo,
                EnableRaisingEvents = true,
            };
 
            _serverProcess.Exited += ServerExited;
 
            if (_serverProcess.Start())
            {
                InitializeDispatcher(new NamedPipeClientStream(".", WindowsUtils.CreatePipeName(_serverProcess.Id), PipeDirection.InOut));
                Dispatcher.Connect();
 
                // Add a pipe to the logger to allow the server to send log requests. This avoids having an elevated process writing
                // to a less privileged location. It also simplifies troubleshooting because log events will be chronologically
                // ordered in a single file.
                _log.AddNamedPipe(WindowsUtils.CreatePipeName(_serverProcess.Id, "log"));
 
                HasElevated = true;
 
                _log.LogMessage("Elevated command instance started.");
            }
            else
            {
                _log?.LogMessage($"Failed to start the elevated command instance.");
                throw new Exception("Failed to start the elevated command instance.");
            }
        }
    }
 
    private void ServerExited(object sender, EventArgs e)
    {
        _log?.LogMessage($"Elevated command instance has exited.");
    }
}