Skip to content
This repository was archived by the owner on Apr 20, 2023. It is now read-only.

Commit fa47e95

Browse files
authored
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
1 parent 5c35438 commit fa47e95

File tree

12 files changed

+432
-30
lines changed

12 files changed

+432
-30
lines changed

Microsoft.DotNet.Cli.sln

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Microsoft Visual Studio Solution File, Format Version 12.00
22
# Visual Studio 15
3-
VisualStudioVersion = 15.0.27004.2008
3+
VisualStudioVersion = 15.0.27130.2020
44
MinimumVisualStudioVersion = 10.0.40219.1
55
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED2FE3E2-F7E7-4389-8231-B65123F2076F}"
66
EndProject
@@ -232,6 +232,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.ShellShim.
232232
EndProject
233233
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}"
234234
EndProject
235+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "tool_launcher", "src\tool_launcher\tool_launcher.csproj", "{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}"
236+
EndProject
235237
Global
236238
GlobalSection(SolutionConfigurationPlatforms) = preSolution
237239
Debug|Any CPU = Debug|Any CPU
@@ -1640,6 +1642,30 @@ Global
16401642
{91BFE800-1624-4A58-A1CE-339705A8FFD0}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
16411643
{91BFE800-1624-4A58-A1CE-339705A8FFD0}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
16421644
{91BFE800-1624-4A58-A1CE-339705A8FFD0}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
1645+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1646+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
1647+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|x64.ActiveCfg = Debug|Any CPU
1648+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|x64.Build.0 = Debug|Any CPU
1649+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|x86.ActiveCfg = Debug|Any CPU
1650+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Debug|x86.Build.0 = Debug|Any CPU
1651+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|Any CPU.ActiveCfg = Release|Any CPU
1652+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|Any CPU.Build.0 = Release|Any CPU
1653+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|x64.ActiveCfg = Release|Any CPU
1654+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|x64.Build.0 = Release|Any CPU
1655+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|x86.ActiveCfg = Release|Any CPU
1656+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.MinSizeRel|x86.Build.0 = Release|Any CPU
1657+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
1658+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|Any CPU.Build.0 = Release|Any CPU
1659+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|x64.ActiveCfg = Release|Any CPU
1660+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|x64.Build.0 = Release|Any CPU
1661+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|x86.ActiveCfg = Release|Any CPU
1662+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.Release|x86.Build.0 = Release|Any CPU
1663+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU
1664+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU
1665+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU
1666+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|x64.Build.0 = Release|Any CPU
1667+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|x86.ActiveCfg = Release|Any CPU
1668+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5}.RelWithDebInfo|x86.Build.0 = Release|Any CPU
16431669
EndGlobalSection
16441670
GlobalSection(SolutionProperties) = preSolution
16451671
HideSolutionNode = FALSE
@@ -1713,6 +1739,7 @@ Global
17131739
{E7C72EF2-8480-48B4-AAE8-A596F1A6048E} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
17141740
{E84C08C9-70D7-48B0-9507-ADB8B9A2694C} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
17151741
{91BFE800-1624-4A58-A1CE-339705A8FFD0} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
1742+
{EDF19BE6-F20F-4AD0-8E3B-E837030726A5} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
17161743
EndGlobalSection
17171744
GlobalSection(ExtensibilityGlobals) = postSolution
17181745
SolutionGuid = {B526D2CE-EE2D-4AD4-93EF-1867D90FF1F5}

TestAssets/TestProjects/TestAppSimple/Program.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ public class Program
1010
public static void Main(string[] args)
1111
{
1212
Console.WriteLine("Hello World!");
13+
14+
if (args.Length > 0)
15+
{
16+
for (int i = 0; i < args.Length; i++)
17+
{
18+
Console.WriteLine($"{i} = {args[i]}");
19+
}
20+
}
1321
}
1422
}
1523
}

src/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using System.Text;
8-
using System.Threading.Tasks;
98

