2009-03-17 12 views
9

Actualmente estoy escribiendo una pequeña aplicación de servicio de Windows y yo puedo con éxito en/etc desinstalarlo a través de algo como esto:Establecer 'Parámetros de inicio' en la instalación del servicio con .Net ServiceInstaller?

 serviceProcessInstaller = new ServiceProcessInstaller(); 
     serviceInstaller = new System.ServiceProcess.ServiceInstaller(); 
     serviceProcessInstaller.Account = ServiceAccount.LocalSystem; 
     serviceInstaller.ServiceName = "ABC"; 
     serviceInstaller.StartType = ServiceStartMode.Automatic; 
     serviceInstaller.Description = "DEF"; 
     Installers.AddRange(new Installer[] { serviceProcessInstaller, serviceInstaller }); 

... pero al parecer no puedo configurar los parámetros de inicio allí ... o puedo ? Prefiero no seguir adelante y modificar el registro más adelante ... por lo tanto, la Pregunta ... ¿hay alguna forma en que pueda establecer estos parámetros programáticamente?

+1

¿Te refieres a los parámetros de la línea de comando? ¿Por qué no usar app.config para configurar tu servicio? – cdonner

Respuesta

7

Los parámetros se pueden establecer mediante P/Invocando la API ChangeServiceConfig. Aparecen después de la ruta entre comillas y el nombre del archivo en su ejecutable en el argumento lpBinaryPathName.

Los parámetros se pondrá a disposición a su servicio cuando se inicia a través del método principal:

static void Main(string[] args) 

(principal, tradicional establecimiento de un archivo llamado Program.cs).

A continuación se muestra cómo se puede modificar un instalador para llamar a esta API después de que se ejecute la lógica de instalación del servicio normal. Las partes de esto que probablemente necesitará modificar están en el constructor.

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Configuration.Install; 
using System.ComponentModel; 
using System.Configuration.Install; 
using System.Diagnostics; 
using System.Runtime.InteropServices; 
using System.ServiceProcess; 
using System.Text; 

namespace ServiceTest 
{ 
    [RunInstaller(true)] 
    public class ProjectInstaller : Installer 
    { 
     private string _Parameters; 

     private ServiceProcessInstaller _ServiceProcessInstaller; 
     private ServiceInstaller _ServiceInstaller; 

     public ProjectInstaller() 
     { 
      _ServiceProcessInstaller = new ServiceProcessInstaller(); 
      _ServiceInstaller = new ServiceInstaller(); 

      _ServiceProcessInstaller.Account = ServiceAccount.LocalService; 
      _ServiceProcessInstaller.Password = null; 
      _ServiceProcessInstaller.Username = null; 

      _ServiceInstaller.ServiceName = "Service1"; 

      this.Installers.AddRange(new System.Configuration.Install.Installer[] { 
       _ServiceProcessInstaller, 
       _ServiceInstaller}); 

      _Parameters = "/ThisIsATest"; 
     } 

     public override void Install(IDictionary 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 + " " + _Parameters; 

        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(); 
      } 
     } 

     [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; 
    } 
} 
+0

Impresionante, no tan elegantemente como esperaba, pero ¡gracias! –

+0

+1 ¡Bien hecho! –

+0

Esto puede funcionar, pero es innecesariamente complejo. Ver la respuesta de ** bugfixr ** para un código mucho más simple - no se requiere P/Invoke. – EM0

1

Esto no es posible en código administrado.

Sin embargo, hay una solución decente. Si todo lo que quiere es tener el mismo ejecutable para el servicio de Windows y la GUI (escenario más común). Ni siquiera necesitas parámetros. Sólo echa en el método principal de System.Environment.UserInteractive propiedad y decidir qué hacer ...

static void Main(string[] args) 
{ 
    if (System.Environment.UserInteractive) 
    { 
     // start your app normally 
    } 
    else 
    { 
     // start your windows sevice 
    } 
} 
+0

¡Ooooh! No lo sabía ... ¡gracias! –

+0

esto no responde a la pregunta original – sotn

1

Por alguna extraña razón mi QUERY_SERVICE_CONFIG estructura no estaba recibiendo el valor total de lpBinaryPathName, sólo el primer carácter. Cambiarlo a la clase de abajo pareció solucionar el problema. Hay código completo en http://www.pinvoke.net/default.aspx/advapi32/QueryServiceConfig.html

