Windows 2003 Shell Scripting

Shell Scripting im Netzwerk

19.11.2008 von Armin Hanisch
Mit Scripts lassen sich Arbeiten im Netzwerk vereinfachen und automatisieren. Im vierten Teil unserer Serie „Shell Scripting unter Windows 2003“ stellen wir Ihnen Scripte rund um den Einsatz im Netzwerk vor.

Unter Linux sind Scripte der Alltag, mittlerweile holt auch die Windows-Fraktion auf. Spätestens, seit Microsoft die Power Shell eingeführt hat, sind auch unter Windows komplexe und hilfreiche Scripte möglich. In dieser Artikelreihe zeigen wir Ihnen Codestücke zur Lösung für Aufgabenstellungen aus der täglichen Praxis. Die Scripte sind teilweise etwas komplexer oder setzen Kommandos auf manchmal ungewöhnliche Weise ein. Wir nehmen nicht in Anspruch, immer die optimale Lösung gewählt zu haben, und es gibt für manche Aufgaben sicher auch noch alternative Ansätze oder das Erstellen eines Scripts für den WSH. Sinn dieser Scripte ist das Verdeutlichen einer bestimmten Herangehensweise an solche Probleme. So können Sie selbst solche Shell Scripte ohne Probleme bauen, wenn Sie sich über den Ablauf der von Ihnen gewählten Befehle im Klaren sind.

Im vierten Teil dieser Serie befassen wir uns mit Scripten im Netzwerkeinsatz. Damit schließen wir die Serie ab, die in den einzelnen Blöcken außerdem die Bereiche Allgemeinen Problemstellung, Datei- und Systemverwaltung sowie ActiveDirectory behandelt. Auch hier können Sie sich wieder an den Beispielscripten bedienen und diese an Ihre lokale Umgebung anpassen.

Windows 2003 Shell Scripting: Das Buch zu unserer Serie.

Unsere neue Serie zum Shell Scripting unter Windows 2003 basiert auf Kapitel 16 des Standardwerks „Windows 2003 Shell Scripting - Abläufe automatisieren ohne Programmierkenntnisse“ von Armin Hanisch aus dem Haus Pearson Education. Sie können dieses 320 Seiten starke Buch auch in unserem Partner-Buchshop bestellen.

Artikelserie: Shell Scripting unter Windows 2003

Teil 1: Allgemeine Problemstellungen

Teil 2: System- und Dateiverwaltung

Teil 3: ActiveDirectory

Teil 4: Netzwerk

Domain Controller finden

Das folgende Script kann als Test verwendet werden, ob die DNS-Einträge für den Beitritt zu einer Domäne richtig konfiguriert sind. Alternativ lässt sich damit auch einfach ein Domain Controller für die angegebene Domain finden.

Parameter

Code

@echo off

:: haben wir einen parameter?
if x%1 == x (echo Bitte DNS-Namen der Domain angeben.) &
goto :eof

:: DNS-Abfrage nach dem service record und suche der
hosts
:: ACHTUNG! Das folgende Kommmando ist *eine* logische
Zeile!
nslookup -type=SRV _ldap._tcp.dc._msdcs.%1 2> nul | find
"hostname" > %temp%\dclist.txt

:: nichts gefunden? dann fehlermeldung ausgeben
if errorlevel 1 (echo Kein DC fuer %1 gefunden.) & goto
:eof

:: verarbeiten der liste der domain controller
call :process %temp%\dclist.txt
if defined DCNAME echo Domain Controller fuer %1:
%DCNAME%

:: shell script beenden
goto :eof


:: verarbeiten der liste der domain controller
:process
set DCNAME=
if not exist %1 (echo Datei %1 existiert nicht) & goto
:eof
if %~z1 == 0 (echo Datei %1 ist leer) & goto :eof
for /F "delims== tokens=2" %%f in (%1) do @set
DCNAME=%%f
set DCNAME=%DCNAME: =%
del %temp%\dclist.txt
goto :eof

Funktion

