我创建了一个 Windows 服务,它可以简单地轮询 git 存储库、检查新版本并自动安装我正在构建的应用程序的新版本。
当我尝试从 Windows 服务内部运行下载的 MSI 安装程序时,它无法打开。我尝试过使用 msiexec 并从调用 msiexec 的批处理文件运行安装程序,但我没有运气。我希望能够从批处理文件中调用 MSI,这样我就可以在其中粘贴一个 taskkill 命令来退出应用程序(如果应用程序当前打开)。这是我完成所有这些工作的代码。
// Build a new batch file to run for our installer routines here
FileInfo InstallerInfo = new FileInfo(InstallerPath);
string InstallerName = InstallerInfo.Name;
string InstallerDirectory = InstallerInfo.DirectoryName;
string UpdateBatchName = $"InvokeUpdate_{Path.GetFileNameWithoutExtension(InstallerName)}.bat";
string UpdateBatchPath = Path.Combine(InstallerDirectory, UpdateBatchName);
string InstallerFileContent =
"@echo OFF\n" + // Disable echo for the script
"taskkill /F /IM FulcrumInjector*\n" + // Attempt to kill instances of the FulcrumInjector application
$"msiexec /passive /i \"{InstallerPath}\""; // Boot the new MSI installer in a passive mode to update the injector
// Write out our installer file content and ensure it exists once done
File.WriteAllText(UpdateBatchPath, InstallerFileContent);
if (!File.Exists(UpdateBatchPath))
throw new FileNotFoundException($"Error! Failed to build batch file to invoke update for installer \"{InstallerPath}\"!");
// Build a new process to boot our batch file and invoke an update here
ProcessStartInfo UpdaterStartInfo = new ProcessStartInfo
{
Verb = "runas", // Forces the process to run as the user who invoked it
FileName = "cmd.exe", // File to invoke (CMD for booting the batch file)
CreateNoWindow = false, // Specifies if we should make a window for this process
UseShellExecute = false, // Uses shell execution or not
RedirectStandardError = true, // Redirects standard output
RedirectStandardOutput = true, // Redirects standard error
WorkingDirectory = InstallerDirectory, // Sets the working directory of the process to our installer folder
Arguments = $"/C \"{UpdateBatchPath}\"", // Arguments to pass into the CMD window when booted
};
// Build our process and store the standard output and error information
Process UpdaterProcess = Process.Start(UpdaterStartInfo);
UpdaterProcess.ErrorDataReceived += (_, ErrorArgs) =>
{
// Only log out our data for the output if it's not empty
if (string.IsNullOrWhiteSpace(ErrorArgs.Data)) return;
this._serviceLogger.WriteLog("[BAT ERROR] >> " + ErrorArgs.Data);
};
UpdaterProcess.OutputDataReceived += (_, OutputArgs) =>
{
// Only log out our data for the output if it's not empty
if (string.IsNullOrWhiteSpace(OutputArgs.Data)) return;
this._serviceLogger.WriteLog("[BAT OUTPUT] >> " + OutputArgs.Data);
};
// Begin reading the output and error streams for our process
UpdaterProcess.BeginErrorReadLine();
UpdaterProcess.BeginOutputReadLine();
// Wait for the process to exit out and return based on status
UpdaterProcess.WaitForExit();
return UpdaterProcess.ExitCode == 0;
当我找到我正在写的批处理文件时,它的内容是正确的,当我通过双击它手动运行它时,批处理文件会正确执行并打开我尝试运行的安装程序。但是当我在代码中使用 Process.Start 时,它不会启动 MSI 文件。我已重定向批处理文件的输出,并且可以看到它正在执行,但安装程序从未显示。当脚本运行时,我看到任务管理器中打开了一个新的 msiexec 实例,但没有任何结果。谁能解释一下这是为什么吗?
又花了几个小时挖掘文档并摆弄
MSIEXEC
,我找到了解决方案。修复方法非常简单。我只需使用 MSIEXEC
标志而不是 /A
重写对 /I
的调用。完成此操作后,我就能够让服务直接从 C# 代码调用 MSIEXEC
并执行安装程序。我还放弃了使用批处理文件,并在我正在构建的方法中添加了一些 C# 代码,以便在安装程序运行之前终止应用程序的现有实例。
这就是我的最终结果。此解决方案不需要使用 Wix 添加用户帐户或使用不同的用户帐户调用
MSIEXEC
!我的 Windows 服务仍在 Local Service
帐户下运行,到目前为止我还没有遇到问题。希望这对将来的人有用。
注意: 我唯一要提到的是,出于某种原因,我的 MSI 尝试默认将应用程序安装在我的
X
驱动器上,而不是我的 C
驱动器上。我的解决方案是当我调用 TARGETDIR
时,强制安装程序上的 C:\
属性为 MSIEXEC
。
// Configure an installer log file here
FileInfo InstallerInfo = new FileInfo(InstallerPath);
string InstallerDirectory = InstallerInfo.DirectoryName;
string InstallerName = Path.GetFileNameWithoutExtension(InstallerInfo.Name);
string InstallerLogFile = Path.Combine(InstallerDirectory, $"InstallerLog_{InstallerName}.log");
// Build our argument string for the msiexec process
string UpdaterArguments =
$"/A \"{InstallerPath}\" " + // Install the package as an administrator
$"TARGETDIR=\"C:\\\" " + // Specify our target install directory
$"/L*V \"{InstallerLogFile}\""; // Sets logging output to the log file name given
// Build a new process to invoke our installer msi file here
ProcessStartInfo UpdaterStartInfo = new ProcessStartInfo
{
// Configuration for process bootup
Verb = "runas", // Forces the process to run as the user who invoked it
UseShellExecute = true, // Uses shell execution or not
FileName = "msiexec.exe", // File to invoke. MSIEXEC for installing MSI files
Arguments = UpdaterArguments, // Arguments to pass into the MSIEXEC when booted
};
// Build our process and and store the start info built above
Process UpdaterProcess = new Process();
UpdaterProcess.StartInfo = UpdaterStartInfo;
// Start the process and wait for it to exit
UpdaterProcess.Start();
UpdaterProcess.WaitForExit();
// Return out if we've got an exit code of 0 or not
return UpdaterProcess.ExitCode == 0;