Delphi ini file vista/xp/win7 - delphi

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');

Related

run program with administrator access to write to registery

i tried to use this tutorial to work:
https://stackoverflow.com/a/14710803/1149172
first create a file with name uac.manifest with provided content
then create uac.rc width content of 1 24 "uac.manifest"
then copied the files to program folder (in delphi program sources folder) and changed the project file like this:
program Project4;
{.$R 'uac.res' 'uac.rc'} // UAC only
uses
Vcl.Forms,
Unit6 in 'Unit6.pas' {Form6};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm6, Form6);
Application.Run;
end.
at last i put my registery code at the form
procedure AddEntryToRegistry;
var key: string;
Reg: TRegIniFile;
begin
key := 'Software\Microsoft\Windows\CurrentVersion\Run';
Reg := TRegIniFile.Create;
try
Reg.RootKey:=HKEY_LOCAL_MACHINE;
Reg.CreateKey(Key);
if Reg.OpenKey(Key,False) then Reg.WriteString(key, 'MyApp', 'c:MyApp.exe');
finally
Reg.Free;
end;
end;
every thing seems ok and i dont got any runtime rror
but after clicking the button nothing happened (dont shoe any error and dont add the key to registery)!!!
where is wrong with my works!?
my delphi is xe5 and working on win 8ul
Firstly, I'm going to take it as read that your program is running elevated. If that's not happening then there's no need to look at any code. You did not say otherwise, so let us proceed under the assumption that you are succeeding to elevate.
You are suffering from the registry redirector. Your 32 bit process is running on a 64 bit machine. And so HKLM\Software is redirected to the 32 bit view, stored at HKLM\Software\Wow6432Node.
You can, if you need, use the KEY_WOW64_64KEY flag to access the 64 bit view. Combine this with the flags in the registry object's Access property.
However, the system reads keys from both 32 and 64 bit views of the registry when enumerating the startup programs so you do not need to do this. For the sake of simplicity and predictability I would leave your 32 bit program writing to the 32 bit view.
Your call to CreateKey should be removed. The system creates that key and you can safely assume it exists. And you should not use TRegIniFile. Use TRegistry instead.
Your code should look like this:
procedure AddEntryToRegistry;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create(KEY_ALL_ACCESS);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Run', False) then
Reg.WriteString('MyApp', 'C:\MyApp.exe');
finally
Reg.Free;
end;
end;
Should you feel that you need to write to the 64 bit view then it is done like this:
procedure AddEntryToRegistry;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
Reg.Access := KEY_ALL_ACCESS or KEY_WOW64_64KEY;
if Reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Run', False) then
Reg.WriteString('MyApp', 'C:\MyApp.exe');
finally
Reg.Free;
end;
end;
Judging by your comments, it looks like you might be failing to elevate. The lack of a UAC dialog when your program starts is the tell-tale sign that this is happening. Once your program starts without a UAC dialog, there's no point continuing. You will not write to HKLM without elevation.
Regarding your manifest, you can link only one. So if you want to specify a manifest other than the Enable runtime themes manifest that the IDE can provide, you need to do it all yourself.
In the project options specify that you want to use a custom manifest. That's under Project | Options | Application | Runtime themes. Set the drop down to Use custom manifest. And then supply the file name of your manifest. You'll want to add in the comctl32 v6 part to make sure that you get runtime themes. But don't worry about that now. Just concentrate on getting elevation sorted, and the registry code working.
You are also silently ignoring any errors which does make things a little harder to debug. If it so happens that you are not elevating, then running the code is rather pointless. You know it must fail. But you could at least make it easier to diagnose the problem by throwing an error if OpenKey fails.
procedure AddEntryToRegistry;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create(KEY_ALL_ACCESS);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if not Reg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Run', False) then
raise EMyExceptionClass.Create('Could not open registry key');
Reg.WriteString('MyApp', 'C:\MyApp.exe');
finally
Reg.Free;
end;
end;
One final point to make is that writing to this registry key is an admin task. You should require elevation once only, not every time your application starts. If you are planning to require elevation for your application just for this purpose, then you must re-design. This admin task should be performed outside your main application. The most natural place is inside your install program which users will accept requiring elevation.
You are saving your app path to the Run key of the HKEY_LOCAL_MACHINE hive. You should be using the HKEY_CURRENT_USER hive instead, then you will not need to use UAC elevation anymore (unless your app is doing other things that require admin rights).

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.

