【问题标题】:Set 'Start Parameters' on Service Installation with .Net ServiceInstaller?使用 .Net ServiceInstaller 在服务安装上设置“启动参数”?
【发布时间】:2009-03-17 00:38:44
【问题描述】:

我目前正在编写一个小的 Windows 服务应用程序,我可以通过以下方式成功地安装/卸载它等:

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

...但我显然无法在那里设置启动参数...或者我可以吗?我宁愿以后不继续修改注册表。因此问题...有什么办法可以通过编程方式设置这些参数?

【问题讨论】:

  • 你指的是命令行参数吗?为什么不使用 app.config 来配置您的服务?

标签: .net service installation


【解决方案1】:

这里有一个更简洁的答案:

在您的 ServiceInstaller 类(使用 Installer 作为基类的类)中,添加以下两个覆盖:

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


}

【讨论】:

  • +1,但这不能正确处理引号。请参阅stackoverflow.com/questions/4862580/… 以获得相同的解决方案,但引号固定。此外,似乎没有必要为卸载执行此操作 - 对我而言,该服务可以在不调整路径的情况下正常卸载。
  • E.M,这个解决方案只是看起来不正确。而且您的替代解决方案看起来不错,但确实不正确。发生这种情况是因为后来的 .NET 将 assemblypath 的整个值括在引号中。这个特殊的解决方案是使命令行正确的黑客。
  • @bugfixr,在您的示例中将添加的字符串更改为 "\" /service \"" 会更正确。所以最后所有的引号都会被平衡,唯一的副作用是服务的额外空命令行参数。
【解决方案2】:

可以通过 P/Invoking ChangeServiceConfig API 来设置参数。它们出现在 lpBinaryPathName 参数中您的可执行文件的引用路径和文件名之后。

当服务通过 Main 方法启动时,这些参数将可供您的服务使用:

static void Main(string[] args)

(Main 通常位于名为 Program.cs 的文件中)。

以下显示了在正常服务安装逻辑运行后如何修改安装程序以调用此 API。您最可能需要修改的部分在构造函数中。

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

【讨论】:

  • 太棒了 - 不像我希望的那样优雅,但谢谢!
  • 这可能有效,但它过于复杂。请参阅 bugfixr 的答案以获得更简单的代码 - 无需 P/Invoke。
【解决方案3】:

有一种将启动参数添加到服务的托管方式(不在管理控制台 (services.msc) 的“启动参数”/“启动参数”部分,而是在“可执行文件路径”/“Pfad zur EXE- Datei”,就像所有 Windows 的原生服务一样)。

将以下代码添加到 System.Configuration.Install.Installer 的子类中:(在 C# 友好的 VB 代码中)

'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

玩得开心!

哈哈

【讨论】:

    【解决方案4】:

    我找到了一种在服务安装时添加启动参数的方法:

    Am I Running as a Service

    【讨论】:

      【解决方案5】:

      这在托管代码中是不可能的。

      但有一个不错的解决方案。如果您想要的只是具有相同的 Windows 服务和 GUI 可执行文件(最常见的场景)。你甚至不需要参数。只需检查 System.Environment.UserInteractive 属性的 Main 方法并决定要做什么...

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

      【讨论】:

      • 这不是对原始问题的回答
      【解决方案6】:

      由于某种奇怪的原因,我的 QUERY_SERVICE_CONFIG 结构没有得到 lpBinaryPathName 的完整值,只有第一个字符。将其更改为下面的类似乎可以解决问题。完整代码在http://www.pinvoke.net/default.aspx/advapi32/QueryServiceConfig.html

      编辑:还要注意这设置了windows服务的“可执行路径”,但没有设置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;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-19
        • 2013-05-23
        • 1970-01-01
        • 2010-11-14
        • 1970-01-01
        • 2014-11-20
        相关资源
        最近更新 更多