bsi-mail-check-service

Wie ihr eventuell in den News mitbekommen habt, wurden mal wieder E-Mail Adressen im großen Stil gehackt. 18 Millionen deutsche Adressen sollen ebenfalls betroffen sein. Das BSI bietet erneut an, E-Mail Adressen auf eine Gefährdung hin zu überprüfen.

Ich kann empfehlen, dass jeder das für seine privaten Adressen einmal durchgeht.

Wenige E-Mails einzeln testen

Nach dem Absenden der Eingabe erhaltet ihr einen Code, bestehend aus 4 Zeichen. Speichert euch diesen Code!
Bei der Überprüfung wird die eingegebene Adresse mit der Liste gehackter Adressen abgeglichen und bei einer Übereinstimmung (also bei der Möglichkeit eines erfolgreichen Hacks!) eine Bestätigungsmail an diese Adresse versendet. In dieser Mail steht dann ebenfalls ein Code. Überprüft diese 2 Codes – wenn die Codes übereinstimmen ist die E-Mail Adresse vermutlich betroffen; wenn nicht handelt es sich ggf. um einen Hoax.

Viele E-Mails automatisiert testen

Als IT-Administrator bin ich natürlich auch für die Sicherheit aller E-Mail-Adressen des Unternehmens zuständig. Daher war es nötig, alle E-Mail-Adressen prüfen zu lassen. Da das BSI keine „Massenabfertigung“ anbietet, musste eine Automatisierung her; keine Zeit dutzende Adressen von Hand dort einzugeben und den Code zu kopieren.
Ich habe mir innerhalb weniger Minuten ein AutoIt Skript gebastelt, dass diesen Job in kürzester Zeit erledigt.

Hier der Code:

;options
AutoItSetOption("SendKeyDelay", 100)
AutoItSetOption("SendKeyDownDelay", 50)
AutoItSetOption("MouseClickDelay", 20)
For $iCount = 1 To 45
WinWaitActive("Microsoft Excel - ExportData.csv","")
Send("{CTRLDOWN}c{CTRLUP}{ALTDOWN}{TAB}{ALTUP}")
WinWaitActive("BSI-Sicherheitstest - Google Chrome","")
Send("{CTRLDOWN}lv{CTRLUP}{ENTER}")
MouseClick("left",1271, 168,1)
Send("{TAB}{ALTDOWN}{TAB}{ALTUP}")
WinWaitActive("Microsoft Excel - ExportData.csv","")
Send("{TAB}{CTRLDOWN}c{CTRLUP}{ALTDOWN}{TAB}{ALTUP}")
WinWaitActive("BSI-Sicherheitstest - Google Chrome","")
Send("{CTRLDOWN}v{CTRLUP}{TAB}{SPACE}")
MouseClick("left",1378, 355,2)
Send("{CTRLDOWN}c{CTRLUP}{ALTDOWN}{TAB}{ALTUP}")
WinWaitActive("Microsoft Excel - ExportData.csv","")
Send("{TAB}{F2}{CTRLDOWN}v{CTRLUP}{ENTER}{LEFT}{LEFT}{CTRLDOWN}c{CTRLUP}")
Next

Ich denke das Prinzip wird klar. Ließe sich vielleicht optimieren aber Zeit und Nutzen stehen da nicht im Verhältnis.

Aufbau:
Ihr müsst

  1. Chrome benutzen oder die WinWaitActive Fenstertitel anpassen,
  2. auf einem Monitor mit 1920×1080 sowohl Excel als auch den Browser halbieren, Excel links, Browser rechts
  3. in der Excel Tabelle 2 Spalten haben: 1. Spalte der BSI-Link, 2. Spalte die Mail Adresse, in die dritte Spalte wird automatisch der Code eingetragen, je 1 Zeile für jede zu überprüfene E-Mail-Adresse
  4. diese Excel Datei ExportData.csv nennen oder den WinWaitActive Fenstertitel anpassen
  5. die 2 Click-Koordinaten nochmal überprüfen (nutzt dazu „AutoIt Window Info“ – Au3Info.exe)

