【完全ガイド】Azure環境でWindows Updateを自動で適用するPowerShellスクリプト


## はじめに
Azure上でのWindows Update管理、ちょっと面倒に感じていませんか？

そこで今回は、**Azure環境でWindows Updateを自動化できるPowerShellスクリプト**を
紹介します。


このスクリプトを作成したのは、Automation Update Managementから
Azure Update Managerへの切り替えが必要になったことがきっかけです。

https://learn.microsoft.com/ja-jp/azure/update-manager/migration-overview?WT.mc_id=Portal-Microsoft_Azure_Automation

<br>
同じようにAutomation Update Managementからの切り替えに<br>悩んでいる人に届けば幸いです。

## この記事で得られること

:::note info<br>
**Azure VMのWindows Updateを完全自動化するスクリプトの詳細**
**定期実行のスケジュール設定方法**
**自動化に伴うトラブルシューティング**

:::

## こんな人におすすめ

:::note info<br>
**Azure環境の仮想マシンを効率よく管理したい方**
**手動でのWindows Updateに手間を感じているシステム管理者**
**PowerShellスクリプトを活用して自動化を学びたい方**
:::

## スクリプトの概要
今回紹介するスクリプトでは、以下の機能を実現します。

:::note info<br>
**１. Azure VMのWindows Updateを定期的に実行**
**２. 再起動のスケジュール設定**（任意の時間に再起動をスケジュール可能）
**３. Updateにかかる時間を指定**

:::

これにより日々のメンテナンス作業を自動化し、ヒューマンエラーのリスクを最小限に抑えます。

## 対象環境

実際に確認した環境は以下の通りです。

| OS        | VM サイズ                                 |
|-----------|------------------------------------------|
| W2012R2,W2016,W2019,W2022   | Standard_A4_v2, Standard_B2ms, Standard_D2ds_v4, Standard_D2ds_v5, Standard_D2s_v3, Standard_D4ds_v4, Standard_D4ds_v5, Standard_D4s_v3, Standard_D8ds_v5, Standard_DS11_v2, Standard_DS12_v2, Standard_DS2_v2, Standard_DS3_v2, Standard_DS4_v2, Standard_E2ds_v4, Standard_E2ds_v5, Standard_E2s_v3, Standard_E2s_v5, Standard_E4ds_v4, Standard_E4ds_v5, Standard_E4s_v3, Standard_E8ds_v5, Standard_E8s_v3, Standard_GS1|



## スクリプトの内容と導入手順

### 1. 事前準備
#### 　〇必要な環境
- **Azure仮想マシン**（Windows Server）
- **Azure PowerShellモジュール**をインストール済みであること

#### PowerShellモジュールのインストール
まずはPowerShellのAzureモジュールをインストールしてください。

```Powershell:Powershell
Install-Module -Name Az -AllowClobber -Force
```
他のモジュールが必要になる可能性もありますが、エラーに応じて
必要なモジュールを調べてインストールしてください。

#### 更新プログラムの確認
VM→更新プログラムをクリックすると更新可能なプログラム名が表示されます。
適用するパッチを指定する場合は、この画面で表示されたKB番号を指定してください
<font color="Red">**※ここに表示されていないパッチは、スクリプトで指定してもパッチが適用されません**</font>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3890692/993d4c69-d3c2-a856-c023-9447ebac4434.png)



### 2. スクリプトの解説

以下がWindows Updateを自動化するPowerShellスクリプトです。
このスクリプトは、Azureの仮想マシン上で決められた日時にWindows Updateを実行します。


