使用 .Net ServiceInstaller 在服务安装上设置“启动参数”?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/652654/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 12:20:50  来源:igfitidea点击:

Set 'Start Parameters' on Service Installation with .Net ServiceInstaller?

.netserviceinstallation

提问by J?rg Battermann

I am currently writing a little windows service application and I can successfully in/uninstall it etc via something like this:

我目前正在编写一个小的 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 });

... but I apparently cannot set the startup parameters there... or can I? I'd rather not go ahead and modify the registry later on.. therefore the Question... is there any way I can set these parameters programatically?

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

采纳答案by Rolf Kristensen

I have found a way to add start parameters on service installation:

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

Am I Running as a Service

我是作为服务运行吗

回答by Jason Kresowaty

The parameters can be set by P/Invoking the ChangeServiceConfig API. They appear after the quoted path and file name to your executable in the lpBinaryPathName argument.

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

The parameters will be made available to your service when it starts through the Main method:

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

static void Main(string[] args)

(Main is traditionally located in a file called Program.cs).

(Main 传统上位于名为 Program.cs 的文件中)。

The following shows how you might modify an installer to call this API after the normal service installation logic runs. The parts of this that you most likely will need to modify are in the constructor.

下面显示了如何在正常的服务安装逻辑运行后修改安装程序以调用此 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;
    }
}

回答by bugfixr

Here's a more concise answer:

这是一个更简洁的答案:

In your ServiceInstaller class (the one that uses the Installer as a base class), add the following two overrides:

在您的 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);
    }


}

回答by bugfixr

There is a managed way to add start parameters to a services (not in the "Start parameters"/"Startparameter" section of the management console (services.msc) but in "Path to executable"/"Pfad zur EXE-Datei" like all the windows' native services do).

有一种将启动参数添加到服务的托管方法(不在管理控制台 (services.msc) 的“启动参数”/“启动参数”部分,而是在“可执行文件的路径”/“Pfad zur EXE-Datei”中,例如所有 Windows 的本机服务都可以)。

Add the following code to your subclass of System.Configuration.Install.Installer: (in C#-friendly VB-Code)

将以下代码添加到 System.Configuration.Install.Installer 的子类中:(在 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

Have fun!

玩得开心!

chha

回答by lubos hasko

This is not possible to do in managed code.

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

But there is one decent solution though. If all you want is having the same executable for windows service and GUI (most common scenario). You don't even need parameters. Just check in Main method for System.Environment.UserInteractiveproperty and decide what to do...

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

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

回答by Axl

For some strange reason my QUERY_SERVICE_CONFIG struct wasn't getting the full value of lpBinaryPathName, only the first character. Changing it to the class below seemed to fix the problem. There is complete code at http://www.pinvoke.net/default.aspx/advapi32/QueryServiceConfig.html

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

Edit:Also note this sets the "Path to executable" of the windows service but does not set the "Start parameters" of the windows service.

编辑:另请注意,这会设置 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;
}