Inno Setup by default looks at the PrivilegesRequired
setup variable, if this is set to admin
or poweruser
, the installer installs the
Inno Setup 6 has a built-in support for selecting between "Install for all users" and "Install for me only".
Basically, you can simply set PrivilegesRequiredOverridesAllowed:
[Setup]
PrivilegesRequiredOverridesAllowed=commandline dialog
For Inno Setup 5: As you found yourself, the logic is hard-coded. You cannot really control that.
The closest you can get is by using the undocumented (deprecated) PrivilegesRequired=none
.
With this value (and with a help of installer-autodetection in Windows):
It's not exactly, what you want, but I do not think you can get any closer.
You can of course copy (move) the registry key between the HKCU
and HKLM
yourself by a code:
function MoveHKCUUninstallKeyToHKLM: Boolean;
var
UninstallKey: string;
AppId: string;
I: Integer;
ValueNames: TArrayOfString;
ValueName: string;
ValueStr: string;
ValueDWord: Cardinal;
begin
if '{#emit SetupSetting("AppId")}' <> '' then
begin
AppId := '{#emit SetupSetting("AppId")}';
end
else
begin
AppId := '{#emit SetupSetting("AppName")}';
end;
Result := False;
if AppId = '' then
begin
Log('Cannot identify AppId');
end
else
begin
UninstallKey :=
'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + AppId + '_is1';
Log(Format(
'AppId identified as "%s", using uninstall key "%s"', [AppId, UninstallKey]));
if not RegKeyExists(HKCU, UninstallKey) then
begin
Log('HKCU uninstall key not found');
end
else
if RegKeyExists(HKLM, UninstallKey) then
begin
Log('HKLM uninstall key exists already');
end
else
begin
Log('HKCU uninstall key found and HKLM key not exists yet');
if not RegGetValueNames(HKCU, UninstallKey, ValueNames) then
begin
Log('Cannot list uninstall key values');
end
else
begin
I := 0;
Result := True;
while (I < GetArrayLength(ValueNames)) and Result do
begin
ValueName := ValueNames[I];
if RegQueryStringValue(HKCU, UninstallKey, ValueName, ValueStr) then
begin
if not RegWriteStringValue(HKLM, UninstallKey, ValueName, ValueStr) then
begin
Log(Format('Error moving "%s" string value', [ValueName]));
Result := False;
end
else
begin
Log(Format('Moved "%s" string value', [ValueName]));
end;
end
else
if RegQueryDWordValue(HKCU, UninstallKey, ValueName, ValueDWord) then
begin
if not RegWriteDWordValue(HKLM, UninstallKey, ValueName, ValueDWord) then
begin
Log(Format('Error moving "%s" dword value', [ValueName]));
Result := False;
end
else
begin
Log(Format('Moved "%s" dword value', [ValueName]));
end;
end
else
begin
{ All uninstall values written by Inno Setup are either string or dword }
Log(Format('Value "%s" is neither string nor dword', [ValueName]));
Result := False;
end;
Inc(I);
end;
if Result then
begin
if not RegDeleteKeyIncludingSubkeys(HKCU, UninstallKey) then
begin
Log('Error removing HKCU uninstall key');
Result := False;
end
else
begin
Log('Removed HKCU uninstall key');
end;
end;
if not Result then
begin
if not RegDeleteKeyIncludingSubkeys(HKCU, UninstallKey) then
begin
Log('Failed to move uninstall key to HKLM, ' +
'and also failed to rollback the changes');
end
else
begin
Log('Failed to move uninstall key to HKLM, rolled back the changes');
end;
end;
end;
end;
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
Log('Post install');
MoveHKCUUninstallKeyToHKLM;
end;
end;
The PrivilegesRequired=none
solution was not what I wanted. In some cases, it still prompts for elevation on administrator accounts and also the registry destination was still not reflective of the users selection.
Since I was already using a native helper DLL in my Inno Setup project, I coded this in C++ as I'm more comfortable there. I'm calling this method is called in CurStepChanged
where CurPage=ssDoneInstall
. Just call this method with the [Setup]
AppId
and whether or not the registry keys should be installed locally or not.
#include <shlwapi.h>
extern "C" __declspec(dllexport)
bool DetectAndMoveRegKeyW(LPCWSTR app_id, bool install_local)
{
std::wstring s_app = app_id;
std::wstring path =
L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + s_app + L"_is1";
LPCWSTR c_path = path.c_str();
LRESULT res;
HKEY source = nullptr, subKey = nullptr;
// try to find source in HKLM
source = HKEY_LOCAL_MACHINE;
res = RegOpenKeyExW(source, c_path, 0, KEY_READ, &subKey);
if (subKey != nullptr)
RegCloseKey(subKey);
// try to find source in HKCU
if (res != ERROR_SUCCESS)
{
subKey = nullptr;
source = HKEY_CURRENT_USER;
res = RegOpenKeyExW(source, c_path, 0, KEY_READ, &subKey);
if (subKey != nullptr)
RegCloseKey(subKey);
}
if (res != ERROR_SUCCESS)
return false; // cant find the registry key
HKEY dest = install_local ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
if (source == dest)
return true; // registry already in the right place
// copy registry key to correct destination
HKEY hOldKey;
HKEY hNewKey;
bool bResult = false;
if (RegOpenKeyW(source, c_path, &hOldKey) == 0)
{
if (RegCreateKeyW(dest, c_path, &hNewKey) == 0)
{
bResult = (SHCopyKeyW(hOldKey, nullptr, hNewKey, 0) == 0);
RegCloseKey(hNewKey);
}
RegCloseKey(hOldKey);
if (bResult)
{
RegDeleteKeyW(source, c_path);
}
}
return bResult;
}
I'm exporting this method as cdecl
instead of stdcall
, this is because VC++ ignores the C extern and mangles method names anyways when using stdcall
. You'll need to import this as cdecl
in inno (see inno docs for this). Also, of course this is the Unicode-only implementation, if you require an Ansi version it should be simple enough.
IMPORTANT NOTICE:
This code is incomplete, it doesn't account for 64bit registry redirection. Inno-Setup completely ignores windows registry redirection and this code doesn't search the 64 bit registry at all since Inno and itself are running in 32bit.