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.
Related
Our program is something like this:
C:\Main
C:\Main\Utils
C:\Main\App.exe
For C:\Main and all subfolders, we have all privileges for Everyone set to allow Full Control.
In our code, we create a .bmp file:
BmpSt := TMemoryStream.Create;
BmpSt.SetSize(Length(dta));
BmpSt.WriteBuffer(dta[1],Length(dta));
BmpSt.Seek(0,soFromBeginning);
Bmp := '\Main\Util\' + FormatDateTime('YYYYMMDDHHNNSS',Now) + '.bmp';
BmpSt.SaveToFile(Bmp);
if not DeleteFile(Bmp) then begin
ShowMessage('No');
end;
If I don't run App.exe as an administrator, it will never delete the file. However, when I do run it as an administrator, it deletes the file just fine.
Privileges are set to Full Control for Everyone.
I know this might not exactly be a Delphi question since its heavily dealing with Windows UAC, but this code worked fine when our program was in Delphi 5, now we are building it in Delphi 10.
We are also getting similiar issues using a TPrinter object to insert the above .bmp onto a print page, but I'm confident that the issues with DeleteFile will solve those.
Right after using DeleteFile() you can use GetLastError() to get what went wrong and trigger an exception for it with CheckOSError(). Once you know why it is failing you can troubleshoot further.
if not deletefile(bmp) then begin
CheckOSError(GetLastError);
end;
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.
I have a function which works ok to check if a local file is in use.
However if I map a network drive and try to check if a file from the mapped drive is in use then the result of the function is always false.
I need to wait before a large file is being copied to the mapped drive and after completion I rename the file.
If the file in not in use then i start performing various actions else i wait another minute and check again.
How can I modify the function below in order to work with mapped drive files that are constantly copied?
Thank you
function IsFileInUse(FileName: TFileName): Boolean;
var
HFileRes: HFILE;
begin
Result := False;
if not FileExists(FileName) then
begin
showmessage('Fisierul "'+Filename+'" nu exista!');
Exit;
end
else
begin
HFileRes := CreateFile(PChar(FileName),
GENERIC_READ or GENERIC_WRITE,
0,
nil,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
Result := (HFileRes = INVALID_HANDLE_VALUE);
if not Result then
CloseHandle(HFileRes);
end;
end;
What you are claiming is that CreateFile succeeds in opening a file in exclusive mode whilst another party is writing to the file. Possible explanations:
You have made a mistake with the file name and the file you are opening is not the one in use.
The other party is writing to the file without having locked it. In other words it opened the file with a share mode that allowed other parties to read and write. This would be quite unusual. If this is the case then you need to fix the other process.
The remote file server is broken and fails to respect locks. I'd regard this as quite unlikely.
I think the final option can be rejected immediately. Option 2 seems rather unlikely. Which leaves option 1. You are able to lock the file because it is not locked.
I'd also comment that the function is spurious. You can remove it. Simply attempt whatever operation you need to perform. If that operation fails due to a sharing violation you know that the file was locked. Consider also the race condition in any code using that function. The fact that a file is unlocked now does not prevent another party locking the file before you can do anything with it.
We have a client server software that needs to be updated. I need to check if the file is currently being accessed. Is this possible if so how Delphi code if possible. The only place I can see if the file is open is under the shared folders open files. I have tried this code but just shows that the file is not opened.
function TfrmMain.FileInUse(FileName: string): Boolean;
var H_File : HFILE;
begin
Result := False;
if not FileExists(FileName) then
begin
showmessage ('Doesnt Exist');
exit;
end;
H_File := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, 0,
nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
Result := (H_File = INVALID_HANDLE_VALUE);
showmessage('Opened');
if not Result then
CloseHandle(H_File);
end;
There is a great deal of information you can access over the WBEM sub-system provided by Windows. I believe there are good WBEM components out there, but you could also import the "Microsoft WMI Scripting" COM Type Library (though this takes a little work to figure out how it works).
If you query for Win32_ServerConnection objects, you get a list of items currently in use, much like you can view using the 'Computer Management' tool from the Administrative Tools.
Not necessarily an answer, but I am currently doing something similar - because the main executable might be updated during working hours though I have created a intermediary application that checks to see if a locally cached copy of the file is up to date, I then run this locally cached copy.
I found this similar item, someone proposes to use the NetFileEnum function
Update: I've added the following code:
function TSettingsForm.AppDataPath: string;
//CSIDL_APPDATA Individual user Data
//CSIDL_COMMON_APPDATA Common to Computer Data
// works so long as people have at least IE 4. (and Win95 or better)
var
r: Bool;
path: array[0..Max_Path] of Char;
begin
r := ShGetSpecialFolderPath(0, path, CSIDL_APPDATA, False) ;
if r then result := path
else result := '';
end;
And I've changed the setinifilename function (See below). It will not create the folder structure.
--End update--
I'm behind the times, on what to and not to do. This is how I am currently saving the settings for my software. I just tested it on Vista not logged in as an administrator, and it gives me an error message cannot write ini file. So I'm guessing I'm supposed to write the data to a data folder? I've never used vista/win7 before, and want this software to be windows 2K+ compatible. What should I do to save the settings. I also really didn't want to mess with the registry, because every little bit you add to it, slows down the computer just that much more... (or so It seems)
Thanks for any input.
procedure TSettingsForm.setinifilename;
var filename:string;
Path:string;
begin
filename:='key.ini';
path:=AppDataPath+'\MyCompanyName\ProductName\';
if NOT DirectoryExists(path) then
CreateDir(path);
inifilename:= path+filename;
end;
procedure TSettingsForm.SaveSettings;
var
appINI: TIniFile;
begin
appINI := TIniFile.Create(inifilename) ;
try
low:= Trunc (edt_low.value);
high:=Trunc (edt_high.value);
appINI.WriteInteger('SPEED','LOW',low);
appINI.WriteInteger('SPEED','HIGH',high);
appINI.WriteString('PROXY','SERVER',edtProxyServer.Text);
appINI.WriteString('PROXY','PORT',edtProxyPort.Text);
appINI.WriteString('PROXY','USERNAME',edtProxyUserName.Text);
appINI.WriteString('PROXY','PASSWORD',edtProxyPass.Text);
// status.text:='Saved Data';
finally
appIni.Free;
end;
end;
procedure TSettingsForm.GetSettings;
Var
appINI : TIniFile;
begin
appINI := TIniFile.Create(inifilename) ;
try
//if no last user return an empty string
edt_low.value:= appINI.ReadInteger('SPEED','LOW',0);
edt_high.value:= appINI.ReadInteger('SPEED','HIGH',0);
low:= Trunc (edt_low.Value);
high := Trunc (edt_high.Value);
edtProxyServer.Text:=appINI.ReadString('PROXY','SERVER','');
edtProxyPort.Text:=appINI.ReadString('PROXY','PORT','0');
edtProxyUserName.Text:=appINI.ReadString('PROXY','USERNAME','');
edtProxyPass.Text:= appINI.ReadString('PROXY','PASSWORD','');
finally
appINI.Free;
end;
end;
In Vista, your program is NOT allowed to write to the program files directory where your program is located.
You now have to save your ini files in the AppData directory.
A description of how to do this in delphi is at:
http://www.theabsolute.net/sware/delphivista.html#datafolder
And to be Vista/Windows 7 compatible, the rest of that web page will be a good guideline.
For your update, you cannot CreateDir more than 1 level deep at once. Use the ForceDirectories function instead:
path:=AppDataPath+'\MyCompanyName\ProductName\';
if NOT DirectoryExists(path) then
ForceDirectories(path);
p.s. Don't be afraid to write program settings to the Registry. That's what the registry is for. In fact, it properly handles settings for different users for you when different users are logged in. The Registry works in the same way in 98/Vista/7. Whereas ini files have actually been depreciated, and are no longer used by Windows.
You say you don't want to mess with the registry because "every little bit you add to it, slows down the computer just that much more". Actually that is NOT true. The registry is simply a database. And if it is 10 MB or 100 MB, the difference in time it takes to access is imperceptable.
It's all those companies selling Registry Cleaner programs that are trying to keep this fairy tale going. Using their cleaners can do you more harm than good. All they need to do is wipe out one or two important entries and you can be in deep doo-doo. Please read this article about Registry Cleaners, and especially the "Marginal performance benefit" section which explains correctly that the problems Windows 98 and earlier had with the Registry have been mostly fixed.
If your program adds more than 2 or 3 KB to the Registry, that will be a lot, and it is an insignificant amount. Use the registry. Do it right.
You should use the ApplicationData directory for your app data, In Delphi you can find this folder programatically using the shell api function SHGetSpecialFolderLocation
Embarcadero have a FAQ page on this, here.
As already mentioned - dont save anything in the app folder.
You should split your configuration settings into two parts :
One part containing the settings that must work regardlees of the user - that part should be stored in COMMON_APPDATA.
A Second part containing the individual users settings (users personal choice of font etc) - that part should be stored in APPDATA
As for the CreateDir, it is true that you cannot create more than one level at a time - however, Delphi has the ForceDirectories function that can do exactly that.
e.g. ForceDirectories('C:\MyFolder\SubFolder\SubSubFolder');