C# window Service实现调用有UI的应用程序(关于win xp以后的window系统)

用服务去打开一个UI程序,是不可能的,服务后台的进程所使用的用户是system,这个用户是不需要UI的,因此也就限制了打开有UI线程的应用。因此,你要打开一个UI线程,必须使用一个管理员权限的账号去打开程序,默认一个程序去打开另一个程序,后一个程序其使用的win账号是前一个程序的win账号。

我开发的系统中有一接口程序(这里就称Task,是一个C#的Console Application)经常无故的死掉,导致第二天的数据不能正常解析,所以,我写了一个window service去监视Task,如果发现Task在进程列表中不存在或线程数少于两个(Task为多线程程序),就重新调起Task。

  开始没接触过window service调用application的例子,在网上查了下,百度的实现方法大致都是直接初始一个新的进程实例,然后将要调用的程序路径赋给这个新的进程实例,最后启动进程。这样写了后,我在window server 2008(R2)系统或window 7的任务管理器中能看到被调用程序的进程,但是没有被调用程序的UI。这问题一直耽搁我好长时间,最后在google中看到Pero Mati? 写的一篇文章 Subverting Vista UAC in Both 32 and 64 bit Architectures

  原文章地址为:http://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-32-and-64-bit-Archite

  这里也很感谢作者,我在google和bing中搜了好多都没能解决问题。

  原来问题在于,xp系统的用户和window service运行在一个session下,在xp以后,windows系统改变了用户会话管理的策略,window service独立运行在session0下,依次给后续的登录用户分配sessionX(X =1,2,3...),session0没有权限运行UI。所以在window xp以后的系统下,window service调用有UI的application时只能看到程序进程但不能运行程序的UI。

  原文章Pero Mati?给出了详细的解释和代码,请大家去仔细阅读,这里我只想谢我自己的解决过程中的问题

  作者的解决思路是:window service创建一个和与当前登陆用户可以交互的进程,这个进程运行在admin权限下,能够调起应用程序的UI

  具体的做法是:widow service复制winlogon.exe进程句柄,然后通过调用api函数CreateProcessAsUser()以winlogon.exe权限创建新进程,新创建的进程有winlogon.exe的权限(winlogon.exe运行在system权限下),负责调用程序。

  这里CreateProcessAsUser()是以用户方式创建新的进程,winlogon.exe进程是负责管理用户登录的进程,每个用用户登录系统后都会分配一个winlogon.exe进程,winlogon.exe与用户的session ID关联。以下是作者原文,作者的描述很详细很到位,这也是我贴出来作者原文的目的。^_^    ^_^

  作者原文:

First, we are going to create a Windows Service that runs under the System account. This service will be responsible for spawning an interactive process within the currently active User’s Session. This newly created process will display a UI and run with full admin rights. When the first User logs on to the computer, this service will be started and will be running in Session0; however the process that this service spawns will be running on the desktop of the currently logged on User. We will refer to this service as the LoaderService.

Next, the winlogon.exe process is responsible for managing User login and logout procedures. We know that every User who logs on to the computer will have a unique Session ID and a corresponding winlogon.exe process associated with their Session. Now, we mentioned above, the LoaderService runs under the System account. We also confirmed that each winlogon.exe process on the computer runs under the System account. Because the System account is the owner of both the LoaderService and the winlogon.exe processes, our LoaderService can copy the access token (and Session ID) of the winlogon.exe process and then call the Win32 API function CreateProcessAsUser to launch a process into the currently active Session of the logged on User. Since the Session ID located within the access token of the copied winlogon.exe process is greater than 0, we can launch an interactive process using that token.

  

  我以作者的源代码实现后在win7下完美的调起了Task的UI。但当我部署到服务器(服务器系统是window server2008r2)时出问题了,怎么都调不起Task程序的UI,甚至连Task的进程都看不到了。之后我通过测试程序,看到在服务器上登陆了三个用户,也分配了三个session,我所登陆账户的session id为3,但却发现存在4个winlogon.exe进程,通过我所登陆的session id关联到的winlogon.exe进程的id却是4,???这问题让我彻底的乱了...(现在依然寻找这问题的答案)

  winlogon.exe不靠谱,我只能通过拷贝其他进程的句柄创建用于调用程序UI的进程,找了半天发现explorer.exe桌面进程肯定运行在用户下,所以尝试了用explorer.exe进程替代winlogon.exe,测试后,在win7 和window server 2008r2下都能完美调用Task的UI。

启动程序的代码:

 //启动Task程序
                ApplicationLoader.PROCESS_INFORMATION procInfo;
                ApplicationLoader.StartProcessAndBypassUAC(applicationName, out procInfo);

  

ApplicationLoader类

using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace WS_Monitor_Task_CSharp
{

     /// <summary>
     /// Class that allows running applications with full admin rights. In
     /// addition the application launched will bypass the Vista UAC prompt.
     /// </summary>
    public  class ApplicationLoader
    {

        #region Structrures

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        {
            public int Length;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct STARTUPINFO
        {
            public int 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;

        }

         [StructLayout(LayoutKind.Sequential)]
          public struct PROCESS_INFORMATION
          {
              public IntPtr hProcess;
              public IntPtr hThread;
              public uint dwProcessId;
              public uint dwThreadId;
          }

          #endregion

          #region Enumberation
          enum TOKEN_TYPE : int
          {
              TokenPrimary = 1,
              TokenImpersonation = 2
          }

          enum SECURITY_IMPERSONATION_LEVEL : int
          {
              SecurityAnonymous = 0,
              SecurityIdentification = 1,
              SecurityImpersonation = 2,
              SecurityDelegation = 3,
          }

          #endregion

          #region Constants

          public const int TOKEN_DUPLICATE = 0x0002;
          public const uint MAXIMUM_ALLOWED = 0x2000000;
          public const int CREATE_NEW_CONSOLE = 0x00000010;

          public const int IDLE_PRIORITY_CLASS = 0x40;
          public const int NORMAL_PRIORITY_CLASS = 0x20;
          public const int HIGH_PRIORITY_CLASS = 0x80;
          public const int REALTIME_PRIORITY_CLASS = 0x100;

          #endregion

          #region Win32 API Imports

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

          [DllImport("kernel32.dll")]
          static extern uint WTSGetActiveConsoleSessionId();

          [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
          public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
             ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment,
            String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);

          [DllImport("kernel32.dll")]
          static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId);

          [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
          public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
              ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,
               int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);

          [DllImport("kernel32.dll")]
          static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

          [DllImport("advapi32", SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
          static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, ref IntPtr TokenHandle);

          #endregion

          /// <summary>
          /// Launches the given application with full admin rights, and in addition bypasses the Vista UAC prompt
          /// </summary>
          /// <param name="applicationName">The name of the application to launch</param>
          /// <param name="procInfo">Process information regarding the launched application that gets returned to the caller</param>
          /// <returns></returns>
          public static bool StartProcessAndBypassUAC(String applicationName, out PROCESS_INFORMATION procInfo)
          {
              uint winlogonPid = 0;
              IntPtr hUserTokenDup = IntPtr.Zero,
                  hPToken = IntPtr.Zero,
                  hProcess = IntPtr.Zero;
              procInfo = new PROCESS_INFORMATION();

              // obtain the currently active session id; every logged on user in the system has a unique session id
              TSControl.WTS_SESSION_INFO[] pSessionInfo = TSControl.SessionEnumeration();
              uint dwSessionId = 100;
              for (int i = 0; i < pSessionInfo.Length; i++)
              {
                  if (pSessionInfo[i].SessionID != 0)
                  {
                      try
                      {
                          int count = 0;
                          IntPtr buffer = IntPtr.Zero;
                          StringBuilder sb = new StringBuilder();

                          bool bsuccess = TSControl.WTSQuerySessionInformation(
                             IntPtr.Zero, pSessionInfo[i].SessionID,
                             TSControl.WTSInfoClass.WTSUserName, out sb, out count);

                          if (bsuccess)
                          {
                              if (sb.ToString().Trim() == "dmpadmin")
                              {
                                  dwSessionId = (uint)pSessionInfo[i].SessionID;
                              }
                          }
                      }
                      catch (Exception ex)
                      {
                          LoaderService.WriteLog(ex.Message.ToString(),"Monitor");
                      }
                  }
              }

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

              LoaderService.WriteLog(winlogonPid.ToString(), "Monitor");

              // 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;
              }

              // By default CreateProcessAsUser creates a process on a non-interactive window station, meaning
              // the window station has a desktop that is invisible and the process is incapable of receiving
              // user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user
              // interaction with the new process.
              STARTUPINFO si = new STARTUPINFO();
              si.cb = (int)Marshal.SizeOf(si);
              si.lpDesktop = @"winsta0\default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop

              // 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
                                               );

              // invalidate the handles
              CloseHandle(hProcess);
              CloseHandle(hPToken);
              CloseHandle(hUserTokenDup);
              LoaderService.WriteLog("launch Task","Monitor");

              return result; // return the result
          }

      }
}

