TEMTECOMAI ORTHOSTATIC HYPOTENSION

元ダメプログラマで現ダメ中間管理職の駄文

カテゴリ: WSUS

一時期随分と悩んでいた件、sem さんからコメントにてお教えいただいたように、DB 側の remote query timeout の値を初期値の600 から無制限の 0 に変更したところ無事に完了するようになりました。
感謝です。
USE SUSDB ;
GO
EXEC sp_configure 'remote query timeout', 0 ;
GO
RECONFIGURE ;
GO

ここ数日の成果物は PowerShell のスクリプト ファイル (*.ps1) と SQL Serve 用のスクリプト ファイル (*.sql) に分散してしまっている。
どうせなら PowerShell に一本化したいよね。
残念ながら Windows Server 2008 R2、WSUS 3.0、Windows Internal Database という我が WSUS 環境では PowerShell による管理が整備されきっていない時代の物らしく、WSUS はアセンブリを読み込んで .NET 的なアプローチをし、SQL Server は sqlcmd.exe に sql ファイルを流し込む事になるようです。
しかも sqlcmd.exe は Windows Internal Database ではインストールされないようなので別途無償版の SQL Server Management Studio Express とかをインストールしないとなりませんね。
Windows Server 2012 の WSUS 4.0 では PowerShell のコマンドレットが用意されているみたいですね。
Windows Internal Database はどうなんでしょうか。。。 SQL Server 2012 以降なら PowerShell 用の sqlps モジュールもありますが、WID ではダメなんでしょうか。。。 ダメかもわからんね。。。

スクリプト センターにある Re-index the WSUS 3.0 Database っていう SQL はデータベースのインデックスを再構成するためのメンテナンス スクリプトだけれど、こいつを流すと OS の表示が Windows 10 から Vista に戻っちゃうよ。
tbComputerTargetDetail テーブルの OSDescription 列の内容が戻っちゃうんだよね。
なので先日のスクリプトと合体させてみた。
USE SUSDB; 
GO 
SET NOCOUNT ON;

-- Rebuild or reorganize indexes based on their fragmentation levels 
DECLARE @work_to_do TABLE ( 
    objectid int 
    , indexid int 
    , pagedensity float 
    , fragmentation float 
    , numrows int 
) 

DECLARE @objectid int; 
DECLARE @indexid int; 
DECLARE @schemaname nvarchar(130);  
DECLARE @objectname nvarchar(130);  
DECLARE @indexname nvarchar(130);  
DECLARE @numrows int 
DECLARE @density float; 
DECLARE @fragmentation float; 
DECLARE @command nvarchar(4000);  
DECLARE @fillfactorset bit 
DECLARE @numpages int 
 
-- Select indexes that need to be defragmented based on the following 
-- * Page density is low 
-- * External fragmentation is high in relation to index size 
PRINT 'Estimating fragmentation: Begin. ' + convert(nvarchar, getdate(), 121)  
INSERT @work_to_do 
SELECT 
    f.object_id 
    , index_id 
    , avg_page_space_used_in_percent 
    , avg_fragmentation_in_percent 
    , record_count 
FROM  
    sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, 'SAMPLED') AS f 
WHERE 
    (f.avg_page_space_used_in_percent   < 85.0 and f.avg_page_space_used_in_percent/100.0 * page_count   < page_count - 1) 
    or (f.page_count > 50 and f.avg_fragmentation_in_percent > 15.0) 
    or (f.page_count > 10 and f.avg_fragmentation_in_percent > 80.0) 

PRINT 'Number of indexes to rebuild: ' + cast(@@ROWCOUNT as nvarchar(20)) 

PRINT 'Estimating fragmentation: End. ' + convert(nvarchar, getdate(), 121) 

SELECT @numpages = sum(ps.used_page_count) 
FROM 
    @work_to_do AS fi 
    INNER JOIN sys.indexes AS i ON fi.objectid = i.object_id and fi.indexid = i.index_id 
    INNER JOIN sys.dm_db_partition_stats AS ps on i.object_id = ps.object_id and i.index_id = ps.index_id 