Zu Beginn wird geprüft, ob überhaupt ein Domänenname angegeben wurde. Falls nicht, wird eine entsprechende Meldung ausgegeben und der Batch beendet.

Danach folgt der Kern des Shell Scripts. Über einen Aufruf von nslookup wird nach den DNS Service-Records für den Eintrag der Domain Controller gesucht. Dazu wird über die Option -type der Abfragetyp auf SRV-Einträge umgestellt und dann nach dem LDAP-Eintrag auf TCP-Basis für den Namen der Domäne gesucht. Falls der DNS-Server einen Eintrag findet, liefert nslookup die unten stehende Ausgabe (hier am Beispiel der Demo-Domain shellbook.local).

C:\tmp>nslookup -type=SRV _ldap._tcp.dc._
msdcs.shellbook.local
Server: localhost
Address: 127.0.0.1

_ldap._tcp.dc._msdcs.shellbook.local SRV service
location:
priority = 0
weight = 100
port = 389
svr hostname = silicony.shellbook.local
silicony.shellbook.local internet address = 192.168.229.11

C:\tmp>

Wir sind nur an der Zeile interessiert, die mit „scr hostname“ beginnt. Hier findet sich der DNS-Name des jeweiligen Domain Controllers. Um diese Information für die spätere Verarbeitung zu speichern, wird die Ausgabe in eine Datei umgeleitet. Dazu dient der Teil hinter nslookup.

2> nul | find "hostname" > %temp%\dclist.txt

Die Fehlerausgabe (falls kein DNS-Eintrag gefunden wird, geht das berüchtigte „non-existant domain“ statt nach StdErr ins Null-Device) wird ignoriert. Die Ausgabe für StdOut wird per Piping an find übergeben, das nach dem String hostname sucht. Sollten bei der Abfrage mehrere Domain Controller gefunden werden, enthält die Datei dclist.txt nach der Ausführung mehr als eine Zeile. Wir verwenden dann den letzten gefundenen Eintrag (mehr dazu weiter unten).

Jetzt wird der durch find erzeugte Errorlevel geprüft und bei einem Inhalt ungleich 0 eine Meldung ausgegeben, dass kein Domain Controller gefunden werden konnte.

Verarbeitung der Ergebnisdatei

Im Anschluss folgt die Verarbeitung der Ergebnisdatei. Um hier auch eine Prüfung auf leere Dateien und andere mögliche Probleme zu ermöglichen, wird nicht direkt mit dem Lesen der Datei begonnen, sondern über das Kommando call eine interne Sprungmarke aufgerufen, die als ersten Parameter den Namen der Ergebnisdatei erhält. Damit Sie nicht zurückblättern müssen, hier nochmals der Teil des Scripts, der die Verarbeitung der Ergebnisdatei übernimmt.

:: verarbeiten der liste der domain controller
:process
set DCNAME=
if not exist %1 (echo Datei %1 existiert nicht) & goto
:eof
if %~z1 == 0 (echo Datei %1 ist leer) & goto :eof
for /F "delims== tokens=2" %%f in (%1) do @set
DCNAME=%%f
set DCNAME=%DCNAME: =%
del %temp%\dclist.txt
goto :eof

Zuerst wird die Umgebungsvariable DCNAME geleert (falls Sie einen anderen Namen bevorzugen, verwenden Sie ihn einfach, dann aber überall im Batch). Jetzt wird sicherheitshalber geprüft, ob die angegebene Datei überhaupt existiert. Danach folgt ein Test, ob die Datei leer ist oder nicht, indem die Dateigröße auf 0 Byte getestet wird.