TSControl类

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace WS_Monitor_Task_CSharp
{
    public class TSControl
    {
        /**/
        /// <summary>
        /// Terminal Services API Functions,The WTSEnumerateSessions function retrieves a list of sessions on a specified terminal server,
        /// </summary>
        /// <param name="hServer">[in] Handle to a terminal server. Specify a handle opened by the WTSOpenServer function, or specify WTS_CURRENT_SERVER_HANDLE to indicate the terminal server on which your application is running</param>
        /// <param name="Reserved">Reserved; must be zero</param>
        /// <param name="Version">[in] Specifies the version of the enumeration request. Must be 1. </param>
        /// <param name="ppSessionInfo">[out] Pointer to a variable that receives a pointer to an array of WTS_SESSION_INFO structures. Each structure in the array contains information about a session on the specified terminal server. To free the returned buffer, call the WTSFreeMemory function.
        /// To be able to enumerate a session, you need to have the Query Information permission.</param>
        /// <param name="pCount">[out] Pointer to the variable that receives the number of WTS_SESSION_INFO structures returned in the ppSessionInfo buffer. </param>
        /// <returns>If the function succeeds, the return value is a nonzero value. If the function fails, the return value is zero</returns>
        [DllImport("wtsapi32", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool WTSEnumerateSessions(int hServer, int Reserved, int Version, ref long ppSessionInfo, ref int pCount);

        /**/
        /// <summary>
        /// Terminal Services API Functions,The WTSFreeMemory function frees memory allocated by a Terminal Services function.
        /// </summary>
        /// <param name="pMemory">[in] Pointer to the memory to free</param>
        [DllImport("wtsapi32.dll")]
        public static extern void WTSFreeMemory(System.IntPtr pMemory);

        /**/
        /// <summary>
        /// Terminal Services API Functions,The WTSLogoffSession function logs off a specified Terminal Services session.
        /// </summary>
        /// <param name="hServer">[in] Handle to a terminal server. Specify a handle opened by the WTSOpenServer function, or specify WTS_CURRENT_SERVER_HANDLE to indicate the terminal server on which your application is running. </param>
        /// <param name="SessionId">[in] A Terminal Services session identifier. To indicate the current session, specify WTS_CURRENT_SESSION. You can use the WTSEnumerateSessions function to retrieve the identifiers of all sessions on a specified terminal server.
        /// To be able to log off another user‘s session, you need to have the Reset permission </param>
        /// <param name="bWait">[in] Indicates whether the operation is synchronous.
        /// If bWait is TRUE, the function returns when the session is logged off.
        /// If bWait is FALSE, the function returns immediately.</param>
        /// <returns>If the function succeeds, the return value is a nonzero value.
        /// If the function fails, the return value is zero.</returns>
        [DllImport("wtsapi32.dll")]
        public static extern bool WTSLogoffSession(int hServer, long SessionId, bool bWait);

        [DllImport("Wtsapi32.dll")]
        public static extern bool WTSQuerySessionInformation(
            System.IntPtr hServer,
            int sessionId,
            WTSInfoClass wtsInfoClass,
            out StringBuilder ppBuffer,
            out int pBytesReturned
            );

        public enum WTSInfoClass
        {
            WTSInitialProgram,
            WTSApplicationName,
            WTSWorkingDirectory,
            WTSOEMId,
            WTSSessionId,
            WTSUserName,
            WTSWinStationName,
            WTSDomainName,
            WTSConnectState,
            WTSClientBuildNumber,
            WTSClientName,
            WTSClientDirectory,
            WTSClientProductId,
            WTSClientHardwareId,
            WTSClientAddress,
            WTSClientDisplay,
            WTSClientProtocolType
        }

        /**/
        /// <summary>
        /// The WTS_CONNECTSTATE_CLASS enumeration type contains INT values that indicate the connection state of a Terminal Services session.
        /// </summary>
        public enum WTS_CONNECTSTATE_CLASS
        {
            WTSActive,
            WTSConnected,
            WTSConnectQuery,
            WTSShadow,
            WTSDisconnected,
            WTSIdle,
            WTSListen,
            WTSReset,
            WTSDown,
            WTSInit,
        }

        /**/
        /// <summary>
        /// The WTS_SESSION_INFO structure contains information about a client session on a terminal server.
        /// if the WTS_SESSION_INFO.SessionID==0, it means that the SESSION is the local logon user‘s session.
        /// </summary>
        public struct WTS_SESSION_INFO
        {
            public int SessionID;
            [MarshalAs(UnmanagedType.LPTStr)]
            public string pWinStationName;
            public WTS_CONNECTSTATE_CLASS state;
        }

        /**/
        /// <summary>
        /// The SessionEnumeration function retrieves a list of
        ///WTS_SESSION_INFO on a current terminal server.
        /// </summary>
        /// <returns>a list of WTS_SESSION_INFO on a current terminal server</returns>
        public static WTS_SESSION_INFO[] SessionEnumeration()
        {
            //Set handle of terminal server as the current terminal server
            int hServer = 0;
            bool RetVal;
            long lpBuffer = 0;
            int Count = 0;
            long p;
            WTS_SESSION_INFO Session_Info = new WTS_SESSION_INFO();
            WTS_SESSION_INFO[] arrSessionInfo;
            RetVal = WTSEnumerateSessions(hServer, 0, 1, ref lpBuffer, ref Count);
            arrSessionInfo = new WTS_SESSION_INFO[0];
            if (RetVal)
            {
                arrSessionInfo = new WTS_SESSION_INFO[Count];
                int i;
                p = lpBuffer;
                for (i = 0; i < Count; i++)
                {
                    arrSessionInfo[i] =
                        (WTS_SESSION_INFO)Marshal.PtrToStructure(new IntPtr(p),
                        Session_Info.GetType());
                    p += Marshal.SizeOf(Session_Info.GetType());
                }
                WTSFreeMemory(new IntPtr(lpBuffer));
            }
            else
            {
                //Insert Error Reaction Here
            }
            return arrSessionInfo;
        }

        public TSControl()
        {
            //
            // TODO: 在此处添加构造函数逻辑
            // 

        }

    }
}

以上TSControl类为通过用户登录名获取用户session Id的类,ApplicationLoader类中大多是原作者的代码。

第一次发博客,语言组织的很乱,不正确或者大家不是很懂的地方请指出,我会重新修改或者直接回复的,希望这些东西对大家有用

  

原文地址:https://www.cnblogs.com/qi123/p/9583614.html

时间: 2024-08-07 21:41:43

C# window Service实现调用有UI的应用程序(关于win xp以后的window系统)的相关文章

WCF宿主Window Service Demo

尝试了下将服务寄宿在window 服务上.具体步骤如下 整个解决方案截图 一.创建window 服务 Wcf.WinService namespace Wcf.WinService { public partial class CalService : ServiceBase { public ServiceHost serviceHost = null; //服务宿主 public CalService() { InitializeComponent(); base.ServiceName =

SVN之二:配置window service自动启动

1.需求 系统启动,SVN的服务器进程也自动启动,解决系统重启后,需要手动启动svnserve的问题. 2.解决方法 将svnserve.exe添加window service,设置为自动启动,注意语法格式"="后面一定要有一个空格:具体如下: 添加服务 C:\>sc create svnserver binpath= "C:\Program Files\SlikSvn\bin\svnserve.exe --se rvice -r C:\test" displ

java打包成window service服务[转]

1 解释 java project  我说的是main方法作为程序入口的java工程,有别于 web project. 这样的工程 一般都是web project的附属扫描程序或一些独立的执行程序,如数据同步程序等.     把这样的project 要部署到生产机上去运行,这样就涉及到两个问题:         1 打包问题,我们一般不会把整个工程文件夹给放上去,一般做法是打一个jar包.        2 执行问题  最好的做法就是这些main方法程序的工程 对客户来说是透明的 就要求工程 发

【Window Service】关于Window Service的两三事

引言  Window Service通常用于寄宿WCF服务或者定时作业.下面记录一下它的用法. 创建 创建Window Service项目后,可以看到Program和Service1类.Program是程序的主入口,而Service1则是我们逻辑实现的主要地方 ,两个关键方法是OnStart和OnStop,用于实现服务启动和结束时的逻辑. 安装 在Service1类的设计界面上右击,选择添加安装程序,就可以完成了安装程序的创建. Nlog Window Service作为一个后台程序,发生了什么

注册Tomcat到Window Service服务

注册Tomcat到Window Service服务 1.软件和环境 环境:JDK1.8 应用服务:Tomcat9.0(非安装版) 操作系统:Windows 2012 2.设置Tomcat为系统服务 win+R打开运行窗口,输入cmd打开dos窗口,使用cd命令将位置切换到tomcat路径下的bin文件. cd C:\apache-tomcat-9.0.8\bin.输入service命令可以查看service.bat文件的使用说明, C:\apache-tomcat-9.0.8\bin>servi

jsWindow 对象 Window 对象 Window 对象表示浏览器中打开的窗口。 如果文档包含框架(frame 或 iframe 标签),浏览器会为 HTML 文档创建一个 window 对象,并为每个框架创建一个额外的 window 对象。 注释:没有应用于 window 对象的公开标准,不过所有浏览器都支持该对象。 Window 对象集合 集合 描述 frames[] 返回窗口中所有命

一.JSX简介 JSX就是Javascript和XML结合的一种格式.React发明了JSX,利用HTML语法来创建虚拟DOM.当遇到<,JSX就当HTML解析,遇到{就当JavaScript解析. 如下(JS写法) var child1 = React.createElement('li', null, 'First Text Content'); var child2 = React.createElement('li', null, 'Second Text Content'); var

[ios]IOS的AppDelegate方法中的事件触发调用 以及 关闭 ios应用程序

IOS的AppDelegate方法中的事件触发调用 参考:http://blog.sina.com.cn/s/blog_a573f7990101bphp.html //当应用程序将要进入非活动状态执行,在此期间,应用程序不接受消息或事件,比如来电 - (void)applicationWillResignActive:(UIApplication *)application { NSLog(@"应用程序将要进入非活动状态,即将进入后台"); } //应用程序已经进入后台运行 - (vo

VS2013 C# 调用 cognex 的QuickBuild做程序时发生一个错误

今天在用 VS2013 C# 调用 cognex 的QuickBuild做程序时发生一个错误,如下所示 混合模式程序集是针对"v2.0.50727"版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集 网上搜索得到解决办法: 在app.config中添加一个配置节:startup <startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime

使用Monkey进行UI或应用程序测试

使用Monkey进行UI或应用程序测试 Monkey是运行于模拟器或手机上的一个程序,通过生成伪随机的大量的系统级的用户事件流来模拟操作,包括单击.触摸.手势等.从而为正在开发中的应用程序通过随机响应进行压力测试. 最简单使用monkey的方式是通过下面的命令行,它可以运行指定的应用程序并向其发送500个伪随机事件. $ adb shell monkey -v -p your.package.name 500 关于monkey更多的选项及详细信息