29

I am dealing with an application that still uses INI files for configuration. I wanted to make a temporary change to an INI file, but the result was not what I expected.

Let's say the original INI file is

[mysection]
mykey=myvalue

and I wanted to make a revertable change for testing purposes. From my (maybe incomplete or wrong) memories, I recalled that a lot of programs used batch files to modify INI files like so:

echo [mysection]>>test.ini
echo mykey=anothervalue>>test.ini

resulting in an INI file like

[mysection]
mykey=myvalue
[mysection]
mykey=anothervalue

The "revertable" part of this would be that I could simply delete the last 2 lines.

I don't recall any utilities which would have taken care about duplicate sections or keys. Obviously, "echoing" the section is needed to make sure that the section exists in case it did not exist before.

For the last 30 years or so, I believed that the last entry of an INI file wins, exactly for the reason to support these "echoing" changes.

Now, as I said, this change did not work out for the application I am using today. My first thought was that they used an incompatible INI file parser. So I conducted the following test myself, just relying on the Windows API:

// C#
static void Main()
{
    var sb = new StringBuilder(500);
    GetPrivateProfileString(
        "mysection", "mykey","", sb, (uint)sb.Capacity, "e:\\test.ini");
    if (sb.ToString() != "anothervalue")
        throw new ApplicationException("Not what I expected");
}

Guess what: the result is not what I expected.

Could someone confirm (or disprove) that I was wrong for 30 years? Did Windows 3.x and Windows 95 consider the first section and first key of INI files only?

(I am only interested in the "official" way of reading INI files using the Windows API GetPrivateProfileString())

user3840170
  • 23,072
  • 4
  • 91
  • 150
