when you publish a linux-x64 command-line using self-contained mode, and then run the program using the supplied ELF exe, Environment.CommandLine (and Environment.GetCommandLineArgs()[0]) refers to the .dll instad of the ELF.
@jeffschwMSFT Can you attach an area- label to this or move it to the repo it belongs? ...or dispose of it as you see fit. :)
cc @sdmaclea (we may need to copy this to core-setup)
Note that this problem surfaces on all platforms.
E.g., using PowerShell Core v6.2 on Windows:
PS> [Environment]::GetCommandLineArgs()
C:\Program Files\PowerShell\6\pwsh.dll
@jeffschwMSFT I agree this should move to core-setup
move to core-setup
I am not sure about it.
There will be change required in CoreCLR to fix this. There may or may not be change required in core-setup too. It depends on the design. Maybe we should fix discuss what the fix is going to look like.
I am not sure about it.
I had assumed that CoreCLR was populating Environment.CommandLine from a property passed by AppHost to host policy.
I was intending to change AppHost, to correct the CommandLine.
Environment.CommandLine is create by stitching the managed entrypoint .dll name and individual arguments together: https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Environment.cs#L64. AppHost has no control over it.
fyi see linked issue for more details on how this works together.
On a related note: Once this is fixed, will Environment.CommandLine / Environment.GetCommandLineArgs reflect an intentionally set _custom_ invocation name (argv[0], reflected in $0 in POSIX-like shells), as used on Unix-like platforms?
Prepending the actual invocation name with - is a convention used to signal to POSIX-like shells such as Bash that they should act as _login shells_, and utilities such as ssh, macOS terminal emulators and Bash's exec -l use this mechanism.
For PowerShell to be a good Unix shell citizen (to be able to fully act as a user's default shell), it must therefore know its true invocation name, so it can check for - as the first character - see https://github.com/PowerShell/PowerShell-RFC/pull/171.
A quick Bash example that shows that exec -l prepends - to the invocation name, which sh sees reflected in $0:
# Run from Bash.
$ (exec -l sh -c 'echo $0')
-sh
Also, if invocation happened _via a symlink_, it is the symlink's path that should be reported by Environment.CommandLine / Environment.GetCommandLineArgs: see https://github.com/dotnet/runtime/issues/30212#issuecomment-710357008
For some reason the reference from #25841 is not showing up. Adding it manually here.
The PowerShell test suite currently uses a C# testexe to validate some aspects of command-line passing, but due to this issue I am currently unable to verify that a "force quote" thing is working. Many Windows apps need that.
@Artoria2e5:
I think the only thing to be fixed here, if feasible, is that the actual executable (original entry point) not the managed DLL filename should be reflected in Environment.GetCommandLineArgs(0) / the first argument in Environment.CommandLine.
As @jkotas stated above, .NET Core _reconstructs_ a _hypothetical_ command line that may or may not exactly reflect the actual command line passed _on Windows_. On Unix, there is no command line to begin with.
(permalink to the source code).
On Windows, the reconstructed command line is fully _equivalent_ to the original one, assuming that (a) you make the call without involvement of a shell (e.g., from the Windows Run dialog, WinKey-R) and (b) a .NET / Microsoft C/C++-compiler-based executable parses it.
On Unix, the reconstructed command line is not robustly usable in any real-world sense, because you'd need a _shell_ to call it, and a shell's own up-front interpretation of such a command line would alter the call (e.g., foo '*.txt' would be reported as something like /path/to/foo *.txt - note the absence of quotes - which, if used as-is in a POSIX-like shell such as bash would cause globbing (expansion of wildcard pattern *.txt to matching filenames in the current dir)).
While calling the Windows API when running on Windows to get the true command line is feasible, I don't think it is worth the trouble to implement this in Environment.CommandLine.
For your use case, the simplest solution is to compile a .NET _Framework_ console application, where the true command line _is_ being reported; this is as simple as a single Add-Type call from _Windows PowerShell_; the following command creates a printArgs.exe executable in the current dir; when invoked, printArgs.exe prints its parsed arguments individually as well as the raw command line:
Add-Type -OutputType ConsoleApplication -OutputAssembly ./printArgs.exe -TypeDefinition @'
using System;
static class ConsoleApp {
static int Main(string[] args) {
Console.WriteLine("\n{0} argument(s) received (enclosed in <...> for delineation):\n", args.Length);
for (int i = 0; i < args.Length; ++i) {
Console.WriteLine(" <{0}>", args[i]);
}
Console.WriteLine("\nCommand line:\n\n {0}\n", Environment.CommandLine);
return 0;
}
}
'@
This is essentially what the dbea (Debug-ExecutableArguments) command from the Native PowerShell module does behind the scenes.
The alternative is to use a P/Invoke call to the Windows API's GetCommandLineW function, as demonstrated in https://github.com/PowerShell/PowerShell/issues/1995#issuecomment-677664318.
A workaround for Environment.GetCommandLineArgs()[0] returning the wrong value is System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName (#42768 will make this simpler and more efficient for 5.0: Environment.ProcessPath); the added advantage is always getting a _full_ path back, whereas on Windows Environment.GetCommandLineArgs()[0] can situationally report a relative one.