- Offizieller Beitrag
Bis vor Kurzem hatte ich mich mit dem Thema "Update über's Internet", für meine eigenen Programme, noch gar nicht beschäftigt.
Ich hatte sie als ZIP-Archiv hier veröffentlicht und man musste für eine neue Version halt das ZIP-Archiv entpacken bzw. die alte Version damit überschreiben.
Bei meiner "Digitaluhr v4" habe ich nun erstmals eine Update-Funktion eingebaut: Es gibt einen Button, der beim ersten anklicken auf meiner Homepage nachsieht, ob es eine neue Version gibt und falls ja, wird nach nochmaligen draufklicken das Update durchgeführt.
Dabei war es natürlich enorm hilfreich, dass sich alle benötigten Ressourcen (Grafiken, Sounds, Fonts, etc.) mit in der Exe-Datei befinden (BASE64-codiert), denn so muss ich nur diese eine Exe-Datei updaten/austauschen.
Problem beim updaten, ist die Tatsache, dass das Programm ja gerade ausgeführt wird. Man kann also nicht einfach so die Exe-Datei austauschen.
Es gibt aber einige Beispiele im Netz, wie man mit Hilfe eines temporären Batchscripts, diesen Austausch vornehmen kann.
Nachdem ich mir das alles zusammengesucht habe, dachte ich mir, ich fasse die benötigten Schritte mal zusammen, um es euch einfacher zu machen, falls ihr sowas auch vorhabt.
Einleitung zu Ende, hier geht's richtig los:
Wichtigste Vorraussetzung ist der Homepage-Speicherplatz (Webhosting). Ich weiß nicht, ob es etwas Brauchbares kostenlos gibt. Ich denke aber, dass man für eine eigene Domain wohl bezahlen muss. Auf jeden Fall braucht ihr halt Speicherplatz und die Möglichkeit dort Dateien hochzuladen (FTP).
Und es sollte möglich sein, die Dateien von dort per HTTP oder HTTPS wieder herunterzuladen. Für dieses Beispiel-Programm stelle ich die erforderlichen Dateien auf meinem Webspace zur Verfügung. Das Testprogramm funktioniert also!
Was muss auf den Server hochgeladen werden:
1. Die neue Version von eurem Programm ("selfupdate_test.exe")
2. Eine Textdatei (ANSI) mit der neuen Versionsnummer ("selfupdate_test_version.txt")
3. Eine Binärdatei mit der SHA1-Checksumme des neuen Programms ("selfupdate_test_hash.bin").
Zu Erstens ist das einfach das folgende Script als kompilierte Exe:
#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Outfile=selfupdate_test.exe
#AutoIt3Wrapper_Res_Fileversion=1.1.0.0
#AutoIt3Wrapper_Res_Language=1031
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
MsgBox(0, 'Selfupdate Test', 'Update erfolgreich!' & @CRLF & 'Neue Version v1.1')
Zu Zweitens erstellt man mit dem Editor eine Text-Datei und schreibt da nur die neue Versionsnummer rein z.B.: "1.1.0.0" (ohne die Anführungszeichen und ohne Zeilenumbruch).
Zu Drittens könnt ihr dieses kleine Script verwenden, um eine Binärdatei mit der SHA1-Checksumme zu erzeugen:
#include <Crypt.au3>
#include <FileConstants.au3>
Global $sFilename, $dHash, $hFile
FileChangeDir(@ScriptDir)
$sFilename = 'selfupdate_test.exe'
$dHash = _Crypt_HashFile($sFilename, $CALG_SHA1)
$hFile = FileOpen('selfupdate_test_hash.bin', $FO_OVERWRITE + $FO_BINARY)
If $hFile <> -1 Then
FileWrite($hFile, $dHash)
FileClose($hFile)
EndIf
Alles anzeigen
Das Update-Script:
Das eigentliche Update übernehmen dann die zwei Funktionen (ausführlich kommentiert) aus diesem Script. Das restliche Drumherum stellt eine kleine GUI dar, womit ich euer Programm andeuten will:
#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_Outfile=SelfUpdate.exe
#AutoIt3Wrapper_Res_Fileversion=1.0.0.0
#AutoIt3Wrapper_Res_Language=1031
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
#include <Crypt.au3>
#include <FileConstants.au3>
#include <GUIConstantsEx.au3>
#include <InetConstants.au3>
#include <Misc.au3>
#include <MsgBoxConstants.au3>
#include <StaticConstants.au3>
#include <StringConstants.au3>
If Not @Compiled Then Exit MsgBox($MB_ICONERROR, 'Fehler', 'Bitte das Script erst kompilieren!') ; das Update nur im kompilierten Zustand ausfuehren!
#Region *** Links zu den Dateien, die auf dem eigenen Server liegen muessen ***
Global Const $sUpdateURL = 'https://www.technik-hobby.de/software/selfupdate_test.exe' ; die URL zur neuen Version des Programms
Global Const $sHashURL = 'https://www.technik-hobby.de/software/selfupdate_test_hash.bin' ; die URL fuer die SHA1-Checksumme des neuen Programms und
Global Const $sVersionURL = 'https://www.technik-hobby.de/software/selfupdate_test_version.txt' ; die URL fuer die ANSI-Datei mit der neuen Versionsnummer ("1.1.0.0")
#EndRegion *** Links zu den Dateien, die auf dem eigenen Server liegen muessen ***
#Region *** Wichtige globale Variablen ***
Global Const $sAppVersion = FileGetVersion(@ScriptFullPath) ; die Versionsnummer des Scripts auslesen (#AutoIt3Wrapper_Res_Fileversion)
Global Const $aUpdateText[3] = ['Neue Version?', 'Version aktuell!', 'Update'] ; die Beschriftungen fuer den Update-Button
Global $iUpdateStep = 0, $sNewVersion
#EndRegion *** Wichtige globale Variablen ***
#Region *** das eigene Programm ***
Global $hGui = GUICreate('SelfUpdate', 320, 160)
Global $idServerVersion = GUICtrlCreateLabel('', 90, 50, 140, 20, $SS_CENTER)
Global $idUpdate = GUICtrlCreateButton($aUpdateText[$iUpdateStep], 90, 70, 140, 30)
GUISetState()
While True
Switch GUIGetMsg()
Case $GUI_EVENT_CLOSE
Exit
Case $idUpdate
Switch $iUpdateStep ; der Button hat unterschiedliche Bedeutungen:
Case 0 ; beim ersten anklicken wird nach einer neuen Version gesucht
$sNewVersion = _CheckNewVersion($sVersionURL, $sAppVersion)
$iUpdateStep += @extended + 1 ; @extended hat den Wert 1, wenn eine neue Version vorliegt, ansonsten 0
If Not @extended Then GUICtrlSetState($idUpdate, $GUI_DISABLE)
Case 2 ; beim zweiten anklicken wird das Update ausgefuehrt (wenn die Server-Version neuer war)
_SelfUpdate($sUpdateURL, $sHashURL)
EndSwitch
GUICtrlSetData($idServerVersion, 'Server: v' & $sNewVersion)
GUICtrlSetData($idUpdate, $aUpdateText[$iUpdateStep]) ; die Beschriftung des Update-Buttons aendern
EndSwitch
WEnd
#EndRegion *** das eigene Programm ***
#Region *** die beiden Funktionen fuer das Update ***
Func _CheckNewVersion($sURL, $sOldVersion) ; fuehrt einen Test durch, ob die Serverversion neuer als die lokale Version ist
Local $sNewVersion, $iRet
$sNewVersion = BinaryToString(InetRead($sURL, $INET_FORCERELOAD)) ; die Textdatei mit der neuen Version vom Server holen
If $sNewVersion = '' Then Return SetError(1, 0, $sOldVersion) ; wenn dabei ein Fehler aufgetreten ist, dann Funktion verlassen
$iRet = _VersionCompare($sNewVersion, $sOldVersion) ; vergleichen, ob die Server-Version neuer als die Programmversion ist
If $iRet < 1 Then Return SetError(2, 0, $sOldVersion) ; wenn nein, dann die alte Versionnummer zurueckgeben
Return SetError(0, 1, $sNewVersion) ; wenn ja, dann die neue Versionsnummer zurueckgeben
EndFunc ;==>_CheckNewVersion
Func _SelfUpdate($sURL, $sHashURL) ; hiermit wird die neue Serverversion heruntergeladen, die Checksumme ueberprueft und die Exe ausgetauscht
If Not @Compiled Then Return SetError(1, 0, 0) ; wenn das Script nicht kompiliert wurde, Funktion verlassen
Local $sBatchFile, $sUpdateFile, $sSelfFile, $iPID, $hFile, $sBatchScript, $dHashLocal, $dHashServer
Local $iDlSize, $hDownload, $iDlRead, $iPercent, $iTimer = TimerInit()
$sBatchFile = 'SelfUpdate.bat' ; die Batchdatei wird unten temporaer erstellt und dann wieder geloescht
$sUpdateFile = 'SelfUpdate_new.exe' ; das ist der Dateiname fuer InetGet (heruntergeladene Datei)
FileChangeDir(@ScriptDir) ; das WorkingDir aendern
$iDlSize = InetGetSize($sURL, $INET_FORCERELOAD) ; die Dateigroesse holen
ProgressOn('Update wird heruntergeladen', StringFormat('0 von %i Bytes (0%)', $iDlSize)) ; Progressfenster oeffnen
$hDownload = InetGet($sURL, $sUpdateFile, $INET_FORCERELOAD, $INET_DOWNLOADBACKGROUND) ; Datei im Hintergrund herunterladen (Download-Handle)
While Not InetGetInfo($hDownload, $INET_DOWNLOADCOMPLETE) ; Schleife ausfuehren, solange wie der Download andauert
$iDlRead = InetGetInfo($hDownload, $INET_DOWNLOADREAD) ; bisher uebertragene Bytes
$iPercent = Int(100 / $iDlSize * $iDlRead) ; Prozent ausrechnen
ProgressSet($iPercent, StringFormat('%i von %i Bytes (%i%)', $iDlRead, $iDlSize, $iPercent)) ; Progressbar aktualisieren
Sleep(10) ; ein kurzes Sleep, um den Prozessor zu entlasten
If TimerDiff($iTimer) > 60000 Then ; wenn der Download laenger als 60 sek. dauert, Fehlermeldung
InetClose($hDownload) ; Download-Handle schliessen
ProgressOff() ; Progressfenster schliessen
MsgBox($MB_ICONERROR, 'Fehler', 'Zeitfehler!') ; Fehlermeldung ausgeben
Return SetError(2, 0, 0) ; Funktion verlassen
EndIf
WEnd
If InetGetInfo($hDownload, $INET_DOWNLOADERROR) <> 0 Then ; wenn beim Download ein Fehler aufgetreten ist, dann...
InetClose($hDownload) ; Download-Handle schliessen
ProgressOff() ; Progressfenster schliessen
MsgBox($MB_ICONERROR, 'Fehler', 'Downloadfehler!') ; Fehlermeldung ausgeben
Return SetError(3, 0, 0) ; Funktion verlassen
EndIf
InetClose($hDownload) ; Download-Handle schliessen
ProgressSet($iPercent, StringFormat('%i von %i Bytes (100%)', $iDlSize, $iDlSize, $iPercent)) ; die Progressbar auf 100% setzen
Sleep(1000) ; kurz warten, damit der Benutzer das registriert
ProgressOff() ; Progressfenster schliessen
#Region *** optionaler Checksummen-Test ***
; Wenn der eigene Server kein HTTPS (S = secure/sicher) anbietet, koennte man hier noch, mit Hilfe von "_Crypt_HashFile",
; eine Datei-Checksumme erstellen und diese mit einer, vorher auf dem Server gespeicherten, Checksumme vergleichen:
$dHashLocal = _Crypt_HashFile($sUpdateFile, $CALG_SHA1) ; Checksumme (binaer) von der heruntergeladenen Datei erstellen
$dHashServer = InetRead($sHashURL, $INET_FORCERELOAD) ; Checksumme (binaer) vom Server holen
If $dHashLocal <> $dHashServer Then ; wenn die Checksummen nicht uebereinstimmen, dann...
MsgBox($MB_ICONERROR, 'Fehler', 'Checksummenfehler!') ; Fehlermeldung ausgeben
Return SetError(4, 0, 0) ; Funktion verlassen
EndIf
#EndRegion *** optionaler Checksummen-Test ***
; Ab hier war der Download erfolgreich und es kann das Ueberschreiben der Exe-Datei stattfinden.
$sSelfFile = @ScriptName ; das ist der Name von diesem Programm
$iPID = @AutoItPID ; die PID von diesem Programm
; die Batchbefehle mit Stringformat zusammenstellen ("\r\n" = @crlf)
$sBatchScript = StringFormat( _
'@echo off\r\n' & _ ; keine Ausgabe in der Konsole
':loop\r\n' & _ ; Sprungmarke setzen
'tasklist /nh /fi "pid eq %i" | find ":" > nul\r\n' & _ ; nach der PID von diesem Programm (%i = $iPID) suchen
'if errorlevel 1 (\r\n' & _ ; wenn das Programm noch laeuft (":" wird nur gefunden, wenn nicht), dann...
' ping -n 2 localhost\r\n' & _ ; ca. 2 sek. warten, (ping wird als kurze Pause benutzt)
' goto loop\r\n' & _ ; bis es sich beendet hat (goto loop)
')\r\n' & _
'del "%s"\r\n' & _ ; das alte Programm (%s = $sSelfFile) wird geloescht
'ren "%s" "%s"\r\n' & _ ; das neue Programm (%s = $sUpdatefile) wird in das alte Programm (%s = $sSelfFile) umbenannt
'start "" "%s"\r\n' & _ ; das neue (alte) Programm (%s = $sSelfFile) wird gestartet
'start /b "" cmd /c del "%~f0"&exit /b\r\n', _ ; die Batchdatei loescht sich selbst ("%~f0" ist der Dateiname vom Batchscript selbst)
$iPID, $sSelfFile, $sUpdateFile, $sSelfFile, $sSelfFile)
$hFile = FileOpen($sBatchFile, $FO_OVERWRITE) ; Batchdatei zum ueberschreiben oeffnen
If $hFile = -1 Then ; wenn dabei ein Fehler aufgetreten ist, dann...
MsgBox($MB_ICONERROR, 'Fehler', 'Batchdatei konnte nicht gespeichert werden!') ; Fehlermeldung ausgeben
Return SetError(5, 0, 0) ; Funktion verlassen
EndIf
FileWrite($hFile, $sBatchScript) ; Batchscript in die Datei schreiben
FileClose($hFile) ; die Batchdatei schliessen
Run($sBatchFile, @ScriptDir, @SW_HIDE) ; und versteckt ausfuehren
Exit ; dieses Programm beenden
EndFunc ;==>_SelfUpdate
#EndRegion *** die beiden Funktionen fuer das Update ***
Alles anzeigen
Die Scripte findet ihr auch als Anhang (zum runterladen)!