-- Declare the cursor for the list of indexes to be processed. 
DECLARE curIndexes CURSOR FOR SELECT * FROM @work_to_do 

-- Open the cursor. 
OPEN curIndexes 

-- Loop through the indexes 
WHILE (1=1) 
BEGIN 
    FETCH NEXT FROM curIndexes 
    INTO @objectid, @indexid, @density, @fragmentation, @numrows; 
    IF @@FETCH_STATUS   < 0 BREAK; 

    SELECT  
        @objectname = QUOTENAME(o.name) 
        , @schemaname = QUOTENAME(s.name) 
    FROM  
        sys.objects AS o 
        INNER JOIN sys.schemas as s ON s.schema_id = o.schema_id 
    WHERE  
        o.object_id = @objectid; 

    SELECT  
        @indexname = QUOTENAME(name) 
        , @fillfactorset = CASE fill_factor WHEN 0 THEN 0 ELSE 1 END 
    FROM  
        sys.indexes 
    WHERE 
        object_id = @objectid AND index_id = @indexid; 

    IF ((@density BETWEEN 75.0 AND 85.0) AND @fillfactorset = 1) OR (@fragmentation   < 30.0) 
        SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REORGANIZE'; 
    ELSE IF @numrows >= 5000 AND @fillfactorset = 0 
        SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD WITH (FILLFACTOR = 90)'; 
    ELSE 
        SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD'; 
    PRINT convert(nvarchar, getdate(), 121) + N' Executing: ' + @command; 
    EXEC (@command); 
    PRINT convert(nvarchar, getdate(), 121) + N' Done.'; 
END 

-- Close and deallocate the cursor. 
CLOSE curIndexes; 
DEALLOCATE curIndexes; 

IF EXISTS (SELECT * FROM @work_to_do) 
BEGIN 
    PRINT 'Estimated number of pages in fragmented indexes: ' + cast(@numpages as nvarchar(20)) 
    SELECT @numpages = @numpages - sum(ps.used_page_count) 
    FROM 
        @work_to_do AS fi 
        INNER JOIN sys.indexes AS i ON fi.objectid = i.object_id and fi.indexid = i.index_id 
        INNER JOIN sys.dm_db_partition_stats AS ps on i.object_id = ps.object_id and i.index_id = ps.index_id 
    PRINT 'Estimated number of pages freed: ' + cast(@numpages as nvarchar(20)) 
END 
GO 

-- 不具合対応 ここから

-- Windows 8.1 の PC 情報を修正
UPDATE [SUSDB].[dbo].[tbComputerTargetDetail]
SET [OSDescription] = 'Windows 8.1'
WHERE [OSMajorVersion] = '6'
AND [OSMinorVersion] = '3'
AND [OldProductType] = '1'
AND ([OSDescription]   <> 'Windows 8.1' or [OSDescription] IS NULL) 

-- Windows Server 2012 R2 の PC 情報を修正
UPDATE [SUSDB].[dbo].[tbComputerTargetDetail]
SET [OSDescription] = 'Windows Server 2012 R2'
WHERE [OSMajorVersion] = '6'
AND [OSMinorVersion] = '3'
AND [OldProductType]   <> '1'
AND ([OSDescription]   <> 'Windows Server 2012 R2' or [OSDescription] IS NULL) 

-- Windows 10 の PC 情報を修正
UPDATE [SUSDB].[dbo].[tbComputerTargetDetail] 
SET [OSDescription] = 'Windows 10' 
WHERE [OSMajorVersion] = '10' 
AND [OSMinorVersion] = '0' 
AND [OldProductType] = '1' 
AND ([OSDescription]   <> 'Windows 10' or [OSDescription] IS NULL)
-- 不具合対応 ここまで --


--Update all statistics 
PRINT 'Updating all statistics.' + convert(nvarchar, getdate(), 121)  
EXEC sp_updatestats 
PRINT 'Done updating statistics.' + convert(nvarchar, getdate(), 121)  
GO 
まぁまぁいいんじゃない?

