如何从 .NET Windows 服务启动 exe 以更新服务
How to start an exe from a .NET Windows Service for updating the service
我有一个 windows 服务,我想自动更新它。我开始使用 wyBuild 来实现它,但遇到了一些问题,因此决定尝试构建我自己的。我写了一个独立的 exe,可以调用它来执行更新过程:检查是否有更新的新 zip 文件,下载它,解压缩,停止 windows 服务,从 zip 复制文件,然后重新启动服务。当我从命令行 运行 它时,这个 exe 工作正常并且并不难写。
但是,现在我希望服务(正在更新的同一服务)shell 输出到更新程序 exe 以更新自身。我首先尝试 Process.Start:
var proc = Process.Start(pathToUpdaterExe);
proc.WaitForExit(60000);
这调用了更新程序,但是当更新程序停止服务时,进程被杀死并且更新停止。我做了一些搜索,听起来解决方案是使用单独的 AppDomain。这就是我现在拥有的:
Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
Evidence objEvidence = new System.Security.Policy.Evidence(baseEvidence);
AppDomainSetup setup = new AppDomainSetup();
var updateDomain = AppDomain.CreateDomain("updateDomain", objEvidence, setup);
updateDomain.ExecuteAssembly(updater);
AppDomain.Unload(updateDomain);
但是,现在我在尝试复制新 Service.dll
时收到错误 System.IO.IOException: "The process cannot access the file 'C:\Program Files (x86)\Company\Service\Service.dll' because it is being used by another process"
同样,此时我已停止 服务。我已经通过日志记录确认了这一点。我无法想象什么 Service.dll 仍然被锁定,所以我添加了代码来检查是什么锁定了它:
public static IEnumerable<Process> GetProcessesLocking(string filePath)
{
var result = new List<Process>();
result.Clear();
var processes = Process.GetProcesses();
foreach (Process proc in processes)
{
try
{
if (proc.HasExited) continue;
foreach (ProcessModule module in proc.Modules)
{
if ((module.FileName.ToLower().CompareTo(filePath.ToLower()) == 0))
{
result.Add(proc);
break;
}
}
}
catch (Exception ex)
{
Log(ex.ToString());
Log("There was an error checking " + proc.ProcessName );
}
}
return result;
}
然而,此代码表明没有任何东西锁定 dll(结果为空,没有任何记录表明错误)。
我怀疑我 运行 遇到了一些 UAC 问题,这是 IOException 的真正原因。 windows 服务 运行 作为 LocalSystem。所有这些问题:应该 我如何从 windows 服务中 运行 更新 exe,以便它有权复制 c:\Program Files 中的文件?
更新
正如评论和答案所暗示的那样,Process.Start 可以工作,但有一些细微差别。您必须启动 cmd.exe 并使用 it 来启动更新程序。我还发现我无法使用更新程序 exe 的完整路径,我需要设置 UseShellExecute=false。这是我从 .NET 服务启动更新程序的最终工作代码:
var cmd = "/c start updater.exe";
var startInfo = new ProcessStartInfo("cmd.exe");
startInfo.Arguments = cmd;
startInfo.WorkingDirectory = AssemblyDirectory;
startInfo.UseShellExecute = false;
var proc = Process.Start(startInfo);
我确实做了这件事 - 使用更简单(有些人可能会说笨拙)的方法。服务:
- 生成批处理命令,
- 将新的可执行文件下载到暂存位置,
- 启动一个进程:cmd.exe 依次运行批处理脚本 w/o 等待它完成,然后
- 立即自行终止。
批处理命令:
- Ping 127.0.0.1 五次,
- 将可执行文件复制到最终位置,
- 重新启动服务。
像发条一样工作。 ping 是一个可靠的 5 秒延迟 - 让服务在复制文件之前关闭。
编辑:
只是为了完整性 - 我意识到通过批处理 cmd 正在 ping 127.0.0.1 而不是 128.0.0.1 所以我编辑了这个答案以反映这一点。我想两者都可以工作——但是 128.0.0.1 ping 超时,其中 127.0.0.1 解析为 "me"。由于我只是将它用作穷人的延迟,因此无论哪种方式都可以达到目的。
我有一个 windows 服务,我想自动更新它。我开始使用 wyBuild 来实现它,但遇到了一些问题,因此决定尝试构建我自己的。我写了一个独立的 exe,可以调用它来执行更新过程:检查是否有更新的新 zip 文件,下载它,解压缩,停止 windows 服务,从 zip 复制文件,然后重新启动服务。当我从命令行 运行 它时,这个 exe 工作正常并且并不难写。
但是,现在我希望服务(正在更新的同一服务)shell 输出到更新程序 exe 以更新自身。我首先尝试 Process.Start:
var proc = Process.Start(pathToUpdaterExe);
proc.WaitForExit(60000);
这调用了更新程序,但是当更新程序停止服务时,进程被杀死并且更新停止。我做了一些搜索,听起来解决方案是使用单独的 AppDomain。这就是我现在拥有的:
Evidence baseEvidence = AppDomain.CurrentDomain.Evidence;
Evidence objEvidence = new System.Security.Policy.Evidence(baseEvidence);
AppDomainSetup setup = new AppDomainSetup();
var updateDomain = AppDomain.CreateDomain("updateDomain", objEvidence, setup);
updateDomain.ExecuteAssembly(updater);
AppDomain.Unload(updateDomain);
但是,现在我在尝试复制新 Service.dll
时收到错误 System.IO.IOException: "The process cannot access the file 'C:\Program Files (x86)\Company\Service\Service.dll' because it is being used by another process"同样,此时我已停止 服务。我已经通过日志记录确认了这一点。我无法想象什么 Service.dll 仍然被锁定,所以我添加了代码来检查是什么锁定了它:
public static IEnumerable<Process> GetProcessesLocking(string filePath)
{
var result = new List<Process>();
result.Clear();
var processes = Process.GetProcesses();
foreach (Process proc in processes)
{
try
{
if (proc.HasExited) continue;
foreach (ProcessModule module in proc.Modules)
{
if ((module.FileName.ToLower().CompareTo(filePath.ToLower()) == 0))
{
result.Add(proc);
break;
}
}
}
catch (Exception ex)
{
Log(ex.ToString());
Log("There was an error checking " + proc.ProcessName );
}
}
return result;
}
然而,此代码表明没有任何东西锁定 dll(结果为空,没有任何记录表明错误)。
我怀疑我 运行 遇到了一些 UAC 问题,这是 IOException 的真正原因。 windows 服务 运行 作为 LocalSystem。所有这些问题:应该 我如何从 windows 服务中 运行 更新 exe,以便它有权复制 c:\Program Files 中的文件?
更新
正如评论和答案所暗示的那样,Process.Start 可以工作,但有一些细微差别。您必须启动 cmd.exe 并使用 it 来启动更新程序。我还发现我无法使用更新程序 exe 的完整路径,我需要设置 UseShellExecute=false。这是我从 .NET 服务启动更新程序的最终工作代码:
var cmd = "/c start updater.exe";
var startInfo = new ProcessStartInfo("cmd.exe");
startInfo.Arguments = cmd;
startInfo.WorkingDirectory = AssemblyDirectory;
startInfo.UseShellExecute = false;
var proc = Process.Start(startInfo);
我确实做了这件事 - 使用更简单(有些人可能会说笨拙)的方法。服务:
- 生成批处理命令,
- 将新的可执行文件下载到暂存位置,
- 启动一个进程:cmd.exe 依次运行批处理脚本 w/o 等待它完成,然后
- 立即自行终止。
批处理命令:
- Ping 127.0.0.1 五次,
- 将可执行文件复制到最终位置,
- 重新启动服务。
像发条一样工作。 ping 是一个可靠的 5 秒延迟 - 让服务在复制文件之前关闭。
编辑:
只是为了完整性 - 我意识到通过批处理 cmd 正在 ping 127.0.0.1 而不是 128.0.0.1 所以我编辑了这个答案以反映这一点。我想两者都可以工作——但是 128.0.0.1 ping 超时,其中 127.0.0.1 解析为 "me"。由于我只是将它用作穷人的延迟,因此无论哪种方式都可以达到目的。