Windows Vista以降でサービスから画面を表示する

Windows VISTA以降のWindowsではセッション0分離の機能により、サービスから直接UIを表示することができなくなりました。Microsoftの互換性情報では代替手段として「ユーザーログイン時に常駐プロセスを起動しサービスと通信を行う。」「WTSSendMessage関数を使用してメッセージボックス相当の表示を行う。」「CreateProcessAsUserを使用してログインユーザー権限でアプリケーションを起動する。」の三つの方法が提示されています。ここでは最後の「CreateProcessAsUserを使用してログインユーザー権限でアプリケーションを起動する。」サンプルを提示します。
サービスから画面を表示するにはRemote Desktop Services APIを使用します。
WTSGetActiveConsoleSessionId関数で物理コンソールのセッションIDを取得します。
WTSQueryUserToken関数で取得したセッションIDのユーザートークンを取得します。
DuplicateTokenEx関数で取得したユーザートークンを複製し、プライマリトークンを作成します。
CreateEnvironmentBlockを使用してユーザーの環境変数を取得します。
CreateProcessAsUser関数を使用して画面を表示するプロセスを起動します。

BOOL    bRet(FALSE);
HANDLE  hUserToken(INVALID_HANDLE_VALUE);
if (::WTSQueryUserToken(WTSGetActiveConsoleSessionId(), &hUserToken))
{
    SECURITY_ATTRIBUTES sa;
    memset(&sa, 0x00, sizeof(sa));
    sa.nLength	= sizeof(SECURITY_ATTRIBUTES);
    HANDLE	hDupedToken(INVALID_HANDLE_VALUE);
    if (::DuplicateTokenEx(hUserToken, 0, &sa, SecurityIdentification, TokenPrimary, &hDupedToken))
    {
        PVOID	lpEnvironment(NULL);
        if (::CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE))
        {
            STARTUPINFO stStartUpInfo;
            memset(&stStartUpInfo, 0, sizeof(STARTUPINFO));
            stStartUpInfo.cb        = sizeof(STARTUPINFO);
            stStartUpInfo.lpDesktop = _T("winsta0¥¥default");
            PROCESS_INFORMATION ProcessInfo;
            memset(&ProcessInfo, 0, sizeof(PROCESS_INFORMATION));
            ::CreateProcessAsUser(hDupedToken, _T(""), (char*)(const char*)strCmd, NULL, NULL
                    , FALSE, CREATE_UNICODE_ENVIRONMENT, lpEnvironment, NULL, &stStartUpInfo, &ProcessInfo);
        }
    }
}