「WSUS メンテナンスガイド」をコマンドラインで実施する

「WSUS メンテナンスガイド新版」「WSUS 管理コンソールにつながらない！」を解消するための 7 つのワザ」に記述の手順は SQL Server Management Studio のインストールが必要だったり GUI による操作が必要です。

https://jpmem.github.io/blog/wsus/2022-05-09_01/

https://jpmem.github.io/blog/wsus/2019-03-28_01/

# サンプルが想定している環境
16GB の物理メモリーを搭載サーバーにたいして設定するサンプルとなっております。必要に応じてチューニングしてください。
- IIS WsusPool アプリケーションの「リサイクル\プライベートメモリ制限 (KB)」: 8GB
- SUSDB をホストする SQL インスタンスの最小サーバー メモリ: 1GB

上記はその他インストールされているアプリケーション等は考慮していないため、ご注意ください。

# この記事について
上記公式サポートチームの記事を理解された方がコマンドラインで実施できることを目的としていますため、値については解説いたしません。
また、特記のないコマンドについては PowerShell にて実行するものとしております。

:::note info
"[下準備]" 以外の項目は、元記事の項番を引用しています。
:::
:::note warn
下記サンプルは動作を保証しません。必ず実施前に動作検証をしてください。
:::

# [下準備] WsusDBMaintenance.sql をダウンロード
https://jpmem.github.io/blog/wsus/2014-03-05_01/ より WsusDBMaintenance.sql をダウンロードします。
後ほど、保存先のフォルダーをカレントディレクトリとして参照します。

# [下準備] SQL Server PowerShell モジュールをインストール

```Shell
Set-ExecutionPolicy Unrestricted -Scope Process -Force
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null
If (@(Get-PackageSource | Where-Object Name -eq nuget.org).Count -eq 0){
    Import-PackageProvider -Name NuGet
    Register-PackageSource -Name nuget.org -ProviderName NuGet -Location https://api.nuget.org/v3/index.json
}
Install-Module -Name PowerShellGet -RequiredVersion 2.2.5 -AllowClobber -Force | Out-Null
If (@(Get-PackageSource | Where-Object Name -eq PSGallery).Count -eq 0){
    Register-PSRepository -Default
}
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted 
Install-Module -Name SqlServer -Repository PSGallery -AllowClobber -Force | Out-Null
```
## 確認方法
下記コマンドを実行し、SqlServerがインストールされていることを確認。
```Shell
Get-InstalledModule -Name SqlServer
```

![実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667945/5e6a58e2-f1cc-7d14-91d3-ad239da62565.png)