```powershell
#---------------------------
#MaintenanceName
$MaintenanceName = "XXX"

#VMを代入する
$VMname =@()
$VMname += "XXXServer"
$VMname += "XXXServer"


#時間を代入する
$startTime = "2024-XX-XX 04:30"
$updateTime = "2024-XX-XX 02:00"
$stopTime = "2024-XX-XX 04:15"
$ExpirationDateTime = "2024-XX-XX 04:00"

#サブスクリプションID
$SubscriptionId = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"

#リソースグループ名
$ResourceGroupName = "XXXXX"

#AutomationAccount名
$AutomationName = "XXXXXXX"

#OS種別19
$VMOS =@()
$VMOS += "W2016"
$VMOS += "W2022"

#対象VM
$targetMachine =@()
for($i=0; $i -lt $VMname.Length; $i++){
$NM = $VMname[$i]
$targetMachine += "/subscriptions/${SubscriptionId}/resourceGroups/${ResourceGroupName}/providers/Microsoft.Compute/virtualMachines/${NM}"
}

#AzMaintenanceConfigurationに必要なパラメータ
$scope = "InGuestPatch"
$location = "japaneast"
$timeZone = "Tokyo Standard Time" 
$duration =  "02:00"
$recurEvery = "1Day"
$RebootOption = "IfRequired";
#---------------------------

#チェック用の変数
$ck_up = 0
$ck_down = 0

#Runbook名の変数
$Runbook = 'XXXX'

#起動停止のスケジュールが無い場合のメッセージ
$msg_up = "WinUp起動のスケジュール設定がなかったので設定しました"
$msg_down = "WinUp停止のスケジュール設定がなかったので設定しました"

#Azureに接続
Connect-AzAccount
Set-AzContext $SubscriptionId

$Item = Get-AzAutomationSchedule -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationName

for($i=0; $i -lt $Item.Length; $i++){
        
    $AutoName = $Item[$i].Name.ToLower()

    if($AutoName -eq "winup起動"){

    #WinUp起動設定
    New-AzAutomationSchedule `
        -AutomationAccountName $AutomationName `
        -Name $AutoName `
        -StartTime $startTime `
        -OneTime `
        -ResourceGroupName $ResourceGroupName `
        -TimeZone $timeZone

        $ck_up++

    }elseif($AutoName -eq "winup停止"){

    #WinUp停止設定
    New-AzAutomationSchedule `
        -AutomationAccountName $AutomationName `
        -Name $AutoName `
        -StartTime $stopTime `
        -OneTime `
        -ResourceGroupName $ResourceGroupName `
        -TimeZone $timeZone

        $ck_down++

    }else{
    }
}

if($ck_up -eq 0){

    #WinUp起動新規作成
    New-AzAutomationSchedule `
        -AutomationAccountName $AutomationName `
        -Name "WinUp起動" `
        -StartTime $startTime `
        -OneTime `
        -ResourceGroupName $ResourceGroupName `
        -TimeZone $timeZone

    #起動スケジュールとRunbookを紐付ける
    $StartParams = @{"Action" = "START";"ResourceGroupName" = $ResourceGroupName}
    Register-AzAutomationScheduledRunbook -AutomationAccountName $AutomationName -RunbookName $Runbook -ScheduleName "WinUp起動"  -ResourceGroupName $ResourceGroupName -Parameters $StartParams

}

if($ck_down -eq 0){

    #WinUp停止新規作成
    New-AzAutomationSchedule `
        -AutomationAccountName $AutomationName `
        -Name "WinUp停止" `
        -StartTime $stopTime `
        -OneTime `
        -ResourceGroupName $ResourceGroupName `
        -TimeZone $timeZone

    #停止スケジュールとRunbookを紐付ける
    $StopParams = @{"Action" = "STOP";"ResourceGroupName" = $ResourceGroupName}
    Register-AzAutomationScheduledRunbook -AutomationAccountName $AutomationName -RunbookName $Runbook -ScheduleName "WinUp停止"  -ResourceGroupName $ResourceGroupName -Parameters $StopParams
}

#旧WindowsUpdate削除
$UpdateSet = Get-AzAutomationSoftwareUpdateConfiguration -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationName

for($i=0; $i -lt $UpdateSet.Length; $i++){
    $UpdateName = $UpdateSet[$i].Name
    $UpdateName

    Remove-AzAutomationSoftwareUpdateConfiguration -ResourceGroupName $ResourceGroupName -AutomationAccountName $AutomationName -Name $UpdateName
}

#WindowsUpdate設定
for($i=0; $i -lt $VMname.Length; $i++){
$NM = $VMname[$i]
$KB = ""

#OSごとのKB番号を連想配列で定義する
$KBMap = @{
    "W2012R2" = @(5041XXX, 5041XXX)
    "W2016"   = @(5041XXX, 5041XXX)
    "W2019"   = @(5041XXX)
    "W2022"   = @(5041XXX)
}

# OSの種類に基づいてKB番号を取得
$KB = $KBMap[$VMOS[$i]]

#パッチオーケストレーションをCustomer Managed Schedulesに変更
$VirtualMachine = Get-AzVM -ResourceGroupName $ResourceGroupName -Name $VMname[$i]
Set-AzVMOperatingSystem -VM $VirtualMachine -PatchMode "AutomaticByPlatform" 
$AutomaticByPlatformSettings = $VirtualMachine.OSProfile.WindowsConfiguration.PatchSettings.AutomaticByPlatformSettings
 
if ($null -eq $AutomaticByPlatformSettings) {
   $VirtualMachine.OSProfile.WindowsConfiguration.PatchSettings.AutomaticByPlatformSettings = New-Object -TypeName Microsoft.Azure.Management.Compute.Models.WindowsVMGuestPatchAutomaticByPlatformSettings -Property @{BypassPlatformSafetyChecksOnUserSchedule = $true}
   }else {
   $AutomaticByPlatformSettings.BypassPlatformSafetyChecksOnUserSchedule = $true
}

Update-AzVM -VM $VirtualMachine -ResourceGroupName $ResourceGroupName

#Update用のメンテナンス構成を作成
$config = New-AzMaintenanceConfiguration `
   -ResourceGroup $ResourceGroupName `
   -Name "${MaintenanceName}_${NM}" `
   -MaintenanceScope $scope `
   -Location $location `
   -StartDateTime $updateTime `
   -ExpirationDateTime $stopTime `
   -TimeZone $timeZone `
   -Duration $duration `
   -RecurEvery $recurEvery `
   -WindowParameterKbNumberToInclude $KB `
   -InstallPatchRebootSetting $RebootOption `
   -ExtensionProperty @{"InGuestPatchMode"="User"}

   Get-AzMaintenanceConfiguration | Format-Table -Property Name,Id

#作成したメンテナンス構成を各VMと紐づける
   New-AzConfigurationAssignment `
   -ResourceGroupName $ResourceGroupName `
   -Location $location `
   -ResourceName $VMname[$i] `
   -FilterOsType Windows `
   -ResourceType VirtualMachines `
   -ProviderName Microsoft.Compute `
   -ConfigurationAssignmentName $config.Name `
   -MaintenanceConfigurationId $config.Id 
   
   }

Disconnect-AzAccount

if($ck_up -eq 0){
    $msg_up
}

if($ck_down -eq 0){
    $msg_down
}

PAUSE

```

