My Inno Setup script downloads some resources using built-in functionalities.
It creates Download Wizard Page:
DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), #OnDownloadProgress);
It adds several items that should be downloaded:
if WizardIsTaskSelected('taskA') then
begin
DownloadPage.Add('https://randomresource/taskA.zip', 'taskA.zip', '');
end;
if WizardIsTaskSelected('taskB') then
begin
DownloadPage.Add('https://randomresource/taskB.zip', 'taskB.zip', '');
end;
and last step is to show the Wizard Page and start downloading:
try
try
DownloadPage.Download;
Result := True;
except
SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
Result := False;
end;
finally
DownloadPage.Hide;
end;
All of the above is basically from examples provided by Inno Setup. There is one issue: if any of the downloads fails (throws exception or anything) it interrupts the whole download process and other items will not be downloaded. I would want it to continue downloading the rest of the files. I went through the Inno Setup documentation and didn't find any flag which would enable that. Is there a solution for that? Thanks in advance.
A simple solution is to download each file separately.
The below code will allow user to select what to do on each file's download error:
Retry the download
Skip the download
Abort the installation.
var
DownloadPage: TDownloadWizardPage;
function RobustDownload(
Url, BaseName, RequiredSHA256OfFile: String): Boolean;
var
Retry: Boolean;
Answer: Integer;
begin
repeat
try
DownloadPage.Clear;
DownloadPage.Add(Url, BaseName, RequiredSHA256OfFile);
DownloadPage.Download;
Retry := False;
Result := True;
except
if DownloadPage.AbortedByUser then
begin
Log('Aborted by user.')
Result := False;
Retry := False;
end
else
begin
// Make sure the page displays the URL that fails to download
DownloadPage.Msg2Label.Caption := Url;
Answer :=
SuppressibleMsgBox(
AddPeriod(GetExceptionMessage),
mbCriticalError, MB_ABORTRETRYIGNORE, IDABORT);
Retry := (Answer = IDRETRY);
Result := (Answer <> IDABORT);
end;
end;
until not Retry;
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
if CurPageID = wpReady then
begin
try
DownloadPage :=
CreateDownloadPage(
SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc),
#OnDownloadProgress);
DownloadPage.Show;
Result :=
RobustDownload('https://example.com/setup1.exe', 'setup1.exe', '') and
RobustDownload('https://example.com/setup2.exe', 'setup2.exe', '') and
RobustDownload('https://example.com/setup3.exe', 'setup3.exe', '');
finally
DownloadPage.Hide;
end;
end
else Result := True;
end;
I use this code to ask for a password:
Inno Setup - Move the password page before the welcome page (first page)
And this code for custom language selector:
Inno Setup - Language selector with VCL Styles
When I merge them, it does not work.
I need password before that the language selector, so this is no correct:
function InitializeSetup(): Boolean;
var
Language: string;
begin
Result := True;
Language := ExpandConstant('{param:LANG}');
if Language = '' then
begin
Log('No language specified, showing language dialog');
SelectLanguage();
Result := False;
Exit;
end
else
begin
Log('Language specified, proceeding with installation');
Result := AskPassword();
end;
end;
And this way, with an incorrect password the setup continues.
function InitializeSetup(): Boolean;
var
Language: string;
begin
Result := True;
Language := ExpandConstant('{param:LANG}');
if Language = '' then
begin
Result := AskPassword();
Log('No language specified, showing language dialog');
SelectLanguage();
Result := False;
Exit;
end
else
begin
Log('Language specified, proceeding with installation');
end;
end;
Inno Setup 6
Inno Setup 6 has event attributes features that helps solving this problem.
Just make sure that each of your event implementation have an unique name, e.g. appending unique suffix. And add event attribute with the name of the implemented event.
[Code]
function InitializeSetup(): Boolean;
begin
Result := ...
end;
<event('InitializeSetup')>
function InitializeSetup2(): Boolean;
begin
Result := ...
end;
Inno Setup 5
In general, the easiest is to keep both implementations of the event function separate and add one wrapper implementation that call both.
function InitializeSetup1(): Boolean;
var
Language: string;
begin
Result := True;
Language := ExpandConstant('{param:LANG}');
if Language = '' then
begin
Log('No language specified, showing language dialog');
SelectLanguage();
Result := False;
Exit;
end
else
begin
Log('Language specified, proceeding with installation');
Result := True;
end;
end;
function InitializeSetup2(): Boolean;
begin
Result := AskPassword();
end;
function InitializeSetup(): Boolean;
begin
{ Order the calls the way you want the checks to be performed }
Result :=
InitializeSetup2() and
InitializeSetup1();
end;
For more general discussion of the problem, see
Merging event function (InitializeWizard) implementations from different sources
Though in your specific case, it's more complicated, as you will also need to pass the password from the first instance to the other, similarly to how the language is passed from the first instance to the other.
So actually, the InitializeSetup2 (password) implementation will have to be similar like the InitializeSetup1 (language), not to ask for the password again.
I actually do not really understand, why you complicate the things so much by not asking for language before the password. It would actually make sense. To get a localized password prompt.
Is there any way to pause the execution of Inno Setup until user makes some interactions with the message box. I am using a message box to confirm whether or not to keep user data. I want to stop all other executions in setup until the user selects yes or no.
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usPostUninstall then
begin
if DirExists(ExpandConstant('{localappdata}\{#MyBuildId}\storage')) then
if MsgBox('Do you want to delete the saved user data?',
mbConfirmation, MB_YESNO) = IDYES
then
DelTree(ExpandConstant('{localappdata}\{#MyBuildId}\storage'), True, True, True);
end;
end;
I am using a separate procedure to uninstall the previous version in the beginning of the install.
procedure CurStepChanged(CurStep: TSetupStep);
begin
if (CurStep=ssInstall) then
begin
if (IsUpgrade()) then
begin
UnInstallOldVersion();
end;
end;
end;
So when starting to install a new setup first it uninstalls the old version. The user data deletion message box is also shown. But the execution is not pausing. It uninstalls and reinstalls the app while the message box is showing
function GetUninstallString(): String;
var
sUnInstPath: String;
sUnInstallString: String;
begin
sUnInstPath :=
ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#MyAppId}_is1');
sUnInstallString := '';
if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
Result := sUnInstallString;
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','', SW_HIDE,
ewWaitUntilTerminated, iResultCode) then
Result := 3
else
Result := 2;
end else
Result := 1;
end;
When you execute the Inno Setup uninstaller .exe, it clones itself to a temporary folder and runs the clone internally. The main process waits for the clone to (almost) finish, before it terminates itself. The clone can then delete the main uninstaller .exe (as it is not locked anymore). The main process is terminated just after the actual uninstallation completes. But before CurUninstallStepChanged(usPostUninstall). So if you display your message box there, the main uninstaller process is terminated already and so is the Exec in UnInstallOldVersion.
If possible, do the data deletion on usUninstall, not on usPostUninstall.
The Windows ribbon framework markup supports an EnablePinning attribute for the recent items menu in the application menu:
<ApplicationMenu.RecentItems>
<RecentItems CommandName="MRU" EnablePinning="true" />
</ApplicationMenu.RecentItems>
I expected that there would be a matching property that can be queried/updated at runtime, but I can't find a property key. Does anyone know if there is one, and, if so, what it is?
Alternatively, is there another way to turn pinning on/off at runtime? Neither the element nor its parent support application modes.
TIA
Clarification: What I'm trying to do is enable/disable pinning for the entire menu at runtime. I'm not concerned about the pin states of the individual items.
I'm not sure if you can modify the pinned state from existing entries but it's definitely possible to programmatically query the state and add new items with a specific state using the UI_PKEY_Pinned property:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd940401(v=vs.85).aspx
Wrappers such as the Windows Ribbon Framework for Delphi or the Windows Ribbon for WinForms (.NET) provide an easy access to the API model. This CodeProject article also describes how to query/add recent items using C#.
If you want to change the state during runtime, you could for example query the state of all items, remove them from the list, adjust whetever you need and add them to the list again. Didn't do that yet, could be worth a try however.
Hmm... this will be quite difficult to accomplish as the flag is defined in the XML which will be compiled into a resource file that is linked to the application and then loaded on start up. You could create another resource definition and reload the ribbon if you want to disable/enable the flagging, but that's quite a lot overhead and certainly noticeable from an users perspective as it requires the creation of a new window handle.
I place the recent items by inside UpdateProperty
TRecentItem = class(TInterfacedObject, IUISimplePropertySet)
private
FRecentFile: TSSettings.TRecentFile;
protected
function GetValue(const key: TUIPropertyKey; out value: TPropVariant): HRESULT; stdcall;
public
procedure Initialize(const RecentFile: TSSettings.TRecentFile); safecall;
end;
function TMyForm.UpdateProperty(commandId: UInt32; const key: TUIPropertyKey;
currentValue: PPropVariant; out newValue: TPropVariant): HRESULT;
var
I: Integer;
psa: PSafeArray;
pv: Pointer;
RecentItem: TRecentItem;
begin
if (key = UI_PKEY_RecentItems) then
begin
psa := SafeArrayCreateVector(VT_UNKNOWN, 0, Settings.RecentFiles.Count);
if (not Assigned(psa)) then
Result := E_FAIL
else
begin
for I := 0 to Settings.RecentFiles.Count - 1 do
begin
RecentItem := TRecentItem.NewInstance() as TRecentItem;
RecentItem.Initialize(Settings.RecentFiles[I]);
pv := Pointer(IUnknown(RecentItem));
Check(SafeArrayPutElement(psa, I, pv^));
end;
Result := UIInitPropertyFromIUnknownArray(UI_PKEY_RecentItems, psa, PropVar);
SafeArrayDestroy(psa);
end;
end;
If a pin was changed, I get this command while closing the application menu:
function TMyForm.Execute(commandId: UInt32; verb: _UIExecutionVerb;
key: PUIPropertyKey; currentValue: PPropVariant;
commandExecutionProperties: IUISimplePropertySet): HRESULT; stdcall;
var
Count: Integer;
I: Integer;
Pinned: Boolean;
psa: PSafeArray;
pv: IUnknown;
RecentFile: UInt32;
SimplePropertySet: IUISimplePropertySet;
Value: TPropVariant;
begin
if ((commandId = cmdAppRecentItems)
and Assigned(key) and (key^ = UI_PKEY_RecentItems)
and Assigned(currentValue) and (currentValue^.vt = VT_ARRAY + VT_UNKNOWN)) then
begin
psa := nil;
Result := UIPropertyToIUnknownArrayAlloc(key^, currentValue^, psa);
if (Succeeded(Result)) then
begin
Result := SafeArrayGetUBound(psa, 1, Count);
for I := 0 to Count do
if (Succeeded(Result)) then
begin
Result := SafeArrayGetElement(psa, I, pv);
if (Succeeded(Result) and Assigned(pv)) then
begin
Result := pv.QueryInterface(IUISimplePropertySet, SimplePropertySet);
if (Succeeded(Result)) then
Result := SimplePropertySet.GetValue(UI_PKEY_Pinned, Value);
if (Succeeded(Result)) then
Result := UIPropertyToBoolean(UI_PKEY_Pinned, Value, Pinned);
if (Succeeded(Result)) then
Settings.RecentFiles.SetPinned(I, Pinned);
end;
end;
SafeArrayDestroy(psa);
end;
end
end;
... but I didn't find a documentation of this solution.
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