Grob sollte das reichen, ggf. müsst ihr noch Kleinigkeiten korrigieren aber ich denke damit sollte es schonmal grob laufen.

Hier das Video des laufenden Skripts:

Das Passwort von Windows Accounts zu ändern ist eigentlich eine einfache Angelegenheit. Auch in der Konsole gibt es einen sehr einfachen Befehl:

net user [username] [passwort]


Ein sehr simples Skript zum Neusetzen eines Passworts könnte also so aussehen:

@echo off & setlocal & color 9f
set user=user
set newpw=muhuu123!
net user %user% %newpw%
endlocal

Aber an dieser Stelle geht es mir natürlich nicht um das Ändern eines einzelnen Passworts. Es geht um die Massenänderung eines Passworts, beispielsweise eines Administratorpassworts auf jedem Computer eines Netzwerks.
Nun kann es jedoch bei Dutzenden, Hunderten oder Tausenden Nutzern/Computern zu verschiedenen Problemen oder Nebeneffekten kommen. Der Nutzer könnte nicht existieren, deaktiviert sein oder ein Password mit Ablaufdatum besitzen. Diese Faktoren sollten bedacht werden.

Ich habe ein Skript geschrieben, welches alle diese Faktoren berücksichtigt:

@echo off & setlocal & color 9f
set wd=\\lea\Deployment\Sonstiges\change-admin-pw
set log=%wd%\change-admin-pw.log

REM ###### EDIT THIS ######
REM desired local user account
set user=user
REM is it an active directory domain user or a local user account; can be yes, 1, no, 0
set domainuser=no
REM needed if domain user
set domainparam=
REM desired new password, pay attention to password policies if activated
set newpw=my4w3s0mePW!
REM checks if the desired user account is activated on the machine; can be yes, 1, no, 0
set checkactive=yes
REM needed if active check is performed
set active=0
REM if the user is deactivated, should it get activated, can be yes, 1, no, 0
set reactivate=yes
REM checks if the old password has an expiration date, which would also get changed to a newer date; can be yes, 1, no, 0
set checkpwexpire=yes
REM needed if expiration check is performed
set expires=0
REM changes password though it has an expiration date; can be yes, 1, no, 0
set changeanyways=yes

REM check for empty variables
if "%user%"=="" goto end
if "%newpw%"=="" goto end

REM prepare domain usage
if "%domainuser%"=="yes" set domainparam=/domain
if "%domainuser%"=="1" set domainparam=/domain

REM check if user exists
net user %user% %domainparam%
if not %errorlevel%==0 echo %date% %time:~0,8% Nutzer %user% scheint nicht zu existieren >> %log% && goto end
goto active

:active
if "%checkactive%"=="0" goto expires
if "%checkactive%"=="no" goto expires
REM check if user is active
for /f "tokens=1-3" %%i in ('net user %user% %domainparam%') do ( if "%%i %%j"=="Konto aktiv" set active=%%k )
if "%active%"=="Ja" goto expires
if "%active%"=="Yes" goto expires
REM activate the user if wished
if "%reactivate%"=="yes" net user %user% %domainparam% /Active:YES
if "%reactivate%"=="1" net user %user% %domainparam% /Active:YES
echo %date% %time:~0,8% Nutzer %user% ist deaktiviert. Passwort wird trotzdem zurückgesetzt. >> %log%
goto expires

:expires
if "%checkpwexpire%"=="0" goto changepw
if "%checkpwexpire%"=="no" goto changepw
REM check if user password has an expiration date
for /f "tokens=1-4" %%i in ('net user %user% %domainparam%') do ( if "%%i %%j %%k"=="Kennwort läuft ab" set expires=%%l )
if "%expires%"=="Nie" goto changepw
if "%expires%"=="Never" goto changepw
if "%changeanyways%"=="yes" goto changepw
if "%changeanyways%"=="1" goto changepw
echo %date% %time:~0,8% Nutzer %user% hat ein zeitlich limitiertes Passwort, Passwortänderung wird abgebrochen. >> %log%
goto end