### スクリプトのポイント
:::note info
**①#--に囲まれた変数を変更するだけで、様々なVM台数、OSVerでも適用が可能。**
**②VM種別に応じたKBパッチを適用することが出来る。**
**③Automationスケジュールによって、Windows Updateの時間に合わせて
　VMを起動・停止を行うことが出来る**
:::

### スクリプト実行後のAzure側の動作
:::note info
**①VMを起動させる**
→スクリプトで設定した、Automationの「WinUP起動」にて実施
**②設定した時間にUpdateを実施する**
→設定した時間内で実施、この時適用されるパッチは事前に設定したもの
**③VMを停止する**
→スクリプトで設定した、Automationの「WinUP停止」にて実施
:::


## スクリプト実行の流れ

①PowerShell ISEでスクリプトを実行します。

②スクリプトを実行するMicrosoftアカウントにログインします

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3890692/22343e12-4c1e-e85e-4612-51fa6b953a8f.png)


③下記のメッセージが流れればスクリプトの実行は完了です。

・WinUp起動のスケジュール設定がなかったので設定しました
・WinUp停止のスケジュール設定がなかったので設定しました
・続行するには、Enter キーを押してください...: 

実行ウィンドウを上にスクロールしていくと色々書いていますが、
エラーがなければ実行完了です。


