安装后无法使用 WPF App 使用 SC.exe 创建 Windows 服务 C#

问题描述

我创建了一个 WPF 桌面应用程序和一个工作服务(所有 .NET 6.0 预览版 3),使用 Microsoft Visual Studio 安装程序项目扩展将它们打包在 .MSI 安装文件中,该扩展将在机器上安装 WPF 应用程序。

虽然应用程序安装并正常运行,但我必须以某种方式实现应在安装 WPF 应用程序后运行的服务安装。我为此创建了一个函数,它以管理员身份运行 sc.exe 并使用 Process.Start() 安装服务,如下所示:

private static void InstallService()
{
      const string ServiceName = "SomeService";
      var path = Path.GetFullPath(@".\SomeService.exe");

      var psi = new processstartinfo
      {
         FileName = @"C:\Windows\system32\sc.exe",Arguments = $"create { ServiceName } binPath= { path } start= auto",Verb = "runas",UseShellExecute = true,};

      try
      {
         Process.Start(psi);
      }
      catch (Exception ex)
      {
         MessageBox.Show($"Installation has Failed: { ex.Message + ex.StackTrace }");
      }
}

函数的问题在于,当应用程序在 Visual Studio 中运行时以及从 Visual Studio 创建的 'bin\Release' 文件夹中运行时,它都能正常执行。然后该服务安装并且可以启动。但是,当使用 .MSI 包安装程序时,服务不会安装并且不显示MessageBox,这表明没有抛出异常 .

我尝试过的:

  • 执行该函数时,会显示 UAC 提示,然后显示 过程开始。我尝试将整个应用程序运行为 管理员,但这并没有解决问题。

  • 我还尝试将“bin\Release”目录中的所有文件复制到安装应用程序的目录中,并用“bin\Release”中的文件替换每个文件,这样两个目录都应该是一样,但这也没有解决问题。

安装函数执行后,服务应该用另一个函数来启动它:

private static void RunService()
{
      const string ServiceName = "SomeService";

      var psi = new processstartinfo
      {
           FileName = @"C:\Windows\system32\sc.exe",Arguments = $"start { ServiceName }",};

      try
      {
           Process.Start(psi);
      }
      catch (Exception ex)
      {
           MessageBox.Show($"Running was not approved or Failed: { ex.Message }");
      }
}

然而,此功能在两种情况下都能正常运行,尽管很明显只有在先前安装了该服务时才能使用,而这在 .MSI 安装的应用程序中无法完成。至于使用Process.Start()代替ServiceController类,应用认不应该以管理员身份运行,而使用ServiceController是不可能的,所以我将 Process.Start()Verb = "runas" 一起使用,它以管理员身份运行进程,仅在需要时显示 UAC 提示(仅在服务尚未运行时才启动)。

有没有办法解决这个问题并在 .MSI 安装的 WPF 应用程序中安装一个工作服务?

解决方法

当我进一步分析所有可能的因素时,我终于注意到了导致这个问题的原因。

通常,Visual Studio 生成的路径没有任何空格,因此它们可以作为命令参数编写,无需双引号。在我的例子中,包含项目文件的路径也没有任何空格,这导致没有双引号的命令可以正常执行。然而,安装路径确实包含空格,因为它被设计为更加用户友好,这导致这段代码无法按预期执行:

// the path parameter in the command will end when there will be a space somewhere in the path

Arguments = $"create { ServiceName } binPath= { path } start= auto"

path 变量仅包含完整路径,不包含在双引号中。

为防止出现此问题,必须使用双引号,并且包含 \ 符号会通知编译器这些双引号是字符串的一部分:

// the path parameter is wrapped in double quotes and will be fully read

Arguments = $"create { ServiceName } binPath= \"{ path }\" start= auto"

当路径完全写入代码时,丢失的双引号很容易被注意到。然而,当使用字符串插值时,它可能会导致问题,就像我的情况一样。