109
namespace Microsoft.DotNet.Cli.Utils
1110
{
@@ -22,7 +21,12 @@ public static class ArgumentEscaper
2221
/// <returns></returns>
2322
public static string EscapeAndConcatenateArgArrayForProcessStart(IEnumerable<string> args)
2423
{
25-
return string.Join(" ", EscapeArgArray(args));
24+
var escaped = EscapeArgArray(args);
25+
#if NET35
26+
return string.Join(" ", escaped.ToArray());
27+
#else
28+
return string.Join(" ", escaped);
29+
#endif
2630
}
2731

2832
/// <summary>
@@ -36,7 +40,12 @@ public static string EscapeAndConcatenateArgArrayForProcessStart(IEnumerable<str
3640
/// <returns></returns>
3741
public static string EscapeAndConcatenateArgArrayForCmdProcessStart(IEnumerable<string> args)
3842
{
39-
return string.Join(" ", EscapeArgArrayForCmd(args));
43+
var escaped = EscapeArgArrayForCmd(args);
44+
#if NET35
45+
return string.Join(" ", escaped.ToArray());
46+
#else
47+
return string.Join(" ", escaped);
48+
#endif
4049
}
4150

4251
/// <summary>

src/dotnet/OSVersionUtil.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using Microsoft.DotNet.PlatformAbstractions;
6+
7+
namespace Microsoft.DotNet
8+
{
9+
class OSVersionUtil
10+
{
11+
public static bool IsWindows8OrNewer()
12+
{
13+
if (RuntimeEnvironment.OperatingSystemPlatform != Platform.Windows)
14+
{
15+
return false;
16+
}
17+
18+
if (!Version.TryParse(RuntimeEnvironment.OperatingSystemVersion, out var winVersion))
19+
{
20+
// All current versions of Windows have a valid System.Version value for OperatingSystemVersion.
21+
// If parsing fails, let's assume Windows is newer than Win 8.
22+
return true;
23+
}
24+
25+
// Windows 7 = "6.1"
26+
// Windows 8 = "6.2"
27+
// Windows 8.1 = "6.3"
28+
if (winVersion.Major > 6)
29+
{
30+
return true;
31+
}
32+
33+
return winVersion.Minor >= 2;
34+
}
35+
}
36+
}

src/dotnet/ShellShim/ShellShimMaker.cs

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
using System;
55
using System.IO;
6+
using System.Linq;
67
using System.Runtime.InteropServices;
78
using System.Text;
9+
using System.Xml.Linq;
810
using Microsoft.DotNet.Cli.Utils;
911
using Microsoft.DotNet.Tools;
1012
using Microsoft.Extensions.EnvironmentAbstractions;
@@ -13,34 +15,57 @@ namespace Microsoft.DotNet.ShellShim
1315
{
1416
public class ShellShimMaker
1517
{
18+
private const string LauncherExeNet45ResourceName = "Microsoft.DotNet.Tools.Launcher.Executable.Net45";
19+
private const string LauncherExeNet35ResourceName = "Microsoft.DotNet.Tools.Launcher.Executable.Net35";
20+
private const string LauncherConfigNet45ResourceName = "Microsoft.DotNet.Tools.Launcher.Config.Net45";
21+
private const string LauncherConfigNet35ResourceName = "Microsoft.DotNet.Tools.Launcher.Config.Net35";
22+
23+
private readonly string _launcherExeResourceName;
24+
private readonly string _launcherConfigResourceName;
1625
private readonly string _pathToPlaceShim;
1726

1827
public ShellShimMaker(string pathToPlaceShim)
1928
{
2029
_pathToPlaceShim =
2130
pathToPlaceShim ?? throw new ArgumentNullException(nameof(pathToPlaceShim));
31+
32+
if (OSVersionUtil.IsWindows8OrNewer())
33+
{
34+
_launcherExeResourceName = LauncherExeNet45ResourceName;
35+
_launcherConfigResourceName = LauncherConfigNet45ResourceName;
36+
}
37+
else
38+
{
39+
_launcherExeResourceName = LauncherExeNet35ResourceName;
40+
_launcherConfigResourceName = LauncherConfigNet35ResourceName;
41+
}
2242
}
2343

2444
public void CreateShim(string packageExecutablePath, string shellCommandName)
2545
{
26-
var packageExecutable = new FilePath(packageExecutablePath);
46+
FilePath shimPath = GetShimPath(shellCommandName);
2747

28-
var script = new StringBuilder();
2948
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
3049
{
31-
script.AppendLine("@echo off");
32-
script.AppendLine($"dotnet {packageExecutable.ToQuotedString()} %*");
50+
CreateConfigFile(shimPath.Value + ".config", entryPoint: packageExecutablePath, runner: "dotnet");
51+
using (var shim = File.Create(shimPath.Value))
52+
using (var exe = typeof(ShellShimMaker).Assembly.GetManifestResourceStream(_launcherExeResourceName))
53+
{
54+
exe.CopyTo(shim);
55+
}
3356
}
3457
else
3558
{
59+
var packageExecutable = new FilePath(packageExecutablePath);
60+
61+
var script = new StringBuilder();
3662
script.AppendLine("#!/bin/sh");
3763
script.AppendLine($"dotnet {packageExecutable.ToQuotedString()} \"$@\"");
38-
}
3964

40-
FilePath scriptPath = GetScriptPath(shellCommandName);
41-
File.WriteAllText(scriptPath.Value, script.ToString());
65+
File.WriteAllText(shimPath.Value, script.ToString());
4266

43-
SetUserExecutionPermissionToShimFile(scriptPath);
67+
SetUserExecutionPermissionToShimFile(shimPath);
68+
}
4469
}
4570

4671
public void EnsureCommandNameUniqueness(string shellCommandName)
@@ -53,28 +78,43 @@ public void EnsureCommandNameUniqueness(string shellCommandName)
5378
}
5479
}
5580

81+
internal void CreateConfigFile(string outputPath, string entryPoint, string runner)
82+
{
83+
XDocument config;
84+
using (var resource = typeof(ShellShimMaker).Assembly.GetManifestResourceStream(_launcherConfigResourceName))
85+
{
86+
config = XDocument.Load(resource);
87+
}
88+
89+
var appSettings = config.Descendants("appSettings").First();
90+
appSettings.Add(new XElement("add", new XAttribute("key", "entryPoint"), new XAttribute("value", entryPoint)));
91+
appSettings.Add(new XElement("add", new XAttribute("key", "runner"), new XAttribute("value", runner ?? string.Empty)));
92+
config.Save(outputPath);
93+
}
94+
5695
public void Remove(string shellCommandName)
5796
{
58-
File.Delete(GetScriptPath(shellCommandName).Value);
97+
File.Delete(GetShimPath(shellCommandName).Value);
5998
}
6099

61-
private FilePath GetScriptPath(string shellCommandName)
100+
private FilePath GetShimPath(string shellCommandName)
62101
{
63102
var scriptPath = Path.Combine(_pathToPlaceShim, shellCommandName);
64103
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
65104
{
66-
scriptPath += ".cmd";
105+
scriptPath += ".exe";
67106
}
68107

69108
return new FilePath(scriptPath);
70109
}
71110

72111
private static void SetUserExecutionPermissionToShimFile(FilePath scriptPath)
73112
{
74-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;
113+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
114+
return;
75115

76116
CommandResult result = new CommandFactory()
77-
.Create("chmod", new[] {"u+x", scriptPath.Value})
117+
.Create("chmod", new[] { "u+x", scriptPath.Value })
78118
.CaptureStdOut()
79119
.CaptureStdErr()
80120
.Execute();

src/dotnet/dotnet.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,5 @@
7676
<Folder Include="commands\dotnet-migrate\xlf" />
7777
<Folder Include="dotnet-complete\commands\" />
7878
</ItemGroup>
79+
<Import Project="dotnet.win.targets" Condition="'$(OS)' == 'Windows_NT'" />
7980
</Project>

src/dotnet/dotnet.win.targets

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<!-- This file should only be used when building dotnet for windows. -->
2+
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<!-- Only included to ensure this is built first. -->
9+
<ProjectReference Include="..\tool_launcher\tool_launcher.csproj"
10+
ReferenceOutputAssembly="false"
11+
SkipGetTargetFrameworkProperties="true"
12+
SetTargetFramework="TargetFramework=net45"
13+
PrivateAssets="All" />
14+
<ProjectReference Include="..\tool_launcher\tool_launcher.csproj"
15+
ReferenceOutputAssembly="false"
16+
SkipGetTargetFrameworkProperties="true"
17+
SetTargetFramework="TargetFramework=net35"
18+
PrivateAssets="All" />
19+
</ItemGroup>
20+
21+
<Target Name="EmbedDotnetLauncher" BeforeTargets="PrepareForBuild">
22+
<MSBuild Projects="..\tool_launcher\tool_launcher.csproj" Targets="GetTargetPath" Properties="TargetFramework=net45;Configuration=$(Configuration)">
23+
<Output TaskParameter="TargetOutputs" PropertyName="DotnetLauncherNet45FullPath" />
24+
</MSBuild>
25+
<MSBuild Projects="..\tool_launcher\tool_launcher.csproj" Targets="GetTargetPath" Properties="TargetFramework=net35;Configuration=$(Configuration)">
26+
<Output TaskParameter="TargetOutputs" PropertyName="DotnetLauncherNet35FullPath" />
27+
</MSBuild>
28+
<ItemGroup>
29+
<EmbeddedResource Include="$(DotnetLauncherNet45FullPath)" LogicalName="Microsoft.DotNet.Tools.Launcher.Executable.Net45" />
30+
<EmbeddedResource Include="$(DotnetLauncherNet45FullPath).config" LogicalName="Microsoft.DotNet.Tools.Launcher.Config.Net45" />
31+
<EmbeddedResource Include="$(DotnetLauncherNet35FullPath)" LogicalName="Microsoft.DotNet.Tools.Launcher.Executable.Net35" />
32+
<EmbeddedResource Include="$(DotnetLauncherNet35FullPath).config" LogicalName="Microsoft.DotNet.Tools.Launcher.Config.Net35" />
33+
</ItemGroup>
34+
</Target>
35+
36+
</Project>

0 commit comments

Comments
 (0)