Runtime: UseWindowsService doesn't work correctly with single file executables

Created on 7 Aug 2019  路  16Comments  路  Source: dotnet/runtime

Describe the bug

.UseWindowsService() sets the base path to AppContext.BaseDirectory which ends up being something like C:\Windows\Temp\.net\FileUploader\blc0j22k.v0p when using a single file exe and running as a service. This prevents the appsettings.json files from being loaded if they're saved alongside the exe which I've been told is an acceptable deployment scenario.

To Reproduce

Steps to reproduce the behavior:

  1. Using version 3.0.0-preview7.19362.4 of package Microsoft.Extensions.Hosting.WindowsServices
  2. Create an appsettings.json with a setting in it
  3. In .ConfigureServices(ctx, services) call ctx.Configuration.GetValue<string>("SomeValue")
  4. Publish the project using dotnet publish --configuration Release --output artifacts /p:PublishSingleFile=true /p:PublishTrimmed=true
  5. Install the service & run it
  6. The value in appsettings.json will not be loaded

Expected behavior

The appsettings.json stored alongside the exe should be loaded when running a single file exe as a service.

Current workaround

I originally was using the following code before I added in the Microsoft.Extensions.Hosting.WindowsServices package which I got from the asp.net core 2.2 docs:

var isService = !(Debugger.IsAttached || args.Contains("--console"));

if (isService)
{
    using var process = Process.GetCurrentProcess();
    var pathToExe = process.MainModule.FileName;
    var pathToContentRoot = Path.GetDirectoryName(pathToExe);
    Directory.SetCurrentDirectory(pathToContentRoot);
}

After adding this package I modified the code like so:

 var builder = Host.CreateDefaultBuilder()
     .UseWindowsService()
     .ConfigureServices((ctx, services) =>
     {
     });

+if (WindowsServiceHelpers.IsWindowsService())
+{
+    using var process = Process.GetCurrentProcess();
+    var pathToExe = process.MainModule.FileName;
+    var pathToContentRoot = Path.GetDirectoryName(pathToExe);
+
+    builder.UseContentRoot(pathToContentRoot);
+}
 else
 {
     builder.UseConsoleLifetime();
 }

 var host = builder.Build();

Depending on how you publish the application this might not be a bug but rather a documentation note. It seems worth mentioning though.

area-Extensions-Hosting

Most helpful comment

I'd definitely prefer solving this via https://github.com/dotnet/core-setup/issues/7491

Yes @dasMulli, the intention in the next version is for the AppContext.BaseDirectory to point to the apphost directory, as noted in the design.

Given this, I'm going to mark this as External but leave it open for us to verify as 5.0 develops and we have the new single-file bundling logic to test out.

All 16 comments

Describe the bug

The previous workaround does not work anymore with 3.0.100 of .NET Core. Now if we execute the application will work as intended, even without the workaround, but if we use it as a service it always breaks when starting the service.

To Reproduce

Steps to reproduce the behavior:

  1. Create a new ASP.NET Core Web Application from Visual Studio
  2. Chose API
  3. Install package 'Microsoft.Extensions.Hosting.WindowsServices' version 3.0.0
  4. Add UseWindowsService() to the HostBuilder on Program.cs
  5. On application root folder execute dotnet publish -o .\publish -r win-x64 --self-contained -c Release -p:PublishSingleFile=True -p:PublishTrimmed=True
  6. Open a Powershell with admin permissions
  7. Execute New-Service -Name MyTestService -BinaryPathName {full-path-to-your-executable}
  8. If everything worked as intended now we can start the service using the same PowerShell Start-Service -Name MyTestService
  9. An error will appear Start-Service : Service 'MyTestService (MyTestService )' cannot be started due to the following error: Cannot start service MyTestService on computer '.'.
  10. Open EventViewer and an error should be there similar to Exception Info: System.IO.FileNotFoundException: The configuration file 'appSettings.json' was not found and is not optional. The physical path is 'C:\WINDOWS\TEMP\.net\WebApplication2\hp1o1feq.wdy\appSettings.json'.

Expected behavior

The appsettings.json stored alongside the exe should be loaded when running a single file exe as a service.

Screenshots

EventViewer

event-viewer

