How to configure Inno Setup to uninstall everything? - inno-setup

I am new to Inno Setup. Stuck on one issue ~ how to configure the uninstall piece to remove all files, folders, subfolders, and even new files/folders etc. created by application (in other words, a 100% removal of the application and associated files).
I hunted around here and also on their forum, and came up empty. Can anyone point me to a document, FAQ etc. regarding how to do this?
UPDATE
Thanks for all the feedback so far (very awesome). So it looks like I can delete everything using the {app}*.* directive in the uninstall section. Also looks like everyone is advising against it. So the question now becomes (I am wondering if this should be a totally new question) is there a way during the uninstall that we can ask the user 'Do you want to remove all project files associated with this application?' and if they answer YES, to run the uninstall {app}*.* piece?
Thanks -

I think the recommended approach is to specify what to remove in the uninstall section. The reasoning is that what if for whatever reason the user decided to put their own files in your installation directory that they didn't want removed, or saved data that they might want to keep around (maybe they uninstall is to install a newer version?)
That being said, I don't know offhand what the script is, but if you use ISTool (highly recommend) just got to the Uninstall Delete section and add things you want removed. It should present all the possible options in a nice GUI and generate the script for you.
Edit: An example from the Inno Setup documentation:
[UninstallDelete]
Type: files; Name: "{win}\MYPROG.INI"
But they strongly you don't do something like
[UninstallDelete]
Type: files; Name: "{app}\*.*"
NOTE: Don't be tempted to use a wildcard here to delete all files in
the {app} directory. I strongly
recommend against doing this for two
reasons. First, users usually don't
appreciate having their data files
they put in the application directory
deleted without warning (they might
only be uninstalling it because they
want to move it to a different drive,
for example). It's better to leave it
up to the end users to manually remove
them if they want. Also, if the user
happened to install the program in the
wrong directory by mistake (for
example, C:\WINDOWS) and then went to
uninstall it there could be disastrous
consequences. So again, DON'T DO THIS!

