Windows服务(system权限)程序显示界面与用户交互,Session0通知Session1里弹出对话框(真的很牛) good

时间:2021-09-16 08:52:00
 

1、VC2008中编写“Windows服务”(Windows Service)程序

vc2008下新建一个 ATL 项目-》 选择创建一个“服务”类型的ATL 项目TestService,将生成如下代码,
 
class  CTestServiceModule  :  public  CAtlServiceModuleT <  CTestServiceModule ,  IDS_SERVICENAME  >
{
public  :
        DECLARE_LIBID ( LIBID_TestServiceLib  )
        DECLARE_REGISTRY_APPID_RESOURCEID  ( IDR_TESTSERVICE ,  "{1FF78006-B225-4CC0-A7DE-E0C9D31C9937}"  )
        HRESULT  InitializeSecurity  ()  throw ()
      {
              // TODO :  调用 CoInitializeSecurity  并为服务提供适当的
              //  安全设置
              //  建议 - PKT  级别的身份验证、
              // RPC_C_IMP_LEVEL_IDENTIFY  的模拟级别
              //  以及适当的非 NULL  安全说明符。
 
              return  S_OK  ;
      }
        // 重写这个函数来启动任务啦
        HRESULT  Run  ( int  nShowCmd  =  SW_HIDE  )  throw ()
      {
              HRESULT  hr  =  S_OK ;
              hr  =  __super  :: PreMessageLoop (  nShowCmd );
              if  ( hr  ==  S_OK )
            {
                    if  ( m_bService  )
                  {
                         // 需要定义 #define   _ATL_NO_COM_SUPPORT才能启动服务时走到这里
                          // 可以在这里启动线程,或者什么其他东西来做自己的工作的啦
                          // 这里是什么都没有做了,只输出一条信息
 
                          LogEvent ( _T  ( "widebright  的服务启动咯,呵呵  " ));
                          SetServiceStatus ( SERVICE_RUNNING  );
                  }
                    // 进入消息循环,不停的处理消息,可能最后分发到  Handler 去处理,调用了 OnShutdown 等函数的。
                    __super :: RunMessageLoop  ();
            }
              if  ( SUCCEEDED  ( hr ))
            {
                    hr  =  __super  :: PostMessageLoop ();
            }
 
              // 可以在适当的时候调用 Uninstall 函数来卸载掉服务
              //__super::Uninstall();
              return  hr  ;
      }
        // 重写,服务退出处理
        void  OnShutdown  ()  throw ()
      {
              LogEvent ( _T  ( "TestService  的服务退出咯,一点都不好玩呵呵  " ));
      }
 
};
 
CTestServiceModule  _AtlModule ;
 
 
 
//
extern  "C"  int  WINAPI  _tWinMain  ( HINSTANCE  ,  HINSTANCE  ,
                                 LPTSTR   ,  int  nShowCmd )
{
     return  _AtlModule  . WinMain (  nShowCmd );
}
  我只要根据需要重写相应的函数来实现自己想要的功能就行了,比如你想创建的“服务”随系统启动,可以重写CAtlServiceModuleT   的Install函数,把里面的CreateService函数的参数修改一下,例如添加与用户交互可以使用 SERVICE_INTERACTIVE_PROCESS,具体可以去MSDN上查找CreateService这个API的说明。

如果想处理服务 停止和启动的动作,可以参考CAtlServiceModuleT 的源代码重写OnStop ()等函数。我上面简单到重写了Run函数,输出一条“事件”其实具体 工作是可以放到这里来完成的吧。

编译,生成程序之后就可以测试了,

执行“TestService -/Service” 就可以把服务注册到系统了,命令行参数其实是在CAtlServiceModuleT::ParseCommandLine 这个函数里面处理,可以去看一下,必要的话重写也是可以的,加上调用 UnInstall来删除服务的代码也很不错的吧。

注册后,就看用“sc start” 或者“net start” 等命令来操纵服务了。在“服务”控制器里面控制与可以:如图
Windows服务(system权限)程序显示界面与用户交互,Session0通知Session1里弹出对话框(真的很牛) good

