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

UseWindowsService doesn't work correctly with single file executables #36065

Closed
xt0rted opened this issue Aug 6, 2019 · 16 comments
Closed

UseWindowsService doesn't work correctly with single file executables #36065

xt0rted opened this issue Aug 6, 2019 · 16 comments

Comments

@xt0rted
Copy link
Contributor

xt0rted commented Aug 6, 2019

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.

@liciniomendes
Copy link

liciniomendes commented Oct 17, 2019

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]

@analogrelay
Copy link
Contributor

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 ?

@analogrelay
Copy link
Contributor

analogrelay commented Nov 15, 2019

@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?

@analogrelay
Copy link
Contributor

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.

@vitek-karas
Copy link
Member

@swaroop-sridhar

@swaroop-sridhar
Copy link
Contributor

@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

@analogrelay
Copy link
Contributor

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?

@dasMulli
Copy link
Contributor

dasMulli commented Dec 8, 2019

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.

@swaroop-sridhar
Copy link
Contributor

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.

@analogrelay
Copy link
Contributor

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.

@BurhanEyimaya
Copy link

BurhanEyimaya commented Jan 30, 2020

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();
});

@bddckr
Copy link

bddckr commented Jan 30, 2020

@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

@dasMulli
Copy link
Contributor

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

@maryamariyan
Copy link
Member

Not blocking release of 5.0 product.

@maryamariyan
Copy link
Member

@xt0rted could you please check if this still repros on 5.0? (Dupe of #3704)

@maryamariyan
Copy link
Member

Closing issue as resolved on .NET 5.0.

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

@ghost ghost locked as resolved and limited conversation to collaborators Dec 12, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests