Indy somethimes throws EIdOSSLCreatingContextError - multithreading

I have a scheduled task that runs every 15 mins.
The scheduled task starts a multi threaded service that sends web requests to a web server. Sometimes, but not always, the TidHTTP.Post throws a EIdOSSLCreatingContextError exception. It's practically impossible for me to reproduce in a debug environment, but my log has 344 instances of the exception over the course of 10 days.
Here is the code that crashes, be aware that multiple threads can reach this code at the same time:
class function THTTPClient.Post(aUrl : string; aPayload : string;
aHeaderFields : TStringList) : string;
var
oHTTP: TIdHTTP;
ssPayload: TStringStream;
bSecure: Boolean;
sslIOHandler:TIdSSLIOHandlerSocketOpenSSL;
begin
oHTTP := nil;
ssPayload := TStringStream.Create(aPayload, TEncoding.UTF8);
try
oHTTP := TIdHTTP.Create;
oHTTP.Request.CustomHeaders.AddStrings(aHeaderFields);
bSecure := StartsText('HTTPS:', aUrl);
if bSecure then
begin
sslIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(oHTTP);
sslIOHandler.SSLOptions.Method := sslvSSLv23;
sslIOHandler.SSLOptions.Mode := sslmUnassigned;
sslIOHandler.SSLOptions.VerifyMode := [];
sslIOHandler.SSLOptions.VerifyDepth := 0;
sslIOHandler.Host := '';
sslIOHandler.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2, sslvSSLv23, sslvSSLv3];
oHTTP.IOHandler := sslIOHandler;
end;
oHTTP.HandleRedirects := true;
try
Result := oHTTP.Post(aUrl, ssPayload);
except
on E:Exception do
begin
Result := E.Message;
raise;
end;
end;
finally
oHTTP.Free;
ssPayload.Free;
end;
end;
Eurekalog is used to log the errors. Here is the exception details:
Application:
--------------------------------------------------------------
1.1 Start Date : Tue, 5 Jun 2018 03:52:26 +0200
1.2 Name/Description: AutoRunMT.exe
1.3 Version Number : 7.12.48.94
1.4 Parameters :
1.5 Compilation Date: Fri, 18 May 2018 10:45:35 +0200
1.6 Up Time : 23 hour(s), 21 minute(s), 59 second(s)
Exception:
----------------------------------------------------
2.1 Date : Wed, 6 Jun 2018 03:14:25 +0200
2.2 Address : 006625DF
2.3 Module Name : AutoRunMT.exe
2.4 Module Version: 7.12.48.94
2.5 Type : EIdOSSLCreatingContextError
2.6 Message : Error creating SSL context.
2.7 ID : E99A7A81
2.8 Count : 1
2.9 Status : New
2.10 Note :
2.11 Sent : 0

Related

TNewCheckListBox Checked not being updated

I'm trying to write an installer in Inno Setup that includes a page where a TNewCheckListBox is created and then used to populate a selection of choices on the next page:
var
ListBox: TNewCheckListBox;
SelectedConfigs: array of Integer;
function ConfigurationPage(): Integer;
var
Page: TWizardPage;
I: Integer;
begin
Page := CreateCustomPage(wpSelectComponents, 'Select Configuration',
'Please select the configuration you would like to install to.');
ListBox := TNewCheckListBox.Create(Page);
ListBox.Parent := Page.Surface;
ListBox.Left := ScaleX(0);
ListBox.Top := ScaleY(0);
ListBox.Width := Page.SurfaceWidth;
ListBox.Height := Page.SurfaceHeight;
ListBox.BorderStyle := bsNone;
for I := 0 to GetArrayLength(ConfigurationNames) - 1 do
begin
ListBox.AddCheckBox(ConfigurationNames[I], '', 0, False, True, False, False, nil);
end;
ListBox.ItemIndex := 0;
Result := Page.ID;
end;
procedure PortSelectionPage(PageID: Integer);
var
Page: TWizardPage;
ArrayLength: Integer;
I: Integer;
begin
Page := CreateCustomPage(PageID, 'Select Port',
'Please select the port you want the configurations to receive on.');
for I := 0 to ListBox.Items.Count - 1 do
begin
Log(Format('ListBox[%d], %s is checked? %d', [I, ListBox.Items[I], ListBox.Checked[I]]));
if ListBox.Checked[I] then
begin
ArrayLength := GetArrayLength(SelectedConfigs);
SetArrayLength(SelectedConfigs, ArrayLength + 1);
SelectedConfigs[ArrayLength] := I;
Log(Format('Config %d was selected...', [I]));
end;
end;
end;
procedure InitializeWizard;
var
PageID: Integer;
begin
PageID := ConfigurationPage;
PortSelectionPage(PageID);
end;
The problem I'm having is that, regardless of the selections I make in the ConfigurationPage procedure, the SelectedConfigs array is not being updated and my debugging messages are showing that none of the options are selected. Before creating the PortSelectionPage procedure, the code lived on the CurStepChanged event handler so I'm not sure if the issue is with my having moved the code to a different page or if there's something else going on here. Do I need to force an update to the component or should I be using event handlers instead? If so, how would I implement one for this usecase?
Your whole code is executed from InitializeWizard event function. Hence even before the installer wizard is shown.
You have to query the checkbox states only after the user changes them. For example, from some of these events:
NextButtonClick
CurStepChanged
CurPageChanged

