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

Posted

技术标签:

【中文标题】如何使用 C# 从 Windows 服务运行 EXE 程序?【英文标题】:How can I run an EXE program from a Windows Service using C#? 【发布时间】:2011-07-15 13:04:05 【问题描述】:

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

这是我的代码:

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

当我运行此服务时,应用程序没有启动。 我的代码有什么问题?

【问题讨论】:

据我所知,编写的代码没有任何问题,您是否遇到异常? 告诉我们当你运行这个程序时发生了什么? 这在 2019 年 6 月在 Visual Studio 中使用 .NET 4.7.2 应用程序工作:code.msdn.microsoft.com/windowsapps/… @xoops 你有没有找到任何解决方案我面临同样的问题。 也请查看这篇文章:codeproject.com/Tips/1071738/… 【参考方案1】:

这永远行不通,至少在 Windows Vista 或更高版本下不行。关键问题是您试图从 Windows 服务中执行此操作,而不是标准 Windows 应用程序。您展示的代码可以在 Windows 窗体、WPF 或控制台应用程序中完美运行,但在 Windows 服务中根本无法运行。

Windows 服务无法启动其他应用程序,因为它们没有在任何特定用户的上下文中运行。与常规 Windows 应用程序不同,services are now run in an isolated session 被禁止与用户或桌面交互。这样就没有地方让应用程序运行了。

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

How can a Windows Service start a process when a Timer event is raised? which process in windows is user specific? windows service (allow service to interact with desktop)

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

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

【讨论】:

根据 EXE 的名称,我猜他正在尝试启动一个控制台应用程序,因此不希望与它进行交互——所以从一个运行它没有问题服务。 @Gabe:控制台应用程序仍然显示一个界面。如果没有桌面,该界面应该显示在哪里? @Gabe:你是对的;我们俩都在做出可能正确也可能不正确的假设。我想,与其发布几乎总是被忽略的通常的澄清请求,不如发布一个答案。我的水晶球说这确实是问题所在,就像以前许多其他人一样。无论如何都值得知道。至于应用程序不需要显示UI的可能性,您可以考虑this answer中提出的解决方案。 @Cody:“Windows 服务无法启动其他应用程序,因为它们没有在任何特定用户的上下文中运行” - 这不是真的,每个服务都有一个用户上下文,可以是系统帐户或普通用户。 @Turek:我认为你没有抓住重点。这是因为您无法从 Windows 服务显示 UI,因为它们没有在具有桌面的用户上下文中运行。由于控制台应用程序试图打开控制台窗口,它们不会正常工作。但是在 Vista 和更高版本下,服务肯定不会在普通用户的上下文中运行。永远。【参考方案2】:

我已经尝试过这篇文章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
                            );

【讨论】:

非常适合我。在通过 Windows 服务请求扫描时,我能够显示来自扫描仪的错误。谢谢 如果是,请为解决方案 +1。 @stambikk 我在windows 7下用过 @stambikk 我在 Windows 10 上使用它 @AntaoAlmada - 我需要更改这一行吗 si.lpDesktop = @"winsta0\default";让它在 Windows 10 上运行?因为它在 Windows 10 上不适合我【参考方案3】:

您可以使用 Windows 任务计划程序来实现此目的,有许多库(如 TaskScheduler)可以帮助您。

例如,假设我们要安排一个将在 5 秒后执行一次的任务:

using (var ts = new TaskService())
        

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

        

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;
                
            
                         
        
    

【讨论】:

我不确定为什么这个答案没有得到更多关注。 Pranesh 的回答建议在系统帐户下运行 Win 服务,该选项在大多数公司环境中不可用。这个(Rahmat 的)答案(仅使用任务计划程序的部分)非常简单,很有趣 - 它在大多数情况下都有效。 它在系统用户下运行task/exe,我们想在当前登录用户下运行它,有什么帮助吗? 是的,你可以看看Creating tasks that run as a different user 如果我们不知道用户凭据怎么办? @RahmatAnjirabi @BahadırÖz 试试这种方式 Creating tasks that run as a system account 我希望这项技术能有所帮助。【参考方案4】:

获得最多支持的最佳答案并没有错,但仍然与我要发布的相反。我说它完全可以运行来启动一个 exe 文件,您可以在任何用户的上下文中执行此操作。从逻辑上讲,您不能拥有任何用户界面或要求用户输入...

这是我的建议:

    创建一个简单的控制台应用程序,该应用程序执行您的服务应在启动时立即执行的操作,而无需用户交互。我真的建议不要使用 Windows 服务项目类型,尤其是因为您(当前)不能使用 .NET Core。 添加代码以启动您要从服务调用的 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);
    使用 NSSM(非吸吮服务管理器)将该控制台应用程序注册为服务。 NSSM 可以通过命令行控制,并且可以显示一个 UI 来配置服务,或者您可以通过命令行对其进行配置。如果您知道该用户的登录数据,则可以在任何用户的上下文中运行该服务。

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

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

【讨论】:

【参考方案5】:

您可以在 Windows XP 中很好地执行来自 Windows 服务的 .exe。我过去自己做过。

