
Alle auf Deutsch verfügbaren "Tales from the Script"-Kolumnen finden Sie hier.
Systemadministratoren beschweren sich oft darüber, dass Microsoft keine passenden Tools zur Updateverwaltung zur Verfügung stellt - und finden später dann heraus, dass wir solche Sachen sehr wohl anbieten.
Ein Beispiel hierfür ist der Windows Update-Dienst. In den meisten Fällen reichen seine Standardfunktionen Administratoren aus – allerdings nicht immer. So wird zum Beispiel immer wieder gefragt: Wie kann ich als Administrator feststellen, ob ein Benutzer Automatische Updates deaktiviert hat? Wie kann ich herausfinden, ob Automatische Updates auf allen Computern aktiviert ist? Wie kann ich überprüfen, ob ein bestimmter Patch auf einem bestimmten Computer installiert ist.
Wie sich herausstellt, verfügen Sie bereits über ein Tool für die Verwaltung von Windows Update: nämlich Skripte. Jedesmal wenn Sie Automatische Updates auf einem Computer installieren, erhalten Sie kostenlos eine Bibliothek von COM-Objekten dazu. Mit diesen können Sie den Dienst aktivieren und deaktivieren, den Installationsplan ändern, die Liste der installieren Updates anzeigen und sogar weitere Updates installieren. Nett, oder?
Bevor wir beginnen, müssen wir ein paar Einschränkungen machen. Erstens ist das Objektmodell für den Windows Update-Client überraschend umfangreich. Es ist nicht möglich, es in diesem einen Artikel vollständig zu besprechen. Das Beste, was wir erreichen können, ist, Ihnen einen Überblick zu geben und einige Beispielskripte zu Verfügung zu stellen, die einige der häufigsten Aufgaben abdecken. Wenn Sie mehr erfahren möchten, dann schauen Sie sich die Windows Update Agent API im MSDN an.
Außerdem ist alles im Bezug auf Remotezugriff (also das Ausführen von Windows Update-Skripten gegen andere Computer) etwas verwirrend - um es mal vorsichtig auszudrücken. Sie werden in diesem Artikel feststellen, dass einige Skripte auch gegen Remotecomputer ausgeführt werden können und andere nicht. Es wäre zwar schöner, wenn alle Skripte diese Fähigkeit hätten, aber unglücklicherweise ist es eben nicht so.
Standardmäßig werden alle Skripte in diesem Artikel gegen den lokalen Computer ausgeführt. Wenn Sie sie gegen einen Remotecomputer ausführen möchten (zumindest bei denen, die hierzu in der Lage sind), dann müssen Sie dem CreateObject-Aufruf als zweiten Parameter den Namen des Remotecomputers mitgeben. Wenn Sie ein Skript gegen den Computer atl-ws-01 ausführen möchten, muss die Codezeile also zum Beispiel so aussehen:
Set objSession = CreateObject("Microsoft.Update.Session", "atl-ws-01")
Das hat natürlich nichts mit Windows Update zu tun - es ist einfach ein VBScript-Feature. Sie können mit VBScript immer einen Remotecomputer als zweiten Parameter für CreateObject angeben. Nur funktioniert das eben nur dann, wenn das COM-Objekt remote ausgeführt werden kann. Einzige Ausnahme ist das erste Skript. Hier müssen Sie der Variablen strComputer den Namen des Remotecomputers als Wert zuweisen:
strComputer = "atl-ws-01"
| • | Dieses Skript kann gegen Remotecomputer ausgeführt werden. |
Bevor Sie sich darüber Sorgen machen, ob Automatische Updates korrekt konfiguriert ist oder nicht, sollten Sie erstmal feststellen, ob dieser Dienst installiert ist. Da es sich um einen Dienst handelt, können Sie die Installation mit dem folgenden WMI-Skript überprüfen:
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colServices = objWMIService.ExecQuery _
("Select * from Win32_Service Where DisplayName = 'Automatische Updates'")
If colServices.Count = 0 Then
Wscript.Echo " Automatische Updates nicht installiert."
Else
For Each objService in colServices
Wscript.Echo " Automatische Updates: " & objService.State
Next
End If
Da wir uns diesen Monat auf Windows Update und nicht auf WMI konzentrieren, wollen wir nicht viele Worte zu diesem Skript verlieren. Es fragt den Computer nach allen Diensten ab, die als DisplayName den Wert Automatische Updates haben. Dann prüft das Skript die Eigenschaft Count der zurückgegebenen Collection. Wenn Count 0 ist, gibt es keine Instanzen des Dienstes. Wir nehmen in diesem Fall an, dass er nicht installiert ist. Wenn Count einen Wert ungleich 0 hat, dann gehen wir davon aus, dass der Dienst installiert ist.
| • | Dieses Skript kann nicht gegen Remotecomputer ausgeführt werden. |
Leider ist es möglich, dass der Dienst zwar ausgeführt wird, aber deaktiviert ist. Das folgende Skript überprüft nicht nur, ob der Dienst Automatische Updates aktiviert ist, sondern zeigt auch die konfigurierte Benachrichtigung an (also, ob Updates automatisch heruntergeladen und installiert werden, oder heruntergeladen und nicht installiert werden, usw.).
Set objAutoUpdate = CreateObject("Microsoft.Update.AutoUpdate")
Set objSettings = objAutoUpdate.Settings
Select Case objSettings.NotificationLevel
Case 0
Wscript.Echo "Benachrichtigungsstufe: Nicht von Benutzer " & _
"oder über ein GPO konfiguriert."
Case 1
Wscript.Echo "Benachrichtigungsstufe: Deaktiviert."
Case 2
Wscript.Echo "Benachrichtigungsstufe: Benutzer wird vor dem Herunterladen " & _
"und Installieren benachrichtigt."
Case 3
Wscript.Echo "Benachrichtigungsstufe: Updates werden automatisch " & _
"heruntergeladen. Benutzer wird vor Installation benachrichtigt."
Case 4
Wscript.Echo "Benachrichtigungsstufe: Updates werden nach dem angegebenen " & _
"Zeitplan installiert."
Case Else
Wscript.Echo "Benachrichtigungsstufe konnte nicht festgestellt werden."
End Select
Das Skript beginnt mit der Erstellung einer Instanz von Microsoft.Update.AutoUpdate und erstellt dann eine Instanz des Settings-Objektes. (Das Settings-Objekt ist Teil von Microsoft.Update.AutoUpdate.)
Anmerkung: Das Settings-Objekt kann nicht remote erstellt werden - nur lokal. Was bedeutet das für uns? Es bedeutet, dass dieses Skript nicht gegen einen Remotecomputer ausgeführt werden kann. Das ist zugegebenermaßen wohl nicht das, was Sie jetzt hören wollten, aber so ist es nun mal.
Nach dem Erstellen des Settings-Objektes müssen wir nur noch den Wert von NotificationLevel ausgeben. Da NotificationLevel einen Integer-Wert zurückgibt (zum Beispiel 1 wenn der Dienst deaktiviert ist), nutzen wir einen Select/Case-Block um eine entsprechende Benachrichtigung auszugeben.
| • | Dieses Skript kann nicht gegen Remotecomputer ausgeführt werden. |
Nachdem Sie nun wissen wie der Dienst konfiguriert ist, können Sie jetzt Änderungen vornehmen:
Const SCHEDULED_INSTALLATION = 4
Set objAutoUpdate = CreateObject("Microsoft.Update.AutoUpdate")
Set objSettings = objAutoUpdate.Settings
objSettings.NotificationLevel = SCHEDULED_INSTALLATION
objSettings.Save
Wie sich herausstellt, ist NotificationLevel eine Eigenschaft, die gelesen und geschrieben werden kann. Sie müssen sie nur auf den gewünschten Wert setzen und die Methode Save aufrufen. Im vorherigen Skript wurde eine Konstante mit dem Namen SCHEDULED_INSTALLATION definiert. Ihr wurde der Wert 4 zugewiesen - dies bedeutet, dass Updates automatisch heruntergeladen und nach Zeitplan installiert werden.
| • | Dieses Skript kann nicht gegen Remotecomputer ausgeführt werden. |
Sie möchten wahrscheinlich nicht nur wissen, ob der Dienst aktiviert ist, sondern auch wann die Updates installiert werden. Auch diese Informationen können Sie über das Settings-Objekt abfragen. Die gesuchten Eigenschaften heißen ScheduledInstallationDay und ScheduledInstallationTime:
Set objAutoUpdate = CreateObject("Microsoft.Update.AutoUpdate")
Set objSettings = objAutoUpdate.Settings
Select Case objSettings.ScheduledInstallationDay
Case 0
Wscript.Echo "Installationstag: Täglich"
Case 1
Wscript.Echo "Installationstag: Sonntag"
Case 2
Wscript.Echo "Installationstag: Montag"
Case 3
Wscript.Echo "Installationstag: Dienstag"
Case 4
Wscript.Echo "Installationstag: Mittwoch"
Case 5
Wscript.Echo "Installationstag: Donnerstag"
Case 6
Wscript.Echo "Installationstag: Freitag"
Case 7
Wscript.Echo "Installationstag: Samstag"
Case Else
Wscript.Echo "Zeitplan könnte nicht abgefragt werden."
End Select
If objSettings.ScheduledInstallationTime = 0 Then
Wscript.Echo "Installationszeit: 12:00 AM"
ElseIf objSettings.ScheduledInstallationTime = 12 Then
Wscript.Echo "Installationszeit: 12:00 PM"
Else
If objSettings.ScheduledInstallationTime > 12 Then
intScheduledTime = objSettings.ScheduledInstallationTime - 12
strScheduledTime = intScheduledTime & ":00 PM"
Else
strScheduledTime = objSettings.ScheduledInstallationTime & ":00 AM"
End If
Wscript.Echo "Installationszeit: " & strScheduledTime
End If
Das Skript sieht kompliziert aus, aber das liegt nur daran, dass sowohl ScheduledInstallationDay als auch ScheduledInstallationTime als Integerwerte zurückgegeben werden. Der größte Teil des Skriptes kümmert sich darum, diese Werte in aussagekräftige Ausgaben umzuwandeln. Der Installationstag wird zum Beispiel mit folgenden Werten zurückgegeben:
| • | 0 - Täglich |
| • | 1 - Sonntag |
| • | 2 - Montag |
| • | 3 - Dienstag |
| • | 4 - Mittwoch |
| • | 5 - Donnerstag |
| • | 6 - Freitag |
| • | 7 - Samstag |
| • | Dieses Skript kann nicht gegen Remotecomputer ausgeführt werden. |
Das Ändern des Zeitplanes erfordert nicht mehr als das Schreiben von neuen Werten für ScheduledInstallationDay und ScheduledInstallationTime und dem Aufrufen der Methode Save:
Const EVERY_THURSDAY = 5
Const FOUR_AM = 4
Set objAutoUpdate = CreateObject("Microsoft.Update.AutoUpdate")
Set objSettings = objAutoUpdate.Settings
objSettings.ScheduledInstallationDay = EVERY_THURSDAY
objSettings.ScheduledInstallationTime = FOUR_AM
objSettings.Save
Wir beginnen das Skript mit der Definition von zwei Konstanten. EVERY_THURSDAY mit dem Wert 5 (legt fest, dass die Updates jeden Donnerstag installiert werden sollen) und FOUR_AM mit dem Wert 4 (legt fest, dass die Installation um 4 Uhr morgens stattfinden soll). Dann erstellen wir eine Instanz von Microsoft.Update.AutoUpdate und Settings und ändern und speichern die Werte:
objSettings.ScheduledInstallationDay = EVERY_THURSDAY objSettings.ScheduledInstallationTime = FOUR_AM objSettings.Save
| • | Dieses Skript kann nicht gegen Remotecomputer ausgeführt werden. |
Hier noch ein Bonus-Skript - ein kleines Geschenk von den Scripting Guys. Wie Sie wissen, muss der Computer nach einigen Patches neu gestartet werden. Und wie Sie wahrscheinlich auch wissen, wird dem Benutzer nach deren Installation ein Dialogfenster angezeigt, mit dem er gefragt wird, ob er jetzt oder später neu starten möchte. Und hier kommt das Skript ins Spiel: Sie können angeben, ob ein Computer neu gestartet wird oder nicht:
Set objSysInfo = CreateObject("Microsoft.Update.SystemInfo")
If objSysInfo.RebootRequired Then
Wscript.Echo "Dieses Computer muss neu gestartet werden."
Else
Wscript.Echo " Dieses Computer muss nicht neu gestartet werden."
End If
Wir erstellen eine Instanz von Microsoft.Update.SystemInfo und überprüfen den Wert von RebootRequired. Wenn er Wahr ist bedeutet das, dass der Computer neu gestartet werden muss.
| • | Dieses Skript kann gegen Remotecomputer ausgeführt werden. |
Eine der guten Sachen an Windows Update ist, dass es eine Updatehistorie speichert. Und zwar nicht nur die Updates, die auf dem Computer installiert sind, sondern auch die fehlgeschlagenen Installationen und die deinstallierten Updates. Und das Beste ist, Sie können diese Historie per Skript abfragen:
Set objSession = CreateObject("Microsoft.Update.Session")
Set objSearcher = objSession.CreateUpdateSearcher
intHistoryCount = objSearcher.GetTotalHistoryCount
Set colHistory = objSearcher.QueryHistory(0, intHistoryCount)
For Each objEntry in colHistory
Wscript.Echo "Titel: " & objEntry.Title
Wscript.Echo "Beschreibung: " & objEntry.Description
Wscript.Echo "Datum: " & objEntry.Date
Select Case objEntry.Operation
Case 1
Wscript.Echo "Aktion: Installation"
Case 2
Wscript.Echo "Aktion: Deinstallation"
Case Else
Wscript.Echo "Aktion: Nicht feststellbar."
End Select
Select Case objEntry.ResultCode
Case 0
Wscript.Echo "Ergebnis: Aktion noch nicht gestartet."
Case 1
Wscript.Echo "Ergebnis: Aktion läuft noch."
Case 2
Wscript.Echo "Ergebnis: Erfolgreich abgeschlossen"
Case 3
Wscript.Echo "Ergebnis: Vollständig. Es sind jedoch Fehler " & _
" aufgetreten."
Case 4
Wscript.Echo "Ergebnis: Fehlgeschlagen"
Case 5
Wscript.Echo "Ergebnis: Abgebrochen"
Case Else
Wscript.Echo "Ergebnis: Nicht feststellbar."
End Select
Set objIdentity = objEntry.UpdateIdentity
Wscript.Echo "Update ID: " & objIdentity.UpdateID
Wscript.Echo
Next
Das Skript erstellt eine Instanz von Microsoft.Update.Session und verwendet dann die Methode CreateUpdateSearcher um eine Instanz des Objektes Searcher zu erstellen.
Anmerkung: Wir könnten das Searcher-Objekt auch direkt erstellen und uns eine Codezeile sparen. Mit dem Session-Objekt kann das Skript jedoch auch gegen Remotecomputer ausgeführt werden.
Die nächste Zeile stellt die Anzahl der Einträge in der Updatehistorie fest:
intHistoryCount = objSearcher.GetTotalHistoryCount
Dies ist notwendig, da wir gleich alle Einträge in der Collection mit der QueryHistory-Methode abfragen. Wenn wir diese Methode aufrufen, müssen wir über Indexnummern das erste und das letzte Update aus der Historie angeben, das abgefragt werden soll. Wenn wir alle Updates abfragen wollen, beginnen wir mit 0 und der Indexnummer des letzten Updates. Die letzte Indexnummer haben wir gerade in intHistoryCount gespeichert. Alle Update können wir also mit dieser Zeile abfragen:
Set colHistory = objSearcher.QueryHistory(0, intHistoryCount)
Danach geben wir einfach alle Elemente mit einer For/Each-Schleife aus.
Weiterer Hinweis: Die Einträge in der Updatehistorie werden in umgekehrter Reihenfolge gespeichert. Das letzte Update hat die Indexnummer 0. Das zuletzt durchgeführte Update erhalten Sie also mit der folgenden Codezeile:
Set colHistory = objSearcher.QueryHistory(0, 1)
| • | Dieses Skript kann gegen Remotecomputer ausgeführt werden. |
Dieser Artikel wird langsam etwas lang für eine Kolumne - betrachten Sie das folgende Skript daher ebenfalls als Bonus. Es ruft von der Windows Updates-Website detaillierte Informationen zu allen, auf dem Computer verfügbaren, Updates ab (wenn Sie einen internen SUS-Server verwenden, fragt es natürlich diesen ab). Wir besprechen das Skript nicht detailliert. Es kann Ihnen jedoch als Grundlage für eigene Skripte dienen.
Set objSession = CreateObject("Microsoft.Update.Session")
Set objSearcher = objSession.CreateUpdateSearcher
Set objResults = objSearcher.Search("Type='Software'")
Set colUpdates = objResults.Updates
For i = 0 to colUpdates.Count - 1
Wscript.Echo "Title: " & colUpdates.Item(i).Title
Wscript.Echo "Autoselect on Web sites: " & colUpdates.Item(i).AutoSelectOnWebSites
For Each strUpdate in colUpdates.Item(i).BundledUpdates
Wscript.Echo "Bundled update: " & strUpdate
Next
Wscript.Echo "Can require source: " & colUpdates.Item(i).CanRequireSource
Set objCategories = colUpdates.Item(i).Categories
For z = 0 to objCategories.Count - 1
Wscript.Echo "Category name: " & objCategories.Item(z).Name
Wscript.Echo "Category ID: " & objCategories.Item(z).CategoryID
For Each strChild in objCategories.Item(z).Children
Wscript.Echo "Child category: " & strChild
Next
Wscript.Echo "Category description: " & objCategories.Item(z).Description
Wscript.Echo "Category type: " & objCategories.Item(z).Type
Next
Wscript.Echo "Deadline: " & colUpdates.Item(i).Deadline
Wscript.Echo "Delta compressed content available: " & _
colUpdates.Item(i).DeltaCompressedContentAvailable
Wscript.Echo "Delta compressed content preferred: " & _
colUpdates.Item(i).DeltaCompressedContentPreferred
Wscript.Echo "Description: " & colUpdates.Item(i).Description
Wscript.Echo "EULA accepted: " & colUpdates.Item(i).EULAAccepted
Wscript.Echo "EULA text: " & colUpdates.Item(i).EULAText
Wscript.Echo "Handler ID: " & colUpdates.Item(i).HandlerID
Set objIdentity = colUpdates.Item(i).Identity
Wscript.Echo "Revision number: " & objIdentity.RevisionNumber
Wscript.Echo "Update ID: " & objIdentity.UpdateID
Set objInstallationBehavior = colUpdates.Item(i).InstallationBehavior
Wscript.Echo "Can request user input: " & objInstallationBehavior.CanRequestUserInput
Select Case objInstallationBehavior.Impact
Case 0
Wscript.Echo "Installation impact: Typical"
Case 1
Wscript.Echo "Installation impact: Negligible"
Case 2
Wscript.Echo "Installation impact: High"
Case Else
Wscript.Echo "The installation impact could not be determined."
End Select
Select Case objInstallationBehavior.RebootBehavior
Case 0
Wscript.Echo "Reboot behavior: No reboot required after installation."
Case 1
Wscript.Echo "Reboot behavior: A reboot is required after installation."
Case 2
Wscript.Echo "Reboot behavior: A reboot might be required after installation."
Case Else
Wscript.Echo "Reboot behavior: No information available regarding the need for a reboot."
End Select
Wscript.Echo "Requires network connectivity: " & objInstallationBehavior.RequiresNetworkConnectivity
Wscript.Echo "Is beta: " & colUpdates.Item(i).IsBeta
Wscript.Echo "Is hidden: " & colUpdates.Item(i).IsHidden
Wscript.Echo "Is installed: " & colUpdates.Item(i).IsInstalled
Wscript.Echo "Is mandatory: " & colUpdates.Item(i).IsMandatory
Wscript.Echo "Is uninstallable: " & colUpdates.Item(i).IsUninstallable
For Each strLanguage in colUpdates.Item(i).Languages
Wscript.Echo "Supported language: " & strLanguage
Next
Wscript.Echo "Last deployment change time: " & colUpdates.Item(i).LastDeploymentChangeTime
Wscript.Echo "Maximum download size: " & colUpdates.Item(i).MaxDownloadSize
Wscript.Echo "Minimum download size: " & colUpdates.Item(i).MinDownloadSize
Wscript.Echo "Microsoft Security Response Center severity: " & colUpdates.Item(i).MsrcSeverity
Wscript.Echo "Support URL: " & colUpdates.Item(i).SupportURL
Select Case colUpdates.Item(i).Type
Case 1
Wscript.Echo "Update type: Software"
Case 2
Wscript.Echo "Update type: Driver"
Case Else
Wscript.Echo "Update type: The update type could not be determined."
End Select
Wscript.Echo "Uninstallation notes: " & colUpdates.Item(i).UninstallationNotes
x = 1
For Each strStep in colUpdates.Item(i).UninstallationSteps
Wscript.Echo x & " -- " & strStep
x = x + 1
Next
For Each strArticle in colUpdates.Item(i).KBArticleIDs
Wscript.Echo "KB article: " & strArticle
Next
Wscript.Echo
Next
Dieses Skript sucht nach Software-Updates. Wenn Sie nach Treibern und Updates für Hardware suchen wollen, ändern Sie Zeile 3 des Skriptes folgendermaßen ab:
Set objResults = objSearcher.Search("Type='Driver'")
| • | Dieses Skript kann gegen Remotecomputer ausgeführt werden. |
Noch ein Skript, und wir haben es geschafft. Der Suchmechanismus von Automatische Updates ist etwas eingeschränkt. Sie können zwar nach Eigenschaften, wie Type und IsInstalled filtern, aber es ist nicht möglich nach einem bestimmten Update zu suchen (zum Beispiel über den Titel). Das führt uns zu der Frage, wie Sie feststellen können, ob ein Update installiert ist, wenn Sie mit einem Skript nicht nach einem bestimmten Update suchen können.
Die Antwort ist "Brute Force": Um nach einem bestimmten Update zu "suchen", gehen Sie einfach die gesamte Collection durch. Das folgende Beispielskript prüft, ob ein Update mit dem Namen Microsoft Windows Rights Management Services Client with Service Pack 1 installiert ist:
Set objSession = CreateObject("Microsoft.Update.Session")
Set objSearcher = objSession.CreateUpdateSearcher
Set objResults = objSearcher.Search("Type='Software'")
Set colUpdates = objResults.Updates
For i = 0 to colUpdates.Count - 1
If colUpdates.Item(i).Title = _
"Microsoft Windows Rights Management Services Client with Service Pack 1" Then
If colUpdates.Item(i).IsInstalled <> 0 Then
Wscript.Echo "Update ist installiert."
Wscript.Quit
Else
Wscript.Echo " Update ist nicht installiert."
Wscript.Quit
End If
End If
Next
Bis auf die For/Next-Schleife entspricht das Skript den vorherigen. Im letzten Skript haben wir einfach die Eigenschaften ausgegeben. In diesem Skript gehen wir die Collection durch und überprüfen bei jedem Titel, ob er Microsoft Windows Rights Management Services Client with Service Pack 1 lautet:
If colUpdates.Item(i).Title = _
"Microsoft Windows Rights Management Services Client with Service Pack 1" Then
Wenn das der Fall ist, überprüfen wir mit der folgenden Zeile die Eigenschaft IsInstalled:
If colUpdates.Item(i).IsInstalled <> 0 Then
Wenn IsInstalled den Wert 0 hat, dann ist das Update nicht installiert. Danach verwenden wir Wscript.Quit um das Skript zu verlassen. (Warum? Wir haben das gesucht Update gefunden. Es ist also nicht nötig das Skript weiter abzuarbeiten.)