Additional context

位 dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   3.0.100
 Commit:    04339c3a26

Runtime Environment:
 OS Name:     Windows
 OS Version:  6.3.9600
 OS Platform: Windows
 RID:         win81-x64
 Base Path:   C:\Program Files\dotnet\sdk\3.0.100\

Host (useful for support):
  Version: 3.0.0
  Commit:  7d57652f33

.NET Core SDKs installed:
  2.1.801 [C:\Program Files\dotnet\sdk]
  2.2.401 [C:\Program Files\dotnet\sdk]
  3.0.100 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Oof, the issue is that AppContext.BaseDirectory isn't the original directory of the single-file exe, it's the directory into which the content was extracted. We intentionally don't embed the content files in single-file scenarios because for non-Windows Services we want them to stay next to the exe instead of being embedded in it.

We need to think a bit about this scenario. Thoughts @davidfowl ?

@vitek-karas @elinor-fung Is there a way to detect when we've been "expanded" from a single-file exe and to get back the path of the original single-file exe? Perhaps using AppContext.GetData?

If that doesn't work, we're going to have to make sure this design works with the newer single-file plan in 5.0.

@swaroop-sridhar

@anurse To get the directory containing the actual single-file app, you can use: Process.GetCurrentProcess().MainModule.FileName or use platform-specific API such as pinvoke into GetModuleFileNameW(Null, , )

https://github.com/dotnet/core-setup/issues/7491

Is there also a way to detect when we're launched as a single-file app? Or would it be reasonable to use Process.GetCurrentProcess().MainModule.FileName as a direct replacement for AppContext.BaseDirectory in all cases? I'd expect the Process API to be incorrect if we are launched from dotnet.exe, right?

Linking https://github.com/dotnet/core-setup/issues/7491 for transparency.

If the runtime adopts the behavior described in the design document this issue could likely be closed without action..

We propose that AppContext.BaseDirectory should always be set to the directory where the AppHost bundle resides. This scheme doesn't provide an obvious mechanism to access the contents of the extraction directory -- by design. The recommended method for accessing content files from the bundle are:

  • Do not bundle application data files into the single-exe; instead them next to the bundle. This way, the application binary is a single-file, but not the whole application.
  • Embed data files as managed resources into application binary, and access them via resource management APIs.

Yes @dasMulli, the intention in the next version is for the AppContext.BaseDirectory to point to the apphost directory, as noted in the design.

@anurse, I agree that an API to detect whether running from a single-file is useful in certain cases. There's a proposal in the design to add this API in the next version.

I'd definitely prefer solving this via https://github.com/dotnet/core-setup/issues/7491

Yes @dasMulli, the intention in the next version is for the AppContext.BaseDirectory to point to the apphost directory, as noted in the design.

Given this, I'm going to mark this as External but leave it open for us to verify as 5.0 develops and we have the new single-file bundling logic to test out.

I think it would be very good if we can set the content root from application.

Something like this :

var builder = Host.CreateDefaultBuilder()
.UseWindowsService(settings =>                                   
{
    settings.ContentRoot = Directory.GetCurrentDirectory();
});

@BurhanEyimaya You can, just need to make sure to call is after UserWindowsService AFAIK.
https://github.com/dotnet/extensions/blob/275e691f7e575f208290d1cbb8cb450f4a3a85d6/src/Hosting/Hosting/src/HostingHostBuilderExtensions.cs#L35-L41

Just fyi, Directory.GetCurrentDirectory() will point to %WINDIR%\system32 for windows services so that's why it would be good to have a generic API that tells you the location of whatever is the entry point (being entry point dll file for dotnet path/to/foo.dll or the main module foo.exe for app host with or without single file publishing).

Not blocking release of 5.0 product.

@xt0rted could you please check if this still repros on 5.0? (Dupe of https://github.com/dotnet/runtime/issues/3704)

Closing issue as resolved on .NET 5.0.

@xt0rted if this gets repro on 5.0 we could reopen it again.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

omariom picture omariom  路  3Comments

nalywa picture nalywa  路  3Comments

bencz picture bencz  路  3Comments

jamesqo picture jamesqo  路  3Comments

sahithreddyk picture sahithreddyk  路  3Comments