How can I read 64-bit registry key from a 32-bit process? - windows

I've been using the value of key MachineGuid from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography to uniquely identify hosts, but from 32-bit processes running on 64-bit computers, the value appears to be missing. I guess it's searching under Wow6432Node, where it is indeed missing. According to this you should be able to get to the right key by adding a flag, but below code still doesn't appear to do the job. What am I missing?
const
KEY_WOW64_64KEY=$0100;
var
r:HKEY;
s:string;
i,l:integer;
begin
//use cryptography machineguid, keep a local copy of this in initialization?
l:=40;
if RegOpenKeyEx(HKEY_LOCAL_MACHINE,PChar('Software\Microsoft\Cryptography'),
0,KEY_QUERY_VALUE,r)=ERROR_SUCCESS then
begin
SetLength(s,l);
if RegQueryValue(r,'MachineGuid',PChar(s),l)=ERROR_SUCCESS then
begin
SetLength(s,l);
RegCloseKey(r);
end
else
begin
//try from-32-to-64
RegCloseKey(r);
if RegOpenKeyEx(HKEY_LOCAL_MACHINE,PChar('Software\Microsoft\Cryptography'),
0,KEY_QUERY_VALUE or KEY_WOW64_64KEY,r)=ERROR_SUCCESS then
begin
l:=40;
if RegQueryValue(r,'MachineGuid',PChar(s),l)=ERROR_SUCCESS then
SetLength(s,l)
else
l:=0;
RegCloseKey(r);
end;
end;
end;

