2009-12-03 14 views
27

Ho cercato e non ho trovato quello che dovrebbe essere una semplice domanda:In che modo un servizio Windows può determinare il suo nome servizio?

Come può un servizio di Windows determinare il ServiceName per il quale è stato avviato?

so l'installazione può incidere al Registro di sistema e aggiungere un argomento della riga di comando, ma logicamente che sembra che dovrebbe essere inutile, quindi questa domanda.

Spero di eseguire più copie di un singolo binario in modo più pulito rispetto all'hack del registro.

Edit:

Questo è scritto in C#. Le mie applicazioni main() punto di ingresso fa cose diverse, a seconda di argomenti della riga di comando :

  • installare o disinstallare il servizio. La riga di comando può fornire un Nome servizio non predefinito e può modificare il numero di thread di lavoro.
  • Esegui come eseguibile da riga di comando (per il debug),
  • Esegui come "Servizio Windows". Qui, crea un'istanza del mio ServiceBase -derived classe , quindi chiama System.ServiceProcess.ServiceBase.Run (istanza);

Attualmente, la fase di installazione aggiunge il nome del servizio e filo conteggio alla ImagePath nel Registro di sistema in modo che l'applicazione può determinare è ServiceName.

+0

Non hai menzionato con quale lingua hai scritto il servizio. – Walter

risposta

24

Da: https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=387024

Ecco una soluzione WMI. Override del ServiceBase.ServiceMainCallback() potrebbe anche funzionare, ma questo sembra funzionare per me ...

protected String GetServiceName() 
    { 
     // Calling System.ServiceProcess.ServiceBase::ServiceNamea allways returns 
     // an empty string, 
     // see https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=387024 

     // So we have to do some more work to find out our service name, this only works if 
     // the process contains a single service, if there are more than one services hosted 
     // in the process you will have to do something else 

     int processId = System.Diagnostics.Process.GetCurrentProcess().Id; 
     String query = "SELECT * FROM Win32_Service where ProcessId = " + processId; 
     System.Management.ManagementObjectSearcher searcher = 
      new System.Management.ManagementObjectSearcher(query); 

     foreach (System.Management.ManagementObject queryObj in searcher.Get()) { 
      return queryObj["Name"].ToString(); 
     } 

     throw new Exception("Can not get the ServiceName"); 
    } 
+1

Per un servizio a 64 bit "selezionerà * Da Win32_Service" funzionerà ancora? – shindigo

+1

Risposta al mio commento: sì, questa query funziona su WIN7 con servizio a 64 bit. Assicurati di non tentare prima che venga chiamato il servizio.Run()! Inoltre - la cartella di lavoro è Windows \ System32 – shindigo

+0

Grazie per aver trovato il tempo di postare questo, mi hai salvato così tanto dolore. – Waltzy

1

Il punto di ingresso ServiceMain() che ogni eseguibile del servizio deve implementare riceve il ServiceName come primo argomento di input.

Se si scrive il servizio utilizzando .NET, il punto di ingresso ServiceMain() è implementato da .NET per l'utente. Il ServiceName viene assegnato quando il servizio viene installato utilizzando la proprietà ServiceProcess.ServiceBase.ServiceName. Se si sta tentando di personalizzare un servizio .NET per supportare valori di ServiceName dinamici, non ho idea di come accedere al ServiceName effettivo in fase di runtime.

+0

per identificare in fase di esecuzione utilizzare il comando SC QUERYEX – lsalamon

+0

@Remy - sembra che ServiceMain sia per codice C++ (http://msdn.microsoft.com/en-us/library/ms685138%28VS.85%29.aspx), I sto usando C#. @Isalamon - Credo che tu stia sbagliando, ma se puoi mostrare come ** SC ** può essere usato, per favore pubblica una risposta con i dettagli. – NVRAM

+0

Anche i servizi .NET hanno un punto di ingresso ServiceMain(), solo uno che .NET implementa per te (il metodo ServiceBase.ServiceMainCallback()). Ho appena notato che l'evento ServiceBase.OnStart ha un parametro args. Non ho scritto servizi in .NET, ma supponendo che ServiceBase non escluda il primo parametro fornito da ServiceMain() (il ServiceName) durante la creazione del parametro args, dovresti essere in grado di estrarre il ServiceName da esso. –

5

proprietà ServiceBase.ServiceName dà il nome in fase di compilazione del servizio. Se si specifica un nome diverso durante l'installazione del servizio, l'attributo ServiceName non darà il nome corretto. Quindi, ho dovuto usare sotto il codice per ottenere il nome del servizio del mio servizio.

Si tratta di un'alternativa (senza l'utilizzo di LINQ) il metodo di NVRAM:

/** 
* Returns the service name of currently running windows service. 
*/ 
static String getServiceName() 
{ 
    ServiceController[] scServices; 
    scServices = ServiceController.GetServices(); 

    // Display the list of services currently running on this computer. 
    int my_pid = System.Diagnostics.Process.GetCurrentProcess().Id; 

    foreach (ServiceController scTemp in scServices) 
    { 
     // Write the service name and the display name 
     // for each running service. 

     // Query WMI for additional information about this service. 
     // Display the start name (LocalSytem, etc) and the service 
     // description. 
     ManagementObject wmiService; 
     wmiService = new ManagementObject("Win32_Service.Name='" + scTemp.ServiceName + "'"); 
     wmiService.Get(); 

     int id = Convert.ToInt32(wmiService["ProcessId"]); 
     if (id == my_pid) 
     { 
      return scTemp.ServiceName; 
#if IS_CONSOLE 
      Console.WriteLine(); 
      Console.WriteLine(" Service :  {0}", scTemp.ServiceName); 
      Console.WriteLine(" Display name: {0}", scTemp.DisplayName); 

      Console.WriteLine(" Start name:  {0}", wmiService["StartName"]); 
      Console.WriteLine(" Description:  {0}", wmiService["Description"]); 

      Console.WriteLine(" Found......."); 
#endif 
     } 
    } 
    return "NotFound"; 
} 

mi è stato erroneamente cercando di ottenere il nome del servizio di Windows, come prima linea nel main() senza prima chiamare ServiceBase.Run().Dobbiamo registrare il nostro eseguibile come servizio utilizzando ServiceBase.Run() prima di ottenere il suo nome.

Ref .: http://msdn.microsoft.com/en-us/library/hde9d63a.aspx#Y320

+1

+1 per indicare che il nome del servizio non è disponibile fino a quando viene chiamato ServiceBase.Run(). In retrospettiva, sembra ovvio, ma stavo controllando il costruttore e ottenendo ancora il nome in fase di compilazione. – msulis

+1

Cosa succede se stiamo ospitando più servizi all'interno dello stesso exe? Saranno eseguiti in diversi processi? In caso contrario, dobbiamo indicare il nome del servizio del thread corrente anziché il processo corrente. In tal caso, come possiamo farlo? – drowa

2

Versione corta con LINQ

int processId = System.Diagnostics.Process.GetCurrentProcess().Id; 
    ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_Service where ProcessId = " + processId); 
    ManagementObjectCollection collection = searcher.Get(); 
    var serviceName = (string)collection.Cast<ManagementBaseObject>().First()["Name"]; 
0

Con la ricerca di una soluzione migliore che ho provato questo:

string serviceName = "myDynamicServiceName"; 
string serviceBin = "path\\to\\Service.exe"; 
string configFile = "path\\to\\myConfig.xml"; 
string credentials = "obj= .\\mytestuser password= test"; 

string scCommand = string.Format("sc create {0} start= auto binPath= \"\\\"{1}\\\" -ini={2} -sn={3}\" type= own{4}", serviceName, serviceBin, configFile , serviceName ,credentials); 

ho superato l'servicename e un file di configurazione per il binpath. Il servizio è stato installato utilizzando lo SC.exe (io non uso l'installutil!)

Sul servizio è possibile ottenere la linea di comando-Argomenti

protected override void OnStart(string[] args){ 
    string binpath = new System.IO.FileInfo(System.Reflection.Assembly.GetAssembly(this.GetType()).Location).DirectoryName + "\\"; 
    System.IO.StreamWriter sw = new System.IO.StreamWriter(binpath + "test.log"); 

    sw.WriteLine(binpath); 

    string[] cmdArgs = System.Environment.GetCommandLineArgs(); 
    foreach (string item in cmdArgs) { 
     sw.WriteLine(item); 
    } 

    sw.Flush(); 
    sw.Dispose(); 
    sw = null; 
} 
0

Cosa c'è di sbagliato con this.ServiceName, se sei dentro il service.cs?

cioè:

protected override void OnStart(string[] args) 
    { 
     Logger.Info($"{this.ServiceName} started on {Environment.MachineName}..."); 
    } 
+2

Questo non è lo stesso nome di servizio che appare nell'elenco dei servizi di Windows :( – fabriciorissetto

+0

ServiceBase.ServiceName e ServiceInstaller.ServiceName sono due proprietà diverse, ma dovrebbero essere uguali. https://docs.microsoft.com/en-us/dotnet/api/system.serviceprocess.serviceinstaller?view=netframework-4.7.1 –

0

Ho avuto un problema di pollo e uova in cui avevo bisogno di conoscere la posizione di servizio prima di completare Service.Run() (Il servizio potrebbe essere parte di un'installazione client o server, installatore li ho nominati in modo appropriato, e avevo bisogno di rilevare quale era all'avvio)

Mi sono affidato al registro per ottenere il nome.

public String IdentifySelfFromRegistry() 
{ 
    String executionPath = Assembly.GetEntryAssembly().Location; 
    Microsoft.Win32.RegistryKey services = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(
      @"SYSTEM\CurrentControlSet\services"); 
    if (services != null) 
    { 
     foreach(String subkey in services.GetSubKeyNames()) 
     { 
      if (executionPath.Equals(ServicePathFromServiceKey(services.OpenSubKey(subkey)))) 
       return subkey; 
     } 
    } 
    return String.Empty; 
} 

protected static String ServicePathFromServiceKey(Microsoft.Win32.RegistryKey serviceKey) 
{ 
    if (serviceKey != null) 
    { 
     String exec = serviceKey.GetValue(ServicePathEntry) as String; 
     if (exec != null) 
      return exec.Trim('\"'); 
    } 
    return String.Empty; 
} 
Problemi correlati