Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations TouchToneTommy on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Mysterious TIniFile Coding Issue 1

Status
Not open for further replies.

Glenn9999

Programmer
Jun 19, 2004
2,311
US
I have a program here that's basically been working well. I've chosen to share it, but I get a report back on something from someone. The report indicates that the message in the code below is being triggered.

Code:
ProgramPath := configini.ReadString('Configs', 'ProgramPath', 'Error');
if ProgramPath = 'Error' then
  HandleError('ProgramPath not found in ini file.')

The program is distributed with a stock INI file and short of the user going in and completely deleting or changing the tag, the message should logically never appear. Is there something about any of the newer operating systems than XP which makes TIniFile not work properly? The Create line is not too far up the chain from the shown code in terms of logic, so there shouldn't be much of a chance that anything is corrupted in the meantime.

For this program, failure to read this line constitutes a show-stopper, but I can't duplicate it on anything I have, so I'd really be interested if there are issues that I might not be aware of given what I'm using or not?

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Write permission? Is that default when you create a IniFile and only read it? The design of the program indicates that the INI file be in the same folder as the program. Are INI files in program-oriented directories limited in this way on the newer OSes?

FWIW, I thought I'd include the code behind the Create method, just in case there's some hangup about it in newer OSes.

Code:
function GetHomeDirectory: string;
// returns the directory that the main program is running in.
  begin
    Result := ExtractFilePath(Paramstr(0));
  end;

configini := TIniFile.Create(GetHomeDirectory + 'myprogram.ini');



It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
To create a file (not only INI) within a folder you must have write permission in this folder.
As I said before, try creating the INI within another folder, or try to test execute your application as administrator.

note: only the administrator usually has permission to write in the Program Files folder


[URL unfurl="true"]http://www.imoveisemexposicao.com.br/imoveis-venda-são_paulo-residencial-apartamento[/url]
 
Glenn,

I suppose this is W7 we are talking about?
if your program file is located in the program files directory it could well be that the ini file your program reads is NOT the ini file under the program files folder.
as Imex already implied UAC may be the culpritt here.

if you created the ini file with unsufficient rights, it ends up under this path:

C:\Users\<username>\AppData\Local\VirtualStore\Program Files\<your program folder>
Cheers,
Daddy

-----------------------------------------------------
Helping people is my job...
 
Adding a general advice,

Configuration data that is not user related but program relate should reside in the %ProgramData% folder
The XP equivalent is \Documents and Settings\All Users\Application Data

/Daddy

-----------------------------------------------------
Helping people is my job...
 
Hmm Daddy is as usual correct in this, but he's not going into much detail as to how you would for example, find what %ProgramData% actually is on any Windows platform. Or how to get the permissions to write to it when you do, and it is quite involved.
I'm working through it now (as I need to update several of my own apps) and will post the results as soon as I have them, unless Daddy gets there first that is!

Steve: N.M.N.F.
If something is popular, it must be wrong: Mark Twain
 
I would hazard a guess that the JCL fileutils.pas already has functions to get these paths. You can also read the environment variables which should point you to the correct folders.
 
Djang: I haven't looked at JCL fileutils yet? But have done a good deal of web searching for 'working' answers to this (or put another way, wasted a whole day)

First of All I am using Delphi XE (1) under Windows 7, User logged in as a normal account.

Getting the paths to the system folders is simple enough
You need to pass in a constant that is declared in the shfolder unit
e.g. CSIDL_COMMON_APPDATA for the Users Program Data folder

So thanks to Zarko Gajic of about.com Delphi for this
Code:
function GetSpecialFolderPath(folder : integer) : string;
const SHGFP_TYPE_CURRENT = 0;
var  path: array [0..MAX_PATH] of char;
begin
   if SUCCEEDED(SHGetFolderPath(0,folder,0,SHGFP_TYPE_CURRENT,@path[0])) then
     Result := path
   else
     Result := '';
 end;


Then you need to get permission to write in here (assuming the user wont have admin rights)

The most common thing I found was to raise the privileges of your application by using a custom manifest.

Created used this manifest source, there are several example of this on the web

Code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity  version="3.1.0.0"
  processorArchitecture="*"
  name="RTO_Monitor"
  type="win32"/>
  <description>elevate execution level</description>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="highestAvailable"
                                 uiAccess="true"/>
      </requestedPrivileges>
    </security>
  </trustInfo>
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <!--The ID below indicates application support for Windows Vista   -->
      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
      <!--The ID below indicates application support for Windows 7 -->
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
    </application>
  </compatibility>
</assembly>

Most speak about compiling this externally using BRCC32, but I found that (in XE anyway) adding the manifest source and the .rc file (below) to the project is the only way to get this to compile at all.
I think XE compiles it for you, but I am not 100% about this.

Code:
1 24 "RTO_Monitor.manifest"

adding this to the main code

Code:
implementation
{$R *.DFM}
{$R 'RTO_Monitor.res'}

