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

fix: Application.ExecutablePath returns dll instead of exe #2801

Merged
merged 1 commit into from
Feb 10, 2020

Conversation

RussKie
Copy link
Member

@RussKie RussKie commented Feb 4, 2020

Resolves #1143 following the advice from @swaroop-sridhar in dotnet/runtime#11201 (comment)

Proposed changes

In .NET artifacts are DLLs even for executable projects. With some automagic they get bundled into executables.
However Assembly.GetEntryAssembly() always returns the dll instead of the exe.

Following the guidance from the Runtime team retrieve the path to the executable via GetModuleFileNameW call.

Customer Impact

  • Customers can now retrieve a path to the applications executable instead of the application's dll.

Regression?

  • Yes

Risk

  • Small-medium due to possibly unaccounted use cases

Test methodology

  • I have created several app targeting net472 and netcoreapp5.0 to capture outputs of calling Application.ExecutablePath in the following scenarios:
    • a single project app:
     [CmdletBinding(PositionalBinding=$false)]
     Param(
         [string] $frameworks = 'net472;netcoreapp5.0',
         [string] $targetDirectory = '.\',
         [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties
     )
    
     function Invoke-Tests {
         Param(
             [string] $frameworks,
             [string] $targetDirectory
         )
    
         $frameworks.Split(';') | ForEach-Object {
             $framework = $_
             Write-Host "> Executing test for $framework"
             $output = [System.IO.Path]::Combine($targetDirectory, "app1.$framework.log");
             dotnet.exe run --framework $framework > $output;
         }
     }
    
     function Update-Csproj {
         Param(
             [string] $csprojName,
             [string] $frameworks
         )
    
         [xml]$csproj = Get-Content $csprojName;
    
         # update the project to console from UI
         $csproj.Project.PropertyGroup.OutputType = 'Exe';
         # convert from single to multi-targeting
         $csproj.Project.PropertyGroup.TargetFramework = '';
         $node = $csproj.CreateElement("TargetFrameworks")
         $csproj.Project.PropertyGroup.AppendChild($node) | Out-Null;
         $csproj.Project.PropertyGroup.TargetFrameworks = $frameworks;
    
         $csproj.Save("$($pwd.Path)\$csprojName");
     }
    
     function Update-Program {
         [string]$program = Get-Content 'Program.cs' -Raw;
         $program = $program.Replace('Application.Run(new Form1());', 'Console.WriteLine(Application.ExecutablePath);').Replace('Application.SetHighDpiMode(HighDpiMode.SystemAware);', '');
         $program | Out-File -FilePath 'Program.cs' -Encoding utf8 -Force
     }
    
     $appFolder = [System.IO.Path]::GetRandomFileName()
     $outputDirectory = [System.IO.Path]::GetFullPath($targetDirectory);
    
     try {
         [System.IO.Directory]::CreateDirectory($appFolder) | Out-Null;
         Push-Location $appFolder
         dotnet.exe new winforms -n app
         Set-Location app
    
         Update-Csproj -csprojName 'app.csproj' -frameworks $frameworks
         Update-Program
         Invoke-Tests -frameworks $frameworks -targetDirectory  $outputDirectory
     }
     catch {
         Write-Host $_ -ForegroundColor Red
     }
     finally {
         Pop-Location
         [System.IO.Directory]::Delete($appFolder, $true);
     }
    • a multi project app where Application.ExecutablePath is called from the library project
     [CmdletBinding(PositionalBinding=$false)]
     Param(
         [string] $frameworks = 'net472;netcoreapp5.0',
         [string] $targetDirectory = '.\',
         [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties
     )
    
     function Invoke-Tests {
         Param(
             [string] $frameworks,
             [string] $targetDirectory
         )
    
         $frameworks.Split(';') | ForEach-Object {
             $framework = $_
             Write-Host "> Executing test for $framework"
             $output = [System.IO.Path]::Combine($targetDirectory, "apps.$framework.log");
             dotnet.exe run --framework $framework > $output;
         }
     }
    
     function Update-Csproj {
         Param(
             [string] $csprojName,
             [string] $frameworks
         )
    
         [xml]$csproj = Get-Content $csprojName;
    
         # update the project to console from UI
         if ($csproj.Project.PropertyGroup.OutputType) {
             $csproj.Project.PropertyGroup.OutputType = 'Exe';
         }
         # convert from single to multi-targeting
         $csproj.Project.PropertyGroup.TargetFramework = '';
         $node = $csproj.CreateElement("TargetFrameworks")
         $csproj.Project.PropertyGroup.AppendChild($node) | Out-Null;
         $csproj.Project.PropertyGroup.TargetFrameworks = $frameworks;
    
         $csproj.Save("$($pwd.Path)\$csprojName");
     }
    
     function New-TestClass {
         "namespace lib
         {
             public static class TestClass
             {
                 public static string GetApplicationExecutablePath() 
                     => System.Windows.Forms.Application.ExecutablePath;
             }
         }" | Out-File 'TestClass.cs' -Encoding utf8 -Force
    
     }
    
     function Update-Program {
         [string]$program = Get-Content 'Program.cs' -Raw;
         $program = $program.Replace('Application.Run(new Form1());', "Console.WriteLine(lib.TestClass.GetApplicationExecutablePath());").Replace('Application.SetHighDpiMode(HighDpiMode.SystemAware);', '');
         $program | Out-File -FilePath 'Program.cs' -Encoding utf8 -Force
     }
    
     $appFolder = [System.IO.Path]::GetRandomFileName()
     $outputDirectory = [System.IO.Path]::GetFullPath($targetDirectory);
    
     try {
         [System.IO.Directory]::CreateDirectory($appFolder) | Out-Null;
         Push-Location $appFolder
         dotnet.exe new winforms -n app
         dotnet.exe new winformslib -n lib
         dotnet.exe add app/app.csproj reference lib/lib.csproj
    
         Push-Location lib
         New-TestClass
         Update-Csproj -csprojName 'lib.csproj' -frameworks $frameworks
         Pop-Location
    
         Set-Location app
         Update-Csproj -csprojName 'app.csproj' -frameworks $frameworks
         Update-Program
         Invoke-Tests -frameworks $frameworks -targetDirectory  $outputDirectory
     }
     catch {
         Write-Host $_ -ForegroundColor Red
     }
     finally {
         Pop-Location
         [System.IO.Directory]::Delete($appFolder, $true);
     }
  • Added the created projects by running the above scripts and run them against privately build binaries to confirm the results of netcoreapp5.0 were the same as the results from net472 app.

Test results

Microsoft Reviewers: Open in CodeFlow

In .NET artifacts are DLLs even for executable projects. With some automagic
they get bundled into executables.
However `Assembly.GetEntryAssembly()` always returns the dll instead of the exe.

Following the guidance from the Runtime team retrieve the path to the
executable via `GetModuleFileNameW` call.

Resolves dotnet#1143
@RussKie
Copy link
Member Author

RussKie commented Feb 4, 2020

@merriemcgaw I think we should consider this fix for servicing release/3.1

@RussKie
Copy link
Member Author

RussKie commented Feb 4, 2020

@dotnet/dotnet-winforms I'd appreciate your thoughts on whether we should check some specific use cases.

@JeremyKuhne @hughbe @weltkante any thoughts on how we better test this change?
Is there a point in creating a multi-targeting app to compare the results? Or should we confine to writing a functional test and confirm the output is an exe instead of a dll?

@RussKie RussKie self-assigned this Feb 4, 2020
@codecov
Copy link

codecov bot commented Feb 4, 2020

Codecov Report

Merging #2801 into master will decrease coverage by 0.00498%.
The diff coverage is 100%.

@@                 Coverage Diff                 @@
##              master       #2801         +/-   ##
===================================================
- Coverage   59.31806%   59.31307%   -0.00499%     
===================================================
  Files           1243        1243                 
  Lines         430508      430492         -16     
  Branches       38972       38970          -2     
===================================================
- Hits          255369      255338         -31     
- Misses        169742      169769         +27     
+ Partials        5397        5385         -12
Flag Coverage Δ
#Debug 59.31307% <100%> (-0.00499%) ⬇️
#production 32.14399% <100%> (-0.01013%) ⬇️
#test 98.97829% <ø> (ø) ⬆️

@weltkante
Copy link
Contributor

weltkante commented Feb 4, 2020

I want to point at the fact that you'll want this API to work for all deployment modes, so you'll probably also want to test them in different deployment modes. You can run a WinForms application via dotnet run, you can deploy a self contained application, deploy with shared runtime etc. (don't know if there are more ways to deploy, whatever is supported or is currently developed, like approaches to AOT compilation, if its going to be supported by stock VS/.NET Core you'll probably also want to support it in WinForms, otherwise people are going to trip over it if the API doesn't work in some deployment mode).

I'm not great with tests so can't really offer suggestions here. Since you need to test applications in different deployment modes you can't really use xunit here (because that would mess up what the entry point assembly is), I have no idea about which other options you have available, sorry.

Though considering you are basically just wrapping the win32 API for getting the executable, it is probably more important to test the features dependent on this functionality, rather than getting the executable path itself (to make sure everyone plays well with the fact that it now basically always is an unmanaged application, where previously this was a rare corner case - callers may need to be switched to call Assembly.GetEntryAssembly if they need the managed assembly).

[edit] I checked the callers and they should be fine, there are only two callers:

  • Application.Restart always wants the exe
  • Application.GetAppFileVersionInfo already has code looking for the managed entry point first (Assembly.GetEntryAssembly) and only uses Application.ExecutablePath as a fallback. If you have no managed entrypoint you are in a hosted scenario and returning the version of the hosting unmanaged exe is the correct thing to do (Desktop did the same)

Uri codeBase = new Uri(cb);
if (codeBase.IsFile)
{
s_executablePath = codeBase.LocalPath + Uri.UnescapeDataString(codeBase.Fragment);
Copy link
Member

@Tanya-Solyanik Tanya-Solyanik Feb 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like after the change, file path is not formatted, instead, according to docs, we return what was specified when the module was loaded.

The string returned will use the same format that was specified when the module was loaded. Therefore, the path can be a long or short file name, and can use the prefix "\?". For more information, see Naming a File.

Also if the app was started using only only the file name and GetModuleFileName returns that short name, then Path.GetFullPath would resolve the full path based on the current working directory, which might not be the same as the app was started from. See remarks here.

[EDIT] fix formatting

Copy link
Contributor

@weltkante weltkante Feb 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure GetModuleFileName can return a relative file name? I was under the impression that only the format (long/short/escaped) could differ, but that it always is an absolute path. (Doc says "Retrieves the fully qualified path ..." as its first line and then notes later that the format can differ, but I don't think it retracts the fact that its fully qualified.)

@JeremyKuhne
Copy link
Member

Can you add the output of the scripts you ran?

@RussKie
Copy link
Member Author

RussKie commented Feb 6, 2020

You can run a WinForms application via dotnet run, you can deploy a self contained application

I've tested all of these scenarios, I failed to mentioned that in the PR message, my bad.

. . .

I've run apps in a number of use-cases, you think I've missed some please let me know.
It looks like the proposed fix works better 😏

Breaking the results into multiple posts for ease of navigation

@RussKie
Copy link
Member Author

RussKie commented Feb 6, 2020

Single project app

  • net472
PS C:\Development\winforms> C:\Development\!Tests\DynamicApps\SingleApp\app\bin\Debug\net472\app.exe
C:\Development\!Tests\DynamicApps\SingleApp\app\bin\Debug\net472\app.exe

PS C:\Development\winforms> ..\!Tests\DynamicApps\SingleApp\app\bin\Debug\net472\app.exe
C:\Development\!Tests\DynamicApps\SingleApp\app\bin\Debug\net472\app.exe

PS C:\Development\winforms> & '\\SCREAMERMS\c$\Development\!Tests\DynamicApps\SingleApp\app\bin\Debug\net472\app.exe'
\\screamerms\c$\Development\!Tests\DynamicApps\SingleApp\app\bin\Debug\net472\app.exe

PS C:\Development\winforms> \\SCREAMERMS\c$\Development\!Tests\DynamicApps\SingleApp\app\bin\Debug\net472\app.exe
\\screamerms\c$\Development\!Tests\DynamicApps\SingleApp\app\bin\Debug\net472\app.exe

PS C:\Development\winforms> \\.\C:\Development\!Tests\DynamicApps\SingleApp\app\bin\Debug\net472\app.exe
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'app.exe' or one of its dependencies. The network path was not found.

PS C:\Development\winforms> \\?\C:\Development\!Tests\DynamicApps\SingleApp\app\bin\Debug\net472\app.exe
Unhandled Exception: System.UriFormatException: Invalid URI: The format of the URI could not be determined.
   at System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind)
   at System.Windows.Forms.Application.get_ExecutablePath()
   at app.Program.Main() in C:\Development\!Tests\DynamicApps\vjyp220f.rar\app\Program.cs:line 20

PS C:\Development\winforms> \\127.0.0.1\C$\Development\!Tests\DynamicApps\SingleApp\app\bin\Debug\net472\app.exe
\\127.0.0.1\C$\Development\!Tests\DynamicApps\SingleApp\app\bin\Debug\net472\app.exe
  • netcoreapp5.0
PS C:\Development\winforms> C:\Development\winforms\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe
C:\Development\winforms\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe

PS C:\Development\winforms> .\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe
C:\Development\winforms\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe

PS C:\Development\winforms> & '\\SCREAMERMS\c$\Development\winforms\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe'
\\SCREAMERMS\c$\Development\winforms\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe

PS C:\Development\winforms> \\SCREAMERMS\c$\Development\winforms\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe
\\SCREAMERMS\c$\Development\winforms\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe

PS C:\Development\winforms> \\.\C:\Development\winforms\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe
\\.\C:\Development\winforms\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe

PS C:\Development\winforms> \\?\C:\Development\winforms\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe
\\?\C:\Development\winforms\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe

PS C:\Development\winforms> \\127.0.0.1\C$\Development\winforms\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe
\\127.0.0.1\C$\Development\winforms\artifacts\bin\SingleApp\Debug\netcoreapp5.0\SingleApp.exe
  • netcoreapp5.0 dotnet publish -c Release -r win-x64 -p:PublishSingleFile=True --self-contained false|true
PS C:\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish> .\SingleApp.exe
C:\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe

PS C:\Development\winforms> C:\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe
C:\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe

PS C:\Development\winforms> .\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe
C:\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe

PS C:\Development\winforms> & '\\SCREAMERMS\c$\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe'
\\SCREAMERMS\c$\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe

PS C:\Development\winforms> \\SCREAMERMS\c$\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe
\\SCREAMERMS\c$\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe

PS C:\Development\winforms> \\.\C:\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe
\\.\C:\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe

PS C:\Development\winforms> \\?\C:\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe
\\?\C:\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe

PS C:\Development\winforms> \\127.0.0.1\C$\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe
\\127.0.0.1\C$\Development\winforms\artifacts\bin\SingleApp\Release\netcoreapp5.0\win-x64\publish\SingleApp.exe

@RussKie
Copy link
Member Author

RussKie commented Feb 6, 2020

Multi project app

  • net472
PS C:\Development\winforms> C:\Development\!Tests\DynamicApps\MultiApp\app\bin\Debug\net472\app.exe
C:\Development\!Tests\DynamicApps\MultiApp\app\bin\Debug\net472\app.exe

PS C:\Development\winforms> ..\!Tests\DynamicApps\MultiApp\app\bin\Debug\net472\app.exe
C:\Development\!Tests\DynamicApps\MultiApp\app\bin\Debug\net472\app.exe

PS C:\Development\winforms> & '\\SCREAMERMS\c$\Development\!Tests\DynamicApps\mULTIApp\app\bin\Debug\net472\app.exe'
\\screamerms\c$\Development\!Tests\DynamicApps\mULTIApp\app\bin\Debug\net472\app.exe

PS C:\Development\winforms> \\SCREAMERMS\c$\Development\!Tests\DynamicApps\mULTIApp\app\bin\Debug\net472\app.exe
\\screamerms\c$\Development\!Tests\DynamicApps\mULTIApp\app\bin\Debug\net472\app.exe

PS C:\Development\winforms> \\.\C:\Development\!Tests\DynamicApps\MultiApp\app\bin\Debug\net472\app.exe
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'app.exe' or one of its dependencies. The network path was not found.

PS C:\Development\winforms> \\?\C:\Development\!Tests\DynamicApps\MultiApp\app\bin\Debug\net472\app.exe
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'lib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
   at app.Program.Main()

PS C:\Development\winforms> \\127.0.0.1\C$\Development\!Tests\DynamicApps\MultiApp\app\bin\Debug\net472\app.exe
\\127.0.0.1\C$\Development\!Tests\DynamicApps\MultiApp\app\bin\Debug\net472\app.exe

PS C:\Development\winforms> \\127.0.0.1\C$\DEVELO~1\!Tests\DynamicApps\MultiApp\app\bin\Debug\net472\app.exe
\\127.0.0.1\C$\DEVELO~1\!Tests\DynamicApps\MultiApp\app\bin\Debug\net472\app.exe
  • netcoreapp5.0
PS C:\Development\winforms> C:\Development\winforms\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe
C:\Development\winforms\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe

PS C:\Development\winforms> .\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe
C:\Development\winforms\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe

PS C:\Development\winforms> & '\\SCREAMERMS\c$\Development\winforms\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe'
\\SCREAMERMS\c$\Development\winforms\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe

PS C:\Development\winforms> \\SCREAMERMS\c$\Development\winforms\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe
\\SCREAMERMS\c$\Development\winforms\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe

PS C:\Development\winforms> \\.\C:\Development\winforms\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe
\\.\C:\Development\winforms\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe

PS C:\Development\winforms> \\?\C:\Development\winforms\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe
\\?\C:\Development\winforms\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe

PS C:\Development\winforms> \\127.0.0.1\C$\Development\winforms\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe
\\127.0.0.1\C$\Development\winforms\artifacts\bin\MultiApp\Debug\netcoreapp5.0\MultiApp.exe

PS C:\Development\winforms> \\127.0.0.1\C$\DEVELO~1\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe
\\127.0.0.1\C$\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe
  • netcoreapp5.0 dotnet publish -c Release -r win-x64 -p:PublishSingleFile=True --self-contained false|true
PS C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish> .\MultiApp.exe
C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe

PS C:\Development\winforms> C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe
C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe

PS C:\Development\winforms> .\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe
C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe

PS C:\Development\winforms> & '\\SCREAMERMS\c$\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe'
\\SCREAMERMS\c$\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe

PS C:\Development\winforms> \\SCREAMERMS\c$\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe
\\SCREAMERMS\c$\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe

PS C:\Development\winforms> \\.\C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe
\\.\C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe

PS C:\Development\winforms> \\?\C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe
\\?\C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe

PS C:\Development\winforms> \\127.0.0.1\C$\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe
\\127.0.0.1\C$\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe

PS C:\Development\winforms> \\127.0.0.1\C$\DEVELO~1\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe
\\127.0.0.1\C$\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe

@RussKie
Copy link
Member Author

RussKie commented Feb 6, 2020

Overall the behaviour is comparable with the following notable differencces:

  • net472 crash when attempting to execute the following whilst netcoreapp5.0 works
\\?\C:\Development\!Tests\DynamicApps\MultiApp\app\bin\Debug\net472\app.exe
\\.\C:\Development\!Tests\DynamicApps\MultiApp\app\bin\Debug\net472\app.exe
  • net472 retains 8.3 path format, whilst netcoreapp5.0 doesn't:
PS C:\Development\winforms> \\127.0.0.1\C$\DEVELO~1\!Tests\Dynami~1\MultiApp\app\bin\Debug\net472\app.exe
\\127.0.0.1\C$\DEVELO~1\!Tests\Dynami~1\MultiApp\app\bin\Debug\net472\app.exe

PS C:\Development\winforms> \\127.0.0.1\C$\DEVELO~1\winforms\artifa~1\bin\MultiApp\Release\netcor~1.0\win-x64\publish\MultiApp.exe
\\127.0.0.1\C$\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe

I'm not sure how significant this one is... 🤔

@RussKie
Copy link
Member Author

RussKie commented Feb 6, 2020

@dreddy-work will this have any impact on ClickOnce scenarios?

The docs

This path will be different depending on whether the Windows Forms application is deployed using ClickOnce.

How can we test this?

@RussKie RussKie added the waiting-review This item is waiting on review by one or more members of team label Feb 6, 2020
@dreddy-work
Copy link
Member

@dreddy-work will this have any impact on ClickOnce scenarios?

  • We are not supporting ClickOnce in core yet.

@dreddy-work
Copy link
Member

dreddy-work commented Feb 6, 2020

@dreddy-work will this have any impact on ClickOnce scenarios?

The docs

This path will be different depending on whether the Windows Forms application is deployed using ClickOnce.

How can we test this?

What i see from doc is, in a scenario where application is deployed using clickonce, user launch their application either by clicking "*.Application" file or running setup.exe that is available in publish folder. In these cases, i see Application.ExecutablePath returning the actual winforms application path as"%appdata%\local\2.0......exe". This is the actual location of winforms application from where clickonce is running the app.

However, this is inline with current core behavior ( returning the dll path instead of exe that invoked it).

@dreddy-work
Copy link
Member

dreddy-work commented Feb 6, 2020

With this change, what do you see when you run the core winforms app using dotnet.exe ?

dotnet.exe path or winforms.dll path?

@RussKie
Copy link
Member Author

RussKie commented Feb 7, 2020

With this change, what do you see when you run the core winforms app using dotnet.exe ?

PS C:\Development\winforms> dotnet C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\MultiApp.dll
C:\Program Files\dotnet\dotnet.exe

And I think this is correct, since the executable is dotnet.exe. I wouldn't expect anything else.

@RussKie RussKie marked this pull request as ready for review February 7, 2020 02:58
@RussKie RussKie requested a review from a team as a code owner February 7, 2020 02:58
Copy link
Contributor

@weltkante weltkante left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - also checked the callers:

  • Application.Restart always wants the exe
  • Application.GetAppFileVersionInfo already has code looking for the managed entry point first (Assembly.GetEntryAssembly) and only uses Application.ExecutablePath as a fallback. If you have no managed entrypoint you are in a custom host scenario and returning the version of the hosting unmanaged exe is the correct thing to do (Desktop did the same)

@merriemcgaw
Copy link
Member

Agreed, this is a good servicing candidate. Please add the servicing-consider label when this is ready to go to shiproom.

@RussKie RussKie added the Servicing-consider .NET Shiproom label indicating a PR seeks to enter into a branch under Tell-Mode criteria label Feb 8, 2020
@RussKie RussKie removed the waiting-review This item is waiting on review by one or more members of team label Feb 10, 2020
@RussKie RussKie merged commit 2af3af9 into dotnet:master Feb 10, 2020
@RussKie RussKie deleted the fix_Application.ExecutablePath branch February 10, 2020 05:17
@ghost ghost added this to the 5.0 milestone Feb 10, 2020
RussKie added a commit to RussKie/winforms that referenced this pull request Feb 11, 2020
…2801)

In .NET artifacts are DLLs even for executable projects. With some automagic
they get bundled into executables.
However `Assembly.GetEntryAssembly()` always returns the dll instead of the exe.

Following the guidance from the Runtime team retrieve the path to the
executable via `GetModuleFileNameW` call.

Resolves dotnet#1143

(cherry picked from commit 2af3af9)
@RussKie
Copy link
Member Author

RussKie commented Feb 11, 2020

I ran further tests to verify attribute-based information, such as ProductName or CompanyName, can be retrieved after the change.

I added the following attributes and published the app:

[assembly: AssemblyTitle("Test app title")]
[assembly: AssemblyCompany("Test app company")]
[assembly: AssemblyProduct("Test app product")]
[assembly: AssemblyDescription("Test app description")]
[assembly: AssemblyCopyright("Copyright © 2020 Microsoft")]
[assembly: AssemblyVersion("1.2.3")]
[assembly: AssemblyFileVersion("1.2.3.4")]
[assembly: AssemblyInformationalVersion("2.3.4.5")]
PS C:\MultiApp\app> dotnet publish -c Release -r win-x64 -p:PublishSingleFile=True --self-contained true

Microsoft (R) Build Engine version 16.4.0-preview-19460-01+e460c009a for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  Restore completed in 27.35 ms for C:\Development\winforms\src\System.Windows.Forms.Primitives\src\System.Windows.Forms.Primitives.csproj.
  Restore completed in 27.35 ms for C:\Development\winforms\src\System.Windows.Forms\src\System.Windows.Forms.csproj.
  Restore completed in 27.35 ms for C:\Development\winforms\src\Accessibility\src\Accessibility.ilproj.
  Restore completed in 27.35 ms for C:\Development\winforms\src\Accessibility\ver\Accessibility-version.csproj.
  Restore completed in 31.19 ms for C:\Development\winforms\src\System.Windows.Forms\tests\IntegrationTests\System.Windows.Forms.IntegrationTests.Common\System.Windows.Forms.IntegrationTests.Common.csproj.
  Restore completed in 31.19 ms for C:\MultiApp\lib\lib.csproj.        Restore completed in 31.25 ms for C:\MultiApp\app\MultiApp.csproj.   Accessibility-version -> C:\Development\winforms\artifacts\bin\Accessibility-version\Release\netcoreapp5.0\Accessibility-version.dll
  Accessibility -> C:\Development\winforms\artifacts\bin\Accessibility\Release\netcoreapp5.0\Accessibility.dll
  System.Windows.Forms.Primitives -> C:\Development\winforms\artifacts\bin\System.Windows.Forms.Primitives\Release\netcoreapp5.0\System.Windows.Forms.Primitives.dll
  System.Windows.Forms -> C:\Development\winforms\artifacts\bin\System.Windows.Forms\Release\netcoreapp5.0\System.Windows.Forms.dll
  System.Windows.Forms.IntegrationTests.Common -> C:\Development\winforms\artifacts\bin\System.Windows.Forms.IntegrationTests.Common\Release\netcoreapp5.0\System.Windows.Forms.IntegrationTests.Common.dll
  lib -> C:\Development\winforms\artifacts\bin\lib\Release\netcoreapp5.0\lib.dll
  MultiApp -> C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\MultiApp.dll
  MultiApp -> C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\

PS C:\MultiApp\app> C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe

Exe:      C:\Development\winforms\artifacts\bin\MultiApp\Release\netcoreapp5.0\win-x64\publish\MultiApp.exe
Company:  Test app company
Product:  Test app product

image

@ghost ghost locked as resolved and limited conversation to collaborators Feb 2, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Servicing-consider .NET Shiproom label indicating a PR seeks to enter into a branch under Tell-Mode criteria
Projects
None yet
Development

Successfully merging this pull request may close these issues.

In self-contained app Application.ExecutablePath returns dll
6 participants