:changepw
net user %user% %newpw% %domainparam%
set pwEL=%errorlevel%
if %pwEL%==0 echo %date% %time:~0,8% Passwort geändert. User: %user% - Aktiviert: %active% - PW läuft ab: %expires% >> %log% && goto end
echo %date% %time:~0,8% Fehler beim Ändern des Passworts: %pwEL%. User: %user% - Aktiviert: %active% - PW läuft ab: %expires% >> %log%
REM Errorlevel 2 means that the chosen password doen't meet the password policy guidelines
goto end

:end
endlocal

Erläuterung:
Anhand der Variablen lässt sich das Verhalten des Skriptes in bestimmten Situationen steuern. Die Kommentare sollten eigentlich alles soweit klar machen.
Ich habe das Skript noch nicht ausführlich getestet, es sollte aber eigentlich keine Probleme geben.

Nun gibt es noch ein letztes Problem: ein solches Skript sollte niemals einfach so auf den Netzlaufwerken liegen, schließlich steht das Passwort dort im Klartext. Und die Ordner, in denen die Gruppenrichtlinienskripte liegen, sind oftmals für die Computer- oder Nutzerobjekte der Domäne lesbar. IT-erfahrene Nutzer könnten also das Skript finden und einsehen.

Es ist also wichtig das Passwort oder das komplette Skript zu verschlüsseln. Je nach Firma, Größe, Nutzergruppe, Sicherheitsrichtlinien usw. muss man in diesen Punkt mehr oder weniger Arbeit stecken.
Ich habe an dieser Stelle ein sehr einfaches Tool gefunden, welches Batch Skripte in .exe Dateien kompilieren kann und damit den Inhalt (für die meisten Menschen) unlesbar macht: Bat To Exe Converter von F2KO macht genau das, was der Name sagt. Außerdem kann die .exe Datei mit einem Passwort verschlüsselt, unsichtbar ausgeführt und mit einem Icon sowie vielen Metainformationen versehen werden.
change-reset-local-user-passwords-bat-to-exe-compiler

Also, Nutzer und endgültiges Passwort in das Skript eintragen, kompilieren und die .exe Datei auf das Netzlaufwerk legen und verteilen. Wenig später ist das Passwort überall neu gesetzt.

Der Leser Matthias Buchner fragte mich vor einiger Zeit, wie man mit Batch bestimmte Aktionen in allen Benutzerprofilen eines PCs ausführen kann. Die Lösung lieferte er später selbst und ich möchte euch das natürlich nicht vorenthalten. Mehr zu seiner Lösung in Schritt 4, vorher erstmal etwas Code-Kennenlernen und Basics.

Allgemein gesehen geht es darum Batch Aktionen in allen Unterordnern eines Ordners auszuführen. Dies lässt sich dann später auf das C:\Users\ Verzeichnis anwenden um den erwähnten Effekt zu erzielen.

Schritt 1: Unterordner eines Ordners ausgeben

Fangen wir klein an:

REM Achtung, Doppelbackslash Bug, siehe unten
setlocal
set rootdir=%cd%
set localusersdir=%systemdrive%\Users
for /d %%i in (%localusersdir%\*) do (
  echo %%i
)
pause
endlocal

Das Script gibt alle Unterordner eines Ordners aus. (linker Screenshot)
Erste mögliche Problemstelle: damit der Code funktioniert muss am Ende des Pfades \* stehen. Wenn die Pfadvariable jetzt bereits ein \ am Ende zu stehen hat (Laufwerksangabe z.B. per „C:\“), werden Pfade mit doppelten Backslashes ausgegeben. (rechter Screenshot) Das kann natürlich zu weiteren Problemen führen.
batch-execute-actions-in-each-subdir-simple batch-execute-actions-in-each-subdir-doublebackslash

Um das Problem zu beheben und unabhängig davon zu sein, wie der Pfad am Ende aussieht, habe ich folgenden Workaround gebastelt:

setlocal ENABLEDELAYEDEXPANSION
set rootdir=%cd%
set localusersdir=%systemdrive%\Users
for /d %%i in (%localusersdir%\*) do (
  set dir=%%i
  echo !dir:\\=\!
)
pause
endlocal

Bei der Verarbeitung auftretende Doppelbackslashes werden durch einfache Backslashes ersetzt.
batch-execute-actions-in-each-subdir-doublebackslash-fix

Schritt 2: Aktionen auf Elemente in Unterordnern ausführen

Damit lassen sich jetzt also Aktionen auf die direkten Unterordner eines Ordners ausführen. Das hilft jetzt aber selten.
Wichtiger ist es auf einen bestimmten Ordner oder eine Datei in jedem Unterordner Aktionen auszuführen.
Dazu wäre folgender Code eine Grundlage:

setlocal ENABLEDELAYEDEXPANSION
set localusersdir=%systemdrive%\Users
for /d %%i in (%localusersdir%\*) do (
  set dir=%%i
  del /q /f "!dir:\\=\!\Desktop\passwords.txt"
)
pause
endlocal

Der Code schaut in allen Benutzerprofilen auf dem Desktop nach einer passwords.txt und löscht diese, falls vorhanden. Funktioniert dank Workaround auch bei Doppelbackslashes.
batch-execute-actions-in-each-subdir-delete-edit-file-doublebackslash batch-execute-actions-in-each-subdir-delete-edit-file

Das Beispiel lässt sich jetzt beliebig ausbauen.

Schritt 3: mehrfache Verschachtelung

Dieser Fall kommt vielleicht seltener vor, war aber tatsächlich die Anfrage des Lesers: Bestimmte Aktionen in allen Benutzerprofilen ausführen; diese Aktion war eine Manipulation der Firefox Einstellungen, die wiederum in allen Firefox Profilen stattfinden soll.
Also [foreach] User -> [foreach] Firefox Browser Profile -> [do smth]
Dazu muss der oben gezeigte Code verschachtelt werden:

@echo off
setlocal ENABLEDELAYEDEXPANSION
set localusersdir=%systemdrive%\Users\
for /d %%i in (%localusersdir%\*) do (
  set dir=%%i
  echo User Profile: !dir!
  for /d %%j in ("!dir:\\=\!\AppData\Roaming\Mozilla\Firefox\Profiles\*") do (
    set deepdir=%%j
    echo Browser Profile: !deepdir!
    if "!deepdir:~-8!"==".default" (
      echo profile valid
      findstr /i /c:devtools.toolbox.selectedTool !deepdir!\prefs.js
      if not errorlevel 1 (
        echo prefs.js valid, rename it
        ren "!deepdir!\prefs.js" "prefs_backup.js"
      )
    )
  )
  echo.
)
pause
endlocal

Erläuterung: Jeder Firefox Profilordner aller lokalen Benutzer wird überprüft, ob er mit „.default“ endet. Wenn das der Fall ist wird in dem Ordner in der Datei prefs.js nachgesehen, ob die Zeichenkette „devtools.toolbox.selectedTool“ enthalten ist. Wenn ja wird die Datei in prefs_backup.js umbenannt, wodurch die Einstellungen des Firefox Profils zurückgesetzt werden (also seid vorsichtig mit dem Beispiel).

batch-execute-actions-in-each-subdir-nested-commands-complex
Der Screenshot zeigt die 4 unterschiedlichen Reaktionen:
User 1: Profil gefunden jedoch existiert keine prefs.js in dem Ordner (ungewöhnlich, hätte ich es nicht provoziert)
User 2: Profil gefunden, prefs.js gefunden, jedoch enthält sie den gesuchten String nicht
User 3: kein Firefox Profilordner gefunden
User 4: Profil gefunden, prefs.js gefunden, String gefunden (beim 2. Profil), Datei wird umbenannt

