Table of Contents
The Setup
API is the recommended way to parse driver.INF and txtsetup.OEM files. Unfortunately, the Setup
API is a collection of C style functions for which the .NET Framework has no equivalent interface. This article provides some simple .NET (C#) classes that wrap common functions from the Setup
API used for parsing INF files.
I once had a need to parse hundreds of driver.INF, txtsetup.OEM, and other random .INF files written by dozens of third-party companies and individuals. I quickly found that the syntax of these files is more complex than I originally thought. At first they appear simple, for example:
[Section 1]
key1 = field1,field2,fieldN
key2 = int,string,or,binary data
no,key,on,this,line
[Section2]
key1 = field1,field2
[Section 1]
keyN = field1,field2,fieldN
There can be many sections, each with many "key
" lines. The keys are not necessarily unique, even within a section. Keys are not even required (a line can just be a list of values). Notice that [Section 1]
appears twice. This is valid syntax that the Setup
API handles transparently, as if the section wasn't physically split.
There can be many fields on a given line. The fields can contain integers, strings, multi-strings, and binary data. There are all kinds of rules regarding comments, string fields with embedded blanks/commas/quotes, decimal vs. hex integer formats, line continuation, and localized string substitution.
The text encoding for .INF files is not ASCII or even UTF-8. It's "Windows Western European", also known as Code Page 1252. This is a single-byte encoding very similar to, but not the same as, ASCII. Certain characters, such as the "registered" or "trademark" symbols (I don't remember which) have different encodings in Code Page 1252 compared to ASCII (or don't even exist in ASCII).
Heed my warning, grasshopper: You do not want to parse such files manually. You want to use the Setup
API since it can handle all the syntactic nuances you might stumble across.
After using the Setup
API directly for a while, I found myself repeating the same code patterns and error checking over and over. Many of the functions use a file handle or a C structure just begging to be encapsulated in a class, so I decided to write some C# wrapper classes. Using these classes made my code significantly cleaner and easier to write.
The download contains two classes you will use directly: InfFile
and InfLine
.
- The
InfFile
constructor takes a file path. It merely captures the path, so no errors are possible. InfFile.Open()
opens the file. It returns 0 if it succeeds, a Win32 error code if it fails. More on error checking later.InfFile.EnumSection()
can be used to enumerate the sections within the file.InfFile.FindFirstLine()
returns an InfLine
representing the first line within the specified section.InfFile.FindFirstKey()
returns an InfLine
representing the first line within the specified section having the specified key.- When you're finished with the file, be sure to call
InfFile.Close()
or Dispose()
. InfFile implements IDisposable
.
The InfLine
class represents a line from within a section in the file. It has methods that do the following:
- Get the number of fields on the line.
- Get the string, multi-string (array), int, or bytes in the Nth field
- Get an
InfLine
for the next sequential line or the next line with a given key.
When calling InfLine.GetString(int fieldNum)
, pass 0
to get the line's key (value left of "=
"). Pass 1
to get the first comma-separated value to the right of the "=
", and so on.
As a rule, you have to have some idea of what sections and keys you're looking for, the type of data in each field, and so on. When parsing a true driver.inf file, many lines are references to other sections. For example, a string
read from one line might the name of (or part of the name of) a section.
Both classes contain two error-related properties: LastError
and LastMessage
. LastError
is the Win32
error code from the last method called. After calling any method in either class, you can check if LastError
is 0
. If it's not 0
, an error occurs and LastMessage
contains the error message corresponding to the error code in LastError
. These properties are reset when any method is called.
Also, most of the methods in both classes, such as InfLine.GetInteger()
, have two overloads. In this case, both overloads accept an int
specifying the field number that should contain an integer. An error occurs if the field doesn't contain an integer or there is no such field. The first overload (shown below) returns a Win32
error code (error codes are always uint
). If no error occurs, it returns 0
and the int
value read from the file is passed back as an out
parameter. The second overload returns no error information.
public uint GetInteger(int fieldNum, out int intVal);
public int GetInteger(int fieldNum);
In either case, you can check LastError
after calling the method, but sometimes it's more convenient to check the error code returned directly by the method.
Consider this simple INF file (from test.inf in the download).
[Section 1]
key1 = "hello, world", 42
key2 = "written in", 2010
[No Keys Section]
val1, val2
val3, val4
The first section contains two lines, each with a key, a string, and an integer. The integer fields could be parsed as strings, but we'll be parsing them directly into int
variables. The second section contains lines with no keys, just comma-separated string values.
The following code opens and parses the test.inf file. This code is also in the download.
static void Main(string[] args)
{
using (InfFile file = new InfFile(".\\test.inf"))
{
if (file.Open() == 0)
{
ReadSection1(file);
ReadSection2(file);
}
else
{
Console.WriteLine("An error occurred opening the INF file: " +
file.LastMessage);
}
}
}
static void ReadSection1(InfFile file)
{
InfLine line = file.FindFirstKey("Section 1", "key1");
if (line == null)
{
Console.WriteLine("Error getting first key: " + file.LastMessage);
}
else
{
do
{
string keyVal;
string strVal;
int intVal;
if (line.GetString(0, out keyVal) == 0 &&
line.GetString(1, out strVal) == 0 &&
line.GetInteger(2, out intVal) == 0)
{
Console.WriteLine("{0} = {1}, {2}", keyVal, strVal, intVal);
}
} while (line.FindNextLine() == 0);
if (line.LastError != InfError.ERROR_LINE_NOT_FOUND)
{
Console.WriteLine("Unexpected error: " + line.LastMessage);
}
}
}
static void ReadSection2(InfFile file)
{
InfLine line = file.FindFirstLine("No Keys Section");
if (line == null)
{
Console.WriteLine("Error getting first line: " + file.LastMessage);
}
else
{
do
{
string str1, str2;
if (line.GetString(1, out str1) == 0 &&
line.GetString(2, out str2) == 0)
{
Console.WriteLine("Strings are \"{0}\" and \"{1}\"", str1, str2);
}
} while (line.FindNextLine() == 0);
if (line.LastError != InfError.ERROR_LINE_NOT_FOUND)
{
Console.WriteLine("Unexpected error: " + line.LastMessage);
}
}
}
Finally, here's the output.
key1 = hello, world, 42
key2 = written in, 2010
Strings are "val1" and "val2"
Strings are "val3" and "val4"
- 12th May, 2010: Initial version