{sysuserinfoorg} constant is empty - inno-setup

I am using the following but the value of {sysuserinfoorg} is an empty string:
Data := 'UNINSTALL=' {sysuserinfoorg};
The above line returns 'UNINSTALL'.
I also tried ExpandConstant('{sysuserinfoorg}') with the same result.
I am using this in the DeinitializeUninstall procedure and sending the Data via HTTP post to my server which writes it to a log file. It all works but the {sysuserinfoorg} is empty. What I am after is some information that identifies the user and/or their organization. Inno Setup doc indicates {sysuserinfoorg} includes data from the registry on who the machine is registered to.

The ExpandConstant is the correct way:
Data := 'UNINSTALL=' + ExpandConstant('{sysuserinfoorg}');
The problem can be that there's actually no registered company.
Check the value of RegisteredOrganization value in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion key.
On my machine, it's empty, hence the {sysuserinfoorg} is empty too.
The mere {sysuserinfoorg} is nonsense in Pascal code.
Data := 'UNINSTALL=' {sysuserinfoorg};
The {...} is a comment in Pascal, as you can see with enabled syntax-highlighting.
Hence the code compiles as:
Data := 'UNINSTALL=';
You can also try to retrieve the value explicitly by reading registry:
if RegQueryStringValue(
HKEY_LOCAL_MACHINE_64, 'SOFTWARE\Microsoft\Windows NT\CurrentVersion',
'RegisteredOrganization', S) then
begin
Log('64-bit RegisteredOrganization = ' + S)
end
else
begin
Log('64-bit RegisteredOrganization is not defined')
end;
if RegQueryStringValue(
HKEY_LOCAL_MACHINE_32, 'SOFTWARE\Microsoft\Windows NT\CurrentVersion',
'RegisteredOrganization', S) then
begin
Log('32-bit RegisteredOrganization = ' + S)
end
else
begin
Log('32-bit RegisteredOrganization is not defined')
end;
Log('sysuserinfoname = ' + ExpandConstant('{sysuserinfoname}'));

Related

Inserting images to an Excel sheet in Delphi

