Is it possible to accept custom command line parameters with Inno Setup - command-line

I am preparing an installer with Inno Setup. But I'd like to add an additional custom (none of the available parameters) command line parameters and would like to get the value of the parameter, like:
setup.exe /do something
Check if /do is given, then get the value of something. Is it possible? How can I do this?

With InnoSetup 5.5.5 (and perhaps other versions), just pass whatever you want as a parameter, prefixed by a /
c:\> myAppInstaller.exe /foo=wiggle
and in your myApp.iss:
[Setup]
AppName = {param:foo|waggle}
The |waggle provides a default value if no parameter matches. Inno setup is not case sensitive. This is a particularly nice way to handle command line options: They just spring into existence. I wish there was as slick a way to let users know what command line parameters the installer cares about.
BTW, this makes both #knguyen's and #steve-dunn's answers somewhat redundant. The utility functions do exactly what the built-in {param: } syntax does.

Further to #DanLocks' answer, the {param:*ParamName|DefaultValue*} constant is documented near the bottom of the Constants page:
http://www.jrsoftware.org/ishelp/index.php?topic=consts
I found it quite handy for optionally suppressing the license page. Here is all I needed to add (using Inno Setup 5.5.6(a)):
[code]
{ If there is a command-line parameter "skiplicense=true", don't display license page }
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := False
if PageId = wpLicense then
if ExpandConstant('{param:skiplicense|false}') = 'true' then
Result := True;
end;

Inno Setup directly supports switches with syntax /Name=Value using {param} constant.
You can use the constant directly in sections, though this use is quite limited.
An example:
[Registry]
Root: HKCU; Subkey: "Software\My Company\My Program\Settings"; ValueType: string; \
ValueName: "Mode"; ValueData: "{param:Mode|DefaultMode}"
You will more likely want to use switches in Pascal Script.
If your switch has the syntax /Name=Value, the easiest way to read its value is using ExpandConstant function.
For example:
if ExpandConstant('{param:Mode|DefaultMode}') = 'DefaultMode' then
begin
Log('Installing for default mode');
end
else
begin
Log('Installing for different mode');
end;
If you want to use a switch value to toggle entries in sections, you can use Check parameter and a auxiliary function, like:
[Files]
Source: "Client.exe"; DestDir: "{app}"; Check: SwitchHasValue('Mode', 'Client')
Source: "Server.exe"; DestDir: "{app}"; Check: SwitchHasValue('Mode', 'Server')
[Code]
function SwitchHasValue(Name: string; Value: string): Boolean;
begin
Result := CompareText(ExpandConstant('{param:' + Name + '}'), Value) = 0;
end;
Ironically it is more difficult to check for a mere presence of switch (without a value).
Use can use a function CmdLineParamExists from #TLama's answer to Passing conditional parameter in Inno Setup.
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
if CompareText(ParamStr(I), Value) = 0 then
begin
Result := True;
Exit;
end;
end;
You can obviously use the function in Pascal Script:
if CmdLineParamExists('/DefaultMode') then
begin
Log('Installing for default mode');
end
else
begin
Log('Installing for different mode');
end;
But you can even use it in sections, most typically using Check parameter:
[Files]
Source: "MyProg.hlp"; DestDir: "{app}"; Check: CmdLineParamExists('/InstallHelp')
A related problem:
Add user defined command line parameters to /? window

If you want to parse command line arguments from code in inno, then use a method similar to this. Just call the inno script from the command line as follows:
c:\MyInstallDirectory>MyInnoSetup.exe -myParam parameterValue
Then you can call the GetCommandLineParam like this wherever you need it:
myVariable := GetCommandLineParam('-myParam');
{ ================================================================== }
{ Allows for standard command line parsing assuming a key/value organization }
function GetCommandlineParam (inParam: String):String;
var
LoopVar : Integer;
BreakLoop : Boolean;
begin
{ Init the variable to known values }
LoopVar :=0;
Result := '';
BreakLoop := False;
{ Loop through the passed in arry to find the parameter }
while ( (LoopVar < ParamCount) and
(not BreakLoop) ) do
begin
{ Determine if the looked for parameter is the next value }
if ( (ParamStr(LoopVar) = inParam) and
( (LoopVar+1) <= ParamCount )) then
begin
{ Set the return result equal to the next command line parameter }
Result := ParamStr(LoopVar+1);
{ Break the loop }
BreakLoop := True;
end;
{ Increment the loop variable }
LoopVar := LoopVar + 1;
end;
end;

This is the function I wrote, which is an improvement of Steven Dunn's answer. You can use it as:
c:\MyInstallDirectory>MyInnoSetup.exe /myParam="parameterValue"
myVariable := GetCommandLineParam('/myParam');
{ util method, equivalent to C# string.StartsWith }
function StartsWith(SubStr, S: String): Boolean;
begin
Result:= Pos(SubStr, S) = 1;
end;
{ util method, equivalent to C# string.Replace }
function StringReplace(S, oldSubString, newSubString: String): String;
var
stringCopy: String;
begin
stringCopy := S; { Prevent modification to the original string }
StringChange(stringCopy, oldSubString, newSubString);
Result := stringCopy;
end;
{ ================================================================== }
function GetCommandlineParam(inParamName: String): String;
var
paramNameAndValue: String;
i: Integer;
begin
Result := '';
for i := 0 to ParamCount do
begin
paramNameAndValue := ParamStr(i);
if (StartsWith(inParamName, paramNameAndValue)) then
begin
Result := StringReplace(paramNameAndValue, inParamName + '=', '');
break;
end;
end;
end;

Yes it is possible, you can use the ParamStr function in PascalScript to access all the commandline parameters. The ParamCount function will give you the number of commandline parameters.
Another possibility is to use GetCmdTail

In response to:
"With InnoSetup 5.5.5 (and perhaps other versions), just pass whatever you want as a parameter, prefixed by a /"
"#NickG, yes, every constant you can expand by the ExpandConstant function"
This is not the case. Trying to use a command line parameter in ExpandConstant in InnoSetup 5.5.6 results in a runtime error.
PS: I would have added a comment directly but apparently I dont have enough "reputation"

I've modified a little bit knguyen's answer. Now it's case insensitive (you can write en console /myParam or /MYPARAM) and it can accept default value. Also I fixed the case when you receive larger parameter then expected (for ex: /myParamOther="parameterValue" in place of /myParam="parameterValue". Now myParamOther doesn't match).
function GetCommandlineParam(inParamName: String; defaultParam: String): String;
var
paramNameAndValue: String;
i: Integer;
begin
Result := defaultParam;
for i := 0 to ParamCount do
begin
paramNameAndValue := ParamStr(i);
if (Pos(Lowercase(inParamName)+'=', AnsiLowercase(paramNameAndValue)) = 1) then
begin
Result := Copy(paramNameAndValue, Length(inParamName)+2, Length(paramNameAndValue)-Length(inParamName));
break;
end;
end;
end;

I found the answer: GetCmdTail.

You can pass parameters to your installer scripts. Install the Inno Setup Preprocessor and read the documentation on passing custom command-line parameters.

Related

PostgreSQL/JPA - Import functions in import.sql file?

I'm trying to define some PostgreSQL functions and triggers in my JPA import.sql file. I'm using Hibernate 5.x as my underlying JPA provider. Since my import.sql file has commands that are multiple lines, I've got this property set in my persistence.xml file:
<property name="hibernate.hbm2ddl.import_files_sql_extractor" value="org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor" />
From there, I'm trying to define my functions in import.sql. They look something like this:
DROP FUNCTION IF EXISTS update_total_feedback_count() CASCADE;
CREATE FUNCTION update_total_feedback_count() RETURNS TRIGGER AS
$$
DECLARE
application_version_id BIGINT := 0;
app_version_metadata_id BIGINT:= 0;
application_id BIGINT := 0;
app_metadata_id BIGINT := 0;
BEGIN
IF (TG_OP = 'INSERT') THEN
SELECT INTO application_version_id tbl_application_version.id
FROM tbl_application_version
INNER JOIN tbl_feedback ON tbl_feedback.application_version_id = tbl_application_version.id
WHERE tbl_feedback.id = NEW.id;
SELECT INTO app_version_metadata_id tbl_application_version.application_version_metadata_id
FROM tbl_application_version
WHERE id = application_version_id;
SELECT INTO app_metadata_id registered_application_metadata_id
FROM tbl_registered_application
INNER JOIN tbl_application_version ON tbl_application_version.registered_application_id = tbl_registered_application.id
WHERE tbl_application_version.id = application_version_id;
UPDATE tbl_registered_application_metadata SET feedbackcount = (feedbackcount + 1), lastfeedbackdate = NEW.createddate WHERE id = app_metadata_id;
UPDATE tbl_application_version_metadata SET feedbackcount = (feedbackcount + 1), lastfeedbackdate = NEW.createddate WHERE id = app_version_metadata_id;
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
-- IMPLEMENT THIS TRIGGER
RETURN NULL;
END IF;
END;
$$
LANGUAGE 'plpgsql';
ALTER FUNCTION update_total_feedback_count() OWNER TO feedback_tracker;
However, when I deploy my WAR file, I get an error saying something like this:
Unterminated dollar quote started at position 65 in SQL CREATE
FUNCTION my_function() RETURNS TRIGGER AS $$
So, clearly it's dying on the $$ in my function declaration. Is there a way around this? Should I be declaring my function/trigger differently? Is there a property I can set in my persistence.xml file that will get around this?
the problem with hibernate's default SqlStatementParser implementation, which is used in multiline sql command extractor.
if you look at grammar definition hibernate-core/src/main/antlr/sql-stmt.g there is definition of Statement End:
STMT_END
: ';' ( '\t' | ' ' | '\r' | '\n' )*
;
NOT_STMT_END
: ~( ';' )
;
This tells that statement end is semicolon symbol followed by "Space" "tab" "carret return" or "new line" symbols.
THUS: DEFAULT IMPLEMENTATION IN HIBERNATE DOESN'T SUPPORT DOLLAR QUOTING.
If you don't want to implement custom hibernate's parser you can rewrite all functions without dollar quoting, using simple ' quoting. But you will need to carefully escape ' chars.
UPDATE: you can create your custom ImportSqlCommandExtractor. For example: separate your commands with --****** (6 star symbols in comment, just to make your file proper SQL file, but with custom command separation in comments, or choose any insane combination, which you like) and then split those in simple implementation
public class ImportSqlCE implements ImportSqlCommandExtractor {
private static final Logger log = LoggerFactory.getLogger(ImportSqlCE.class);
#Override
public String[] extractCommands(Reader reader) {
try {
String allCommands = IOUtils.toString(reader);
return allCommands.split("--******");
} catch (IOException e) {
log.error("error reading import commands", e);
log.info("sengind default empty command set");
return new String[0];
}
}
}
and then configure hibernate to use it <property name="hibernate.hbm2ddl.import_files_sql_extractor" value="example.ImportSqlCE" />
with this your import.sql will support dollar quoting (i.e. it will simply ignore any sql awareness of what is happening.)

Delphi Unique Dynamic Form Creation

I am using multiple forms on my project. (Client Server Application) I used chat but i have some problems.
1- I have a user list on my listview. And i am open new chat form here.
procedure CreateNewChat(User: String);
var
ChatForm: TChatForm;
begin
ChatForm:= TChatForm.Create(nil);
if assigned (ChatForm) then
ChatForm.User:= User;
Chat.Socket:= MySocket; // TClientSocket new Instance
ChatForm.Show;
end;
///Chatform
procedure ParseData(Cmd:string);
begin
if Cmd <> '' then
begin
/// parsing...
end;
New connections are adding to listview like that
var
Item: TListItem;
NewTempForm: TTempForm;
begin
NewTempForm := tempForm.Create;
Item := Listview.Items.Add;
if User = '' then
Exit;
Item.Caption := User;
Item.SubItems.Add('OK');
Item.GroupId := GroupId;
Item.SubItems.Objects[0] := NewTempForm;
end;
My problems are started on here.
1- How can i detect form according to user? So, if two or higher form is open then how can i show received message on them? Because i was create them dynamically. I need a object for detect.
I tryed like that
var
tempForm: TTempForm; // this is an empty object class
sTempStr: String;
begin
if ListView.Selected <> nil then
begin
tempForm := TTempForm(ListView.Selected.SubItems.Objects[0]); // this is for unique form creation
if tempForm.frmTasks = nil then // if there is not, create!
begin
tempForm.frmTasks := TfrmTasks.Create(nil);
end;
But my received message is appear on all forms. Actually should appear just one form. (Which user sent it)
Thanks.

How to prevent using anything except application in Delphi XE

I'm developing a VCL Form Application
When the application started
I need to prevent the user of the computer to do anything
He can't close the application by ALT+F4 or CTRL+ALT+DEL
He can't change to another window by ALT+Tab
He can't Go to the desktop by clicking Windows + D
This will used in Cyber Cafe and it is a Server/Client application
so before the Admin in the Server giving access to the Client, He can't do anything in the computer .. just a full screen for my form
Because it is the login system of Windows that traps the CTRL-ALT-DEL combination (and so it is not accessible to user applications), you will need to change the system's keyboard scancode map to ignore at least one of those keys.
First you have to ensure Autologon on your computer, else you can login. This could also be done in the registry.
Then the tricky part editing the scancode map. In MSDN you'll find an article about how to do: https://msdn.microsoft.com/en-us/library/windows/hardware/jj128267%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
I've found this reg file that is suppose to disable CTRL + ALT + DELETE but I havent tested it
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
"Scancode Map"=hex:00,00,00,00,00,00,00,00,03,00,00,00,00,00,38,00,00,00,38,e0,\
00,00,00,00
Be aware that this is extremely hirisk. Both using the REG file and changing the scancode map. I suggest you test your stuff in a virtual Machine.
You also need to prevent the user from shuttingdown the computer. Which is also done in registry:
User Key: [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\
Explorer]
System Key: [HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\
Explorer]
Value Name: NoClose
Data Type: REG_DWORD (DWORD Value)
Value Data: (0 = shutdown enabled, 1 = shutdown disabled)
So inorder for shuttingdown the computer your probram must make a call to ShutdownwindowsEx
function ExitWindows(iFlags: Integer): Boolean;
var
osVerInfo: TOSVersionInfo;
function SetPrivilege(sPrivilegeName: string; bEnabled: Boolean): Boolean;
var
TPPrev, TP: TTokenPrivileges;
Token: THandle;
dwRetLen: DWord;
begin
Result := False;
OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, Token);
TP.PrivilegeCount := 1;
if (LookupPrivilegeValue(nil, PChar(sPrivilegeName), TP.Privileges[0].LUID)) then
begin
if (bEnabled) then
TP.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED
else
TP.Privileges[0].Attributes := 0;
dwRetLen := 0;
Result := AdjustTokenPrivileges(Token, False, TP, sizeof(TPPrev), TPPrev, dwRetLen);
end;
CloseHandle(Token);
end;
begin
Result := True;
osVerInfo.dwOSVersionInfoSize := sizeof(TOSVersionInfo);
if GetVersionEx(osVerInfo) then
case osVerInfo.dwPlatformId of
VER_PLATFORM_WIN32_WINDOWS:
if not ExitWindowsEx(iFlags, 0) then
Result := False; // handle errors...
VER_PLATFORM_WIN32_NT:
if SetPrivilege('SeShutdownPrivilege', True) then
begin
if not ExitWindowsEx(iFlags, 0) then
Result := False; // handle errors...
SetPrivilege('SeShutdownPrivilege', False)
end
else
Result := False; // handle errors...
else
Result := False;
end;
end;

Using Word OLE in Lazarus FreePascal

Im trying to get FreePascal to open up a word document, append some text and data to it and then close it. I've managed to get connected and can write a single line to the document but anything over that is defeating me. Currently I am attempting methods details in this Visual Basic reference, which is pretty similar to how I would expect FreePascal to handle things.
Basically I think I have misunderstood how the relationship between Lazarus and the Word OLE actually works, can anyone offer me any examples on how to construct a simple document that I can build on?
The following code, opens the document but then completely replaces its contents
program officAuto;
{$IFDEF FPC}
{$MODE Delphi}
{$ELSE}
{$APPTYPE CONSOLE}
{$ENDIF}
uses
SysUtils, Variants, ComObj;
const
ServerName = 'Word.Application';
var
Server, Doc : Variant;
oPara : Variant;
w:widestring;
begin
if Assigned(InitProc) then
TProcedure(InitProc);
try
Server := CreateOleObject(ServerName);
except
WriteLn('Unable to start Word.');
Exit;
end;
w:= UTF8Decode('c:\mydoc.docx');
Server.Visible := True; {Make Word visible}
Doc := Server.Documents.Open(w);
Doc.Range.Text := 'This is a Heading';
Doc.Range.Font.Bold := True;
Doc.Format.SpaceAfter := 24;
end.
Whereas this, based on the code above, in attempting to print a string at a bookmark, opens the document, retains the contents, moves to the bookmark and then does nothing.
w:= UTF8Decode('c:\mydoc.docx');
Server.Visible := True;
Doc := Server.Documents.Open(w);
oPara := Doc.Content.Paragraphs.Add(Doc.Bookmarks.Item('\Bookmark1').Range);
oPara := Doc.Range.Text('Where will this appear if at all!');
Ah I worked it out. The following code works as expected:
program officAuto;
{$IFDEF FPC}
{$MODE Delphi}
{$ELSE}
{$APPTYPE CONSOLE}
{$ENDIF}
uses
SysUtils, Variants, ComObj;
var
Server, Connect : Variant;
oWord, oPara1, oPara2 : Variant;
w:widestring;
begin
if Assigned(InitProc) then
TProcedure(InitProc);
try
Server := CreateOleObject('Word.Application');
except
WriteLn('Unable to start Word.');
Exit;
end;
// oWord := Server.Documents.Add;
w:= UTF8Decode('c:\mydoc.docx');
Server.Visible := True;
Server.Documents.Open(w);
oPara1 := Server.ActiveDocument.Content.Paragraphs.Add;
oPara1.Range.Text := 'This is a Heading';
oPara1.Range.Font.Bold := True;
oPara1.Format.SpaceAfter := 24;
oPara1.Range.InsertParagraphAfter();
oPara2 := Server.ActiveDocument.Content.Paragraphs.Add;
oPara2.Range.Text := 'Where will this appear if at all!';
oPara2.Range.Font.Bold := False;
oPara2.Format.SpaceAfter := 24;
oPara2.Range.InsertParagraphAfter();
end.

InnoSetup Dynamic ComboBox, check which item is selected and execute program

In InnoSetup I want to disply a ComboBox on the Finished Page which shows the Components that were installed.
You can choose "None" or any of the installed Components and start the associated program when clicking on finish.
This is my code so far:
procedure CurPageChanged(CurPageID: Integer);
var
NewComboBox1: TNewComboBox;
begin
if (CurPageID = wpFinished) then begin
NewComboBox1 := TNewComboBox.Create(WizardForm);
with NewComboBox1 do begin
Parent := WizardForm.FinishedPage;
Left := ScaleX(256);
Top := ScaleY(208);
Width := ScaleX(145);
Height := ScaleY(21);
ItemIndex := 0;
Style := csDropDownList;
Items.Add('None');
if IsComponentSelected('1') then
Items.Add('Component 1');
if IsComponentSelected('2') then
Items.Add('Component 2');
if IsComponentSelected('3') then
Items.Add('Component 3');
end;
end;
end;
First I want to set "None" as automatically selected. when the page is shown. I have looked up many Pascal forums but none of the solutions worked, like NewComboBox1.ItemSelected=0 (or similar, don't remember correctly...). So how do I achieve this?
Then I don't know how to make a program start when clicking on Finish. I thought
function NextButtonClick
might help but then no Next button worked in the setup.
Maybe there is also a problem because the list is created depending on which Components were selected, so item 1 is not Component 1, if Component 1 was not selected but Component 2 for instance.
I thought one might solve this by making the items invisible instead of not creating them at all.
I looked in the Support Classes Reference in the IS help file but didn't find anything that would help me.
I am looking forward to your answers!
There's no simple way to do this due to a lack of missing access to the file name and destination directory which the component is binded to. Even TSetupComponentEntry internal record doesn't contain this information, but even if would, you won't be able to access it. So, the following script uses its own separate array which contains the component/file linkage needed for this task:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Components]
Name: "program_32"; Description: "Program 32-bit"
Name: "program_x64"; Description: "Program 64-bit"
Name: "program_ia64"; Description: "Program IA 64-bit"
[Files]
Source: "MyProg.exe"; DestDir: "{app}"; Components: program_32
Source: "MyProg-x64.exe"; DestDir: "{app}"; Components: program_x64
Source: "MyProg-IA64.exe"; DestDir: "{app}"; Components: program_ia64
[Code]
type
TFileData = record
Component: string;
Description: string;
FileName: string;
Parameters: string;
end;
var
ComponentCombo: TNewComboBox;
ComponentArray: array of TFileData;
SelectionArray: array of TFileData;
procedure InitializeWizard;
begin
// this is a weakness of this solution - you need to fill the array
// of components that can be added to the final combo box when they
// are selected on component selection page. This is needed because
// you can't get neither file name nor destination directory of the
// file for the component from script. As first, set how many items
// you want to add to your component array storage
SetArrayLength(ComponentArray, 2);
// the Component member must match to the "Name" parameter from the
// [Components] section item since it's used in IsComponentSelected
// function call
ComponentArray[0].Component := 'program_32';
// the Description member is the text displayed in the combo item
ComponentArray[0].Description := 'Program 32-bit';
// the FileName member is the name of the file including path. This
// member may contain InnoSetup constants
ComponentArray[0].FileName := '{app}/MyProg.exe';
// the Parameters member contains execution parameters
ComponentArray[0].Parameters := '-a';
// this is the second item that can be added to the combo box, note
// that the program_ia64 component is not added to this array, what
// means, that it cannot be added to the "run" combo box. It's such
// kind of a filter for components like help files etc.
ComponentArray[1].Component := 'program_x64';
ComponentArray[1].Description := 'Program 64-bit';
ComponentArray[1].FileName := '{app}/MyProg-x64.exe';
ComponentArray[1].Parameters := '-b';
end;
procedure CurPageChanged(CurPageID: Integer);
var
I: Integer;
begin
if (CurPageID = wpFinished) then
begin
ComponentCombo := TNewComboBox.Create(WizardForm);
ComponentCombo.Parent := WizardForm.FinishedPage;
ComponentCombo.Left := ScaleX(256);
ComponentCombo.Top := ScaleY(208);
ComponentCombo.Width := ScaleX(145);
ComponentCombo.Height := ScaleY(21);
ComponentCombo.Style := csDropDownList;
ComponentCombo.Items.Add('None');
for I := 0 to GetArrayLength(ComponentArray) - 1 do
if IsComponentSelected(ComponentArray[I].Component) then
begin
ComponentCombo.Items.Add(ComponentArray[I].Description);
SetArrayLength(SelectionArray, GetArrayLength(SelectionArray) + 1);
SelectionArray[High(SelectionArray)] := ComponentArray[I];
end;
ComponentCombo.ItemIndex := 0;
end;
end;
function NextButtonClick(CurPageID: Integer): Boolean;
var
FileData: TFileData;
ResultCode: Integer;
begin
Result := True;
if (CurPageID = wpFinished) and (ComponentCombo.ItemIndex > 0) then
begin
FileData := SelectionArray[ComponentCombo.ItemIndex - 1];
Exec(ExpandConstant(FileData.FileName), FileData.Parameters, '', SW_SHOW,
ewNoWait, ResultCode);
end;
end;