这时候在Run函数中启动一个Notepad.exe,此时没有界面显示,在xp下可以使用下面的方法实现notepad与用户的交互:
//for xp system
DWORD  _stdcall  LaunchAppIntoSession0 (  LPTSTR  lpCommand  )
{
        ////////////////////////////////////////////system show dlg////////////////////
 
        HDESK  hdeskCurrent  ;
        HDESK  hdesk  ;
        HWINSTA  hwinstaCurrent  ;
        HWINSTA  hwinsta  ;
        hwinstaCurrent  =  GetProcessWindowStation  ();
        if  ( hwinstaCurrent  ==  NULL )
      {
              return  FALSE  ;
      }
        hdeskCurrent  =  GetThreadDesktop  ( GetCurrentThreadId ());
        if  ( hdeskCurrent  ==  NULL ){
              return  FALSE  ;
      }
        // 打开 winsta0
        // 打开 winsta0
        hwinsta  =  OpenWindowStation  ( L "Winsta0"  ,  FALSE ,  WINSTA_ALL_ACCESS );
        //          WINSTA_ACCESSCLIPBOARD|
        //          WINSTA_ACCESSGLOBALATOMS |
        //          WINSTA_ENUMDESKTOPS |
        //          WINSTA_CREATEDESKTOP |
        //          WINSTA_CREATEDESKTOP |
        //          WINSTA_ENUMERATE |
        //          WINSTA_EXITWINDOWS |
        //          WINSTA_READATTRIBUTES |
        //          WINSTA_READSCREEN |
        //          WINSTA_WRITEATTRIBUTES);
        if  ( hwinsta  ==  NULL ){
              return  FALSE  ;
      }
        if  (! SetProcessWindowStation  ( hwinsta ))
      {
              return  FALSE  ;
      }
        // 打开 desktop
        hdesk  =  OpenDesktop  ( L "default"  , 0,  FALSE ,
              DESKTOP_CREATEMENU  |
              DESKTOP_CREATEWINDOW  |
              DESKTOP_ENUMERATE |
              DESKTOP_HOOKCONTROL |
              DESKTOP_JOURNALPLAYBACK  |
              DESKTOP_JOURNALRECORD  |
              DESKTOP_READOBJECTS  |
              DESKTOP_SWITCHDESKTOP  |
              DESKTOP_WRITEOBJECTS );
        if  ( hdesk  ==  NULL ){
              return  FALSE  ;
      }
        SetThreadDesktop ( hdesk  );
        ////////////////////////////////////////////end of system show dlg////////////////////
       
        STARTUPINFO  si  = {  sizeof (  si ) }; 
        SECURITY_ATTRIBUTES  saProcess  ,  saThread
        PROCESS_INFORMATION  piProcessB  ,  piProcessC
 
 
        // Prepare to spawn Process B from Process A. 
        // The handle identifying the new process 
        // object should be inheritable. 
        saProcess . nLength  =  sizeof (  saProcess ); 
        saProcess . lpSecurityDescriptor  =  NULL
        saProcess . bInheritHandle  =  TRUE
 
        // The handle identifying the new thread 
        // object should NOT be inheritable. 
        saThread . nLength  =  sizeof (  saThread );
        saThread . lpSecurityDescriptor  =  NULL ;
        saThread . bInheritHandle  =  FALSE
 
        CreateProcess ( NULL  ,  lpCommand , &  saProcess , & saThread 
              FALSE , 0,  NULL  ,  NULL , &  si , & piProcessB  ); 
 
 
        if  (! SetProcessWindowStation  ( hwinstaCurrent ))
              return  FALSE  ;
        if  (! SetThreadDesktop  ( hdeskCurrent ))
              return  FALSE  ;
        if  (! CloseWindowStation  ( hwinsta ))
              return  FALSE  ;
        if  (! CloseDesktop  ( hdesk ))
              return  FALSE  ;
        return  TRUE  ;
}

这种方法的关键是OpenWindowStation、SetProcessWindowStation、OpenDesktop和SetThreadDesktop这四个函数。这种方法的思路是:当前进程所处于的Session必须有界面交互能力,这样才能显示出对话框。由于第一个交互式用户会登录到拥有WinSta0的Session 0,所以,强制性地把服务所在的进程与WinSta0关联起来,并且打开当前的桌面,把工作线程挂到该桌面上,就可以显示出对话框。

