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 @@
frmSearchReplace
+ +
updater.res
+
icon.res
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;