読者です 読者をやめる 読者になる 読者になる

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

Windows C++

今回は Windows の Service からプログラムを起動する方法を紹介します.Vista 以降ではサービスからユーザセッションにプログラムを実行するには CreateProcessWithLogonW を使用しますが,ここでは指定したプロセスのトークンを複製して CreateProcessAsUser 関数でプログラムを起動する方法を紹介します.例えばこのプログラムに winlogon.exe プロセスのハンドルを渡すと,Local System Account ユーザでプログラムを実行することになります.

早速以下に私の実装例を示します.
プログラムの作成には

を参考にさせていただきました.

process.hpp

#ifndef PROCESS_HPP_20100714_
#define PROCESS_HPP_20100714_


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


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


namespace process {
    BOOL createProcess(const std::wstring& app, const std::wstring& param, HANDLE process = nullptr);
}


#endif

process.cpp

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

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


namespace {
    // プログラム名が name のプロセスの ID を返す
    DWORD getProcessId(const std::wstring& name)
    {
        CHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0));

        if (snapshot != INVALID_HANDLE_VALUE) {
            PROCESSENTRY32 entry = { sizeof(PROCESSENTRY32) };

            if (Process32First(snapshot, &entry)) {
                do {
                    if (wcscmp(entry.szExeFile, name.c_str()) == 0) {
                        return entry.th32ProcessID;
                    }
                } while (Process32Next(snapshot, &entry));
            }
        }

        return 0;
    }


    // token ユーザ,env 環境変数で app プログラムを実行(引数 param)
    BOOL createProcessAsUser(const std::wstring& app, const std::wstring& param, HANDLE token, DWORD creationFlags, LPVOID env)
    {
        wchar_t arg[MAX_PATH] = L"";

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

        STARTUPINFO         si     = { sizeof(STARTUPINFO), nullptr, L"winsta0\\default" };
        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::createProcess(const std::wstring& app, const std::wstring& param, HANDLE process)
{
    BOOL retval = FALSE;

    if (process) {
        CHandle processToken;

        // process のユーザトークン
        // CreateProcessAsUser が TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY を要求することに注意
        if (OpenProcessToken(process, TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, &processToken.m_h)) {
            CHandle userToken;

            // CreateProcessAsUser のためにトークンを複製し,プライマリトークンを作成する
            if (DuplicateTokenEx(processToken, MAXIMUM_ALLOWED, nullptr, SecurityIdentification, TokenPrimary, &userToken.m_h)) {
                DWORD  sessionId     = WTSGetActiveConsoleSessionId();
                DWORD  creationFlags = CREATE_NEW_CONSOLE | NORMAL_PRIORITY_CLASS;
                LPVOID env           = nullptr;

                // アクティブユーザのセッションを設定します
                SetTokenInformation(userToken, TokenSessionId, &sessionId, sizeof(DWORD));

                // 環境変数を設定します
                if (CreateEnvironmentBlock(&env, userToken, TRUE)) {
                    creationFlags |= CREATE_UNICODE_ENVIRONMENT;
                }
                else {
                    env = nullptr;
                }

                retval = createProcessAsUser(app, param, userToken, creationFlags, env);

                DestroyEnvironmentBlock(env);
            }
        }
    }
    else { // 指定されなければ,winlogon.exe のユーザトークンを用いる
        CHandle target(OpenProcess(MAXIMUM_ALLOWED, FALSE, getProcessId(L"winlogon.exe")));

        if (target) {
            return createProcess(app, param, target);
        }
    }

    return retval;
}

process.cpp に指定したプロセスのユーザトークンでプログラムを実行する関数 createProcess を実装しました.process.cpp をプロジェクトに加え(他のソースコードと一緒にコンパイル),process.hpp を include することにより createProcess が使用可能になります.createProcess はプロセスのハンドルを受け取り,CreateProcessAsUser 用にそのトークンを複製し,CreateProcessAsUser を用いてユーザセッションに指定されたプログラムを実行します.

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

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::createProcess(L"C:\\Windows\\System32\\notepad.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;
}

その2書きました。こちらも参考になれば幸いです。