我目前正在编写一个小的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 });
...但我显然无法在那里设置启动参数......或者我可以吗?我宁愿不继续以后修改注册表..因此问题...有什么办法我可以用编程方式设置这些参数吗?
我找到了一种在服务安装上添加启动参数的方法:
可以通过P /调用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;
}
}
这是一个更简洁的答案:
在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);
}
}
有一种托管方法可以向服务添加启动参数(不在管理控制台的“启动参数”/“启动参数”部分(services.msc)中,而是在“可执行文件路径”/“Pfad zur EXE-Datei”中添加所有Windows的本地服务都这样做。
将以下代码添加到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
玩得开心!
g ^
这在托管代码中是不可能的。
但是,有一个不错的解决方案。如果您只想拥有与Windows服务和GUI相同的可执行文件(最常见的场景)。你甚至不需要参数。只需检查System.Environment.UserInteractive
属性的主要方法,并决定做什么...
static void Main(string[] args)
{
if (System.Environment.UserInteractive)
{
// start your app normally
}
else
{
// start your windows sevice
}
}
出于某些奇怪的原因,我的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;
}