Windows サービスプログラムからユーザプログラムを実行する方法 - その2

Windows サービスプログラムからユーザプログラムを実行する方法 の続きです。
前回は winlogon.exe のトークンを元にプロセスを起動させましたが、今回はログオンユーザのリンクトークン(自身の制限されたトークンに紐付く管理者トークン)を元に、ログオンユーザの管理者権限でプロセスを起動させる方法をご紹介します。
詳しくは以下のリンク先を参照してください。

早速以下に私の実装例を示します.

process.hpp

#ifndef PROCESS_HPP_20121029_
#define PROCESS_HPP_20121029_


#if defined(_MSC_VER) && (_MSC_VER >= 1020)
# pragma once
#endif


#include <string>
#include <windows.h>


namespace process {
    BOOL createProcessAsActiveUser(const std::wstring& app, const std::wstring& param, PROCESS_INFORMATION* pi = nullptr);
}


#endif

process.cpp

#include <windows.h>
#include <userenv.h>
#include <wtsapi32.h>
#include <atlbase.h>
#include "process.hpp"

#pragma comment(lib, "userenv.lib")
#pragma comment(lib, "wtsapi32.lib")


namespace {
    BOOL createProcessAsUser(const std::wstring& app, const std::wstring& param, HANDLE token, DWORD creationFlags, LPVOID env, PROCESS_INFORMATION* pi = nullptr)
    {
        wchar_t     arg[32768] = L"";
        STARTUPINFO si = { sizeof(STARTUPINFO), nullptr, L"winsta0\\default" };

        wcscpy_s(arg, (param.empty() ? app.c_str() : (app + L" " + param).c_str()));

        if (pi) {
            return CreateProcessAsUser(token, nullptr, arg, nullptr, nullptr, FALSE, creationFlags, env, nullptr, &si, pi);
        }
        else {
            PROCESS_INFORMATION pi     = {};
            const BOOL          retval = CreateProcessAsUser(token, nullptr, arg, nullptr, nullptr, FALSE, creationFlags, env, nullptr, &si, &pi);

            CloseHandle(pi.hThread);
            CloseHandle(pi.hProcess);

            return retval;
        }
    }
}


BOOL process::createProcessAsActiveUser(const std::wstring& app, const std::wstring& param, PROCESS_INFORMATION* pi)
{
    const DWORD sessionId = WTSGetActiveConsoleSessionId();
    CHandle     userToken;
    BOOL        retval = FALSE;

    if (WTSQueryUserToken(sessionId, &userToken.m_h)) {
        CHandle linkedToken;
        DWORD   size = sizeof(linkedToken.m_h);

        if (GetTokenInformation(userToken, TokenLinkedToken, &linkedToken.m_h, size, &size)) {
            CHandle duplicatedToken;

            if (DuplicateTokenEx(linkedToken, MAXIMUM_ALLOWED, nullptr, SecurityImpersonation, TokenPrimary, &duplicatedToken.m_h)) {
                DWORD  creationFlags = CREATE_NEW_CONSOLE | NORMAL_PRIORITY_CLASS;
                LPVOID env           = nullptr;

                if (CreateEnvironmentBlock(&env, duplicatedToken, TRUE)) {
                    creationFlags |= CREATE_UNICODE_ENVIRONMENT;
                }
                else {
                    env = nullptr;
                }

                retval = createProcessAsUser(app, param, duplicatedToken, creationFlags, env, pi);

                DestroyEnvironmentBlock(env);
            }
        }
    }

    return retval;
}

process.cpp にログオンユーザの管理者権限でプログラムを実行する関数 createProcessAsActiveUser を実装しました.process.cpp をプロジェクトに加え(他のソースコードと一緒にコンパイル),process.hpp を include することにより createProcessAsActiveUser が使用可能になります.

以下のテストコード(テストサービス)で createProcessAsActiveUser の動作を確認できます.

test.cpp

#include <string>
#include <windows.h>
#include "process.hpp"


namespace {
    wchar_t SERVICE_NAME[] = L"TestService";
    wchar_t DISPLAY_NAME[] = L"テストサービス";


    // このサービスプログラムの実行ファイルのパスを返します
    const std::wstring getAppPath(HINSTANCE instance = nullptr)
    {
        wchar_t path[MAX_PATH] = L"";

        GetModuleFileName(instance, path, MAX_PATH);

        return std::wstring(path);
    }


