我想出了另一种解决方法来安装具有绑定重定向的服务。由于我有很多服务,这就是我决定追求的。
将 Windows 安装程序更改为控制台应用程序并实现自行安装的功能(使用命令行和 ManagedInstallerClass.InstallHelper)。
实现一个能够在完全独立的程序集中执行命令行的安装程序类,例如CommandLineInstaller.DLL. CommandLineInstaller.DLL
,应以相同的方式实现方法 Install/Uninstall/Rollback - 使用以下参数执行命令行:
FileName, WorkingDirectory, Args, WindowStyle
.
修改安装项目以部署 1) 服务和 b)CommandLineInstaller.DLL
修改安装项目自定义操作:运行 CommandLineInstaller.DLL 的操作,而不是运行服务的操作。安装操作的 CustomActionData 属性将如下所示:
/FileName="[TARGETDIR]MyService.exe" /Args="/install" WindowStyle="Hidden"
动作配置: 安装:myservice /install 回滚:myservice /uninstall 卸载:myservice /uninstall
无需编写提交,AFAIK。
现在,安装项目将在自己的进程中执行 CommandLineInstaller.DLL 安装程序。然后 CommandLineInstaller.DLL 将依次在其自己的进程中启动 MyService.exe,并按照应有的方式进行血腥绑定重定向。
PSMyService.exe
可以使用退出代码机制来通知安装程序失败,我强烈建议从 CommandLineInstaller 检查它们。
希望这是一个足够好的大纲。
PS 请注意 TARGETDIR 在将其自身传递给目录时需要有一个斜杠:
/WorkDir="[TARGETDIR]\"
安装 CustomActionData 的示例:
/FileName="[TARGETDIR]\MyService.exe" /Args="/install" /WorkingDir="[TARGETDIR]\" /ValidExitCode="0" /WindowStyle="Normal"
一些代码:
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
namespace QT.Install
{
[RunInstaller(true)]
public partial class ExecuteCommandInstaller : System.Configuration.Install.Installer
{
public class CommandArgs
{
public string FileName { get; set; }
public string WorkingDir { get; set; }
public string Args { get; set; }
public string ValidExitCode { get; set; }
public ProcessWindowStyle WindowStyle { get; set; }
}
public ExecuteCommandInstaller()
{
InitializeComponent();
}
public override void Install(IDictionary stateSaver)
{
base.Install(stateSaver);
ExecuteCommand(stateSaver);
}
public override void Commit(IDictionary savedState)
{
base.Commit(savedState);
ExecuteCommand(savedState);
}
public override void Uninstall(IDictionary savedState)
{
base.Uninstall(savedState);
ExecuteCommand(savedState);
}
public override void Rollback(IDictionary savedState)
{
base.Rollback(savedState);
ExecuteCommand(savedState);
}
private void ExecuteCommand(IDictionary stateSaver)
{
CommandArgs commandArgs = new CommandArgs()
{
FileName = StripDoubleSlash(Context.Parameters["FileName"] ?? ""),
WorkingDir = StripDoubleSlash(Context.Parameters["WorkingDir"] ?? ""),
Args = Context.Parameters["Args"] ?? "",
ValidExitCode = Context.Parameters["ValidExitCode"] ?? "*"
};
try
{
commandArgs.WindowStyle = (ProcessWindowStyle)Enum.Parse(typeof(ProcessWindowStyle), Context.Parameters["WindowStyle"] ?? "Hidden");
}
catch (Exception err)
{
throw new Exception($"Invalid WindowStyle parameter value: {Context.Parameters["WindowStyle"]}", err);
}
InternalExecuteCommand(commandArgs);
}
private void InternalExecuteCommand(CommandArgs commandArgs)
{
if (string.IsNullOrEmpty(commandArgs.FileName))
throw new Exception("FileName is not specified.");
System.Diagnostics.ProcessStartInfo startInfo = new ProcessStartInfo(commandArgs.FileName, commandArgs.Args);
if (!string.IsNullOrEmpty(commandArgs.WorkingDir))
startInfo.WorkingDirectory = commandArgs.WorkingDir;
startInfo.WindowStyle = commandArgs.WindowStyle;
using (var process = Process.Start(startInfo))
{
process.WaitForExit();
if (commandArgs.ValidExitCode != "*")
{
if (process.ExitCode.ToString() != commandArgs.ValidExitCode)
throw new Exception($"Executing {commandArgs.FileName} {commandArgs.Args} returned exit code {process.ExitCode}. Expected exit code is: {commandArgs.ValidExitCode}.");
}
}
}
private static string StripDoubleSlash(string value)
{
return value.Replace("\\\\", "\\");
}
}
}