Thomas Weller
  • 485
  • 4
  • 8
  • Depending on the parser algorithm used you'll get a different answer: Loading the INI and populating a datastructure with the values then looking up a key will give the last matching entry. If instead the INI is scanned for a given key each time it's needed with the first match returned will give the first-listed one's value (natch). Other variations are possible. Just a thought... – Alex Hajnal Dec 16 '21 at 10:10
  • 8
    @Alex the parser algorithm doesn’t vary here, the question is specifically about GetPrivateProfileString. – Stephen Kitt Dec 16 '21 at 11:05
  • 10
    @Alex given Microsoft’s focus on backwards compatibility, I suspect that they would be unlikely to change the function’s behaviour (even if its implementation changed). – Stephen Kitt Dec 16 '21 at 11:09
  • @StephenKitt My thought was that the underlying implementation may have changed. Examples were what came immediately to mind. – Alex Hajnal Dec 16 '21 at 11:09
  • Note that you can edit comments for five minutes, no need to delete and re-create them ;-). – Stephen Kitt Dec 16 '21 at 11:11
  • 4
    @StephenKitt: the implementation definitely has changed. E.g. INI files can now be mapped to the Registry, so you may not get a value from the file at all. That's also the reason why I asked the question here. Maybe there was a breaking change - but obviously not so breaking – Thomas Weller Dec 16 '21 at 11:41
  • Just a thought, but if an installer is echoing values to the INI file, then it's possible that those values are for a game or something 'less-windows-friendly', and might not be using GetPrivateProfileString to retrieve the value. It might be an apples vs. oranges kind of thing. Yes, it's probably that GetPrivateProfileString() works a certain way for backwards compatibility (and hasn't changed) but if some jack-wagon third party code is doing the R/W, all bets are off. – Geo... Dec 16 '21 at 15:56
  • Where is the extern declaration of GetPrivateProfileString? That's probably where your issue lies. – selbie Dec 17 '21 at 05:52
  • 1
    @selbie: it's unlikely that a mistake in the P/Invoke declaration would change whether the implementation reads the first or the last entry. The major thing that could be declared in a wrong way is the encoding of the buffer. But with ASCII content, that will not make much of a difference. But yes, I'm considering Encoding issues ... – Thomas Weller Dec 17 '21 at 08:09
  • @ThomasWeller It’s pretty silly to do it in C# to begin with, though. This is a C API, adding .NET to the mix just adds unnecessary indirection that might distort the results, which should focus on the behaviour of the actual underlying API, not its marshalling through the CLR. – user3840170 Dec 17 '21 at 10:20
  • @user3840170: you're right. I'll add a native project as well. – Thomas Weller Dec 17 '21 at 10:26
  • 2
    FYI, at least on UNIX, echo [mysection]>>test.ini may not do what you expect. If you have a file named m (or y, s, e, c, ...etc...) in your current directory, for example, [mysection] can be treated as a glob expansion matching that filename, and thus write only the name of said file instead of the string [mysection]. Quoting it as echo "[mysection]" will prevent this. – Charles Duffy Dec 17 '21 at 17:23
  • @CharlesDuffy: thanks for the hint. I'm using Linux, so that's interesting. This question is clearly a Windows question, so no need to edit the question. – Thomas Weller Dec 17 '21 at 17:31
  • I've never seen this technique for temporary changes before. I always edited the ini file with a text editor and commented out the original value and added the new value. – JeremyP Dec 21 '21 at 08:57

2 Answers2

57

You have been wrong for 30 years.

I wrote the test program below and compiled it with Borland Pascal 7:

uses WinTypes, WinProcs, WinCrt;

var buffer: string; len: Integer; begin len := GetPrivateProfileString( 'mysection', 'mykey', '', @buffer[1], 255, 'C:\TEST.INI'); buffer[0] := Chr(len); WriteLn(buffer); end.

Then I ran it with the INI file from the question saved to C:\TEST.INI. It output myvalue both in Windows 3.10:

Test program run in Windows 3.10

and in Windows 95:

Test program run in Windows 95

Whatever INI-modifying utilities you were speaking of were simply not doing their job right. Sorry, old software was made of duct tape, smoke and mirrors, just like today. Where did you think we inherited this from?

user3840170
  • 23,072
  • 4
  • 91
  • 150
  • 3
    Another interesting test would be to show the result of GetPrivateProfileString without specifying a section or key — does it return the duplicate sections, or just the first one? – Stephen Kitt Dec 16 '21 at 11:10
  • 1
    It seems that the API call in 3.x was not capable of listing sections yet. In Windows 95, the names are listed duplicated. All this leans towards this API being based on an ad-hoc O(1)-ish-space scanner, without any persistent data structures. – user3840170 Dec 16 '21 at 11:43
  • 2
    @StephenKitt: I am planning to reverse engineer how GetPrivateProfileString works in detail, including abuse. I'll notify you when I have set up a Github repository – Thomas Weller Dec 16 '21 at 11:43
  • 3
    @user3840170: that Lexer/Parser/Scanner statement is exactly what I expect as well. It seems to have a rather small/trivial state machine. – Thomas Weller Dec 16 '21 at 11:46
  • 17
    There was no duct tape and no special magic. It always has been a simple loop scanning for headers and entries in sequential order. It's all about space. There is no space to hold multiple ini files which are usually only needed during startup or a few special times. It's more efficient when applications keep whatever they need long term in private structures. Similar there is no space for file system watching code to synchronize potential updates. 16 bit Windows is about delivering function at all, not doing it the most sophisticated way. – Raffzahn Dec 16 '21 at 12:30
  • 1
    @StephenKitt: the repo is here: https://github.com/WelliSolutions/IniFileFormatReverseEngineering I'm starting the implementation of tests now. – Thomas Weller Dec 16 '21 at 17:06
  • 17
    I am impressed that you were able to build a test in less than an hour. You must have had a dev environment ready to go. – Joshua Dec 16 '21 at 19:07
  • 11
    @Joshua Pretty much. I already had BP7 installed in a Windows 3.1 VM, the rest is a mere matter of typing. – user3840170 Dec 16 '21 at 19:41
  • 5
    Back in the day we often used to write our own minimal, inflexible, ini-file handlers. That's actually a hard habit to shift. Behaviour from duplicate keys was all-but undefined as it depended on the implementation (to put what @Raffzahn say a bit differently) – Chris H Dec 17 '21 at 09:08
  • So when was the semicolon as a comment character introduced into Windows? It was certainly valid already in 3.x. Or does GetPrivateProfileString() not support it for some reason? (Don't have running system to test, sorry.) – Lou Knee Dec 18 '21 at 14:42
0

Note that this - embedding multiple apparently-valid entries in a configuration file - is an awful approach to troubleshooting for a whole bunch of reasons, hence the need for INI files to allow comments or, as Microsoft themselves put it,

"You can include comments in initialization files. You must begin each line of a comment with a semicolon (;)."

I took this quote from the embedded documentation already present in WfW 3.11:

 64128  11-01-1993 03:11   WIN311/SYSINI.WRI
 22272  11-01-1993 03:11   WIN311/WININI.WRI
 76400  11-11-1993 03:11   WIN311/SYSTEM/KRNL386.EXE
Lou Knee
  • 261
  • 1
  • 5
  • What exactly is "awful" and what are the "whole bunch of reasons" that you consider it as awful? How does this relate to my question, which was about the precedence of duplicate entries and not about comments? – Thomas Weller Feb 21 '22 at 12:37
  • what are the "whole bunch of reasons... You don't know which value is being used by which application reading the file; there's no record of which was the original and which is the working modified version; if you append to the end of a longer file the second instance may not be visible in the editor (sysedit in EGA mode?); how does anyone else looking at the same file know what's going on...

    – Lou Knee Feb 21 '22 at 13:24
  • But the question in big letters at the top is Did INI files work in a different way on Windows 3.x than today? and my answer is that semicolon comments have been working since at least WfW3.11, with reference. – Lou Knee Feb 21 '22 at 13:28
  • And yet you don't answer the question whether they worked differently or not. Comments were supported in WfW 3.11 and they are supported today. So is your answer "They work the same way"? – Thomas Weller Feb 21 '22 at 14:51
  • 1
    Why did you choose to answer specifically on comments in INI files and not e.g. on Unicode support of INI files, which could also be a topic? Or about Registry mapping? A lot of things actually have changed regarding INI files. – Thomas Weller Feb 21 '22 at 14:53
  • Why did you choose to answer specifically on comments in INI files... because commenting out the old values (inc. why and who by) is a much less awful approach to troubleshooting, which is what you claim prompted the question.

    – Lou Knee Feb 22 '22 at 13:40
  • Ok, I see. In fact I knew about comments already, but I thought they weren't necessary, because of the way INI files are parsed. I have already taken countermeasures before I asked the question. – Thomas Weller Feb 22 '22 at 14:03