Inno Setup Pascal script problem... "Unknown Identifier"

I'm trying to check if java 8 is in registry or java 9-11 are in registry, so i make this script:
[Code]
{ Script to check if a JRE is installed, it will search for the old java 8 location and for the new java 11 location }
function InitializeSetup(): Boolean;
var
ErrorCode: Integer;
JavaVer: string;
begin
{ checking for old java 8 location }
RegQueryStringValue(
HKLM64, 'SOFTWARE\JavaSoft\Java Runtime Environment', 'CurrentVersion', JavaVer);
ResultOldJava := (Length(JavaVer) > 0);
{ checking for new java 9-11 location }
RegQueryStringValue(
HKLM64, 'SOFTWARE\JavaSoft\JDK', 'CurrentVersion', JavaVer);
ResultNewJava := (Length(JavaVer) > 0);
if not ResultOldJava and not ResultNewJava then
begin
if MsgBox('ATENCIÓN: Gestor requiere Java 64 Bits instalado en el sistema. No se ha encontrado, ¿Desea abrir la página de descargas oficial? Por favor, recuerde que es necesaria la versión de 64 bits.', mbConfirmation, MB_YESNO) = idYes then
begin
ShellExec(
'open', 'https://www.java.com/es/download/manual.jsp#win',
'', '', SW_SHOWNORMAL, ewNoWait, ErrorCode);
end;
end;
end;
The problem is that it's printing this error:
Unknown Identifier 'ResultOldJava'
What is wrong? my skills in pascal are very low
You have declare the ResultOldJava variable, the same way you already declare ErrorCode and JavaVer:
function InitializeSetup(): Boolean;
var
ErrorCode: Integer;
JavaVer: string;
ResultOldJava: Boolean;
begin
For others, who arrive here with the same error message, but on a function or procedure call, rather than on a variable identifier, see Inno Setup - Pascal code visibility - "Unknown identifier" error.

Inno Setup - Merging implementations of event functions that return boolean (like InitializeSetup)

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.

Inno Setup - Conditionally hide/show static text based on task selection