Nun wird in einer for-Schleife jede Zeile der Datei gelesen. Als Trennzeichen wird das Gleichheitszeichen definiert, und wir lesen das zweite Token (dies ist der Teil nach dem „=“ mit dem Hostnamen). Dieses Token wird per set in der Variablen DCNAME gespeichert. Diese Schleife ist auch der Grund, warum bei mehr als einem Domain Controller immer der letzte verwendet wird. Jeder Durchlauf überschreibt den vorherigen Inhalt der Umgebungsvariablen. Als kleine Übung können Sie das Shell Script so umbauen, dass nach der ersten erfolgreichen Zuweisung die Schleife die Variable nicht mehr belegt. Als Übung für Fortgeschrittene sollten Sie versuchen, die Schleife so zu ändern, dass bei mehr als einem Domain Controller alle Hostnamen durch Leerzeichen getrennt in der Variablen DCNAME enthalten sind.

Nach der for-Schleife werden noch alle eventuell vorhandenen Leerzeichen in der Variablen entfernt, sodass nur der Hostname übrig bleibt. Die nun nicht mehr benötigte Datei wird gelöscht, damit folgen das Beenden des Aufrufs und die Rückkehr zum Hauptbatch.

Wie Sie sehen, ist der Aufwand auch für Aufgabenstellungen, die normalerweise gleich an VBScript und LDAP denken lassen, mit der Shell nicht sehr groß und auch für Nichtprogrammierer lösbar. Gerade im Bereich Netzwerk bietet die Shell mittlerweile von dnscmd über nslookup und bis zu netsh alle Möglichkeiten, die ein Administrator benötigt.

Netzwerkadresse berechnen

Dieses Script ist aus reiner Faulheit entstanden. In diesem Fall benötigten wir für eine IP-Adresse im Dialogfeld der TCP/IP-Eigenschaften schnell den Netzwerkanteil. Leider finden sich im Dialogfeld nur die IP-Adresse und die Subnet-Maske. Dieses Script nimmt Ihnen in Zukunft die Rechnerei ab und ist gleichzeitig ein gutes Beispiel für den sinnvollen Einsatz von set /a.

Parameter

Code

@echo off
if !%1 == ! goto noparm
if !%2 == ! goto noparm
setlocal
:: das for-Kommando ist eine Zeile!
for /F "delims=. tokens=1,2,3,4" %%f in ("%1") do for /F
"delims=. tokens=1,2,3,4" %%m in ("%2") do call :doByte
%%f %%g %%h %%i %%m %%n %%o %%p
::
goto :eof

:noparm
echo Calculate network address from ip and subnet mask
echo.
echo Usage: %0 [ipaddr] [netmask]
echo.
echo Either [ipaddr] or [netmask] is missing ....
goto :eof

:doByte
set /A byte="%1 & %5"
set net=%byte%
set /A byte="%2 & %6"
set net=%net%.%byte%
set /A byte="%3 & %7"
set net=%net%.%byte%
set /A byte="%4 & %8"
set net=%net%.%byte%
echo Network address = %net%
endlocal

Funktion

Aufruf beispielsweise mit

getnetwork.bat72.31.168.10 255.255.240.0

Die Ausgabe Network address = 172.31.160.0 liefert dann die Netzwerkadresse für eine bestimmte IP anhand der Subnetmaske.

Die Funktion des Scripts selbst ist eigentlich nur eine einzige Demonstration der Nutzung von set für die Arbeit mit Bitoperatoren. Dazu werden die beiden Parameter anhand des Trennzeichens "." mit zwei for-Schleifen in die einzelnen Bytes aufgeteilt. Diese werden dann ab der Sprungmarke :doByte logisch UND-verknüpft, um die Netzwerkadresse zu erhalten.

GetMAC für NT4 und W2K

Ab Windows Server 2003 steht das Kommando getmac zur Verfügung, mit dessen Hilfe die Mac-Adresse des lokalen Computers ausgegeben werden kann. Diese Funktionalität soll auch unter Windows 2000 und XP bereitstehen.

Die Mac-Adresse ist durch ipconfig /all bereits seit Windows 2000 verfügbar. Aus der entsprechenden Zeile in der Ausgabe muss nun der Wert extrahiert werden. Danach ist die Ausgabe so zu filtern, dass auch nur Zeilen mit einer Ethernetadresse ausgegeben werden. Sind mehrere Adapter im System verfügbar, soll die Mac-Adresse für alle Adapter ausgegeben werden. Geben Sie das unten stehende Kommando ein. Sie erhalten dann eine Ausgabe mit allen definierten Mac-Adressen.

