Skip to content

Fix exception and enable event logging when service has no name #67827

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

Merged
merged 13 commits into from
Apr 12, 2022

Conversation

danmoseley
Copy link
Member

Fix #62923

While all Windows services must have a non empty name, if you're starting a service in its own process, such that it's unambiguous which service you want to start, you do not need to pass that name. Therefore ServiceBase does not require you set ServiceName. However if ServiceBase is configured to write to the event log, which it is by default, it will fail because it uses the ServiceName as the source name. The result is an exception, and no expected messages, in the event log.

The Microsoft.Extensions.Hosting library makes it easy to pass no service name to ServiceBase:


if (windowsServiceOptionsAccessor == null)
{
throw new ArgumentNullException(nameof(windowsServiceOptionsAccessor));
}
_hostOptions = optionsAccessor.Value;
ServiceName = windowsServiceOptionsAccessor.Value.ServiceName;

The fix as indicated by @ericstj is for ServiceBase to populate its empty ServiceName using the value passed from the SCM in its callback.

  • Increase timeouts that are sporadically being hit per test history data
  • Add logging to TestServiceInstaller
  • Update ServiceTypeOptions to include others present in winnt.h, but commented out
  • Rename SERVICE_ constants to match winnt.h (it's less confusing)
  • Remove defunct comment
  • Add failing test
  • Remove event log noise
  • Make test pass

@ghost
Copy link

ghost commented Apr 11, 2022

Tagging subscribers to this area: @dotnet/area-system-serviceprocess
See info in area-owners.md if you want to be subscribed.

Issue Details

Fix #62923

While all Windows services must have a non empty name, if you're starting a service in its own process, such that it's unambiguous which service you want to start, you do not need to pass that name. Therefore ServiceBase does not require you set ServiceName. However if ServiceBase is configured to write to the event log, which it is by default, it will fail because it uses the ServiceName as the source name. The result is an exception, and no expected messages, in the event log.

The Microsoft.Extensions.Hosting library makes it easy to pass no service name to ServiceBase:


if (windowsServiceOptionsAccessor == null)
{
throw new ArgumentNullException(nameof(windowsServiceOptionsAccessor));
}
_hostOptions = optionsAccessor.Value;
ServiceName = windowsServiceOptionsAccessor.Value.ServiceName;

The fix as indicated by @ericstj is for ServiceBase to populate its empty ServiceName using the value passed from the SCM in its callback.

  • Increase timeouts that are sporadically being hit per test history data
  • Add logging to TestServiceInstaller
  • Update ServiceTypeOptions to include others present in winnt.h, but commented out
  • Rename SERVICE_ constants to match winnt.h (it's less confusing)
  • Remove defunct comment
  • Add failing test
  • Remove event log noise
  • Make test pass
Author: danmoseley
Assignees: -
Labels:

area-System.ServiceProcess

Milestone: -

@danmoseley
Copy link
Member Author

@dotnet/dnceng Helix error:


.packages/microsoft.dotnet.helix.sdk/7.0.0-beta.22179.1/tools/xharness-runner/XHarnessRunner.targets(122,5): error MSB4018: The "CreateXHarnessAppleWorkItems" task failed unexpectedly.
System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
 ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: RevocationStatusUnknown

@premun
Copy link
Member

premun commented Apr 11, 2022

@danmoseley the SSL problem is already fixed in Arcade but runtime hasn't consumed the .NET 7.0 Arcade yet:
#67349


TestService.DebugTrace("TestServiceInstaller: instructing ServiceController to Delete service " + ServiceName);
if (!Interop.Advapi32.DeleteService(serviceHandle))
{
throw new Win32Exception($"Could not delete service '{ServiceName}'");
string errorMessage = new Win32Exception().Message;
throw new Win32Exception($"Could not delete service '{ServiceName}'. {errorMessage}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ends up calling

public Win32Exception(string? message) : this(Marshal.GetLastPInvokeError(), message)
that will read the last PInvoke error that can be corrupted by now.

Copy link
Member Author

@danmoseley danmoseley Apr 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh - Win32Exception is a bit of a trap. So is this the correct pattern to achieve this?

Win32Exception ex = new Win32Exception(); 
throw new Win32Exception(ex.NativeErrorCode, $"Could not delete service '{ServiceName}'. {ex.Message}");

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is correct. I think writing it like this looks better:

int errorCode = Marshal.GetLastWin32Error();
string errorMessage = new Win32Exception(errorCode).Message;
throw new Win32Exception(errorCode, $"Could not delete service '{ServiceName}'. {errorMessage}");

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar
https://github.com/danmoseley/runtime/blob/a431bbc76dd80cb6f960725547ad3ccb5c9cf679/src/libraries/System.Drawing.Common/src/misc/DbgUtil.cs#L57-L59

I wonder what would be an appropriate new API to make this (seemingly reasonable) pattern easier.

throw Win32Exception.WithMessagePrefix($"Could not delete service '{ServiceName}.');

?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the above suggestion would still run string formatting machinery before reading the last error.

Copy link
Member

@jkotas jkotas Apr 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an API that forces you to call Marshal.GetLastWin32Error yourself and gives you the error message for the OS error code would be more flexible.

I think the current behavior of Win32Exception constructors that call Marshal.GetLastWin32Error for you is very error prone.

Copy link
Member Author

@danmoseley danmoseley Apr 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, so eg this? (assuming I want the code in the exception object too)

int errorCode = Marshal.GetLastPInvokeError();
string errorMessage = Marshal.GetMessageForPInvokeError(errorCode);
throw new Win32Exception(errorCode, $"Could not delete service '{ServiceName}'. {errorMessage}");

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. (The ServiceController project targets netstandard, so you won't be able to use this here actually.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created #67872

@danmoseley
Copy link
Member Author

Any other feedback?

@danmoseley
Copy link
Member Author

Interpreter failure is #67880

@danmoseley danmoseley merged commit 991c4fe into dotnet:main Apr 12, 2022
@danmoseley danmoseley deleted the svc.bugs branch April 12, 2022 02:25
Copy link
Member

@ericstj ericstj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you for fixing this!

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

Successfully merging this pull request may close these issues.

First chance exceptions and missing logging when a service isn't given a service name
4 participants