Inno Setup - Check if a component is installed - inno-setup

What I really want to do is have Inno Setup uninstall a component, if it's unchecked in a subsequent run. But, if I'm not mistaken, that is not possible in Inno Setup (actually, correct me, if I'm wrong on this).
So, instead I want to make check function to see if a component is installed, so I can hide it during subsequent runs. I'm not sure where else to get that info other than the Inno Setup: Selected Components under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\[AppName]_is1.
Now the problem is my Inno Setup: Selected Components is as,as2,as3,bs,bs2,bs3.
How can I detect as, without detecting as2 or as3?

Indeed, Inno Setup does not support uninstalling components.
For a similar question (and maybe better), see:
Inno Setup: Disable already installed components on upgrade
For checking of installed components, I'd rather suggest you to check for existence of files corresponding to the component.
Anyway, to answer your actual question: If you want to scan the Inno Setup: Selected Components entry, you can use this function:
function ItemExistsInList(Item: string; List: string): Boolean;
var
S: string;
P: Integer;
begin
Result := False;
while (not Result) and (List <> '') do
begin
P := Pos(',', List);
if P > 0 then
begin
S := Copy(List, 1, P - 1);
Delete(List, 1, P);
end
else
begin
S := List;
List := '';
end;
Result := (CompareText(S, Item) = 0);
end;
end;
Note that the uninstall key can be present in HKCU (not in HKLM) under certain circumstances.

Related

How to rename program group in an Inno Setup update installation?

Our Inno Setup based installer creates a program group and included the major version number in its name, e.g. "Foo Bar 2". This was initially done by the directives
[Setup]
; Note: there is no AppId
...
DefaultGroupName={#AppName} {#AppVersion}
UsePreviousGroup=yes
Inno Setup remembers this name and reuses it. So for our upcoming new release, the major number is now 3 but the program group is not updated to "Foo Bar 3" and remains at "Foo Bar 2".
I have unsuccessfully tried removing {group} via [InstallDelete]. Deleting the group folder or renaming it also does not work for me.
Based on Bill's hint, I would like to know how I can strip the version number of the program group during an update installation.
If the user changed the name of the group and it does no longer match the default name, I'm not going to change it.
You will have to code the in Pascal Script. Something like this:
#define AppName "My Program"
[Setup]
DefaultGroupName={#AppName}
[Code]
var
GroupPath: string;
procedure InitializeWizard();
var
GroupName: string;
begin
if not WizardForm.NoIconsCheck.Checked then
begin
GroupName := RemoveBackslashUnlessRoot(WizardForm.GroupEdit.Text);
if GroupName = '{#AppName} 2' then
begin
GroupPath := AddBackslash(ExpandConstant('{autoprograms}')) + GroupName;
WizardForm.GroupEdit.Text := '{#SetupSetting("DefaultGroupName")}';
Log(Format('Old version Start menu group "%s" (path "%s") was found, ' +
'it will be deleted and new icons will be created in "%s"', [
GroupName, GroupPath, WizardForm.GroupEdit.Text]));
end;
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
begin
if GroupPath <> '' then
begin
Log(Format('Deleting old Start menu group from folder "%s"', [GroupPath]));
if DelTree(GroupPath, True, True, True) then Log('Successfully deleted')
else Log('Error while deleting');
end;
end;
end;
Use with care. Calling DelTree (particularly with Administrator permissions) can cause disaster, if the path is calculated incorrectly. You might consider deleting specific individual icon files only and then the empty (if) folder instead.
Though note that start menu groups shall not be used as per Windows guidelines since like Windows 7. So the correct solution would be to remove the group instead of renaming it.

Inno Setup : Create Uninstall Registry Key only when needed

I need to force the directive CreateUninstallRegKey in the [Setup] Section to only create the Registry Key for uninstall when needed.
For example , if I set a condition to create Uninstall Registry Key,
it must only be created when the condition goes True.
Otherwise the Uninstall Registry Key must not be created.
How can I do this in Inno Setup?
UPDATED QUESTION
The code I wrote is:
[Setup]
CreateUninstallRegKey=RegKeyDeterminer
[Code]
function RegKeyDeterminer(): Boolean;
begin
Result:= ISDoneError = True;
if ISDoneError = True then Result:= True;
end;
With this code , The Uninstall Registry Key is always creating. (It should be something wrong in the code I wrote.)
The Uninstall Registry Key must not be created if ISDoneError = True.
The Uninstall Registry Key must be created if ISDoneError = False.
ISDoneError only has True or False values.(It is a Boolean Function in ISDone.dll which is a Dynamic Link Library that used to extract files from 7-Zip,RAR,Binary etc. archives in Inno Setup.)
These are the conditions.
If you can see any mistakes or condition setting errors , then correct my code.
Thank You.
The CreateUninstallRegKey directive can take a boolean expression/function as its value.
So just implement the function to return True when you need to create the key and False otherwise.
[Setup]
CreateUninstallRegKey=CreateKey
[Code]
function CreateKey: Boolean;
begin
Result := condition;
end;

Inno setup hide installation items when switching from one installation to ther other

I should need your help.
I wonder if there is a possibility in Inno to set 2 different installation masks for 2 products (by selecting from the Dropdown).
We will call the 2 different installations “SETUP” and “PROGRAM”.
When Installing “SETUP” we should have the possibility to check/uncheck boxes for:
A.exe , B.exe, C.exe and D.exe that will be installed (no other check boxes should be seen).
When installing “PROGRAM” we should have the possibility to check/uncheck boxes for
A.exe, B.exe (common to “SETUP”), F.exe and G.exe (no other boxes should be seen).
I tried to add the “Flags : fixed” in [Components] section but am unable to hide the checkboxes linked to the other installation (from the drop down menu when selecting to install SETUP or PROGRAM we see the “greyed ”check box).
Is there a way to hide completely “C.exe” and “D.exe” when installing “PROGRAM” and hide completely “F.exe” and “G.exe” when installing “SETUP” ?
Thanks in advance for your help.
Meleena.
To hide components at runtime, the only way I can think of (in current version) is deleting the items from the components list. At this time, you can reliably identify component only by its description, so the idea in this code is making a list of component descriptions, iterating ComponentsList and deleting all that matches by its description:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Components]
Name: "ProgramA"; Description: "{cm:CompDescrProgramA}";
Name: "ProgramB"; Description: "{cm:CompDescrProgramB}";
Name: "ProgramC"; Description: "{cm:CompDescrProgramC}";
Name: "ProgramD"; Description: "{cm:CompDescrProgramD}";
[CustomMessages]
; it's much better for maintenance to store component descriptions
; into the [CustomMessages] section
CompDescrProgramA=Program A
CompDescrProgramB=Program B
CompDescrProgramC=Program C
CompDescrProgramD=Program D
[Code]
function ShouldHideCD: Boolean;
begin
// here return True, if you want to hide those components, False
// otherwise; it is the function which identifies the setup type
Result := True;
end;
procedure DeleteComponents(List: TStrings);
var
I: Integer;
begin
// iterate component list from bottom to top
for I := WizardForm.ComponentsList.Items.Count - 1 downto 0 do
begin
// if the currently iterated component is found in the passed
// string list, delete the component
if List.IndexOf(WizardForm.ComponentsList.Items[I]) <> -1 then
WizardForm.ComponentsList.Items.Delete(I);
end;
end;
procedure InitializeWizard;
var
List: TStringList;
begin
// if components should be deleted, then...
if ShouldHideCD then
begin
// create a list of component descriptions
List := TStringList.Create;
try
// add component descriptions
List.Add(ExpandConstant('{cm:CompDescrProgramC}'));
List.Add(ExpandConstant('{cm:CompDescrProgramD}'));
// call the procedure to delete components
DeleteComponents(List);
finally
// and free the list
List.Free;
end;
end;
end;
Note, that once you'll delete the items from the ComponentsList, you cannot add them back because each item is holding a TItemState object instance which is released at deletion and there's no way to create nor define such object from script.

Inno Setup crashes in appendChild msxml

I want to modify xml file in Inno Setup - but installer crashes. I tried different things, and as result got sample code with problem
procedure testXml();
var
xmlDocLocal, nodeLocal: Variant;
begin
try
xmlDocLocal := CreateOleObject('MSXML2.DOMDocument');
xmlDocLocal.async := False;
xmlDocLocal.resolveExternals := False;
xmlDocLocal.loadXML('<root></root>');
nodeLocal := xmlDocLocal.CreateElement('element1');
xmlDocLocal.documentElement.appendChild(nodeLocal);
except
end;
end;
During second call, installer crashes on the appendChild method. What am I doing wrong ?
Three ideas: first, we're using InnoSetup, but for us the OleObject needs to be created with another string ending with the specific version 6.0:
try
XMLDoc := CreateOleObject('MSXML2.DOMDocument.6.0');
except
RaiseException('Please install MSXML first.'#13#13'(Error ''' + GetExceptionMessage + ''' occurred)');
end;
Second idea: try adding an xml header to the XML string you have in your code. Like this:
xmlDocLocal.loadXML('<?xml version="1.0" encoding="UTF-8" ?><root></root>');
Third idea: try checking for errors (as I already showed in the first snippet). That might give you a pretty good idea what goes wrong. This is how we do it (and it works):
XMLDoc.load(XMLFileName);
if XMLDoc.parseError.errorCode <> 0 then
XMLDoc.load(XMLFileName2);
if XMLDoc.parseError.errorCode <> 0 then
RaiseException('Error on line ' + IntToStr(XMLDoc.parseError.line) + ', position ' + IntToStr(XMLDoc.parseError.linepos) + ': ' + XMLDoc.parseError.reason);
Hope this helps you. Hard to solve an unknown issue ;-)
Though this is an old issue, I would like to bring it up once more. I am using InnoSetup 6 and have spent two days working on this until I found this stackoverflow issue. For me it seems that the problem is still there. My installer keeps crashing with an Access Violation and I boilt it down to a very similar example like the one above. It makes no difference if I use createElement or createNode.
xmlDocument := CreateOleObject('MSXML2.DOMDocument.6.0');
xmlDocument.async := false;
xmlDocument.resolveExternals := false;
xmlDocument.loadXML('<broker><web bind="http://localhost:8161"></web></broker>');
//xmlDocument.setProperty('SelectionLanguage', 'XPath');
// Select the <web> node
node := xmlDocument.selectSingleNode('/broker/web');
// save attribute value into variable
bind := node.getAttribute('bind');
// remove legacy attribute
node.removeAttribute('bind');
// add new <binding> element as first child of <web>
//newNode := xmlDocument.createNode(1, 'binding', '');
newNode := xmlDocument.createElement('binding');
newNode.setAttribute('uri', bind);
Log(Format('### Appending %s as first child of %s', [newNode.xml, node.xml]));
node.appendChild(newNode);
Log(Format('### Inserted %s as first child of %s', [newNode.xml, node.xml]));
xmlDocument.Save(bootConfig);
All I see when running the code above is this:
The difference with createElement and createNode is, that createElement creates the exception message above and createNode simply kills the installer silently.
The last I see in the logs is this line:
2022-04-25 13:44:47.597 ### Appending <binding uri="http://localhost:8161"/> as first child of <web></web>
2022-04-25 13:44:47.597 CurStepChanged raised an exception.
2022-04-25 13:44:47.597 Exception message:
2022-04-25 13:44:47.597 Message box (OK):
Runtime error (at 211:2827):
Access violation at address 03CC8380. Execution of address 03CC8380.
Has this been addressed in some way? I cannot see from Russel Jordan's site that there has been any bugfix for this.

Hook standard Inno Setup checkbox

I added an InputOptionWizardPage for selecting tasks. This works fine, but I would like to add some custom functionality. One task is dependent on the other, so if the second checkbox is checked, the first should be checked and grayed out.
To do this, I need to access the properties of a checkbox. I found ways to do this using a completely custom page, where I would explicitly create the checkbox myself, but that would be a lot of work, since most of what I have so far is satisfactory.
How can I hook a checkbox that was created by Inno Setup, using MyInputOptionWizardPage.Add('This will add a checkbox with this caption')?
In attempt to answer your question directly.
I suspect you have used CreateInputOptionPage() which returns a TInputOptionWizardPage
This has the '.Add('Example')` method that you mention.
TInputOptionWizard descends TWizardPage which descends from TComponent which has the methods you need.
Update: Replaced original Code, this example is based on a review of options available in the InnoSetup source code of ScriptClasses_C.pas My original example I thought
that TRadioButton and TCheckBox where individual controls. They instead its one control called TNewCheckListBox. There is a couple of ways someone could pull this off but the safest way is to use.
This example is a complete Inno Setup Script.
[Setup]
AppName='Test Date Script'
AppVerName='Test Date Script'
DefaultDirName={pf}\test
[Code]
const
cCheckBox = false;
cRadioButton = true;
var
Opt : TInputOptionWizardPage;
function BoolToStr(Value : Boolean) : String;
begin
if Value then
result := 'true'
else
result := 'false';
end;
procedure ClickEvent(Sender : TObject);
var
Msg : String;
I : Integer;
begin
// Click Event, allowing inspection of the Values.
Msg := 'The Following Items are Checked' +#10#13;
Msg := Msg + 'Values[0]=' + BoolToStr(Opt.Values[0]) +#10#13;
Msg := Msg + 'Values[1]=' + BoolToStr(Opt.Values[1]) +#10#13;
Msg := Msg + 'Values[2]=' + BoolToStr(Opt.Values[2]);
MsgBox(Msg,mbInformation,MB_OK);
end;
procedure InitializeWizard();
var
I : Integer;
ControlType : Boolean;
begin
ControlType := cCheckBox;
Opt := CreateInputOptionPage(1,'Caption','Desc','SubCaption',ControlType, false);
Opt.Add('Test1');
Opt.Add('Test2');
Opt.Add('Test3');
// Assign the Click Event.
Opt.CheckListBox.OnClickCheck := #ClickEvent;
end;
You can also control tasks by parent relationships, it gives you a similar behavior to what your asking for but is not 100% the same. I know this does not answer your question directly, but intends to give you an option that maybe easier to implement. Doing it this way you don't have to worry about managing a custom dialog at all.
[Setup]
;This allows you to show Lines showing parent / Child Relationships
ShowTasksTreeLines=yes
[Tasks]
;Parent Tasks don't use "\"
Name: p1; Description: P1 Test;
;Child Tasks are named ParentTaskName\ChildTaskName
;Flag don't inheritcheck:Specifies that the task
;should not automatically become checked when its parent is checked
Name: p1\c1; Description: C1 Test; Flags: dontinheritcheck;
Name: p1\c2; Description: C2 Test;
;Default behavior is that child must be selected
;when a parent is selected
;this can be overridden using the:
;doninheritcheck flag and the checkablealone flag.
Name: p2; Description: P2 Test; Flags: checkablealone;
Name: p2\c1; Description: P2-C1 Test; Flags: dontinheritcheck;
Name: p2\c2; Description: P2-C2 Test; Flags: dontinheritcheck;

Resources