diff --git a/extra/build_everything.cmd b/extra/build_everything.cmd
index f4cfb597..45bced5e 100644
--- a/extra/build_everything.cmd
+++ b/extra/build_everything.cmd
@@ -153,6 +153,7 @@ cd /d "%base_dir%\packages\%package_dir%\"
brcc32 ..\..\res\version.rc
brcc32 ..\..\res\icon.rc
brcc32 ..\..\res\manifest.rc
+brcc32 ..\..\res\updater.rc
"%compiler%" %params% -e"%base_dir%\out" heidisql.dpr
if not %errorlevel% == 0 goto end
diff --git a/extra/updater/helpers.pas b/extra/updater/helpers.pas
deleted file mode 100644
index 7ad674d8..00000000
--- a/extra/updater/helpers.pas
+++ /dev/null
@@ -1,91 +0,0 @@
-unit helpers;
-
-interface
-
-type
- procedure ExtractUpdater;
- procedure UpdateItWith(const _file: String);
-
-implementation
-
-{$R updater.res}
-
-
-{**
- Extract the updater from resource.
-}
-procedure ExtractUpdater;
-var
- ri: HRSRC;
- rh: THandle;
- bf: PChar;
- ms: TMemoryStream;
-begin
- // look for resource UPDATER
- ri := FindResource( HInstance, 'UPDATER', 'EXE' );
-
- // define the Handle
- rh := LoadResource( HInstance, ri );
-
- if ( rh <> 0 ) then
- begin
- // alloc memory space
- ms := TMemoryStream.Create();
- try
- ms.Clear();
- bf := LockResource( rh );
-
- // load the resource on memory stream
- ms.WriteBuffer( bf[0], SizeOfResource( HInstance, ri ) );
- ms.Seek( 0, 0 );
-
- // save the resource like executable
- ms.SaveToFile( ExtractFilePath( Application.ExeName ) + 'updater.exe' );
- finally
- // free memory
- UnlockResource( rh );
- FreeResource( rh );
- ms.Free();
- end;
- end;
-end;
-
-
-
-{**
- Update the current application with the file.
-
- @param _file the file that will replace the current application
-}
-procedure UpdateItWith(const _file: String);
-var
- app: String;
-begin
- // sanitize: try to remove the oldest updater
- DeleteFile( PAnsiChar( ExtractFilePath( Application.ExeName ) + 'updater.exe' ) );
-
- // retrive the application name without extension
- app := Copy( Application.Exename, 1, ( Length( Application.Exename ) - 4 ) );
-
- // sanitize: try to remove the oldest file
- DeleteFile( PAnsiChar( app + '.old' ) );
-
- // verify the file existence
- if ( FileExists( _file ) ) then
- begin
- // copy to current application folder the newest file
- CopyFile( PChar( _file ), PChar( app + '.new' ), False );
-
- // extract the updater from current application resource
- ExtractUpdater();
-
- // call the updater
- WinExec( PAnsiChar( '"' + ExtractFilePath( Application.ExeName ) + 'updater.exe" "' + app + '"' ), 0 );
-
- // stop running the current application
- Application.Terminate();
- end;
-end;
-
-
-
diff --git a/extra/updater/makeresource.cmd b/extra/updater/makeresource.cmd
deleted file mode 100644
index 1f74bad0..00000000
--- a/extra/updater/makeresource.cmd
+++ /dev/null
@@ -1,3 +0,0 @@
-@echo off
-brcc32 updater.rc updater.res
-ren updater.RES updater.res
\ No newline at end of file
diff --git a/extra/updater/updater.dpr b/extra/updater/updater.dpr
deleted file mode 100644
index 0fad7759..00000000
--- a/extra/updater/updater.dpr
+++ /dev/null
@@ -1,54 +0,0 @@
-program updater;
-
-{$APPTYPE CONSOLE}
-
-uses
- SysUtils, Windows;
-
-const
- _maxloop = 10;
-
-procedure Update;
-var
- _currentfilename: String;
- _oldfilename: String;
- _newfilename: String;
- _minloop: Integer;
-begin
- if ( ParamCount > 0 ) then
- begin
- _currentfilename := ParamStr(1) + '.exe';
- _oldfilename := ParamStr(1) + '.old';
- _newfilename := ParamStr(1) + '.new';
-
- // try to delete the old file
- if ( FileExists( _oldfilename ) ) then
- begin
- DeleteFile( PAnsiChar( _oldfilename ) );
- end;
-
- // try to execute
- _minloop := 0;
- repeat
- // try to rename the current filename
- if ( RenameFile( _currentfilename, _oldfilename ) ) then
- begin
- // rename the new file with the current filename
- RenameFile( _newfilename, _currentfilename );
-
- // call again the application
- WinExec( PAnsiChar( _currentfilename ), 0 );
- Exit;
- end;
-
- // if doesn't rename is because the application still running, so
- // wait for 2 seconds and try again
- Sleep( 2000 );
- _minloop := _minloop + 1;
- until ( _minloop <= _maxloop );
- end;
-end;
-
-begin
- Update();
-end.
diff --git a/extra/updater/updater.exe b/extra/updater/updater.exe
deleted file mode 100644
index f8e66639..00000000
Binary files a/extra/updater/updater.exe and /dev/null differ
diff --git a/packages/delphi2010/heidisql.dpr b/packages/delphi2010/heidisql.dpr
index ea665933..63a17665 100644
--- a/packages/delphi2010/heidisql.dpr
+++ b/packages/delphi2010/heidisql.dpr
@@ -41,6 +41,7 @@ uses
{$R ..\..\res\icon.RES}
{$R ..\..\res\version.RES}
{$R ..\..\res\manifest.RES}
+{$R ..\..\res\updater.RES}
var
DoStop, prefAllowMultipleInstances: Boolean;
diff --git a/packages/delphi2010/heidisql.dproj b/packages/delphi2010/heidisql.dproj
index a46a4b3e..a768f87b 100644
--- a/packages/delphi2010/heidisql.dproj
+++ b/packages/delphi2010/heidisql.dproj
@@ -23,6 +23,7 @@
true
+ ..\..\res
heidisql.exe
@@ -45,7 +46,6 @@
HeidiSQL
- ..\..\res
3
..\..\out\heidisql.exe
7.0
@@ -198,6 +198,9 @@
+
+
+
diff --git a/res/updater.exe b/res/updater.exe
new file mode 100644
index 00000000..4370abc2
Binary files /dev/null and b/res/updater.exe differ
diff --git a/extra/updater/updater.rc b/res/updater.rc
similarity index 100%
rename from extra/updater/updater.rc
rename to res/updater.rc
diff --git a/res/updater/updater.dpr b/res/updater/updater.dpr
new file mode 100644
index 00000000..2f8edd30
--- /dev/null
+++ b/res/updater/updater.dpr
@@ -0,0 +1,273 @@
+program updater;
+
+{ A window which terminates running HeidiSQL instances and moves the downloaded update file to
+ its desired directory. Avoids to use any VCL unit, to keep the executable small. }
+
+uses
+ Windows, Messages;
+
+var
+ WClass: TWndClass;
+ BackupPath, AppPath, DownloadPath: String;
+ hAppHandle, HLabel: HWND;
+ AppMsg: TMsg;
+
+const
+ AppName = 'HeidiSQL';
+ WindowPadding = 10;
+ WindowWidth = 600;
+ WindowHeight = 80;
+ QuitTimeout = 20000; // We long we're gracefully waiting for a window to be gone, in milliseconds
+ TerminatedCheck = 200; // Interval between WM_CLOSE posts to a window, in milliseconds
+ PathDelim = '\';
+
+
+
+{ We don't include SysUtils unit, so we need to implement our own versions of some basic functions here }
+
+function FileExists(Filename: String): Boolean;
+var
+ Find: THandle;
+ Data: TWin32FindData;
+begin
+ Find := FindFirstFile(PChar(Filename), Data);
+ Result := Find <> INVALID_HANDLE_VALUE;
+end;
+
+function IntToStr(Value: Int64): String;
+var
+ Minus : Boolean;
+begin
+ Result := '';
+ if Value = 0 then
+ Result := '0';
+ Minus := Value < 0;
+ if Minus then
+ Value := -Value;
+ while Value > 0 do begin
+ Result := Char((Value mod 10) + Integer('0')) + Result;
+ Value := Value div 10;
+ end;
+ if Minus then
+ Result := '-' + Result;
+end;
+
+function ExtractFileName(const FileName: string): string;
+var
+ i: Integer;
+begin
+ for i:=Length(Filename) downto 0 do
+ if Filename[i] = PathDelim then
+ break;
+ Result := Copy(FileName, i+1, MaxInt);
+end;
+
+function ExtractFilePath(const FileName: string): string;
+var
+ i: Integer;
+begin
+ for i:=Length(Filename) downto 0 do
+ if Filename[i] = PathDelim then
+ break;
+ Result := Copy(FileName, 1, i);
+end;
+
+
+
+procedure Status(Text: String; IsError: Boolean=False);
+begin
+ // Display status message on label
+ SendMessage(HLabel, WM_SETTEXT, 1, Integer(PChar(Text)) );
+ UpdateWindow(hLabel);
+ if IsError then begin
+ Sleep(4000);
+ Halt(1);
+ end;
+end;
+
+
+function EnumAllInstances(Wnd: HWND; LParam: LPARAM): Bool; stdcall;
+var
+ WndTitle, Hint: String;
+ i, p, MajorVer, Code, WaitTime: Integer;
+begin
+ // Callback function which passes one window handle
+ // EnumWindows will stop processing if we return false
+ Result := True;
+ if IsWindowVisible(Wnd) and
+ ((GetWindowLong(Wnd, GWL_HWNDPARENT) = 0) or
+ (HWND(GetWindowLong(Wnd, GWL_HWNDPARENT)) = GetDesktopWindow)) then begin
+ SetLength(WndTitle, 256);
+ for i:=1 to 256 do
+ WndTitle[i] := ' ';
+ GetWindowText(Wnd, PChar(WndTitle), 256);
+ // Check if this is a HeidiSQL window, must contain "HeidiSQL 1.2.3.4"
+ p := Pos(AppName, WndTitle);
+ Val(WndTitle[p+Length(AppName)+1], MajorVer, Code);
+ if Code <> 0 then MajorVer := 0;
+ if (p > 0) and (MajorVer <> 0) then begin
+ Hint := 'Closing "'+WndTitle+'"';
+ Status(Hint);
+ PostMessage(Wnd, WM_CLOSE, 0, 0);
+ WaitTime := 0;
+ while WaitTime < QuitTimeout do begin
+ Sleep(TerminatedCheck);
+ Inc(WaitTime, TerminatedCheck);
+ Status(Hint + ', wait time left: '+IntToStr((QuitTimeout - WaitTime) div 1000)+' seconds.');
+ if not IsWindow(Wnd) then
+ break;
+ end;
+ if IsWindow(Wnd) then begin
+ Status('Error: Could not terminate session '+WndTitle, true);
+ Result := False;
+ end;
+ end;
+ end;
+end;
+
+
+// Callback function for Timer
+procedure FormShow(wnd: HWND; uMsg: UINT; idEvent: UINT; dwTime: DWORD); stdcall;
+var
+ SUInfo: TStartupInfo;
+ ProcInfo: TProcessInformation;
+begin
+ KillTimer(hAppHandle, 0);
+ AppPath := Paramstr(1);
+ DownloadPath := ParamStr(2);
+
+ // Paremeter syntax check
+ if (AppPath = '') or (DownloadPath = '') then begin
+ Status('Syntax: '+ExtractFilename(Paramstr(0))+' OldFile.exe NewFile.exe'+#13#10+
+ 'Please don''t execute this file directly.', True);
+ end;
+ if (not FileExists(AppPath)) or (not FileExists(DownloadPath)) then
+ Status('Error: Either target file "'+AppPath+'" or download file "'+DownloadPath+'" does not exist.', True);
+
+ // Terminate running instances
+ Status('Close running '+AppName+' instances ...');
+ EnumWindows(@EnumAllInstances, 0);
+
+ // Backup old .exe to working directory
+ Status('Creating backup of old file ...');
+ BackupPath := ExtractFilepath(Paramstr(0))+ExtractFilename(AppPath)+'.backup.exe';
+ if FileExists(BackupPath) then
+ DeleteFile(PChar(BackupPath));
+ if not MoveFile(PChar(AppPath), PChar(BackupPath)) then
+ Status('Failed to create backup file "'+BackupPath+'" from "'+AppPath+'"', True)
+ else
+ Status('Success.');
+
+ // Move update file to final path
+ Status('Moving downloaded file to desired directory ...');
+ if not MoveFile(PChar(DownloadPath), PChar(AppPath)) then
+ Status('Failed to move file "'+DownloadPath+'" to "'+AppPath+'"', True)
+ else begin
+ Status('Success. Restarting '+AppName+' now ...');
+ FillChar(SUInfo, SizeOf(SUInfo), #0);
+ SUInfo.cb := SizeOf(SUInfo);
+ SUInfo.dwFlags := STARTF_USESHOWWINDOW;
+ SUInfo.wShowWindow := SW_SHOWNORMAL;
+ CreateProcess(
+ nil,
+ PChar(AppPath),
+ nil,
+ nil,
+ False,
+ CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS,
+ nil,
+ PChar(ExtractFilePath(AppPath)),
+ SUInfo,
+ ProcInfo
+ );
+ end;
+ PostQuitMessage(0);
+end;
+
+
+function WindowProc(hWnd, msg, wpr, lpr: Longint): Longint; stdcall;
+var
+ x, y: integer;
+ Font: HFont;
+begin
+ // Custom window procedure
+ case msg of
+
+ WM_CREATE: begin
+ // Center window
+ x := GetSystemMetrics(SM_CXSCREEN);
+ y := GetSystemMetrics(SM_CYSCREEN);
+ MoveWindow(hWnd,
+ (x div 2) - (WindowWidth div 2),
+ (y div 2) - (WindowHeight div 2),
+ WindowWidth,
+ WindowHeight,
+ true);
+ // Create status label
+ HLabel := CreateWindow(
+ 'STATIC', // Class name
+ 'Status:', // Label's text
+ WS_VISIBLE or WS_CHILD or SS_LEFT, // Styles
+ WindowPadding, // X pos
+ WindowPadding, // Y pos
+ WindowWidth - 2*WindowPadding, // Width
+ WindowHeight - 2*WindowPadding, // Height
+ hWnd, // Parent hwnd
+ 0, // ID
+ hAppHandle, // HInstance of program
+ nil // Params for main window
+ );
+ // Cosmetics
+ Font := Createfont(-11, 0, 0, 0, 0, 0, 0, 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, 'Tahoma');
+ SendMessage(HLabel, WM_SETFONT, Font, 1);
+ SetBkColor(hwnd, COLOR_BTNFACE+1);
+ end;
+
+ WM_SHOWWINDOW: SetTimer(hAppHandle, 0, 200, @FormShow);
+
+ WM_DESTROY: PostQuitMessage(0);
+
+ end;
+
+ Result := DefWindowProc(hWnd, msg, wpr, lpr);
+end;
+
+
+
+// Main program goes here
+begin
+ // Define window class
+ WClass.hInstance := hInstance;
+ WClass.style := CS_HREDRAW or CS_VREDRAW;
+ WClass.hIcon := LoadIcon(hInstance, IDI_WINLOGO);
+ WClass.lpfnWndProc := @WindowProc;
+ WClass.hbrBackground := COLOR_BTNFACE+1;
+ WClass.lpszClassName := 'WndClass';
+ WClass.hCursor := LoadCursor(0, IDC_ARROW);
+ WClass.cbClsExtra := 0;
+ WClass.cbWndExtra := 0;
+ WClass.lpszMenuName := '';
+ RegisterClass(WClass);
+
+ // Create form
+ hAppHandle := CreateWindow(
+ WClass.lpszClassName,
+ AppName+' Updater',
+ WS_POPUPWINDOW or WS_CAPTION or WS_VISIBLE,
+ 100, // Default x + y coordinates, will be centered in WM_CREATE
+ 100,
+ WindowWidth,
+ WindowHeight,
+ 0,
+ 0,
+ hInstance,
+ nil
+ );
+
+ // Message loop
+ while GetMessage(AppMsg, 0, 0, 0) do begin
+ TranslateMessage(AppMsg);
+ DispatchMessage(AppMsg);
+ end;
+ ExitCode := AppMsg.wParam;
+end.
diff --git a/res/updater/updater.dproj b/res/updater/updater.dproj
new file mode 100644
index 00000000..f8037f60
--- /dev/null
+++ b/res/updater/updater.dproj
@@ -0,0 +1,110 @@
+
+
+ {24E2AAD6-CDE7-46EC-95BD-79028373B17A}
+ 12.0
+ updater.dpr
+ Debug
+ DCC32
+
+
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ ..\
+ vcl;rtl;vclx;vclimg;vclactnband;dbrtl;vcldb;vcldbx;bdertl;vcltouch;xmlrtl;dsnap;dsnapcon;TeeUI;TeeDB;Tee;vclib;ibxpress;adortl;IndyCore;IndySystem;IndyProtocols;inet;intrawebdb_100_140;Intraweb_100_140;VclSmp;vclie;websnap;webdsnap;inetdb;inetdbbde;inetdbxpress;soaprtl;vclribbon;dbexpress;DbxCommonDriver;DataSnapIndy10ServerTransport;DataSnapProviderClient;DbxClientDriver;DataSnapServer;DBXInterBaseDriver;DBXMySQLDriver;dbxcds;DBXFirebirdDriver;DBXSybaseASEDriver;DBXSybaseASADriver;DBXOracleDriver;DBXMSSQLDriver;DBXInformixDriver;DBXDb2Driver;madBasic_;madDisAsm_;madExcept_;SynEditR;VirtualTreesR
+ ..\updater.exe
+ 00400000
+ WinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE;$(DCC_UnitAlias)
+ x86
+ false
+ false
+ false
+ false
+ false
+
+
+ false
+ RELEASE;$(DCC_Define)
+ 0
+ false
+
+
+ DEBUG;$(DCC_Define)
+
+
+
+ MainSource
+
+
+ Base
+
+
+ Cfg_2
+ Base
+
+
+ Cfg_1
+ Base
+
+
+
+
+ Delphi.Personality.12
+
+
+
+
+ updater.dpr
+
+
+ False
+ True
+ False
+
+
+ False
+ False
+ 1
+ 0
+ 0
+ 0
+ False
+ False
+ False
+ False
+ False
+ 1031
+ 1252
+
+
+
+
+ 1.0.0.0
+
+
+
+
+
+ 1.0.0.0
+
+
+
+ File D:\heidisql\trunk\components\pngcomponents\build\PngComponentsD.bpl not found
+ Microsoft Office 2000 Sample Automation Server Wrapper Components
+ Microsoft Office XP Sample Automation Server Wrapper Components
+
+
+
+ 12
+
+
diff --git a/source/updatecheck.dfm b/source/updatecheck.dfm
index 4490e3ce..48d879a1 100644
--- a/source/updatecheck.dfm
+++ b/source/updatecheck.dfm
@@ -61,6 +61,7 @@ object frmUpdateCheck: TfrmUpdateCheck
Height = 25
Anchors = [akLeft, akRight, akBottom]
Caption = 'Download nightly build'
+ ElevationRequired = True
ModalResult = 1
TabOrder = 0
OnClick = btnBuildClick
diff --git a/source/updatecheck.pas b/source/updatecheck.pas
index 339f777a..9ed9c1a3 100644
--- a/source/updatecheck.pas
+++ b/source/updatecheck.pas
@@ -217,8 +217,11 @@ end;
procedure TfrmUpdateCheck.btnBuildClick(Sender: TObject);
var
Download: TDownLoadURL;
- ScriptFile: Textfile;
- ExeName, ScriptFilename, ScriptContent: String;
+ ExeName, UpdaterFilename: String;
+ ResInfoblockHandle: HRSRC;
+ ResHandle: THandle;
+ ResPointer: PChar;
+ Stream: TMemoryStream;
begin
Download := TDownLoadURL.Create(Self);
Download.URL := BuildURL;
@@ -239,60 +242,25 @@ begin
if not FileExists(Download.Filename) then
Raise Exception.Create('Downloaded file not found: '+Download.Filename);
- // The Visual Basic Script code which kills this exe and moves the
- // downloaded file to the application directory.
- // This file moving can fail due to several reasons. Especially in Vista
- // where users are normally not admins, they'll get a "Permission denied".
- // However, the script does several write attempts and quits with a clear
- // error message if it couldn't move the file.
- // TODO: move this code to a seperate file for easier debugging
Status('Update in progress ...');
- ScriptContent := ''' This is a temporary script which shall update your ' + APPNAME + CRLF +
- ''' with a nightly build.' + CRLF +
- CRLF +
- 'ExeName = "'+ExeName+'"' + CRLF +
- 'DownloadFileName = "'+Download.Filename+'"' + CRLF +
- 'TargetFileName = "'+Application.ExeName+'"' + CRLF +
- CRLF +
- 'WScript.Echo "Terminating """&ExeName&""" ..."' + CRLF +
- 'Set Shell = WScript.CreateObject("WScript.Shell")' + CRLF +
- 'Shell.Run("taskkill /im """&ExeName&""" /f")' + CRLF +
- CRLF +
- 'Set FileSystem = CreateObject("Scripting.FileSystemObject")' + CRLF +
- 'Set DownloadFile = FileSystem.GetFile(DownloadFileName)' + CRLF +
- 'Set TargetFile = FileSystem.GetFile(TargetFileName)' + CRLF +
- 'On Error Resume Next' + CRLF +
- 'MaxAttempts = 10' + CRLF +
- 'for x = 1 to MaxAttempts' + CRLF +
- ' WScript.Echo "Deleting "&ExeName&" (attempt "&x&" of "&MaxAttempts&") ..."' + CRLF +
- ' TargetFile.Delete' + CRLF +
- ' If Err.Number = 0 Then' + CRLF +
- ' Err.Clear' + CRLF +
- ' Exit For' + CRLF +
- ' End If' + CRLF +
- ' Err.Clear' + CRLF +
- ' WScript.Sleep(2000)' + CRLF +
- 'Next' + CRLF +
- 'If Err.Number <> 0 Then' + CRLF +
- ' WScript.Echo "Error: Cannot delete file "&TargetFileName' + CRLF +
- ' WScript.Sleep(10000)' + CRLF +
- ' Wscript.Quit' + CRLF +
- 'End If' + CRLF +
- 'Err.Clear' + CRLF +
- CRLF +
- 'WScript.Echo "Installing new build ..."' + CRLF +
- 'DownloadFile.Move TargetFileName' + CRLF +
- CRLF +
- 'WScript.Echo "Restarting ..."' + CRLF +
- 'Shell.Run(""""&TargetFileName&"""")';
- // Write script file to disk
- ScriptFilename := GetTempDir + APPNAME + '_Update.vbs';
- AssignFile(ScriptFile, ScriptFilename);
- Rewrite(ScriptFile);
- Write(Scriptfile, ScriptContent);
- CloseFile(ScriptFile);
- // Calling the script will now terminate the running exe...
- ShellExec('cscript.exe', '', '"'+ScriptFilename+'"');
+ ResInfoblockHandle := FindResource(HInstance, 'UPDATER', 'EXE');
+ ResHandle := LoadResource(HInstance, ResInfoblockHandle);
+ if ResHandle <> 0 then begin
+ Stream := TMemoryStream.Create;
+ try
+ ResPointer := LockResource(ResHandle);
+ Stream.WriteBuffer(ResPointer[0], SizeOfResource(HInstance, ResInfoblockHandle));
+ Stream.Position := 0;
+ UpdaterFilename := GetTempDir + AppName+'_updater.exe';
+ Stream.SaveToFile(UpdaterFilename);
+ // Calling the script will now post a WM_CLOSE this running exe...
+ ShellExec(UpdaterFilename, '', '"'+ParamStr(0)+'" "'+Download.Filename+'"');
+ finally
+ UnlockResource(ResHandle);
+ FreeResource(ResHandle);
+ Stream.Free;
+ end;
+ end;
end;