I have a question here, I have an application and I need to add images to it. I have tried:
Sheet.Shapes.AddPicture(G_V.Prog_Dir+'pic.BMP',false,true, 190, 10+(15*rowcount), 100, 100 );
it works just fine, but I don't want to give parameters, I want to insert pictures to specified (and parametric) cells because I need to add picture to the last column of the page; this excel needs to be printed I must mention that. So I tried:
Sheet.Range['E'+inttostr(rowcount),'E'+inttostr(rowcount)].Select;
Sheet.Pictures.Insert(G_V.Prog_Dir+'pic.BMP');
It looks OK at first sight, however I think this code links images to the sheet. For example, I send the created Excel to another computer and these images cannot be seen (I don't recall the exact error) and when I searched it, I found out that receiving computer needs to have images at the exact path. As a solution to this, "Sheet.Shapes.AddPicture" recommended but
as I stated before, I need another solution here.
I didn't see anybody experiencing this kind of problem, I hope someone helps me out.
You can use this code to import picture. You do not need exact path if you use AddPicture function with this parameters. it copies photo and paste it to excel.
Set p = ActiveSheet.Shapes.AddPicture(FileName:=PictureFileName,_
linktofile:=msoFalse, savewithdocument:=msoCTrue, _
left:=TargetCell.Left, Top:=TargetCell.Top, Width:=-1,_
Height:=TargetCell.Height)
The simple solution is to add the picture to the clipboard and then paste it into the sheet.
implementation
uses
VCL.Clipbrd, VCL.Graphics, Excel2000;
procedure TForm1.Test;
var
Bitmap: TBitmap;
Excel: TExcelApplication;
Worksheet: TExcelWorksheet;
begin
Bitmap:= TBitmap.Create;
try
Bitmap.LoadFromFile('c:\test.bmp');
Clipboard.Assign(Bitmap);
Excel:= TExcelApplication.Create(self);
Worksheet:= Excel.Worksheets[0];
Worksheet.Range['a1','a1'].Select;
Worksheet.Paste;
finally
Bitmap.Free;
end;
end;
This destroys the previous contents of the clipboard, which is bad form.
You can save/restore the clipboard data using the following routines (based on: https://www.devexpress.com/Support/Center/Question/Details/Q93874)
procedure TForm1.RestoreClipboardData(SourceDataStream: TMemoryStream);
var
AData: THandle;
ADataPtr: Pointer;
begin
if SourceDataStream.Size = 0 then Exit;
Clipboard.Open;
try
AData := GlobalAlloc(GMEM_MOVEABLE + GMEM_DDESHARE, SourceDataStream.Size);
try
ADataPtr := GlobalLock(AData);
try
SourceDataStream.Position := 0;
SourceDataStream.ReadBuffer(ADataPtr^, SourceDataStream.Size);
SetClipboardData(CF_BITMAP, AData);
finally
GlobalUnlock(AData);
end;
except
GlobalFree(AData);
raise;
end;
finally
Clipboard.Close;
end;
end;
procedure TForm1.SaveClipboardData(DestDataStream: TMemoryStream);
var
AData: THandle;
ADataPtr: Pointer;
begin
if DestDataStream = nil then raise Exception.Create('Dest is nil');
Clipboard.Open;
try
AData := GetClipboardData(CF_BITMAP);
if AData = 0 then Exit;
ADataPtr := GlobalLock(AData);
try
DestDataStream.Size := GlobalSize(AData);
DestDataStream.Position := 0;
DestDataStream.Write(ADataPtr^, DestDataStream.Size);
finally
GlobalUnlock(AData);
end;
finally
Clipboard.Close;
end;
end;

How to have responsive UI (Form) when performing long-running export task?

Good day people. First off, I'm not an native English speaker I might have some grammar mistakes or such.
I need an advice from people who has done something or an application alike mine, well, the thing is that I'm using a TProgressBar in my delphi form, another component called "TExcelApplication" and a TDBGrid.
When I export the DBGrid's content, the application "freezes", so I basically put that ProgressBar for the user to see how much the process is completed. I've realized that when the TDBGrid is retrieving and exporting each row to the new Excel workbook, you can't move the actual form, so you have to wait until the process is completed to move that form.
So, is it possible to do something (I thought about threads but I'm not sure if they could help) so the user could move the window if he wanted?
Thank you so much for taking your time in reading and giving me an advice. I'm using Delphi XE.
Here's the code I use to export the rows:
with ZQDetalles do
begin
First;
while not EOF do
begin
i := i + 1;
workSheet.Cells.Item[i,2] := DBGridDetalles.Fields[0].AsString;
workSheet.Cells.Item[i,3] := DBGridDetalles.Fields[1].AsString;
workSheet.Cells.Item[i,4] := DBGridDetalles.Fields[2].AsString;
workSheet.Cells.Item[i,5] := DBGridDetalles.Fields[3].AsString;
workSheet.Cells.Item[i,6] := DBGridDetalles.Fields[4].AsString;
workSheet.Cells.Item[i,7] := DBGridDetalles.Fields[5].AsString;
workSheet.Cells.Item[i,8] := DBGridDetalles.Fields[6].AsString;
workSheet.Cells.Item[i,9] := DBGridDetalles.Fields[7].AsString;
Next;
barraProgreso.StepIt;
end;
end;
If you want to see the whole code for the "Export" button, then feel free to see this link: http://pastebin.com/FFWAPdey
Whenever you're doing stuff that takes a significant amount of time in an application with GUI you want to put it in a seperate thread so the user can still operate the form. You can declare a simple thread as such:
TWorkingThread = class(TThread)
protected
procedure Execute; override;
procedure UpdateGui;
procedure TerminateNotify(Sender: TObject);
end;
procedure TWorkingThread.Execute;
begin
// do whatever you want to do
// make sure to use synchronize whenever you want to update gui:
Synchronize(UpdateGui);
end;
procedure TWorkingThread.UpdateGui;
begin
// e.g. updating the progress bar
end;
procedure TWorkingThread.TerminateNotify(Sender: TObject);
begin
// this gets executed when the work is done
// usually you want to give some kind of feedback to the user
end;
// ...
// calling the thread:
procedure TSettingsForm.Button1Click(Sender: TObject);
var WorkingThread: TWorkingThread;
begin
WorkingThread := TWorkingThread.Create(true);
WorkingThread.OnTerminate := TerminateNotify;
WorkingThread.FreeOnTerminate := true;
WorkingThread.Start;
end;
It's pretty straight forward, remember to always use Synchronize when you want to update visual elements from a thread. Usually, you also want to take care that the user can't invoke the thread again while it's still doing work as he's now able to use the GUI.
If the number of rows is small (and you know how many you'll have), you can transfer the data much more quickly (and all at once) using a variant array of variants, something like this:
var
xls, wb, Range: OLEVariant;
arrData: Variant;
RowCount, ColCount, i, j: Integer;
Bookmark: TBookmark;
begin
// Create variant array where we'll copy our data
// Note that getting RowCount can be slow on large datasets; if
// that's the case, it's better to do a separate query first to
// ask for COUNT(*) of rows matching your WHERE clause, and use
// that instead; then run the query that returns the actual rows,
// and use them in the loop itself
RowCount := DataSet1.RecordCount;
ColCount := DataSet1.FieldCount;
arrData := VarArrayCreate([1, RowCount, 1, ColCount], varVariant);
// Disconnect from visual controls
DataSet1.DisableControls;
try
// Save starting row so we can come back to it after
Bookmark := DataSet1.GetBookmark;
try
{fill array}
i := 1;
while not DataSet1.Eof do
begin
for j := 1 to ColCount do
arrData[i, j] := DataSet1.Fields[j-1, i-1].Value;
DataSet1.Next;
Inc(i);
// If we have a lot of rows, we can allow the UI to
// refresh every so often (here every 100 rows)
if (i mod 100) = 0 then
Application.ProcessMessages;
end;
finally
// Reset record pointer to start, and clean up
DataSet1.GotoBookmark;
DataSet1.FreeBookmark;
finally
// Reconnect GUI controls
DataSet1.EnableControls;
end;
// Initialize an instance of Excel - if you have one
// already, of course the next couple of lines aren't
// needed
xls := CreateOLEObject('Excel.Application');
// Create workbook - again, not needed if you have it.
// Just use ActiveWorkbook instead
wb := xls.Workbooks.Add;
// Retrieve the range where data must be placed. Again, your
// own WorkSheet and start of range instead of using 1,1 when
// needed.
Range := wb.WorkSheets[1].Range[wb.WorkSheets[1].Cells[1, 1],
wb.WorkSheets[1].Cells[RowCount, ColCount]];
// Copy data from allocated variant array to Excel in single shot
Range.Value := arrData;
// Show Excel with our data}
xls.Visible := True;
end;
It still takes the same amount of time to loop through the rows and columns of the data, but the time taken to actually transfer that data to Excel is drastically reduced, particularly if there's a good amount of data.

InnoSetup - Set DefaultDirName at runtime

I have an application that needs to allow for up to three simultaneous installations on the same machine.
For some reason, the following code behaves as if UsePreviousAppDir is set to yes. The second time I install the app, the path ends up mangled.
The value I want to see is
C:\Our App\install_x where x corresponds to the user's selection.
It works the first time, but the second run results in something like this:
C:\Our App\install_x\install_y, where x corresponds to the value selected with the first installation and y corresponds to the value selected during this installation.
The install version is a radio button selection grabbed from the first screen in the installer. How do I eliminate this issue?
Setup section:
[Setup]
AppName=Our App
AppId=Our App
AppVerName=Our App Version(CM)
DefaultDirName=C:\Our App
DefaultGroupName=Our Group Name
OutputDir=..\
OutputBaseFilename=mm_setup
DisableStartupPrompt=yes
Compression=zip
UsePreviousAppDir=no
VersionInfoDescription=Our App Setup
CreateUninstallRegKey=no
DirExistsWarning=no
And the method where I set the install version:
procedure gSetVersion;
begin
if gVersionPage.SelectedValueIndex = 0 then
begin
gInstallArea := 'install_a';
end
else if gVersionPage.SelectedValueIndex = 1 then
begin
gInstallArea := 'install_b';
end
else if gVersionPage.SelectedValueIndex = 2 then
begin
gInstallArea := 'install_c';
end
WizardForm.DirEdit.Text := WizardDirValue + '\' + gInstallArea;
end;
Solved the problem via the following hack. Not sure why it was necessary to manually edit the string when the `UsePreviousAppDir=no' was set, but this works
procedure gSetVersion;
var
installVersionIndex: Integer;
installDir: String;
begin
case gVersionPage.SelectedValueIndex of
0: gInstallArea := 'install_a';
1: gInstallArea := 'install_b';
2: gInstallArea := 'install_c';
end
//Set the default installation folder.
//This is necessary because InnoSetup intermittently
//ignores the 'UsePreviousAppDir=no' [Setup] directive
//and because the 'DefaultDirName' directive gets populated
//prior to the user selecting the install version
installVersionIndex := Pos('install_', WizardDirValue);
installDir := WizardDirValue;
if installVersionIndex > 0 then
begin
Delete(installDir, installVersionIndex, 20);
end
WizardForm.DirEdit.Text := installDir + '\' + gInstallArea;
end;

Why I can't insert commas plus spaces in a RichEdit Control (Delphi)

I’m trying to replace a "comma" with "comma + space" using the following code in a procedure called by the OnChange Event on a RichEdit control in Delphi 2010.
SomeString := RichEdit.Lines.Strings[RichEdit.Lines.Count-1];
Position := Pos(',', SomeString);
if Position > 0 then
begin
Delete(SomeString, Position, 1);
Insert(', ', SomeString, Position);
RichEdit.Lines.Strings[RichEdit.Lines.Count-1] := SomeString;
end;
It works perfectly, but I can’t use BACKSPACE and DEL via keyboard anymore (on the RichEdit Control), because the inserted characters act like barriers. It doesn’t happen with another set of inserted characters, only with "comma + space".
Can someone tell me what am I doing wrong here ?
Just try this code
//get the string
SomeString := RichEdit.Lines.Strings[RichEdit.Lines.Count-1];
//replace the 'comma' with 'comma+space'
SomeString :=StringReplace(SomeString ,',',', ',[rfReplaceAll,rfIgnoreCase]);
//now delete the extra space which gets added each time you call this event
SomeString :=StringReplace(SomeString ,', ',', ',[rfReplaceAll,rfIgnoreCase]);
//your desired result
RichEdit.Lines.Strings[RichEdit.Lines.Count-1] := SomeString ;
Remember the BACKSPACE and DEL are working fine. But in case of your code there was an extra space added each time you try and change the contents of RichEdit. Hence giving an impression of the 2 keys not working.
Since you are having problems deleting the space in front of comma I will provide with another solution
Add a Boolean variable to the class isComma: Boolean.
Later on OnKeyPress event of RichEdit inset this code
isComma := False;
if key = ',' then
isComma := True;
OnChange event code goes here
var
CurPoint: TPoint;
cText: string;
cLine: string;
begin
if isComma then
begin
cLine:=' '; //character to be added after comma, ' ' in here
CurPoint:=RichEdit1.CaretPos;
cText:=RichEdit1.Lines.Strings[CurPoint.y];
//add ' ' where the cursor is i.e. after the comma
cText:=Copy(cText,0,CurPoint.x)+cLine+Copy(cText,CurPoint.x+1,Length(cText));
RichEdit1.Lines.Strings[CurPoint.y]:=cText;
end;
end;

Inno Setup - How to read an INF file during the Setup

i need to know how to read a value from INF file [.inf], during the setup. I want the installer to check the version of the program that i am going to update, This program version is not stored in the registry or any other file, is only in the .inf file. Then is a must to get the version from it.
I got your answers, #Tlama and i cannot use a DLL to get version of the software.
This program only save the current version in the INF file.
What i want to do, is to make the installer to check the current versión of the software that i am working with, and display that version in a label text.
The inf information is this:
NetVersion=1.1.1.1
PatchVersion=2.0.1
ProductName=SoftwareX
I just need the PatchVersion to display after where it says version: #### :
this is the code i am trying to fix:
function GetInfsam: String;
var
sVersion : String;
Begin
sVersion := '';
GetIniString('', 'PatchVersion', 'sVersion', '{app}\Sam.inf');
Result := sVersion;
end;
Procedure InitializeWizard7();
var
L2Ver1 : Tlabel;
L2Ver2 : Tlabel;
Begin
L2Ver1:= TLabel.Create(WizardForm);
L2Ver1.Transparent:= True;
L2Ver1.AutoSize:= False;
L2Ver1.WordWrap:= True;
L2Ver1.Font.name:= 'Agency FB';
L2Ver1.Font.Size:= 12;
L2Ver1.Font.Color:= clwhite;
L2Ver1.Caption:= 'Version:';
L2Ver1.Parent:= WizardForm.SelectdirPage;
L2Ver1.Left := 5;
L2Ver1.top := 260;
L2Ver1.Width := 150;
L2Ver1.Height := 40;
L2Ver2:= TLabel.Create(WizardForm);
L2Ver2.Transparent:= True;
L2Ver2.AutoSize:= False;
L2Ver2.WordWrap:= True;
L2Ver2.Font.name:= 'Agency FB';
L2Ver2.Font.Size:= 12;
L2Ver2.Font.Color:= clwhite;
L2Ver2.Caption:= GetInfsam;
L2Ver2.Parent:= WizardForm.SelectdirPage;
L2Ver2.Left := L2Ver1.Width + L2Ver1.Left + 8;
L2Ver2.top := 260;
L2Ver2.Width := 100;
L2Ver2.Height := 40;
End;
Please, i need help to fix my code.
How to read INF file ?
INF files are just sort of INI files with the specified syntax. So to work with INF files you need to treat them as ordinary INI files. Assuming you have a INF file like this:
[Add.Code]
File.dll=File.dll
[File.dll]
File=http://www.code.com/file.dll
FileVersion=1,0,0,143
You can read the FileVersion key by using GetIniString this way:
procedure InitializeWizard;
var
Version: string;
begin
Version := GetIniString('File.dll', 'FileVersion', '', 'c:\File.inf');
if Version <> '' then
MsgBox('File version: ' + Version, mbInformation, MB_OK);
end;
Update:
1. Malformed INF file
According to your update, if the content of your INF file looks like this:
NetVersion=1.1.1.1
PatchVersion=2.0.1
ProductName=SoftwareX
then it's not a well formed INF file, but a name value pair text file saved with INF extension. Real INF files must have a valid [] section for each key value set, but this section is missing in your file.
2. GetIniString function cannot be called with empty Section parameter value
You must not call the GetIniString function with empty Section parameter value, because for the internally called GetPrivateProfileString function it means to return all section names for a given file, not value of a specified key. So for instance the following call is invalid, because the first parameter Section cannot be empty:
GetIniString('', 'KeyName', 'Default', 'c:\File.xxx');
3. How to work with a name value pair text file ?
You'll just need to work with that file as with a text file. For a key value text file handling would be ideal to use the TStringList class, or at least in Delphi. In InnoSetup unfortunately the TStringList class doesn't have published properties needed for a key value content manipulation, so you'll need to make your own key value text file parsing function. Here's the one for getting value for a given key. As the key value delimiter is supposed to be the = sign. This function returns a key value when succeed to find a AKeyName key in a given AFileName file or default ADefault value when fails:
function GetKeyValue(const AKeyName, AFileName, ADefault: string): string;
var
I: Integer;
KeyPos: Integer;
KeyFull: string;
FileLines: TArrayOfString;
begin
Result := ADefault;
if LoadStringsFromFile(AFileName, FileLines) then
begin
KeyFull := AKeyName + '=';
for I := 0 to GetArrayLength(FileLines) - 1 do
begin
FileLines[I] := TrimLeft(FileLines[I]);
KeyPos := Pos(KeyFull, FileLines[I]);
if KeyPos > 0 then
begin
Result := Copy(FileLines[I], KeyPos + Length(AKeyName) + 1, MaxInt);
Break;
end;
end;
end;
end;
To read a value of the PatchVersion key from the Sam.inf file expected in the currently selected install path you can use something like this. This script will update the label value whenever your user change the text in the directory edit box and when the directory selection page is going to be displayed:
var
// target version label must be declared globally
L2Ver2: TLabel;
procedure DirEditChange(Sender: TObject);
var
FilePath: string;
begin
// assign the expected INF file path
FilePath := AddBackslash(WizardForm.DirEdit.Text) + 'Sam.inf';
// read the PatchVersion key value, return N/A if not found
L2Ver2.Caption := GetKeyValue('PatchVersion', FilePath, 'N/A');
end;
procedure InitializeWizard;
begin
// create the target label as before
L2Ver2 := TLabel.Create(WizardForm);
...
// bind the DirEditChange method to the directory edit's OnChange event
WizardForm.DirEdit.OnChange := #DirEditChange;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
// if the page has been turned to the select directory page, update the
// label caption by firing the assigned OnChange event method manually
if (CurPageID = wpSelectDir) then
DirEditChange(nil);
end;
All my INF files have a structure similar to INI files.
If that's your case too I suggest you to open your INF as if it was INI, using (as suggested by TLama)
in setup functions:
function GetIniInt(const Section, Key: String; const Default, Min, Max: Longint; const Filename: String): Longint;
function GetIniString(const Section, Key, Default, Filename: String): String;
in preprocessor:
str ReadIni(str 1, str 2, str 3, str? 4)
Description (from Inno Setup help)
Reads the value from an INI file. Argument 1 must be the name of the INI file, argument 2 – the name of a section in the INI file, the third argument is the key in the section to read. Last optional argument can be used to provide the default value that will be returned on failure, if it is omitted, an empty string is returned.

Resources