您需要确保已在 Windows 服务属性中选中“允许与桌面交互”选项。如果不这样做,它将不会执行。

我需要检查 Windows 7 或 Vista,因为这些版本需要额外的安全权限,因此可能会引发错误,但我很确定它可以直接或间接实现。对于 XP,我确信我自己已经完成了。

【讨论】:

无论何时打开 exe,它都会在 win 7 上提示。【参考方案6】:

首先,我们将创建一个在 系统帐号。该服务将负责生成一个 当前活动用户会话中的交互过程。这 新创建的进程将显示一个 UI 并以完全管理员身份运行 权利。当第一个用户登录计算机时,该服务将 被启动并将在 Session0 中运行;然而这个过程 此服务生成将在当前的桌面上运行 登录用户。我们将此服务称为 LoaderService。

接下来,winlogon.exe进程负责管理用户登录 和注销程序。我们知道,每个登录到 计算机将有一个唯一的 Session ID 和一个对应的 winlogon.exe 进程与他们的 Session 相关联。现在,我们提到 上面,LoaderService 在 System 账户下运行。我们也 确认电脑上的每个winlogon.exe进程运行在 系统帐户。因为系统帐户是两者的所有者 LoaderService 和 winlogon.exe 进程,我们的 LoaderService 可以复制winlogon.exe进程的访问令牌(和Session ID) 然后调用 Win32 API 函数 CreateProcessAsUser 来启动一个 处理到登录用户的当前活动会话。自从 位于复制的访问令牌内的会话 ID winlogon.exe进程大于0,我们可以启动一个交互 使用该令牌进行处理。

试试这个。 Subverting Vista UAC in Both 32 and 64 bit Architectures

【讨论】:

【参考方案7】:

你应该查看这篇文章Impact of Session 0 Isolation on Services and Drivers in Windows并下载.docx file 并仔细阅读,这对我很有帮助。

