File: CommandFactory.cs
Web Access
Project: Microsoft.Arcade.Common.csproj (Microsoft.Arcade.Common)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;

namespace Microsoft.Arcade.Common;

public class CommandFactory : ICommandFactory
{
    public ICommand Create(string executable, params string[] args)
    {
        return Create(executable, ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args));
    }

    public ICommand Create(string executable, IEnumerable<string> args)
    {
        return Create(executable, ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args));
    }

    public ICommand Create(string executable, string args)
    {
        ResolveExecutablePath(ref executable, ref args);

        return new Command(executable, args);
    }

    private static void ResolveExecutablePath(ref string executable, ref string args)
    {
        // On Windows, we want to avoid using "cmd" if possible (it mangles the colors, and a bunch of other things)
        // So, do a quick path search to see if we can just directly invoke it
        var useCmd = ShouldUseCmd(executable);

        if (useCmd)
        {
            var comSpec = System.Environment.GetEnvironmentVariable("ComSpec");

            // cmd doesn't like "foo.exe ", so we need to ensure that if
            // args is empty, we just run "foo.exe"
            if (!string.IsNullOrEmpty(args))
            {
                executable = (executable + " " + args).Replace("\"", "\\\"");
            }
            args = $"/C \"{executable}\"";
            executable = comSpec;
        }
    }

    private static bool ShouldUseCmd(string executable)
    {
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
        {
            var extension = Path.GetExtension(executable);
            if (!string.IsNullOrEmpty(extension))
            {
                return !string.Equals(extension, ".exe", StringComparison.Ordinal);
            }
            else if (executable.Contains(Path.DirectorySeparatorChar))
            {
                // It's a relative path without an extension
                if (File.Exists(executable + ".exe"))
                {
                    // It refers to an exe!
                    return false;
                }
            }
            else
            {
                // Search the path to see if we can find it 
                foreach (var path in System.Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator))
                {
                    var candidate = Path.Combine(path, executable + ".exe");
                    if (File.Exists(candidate))
                    {
                        // We found an exe!
                        return false;
                    }
                }
            }

            // It's a non-exe :(
            return true;
        }

        // Non-windows never uses cmd
        return false;
    }
}