下記リンクを辿れば必要なことはだいたい記述されている。
なので以下メモ書き。
前提知識
セッションに関する知識があれば何をやっているか理解しやすい。 次の資料がわかりやすかった。
ざっくりと書くと
- サービスにはSessionId0が割り当てられる
- ユーザごとに個別のSessionId(1以上)が割り当てられる1
- SessionはWindowStationを含み、WindowStationはDesktopを含む
- ユーザが普段目にするのは自分のセッションのDesktop
説明
https://stackoverflow.com/a/24122826 であげられているソースについて
サービスからGUIを立ち上げるにはログオンしているユーザのセッションのdesktopに対してプロセスを起動できれば良い。
実際、このコードでやってるのは
- ログオンユーザのセッションIDを取得
- 取得したセッションIDからトークンを取得
- WindowStationとDesktopを指定してプロセスをスタート
ちょいと細かく書くと
l.174~l.195:アクティブユーザのsessionを取得
WTSEnumerateSessions
:Remote Desktop Session Host server上のsessionリストを取得l.197:user token取得
WTSQueryUserToken
:session IDからprimary access tokenを取得l.200:user tokenを複製2
DuplicateTokenEx
:tokenを複製する。primary token/impersonation tokenを作ることができる。l.229:デフォルトデスクトップを表示先に指定
startInfo.lpDesktop = "winsta0\\default";
l.236:指定したトークンでプロセスを立ち上げ
CreateProcessAsUser
:tokenで指定されたユーザのsecurity contextでプロセスを立ち上げる。
なおこのサービスはLocalSystem account
で動かすことが前提のもよう。
WTSQueryUserToken の説明
To call this function successfully, the calling application must be running within the context of the LocalSystem account and have the SE_TCB_NAME privilege.
https://docs.microsoft.com/ja-jp/windows/win32/api/wtsapi32/nf-wtsapi32-wtsqueryusertoken
LocalSystem account
以外でサービスを実行する場合
やることはたいして変わらない
- サービスを実行するユーザーに
CreateProcessAsUser
を実行するための権限を与える3 - ログオンユーザのプロセスを取得
- プロセスからトークンを取得
- WindowStationとDesktopを指定してプロセスをスタート
サービスを実行するユーザーとログオンユーザが同じ場合
動作環境
Windows 10 Pro、バージョン20H2、OSビルド19042.867
.Net Framework 4.7.2
ソースコード
GetSessionUserToken
の部分を次のようにすれば良い4。
foreach (System.Diagnostics.Process p in System.Diagnostics.Process.GetProcesses()) { if (p.ProcessName.Equals("explorer")) explorerHandle = p.Handle; } if (OpenProcessToken(explorerHandle, TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, out var hImpersonationToken)) { var bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary, ref phUserToken); CloseHandle(hImpersonationToken); }
stackoverflowには
The problem with Shrike's answer is that it does not work with a user connected over RDP.
とあるが手元の環境ではリモートデスクトップ経由で問題なく動作した。Windows10だとうまくいく?ユーザー一つしかないから?
- remote desktop経由でセッションを確認
C:\Users\******>query session セッション名 ユーザー名 ID 状態 種類 デバイス services 0 Disc >rdp-tcp#1 ****** 14 Active console 15 Conn
- ローカルでセッションを確認
C:\Users\******>query session セッション名 ユーザー名 ID 状態 種類 デバイス services 0 Disc >console ****** 14 Active
サービスを実行するユーザーとログオンユーザが異なる場合
上記コードではうまくいかなかった。権限周りでの設定がなんやかんやいりそう。
-
XP以前は最初にログオンしたユーザにSessionId0が割り当てられた。サービスとユーザを同じセッションにするとセキュリティ上問題があるのでVista以降は分けられたとか。調べてみると面白い。↩
-
WTSQueryUserToken
でprimary access tokenが得られるので動かすだけなら不要。とはいえ複製しといた方が無難。↩ -
ローカルセキュリティポリシーでローカルポリシー->ユーザー権利の割り当て->プロセスレベルトークンの置き換え。「プロセスレベルトークンの置き換え」の説明:このセキュリティ設定は、あるサービスから別のサービスを開始するために、CreateProcessAsUser() アプリケーション プログラミング インターフェイス (API) を呼び出すことができるユーザー アカウントを決定します。↩
-
DuplicateTokenEx
はなくても動きはする。↩