从服务执行Shell命令

6

我正在尝试在我创建的C#服务中执行一个shell命令。然而,这个命令似乎不能被执行。作为一个标准的控制台应用程序,它却能够完美地工作,因此我知道命令本身或从代码中执行的方式都没有问题。有谁能告诉我这为什么不起作用吗?请记住我对C#还很新,所以这可能只是我的经验问题。以下是来自服务本身的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Management;
using System.Diagnostics;
using System.ServiceProcess;
using System.Threading;

namespace AdapterDisableTest
{
    class Program : ServiceBase
    {
        //private static Timer workTimer;

        static void Main(string[] args)
        {
            ServiceBase.Run(new Program());
        }

        public Program()
        {
            this.ServiceName = "AdapterDisableTest";
        }

        protected override void OnStart(string[] args)
        {
            base.OnStart(args);

            Process myProcess = new Process();

            myProcess.StartInfo.FileName = @"C:\Program Files\Oracle\VirtualBox\VBoxManage.exe";
            myProcess.StartInfo.Arguments = "controlvm test setlinkstate1 off";
            myProcess.StartInfo.UseShellExecute = false;
            myProcess.StartInfo.CreateNoWindow = true;
            myProcess.Start();

        }

        protected override void OnStop()
        {
            base.OnStop();

            //TODO: clean up any variables and stop any threads
        }

    }
}

1
你要启动的进程是否是交互式的(即它是否有用户界面)?如果是,那就不太好了——请查看这个类似的线程,其中包含一些非常有用的信息:https://dev59.com/P2855IYBdhLWcg3wyniC。 - Martin
顺便提一下,如果它是一个交互式进程,你仍然可以从服务中启动它,但这需要使用Win32 API,而不是在.Net中使用内置的Process功能。 - Martin
不,我尝试运行的应用程序没有GUI界面(VirtualBox本身有GUI界面,但这个实用程序完全由cli驱动)。我不太确定您所说的交互式服务是什么意思,但我可以告诉您,该应用程序接受参数,立即执行它们,然后从命令提示符中自我终止。我假设这就是您需要确定的内容? - Kevin Mills
在进程的StartInfo中添加一个工作目录可能是值得的,因为从服务启动进程时,起始文件夹的行为是不同的 - 尝试设置StartInfo.WorkingDirectory = @"C:\Program Files\Oracle\VirtualBox"并查看发生了什么。 - Martin
好的想法,但不幸的是那也没起作用。 - Kevin Mills
2个回答

0

如果它是一个需要桌面的Windows应用程序,你基本上就没戏了。如果它是一个标准控制台应用程序,你需要重定向标准输入、标准输出和标准错误。你还需要将ProcessStartInfoUseShellExecute属性设置为false:因为你的服务无法启动操作系统shell(cmd.exe),因为那需要访问桌面...而且,如果该属性设置为true,你也无法重定向标准输入。

标准输入通常连接到键盘。你的服务无法访问键盘。标准输出和标准错误通常连接到cmd.exe控制台窗口。

一旦你重定向了标准输出和标准错误,你需要将处理程序连接到这些Process对象事件:

  • Exited当进程结束时引发。
  • OutputDataReceived当进程通过标准输出写入数据时引发(实际上,在任何缓冲区刷新输出流时发生,因此可能会滞后于实际的写操作)。
  • ErrorDataReceived当进程写入标准错误时引发。

我倾向于将标准输出和标准错误重定向到同一个流,并确保该流是无缓冲的。这相当于 cmd.exe 的咒语。

some-command 2>&1

将标准输出和标准错误重定向到同一个文件句柄。


这个工具不需要桌面环境,它只是一个标准的控制台应用程序,使用我指定的一些参数来执行,例如 myProcess.StartInfo.Arguments = "controlvm test setlinkstate1 off"。很抱歉,你的解释有点超出了我的理解范围。你能否再简单地解释一下? - Kevin Mills
这就是我所说的。除非你指定 UseShellExecute = false;,否则你的可执行进程将在 CMD.EXE 的一个实例下启动。这需要一个桌面,而你的服务无法访问它。 - Nicholas Carey
这已经在以下行中设置:myProcess.StartInfo.UseShellExecute = false - Kevin Mills

0
事实证明,这个问题之所以无法解决,是由于应用程序兼容性-会话0隔离。本质上,服务启动了进程,但它被启动在会话0中,因此对用户而言是隐藏的,用户无法与其交互。
为了绕过这个限制,我能够按照Pero Matić撰写的在32位和64位架构中规避Vista UAC指南进行操作。这个简短的指南让我能够从服务中生成一个进程,放在当前登录用户的会话中,而不是在会话0中。可惜的是,我不再拥有完成代码,因为我使用它的项目已经取消了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接