Registry "forgetting" values

My software stores some values in HKCU\Software\mysoftware which has never been a problem. However, I now also store some values in HKCU\Software\mysoftware\Licenses - ie a subdirectory of the existing data. However, 24 hours later those values simply disappear.
I put logging in my software to check it wasn't deleting the keys and there are no log entries, which is quite expected as the software isn't even running. Somebody suggested using process viewer (from Microsoft) which can trace registry events: however, that gave up working (too many events, even though I'm only watching HKCU\Software\mysoftware registry events) so I'm still no closer to finding what is deleting those keys!
I use Avast! anti-virus which somebody else suggested could be a culprit. The actual keys are of the form REG_SZ with "[keyname]=[base64string]". Nothing fancy, around 150 characters. A few hours after the software is closed I can use registry editor to see those values. But sometime later, they're gone! I rarely reboot.
So what else could be deleting the keys?
UPDATE
Here's the Delphi 6 code:
procedure XXX;
var
registry : TRegistry;
code : string;
begin
try
registry := TRegistry.Create;
if registry.OpenKey(REGISTRY_ROOT, true) then begin
code := [...a base64 string, around 150 chars on average...];
registry.WriteString(simname, code);
end;
finally
FreeAndNil(registry);
end;
end;
Still no closer to finding a solution; however, all I can think of (suggested elsewhere) is that it's the anti-virus confining the application to a sandbox, though there's no notification of such.

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.

How to delete files matching pattern within a directory

That is, delete all files matching pattern within a given directory
Example, Delete all *.jpg files within DirectoryName
procedure TForm1.Button1Click(Sender: TObject);
begin
DeleteFiles(ExtractFilePath(ParamStr(0)),'*.jpg');
end;
procedure DeleteFiles(APath, AFileSpec: string);
var
lSearchRec:TSearchRec;
lPath:string;
begin
lPath := IncludeTrailingPathDelimiter(APath);
if FindFirst(lPath+AFileSpec,faAnyFile,lSearchRec) = 0 then
begin
try
repeat
SysUtils.DeleteFile(lPath+lSearchRec.Name);
until SysUtils.FindNext(lSearchRec) <> 0;
finally
SysUtils.FindClose(lSearchRec); // Free resources on successful find
end;
end;
end;
In more recent versions of Delphi, you would probably use the classes in System.IOUtils, which are essentially wrapping FindFirst, FindNext etc:
procedure DeleteFilesMatchingPattern(const Directory, Pattern: string);
var FileName: string;
begin
for FileName in TDirectory.GetFiles(Directory, Pattern) do TFile.Delete(FileName);
end;
You can use the SHFileOperation function. The nice thing about using SHFileOperation is you have the option of deleting the files to the recycle bin and you get the normal API animations so the user will know what is going on. The downside is the delete will take a little longer than Jeff's code.
There are several wrappers out there. I use this free wrapper from BP Software. The entire wrapper file is only 220 lines and is easy to read and use. I don't install this as a component. I have found it easier to add this unit to my project and just Create and free the object as needed.
Update: The download link for the BP Software site is no longer valid. There is an older version on the Embarcadero website.
TSHFileOp (1.3.5.1) (3 KB) May
31, 2006 TComponent that is a wrapper
for the SHFileOperation API to copy,
move, rename, or delete (with
recycle-bin support) a file system
object.
The file name parameter for SHFileOperation supports MS DOS style wildcards. So you can use the component like this:
FileOps := TSHFileOp.Create(self);
FileOps.FileList.Add(DirectoryName + '\*.jpg');
FileOps.HWNDHandle := self.Handle;
FileOps.Action := faDelete;
FileOps.SHOptions :=
[ofAllowUndo, ofNoConfirmation, ofFilesOnly, ofSimpleProgress];
FileOps.Execute;
I usually show the "Are you sure" message myself so I always pass the ofNoConfirmation flag so Windows does not ask again.
If you don't want to delete every jpg file or you need to delete from multiple directories you can add full file names or different paths with wild cards to the FileList string list before calling execute.
Here is the MSDN Page for SHFileOperation
Note that SHFileOperation has been replaced by IFileOperation starting with Windows Vista. I have continued to use SHFileOperation on Windows Vista without any problems.

Resources