von postNils Kaczenski post25. Dezember 2008, 10:00 Uhr post http://faq-o-matic.net/?p=1014
post Kategorie: Administration, Scripting, Sicherheit, Windows

Es ist keine schlechte Idee, die Kennwörter von Dienst- und Taskausführungskonten zu ändern. Vielfach sind diese nämlich mit einem, naja, Startkennwort ausgestattet, das nicht den hohen Sicherheitsanforderungen für solche Konten entspricht. Doch fast niemand traut sich ran, denn es gibt so eine tolle Ausrede: Wer weiß, wo dieses Konto überall genutzt wird, und nachher läuft da gar nichts mehr!

Aber so schwierig ist es gar nicht, so etwas sicher herauszufinden. Wie so oft, hilft auch hier eine Skriptlösung. Betrachten wir dazu getrennt die Dienstkonten und die Konten, mit denen Geplante Tasks laufen.


Teil 1: Dienstkonten

Dienstkonten lassen sich gut mit WMI abfragen und dann in eine Datenbank schreiben. Alles, was man dazu benötigt, ist:

  • Ein SQL Server (die Express Edition reicht hier vollkommen)
  • Ein Konto, das auf die Server zugreifen darf, idealerweise mit Adminrechten

Auf dem SQL Server richtet man eine Datenbank beliebigen Namens ein, z.B. so:

CREATE DATABASE MeineAdminDB

Dann führt man folgendes Skript in einem Admintool für den SQL Server aus (SQL Server Management Studio, -Express oder einfach Ofarim):

USE MeineAdminDB

