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

App does not Run as a service in a Windows Container #52416

Closed
Silvenga opened this issue May 6, 2021 · 12 comments · Fixed by #62452
Closed

App does not Run as a service in a Windows Container #52416

Silvenga opened this issue May 6, 2021 · 12 comments · Fixed by #62452
Labels
area-Extensions-Hosting help wanted [up-for-grabs] Good issue for external contributors
Milestone

Comments

@Silvenga
Copy link
Contributor

Silvenga commented May 6, 2021

Description

Using the library Microsoft.Extensions.Hosting.WindowsServices and a builder like the following:

public static IHostBuilder CreateHostBuilder(string[] args)
{
    return Host.CreateDefaultBuilder(args)
        .UseWindowsService(options => options.ServiceName = "foobar")
        .ConfigureServices((hostContext, services) =>
        {
            services.AddHostedService<Agent>();
        });
}

When executing as a service in a Windows Container the following can be observed:

System event logs:

# Get-EventLog -LogName Application -Newest 10 | %{ $_.Message }
A timeout was reached (30000 milliseconds) while waiting for the foobar...
A timeout was reached (30000 milliseconds) while waiting for the foobar...
A timeout was reached (30000 milliseconds) while waiting for the foobar...

Application event logs:

# Get-EventLog -LogName Application -Newest 10 | %{ $_.Message }
Windows Management Instrumentation Service subsystems initialized successfully
Category: Microsoft.Extensions.Hosting.Internal.Host
EventId: 2

Hosting started

Category: Microsoft.Hosting.Lifetime
EventId: 0

Content root path: C:\Windows\system32

Category: Microsoft.Hosting.Lifetime
EventId: 0

Hosting environment: Production

Category: Microsoft.Hosting.Lifetime
EventId: 0

Application started. Press Ctrl+C to shut down.

Category: Microsoft.Extensions.Hosting.Internal.Host
EventId: 1

Hosting starting

Category: Microsoft.Extensions.Hosting.Internal.Host
EventId: 2

Hosting started

Category: Microsoft.Hosting.Lifetime
EventId: 0

Content root path: C:\Windows\system32

Category: Microsoft.Hosting.Lifetime
EventId: 0

Hosting environment: Production

Category: Microsoft.Hosting.Lifetime
EventId: 0

Application started. Press Ctrl+C to shut down.

It appears the the "host" (that the right name?) does not detect it's running as a service. So the app defaults to running as a console app.

Digging deeper, there's a check for "am I a service?" in Microsoft.Extensions.Hosting.WindowsServices.IsWindowsService().

public static bool IsWindowsService()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return false;
}
var parent = Internal.Win32.GetParentProcess();
if (parent == null)
{
return false;
}
return parent.SessionId == 0 && string.Equals("services", parent.ProcessName, StringComparison.OrdinalIgnoreCase);
}

Looking at what this checks:

  • Is Windows? Yes.
  • Is there a parent process? Yes.
  • Is this process named services? Yes.
  • Is this process executing from session 0? No?!?
# pslist.exe -accepteula -t

Name                             Pid Pri Thd  Hnd      VM      WS    Priv
Idle                               0   0   2    0       8       8      60
  System                           4   8  88 3565    3696     144     160
wininit                          276  13   3  167 4194303    7276    1640
  services                       316   9   5  225 4194303    6832    2348
    foobar                       2908   8  10  241 4194303   25380   12688

# Get-Process -id 316 | select name,SessionId

Name     SessionId
----     ---------
services         1

Apparently services doesn't have to run under session 0?

Other information

While "why are running an app in a Windows Container as a Windows Service? Don't do that." would be a completely valid answer.... We are using Windows Containers to provide short lived sandboxes for integration tests - in this case - testing a Wix installer, installing a .NET 5 based Windows Service (self-contained mode). It would be nice if we could continue doing so with .NET 5.

We were historically using TopShelf under .NET Framework, but we migrated to .NET 5. TopShelf does not perform this session 0 check, only the services name check: https://github.com/Topshelf/Topshelf/blob/03bde8963da8c057ffc6df07ef9600ffdf6047c1/src/Topshelf/Runtime/DotNetCore/DotNetCoreHostEnvironment.cs#L57-L80

Therefore, I propose removing that session id check going forward.

@dotnet-issue-labeler dotnet-issue-labeler bot added area-Extensions-Hosting untriaged New issue has not been triaged by the area owner labels May 6, 2021
@ghost
Copy link

ghost commented May 6, 2021

Tagging subscribers to this area: @eerhardt, @maryamariyan
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

Using the library Microsoft.Extensions.Hosting.WindowsServices and a builder like the following:

public static IHostBuilder CreateHostBuilder(string[] args)
{
    return Host.CreateDefaultBuilder(args)
        .UseWindowsService(options => options.ServiceName = "foobar")
        .ConfigureServices((hostContext, services) =>
        {
            services.AddHostedService<Agent>();
        });
}

When executing as a service in a Windows Container the following can be observed:

System event logs:

# Get-EventLog -LogName Application -Newest 10 | %{ $_.Message }
A timeout was reached (30000 milliseconds) while waiting for the foobar...
A timeout was reached (30000 milliseconds) while waiting for the foobar...
A timeout was reached (30000 milliseconds) while waiting for the foobar...

Application event logs:

# Get-EventLog -LogName Application -Newest 10 | %{ $_.Message }
Windows Management Instrumentation Service subsystems initialized successfully
Category: Microsoft.Extensions.Hosting.Internal.Host
EventId: 2

Hosting started

Category: Microsoft.Hosting.Lifetime
EventId: 0

Content root path: C:\Windows\system32

Category: Microsoft.Hosting.Lifetime
EventId: 0

Hosting environment: Production

Category: Microsoft.Hosting.Lifetime
EventId: 0

Application started. Press Ctrl+C to shut down.

Category: Microsoft.Extensions.Hosting.Internal.Host
EventId: 1

Hosting starting

Category: Microsoft.Extensions.Hosting.Internal.Host
EventId: 2

Hosting started

Category: Microsoft.Hosting.Lifetime
EventId: 0

Content root path: C:\Windows\system32

Category: Microsoft.Hosting.Lifetime
EventId: 0

Hosting environment: Production

Category: Microsoft.Hosting.Lifetime
EventId: 0

Application started. Press Ctrl+C to shut down.

It appears the the "host" (that the right name?) does not detect it's running as a service. So the app defaults to running as a console app.

Digging deeper, there's a check for "am I a service?" in Microsoft.Extensions.Hosting.WindowsServices.IsWindowsService().

public static bool IsWindowsService()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return false;
}
var parent = Internal.Win32.GetParentProcess();
if (parent == null)
{
return false;
}
return parent.SessionId == 0 && string.Equals("services", parent.ProcessName, StringComparison.OrdinalIgnoreCase);
}

Looking at what this checks:

  • Is Windows? Yes.
  • Is there a parent process? Yes.
  • Is this process named services? Yes.
  • Is this process executing from session 0? No?!?
# pslist.exe -accepteula -t

Name                             Pid Pri Thd  Hnd      VM      WS    Priv
Idle                               0   0   2    0       8       8      60
  System                           4   8  88 3565    3696     144     160
wininit                          276  13   3  167 4194303    7276    1640
  services                       316   9   5  225 4194303    6832    2348
    foobar                       2908   8  10  241 4194303   25380   12688

# Get-Process -id 316 | select name,SessionId

Name     SessionId
----     ---------
services         1

Apparently services doesn't have to run under session 0?

Other information

While "why are running an app in a Windows Container as a Windows Service? Don't do that." would be a completely valid answer.... We are using Windows Containers to provide short lived sandboxes for integration tests - in this case - testing a Wix installer, installing a .NET 5 based Windows Service (self-contained mode). It would be nice if we could continue doing so with .NET 5.

We were historically using TopShelf under .NET Framework, but we migrated to .NET 5. TopShelf does not perform this session 0 check, only the services name check: https://github.com/Topshelf/Topshelf/blob/03bde8963da8c057ffc6df07ef9600ffdf6047c1/src/Topshelf/Runtime/DotNetCore/DotNetCoreHostEnvironment.cs#L57-L80

Therefore, I propose removing that session id check going forward.

Author: Silvenga
Assignees: -
Labels:

area-Extensions-Hosting, untriaged

Milestone: -

@ghost ghost added this to Untriaged in ML, Extensions, Globalization, etc, POD. May 6, 2021
@maryamariyan maryamariyan removed the untriaged New issue has not been triaged by the area owner label May 6, 2021
@maryamariyan maryamariyan added this to the 6.0.0 milestone May 6, 2021
@ghost ghost moved this from Untriaged to 6.0.0 in ML, Extensions, Globalization, etc, POD. May 6, 2021
@Silvenga
Copy link
Contributor Author

Silvenga commented May 7, 2021

If this proposal is acceptable, I would be happy to make a pr. 😸

@maryamariyan maryamariyan added the needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration label May 7, 2021
@ghost ghost moved this from 6.0.0 to Untriaged in ML, Extensions, Globalization, etc, POD. May 7, 2021
@eerhardt
Copy link
Member

Getting help from @GrabYourPitchforks offline, the following approach was recommended for identifying if the current process is hosted as a windows service:

  1. enumerate the list of running services on the machine;
  2. call QueryServiceStatusEx on each service handle; and
  3. compare the service's PID to the current process's PID.

We can accomplish (1) above with calling:

/// <summary>
/// Gets the services (not including device-driver services) on the local machine.
/// </summary>
/// <returns>Set of service controllers</returns>
public static ServiceController[] GetServices()
{
return GetServices(DefaultMachineName);
}

Then p/invoke into QueryServiceStatusEx to get the service's PID. Comparing the current PID is straight-forward from there.

Moving to 7.0 and marking as "up for grabs".

@eerhardt eerhardt modified the milestones: 6.0.0, 7.0.0 Jul 22, 2021
@eerhardt eerhardt added help wanted [up-for-grabs] Good issue for external contributors and removed needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration labels Jul 22, 2021
@Silvenga
Copy link
Contributor Author

