I have Inno Setup code to show error message when installing a setup developed using Inno Setup. The error message will shown when date of expire happens.
The code is as follows:
const MY_EXPIRY_DATE_STR = '20171112'; // Date format: yyyymmdd
function InitializeSetup(): Boolean;
begin
// If current date exceeds MY_EXPIRY_DATE_STR then return false and
// exit Installer.
Result :=
CompareStr(GetDateTimeString('yyyymmdd', #0,#0), MY_EXPIRY_DATE_STR) <= 0;
if not Result then
MsgBox('Due to some problem', mbError, MB_OK);
end;
Now my question is that I want verify the date using internet, not by local system date.
Use some online service to retrieve the time (or build your own service).
See Free Rest API to retrieve current datetime as string (timezone irrelevant).
Make sure, you use HTTPS, so that it not easy to bypass the check.
The following example uses TimeZoneDB service.
You have to set your own API key (that you get after a free registration).
const
TimezoneDbApiKey = 'XXXXXXXXXXXX';
function GetOnlineTime: string;
var
Url: string;
XMLDocument: Variant;
XMLNodeList: Variant;
WinHttpReq: Variant;
S: string;
P: Integer;
begin
try
// Retrieve XML from with current time in London
// See https://timezonedb.com/references/get-time-zone
WinHttpReq := CreateOleObject('WinHttp.WinHttpRequest.5.1');
Url :=
'https://api.timezonedb.com/v2/get-time-zone?key=' + TimezoneDbApiKey +
'&format=xml&by=zone&zone=Europe/London';
WinHttpReq.Open('GET', Url, False);
WinHttpReq.Send('');
if WinHttpReq.Status <> 200 then
begin
Log('HTTP Error: ' + IntToStr(WinHttpReq.Status) + ' ' +
WinHttpReq.StatusText);
end
else
begin
Log('HTTP Response: ' + WinHttpReq.ResponseText);
// Parse the XML
XMLDocument := CreateOleObject('Msxml2.DOMDocument.6.0');
XMLDocument.async := False;
XMLDocument.loadXML(WinHttpReq.ResponseText);
if XMLDocument.parseError.errorCode <> 0 then
begin
Log('The XML file could not be parsed. ' + XMLDocument.parseError.reason);
end
else
begin
XMLDocument.setProperty('SelectionLanguage', 'XPath');
XMLNodeList := XMLDocument.selectNodes('/result/formatted');
if XMLNodeList.length > 0 then
begin
S := Trim(XMLNodeList.item[0].text);
// Remove the time portion
P := Pos(' ', S);
if P > 0 then
begin
S := Copy(S, 1, P - 1);
// Remove the dashes to get format yyyymmdd
StringChange(S, '-', '');
if Length(S) <> 8 then
begin
Log('Unexpected date format: ' + S);
end
else
begin
Result := S;
end;
end;
end;
end;
end;
except
Log('Error: ' + GetExceptionMessage);
end;
if Result = '' then
begin
// On any problem, fallback to local time
Result := GetDateTimeString('yyyymmdd', #0, #0);
end;
end;
Related
I originally asked about this question on another platform (here).
In Inno Setup it has the following message definition:
ErrorFileHash2=Invalid file hash: expected %1, found %2
This message is displayed when the installer tries to download and run a file with the wrong hash value.
In my script I have:
function NextButtonClick(CurPageID: integer): boolean;
begin
Result := True;
if (CurPageID = wpSelectTasks) then
begin
DownloadPage.Clear;
if (WizardIsTaskSelected('downloadhelp')) then
AddFileForDownload('{#HelpDocSetupURL}', 'HelpDocSetup.exe',
'{#GetSHA256OfFile("..\HelpNDoc\CHM\Output\MSAHelpDocumentationSetup.exe")}');
end
else
if (CurPageID = wpReady) then
begin
DownloadPage.Show;
try
try
DownloadPage.Download;
Result := True;
except
SuppressibleMsgBox(
AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
Result := False;
end;
finally
DownloadPage.Hide;
end;
end;
end;
The error message that is displayed when there is an issue is rather ugly. The following was suggested to me:
It only shows a message box if you don't handle the exception. Use try/except and then you can do things like re-raising the exception with a filename added or using a task dialog.
I thought I would try the message box designer:
Which creates the following code:
// Display a message box
SuppressibleTaskDialogMsgBox(
'Unable to download [file]', 'This is because the checksum value does not match',
mbError, MB_OK, ['OK'], 0, IDOK);
But I don't know what I am doing here.
How do I handle the exception that displays this error?
How do I show a better task dialog? Once that also includes the has details and file name?
Just replace your current:
SuppressibleMsgBox(
AddPeriod(GetExceptionMessage), mbCriticalError, MB_OK, IDOK);
With your new code:
SuppressibleTaskDialogMsgBox(
'Unable to download [file]', 'This is because the checksum value does not match',
mbError, MB_OK, ['OK'], 0, IDOK);
If you want to identify the failed download, you can use the value of the DownloadPage.Msg2Label.Caption (you can see it if, you move the message box).
If you need to include the hashes in your message, you would have to parse the data from the error message. That's bit fragile approach. But if you provide a fallback message, in case the parsing fails, it's doable.
The following function tries to parse the data out of any standard Inno Setup string:
function ParseDataFromSetupMessage(
Msg: string; ID: TSetupMessageID; var Data: TArrayOfString): Boolean;
var
MsgOrig, Pattern, PatternOrig, S: string;
I, P, P2: Integer;
begin
try
MsgOrig := Msg;
Pattern := SetupMessage(ID);
PatternOrig := Pattern;
while (Msg <> '') and (Pattern <> '') do
begin
P := Pos('%', Pattern);
if (P = 0) or (P = Length(Pattern)) or (P > 1) then
begin
if (P = 0) or (P = Length(Pattern)) then P := Length(Pattern) + 1;
if Copy(Msg, 1, P - 1) <> Copy(Pattern, 1, P - 1) then Abort;
Delete(Msg, 1, P - 1);
Delete(Pattern, 1, P - 1);
end
else
if (Pattern[2] < '1') or (Pattern[2] > '9') then
begin
if Copy(Msg, 1, 1) <> '%' then Abort;
Delete(Pattern, 1, 1);
Delete(Msg, 1, 1);
end
else
begin
I := StrToInt(Pattern[2]);
Delete(Pattern, 1, 2);
if Length(Pattern) = 0 then
begin
S := Msg;
SetLength(Msg, 0);
end
else
begin
P := Pos('%', Pattern);
if P = 0 then P := Length(Pattern) + 1;
P2 := Pos(Copy(Pattern, 1, P - 1), Msg);
if P2 = 0 then Abort;
S := Copy(Msg, 1, P2 - 1);
Delete(Msg, 1, P2 - 1);
end;
if GetArrayLength(Data) < I then
SetArrayLength(Data, I);
Data[I - 1] := S;
end;
end;
if Msg <> Pattern then Abort;
Result := True;
except
Log(Format('"%s" does not seem to match format string "%s".', [
MsgOrig, PatternOrig]));
Result := False;
end;
end;
You can use both in your except block like this:
except
Msg := GetExceptionMessage;
if ParseDataFromSetupMessage(Msg, msgErrorFileHash2, Data) then
begin
Expected := Data[0];
Hash := Data[1];
Msg :=
'This is because the checksum value does not match.' + #13+
'Download: ' + DownloadPage.Msg2Label.Caption + #13 +
'Expected: ' + Expected + #13 +
'Got: ' + Hash;
end
else
begin
// Failed for other reasons?
Msg :=
'Download has failed.' + #13+
'Download: ' + DownloadPage.Msg2Label.Caption + #13 +
'Details: ' + Msg;
end;
SuppressibleTaskDialogMsgBox(
'Unable to download', Msg, mbError, MB_OK, ['OK'], 0, IDOK);
Result := False;
end;
I'm creating a Inno Setup installer/updater for my application. Now I need to find a way to check if a new version is available and if it is available it should be installed automatically over the already installed version.
The special case is that the version number is in a file with other data.
The file that Inno Setup need to read looks like:
#Eclipse Product File
#Fri Aug 18 08:20:35 CEST 2017
version=0.21.0
name=appName
id=appId
I already found a way to update the application using a script that only read a text file with the version number in it.
Inno setup: check for new updates
But in my case it contains more data that the installer does not need. Can someone help me to build a script that can parse the version number out of the file?
The code that I already have looks like:
function GetInstallDir(const FileName, Section: string): string;
var
S: string;
DirLine: Integer;
LineCount: Integer;
SectionLine: Integer;
Lines: TArrayOfString;
begin
Result := '';
Log('start');
if LoadStringsFromFile(FileName, Lines) then
begin
Log('Loaded file');
LineCount := GetArrayLength(Lines);
for SectionLine := 0 to LineCount - 1 do
Log('File line ' + lines[SectionLine]);
if (pos('version=', Lines[SectionLine]) <> 0) then
begin
Log('version found');
S := RemoveQuotes(Trim(Lines[SectionLine]));
StringChangeEx(S, '\\', '\', True);
Result := S;
Exit;
end;
end;
end;
But when running the script the check for checking if the version string is on the line does not work.
Your code is almost correct. You are only missing begin and end around the code, that you want to repeat in the for loop. So only the Log line repeats; and the if is executed for out-of-the-range LineCount index.
It becomes obvious, if you format the code better:
function GetInstallDir(const FileName, Section: string): string;
var
S: string;
DirLine: Integer;
LineCount: Integer;
SectionLine: Integer;
Lines: TArrayOfString;
begin
Result := '';
Log('start');
if LoadStringsFromFile(FileName, Lines) then
begin
Log('Loaded file');
LineCount := GetArrayLength(Lines);
for SectionLine := 0 to LineCount - 1 do
begin { <--- Missing }
Log('File line ' + lines[SectionLine] );
if (pos('version=', Lines[SectionLine]) <> 0) then
begin
Log('version found');
S := RemoveQuotes(Trim(Lines[SectionLine]));
StringChangeEx(S, '\\', '\', True);
Result := S;
Exit;
end;
end; { <--- Missing }
end;
end;
In my script I am checking whether a directory and two files in this directory exist. While the first returns the correct value, the second check does not. I've checked it multiple times that these files exist in the designated directory but Inno Setup will always tell me that they do not exist. This is happening on a virtual Windows Server, and can't be reproduced on my local machine. There it always returns the correct Value.
UpdatePath := ExpandConstant('{app}');
if DirExists(UpdatePath) then begin
ExePath := UpdatePath+'\Application.exe';
ConfigFilePath := UpdatePath+'\Application.exe.config';
if FileExists(ExePath) and FileExists(ConfigFilePath) then begin //this one returns incorrect values
//Do Stuff
end else begin
MsgBox(FmtMessage(CustomMessage('DirPageFileNotFound'), [ExePath, ConfigFilePath]),mbInformation,MB_OK);
Result := False;
end;
end else begin
MsgBox(FmtMessage(CustomMessage('DirPageDirectoryNotFound'), [UpdatePath]),mbInformation,MB_OK);
Result := False;
end;
As you can see, I'm checking for an executable which can also be executed when double-clicked. It is there but Inno Setup will always tell me that it's not there. Is the virtual environment messing with it? What is happening here?
To debug the issue, try adding following code. Then check the log file of the installer and the output of the dir command:
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
function GetFileAttributes(lpFileName: string): DWORD;
external 'GetFileAttributes{#AW}#kernel32.dll stdcall';
function GetLastError() : LongInt;
external 'GetLastError#kernel32.dll stdcall';
const
INVALID_FILE_ATTRIBUTES = $FFFFFFFF;
procedure ...;
var
UpdatePath: string;
ExePath: string;
FindRec: TFindRec;
Attrs: DWORD;
LastError: LongInt;
ResultCode: Integer;
begin
Log('InitializeWizard');
UpdatePath := ExpandConstant('{app}');
ExePath := UpdatePath+'\Application.exe';
if FileExists(ExePath) then
begin
Log(ExePath + ' exists');
end
else
begin
LastError := GetLastError;
Log(ExePath + ' does not exist - ' +
Format('System Error. Code: %d. %s', [
LastError, SysErrorMessage(LastError)]));
end;
if not FindFirst(UpdatePath + '\*', FindRec) then
begin
LastError := GetLastError;
Log(UpdatePath + ' not found - ' +
Format('System Error. Code: %d. %s', [
LastError, SysErrorMessage(LastError)]));
end
else
begin
repeat
Log('Found file: ' + FindRec.Name + ' in ' + UpdatePath);
until not FindNext(FindRec);
end;
Attrs := GetFileAttributes(ExePath);
if Attrs <> INVALID_FILE_ATTRIBUTES then
begin
Log(ExePath + ' attributes = ' + IntToStr(Attrs));
end
else
begin
LastError := GetLastError;
Log(Format('Cannot get attributes of ' + ExePath +
': System Error. Code: %d. %s', [
LastError, SysErrorMessage(LastError)]));
end;
Exec(ExpandConstant('{cmd}'), '/k dir "' + UpdatePath + '"', '', SW_SHOW,
ewWaitUntilTerminated, ResultCode);
end;
The FileExists internally uses FindFirst/FindNext and GetFileAttributes. So this is to find out what causes the problem.
My wild guess is that the target machine is 64-bit and file system redirection jumps in for some reason.
Try using EnableFsRedirection to disable the redirection before you call FileExists:
EnableFsRedirection(False);
I have the following code and I want to extract it into a plain text but I cannot manage to do it.
The code works but what I need is to show it in the ExpandConstant field.
I have tried several ways but no luck so far.
function LoadValueFromXML(const AFileName, APath: string): string;
var
XMLNode: Variant;
XMLDocument: Variant;
begin
Result := '';
XMLDocument := CreateOleObject('Msxml2.DOMDocument.3.0');
try
XMLDocument.async := False;
XMLDocument.load(AFileName);
if (XMLDocument.parseError.errorCode <> 0) then
MsgBox('The XML file could not be parsed. ' +
XMLDocument.parseError.reason, mbError, MB_OK)
else
begin
XMLDocument.setProperty('SelectionLanguage', 'XPath');
XMLNode := XMLDocument.selectSingleNode(APath);
Result := XMLNode.text;
end;
except
MsgBox('An error occured!' + #13#10 + GetExceptionMessage, mbError, MB_OK);
end;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = CustomPageID then
CustomEdit.Text := LoadValueFromXML('C:\Games\World_of_Tanks_test\WoTLauncher.xml', '//info/patch_info_urls/item');
end;
procedure ClienteWot();
var
StaticText: TNewStaticText;
begin
StaticText := TNewStaticText.Create(WizardForm);
StaticText.Parent := WizardForm.SelectComponentsPage;
StaticText.Left := 425;
StaticText.Top := ScaleY(40);
StaticText.Font.Style := [fsBold];
//StaticText.Font.Color := clRed;
StaticText.Caption := ExpandConstant('Cliente WOT: -->>> Show XML Url <<<---');
end;
If you want to inline a value into a string that can be passed to the ExpandConstant function call, you can use e.g. the Format function:
var
...
URL: string;
begin
...
URL := LoadValueFromXML('C:\MyFile.xml', '//node/subnode');
StaticText.Caption := ExpandConstant(Format('{username} %s', [URL]));
end;
The above pseudocode example reads the XML value and assigns the returned value to the URL variable. Then it evaluates the inner statement of the second line:
Format('{username} %s', [URL])
Which inlines the URL string value into the given string (in place of %s) and produces a string like:
'{username} www.example.com'
And this string is then processed by the ExpandConstant function call (the outer statement) and assigned to the static text caption, which might be e.g.:
'MyUserName www.example.com'
I wanted to know if anyone ones a way that I can export data from a DBGrid to Excel ? I am using Delphi 7 , Excel 2007 and ADO .
Any help will be appreciated.
If you want a fast export of raw data, just export your recordset (ADODataset.recordset) with something like that:
procedure ExportRecordsetToMSExcel(DestName: string; Data: _Recordset);
var
ovExcelApp: OleVariant;
ovExcelWorkbook: OleVariant;
ovWS: OleVariant;
ovRange: OleVariant;
begin
ovExcelApp := CreateOleObject('Excel.Application'); //If Excel isnt installed will raise an exception
try
ovExcelWorkbook := ovExcelApp.WorkBooks.Add;
ovWS := ovExcelWorkbook.Worksheets.Item[1]; // go to first worksheet
ovWS.Activate;
ovWS.Select;
ovRange := ovWS.Range['A1', 'A1']; //go to first cell
ovRange.Resize[Data.RecordCount, Data.Fields.Count];
ovRange.CopyFromRecordset(Data, Data.RecordCount, Data.Fields.Count); //this copy the entire recordset to the selected range in excel
ovWS.SaveAs(DestName, 1, '', '', False, False);
finally
ovExcelWorkbook.Close(SaveChanges := False);
ovWS := Unassigned;
ovExcelWorkbook := Unassigned;
ovExcelApp := Unassigned;
end;
end;
It is working by using Tfilestream component
procedure TForm2.ExportdatatoexcelClick(Sender: TObject);
var
Stream: TFileStream;
i: Integer;
OutLine,f: string;
sTemp,s: string;
begin
Stream := TFileStream.Create('D:\Yogesh Delphi\employee1.csv', fmCreate);
try
s := string(adotable1.Fields[0].FieldName);
for I := 1 to adotable1.FieldCount - 1 do
begin
s:= s+ ',' + string(adotable1.Fields[I].FieldName);
end;
s:= s+ #13#10;
stream.Write(s[1], Length(s) * SizeOf(Char));
{S := '';
for I := 0 to adotable1.FieldCount - 1 do
begin
S := (adotable1.Fields[I].FieldName);
outline := OutLine+S + ' ,';
end; }
while not adotable1.Eof do
begin
// You'll need to add your special handling here where OutLine is built
s:='';
OutLine := '';
for i := 0 to adotable1.FieldCount - 1 do
begin
sTemp := adotable1.Fields[i].AsString;
// Special handling to sTemp here
OutLine := OutLine + sTemp +',';
end;
// Remove final unnecessary ','
SetLength(OutLine, Length(OutLine) - 1);
// Write line to file
Stream.Write(OutLine[1], Length(OutLine) * SizeOf(Char));
// Write line ending
Stream.Write(sLineBreak, Length(sLineBreak));
adotable1.Next;
end;
finally
Stream.Free; // Saves the file
end;
showmessage('Records Successfully Exported.') ;
end;
{Yog}