Runtime: Not possible to get the executable path for self-contained executable

Created on 28 Jun 2019  路  25Comments  路  Source: dotnet/runtime

When calling Assembly.Get*Assembly().CodeBase for a self-contained .NET Core 3 project, it reports a temporary directory rather than the folder where the executable actually is located.

using System;
using System.Reflection;

namespace AssemblyReflectionIssue
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (var file in Assembly.GetEntryAssembly().GetFiles())
            {
                Console.WriteLine(file.Name);
            }
            Console.WriteLine(Assembly.GetCallingAssembly().CodeBase);
            Console.WriteLine(Assembly.GetEntryAssembly().CodeBase);
            Console.WriteLine(Assembly.GetExecutingAssembly().CodeBase);
        }
    }
}

Put the code above in a project with these settings:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <LangVersion>8.0</LangVersion>
    <Platforms>x64</Platforms>
    <PublishSingleFile>true</PublishSingleFile>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
  </PropertyGroup>

</Project>

Then publish the app to e.g. C:AssemblyReflectionIssue and launch it. Something like this will be printed:

C:UsersuserAppDataLocalTemp.netAssemblyReflectionIssue2bn5yvkd.pjbAssemblyReflection.dll
file:///C:/Users/user/AppData/Local/Temp/.net/AssemblyReflectionIssue/2bn5yvkd.pjb/AssemblyReflection.dll
file:///C:/Users/user/AppData/Local/Temp/.net/AssemblyReflectionIssue/2bn5yvkd.pjb/AssemblyReflection.dll
file:///C:/Users/user/AppData/Local/Temp/.net/AssemblyReflectionIssue/2bn5yvkd.pjb/AssemblyReflection.dll

I would expect at least one of the calls to return the actual path (C:AssemblyReflectionIssue)?

area-AssemblyLoader-coreclr question

Most helpful comment

You can use Process.GetCurrentProcess().MainModule.FileName to get the filename of the binary that launched the process. Or if you are on Windows, you can P/Invoke GetModuleFileName(NULL, ...) to get the filename of the main process module (it is faster than calling Process.GetCurrentProcess().MainModule.FileName).

All 25 comments

@jeffschwMSFT

@sirjeppe you are correct. As we built this first wave of this feature we have not provided the convenience APIs that return all the paths involved in the process. Right now there are people taking advantage of the CodeBase path to determine if they are running single-exe or not. We are open to feedback about what APIs would be nice on determining the location of files. In the meantime could you use current working directory?

cc @vitek-karas @elinor-fung @lpereira

You can use Process.GetCurrentProcess().MainModule.FileName to get the filename of the binary that launched the process. Or if you are on Windows, you can P/Invoke GetModuleFileName(NULL, ...) to get the filename of the main process module (it is faster than calling Process.GetCurrentProcess().MainModule.FileName).

For the records:

public static class Extensions {
    [System.Runtime.InteropServices.DllImport("kernel32.dll")]
    static extern uint GetModuleFileName(IntPtr hModule, System.Text.StringBuilder lpFilename, int nSize);
    static readonly int MAX_PATH = 255;
    public static string GetExecutablePath() {
        if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) {
            var sb = new System.Text.StringBuilder(MAX_PATH);
            GetModuleFileName(IntPtr.Zero, sb, MAX_PATH);
            return sb.ToString();
        }
        else {
            return System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
        }
    }
}

I guess this can be closed then?

@Symbai for now it can. We have it on our roadmap to potentially offer convenience APIs to provide this sort of information. We are gathering data on what makes the most sense.

For the record: I work for Microsoft, but have no work account for GitHub so I'm using my private one.

@jeffschwMSFT - The current page (https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.getentryassembly?view=netframework-4.8) for at least the Assembly.GetEntryAssembly() method says: "Gets the process executable in the default application domain. In other application domains, this is the first executable that was executed by ExecuteAssembly(String)." - i.e. that it should return the executable (not the temporarily extracted modules which seems to be the case currently, if I have interpreted the docs correctly, of course). IMO this is misleading in the case I have reported here, so I would love to see an update to the documentation as the outcome of this ticket before it is closed that explains/provides the workaround by @Symbai and/or some other text to inform users about this behavior mismatch between self-contained executables and regular executables. Preferrably with a complete solution to get the expected path as mentioned in e.g. many Stack Overflow posts and the official documentation etc.

@sirjeppe docs have a separate tracking mechanism for feedback. I would encourage this issue to be about the need for better API access and we can file a specific issue to update the wording. Make sense?

Absolutely! Do you mean this ticket will stay open until such APIs are in place or are you still agreeing that this issue will be closed and that a new ticket will be opened on the documentation clarification?

We may keep this one open for tracking the need for better API. The doc feedback is better suited here: https://github.com/dotnet/docs

Ok! I will report a second ticket there then. Thank you!

@sirjeppe even better you could consider offering a PR in the docs repo...

Environment.CurrentDirectory seems to get the job done.

Environment.CurrentDirectory seems to get the job done.

That can be set by parent process though, so not the same thing as path to executable.

@Symbai
How would we get this for linux/mac in the meanwhile?

@swaroop-sridhar

@kamronbatman, you can use Process.GetCurrentProcess().MainModule.FileName as @jkotas suggested above, which is platform independent.
Additionally, on linux/mac, you can call dirname(readlink("/proc/self/exe", buffer, PATH_MAX)) via P/Invoke.

Is there still no convenient way to do this as of .NET Core 3.0 stable?
Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName) feels kinda messy to me

@Svetomech

This works for me. Directory.GetCurrentDirectory()
I am using .NET Core 3.1.100. I guess it will work for .NET Core 3.0 too.

@fatihyildizhan Process current directory isn't the same as application directory, it'll only be the same when program launched from same folder it's in. But it can be changed both in process and from launching process.

@fatihyildizhan this would not work for you if you use self-contained executable, a .NET Core 3.0+ feature. Just an example.

try this

Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location)

For the records:

public static class Extensions {
  [System.Runtime.InteropServices.DllImport("kernel32.dll")]
  static extern uint GetModuleFileName(IntPtr hModule, System.Text.StringBuilder lpFilename, int nSize);
  static readonly int MAX_PATH = 255;
  public static string GetExecutablePath() {
      if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) {
          var sb = new System.Text.StringBuilder(MAX_PATH);
          GetModuleFileName(IntPtr.Zero, sb, MAX_PATH);
          return sb.ToString();
      }
      else {
          return System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
      }
  }
}

I guess this can be closed then?

@Symbai
Why is GetModuleFileName a better choice when running Windows? System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; seems to return the correct result for me

@mlockett42 See the above answer from jkotas https://github.com/dotnet/runtime/issues/13051#issuecomment-510267727

Was this page helpful?
0 / 5 - 0 ratings