Application cant access file on startup - delphi

I'm doing an application(XE6 , Firemonkey) to synchronize files between a shared folder and a computer/s. This application checks every x hours if there are new files to be synchronized, and it starts on windows start-up.
I can do everything, my application starts on start-up, and it does the synchronization, as long as i'm the one starting it. Whem the application auto starts on start up it gives me an exception "EINOUTERROR" - File Access Denied.
On starting the application reads a small .txt file to set up it self (shared folder location, rate of synchronization etc), my guess is that since its the windows starting the app runs it without privileges to read the .txt, but even after changing the .txt permissions to full control on everyone it gives the same error.
File open code:
AssignFile(myFile,'Dados.txt');
if FileExists('Dados.txt') then
Append(myFile)
else
Rewrite(myFile);
FileMode := fmOpenRead;
Reset(myFile);
Code of placing the app on startup programs :
procedure TSyncM.RunOnStartup(const sCmdLine: string; bRunOnce: boolean; Remove: Boolean) ;
var sKey: string;
Section: string;
const ApplicationTitle = 'GEN4Sync';
begin
if (bRunOnce) then
sKey := 'Once'
else
sKey := '';
Section := 'Software\Microsoft\Windows\CurrentVersion\Run' + sKey + #0;
with TRegIniFile.Create('') do
try
RootKey := HKEY_CURRENT_USER;
if Remove then
DeleteKey(Section, ApplicationTitle)
else
WriteString(Section, ApplicationTitle, sCmdLine) ;
finally
Free;
end;
end;
If i comment the piece of code that calls the reading of that .txt my app starts and executes well, but i don't want to set it up everytime.
Thanks in advance

I think that the issue is related to your use of relative paths. You have written the code under the assumption that the working directory is the same directory as contains the executable. That is not necessarily so.
When you start the application by double clicking on the executable file, for instance, the shell ensures that the initial working directory is the directory containing the executable file. However, when Windows starts your program at startup I suspect that the working directory is the system directory. And of course your file is not found there, and you don't have rights to write there.
Instead of using relative paths, use the full path to the file.
FileName := ExtractFilePath(ParamStr(0)) + 'Dados.txt';
Or perhaps
FileName := TPath.Combine(ExtractFilePath(ParamStr(0)), 'Dados.txt');
Note that this does also assume that your executable file is located in a folder which you can write to. That is often not the case so you may need to find a different location.
I do have to comment that I find it somewhat incongruous that you are mixing the very modern (FireMonkey) with the ancient (Pascal I/O). Perhaps it is time to move to a more modern I/O technique.

Related

How to share an application and associated datafile

