Is it possible in the body of an email to make a link that will launch my Delphi app installed in the local computer? - windows

I m looking for a way to improve the navigation between different tools (under windows). I would like to add in the body of an email a special "link" that when the user will click on it will launch my delphi app installed in his local computer. Is it possible and if yes how to do?

Yes, it is possible. Your application must create a moniker (for example, from its setup or first launch). This will allow the mail client to open an URL pointing to your application, and Windows will launch it and pass the URL to it.
Here is some source code to register/unregister the URL protocol:
function RegisterURLProtocol(
const ProtocolID : String;
const ProtocolName : String;
const DefaultIcon : String;
const OpenCommand : String) : Boolean;
var
Reg : TRegistry;
begin
Result := FALSE;
Reg := TRegistry.Create(KEY_WRITE);
try
Reg.RootKey := HKEY_CLASSES_ROOT;
if not Reg.OpenKey(ProtocolID, TRUE) then
Exit;
Reg.WriteString('', 'URL:' + ProtocolName);
Reg.WriteString('URL Protocol', '');
if Reg.OpenKey('DefaultIcon', True) then begin
Reg.WriteString('', DefaultIcon);
end;
Reg.CloseKey;
if not Reg.OpenKey(ProtocolID + '\shell\open\command', True) then
Exit;
Reg.WriteString('', OpenCommand);
Result := TRUE;
finally
FreeAndNil(Reg);
end;
end;
function UnregisterURLProtocol(
const ProtocolID : String) : Boolean;
var
Reg : TRegistry;
begin
Result := FALSE;
Reg := TRegistry.Create(KEY_WRITE);
try
Reg.RootKey := HKEY_CLASSES_ROOT;
if not Reg.KeyExists(ProtocolID) then
Exit;
Reg.DeleteKey(ProtocolID + '\DefaultIcon');
Reg.DeleteKey(ProtocolID + '\shell\open\command');
Reg.DeleteKey(ProtocolID + '\shell\open');
Reg.DeleteKey(ProtocolID + '\shell');
Reg.DeleteKey(ProtocolID);
Result := TRUE;
finally
FreeAndNil(Reg);
end;
end;
ProtocolID is the identifier for your protocol. You can use something like 'MyApp'. If you had to register the HTTP protocol, you would use 'http'. The application will be opened by Windows when the user clicks on the link. The application will receive what is specified by the OpenCommand (A string you specify). You can include %1 in that OpenCommand and it will be replaced by the URL except the protocol.
ProtocolName is a string describing your protocol.
The URL to place in the mail would be like this:
MyApp://SomeParameters/SomeMoreParameters

Related

Downloading SFTP files through WinSCP on a Windows Service freezes