但是,这是一个适合我的情况的类:

    [StructLayout(LayoutKind.Sequential)]
    internal struct PROCESS_INFORMATION
    
        public IntPtr hProcess;
        public IntPtr hThread;
        public uint dwProcessId;
        public uint dwThreadId;
    
    
    [StructLayout(LayoutKind.Sequential)]
    internal struct SECURITY_ATTRIBUTES
    
        public uint nLength;
        public IntPtr lpSecurityDescriptor;
        public bool bInheritHandle;
    


    [StructLayout(LayoutKind.Sequential)]
    public struct STARTUPINFO
    
        public uint cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public uint dwX;
        public uint dwY;
        public uint dwXSize;
        public uint dwYSize;
        public uint dwXCountChars;
        public uint dwYCountChars;
        public uint dwFillAttribute;
        public uint dwFlags;
        public short wShowWindow;
        public short cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;

    

    internal enum SECURITY_IMPERSONATION_LEVEL
    
        SecurityAnonymous,
        SecurityIdentification,
        SecurityImpersonation,
        SecurityDelegation
    

    internal enum TOKEN_TYPE
    
        TokenPrimary = 1,
        TokenImpersonation
    

    public static class ProcessAsUser
    

        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool CreateProcessAsUser(
            IntPtr hToken,
            string lpApplicationName,
            string lpCommandLine,
            ref SECURITY_ATTRIBUTES lpProcessAttributes,
            ref SECURITY_ATTRIBUTES lpThreadAttributes,
            bool bInheritHandles,
            uint dwCreationFlags,
            IntPtr lpEnvironment,
            string lpCurrentDirectory,
            ref STARTUPINFO lpStartupInfo,
            out PROCESS_INFORMATION lpProcessInformation);


        [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx", SetLastError = true)]
        private static extern bool DuplicateTokenEx(
            IntPtr hExistingToken,
            uint dwDesiredAccess,
            ref SECURITY_ATTRIBUTES lpThreadAttributes,
            Int32 ImpersonationLevel,
            Int32 dwTokenType,
            ref IntPtr phNewToken);


        [DllImport("advapi32.dll", SetLastError = true)]
        private static extern bool OpenProcessToken(
            IntPtr ProcessHandle,
            UInt32 DesiredAccess,
            ref IntPtr TokenHandle);

        [DllImport("userenv.dll", SetLastError = true)]
        private static extern bool CreateEnvironmentBlock(
                ref IntPtr lpEnvironment,
                IntPtr hToken,
                bool bInherit);


        [DllImport("userenv.dll", SetLastError = true)]
        private static extern bool DestroyEnvironmentBlock(
                IntPtr lpEnvironment);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool CloseHandle(
            IntPtr hObject);

        private const short SW_SHOW = 5;
        private const uint TOKEN_QUERY = 0x0008;
        private const uint TOKEN_DUPLICATE = 0x0002;
        private const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
        private const int GENERIC_ALL_ACCESS = 0x10000000;
        private const int STARTF_USESHOWWINDOW = 0x00000001;
        private const int STARTF_FORCEONFEEDBACK = 0x00000040;
        private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;


        private static bool LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock)
        
            bool result = false;


            PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
            SECURITY_ATTRIBUTES saProcess = new SECURITY_ATTRIBUTES();
            SECURITY_ATTRIBUTES saThread = new SECURITY_ATTRIBUTES();
            saProcess.nLength = (uint)Marshal.SizeOf(saProcess);
            saThread.nLength = (uint)Marshal.SizeOf(saThread);

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


            //if this member is NULL, the new process inherits the desktop
            //and window station of its parent process. If this member is
            //an empty string, the process does not inherit the desktop and
            //window station of its parent process; instead, the system
            //determines if a new desktop and window station need to be created.
            //If the impersonated user already has a desktop, the system uses the
            //existing desktop.

            si.lpDesktop = @"WinSta0\Default"; //Modify as needed
            si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
            si.wShowWindow = SW_SHOW;
            //Set other si properties as required.

            result = CreateProcessAsUser(
                token,
                null,
                cmdLine,
                ref saProcess,
                ref saThread,
                false,
                CREATE_UNICODE_ENVIRONMENT,
                envBlock,
                null,
                ref si,
                out pi);


            if (result == false)
            
                int error = Marshal.GetLastWin32Error();
                string message = String.Format("CreateProcessAsUser Error: 0", error);
                FilesUtilities.WriteLog(message,FilesUtilities.ErrorType.Info);

            

            return result;
        


        private static IntPtr GetPrimaryToken(int processId)
        
            IntPtr token = IntPtr.Zero;
            IntPtr primaryToken = IntPtr.Zero;
            bool retVal = false;
            Process p = null;

            try
            
                p = Process.GetProcessById(processId);
            

            catch (ArgumentException)
            

                string details = String.Format("ProcessID 0 Not Available", processId);
                FilesUtilities.WriteLog(details, FilesUtilities.ErrorType.Info);
                throw;
            


            //Gets impersonation token
            retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, ref token);
            if (retVal == true)
            

                SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
                sa.nLength = (uint)Marshal.SizeOf(sa);

                //Convert the impersonation token into Primary token
                retVal = DuplicateTokenEx(
                    token,
                    TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
                    ref sa,
                    (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                    (int)TOKEN_TYPE.TokenPrimary,
                    ref primaryToken);

                //Close the Token that was previously opened.
                CloseHandle(token);
                if (retVal == false)
                
                    string message = String.Format("DuplicateTokenEx Error: 0", Marshal.GetLastWin32Error());
                    FilesUtilities.WriteLog(message, FilesUtilities.ErrorType.Info);

                

            

            else
            

                string message = String.Format("OpenProcessToken Error: 0", Marshal.GetLastWin32Error());
                FilesUtilities.WriteLog(message, FilesUtilities.ErrorType.Info);

            

            //We'll Close this token after it is used.
            return primaryToken;

        

        private static IntPtr GetEnvironmentBlock(IntPtr token)
        

            IntPtr envBlock = IntPtr.Zero;
            bool retVal = CreateEnvironmentBlock(ref envBlock, token, false);
            if (retVal == false)
            

                //Environment Block, things like common paths to My Documents etc.
                //Will not be created if "false"
                //It should not adversley affect CreateProcessAsUser.

                string message = String.Format("CreateEnvironmentBlock Error: 0", Marshal.GetLastWin32Error());
                FilesUtilities.WriteLog(message, FilesUtilities.ErrorType.Info);

            
            return envBlock;
        

        public static bool Launch(string appCmdLine /*,int processId*/)
        

            bool ret = false;

            //Either specify the processID explicitly
            //Or try to get it from a process owned by the user.
            //In this case assuming there is only one explorer.exe

            Process[] ps = Process.GetProcessesByName("explorer");
            int processId = -1;//=processId
            if (ps.Length > 0)
            
                processId = ps[0].Id;
            

            if (processId > 1)
            
                IntPtr token = GetPrimaryToken(processId);

                if (token != IntPtr.Zero)
                

                    IntPtr envBlock = GetEnvironmentBlock(token);
                    ret = LaunchProcessAsUser(appCmdLine, token, envBlock);
                    if (envBlock != IntPtr.Zero)
                        DestroyEnvironmentBlock(envBlock);

                    CloseHandle(token);
                

            
            return ret;
        

    

要执行,只需像这样调用:

string szCmdline = "AbsolutePathToYourExe\\ExeNameWithoutExtension";
ProcessAsUser.Launch(szCmdline);

【讨论】:

【参考方案8】:

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

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

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

更新:

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

【讨论】:

是什么让您觉得 EXE 正在被复制到不同的位置?【参考方案9】:

System.Diagnostics.Process.Start("Exe Name");

【讨论】:

以上是关于如何使用 C# 从 Windows 服务运行 EXE 程序?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 C# 从 Windows Temp 文件夹中删除所有文件? (进程运行时)

c#如何使用progress

如何使用c#和windows服务创建无敌的windows应用程序

如何在基于 C# 的 Windows 服务中处理以不同时间间隔并行运行的多个任务?

如何从另一个桌面应用程序控制 Windows 服务

如何从 Windows 服务 C# 取消关机