Sounds good! I'll see if I have time to work this in the near future.

@Silvenga
Copy link
Contributor Author

I'm not going to be able to work this any time soon. Needing to install native build sdk's/python, modifying PATH, etc. is a bit heavy for my current setup.

I don't suppose there's a way to work extensions without needing to build the whole runtime?

@eerhardt
Copy link
Member

I don't suppose there's a way to work extensions without needing to build the whole runtime?

Check out @ViktorHofer's response here:
#43118 (comment)

You can build the src project with:

eerhardt@EERHARDT5  C:\git\runtime2   main ≣                                                               [13:30]
❯ dotnet build .\src\libraries\Microsoft.Extensions.Hosting.WindowsServices\src\

and you can run the test project against .NET Framework with:

eerhardt@EERHARDT5  C:\git\runtime2   main ≣                                                               [13:52]
❯ dotnet test .\src\libraries\Microsoft.Extensions.Hosting.WindowsServices\test\ -f net461

So you can do some development without building the whole runtime.

@Silvenga
Copy link
Contributor Author

Oh, thanks - I didn't think about not building the solution (related to the Directory.Build.props I assume?).

My tooling hates this solution - removing the $(NetCoreAppCurrent) resolves most of the problems, I just have to make sure not to commit that. 😹

@wrzucher
Copy link
Contributor

Getting help from @GrabYourPitchforks offline, the following approach was recommended for identifying if the current process is hosted as a windows service:

  1. enumerate the list of running services on the machine;
  2. call QueryServiceStatusEx on each service handle; and
  3. compare the service's PID to the current process's PID.

We can accomplish (1) above with calling:

/// <summary>
/// Gets the services (not including device-driver services) on the local machine.
/// </summary>
/// <returns>Set of service controllers</returns>
public static ServiceController[] GetServices()
{
return GetServices(DefaultMachineName);
}

Then p/invoke into QueryServiceStatusEx to get the service's PID. Comparing the current PID is straight-forward from there.

Moving to 7.0 and marking as "up for grabs".

I am trying to fix this issue but unfortunately, the described solution does not work.

QueryServiceStatusEx return 0 for service. I think, this happens because service is not running yet, it has status StartPending (I checked it) on the moment when we call QueryServiceStatusEx.

You could see my code here: wrzucher@e5ade03

So, I don't know how this problem could be fixed.

@dmytro-pryvedeniuk
Copy link

The reporter proposes to delete session id check. Isn't it enough? Do you think it can cause some side-effects?

@ZeroRyogi
Copy link

ZeroRyogi commented Nov 17, 2021

Please review #51269 (comment) as the cause of the issue may not be related only to the SessionId validation.

After doing a little bit of testing it appears that the cause of the issue is not the failed validation of parent.SessionId == 0 but rather, at this point var parent = Internal.Win32.GetParentProcess(); has already exited so the fields of parent are no longer available. So if we remove the SessionId validation, it will still fail at parent.ProcessName:

ProcessValidation

At this point a work around to avoid the exception when directly executing the .exe would be to doing so from the command line:

runas /user:UserName .\path\to\exe\service.exe

In this case parent would be null, the exe would not execute as windows service (because we are doing it directly) but the exception will be avoided.

So, the deletion of the check may be not enough to solve the issue.

Is it possible that the "service" is not being created and spawned as an actual Windows Service but rather as a stand alone executable which expected behavior is to continue running at infinitum?

@jhennessey
Copy link

jhennessey commented Nov 23, 2021

I hit this issue as well and can confirm that getting rid of the parent.SessionId == 0 check fixed it for me.

eerhardt added a commit to eerhardt/runtime that referenced this issue Dec 6, 2021
Removing SessionId check in IsWindowsService(). This check is not correct when the process is being run in a Windows container. The container will get a different SessionId.

Fix dotnet#52416
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Dec 6, 2021
@eerhardt
Copy link
Member

eerhardt commented Dec 6, 2021

I've created #62452 to remove the check.

ML, Extensions, Globalization, etc, POD. automation moved this from 7.0.0 to Done Dec 9, 2021
eerhardt added a commit that referenced this issue Dec 9, 2021
Removing SessionId check in IsWindowsService(). This check is not correct when the process is being run in a Windows container. The container will get a different SessionId.

Fix #52416
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Dec 9, 2021
@dotnet dotnet locked as resolved and limited conversation to collaborators Jan 8, 2022
eerhardt added a commit to eerhardt/runtime that referenced this issue Aug 18, 2022
Removing SessionId check in IsWindowsService(). This check is not correct when the process is being run in a Windows container. The container will get a different SessionId.

Fix dotnet#52416
carlossanlop pushed a commit that referenced this issue Sep 7, 2022
…74188)

* App does not Run as a service in a Windows Container (#62452)

Removing SessionId check in IsWindowsService(). This check is not correct when the process is being run in a Windows container. The container will get a different SessionId.

Fix #52416

* Add 6.0.x servicing changes
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-Extensions-Hosting help wanted [up-for-grabs] Good issue for external contributors
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants