AutoHotkeyでタイマーシャットダウンするGUIアプリケーション

# 概要
GUIウィンドウを表示し、指定した時刻もしくは指定した時間経過後に自動的にシャットダウンするアプリケーションです。
間違ってシャットダウンを実行してしまった時のために、最後に実行確認のダイアログを表示する安全設計。俺は自分を信用しない。
![AutoHotkeyShutdown.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189679/23520c9e-e5e7-b263-dfeb-1571e8ad3daa.png)
![AutoHotkeyShutdownConfirm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189679/5e4c35cf-1edc-624b-5ef9-54298940fb04.png)

# ソース
シャットダウン実行ダイアログの表示は[Win]+[Esc]に割り当てています。
ところで`Shutdown`コマンドの引数にある「実行後電源を切る」ってどういう機能なんですかね。Windows8.1/10用の機能？

```ini
/**
 * シャットダウン実行ダイアログを表示し、シャットダウンを実行する。
 */
ConfirmShutdown() {
    ;実行種別の値格納変数
    Static sImmediately
    Static sAtTime
    Static sAfterTime
    ;実行種別のウィンドウハンドル
    Static sHwndImmediately
    Static sHwndAtTime
    Static sHwndAfterTime

    ;シャットダウン種別の値格納変数
    Static sShutdown
    Static sReboot
    Static sLogoff
    Static sForce
    Static sAfterProcess
    ;実行種別のウィンドウハンドル
    Static sHwndShutdown
    Static sHwndReboot
    Static sHwndLogoff
    Static sHwndForce
    Static sHwndAfterProcess

    ;指定時刻の値格納変数
    Static sSpecifiedDate
    Static sSpecifiedTime
    ;指定時刻のウィンドウハンドル
    Static sHwndLabelSpecifiedDateTime
    Static sHwndSpecifiedDate
    Static sHwndSpecifiedTime

    ;指定経過時間の値格納変数
    Static sHour
    Static sMin
    Static sSec
    ;指定経過時間のウィンドウハンドル
    Static sHwndLabelAfterTime
    Static sHwndHour
    Static sHwndLabelHour
    Static sHwndMin
    Static sHwndLabelMin
    Static sHwndSec
    Static sHwndLabelSec

    ;現在設定中のタイマーの値格納変数
    Static sTimerDate
    Static sTimerTime
    ;現在設定中のタイマーのウィンドウハンドル
    Static sHwndLabelTimer
    Static sHwndTimerDate
    Static sHwndTimerTime

    ;ボタンのウィンドウハンドル
    Static sHwndExecute
    Static sHwndCancel
    Static sHwndClearTimer

    ;シャットダウン実行のGUIを作成
    Gui, Shutdown:New, , シャットダウン実行

    ;最小化ボタン有効化・最大化ボタン無効化
    Gui, Shutdown:+MinimizeBox -Resize

    ;メッセージ
    Gui, Shutdown:Add, Text, Xm+0 W320, PCをシャットダウンします。`r`n実行種別とシャットダウン種別を選択して[実行]ボタンを押してください。`r`nタイマーをセットする場合は時間を入力してください。`r`n現在設定中のタイマーを解除する場合、[タイマー解除]ボタンを押してください。

    ;シャットダウン種別
    Gui, Shutdown:Add, GroupBox, Xm+0 Ym+80 W320 H76, シャットダウン種別
    Gui, Shutdown:Add, Radio, HwndsHwndShutdown Vsshutdown Xp+10 Yp+20 Group Checked, シャットダウン
    Gui, Shutdown:Add, Radio, HwndsHwndReboot VsReboot, 再起動
    Gui, Shutdown:Add, Radio, HwndsHwndLogoff VsLogoff, ログオフ
    Gui, Shutdown:Add, Checkbox, X+80 Y+-30 HwndsHwndForce VsForce Checked, 強制的に実行
    Gui, Shutdown:Add, Checkbox, HwndsHwndAfterProcess VsAfterProcess, 実行後電源を切る

    ;実行種別グループボックス
    Gui, Shutdown:Add, GroupBox, Xm+0 Y+15 W320 H148, 実行種別

    ;指定時刻
    Gui, Shutdown:Add, GroupBox, HwndsHwndLabelSpecifiedDateTime Xm+10 Yp+35 W300 H48,
    Gui, Shutdown:Add, DateTime, HwndsHwndSpecifiedDate VsSpecifiedDate GCalcTime Xp+10 Yp+20 W120, yyyy年MM月dd日
    Gui, Shutdown:Add, DateTime, HwndsHwndSpecifiedTime VsSpecifiedTime GCalcTime X+10 W70, Time

    ;指定経過時間
    Gui, Shutdown:Add, GroupBox, HwndsHwndLabelAfterTime Xm+10 Y+15 W300 H48,
    Gui, Shutdown:Add, Edit, HwndsHwndHour VsHour Xp+10 Yp+20 W50 Number,
    Gui, Shutdown:Add, UpDown, ,
    Gui, Shutdown:Add, Text, HwndsHwndLabelHour GCalcTime X+3 Yp+6, 時間

    Gui, Shutdown:Add, Edit, HwndsHwndMin VsMin GCalcTime X+10 Yp-6 W50 Number,
    Gui, Shutdown:Add, UpDown, Range-1-60,
    Gui, Shutdown:Add, Text, HwndsHwndLabelMin X+3 Yp+6, 分

    Gui, Shutdown:Add, Edit, HwndsHwndSec VsSec GCalcTime X+10 W50 Number Yp-6,
    Gui, Shutdown:Add, UpDown, Range-1-60,
    Gui, Shutdown:Add, Text, HwndsHwndLabelSec X+3 Yp+6, 秒 後

    ;現在設定中のタイマー
    Gui, Shutdown:Add, GroupBox, HwndsHwndLabelTimer Xm+0 Y+26 W320 H48, 現在設定中のタイマー
    Gui, Shutdown:Add, Edit, HwndsHwndTimerDate VsTimerDate Xp+10 Yp+20 W120 ReadOnly,
    Gui, Shutdown:Add, Edit, HwndsHwndTimerTime VsTimerTime X+10 W70 ReadOnly,

    ;ボタン
    Gui, Shutdown:Add, Button, HwndsHwndExecute GShutdownExec Xm+0 Y+20 W100 Default, 実行(&E)
    Gui, Shutdown:Add, Button, HwndsHwndCancel GShutdownCancel X+10 W100, キャンセル(&C)
    Gui, Shutdown:Add, Button, HwndsHwndClearTimer GClearTimer X+10 W100, タイマー解除(&T)

    ;実行種別ラジオボタン
    Gui, Shutdown:Add, Radio, HwndsHwndImmediately VsImmediately GSelectType Xm+18 Ym+182 Group Checked, 即時実行
    Gui, Shutdown:Add, Radio, HwndsHwndAtTime VsAtTime GSelectType, 指定時刻に実行
    Gui, Shutdown:Add, Radio, HwndsHwndAfterTime VsAfterTime GSelectType Y+42, 指定時間経過後に実行

    If (gShutdownTimer != "")
    {
        ;シャットダウンタイマーが空でない場合、現在設定中のタイマー時刻を表示
        FormatTime, vTimerDate, %gShutdownTimer%, yyyy年MM月dd日
        FormatTime, vTimerTime, %gShutdownTimer%, HH:mm:ss

        ;GUIを更新
        GuiControl, , %sHwndTimerDate%, %vTimerDate%
        GuiControl, , %sHwndTimerTime%, %vTimerTime%
    }
    Else
    {
        ;シャットダウンタイマーが空の場合、現在設定中のタイマーと[タイマー解除]ボタンを非活性
        GuiControl, Disable, %sHwndLabelTimer%
        GuiControl, Disable, %sHwndTimerDate%
        GuiControl, Disable, %sHwndTimerTime%

        GuiControl, Disable, %sHwndClearTimer%
    }

    ;GUIの表示
    Gui, Shutdown:Show

    GuiControl, , %sHwndAfterTime%, 1
    GuiControl, , %sHwndAtTime%, 1
    GuiControl, , %sHwndImmediately%, 1

/**
 * 実行種別ラジオボタン選択時の動作。
 */
SelectType:
    ;GUI変数を確定
    Gui, Shutdown:Submit, NoHide

    If (sImmediately)
    {
        ;即時実行の場合、指定時刻・指定時間経過後を無効
        GuiControl, Disable, %sHwndLabelSpecifiedDateTime%
        GuiControl, Disable, %sHwndSpecifiedDate%
        GuiControl, Disable, %sHwndSpecifiedTime%

        GuiControl, Disable, %sHwndLabelAfterTime%
        GuiControl, Disable, %sHwndHour%
        GuiControl, Disable, %sHwndLabelHour%
        GuiControl, Disable, %sHwndMin%
        GuiControl, Disable, %sHwndLabelMin%
        GuiControl, Disable, %sHwndSec%
        GuiControl, Disable, %sHwndLabelSec%

        ;画面更新を停止
        SetTimer, ShutdownGuiRefresh, Off
    }
    If (sAtTime)
    {
        ;指定時刻に実行の場合、指定時刻を有効・指定時間経過後を無効
        GuiControl, Enable, %sHwndLabelSpecifiedDateTime%
        GuiControl, Enable, %sHwndSpecifiedDate%
        GuiControl, Enable, %sHwndSpecifiedTime%

        GuiControl, Disable, %sHwndLabelAfterTime%
        GuiControl, Disable, %sHwndHour%
        GuiControl, Disable, %sHwndLabelHour%
        GuiControl, Disable, %sHwndMin%
        GuiControl, Disable, %sHwndLabelMin%
        GuiControl, Disable, %sHwndSec%
        GuiControl, Disable, %sHwndLabelSec%

        ;画面更新を開始
        SetTimer, ShutdownGuiRefresh, 0
    }
    If (sAfterTime)
    {
        ;指定時間経過後に実行の場合、指定時刻を無効・指定時間経過後を有効
        GuiControl, Disable, %sHwndLabelSpecifiedDateTime%
        GuiControl, Disable, %sHwndSpecifiedDate%
        GuiControl, Disable, %sHwndSpecifiedTime%

        GuiControl, Enable, %sHwndLabelAfterTime%
        GuiControl, Enable, %sHwndHour%
        GuiControl, Enable, %sHwndLabelHour%
        GuiControl, Enable, %sHwndMin%
        GuiControl, Enable, %sHwndLabelMin%
        GuiControl, Enable, %sHwndSec%
        GuiControl, Enable, %sHwndLabelSec%

        ;画面更新を開始
        SetTimer, ShutdownGuiRefresh, 0
    }
Return

/**
 * 指定時刻・指定時間経過入力時の動作。
 */
CalcTime:
    ;GUI変数を確定
    Gui, Shutdown:Submit, NoHide

    If (sAtTime)
    {
        ;日付の更新を時刻に反映させる
        FormatTime, vSpecifiedDate, %sSpecifiedDate%, yyyyMMdd
        FormatTime, vSpecifiedTime, %sSpecifiedTime%, HHmmss
        vSpecifiedTime := vSpecifiedDate . vSpecifiedTime

        ;GUIを更新
        GuiControl, , %sHwndSpecifiedTime%, %vSpecifiedTime%
    }

    If (sAfterTime)
    {
        If (sSec >= 60 || sMin >= 60)
        {
            ;秒または分が60の場合、繰り上げを行う
            vMin := sSec // 60
            vSec := sSec - (vMin * 60)
            vMin := sMin + vMin

            vHour := vMin // 60
            vMin := vMin - (vHour * 60)
            vHour := sHour + vHour

            ;GUIを更新
            GuiControl, , %sHwndSec%, %vSec%
            GuiControl, , %sHwndMin%, %vMin%
            GuiControl, , %sHwndHour%, %vHour%
        }

        If (sSec < 0 || sMin < 0)
        {
            ;秒または分が-1の場合、繰り下げ処理を行う
            If (sSec < 0)
            {
                ;秒が-1の場合
                If (sHour == 0 && sMin == 0)
                {
                    ;時と分がどちらも0の場合、それ以上繰り下げ処理を行わない
                    vMin := 0
                    vSec := 0
                }
                Else
                {
                    ;それ以外の場合、分を繰り下げ処理
                    vMin := sMin - 1
                    vSec := 59
                }
            }
            Else
            {
                ;-1でない場合
                vMin := sMin
                vSec := sSec
            }

            If (vMin < 0)
            {
                ;分が-1の場合
                If (sHour == 0)
                {
                    ;時間が0の場合、それ以上繰り下げ処理を行わない
                    vHour := 0
                    vMin := 0
                }
                Else
                {
                    ;それ以外の場合、時間を繰り下げ処理
                    vHour := sHour - 1
                    vMin := 59
                }
            }
            Else
            {
                ;-1でない場合
                vHour := sHour
            }

            ;GUIを更新
            GuiControl, , %sHwndSec%, %vSec%
            GuiControl, , %sHwndMin%, %vMin%
            GuiControl, , %sHwndHour%, %vHour%
        }
    }

    ;Guiの定期更新を開始
    SetTimer, ShutdownGuiRefresh, 0
Return

/**
 * GUIの時刻を更新する。
 */
ShutdownGuiRefresh:
    ;GUI変数を確定
    Gui, Shutdown:Submit, NoHide

    ;更新間隔を設定
    vTimer :=  250
    SetTimer, ShutdownGuiRefresh, %vTimer%

    ;現在日時を取得
    FormatTime, vNowDateTime, , yyyyMMddHHmmss

    If (sAtTime)
    {
        ;シャットダウン時刻を計算
        vTime := sSpecifiedTime
        EnvSub, vTime, vNowDateTime, Seconds

        If (vTime > 0)
        {
            ;現在時刻以降を指定した場合、時間・分・秒を計算
            vHour := (vTime // 60 // 60)
            vMin := (vTime - (vHour * 60 * 60)) // 60
            vSec := (vTime - (vHour * 60 * 60) - (vMin * 60))
        }
        Else
        {
            ;現在時刻より前を指定した場合、全て0
            vHour := 0
            vMin := 0
            vSec := 0
        }

        ;GUIを更新
        GuiControl, , %sHwndHour%, %vHour%
        GuiControl, , %sHwndMin%, %vMin%
        GuiControl, , %sHwndSec%, %vSec%
    }

    If (sAfterTime)
    {
        ;シャットダウン時刻を計算
        vSepcifiedDateTime := vNowDateTime

        EnvAdd, vSepcifiedDateTime, sHour, Hours
        EnvAdd, vSepcifiedDateTime, sMin, Minutes
        EnvAdd, vSepcifiedDateTime, sSec, Seconds

        ;GUIを更新
        GuiControl, , %sHwndSpecifiedDate%, %vSepcifiedDateTime%
        GuiControl, , %sHwndSpecifiedTime%, %vSepcifiedDateTime%
    }
Return

/**
 * [実行]ボタンクリック時の動作。
 */
ShutdownExec:
        ;シャットダウン種別
        gShutdownType := sShutdown + sReboot * 2 + sForce * 4 + sAfterProcess * 8
        vShutdownType := sShutdown + sReboot * 2 == 0 ? "ログオフ" : sShutdown + sReboot * 2 == 1 ? "シャットダウン" : "再起動"
        ;GUI変数を確定
        Gui, Shutdown:Submit, NoHide

    If (sImmediately || (sHour == 0 && sMin == 0 && sSec == 0))
    {
        ;即時実行またはシャットダウン時刻が0秒後の場合、シャットダウンを即時実行
        SetTimer, StartShutdown, 0
    }
    Else
    {
        ;即時実行以外の場合、シャットダウン実行確認タイマーを設定し、バルーンメッセージを表示
        vTimerMSec := ((sHour * 60 * 60) + (sMin * 60) + sSec) * 1000
        FormatTime, vStartTime, %sSpecifiedTime%, M月dd日 H時mm分ss秒
        SetTimer, StartShutdown, %vTimerMSec%

        TrayTip, ShutDown Timer, %vShutdownType%の実行を予約しました。`r`n%vStartTime%に開始されます。`r`n`r`nタイマーを解除する場合はもう一度[シャットダウンの実行]を表示し、[タイマー解除]を選択してください。, 2, 17

        ;現在設定中のタイマーを更新
        gShutdownTimer := sSpecifiedTime
    }

    ;GUIウィンドウを破棄
    Gui, Shutdown:Destroy
Return

/**
 * [タイマー解除]ボタンクリック時の動作。
 */
ClearTimer:
    ;シャットダウン実行確認タイマーを解除し、バルーンメッセージを表示
    SetTimer, StartShutdown, Off
    gShutdownTimer := ""
    gShutdownType := ""
    TrayTip, ShutDown Timer, `r`n設定済みのタイマーを解除しました。, 2, 17

    ;現在設定中のタイマーをクリアし、[タイマー解除]ボタンを非活性
    GuiControl, , %sHwndTimerDate%,
    GuiControl, , %sHwndTimerTime%,

    GuiControl, Disable, %sHwndLabelTimer%
    GuiControl, Disable, %sHwndTimerDate%
    GuiControl, Disable, %sHwndTimerTime%

    GuiControl, Disable, %sHwndClearTimer%
Return

/**
 * [Esc][閉じる][キャンセル]ボタンクリック時の動作。
 */
ShutdownGuiEscape:
ShutdownGuiClose:
ShutdownCancel:
    ;メッセージ
    vMessage := "シャットダウンの実行を中止しました。"
    If (gShutdownTimer)
    {
        ;現在設定中のタイマーがある場合、メッセージを追加
        vMessage := vMessage . "`r`n設定済みのタイマーは有効です。"
    }

    ;バルーンメッセージを表示
    TrayTip, ShutDown Timer, %vMessage%, 2, 17

    ;GUIウィンドウを破棄
    Gui, Shutdown:Destroy
Return
}

/**
 * タイマーをオフにし、シャットダウン実行確認を表示する。
 */
StartShutdown:
    ;シャットダウン種別
    vShutdownType := gShutdownType
    vShutdownTypeName := Mod(gShutdownType, 4) == 0 ? "ログオフ" : Mod(gShutdownType, 4) == 1 ? "シャットダウン" : "再起動"

    ;シャットダウン実行確認タイマーを解除
    SetTimer, StartShutdown, Off
    gShutdownTimer := ""
    gShutdownType := ""

    ;メッセージボックスのタイトル(グローバル変数)
    gWinTitle := vShutdownTypeName . "開始"

    ;カウントダウン表示更新を開始
    SetTimer, ShutdownCountDown, 0

    ;タイムアウトに10秒を設定し、メッセージボックスを表示
    MsgBox, 262209, %gWinTitle%, [OK]を選択するか10秒後に%vShutdownTypeName%します。`[キャンセル]を選択すると%vShutdownTypeName%を中止します。`r`n`r`nあと !Sec! 秒, 10

    IfMsgBox, Cancel
    {
        ;[キャンセル]が押された場合、カウントダウン表示更新を解除し、グローバル変数をクリア
        SetTimer, ShutdownCountDown, Off
        gWinTitle := ""
        gHwnd := ""
        gText := ""
        Return
    }

    ;[OK]が押された/タイムアウトした場合、シャットダウン実行
    ;ConfirmDoTimaEnd()
    Shutdown, %vShutdownType%
