Lazarus FileSize error - filesize

I get an error:
unit1.pas(91,31) Error: Incompatible type for arg no. 1: Got "File Of Byte", expected "AnsiString"
My code:
var
f : file of byte;
...
AssignFile(f, FileName);
Reset(f);
try
TotalBytes := FileSize(f); // line 93
finally
CloseFile(f);
end;
Can someone help me?

As #Abelisto said, in Lazarus there are two functions FileSize, one in the System unit and one in the Lazarus unit fileutil.
The former takes a File as parameter, whereas the latter takes a string.
So if your code has fileutil in a uses clause, the one from that unit takes precedence over the one in System. That explains the error message.
You will have to fully qualify the call, so instead of a plain FileSize(f), use System.FileSize(f), or, alternatively, use FileSize(FileName) or fileutil.FileSize(FileName).

Line 91 appears to be
Reset(f);
so it is not clear why you include the comment about line 93.
However, if you are getting an error on Reset(f), the cause must be something you have not told us in your q. To see why, please follow the steps below carefully.
Note: The reason for basing the call to FileSize in my code on the copy of the compiled EXE is so that the file is guaranted to exist but is not the EXE itself, because when the EXE is running it cannot by opened in a shareable mode, so attempting to call Reset on it would fail.
Compile (but do not run yet) the console app below.
Copy the resulting exe to a file in the same directory, but with the extension '.BU' rather than '.EXE' is that attempting Reset on the EXE itself will result in a RunError(5), which means "Access denied", because when the EXE is opened by the OS it isn't opened in a shareable mode.
Now run the app. It should correctly report the size of the .BU file.
Assuming the EXE works as predicted, you need to identify where your error is coming from. My first guess would be that the instance of FileSize isn't the one in the System unit - my code calls System.FileSize to ensure that the correct instance of FileSize gets invoked. You can check that by changing your code to TotalBytes := System.FileSize(... - if the error goes away, you've found the cause.
Code:
program Files2;
{$mode objfpc}{$H+}
uses
SysUtils;
var
TotalBytes : Int64;
f : file of byte;
FileName : String;
begin
FileName := ChangeFileExt(ParamStr(0), '.BU'); // get name of this app
AssignFile(f, FileName);
Reset(f);
try
TotalBytes := System.FileSize(f);
writeln('Size of ', FileName, ' = ', TotalBytes, ' bytes');
readln;
finally
CloseFile(f);
end;
end.

Related

Inno Setup throws exception "The process cannot access the file because it is being used by another process." on repeated use of ExtractTemporaryFile

I have an Inno Setup script with silent install options that have procedure that uses ExtractTemporaryFile:
procedure SetAccessPermission(folderName : string; groupName : string );
Var
output : string;
ErrorCode : integer;
begin
ExtractTemporaryFile('EnvironmentAdjustment.exe');
ExtractTemporaryFile('EnvironmentAdjustment.exe.config');
ExtractTemporaryFile('Win32Security.dll');
ShellExec('', ExpandConstant('{tmp}\EnvironmentAdjustment.exe'),
ExpandConstant('"' + folderName + '" "' + groupName + '" '),
ExpandConstant('{tmp}'), SW_HIDE, ewWaitUntilTerminated, ErrorCode);
LoadStringFromFile(ExpandConstant(resultPath + '\folderaccess.txt'), output);
Log('Result: ' + SysErrorMessage(ErrorCode) + ')');
end;
That procedure is used several times in the code:
SetAccessPermission('{app}\WebSite\imagescache', groupForSettingReadWritePermisions);
SetAccessPermission('{app}\WebSite\formscache', groupForSettingReadWritePermisions);
But on the second use exception is thrown:
The process cannot access the file because it is being used by another process.
I've added some logs and found that it definitely fails on the first ExtractTemporaryFile executing. No any existing processes don't use that file, so I think that's something inside in Inno Setup. I wasted too much time to understand what goes wrong, but I have not idea still. Also, this issue occurs only on single machine with Windows Server 2012 R2 (and even there such problem started only some time ago), script is executed properly on all others, so that adds more questions.
As #Andrew commented, the file is most likely locked by some antivirus or something similar. Use Process Explorer/Process Monitor to check, what process is using the file.
Anyway, it's waste to extract the files multiple times. Extract them on the first use only:
if not FileExists(ExpandConstant('{tmp}\EnvironmentAdjustment.exe')) then
begin
ExtractTemporaryFile('EnvironmentAdjustment.exe');
ExtractTemporaryFile('EnvironmentAdjustment.exe.config');
ExtractTemporaryFile('Win32Security.dll');
end;
Or do what Inno Setup does: Retry the extraction after small pause, when it fails.

