TEMTECOMAI ORTHOSTATIC HYPOTENSION

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

タグ:WSUS

2017年3月の Windows 10 累積的な更新プログラム KB4013429 は Active Directory 管理センターがクラッシュする件に対応しています。

KB4013429

Addressed issue where the Active Directory Administrative Center (ADAC) crashes when attempting to modify any attribute of any user account in Active Directory.
累積的な更新プログラムの一覧の日本語ページにはまだ掲載されていないようですが英語版のページには掲載されていますね。
まずはよかったよかった。

アカウント情報を編集して更新しようとすると Active Directory 管理センターが不明なエラーを発して落ちてしまう問題の原因が分かった。
以下は先週末自宅に引きこもって実験した結果。

まずは手順

  1. Windows 10 Enterprise 評価版を使って VM を作成。 (Windows 10 Version 1607)
  2. 作成した VM を Active Directory に追加。
  3. RSAT を VM にインストール。
  4. Active Directory 管理センターが正常に使えることを確認。
  5. Windows Update で OS を最新状態にする。
  6. Active Directory 管理センターが異常終了することを確認。
  7. 「インストールされた更新プログラム」 から累積的な更新プログラムを削除。
  8. Microsoft Update カタログから一つ前の累積的な更新プログラムをダウンロードしてインストール。
  9. Active Directory の動作を確認。
  10. 以下 7 から 9 を繰り返し、Active Directory 管理センターが正常終了するポイントを見つける。

結果、2016年12月13日公開の累積的な更新プログラム 「KB3206632 (14393.571)」 をインストールすると Active Directory 管理センターが異常終了することが判明。
その少し前、2016年12月9日に公開された 「KB3201845 (14393.479)」 までにとどめておけば良い。

残念ながら最新の累積的な更新プログラムである 2017年1月10公開の 「KB3213986 (14393.693)」 でもこの現象は継続中。

【参考】
Windows 10 用のリモート サーバー管理ツール (RSAT) のダウンロード
https://www.microsoft.com/ja-JP/download/details.aspx?id=45520
Microsoft Update カタログ
https://catalog.update.microsoft.com/v7/site/home.aspx
Windows 10 バージョン 1607 (64bit) と Windows Server 2016 の累積的な更新プログラム一覧
https://support.microsoft.com/ja-jp/help/4000825

ここ数日の成果物は 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 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 に関する修正も入れる。
無事に終了。

↑このページのトップヘ