From fa47e95e907f622e22ed4af3d02049436a6f0de7 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 18 Jan 2018 14:54:10 -0800 Subject: [PATCH] Generate a .NET Framework shim app in dotnet-install-tools on Windows instead of a batch script (#8384) Implement a simple launcher tool for running new processes on Windows - This application takes two parameters via the .exe.config configuration file - entryPoint: required - the file path to the new process being launched - runner: optional - the executable or interpretter used to launch the entryPoint - Update dotnet-install-tool to generate an exe instead of a batch script file --- Microsoft.DotNet.Cli.sln | 29 ++++- .../TestProjects/TestAppSimple/Program.cs | 8 ++ .../ArgumentEscaper.cs | 15 ++- src/dotnet/OSVersionUtil.cs | 36 ++++++ src/dotnet/ShellShim/ShellShimMaker.cs | 70 ++++++++--- src/dotnet/dotnet.csproj | 1 + src/dotnet/dotnet.win.targets | 36 ++++++ src/tool_launcher/Program.cs | 116 ++++++++++++++++++ src/tool_launcher/app.net35.config | 18 +++ src/tool_launcher/app.net45.config | 18 +++ src/tool_launcher/tool_launcher.csproj | 38 ++++++ .../ShellShimMakerTests.cs | 81 ++++++++++-- 12 files changed, 434 insertions(+), 32 deletions(-) create mode 100644 src/dotnet/OSVersionUtil.cs create mode 100644 src/dotnet/dotnet.win.targets create mode 100644 src/tool_launcher/Program.cs create mode 100644 src/tool_launcher/app.net35.config create mode 100644 src/tool_launcher/app.net45.config create mode 100644 src/tool_launcher/tool_launcher.csproj diff --git a/Microsoft.DotNet.Cli.sln b/Microsoft.DotNet.Cli.sln index 1b5f2fe4d..0748db81a 100644 --- a/Microsoft.DotNet.Cli.sln +++ b/Microsoft.DotNet.Cli.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27004.2008 +VisualStudioVersion = 15.0.27130.2020 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED2FE3E2-F7E7-4389-8231-B65123F2076F}" EndProject @@ -232,6 +232,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.ShellShim. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.ToolPackage.Tests", "test\Microsoft.DotNet.ToolPackage.Tests\Microsoft.DotNet.ToolPackage.Tests.csproj", "{91BFE800-1624-4A58-A1CE-339705A8FFD0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "tool_launcher", "src\tool_launcher\tool_launcher.csproj", "{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1640,6 +1642,30 @@ Global {91BFE800-1624-4A58-A1CE-339705A8FFD0}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {91BFE800-1624-4A58-A1CE-339705A8FFD0}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU {91BFE800-1624-4A58-A1CE-339705A8FFD0}.RelWithDebInfo|x86.Build.0 = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|x64.ActiveCfg = Debug|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|x64.Build.0 = Debug|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|x86.ActiveCfg = Debug|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|x86.Build.0 = Debug|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|Any CPU.ActiveCfg = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|Any CPU.Build.0 = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|x64.ActiveCfg = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|x64.Build.0 = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|x86.ActiveCfg = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|x86.Build.0 = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|Any CPU.Build.0 = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|x64.ActiveCfg = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|x64.Build.0 = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|x86.ActiveCfg = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|x86.Build.0 = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1713,6 +1739,7 @@ Global {E7C72EF2-8480-48B4-AAE8-A596F1A6048E} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {E84C08C9-70D7-48B0-9507-ADB8B9A2694C} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {91BFE800-1624-4A58-A1CE-339705A8FFD0} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} + {EDF19BE6-F20F-4AD0-8E3B-E837030726A5} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B526D2CE-EE2D-4AD4-93EF-1867D90FF1F5} diff --git a/TestAssets/TestProjects/TestAppSimple/Program.cs b/TestAssets/TestProjects/TestAppSimple/Program.cs index 2130cd0a7..d431e919c 100644 --- a/TestAssets/TestProjects/TestAppSimple/Program.cs +++ b/TestAssets/TestProjects/TestAppSimple/Program.cs @@ -10,6 +10,14 @@ namespace ConsoleApplication public static void Main(string[] args) { Console.WriteLine("Hello World!"); + + if (args.Length > 0) + { + for (int i = 0; i < args.Length; i++) + { + Console.WriteLine($"{i} = {args[i]}"); + } + } } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs b/src/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs index c8469a9ac..79ab2aa1c 100644 --- a/src/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs +++ b/src/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading.Tasks; namespace Microsoft.DotNet.Cli.Utils { @@ -22,7 +21,12 @@ namespace Microsoft.DotNet.Cli.Utils /// public static string EscapeAndConcatenateArgArrayForProcessStart(IEnumerable args) { - return string.Join(" ", EscapeArgArray(args)); + var escaped = EscapeArgArray(args); +#if NET35 + return string.Join(" ", escaped.ToArray()); +#else + return string.Join(" ", escaped); +#endif } /// @@ -36,7 +40,12 @@ namespace Microsoft.DotNet.Cli.Utils /// public static string EscapeAndConcatenateArgArrayForCmdProcessStart(IEnumerable args) { - return string.Join(" ", EscapeArgArrayForCmd(args)); + var escaped = EscapeArgArrayForCmd(args); +#if NET35 + return string.Join(" ", escaped.ToArray()); +#else + return string.Join(" ", escaped); +#endif } /// diff --git a/src/dotnet/OSVersionUtil.cs b/src/dotnet/OSVersionUtil.cs new file mode 100644 index 000000000..b110aa0b5 --- /dev/null +++ b/src/dotnet/OSVersionUtil.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Microsoft.DotNet.PlatformAbstractions; + +namespace Microsoft.DotNet +{ + class OSVersionUtil + { + public static bool IsWindows8OrNewer() + { + if (RuntimeEnvironment.OperatingSystemPlatform != Platform.Windows) + { + return false; + } + + if (!Version.TryParse(RuntimeEnvironment.OperatingSystemVersion, out var winVersion)) + { + // All current versions of Windows have a valid System.Version value for OperatingSystemVersion. + // If parsing fails, let's assume Windows is newer than Win 8. + return true; + } + + // Windows 7 = "6.1" + // Windows 8 = "6.2" + // Windows 8.1 = "6.3" + if (winVersion.Major > 6) + { + return true; + } + + return winVersion.Minor >= 2; + } + } +} diff --git a/src/dotnet/ShellShim/ShellShimMaker.cs b/src/dotnet/ShellShim/ShellShimMaker.cs index 084a2d4a6..e51aa32e1 100644 --- a/src/dotnet/ShellShim/ShellShimMaker.cs +++ b/src/dotnet/ShellShim/ShellShimMaker.cs @@ -3,8 +3,10 @@ using System; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Xml.Linq; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Tools; using Microsoft.Extensions.EnvironmentAbstractions; @@ -13,34 +15,57 @@ namespace Microsoft.DotNet.ShellShim { public class ShellShimMaker { + private const string LauncherExeNet45ResourceName = "Microsoft.DotNet.Tools.Launcher.Executable.Net45"; + private const string LauncherExeNet35ResourceName = "Microsoft.DotNet.Tools.Launcher.Executable.Net35"; + private const string LauncherConfigNet45ResourceName = "Microsoft.DotNet.Tools.Launcher.Config.Net45"; + private const string LauncherConfigNet35ResourceName = "Microsoft.DotNet.Tools.Launcher.Config.Net35"; + + private readonly string _launcherExeResourceName; + private readonly string _launcherConfigResourceName; private readonly string _pathToPlaceShim; public ShellShimMaker(string pathToPlaceShim) { _pathToPlaceShim = pathToPlaceShim ?? throw new ArgumentNullException(nameof(pathToPlaceShim)); + + if (OSVersionUtil.IsWindows8OrNewer()) + { + _launcherExeResourceName = LauncherExeNet45ResourceName; + _launcherConfigResourceName = LauncherConfigNet45ResourceName; + } + else + { + _launcherExeResourceName = LauncherExeNet35ResourceName; + _launcherConfigResourceName = LauncherConfigNet35ResourceName; + } } public void CreateShim(string packageExecutablePath, string shellCommandName) { - var packageExecutable = new FilePath(packageExecutablePath); + FilePath shimPath = GetShimPath(shellCommandName); - var script = new StringBuilder(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - script.AppendLine("@echo off"); - script.AppendLine($"dotnet {packageExecutable.ToQuotedString()} %*"); + CreateConfigFile(shimPath.Value + ".config", entryPoint: packageExecutablePath, runner: "dotnet"); + using (var shim = File.Create(shimPath.Value)) + using (var exe = typeof(ShellShimMaker).Assembly.GetManifestResourceStream(_launcherExeResourceName)) + { + exe.CopyTo(shim); + } } else { + var packageExecutable = new FilePath(packageExecutablePath); + + var script = new StringBuilder(); script.AppendLine("#!/bin/sh"); script.AppendLine($"dotnet {packageExecutable.ToQuotedString()} \"$@\""); + + File.WriteAllText(shimPath.Value, script.ToString()); + + SetUserExecutionPermissionToShimFile(shimPath); } - - FilePath scriptPath = GetScriptPath(shellCommandName); - File.WriteAllText(scriptPath.Value, script.ToString()); - - SetUserExecutionPermissionToShimFile(scriptPath); } public void EnsureCommandNameUniqueness(string shellCommandName) @@ -53,17 +78,31 @@ namespace Microsoft.DotNet.ShellShim } } - public void Remove(string shellCommandName) + internal void CreateConfigFile(string outputPath, string entryPoint, string runner) { - File.Delete(GetScriptPath(shellCommandName).Value); + XDocument config; + using (var resource = typeof(ShellShimMaker).Assembly.GetManifestResourceStream(_launcherConfigResourceName)) + { + config = XDocument.Load(resource); + } + + var appSettings = config.Descendants("appSettings").First(); + appSettings.Add(new XElement("add", new XAttribute("key", "entryPoint"), new XAttribute("value", entryPoint))); + appSettings.Add(new XElement("add", new XAttribute("key", "runner"), new XAttribute("value", runner ?? string.Empty))); + config.Save(outputPath); } - private FilePath GetScriptPath(string shellCommandName) + public void Remove(string shellCommandName) + { + File.Delete(GetShimPath(shellCommandName).Value); + } + + private FilePath GetShimPath(string shellCommandName) { var scriptPath = Path.Combine(_pathToPlaceShim, shellCommandName); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - scriptPath += ".cmd"; + scriptPath += ".exe"; } return new FilePath(scriptPath); @@ -71,10 +110,11 @@ namespace Microsoft.DotNet.ShellShim private static void SetUserExecutionPermissionToShimFile(FilePath scriptPath) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return; CommandResult result = new CommandFactory() - .Create("chmod", new[] {"u+x", scriptPath.Value}) + .Create("chmod", new[] { "u+x", scriptPath.Value }) .CaptureStdOut() .CaptureStdErr() .Execute(); diff --git a/src/dotnet/dotnet.csproj b/src/dotnet/dotnet.csproj index 02b44c4c6..0b640f004 100644 --- a/src/dotnet/dotnet.csproj +++ b/src/dotnet/dotnet.csproj @@ -76,4 +76,5 @@ + diff --git a/src/dotnet/dotnet.win.targets b/src/dotnet/dotnet.win.targets new file mode 100644 index 000000000..4e4e56170 --- /dev/null +++ b/src/dotnet/dotnet.win.targets @@ -0,0 +1,36 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tool_launcher/Program.cs b/src/tool_launcher/Program.cs new file mode 100644 index 000000000..5ecf92671 --- /dev/null +++ b/src/tool_launcher/Program.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Configuration; +using System.Diagnostics; +using Microsoft.DotNet.Cli.Utils; + +namespace Microsoft.DotNet.Tools.Launcher +{ + /// + /// The app is simple shim into launching arbitrary command line processes. + /// It is configured via app settings .NET config file. (See app.config). + /// + /// + /// Launching new processes using cmd.exe and .cmd files causes issues for long-running process + /// because CTRL+C always hangs on interrupt with the prompt "Terminate Y/N". This can lead to + /// orphaned processes. + /// + class Program + { + private const string TRACE = "DOTNET_LAUNCHER_TRACE"; + private const int ERR_FAILED = -1; + private static bool _trace; + + public static int Main(string[] argsToForward) + { + bool.TryParse(Environment.GetEnvironmentVariable(TRACE), out _trace); + + try + { + var appSettings = ConfigurationManager.AppSettings; + + var entryPoint = appSettings["entryPoint"]; + if (string.IsNullOrEmpty(entryPoint)) + { + LogError("The launcher must specify a non-empty appSetting value for 'entryPoint'."); + return ERR_FAILED; + } + + var exePath = entryPoint; + var runner = appSettings["runner"]; + + var args = new List(); + + if (!string.IsNullOrEmpty(runner)) + { + args.Add(entryPoint); + exePath = runner; + } + + args.AddRange(argsToForward); + + var argString = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(args); + + using (var process = new Process + { + StartInfo = + { + FileName = exePath, + Arguments = argString, + CreateNoWindow = false, + UseShellExecute = false, + } + }) + { + LogTrace("Starting a new process."); + LogTrace("filename = " + process.StartInfo.FileName); + LogTrace("args = " + process.StartInfo.Arguments); + LogTrace("cwd = " + process.StartInfo.WorkingDirectory); + + try + { + process.Start(); + } + catch (Win32Exception ex) + { + LogTrace(ex.ToString()); + LogError($"Failed to start '{process.StartInfo.FileName}'. " + ex.Message); + return ERR_FAILED; + } + + process.WaitForExit(); + + LogTrace("Exited code " + process.ExitCode); + + return process.ExitCode; + } + } + catch (Exception ex) + { + LogError("Unexpected error launching a new process. Run with the environment variable " + TRACE + "='true' for details."); + LogTrace(ex.ToString()); + return ERR_FAILED; + } + } + + private static void LogError(string message) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.BackgroundColor = ConsoleColor.Black; + Console.Error.WriteLine("ERROR: " + message); + Console.ResetColor(); + } + + private static void LogTrace(string message) + { + if (!_trace) + return; + + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.BackgroundColor = ConsoleColor.Black; + Console.WriteLine("[dotnet-launcher] " + message); + Console.ResetColor(); + } + } +} diff --git a/src/tool_launcher/app.net35.config b/src/tool_launcher/app.net35.config new file mode 100644 index 000000000..71c90e716 --- /dev/null +++ b/src/tool_launcher/app.net35.config @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/src/tool_launcher/app.net45.config b/src/tool_launcher/app.net45.config new file mode 100644 index 000000000..989814a58 --- /dev/null +++ b/src/tool_launcher/app.net45.config @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/src/tool_launcher/tool_launcher.csproj b/src/tool_launcher/tool_launcher.csproj new file mode 100644 index 000000000..7adc700e2 --- /dev/null +++ b/src/tool_launcher/tool_launcher.csproj @@ -0,0 +1,38 @@ + + + + net45;net35 + AnyCPU + exe + false + Microsoft.DotNet.Tools.Launcher + dotnet-tool-launcher + + A simple Windows-only shim for launching new processes. + + app.$(TargetFramework).config + + + + + + + + + + + + + + + + + + + $(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client + + + + + + diff --git a/test/Microsoft.DotNet.ShellShim.Tests/ShellShimMakerTests.cs b/test/Microsoft.DotNet.ShellShim.Tests/ShellShimMakerTests.cs index 5b9d9946c..75379aaae 100644 --- a/test/Microsoft.DotNet.ShellShim.Tests/ShellShimMakerTests.cs +++ b/test/Microsoft.DotNet.ShellShim.Tests/ShellShimMakerTests.cs @@ -6,21 +6,50 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Xml.Linq; using FluentAssertions; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.TestFramework; using Microsoft.DotNet.Tools.Test.Utilities; using Xunit; +using Xunit.Abstractions; namespace Microsoft.DotNet.ShellShim.Tests { public class ShellShimMakerTests : TestBase { - private readonly string _pathToPlaceShim; + private readonly ITestOutputHelper _output; - public ShellShimMakerTests() + public ShellShimMakerTests(ITestOutputHelper output) { - _pathToPlaceShim = Path.GetTempPath(); + _output = output; + } + + [Theory] + [InlineData("my_native_app.exe", null)] + [InlineData("./my_native_app.js", "nodejs")] + [InlineData(@"C:\tools\my_native_app.dll", "dotnet")] + public void GivenAnRunnerOrEntryPointItCanCreateConfig(string entryPoint, string runner) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return; + + var shellShimMaker = new ShellShimMaker(TempRoot.Root); + + var tmpFile = Path.Combine(TempRoot.Root, Path.GetRandomFileName()); + + shellShimMaker.CreateConfigFile(tmpFile, entryPoint, runner); + + new FileInfo(tmpFile).Should().Exist(); + + var generated = XDocument.Load(tmpFile); + + generated.Descendants("appSettings") + .Descendants("add") + .Should() + .Contain(e => e.Attribute("key").Value == "runner" && e.Attribute("value").Value == (runner ?? string.Empty)) + .And + .Contain(e => e.Attribute("key").Value == "entryPoint" && e.Attribute("value").Value == entryPoint); } [Fact] @@ -28,7 +57,7 @@ namespace Microsoft.DotNet.ShellShim.Tests { var outputDll = MakeHelloWorldExecutableDll(); - var shellShimMaker = new ShellShimMaker(_pathToPlaceShim); + var shellShimMaker = new ShellShimMaker(TempRoot.Root); var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName(); shellShimMaker.CreateShim( @@ -39,14 +68,37 @@ namespace Microsoft.DotNet.ShellShim.Tests stdOut.Should().Contain("Hello World"); } + [Theory] + [InlineData("arg1 arg2", new[] { "arg1", "arg2" })] + [InlineData(" \"arg1 with space\" arg2", new[] { "arg1 with space", "arg2" })] + [InlineData(" \"arg with ' quote\" ", new[] { "arg with ' quote" })] + public void GivenAShimItPassesThroughArguments(string arguments, string[] expectedPassThru) + { + var outputDll = MakeHelloWorldExecutableDll(); + + var shellShimMaker = new ShellShimMaker(TempRoot.Root); + var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName(); + + shellShimMaker.CreateShim( + outputDll.FullName, + shellCommandName); + + var stdOut = ExecuteInShell(shellCommandName, arguments); + + for (int i = 0; i < expectedPassThru.Length; i++) + { + stdOut.Should().Contain($"{i} = {expectedPassThru[i]}"); + } + } + [Fact] public void GivenAnExecutablePathWithExistingSameNameShimItThrows() { var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName(); - MakeNameConflictingCommand(_pathToPlaceShim, shellCommandName); + MakeNameConflictingCommand(TempRoot.Root, shellCommandName); - var shellShimMaker = new ShellShimMaker(_pathToPlaceShim); + var shellShimMaker = new ShellShimMaker(TempRoot.Root); Action a = () => shellShimMaker.EnsureCommandNameUniqueness(shellCommandName); a.ShouldThrow() @@ -61,7 +113,7 @@ namespace Microsoft.DotNet.ShellShim.Tests { var shellCommandName = nameof(ShellShimMakerTests) + Path.GetRandomFileName(); - var shellShimMaker = new ShellShimMaker(_pathToPlaceShim); + var shellShimMaker = new ShellShimMaker(TempRoot.Root); Action a = () => shellShimMaker.EnsureCommandNameUniqueness(shellCommandName); a.ShouldNotThrow(); @@ -72,17 +124,18 @@ namespace Microsoft.DotNet.ShellShim.Tests File.WriteAllText(Path.Combine(pathToPlaceShim, shellCommandName), string.Empty); } - private string ExecuteInShell(string shellCommandName) + private string ExecuteInShell(string shellCommandName, string arguments = "") { ProcessStartInfo processStartInfo; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + var file = Path.Combine(TempRoot.Root, shellCommandName + ".exe"); processStartInfo = new ProcessStartInfo { - FileName = "CMD.exe", - Arguments = $"/C {shellCommandName}", - UseShellExecute = false + FileName = file, + UseShellExecute = false, + Arguments = arguments, }; } else @@ -90,11 +143,13 @@ namespace Microsoft.DotNet.ShellShim.Tests processStartInfo = new ProcessStartInfo { FileName = "sh", - Arguments = shellCommandName, + Arguments = shellCommandName + " " + arguments, UseShellExecute = false }; } - processStartInfo.WorkingDirectory = _pathToPlaceShim; + + _output.WriteLine($"Launching '{processStartInfo.FileName} {processStartInfo.Arguments}'"); + processStartInfo.WorkingDirectory = TempRoot.Root; processStartInfo.EnvironmentVariables["PATH"] = Path.GetDirectoryName(new Muxer().MuxerPath); processStartInfo.ExecuteAndCaptureOutput(out var stdOut, out var stdErr);