It seems to be important not to have any references to XPMan in the code I have none.

Having done this recompiling the project throws up these warnings (for me)

[DCC Warning] W1056 Warning: Duplicate resource: Type 14 (ICON GROUP), ID MAINICON; File C:\Users\Steve\Newsoft\321\RTO_Monitor\RTO_Monitor.RES resource kept; file C:\Users\Steve\Newsoft\321\RTO_Monitor\RTO_Monitor.res resource discarded.
[DCC Warning] W1056 Warning: Duplicate resource: Type 14 (ICON GROUP), ID MAINICON; File C:\Users\Steve\Newsoft\321\RTO_Monitor\RTO_Monitor.RES resource kept; file C:\Users\Steve\Newsoft\321\RTO_Monitor\RTO_Monitor.res resource discarded.
[DCC Warning] W1056 Warning: Duplicate resource: Type 16 (VERSIONINFO), ID 1; File C:\Users\Steve\Newsoft\321\RTO_Monitor\RTO_Monitor.RES resource kept; file C:\Users\Steve\Newsoft\321\RTO_Monitor\RTO_Monitor.res resource discarded.
[DCC Warning] W1056 Warning: Duplicate resource: Type 16 (VERSIONINFO), ID 1; File C:\Users\Steve\Newsoft\321\RTO_Monitor\RTO_Monitor.RES resource kept; file C:\Users\Steve\Newsoft\321\RTO_Monitor\RTO_Monitor.res resource discarded.

When I have seen this sort of thing before I have found that, something will be wrong, perhaps here the .res is not actually going to work, as is born out by the results..
But I am not sure about this at all, no idea about why the warnings occur or how to fix them.