You should probably have made this a totally new question, but I'll answer your updated question here as well. Have a look at the section "Pascal Scripting: Uninstall Code" in the Inno Setup Documentation.
To give an example how to conditionally delete data files as part of the uninstallation process:
[Code]
procedure DeleteBitmaps(ADirName: string);
var
FindRec: TFindRec;
begin
if FindFirst(ADirName + '\*.*', FindRec) then begin
try
repeat
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY <> 0 then begin
if (FindRec.Name <> '.') and (FindRec.Name <> '..') then begin
DeleteBitmaps(ADirName + '\' + FindRec.Name);
RemoveDir(ADirName + '\' + FindRec.Name);
end;
end else if Pos('.bmp', AnsiLowerCase(FindRec.Name)) > 0 then
DeleteFile(ADirName + '\' + FindRec.Name);
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usUninstall then begin
if MsgBox('Do you want to delete all data files?', mbConfirmation,
MB_YESNO) = IDYES
then begin
DeleteBitmaps(ExpandConstant('{app}'));
end;
end;
end;
But depending on the amount of stuff you need to clean up you might be better off to create a special helper program that is part of the installation, and which can be executed during the uninstallation of the app (using an entry in the [UninstallRun] section).

There are cases to want to delete files which were not initially written to the user's disk at time of installation. One of these cases is when you have an application that updates itself when it is started. New files can be added to the disk in this manner which are not a part of the uninstaller.
For this case I suggest that you create a "patch manifest" file which keeps a running record of what files should be in the {app} directory. Find below a sample of code that reads from a file in the {app} dir called 'patch_manifest.txt'
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
i: Integer;
arrayLen: Longint;
item: String;
itemsToDelete: Array of String;
begin
case CurUninstallStep of
usUninstall:
begin
LoadStringsFromFile(ExpandConstant('{app}') + '\patch_manifest.txt', itemsToDelete);
arrayLen := GetArrayLength(itemsToDelete);
for i := 0 to arrayLen-1 do
begin
item := ExpandConstant('{app}') + '\' + itemsToDelete[i];
if FileExists(item) then
DeleteFile(item);
if DirExists(item) then
RemoveDir(item);
end;
end;
end;
end;
and a sample of the patch_manifest.txt
data/something_here.dat
data/moredatahere.dat
data/
Launcher.exe
patch_manifest.txt
Note: The order of the lines in the patch_manifest is important. All files within a directory should first be listed followed by the directory - directories which are not empty cannot be delete.
Your application should be shipped with a patch_manifest and the patch_manifest should be updated with every patch. Make this part of your build process so you don't forget to update it!
It is very important that you do not delete by wildcard (.) even if you prompt the user. Uninstaller's have elevated privileges which could potentially destroy a user's computer. Take the case of a user who accidentally installed your application to C:\Windows\ or C:\Program Files.
Another good idea is to verify that the file is in fact "your file" by performing an MD5 check prior to deleting it. In this case your patch_manifest.txt would not only include the relative path to the file but also the MD5 checksum.

You can't use InnoSetup to uninstall anything it didn't install, and you shouldn't want to do so. I for one would be very unhappy if I installed an application, entered a lot of data, and then decided to use something else instead that would read that data from your app. If your uninstall killed all of the work I'd already done, I might be tempted to come looking for you. And probably not to buy you a cup of coffee.
Or consider the case where I install your application in the process of evaluating several. I try the apps, and keep going back to yours because I like it a little better, and each time I enter more data. Then I decide not to do anything for a while, so I remove all the test apps. In a week, I decide I need to use your app now after all, and I reinstall it. Oops! All of the work I did testing it that I now wanted to use is gone.
The above is why, when you uninstall an application, it leaves behind anything you created in the folders, like configuration files, data files, etc. It doesn't know what you want it to do with them, so it leaves them alone.

This should do the trick:
[Dirs]
Name: "{app}"; Flags: uninsalwaysuninstall

Add delete file/folder item in Inno Setup studio. Or directly use the script as follows.
[Generated code sample]
[UninstallDelete]
Type: filesandordirs; Name: "{app}\unnecessary_files"

I wanted to delete gData.dat which created on run time in installed folder
and was working fine for me
[UninstallDelete]
Type: files; Name: "{app}\gData.dat"

Isn't that the default if your don't specify "uninsneveruninstall" for an entry?
edit - Sorry I hadn't realised you were talking about newly created data files.

To delete everything I use this :
[UninstallDelete]
Type:files; Name: "{app}"

Related

Inno Setup: Cannot call "WIZARDISCOMPONENTSELECTED" function during Uninstall

What would cause the message:
Cannot call "WIZARDISCOMPONENTSELECTED" function during Uninstall.
It has to be here:
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usUninstall then
begin
if WizardIsComponentSelected('mycomponent') then
begin
DoSomething(False);
end;
end;
end;
How do you determine if the component was selected at uninstall time? It has to undo some stuff before uninstalled. Typically Inno Setup has ways to get if something was installed or not.
There's no API in Pascal Script to tell in the uninstaller, what tasks were selected when installing. Probably because, there might have been multiple installations, each with different set of tasks. So it would be questionable, what set of tasks the API should report.
What you should (and need) to do is to check the effect of the task. Like checking if a specific file or a specific Registry key exists.
Though note that Tasks parameters still work in the uninstaller sections [UninstallDelete] and [UninstallRun]. So you can make use of them.
See also Inno Setup Uninstall some components only

Can I put setup command line parameters in a file which is called during installation instead?

After creating my setup.exe I have to pack it for various software deployment tools. Therefore I can't call the setup.exe with parameters, instead I have placed my own parameters in a setup.ini file next to the setup.exe
[Code]
var
MyIniFile: String;
function InitializeSetup(): Boolean;
var
LoadFromIniFile: String;
begin
Result := true;
MyIniFile := ExpandConstant('{srcexe}'); //writes the full path of the setup.exe in "MyIniFile"
MyIniFile := Copy(MyIniFile, 1, Length(MyIniFile) - Length(ExtractFileExt(MyIniFile))) + '.ini'; //changes the ".exe" into ".ini"
if FileExists(MyIniFile) then LoadFromIniFile := MyIniFile; //checks wether there is a ini-file
if LoadFromIniFile <> '' then begin
MyLogFile := GetIniString('Setup', 'Log', MyLogFile , LoadFromIniFile);
ProductName := GetIniString('Setup', 'ProductName', ProductName, LoadFromIniFile);
end;
end;
Now I want to also place the so called "Setup Command Line Parameters" (listed on the Inno Setup Help site) in my ini-file. I think that there is a way for the /Dir="x:\dirname parameter, which I did not figure out yet. But I also want to have the /SILENT parameter in there, do you think there is a way to do this? If yes, how would you do this? If not, can you please give me a hint why not?
So customize your installer for different products, I'd recommend you to use a pre-processor and automatically build the installer for each product (with different "defines"), instead of using an external INI file.
For example to be able to change application name and resulting executable when building the installer, use a script like:
[Setup]
AppName={#AppName}
OutputBaseFilename={#BaseFilename}
Now you can create two different installers automatically using command-line:
ISCC.exe Example1.iss /dAppName=App1 /dBaseFilename=SetupApp1
ISCC.exe Example1.iss /dAppName=App2 /dBaseFilename=SetupApp2
Regarding the implicit silent installation:
There's no API other than the command-line /SILENT switch to trigger silent installation.
But you can create a near-silent installation by disabling most installer pages:
[Setup]
DisableWelcomePage=true
DisableDirPage=true
DisableProgramGroupPage=true
DisableReadyPage=true
DisableFinishedPage=true
Actually the above example disables all default pages. But Inno Setup compiler will ignore the DisableReadyPage=true, if all other previous pages are disabled.
You may want to choose a different page to show instead. For example a Welcome page (by omitting DisableWelcomePage=true, but keeping the DisableReadyPage=true).
If you do not mind about using external files (as you already use an external INI file), you can of course wrap the installer to a batch file and call the installer with the /SILENT switch.

Inno Setup error when installing to USB drive root: "You must enter a full path with drive letter"

I would like to know how to fix this kind of Error:
You must enter a full path with drive letter; for example: C:\APP or a UNC path in the form: \server\share
This appears whenever I try to force the Inno Setup Compiler (5.5.5 u) to put my stuff into, let say H:\ instead of H:\New Folder.
I need the compiler to customize my destination location to H:\.
Here is my sample program;
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={drive:F:}
AppendDefaultDirName=no
[Files]
Source: "File1.txt"; DestDir: "{code:GetExeLocation|{app}\My_Portable_App}"; \
Flags: ignoreversion
[Code]
var
UsagePage: TInputOptionWizardPage;
procedure InitializeWizard;
begin
{ Create the pages }
UsagePage := CreateInputOptionPage(wpWelcome,
'Installation Type', 'Select Installation Option',
'Where would you like to install this program',
True, False);
UsagePage.Add('Normal – PC Hard Disk Installation ');
UsagePage.Add('Portable – USB Drive Installation');
{Set Default – Normal Install}
UsagePage.SelectedValueIndex := 0;
end;
var
bIsPortable : Boolean;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
// If we have clicked the next button on our custom page.
if (CurPageID = UsagePage.ID) then
begin
bIsPortable := not (UsagePage.SelectedValueIndex = 0);
end;
result := true; // need to return a value so next key has an impact
end;
function GetExeLocation (Param: String) : String;
begin
if bIsPortable then
result := Param
else
result := Param;
end;
function InstallType(sRequiredType:String):Boolean;
begin
if sRequiredType = 'Portable' then
result := bIsPortable
else
result := not bIsPortable;
end;
Explanation:
When I select "Normal – PC Hard Disk Installation", as my choice, all my installation files or folders should go to normal Path that is to C:\My Program, but when I select "Portable – USB Drive Installation" as my Entry, I would like to put all my installation files or folders directly into the USB Pen drive Root, that is in here H:\, where "H" is my USB Pen Drive letter which I have selected to put my stuff in. But my Program does not allow me to do so, instead it adds a New Folder by default to put my installation files or folders over there, that is to H:\New Folder which I do not need that at all!. And when I force to do what I want, it ends giving me an Error!
Please I need Your Help to fix this, and if this inno-setup can not do what I want, please point me another one, and I will be Thankful for that!
EDIT:
Let focus on the second choice that is "('portable – usb drive installation')" because that is my real target.
From the Source: I made some changes so that to make it more clear.
I added my destination Directory, that is {code:GetExeLocation|{app}\My_Portable_App}. So what I want here is that, all my installation files or folders to be installed inside this directory, I mean My_Portable_App. And the path to my USB pen drive should be H:\My_Portable_App. So when this goes fine, I want to see only This Folder My_Portable_App in my USB pen drive that will contain all my stuffs in there!!!
Thanks in advance!
If you want to install directly into h:\ then you should explicitly enter that into the location box. If you also want to stop the My Program being appended after using the browse dialog then you need to ensure that AppendDefaultDirName is set to no.
Also note that for file2, the DestDir will end up being set to {app}/{app} if bIsPortable is true which will most likely expand to an invalid path.
Your best option is to use a {code:...} function to create a single "default" path based on bIsPortable and then everything can just install into {app} from there.
I struggled with the validation too, where root was not valid in the TInputDirWizardPage.
As it turns out there is a simple Inno Setup option that changes this behavior:
AllowRootDirectory=yes
will allow the user to specify a drive root without error.
also see,
AllowUNCPath=yes/no
and
AllowNetworkDrive=yes/no
in the Inno help file for other validation modifiers that apply to the Select Destination Location Page.
This setup file must be located on disc c:.
That's all.

How can I create a INI-file in the {commonappdata} which can be written to by any uses when using Innosetup

If I want to create files in the {commonappdata} folder, I need to add the Permissions:users-modify parameter on a [Files] Source:... line.
This works great ( I finally figured out how to make sure no roaming folder was used when changing one of the installed files from within the installed program ).
However , how can I make INI-entries from the [INI] Filename:... section also writeable by any user on the PC ? There are program values, not user values, after all.
As it is now, the ini-file is written by the Inno-setup installation, but if I later start the installed program and change the ini file grammatically, a roaming version of the ini file is written.
To be complete:
I know about the way to create the ini-file as a template when installing your application, and than , upon first run of the program , copy them over to the the {commonappdata} folder, but I am just wondering whether this can be achieved from within the Inno-Setup script.
I am running the latest version of Inno-Setup 5.4.2 under Windows 7 Prof 64 ( if this should make a difference ).
You'll be blamed for this design. You might have contradicting user settings if you keep them in application data folders. Refer to this question for more blame. ;)
Anyway, only [Files], [Dirs] and [Registry] sections allow 'Permissions' parameter. So it is not possible for an [Ini] section to create an ini file with modified permissions. One way to achieve your task can be to use the 'ini' section to collect necessary information during setup, then as a post-install action, transfer the contents of the ini file to one with modified permissions created through the 'files' section. Something like this:
[Files]
Source: MyProg.ini; DestDir: {commonappdata}\MyCompany; Permissions: users-modify;
;// ini file contents will be transferred to this file
[Ini]
filename: {commonappdata}\MyCompany\MyProg_Temp.ini; section: users; key: username; string: {username}; Flags: UninsDeleteEntry;
;// this is used in the installation, and will be deleted in post-install
...
[Code]
procedure CurStepChanged(CurStep: TSetupStep);
var
s: string;
begin
if CurStep = ssPostInstall then begin
if LoadStringFromFile(ExpandConstant('{commonappdata}\MyCompany\MyProg_Temp.ini'), s) and
SaveStringToFile(ExpandConstant('{commonappdata}\MyCompany\MyProg.ini'), s, False) then
DeleteFile(ExpandConstant('{commonappdata}\MyCompany\MyProg_Temp.ini'));
end;
end;
Inno Setup allows to set permissions only in sections [Files] [Dirs] and [Registry] and you'll have to use those somehow. Here are 2 solution to this problem. Both are good but each with a slight disadvantage.
Solution #1: Set permission to the entire directory
[Dirs]
Name: {commonappdata}\MyCompany; Permissions:everyone-modify
[INI]
Filename: {commonappdata}\MyCompany\MyProg.ini; Section: "SomeSection"; Key: "SomeKey"; String: "SomeValue"
This is a great solution if you don't mind to have the entire directory's permissions modified. I did mind and came up with a second solution.
Solution #2: Create your .ini file in {tmp} and copy it in the [Files] section:
#define TargetIniDir "{commonappdata}\MyCompany"
#define TargetIniName "MyProg.ini"
....
[Files]
Source: {tmp}\{#TargetIniName}; DestDir: {#TargetIniDir}; Flags:external; Permissions: users-modify;
....
[Code]
procedure PrepareIniFileForCopy(section, key, value, iniFileTemp, iniFileTarget:String);
begin
if FileExists(iniFileTarget) then
FileCopy(iniFileTarget, iniFileTemp, False);
SetIniString(section, key, value, iniFileTemp);
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
iniFile, iniFileTemp:String;
begin
if CurStep=ssInstall then begin
PrepareIniFileForCopy('SomeSection', 'SomeKey', 'SomeValue', ExpandConstant('{tmp}\{#TargetIniName}'), ExpandConstant('{#TargetIniDir}\{#TargetIniName}'));
end;
end;
This will create your .ini file in the {tmp} directory (which will be deleted after install finishes), then copied in the [Files] section to the desired commondata directory with desired permissions.
Note the external flag in the [Files] section which means the file isn't packed to the setup at compile time, rather taken dynamically at installation time. Also note that the creation of the temporary file must be done before the install (CurStep=ssInstall means right before installation).
I think this solution is good, but not very pretty. You split your operation to two different places, that rely one one being done before the other.
Both solution can add values to existing .ini files, not just create new ones.

Inno setup and DefaultDirName

Is there some way to set the DefaultDirName by code depending on some decission a user did on installtion?
Let me comment:
I have some code which is build for two different systems (using different interops/ocx's and such stuff). My input files are stored in two directories input\A and input\B.
I want to have only one setup-file for both systems.
In the setup file i use CreateInputOptionPage with 2 options to determin which files to install (using Check on each file). This works okay.
But i do also have some ShellExec on finish of setup, which at the moment uses {app} to e.g. register some .Net classes and ShellExec to unregister the .Net classes on InitializeUninstall (also uses {app})
The setup must install the software on two different locations (depending on the selection of the user (eg. c:\software_a or c:\software_b). Can't change this.
So is there some way to specify the DefaultDirName before the files get copied to the system, so i can use the same ShellExec on install and uninstall? I could of course add the same ShellExec for both systems on installtation and use an if to check which files to register (depending on the user selection) but on uninstall i would not have this information (user selection), so i can not unregister the .Net classes.
thanks
In your CreateInputOptionPage code section, you might be able to set a value then use that value in the code snippet below. I haven't tested it but it might work.
[Setup]
DefaultDirName={code:getpath}
[Code]
function GetPath( Default: string ): string;
begin
if (CreateInputOptionPageValue1) then
Result := ExpandConstant({sd}) + '\path1';
else
Result := ExpandConstant({sd}) + '\path2';
end;
If you need to change the install folder after the DefaultDirName has been initialized, this was working for me quite well:
procedure CurPageChanged(CurPageID: Integer);
begin
{ updates the install path depending on the install type or the entered suffix }
if CurPageID = wpSelectDir then begin
WizardForm.DirEdit.Text := ExpandConstant('{pf}') + '\MyAppName' + GetAppSuffix('');
end;
end;
Cheers
Chris

Resources