Validate a /DIR param and default {app} value - inno-setup

There is a way to validate the value passed by command line with /DIR= parameter? Something like this:
C:\>MySetup.exe /DIR="An\invalid\path\here"
By validate I mean: if the directory doesn't exist, I would like to use the default value of the constant {app}, considering that the software may already be installed (UserPreviousAppDir=yes).
I tried to validate the value passed by /DIR= with CurPageChanged() in [Code] section.
[Code]
procedure CurPageChanged(CurPageID: Integer);
var
Dir, DirCmd: String;
begin
if (CurPageID = wpSelectDir) then
begin
// default directory
Dir := ExpandConstant('{app}'); // <- Error here
// test /DIR parameter
DirCmd := ExpandConstant('{param:DIR|0}');
if ( DirExists(DirCmd) ) then
Dir := DirCmd;
// set Select Destination Location page
WizardForm.DirEdit.Text := Dir;
end;
end;
The problem I see is that before the Select Destination Location page the constant {app} has not been defined yet and WizardDirValue() has the same value passed by /DIR=. So I can check that the directory do or not exists, but I can't find a way to replace it with the default value of {app} if no /DIR= had been used.

There is a solution using AppId and InstallLocation for scripts with Uninstall configuration.
[Code]
function GetAppIdString(): String;
var
S: String;
begin
S := '{#SetupSetting("AppId")}';
// ignore first { when AppId={{
if ( (S[1] = '{') and (S[2] = '{') ) then
S := Copy(S, 2, Length(S) - 1);
Result := S;
end;
function GetRegistryInstallLocation(): String;
var
RegKey: String;
Path: String;
begin
// InnoSetup uninstall registry key
RegKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + GetAppIdString() + '_is1\';
// try LocalMachine and CurrentUser
if not RegQueryStringValue(HKEY_LOCAL_MACHINE, RegKey, 'InstallLocation', Path) then
if not RegQueryStringValue(HKEY_CURRENT_USER , RegKey, 'InstallLocation', Path) then
Path := '';
// result
Result := Path;
end;
procedure InitializeWizard();
var
Dir, DirCmd: String;
begin
// default directory
//Dir := ExpandConstant('{app}'); // <- Error here
Dir := GetRegistryInstallLocation();
// test /DIR parameter
DirCmd := ExpandConstant('{param:DIR|0}');
if ( DirExists(DirCmd) ) then
Dir := DirCmd;
// set Select Destination Location page
WizardForm.DirEdit.Text := Dir;
end;

Related

Read installation path from JSON file in Inno Setup

I want to create a script for Inno Setup where the install path would be taken from a file in defined directory - no registry. I suppose it would require writing specific code for it, where would be defined some variable which will contain the value after reading the file. The path and name of the file is always the same for any user so the only value that changes is the install path.
Complete structure, where InstallLocation is the variable:
{
"FormatVersion": 0,
"bIsIncompleteInstall": false,
"AppVersionString": "1.0.1",
...
"InstallLocation": "h:\\Program Files\\Epic Games\\Limbo",
...
}
Any ideas for ideal code that would do this?
Thank you
Implement a scripted constant to provide the value to DefaultDirName directive.
You can use JsonParser library to parse the JSON config file.
[Setup]
DefaultDirName={code:GetInstallLocation}
[Code]
#include "JsonParser.pas"
// Here go the other functions the below code needs.
// See the comments at the end of the post.
const
CP_UTF8 = 65001;
var
InstallLocation: string;
<event('InitializeSetup')>
function InitializeSetupParseConfig(): Boolean;
var
Json: string;
ConfigPath: string;
JsonParser: TJsonParser;
JsonRoot: TJsonObject;
S: TJsonString;
begin
Result := True;
ConfigPath := 'C:\path\to\config.json';
Log(Format('Reading "%s"', [ConfigPath]));
if not LoadStringFromFileInCP(ConfigPath, Json, CP_UTF8) then
begin
MsgBox(Format('Error reading "%s"', [ConfigPath]), mbError, MB_OK);
Result := False;
end
else
if not ParseJsonAndLogErrors(JsonParser, Json) then
begin
MsgBox(Format('Error parsing "%s"', [ConfigPath]), mbError, MB_OK);
Result := False;
end
else
begin
JsonRoot := GetJsonRoot(JsonParser.Output);
if not FindJsonString(JsonParser.Output, JsonRoot, 'InstallLocation', S) then
begin
MsgBox(Format('Cannot find InstallLocation in "%s"', [ConfigPath]),
mbError, MB_OK);
Result := False;
end
else
begin
InstallLocation := S;
Log(Format('Found InstallLocation = "%s"', [InstallLocation]));
end;
ClearJsonParser(JsonParser);
end;
end;
function GetInstallLocation(Param: string): string;
begin
Result := InstallLocation;
end;
The code uses functions from:
How to parse a JSON string in Inno Setup? (ParseJsonAndLogErrors, ClearJsonParser, GetJsonRoot, FindJsonValue and FindJsonString);
Inno Setup - Convert array of string to Unicode and back to ANSI (MultiByteToWideChar and LoadStringFromFileInCP).