I'm trying to build a service that downloads some log files using SFTP and imports them to the database.
Because Delphi doesn't come with SFTP components, I have created a BAT file to download the logs using WinSCP
DownloadLogs.bat:
WinSCP.com < DownloadLogs.commands
DownloadLogs.commands:
open sftp://root:password#myserver.com
option confirm off
get -delete /var/lib/3cxpbx/Instance1/Data/Logs/CDRLogs files
exit
This is my service:
procedure TsrvCentralita.ServiceExecute(Sender: TService);
const SecondsBetweenExecutions = 10;
var Counter: integer;
dmLogs: TdmLogs;
begin
Counter := 0;
while not Terminated do begin
Inc(Counter);
if Counter > SecondsBetweenExecutions then begin
Counter := 0;
dmLogs := TdmLogs.Create(Self);
try
if dmLogs.DownloadLogs then dmLogs.ImportLogs;
finally
dmLogs.Free;
end;
end;
Sleep(1000);
ServiceThread.ProcessRequests(False);
end;
end;
And this is how I call the BAT file:
function ExecAppWait(AppName: string; Params: string = ''; Directory: string = ''; Hidden: boolean = False): Boolean;
var ShellExInfo: TShellExecuteInfo;
begin
FillChar(ShellExInfo, SizeOf(ShellExInfo), 0);
with ShellExInfo do begin
cbSize := SizeOf(ShellExInfo);
fMask := see_Mask_NoCloseProcess;
Wnd := Application.Handle;
lpFile := PChar(AppName);
lpDirectory := PChar(Directory);
lpParameters := PChar(Params);
if Hidden then nShow := sw_Hide
else nShow := sw_ShowNormal;
end;
Result := ShellExecuteEx(#ShellExInfo);
if Result then
while WaitForSingleObject(ShellExInfo.HProcess, 100) = WAIT_TIMEOUT do begin
Application.ProcessMessages; // give processor time to other tasks
if Application.Terminated then
Break;
end;
end;
function TdmLogs.DownloadLogs(Hidden: boolean = True): boolean;
var Path: string;
begin
Path := TPath.Combine(TPath.GetDirectoryName(Application.ExeName), 'SFTP');;
ExecAppWait(TPath.Combine(Path, 'LogsCentralita.bat'), '', Hidden);
Result := Length(TDirectory.GetFiles(TPath.Combine(Path, 'Files'), '*.log')) > 0
end;
When I debug the DownloadLogs function on my application, it works fine, but when running as a service it freezes. Do you know what is wrong ?, shouldn't I be able to call CMD.exe from a service ?.
Thank you.
update
Following Martin Prikryl's answer I now execute WinSCP this way:
function TdmCentralita.DownloadLogs(SaveOutput: boolean = False): boolean;
var IniFile: TIniFile;
Path, Params, User, Password, Server, Hostkey, RemotePath: string;
begin
IniFile := TIniFile.Create(TPath.ChangeExtension(GetModuleName(HInstance), '.ini'));
Server := IniFile.ReadString('Centralita', 'Servidor', '');
Hostkey := IniFile.ReadString('Centralita', 'Hostkey', '');
User := IniFile.ReadString('Centralita', 'Usuario', 'root');
Password := DecryptStr(IniFile.ReadString('Centralita', 'Password', ''), 223);
RemotePath := IniFile.ReadString('Centralita', 'PathRemoto', '');
IniFile.Free;
while (RightStr(RemotePath, 1) = '\') or (RightStr(RemotePath, 1) = '/') do RemotePath := Copy(RemotePath, 1, Length(RemotePath) - 1);
RemotePath := RemotePath + '/*.log';
Path := TPath.Combine(TPath.GetDirectoryName(GetModuleName(HInstance)), 'SFTP');
if not TDirectory.Exists(TPath.Combine(Path, 'files')) then TDirectory.CreateDirectory(TPath.Combine(Path, 'files'));
Params := '/ini=null /command "open sftp://' + User + ':' + Password + '#' + Server + ' -hostkey=""' + Hostkey + '""" "option confirm off" "get -delete ' + RemotePath + ' files\*" "exit"';
if SaveOutput then Params := Params + ' /log="' + Path + '\Log.txt" /loglevel=0';
ExecAppWait('WinSCP.com', Params, Path, True);
Result := Length(TDirectory.GetFiles(TPath.Combine(Path, 'Files'), '*.log')) > 0
end;
Your script does not contain SSH host key. And due to the strange way you provide the commands (an input redirection instead of /script or /command switches), WinSCP starts in an interactive mode. So it prompts for hostkey verification, and hangs.
Add -hostkey switch to your open command. See:
Verifying the host key in script
My script works fine when executed manually, but fails or hangs when run by Windows Scheduler, SSIS or other automation service. What am I doing wrong?
And use /script or /command switches to make WinSCP abort on any problem, instead of hanging.
You should also read the batch file output for better error handling in the future.

Inno Setup: Disable components page on upgrade

Is there a way to disable the Components Page for Upgrades? I would like to enable upgrades of my software but I don't want to allow the users to change the selection of components in case of an upgrade.
Instead the installer you upgrade all existing components from the first installation.
I am worried that it the user selects less components during the upgrade those missing components will stay installed as the old version and you get a mess.
I added the following to my script:
[Setup]
DisableDirPage=auto
DisableProgramGroupPage=auto
DirExistsWarning=auto
I just need a way to disable the components page and use the selection of the previous install (full install) for the upgrade. Is that possible?
I have found a related directive:
[Setup]
UsePreviousTasks=true
UsePreviousTasks is reading the existing section out of the registry which is good. Now I need to find a way to hide the selection window.
Thanks,
Wolfgang
To hide a page from user use the ShouldSkipPage event method. If you return True in this method, the page won't be shown to user. If False, the page will be displayed as usually. Here 's an example of how to check if the installation is an upgrade and if so, skip the Select Components wizard page:
[Setup]
AppId=B75E4823-1BC9-4AC6-A645-94027A16F5A5
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
; here is the place for your [Components] section and the rest of your script
[Code]
const
UninstallKey = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#SetupSetting("AppId")}_is1';
function IsUpgrade: Boolean;
var
Value: string;
begin
Result := (RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or
RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> '');
end;
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := (PageID = wpSelectComponents) and IsUpgrade;
end;
Another option you mentioned might be to disable all the controls of the page. The next script shows as the previous one how to check if the installation is an upgrade and if so, disables all the controls on the Select Components wizard page:
[Setup]
AppId=B75E4823-1BC9-4AC6-A645-94027A16F5A5
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
; here is the place for your [Components] section and the rest of your script
[Code]
const
UninstallKey = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#SetupSetting("AppId")}_is1';
function IsUpgrade: Boolean;
var
Value: string;
begin
Result := (RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or
RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> '');
end;
procedure DisablePageControls(Page: TNewNotebookPage);
var
I: Integer;
begin
Page.Enabled := False;
for I := 0 to Page.ControlCount - 1 do
Page.Controls[I].Enabled := False;
end;
procedure InitializeWizard;
begin
if IsUpgrade then
DisablePageControls(WizardForm.SelectComponentsPage);
end;
The IsUpgrade function mentioned in TLama's answer has a bug. If AppId starts with a "{" which must be doubled, this isn't resolved and they registry key will not be found. Here's a corrected function that works for me:
function IsUpgrade: Boolean;
var
Value: string;
UninstallKey: string;
begin
UninstallKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' +
ExpandConstant('{#SetupSetting("AppId")}') + '_is1';
Result := (RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or
RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> '');
end;
Leave the separate const away for this function, it won't work with that extra function call.
Apart from that, 64-bit systems don't seem to cause any issues. If InnoSetup runs in 32-bit mode, the registry virtualisation is in effect and redirects you to the correct key already.
Something like that:
if CurPageID=wpSelectComponents then
begin
if ExtraOptionAvailable() then
begin
Wizardform.ComponentsList.Checked[6] := true;
Wizardform.ComponentsList.ItemEnabled[6] := true;
end else begin
Wizardform.ComponentsList.Checked[6] := false;
Wizardform.ComponentsList.ItemEnabled[6] := false;
end;
end;

How to read a text file from the Internet resource?

I would like to read a text file containing a version number from the Internet resource. Then I need to use this version number within my script.
How to do this in InnoSetup ?
There are many ways how to get a file from the Internet in InnoSetup. You can use an external library like for instance InnoTools Downloader, write your own library, or use one of the Windows COM objects. In the following example I've used the WinHttpRequest COM object for file receiving.
The DownloadFile function in this script returns True, when the WinHTTP functions doesn't raise any exception, False otherwise. The response content of the HTTP GET request to an URL, specified by the AURL parameter is then passed to a declared AResponse parameter. When the script fails the run on exception, AResponse parameter will contain the exception error message:
[Code]
function DownloadFile(const AURL: string; var AResponse: string): Boolean;
var
WinHttpRequest: Variant;
begin
Result := True;
try
WinHttpRequest := CreateOleObject('WinHttp.WinHttpRequest.5.1');
WinHttpRequest.Open('GET', AURL, False);
WinHttpRequest.Send;
AResponse := WinHttpRequest.ResponseText;
except
Result := False;
AResponse := GetExceptionMessage;
end;
end;
procedure InitializeWizard;
var
S: string;
begin
if DownloadFile('http://www.example.com/versioninfo.txt', S) then
MsgBox(S, mbInformation, MB_OK)
else
MsgBox(S, mbError, MB_OK)
end;

How I can list the contents of a folder of a remote machine

I'm looking for a windows api function or another way to get the content (folder and files) of a folder located in a machine on my LAN. off course which I have a valid windows user and password for every machine which I want to access.
You can use the WMI , check the CIM_DataFile and CIM_Directory classes.
Some Notes
1.First you must enable the wmi remote access in the client machines. Read these articles to see how do this and the differences between windows versions Connecting to WMI on a Remote Computer, Securing a Remote WMI Connection.
2.Always you must use filters (Where conditions) to restrict the result of these WMI classes.
3.Always you must use the Drive field as condition, because these classes return the files of all drives.
4.The Wmi interprets the \ (Backslash) character as a reserverd symbol so you must need to escape that character to avoid problems with the WQL sentence.
Delphi Code
{$APPTYPE CONSOLE}
uses
SysUtils,
ActiveX,
ComObj,
Variants;
procedure GetRemoteFolderContent(Const WbemComputer,WbemUser,WbemPassword,Path:string);
const
wbemFlagForwardOnly = $00000020;
var
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObjectSet: OLEVariant;
FWbemObject : OLEVariant;
oEnum : IEnumvariant;
iValue : LongWord;
WmiPath : string;
Drive : string;
begin;
//The path
//Get the drive
Drive :=ExtractFileDrive(Path);
//get the path and add a backslash to the end
WmiPath :=IncludeTrailingPathDelimiter(Copy(Path,3,Length(Path)));
//escape the backslash character
WmiPath :=StringReplace(WmiPath,'\','\\',[rfReplaceAll]);
Writeln('Connecting');
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
//Establish the connection
FWMIService := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
Writeln('Files');
Writeln('-----');
//Get the files from the specified folder
FWbemObjectSet:= FWMIService.ExecQuery(Format('SELECT * FROM CIM_DataFile Where Drive="%s" AND Path="%s"',[Drive,WmiPath]),'WQL',wbemFlagForwardOnly);
oEnum := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
while oEnum.Next(1, FWbemObject, iValue) = 0 do
begin
Writeln(Format('%s',[FWbemObject.Name]));
FWbemObject:=Unassigned;
end;
Writeln('Folders');
Writeln('-------');
//Get the folders from the specified folder
FWbemObjectSet:= FWMIService.ExecQuery(Format('SELECT * FROM CIM_Directory Where Drive="%s" AND Path="%s"',[Drive,WmiPath]),'WQL',wbemFlagForwardOnly);
oEnum := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
while oEnum.Next(1, FWbemObject, iValue) = 0 do
begin
Writeln(Format('%s',[FWbemObject.Name]));
FWbemObject:=Unassigned;
end;
end;
begin
try
CoInitialize(nil);
try
GetRemoteFolderContent('remote_machine','user','password','C:\');
GetRemoteFolderContent('remote_machine','user','password','C:\Program Files');
finally
CoUninitialize;
end;
except
on E:EOleException do
Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Writeln('Press Enter to exit');
Readln;
end.
Without the authorization part, it's simple enough. The right way to do the authorization is to call Windows.pas method WNetAddConnection2 and go that way.
However, because I'm in a simple hack mode, I tried this, and it basically works:
uses Types, IOUtils, ShellApi; // Works in Delphi XE.
procedure TForm5.Button1Click(Sender: TObject);
var
dirs:TStringDynArray;
files:TStringDynArray;
apath, dir,filename:String;
begin
ListBox1.Items.Clear;
apath := '\\hostname\sharename';
// This should be calling WNetAddConnection2:
// instead It's an evil (portable) hack.
ShellExecute(HWND(0), 'open', PChar('net use /delete '+ apath),
nil,nil,SW_SHOW );
ShellExecute(HWND(0), 'open', PChar('net use '+ apath+' /user:uid pswd'),
nil,nil,SW_SHOW );
dirs := TDirectory.GetDirectories(apath);
if Length(dirs)=0 then
ListBox1.Items.Add('None found.')
else
for dir in dirs do
ListBox1.Items.Add('Directory: '+dir);
files := TDirectory.GetFiles(apath);
for filename in files do
ListBox1.Items.Add('File: '+filename );
end;
Abject Apologies for the ugly hack of ShellExecute "net use". (Grin) Note that I have elected to "mount" this shared folder without giving it a drive letter, avoiding the problem of what to do if that drive is already mapped.
Here's a good link with a WNetAddConnection2 code sample that I will link to instead of poaching. It shows a sample of the non-evil way to do it. :-) Then you can use the Directory enumeration code as I have shown above.
I suppose this is included in Warren's answer, but to cut to the chase, IOUtils.TDirectory supports UNCs:
implementation
uses IOUtils,types;
procedure GetFiles;
var
i: integer;
files: TStringDynArray;
begin
files := TDirectory.GetFiles('\\aServer\aPath\aShare\', '*.aFileFilter');
for i := Low(files)to High(files) do
memo1.Lines.Add(files[i]);
end;
etc, etc...

Inno Setup: How to automatically uninstall previous installed version?

I'm using Inno Setup to create an installer.
I want the installer to automatically uninstall the previous installed version, instead of overwriting it. How can I do that?
I have used the following. I'm not sure it's the simplest way to do it but it works.
This uses {#emit SetupSetting("AppId")} which relies on the Inno Setup Preprocessor. If you don't use that, cut-and-paste your App ID in directly.
[Code]
{ ///////////////////////////////////////////////////////////////////// }
function GetUninstallString(): String;
var
sUnInstPath: String;
sUnInstallString: String;
begin
sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
sUnInstallString := '';
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
Result := sUnInstallString;
end;
{ ///////////////////////////////////////////////////////////////////// }
function IsUpgrade(): Boolean;
begin
Result := (GetUninstallString() <> '');
end;
{ ///////////////////////////////////////////////////////////////////// }
function UnInstallOldVersion(): Integer;
var
sUnInstallString: String;
iResultCode: Integer;
begin
{ Return Values: }
{ 1 - uninstall string is empty }
{ 2 - error executing the UnInstallString }
{ 3 - successfully executed the UnInstallString }
{ default return value }
Result := 0;
{ get the uninstall string of the old app }
sUnInstallString := GetUninstallString();
if sUnInstallString <> '' then begin
sUnInstallString := RemoveQuotes(sUnInstallString);
if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
Result := 3
else
Result := 2;
end else
Result := 1;
end;
{ ///////////////////////////////////////////////////////////////////// }
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep=ssInstall) then
begin
if (IsUpgrade()) then
begin
UnInstallOldVersion();
end;
end;
end;
Alternatives
See also this blog post "Inno Setup Script Sample for Version Comparison" which goes one step further, and reads the version number of any previously installed version, and compares that version number with that of the current installation package.
You should be able to read the uninstall string from the registry, given the AppId (i.e. the value you used for AppID in the [Setup]-section). It could be found under Software\Microsoft\Windows\CurrentVersion\Uninstall\{AppId}\ (could be either HKLM or HKCU, so best check both) where {AppId} should be substituted with the actual value you used. Look for the UninstallString or QuietUninstallString values and use the Exec function to run it from your InitializeSetup() event function.
If you "just want to remove the old icons" (because yours have changed/updated) you can use this:
; attempt to remove previous versions' icons
[InstallDelete]
Type: filesandordirs; Name: {group}\*;
This is run "at the beginning of installation" so basically removes the old icons, and your new ones will still be installed there after this is completely done.
I just do this with every install "in case anything has changed" icon wise (it all gets reinstalled anyway).
When using Inno Setup, there's no reason to uninstall a previous version unless that version was installed by a different installer program. Otherwise upgrades are handled automatically.
Here is a simplified version based on answer from Craig McQueen:
const
UninstallRegisterPath = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + '{#emit SetupSetting("AppName")}' + '_is1';
function GetUninstallerPath(): String;
begin
result := '';
if (not RegQueryStringValue(HKLM, UninstallRegisterPath, 'UninstallString', result)) then
RegQueryStringValue(HKCU, UninstallRegisterPath, 'UninstallString', result);
end;
procedure UninstallOldVersion();
var
UninstallerPath: String;
ResultCode: Integer;
begin
UninstallerPath := GetUninstallerPath();
if (UninstallerPath <> '') then begin
Exec(UninstallerPath, '/VERYSILENT /NORESTART /SUPPRESSMSGBOXES', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep = ssInstall) then
begin
UninstallOldVersion();
end;
end;
Note: in my case, I use AppName instead of AppId.
The answer provided by Craig McQueen is totally viable. Although, I would add those comments:
The {#emit SetupSetting("AppId")} code does not work for me, so I just add my App ID.
I didn't want to execute my uninstallation program, because I have a INI config file stored in the AppData/ folder which is removed by the uninstaller, and I don't want it to be erased when installing a new version. So, I modified a bit the code provided by Craig McQueen to remove the directory where is installed the program, after retrieving its path.
So, regarding the code of Craig McQueen, changes are:
Retrieve the InstallLocation key instead of the UninstallString key.
Use the DelTree function instead of the Exec(sUnInstallString, ...)
For anyone that uses the GetUninstallString() suggested above to force an uninstall inside CurStepChanged() and has disk caching issues, see below for a related solution that actually waits a while after unistallation for the uninstaller exe to be deleted!
Disk caching issue with inno-setup?
For those interested, I wrote a DLL for Inno Setup 6 and later that provides a simple mechanism for supporting automatic uninstall.
The DLL provides a way to detect if the package you are installing is already installed (via AppId) and to decide, based on the installed version, if you want to automatically uninstall it (for example, you might want to automatically uninstall if user is downgrading).
https://github.com/Bill-Stewart/UninsIS
You can exec an uninstaller in the [code] section. You have to figure out how to get the path to the existing uninstaller. For simplicity when I install my apps I add a registry string value that points to the folder containing the uninstaller, and just exec the uninstaller in the InitializeWizard callback.
Keep in mind that Inno setup uninstaller names are all of the form uninsnnn.exe, you need to take that into account in your code.
i got edited #Crain Mc-Queen code , i think this code is better because not need to modified in different project :
[Code]
function GetNumber(var temp: String): Integer;
var
part: String;
pos1: Integer;
begin
if Length(temp) = 0 then
begin
Result := -1;
Exit;
end;
pos1 := Pos('.', temp);
if (pos1 = 0) then
begin
Result := StrToInt(temp);
temp := '';
end
else
begin
part := Copy(temp, 1, pos1 - 1);
temp := Copy(temp, pos1 + 1, Length(temp));
Result := StrToInt(part);
end;
end;
function CompareInner(var temp1, temp2: String): Integer;
var
num1, num2: Integer;
begin
num1 := GetNumber(temp1);
num2 := GetNumber(temp2);
if (num1 = -1) or (num2 = -1) then
begin
Result := 0;
Exit;
end;
if (num1 > num2) then
begin
Result := 1;
end
else if (num1 < num2) then
begin
Result := -1;
end
else
begin
Result := CompareInner(temp1, temp2);
end;
end;
function CompareVersion(str1, str2: String): Integer;
var
temp1, temp2: String;
begin
temp1 := str1;
temp2 := str2;
Result := CompareInner(temp1, temp2);
end;
function InitializeSetup(): Boolean;
var
oldVersion: String;
uninstaller: String;
ErrorCode: Integer;
vCurID :String;
vCurAppName :String;
begin
vCurID:= '{#SetupSetting("AppId")}';
vCurAppName:= '{#SetupSetting("AppName")}';
//remove first "{" of ID
vCurID:= Copy(vCurID, 2, Length(vCurID) - 1);
//
if RegKeyExists(HKEY_LOCAL_MACHINE,
'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + vCurID + '_is1') then
begin
RegQueryStringValue(HKEY_LOCAL_MACHINE,
'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + vCurID + '_is1',
'DisplayVersion', oldVersion);
if (CompareVersion(oldVersion, '{#SetupSetting("AppVersion")}') < 0) then
begin
if MsgBox('Version ' + oldVersion + ' of ' + vCurAppName + ' is already installed. Continue to use this old version?',
mbConfirmation, MB_YESNO) = IDYES then
begin
Result := False;
end
else
begin
RegQueryStringValue(HKEY_LOCAL_MACHINE,
'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + vCurID + '_is1',
'UninstallString', uninstaller);
ShellExec('runas', uninstaller, '/SILENT', '', SW_HIDE, ewWaitUntilTerminated, ErrorCode);
Result := True;
end;
end
else
begin
MsgBox('Version ' + oldVersion + ' of ' + vCurAppName + ' is already installed. This installer will exit.',
mbInformation, MB_OK);
Result := False;
end;
end
else
begin
Result := True;
end;
end;
I must be missing something.
The new files are copied to the target directory before the removal of the old installation occurs.
Then comes the uninstaller deletes them and remove the directory.
Do not use the [Run] section, but the [UninstallRun].
Infact, the program under [Run] are executed after the installation, causing to uninstall your program immediately after the installation :-|
Instead, the [UninstallRun] section is evaluated before the installation.
Follow this link: http://news.jrsoftware.org/news/innosetup/msg55323.html
In InitializeSetup() function, you can call "MSIEXEC /x {your program ID}" after user prompt to uninstall old old version

Resources