1

I'm trying to find the best way to run a .NET Core app from another .NET Core app. I know there are lots of questions on this already, but none get at the part of the problem that concerns me.

Similar to this question, I want to start a new process using the path to the .NET Core dll. My problem is that this (as well as all the other answered questions I've found) assume dotnet is in the PATH, which is not necessarily true. Is there any way to use the same dotnet runtime as the parent process, given that it's also a .NET Core app?

The best I've come up with is this, taking advantage of the fact that the current process is likely a dotnet process with the dll as an argument, but it feels a little hacky.

var dotnetCommand = Path.GetFileName(processPath).Contains("dotnet") ? processPath : "dotnet";
var process = new Process
{
    StartInfo = new ProcessStartInfo
    {
        FileName = dotnetCommand,
        Arguments = "path\to\app.dll"
    }
};

process.Start();
  • If the current process is likely to be dotnet, how did it get started? Form where the dotnet command was located to run the current process? – Chetan Mar 20 '20 at 19:23
  • It could have been started with `dotnet` on the PATH, `dotnet` installed elsewhere, or a locally self-contained `dotnet` runtime – Jonathan Noyola Mar 20 '20 at 19:50
  • Is this so hard? I want to create a CMS in one project but use that core cms in other solutions (just changing the database, css, images, js for each instance of use) so that I add features to the core then just update the DLLs (build output) in my dependencies when im ready. It will be possible to run different versions of the CMS based on if I have updated the DLLs or not (and im ok with that). – Andy Oct 05 '20 at 23:50

2 Answers2

1

You can find the path to dotnet.exe like this:

System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;

Or you could just move to .NET Core 3, which builds executables by default.

Default executables

.NET Core now builds runtime-dependent executables by default. This behavior is new for applications that use a globally installed version of .NET Core. Previously, only self-contained deployments would produce an executable.

During dotnet build or dotnet publish, an executable (known as the appHost) is created that matches the environment and platform of the SDK you're using. You can expect the same things with these executables as you would other native executables, such as:

You can double-click on the executable. You can launch the application from a command prompt directly, such as myapp.exe on Windows, and ./myapp on Linux and macOS.

https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-3-0#default-executables

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
  • Oops, I missed a line in my example where the `processPath` variable is actually using `MainModule.FileName` already. .NET Core 3 sounds like a good solution, but would I then need to package 3 different published child apps (Windows, Mac, Linux) since they contain their own runtimes? It's not quite the same as being able to publish a single portable child app and use the runtime of the parent (which may be installed globally), right? – Jonathan Noyola Mar 20 '20 at 19:47
  • The problem with this solution is, when you have mixed scenario. For example, your "main" application is a .net core 3.x executable (thus does not use `dotnet.exe`), but the thing you start is still dll-based. In this case you need some other way to find `dotnet.exe` and this can get quite complex. You'll can use one of the many "muxer-locator implementations" that can be found on github.com example azure-sdk, asp.net core sources, etc. (Search for `DotNetMuxer.cs`) – Christian.K Mar 21 '20 at 11:27
  • I think Christian has the right idea. I looked up [asp.net core's DotNetMuxer.cs](https://github.com/dotnet/aspnetcore/blob/master/src/Shared/CommandLineUtils/Utilities/DotNetMuxer.cs), but it turns out it's basically what I'm already doing. But at least this demonstrates that it's not an uncommon problem and this appears to be a fine solution. I would accept that in an answer. – Jonathan Noyola Mar 21 '20 at 19:09
  • appHost exe is something I was looking for. I was wondering how my framework dependent .Net core app is working without dotnet command. – Ankush Jain Jul 28 '22 at 17:00
0

You could go with what @DavidBrowne suggests in his answer. Using System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName "knowing" that it is dotnet.exe (with full path) and then using that. And mind you, depending on your situation that might be "good enough" and below is too complicated. Having that said...

However, that has two issues:

  • What if the process trying to execute another is itself not a .NET Core process (but a .NET Framework based one, or some IIS/ASP.NET Core in-process hosted app)?
  • What if the process trying to execute another is itself based on .NET Core 3.x executable (that is is started via MyProc.exe and not dotnet.exe MyProc.dll)?

If you write an application that only executes other executables that you also control, you can at least circumvent the 2nd issue by just making sure that everything is build the same way (using dotnet.exe or using .NET Core 3.x executables).

However, if that is not possible for some reason (e.g. issue one above), then you need to find the location of dotnet.exe on a system. The logic to do so is not super trivial and existing code on GitHub shows how it is done. Lookup DotNetMuxer.cs and you'll get some implementations. For example the one of ASP.NET Core.

A even more involved one, would also consider the FX_DEPS_FILE environment variable and could look like this:

// One of the many muxer-locator implementations that can be found on github.com
// (Example azure-sdk, asp.net core sources, etc.)

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;

namespace ProcessIsolation
{
    /// <summary>
    /// Utilities for finding the "dotnet.exe" file from the currently running .NET Core application
    /// </summary>
    internal static class DotNetMuxer
    {
        private const string MuxerName = "dotnet";

        static DotNetMuxer()
        {
            MuxerPath = TryFindMuxerPath();
        }

        /// <summary>
        /// The full filepath to the .NET Core muxer.
        /// </summary>
        public static string MuxerPath { get; }

        /// <summary>
        /// Finds the full filepath to the .NET Core muxer,
        /// or returns a string containing the default name of the .NET Core muxer ('dotnet').
        /// </summary>
        /// <returns>The path to <c>dotnet.exe</c> or <c>null</c> if not found.</returns>
        public static string MuxerPathOrDefault()
            => MuxerPath ?? MuxerName;

        private static string TryFindMuxerPath()
        {
            var fileName = MuxerName;
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                fileName += ".exe";
            }

            var mainModule = Process.GetCurrentProcess().MainModule;
            if (!string.IsNullOrEmpty(mainModule?.FileName)
                && Path.GetFileName(mainModule.FileName).Equals(fileName, StringComparison.OrdinalIgnoreCase))
            {
                return mainModule.FileName;
            }

            // if Process.MainModule is not available or it does not equal "dotnet(.exe)?", fallback to navigating to the muxer
            // by using the location of the shared framework
            var fxDepsFile = AppContext.GetData("FX_DEPS_FILE") as string;

            if (string.IsNullOrEmpty(fxDepsFile))
            {
                return null;
            }

            var muxerDir = new FileInfo(fxDepsFile) // Microsoft.NETCore.App.deps.json
                .Directory? // (version)
                .Parent? // Microsoft.NETCore.App
                .Parent? // shared
                .Parent; // DOTNET_HOME

            if (muxerDir == null)
            {
                return null;
            }

            var muxer = Path.Combine(muxerDir.FullName, fileName);
            return File.Exists(muxer)
                ? muxer
                : null;
        }
    }
}
Christian.K
  • 47,778
  • 10
  • 99
  • 143
  • I think you hit it right on the nose with the 2nd bullet. In my case I do control the parent app, so I can ensure it's always .NET Core, so I think asp.net Core's DotNetMuxer is what I'm looking for (which, as I mentioned, is fairly similar to what I already have in the question). Thanks! – Jonathan Noyola Mar 23 '20 at 00:50