But the project does run.
However....
When attempting to write an ini file to the location it falls over. (access denied)
Code:
  FName := ChangeFileExt(extractfilename(Application.ExeName), '.INI' );
  FIniFile := TIniFile.Create(GetSpecialFolderPath(CSIDL_COMMON_APPDATA) + '\Application Data\Newsoft\RTO_Monitor\' + FName);
  with FIniFile do
     try
        WriteString(SECTION, 'LoadName', LoadFilename);
  //etc..

So it hast worked, now I must decide whether or not to waste another day, or just continue installing apps to a 'new' folder and carry on as before.
Unless anyone can see what is wrong with this?





Steve: N.M.N.F.
If something is popular, it must be wrong: Mark Twain
 
Hi sggaunt,

yes it's worth the hassle because this way you are adhering to the windows design guidelines and your application is guaranteed to work with future windows versions...
about CSIDL_COMMON_APPDATA:

CSIDL_COMMON_APPDATA This folder should be used for application data that is not user specific. For example, an application may store a spell check dictionary, a database of clip-art or a log file in the CSIDL_COMMON_APPDATA folder. This information will not roam and is available to anyone using the computer. By default, this location is read-only for normal (non-admin, non-power) Users. If an application requires normal Users to have write access to an application specific subdirectory of CSIDL_COMMON_APPDATA, then the application must explicitly modify the security on that sub-directory during application setup. The modified security must be documented in the Vendor Questionnaire.

so to resume:
global application settings go under CSIDL_COMMON_APPDATA\<Your program name>, this should be one time setup data (ie the setup program should write these settings)
user data should go under CSIDL_LOCAL_APPDATA\<Your program name>

normally an end user application should not have the need to perform UAC elevation...

/Daddy


-----------------------------------------------------
Helping people is my job...
 
So I am attempting to write the .ini files (user program settings/ data file locations etc) to the wrong location anyway?
and don't need to do the elevation. Will try again.

Steve: N.M.N.F.
If something is popular, it must be wrong: Mark Twain
 
yes indeed:

you need 2 separate ini files:

- one for the application/all users (ie data file locations - if they are the same for everyone)
- one for user specific settings

doing it like this also has the advantage that your application will work under Citrix/RDP environments...

/Daddy

-----------------------------------------------------
Helping people is my job...
 
Hmm diddnt work
I still get a write error

I hard coded it to ensure no string errors

FIniFile := TIniFile.Create('C:Users\Steve\Appdata\Local\Newsoft\RTO_Monitor\RTO_Monitor.ini');

Oddly 'I' can create folders/files in here with Windows explorer.
Is this something to do with the app being developed in it own folder?
But it is a sub folder of the current user in this case C:\Users\Steve\

Steve: N.M.N.F.
If something is popular, it must be wrong: Mark Twain
 
If I put the exe in the Appdata sub folder (where the .ini file should go) then it works!
Will it work in the Program files folder?

Steve: N.M.N.F.
If something is popular, it must be wrong: Mark Twain
 
Yes that (Application in Program Files(x86)) worked too, the inifile was updated in the Appdata sub folder.
So it should work after an install.
Complicates testing a little!

Steve: N.M.N.F.
If something is popular, it must be wrong: Mark Twain
 
Hmm still learning, I think the not writing was a bit of a red herring. As I later found that by doing this
in an attempt to be able to test, I was able to use both the Debug and release builds in the environment, even though I thought that the release version would not work.
On making an installer and deploying to my Vista Machine it fell over (no write access)
What I have found is that if the folders where I want to put the .ini exist then I can write the file (files can be written folders cannot be created)

Code:
{$IFDEF DEBUG}
    FIniFile := TIniFile.Create(FName);
    CommsModule.CPortDriver.LoadSettings(stIniFile, extractfilepath(Application.ExeName) + 'ComPort.ini' );
{$ELSE}
    FIniFile := TIniFile.Create(GetSpecialFolderPath(CSIDL_LOCAL_APPDATA) + '\Newsoft\RTO_Monitor\' + FName);
    CommsModule.CPortDriver.LoadSettings(stIniFile, GetSpecialFolderPath(CSIDL_LOCAL_APPDATA) + '\Newsoft\RTO_Monitor\ComPort.ini' );
{$ENDIF}

So I can get the installer to create the folders, trying now.


Steve: N.M.N.F.
If something is popular, it must be wrong: Mark Twain
 
Yes the installer should create the folder.

/Daddy

-----------------------------------------------------
Helping people is my job...
 
Some scripting was required but yes it does!
I use Innosetup, paid installers might have an option to do this for you?

The real problem was TInifile.create will not create a path in a system folder (why?)
But as I might want to distribute apps for testing without an installer, came up with this.

in FormActivate.
Code:
 AppdataLoc := GetSpecialFolderPath(CSIDL_LOCAL_APPDATA) + '\'+ COMPANYNAME + '\' + ChangeFileExt(extractFilename(application.ExeName),'');

    if not sysutils.DirectoryExists(AppDataLoc) then
        begin
          if not SysUtils.ForceDirectories(AppDataLoc) then
               showmessage('ERROR: Failed to create '+  AppDataLoc);
        end;

    ReadIniFile;

and in the Ini read/write functions

Code:
   FName := ChangeFileExt(extractfilename(Application.ExeName), '.ini' );

{$IFDEF DEBUG}
    FIniFile := TIniFile.Create(FName);
    CommsModule.CPortDriver.StoreSettings(stIniFile,  extractfilepath(Application.ExeName) + 'ComPort.ini');
{$ELSE}
    if sysutils.DirectoryExists(AppDataLoc) then
      begin
         FIniFile := TIniFile.Create(AppDataLoc + '\' + FName);
         CommsModule.CPortDriver.StoreSettings(stIniFile, AppDataLoc +'\'+ 'ComPort.ini');
      end
    else
      exit;
{$ENDIF}

    with FIniFile do
     try
       .....


Steve: N.M.N.F.
If something is popular, it must be wrong: Mark Twain
 
Thanks for the conversation. I'm glad that bringing up the issue was helpful to someone else. This said in reading this, I'm trying to put all this together for something that I can do (and not test since I don't have access to anything to test it on) which will fix the problem. I don't mean any ill-will here, but unfortunately I'm really not seeing much regarding a solution, especially since the user I mentioned has tried it as an administrator as well.

What seems to be coming out of this is that any associated configuration data files have to be put into the directory path that qualifies as "Application Data", due to the user-unfriendliness of the newer versions of Windows. This would necessitate that any applications, no matter how small, would require an installer to function with any associated files, or to depend on the end-user to navigate the labyrinth of directories in the mystery of putting the file in the right place and hope they did it right (or can locate it to edit it). This would constitute something that would go down as "very bad", given the necessity of this file.

Ultimately, not being able to read this file in the indicated place would break about 3/4 of the intended end-user requirements for the program. As well, replacing the process with something else would require a 100% total rewrite of the application, which would require substantial time on such platforms in order to get it working properly.

From what it seems, the only good solution would be copying the distributed file to this "Application Data" directory, and then reading the file from there. But wouldn't that cause the exact same problems as indicated here previously, since any changes made to it would have to be copied back where the user would readily find it? Or am I misunderstanding this and it's a much easier fix than the other comments in this thread seems?

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Glenn.
Not sure I fully understand what you want to do, but it sounds similar to my primary users requirement.
i.e He wants his customers to be able to replace their own ini files.
In fact he intend to distribute his own (much larger and more complex) VB.net application as an install to its own folder, in the root of the 'C' main drive, which as we have seen is now a No No.

As far as the App data folder thing goes, I don't see this as a problem now..
I found (finally) that in fact you don't 'need' an installer, but as the ini file routines cannot create a path it seems difficult.
But you can use 'ForceDirectories' to create a path to your folders in the App Data folder, before you write the ini file
Then you can write to it.

Users can access the application data folder and replace the ini file, but as it counts as a system folder it is set to hidden, so you don't see it by default in Windows explorer.
But that is easily changed in folder options.


Steve: N.M.N.F.
If something is popular, it must be wrong: Mark Twain
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top