    // サービスを登録し,実行します
    void registerService()
    {
        if (SC_HANDLE scManager = OpenSCManager(nullptr, nullptr, SC_MANAGER_CREATE_SERVICE)) {
            if (SC_HANDLE service = CreateService(scManager, SERVICE_NAME, DISPLAY_NAME,
                SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START,
                SERVICE_ERROR_NORMAL, getAppPath().c_str(), nullptr, 0, nullptr, nullptr, nullptr)) {

                StartService(service, 0, nullptr);

                CloseServiceHandle(service);
            }

            CloseServiceHandle(scManager);
        }
    }


    // サービスを終了し,解除します
    void unregisterService()
    {
        if (SC_HANDLE scManager = OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT)) {
            if (SC_HANDLE service = OpenService(scManager, SERVICE_NAME, SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE)) {
                SERVICE_STATUS ss;

                QueryServiceStatus(service, &ss);
                if (ss.dwCurrentState == SERVICE_RUNNING) { // もし実行中なら
                    ControlService(service, SERVICE_CONTROL_STOP, &ss); // 終了する
                }

                DeleteService(service);
                CloseServiceHandle(service);
            }
            
            CloseServiceHandle(scManager);
        }
    }


    HANDLE                gStopEvent     = nullptr;
    SERVICE_STATUS        gServiceStatus = {};
    SERVICE_STATUS_HANDLE gServiceHandle = nullptr;


    DWORD WINAPI handlerEx(DWORD control, DWORD, LPVOID, LPVOID)
    {
        switch (control) {
        case SERVICE_CONTROL_STOP: // 終了要求
            gServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
            gServiceStatus.dwCheckPoint   = 0;
            gServiceStatus.dwWaitHint     = 2000;

            SetServiceStatus(gServiceHandle, &gServiceStatus);
            SetEvent(gStopEvent);

            break;
        case SERVICE_CONTROL_INTERROGATE:
            SetServiceStatus(gServiceHandle, &gServiceStatus);

            break;
        }

        return NO_ERROR;
    }


    VOID WINAPI serviceMain(DWORD, LPWSTR*)
    {
        gStopEvent     = CreateEvent(nullptr, FALSE, FALSE, nullptr);
        gServiceHandle = RegisterServiceCtrlHandlerEx(SERVICE_NAME, &handlerEx, nullptr);

        gServiceStatus.dwServiceType             = SERVICE_WIN32_OWN_PROCESS;
        gServiceStatus.dwCurrentState            = SERVICE_RUNNING;
        gServiceStatus.dwControlsAccepted        = SERVICE_ACCEPT_STOP;
        gServiceStatus.dwWin32ExitCode           = NO_ERROR;
        gServiceStatus.dwServiceSpecificExitCode = 0;
        gServiceStatus.dwCheckPoint              = 0;
        gServiceStatus.dwWaitHint                = 0;

        SetServiceStatus(gServiceHandle, &gServiceStatus);

        { // サービス開始
            process::createProcessAsActiveUser(L"C:\\Windows\\System32\\taskmgr.exe", L""); // タスクマネージャを起動

            // このサービスは終了要求を待つのみ
            WaitForSingleObject(gStopEvent, INFINITE);
        }

        { // 終了処理
            CloseHandle(gStopEvent);
            gServiceStatus.dwCurrentState = SERVICE_STOPPED;
            gServiceStatus.dwCheckPoint   = 0;
            gServiceStatus.dwWaitHint     = 0;

            SetServiceStatus(gServiceHandle, &gServiceStatus);
        }
    }
}


int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR cmdLine, int)
{
    if (wcscmp(cmdLine, L"-register") == 0 || wcscmp(cmdLine, L"/register") == 0) { // サービス登録
        registerService();
    }
    else if (wcscmp(cmdLine, L"-unregister") == 0 || wcscmp(cmdLine, L"/unregister") == 0) { // サービス解除
        unregisterService();
    }
    else {
        SERVICE_TABLE_ENTRY services[] = {
            { SERVICE_NAME, &serviceMain }, { nullptr, nullptr }
        };

        StartServiceCtrlDispatcher(services);
    }

    return 0;
}