## 実行結果と実行後の適用確認

- メンテナンス構成にリソースやスケジュール、更新プログラム(KBパッチ)が
設定されていること(設定した台数分作成されます)
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3890692/fa066e7d-0948-daf0-56f8-30b8882363a8.png)
<br>

- Automationの指定したRunbook内にWinup起動・停止のスケジュールが
追加されていること
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3890692/3ae40352-f113-eb16-62fb-17e6c70918c9.png)
<br>

- VMの更新プログラムの中に適用したいパッチが入っていること
　(事前に確認していると思いますが、最終確認も兼ねて確認することをお勧めします)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3890692/f7d966e5-0fb3-f74a-1978-d52c86aaf10d.png)

## まとめ
このスクリプトを導入することで、Azure上でのWindows Updateを自動化することが
可能です。VM台数分、VMそれぞれの設定値に合わせて設定することが出来るので、
多数の仮想マシンを管理する際には、効率化が期待できます。

複数サブスクリプションに適用したい場合は、
それぞれのサブスクリプションに合わせたパラメータを使用してください。
(複数環境を作成するためにパラメータ作成シート等があると便利です)

## おわりに
この記事が役に立った場合は「いいね」や「フォロー」をしていただけると励みになります。また、質問やフィードバックはコメント欄でお待ちしています。

## おまけ
#### 試してないけど出来そうなこと
・**1度スクリプトを実行すれば、1か月置きや半年置きなどスクリプトを実行しなくても
　WindowsUpdateを実行すること**
→スケジュールを1dayから変更すれば可能なはず。公式ドキュメントによると
　日、週、月単位でスケジュール設定が可能っぽい
 
 https://learn.microsoft.com/ja-jp/azure/update-manager/manage-vms-programmatically?tabs=cli%2Cazurecl

参考↓
```Powershell:Powershell
$recurEvery = "2Months"
```

・**パッチを指定せずにスクリプトを実行する**
→空白(または該当行を削除)にすることで出来るはず

参考↓
```Powershell:Powershell
-WindowParameterKbNumberToInclude  `
```


・**1つのメンテナンス構成に2つのVMを設定すること**
→当社では1環境あたりに複数VMが存在し、かつOSが違う(＝適用パッチが違う)ことも
　あることから、VM台数分のメンテナンス構成を作成するスクリプトになっているが
　1環境のVMが同じで、適用パッチも同じであれば、1つのメンテナンス構成にまとめられる　
　はず

参考↓(恐らくこれだと複数OS選択時は、すべてのOSに対するKBパッチが集約されるはず)
```Powershell:Powershell
$allKB = @()

for($i=0; $i -lt $VMname.Length; $i++){
    $NM = $VMname[$i]
    $KB = ""

    $KBMap = @{
        "W2012R2" = @(5041XXX, 5041XXX)
        "W2016"   = @(5041XXX, 5041XXX)
        "W2019"   = @(5041XXX)
        "W2022"   = @(5041XXX)
    }

    $KB = $KBMap[$VMOS[$i]]
    $allKB += $KB
}
```

<font color="Red">**※あくまで参考です。使う際は必ずテストを行ってください**</font>



## 参考リンク
- [Microsoft公式：Update Management の概要](https://learn.microsoft.com/ja-jp/azure/automation/update-management/overview)
- [Azure portal と Azure Policy を使用してマシンの定期的な更新をスケジュールする](https://learn.microsoft.com/ja-jp/azure/update-manager/scheduled-patching?tabs=schedule-updates-single-machine%2Cschedule-updates-scale-overview%2Cwindows-maintenance)
- [Automatic Guest Patching for Azure Virtual Machines and Scale Sets](https://learn.microsoft.com/ja-jp/azure/virtual-machines/automatic-vm-guest-patching)