Als Entwickler ist man häufig konfrontiert mit der Aufgabe, Software auf viele Rechner eines Netzwerks
zu verteilen. Der Anwender sollte davon nichts merken, und die Programme nach dem Einschalten seines PC sofort
im Startmenü sehen. Und ... weil man nicht von Rechner zu Rechner laufen möchte, sollte das ganze
auch möglichst vollautomatisch geschehen.
Dieser Artikel behandelt einige Wege, wie die automatische Softwareverteilung in Windows - Netzwerken
durchgeführt werden kann.
Microsoft hat Ende der 90er Jahre damit angefangen, die Softwareverteilung zu Strukturieren, und erfand damals
eine Datenbank - basierte Installationstechnologie, bekannt unter dem Namen "Windows Installer".
Dabei wurden alle Komponenten der zu Installierenden Software, also ausführbare Programme (EXE), dynamische Bibliotheken (DLL, OCX),
und alle weiteren erforderlichen Dateien zusammen in eine große Installationsdatenbank gepackt, die die Endung "MSI" erhält.
In dieser Datenbank können auch sehr detaillierte Regeln und Anweisungen hinterlegt werden, um beispielsweise vorzugeben,
welche Datei wohin installiert werden soll
welche Dialoge der Benutzer bei der Installation angezeigt bekommt (wenn überhaupt)
welche Programmteile ("Features") dem Benutzer zur Installation angeboten werden
ob Symbole und / oder Verknüpfungen im Startmenü oder auf dem Desktop angelegt werden sollen
ob bestimmte DLLs oder OCXe registriert werden sollen
welche anderen Aktionen noch zusätzlich durchgeführt werden sollen (sogenannte "Custom Actions")
ob systemweite Shortcuts für den Programmstart eingerichtet werden sollen
Diese Punkte geben nur einen kleinen Bruchteil dessen wieder, was alles in einer MSI - Datenbank hinterlegt werden kann.
Zum Ausführen einer MSI - basierten Installation gibt es den "Windows Installer Service", ein Dienst, der ganz normal unter den Windows - Diensten eingetragen ist und beim Starten des PC von selbst anläuft.
In Windows 2000 ist dieser Dienst von Haus aus bereits auf dem System (in der Version 1.1), in Windows XP natürlich ebenfalls (Version ist mindestens 2.0, ServicePack - abhängig).
Wenn man auf diesen Systemen eine MSI - Datei doppelklickt, so startet der Windows Installer - Dienst, um die MSI - basierte Installation durchzuführen.
Wie erstellt man eine MSI - Datenbank ?
Dazu gibt es mehrere Wege. Der bequemste ist sicherlich, ein speziell dafür optimiertes Programm zu erwerben, dem man nur noch seine Dateien und die Regeln übergeben muß.
Marktführer in diesem Bereich sind die Produkte "InstallShield" von der Firma Macrovision, und der "Wise for Windows Installer" von der Firma Wise Solutions.
Wer es lieber kostenlos (und dafür etwas komplizierter) probieren möchte, kann auch ohne diese Produkte zum Ziel kommen.
Ab Visual Studio .NET bietet Microsoft z.B. für alle in Visual Studio erzeugten Programme die Möglichkeit an, mit einem in Visual Studio integrierten Tool MSI - Datenbanken zu erzeugen. Dies ist ebenfalls noch recht bequem.
Ein Vorläufer dieses "Visual Studio Installers" wurde mal für Visual Studio 6 als nachträgliches Add-On angeboten;
interessanterweise gibt es diese Version noch heute (Stand: Juli 2005) im Netz zum Download. Damit ist es für Besitzer von Visual Studio 6 ebenfalls kostenlos möglich,
MSI - Dateien halbwegs bequem zusammenzustellen. Allerdings bietet diese Vorläuferversion nur die Nutzung eines Bruchteils der Möglichkeiten, die die Windows Installer - Technologie zur Verfügung stellt.
Für den Anfang reicht es aber vollkommen aus.
Wer sich mit diesem Thema beschäftigt, kommt nicht drum herum, sich einmal das "Windows Installer SDK" von Microsoft herunterzuladen, in dem die
Windows Installer - Technologie dokumentiert ist, und auch Tools angeboten werden, mit denen man Unterstützung bei der Arbeit findet.
Das Wichtigste ist sicherlich "Orca", ein Programm zum Bearbeiten von MSI - Datenbanken. Auch das alles kostenlos.
Mithilfe des Windows Installer SDK ist es sogar möglich, von Grund auf neue MSI - Datenbanken "von Hand" herzustellen, was jedoch einen enormen Aufwand darstellen kann.
Der einfachste Weg in einer Domäne mit Windows 2000 Server oder Windows 2003 Server ist der, eine Gruppenrichtlinie im Active Directory anzulegen,
und dort die MSI - Pakete einfach einzufügen. Dazu legt man im Active Directory am besten zunächst eine neue Organizational Unit (OU) an:
Danach legt man eine neue Gruppenrichtlinie an, die man sogleich bearbeiten kann:
Hier bitte auf "Neu" drücken, und einen Namen vergeben.
Dann auf "Bearbeiten" ...
Unter Computerkonfiguration / Softwareinstallation kann man dann im rechten weißen Teilfenster
MSI - Pakete einfügen, wie hier im Bild bereits zu sehen ist. Man drückt dort die rechte Maustaste,
und sagt "Neu / Paket". Dann wählt man das Paket mit einem Datei-Suchdialog aus, und sagt "zugewiesen".
Das ist für die Gruppenrichtlinie erstmal alles. Wichtig ist dabei, daß der Pfad zum Paket voll qualifiziert ist,
also z.B. "\\Server1\Freigabe1\Paket.msi" lautet, und nicht etwa "N:\Paket.msi", denn das Laufwerk "N:" ist möglicherweise
nicht auf jedem PC eingerichtet, wo das Paket nachher installiert werden soll.
Jetzt müssen nur noch die PCs in die OU eingefügt werden, die unter diese Gruppenrichtlinie fallen sollen:
Dazu geht man im Active Directory zurück zur Liste der Mitgliedscomputer, und drückt auf die rechte Maustaste und "Verschieben",
um alle PCs, die von der Gruppenrichtlinie betroffen sein sollen, in die eben neu erschaffene OU einzufügen.
Wenn man jetzt alles richtig gemacht hat, werden die betroffenen PCs beim nächsten Neu-Start automatisch die MSI - Pakete,
die in der Gruppenrichtlinie zugewiesen wurden, installiert bekommen.
Die kleinen Problemchen ...
Der häufigste Fehler, der passieren kann, ist, daß die betroffenen PCs eine falsche DNS - Einstellung haben.
Als DNS - Server muß unbedingt der Active Directory - Server eingetragen sein, der die Gruppenrichtlinien "kennt",
sonst wird keine Software verteilt.
Des Weiteren müssen alle PCs natürlich Zugriff auf die Freigabe und den Ordner haben, in der die MSI-Pakete liegen.
Ich sage bewußt Freigabe UND Ordner, denn sowohl für die Freigabe, als auch für den Ordner können Berechtigungen vergeben werden.
Weil die PCs beim booten mit dem Benutzerkonto "SYSTEM" versuchen, die MSI - Pakete abzuholen,
sollte als Berechtigung "Domänen-Computer" und "Authentifizierte Benutzer" eingetragen sein, mindestens mit Leserechten.
Ich habe auch schon selbst erlebt, daß die Netzwerkkarten - Einstellungen die Installation verhindern; wenn nämlich die Netzwerkkarte nicht weiß,
ob sie mit 10 oder 100 MBit laufen soll, und auf "Auto-Negotiate" eingestellt ist, kann auch dies verhindern, daß die Pakete
installiert werden. Also, besser prüfen, welche Einstellung richtig ist (in der Regel die 100), und dies fest einstellen in den Eigenschaften der Netzwerkkarte unter den Netzwerkeinstellungen.
Bisweilen kommt es auch schonmal vor, daß während der Installation des msi - Pakets eine Warnmeldung aufgeht mit dem Hinweis: "The following application should be closed before continuing the install:".
Besonders ärgerlich ist es, wenn diese Meldung bei einer automatisierten Boot-Installation passiert, mit automatisierter Windows-Anmeldung, dann bleibt die Installation nämlich einfach dort stecken, "unter" der Windows-Sitzung unsichtbar, solange, bis man sich abmeldet.
Meldet man sich ab und probiert dann die Knöpfe "Wiederholen" oder "Ignorieren", erscheint der folgende Fehler: "Error 1931: Der Windows Installer-Dienst kann die Systemdatei "C:\Winnt\System32\OLEAUT32.DLL" nicht aktualisieren, weil die Datei von Windows geschützt wird".
Das kann auch jede andere beliebige DLL sein. Ursache: In der msi-Installationsdatei war diese DLL als Datei eingefügt, und nicht als Merge-Modul, und dann kommt dieser Fehler.
Abhilfe: Nicht diese Einzel-Datei in die Installation packen, sondern als Merge-Modul, diese Module gibt es für alle gängigen Windows - System-Dlls. In diesem Beispiel wäre es die Datei OLEAUT32.MSM.
Also im Installationsprojekt klicken auf "Add / Merge Module", und dann im Pfad "\Programme\Microsoft Visual Studio\Common\Tools\VsInst\BuildRes" das Modul auswählen. Wer aus dem Microsoft Windows Platform SDK das Windows Installer SDK installiert hat, hat auch diesen Pfad mitsamt allen Merge-Modulen.
... und die ernsten Probleme
Im Mai und Juni 2005 habe ich selbst Blut und Wasser geschwitzt aufgrund bestimmter Ereignisse, und will diese Erfahrung gerne zur Warnung dokumentieren und weitergeben.
Hintergrund: Seit etwa Anfang Mai verteilt Microsoft per Windows - Update auf Windows 2000 - Rechner eine neue Version des Windows Installers, die Version 3.1
(Ein ausführlicher Forum-Thread zur Historie liegt auf Installsite.de).
Dessen erste Version führte dazu, daß man bestimmte MSI Pakete (mindestens meine!) plötzlich nicht mehr in der Gruppenrichtlinie einfügen konnte; man bekam beim Versuch schlicht eine Fehlermeldung nach dem Motto "Die Datei ist kein gültiges Installationspaket".
Die erste Version des Windows Installers 3.1 wurde dann von Microsoft als fehlerhaft zurückgezogen, und kurz darauf als "Windows Installer 3.1 (V2)" erneut verteilt.
Diese Version hat zwar nicht mehr den besagten Fehler, das heißt die Pakete gehen jetzt wieder rein in die Gruppenrichtlinie, jedoch wird kein Paket mehr verteilt.
Fatalerweise werden die Pakete, die man rausnimmt aus der Gruppenrichtlinie, erfolgreich deinstalliert.
Ich habe trotz intensiver Prüfung (inklusive Deinstallation des neuen Installers) keinen Weg gefunden, danach nochmal die Softwareverteilung wieder hinzubekommen.
Wenn ich eines Tages mehr dazu weiß, werde ich es hier wiedergeben.
Dieses Problem führte zur Entwicklung meiner eigenen Bootloader-Installationstechnik, die im kommenden Artikel beschrieben wird.
Ein Tip erreichte mich von Christoph Holtkamp: Wenn die Anwendung nicht automatisch beim PC-Start installiert wird, kann man unter den Eigenschaften des Pakets,
auf dem Reiter "Bereitstellung von Software" die Option "Anwendung bei Anmeldung installieren" aktivieren:
Auch hier gibt es wieder einen Trick: Diese Option ist häufig ausgegraut (deaktiviert), aber wenn man auf "zugewiesen" klickt, selbst wenn es bereits aktiv ist, wird
"Anwendung bei Anmeldung installieren" plötzlich enabled (bedienbar). Danke an Christoph Holtkamp für den Hinweis.
Ich stand vor folgendem Problem: Ich kann mich nicht darauf verlassen, daß jeder Anwender alle DLLs, OCX-Komponenten etc hat, die mein Programm benötigt.
Mein Programm basiert zum Teil auf Visual Basic 6.0, zum Teil aus Visual C++ 6.0, und dabei werden viele einzelnen Komponenten benötigt, zum großen Teil auch COM - Komponenten, die registriert werden müssen.
Jeder Anwender bekommt über sein Profil (Das kann ich über den Server steuern) nur eine Verknüpfung auf mein EXE - Programm,
dieses liegt auf einer Freigabe auf dem Server (\\Server\Freigabe\Anwendung.exe). Soweit funktioniert alles einwandfrei.
Wenn er aber das Programm startet, und hat nicht alle Komponenten bei sich installiert, dann bekommt er früher oder später einen Laufzeitfehler.
Also habe ich folgende Lösung eingebaut: Mein Programm führt als allererstes einen Bootloader - Code aus, daß heißt, ein kleines Unterprogramm, welches nichts anderes macht, als bestimmte MSI - Pakete zu installieren, um sicherzustellen, daß alle Komponenten vorhanden sind.
Das Programm braucht anschließend nicht beendet zu werden, sondern kann gleich weiterlaufen; die DLLs sind anschließend verfügbar.
Der Anwender merkt davon nichts !
Die Technik
Hier erst einmal der Code, wie er in Visual Basic 6.0 eingebaut wurde. Er läuft, während der anfängliche Splash-Screen angezeigt wird:
Public Type PROCESS_INFORMATION
hProcess AsLong
hThread AsLong
dwProcessId AsLong
dwThreadId AsLongEnd Type
Public Type STARTUPINFO
cb AsLong
lpReserved AsString
lpDesktop AsString
lpTitle AsString
dwX AsLong
dwY AsLong
dwXSize AsLong
dwYSize AsLong
dwXCountChars AsLong
dwYCountChars AsLong
dwFillAttribute AsLong
dwFlags AsLong
wShowWindow AsInteger
cbReserved2 AsInteger
lpReserved2 AsLong
hStdInput AsLong
hStdOutput AsLong
hStdError AsLongEnd Type
PublicConst NORMAL_PRIORITY_CLASS = &H20
PublicConst CREATE_NO_WINDOW = &H8000000
PublicConst WAIT_TIMEOUT = 258
PublicConst WAIT_OBJECT_0 = 0
PublicConst LOGON_WITH_PROFILE = 1
PublicDeclareFunction CloseHandle Lib "kernel32" _
(ByVal hObject AsLong) AsLongPublicDeclareFunction CreateProcess Lib "kernel32" Alias "CreateProcessA" _
(ByVal lpApplicationName AsLong, ByVal lpCommandLine AsString, _
ByVal lpProcessAttributes AsLong, ByVal lpThreadAttributes AsLong, _
ByVal bInheritHandles AsLong, ByVal dwCreationFlags AsLong, _
ByVal lpEnvironment AsLong, ByVal lpCurrentDirectory AsLong, _
lpStartupInfo As STARTUPINFO, _
lpProcessInformation As PROCESS_INFORMATION) AsLongPublicDeclareFunction CreateProcessWithLogonW Lib "AmiCreateProcess.dll" _
Alias "CreateProcessWithLogon" _
(ByVal strUserName AsString, ByVal strDomain AsString, _
ByVal strPassword AsString, ByVal dwLogonFlags AsLong, _
ByVal strCommandLine AsString, ByVal dwCreationFlags AsLong, _
ByRef lHandleProcess AsLong) AsLongPublicDeclareFunction GetExitCodeProcess Lib "kernel32" _
(ByVal hProcess AsLong, lpExitCode AsLong) AsLongPublicDeclareFunction GetLastError Lib "kernel32" () AsLongPublicDeclareSub Sleep Lib "kernel32" (ByVal dwMilliseconds AsLong)
PublicDeclareFunction WaitForSingleObject Lib "kernel32" _
(ByVal hHandle AsLong, ByVal dwMilliseconds AsLong) AsLongPublicSub CheckInstallations()
' Hier wird geprüft, ob auf dem Server noch Dateien liegen,
' die Installiert werden müssen oder sollen:
Dim strBefehl AsStringDim lResult AsLong, i AsInteger
strBefehl = "msiexec /I \\" & gstrServerName & _
"\Freigabe$\bin\msi\WFSetup.1.2.2.msi /qn"
lResult = ExecShellCmd(strBefehl, False, True)
' Dem Windows - Installer noch eine Minute Zeit geben, seine Arbeit zu machen
For i = 1 To 5
Sleep 200
DoEvents
Next i
EndSubPublicFunction ExecShellCmd(ByVal strCmdLine AsString, _
ByVal bInvisible As Boolean, ByVal bImpersonate As Boolean) AsLongDim lResult AsLongDim ProcInfo As PROCESS_INFORMATION
Dim StartInfo As STARTUPINFO
Dim lCreationFlags AsLongOnError GoTo ErrorHandler
If bInvisible Then
lCreationFlags = NORMAL_PRIORITY_CLASS + CREATE_NO_WINDOW
Else
lCreationFlags = NORMAL_PRIORITY_CLASS
EndIf' Initialisierung der STARTUPINFO - Struktur:
StartInfo.cb = Len(StartInfo)
' Starten der Applikation in der Shell:
If (bImpersonate) Then
lResult = CreateProcessWithLogonW("Administrator", gstrDomäne, _
gstrAdminPassword, LOGON_WITH_PROFILE, strCmdLine, _
lCreationFlags, ProcInfo.hProcess)
Else
lResult = CreateProcess(0&, strCmdLine$, 0&, 0&, 1&, _
lCreationFlags, 0&, 0&, StartInfo, ProcInfo)
EndIfIf (lResult = 0) Then
lResult = GetLastError
Else' Warten auf das Ende der Shell - Applikation:
Do
lResult = WaitForSingleObject(ProcInfo.hProcess, 500)
If lResult <> WAIT_TIMEOUT And lResult <> WAIT_OBJECT_0 Then
ExecShellCmd = lResult
ExitFunctionEndIfIf lResult <> WAIT_TIMEOUT ThenExitDoEndIf
DoEvents
Loop
GetExitCodeProcess ProcInfo.hProcess, lResult
CloseHandle ProcInfo.hProcess
EndIf
ExecShellCmd = lResult
ExitFunction
ErrorHandler:
Fehlerbehandlung "ExecShellCmd", Err.Number, Err.Description, strCmdLine
EndFunction
Was passiert hier ? Die ersten 57 Zeilen geben bloß die Definitionen wieder, die nötig sind, um die hier benutzten Betriebssystem-APIs aufrufen zu können, außerdem deren benötigte Strukturen.
Die Funktion CheckInstallations() macht (hartkodiert) einen einzigen manuellen Aufruf des Windows - Installers, und gibt ihm die genaue Position des Pakets an, welches installiert werden soll.
Hier kann man sich natürlich eine Schleife und alles mögliche sonst zur automatischen Erkennung neuer und weiterer Pakete einbauen.
MSIEXEC ist der Windows - Installer - Dienst, den man auch manuell aufrufen kann. Die Parameter, die man mitgeben kann, erfährt man durch Aufruf von "MSIEXEC /?", oder noch besser aus den Büchern meiner Empfehlungen.
Die Option "/I" nennt ein bestimmtes MSI-Paket, welches installiert werden soll, und "/qn" sagt "quiet, no Dialogs" (also: ruhig, keine Benutzerdialoge).
ExecShellCmd() dient zum Aufrufen einer Kommandozeile in einer MS-DOS-Box, die dabei (wie in diesem Fall) durchaus unsichtbar bleiben kann.
Diese Funktion wartet auch solange, bis die Arbeit erledigt ist, und gibt den Returncode des aufgerufenen Programms zurück. Ich will hier nicht zu weit ins Detail gehen, und beschränke mich nur auf den Impersonation - Mechanismus.
Die Benutzer, die mein Programm aufrufen, arbeiten unter einem Benutzer-Account, der sehr stark reduziert ist in Bezug auf die Berechtigungen, mit denen die Benutzer arbeiten.
Niemals zum Beispiel wäre es denen erlaubt, selbständig irgendwelche Installationen vorzunehmen, dann könnte ich jeden Tag an die Standorte fahren, und die kaputten Systeme wieder reparieren.
Also muß die besagte DOS-Box im Kontext eines anderen Benutzers aufgemacht werden, in diesem Fall gleich mit dem Administrator - Account, damit überhaupt das Recht dazu besteht, ein MSI - Paket lokal zu installieren.
Diese Technik nennt man "Impersonation", und die ist recht tricky. Ich habe es aus VB heraus nie geschafft, die dafür vorgesehene API - Funktion "CreateProcessWithLogonW" aufzurufen, trotz Übergabe von Unicode - Zeichen raucht die immer ab.
Also habe ich die Funktion in eine C++ DLL gepackt, die einfach neben der EXE liegt (und daher sofort gefunden wird), und dann gehts. Die DLL ist bewußt keine COM - DLL und braucht daher nicht registriert zu werden.
Sie heißt "AmiCreateProcess.dll", hat gerade mal 44 Kilobyte, und kann mit dem folgenden Sourcecode komplett mit VC 6.0 compiliert und erstellt werden:
Wer also möchte, kann sich anhand dieses Sourcecodes selbst seinen Bootloader einbauen.
Das Beipiel ist in Visual Basic 6.0 lauffähig, es sollte für einen geübten C++ - Entwickler aber kein
Problem sein, diese paar Zeilen umzuschreiben, um es auch in C++ einzusetzen.
Es macht auch keinen Unterschied, wenn dasselbe Paket wieder und wieder durch den MSIEXEC gejagt wird;
der Windows Installer merkt anhand interner GUIDs, daß dasselbe Paket bereits installiert ist, und läßt gegebenenfalls dann die Finger davon.
Es wird also keineswegs immer wieder installiert, lediglich sichergestellt, dass das Paket da ist.
Deinstallieren geht natürlich genausogut mit dem MSIEXEC, mit entsprechend anderen Aufrufparametern.
"VB / VBA Developer's Guide to the Windows Installer" von Mike Gunderloy (engl.)
Für mich der Klassiker und das wichtigste Nachschlagewerk, insbesondere die einzelnen Datenbanktabellen der MSI sind detailliert dokumentiert.
Wahrscheinlich gibt es mittlerweile bessere Bücher, aber dies war eines der ersten auf dem Markt, und daher habe ich es wohl so ins Herz geschlossen.
Von den Begriffen "VB / VBA" sollte man sich nicht irritieren lassen, ich habe noch keine Codezeile VB oder VBA in dem Buch entdeckt.
"Inside Windows Installer" von Andreas Kerl (deutsch)
Sehr gutes Werk zum Verständnis der Basis-Technologien hinter dem Windows - Installer. Aus meiner Sicht etwas besser strukturiert als der Gunderloy, der Fokus liegt weniger auf der Dokumentation der Tabellen (die im SDK eh alle dokumentiert sind),
als vielmehr im rüberbringen der technischen Hintergründe.
Installsite.de, eine Seite von Stefan Krüger, "dem" Installer - Guru in Deutschland. Sehr gute Seite, viele Informationen, aktive Community. Etwas InstallShield - lastig.
Einstiegsseite der Dokumentation zum Windows Installer von Microsoft.
Platform SDK Download von Microsoft, von dort läßt sich das Windows Installer SDK herunterladen.
Visual Studio Installer 1.1, ein (für Besitzer von Visual Studio 6) kostenloses Add-On zum Erstellen von MSI - Datenbanken.
Support, Feedback, Anregungen
Alles verstanden ? Oder noch Fragen ? Wir freuen uns über Feedback zu den Tips, auch über Verbesserungsvorschläge.
Schreiben Sie an support@a-m-i.de.