Direkt zum Hauptbereich

WSUS auf einem SBS2011 reparieren (Timeout)

Grundsätzliches bei SBS-Themen: Der SBS2011 ist "nicht ohne" und verkommt bei schlechter Behandlung zur wiederlichsten Zicke, die Sie in ihrem Leben getroffen haben.

Wenn Sie sich "mit Computern oder Windows auskennen": Lassen Sie um Himmels Willen die Finger von allem, was außerhalb der "Small Business Standard Konsole" abläuft. Bitte denken Sie ernsthaft darüber nach, diese Aufgabe (und die Verwantwortung!) zu delegieren.
Wenn Sie sich mit Windows Server 2008r2 und dem Microsoft SQL Server auskennen: Bitte lesen Sie diese Anleitung vollständig und handeln Sie erst dann, wenn Sie der Meinung sind, alles verstanden zu haben.
Die Bereinigung nimmt durchaus mal ein paar Tage Zeit in Anspruch. Das ist aber nicht weiter dramatisch, da das letztendlich im Hintergrund abläuft und die Ressourcenbelastung "eher moderat" ist.
Meiner Erfahrung nach kann man das durchaus im Geschäftsbetrieb durchführen. Zumindest haben meine Kunden noch nie etwas davon mitbekommen :-)

Problembeschreibung

Der WSUS-Server vom SBS2011 bzw. 2008r2 "hakt". Entweder, er liefert gar keine Updates mehr aus oder er holt keine neuen Updates. Der Assistent zur Serverbereinigung läuft auf einen Timeout hinaus.

Was machen wir?

Wir werden den WSUS-Server bzw. dessen Datenbank entschlacken. Gerade bei älteren Systemen wächst diese durchaus auf mehrere Gigabyte an und bremst die Mühle bis zum Stillstand herunter. Erschwerend hinzu kommt beim SBS2011, dass das zugrundeliegende Betriebssystem maximal 32GB RAM verwalten kann. Irgendwann ist also definitiv "Ende Gelände".
Natürlich werden wir eine Menge löschen, was aber nicht dramatisch ist.
Bei der Aufräumaktion geht es primär darum, historische Protokolle, Statistiken, zurückgezogene oder aus anderen Gründen obsolete Updates zu löschen.

Ab in die Datenbank

Im Startmenü unter "Microsoft SQL Server 2008 R2" das "SQL Management Studio" starten und mit dem Datenbankmodul

\\.\pipe\MSSQL$MICROSOFT##SSEE\sql\query

verbinden (Windows-Authentifizierung).


Schritt 1: Löschen der Synchronisationshistorie

Wir klicken auf "Neue Abfrage". Zum Löschen geben wir diesen Text in das Abfragefenster ein:

USE SUSDB
GO
DELETE FROM tbEventInstance WHERE EventNamespaceID = '2' AND EVENTID IN ('381', '382', '384', '386', '387', '389')


Auch wenn es verleitet: Der "! Ausführen" Button ist der mit dem roten Ausrufezeichen und nicht der Button mit dem grünen Pfeil.
Wir klicken also auf "! Ausführen" und erhalten wenige Sekunden später das Ergebnis der Löschaktion.

Schritt 2: Löschen aller obsoleten Updates

Bitte das folgende Script im Abfragefenster eingeben:

DECLARE @var1 INT
DECLARE @msg nvarchar(100)
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

Auch hier wieder aus "Ausführen" klicken. Achtung - dieses Script kann tatsächlich Tage(!) laufen. Dass es noch läuft, sehen wir an der Statusleiste:

Wenn das Script durchgelaufen ist, sieht das Ganze so aus:

Es ist sehr warscheinlich, dass nicht alle Updates gelöscht wurden (z.B. dann, wenn nach der Ausführung bis zur Betrachtung des Ergebnisses eine erneute Synchronisation stattgefunden hat).
Ob wir fertig sind, überprüfen wir mit dieser Datenbankabfrage:

USE SUSDB
GO
exec spGetObsoleteUpdatesToCleanup


Wenn unten im Ergebnisfenster eine Liste mit Nummern angezeigt wird, führt man das Script zum Löschen der obsoleten Updates erneut aus.
Das Ganze machen wir so lange, bis "der Dreizeiler" keine Updates mehr anzeigt.

Schritt 3: Reindizierung der Datenbank

Der Index der Datenbank (also das "Was steht wo?") wird anhand des aktuellen Datenbankinhaltes komplett neu aufgebaut.
Hier das passende Script für das Abfragefenster vom SQL-Studio:

/******************************************************************************
This sample T-SQL script performs basic maintenance tasks on SUSDB
1. Identifies indexes that are fragmented and defragments them. For certain
tables, a fill-factor is set in order to improve insert performance.
Based on MSDN sample at http://msdn2.microsoft.com/en-us/library/ms188917.aspx
and tailored for SUSDB requirements
2. Updates potentially out-of-date table statistics.
******************************************************************************/

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


--Update all statistics
PRINT 'Updating all statistics.' + convert(nvarchar, getdate(), 121)
EXEC sp_updatestats
PRINT 'Done updating statistics.' + convert(nvarchar, getdate(), 121)
GO


Für die Laufzeit würde ich maximal 2 Stunden einkalkulieren.

Schritt 4: Verzeichnis aufräumen

Die ganzen Updates "liegen ja irgendwo auf der Platte" und das Verzeichnis mit dem Inhalt kann tatsächlich enorme Ausmaße beanspruchen.

Um das Verzeichnis aufzuräumen, bediene ich mich eines Powershell-Scriptes. # Performs a cleanup of WSUS.
# Outputs the results to a text file.
# Adapted and tested by BigTeddy
# 3 July 2012

$outFilePath = '.\wsusClean.txt'
[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) | Out-File -FilePath $outFilePath


Zur Ausführung des Scriptes speichert man dieses unter einem einprägsamen Namen (z.B. "WSUS-Cleanup.ps1") und startet eine Powershell als Administrator.
Danach wechselt man in das Verzeichnis mit dem Script, startet es und findet im gleichen Ordner nach erfolgter Ausführung die Datei wsusClean.txt mit dem Ergebnis:

SupersededUpdatesDeclined : 7
ExpiredUpdatesDeclined : 0
ObsoleteUpdatesDeleted : 0
UpdatesCompressed : 19717
ObsoleteComputersDeleted : 0
DiskSpaceFreed : 46590272


Zugegeben: Der Speicherplatzgewinn ist in diesem Beispiel nicht sooooo dramatisch. Aber vielleicht hängt das ja auch damit zusammen, dass ich diese Anleitung kurz nach einer erfolgreichen Bereinigung geschrieben habe :-)

Kommentare

Beliebte Artikel