Check installation path for spaces and special symbol in Inno Setup

I cannot find a solution for checking the user selected path to be without any spaces or special characters.
Can you help me?
You can check for space like this:
[Code]
function NextButtonClick(CurPageID: Integer): Boolean;
var
Dir: string;
Msg: string;
begin
Result := True;
if CurPageID = wpSelectDir then
begin
Dir := WizardForm.DirEdit.Text;
if Pos(' ', Dir) > 0 then
begin
Msg := 'The path cannot contain spaces';
if WizardSilent then Log(Msg)
else MsgBox(Msg, mbError, MB_OK);
Result := False;
end;
end;
end;
You may consider using SuppressibleMsgBox function:
What does it mean that message boxes are being suppressed in Inno Setup?

Inno Setup Get default browser

I have a software which requires the default browser installed on user computer.
Is there a way that I can get it?
Thanks
An solution that correctly works on modern versions of Windows cannot be based on association with http protocol, as that's no longer reliable. It should rather be based on a solution like the answer by #GregT to How to determine the Windows default browser (at the top of the start menu).
So something like:
function GetBrowserCommand: string;
var
UserChoiceKey: string;
HtmlProgId: string;
begin
UserChoiceKey :=
'Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.html\UserChoice';
if RegQueryStringValue(HKCU, UserChoiceKey, 'ProgId', HtmlProgId) then
begin
Log(Format('ProgID to registered for .html is [%s].', [HtmlProgId]));
if RegQueryStringValue(HKCR, HtmlProgId + '\shell\open\command', '', Result) then
begin
Log(Format('Command for ProgID [%s] is [%s].', [HtmlProgId, Result]));
end;
end;
{ Fallback for old version of Windows }
if Result = '' then
begin
if RegQueryStringValue(HKCR, 'http\shell\open\command', '', Result) then
begin
Log(Format('Command registered for http: [%s].', [Result]));
end;
end;
end;
If you want to extract browser path from the command, use a code like:
function ExtractProgramPath(Command: string): string;
var
P: Integer;
begin
if Copy(Command, 1, 1) = '"' then
begin
Delete(Command, 1, 1);
P := Pos('"', Command);
end
else P := 0;
if P = 0 then
begin
P := Pos(' ', Command);
end;
Result := Copy(Command, 1, P - 1);
end;
(based on Executing UninstallString in Inno Setup)
Take this:
function GetBrowser() : String;
var
RegistryEntry: String;
Browser: String;
Limit: Integer ;
begin
if RegQueryStringValue(HKEY_CLASSES_ROOT, 'http\shell\open\command', '', RegistryEntry) then
begin
Limit := Pos('.exe' ,RegistryEntry)+ Length('.exe');
Browser := Copy(RegistryEntry, 1, Limit );
MsgBox('Your browser: ' + Browser , mbInformation, MB_OK);
end;
end;

Discard the /TYPE and /COMPONENTS parameter on upgrade

