DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

UACが有効の時、必要な処理だけ管理者に昇格させて実行する

UACが有効の時、アプリケーションを管理者に昇格させて起動する」ではアプリケーション起動時に管理者に昇格させる方法を紹介しました。ここでは、必要な処理を行なうときだけ昇格させる方法を紹介します。

ユーザー アカウント制御 (UAC: User Account Control) - Windows 7 対応アプリケーションの互換性」によるとその方法は2つあり、「ShellExecute() もしくは ShellExecuteEx() を使用して、親プログラムから起動する」方法と、「COM オブジェクトとして分離する」方法です。ここでは簡単な前者の方法のみを紹介します。後者の方法は、「this.Pose() as Expert」などを参考にしてください。

昇格が必要な処理だけを別プロセスで起動する

メインのプログラム(親プログラム)では管理者権限が必要ない処理だけを行い、管理者権限が必要な処理は別のプログラム(子プログラム)として分離します。そして管理者権限が必要な処理を行う時は、親プログラムから子プログラムをProcess.Startメソッドで起動します。

管理者権限が必要な子プログラムは、「UACが有効の時、アプリケーションを管理者に昇格させて起動する」で紹介している方法で起動時に昇格されるようにします。つまり、マニュフェストを追加するか、runas動詞を付けて起動するかのどちらか(両方でも問題はありません)をします。

親プログラムから子プログラムを起動した時、モーダルになるようにする(子プログラムが終了するまで親プログラムが停止されるようにする)には、WaitForExitメソッドを使います(詳しくは「外部アプリケーションを起動して終了まで待機する」で説明しています)。

また、「ユーザーアカウント制御」ダイアログが親プログラム上に表示されるようにするには、ProcessStartInfoのErrorDialogプロパティをtrueにして、ErrorDialogParentHandleプロパティに親プログラムのウィンドウハンドルを設定します。

なお「User Account Control」のガイドラインによれば、「ユーザーアカウント制御」ダイアログでユーザーにキャンセルされて起動できなかった時でもエラーメッセージを出してはいけません。それ以外にも幾つかのガイドラインが書かれていますので(昇格は設定ごとではなく、タスクごとに行なわれるようにするなど)、一度目を通しておくことをお勧めします。

以下の例では、この方法で管理者に昇格してプログラムを起動するメソッドを作成し、それをButton1のClickイベントハンドラで呼び出しています。このコードは、ボタンコントロール「Button1」が配置されているフォームのクラス内に記述されているものとします。

VB.NET
コードを隠すコードを選択
'Button1のClickイベントハンドラ
Private Sub Button1_Click(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) Handles Button1.Click
    'setclock.exeを昇格して実行する
    RunElevated(System.IO.Path.Combine(Application.StartupPath, "setclock.exe"), _
                "2010/10/10 00:00:00", Me, True)
End Sub

''' <summary>
''' 管理者権限が必要なプログラムを起動する
''' </summary>
''' <param name="fileName">プログラムのフルパス。</param>
''' <param name="arguments">プログラムに渡すコマンドライン引数。</param>
''' <param name="parentForm">親プログラムのウィンドウ。</param>
''' <param name="waitExit">起動したプログラムが終了するまで待機する。</param>
''' <returns>起動に成功した時はtrue。
''' 「ユーザーアカウント制御」ダイアログでキャンセルされた時はfalse。</returns>
Public Shared Function RunElevated(ByVal fileName As String, _
                                   ByVal arguments As String, _
                                   ByVal parentForm As Form, _
                                   ByVal waitExit As Boolean) As Boolean
    'プログラムがあるか調べる
    If Not System.IO.File.Exists(fileName) Then
        Throw New System.IO.FileNotFoundException()
    End If

    Dim psi As New System.Diagnostics.ProcessStartInfo()
    'ShellExecuteを使う。デフォルトtrueなので、必要はない。
    psi.UseShellExecute = True
    '昇格して実行するプログラムのパスを設定する
    psi.FileName = fileName
    '動詞に「runas」をつける
    psi.Verb = "runas"
    '子プログラムに渡すコマンドライン引数を設定する
    psi.Arguments = arguments

    If parentForm IsNot Nothing Then
        'UACダイアログが親プログラムに対して表示されるようにする
        psi.ErrorDialog = True
        psi.ErrorDialogParentHandle = parentForm.Handle
    End If

    Try
        '起動する
        Dim p As System.Diagnostics.Process = System.Diagnostics.Process.Start(psi)
        If waitExit Then
            '終了するまで待機する
            p.WaitForExit()
        End If
    Catch generatedExceptionName As System.ComponentModel.Win32Exception
        '「ユーザーアカウント制御」ダイアログでキャンセルされたなどによって
        '起動できなかった時
        Return False
    End Try

    Return True
End Function
C#
コードを隠すコードを選択
//Button1のClickイベントハンドラ
private void Button1_Click(object sender, EventArgs e)
{
    //setclock.exeを昇格して実行する
    RunElevated(System.IO.Path.Combine(Application.StartupPath, "setclock.exe"),
        "2010/10/10 00:00:00", this, true);
}

/// <summary>
/// 管理者権限が必要なプログラムを起動する
/// </summary>
/// <param name="fileName">プログラムのフルパス。</param>
/// <param name="arguments">プログラムに渡すコマンドライン引数。</param>
/// <param name="parentForm">親プログラムのウィンドウ。</param>
/// <param name="waitExit">起動したプログラムが終了するまで待機する。</param>
/// <returns>起動に成功した時はtrue。
/// 「ユーザーアカウント制御」ダイアログでキャンセルされた時はfalse。</returns>
public static bool RunElevated(string fileName, string arguments,
    Form parentForm, bool waitExit)
{
    //プログラムがあるか調べる
    if (!System.IO.File.Exists(fileName))
    {
        throw new System.IO.FileNotFoundException();
    }

    System.Diagnostics.ProcessStartInfo psi =
        new System.Diagnostics.ProcessStartInfo();
    //ShellExecuteを使う。デフォルトtrueなので、必要はない。
    psi.UseShellExecute = true;
    //昇格して実行するプログラムのパスを設定する
    psi.FileName = fileName;
    //動詞に「runas」をつける
    psi.Verb = "runas";
    //子プログラムに渡すコマンドライン引数を設定する
    psi.Arguments = arguments;

    if (parentForm != null)
    {
        //UACダイアログが親プログラムに対して表示されるようにする
        psi.ErrorDialog = true;
        psi.ErrorDialogParentHandle = parentForm.Handle;
    }

    try
    {
        //起動する
        System.Diagnostics.Process p = System.Diagnostics.Process.Start(psi);
        if (waitExit)
        {
            //終了するまで待機する
            p.WaitForExit();
        }
    }
    catch (System.ComponentModel.Win32Exception)
    {
        //「ユーザーアカウント制御」ダイアログでキャンセルされたなどによって
        //起動できなかった時
        return false;
    }

    return true;
}

コントロールに盾アイコンを表示する

上の例では、Button1をクリックすると「ユーザーアカウント制御」ダイアログが表示され、昇格が必要になります。「User Account Control」のガイドラインによると、UACが有効になっている時に昇格が必要なタスクを開始するボタン(あるいはその他のコントロール)には、盾アイコンを表示することが推奨されています。

コントロールに盾アイコンを表示する方法は、「コントロールにUAC盾アイコンを表示する」で説明しています。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • イベントハンドラの意味が分からない、C#のコードをそのまま書いても動かないという方は、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。