はじめに
Delphi の Discord で管理者権限をチェックする関数を提供しました。
せっかくなので、Qiita にも書いておくことにしました。
管理者権限をチェックする
Win32 API の CheckTokenMembership を使い、Sid(Security ID) がグループ(今回は管理者グループ)に属しているかチェックします。
Sid の取得方法
昔の記事を見ると AllocateAndInitializeSid API に SECURITY_NT_AUTHORITY
, SECURITY_BUILTIN_DOMAIN_RID
, DOMAIN_ALIAS_RID_ADMINS
を渡して Sid を貰うという方法が出てくるかも知れません。
この方法だと管理者グループを示す Sid は取れるのですが「昇格した管理者権限」についての Sid は取得できません。
つまり、ユーザーに UAC のダイアログが出て実行を許可された場合に付与される一時的な管理者権限をチェックできません。
これを回避するために CreateWellKnownSid API を使って Sid を取得します。
この API は WELL_KNOWN_SID_TYPE 列挙型 に定義されている良く使われるグループの Sid を返してくれます。
今回はその中から WinBuiltinAdministratorsSid
を使います。これはビルトイン管理者グループを示す列挙子です。
実装
API 定義
上記に紹介した、CheckTokenMembership と CreateWellKnownSid はデフォルトでは定義が存在しないので、まずこれを定義します。
function CheckTokenMembership(
TokenHandle: THandle;
SidToCheck: PSID;
var IsMember: BOOL): BOOL; stdcall;
external advapi32
name 'CheckTokenMembership';
function CreateWellKnownSid(
WellKnownSidType: DWORD;
DomainSid: PSID;
ptrSID: PSID;
var cbSid: DWORD): BOOL; stdcall;
external advapi32
name 'CreateWellKnownSid';
これらの API は型定義からもわかるように、成功すると True を返します。
Administrator 判定
次に、この2つの API を組み合わせて管理者グループに属しているかをチェックします。
2023/10/10 修正
64bit アプリケーションの場合、正しく動作しなかったため修正しました。
function IsAdmin: Boolean;
const
// API 同様、定数も定義されていないのでここで定義
WinBuiltinAdministratorsSid = 26;
begin
Result := False;
var GroupSid: PSID := nil;
var GroupSidSize: DWORD := 0;
// nil を渡して必要なサイズを受取る
CreateWellKnownSid(WinBuiltinAdministratorsSid, nil, nil, GroupSidSize);
if GetLastError = ERROR_INSUFFICIENT_BUFFER then
begin
// 受取ったサイズ分メモリを確保
GetMem(GroupSid, GroupSidSize);
try
var IsMember: BOOL := False;
if
// ビルトイン管理者グループの Sid を取得
CreateWellKnownSid(
WinBuiltinAdministratorsSid,
nil,
GroupSid,
GroupSidSize
) and
// プロセスがビルトイン管理者グループのメンバーかチェック
// 第一引数はアクセストークン、0 を指定すると現在のプロセスのトークンを使う
CheckTokenMembership(0, GroupSid, IsMember)
then
Result := IsMember;
finally
FreeMem(GroupSid);
end;
end;
end;
この関数 IsAdmin を使うと自分が管理者権限を持っているのか、が解ります。
procedure TForm1.Button1Click(Sender: TObject);
begin
if IsAdmin then
管理者権限が必要な何らかの処理
else
ShowMessage('管理者権限がありません');
end;
管理者としてアプリを実行する
IsAdmin はインストーラーを作ったり、特殊な場所にファイルをコピーする時に RunAsAdmin と一緒に使われる事が多いです。
RunAsAdmin は exe ファイルを管理者権限で起動する関数です。
以下のように ShellExecuteEx API に lpVerb(動詞) = 'runas'
を指定すると管理者権限として起動する機能をラッピングしただけです。
function RunAsAdmin(
// アプリケーションのフルパス
const AExeName: String;
// アプリケーションに渡すスイッチ
const ASwitch: String;
// アプリケーションを表示するかどうか
const AShowWindow: Boolean = True): Boolean;
const
SHOW_WINDOW: array [Boolean] of Cardinal = (SW_HIDE, SW_SHOWNORMAL);
MASK_NO_CONSOLE: array [Boolean] of Cardinal = (SEE_MASK_NO_CONSOLE, 0);
var
SEI: TShellExecuteInfo;
begin
ZeroMemory(@SEI, SizeOf(SEI));
with SEI do
begin
cbSize := SizeOf(SEI);
Wnd := 0;
fMask := SEE_MASK_FLAG_NO_UI or MASK_NO_CONSOLE[AShowWindow];
lpVerb := 'runas';
lpFile := PChar(AExeName);
lpParameters := PChar(ASwitch);
nShow := SHOW_WINDOW[AShowWindow];
end;
Result := ShellExecuteEx(@SEI);
end;
以下は C ドライブのルートにファイルをコピーする例です。
procedure TForm1.Button1Click(Sender: TObject);
begin
if IsAdmin then
// 管理者権限を持っている場合は普通の方法でコピーできる
TFile.Copy('D:\Temp\Foo.txt', 'C:\Foo.txt');
else
// 管理者権限を持っていない場合は cmd.exe を管理者として実行し copy させる
RunAsAdmin('cmd.exe', '/D /C "copy D:\Temp\Foo.txt C:\"', False);
end;
最後に
Delphi だと必要な API が定義されていないため、古い方法を使っているコードを見ます。
API は手軽に定義できるので、現在ではこちらの方法を使う方が良いでしょう。