WindowsAppSDK icon indicating copy to clipboard operation
WindowsAppSDK copied to clipboard

PublishSingleFile with WindowsAppSDKSelfContained produces non runnable application

Open Balkoth opened this issue 2 years ago • 5 comments

Describe the bug

If PublishSingleFile and WindowsAppSDKSelfContained are set to true while publishing, an exe is produced which crashes on startup.

Steps to reproduce the bug

  1. Add <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained> to your project file
  2. Add <PublishSingleFile>true</PublishSingleFile> to your publish file
  3. Publish the application
  4. Try to run and see it crashing

Expected behavior

No response

Screenshots

No response

NuGet package version

No response

Packaging type

Unpackaged

Windows version

Windows 10 version 21H2 (19044, November 2021 Update)

IDE

Visual Studio 2022

Additional context

NuGet package version is 1.1.0

Balkoth avatar Jun 08 '22 08:06 Balkoth

Was able to get an application event trace containing:

Application: CsApp2.exe
CoreCLR Version: 6.0.522.21309
.NET Version: 6.0.5
Description: The process was terminated due to an unhandled exception.
Exception Info: System.TypeInitializationException: The type initializer for '<Module>' threw an exception.
 ---> System.DllNotFoundException: Dll was not found.
   at Microsoft.Windows.Foundation.UndockedRegFreeWinRTCS.NativeMethods.WindowsAppRuntime_EnsureIsLoaded()
   at Microsoft.Windows.Foundation.UndockedRegFreeWinRTCS.AutoInitialize.AccessWindowsAppSDK()
   at .cctor()
   --- End of inner exception stack trace ---