# A. IIS チューニング設定
```Shell
Function Set-ValueWsusPool($ItemName, $DisplayName, $NewValue){
    Try{
        $BeforeValue = (Get-ItemProperty $IisWsusPoolPath -Name $ItemName).Value
        Set-ItemProperty -Path (Get-ItemProperty $IisWsusPoolPath).PsPath -Name ($ItemName) -Value ($NewValue)
        $AfterValue = (Get-ItemProperty $IisWsusPoolPath -Name $ItemName).Value
        Write-Host "$DisplayName (Before: $BeforeValue, Request: $NewValue, After: $AfterValue)"
    }
    Catch{
        Write-Warning "[Set-ValueWsusPool] Failed to apply $DisplayName <$ItemName>: (Before: $BeforeValue, Request: $NewValue)`n$($_.Exception.Message)"
    }
}
Import-Module WebAdministration
$Script:IisWsusPoolPath = "iis:\AppPools\WsusPool"
```
```Shell:サンプル (物理メモリーが16GB相当のサイジング)
Set-ValueWsusPool -ItemName "queueLength" -DisplayName "(全般)\キューの長さ" -NewValue ([System.Int64]2000) # 2000
Set-ValueWsusPool -ItemName "cpu.limit" -DisplayName "CPU\制限 (%)" -NewValue ([System.Int64]0) # 0%
Set-ValueWsusPool -ItemName "failure.rapidFailProtectionInterval" -DisplayName "ラピッド フェール保護\エラー間隔" -NewValue (New-TimeSpan -Minutes 30).ToString() # 30分
Set-ValueWsusPool -ItemName "failure.rapidFailProtectionMaxCrashes" -DisplayName "ラピッド フェール保護\最大エラー数" -NewValue ([System.Int64]60) # 60回
Set-ValueWsusPool -ItemName "recycling.periodicRestart.privateMemory" -DisplayName "リサイクル\プライベートメモリ制限 (KB)" -NewValue ([System.Int64]8000000) # 8GB
Set-ValueWsusPool -ItemName "recycling.periodicRestart.memory" -DisplayName "リサイクル\仮想メモリ制限 (KB)" -NewValue ([System.Int64]0) # 0KB
```
## 確認方法
上記実行後に下記のように表示されるため、「Request」と「After」の値が同一になっていることを確認。
```
(全般)\キューの長さ (Before: 1000, Request: 2000, After: 2000)
CPU\制限 (%) (Before: 0, Request: 0, After: 0)
ラピッド フェール保護\エラー間隔 (Before: 00:05:00, Request: 00:30:00, After: 00:30:00)
ラピッド フェール保護\最大エラー数 (Before: 5, Request: 60, After: 60)
リサイクル\プライベートメモリ制限 (KB) (Before: 1487402, Request: 8000000, After: 8000000)
リサイクル\仮想メモリ制限 (KB) (Before: 0, Request: 0, After: 0)
```
![実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667945/48b782db-b4f0-2513-2381-caa75cb98ee3.png)

# B. カスタム インデックスの追加
```Shell
Function Get-WsusSqlServerName{
    If (Test-Path "HKLM:\SOFTWARE\Microsoft\Update Services\Server\Setup"){
        $ServerInstancePath = (Get-Item -Path "HKLM:\SOFTWARE\Microsoft\Update Services\Server\Setup").GetValue("SqlServerName")
        If ($ServerInstancePath -eq "MICROSOFT##WID"){
            $ServerInstancePath = "\\.\pipe\Microsoft##WID\tsql\query"
        }
        Return $ServerInstancePath
    }
}

Invoke-Sqlcmd -ServerInstance (Get-WsusSqlServerName) -Query "/*
https://jpmem.github.io/blog/wsus/2022-05-09_01/ より引用
*/

-- 統計情報の更新クエリ
Use [SUSDB]
Exec sp_msforeachtable 'update statistics ? with fullscan'
Go

-- インデックスの再構築
Use [SUSDB]
Exec sp_MSForEachtable 'DBCC DBREINDEX (''?'')'
Go" -Encrypt Optional
```

![実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667945/21b24797-bc63-ff93-4e7c-69c9244a9172.png)

# C. WSUS DB インデックスの再構成

:::note info
下記コマンドは WsusDBMaintenance.sql をダウンロードしたフォルダーにて実行してください。
エクスプローラーのアドレスバーから PowerShell を実行されるとスムーズです。
![実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667945/f26c5455-151d-7ed0-4be1-8d9bfbafff8b.png)
:::

```Shell
Function Get-WsusSqlServerName{
    If (Test-Path "HKLM:\SOFTWARE\Microsoft\Update Services\Server\Setup"){
        $ServerInstancePath = (Get-Item -Path "HKLM:\SOFTWARE\Microsoft\Update Services\Server\Setup").GetValue("SqlServerName")
        If ($ServerInstancePath -eq "MICROSOFT##WID"){
            $ServerInstancePath = "\\.\pipe\Microsoft##WID\tsql\query"
        }
        Return $ServerInstancePath
    }
}

