Skip to content

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

Closed

Description

@Silvenga

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.

Activity

  1. ghost added
    untriagedNew issue has not been triaged by the area owner
    on May 6, 2021
  2. removed
    untriagedNew issue has not been triaged by the area owner
    on May 6, 2021
  3. added this to the 6.0.0 milestone on May 6, 2021
  4. Silvenga commented on May 7, 2021

    @Silvenga
    ContributorAuthor

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

  5. added
    needs-further-triageIssue has been initially triaged, but needs deeper consideration or reconsideration
    on May 7, 2021
  6. eerhardt commented on Jul 22, 2021

    @eerhardt
    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".

  7. modified the milestones: 6.0.0, 7.0.0 on Jul 22, 2021
  8. added
    help wanted[up-for-grabs] Good issue for external contributors
    and removed
    needs-further-triageIssue has been initially triaged, but needs deeper consideration or reconsideration
    on Jul 22, 2021
  9. Silvenga commented on Jul 22, 2021

    @Silvenga
    ContributorAuthor

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

  10. Silvenga commented on Jul 26, 2021

    @Silvenga
    ContributorAuthor

    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?

  11. eerhardt commented on Jul 26, 2021

    @eerhardt
    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.

  12. Silvenga commented on Jul 26, 2021

    @Silvenga
    ContributorAuthor

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

  13. wrzucher commented on Aug 20, 2021

    @wrzucher
    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.

  14. dmytro-pryvedeniuk commented on Nov 15, 2021

    @dmytro-pryvedeniuk

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

  15. zryogi commented on Nov 17, 2021

    @zryogi

    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?

  16. jhennessey commented on Nov 23, 2021

    @jhennessey

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

  17. added a commit that references this issue on Dec 6, 2021
    dc2ec78
  18. ghost added
    in-prThere is an active PR which will close this issue when it is merged
    on Dec 6, 2021
  19. eerhardt commented on Dec 6, 2021

    @eerhardt
    Member

    I've created #62452 to remove the check.

  20. added a commit that references this issue on Dec 9, 2021
    36bf84f
  21. ghost removed
    in-prThere is an active PR which will close this issue when it is merged
    on Dec 9, 2021
  22. ghost locked as resolved and limited conversation to collaborators on Jan 8, 2022
  23. added a commit that references this issue on Aug 18, 2022
    d70a8d7
  24. added a commit that references this issue on Sep 7, 2022
    84b31e8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions