C# Programming

Image

Windows の シャットダウン、ログオフ

開発環境: Visual Studio 2003 

1.目次

2.目的

Windows をシャットダウンする方法について、.Net/C# eGroup で質問がありました。
ちょっとと調べたんですが、これも結構やっかいなのでメモっておきます。

3.参考書

(1) MSDN Shutting Down
    SE_SHUTDOWN_NAME 特権の説明、サンプルコードが書いてあります。7章のテストコードは、このサンプルコード参考にしています。
(2) アンマネージコードとの相互運用
(3) クラス、構造体、および共用体のマーシャリング
(4) http://www.dotnet247.com/247reference/msgs/9/49583.aspx
    このサンプルが一番参考になりました。MSDNだけでは、きっとできなかったと思う。
(5) .Net/C# eGroup No.267 スレッド
    元ネタです。
(6) マイクロソフト サポート技術情報 - 220706 Windows95/98 でユーザーを強制的にログオフさせる。
(7) C#プログラミング (Windows バージョンの判定)

4.シャットダウン、ログオフする Windows.Forms アプリケーション

 
次のような、Windows をシャットダウン、ログオフする Windows.Forms アプリケーションを作る。
注意
Windows XP Professionalで動作を確認しました。
それ以外は確認していません。どなたか、98系で確認したら結果を教えてね。m_ _m

Image

5.95、98系のシャットダウン


匿名希望様より、2003/1/8   初版作成のコードでは、これをしないで、EWX_POWEROFF | EWX_FORCEIFHUNG を指定するだけだと、ログオフするだけで、シャットダウン にならないという情報をいただきました。

そこで、いろいろ調べてみたところ、次のようなことが判明しました。

1.95、98系のシャットダウンでは、ユーザーを強制的にログオフさせるため、EWX_FORCE フラグを指定して ExitWindowsEx() をコールすると、シェルの仕様により、ログオフに失敗します。プログラムによって、ユーザーを強制的にログオフさせるには、まず、必ず、Explorer のプロセスを終了させ、それから、EWX_LOGOFF および EWX_FORCE フラグを指定して、ExitWindowsEx() をコールする必要があります。
詳細は参考書(6)を見てください。

2.EWX_FORCEIFHUNG は、95、98系ではサポートされていません。

そこで、2003/10/26 版のコードでは、次のような対応を入れてみました。

95、98系か、NT系かの判断を行う。Windows バージョンの判定方法は、参考書(7)を参照してください。
95、98系であれば、FORCEIFHUNG ボタンをディスエーブルする。
95,98系で、FORCE のチェックボックスがチェックされている場合には、ExitWindowsEx() を呼び出す前に、すべてのエクスプローラプロセスをKillする。

エクスプローラを終了させるコードを次に示します。

注意
2003/10/26日付のサンプルコードで、対応してみましたが、95, 98環境が無いので、動作未確認です。
どなたか、98系で確認したら結果を教えてね。m_ _m
Explorer プロセスすべてをKillするコード(動作未確認)
        //////////////////////////////////////////////////////
        /// Windows Me 以前の場合
        //////////////////////////////////////////////////////

        private static void KillExplorer()
        {
            // Windows Me 以前であれば、EWX_FORCE フラグを指定して ExitWindowsEx() をコールすると、
            // シェルの仕様により、ログオフに失敗します。
            // プログラムによって、ユーザーを強制的にログオフさせるには、必ずExplorer のプロセスを終了させ、
            // それから、EWX_LOGOFF および EWX_FORCE フラグを指定して、
            // ExitWindowsEx() をコールする必要があります。
            if (System.Environment.OSVersion.Platform == PlatformID.Win32Windows)
            {
                System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses();
                foreach(Process process in processes)
                {
                    if(process.ProcessName.StartsWith("explorer"))
                        process.Kill();
                }
            }
        }

6.NT系では SE_SHUTDOWN_NAME 特権が必要!

.Net Framework から、シャットダウンする方法としては、WMI のWin32_OperatingSystemクラスの Shutdown, Reboot メソッドがあります。
しかし、これらのメソッドは NT系のみ対応で、95・98系は対応していません。
そこで、98 系、NT系ともに対応している Platform SDK の ExitWindowsEx(...) を使用してシャットダウンします。
ただし、Windows NT, 2000, XP では、シャットダウンするための特権 SE_SHUTDOWN_NAME が必要になります。
このため、Platform SDK を呼び出す必要があります。

このために、次のステップで SE_SHUTDOWN_NAME 特権をセットします。

Step 1: プロセスのハンドルを取得する。
// プロセスのハンドルを取得する。
IntPtr hproc = System.Diagnostics.Process.GetCurrentProcess().Handle;
// IntPtr hproc = GetCurrentProcess(); // この方法でも良い。
Step 2: Token を取得する。
IntPtr hToken = IntPtr.Zero;
if ( ! OpenProcessToken( hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref hToken ) )
  throw new Exception("OpenProcessToken");
Step 3: LUID を取得する。
// SE_SHUTDOWN_NAME に対応する LUID(Local Unique Identifier) を取得します。
long luid = 0;
if ( ! LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref luid))
  throw new Exception("LookupPrivilegeValue");
Step 4: 特権をセットする。
// 特権を設定する。
TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES();
tp.PrivilegeCount = 1;
tp.Privileges = new LUID_AND_ATTRIBUTES();
tp.Privileges.Luid = luid;
tp.Privileges.Attributes = SE_PRIVILEGE_ENABLED;

// 特権をセットする。
if ( ! AdjustTokenPrivileges( hToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero ))
  throw new Exception("AdjustTokenPrivileges");

という厄介なことをしないといけません。
このあたりの説明は、MSDNに不親切に!書いてあります。

しかし、これらの OpenProcessToken(), LookupPrivilegeValue(), AdjustTokenPrivileges() は、Platform SDK なので Interop での呼び出しが必要になってしまいます。つまり、例の DllImport ディレクティブを使った関数呼び出しです。

このようなプラットフォーム呼び出しをするには、.Net Framework の構造から、アンマネージコードで扱われるデータ構造への変換(マーシャリング)が必要になります。このあたりのことは、アンマネージコードとの相互運用を参照してください。
ところが、引数の1つの TOKEN_PRIVILEGES 構造体が曲者で、入れ子になった構造体になっています。おまけに、内側の構造体が配列へのポインターになっていて、簡単にはいきません。このあたりのことは、クラス、構造体、および共用体のマーシャリングを参照してください。

これをプラットフォーム呼び出しをした際に、正しいデータ構造で渡らないと特権をセットできません。
このために、構造体の宣言の前に、[StructLayout(LayoutKind.Sequential)] をつけて、構造体のレイアウトが宣言順(シーケンシャル)になるようにする必要があります。

この構造体の宣言の中で一番注意しないといけないのが、TOKEN_PRIVILEGES です。
というのは、StructLayout はデフォルトで パッキングが8になっています。これは、8バイト境界にあわせてデータを配置するということです。
すなわち、構造体の中で int, int と来た場合に、実際は 4byte, 4byte ごとにデータが配置されるべきなのに、8byte, 8byte となってしまい、Platform SDK 側と合わなくなってしまいます。そこで、さらに StructLayout の中で Pack = 4 を指定して、4byte 境界であわせる必要があります。バイトデータの場合などには、Pack=1 というようにする必要が出てくると思います。

http://www.dotnet247.com/247reference/msgs/9/49583.aspx のサンプルコードで、Pack=1 とさりげなく書いてあったので、このことに気がつきました。もし、このサンプルが無ければわからなかったと思う。みんな良く知ってるね!
このケースでは、int のあとに long が来ているので、Pack=1 でも Pack=4 でもOKです。
どうして、デフォルトが Pack=8 なのか、理解に苦しむなぁ。。。

[StructLayout(LayoutKind.Sequential, Pack=4)]
public struct TOKEN_PRIVILEGES
{
    public int PrivilegeCount;
    public LUID_AND_ATTRIBUTES Privileges;
}

注意: 
本来 TOKEN_PRIVILEGES  は、次のように Privileges[] の配列へのポインターになっていますが、ここでは配列の要素が1つしかないという前提でマーシャリングしています。
まともにやろうとすると、配列のサイズを計算して、ポインター操作まで必要になり、結構大変になりそうです。
幸いにシャットダウンのための特権設定であれば、要素は1つだけですむので、逃げています。^^;
もうちょっとましなマーシャリングメソッドを用意してほしいなぁ!>>MS。

typedef struct _TOKEN_PRIVILEGES {
  DWORD PrivilegeCount;
  LUID_AND_ATTRIBUTES Privileges[];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;

7.Shutdownの実行

シャットダウンの方法には、先に書いたように ExitWindowsEx() を使用しています。
このほかにも、WMI の Win32_OperatingSystemクラスの Shutdown, Reboot メソッドが使えるはずですが、試してません。

[DllImport("user32.dll", SetLastError=true) ]
private static extern bool ExitWindowsEx( int flag, int reserved );

ここで、flag を設定することにより、シャットダウン、ログオフ、リブート、パワーオフの選択と、さらに強制的動作するためのフラグ2つ(EWX_FORCE, EWX_FORCEIFHUNG)を指定することができます。

Flag説明
EWX_FORCE現在実行されているアプリケーションへWM_QUERYENDSESSION メッセージや WM_ENDSESSION メッセージを送信せずにシャットダウンしていきますので、アプリケーションがデータを失う可能性もあります。そのため、このフラグは緊急時のみに使用するように注意書きがあります。
EWX_FORCEIFHUNGプロセスが WM_QUERYENDSESSION または WM_ENDSESSION メッセージに応答しない場合、それらのプロセスを終了させます。
EWX_FORCE とEWX_FORCEIFHUNG が同時に指定された場合には、EWX_FORCEが優先されます。

また、95、98系では、EWX_FORCE フラグを指定して ExitWindowsEx() をコールすると、シェルの仕様により、ログオフに失敗します。
対応方法は5章を見てください。

8.テストコード


テスト環境
Windows XP Professionalで動作を確認しました。
それ以外は確認していません。どなたか、98系で確認したら結果を教えてね。m_ _m
コードは、シャットダウン、ログオフをするためのラッパークラスである ShutdownLibWrap を実装する shutdown.cs と、Windows.Forms 本体である Form1.cs の2つのファイルから構成されます。
シャットダウンパラメータは、最初 Enum にしようかと思ったんですが、フォースを指定するためのフラグと OR する必要があるので、int で渡すようにしています。エラー処理は適当に例外をあげていますので、実際に使う場合には、ちゃんとコーディングしてね^^;

shutdown.cs
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace Uchukamen.Util
{
    /// <summary>
    /// Following information is based on Microsoft MSDN Library,
    /// Platform SDK: Windows System Information
    /// Shutting Down
    /// http://msdn.microsoft.com/library/en-us/sysinfo/base/shutting_down.asp
    /// See following URL for more detail about Interop
    /// http://www.microsoft.com/japan/msdn/library/
    ///   ja/cpguide/html/cpconoutarrayofstructssample.asp
    /// Shutdown 95, 98, 
    ///   
    /// プラットフォーム: 
    /// Windows 98, Windows NT 4.0, Windows ME, 
    /// Windows 2000, Windows XP Home Edition, Windows XP Professional, 
    /// Windows .NET Server family
    /// 
    /// 動作確認プラットフォーム: Windows XP Professional
    /// </summary>
    public class ShutdownLibWrap
    {
        #region ShutdownFlag
        
        /// EWX_FORCE
        /// 上記パラメータと同時に設定します。
        /// プロセスを強制的に終了させます。
        /// このフラグを指定すると、システムは、現在実行されているアプリケーションへ 
        /// WM_QUERYENDSESSION メッセージや WM_ENDSESSION メッセージを送信しません。
        /// この結果、アプリケーションがデータを失う可能性もあります。
        /// したがって、このフラグは、緊急時にのみ指定してください。
        public const int Force = 0x00000004;
            
        /// EWX_FORCEIFHUNG
        /// Windows 2000:プロセスが WM_QUERYENDSESSION または WM_ENDSESSION メッセージに
        /// 応答しない場合、それらのプロセスを終了させます。EWX_FORCE フラグを指定すると、
        /// EWX_FORCEIFHUNG フラグは無視されます。    
        /// </summary>
        public const int ForceIfHung = 0x00000010;

        /// EWX_LOGOFF
        /// 呼び出し側のプロセスのセキュリティコンテキストで実行されている
        /// すべてのプロセスを終了し、現在のユーザーをログオフさせます。
        public const int Logoff = 0x00000000;

        /// EWX_POWEROFF 
        /// システムをシャットダウンした後、電源を切ります。
        /// システムは、パワーオフ機能をサポートしていなければなりません。 
        /// Windows NT/2000/XP:呼び出し側のプロセスに、SE_SHUTDOWN_NAME 特権が必要です。
        public const int Poweroff = 0x00000008;

        /// EWX_REBOOT
        /// システムをシャットダウンした後、システムを再起動します。
        /// Windows NT/2000/XP:呼び出し側のプロセスに、SE_SHUTDOWN_NAME 特権が必要です。
        public const int Reboot = 0x00000002;

        /// EWX_SHUTDOWN
        /// システムをシャットダウンして、電源を切っても安全な状態にします。
        /// すべてのバッファをディスクへフラッシュし(バッファの内容をディスクに書き込み)、
        /// 動作していたすべてのプロセスを停止します。    
        /// Windows NT/2000/XP:呼び出し側のプロセスに、SE_SHUTDOWN_NAME 特権が必要です。
        public const int Shutdown = 0x00000001;

        /// <summary>
        /// <param name="flag">シャットダウン操作</param>
        /// <param name="reserved">予約済み</param>
        /// <returns></returns>
        /// </summary>
        [DllImport("user32.dll", SetLastError=true) ]
        private static extern bool ExitWindowsEx( int flag, int reserved );

        #endregion

        #region Privileges related Structures
        // LUID locally unique identifier 
        // LUID は64bit なので、直接LUID_AND_ATTRIBUTES に long で宣言する。

        /// <summary>
        /// typedef struct _LUID_AND_ATTRIBUTES { 
        ///    LUID   Luid;
        ///    DWORD  Attributes;
        /// } LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;
        /// </summary>
        [StructLayout(LayoutKind.Sequential, Pack=4)]
            public struct LUID_AND_ATTRIBUTES
        {
            public long Luid;
            public int Attributes;
        }

        /// <summary>
        /// typedef struct _TOKEN_PRIVILEGES {
        ///     DWORD PrivilegeCount;
        ///     LUID_AND_ATTRIBUTES Privileges[];
        /// } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES; 
        /// StructLayout の Pack=4 は必要。これにより、
        /// 4byte 境界でパッキングする。
        /// </summary>
        [StructLayout(LayoutKind.Sequential, Pack=4)]
            public struct TOKEN_PRIVILEGES
        {
            public int PrivilegeCount;
            public LUID_AND_ATTRIBUTES Privileges;
        }
        #endregion

        #region Privilege related APIs
        /// <summary>
        /// プロセスに関連付けられているアクセストークンを開きます。
        /// BOOL OpenProcessToken(
        ///     HANDLE ProcessHandle, // プロセスのハンドル
        ///     DWORD DesiredAccess,  // プロセスに対して希望するアクセス権
        ///     PHANDLE TokenHandle   // 開かれたアクセストークンのハンドルへのポインタ
        /// );
        /// </summary>
        [DllImport("advapi32.dll", SetLastError=true) ]
        private static extern bool OpenProcessToken(
            IntPtr ProcessHandle,
            int DesiredAccess, 
            ref IntPtr TokenHandle);

        /// <summary>
        /// 指定されたシステムで使われているローカル一意識別子(LUID)を取得し、
        /// 指定された特権名をローカルで表現します。
        /// BOOL LookupPrivilegeValue(
        ///     LPCTSTR lpSystemName, // システムを指定する文字列のアドレス
        ///     LPCTSTR lpName,  // 特権を指定する文字列のアドレス
        ///     PLUID lpLuid     // ローカル一意識別子のアドレス
        /// );
        /// </summary>
        [DllImport("advapi32.dll", SetLastError=true) ]
        private static extern bool LookupPrivilegeValue(
            string lpSystemName, 
            string lpName, 
            ref long lpLuid );

        /// <summary>
        /// 指定したアクセストークン内の特権を有効または無効にします。
        /// TOKEN_ADJUST_PRIVILEGES アクセス権が必要です。
        /// BOOL AdjustTokenPrivileges(
        ///     HANDLE TokenHandle,  // 特権を保持するトークンのハンドル
        ///     BOOL DisableAllPrivileges,   // すべての特権を無効にするためのフラグ
        ///     PTOKEN_PRIVILEGES NewState,  // 新しい特権情報へのポインタ
        ///     DWORD BufferLength,  // PreviousState バッファのバイト単位のサイズ
        ///     PTOKEN_PRIVILEGES PreviousState, // 変更を加えられた特権の元の状態を受け取る
        ///     PDWORD ReturnLength  // PreviousState バッファが必要とするサイズを受け取る
        ///     );
        /// </summary>
        [DllImport("advapi32.dll", SetLastError=true) ]
        private static extern bool AdjustTokenPrivileges(
            IntPtr TokenHandle, 
            bool DisableAllPrivileges,
            ref TOKEN_PRIVILEGES NewState, 
            int BufferLength,
            IntPtr PreviousState,
            IntPtr ReturnLength 
            );
        #endregion

        /// <summary>
        /// If the function succeeds, the return value is nonzero.
        /// </summary>
        /// <param name="flag"></param>
        /// <returns></returns>
        public static bool DoExitWindows( int flag )
        {   
            try 
            {
                /// Windows NT/2000/XP でシャットダウンするには、
                /// SE_SHUTDOWN_NAME 特権が必要なので、その特権をセットする。
                if (System.Environment.OSVersion.Platform == PlatformID.Win32NT)
                    SetShutdownPrivilege();
                /// Windows Me 以前で Force シャットダウンするには、
                /// Explorer のプロセスを終了させ、
                /// EWX_LOGOFF および EWX_FORCE フラグを指定して、
                /// ExitWindowsEx() をコールする。
                else if (System.Environment.OSVersion.Platform == PlatformID.Win32Windows &&
                    ((flag & Force) == Force))
                    KillExplorer();
            }
            catch  (Exception)
            {
                return false;
            }
            bool result = ExitWindowsEx( flag, 0 );
            return result;
        }

        /// <summary>
        /// Windows NT/2000/XP でシャットダウンするには、
        /// SE_SHUTDOWN_NAME 特権が必要なので、その特権をセットする。
        /// </summary>
        private static void SetShutdownPrivilege()
        {
            const int TOKEN_QUERY = 0x00000008;
            const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
            const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
            const int SE_PRIVILEGE_ENABLED = 0x00000002;

            // プロセスのハンドルを取得する。
            IntPtr hproc = System.Diagnostics.Process.GetCurrentProcess().Handle;
            // IntPtr hproc = GetCurrentProcess(); // この方法でもOK.

            // Token を取得する。
            IntPtr hToken = IntPtr.Zero;
            if ( ! OpenProcessToken( hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref hToken ) )
                throw new Exception("OpenProcessToken");
            
            // LUID を取得する。
            long luid = 0;
            if ( ! LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref luid))
                throw new Exception("LookupPrivilegeValue");

            // 特権を設定する。
            TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES();
            tp.PrivilegeCount = 1;
            tp.Privileges = new LUID_AND_ATTRIBUTES();
            tp.Privileges.Luid = luid;
            tp.Privileges.Attributes = SE_PRIVILEGE_ENABLED;

            // 特権をセットする。
            if ( ! AdjustTokenPrivileges( hToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero ))
                throw new Exception("AdjustTokenPrivileges");
        }

 
        //////////////////////////////////////////////////////
        /// Windows Me 以前の場合
        //////////////////////////////////////////////////////

        private static void KillExplorer()
        {
            // Windows Me 以前であれば、EWX_FORCE フラグを指定して ExitWindowsEx() をコールすると、
            // シェルの仕様により、ログオフに失敗します。
            // プログラムによって、ユーザーを強制的にログオフさせるには、必ずExplorer のプロセスを終了させ、
            // それから、EWX_LOGOFF および EWX_FORCE フラグを指定して、
            // ExitWindowsEx() をコールする必要があります。
            if (System.Environment.OSVersion.Platform == PlatformID.Win32Windows)
            {
                System.Diagnostics.Process[] processes = System.Diagnostics.Process.GetProcesses();
                foreach(Process process in processes)
                {
                    if(process.ProcessName.StartsWith("explorer"))
                        process.Kill();
                }
            }
        }
    }
}
Form1.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Uchukamen.Util;

namespace Shutdown2
{
    /// <summary>
    /// Form1 の概要の説明です。
    /// </summary>
    public class Form1 : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.ComboBox comboBox1;
        private System.Windows.Forms.GroupBox groupBox1;
        private System.Windows.Forms.CheckBox cbForceIfHung;
        private System.Windows.Forms.CheckBox cbForce;
        /// <summary>
        /// 必要なデザイナ変数です。
        /// </summary>
        private System.ComponentModel.Container components = null;

        public Form1()
        {
            //
            // Windows フォーム デザイナ サポートに必要です。
            //
            InitializeComponent();

            //
            // TODO: InitializeComponent 呼び出しの後に、コンストラクタ コードを追加してください。
            //
        }

        /// <summary>
        /// 使用されているリソースに後処理を実行します。
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        /// <summary>
        /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディタで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.comboBox1 = new System.Windows.Forms.ComboBox();
            this.groupBox1 = new System.Windows.Forms.GroupBox();
            this.cbForceIfHung = new System.Windows.Forms.CheckBox();
            this.cbForce = new System.Windows.Forms.CheckBox();
            this.groupBox1.SuspendLayout();
            this.SuspendLayout();
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(16, 168);
            this.button1.Name = "button1";
            this.button1.TabIndex = 0;
            this.button1.Text = "DoIt";
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // comboBox1
            // 
            this.comboBox1.Items.AddRange(new object[] {
                                                           "Logoff",
                                                           "Shutdown",
                                                           "Power Off",
                                                           "Reboot"});
            this.comboBox1.Location = new System.Drawing.Point(16, 24);
            this.comboBox1.Name = "comboBox1";
            this.comboBox1.Size = new System.Drawing.Size(168, 20);
            this.comboBox1.TabIndex = 1;
            // 
            // groupBox1
            // 
            this.groupBox1.Controls.AddRange(new System.Windows.Forms.Control[] {
                                                                                    this.cbForceIfHung,
                                                                                    this.cbForce});
            this.groupBox1.Location = new System.Drawing.Point(16, 64);
            this.groupBox1.Name = "groupBox1";
            this.groupBox1.Size = new System.Drawing.Size(184, 88);
            this.groupBox1.TabIndex = 2;
            this.groupBox1.TabStop = false;
            this.groupBox1.Text = "May the Force be with you ^ ^;";
            // 
            // cbForceIfHung
            // 
            this.cbForceIfHung.Location = new System.Drawing.Point(24, 56);
            this.cbForceIfHung.Name = "cbForceIfHung";
            this.cbForceIfHung.TabIndex = 1;
            this.cbForceIfHung.Text = "Force If Hung";
            this.cbForceIfHung.CheckedChanged += new System.EventHandler(this.cbForceIfHung_CheckedChanged);
            // 
            // cbForce
            // 
            this.cbForce.Location = new System.Drawing.Point(24, 24);
            this.cbForce.Name = "cbForce";
            this.cbForce.TabIndex = 2;
            this.cbForce.Text = "Force";
            this.cbForce.CheckedChanged += new System.EventHandler(this.cbForce_CheckedChanged);
            // 
            // Form1
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
            this.ClientSize = new System.Drawing.Size(224, 206);
            this.Controls.AddRange(new System.Windows.Forms.Control[] {
                                                                          this.groupBox1,
                                                                          this.comboBox1,
                                                                          this.button1});
            this.Name = "Form1";
            this.Text = "Shutdown Test";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.groupBox1.ResumeLayout(false);
            this.ResumeLayout(false);

        }
        #endregion

        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main() 
        {
            Application.Run(new Form1());
        }

        private void button1_Click(object sender, System.EventArgs e)
        {
            int flag = 0;

            switch(this.comboBox1.Text)
            {
                case "Logoff":
                    flag = ShutdownLibWrap.Logoff;
                    break;
                case "Shutdown":
                    flag = ShutdownLibWrap.Shutdown;
                    break;
                case "Poweroff":
                    flag = ShutdownLibWrap.Poweroff;
                    break;
                case "Reboot":
                    flag = ShutdownLibWrap.Reboot;
                    break;
                default:
                    throw new Exception("Unknown Shutdown Flag");
            }
            if(this.cbForce.Checked)
                flag |= ShutdownLibWrap.Force;
            else if(this.cbForceIfHung.Checked)
                flag |= ShutdownLibWrap.ForceIfHung;

            ShutdownLibWrap.DoExitWindows( flag );
        }

        private string [] shutdown = {"Logoff", "Shutdown", "Poweroff", "Reboot"}; 
        private void Form1_Load(object sender, System.EventArgs e)
        {
            this.comboBox1.Items.Clear();
            this.comboBox1.Items.AddRange(shutdown);
            this.comboBox1.SelectedIndex = 0;

            // Windows Me 以前の場合、ForceIfHung はサポートされていない。
            if (System.Environment.OSVersion.Platform == PlatformID.Win32Windows)
                this.cbForceIfHung.Enabled = false;
        }

        private void cbForce_CheckedChanged(object sender, System.EventArgs e)
        {
            if(this.cbForce.Checked)
                this.cbForceIfHung.Checked = false;        
        }

        private void cbForceIfHung_CheckedChanged(object sender, System.EventArgs e)
        {
            if(this.cbForceIfHung.Checked)
                this.cbForce.Checked = false;        
        }
    }
}

9.改訂履歴

2003/1/8   初版作成
2003/10/26   Win98 系のシャットダウンに対応したつもり。