Das Beispiel soll nur zeigen, dass sich für eine Menge von Unterordnern, welche wiederum Unterordner eines Root Ordners sind, komplexe Überprüfungen und Befehle ausführen lassen. Jedoch hat auch das seine Grenzen. Beispielsweise können innerhalb der for Blöcke eine goto Sprungmarken benutzt werden. Es gibt sicher noch weitere Dinge, die in diesem Konstrukt nicht so funktionieren, wie sie es in Batch normalerweise tun, bisher ist mir nur das mit dem goto aufgefallen.

Schritt 4: Code komprimieren und optimieren

Ohne Überprüfungen, mögliche Fehler-Korrekturen, Variablen usw ist das Programm zwar weniger flexibel aber dafür sehr viel kürzer. Ich poste hier das konkrete Beispiel, dass mir Matthias Buchner per Mail geschickt hat:

FOR /D %%i IN (%systemdrive%\Users\*) Do FOR /D %%j IN (%%i\AppData\Roaming\Mozilla\Firefox\Profiles\*) Do (
 ren "%%j\prefs.js" "prefs_backup.js"
 findstr /v /i /c:Test "%%j\prefs_backup.js" >> "%%j\prefs.js"
 del /q/f "%%j\prefs_backup.js" 2>nul 1>nul
)

Alle Firefox Profile aller Nutzer werden nach der prefs.js durchsucht. Über einen typischen Batch Workaround werden aus der prefs.js einfach nur alle Zeilen, die den String „Test“ enthalten, gelöscht.

Mehr Firefox Profile Manipulation mit Batch hier: Batch: Textzeile aus einer Datei herauslöschen/filtern (ist aber das gleiche wie in Schritt 4: bestimmte Zeilen entfernen)

Die Offlinedateien von Windows 7 werden ebenso selten genutzt wie sie das Netzwerk belasten können, wenn sie aktiviert sind.

Manuell kann man die Funktion folgendermaßen deaktivieren:
Start -> „Offlinedateien verwalten“ suchen -> Offlinedateien deaktivieren
oder
Start -> Systemsteuerung -> Synchronisierungscenter -> Offlinedateien verwalten (links im Menü unten) -> deaktivieren

disable-windows-7-offlinefiles-sync-offlinedateien

Im Unternehmen kann es durchaus sinnvoll sein, die Offlinedateien per Gruppenrichtlinie oder per Skript zu deaktivieren.
Und für die Verteilung im Windows AD Netzwerk habe ich natürlich wieder ein Skript vorbereitet:

@echo off & Color 9f & setlocal

set log=\\server\pfad\disable-offline-files.log
set preconfig=99

sc stop CscService
sc config CscService start= DISABLED

:check
for /f "tokens=1,2,3 delims= " %%a in ('reg query "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\CSC" /v Start^|findstr "Start"') do set preconfig=%%c
echo %date% %time:~0,8% - %computername% Offline-Files: %preconfig% >> %log%
if "%preconfig%"=="0x1" goto disable
goto end

:disable
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\CSC" /v Start /t REG_DWORD /d 4 /f
echo %date% %time:~0,8% - %computername% Offline-Files deaktiviert, errorlevel %errorlevel% >> %log%

:end
endlocal
pause

Auszug aus der Logdatei:
disable-windows-7-offlinefiles-sync-offlinedateien-log

tech-struktur2In einem aktuellen Webprojekt verwende ich MongoDB als Datenbank und die Node Erweiterung mongoose als Aufsatz. mongoose ist für alle Entwickler, die bisher pur auf der MongoDB arbeiten, definitiv einen Blick wert.
Anfangs wurde lokal auf dem PC programmiert, mittlerweile beginnen wir den Umstieg auf einen Webhoster. Dabei ergab sich ein Problem, dass ich kurz erläutern möchte.

Die Mongoose Verbindung zur Datenbank erfolgt anhand eines Connection Strings, der bei einer Standardinstallation aller Komponenten sehr einfach aussehen kann:

var mongoose = require("mongoose");
mongoose.connect("mongodb://localhost/proorg-test");

Dieser String verwendet localhost als Server, den Standardport von MongoDB 27017, die Datenbank „proorg-test“ und keine Authentifizierung. Das reicht für den Anfang und funktioniert gut.