CREATE TABLE [dbo].[Win32Service] (
    [idService] [int] IDENTITY (1, 1) NOT NULL ,
    [Caption] [nvarchar] (255) COLLATE Latin1_General_CI_AS NULL ,
    [Name] [nvarchar] (255) COLLATE Latin1_General_CI_AS NULL ,
    [PathName] [ntext] COLLATE Latin1_General_CI_AS NULL ,
    [StartMode] [varchar] (50) COLLATE Latin1_General_CI_AS NULL ,
    [StartName] [nvarchar] (255) COLLATE Latin1_General_CI_AS NULL ,
    [State] [varchar] (50) COLLATE Latin1_General_CI_AS NULL ,
    [SystemName] [nvarchar] (50) COLLATE Latin1_General_CI_AS NULL ,
    [RowDate] [smalldatetime] NULL ,
    [UserName] [nvarchar] (50) COLLATE Latin1_General_CI_AS NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

Damit hat die Datenbank die nötige Tabelle, um die Daten aufzunehmen. Nun führt man das folgende Skript auf allen Servern aus, deren Dienste inventarisiert werden sollen. Eine Hilfestellung dazu gebe ich unter dem Code. Im Code ändere man in den Zeilen 61 und 62 den Namen des SQL-Servers und der Datenbank.

 

  1. """"""""""""""""""""""""""'
  2. ' ServiceInv.vbs
  3. ' Dienst-Mini-Inventarisierung über WMI
  4. '
  5. ' Version: 0.1
  6. ' Datum:   22.12.2005
  7. ' Autor:   Nils Kaczenski (Vorname at Nachname .de)
  8. ' Letzte Änderungen:
  9. '
  10. ' Nils Kaczenski stellt dieses Skript ohne jede
  11. ' Gewährleistung zur Verfügung.
  12. ' Die Verwendung geschieht auf eigene Gefahr.
  13. '
  14. """"""""""""""""""""""""""'
  15.  
  16. Option Explicit
  17. Dim colItems               '
  18. Dim dtmCompInDB            '
  19. Dim intGesamtzahl          '
  20. Dim intZeilenzahl          '
  21. Dim objArgs      '
  22. Dim objConn                '
  23. Dim objItem
  24. Dim objRS                  '
  25. Dim objWMIService
  26. Dim objWSHNetwork          '
  27. Dim strBenutzer            '
  28. Dim strCaption             '
  29. Dim strComputer            '
  30. Dim strConn                '
  31. Dim strDatenbank           '
  32. Dim strDBServer            '
  33. Dim strLogmode             '
  34. Dim strName                '
  35. Dim strPathName            '
  36. Dim strSQL                 '
  37. Dim strStartMode           '
  38. Dim strStartName           '
  39. Dim strState               '
  40.  
  41. strLogmode = "error"
  42.  
  43. say Now & ": Starte Dienst-Inventarisierung …"
  44.  
  45. On Error Resume Next
  46.  
  47. ' Computernamen herausfinden
  48. Set objWSHNetwork = CreateObject("Wscript.Network")
  49. strComputer = objWSHNetwork.ComputerName
  50. strBenutzer = objWSHNetwork.UserDomain & "\" & objWSHNetwork.UserName
  51. If checkit("Fehler beim Zugriff auf WSHNetwork!") Then WScript.Quit
  52.  
  53. ' falls Computername per Argument übergeben, wird dieser genommen:
  54. Set objArgs = WScript.Arguments
  55. If objArgs.Count <> 0 Then
  56.     strComputer = objArgs(0)
  57. End If
  58. saydebug strComputer
  59.  
  60. ' Datenbank-Daten
  61. strDBServer = "SQL01"
  62. strDatenbank = "MeineAdminDB"
  63.  
  64. ' Objekte
  65. Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
  66. If checkit("Fehler beim Zugriff auf WMI!") Then WScript.Quit
  67.  
  68. Set objConn = CreateObject("ADODB.Connection")
  69. Set objRS = CreateObject("ADODB.Recordset")
  70. If checkit("Fehler beim ADODB-Zugriff!") Then WScript.Quit
  71.  
  72. ' Datenbank-Verbindung
  73. strConn = "Provider=SQLOLEDB;Integrated Security=SSPI;" _
  74.     & "Initial Catalog=" & strDatenbank _
  75.     & ";Data Source=" & strDBServer
  76. objConn.Open strConn
  77. If checkit("Fehler beim Verbinden mit der Datenbank!") Then WScript.Quit
  78.  
  79. ' prüfen, ob aktuelle Daten da sind
  80. strSQL = "select max(RowDate) as datum from dbo.Win32Service where " _
  81. & "SystemName='" & strComputer & "'"
  82. objRS.Open strSQL, objConn
  83. If checkit("Fehler beim Abfragen der Datenbank!") Then WScript.Quit
  84. dtmCompInDB = objRS("Datum")
  85. If DateDiff("d", dtmCompInDB, Now) < 7 Then
  86.     ' Daten sind aktuell – Skript verlassen
  87.     say "Vorhandene Daten sind aktuell."
  88.     say "Beende Service-Inventarisierung."
  89.     WScript.Quit
  90. End If
  91. objRS.Close
  92.  
  93. ' vorhandene Daten dieses Rechners löschen
  94. strSQL = "delete from Win32Service where SystemName='" & strComputer & "'"
  95. objConn.Execute strSQL, intZeilenzahl
  96. If checkit("Fehler beim Löschen der Inventardaten!") Then WScript.Quit
  97. say intZeilenzahl & " Zeilen aus der Datenbank gelöscht."
  98.  
  99. ' WMI-Daten auslesen
  100. Set colItems = objWMIService.ExecQuery("Select * from Win32_Service",,48)
  101. If checkit("Fehler beim Auslesen der WMI-Daten!") Then WScript.Quit
  102.  
  103. ' WMI-Daten durchlaufen
  104. For Each objItem In colItems
  105.     strCaption = objItem.Caption
  106.     strName =  objItem.Name
  107.     strPathName =  objItem.PathName
  108.     strStartMode =  objItem.StartMode
  109.     strStartName =  objItem.StartName
  110.     strState =  objItem.State
  111.  
  112.     If checkit("Fehler beim Durchlaufen der WMI-Daten, Item " & intGesamtzahl & "!") Then WScript.Quit
  113.  
  114.     ' SQL-Kommando aufbauen
  115.     strSQL = "insert dbo.Win32Service (" _
  116.     & " SystemName, Caption, [Name], PathName, StartMode," _
  117.     & " StartName, State, RowDate, Username " _
  118.     & ") values ( '" _
  119.     & strComputer & "', '" & strCaption & "', '" & strName _
  120.     & "', '" & strPathName & "', '" & strStartMode _
  121.     & "', '" & strStartName & "', '" & strState & "', '" _
  122.     & Now & "', '" & strBenutzer & "') "
  123.  
  124.     saydebug strSQL
  125.  
  126.     ' Daten in die Datenbank schreiben
  127.     objConn.Execute strSQL, intZeilenzahl
  128.     If checkit("Fehler beim Schreiben in die Datenbank!") Then WScript.Quit
  129.     intGesamtzahl = intGesamtzahl + intZeilenzahl
  130. Next
  131.  
  132. objConn.Close
  133. On Error Goto 0
  134. say intGesamtzahl & " Zeilen in die Datenbank geschrieben."
  135. say "Service-Inventarisierung ist fertig."
  136.  
  137.  
  138. Function checkit(strNachricht)
  139.     checkit = False
  140.     If Err.number <>0 Then
  141.         strNachricht = strNachricht & " [" & Err.description & " (" & Err.number & ")]"
  142.             sayerror strNachricht
  143.         Err.clear
  144.         checkit=True
  145.     End If
  146. End Function
  147.  
  148.  
  149. ' Universelle Ausgabefunktion
  150. Sub say(s)
  151.     saynb s & VbCrLf
  152. End Sub
  153.  
  154. ' Ausgabe ohne Zeilenumbruch
  155. Sub saynb(s)
  156.     WScript.echo s
  157. End Sub
  158.  
  159. ' Ausgabe als Fehlermeldung
  160. Sub sayerror(s)
  161.     say "Fehler (" & Date & ", " & Time & "): " & s
  162. End Sub
  163.  
  164. ' Ausgabe als Warnung
  165. Sub sayalert(s)
  166.     say "Warnung (" & Date & ", " & Time & "): " & s
  167. End Sub
  168.  
  169.  
  170. ' Debug-Ausgabe
  171. Sub saydebug(s)
  172.     If strLogmode = "debug" Then
  173.         say s
  174.     End If
  175. End Sub

Dieses Skript speichert man als "ServiceInv.vbs" und ruft es folgendermaßen – mit ausreichenden Rechten ausgestattet – auf, um den Server "MeinServer" zu inventarisieren:

cscript C:\Pfad\ServiceInv.vbs MeinServer

Dies lässt sich für alle Server gut über ein Batch erledigen (das man mit Excel erzeugen kann), das z.B. so aussieht:

cscript C:\Pfad\ServiceInv.VBS SERVER001
cscript C:\Pfad\ServiceInv.VBS SERVER002
cscript C:\Pfad\ServiceInv.VBS SERVER003

Sollte das Skript einen Server nicht per WMI erreichen, kann das mehrere Gründe haben: Der Server läuft gar nicht, eine Firewall hindert die WMI-Ansprache oder es gibt gar kein WMI auf der Maschine. Die meisten Windows-Server sollten aber klaglos ihre Daten ausgeben.

Hernach stehen die Daten zu den Dienstkonten in der Datenbank. Folgende SQL-Abfrage im SQL-Client (siehe oben) gibt eine schnelle Übersicht, welches Dienstkonto wo verwendet wird. Dabei blendet es die lokalen Standardkonten gleich aus, weil diese ja nicht von der Kennwortänderung betroffen sind:

  1. SELECT StartName
  2. , Caption
  3. , SystemName
  4. , StartMode
  5. FROM Win32Service
  6. WHERE StartName NOT IN (
  7. 'LocalSystem'
  8. , 'NT AUTHORITY\LocalService'
  9. , 'NT AUTHORITY\NetworkService')
  10. ORDER BY StartName, SystemName, Caption

Teil 2: Konten für Geplante Tasks

Mit den Geplanten Tasks ist es leider nicht so einfach, denn diese lassen sich nicht per WMI abfragen – jedenfalls wenn sie mit dem Taskplaner eingerichtet wurden. WMI kann nur die "alten" AT-Tasks verarbeiten (die aber zu Recht niemand mehr will).

Hier hilft das Kommandozeilentool schtasks.exe, das auf Rechnern ab XP und Windows Server 2003 vorhanden ist. Gemeinsam mit psexec (von Sysinternals) wird eine brauchbare Lösung daraus. Man baue sich also (z.B. wieder mit Excel, siehe oben) ein Batch folgender Art:

"C:\Daten\psexec.exe" \\SERVER001 schtasks /query /V /FO csv|find /i "Domain"
"C:\Daten\psexec.exe" \\SERVER002 schtasks /query /V /FO csv|find /i "Domain"
"C:\Daten\psexec.exe" \\SERVER003 schtasks /query /V /FO csv|find /i "Domain"

Dabei steht "Domain" für den Namen der eigenen Domäne, den man hier ersetzt. Die Ausgabe dieses Batches leitet man in eine Datei um. Die Kommandos verbinden sich mit jedem Remoteserver und geben über das Piping mit find nur die Tasks zurück, in denen der Name der Domäne auftaucht – üblicherweise dann, wenn ein Domänenkonto für den Task angegeben ist. In der erzeugten Ausgabedatei findet man also nun genau die Tasks, die man nach der Kennwortänderung bearbeiten muss.

Verwandte Beiträge:

  1. Systeme mit Windows Server 2003 R2 identifizieren
    Es ist nicht ganz leicht, im Rahmen einer Dokumentation im Netzwerk die Server zu identifizieren, die mit Windows Server 2003...
  2. Ein AD-Attribut zu einem Logon-Namen herausfinden
    Das folgende Skript gibt den Wert eines beliebigen Attributs aus dem Active Directory für einen User zurück, dessen Logon-Name (SAM-Name,...
  3. BGInfo um eigene Datenfelder erweitern
    BGInfo von Sysinternals ist für viele Windows-Admins ein unverzichtbares Werkzeug. Es blendet Konfigurationsinformationen über den lokalen Rechner in das Hintergrundbild...
  4. Standardkennwort bei Benutzern prüfen
    Manchmal werden Benutzer durch eine Migration, beim Neuanlegen oder auch beim Zurücksetzen des Kennwortes mit einem Standardkennwort versehen. Ab und...
  5. AD-Informationen schnell auslesen
    Neben eineAm klassischen ADSI-Skript gibt es eine relativ einfache Möglichkeit, Informationen aus dem Active Directory auszulesen. Hierzu lässt sich das...