The code for that method is here: WindowsAppSDK/UndockedRegFreeWinRT-AutoInitializer.cs at main · microsoft/WindowsAppSDK (github.com)

    [DllImport("Microsoft.WindowsAppRuntime.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
    internal static extern int WindowsAppRuntime_EnsureIsLoaded();

My relatively uninformed hunch is that the DllImport is not playing nicely with the single file publishing.

DefaultRyan avatar Jun 10 '22 00:06 DefaultRyan

I've created an internal task to track this: https://task.ms/39946393 and assigning to @DrusTheAxe

DefaultRyan avatar Jun 10 '22 00:06 DefaultRyan

Do you have Microsoft.WindowsAppRuntime.dll as a file along with your "single file"?

Does your project define <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>?

Do you set the property IncludeNativeLibrariesForSelfExtract?


PublishSingleFile has variation of behavior. Depending how you use it you may or may not run into problems

https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file/overview#output-differences-from-net-3x

Bundling all application-dependent files into a single binary provides an application developer with the attractive option to deploy and distribute the application as a single file. This deployment model has been available since .NET Core 3.0 and has been enhanced in .NET 5. Previously in .NET Core 3.0, when a user runs your single-file app, .NET Core host first extracts all files to a directory before running the application. .NET 5 improves this experience by directly running the code without the need to extract the files from the app.

Which would be problematic as [DllImport(Microsoft.WindowsAppRuntime.dll,...)] == LoadLibrary(Microsoft.WindowsAppRuntime.dll) but .NET 5's not quite that simple as noted further down

Output differences from .NET 3.x In .NET Core 3.x, publishing as a single file produced exactly one file, consisting of the app itself, dependencies, and any other files in the folder during publish. When the app starts, the single file app was extracted to a folder and run from there. Starting with .NET 5, only managed DLLs are bundled with the app into a single executable. When the app starts, the managed DLLs are extracted and loaded in memory, avoiding the extraction to a folder. On Windows, this means that the managed binaries are embedded in the single-file bundle, but the native binaries of the core runtime itself are separate files. To embed those files for extraction and get exactly one output file, like in .NET Core 3.x, set the property IncludeNativeLibrariesForSelfExtract to true. For more information about extraction, see Including native libraries.

More info at that link at the end

https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file/overview#including-native-libraries

Single-file doesn't bundle native libraries by default. On Linux, we prelink the runtime into the bundle and only application native libraries are deployed to the same directory as the single-file app. On Windows, we prelink only the hosting code and both the runtime and application native libraries are deployed to the same directory as the single-file app. This is to ensure a good debugging experience, which requires native files to be excluded from the single file.

Starting with .NET 6 the runtime is prelinked into the bundle on all platforms.

There is an option to set a flag, IncludeNativeLibrariesForSelfExtract, to include native libraries in the single file bundle, but these files will be extracted to a directory in the client machine when the single file application is run.

Specifying IncludeAllContentForSelfExtract will extract all files (even the managed assemblies) before running the executable. This preserves the original .NET Core single-file deployment behavior.

DrusTheAxe avatar Jun 10 '22 16:06 DrusTheAxe

I set IncludeNativeLibrariesForSelfExtract in my project yes.

Balkoth avatar Jun 10 '22 16:06 Balkoth

As of 1.1.2 an application published like this still crashes on startup:

<Project>
  <PropertyGroup>
    <PublishProtocol>FileSystem</PublishProtocol>
    <Platform>x64</Platform>
    <RuntimeIdentifier>win10-x64</RuntimeIdentifier>
    <PublishDir>bin\win10-x64\publish\win10-x64\</PublishDir>
    <SelfContained>true</SelfContained>
    <PublishReadyToRun>false</PublishReadyToRun>
    <PublishSingleFile>true</PublishSingleFile>
    <Configuration>Release</Configuration>
    <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
    <PublishTrimmed>false</PublishTrimmed>
    <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
    <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
  </PropertyGroup>
</Project>
Application: TestWindowsAppSDKSelfContained.exe
CoreCLR Version: 6.0.622.26707
.NET Version: 6.0.6
Description: The process was terminated due to an unhandled exception.
Exception Info: System.TypeInitializationException: The type initializer for 'WinRT.ActivationFactory`1' threw an exception.
 ---> System.ArgumentNullException: Value cannot be null. (Parameter 'path1')
   at System.IO.Path.Combine(String path1, String path2)
   at WinRT.DllModule.TryCreate(String fileName, DllModule& module)
   at WinRT.DllModule.TryLoad(String fileName, DllModule& module)
   at WinRT.BaseActivationFactory..ctor(String typeNamespace, String typeFullName)
   at WinRT.ActivationFactory`1..ctor()
   at WinRT.ActivationFactory`1..cctor()
   --- End of inner exception stack trace ---
   at WinRT.ActivationFactory`1.As(Guid iid)
   at Microsoft.UI.Xaml.Application.Make___objRef_global__Microsoft_UI_Xaml_IApplicationStatics()
   at Microsoft.UI.Xaml.Application.get__objRef_global__Microsoft_UI_Xaml_IApplicationStatics()
   at Microsoft.UI.Xaml.Application.Start(ApplicationInitializationCallback callback)
   at TestWindowsAppSDKSelfContained.Program.Main(String[] args) in C:\Work\VisualStudioProjects\TestWindowsAppSDKSelfContained\obj\x64\Release\net6.0-windows10.0.19041.0\win10-x64\App.g.i.cs:line 31

Balkoth avatar Jul 08 '22 06:07 Balkoth

Same issue for 1.1.5

<PropertyGroup>
  <OutputType>WinExe</OutputType>
  <TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
  <TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
  <RootNamespace>TestApp</RootNamespace>
  <ApplicationManifest>app.manifest</ApplicationManifest>
  <Platforms>x64</Platforms>
  <RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
  <PublishProfile>win10-$(Platform).pubxml</PublishProfile>
  <UseWinUI>true</UseWinUI>
  <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
  <WindowsPackageType>None</WindowsPackageType>
  <SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
  <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
  <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
</PropertyGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.5" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.755" />

Exception in Windows Event Viewer from .NET Runtime

Application: TestApp.exe
CoreCLR Version: 6.0.1022.47605
.NET Version: 6.0.10
Description: The process was terminated due to an unhandled exception.
Exception Info: System.TypeInitializationException: The type initializer for 'WinRT.ActivationFactory`1' threw an exception.
 ---> System.ArgumentNullException: Value cannot be null. (Parameter 'path1')
   at System.IO.Path.Combine(String path1, String path2)
   at WinRT.DllModule.TryCreate(String fileName, DllModule& module)
   at WinRT.DllModule.TryLoad(String fileName, DllModule& module)
   at WinRT.BaseActivationFactory..ctor(String typeNamespace, String typeFullName)
   at WinRT.ActivationFactory`1..ctor()
   at WinRT.ActivationFactory`1..cctor()
   --- End of inner exception stack trace ---
   at WinRT.ActivationFactory`1.As(Guid iid)
   at Microsoft.UI.Xaml.Application.Make___objRef_global__Microsoft_UI_Xaml_IApplicationStatics()
   at Microsoft.UI.Xaml.Application.get__objRef_global__Microsoft_UI_Xaml_IApplicationStatics()
   at Microsoft.UI.Xaml.Application.Start(ApplicationInitializationCallback callback)
   at TestApp.Program.Main(String[] args) in C:\Source\TestApp\TestApp\obj\x64\Release\net6.0-windows10.0.19041.0\win10-x64\App.g.i.cs:line 31

If I remove <WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained> then everything works fine.

Windows 11 (OS Build 22000.1098)

aepot avatar Nov 01 '22 11:11 aepot

Same problem here, same configs as above

DooblyNoobly avatar Nov 24 '22 07:11 DooblyNoobly

Not being supported is one thing, but then it should error out when trying to compile the app, listing the incompatible options. Producing a non runnable application is the worst that can happen.

Balkoth avatar Nov 24 '22 07:11 Balkoth

I've just removed Self Contained Windows App SDK and now I package the WindowsAppRuntimeInstall.exe separately with my inno installer that I send to the clients. I run the WindowsAppRuntimeInstall.exe on the install setup using the --quiet and --force parameters. Works well for me. Now I don't have to worry about trying to add it to single file on publish.

Reference: https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/deploy-unpackaged-apps#deploy-windows-app-sdk-runtime

DooblyNoobly avatar Nov 25 '22 05:11 DooblyNoobly

Be carefull if your app has to be run under another user, because this works with WindowsAppSDKSelfContained, but breaks when using the installed runtime.

Balkoth avatar Nov 25 '22 06:11 Balkoth

I have the same issue. 🐈‍⬛

farag2 avatar Jan 11 '23 20:01 farag2

Same issue :(

kmanev073 avatar Mar 24 '23 09:03 kmanev073

Is this supposed to work with the latest version of the SDK - I just tried with a blank project and it's the same as the issue reported

nickrandolph avatar Apr 29 '23 18:04 nickrandolph

Any updates on the topic?

kmanev073 avatar Aug 28 '23 10:08 kmanev073

@DrusTheAxe This appears to be a URFW issue, am hitting the same problem here.

Particularly, it doesn't appear URFW understands that in single-file scenarios, the single file host (my.exe) is in one directory and everything else is extracted into another directory. (.NET only appears to wire up enough hooks for DllImport scenarios to work and leaves the rest to the developer.)

This particular URFW line has me curious: https://github.com/microsoft/WindowsAppSDK/blob/main/dev/UndockedRegFreeWinRT/catalog.cpp#L48-L50

I was going to fix up the search directories myself but it's not clear how this is supposed to this piece of code is meant to behave; I'm seeing URFW pass a lpFileName of Microsoft.UI.Xaml.dll whilst the LoadLibraryEx documentation states:

If this value is used and lpFileName specifies a relative path, the behavior is undefined.

riverar avatar Aug 30 '23 22:08 riverar

Same issue :(

@DrusTheAxe - can you please recommend any temporary work-around to unblock this scenario?

Perhaps a custom build target to copy dependencies or something?

Thanks!

robertev-alchemy avatar Sep 04 '23 12:09 robertev-alchemy

doesn't appear URFW understands that in single-file scenarios, the single file host (my.exe) is in one directory and everything else is extracted into another directory

URFW expects DLLs+EXE are colocated. It's not specialized for DotNet (or other runtimes).

I can think of several options but none particularly good. Need to investigate a little further and discuss with some folks. I suspect we can do something.

@Scottj1s @bpulliam let's talk offline.

This particular URFW line has me curious:...

Why? Is it the LOAD_WITH_ALTERED_SEARCH_PATH?

That's standard fare how COM loads DLLs (even since Windows NT 4.0).

I'm seeing URFW pass a lpFileName of Microsoft.UI.Xaml.dll whilst the LoadLibraryEx documentation states:

Hmmm. That should be an absolute filename (to avoid ambiguities). Thanks for pointing this out!

But even if that's changed to an absolute filename that won't fix your single-file .NET scenario, right?

DrusTheAxe avatar Sep 04 '23 20:09 DrusTheAxe

But even if that's changed to an absolute filename that won't fix your single-file .NET scenario, right?

Agreed.

I can think of several options but none particularly good. Need to investigate a little further and discuss with some folks. I suspect we can do something.

I think ensuring URFW attempts to find its libraries via search path traversal would allow .NET developers to add the extracted directory to the search path. (Perhaps a stretch goal could be to do that for developers via the .NET bootstrap.)

We should also consider adding a note somewhere that dissuades folks from trying single-file publishing as it's known to be completely unsupported/incompatible. Would save some folks a few hours/days of digging!

riverar avatar Sep 04 '23 21:09 riverar

I think ensuring URFW attempts to find its libraries via search path traversal

What search path?

URFW is native code. One question is what .NET needs, then if/how to teach URFW where to look in ways that also work for .NET (w/o negatively impacting others, and w/o 1off solutions for every language and framework).

And of course there's the key question: how's this work for OS reg-free WinRT? Is that any different?

Of course if the OS RegFree WinRT is no help we have the merit of consistency, but also potential opportunity to do something beyond the OS.

Following up but this will likely involve key experts across a few teams and some vacations and other scheduling fine print so please be patient on this topic.

Perhaps a stretch goal could be to do that for developers via the .NET bootstrap

Perhaps. I need to talk to some folks more deeply immersed in that.

We should also consider adding a note somewhere that dissuades folks from trying single-file publishing as it's known to be completely unsupported/incompatible

Is it? I've heard it mentioned but I haven't kept track of its current status. Something else to check with folks.

DrusTheAxe avatar Sep 05 '23 04:09 DrusTheAxe

Some additional dev notes/thoughts as I poke around more:

It looks like the issue is ultimately a result of OS SxS not performing a traditional LoadLibrary-like DLL search for the components in the app's fusion manifest. That is, SxS sees <asmv3:file name="CoreMessagingXP.dll">...</asmv3:fle> in the manifest, attempts to load the file adjacent to the executing image, fails, and gives up.

We wouldn't normally need the manifest but Windows App SDK does due to some component naming decisions made in the past. (Perhaps activation factory stubs following the traditional pattern could help here?)

Enterprising developers wanting to bounce through a loader, to externalize the fusion manifest at runtime, may discover URFW doesn't support loose manifests. But perhaps putting a new activation context on the stack with an auto-generated manifest could work here? (Might be tricky with threading and WASDK/WinUI may have some assumptions built around the active context...)

Anyway, thanks @DrusTheAxe for looking into this. We will all wait (impatiently) for you to return with more news!

riverar avatar Sep 05 '23 16:09 riverar

I have the problem that a build exe works fine, but I can't run it from visual studio until my custom dll is in system32! Next to the exe is not enough..

tpoint75 avatar Nov 02 '23 16:11 tpoint75

It's been 6 months since the issue was opened. Any updates on it?

akhanalcs avatar Dec 04 '23 17:12 akhanalcs

Thanks for your patience folks. Support is in the works!

Scottj1s avatar Jan 06 '24 01:01 Scottj1s

PublishSingleFile will be supported in Windows App SDK 1.5

Scottj1s avatar Jan 26 '24 23:01 Scottj1s

Closing as completed with 1.5.240205001-preview1. If we've missed any edge cases, please enter new issues.

Scottj1s avatar Feb 06 '24 22:02 Scottj1s