Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not possible to get the executable path for self-contained executable #13051

Closed
sirjeppe opened this issue Jun 28, 2019 · 25 comments
Closed

Not possible to get the executable path for self-contained executable #13051

sirjeppe opened this issue Jun 28, 2019 · 25 comments
Labels
area-AssemblyLoader-coreclr question Answer questions and provide assistance, not an issue with source code or documentation.
Milestone

Comments

@sirjeppe
Copy link

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:\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
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)?

@carlossanlop carlossanlop transferred this issue from dotnet/core Jul 9, 2019
@stephentoub stephentoub transferred this issue from dotnet/corefx Jul 10, 2019
@Symbai
Copy link

Symbai commented Jul 10, 2019

dotnet/winforms#1143

@RussKeldorph
Copy link
Contributor

@jeffschwMSFT

@jeffschwMSFT
Copy link
Member

@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

@jkotas
Copy link
Member

jkotas commented Jul 10, 2019

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).

@Symbai
Copy link

Symbai commented Jul 24, 2019

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?

@jeffschwMSFT
Copy link
Member

@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.

@sirjeppe
Copy link
Author

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.

@jeffschwMSFT
Copy link
Member

@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?

@sirjeppe
Copy link
Author

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?

@jeffschwMSFT
Copy link
Member

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

@sirjeppe
Copy link
Author

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

@danmoseley
Copy link
Member

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

@AleksiAleksiev
Copy link

AleksiAleksiev commented Aug 20, 2019

Environment.CurrentDirectory seems to get the job done.

@devlead
Copy link
Contributor

devlead commented Aug 22, 2019

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.

@kamronbatman
Copy link
Contributor

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

@jeffschwMSFT
Copy link
Member

@swaroop-sridhar

@swaroop-sridhar
Copy link
Contributor

@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.

@swaroop-sridhar
Copy link
Contributor

@Svetomech
Copy link

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

@fatihyildizhan
Copy link

@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.

@devlead
Copy link
Contributor

devlead commented Dec 9, 2019

@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.

@Svetomech
Copy link

Svetomech commented Dec 10, 2019

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

@NecroKorn
Copy link

try this

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

@mlockett42
Copy link

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

@Symbai
Copy link

Symbai commented Oct 17, 2020

@mlockett42 See the above answer from jkotas #13051 (comment)

@ghost ghost locked as resolved and limited conversation to collaborators Dec 12, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-AssemblyLoader-coreclr question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests