In the old .csproj format, setting <OutputType>Exe</OutputType> produced an executable that is a console application, even if WinForms was used in the application. Similarly, <OutputType>WinExe</OutputType> always produced a windows GUI application. There are times when a console application is desired, though I'd agree not most times.
This old behavior can be easily confirmed by creating a legacy WinForms project, compiling it with both OutputTypes and running dumpbin /headers MyApp.exe and looking in the output for subsystem. The results are 3 subsystem (Windows CUI) for Exe and 2 subsystem (Windows GUI) for WinExe. Pay attention of the C vs G in that output - it is easy to miss.
After converting to the new SDK format csproj for multitargeting support (as we all want to move to .NET 5.0 but it does not happen overnight), this behavior is broken in the full framework (net48) project - but only if the 'WindowsDesktop' is used and/or 'UseWindowsForms' is true. I'd consider this a regression.
The following project will generate, always, the equal of OutputType == WinExe regardless of what is specified . This is easily confirmed by dumpbin on the output of compilation with both settings.
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<TargetFrameworks>net48;netcoreapp5.0</TargetFrameworks>
<UseWindowsForms>true</UseWindowsForms>
<OutputType>Exe</OutputType>
</PropertyGroup>
</Project>
The following project, _without_ WindowsDesktop and/or UseWindowsForms the OutputType is respected, and will produce an executable with the expected subsystem. Confirmation achieved with the above process.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;netcoreapp5.0</TargetFrameworks>
<OutputType>Exe</OutputType>
</PropertyGroup>
</Project>
If your application does use WinForms, adjusting the project to look like the latter example results in failed compilation., as one would expect.
The only workaround I have been able to discover is not a _reasonable_ one. A post-build step to run editbin /subsystem:console MyApp.exe to twiddle the appropriate bits in the PXE header will make the compiled output behave as desired. To fully/properly integrate this type of workaround in the build pipeline would require something along the lines of Kirill Osenkov's LargeAddressAware package.
Is there a better workaround to restore the previous, and desired, behavior, making the specified OutputType consistent and predictable regardless of what SDK or UseXYZ was also in the project?
Possible related work that impacted dotnet runtime, specifically to be respectful of the differences between the OutputType, which made it more like outputs from Full .NET Framework. Perhaps a change related to this is involved in the regression on the .NET Framework side where a new expected variable is not created in the pipeline.
https://github.com/dotnet/sdk/issues/1899 (.NETCore apps cannot specify a Windows application manifest and other things that must go in exe)
https://github.com/dotnet/sdk/pull/2470 (Add ability to mark self-contained .exe as Windows GUI program.)
https://github.com/dotnet/runtime/issues/2455 (Consider providing a "GUI" version of the shared host)
This was a purposeful behavior change with https://github.com/dotnet/sdk/issues/11398. You can opt out of this behavior by setting the DisableWinExeOutputInference property to true.
I still say this is a regression as it is a breaking behavior change. Also, the word Inference is defined as 'a conclusion reached on the basis of evidence and reasoning' Reasoning that the output type should not be EXACTLY as the user specified (the evidence) is a bug in your inferencing logic. If the user was silent on the output type, then I agree with the remainder of the logic.
@StevenBonePgh just wasted hours debugging this for a small console utility that had to set UseWindowsForms to be able to use ResXResourceWriter. All my console logging from System.CommandLine just disappeared and I couldn't understand it until I found this issue. Thanks for the workaround flag, but this default behaviour feels really odd!
@sfoslund can this "purposeful change behavior" be reverted?
I have working on a .NET framework project that uses the new "sdk-style" format and having this break overnight with no code changes (just because I updated Visual Studio and installed a new SDK) is not acceptable. That is a breaking change and usually the rule is not have breaking changes.
The fact that the code change was made before the doc is updated (see https://github.com/MicrosoftDocs/visualstudio-docs/issues/5893) is also bad practice. I had no way to know that change and only found the issue by chance. In my team we have a rule that the doc should be updated before the code is merged. Microsoft should follow such practice.
At the very least it should have only affected project targeting net5-windows and not .NET framework or .NET Core 3.1.
@Kryptos-FR thanks for your feedback. In general, we do allow for breaking changes across major releases (for example, this change was made in the 5.0 major release). cc @wli3 @dsplaisted
Thanks for the feedback folks. See my comment on the docs issue here: https://github.com/dotnet/docs/issues/21952#issuecomment-743406220
We try to avoid breaking changes, but we also try to have a balance between making improvements and avoiding impactful breaking changes. We try to strike a balance though, as most any change we make has the potential to be a breaking change and we want to be able to make improvements in the tools.
Most helpful comment
I still say this is a regression as it is a breaking behavior change. Also, the word Inference is defined as 'a conclusion reached on the basis of evidence and reasoning' Reasoning that the output type should not be EXACTLY as the user specified (the evidence) is a bug in your inferencing logic. If the user was silent on the output type, then I agree with the remainder of the logic.