-
Notifications
You must be signed in to change notification settings - Fork 5k
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
Conversation
Tagging subscribers to this area: @dotnet/area-system-serviceprocess Issue DetailsFix #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. Line 260 in 62b34d9
The Microsoft.Extensions.Hosting library makes it easy to pass no service name to ServiceBase: Line 31 in 62b34d9
runtime/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/WindowsServiceLifetime.cs Lines 35 to 40 in 62b34d9
Line 13 in 62b34d9
The fix as indicated by @ericstj is for ServiceBase to populate its empty ServiceName using the value passed from the SCM in its callback.
|
@dotnet/dnceng Helix error:
|
src/libraries/Common/src/Interop/Windows/Advapi32/Interop.ServiceProcessOptions.cs
Outdated
Show resolved
Hide resolved
...Controller/tests/System.ServiceProcess.ServiceController.TestService/TestServiceInstaller.cs
Outdated
Show resolved
Hide resolved
@danmoseley the SSL problem is already fixed in Arcade but runtime hasn't consumed the .NET 7.0 Arcade yet: |
|
||
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}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This ends up calling
runtime/src/libraries/System.Private.CoreLib/src/System/ComponentModel/Win32Exception.cs
Line 46 in 441271c
public Win32Exception(string? message) : this(Marshal.GetLastPInvokeError(), message) |
There was a problem hiding this comment.
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}");
There was a problem hiding this comment.
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}");
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder what would be an appropriate new API to make this (seemingly reasonable) pattern easier.
throw Win32Exception.WithMessagePrefix($"Could not delete service '{ServiceName}.');
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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}");
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
created #67872
Any other feedback? |
src/libraries/Common/src/Interop/Windows/Advapi32/Interop.ServiceProcessOptions.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs
Outdated
Show resolved
Hide resolved
Interpreter failure is #67880 |
There was a problem hiding this 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!
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.
runtime/src/libraries/System.ServiceProcess.ServiceController/src/System/ServiceProcess/ServiceBase.cs
Line 260 in 62b34d9
The Microsoft.Extensions.Hosting library makes it easy to pass no service name to ServiceBase:
runtime/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/WindowsServiceLifetimeHostBuilderExtensions.cs
Line 31 in 62b34d9
runtime/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/WindowsServiceLifetime.cs
Lines 35 to 40 in 62b34d9
runtime/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/WindowsServiceLifetimeOptions.cs
Line 13 in 62b34d9
The fix as indicated by @ericstj is for ServiceBase to populate its empty ServiceName using the value passed from the SCM in its callback.