ipconfig /all | find /i "phys"

Das Problem an dieser Ausgabe liegt aber darin, dass Sie nicht feststellen können, welche Mac-Adresse zu welchem Netzwerkadapter gehört. Ist nur ein Adapter vorhanden, ist dies kein Problem, und Sie können dieses Kommando verwenden. Wir möchten Ihnen aber nachfolgend gerne eine etwas komfortablere Version vorstellen, die den Namen getmac.bat trägt.

Code

@echo off
echo Liste der Mac-Adressen fuer %computername%
echo.

set action=
for /F "usebackq tokens=*" %%f in (`ipconfig /all`) do
call :procline "%%f"
goto :eof

:procline
if "%action%" == "getDesc" goto getDesc
if "%action%" == "getMac" goto getMac
echo %1 | find /i "Verbindungsspez" > nul
if errorlevel 1 goto :eof
set action=getDesc
goto :eof

:getDesc
for /F "delims=: tokens=2" %%f in ("%~1") do set
output=%%f
set output=%output:~1%
set action=getMac
goto :eof

:getMac
for /F "delims=: tokens=2" %%f in ("%~1") do set
output=%%f = %output%
echo %output%
set action=
goto :eof

Funktion

Mithilfe einer for-Schleife wird die Ausgabe von ipconfig /all Zeile für Zeile bearbeitet. Jetzt müssen wir feststellen, wo die Ausgabe eines Adapters beginnt. Dies ist mit der Ausgabe eines eventuell definierten verbindungsspezifischen DNS-Suffix der Fall. Für ein deutsches Windows finden Sie den Suchbegriff im fett gedruckten Wort im Listing. Für den Einsatz mit einem englischen Windows müssen Sie dieses Wort durch die entsprechende englische Ausgabe (connection-specific DNS suffix) ersetzen. Der ganze Trick besteht nun darin, sich irgendwie zu merken, dass nach der Zeile mit dem Suffix zuerst eine Zeile mit der Beschreibung des Adapters folgt. Die Zeile nach der Beschreibung ist dann die Zeile mit der Mac-Adresse.

Diese ganze Unterscheidung erfolgt im ersten Block nach der for-Schleife, den wir hier nochmals zum leichteren Nachvollziehen aufführen:

:procline
if "%action%" == "getDesc" goto getDesc
if "%action%" == "getMac" goto getMac
echo %1 | find /i "Verbindungsspez" > nul
if errorlevel 1 goto :eof
set action=getDesc
goto :eof

Zuerst wird mithilfe der Variablen action getestet, ob wir bereits auf eine Beschreibung oder auf eine Mac-Adresse warten. Dann erfolgt ein Sprung an die entsprechenden Sprungmarken. Ist dies noch nicht der Fall, wird mittels von find getestet, ob in der übergebenen Zeile der Text für das verbindungsspezifische Suffix vorkommt. Falls nicht, können wir uns weitere Aktionen schenken und beenden den Aufruf. Liefert find dagegen einen Exit-Code von 0, wird die Zeile ausgeführt, die den Inhalt von action auf den Wert „getDesc“ setzt. Beim nächsten Durchlauf wissen wir also, dass es sich um eine Beschreibungszeile handeln muss.

Die beiden Codeblöcke nach :getDesc und :getMac sind sehr ähnlich. Die übergebene Zeile wird mit einer for-Schleife am Doppelpunkt aufgetrennt und das zweite Wort verwendet. In der Variablen output wird der String aus Mac-Adresse und Beschreibung des Adapters zusammengestellt. Nach der Ausgabe beim Block von :getMac wird die Variable action wieder gelöscht, sodass der Zyklus von Neuem beginnen kann.

Ping-Liste

