I have a bunch of win services written in .NET that use same exact executable with different configs. All services write to the same log file. However since I use the same .exe
Insight can be gained by looking at how Microsoft does this for the SQL Server service. In the Services control panel, we see:
Service name: MSSQLServer
Path to executable: "C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn\sqlservr.exe" -sMSSQLSERVER
Notice that the name of the service is included as a command line argument. This is how it is made available to the service at run time. With some work, we can accomplish the same thing in .NET.
To install the service (can omit /Name to use DEFAULT_SERVICE_NAME):
installutil.exe /Name=YourServiceName YourService.exe
To uninstall the service (/Name is never required since it is stored in the stateSaver):
installutil.exe /u YourService.exe
using System;
using System.Collections;
using System.Configuration.Install;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.ServiceProcess;
namespace TestService
{
[RunInstaller(true)]
public class ProjectInstaller : Installer
{
private const string DEFAULT_SERVICE_NAME = "TestService";
private const string DISPLAY_BASE_NAME = "Test Service";
private ServiceProcessInstaller _ServiceProcessInstaller;
private ServiceInstaller _ServiceInstaller;
public ProjectInstaller()
{
_ServiceProcessInstaller = new ServiceProcessInstaller();
_ServiceInstaller = new ServiceInstaller();
_ServiceProcessInstaller.Account = ServiceAccount.LocalService;
_ServiceProcessInstaller.Password = null;
_ServiceProcessInstaller.Username = null;
this.Installers.AddRange(new System.Configuration.Install.Installer[] {
_ServiceProcessInstaller,
_ServiceInstaller});
}
public override void Install(IDictionary stateSaver)
{
if (this.Context != null && this.Context.Parameters.ContainsKey("Name"))
stateSaver["Name"] = this.Context.Parameters["Name"];
else
stateSaver["Name"] = DEFAULT_SERVICE_NAME;
ConfigureInstaller(stateSaver);
base.Install(stateSaver);
IntPtr hScm = OpenSCManager(null, null, SC_MANAGER_ALL_ACCESS);
if (hScm == IntPtr.Zero)
throw new Win32Exception();
try
{
IntPtr hSvc = OpenService(hScm, this._ServiceInstaller.ServiceName, SERVICE_ALL_ACCESS);
if (hSvc == IntPtr.Zero)
throw new Win32Exception();
try
{
QUERY_SERVICE_CONFIG oldConfig;
uint bytesAllocated = 8192; // Per documentation, 8K is max size.
IntPtr ptr = Marshal.AllocHGlobal((int)bytesAllocated);
try
{
uint bytesNeeded;
if (!QueryServiceConfig(hSvc, ptr, bytesAllocated, out bytesNeeded))
{
throw new Win32Exception();
}
oldConfig = (QUERY_SERVICE_CONFIG)Marshal.PtrToStructure(ptr, typeof(QUERY_SERVICE_CONFIG));
}
finally
{
Marshal.FreeHGlobal(ptr);
}
string newBinaryPathAndParameters = oldConfig.lpBinaryPathName + " /s:" + (string)stateSaver["Name"];
if (!ChangeServiceConfig(hSvc, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
newBinaryPathAndParameters, null, IntPtr.Zero, null, null, null, null))
throw new Win32Exception();
}
finally
{
if (!CloseServiceHandle(hSvc))
throw new Win32Exception();
}
}
finally
{
if (!CloseServiceHandle(hScm))
throw new Win32Exception();
}
}
public override void Rollback(IDictionary savedState)
{
ConfigureInstaller(savedState);
base.Rollback(savedState);
}
public override void Uninstall(IDictionary savedState)
{
ConfigureInstaller(savedState);
base.Uninstall(savedState);
}
private void ConfigureInstaller(IDictionary savedState)
{
_ServiceInstaller.ServiceName = (string)savedState["Name"];
_ServiceInstaller.DisplayName = DISPLAY_BASE_NAME + " (" + _ServiceInstaller.ServiceName + ")";
}
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr OpenSCManager(
string lpMachineName,
string lpDatabaseName,
uint dwDesiredAccess);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr OpenService(
IntPtr hSCManager,
string lpServiceName,
uint dwDesiredAccess);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct QUERY_SERVICE_CONFIG
{
public uint dwServiceType;
public uint dwStartType;
public uint dwErrorControl;
public string lpBinaryPathName;
public string lpLoadOrderGroup;
public uint dwTagId;
public string lpDependencies;
public string lpServiceStartName;
public string lpDisplayName;
}
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool QueryServiceConfig(
IntPtr hService,
IntPtr lpServiceConfig,
uint cbBufSize,
out uint pcbBytesNeeded);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ChangeServiceConfig(
IntPtr hService,
uint dwServiceType,
uint dwStartType,
uint dwErrorControl,
string lpBinaryPathName,
string lpLoadOrderGroup,
IntPtr lpdwTagId,
string lpDependencies,
string lpServiceStartName,
string lpPassword,
string lpDisplayName);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseServiceHandle(
IntPtr hSCObject);
private const uint SERVICE_NO_CHANGE = 0xffffffffu;
private const uint SC_MANAGER_ALL_ACCESS = 0xf003fu;
private const uint SERVICE_ALL_ACCESS = 0xf01ffu;
}
}
using System;
using System.ServiceProcess;
namespace TestService
{
class Program
{
static void Main(string[] args)
{
string serviceName = null;
foreach (string s in args)
{
if (s.StartsWith("/s:", StringComparison.OrdinalIgnoreCase))
{
serviceName = s.Substring("/s:".Length);
}
}
if (serviceName == null)
throw new InvalidOperationException("Service name not specified on command line.");
// Substitute the name of your class that inherits from ServiceBase.
TestServiceImplementation impl = new TestServiceImplementation();
impl.ServiceName = serviceName;
ServiceBase.Run(impl);
}
}
class TestServiceImplementation : ServiceBase
{
protected override void OnStart(string[] args)
{
// Your service implementation here.
}
}
}
I use this function in VB
Private Function GetServiceName() As String
Try
Dim processId = Process.GetCurrentProcess().Id
Dim query = "SELECT * FROM Win32_Service where ProcessId = " & processId.ToString
Dim searcher As New Management.ManagementObjectSearcher(query)
Dim share As Management.ManagementObject
For Each share In searcher.Get()
Return share("Name").ToString()
Next share
Catch ex As Exception
Dim a = 0
End Try
Return "DefaultServiceName"
End Function