如何使用C#从Windows服务运行EXE程序?

xoo*_*ops 43 .net c# windows windows-services process.start

如何EXE使用C#从Windows服务运行程序?

这是我的代码:

System.Diagnostics.Process.Start(@"E:\PROJECT XL\INI SQLLOADER\ConsoleApplication2\ConsoleApplication2\ConsoleApplication2\bin\Debug\ConsoleApplication2.exe");
Run Code Online (Sandbox Code Playgroud)

当我运行此服务时,应用程序无法启动.
我的代码出了什么问题?

Cod*_*ray 65

这将永远不会工作,至少不在Windows Vista或更高版本下.关键问题是您尝试在Windows服务中执行此操作,而不是标准Windows应用程序.您显示的代码将在Windows窗体,WPF或控制台应用程序中完美运行,但在Windows服务中根本不起作用.

Windows服务无法启动其他应用程序,因为它们未在任何特定用户的上下文中运行.与常规Windows应用程序不同,服务现在在隔离会话中运行,并且禁止与用户或桌面交互.这使得应用程序无法运行.

有关这些相关问题的答案中提供了更多信息:

正如您现在可能已经想到的那样,解决您问题的最佳方法是创建标准Windows应用程序而不是服务.这些设计旨在由特定用户运行,并与该用户的桌面相关联.这样,您可以使用已经显示的代码随时运行其他应用程序.

假设您的控制台应用程序不需要任何类型的接口或输出,另一种可能的解决方案是指示进程不要创建窗口.这将阻止Windows阻止您的进程创建,因为它将不再请求创建控制台窗口.您可以在相关问题的答案中找到相关代码.

  • 根据EXE的名称,我猜他正在尝试启动一个控制台应用程序,因此不希望与它进行交互 - 所以从服务运行它没有问题. (4认同)
  • @Gabe:你是对的;我们双方都在做出可能正确也可能不正确的假设。我想,我不会发布通常被忽略的澄清请求,而是发布一个答案。我的水晶球告诉我,这确实是问题所在,就像以前的许多其他问题一样。无论如何,它都值得了解。至于应用程序不需要显示 UI 的可能性,您可以考虑[此答案]中提出的解决方案(http://stackoverflow.com/questions/1369236/how-to-run-console-application-来自 Windows 服务/1369252#1369252)。 (2认同)
  • @Cody:“ Windows服务无法启动其他应用程序,因为它们没有在任何特定用户的上下文中运行”-这不是正确的,每个服务都有用户上下文,可以是系统帐户或普通用户。 (2认同)
  • @Turek:我想你错过了这一点.这是因为它们不能在具有桌面的用户上下文中运行,因此无法从Windows服务显示UI.由于控制台应用程序尝试打开控制台窗口,因此它们不仅可以正常工作.但是在Vista及更高版本中,服务肯定不会在普通用户的环境中运行.永远. (2认同)

Pra*_*nan 11

我试过这篇 Code Project,它对我来说很好.我也使用过代码.文章在截图中的解释非常好.

我正在为这个场景添加必要的解释

您刚刚启动计算机并即将登录.登录时,系统会为您分配唯一的会话ID.在Windows Vista中,操作系统会为第一个登录计算机的用户分配一个会话ID为1.下一个登录用户将被分配一个会话ID为2.依此类推.您可以从"任务管理器"的"用户"选项卡中查看分配给每个登录用户的会话ID. 在此输入图像描述

但是您的Windows服务会议ID为0.此会话与其他会话隔离.这最终会阻止Windows服务调用在用户会话(如1或2)下运行的应用程序.

要从Windows服务调用应用程序,您需要从winlogon.exe复制控件,该控件充当当前登录用户,如下面的屏幕截图所示. 在此输入图像描述

重要代码

// obtain the process id of the winlogon process that 
// is running within the currently active session
Process[] processes = Process.GetProcessesByName("winlogon");
foreach (Process p in processes)
{
    if ((uint)p.SessionId == dwSessionId)
    {
        winlogonPid = (uint)p.Id;
    }
}

// obtain a handle to the winlogon process
hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);

// obtain a handle to the access token of the winlogon process
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
{
    CloseHandle(hProcess);
    return false;
}

// Security attibute structure used in DuplicateTokenEx and   CreateProcessAsUser
// I would prefer to not have to use a security attribute variable and to just 
// simply pass null and inherit (by default) the security attributes
// of the existing token. However, in C# structures are value types and   therefore
// cannot be assigned the null value.
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);