这种方法在 WinXP和Windows2003下工作得不错,很遗憾,在Vista和Windows2008下,一旦执行到OpenWindowStation,试图代开WinSta0工作站时,程序就会出异常。

首先了解一下程序要具备怎样的条件才能与界面交互。Windows提供了三类对象:用户界面对象(User Interface)、GDI对象和内核对象。内核对象有安全性,而前两者没有。为了对前两者提供安全性,通过工作站对象(Window station)和桌面对象(Desktop)来管理用户界面对象,因为工作站对象和桌面对象有安全特性。简单说来,工作站是一个带有安全特性的对象,它与进程相关联,包含了一个或多个桌面对象。当工作站对象被创建时,它被关联到调用进程上,并且被赋给当前Session。交互式工作站WinSta0,是唯一一个可以显示用户界面,接受用户输入的工作站。它被赋给交互式用户的登录Session,包含了键盘、鼠标和显示设备。所有其他工作站都是非交互式的,这就意味着它们不能显示用户界面,不能接受用户的输入。当用户登录到一台启用了终端服务的计算机上时,每个用户都会启动一个Session。每个Session都会与自己的交互式工作站相联系。桌面是一个带有安全特性的对象,被包含在一个窗口工作站对象中。一个桌面对象有一个逻辑的显示区域,包含了诸如窗口、菜单、钩子等等这样的用户界面对象。

 

Vista之前,之所以可以通过打开Winsta0和缺省桌面显示对话框,是因为不管是服务还是第一个登录的交互式用户,都是登录到Session 0中。因此,服务程序可以通过强制打开WinSta0和桌面来获得交互能力。

然而,在Vista和Windows2008中,Session 0专用于服务和其他不与用户交互的应用程序。第一个登录进来,可以进行交互式操作的用户被连到Session 1上。第二个登录进行的用户被分配给Session 2,以此类推。Session 0完全不支持要与用户交互的进程。如果采取在服务进程中启动子进程来显示对话框,子对话框将无法显示;如果采取用OpenWindowStation系统API打开WinSta0的方法,函数调用会失败。总之,Vista和Windows2008已经堵上了在Session 0中产生界面交互的路。这就是原因所在。

 

那么,是否真的没法在服务中弹出对话框了呢?对于服务进程自身来说,确实如此,操作系统已经把这条路堵上了。但是,我们想要的并不是“在服务进程中弹出对话框”,我们想要的不过是“当服务出现某些状况的时候,在桌面上弹出对话框”。既然在Session 0中无法弹出对话框,而我们看到的桌面是Session X,并非Session 0,很自然的一个想法是:能不能让Session 0通知其他的Session,让当前桌面正显示着的Session弹一个对话框呢?

幸运的是,还真可以这样做。
 