まぁね、WSUS がため込んでるデータ量が多すぎて処理が続行不可になってるって話じゃないと思ってますよ。
そこはまだ楽観的です。
新しい更新プログラムも取りに行けているしクライアントも Windows Update の処理ができていますし。

とは言え広大なネットの海にダイブして下記の情報を引っかけました。
その割にはどちらも Technet のフォーラムですが。。。

Getting past WSUS Cleanup Wizard time out, removing unnecessary updates.

なんかね、WSUS 3.0 が使っているデータベースが持っているストアド プロシージャーに spGetObsoleteUpdatesToCleanup ってのがあるらしいんですよ。
Get Obsolete Updates って言うぐらいだから古い更新情報を取得するってことだよね。
で、こいつはヒットした更新情報の ID 一覧を出力する。
そして spDeleteUpdate っていうストアド プロシージャの引数 @localUpdateID にヒットした ID を与えてあげると更新情報のレコードを削除するよっていう話らしい。

ただ spGetObsoleteUpdatesToCleanup で尋常じゃない数の更新情報が出力されるので、結果をいったん一時テーブルに登録して、WHILE を使って一つずつ spDeleteUpdate に渡してやろうっていうのが次のリンク。

WSUS Cleanup - can this be done is phases?

ザックリとまとめるとこんな感じ。
use SUSDB;
GO

DECLARE @var1 INT
DECLARE @msg nvarchar(100)

-- spGetObsoleteUpdatesToCleanup の結果を一時テーブル #results に格納
CREATE TABLE #results(Col1 INT)
INSERT INTO #results(Col1) EXEC spGetObsoleteUpdatesToCleanup

DECLARE WC Cursor FOR
    SELECT Col1 FROM #results
	
OPEN WC
FETCH NEXT FROM WC
INTO @var1

WHILE(@@FETCH_STATUS > -1)
BEGIN
    SET @msg = 'Deleting ' + CONVERT(varchar(10), @var1)
    RAISERROR(@msg, 0, 1) WITH NOWAIT EXEC spDeleteUpdate @localUpdateID=@var1
    FETCH NEXT FROM WC
    INTO @var1
END

CLOSE WC

DEALLOCATE WC

DROP TABLE #results

これを sql ファイルとして保存して Management Studio とかで流してあげればいいよね。

それでどれだけ効果があるのかわからないけれど。

-- 追記です --

この件、解決しましたのでリンクしておきます。

WSUS 3.0 SP2 on Windows Server 2008 R2 (Windows Internal Database 使用)

WSUS のクリーンアップは時間がかかる。
ホントにかかる。
数日待たされる可能性もある。

その間ずっと管理者でログオンして管理ツールを開いていることになる。
リモート デスクトップから操作してたら作業用 PC の電源も切れなくなってしまうし、だからと言ってサーバーにログオンしっぱなしってのもよろしくない。
ならばタスクで自動実行すればいいんじゃないだろうか、と。


とか思っていたらマイクロソフトのスクリプト センターにありましたよ。 さすが Scriptyng Guys だ。
WSUS Cleanup
https://gallery.technet.microsoft.com/ScriptCenter/fd39c7d4-05bb-4c2d-8a99-f92ca8d08218/

[reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")` 
 | out-null 
$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer(); 
$cleanupScope = new-object Microsoft.UpdateServices.Administration.CleanupScope; 
$cleanupScope.DeclineSupersededUpdates = $true        
$cleanupScope.DeclineExpiredUpdates         = $true 
$cleanupScope.CleanupObsoleteUpdates     = $true 
$cleanupScope.CompressUpdates                  = $true 
#$cleanupScope.CleanupObsoleteComputers = $true 
$cleanupScope.CleanupUnneededContentFiles = $true 
$cleanupManager = $wsus.GetCleanupManager(); 
$cleanupManager.PerformCleanup($cleanupScope);
こいつを ps1 ファイルにしてタスク スケジューラーに登録しろ、という話ですかね。
スクリプトをパッと見てすぐにわかりますが、[Refrection.assembly]::LoadWithPartialName() で Microsoft.UpdateServices.Administration を読み込んでいるので、PowerShell らしくない方法ですよね。
Windows Server 2012 の WSUS は PowerShell 用のモジュールがあるらしいのですが、WSUS 3.0 には存在しない。 だからアセンブリを読み込んで処理を行う、ということでしょう。

スクリプトのほとんどの部分はクリーンアップ ウィザードの UI にあるチェックボックスの項目っぽいですね。 これらのプロパティに $true または $false を設定して処理項目を決定するわけですね。
ちょっと UI に表示されている処理項目と数が合わないけれど、まぁクリーンアップの範囲でしょう。
CleanupObsoleteComputers がコメントアウトされてます。 しばらく (30日だっけ?) アクセスしていないコンピューターの情報を削除するって処理でしょうか。

タスクで無人実行するのだから try-catch を使って、エラーもイベントログに出力するようにしたらいいんじゃないかね。

WSUS 3.0 SP2 on Windows Server 2008 R2 (Windows Internal Database 使用)

WSUS のメンテナンスをしようと思った。
WSUS 3.0 には [クリーンナップ ウィザード] という GUI ツールがある。
実行してみた。
終わらない。
翌朝まで放置した。
データベース エラーでタイムアウトしていた。

いろいろ調べて回った。
メンテナンス自体をしばらくサボっていたため、日常メンテナンスに関する情報とタイムアウトに対する対策情報がゴチャゴチャとなってしまった。

まずはサボっていた日常メンテナンスから始めてみる。

WSUS 管理コンソールを起動する。
すでに存在しない古い Windows に関する更新情報を除外する。
[Update Services]-[サーバー]-[Update[オプション]-[製品とクラス] を開き、使っていない古い Windows を対象から外した。

IE の古い累積的なセキュリティ更新情報を "拒否" にする。
Japan WSUS Support Team blog "「IE の累積的なセキュリティ更新プログラム」が承認されていると、更新プログラムの検出処理時に WSUS クライアントの CPU 使用率が高くなる"
ただしこの検索結果一覧は使い勝手が悪すぎる。 検索結果のリストからは親子関係が分からない。 せめて親子関係を表すアイコンも表示されてほしかった。


これで再びクリーンアップ ウィザードを実行してみた。
やっぱりタイムアウトする。

では DB のインデックスを再構築してみよう。 WSUS と言えど情報は DB のレコードなのだから、肥大化した DB を整理してやれば軽くなるはず!!

Japan WSUS Support Team blog 「WSUS DB インデックスの再構成の手順について」
こいつも SQL なので Management Studio から実行。
あまり時間は掛からなかった。


クリーンアップ ウィザードを試してみた。
やっぱりタイムアウトする。

他にやれる事はないだろうか。
SQL で DB に直接介入するってのはどうだ?
WSUS DB にはメンテナンス用のストアド プロシージャーが用意されているのではないだろうか。
PowerShell で Update Service に介入するってのはどうだ?
GUI が CUI になっただけでかもしれないが管理コンソールで発生するタイムアウトは回避できるかもしれない。

まだチャレンジは続きそうだ。

-- 追記です --

この件、解決しましたのでリンクしておきます。

WSUS 3.0 SP2 on Windows Server 2008 R2 (Windows Internal Database 使用)

管理ツールの [Windows Server Update Services] でコンピューターの一覧を見てみると Windows 10 が Vista として表示されてしまう。 (Windows 8.1 も誤表示されていたらしいが WSUS のメンテをサポっていたので気づかなかった)

Microsoft からは WSUS 3.0 の不具合を修正する Hotfix がいくつか出ているようだが、どの Hotfix でも現象は治らない。
ちなみにこの環境にすでに適用してある Hotfix は以下の 4つ。


このうち 4つ目の KB2938066 の HotFix では、Windows Server 2012 R2、Windows 8.1、Windows Storage Server 2012 の表示名を修正するらしいのだが、こちらの環境では Windows 10 の誤表示は治っていない。
Japan WSUS Support Team Blog 「WSUS コンソール画面でオペレーティング システム名が正しく表示されない事象について」


そこで見つけたのが以下の情報
Windows Server Essentials Tips & Tricks 「Windows 10 on WSUS Shows as Windows Vista」

WSUS が使っている SUSDB というデータベースの中のテーブル [tbComputerTargetDetail] の中にはその環境で WSUS に登録されたコンピューターの情報が入っているので、こいつを修正するって感じか?
てっきり Windows OS に関するマスター テーブルのようなものがあってそいつを修正するのかと思ったが、現物の情報を修正するようだ。
UPDATE [SUSDB].[dbo].[tbComputerTargetDetail]
SET [OSDescription] = 'Windows 10'
WHERE [OSMajorVersion] = '10'
AND [OSMinorVersion] = '0'
AND [OldProductType] = '1'
AND ([OSDescription] <> 'Windows 10' or [OSDescription] IS NULL)


これを以下のようにして修正した。
上記のコードは SQL なので、こいつを実行する必要がある。
コマンドラインで実行すればいいのだけれど、いろいろと探索したいところなので SQL Server Management Studio を使う。
Windows Internal Database はリモートからログオンできないらしいのでサーバー自身にインストールを行う必要があるらしい。
この WSUS のサーバーには SQL Server 2008 R2 Management Studio Express をインストールした。

  1. SQL Server へログオンする。

サーバーの種類 : データベース エンジン
サーバー名 : \\.\pipe\mssql$microsoft##ssee\sql\query
認証 : Windows 認証
  1. [新しいクエリ] で先のコードを貼り付け実行する。


せっかくなので先のブログにあるように Windows 8.1 と Windows Server 2012 R2 に関する修正も入れる。
無事に終了。

WSUS は Windows 2000 Server か Windows Server 2003 上で運用します。
同時に使用するデータベースは、SQL Server 2000 のいずれかのエディションです。
SQL Server として無料の MSDE を選択した場合、Windows 2000 Server であれば SQL Server 2000 Desktop Engine(MSDE とか MSDE2000 と呼ばれている)を利用し、Windows Server 2003 であれば同じく SQL Server 2000 Desktop Engine という名称ではありますが実際には別製品の WMSDE(WSUS のインストールセットに同梱されてるそうです) という製品を利用します。(オイラの環境で WSUS を導入してないのがバレバレだねw)
WSUS の FAQ

MSDE2000 は DB のファイルサイズに 2GB 制限がありますので、Windows 2000 Server で WSUS を運用する場合はいずれこの制限に引っかかってしまいます。これを回避するには MSDE2000 ではなくて有償のエディション(SQL Server 2000 Standard 以上)を使う必要があります。
Windows Server 2003 と共に使用する WMSDE は WSUS を安全に運用するために 2GB の制限が解除されてます。
しかし Windows Server 2003 + WSUS + WMSDE を日本語環境で運用すると、WMSDE に 2GB 制限が出てしまうバグがあります。
Windows Server Update Services 日本語版の SQL Server 2000 Desktop Engine (Windows) データベースサイズが 2G バイトに制限される問題について
WSUS を新規にインストールする場合は WSUSSetup.exe /l:ENU というオプションをつけてインストールをすれば回避できます。
すでに WSUS を導入している環境で回避するにはマイクロソフトのサポート情報「WMSDE のデフォルトのデータベース構成を使用して Windows Server Update Services を展開するとエラー イベント 386 およびその他の同期の問題が発生する」に書いてある通り、WMSDE のデータベースをバックアップし、WSUS をアンインストール。WSUS を先のオプションで再インストール。WSUS と IIS のサービスをストップして、バックアップしておいたデータベースを復元。IIS と WSUS のサービスを再開。
という作業を行います。

↑このページのトップヘ