﻿#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 ***
