VBAを使って指定時刻にPCをシャットダウンする方法

# はじめに

通常、PCの電源を切る操作は電源のメニューを開いて「シャットダウン」を選択することで行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235127/89225668-db80-48a2-9add-c7e0f8197e10.png)
しかし、バックアップやウィルススキャンなどの実行中は、それらの処理が終了しないとシャットダウンはできません。

タスクスケジューラを使うと指定した時刻にPCをシャットダウンするようにできますが、設定手順が複雑なのと、シャットダウン開始までの秒数を指定する必要があり、秒数もプログラムで計算すれば、簡単にシャットダウンの予約ができるのではないかと考えました。

ちなみに、タスクスケジューラを使ってシャットダウンの予約を手順は以下の通りです。

>1. 「スタート」をクリックし、画面右上にある「すべて」をクリックします。
>2. 「すべて」の一覧が表示されます。「W」欄から「Windowsツール」をクリックします。
>3. 「Windowsツール」が表示されます。「タスクスケジューラ」をダブルクリックします。
>4. 「タスクスケジューラ」が表示されます。
「操作（A）」タブをクリックし、表示された一覧から「基本タスクの作成（B）」をクリックします。
>5. 「基本タスクの作成ウィザード」が表示されます。
「名前（A）」ボックスにタスクの名前（例えば、「シャットダウン」）を入力して、「次へ（N）」をクリックします。
>6. トリガーの設定で「1回限り（O）」をチェックして、「次へ（N）」をクリックします。
>7. 「開始（S）」ボックスにタスクを開始する日と時刻を入れて、「次へ（N）」をクリックします。
>8. 「タスクでどの操作を実行しますか？」で「プログラムの開始（T）」をクリックして、「次へ（N）」をクリックします。
>9. 「プログラムの開始」の「プログラム/スクリプト（P）」では「参照（R）」をクリックして「shutdown.exe」を選択し、「開く（O）」をクリックします（「C:\Windows\System32\shutdown.exe」がセットされます）。次に、「次へ（N）」をクリックします。
>10. 「要約」で名前、トリガー、操作が表示されるので内容に誤りがないことを確認して、「完了（F）」をクリックします。※ 修正が必要な場合は、「戻る」をクリックして前の手順に戻り修正します。
>11. 「タスクスケジューラ」画面に戻ります。
画面左側の「タスクスケジューラライブラリ」をクリックし、作成したタスクが追加されていることを確認します。

# VBA

具体的にはEXCELのシート上でシャットダウンの開始時刻（時間をB2セルに、分をD2セルに入力）を指定して、シャットダウンボタンをクリックするとその時刻にシャットダウンを開始するようにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235127/36bcb6f7-44d2-40a7-b7c9-a6f513d9030d.png)

シャットダウン開始時刻を指定するEXCELのシートを作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235127/a2ebc34f-1bf4-4c0c-bcd3-5ca1244a4e77.png)

次に、開発タブをクリックし、挿入をクリックし、フォームコントロールのボタンをシート上に挿入します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235127/f5ecfde8-fa14-4036-86f5-ac479ff2ea71.png)

フォームコントロールのボタン上のテキストを適宜編集します（下記の例では「シャットダウン」）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235127/36bcb6f7-44d2-40a7-b7c9-a6f513d9030d.png)
挿入したボタンを右クリックし、「マクロの登録（N）」を選択し、作成したプロシージャを選択して「OK」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235127/7e85d9ca-ae06-423d-95f9-56c4994cfcef.png)

これでシャットダウン開始時刻を入力して「シャットダウン」ボタンをクリックするとシャットダウンのプロシージャが実行されて指定した時刻にシャットダウンが開始されます。

# VBAのコード
VBAで行っているのは指定した時刻と現時刻の差分から秒数をカウントしてシャットダウンコマンドを実行しています。

シャットダウンを行うプロシージャ（ShutdownAtSpecifiedTime）は下記の通りです。

```vb:ShutdownAtSpecifiedTime 
Sub ShutdownAtSpecifiedTime() 'シャットダウン

    Dim targetHour As Integer
    Dim targetMinute As Integer
    Dim currentTime As Date
    Dim targetTime As Date
    Dim remainingSeconds As Long
    Dim command As String
    
    ' セルから指定時刻を取得
    targetHour = Range("B2").Value
    targetMinute = Range("D2").Value
    
    ' 現在時刻
    currentTime = Time
    
    ' 指定時刻を作成
    targetTime = TimeSerial(targetHour, targetMinute, 0)
    
    ' 翌日の時刻なら+1日
    If targetTime < currentTime Then
        targetTime = targetTime + 1
    End If
    
    ' 残り秒数を計算
    remainingSeconds = DateDiff("s", currentTime, targetTime)
    
    ' shutdownコマンドを作成・実行
    command = "shutdown /s /f /t " & remainingSeconds
    Shell command, vbHide
    
End Sub
```

# 全体の流れ
1. セルから「開始時刻（時間と分）」を取得
2. 現在時刻と比較し、あと何秒後かを計算
3. Windowsの`shutdown`コマンドで、シャットダウンを予約

# 処理の詳細
```vb:vba

' セルから指定時刻を取得
targetHour = Range("B2").Value
targetMinute = Range("D2").Value

```
* セル B2 に「時間（例：23）」、D2 に「分（例：15）」をユーザーが入力しておく必要があります。
* それを使って目標時刻を作ります。
* セルB2の入力値は0～24の整数だけを許可、セルD2の入力値は0～59の整数だけを許可するようデータの入力規則で制限をかけるか、VBAの中で制限をかけるようにすると良いと思います。

```vb:vba

currentTime = Time
targetTime = TimeSerial(targetHour, targetMinute, 0)

```
* `Time` は今現在の時刻を取得します。例えば 22:45:00を取得。
* `TimeSerial` で指定した時分秒の各値から時刻を返します。例：TimeSerial(23,15,0)で、23:15:00の時間を取得できます。

```vb:vba

If targetTime < currentTime Then
    targetTime = targetTime + 1
End If

```
* もし「指定時刻」がすでに過ぎていたら、次の日の同じ時刻として扱います（+1日）。

```vb:vba

remainingSeconds = DateDiff("s", currentTime, targetTime)

```
* `DateDiff(単位, 開始日時, 終了日時)`は開始日時から終了日時までの期間を、指定した単位で取得します。
* 本プロシージャでは現在時刻と目標時刻の差の「秒数」が必要なので、単位は秒（"s")を指定します。
* 例えば、22:45 から 23:15 までは 1800秒（=30分）。

```vb:vba

command = "shutdown /s /f /t " & remainingSeconds
Shell command, vbHide

```
* `shutdown` コマンドを作成：
    /s → シャットダウン
    /f → 強制的にアプリを終了
    /t 秒数 → 何秒後にシャットダウンするか

* `Shell`関数は、.exeファイルを実行します。したがって、`shutdown`コマンドがremainingSeconds秒後に指定したパラメタで実行されます。実行時にウィンドウの表示をしないようにするため、実行時の状態として`vbHide`を指定しています。

# おまけ
アプリケーションや保存してないファイルは 強制終了 されるため、事前に保存してから実行してください。
シャットダウンの予約をキャンセルするには、DOS窓を開いて`shutdown -a`を実行すればOKですが、同じシートに以下のプロシージャをボタンで実行できるようにしておいても良いと思います。
```vb:vba
Sub CancelShutdown() 'シャットダウン予約をキャンセル
    Dim command As String
    command = "shutdown -a"
    Shell command, vbHide
End Sub
```