Editar: También tenga en cuenta esto establece la "Ruta al ejecutable" del servicio de Windows, pero no establece los "Parámetros de inicio" del servicio de Windows.

[StructLayout(LayoutKind.Sequential)] 
public class QUERY_SERVICE_CONFIG 
{ 
    [MarshalAs(System.Runtime.InteropServices.UnmanagedType.U4)] 
    public UInt32 dwServiceType; 
    [MarshalAs(System.Runtime.InteropServices.UnmanagedType.U4)] 
    public UInt32 dwStartType; 
    [MarshalAs(System.Runtime.InteropServices.UnmanagedType.U4)] 
    public UInt32 dwErrorControl; 
    [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] 
    public String lpBinaryPathName; 
    [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] 
    public String lpLoadOrderGroup; 
    [MarshalAs(System.Runtime.InteropServices.UnmanagedType.U4)] 
    public UInt32 dwTagID; 
    [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] 
    public String lpDependencies; 
    [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] 
    public String lpServiceStartName; 
    [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] 
    public String lpDisplayName; 
} 
3

Hay camino conseguido añadir parámetros de inicio a un servicio (no en la sección "Parámetros de inicio"/"Startparameter" de la consola de administración (services.msc) pero en "Camino a ejecutable" /" Pfad zur EXE-Datei "como todos los servicios nativos de Windows").

Agregue el código siguiente a su subclase de System.Configuration.Install.Installer: (en C# -Friendly VB-Code)

'Just as sample 
Private _CommandLineArgs As String() = New String() {"/Debug", "/LogSection:Hello World"} 

''' <summary>Command line arguments without double-quotes.</summary> 
Public Property CommandLineArgs() As String() 
    Get 
    Return _CommandLineArgs 
    End Get 
    Set(ByVal value As String()) 
    _CommandLineArgs = value 
    End Set 
End Property 

Public Overrides Sub Install(ByVal aStateSaver As System.Collections.IDictionary) 
    Dim myPath As String = GetPathToExecutable() 
    Context.Parameters.Item("assemblypath") = myPath 
    MyBase.Install(aStateSaver) 
End Sub 

Private Function GetPathToExecutable() As String 
    'Format as something like 'MyService.exe" "/Test" "/LogSection:Hello World' 
    'Hint: The base class (System.ServiceProcess.ServiceInstaller) adds simple-mindedly 
    '  a double-quote around this string that's why we have to omit it here. 
    Const myDelimiter As String = """ """ 'double-quote space double-quote 
    Dim myResult As New StringBuilder(Context.Parameters.Item("assemblypath")) 
    myResult.Append(myDelimiter) 
    myResult.Append(Microsoft.VisualBasic.Strings.Join(CommandLineArgs, myDelimiter)) 
    Return myResult.ToString() 
End Function 

Que se diviertan!

chha

6

Aquí es una respuesta más concisa:

En su clase de ServiceInstaller (el que usa el instalador como clase base), agregue las siguientes dos anulaciones:

public partial class ServiceInstaller : System.Configuration.Install.Installer { 

    public ServiceInstaller() { 
     ... 
    } 

    protected override void OnBeforeInstall(System.Collections.IDictionary savedState) { 
     Context.Parameters["assemblypath"] += "\" /service"; 
     base.OnBeforeInstall(savedState); 
    } 

    protected override void OnBeforeUninstall(System.Collections.IDictionary savedState) { 
     Context.Parameters["assemblypath"] += "\" /service"; 
     base.OnBeforeUninstall(savedState); 
    } 


} 
+0

+1, pero esto no maneja las comillas correctamente. Consulte http://stackoverflow.com/questions/4862580/using-installutil-to-install-a-windows-service-with-startup-parameters para la misma solución, pero con las comillas corregidas. Además, no parece necesario hacer esto para la desinstalación, para mí el servicio se desinstala bien sin ajustar la ruta. – EM0

+1

E.M, esta solución solo parece incorrecta. Y su solución alternativa es realmente incorrecta mientras se ve bien. Sucede porque más tarde .NET incluye todo el valor de assemblypath en comillas. Y esta solución particular es un truco para hacer que la línea de comando sea correcta. – Sergey

+0

@bugfixr, sería más correcto cambiar la cadena agregada a "\"/service \ "" en su muestra. Por lo tanto, todas las comillas serán equilibradas al final. El único efecto secundario será un argumento de línea de comando vacío adicional al servicio. – Sergey

Cuestiones relacionadas