Nach dem Umstieg auf einen (shared) Webserver trifft man jedoch meistens eine andere MongoDB Installation an. Oftmals wird diese mit dem Parameter –auth eine Authentifizierung erfordern, einen anderen Port nutzen und vielleicht auch einen anderen Server verwenden.
Neben den zusätzlichen Angaben für Server, Port und User muss man Mongoose jetzt auch befehlen, sich explizit an der „Admin Tabelle“ zu authentifizieren. Dies ist meisten die admin Tabelle, in der auch ein Admin Nutzer, dessen Daten im Connection String stehen, enthalten sein sollte.

var mongoose = require("mongoose");
mongoose.connect("mongodb://mongodb_admin:r0OtP4s$w0rd@localhost:20718/proganizer", {auth:{authdb:"admin"}});

Dieser String enthält jetzt den wichtigen Teil {auth:{authdb:“admin“}}, damit Mongoose sich mit dem mongodb_admin an der admin Tabelle authentifiziert.

Mehr dazu auch in den MongoDB Security Tutorials.

logo-trans-2012An dieser Stelle auch big shoutouts an uberspace.de, der sympathische deutsche Webhoster mit „Preis-selber-wählen“ Prinzip und einem riesigen Arsenal an vorinstallierten, vorkonfigurierten und bestens dokumentierten Server- und Entwickler-Tools. Ein kleiner Einblick: „SSH. Perl. PHP. Python. Ruby. node.js. Erlang. Lua. Compiler. FastCGI. MySQL. CouchDB. MongoDB. Cronjobs. HTTPS. IMAP. SMTP. Webmail. qmail. vmailmgr. maildrop. Spam­Assassin. ezmlm-idx. DSPAM. ~/service. runwhen. Eigene Logs. Backups. 10 GB Plattenplatz. Und das ist nur der Anfang.“

Mögliche Fehlermeldungen:
Dieser Fehler würde auftreten, wenn trotz –auth Parameter bei MongoDB der Standard Connection String benutzt würde:
assertion 16550 not authorized for query on proganizer.users ns:proganizer.users query:{ username: „SchurigH“ }
Wenn man nur Benutzername und Passwort eingibt, ohne den auth Parameter von Mongoose einzustellen, also

mongoose.connect("mongodb://schurigh_mongoadmin:r0OtP4s$w0rd@localhost:20718/proganizer");

, dann kommt:
auth: couldn’t find user schurigh_mongoadmin@proganizer, proganizer.system.users
Und selbst wenn man jetzt in proganizer.system.users einen neuen Admin User erstellt – das ginge mit db.addUser({user:“proganizer_admin“, pwd: „*****“, roles: [ „readWrite“, „dbAdmin“ ]}); – würde das nicht funktionieren. Ohne {auth:{authdb:“admin“}} geht es nicht.

Registry Werte aus der Registry auslesen ist ein alter Hut, no problem. Auch ganze Pfade aus der Registry lesen stellt grundsätzlich kein Problem dar. Schwierig wird es jedoch, wenn der auszulesende Pfad Leerzeichen enthält. Und seit Windows Vista enthalten ja fast alle Pfade ein Leerzeichen: C:\Program Files\…

Dieser Post dient der Zusammenfassung der wichtigsten Batch Registry Abfragen.

Einfache Werte und Pfade ohne Leerzeichen

@echo on & Color 9f & setlocal
REM einfache Werte
set ff=0
for /f "tokens=1,2,3 delims= " %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Mozilla\Mozilla Firefox" /v "CurrentVersion"^|findstr "CurrentVersion"') do set ff=%%c
echo %ff%

REM einfache Pfade
set spath=0
for /f "tokens=1,2,3 delims= " %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Ghisler\Total Commander" /v "IniFileName"^|findstr "IniFileName"') do set spath=%%c
echo %spath%

Also eine festgelegte Anzahl an Tokens, Leerzeichen als Delimiter, der dritte Token ist der Registry Wert. Token 1 und 2 sind der Name des Registry Werts und der Wert-Typ.