"Variable expected" error when using Inno Setup StringChangeEx function

I am using Inno Download Plugin to download a bunch of files for my installation. These files are listed in "files.json".
FileList := TStringList.Create;
FileList.LoadFromFile(ExpandConstant('{tmp}\files.json'));
for i := 0 to FileList.Count-1 do
begin
fileName := ExtractFileName(FileList[i]);
StringChangeEx(FileList[i], '\', '/',True);
//Add each file to download queque
idpAddFile("www.myapiaddress.com/files/" + FileList[i], ExpandConstant('{tmp}\install\') + fileName );
Log(FileList[i]);
end;
What gives me a headache is the line StringChangeEx(FileList[i], '\', '/',True). As soon as I put that in, the idp.iss stops compiling, giving me the error: Variable expected on
procedure idpAddFile(url, filename: String); external 'idpAddFile#files:idp.dll cdecl';
Installer compiles normal, if I remove StringChangeEx from my script entirely. It does not help to place it in a different location...
Any idea, what might cause this problem?
The StringChangeEx function needs a string variable in the first argument (it's declared as var). You are giving it a string value only.
Your code would work, were FileList a string array. But it's not, it's a class with an array-like string default property. Using a property value is an equivalent of using a function/method return value. A property is just syntactic sugar for getter and setter methods.
You will have to copy the value to a string variable and back.
S := FileList[i];
StringChangeEx(S, '\', '/',True);
FileList[i] := S;
Though you actually do not need to copy it back in your case.
Regarding the reason why the error refers to idp.iss file: There seems to be a bug in Inno Setup that makes it report the error as if it had occurred on the very first line of the Pascal Script code (what in your case is the very first real code in the included idp.iss). I've posted a bug report. It was fixed already.

How does output redirection work in Inno Setup?

I saw this question here: How to get an output of an Exec'ed program in Inno Setup?
But I can't get it to work myself, the commented out code are my attempts to make this work, but I resorted to a bat file because I couldn't make my redirection work. CacheInstanceName and CacheInstanceDir are global variable defined elsewhere:
function CheckCacheExists(): Integer;
var
args: String;
buffer: String;
ResultCode: Integer;
begin
// args := 'qlist ' + CacheInstanceName + ExpandConstant(' nodisplay > {tmp}\appcheck.txt');
// MsgBox(args, mbInformation, MB_OK);
// Exec(CacheInstanceDir + '\bin\ccontrol.exe', 'qlist ' + CacheInstanceName + ExpandConstant(' nodisplay > "{tmp}\appcheck.txt"'), '', SW_SHOW,
ExtractTemporaryFile('checkup.BAT');
Exec(ExpandConstant('{tmp}\checkup.BAT'), CacheInstanceDir + ' ' +
CacheInstanceName + ' ' + ExpandConstant('{tmp}'), '', SW_SHOW,
ewWaitUntilTerminated, ResultCode);
LoadStringFromFile(ExpandConstant('{tmp}\appcheck.txt'),buffer);
if Pos('^', buffer) = 0 then
begin
Result := 0
end
else
begin
Result := 1
end
end;
What am I doing wrong?
The output redirection syntax is a feature of the command prompt, not the core Windows APIs. Therefore if you want to redirect output then you need to invoke the command via {cmd} /c actual-command-line > output-file. Don't forget to include quotes where appropriate, as {tmp} (and other constants) may contain spaces.
However, you should strongly consider rewriting whatever is in that batch file into actual code. Anything you can do in a batch file you can do either directly in the Inno script or in a DLL that you call from the script. And this permits you greater control over error checking and the format of whatever data you want to retrieve.
Try running the command directly on your command line with the arguments in your args string to see what the result is which may give an indication of the problem.
Also, check that the file you are trying to redirect your output to is not in use by another process. I have found that when this occurs the actual command may execute successfully with the Exec command returning True but the ResultCode indicates an error and no output is written to the file used in the redirect. In this particular instance of the file being used by another instance the SysErrorMessage(ResultCode) command returns simply Incorrect function. However, testing directly on the command line as I mentioned first returns that the file is in use by another process.

How to use or expand environment variables in a command instantiated by CreateProcess?

The following code utilizes CreateProcess to run commands with environmental variables. Here, it tries to run notepad %APPDATA%\test.txt.
If I run notepad %APPDATA%\test.txt directly within Windows' CMD, %APPDATA% will be expanded. However, the environmental variable can not be expanded when executed by CreateProcess. Could you help to comment on the reason and the workaround? Any comment will be appreciated!
program TestConsole2;
{$APPTYPE CONSOLE}
uses
Windows, SysUtils;
var
I: Integer;
ProgramName: String;
StartInfo : TStartupInfo;
ProcInfo : TProcessInformation;
CreateOK : Boolean;
begin
try
FillChar(StartInfo, SizeOf(StartInfo), #0);
FillChar(ProcInfo, SizeOf(ProcInfo), #0);
StartInfo.cb := SizeOf(StartInfo);
ProgramName := 'NOTEPAD %APPDATA%\test.txt';
CreateOK := CreateProcess(
nil, PChar(ProgramName), nil, nil, True, 0, nil, nil, StartInfo, ProcInfo);
if CreateOK then WaitForSingleObject(ProcInfo.hProcess, INFINITE);
Readln(ProgramName);
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Call ExpandEnvironmentStrings to expand environment variables.
When you use cmd.exe, it performs the expansion for you. CreateProcess does not so you will need to do it before calling CreateProcess. Alternatively you could use ShellExecute which will expand environment strings.
Your current code does not meet the contract of CreateProcess. The second parameter must be a pointer to modifiable memory. You can get away with this if you are using the ANSI API but when targetting Unicode you code is liable to fail. Pass a pointer to modifiable memory rather than a pointer to a literal. If you added a call to expand the environment variables then you would end up with a modifiable string.
Finally, it looks like you are just trying to open a text file. Why force the user to view it in Notepad? My default editor for text files is not Notepad. I'd loath any program that forced Notepad on me. Instead let the shell open the file in the user's preferred editor. Call ShellExecute, use 'open' as the verb and pass the text file name as the file name parameter. On the other hand, perhaps you know all this and this is just example code. If so, please just ignore this advice.

Wrap an executable to diagnose it's invocations

I have a Windows executable (whoami) which is crashing every so often. It's called from another process to get details about the current user and domain. I'd like to know what parameters are passed when it fails.
Does anyone know of an appropriate way to wrap the process and write it's command line arguments to log while still calling the process?
Say the command is used like this:
'whoami.exe /all'
I'd like a script to exist instead of the whoami.exe (with the same filename) which will write this invocation to log and then pass on the call to the actual process.
From a batch file:
echo Parameters: %* >> logfile.txt
whoami.exe %*
With the caveat that you can have problems if the parameters contain spaces (and you passed the in escaping with "), because the command-line parser basically de-escapes them and they should be re-escaped before passed to an other executable.
You didn't note which programming language. It is not doable from a .bat file if that's what you wanted, but you can do it in any programming language. Example in C:
int main(int argc, void **argv)
{
// dump contents of argv to some log file
int i=0;
for (i=0; i<argc; i++)
printf("Argument #%d: %s\n", argv[i]);
// run the 'real' program, giving it the rest of argv vector (1+)
// for example spawn, exec or system() functions can do it
return 0; // or you can do a blocking call, and pick the return value from the program
}
I don't think using a "script" will work, since the intermediate should have a .exe extension for your ploy to work.
I would write a very small command line program to do this; something like the following (written in Delphi/Virtual Pascal so it will result in a Win32 executable, but any compiled language should do):
program PassThrough;
uses
Dos; // Imports the Exec routine
const
PassTo = 'Original.exe'; // The program you really want to call
var
CommandLine: String;
i: Integer;
f: Text;
begin
CommandLine := '';
for i := 1 to ParamCount do
CommandLine := CommandLine + ParamStr(i) + ' ';
Assign(f,'Passthrough.log');
Append(f);
Writeln(f, CommandLine); // Write a line in the log
Close(f);
Exec(PassTo, CommandLine); // Run the intended program
end.
Can't you just change the calling program to log the parameters it used to call the process, and the exit code?
This would be way easier than trying to dig into whoami.exe
Look for whoami.exe, BACK IT UP, replace it with your own executable and see do whatever you like with it's parameters (maybe save them in a text file).
If you can reproduce the crash, use Process Explorer before crashed process is terminated to see its command line.
http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx

Resources