Mit dem folgenden Script können Sie feststellen, welche Rechner innerhalb des Subnets gerade online sind. Als Ergebnis erhalten Sie eine Liste mit IP-Adressen. Diese könnten dann von einem anderen Script weiterverarbeitet werden.

Mithilfe einer Zählschleife (for /L) wird der Bereich von 1 bis 254 in Einerschritten durchlaufen. Für jeden Wert wird ein einzelnes Ping (-n 1) ausgeführt. Die Ausgabe der gesamten Schleife (deshalb die runden Klammern) wird per Piping an den find-Filter übergeben, der alle Rechner heraussucht, die geantwortet haben. Bei einem englischen Windows müssen Sie den Text „Antwort“ bei find durch „reply“ ersetzen. Durch die Option /i ist die Schreibweise (groß oder klein) gleichgültig. Das Ergebnis dieser Filterung wird in eine temporäre Datei geschrieben.

Diese Datei (%temp%\tmp.dat) wird dann von einer weiteren Schleife Zeile für Zeile gelesen, wobei die Angabe "tokens=3" dafür sorgt, dass nur jeweils das dritte Wort (die IP-Adresse) gelesen wird. Mit jedem gelesenen Wort wird nun das Label :output als Unterroutine angesprungen. Der übergebene Parameter stellt die IP-Adresse dar, die allerdings am Ende noch den Doppelpunkt der ping-Ausgabe trägt.

Antwort von 192.168.1.11: Bytes=32 Zeit<10ms TTL=128

Die drei Zeilen nach :output entfernen den Doppelpunkt und geben die IP-Adresse dann aus.

@echo off
setlocal
set network=192.168.1
echo Scanning ....
if exist %temp%\tmp.dat del %temp%\tmp.dat
(for /L %%f in (1,1,254) do @ping -n 1 %network%.%%f |
find /i "Antwort") > %temp%\tmp.dat
echo == Online sind folgende Rechner ==
for /F "tokens=3" %%f in (%temp%\tmp.dat) do @call
:output %%f
endlocal
goto :eof

:output
set wert=%1
set wert=%wert:~0,-1%
echo %wert%

Port-Liste aus netstat

Dieses Script stammt aus einem Projekt zur Überwachung eines Servers. Es sollte festgestellt werden, zu welchen Prozessen von außen Verbindungen bestanden. Die Ausgabe liefert eine Liste aller Prozesse mit aktiven TCP-Verbindungen, der entfernten Adresse (IP und Port) und der im Prozess laufenden Services. So bekommen Sie schnell einen Überblick, wer von außen mit welchen Prozessen und Diensten in Verbindung steht.

Nachfolgend finden Sie einen (aus Platzgründen gekürzten) Auszug aus der Ausgabe des Shell Scripts.

==== Process ====

dfssvc.exe 1324 Dfs

Connections:
bytebag.shellbook.com:1025
bytebag.shellbook.com:1025
bytebag.shellbook.com:epmap

==== Process ====

dns.exe 1360 DNS

Connections:
bytebag.shellbook.com:ldap

==== Process ====

ntfrs.exe 1464 NtFrs

Connections:
bytebag.shellbook.com:1025
bytebag.shellbook.com:1025
bytebag.shellbook.com:ldap

Code

@echo off
setlocal
netstat -p TCP -a -o | find /i "established" > %temp%\
x.x
if exist %temp%\y.y del %temp%\y.y
for /F "tokens=3,5" %%f in (%temp%\x.x) do echo %%g %%f
>> %temp%\y.y
sort < %temp%\y.y > %temp%\x.x
set pid=-1
for /F "tokens=1,2" %%f in (%temp%\x.x) do call
:procline %%g %%f
endlocal
if exist %temp%\x.x del %temp%\x.x
if exist %temp%\y.y del %temp%\y.y
goto :eof
:procline
if %pid% == %2 goto havePID
echo.
echo.
echo ==== Process ====
tasklist /FI "pid eq %2" /NH /SVC
echo.
echo Connections:
:havePID
set pid=%2
echo %1
goto :eof