Invoke-Sqlcmd -InputFile "WsusDBMaintenance.sql" -ServerInstance (Get-WsusSqlServerName) -Encrypt Optional
```
![実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667945/94fb9c78-cda0-0279-0d26-d946932e52ba.png)

# D. データベースの統計情報の更新とインデックスの再構築
```Shell
Function Get-WsusSqlServerName{
    If (Test-Path "HKLM:\SOFTWARE\Microsoft\Update Services\Server\Setup"){
        $ServerInstancePath = (Get-Item -Path "HKLM:\SOFTWARE\Microsoft\Update Services\Server\Setup").GetValue("SqlServerName")
        If ($ServerInstancePath -eq "MICROSOFT##WID"){
            $ServerInstancePath = "\\.\pipe\Microsoft##WID\tsql\query"
        }
        Return $ServerInstancePath
    }
}

Invoke-Sqlcmd -ServerInstance (Get-WsusSqlServerName) -Query "Use SUSDB
exec sp_msforeachtable 'update statistics ? with fullscan'
Go

Use SUSDB
Exec sp_MSForEachtable 'DBCC DBREINDEX (''?'')'
Go" -Encrypt Optional
```
![実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667945/286026d7-12ea-9142-e414-9d14f6ce70ea.png)

# E. 不要な更新プログラムを「拒否済み」にする
https://jpmem.github.io/blog/wsus/2017-12-11_01/ のスクリプトを実施するのも1つかと思われますが、昨今のWindowsカテゴリの更新プログラム肥大化には十分対応しきれていないと考えてりますため、本記事では省略します。
拒否を検討するまえに、まずは同期対象とする製品・分類のチューニングが要となります。
- [公式] WSUS の分類について https://jpmem.github.io/blog/wsus/2018-06-19_01/
- WSUSの更新プログラムを拒否するスクリプトの実行結果 (Wsustainable) https://qiita.com/rin309/items/c338c61446541a10872f
- WSUSでクライアントOSに必要そうな更新プログラムを調べてみた https://note.com/rin309/n/n0a8d54c92d35
- WSUSの"分類"はどの程度使用されているのか https://qiita.com/rin309/items/7872d2772f02c4eb33a6

# F. WSUS のクリーンアップ ウィザード
```Shell
# 不要な更新プログラムおよび更新のリビジョン
Invoke-WsusServerCleanup -CompressUpdates -CleanupObsoleteUpdates
# 不要な更新ファイル
Invoke-WsusServerCleanup -CleanupUnneededContentFiles
# 期限の切れた更新プログラム
Invoke-WsusServerCleanup -DeclineExpiredUpdates
# 置き換えられた更新プログラム
Invoke-WsusServerCleanup -DeclineSupersededUpdates
```
![実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667945/2dc37596-fa51-0ed3-44e5-f5aea0c9cf68.png)

# 6. SUSDB をホストする SQL インスタンスの最小サーバー メモリ、最大サーバー メモリを設定する。
```Shell
Function Get-WsusSqlServerName{
    If (Test-Path "HKLM:\SOFTWARE\Microsoft\Update Services\Server\Setup"){
        $ServerInstancePath = (Get-Item -Path "HKLM:\SOFTWARE\Microsoft\Update Services\Server\Setup").GetValue("SqlServerName")
        If ($ServerInstancePath -eq "MICROSOFT##WID"){
            $ServerInstancePath = "\\.\pipe\Microsoft##WID\tsql\query"
        }
        Return $ServerInstancePath
    }
}

Invoke-Sqlcmd -ServerInstance (Get-WsusSqlServerName) -Query "exec sp_configure 'show advanced options',1
go
reconfigure
exec sp_configure 'min server memory',1024
go
reconfigure
go
exec sp_configure 'min server memory'
go" -Encrypt Optional
```

# 確認方法
run_value が設定した値になっていることを確認。
```
name         : min server memory (MB)
minimum      : 0
maximum      : 2147483647
config_value : 1024
run_value    : 1024
```
![実行例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/667945/9ed153c3-3302-878b-beb9-3b8ce813b645.png)

:::note info
最大サーバー メモリを設定する場合は min server memory を max server memory に置き換えてください。
:::