In case of an upgrade / re-installation, is there a way to discard the /TYPE and /COMPONENTS parameter value passed on the command line to the installer and instead use the previously used values ?
I can read the values used earlier from Registry (or alternatively make out the details based on existence of files assuming they have not been manually altered)
I have read the following threads and can disable the "Select Components" page in UI mode
Inno Setup Skip "Select Components" page when /Type command-line parameter is specified
InnoSetup: Disable components page on upgrade
However, if the aforesaid parameters are passed from command line, they seem to override the defaults.
You cannot discard them.
What you can do is to check if those parameters were provided and if they were:
Re-launch the installer without them (show below), or
Read the previously selected type and components from registry and re-set the controls accordingly.
Re-launching the installer without /TYPE= and /COMPONENTS=
const
UninstallKey =
'Software\Microsoft\Windows\CurrentVersion\Uninstall\' +
'{#SetupSetting("AppId")}_is1';
function IsUpgrade: Boolean;
var
Value: string;
begin
Result :=
(RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or
RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and
(Value <> '');
end;
function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string;
lpParameters: string; lpDirectory: string; nShowCmd: Integer): THandle;
external 'ShellExecuteW#shell32.dll stdcall';
function InitializeSetup(): Boolean;
var
Params, S: string;
Relaunch: Boolean;
I, RetVal: Integer;
begin
Result := True;
if IsUpgrade then
begin
Relaunch := False;
// Collect current instance parameters
for I := 1 to ParamCount do
begin
S := ParamStr(I);
if (CompareText(Copy(S, 1, 7), '/TYPES=') = 0) or
(CompareText(Copy(S, 1, 12), '/COMPONENTS=') = 0) then
begin
Log(Format('Will re-launch due to %s', [S]));
Relaunch := True;
end
else
begin
// Unique log file name for the child instance
if CompareText(Copy(S, 1, 5), '/LOG=') = 0 then
begin
S := S + '-sub';
end;
// Do not pass our /SL5 switch
// This should not be needed since Inno Setup 6.2,
// see https://groups.google.com/g/innosetup/c/pDSbgD8nbxI
if CompareText(Copy(S, 1, 5), '/SL5=') <> 0 then
begin
Params := Params + AddQuotes(S) + ' ';
end;
end;
end;
if not Relaunch then
begin
Log('No need to re-launch');
end
else
begin
Log(Format('Re-launching setup with parameters [%s]', [Params]));
RetVal :=
ShellExecute(0, '', ExpandConstant('{srcexe}'), Params, '', SW_SHOW);
Log(Format('Re-launching setup returned [%d]', [RetVal]));
Result := (RetVal > 32);
// if re-launching of this setup succeeded, then...
if Result then
begin
Log('Re-launching succeeded');
// exit this setup instance
Result := False;
end
else
begin
Log(Format('Elevation failed [%s]', [SysErrorMessage(RetVal)]));
end;
end;
end;
end;
The code is for Unicode version of Inno Setup.
The code can be further improved to keep the master installer waiting for the child installer to complete. When can make a difference, particularly if the installer is executed by some automatic deployment process.

Use a part of a registry key/value in the Inno Setup script

I have a need to retrieve a path to be used for some stuffs in the installer according an other application previously installed on the system.
This previous application hosts a service and only provides one registry key/value hosting this information: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\APPLICATION hosting the value ImagePath which Data is "E:\TestingDir\Filename.exe".
I need a way to only extract the installation path (E:\TestingDir) without the Filename.exe file.
Any suggestion?
thanks a lot
You can achieve this using a scripted constant.
You define a function that produces the value you need:
[Code]
function GetServiceInstallationPath(Param: string): string;
var
Value: string;
begin
if RegQueryStringValue(
HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Services\APPLICATION',
'ImagePath', Value) then
begin
Result := ExtractFileDir(Value);
end
else
begin
Result := { Some fallback value }
end;
end;
And then you refer to it using {code:GetServiceInstallationPath} where you need it (like in the [Run] section).
For example:
[Run]
Filename: "{code:GetServiceIntallationPath}\SomeApp.exe"
Actually, you probably want to retrieve the value in InitializeSetup already, and cache the value in a global variable for use in the scripted constant. And abort the installation (by returning False from InitializeSetup), in case the other application is not installed (= the registry key does not exist).
[Code]
var
ServiceInstallationPath: string;
function InitializeSetup(): Boolean;
var
Value: string;
begin
if RegQueryStringValue(
HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Services\APPLICATION',
'ImagePath', Value) then
begin
ServiceInstallationPath := ExtractFileDir(Value);
Log(Format('APPLICATION installed to %s', [ServiceInstallationPath]));
Result := True;
end
else
begin
MsgBox('APPLICATION not installed, aborting installation', mbError, MB_OK);
Result := False;
end;
end;
function GetServiceInstallationPath(Param: string): string;
begin
Result := ServiceInstallationPath;
end;
See also a similar question: Using global string script variable in Run section in Inno Setup.
Solved this way:
[code]
var
ServiceInstallationPath: string;
function MyProgCheck(): Boolean;
var
Value: string;
begin
if RegQueryStringValue(
HKEY_LOCAL_MACHINE, 'SYSTEM\ControlSet001\Services\JLR STONE VCATS TO MES',
'ImagePath', Value) then
begin
ServiceInstallationPath := ExtractFileDir(Value);
Result := True;
end
else
begin
Result := False;
end;
end;
and in the [RUN] section I put as check the TRUE condition or FALSE condition on this function according the needs...Thanks everybody answering!

Resources