I have written a program (firemonkey) using delphi community edition. I would like to share the program, but the .exe file that my friends will be downloading has to access a text file from time to time to retrieve strings. When writing the program I used an event handler, to load the text file at startup:
tform3.formCreate (Sender:Tobject);
...
assignfile(myfile,('C:**********.txt));
...
Worked just fine during the
design stage.
As a hobbyist, I now find myself stuck. If I use INNO setup compiler to create an installation program, which I plan to do, I can't have this same hardwired reference ('C:*****) to the data file's location. What I need is to change the above code such that the .exe file can locate the supporting datafile irrespective of where that .exe file (and datafile) ends up on someone else's PC.
How can I do this? i.e. What code do I need (in place of the above) to ensure that the installation program I hand out will install an .exe file that can locate the data file it references?
Any help, much appreciated. Still learning.
Read-only access
If the data file should always be opened in read-only mode, the simplest solution is to place it next to the *.exe file. Then, at runtime, you dynamically find the path to the *.exe file and modify it to find the path to the data file. For instance,
uses
IOUtils;
procedure TForm1.FormCreate(Sender: TObject);
var
FileName: string;
begin
FileName := TPath.Combine(ExtractFilePath(ParamStr(0)), 'data.txt');
ShowMessage(TFile.ReadAllText(FileName, TEncoding.UTF8));
end;
ParamStr(0) contains the path to the *.exe file, such as
'C:\Users\Andreas Rejbrand\Documents\Embarcadero\Studio\Projects\test\Win32\Debug\Project1.exe'
Then ExtractFilePath(ParamStr(0)) is
'C:\Users\Andreas Rejbrand\Documents\Embarcadero\Studio\Projects\test\Win32\Debug\'
and, finally, TPath.Combine(ExtractFilePath(ParamStr(0)), 'data.txt') is
'C:\Users\Andreas Rejbrand\Documents\Embarcadero\Studio\Projects\test\Win32\Debug\data.txt'
Make sure the installer puts the data file next to the *.exe file.
Read and write access
If we are talking about a settings file or some other file that each user needs to change (via the software), you cannot place it next to the *.exe file, because the *.exe file typically resides in the Program Files folder, which is read only. Also, there is only one Program Files folder, but possibly many users on the PC, and each user should have his or her own copy.
The solution is to save the file in the user's own folders, specifically, the AppData folder:
FileName := TPath.GetHomePath + '\Mariner\My Word Processor App\Settings\settings.ini';
(using a different approach to path building).
On my system, this becomes
'C:\Users\Andreas Rejbrand\AppData\Roaming\Mariner\My Word Processor App\Settings\settings.ini'
Your installer (Inno Setup) has built-in support for placing files in this location.
If it is only accessed read only, you could also consider adding it as a resource to the executable. Which would then allow you to simply distribute this executable without the need for an installer.
Delphi Dabbler has an example, but I found it a bit confusing. I'll link to it (PDF) anyway.
You can let the user select the place where the file has to be saved. Proposing AppData folder if the file is for each individual user or CommonAppData if the file has to be shared between different users.
When the use selected the data file destination, you can save it to an INI file. The INI file can be stored, without asking the user, either to the registry or to an INI file saved in the AppData folder or ProgramData folder.
Here is a snipped of source code to get hand on those special folders:
const
SectionWindow = 'Window';
SectionData = 'Data';
CompanyFolder = 'YourCompanyName';
constructor TForm1.Create(AOwner: TComponent);
var
CommonPath : array [0..MAX_PATH] of Char;
LocalPath : array [0..MAX_PATH] of Char;
LangFileName : String;
begin
SHGetFolderPath(0, CSIDL_COMMON_APPDATA, 0, SHGFP_TYPE_CURRENT, #CommonPath[0]);
SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, SHGFP_TYPE_CURRENT, #LocalPath[0]);
FIniSection := SectionWindow;
FIniSectionData := SectionData;
FAppName := ChangeFileExt(ExtractFileName(Application.ExeName), '');
FCommonAppData := IncludeTrailingPathDelimiter(CommonPath) +
CompanyFolder + '\' + FAppName + '\';
FLocalAppData := IncludeTrailingPathDelimiter(LocalPath) +
CompanyFolder + '\' + FAppName + '\';
FIniFileName := FLocalAppData + FAppName + '.ini';
ForceDirectories(FCommonAppData);
ForceDirectories(FLocalAppData);
inherited Create(AOwner);
end;

Checking if the file is in use and by which application?

Trying to use the below mentioned approach to get more details about the locked file.
Is file in use
function GetFileInUseInfo(const FileName : WideString) : IFileIsInUse;
var
ROT : IRunningObjectTable;
mFile, enumIndex, Prefix : IMoniker;
enumMoniker : IEnumMoniker;
MonikerType : LongInt;
unkInt : IInterface;
begin
result := nil;
OleCheck(GetRunningObjectTable(0, ROT));
OleCheck(CreateFileMoniker(PWideChar(FileName), mFile));
OleCheck(ROT.EnumRunning(enumMoniker));
while (enumMoniker.Next(1, enumIndex, nil) = S_OK) do
begin
OleCheck(enumIndex.IsSystemMoniker(MonikerType));
if MonikerType = MKSYS_FILEMONIKER then
begin
if Succeeded(mFile.CommonPrefixWith(enumIndex, Prefix)) and
(mFile.IsEqual(Prefix) = S_OK) then
begin
if Succeeded(ROT.GetObject(enumIndex, unkInt)) then
begin
if Succeeded(unkInt.QueryInterface(IID_IFileIsInUse, result)) then
begin
result := unkInt as IFileIsInUse;
exit;
end;
end;
end;
end;
end;
end;
But the call to
unkInt.QueryInterface(IID_IFileIsInUse, result)
always returns E_NOINTERFACE.
Platform: Windows 7 32 bit-OS, opening word files and .msg files.
Checked opening files from the explorer and trying to delete. It shows proper details about the application in which the file is opened. In my application, I am try to display the information about application in which the file is opened. But when trying to cast the pointer to IFileIsInUse interface, QueryInterface calls fails with return code E_NOINTERFACE which means the object in ROT does not implement IFileIsInUse. AFASIK, MS Office files implements IFileIsInUse
Any idea what is wrong here?
In fact your code works fine. The problem is that the programs you are testing against really do not implement IFileIsInUse. When the system returns E_NOINTERFACE it is accurate. The interface is not implemented.
I tested this with the File Is In Use Sample from the SDK. Files that are added to the ROT by that application, which does implement IFileIsInUse, were picked up by your code. On the other hand, files opened by Acrobat 8 and Word 2010 were not.
The conclusion that I draw from this is that IFileIsInUse is a fine idea in principle, but not much use if applications don't support it. And it appears that there are major applications that do not.
It is clear that you will need to use one or more of the other mechanisms to detect which application has a file locked when you find that IFileIsInUse is not implemented.
SysInternals Process Explorer worked for me to delete a locked .msg file that was causing system problems like locking up the desktop.
Run Process Explorer, use the Find menu,
enter the full path file name,
hit Search.
For deleting a locked file, I opened a cmd window and tried to del the locked file, but the delete hung on the lock.
Then I used Process Explorer to restart the process holding the lock - Explorer.exe.
The del then completed successfully.

How to fix madExcept creating temporal files in User\LocalSettings\Temp

I sterated using "Standard User Analyzer" from Application Compatibility toolkit and it reported that my app is not UAC compatible because:
"DeleteFileA: File (\Device\HarddiskVolume1\Documents and Settings\Administrator\Local Settings\Temp\mtgstudio.madExcept) is denied 'DELETE' access with error 0x5."
"DeleteFileA: File (\Device\HarddiskVolume1\Documents and Settings\Administrator\Local Settings\Temp) is denied 'DELETE' access with error 0x5."
Checking the madExcept.pas file I found:
function GetTempPath : AnsiString;
var arrCh : array [0..MAX_PATH] of AnsiChar;
begin
if windows.GetTempPathA(MAX_PATH, arrCh) > 0 then begin
result := arrCh;
if result <> '' then begin
CreateDirectoryA(PAnsiChar(result), nil);
if result[Length(result)] <> '\' then
result := result + '\';
result := result + KillExt(ExtractFileName(ModuleName(0))) + '.madExcept';
CreateDirectoryA(PAnsiChar(result), nil);
result := result + '\';
end;
end else
result := '';
end;
Is there a good way to overwrite the madExcept behaviour and store the temp files in a UAC allowed location?
It doesn't look like there's anything to fix. The GetTempPath API function is exactly the function to use to get a location where a program is allowed to create temporary files. That the compatibility tester was unable to delete the directories doesn't mean that the directories should have been someplace else. It only means they couldn't be deleted at the time the program tried. It could be that another program (such as the one being tested) had a file open in one of those directories; Windows doesn't allow folders to be deleted when there are open files in them.
One possible source of problems is the way MadExcept creates the directories. It creates them such that they inherit the permissions of their parent directories. If deletion is forbidden for the parent directory, then it will also be forbidden for the newly created temp directories. That partly points to a configuration problem on your system: GetTempPath might be returning a path for a directory that doesn't exist. It just returns the first value it finds in any of the TMP, TEMP, and USERPROFILE environment variables. It's the user's responsibility (not your program's) to make sure those are accurate.
Knowing that MadExcept uses GetTempPath to discover the temp directory gives you an opportunity. You can call SetEnvironmentVariable to change the TMP value for your process, and MadExcept will create its directory there instead. (But if the system-designated location for temporary files already doesn't work, good luck finding some alternative to use.)

Delphi Determine filesize in real time

Is it possible in Delphi to determine the size of a file as it is being copied? I get a notification when a file is first copied to a folder, but need to wait until the copy is complete before I can process the file.
I've used JclFileUtils.GetSizeOfFile(Filename) but that gives me the 'expected' file size, not the current filesize.
Regards, Pieter
Prompted by the first answer I decided to give up on trying to determine when a file copy has completed. Instead I found that using TFileStream gave me a reliable indication whether a file is in use or not.
function IsFileInUse(Filename: string; var ResultMessage: string): boolean;
var
Stream: TFileStream;
begin
Result := True;
ResultMessage := '';
try
Stream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyWrite);
try
Result := False;
finally
FreeAndNil(Stream);
end;
Except on E: Exception do
ResultMessage := 'IsFileInUse: ' + E.Message
end;
end;
In this way I can keep on checking until the file is not in use anymore before attempting to process it.
It depends on the technique that is used by the copying function. Most copy-methods will allocate the disk space first before they start to copy a file. Thus, if you want to copy a file of 4 GB, the system starts by creating a file with random data for 4 GB in total. (Which is done lightning-fast, btw.) It then copies the data itself, but the file size is already what you expect.
This has as advantage that the sysmen can check if there's enough disk space available to actually copy the data.
If you write your own file copy function then you can have total control over how it does this. Else, you're limited to whatever the chosen copy-method offers you. So, how do you copy a file?
If you have control over the file copy process, it is easiest to have the copy routine create the file using a temporary filename, and when done, rename it to correct filename.
That way, you can use Windows folder monitoring to watch for the renaming (JCL contains a component to help with this, not sure about the name from here). When your code gets triggered you are sure the other side has finished writing the file.
A simple trick I used was to have the copying process create new files with a '$$$' extension. My code still got triggered for those but I ignored them until they were renamed to their proper filename.
Hope this helps.

Quickest way to find the oldest file in a directory using Delphi

HI
We have a large number of remote computers that capture video onto disk drives. Each camera has it's own unique directory and there can be up to 16 directories on any one disk.
I'm trying to locate the oldest video file on the disk but using FindFirst/FindNext to compare the File Creation DateTime takes forever.
Does anybody know of a more efficient way of finding the oldest file in a directory? We remotely connect to the pc's from a central HO location.
Regards, Pieter
-- Update
Thank you all for the answers. In the end I used the following.
Map a drive ('w:') to the remote computer using windows.WNetAddConnection2
//Execute dir on the remote computer using cmd.exe /c dir
//NOTE: Drive letters are relative to the remote computer. (psexec -w parameter)
psexec \\<IPAddress> -i /accepteula -w "c:\windows\system32" cmd.exe "/c dir q:\video /OD /TC /B > q:\dir.txt"
//Read the first line of "w:\dir.txt" to get the oldest file in that directory.
//Disconnect from the remote computer using windows.WNetCancelConnection2
You could also try FindFirstFileEx with FindExInfoBasic parameter, and on Windows 7 or Server 2008 R2 or later, FIND_FIRST_EX_LARGE_FETCH which should improve performance.
First, grab the RunDosAppPipedToTStrings routine from this page on how to run a DOS program and pipe its output to a TStrings. The example uses a TMemo's Lines property, but you can pass any TStrings in, such as TStringList. Note that this will fail silently if CreateProcess returns false. You might want to add an else case to the "if CreateProcess" block that raises an exception.
Then create a simple batch file in the same folder as your EXE. Call it getdir.bat. All it should say is:
dir %1
This produces a directory listing of whatever folder you pass to it. Unfortunately, "dir" is a DOS keyword command, not a program, so you can't invoke it directly. Wrapping it in a batch file gets around that. This is a bit of a hack, but it works. If you can find a better way to run DIR, so much the better.
You'll want to invoke RunDosAppPipedToTStrings with code that looks something like this:
procedure GetDirListing(dirname: string; list: TStringList);
const
CMDNAME = '%s\getdir.bat "%s"';
var
path: string;
begin
list.Clear;
path := ExcludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)));
RunDosAppPipedToTStrings(format(CMDNAME, [path, dirname]), list, false);
end;
Then all that's left to do is parse the output, extract date and time and filenames, sort by date and time, and grab the filename of the file with the lowest date. I'll leave that much to you.
If you can run something on the remote computer that can iterate over the directories, that will be the fastest approach. If you wanted to use Mason's example, try launching it with PsExec from SysInternals.
If you can only run an application locally then no, there's no faster way than FindFirst/FindNext, and anything else you do will boil down to that eventually. If your local computer is running Windows 7 you can use FindFirstFileEx instead, which has flags to indicate it should use larger buffers for the transfers and that it shouldn't read the 8.3 alias, which can help the speed a bit.
I had almost the same problem on the fax server software I developed. I had to send the faxes in the order they were received from thousands (all stored in a directory). The solution I adopted (which is slow to start but fast to run) is to make a sorted list of all the files using the
SearchRec.Time
as the key. After the file is in the list, I'm setting the attributes of the file as a faSysFile:
NewAttributes := Attributes or faSysFile;
Now when I do a new search with
FileAttrs := (faAnyFile and not faDirectory);
only the files that are not faSysFile are shown, so I can add to the list the files that are coming in new.
Now you have a list with all the files sorted by time.
Don't forget, when you start your application, first step is to remove the faSysFile attribute from the files in the folder so they can be processed again.
procedure FileSetSysAttr(AFileName: string);
var
Attributes, NewAttributes: Word;
begin
Attributes := FileGetAttr(AFileName);
NewAttributes := Attributes or faSysFile;
FileSetAttr(AFileName, NewAttributes);
end;
procedure FileUnSetSysAttr(AFileName: string);
var
Attributes, NewAttributes: Word;
begin
Attributes := FileGetAttr(AFileName);
NewAttributes := Attributes and not faSysFile;
FileSetAttr(AFileName, NewAttributes);
end;
procedure PathUnSetSysAttr(APathName: string);
var
sr: TSearchRec;
FileAttrs: Integer;
begin
FileAttrs := (faAnyFile and not faDirectory) and (faAnyFile or faSysFile);
APathName := IncludeTrailingBackslash(APathName);
if SysUtils.FindFirst(APathName + '*.*', FileAttrs, sr) = 0 then
try
repeat
if (sr.Attr and faDirectory) = 0 then
FileUnSetSysAttr(APathName + sr.Name);
until SysUtils.FindNext(sr) <> 0;
finally
SysUtils.FindClose(sr);
end;
end;
I know this is not the best solution, but works for me.

Resources