從Win服務啟動UI程式

  • 2019 年 10 月 4 日
  • 筆記

# 從Win服務啟動UI程式

從windows服務啟動一個帶UI程式的介面,這個需求在xp中是很隨意的,從Vista開始似乎沒有那麼隨意了,因為Vista中加入了Session的概念,那麼什麼是Session,我想這篇文章介紹的應該比我權威的多。Session隔離介紹

明白了Session的概念後,我將通過Win32 API來實現從windows服務啟動一個帶UI的介面(從Session 0中啟動Session *的程式),這個實現過程是我從C++程式碼翻譯過來的。

實現的思路

  1. 找到一個除Session 0之外的活動Session
  2. 通過Session ID獲取用戶Token
  3. 通過Token來啟動UI程式

涉及的Win32 API

  1. WTSGetActiveConsoleSessionId獲取活動的Session ID
  2. WTSQueryUserToken根據Session ID獲取用戶Token
  3. CreateProcessAsUser使用用戶Token來啟動UI程式

實現程式碼

public class ProcessAsUser  {      public struct SECURITY_ATTRIBUTES      {          public uint nLength;          public uint lpSecurityDescriptor;          public bool bInheritHandle;      }        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 ushort wShowWindow;          public ushort cbReserved2;          public IntPtr lpReserved2;          public IntPtr hStdInput;          public IntPtr hStdOutput;          public IntPtr hStdError;        }      public struct PROCESS_INFORMATION      {          public IntPtr hProcess;          public IntPtr hThread;          public uint dwProcessId;          public uint dwThreadId;        }        [DllImport("kernel32.dll")]      static extern uint WTSGetActiveConsoleSessionId();        [DllImport("Wtsapi32.dll")]      private static extern bool WTSQueryUserToken(uint SessionId, out uint hToken);        [DllImport("Kernel32.dll")]      private static extern uint GetLastError();        [DllImport("kernel32.dll")]      private static extern bool CloseHandle(IntPtr hSnapshot);        [DllImport("advapi32.dll")]      public extern static bool CreateProcessAsUser(IntPtr hToken,                                              string lpApplicationName,                                              string lpCommandLine,                                              ref SECURITY_ATTRIBUTES lpProcessAttributes,                                              ref SECURITY_ATTRIBUTES lpThreadAttributes,                                              bool bInheritHandle,                                              uint dwCreationFlags,                                              uint lpEnvironment,                                              string lpCurrentDirectory,                                              ref STARTUPINFO lpStartupInfo,                                              out PROCESS_INFORMATION lpProcessInformation);        public static bool StartUIProcessFromService(string exePath)      {          //獲取Session ID          var sId=WTSGetActiveConsoleSessionId();          if (sId == 0)          {              return false;          }          uint hToken;          var isOk=WTSQueryUserToken(sId, out hToken);          if (!isOk || hToken == 0)          {              return false;          }          var lpProcessAttr = new SECURITY_ATTRIBUTES();          lpProcessAttr.nLength = (uint)Marshal.SizeOf(lpProcessAttr);            var lpThreadAttr = new SECURITY_ATTRIBUTES();          lpThreadAttr.nLength = (uint)Marshal.SizeOf(lpThreadAttr);            var lpStratupInfo = new STARTUPINFO();          lpStratupInfo.cb = (uint)Marshal.SizeOf(lpStratupInfo);          lpStratupInfo.lpDesktop = @"winsta0default";            PROCESS_INFORMATION lpProcessInfo;          isOk=CreateProcessAsUser((IntPtr)hToken,                                      exePath,                                      null,                                      ref lpProcessAttr,                                      ref lpThreadAttr,                                      false,                                      0,                                      0,                                      null,                                      ref lpStratupInfo,                                      out lpProcessInfo                                  );          CloseHandle((IntPtr)hToken);          return isOk;      }  }

# 枚舉活動Session ID

之前我們通過WTSGetActiveConsoleSessionId獲取活動Session ID,當有多個用戶登錄時,Windows提供了WTSEnumerateSessions方法枚舉多個Session ID。

主要涉及API

  1. WTSEnumerateSessions 檢索在遠程桌面會話主機 (RD 會話主機) 伺服器上的會話的列表。
  2. WTSFreeMemory 釋放由遠程桌面服務函數分配的記憶體。

實現程式碼

[DllImport("Wtsapi32.dll")]  private static extern void WTSFreeMemory(IntPtr pSessionInfo);    [DllImport("Wtsapi32.dll")]  private extern static bool WTSEnumerateSessions(IntPtr hServer, uint reserved, uint version, out IntPtr ppSessionInfo, out uint pCount);  struct WTS_SESSION_INFO  {      public uint SessionId;      public string pWinStationName;      public WTS_CONNECTSTATE_CLASS State;  }    enum WTS_CONNECTSTATE_CLASS  {      WTSActive,      WTSConnected,      WTSConnectQuery,      WTSShadow,      WTSDisconnected,      WTSIdle,      WTSListen,      WTSReset,      WTSDown,      WTSInit  }    private static uint EnumerateActiveSession()  {      uint dwSessionID = 0xFFFFFFFF;      uint dwCount = 0;      IntPtr intPtr = IntPtr.Zero;      try      {          IntPtr hServer = IntPtr.Zero;          if (WTSEnumerateSessions(hServer, 0, 1, out intPtr, out dwCount))          {              var tmp = intPtr;              for (var i = 0; i < dwCount; ++i)              {                  var pSessionInfo = (WTS_SESSION_INFO)Marshal.PtrToStructure(tmp, typeof(WTS_SESSION_INFO));                    if (WTS_CONNECTSTATE_CLASS.WTSActive == pSessionInfo.State)                  {                      dwSessionID = pSessionInfo.SessionId;                      break;                  }                  if (WTS_CONNECTSTATE_CLASS.WTSConnected == pSessionInfo.State)                  {                      dwSessionID = pSessionInfo.SessionId;                  }                  tmp += Marshal.SizeOf(typeof(WTS_SESSION_INFO));              }              WTSFreeMemory(intPtr);          }          var eCode = GetLastError();      }      catch (Exception ex)      {          var eCode = GetLastError();      }      return dwSessionID;  }