I would suggest you use the IsWow64Process() function to know when you are a 32-process running on a 64-bit OS, and then only apply the KEY_WOW64_64KEY flags in that specific condition. If the app is a 32-bit process on a 32-bit OS, or a 64-bit process on a 64-bit OS, the flags is not needed.
For example:
const
KEY_WOW64_64KEY = $0100;
var
key: HKEY;
str: string;
len: DWORD;
flag: REGSAM;
wow64: BOOL;
begin
flag := 0;
wow64 := 0;
IsWow64Process(GetCurrentProcess(), #wow64);
if wow64 <> 0 then flag := KEY_WOW64_64KEY;
if RegOpenKeyEx(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Cryptography', 0, KEY_QUERY_VALUE or flag, key) = ERROR_SUCCESS then
try
SetLength(str, 40);
len := Length(str) * SizeOf(Char);
if RegQueryValueEx(key, 'MachineGuid', nil, nil, PByte(Pointer(s)), #len) <> ERROR_SUCCESS then len := 0;
SetLength(str, len div SizeOf(Char));
finally
RegCloseKey(key);
end;
end;

Your code is needlessly complex, largely because you are not taking advantage of the built-in TRegistry class which shields you from all the complexities of the low-level registry API. For example, consider the following code:
type
TRegistryView = (rvDefault, rvRegistry64, rvRegistry32);
function RegistryViewAccessFlag(View: TRegistryView): LongWord;
begin
case View of
rvDefault:
Result := 0;
rvRegistry64:
Result := KEY_WOW64_64KEY;
rvRegistry32:
Result := KEY_WOW64_32KEY;
end;
end;
function ReadRegStr(const Root: HKEY; const Key, Name: string;
const View: TRegistryView=rvDefault): string;
var
Registry: TRegistry;
begin
Registry := TRegistry.Create(KEY_READ or RegistryViewAccessFlag(View));
try
Registry.RootKey := Root;
if not Registry.OpenKey(Key) then
raise ERegistryException.CreateFmt('Key not found: %s', [Key]);
if not Registry.ValueExists(Name) then
raise ERegistryException.CreateFmt('Name not found: %s\%s', [Key, Name]);
Result := Registry.ReadString(Name);//will raise exception in case of failure
finally
Registry.Free;
end;
end;
The function ReadRegStr will return the string value named Name from the key Key relative to the root key Root. If there is an error, for example if the key or name do not exists, or if the value is of the wrong type, then an exception will be raised.
The View parameter is an enumeration that makes it simple for you to access native, 32-bit or 64-bit views of the registry. Note that native means native to the process that is running. So it will be the 32-bit view for a 32-bit process and the 64-bit view for a 64-bit process. This enumeration mirrors the equivalent definition in .net.

In my use of this registry key I went a step further. If the value didn't exist I created it: not in HKEY_LOCAL_MACHINE, that would require elevation, but in HKEY_CURRENT_USER. Anyone seeing the introduced key there is unlikely to realise that it's a dummy.
function GetComputerGUID: String;
var
Reg: TRegistry;
oGuid: TGUID;
sGuid: String;
begin
Result := '';
// Attempt to retrieve the real key
Reg := TRegistry.Create(KEY_READ OR KEY_WOW64_64KEY);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Cryptography') and Reg.ValueExists('MachineGuid') then
Result := Reg.ReadString('MachineGuid');
Reg.CloseKey;
finally
Reg.Free;
end;
// If retrieval fails, look for the surrogate
if Result = '' then begin
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_CURRENT_USER;
if Reg.OpenKey('SOFTWARE\Microsoft\Cryptography', True) then begin
if Reg.ValueExists('MachineGuid') then
Result := Reg.ReadString('MachineGuid')
else begin
// If the surrogate doesn't exist, create it
if CreateGUID(oGUID) = 0 then begin
sGuid := Lowercase(GUIDToString(oGUID));
Reg.WriteString('MachineGuid', Copy(sGuid, 2, Length(sGUID) - 2));
Result := Reg.ReadString('MachineGuid');
end;
end;
end;
Reg.CloseKey;
finally
Reg.Free;
end;
end;
if Result = '' then
raise Exception.Create('Unable to access registry value in GetComputerGUID');
end;
That's a good point from #Remy Lebeau - TeamB though; I should mod the above code appropriately.

Call reg.exe using this path
C:\Windows\sysnative\reg.exe
For example:
C:\Windows\sysnative\reg.exe QUERY "HKLM\SOFTWARE\JavaSoft\JDK" /v CurrentVersion
source: https://stackoverflow.com/a/25103599

Related

Launch Windows Optimize application (Windows 10) from Delphi

We have a legacy Delphi 7 application that launches the Windows Defrag and On-screen Keyboard applications as follows:
// Defragmentation application
ShellExecute(0, 'open', PChar('C:\Windows\System32\dfrg.msc'), nil, nil, SW_SHOWNORMAL);
// On-screen keyboard
ShellExecute(0, 'open', PChar('C:\Windows\System32\osk.exe'), nil, nil, SW_SHOWNORMAL);
Both work on Windows XP but fail on Windows 10. I spotted that the defragmentation application has had a name change to dfrgui.exe, but updating the code does not help. The On-screen Keyboard is still called osk.exe on Windows 10.
Both applications can be launched manually / directly from the command line or by double-clicking them in Windows Explorer.
My suspicion is that Windows security is preventing my application from launching anything from C:\Windows\System32, because I can launch several other applications from Program Files and from C:\Windows.
Can anyone help?
Delphi 7 produces only 32-bit apps, there is no option to produce 64-bit apps (that was added in XE2).
Accessing a path under %WINDIR%\System32 from a 32-bit app running on a 64-bit system is subject to WOW64's File
System Redirector, which will silently redirect requests for the 64-bit System32 folder to the 32-bit SysWOW64 folder instead.
Chances are, the apps you are trying to run only exist in the 64-bit System32 folder and not in the 32-bit SysWOW64 folder.
To avoid redirection, you need to either:
replace System32 with the special Sysnative alias in your paths (ie 'C:\Windows\Sysnative\osk.exe'), which only works when running under WOW64, so you have to detect that dynamically at runtime via IsWow64Process():
function GetSystem32Folder: string;
var
Folder: array[0..MAX_PATH] of Char;
IsWow64: BOOL;
begin
Result := '';
if IsWow64Process(GetCurrentProcess(), #IsWow64) and IsWow64 then
begin
SetString(Result, Folder, GetWindowsDirectory(Folder, Length(Folder)));
if Result <> '' then
Result := IncludeTrailingPathDelimiter(Result) + 'Sysnative' + PathDelim;
end else
begin
SetString(Result, Folder, GetSystemDirectory(Folder, Length(Folder)));
if Result <> '' then
Result := IncludeTrailingPathDelimiter(Result);
end;
end;
function RunDefrag: Boolean;
var
SysFolder: string;
Res: Integer;
begin
SysFolder := GetSystem32Folder;
Res := Integer(ShellExecute(0, nil, PChar(SysFolder + 'dfrgui.exe'), nil, nil, SW_SHOWNORMAL));
if Res = ERROR_FILE_NOT_FOUND then
Res := Integer(ShellExecute(0, nil, PChar(SysFolder + 'dfrg.msc'), nil, nil, SW_SHOWNORMAL));
Result := (Res = 0);
end;
function RunOnScreenKeyboard: Boolean;
begin
Result := (ShellExecute(0, nil, PChar(GetSystem32Folder + 'osk.exe'), nil, nil, SW_SHOWNORMAL) = 0);
end;
temporarily disable the Redirector via Wow64DisableWow64FsRedirection(), and then re-enable it via Wow64RevertWow64FsRedirection() when done:
function GetSystem32Folder: string
var
Folder: array[0..MAX_PATH] of Char;
begin
SetString(Result, Folder, GetSystemDirectory(Folder, Length(Folder)));
if Result <> '' then
Result := IncludeTrailingPathDelimiter(Result);
end;
function RunDefrag: Boolean;
var
SysFolder: string;
OldState: Pointer;
Res: Integer;
begin
Wow64DisableWow64FsRedirection(#OldState);
try
SysFolder := GetSystem32Folder;
Res := Integer(ShellExecute(0, nil, PChar(SysFolder + 'dfrgui.exe'), nil, nil, SW_SHOWNORMAL));
if Res = ERROR_FILE_NOT_FOUND then
Res := Integer(ShellExecute(0, nil, PChar(SysFolder + 'dfrg.msc'), nil, nil, SW_SHOWNORMAL));
Result := Res = 0;
finally
Wow64RevertWow64FsRedirection(OldState);
end;
end;
function RunOnScreenKeyboard: Boolean;
var
OldState: Pointer;
begin
Wow64DisableWow64FsRedirection(#OldState);
try
Result := (ShellExecute(0, nil, PChar(GetSystem32Folder + 'osk.exe'), nil, nil, SW_SHOWNORMAL) = 0);
finally
Wow64RevertWow64FsRedirection(OldState);
end;
end;
Update: that being said, it turns out that a 32-bit process running under WOW64 is not allowed to run osk.exe when UAC is enabled:
Delphi - On Screen Keyboard (osk.exe) works on Win32 but fails on Win64
So, you will have to create a helper 64-bit process to launch osk.exe on your app's behalf when it is running under WOW64.
A small addition to Remy Lebeau's answer:
If Wow64DisableWow64FsRedirection is not available in your Delphi version, and/or if you are not sure if your target platform will support this API, you could use following code sample that calls the function dynamically:
https://www.delphipraxis.net/155861-windows-7-64bit-redirection.html
function ChangeFSRedirection(bDisable: Boolean): Boolean;
type
TWow64DisableWow64FsRedirection = Function(Var Wow64FsEnableRedirection: LongBool): LongBool; StdCall;
TWow64EnableWow64FsRedirection = Function(var Wow64FsEnableRedirection: LongBool): LongBool; StdCall;
var
hHandle: THandle;
Wow64DisableWow64FsRedirection: TWow64DisableWow64FsRedirection;
Wow64EnableWow64FsRedirection: TWow64EnableWow64FsRedirection;
Wow64FsEnableRedirection: LongBool;
begin
Result := false;
try
hHandle := GetModuleHandle('kernel32.dll');
#Wow64EnableWow64FsRedirection := GetProcAddress(hHandle, 'Wow64EnableWow64FsRedirection');
#Wow64DisableWow64FsRedirection := GetProcAddress(hHandle, 'Wow64DisableWow64FsRedirection');
if bDisable then
begin
if (hHandle <> 0) and (#Wow64DisableWow64FsRedirection <> nil) then
begin
Result := Wow64DisableWow64FsRedirection(Wow64FsEnableRedirection);
end;
end else
begin
if (hHandle <> 0) and (#Wow64EnableWow64FsRedirection <> nil) then
begin
Result := Wow64EnableWow64FsRedirection(Wow64FsEnableRedirection);
Result := True;
end;
end;
Except
end;
end;

Retrieve the default associated file types of an application?

The function ShellFindExecutable allows finding the program which is associated with a specific file type:
function ShellFindExecutable(const FileName, DefaultDir: string): string;
var
Res: HINST;
Buffer: array [0..MAX_PATH-1] of Char;
I: Integer;
begin
ResetMemory(Buffer, SizeOf(Buffer));
Res := FindExecutable(PChar(FileName), PCharOrNil(DefaultDir), Buffer);
if Res > 32 then
begin
// FindExecutable replaces #32 with #0
for I := Low(Buffer) to High(Buffer) - 1 do
if Buffer[I] = #0 then
Buffer[I] := #32;
Buffer[High(Buffer)] := #0;
Result := Trim(Buffer);
end
else
Result := '';
end;
For example:
DefProgram := ShellFindExecutable('R:\test.txt', '');
// DefProgram: C:\Program Files (x86)\Notepad++\notepad++.exe
But how can I find the file type(s)/extension(s) for which a specific existing program is the default associated application?
Delphi 10.1 Berlin
Windows 7 x64
I don't believe that there is an API function specifically to do that. You will need to iterate through each registered extension, for instance by enumerating keys in HKCR, and check which executable is associated with the open verb.
Rather than using FindAssociation I suspect that IQueryAssociations will be more efficient and robust.
Furthermore what you claim about FindAssociation replacing spaces with nulls is simply not true. You can replace the body of your if statement with Result := Buffer.

How to permanently terminate Windows Explorer (the "explorer.exe" process)?

I'm using the following code to terminate a process:
function KillTask(ExeFileName: string): Integer;
const
PROCESS_TERMINATE = $0001;
var
ContinueLoop: BOOL;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
begin
Result := 0;
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
while Integer(ContinueLoop) <> 0 do
begin
if ((UpperCase(ExtractFileName(FProcessEntry32.szExeFile)) =
UpperCase(ExeFileName)) or (UpperCase(FProcessEntry32.szExeFile) =
UpperCase(ExeFileName))) then
Result := Integer(TerminateProcess(
OpenProcess(PROCESS_TERMINATE,
BOOL(0),
FProcessEntry32.th32ProcessID),
0));
ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
end;
CloseHandle(FSnapshotHandle);
end;
The problem is, when I call the above function in order to permanently terminate the explorer.exe, the Windows Explorer terminates though, but it's re-started afterwards:
KillTask('explorer.exe');
I'm using Delphi XE3, Delphi 7 and Windows 8.
Based on this Exit Explorer feature and code debugged by Luke in this post you may try to use the following code:
Warning:
This way is absolutely undocumented! So all constants and variables appearing in this post are fictitious. Any resemblance to real, documented code is purely coincidental :-)
function ExitExplorer: Boolean;
var
TrayHandle: HWND;
const
WM_EXITEXPLORER = $5B4;
begin
Result := False;
TrayHandle := FindWindow('Shell_TrayWnd', nil);
if TrayHandle <> 0 then
Result := PostMessage(TrayHandle, WM_EXITEXPLORER, 0, 0);
end;
I've tested it in Windows 7, where it works and doesn't even need the administrator elevation. Don't know how about the other systems (I'd say this won't work at least on Windows XP, but it's just a guess).

How to set a Windows folder's permissions using SID's in Delphi

Just now I'm looking at fixing a localization bug with a small application that get's fired during the install of a software package. The small application essentially brute forces permissions on our own folder within Application Data to set EVERYONE to full access.
The problem arises with EVERYONE not being localized. I know I need to use SID's, which for EVERYONE, is S-1-1-0. I can't find a WinAPI function for setting permissions using an SID.
The function just now uses BuildExplicitAccessWithName and SetNamedSecurityInfo as shown below
function setfullaccess(foldername:string):boolean; //B2415 MDE
var
pDACL: PACL;
pEA: PEXPLICIT_ACCESS_A;
R: DWORD;
begin
result := true;
pEA := AllocMem(SizeOf(EXPLICIT_ACCESS));
BuildExplicitAccessWithName(pEA, 'EVERYONE', GENERIC_ALL{GENERIC_READ},GRANT_ACCESS, SUB_CONTAINERS_AND_OBJECTS_INHERIT{NO_INHERITANCE});
R := SetEntriesInAcl(1, pEA, nil, pDACL);
if R = ERROR_SUCCESS then
begin
if SetNamedSecurityInfo(pchar(foldername), SE_FILE_OBJECT,DACL_SECURITY_INFORMATION, nil, nil, pDACL, nil) <> ERROR_SUCCESS then result := false;
LocalFree(Cardinal(pDACL));
end
else result := false;//ShowMessage('SetEntriesInAcl failed: ' + SysErrorMessage(R));
end;
Which functions should I be looking at using instead?
After some searching through the WinAPI documentation I went for the solution below. Essentially I use the SID to lookup the "readable" name and then use that. It won't be the most elegant solution but it works for me.
procedure TTestform.Button4Click(Sender: TObject);
var
Sid: PSID;
peUse: DWORD;
cchDomain: DWORD;
cchName: DWORD;
Name: array of Char;
Domain: array of Char;
pDACL: PACL;
pEA: PEXPLICIT_ACCESS_A;
R: DWORD;
foldername: String; //Temp to hardcode
begin
foldername := 'C:\TEMP'; //Temp to hardcode
Sid := nil;
Win32Check(ConvertStringSidToSidA(PChar('S-1-1-0'), Sid));
cchName := 0;
cchDomain := 0;
//Get Length
if (not LookupAccountSid(nil, Sid, nil, cchName, nil, cchDomain, peUse)) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) then
begin
SetLength(Name, cchName);
SetLength(Domain, cchDomain);
if LookupAccountSid(nil, Sid, #Name[0], cchName, #Domain[0], cchDomain, peUse) then
begin
pEA := AllocMem(SizeOf(EXPLICIT_ACCESS));
BuildExplicitAccessWithName(pEA, PChar(Name), GENERIC_ALL{GENERIC_READ},GRANT_ACCESS, SUB_CONTAINERS_AND_OBJECTS_INHERIT{NO_INHERITANCE});
R := SetEntriesInAcl(1, pEA, nil, pDACL);
if R = ERROR_SUCCESS then
begin
if SetNamedSecurityInfo(pchar(foldername), SE_FILE_OBJECT,DACL_SECURITY_INFORMATION, nil, nil, pDACL, nil) <> ERROR_SUCCESS then ShowMessage('SetNamedSecurityInfo failed: ' + SysErrorMessage(GetLastError));
LocalFree(Cardinal(pDACL));
end
else ShowMessage('SetEntriesInAcl failed: ' + SysErrorMessage(R));
end;
end;
end;
Alternative approach to avoid using both lookup and build
var SID: JwaWinNT.PSid; // JWA clashes with Delphi XE2 Windows.Windows.PSID type
pDACL: PACL;
EA: EXPLICIT_ACCESS;
SID_DATA: array[1..SECURITY_MAX_SID_SIZE] of byte;
SID_DATA_SIZE: DWORD;
SID_DATA_SIZE := Length(SID_DATA);
Pointer(SID) := #SID_DATA; // #SID_DATA[Low(SID_DATA)] for non-static arrays
pDACL := nil;
// if ConvertStringSidToSid(Auth_Users_SID, SID) then
if CreateWellKnownSid(WinAuthenticatedUserSid, nil, SID, SID_DATA_SIZE) then
try
EA.grfInheritance := SUB_CONTAINERS_AND_OBJECTS_INHERIT; // NO_INHERITANCE; // 0
EA.grfAccessMode := GRANT_ACCESS;
EA.grfAccessPermissions := GENERIC_ALL;
EA.Trustee.MultipleTrusteeOperation := NO_MULTIPLE_TRUSTEE;
EA.Trustee.pMultipleTrustee := nil;
EA.Trustee.TrusteeType := TRUSTEE_IS_WELL_KNOWN_GROUP;
EA.Trustee.TrusteeForm := TRUSTEE_IS_SID;
EA.Trustee.ptstrName := pointer(SID);
if ERROR_SUCCESS = SetEntriesInAcl(1, #EA, nil, pDACL) then begin
// optional creation of PATH and 0-bytes dummy file skipped
SetNamedSecurityInfo(pchar(foldername), SE_FILE_OBJECT, .... {see the author's answer above }
end;
finally
LocalFree(pDACL); // SID is not allocated on heap - no need to free it
end;
This snippet can also be checked against the unit at page #2 at http://forum.vingrad.ru/forum/topic-374131/0.html#st_15_view_0

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