//for win7
DWORD  _stdcall  LaunchAppIntoDifferentSession (  LPTSTR  lpCommand  )
{
        DWORD  dwRet  = 0;
        PROCESS_INFORMATION  pi  ;
        STARTUPINFO  si  ;
        DWORD  dwSessionId  ;
        HANDLE  hUserToken  =  NULL ;
        HANDLE  hUserTokenDup  =  NULL ;
        HANDLE  hPToken  =  NULL ;
        HANDLE  hProcess  =  NULL ;
        DWORD  dwCreationFlags  ;
 
        HMODULE  hInstKernel32      =  NULL ;
        typedef  DWORD  ( WINAPI  *  WTSGetActiveConsoleSessionIdPROC )();
        WTSGetActiveConsoleSessionIdPROC  WTSGetActiveConsoleSessionId  =  NULL ;
 
        hInstKernel32  =  LoadLibrary  ( L "Kernel32.dll"  );
 
        if  (! hInstKernel32 
      {
              return  FALSE  ;
      }
 
        OutputDebugString ( L  "LaunchAppIntoDifferentSession 1\n"  );
        WTSGetActiveConsoleSessionId  = ( WTSGetActiveConsoleSessionIdPROC  ) GetProcAddress (  hInstKernel32 , "WTSGetActiveConsoleSessionId"  );
 
 
        // Log the client on to the local computer.
        dwSessionId  =  WTSGetActiveConsoleSessionId  ();
 
        do
      {
              WTSQueryUserToken (  dwSessionId  ,& hUserToken  );
              dwCreationFlags  =  NORMAL_PRIORITY_CLASS  |  CREATE_NEW_CONSOLE ;
              ZeroMemory ( & si  ,  sizeof (  STARTUPINFO  ) );
              si . cb  =  sizeof (  STARTUPINFO  );
              si . lpDesktop  =  L "winsta0\\default"  ;
              ZeroMemory ( & pi  ,  sizeof (  pi ) );
              TOKEN_PRIVILEGES  tp  ;
              LUID  luid  ;
 
              if ( !:: OpenProcessToken  (  GetCurrentProcess (),  TOKEN_ADJUST_PRIVILEGES  |  TOKEN_QUERY
                  |  TOKEN_DUPLICATE  |  TOKEN_ASSIGN_PRIMARY  |  TOKEN_ADJUST_SESSIONID
                  |  TOKEN_READ  |  TOKEN_WRITE  , & hPToken  ) )
            {
                    dwRet  =  GetLastError  ();
                    break ;
            }
              else ;
 
              if  ( ! LookupPrivilegeValue  (  NULL ,  SE_DEBUG_NAME , & luid  ) )
            {
                    dwRet  =  GetLastError  ();
                    break ;
            }
              else ;
              tp . PrivilegeCount  =1;
              tp . Privileges  [0]. Luid  =  luid ;
              tp . Privileges  [0]. Attributes  =  SE_PRIVILEGE_ENABLED ;
 
              if ( ! DuplicateTokenEx  (  hPToken ,  MAXIMUM_ALLOWED ,  NULL  ,  SecurityIdentification  ,  TokenPrimary , &  hUserTokenDup  ) )
            {
                    dwRet  =  GetLastError  ();
                    break ;
            }
              else ;
 
              //Adjust Token privilege
              if ( ! SetTokenInformation  (  hUserTokenDup , TokenSessionId  ,( void *)&  dwSessionId , sizeof  ( DWORD ) ) )
            {
                    dwRet  =  GetLastError  ();
                    break ;
            }
              else ;
 
              if ( ! AdjustTokenPrivileges  (  hUserTokenDup ,  FALSE , & tp  ,  sizeof ( TOKEN_PRIVILEGES  ), ( PTOKEN_PRIVILEGES )  NULL ,  NULL  ) )
            {
                    dwRet  =  GetLastError  ();
                    break ;
            }
              else ;
 
              LPVOID  pEnv  = NULL ;
              if (  CreateEnvironmentBlock  ( & pEnv ,  hUserTokenDup ,  TRUE  ) )
            {
                    dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT  ;
            }
              else  pEnv  = NULL ;
            
 
              // Launch the process in the client's logon session.
              if (  CreateProcessAsUser  (     hUserTokenDup ,     // client's access token
                    NULL ,         // file to execute
                    lpCommand ,         // command line
                    NULL ,             // pointer to process SECURITY_ATTRIBUTES
                    NULL ,             // pointer to thread SECURITY_ATTRIBUTES
                    FALSE ,             // handles are not inheritable
                    dwCreationFlags , // creation flags
                    pEnv ,           // pointer to new environment block
                    NULL ,           // name of current directory
                  &  si ,             // pointer to STARTUPINFO structure
                  &  pi              // receives information about new process
                  ) )
            {
            }
              else
            {
                    dwRet  =  GetLastError  ();
                    break ;
            }
      }
        while ( 0 );
 
        //Perform All the Close Handles task
        if (  NULL  !=  hUserToken  )
      {
              CloseHandle (  hUserToken  );
      }
        else ;
 
        if (  NULL  !=  hUserTokenDup )
      {
              CloseHandle (  hUserTokenDup  );
      }
        else ;
 
        if (  NULL  !=  hPToken  )
      {
              CloseHandle (  hPToken  );
      }
        else ;
 
        return  dwRet  ;
}
 
启动服务后显示了system权限的Notepad.exe,并且可以与用户进行交互
Windows服务(system权限)程序显示界面与用户交互,Session0通知Session1里弹出对话框(真的很牛) good
Windows服务(system权限)程序显示界面与用户交互,Session0通知Session1里弹出对话框(真的很牛) good

 
当然,在本例子启动进程的地方创建一个对话框也是可以
http://blog.sina.com.cn/s/blog_488cff5201017yug.html