// copy the access token of the winlogon process; 
// the newly created token will be a primary token
if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, 
    (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, 
    (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
    {
      CloseHandle(hProcess);
      CloseHandle(hPToken);
      return false;
    }

 STARTUPINFO si = new STARTUPINFO();
 si.cb = (int)Marshal.SizeOf(si);

// interactive window station parameter; basically this indicates 
// that the process created can display a GUI on the desktop
si.lpDesktop = @"winsta0\default";

// flags that specify the priority and creation method of the process
int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;

// create a new process in the current User's logon session
 bool result = CreateProcessAsUser(hUserTokenDup,  // client's access token
                            null,             // file to execute
                            applicationName,  // command line
                            ref sa,           // pointer to process    SECURITY_ATTRIBUTES
                            ref sa,           // pointer to thread SECURITY_ATTRIBUTES
                            false,            // handles are not inheritable
                            dwCreationFlags,  // creation flags
                            IntPtr.Zero,      // pointer to new environment block 
                            null,             // name of current directory 
                            ref si,           // pointer to STARTUPINFO structure
                            out procInfo      // receives information about new process
                            );
Run Code Online (Sandbox Code Playgroud)


Rah*_*abi 5

您可以从Windows任务计划程序中使用此工具,有许多类似TaskScheduler的库可以为您提供帮助。

例如,考虑我们要计划一个任务,该任务将在五秒钟后执行一次:

using (var ts = new TaskService())
        {

            var t = ts.Execute("notepad.exe")
                .Once()
                .Starting(DateTime.Now.AddSeconds(5))
                .AsTask("myTask");

        }
Run Code Online (Sandbox Code Playgroud)

notepad.exe将在五秒钟后执行。

有关详细信息,请访问Wiki。

如果您知道该程序集中需要哪个类和方法,则可以这样自己调用它:

        Assembly assembly = Assembly.LoadFrom("yourApp.exe");
        Type[] types = assembly.GetTypes();
        foreach (Type t in types)
        {
            if (t.Name == "YourClass")
            {
                MethodInfo method = t.GetMethod("YourMethod",
                BindingFlags.Public | BindingFlags.Instance);
                if (method != null)
                {
                    ParameterInfo[] parameters = method.GetParameters();
                    object classInstance = Activator.CreateInstance(t, null);

                    var result = method.Invoke(classInstance, parameters.Length == 0 ? null : parameters);

                    break;
                }
            }

        }
Run Code Online (Sandbox Code Playgroud)

  • 我不确定为什么这个答案没有得到更多关注。Pranesh 的回答建议在系统帐户下运行 Win 服务,这是大多数公司环境中不可用的选项。这个(Rahmat 的)答案(仅使用 Task Scheduler 的部分)非常简单,很有趣 - 并且在大多数情况下都有效。 (2认同)
  • 它正在系统用户下运行任务/exe,因为我们想在当前登录用户下运行它,有什么帮助吗? (2认同)

Cod*_*ife 5

大多数赞成的最佳答案并没有错,但仍然与我要发布的内容相反。我说启动 exe 文件完全可以工作,您可以在任何用户的上下文中执行此操作。从逻辑上讲,您不能拥有任何用户界面或要求用户输入...

这是我的建议:

  1. 创建一个简单的控制台应用程序,该应用程序在没有用户交互的情况下执行您的服务应在启动时正确执行的操作。我真的不建议使用 Windows Service 项目类型,特别是因为您(当前)不能使用 .NET Core。
  2. 添加代码以启动要从服务调用的 exe

启动示例,例如 plink.exe。你甚至可以听输出:

var psi = new ProcessStartInfo()
{
    FileName = "./Client/plink.exe", //path to your *.exe
    Arguments = "-telnet -P 23 127.0.0.1 -l myUsername -raw", //arguments
    RedirectStandardError = true,
    RedirectStandardOutput = true,
    RedirectStandardInput = true,
    UseShellExecute = false,
    CreateNoWindow = true //no window, you can't show it anyway
};

var p = Process.Start(psi);
Run Code Online (Sandbox Code Playgroud)
  1. 使用 NSSM(Non-Sucking Service Manager)将该控制台应用程序注册为服务。NSSM 可以通过命令行进行控制,并且可以显示 UI 来配置服务,或者您可以通过命令行进行配置。如果您知道该用户的登录数据,则可以在该用户的上下文中运行该服务。

我使用了默认的 LocalSystem 帐户,而不是本地服务。它无需输入特定用户的登录信息即可正常工作。如果您需要更高的权限,我什至没有勾选“允许服务与桌面交互”复选框。

允许服务与桌面交互的选项

最后,我只想说最有趣的是,最重要的答案与我的答案完全相反,但我们俩仍然是对的,这只是您解释问题的方式:-D。如果你现在说但你不能使用 Windows 服务项目类型 - 你可以,但我之前有这个并且安装很粗略,在我找到 NSSM 之前这可能是一种无意的黑客攻击。


Sha*_*ain 0

我认为您正在将 .exe 复制到不同的位置。我猜这可能是问题所在。当您复制 exe 时,您并未复制其依赖项。

所以,你可以做的就是,将所有依赖的dll放入GAC中,以便任何.net exe都可以访问它

否则,请勿将 exe 复制到新位置。只需创建一个环境变量并在 C# 中调用 exe 即可。由于路径是在环境变量中定义的,因此您的 C# 程序可以访问该 exe。

更新:

以前,我在 c#.net 3.5 项目中遇到了某种相同的问题,在该项目中,我试图从 c#.net 代码运行 .exe 文件,而该 exe 只不过是另一个项目 exe(其中我为我的项目添加了一些支持 dll)功能)以及我在 exe 应用程序中使用的那些 dll 方法。最后,我通过将该应用程序创建为同一解决方案的单独项目来解决此问题,并将该项目输出添加到我的部署项目中。根据这个场景我回答说,如果不是他想要的那我非常抱歉。

  • 是什么给您留下了 EXE 被复制到其他位置的印象? (2认同)