[Components]
Name: "Slasher"; Description: "Dagon Slasher"; Types: Slasher Full
Name: "Frankenstein"; Description: "Dagon Frankenstein"; Types: Frankenstein Full
[Types]
Name: "Full"; Description: "Dagon Video Tools"
Name: "Slasher"; Description: "Dagon Slasher"
Name: "Frankenstein"; Description: "Dagon FrankenStein"
[Tasks]
Name: "Debug"; Description: "Nothing"; Components: not Slasher
Name: "Vid"; Description: "Install Extra Codecs for Frankenstein"; Flags: unchecked; Components: not Slasher
[Code]
var
Warning: TNewStaticText;
procedure InitializeWizard;
begin
Warning := TNewStaticText.Create(WizardForm);
Warning.Parent := WizardForm.SelectTasksPage;
Warning.Visible := False;
Warning.AutoSize := False;
Warning.SetBounds(
WizardForm.TasksList.Left,
WizardForm.TasksList.Top + WizardForm.TasksList.Height,
WizardForm.TasksList.Width,
50
);
Warning.Font.Color := clRed;
Warning.Caption := 'Warning: This will result in a non-functional "Join in FrankenStein" button in the Tools Menu.';
end;
I used yet another amazing piece of code by TLama. The problem is I need the note to be visible when the user selects the task, and be hidden otherwise (while on the same page).
You have to handle WizardForm.TasksList.OnClickCheck event and update the Warning label visibility accordingly.
var
Warning: TNewStaticText;
procedure TasksListClickCheck(Sender: TObject);
begin
Warning.Visible :=
{ This (and the task index below) has to be kept in sync with the expression }
{ in "Components" parameter of the respective task. }
{ Though note that in your specific case the test }
{ is redundant as when "Slasher" is selected, you have no tasks, }
{ and the "Tasks" page is completely skipped, so you do not even get here. }
(not IsComponentSelected('Slasher')) and
WizardForm.TasksList.Checked[0]; { You can also use WizardIsTaskSelected }
end;
procedure InitializeWizard;
begin
Warning := TNewStaticText.Create(WizardForm);
...
{ Update Warning label visibility on task selection change }
WizardForm.TasksList.OnClickCheck := #TasksListClickCheck
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpSelectTasks then
begin
{ Update initial visibility }
TasksListClickCheck(WizardForm.TasksList);
end;
end;
Side notes:
Do not hard code the height to fixed 50. Scale it with DPI instead: ScaleY(50).
You should set Warning.WordWrap := True as the caption does not fit page width.
You should shrink TasksList's height as the label does not fit below the list. You are missing the WizardForm.TasksList.Height := WizardForm.TasksList.Height - NoteHeight; from #TLama's code. Again note that he is missing the scaling of the NoteHeight.
const
NoteHeight = 50;
procedure InitializeWizard;
begin
WizardForm.TasksList.Height := WizardForm.TasksList.Height - ScaleY(NoteHeight);
Warning := TNewStaticText.Create(WizardForm);
Warning.Parent := WizardForm.SelectTasksPage;
Warning.AutoSize := False;
Warning.WordWrap := True;
Warning.SetBounds(
WizardForm.TasksList.Left,
WizardForm.TasksList.Top + WizardForm.TasksList.Height,
WizardForm.TasksList.Width,
ScaleY(NoteHeight)
);
Warning.Font.Color := clRed;
{ Update Warning label visibility on task selection change }
WizardForm.TasksList.OnClickCheck := #TasksListClickCheck
Warning.Caption :=
'Warning: This will result in a non-functional "Join in FrankenStein" button ' +
'in the Tools Menu.';
end;

Correct code for opening a file with an external editor

Is the following code it right or not and, if it is wrong, please correct it.
Note: I want to open the file with "WordPad.exe" not with "Microsoft Office Word" until if "Microsoft Office Word" is the default program.
My code:
function InitializeSetup: Boolean;
var
S: AnsiString;
begin
// Show the contents of Readme.txt (non Unicode) in a message box
ExtractTemporaryFile('Info.rtf');
Result := True;
end;
procedure AboutButtonOnClick(Sender: TObject);
var
ErrorCode: Integer;
begin
ShellExec('open', ExpandConstant('{tmp}\Info.rtf'), '', '', SW_SHOWNORMAL, ewNoWait,
ErrorCode);
end;
ShellExec('open','Documentname'....); will open with the program associated with the extension of the file. If there is no program associated it will prompt you to select which program you want to view it with.
You could look for WordPad.exe and if it's found you could call ShellExec using the WordPad.EXE directly. Then pass the documentName as a parameter.
Updated with function to do this
procedure OpenDocumentInWordPad(Document : String);
var
WordPad : String;
ErrorCode : Integer;
begin
// Typical Location on XP and later.
WordPad := ExpandConstant('{pf}') + '\Windows NT\Accessories\WordPad.exe'
// Find word pad
if Not FileExists(WordPad) then
begin
// Location in Windows 95/98
WordPad := ExpandConstant('{pf}') + '\Accessories\WordPad.exe'
if Not FileExists(WordPad) then
begin
// Fall back to anything associated with document.
WordPad := Document;
Document := '';
end;
end;
if not ShellExec('open',WordPad,Document,'',SW_SHOW,ewNoWait,ErrorCode) then
begin
MsgBox(SysErrorMessage(ErrorCode),mbError,MB_OK);
end;
end;

Resources