Werte oder Pfade mit Leerzeichen

REM Werte mit Leerzeichen
set value=0
for /f "usebackq skip=2 tokens=1,2*" %%a in (reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\FileZilla Client" /v "Startmenu") do set value=%%c
echo %value%

REM Pfade mit mehreren Leerzeichen
set path1=0
set path2=0
REM Möglichkeit 1: mit Option usebackq, /ve (liest den (Standard) Wert aus)
for /f "usebackq skip=2 tokens=1,2*" %%a in (reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\FileZilla Client" /ve) do set path1=%%c
echo %path1%
REM Möglichkeit 2: ohne Option usebackq
for /f "tokens=2*" %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\FileZilla Client" /ve') do set path2=%%b
echo %path2%

Für Werte oder Pfade mit Leerzeichen müssen 3 Dinge beachtet werden:
1. Die Delimiter entfallen.
2. Tokens empfangen mit * mehrere durch Leerzeichen getrennte Werte und packen sie in den letzten Token zusammen; den wir dann abfragen.
3. Bei der Methode 1 mit usebackq müsst ihr unbedingt auf die Anführungszeichen achten! Der komplette Inhalt in den Klammern wird mit einem „backquote“, also , eingeschlossen. Das ist kein “ und kein ‚ und kein ´ usw, achtet darauf! Alle anderen Angaben kommen wie gewohnt in doppelte Anführungszeichen. Bei der Methode ohne usebackq kommt der Klammerinhalt in normale einfache Anführungszeichen ‚.

Das Ergebnis aller obrigen Code-Schnipsel:
registry-werte-mit-batch-auslesen-mit-leerzeichen-usebackq

Im Windows Netzwerk Schriftarten zu verteilen war komplexer als gedacht. Daher möchte ich mal oberflächlich die möglichen Verteilungsmethoden erläutern und meine Empfehlung geben. Die besten Lösung ist ganz unten, Stichwort FontReg.exe. Ziel ist die Verteilung (also Installation zur Verwendung) von Schriftarten in Windows Netzwerken über Gruppenrichtlinien.

Dafür gibt es verschiedene Methoden: Batch, GPO, VBS und FontReg.

Batch (nicht empfohlen)

REM alle Schriftarten aus dem fonts Unterordner in den Fonts Systemordner kopieren
xcopy fonts\*.* "%windir%\Fonts\" /v /c /l /y /i
REM jede Schriftart einzeln in der Registry anmelden
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "OpenSans-Bold (True Type)" /t REG_SZ /d "OpenSans-Bold.ttf" /f
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "OpenSans-BoldItalic (True Type)" /t REG_SZ /d "OpenSans-BoldItalic.ttf" /f
...

Der Registry Befehl muss für jede gewünschte Schriftart ausgeführt werden.

GPO (nicht empfohlen)

Wie schon GPO Meister Heitbrink erklärt hat:
In der Gruppenrichtlinie unter Computerkonfiguration\Einstellungen\Windows-Einstellungen\Dateien folgende Einstellung für jede Schriftart erstellen:
schriftarten-über-das-netzwerk-verteilen-mit-batch-gpo-vbs-register-font-file-gpo
In der Quelle bitte den korrekten \\server\pfad\ angeben. Dadurch werden die Schriften in den Windows Ordner kopiert.

Nun noch in Computerkonfiguration\Einstellungen\Windows-Einstellungen\Registry folgende Einstellungen, ebenfalls einmal pro Schriftart:
schriftarten-über-das-netzwerk-verteilen-mit-batch-gpo-vbs-copy-font-file-gpo
Den Registry Pfad so übernehmen wie auf dem Bild, „Name“ enthält den Anzeigenamen innerhalb von Programmen, bei der Fontdatei kein Pfad mehr davor packen, nur der Dateiname.

Nachteil

Beide Lösungen sind nicht sehr aufwändig, 2 Batch Befehle bzw. 2 GPO Einstellungen, simpel. Viel eher hat es den Nachteil, dass man bei einer größeren Menge an Schriftarten dann doch recht viel zu tun hat. 20 Schriftarten? Das bedeutet viel rumgetippe.
Besser ist in diesem Fall folgende Lösung:

VBS (nicht empfohlen)

Dieses VBS installiert alle Fontdateien, die sich in einem bestimmten Ordner befinden. Der Ordner wird in strFontSourcePath angegeben.

' via http://www.morovia.com/kb/Installing-Fonts-command-script-10008.html
' didnt work as is, some more lines were needed to make it work

Option Explicit
Dim objShell, objFSO, wshShell
Dim strFontSourcePath, objFolder, objFont, objNameSpace, objFile, oWinFonts

Set objShell = CreateObject("Shell.Application")
Set wshShell = CreateObject("WScript.Shell")
Set objFSO = createobject("Scripting.Filesystemobject")

strFontSourcePath = "\\server\Deployment\Sonstiges\fonts\fonts\"

If objFSO.FolderExists(strFontSourcePath) Then

Set objNameSpace = objShell.Namespace(strFontSourcePath)
Set objFolder = objFSO.getFolder(strFontSourcePath)
Const FONTS = &H14&
Set oWinFonts = objShell.Namespace(FONTS)

For Each objFile In objFolder.files
 If LCase(right(objFile,4)) = ".ttf" Then
  If objFSO.FileExists(wshShell.SpecialFolders("Fonts") & "\" & objFile.Name) Then
   
  Else
   Set objFont = objNameSpace.ParseName(objFile.Name)
   oWinFonts.CopyHere objNameSpace.ParseName(objFile.Name)
   objFont.InvokeVerb("Install")
   Set objFont = Nothing
  End If
 End If
Next
Else
 Wscript.Echo "Font Source Path does not exists"
End If

Die Einbindung in das Netzwerk könnte direkt als Startscript erfolgen. Wenn man jedoch einen leichten Überblick haben möchte, welche Computer die Schriften schon installiert haben, empfiehlt sich dieses Script:

@echo on && color 9f && setlocal
set wd=\\server\Deployment\Sonstiges\fonts
set log=%wd%\install-fonts.txt

echo %date% %time:~0,8% - %computername% startet die Fontinstallation >> %log%

cmd /c cscript //nologo "%wd%\install-fonts.vbs"

::REM Test mit zwei beliebigen Schriftarten
if exist "%windir%\fonts\OpenSans-SemiboldItalic.ttf" echo %date% %time:~0,8% - %computername% hat OpenSans installiert >> %log%
if exist "%windir%\fonts\Lato-ThinItalic.ttf" echo %date% %time:~0,8% - %computername% hat Lato installiert >> %log%

:end
endlocal

Das Script loggt Start und Ende der Schriftinstallation und prüft die erfolgreiche Installation mit 1 beliebigen Schriftart. Ein Neustart muss aber auch bei dieser Methode sein.
Aber egal ob dieser Code, diese Kurzfassung oder anderen Varianten, und egal wie man es dreht und wendet – zu 100% zuverlässig sind die Skripte nicht. Auch in meinem Netzwerk wollten ca. 10% der Rechner diese Variante nicht annehmen.

FontReg.exe (empfohlen)

Nach weiterer Recherche fand ich eine Lösung, die SO einfach ist, dass es mich schon fast aufregt, auf dem Weg dorthin etliche Stunden nicht nicht 100% funktionierende Skriptlösungen investiert zu haben. FontReg ist die Lösung, eine Weiterentwicklung des älteren Windows Utilities fontinst.exe.
Das Tool, wenn es ohne weitere Parameter aufgerufen wird, repariert fehlerhafte oder fehlende Schriftartenverknüpfungen und ermöglicht damit einen einfachen Zweizeiler für die Schriftverteilung:

xcopy "\\server\Deployment\Sonstiges\fonts\fonts\*.*" "c:\Windows\Fonts" /y
"\\server\Deployment\Sonstiges\fonts\FontReg.exe"

Awesome! 🙂