Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

.NET Wrapper for Setup API (Parses INF, OEM Files)

4.42/5 (9 votes)
13 May 2010MIT5 min read 28.5K   682  
.NET classes for parsing driver.INF and txtsetup.OEM using the Setup API

Table of Contents

Introduction

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.

Background

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:

C#
[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.

Using the Code

The download contains two classes you will use directly: InfFile and InfLine.

InfFile

  • 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.

InfLine

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.

Error Checking and Reporting

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.

C#
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.

Sample Code

Consider this simple INF file (from test.inf in the download).

C#
[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.

C#
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)
{
    // Get the InfLine for "key1" in [Section 1].
    InfLine line = file.FindFirstKey("Section 1", "key1");

    if (line == null)
    {
        Console.WriteLine("Error getting first key: " + file.LastMessage);
    }
    else
    {
        // Parse all lines in the section.
        // Expect "key = string, int" on each line.
        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);

        // ERROR_LINE_NOT_FOUND is expected when we run out of lines.
        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
    {
        // Parse all lines in the section.
        // Expect "string1, string2" on each line.
        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);

        // ERROR_LINE_NOT_FOUND is expected when we run out of lines.
        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"

History

  • 12th May, 2010: Initial version

License

This article, along with any associated source code and files, is licensed under The MIT License