Return

/**
 * シャットダウン実行確認をカウントダウンし、表示を更新する。
 */
ShutdownCountDown:
    ;通常は更新間隔を1000ミリ秒に設定
    vTimer := 1000

    If (gHwnd == "")
    {
        ;ウィンドウハンドル未取得の場合、メッセージボックスのウィンドウハンドルを取得
        ControlGet, sHwnd, Hwnd, ,Static2, %gWinTitle%
        gHwnd := sHwnd

        If (gHwnd == "")
        {
            ;ウィンドウハンドルが取得できなかった場合、再取得
            vTimer := 0
        }

        ;カウントダウン時間を10秒に設定
        vSec := 10
    }

    ;更新の再実行を設定
    SetTimer, ShutdownCountDown, %vTimer%

    If (gHwnd != "")
    {
        ;ウィンドウハンドル取得済みの場合
        If (gText == "")
        {
            ;メッセージボックスの表示メッセージ未取得の場合、メッセージを取得してグローバル変数に格納する
            ControlGetText, vText, Static2, A
            gText := vText
        }

        ;表示用テキストをカウントダウン後の内容で更新
        vSetText := gText
        StringReplace, vSetText, vSetText, !Sec!, %vSec%, All

        ;メッセージボックスのテキストを更新
        ControlSetText, , %vSetText%, ahk_id %gHwnd%

        ;カウントダウン時間をマイナス1秒
        vSec -= 1
    }
Return

/**
 * シャットダウン実行確認
 *
 * @hotkey [Win]+[Esc]
 * @target デフォルト
 */
#Esc::
    IfWinNotExist, シャットダウンの実行 ahk_exe AutoHotkey.exe
    {
        ConfirmShutdown()
    }
    Else
    {
        WinActivate
    }
Return
```