Funktion

Zuerst benötigen wir eine Liste aller aktiven TCP-Verbindungen. Das erledigt der Aufruf von netstat -p TCP -a -o. Diese Ausgabe wird gefiltert, sodass nur die Zeilen mit der Kennung ESTABLISHED übrig bleiben. Das Ergebnis wird in eine temporäre Datei x.x geschrieben.

Diese Liste muss nun umgeschichtet werden, sodass Sie die PID des Prozesses als erste Spalte enthält und danach die Adresse des entfernten Rechners. Dazu wird zuerst eine eventuell noch bestehende Datei y.y gelöscht. Danach wird mithilfe einer for-Schleife die Datei umgeformt und in eine zweite Datei y.y geschrieben. Eine Alternative wäre die Ausführung der for-Schleife in einer Subshell und die Umleitung der gesamten Ausgabe per einfachem ">" in die Datei. Dies erspart die Zeile mit dem Löschen der Datei y.y. Dazu müsste die for-Schleife wie folgt modifiziert werden:

(for /F "tokens=3,5" %%f in (%temp%\x.x) do echo %%g%%f) > %temp%\y.y

Diese Datei muss noch sortiert werden, damit alle gleichen PIDs untereinander stehen. So kann die Ausgabe für alle Verbindungen zu einem Prozess erfolgen, bis die PID wechselt. Dazu verwenden wir sort.

Nach dem Sortieren kann die Ausgabe erzeugt werden. Damit wir wissen, welche PID gerade abgearbeitet wird, wird eine Umgebungsvariable (pid) mit der jeweiligen Prozess-ID belegt. Zu Beginn schreiben wir den Startwert -1 in die Variable.

In der nun folgenden for-Schleife wird die sortierte Datei x.x Zeile für Zeile eingelesen. Zuerst wird getestet, ob die in der Zeile verzeichnete PID bereits abgearbeitet wird. Schlägt dieser IF-Vergleich fehl, dann haben wir einen neuen Prozess, und es erfolgt die Ausgabe der Prozessdaten. Dazu werden per tasklist und einem Filter nach Prozess-ID der Name des Prozesses, die PID und die darin laufenden Dienste ausgegeben.

Ab der Marke :havePID laufen beide Zweige der vorigen IF-Abfrage wieder zusammen. Hier wird die Adresse der verbundenen Maschine (als Kombination aus Namen und Portnummer) ausgegeben. Sollten Sie eine numerische Ausgabe der Daten bevorzugen, ergänzen Sie beim Aufruf von netstat die Option -n für numeric output.

Nach der Ausgabe erfolgt noch das obligatorische Aufräumen, bei dem die beiden temporären Dateien gelöscht und mit endlocal die Änderungen an der Umgebung wieder rückgängig gemacht werden.

Fazit

Mit diesem Beitrag endet unsere Serie zum Thema Shell Scripting unter Windows. Wir hoffen, dass wir Ihnen einen Einblick in die Scripting-Fähigkeiten von Windows geben und alltägliche Aufgaben erleichtern konnten. Sollten Sie eigene Scripte entwickelt haben und diese unseren Lesern zur Verfügung stellen wollen, schreiben Sie diese doch in unser Forum. (mja/mzu)

Windows 2003 Shell Scripting: Das Buch zu unserer Serie.

Unsere neue Serie zum Shell Scripting unter Windows 2003 basiert auf Kapitel 16 des Standardwerks „Windows 2003 Shell Scripting - Abläufe automatisieren ohne Programmierkenntnisse“ von Armin Hanisch aus dem Haus Pearson Education. Sie können dieses 320 Seiten starke Buch auch in unserem Partner-Buchshop bestellen .

Inhalt der Artikelserie zum Shell Scripting unter Windows 2003

Teil 1: Allgemeine Problemstellungen

Teil 2: System- und Dateiverwaltung

Teil 3: ActiveDirectory

Teil 4: Netzwerk