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

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.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 6
  • Comments: 16 (13 by maintainers)

Most upvoted comments

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.

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).

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.

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.