|
|
|
|
|
|
|
|
|
|
Letzte Aktualisierung: 04.01.2023
|
|
Automatische Softwareinstallation
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.
Windows Installer
Der simple Weg: Active Directory - basiert
Universell einsetzbar: Die Bootloader - Technik
Buchtips
Links
|
Windows Installer
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 simple Weg: Active Directory - basiert
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.
|
Universell einsetzbar: Die Bootloader - Technik
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 As Long
hThread As Long
dwProcessId As Long
dwThreadId As Long
End Type
Public Type STARTUPINFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
Public Const NORMAL_PRIORITY_CLASS = &H20
Public Const CREATE_NO_WINDOW = &H8000000
Public Const WAIT_TIMEOUT = 258
Public Const WAIT_OBJECT_0 = 0
Public Const LOGON_WITH_PROFILE = 1
Public Declare Function CloseHandle Lib "kernel32" _
(ByVal hObject As Long) As Long
Public Declare Function CreateProcess Lib "kernel32" Alias "CreateProcessA" _
(ByVal lpApplicationName As Long, ByVal lpCommandLine As String, _
ByVal lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, _
ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As Long, _
lpStartupInfo As STARTUPINFO, _
lpProcessInformation As PROCESS_INFORMATION) As Long
Public Declare Function CreateProcessWithLogonW Lib "AmiCreateProcess.dll" _
Alias "CreateProcessWithLogon" _
(ByVal strUserName As String, ByVal strDomain As String, _
ByVal strPassword As String, ByVal dwLogonFlags As Long, _
ByVal strCommandLine As String, ByVal dwCreationFlags As Long, _
ByRef lHandleProcess As Long) As Long
Public Declare Function GetExitCodeProcess Lib "kernel32" _
(ByVal hProcess As Long, lpExitCode As Long) As Long
Public Declare Function GetLastError Lib "kernel32" () As Long
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Public Declare Function WaitForSingleObject Lib "kernel32" _
(ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
Public Sub CheckInstallations()
' Hier wird geprüft, ob auf dem Server noch Dateien liegen,
' die Installiert werden müssen oder sollen:
Dim strBefehl As String
Dim lResult As Long, i As Integer
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
End Sub
Public Function ExecShellCmd(ByVal strCmdLine As String, _
ByVal bInvisible As Boolean, ByVal bImpersonate As Boolean) As Long
Dim lResult As Long
Dim ProcInfo As PROCESS_INFORMATION
Dim StartInfo As STARTUPINFO
Dim lCreationFlags As Long
On Error GoTo ErrorHandler
If bInvisible Then
lCreationFlags = NORMAL_PRIORITY_CLASS + CREATE_NO_WINDOW
Else
lCreationFlags = NORMAL_PRIORITY_CLASS
End If
' 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)
End If
If (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
Exit Function
End If
If lResult <> WAIT_TIMEOUT Then
Exit Do
End If
DoEvents
Loop
GetExitCodeProcess ProcInfo.hProcess, lResult
CloseHandle ProcInfo.hProcess
End If
ExecShellCmd = lResult
Exit Function
ErrorHandler:
Fehlerbehandlung "ExecShellCmd", Err.Number, Err.Description, strCmdLine
End Function
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:
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0500
#include "windows.h"
#include "winbase.h"
#include "atlbase.h"
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
return TRUE;
}
long FAR PASCAL CreateProcessWithLogon(LPCSTR lpszUserName,
LPCSTR lpszDomain, LPCSTR lpszPassword,
DWORD dwLogonFlags, LPCSTR lpszCommandLine,
DWORD dwCreationFlags, HANDLE & hProcess)
{
USES_CONVERSION;
HMODULE hAdvapi32 = LoadLibrary(_T("ADVAPI32.DLL"));
if (hAdvapi32 == NULL)
return -1;
BOOL (WINAPI * fncCreateProcessWithLogonW)
(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPCWSTR, LPWSTR, DWORD,
LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION);
fncCreateProcessWithLogonW = (BOOL (WINAPI *) (LPCWSTR,
LPCWSTR, LPCWSTR, DWORD, LPCWSTR, LPWSTR, DWORD, LPVOID,
LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION))
GetProcAddress(hAdvapi32, "CreateProcessWithLogonW");
if (fncCreateProcessWithLogonW == NULL)
return -2;
STARTUPINFO objStartupInfo;
memset(&objStartupInfo, 0, sizeof(STARTUPINFO));
objStartupInfo.cb = sizeof(STARTUPINFO);
PROCESS_INFORMATION objProcessInfo;
memset(&objProcessInfo, 0, sizeof(PROCESS_INFORMATION));
BOOL bResult = fncCreateProcessWithLogonW(
A2CW(lpszUserName),
A2CW(lpszDomain),
A2CW(lpszPassword),
dwLogonFlags,
NULL,
A2W(lpszCommandLine),
dwCreationFlags,
NULL,
NULL,
&objStartupInfo,
&objProcessInfo
);
FreeLibrary(hAdvapi32);
Sleep(3000);
return (long) bResult;
}
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.
|
Buchtips

|
"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.
Informationen zum Buch
|

|
"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.
Informationen zum Buch
|
|
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.
|
|