🐯

Win 管理者権限で実行したいプログラム

に公開

Windows でアプリ実行途中で管理者権限に移行したいプログラムは、「管理者権限がなければ、昇格を促して、自身を再実行」するという感じに作る模様。

1. 管理者権限の確認

BOOL isAdminUser(void) {
    BYTE  sid_buf[SECURITY_MAX_SID_SIZE];
    DWORD sid_size = sizeof(sid_buf);
    BOOL  is_memb  = FALSE;
    return CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, sid_buf, &sid_size)
        && CheckTokenMembership(NULL, sid_buf, &is_memb)
        && is_memb == TRUE;
}

2. 管理者権限で再実行

TCHAR const* skipArgv0(TCHAR const* cmdline);   // argv[0]相当をスキップして残り文字列を返す関数.

// 0:昇格起動成功 1:失敗  (mainの返り値に使えるように0を成功扱い)
int relaunchAsAdmin(void) {
    TCHAR exe[MAX_PATH];
    GetModuleFileName(NULL, exe, sizeof(exe)/sizeof(TCHAR));
    return (INT_PTR)ShellExecute(NULL, _T("runas"), exe, skipArgv0(GetCommandLine()), NULL, SW_SHOWNORMAL) <= 32;
}

ShellExecute に渡すコマンドライン引数は、アプリごとに調整。

例では GetCommandLine() でコマンドラインに入力されたままの文字列を使い、先頭の実行ファイル名部分だけスキップしている。
argc, argv を連結してもいいけれど、Windows のコマンドライン引数では、空白や '"', '' の扱いが非常に面倒なので、GetCommandLine()を使うほうが楽なのでは、と。

※ skipArgv0() の実装は下記サンプルを参照のこと。

昇格時のアプリ終了コード反映

前記は短くすむけれど、昇格した側でのアプリの終了コードを無視して成功扱いにしているので、昇格時のアプリ終了コード(エラー終了)を反映させたい場合は以下に変更。

int relaunchAsAdmin(void) {
    SHELLEXECUTEINFO sei;
    TCHAR            exe[MAX_PATH];
    DWORD            exit_code = 1;

    GetModuleFileName(NULL, exe, MAX_PATH);
    ZeroMemory(&sei, sizeof(sei));
    sei.cbSize       = sizeof(sei);
    sei.fMask        = SEE_MASK_NOCLOSEPROCESS;
    sei.hwnd         = NULL;
    sei.lpVerb       = _T("runas");
    sei.lpFile       = exe;
    sei.lpParameters = skipArgv0(GetCommandLine());
    sei.lpDirectory  = NULL;
    sei.nShow        = SW_SHOWNORMAL;

    if (!ShellExecuteEx(&sei))
        return 1;

    WaitForSingleObject(sei.hProcess, INFINITE);
    if (GetExitCodeProcess(sei.hProcess, &exit_code) == 0)
        exit_code = 1;
    CloseHandle(sei.hProcess);
    return exit_code;
}

3. サンプル

管理者権限でなければ昇格求めて再実行して、コマンドライン引数を表示するだけのサンプル。
昇格したときに別のコマンドプロンプト窓になっているのを確認しやすいように1キー入力待ちをする。

#include <windows.h>
#include <stdio.h>
#include <tchar.h>

#ifdef _MSC_VER
 #pragma comment(lib, "Advapi32.lib")
 #pragma comment(lib, "User32.lib")
 #pragma comment(lib, "Shell32.lib")
#endif

BOOL isAdminUser(void);
int  relaunchAsAdmin(void);
TCHAR const* skipArgv0(TCHAR const* cmdline);

// メイン.
int _tmain(int argc, TCHAR* argv[]) {
    int i;
    if (!isAdminUser()) {                  // 管理者権限あり?
        _tprintf(_T("* Not admin.\n"));
        return relaunchAsAdmin();          // なかったら昇格求めて再実行.
    }

    // 適当な処理.
    _tprintf(_T("* Admin\n"));

    _tprintf(_T("cmdline: %s\n"), GetCommandLine());
    for (i = 1; i < argc; ++i)
        _tprintf(_T("[%d] %s\n"), i, argv[i]);

    fgetc(stdin);       // 1キー入力待ち.
    return 0;
}

// 管理者権限ありか?
BOOL isAdminUser(void) {
    BYTE  sid_buf[SECURITY_MAX_SID_SIZE];
    DWORD sid_size = sizeof(sid_buf);
    BOOL  is_memb  = FALSE;
    return CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, sid_buf, &sid_size)
        && CheckTokenMembership(NULL, sid_buf, &is_memb)
        && is_memb == TRUE;
}

// 管理者権限でプログラム再実行.
int relaunchAsAdmin(void) {
 #if 0  // 簡易版 (昇格時のアプリ終了コード無視)
    TCHAR exe[MAX_PATH];
    GetModuleFileName(NULL, exe, sizeof(exe)/sizeof(TCHAR));
    return (INT_PTR)ShellExecute(NULL, _T("runas"), exe, skipArgv0(GetCommandLine()), NULL, SW_SHOWNORMAL) <= 32;
 #else  // 昇格時のアプリ終了コード反映.
    SHELLEXECUTEINFO sei;
    TCHAR            exe[MAX_PATH];
    DWORD            exit_code = 1;

    GetModuleFileName(NULL, exe, MAX_PATH);
    ZeroMemory(&sei, sizeof(sei));
    sei.cbSize       = sizeof(sei);
    sei.fMask        = SEE_MASK_NOCLOSEPROCESS;
    sei.hwnd         = NULL;
    sei.lpVerb       = _T("runas");
    sei.lpFile       = exe;
    sei.lpParameters = skipArgv0(GetCommandLine());
    sei.lpDirectory  = NULL;
    sei.nShow        = SW_SHOWNORMAL;

    if (!ShellExecuteEx(&sei))
        return 1;

    WaitForSingleObject(sei.hProcess, INFINITE);
    if (GetExitCodeProcess(sei.hProcess, &exit_code) == 0)
        exit_code = 1;
    CloseHandle(sei.hProcess);
    return exit_code;
 #endif
}

// コマンドライン文字列の、先頭の実行ファイル指定(argv[0]相当) をスキップ.
TCHAR const* skipArgv0(TCHAR const* cmdline) {
    TCHAR const* s = cmdline;
    if (s) {
        char  f = 0;
        while (*s && *s <= _T(' ')) {
            ++s;
        }
        while (*s) {
            if (*s == _T('"')) {
                f = !f;
                ++s;
                continue;
            }
            if (!f && *s && *s <= _T(' '))
                break;
            ++s;
        }
        while (*s && *s <= _T(' ')) {
            ++s;
        }
    }
    return s;
}

4. 追記

(2025-08-21)
"アプリ実行途中で管理者権限に移行したいプログラム" に冒頭の文面微修正。
アプリを最初っから管理者権限で実行するだけなら、.manifest に書けばいいやん、て話だった。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity type="win32" name="YourCompany.YourApp" version="1.0.0.0"/>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>

Discussion