-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
See #62579 (comment)
The default behavior for ServiceBase when a ServiceName isn't specified is to use an empty string. Per https://docs.microsoft.com/en-us/windows/win32/api/winsvc/ns-winsvc-service_table_entrya
If the service is installed with the SERVICE_WIN32_OWN_PROCESS service type, this member is ignored, but cannot be NULL. This member can be an empty string ("").
The problem, however, is that later on we use ServiceName to construct a log:
Lines 238 to 241 in 1a83f61
| _eventLog = new EventLog("Application") | |
| { | |
| Source = ServiceName | |
| }; |
And EventLog will throw for an empty Source:
runtime/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/EventLogInternal.cs
Lines 1300 to 1301 in 1a83f61
| if (Source.Length == 0) | |
| throw new ArgumentException(SR.NeedSourceToWrite); |
So in this common scenario all of these calls to WriteEventLog result in first chance exceptions, and nothing is logged:
Lines 944 to 958 in 1a83f61
| private void WriteLogEntry(string message, EventLogEntryType type = EventLogEntryType.Information) | |
| { | |
| // EventLog failures shouldn't affect the service operation | |
| try | |
| { | |
| if (AutoLog) | |
| { | |
| EventLog.WriteEntry(message, type); | |
| } | |
| } | |
| catch | |
| { | |
| // Do nothing. Not having the event log is bad, but not starting the service as a result is worse. | |
| } | |
| } |
In this scenario we can actually acquire the service name from SCM, it's returned in the ServiceMain callback https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nc-winsvc-lpservice_main_functiona
the first argument (lpServiceArgVectors[0]) is the name of the service
I have confirmed this to be the case. So we can simply capture it here and set ServiceName rather than skipping it:
Lines 866 to 880 in 1a83f61
| if (argCount > 0) | |
| { | |
| char** argsAsPtr = (char**)argPointer.ToPointer(); | |
| //Lets read the arguments | |
| // the first arg is always the service name. We don't want to pass that in. | |
| args = new string[argCount - 1]; | |
| for (int index = 0; index < args.Length; ++index) | |
| { | |
| // we increment the pointer first so we skip over the first argument. | |
| argsAsPtr++; | |
| args[index] = Marshal.PtrToStringUni((IntPtr)(*argsAsPtr))!; | |
| } | |
| } |