/*++

Copyright (c) 2000 Microsoft Corporation

Module Name:

    efinvram.cpp

Abstract:

    Tool that allows you to edit/view EFI
    nvram entries.

Author:

    Vijay Jayaseelan (vijayj) 02-Feb-2001

Revision History:

    None

--*/

extern "C" {
#include <efisbent.h>
#include <setupapi.h>
}

#include <iostream>
#include <string>
#include <exception>
#include <windows.h>
#include <tchar.h>
#include <locale>
#include "msg.h"
#include <libmsg.h>

#define MSFT_PREFIX     L"Microsoft "
#define DEFAULT_NAME    L"Windows"
#define DEFAULT_TIMEOUT 30

//
// Global variables used to get formatted message for this program.
//
HMODULE ThisModule = NULL;
WCHAR Message[4096];

//
// function prototypes
//
NTSTATUS
QueryCanonicalName(
    IN  PCWSTR  Name,
    IN  ULONG   MaxDepth,
    OUT PWSTR   CanonicalName,
    IN  ULONG   SizeOfBufferInBytes
    );

VOID
GetFriendlyName(
    IN const std::wstring &InfFileName,
    OUT std::wstring &Buffer
    );
    
//
// Helper dump operators
//
std::ostream& operator<<(std::ostream &os, const std::wstring &str) {
    FILE    *OutStream = (&os == &std::cerr) ? stderr : stdout;

    fwprintf(OutStream, (PWSTR)str.c_str());
    return os;
}

//
// Helper dump operators
//
std::ostream& operator<<(std::ostream &os, WCHAR *Str) {
    std::wstring WStr = Str;
    os << WStr;
    
    return os;
}

PWSTR GetOptionKey(ULONG MsgId) {
    Message[0] = TEXT('\0');
    
    GetFormattedMessage(ThisModule,
        FALSE,
        Message,
        sizeof(Message)/sizeof(Message[0]),
        MsgId);

    return Message;
}

//
// Exceptions
//
struct ProgramException : public std::exception {
    virtual void Dump(std::ostream &os) = 0;
};
          

//
// Abstracts a Win32 error
//
struct W32Error : public ProgramException {
    DWORD   ErrorCode;
    
    W32Error(DWORD ErrCode = GetLastError()) : ErrorCode(ErrCode){}
    
    void Dump(std::ostream &os) {
        WCHAR   MsgBuffer[4096];

        MsgBuffer[0] = UNICODE_NULL;
        if (GetFormattedMessage(ThisModule,
                                TRUE,
                                MsgBuffer,
                                sizeof(MsgBuffer)/sizeof(MsgBuffer[0]),
                                ErrorCode)){                  
            std::wstring Msg(MsgBuffer);
            os << Msg;
        } else {
            os << std::hex << ErrorCode;
        }
    }
};

//
// Invalid arguments
//
struct InvalidArguments : public ProgramException {
    const char *what() const throw() {
        return "Invalid Arguments";
    }

    void Dump(std::ostream &os) {
        os << what() << std::endl;
    }
};

//
// Invalid arguments
//
struct ProgramUsage : public ProgramException {

    std::wstring PrgUsage;

    ProgramUsage(const std::wstring &Usg) : PrgUsage(Usg) {}
    
    const char *what() const throw() {
        return "Program Usage exception";
    }

    void Dump(std::ostream &os) {
       
        os << PrgUsage << std::endl;
    }
};

//
// Program Arguments abstraction
//
struct ProgramArguments {
    bool ShowUsage;
    bool ListEntries;
    bool AddNewEntry;
    bool DeleteBootEntry;
    bool QuiteMode;
    bool SetActive;
    bool SetOsLoadOptions;
    bool SetTimeout;
    
    std::wstring    LoaderVolumeName;
    std::wstring    LoaderPath;
    std::wstring    BootVolumeName;
    std::wstring    BootPath;
    std::wstring    LoadOptions;
    std::wstring    FriendlyName;
    std::wstring    OsLoadOptions;

    std::wstring    AddOptionKey;
    std::wstring    DeleteOptionKey;
    std::wstring    ListOptionKey;
    std::wstring    OptionsOptionKey;
    std::wstring    SetActiveOptionKey;
    std::wstring    TimeoutOptionKey;
    
    ULONG           Timeout;
    ULONG           EntryId;
    
    
    ProgramArguments(INT Argc, WCHAR *Argv[]) {
        ShowUsage = false;        
        QuiteMode = false;
        ListEntries = AddNewEntry = DeleteBootEntry = false;
        SetActive = false;
        EntryId = -1;
        Timeout = DEFAULT_TIMEOUT;
        SetTimeout = false;

        //
        // get all the options
        //
        ListOptionKey = GetOptionKey(MSG_LIST_OPTION);
        AddOptionKey = GetOptionKey(MSG_ADD_OPTION);
        DeleteOptionKey = GetOptionKey(MSG_DELETE_OPTION);
        OptionsOptionKey = GetOptionKey(MSG_OPTIONS_OPTION);
        SetActiveOptionKey = GetOptionKey(MSG_SETACTIVE_OPTION);
        TimeoutOptionKey = GetOptionKey(MSG_TIMEOUT_OPTION);
        
        //
        // parse the arguments
        //
        for (ULONG Index=1; !ShowUsage && (Index < Argc); Index++) {            
            if (!_wcsicmp(Argv[Index], L"/q")) {
                QuiteMode = true;                
            } else if (!_wcsicmp(Argv[Index], AddOptionKey.c_str())) {               
                std::wstring  LoaderName;
                std::wstring  BootVolName;
                
                AddNewEntry = true;
                ShowUsage = true;

                if (Argc > 3) {
                    Index++;
                    LoaderName = Argv[Index++];
                    BootVolName = Argv[Index++];                    
                    ShowUsage = false;

                    for ( ; (Index < Argc) && (false == ShowUsage); Index++) {
                        if (!_wcsicmp(Argv[Index], SetActiveOptionKey.c_str())) {
                            SetActive = true;
                        } else if (!_wcsicmp(Argv[Index], OptionsOptionKey.c_str())) {
                            SetOsLoadOptions = true;
                            Index++;

                            if (Index < Argc) {
                                OsLoadOptions = Argv[Index];
                            } else {
                                ShowUsage = true;
                            }
                        } else if (!_wcsicmp(Argv[Index], TimeoutOptionKey.c_str())) {
                            SetTimeout = true;
                            Index++;

                            if (Index < Argc) {
                                PWSTR EndChar = NULL;
                                
                                Timeout = wcstoul(Argv[Index], &EndChar, 10);
                                
                                if (errno) {
                                    ShowUsage = true;
                                }
                            }
                        } else {
                            ShowUsage = true;
                        }
                    }
                    
                    //
                    // Verify the arguments
                    //
                    if (!ShowUsage) {                                            
                        if (_waccess(LoaderName.c_str(), 0) || 
                            _waccess(BootVolName.c_str(), 0)) {
                            throw new W32Error(::GetLastError());
                        }        
                
                        //
                        // Get the fully qualified NT name for
                        // the the loader volume and boot volume
                        // name
                        //  
                        WCHAR           CanonicalName[MAX_PATH];                        
                        NTSTATUS        Status;                        
                        std::wstring    NtName;
                        std::wstring    DosDevices = L"\\DosDevices\\";
                        std::wstring::size_type LoaderColonPos = LoaderName.find(L':');
                        std::wstring::size_type BootColonPos = BootVolName.find(L':');
                        
                        if (LoaderColonPos != LoaderName.npos) {
                            NtName = DosDevices + LoaderName.substr(0, LoaderColonPos + 1);

                            Status = QueryCanonicalName(NtName.c_str(),
                                        -1,
                                        CanonicalName,
                                        sizeof(CanonicalName));

                            if (NT_SUCCESS(Status)) {
                                LoaderVolumeName = CanonicalName;
                                LoaderPath = LoaderName.substr(LoaderColonPos + 1);
                            } else {
                                throw new W32Error(RtlNtStatusToDosError(Status));
                            }
                        } else {
                            throw new W32Error(ERROR_PATH_NOT_FOUND);
                        }                        
                        
                        if (BootColonPos != BootVolName.npos) {
                            NtName = DosDevices + BootVolName.substr(0, BootColonPos + 1);

                            Status = QueryCanonicalName(NtName.c_str(),
                                        -1,
                                        CanonicalName,
                                        sizeof(CanonicalName));

                            if (NT_SUCCESS(Status)) {
                                BootVolumeName = CanonicalName;
                                BootPath = BootVolName.substr(BootColonPos + 1);
                            } else {
                                throw new W32Error(RtlNtStatusToDosError(Status));
                            }
                        } else {
                            throw new W32Error(ERROR_PATH_NOT_FOUND);
                        }                                                

                        if (BootVolName[BootVolName.length() - 1] != L'\\') {
                            BootVolName += L"\\";
                        }

                        std::wstring LayoutInf = BootVolName + L"inf\\layout.inf";
                                                
                        //
                        // Verify the inf file path
                        //                    
                        if (_waccess(LayoutInf.c_str(), 0)) {
                            throw new W32Error(::GetLastError());
                        }

                        //
                        // Extract the product friendly name for the inf file
                        //
                        GetFriendlyName(LayoutInf, FriendlyName);
                    }
                }

                break;                
            } else if (!_wcsicmp(Argv[Index], ListOptionKey.c_str())) {
                ListEntries = true;
                break;
            } else if (!_wcsicmp(Argv[Index], DeleteOptionKey.c_str())) {                
                DeleteBootEntry = true;
                Index++;
                
                if (Index < Argc) {
                    PWSTR EndChar = NULL;
                    
                    EntryId = wcstoul(Argv[Index], &EndChar, 10);
                } 
                
                break;
            } else if (!_wcsicmp(Argv[Index], L"/?") ||
               !_wcsicmp(Argv[Index], L"-?") ||
               !_wcsicmp(Argv[Index], L"?") ||
               !_wcsicmp(Argv[Index], L"/h") ||
               !_wcsicmp(Argv[Index], L"-h")) {
                    ShowUsage = true;                       
            } else if (!_wcsicmp(Argv[Index], OptionsOptionKey.c_str())) {
                Index++;
                SetOsLoadOptions = true;
                
                if (Index < Argc) {
                    OsLoadOptions = Argv[Index];
                    Index++;

                    if (Index < Argc) {
                        PWSTR EndChar = NULL;
                        
                        EntryId = wcstoul(Argv[Index], &EndChar, 10);
                    }
                }

                break;
            } else if (!_wcsicmp(Argv[Index], SetActiveOptionKey.c_str())) {
                Index++;
                SetActive = true;
                
                if (Index < Argc) {
                    PWSTR EndChar = NULL;
                    
                    EntryId = wcstoul(Argv[Index], &EndChar, 10);

                    if (errno) {
                        ShowUsage = true;
                    }
                } else {
                    ShowUsage = true;
                }
                
                break;
            } else if (!_wcsicmp(Argv[Index], TimeoutOptionKey.c_str())) {
                Index++;
                SetTimeout = true;
                
                if (Index < Argc) {
                    PWSTR EndChar = NULL;
                    
                    Timeout = wcstoul(Argv[Index], &EndChar, 10);

                    if (errno) {
                        ShowUsage = true;
                    }
                } else {
                    ShowUsage = true;
                }
            } else {
                ShowUsage = true;
            }
        }            

        if (!ShowUsage) {
            ShowUsage = (!ListEntries && !AddNewEntry && !SetActive &&
                         !DeleteBootEntry && !SetOsLoadOptions && !SetTimeout);
        }                         

        if (ShowUsage) {
            throw new ProgramUsage(GetFormattedMessage( ThisModule,
                                                        FALSE,
                                                        Message,
                                                        sizeof(Message)/sizeof(Message[0]),
                                                        MSG_PGM_USAGE));
        }                        
    }

    friend std::ostream& operator<<(std::ostream &os, ProgramArguments &Args) {
        os << "List Entries : " << Args.ListEntries << std::endl;
        os << "Add Entry    : " << Args.AddNewEntry << std::endl;
        os << "Delete Entry : " << Args.DeleteBootEntry << std::endl;
        os << "QuiteMode    : " << Args.QuiteMode << std::endl;
        os << "Loader Vol   : " << Args.LoaderVolumeName << std::endl;
        os << "Loader Path  : " << Args.LoaderPath << std::endl;
        os << "Boot Vol     : " << Args.BootVolumeName << std::endl;
        os << "Boot Path    : " << Args.BootPath << std::endl;
        os << "Friendly Name: " << Args.FriendlyName << std::endl;
        os << "Load Options : " << Args.OsLoadOptions << std::endl;
        os << "Timeout      : " << std::dec << Args.Timeout << " Secs" << std::endl;
        
        return os;
    }
};


VOID
DumpOsBootEntry(
    IN  POS_BOOT_ENTRY  Entry
    )
{
    if (Entry) {
        wprintf(GetFormattedMessage(    ThisModule,
                                        FALSE,
                                        Message,
                                        sizeof(Message)/sizeof(Message[0]),
                                        MSG_BOOT_ENTRY,
                                        OSBEGetId(Entry),
                                        OSBEGetFriendlyName(Entry),
                                        OSBEGetOsLoaderVolumeName(Entry),
                                        OSBEGetOsLoaderPath(Entry),
                                        OSBEGetBootVolumeName(Entry),
                                        OSBEGetBootPath(Entry),
                                        OSBEGetOsLoadOptions(Entry)));
    }
}

VOID
DumpOsBootOptions(
    IN  POS_BOOT_OPTIONS Options
    )
{
    if (Options) {
        ULONG   Index;
        wprintf(GetFormattedMessage(    ThisModule,
                                        FALSE,
                                        Message,
                                        sizeof(Message)/sizeof(Message[0]),
                                        MSG_TIMEOUT_AND_BOOT_ORDER, 
                                        OSBOGetTimeOut(Options)));

        for (Index=0; 
            Index < OSBOGetOrderedBootEntryCount(Options);
            Index++) {
            wprintf(GetFormattedMessage(    ThisModule,
                                            FALSE,
                                            Message,
                                            sizeof(Message)/sizeof(Message[0]),
                                            MSG_ORDERED_BOOT_ENTRIES,
                                            OSBOGetBootEntryIdByOrder(Options, Index)));
        }                                            
            
        wprintf(GetFormattedMessage(    ThisModule,
                                        FALSE,
                                        Message,
                                        sizeof(Message)/sizeof(Message[0]),
                                        MSG_BOOT_ENTRIES));

        POS_BOOT_ENTRY  Entry = OSBOGetFirstBootEntry(Options, &Index);
        
        while (Entry) {
            DumpOsBootEntry(Entry);
            Entry = OSBOGetNextBootEntry(Options, &Index);
        }
        wprintf(GetFormattedMessage(    ThisModule,
                                        FALSE,
                                        Message,
                                        sizeof(Message)/sizeof(Message[0]),
                                        MSG_ACTIVE_ENTRY));

        DumpOsBootEntry(OSBOGetActiveBootEntry(Options));
    }
}


DWORD
ListEntries(
    IN POS_BOOT_OPTIONS OsOptions 
    )
{
    DWORD Result = ERROR_INVALID_PARAMETER;

    if (OsOptions) {
        DumpOsBootOptions(OsOptions);
        Result = ERROR_SUCCESS;
    }

    return Result;
}

DWORD
AddNewEntry(
    IN POS_BOOT_OPTIONS OsOptions,
    IN ProgramArguments &Args
    )
{
    DWORD Result = ERROR_INVALID_PARAMETER;

    if (OsOptions) {
        POS_BOOT_ENTRY NewEntry;
        BOOLEAN Status = TRUE;
        
        NewEntry = OSBOAddNewBootEntry(OsOptions,
                        Args.FriendlyName.c_str(),
                        Args.LoaderVolumeName.c_str(),
                        Args.LoaderPath.c_str(),
                        Args.BootVolumeName.c_str(),
                        Args.BootPath.c_str(),
                        Args.OsLoadOptions.c_str());

        if (NewEntry) {            
            if (Args.SetActive) {
                Status = (OSBOSetActiveBootEntry(OsOptions,
                                NewEntry) != NULL);
            }
            
            if (Status && Args.SetTimeout) {
                OSBOSetTimeOut(OsOptions, Args.Timeout);
            }

            if (Status) {
                Status = OSBOFlush(OsOptions);
            }
        } else {
            Status = FALSE;
        }            

        if (Status) {
            Result = ERROR_SUCCESS;
        } else {
            Result = ERROR_CAN_NOT_COMPLETE;
        }            
    }

    return Result;
}

DWORD
SetBootEntryOptions(
    IN POS_BOOT_OPTIONS OsOptions,
    IN const ProgramArguments &Args
    )
{
    DWORD ErrorCode = ERROR_INVALID_PARAMETER;

    if (OsOptions) {
        POS_BOOT_ENTRY  BootEntry;
        BOOLEAN Status = FALSE;

        if (Args.EntryId != -1) {
            BootEntry = OSBOFindBootEntry(OsOptions,
                            Args.EntryId);
        } else {
            BootEntry = OSBOGetActiveBootEntry(OsOptions);
        }            

        if (BootEntry) {
            Status = (OSBESetOsLoadOptions(BootEntry, 
                            Args.OsLoadOptions.c_str()) != NULL);

            if (Status) {
                Status = OSBOFlush(OsOptions);
            }                
        }                

        if (Status) {
            ErrorCode = ERROR_SUCCESS;
        } else {
            ErrorCode = ERROR_CAN_NOT_COMPLETE;
        }            
    }

    return ErrorCode;
}

DWORD
SetBootEntryActive(
    IN POS_BOOT_OPTIONS OsOptions,
    IN ProgramArguments &Args
    )
{
    DWORD ErrorCode = ERROR_INVALID_PARAMETER;

    if (OsOptions && (Args.EntryId != -1)) {
        POS_BOOT_ENTRY  BootEntry;
        BOOLEAN Status = FALSE;

        BootEntry = OSBOFindBootEntry(OsOptions,
                        Args.EntryId);

        if (BootEntry) {
            Status = (OSBOSetActiveBootEntry(OsOptions,
                            BootEntry) != NULL);

            if (Status) {
                Status = OSBOFlush(OsOptions);
            }                
        }                

        if (Status) {
            ErrorCode = ERROR_SUCCESS;
        } else {
            ErrorCode = ERROR_CAN_NOT_COMPLETE;
        }            
    }
    
    return ErrorCode;
}

DWORD
DeleteBootEntry(
    IN POS_BOOT_OPTIONS OsOptions,
    IN ProgramArguments &Args
    )
{
    DWORD ErrorCode = ERROR_INVALID_PARAMETER;

    if (OsOptions) {
        POS_BOOT_ENTRY  BootEntry;
        BOOLEAN Status = FALSE;

        if (Args.EntryId != -1) {
            BootEntry = OSBOFindBootEntry(OsOptions,
                            Args.EntryId);
        } else {
            BootEntry = OSBOGetActiveBootEntry(OsOptions);
        }            

        if (BootEntry) {
            Status = OSBODeleteBootEntry(OsOptions, BootEntry);

            if (Status) {
                Status = OSBOFlush(OsOptions);
            }                
        }                

        if (Status) {
            ErrorCode = ERROR_SUCCESS;
        } else {
            ErrorCode = ERROR_CAN_NOT_COMPLETE;
        }            
    }
    
    return ErrorCode;
}

DWORD
SetTimeout(
    IN POS_BOOT_OPTIONS OsOptions,
    IN const ProgramArguments &Args
    )
{
    DWORD ErrorCode = ERROR_INVALID_PARAMETER;

    if (OsOptions && Args.SetTimeout) {
        OSBOSetTimeOut(OsOptions, Args.Timeout);
        ErrorCode = OSBOFlush(OsOptions) ? ERROR_SUCCESS : ERROR_CAN_NOT_COMPLETE;
    }
    
    return ErrorCode;
}

    
//
// main() entry point
//
int 
__cdecl
wmain(
    int         Argc,
    wchar_t     *Argv[]
    )
{
    int Result = 0;
    ThisModule = GetModuleHandle(NULL);
    
    try {    
        DWORD ErrorCode = ERROR_INVALID_PARAMETER;
        ProgramArguments    Args(Argc, Argv);        
        POS_BOOT_OPTIONS    BootOptions = NULL;

        //
        // Initialize the library
        //
        if (OSBOLibraryInit((SBEMemAllocateRoutine)malloc, (SBEMemFreeRoutine)free)) {
            BootOptions = EFIOSBOCreate();
        }            

        if (!BootOptions) {
            std::cout << GetFormattedMessage(   ThisModule,
                                                FALSE,
                                                Message,
                                                sizeof(Message)/sizeof(Message[0]),
                                                MSG_ERROR_READING_BOOT_ENTRIES) << std::endl;
            Result = 1;
        } else {
            if (Args.ListEntries) {
                ErrorCode = ListEntries(BootOptions);
            } else if (Args.AddNewEntry) {
                ErrorCode = AddNewEntry(BootOptions, Args);                                
            } else if (Args.DeleteBootEntry) {
                ErrorCode = DeleteBootEntry(BootOptions, Args);
            } else if (Args.SetOsLoadOptions) {
                ErrorCode = SetBootEntryOptions(BootOptions, Args);
            } else if (Args.SetActive) {
                ErrorCode = SetBootEntryActive(BootOptions, Args);
            } else if (Args.SetTimeout) {
                ErrorCode = SetTimeout(BootOptions, Args);
            }

            OSBODelete(BootOptions);
        }            

        if (ErrorCode != ERROR_SUCCESS) {
            throw new W32Error(ErrorCode);
        }        
    }
    catch(ProgramArguments *pArgs) {
        Result = 1;
        std::cout << GetFormattedMessage(   ThisModule,
                                                FALSE,
                                                Message,
                                                sizeof(Message)/sizeof(Message[0]),
                                                MSG_PGM_USAGE) << std::endl;

        if (pArgs) {
            delete pArgs;
        }
    }
    catch(W32Error  *W32Err) {
        if (W32Err) {   // to make prefix happy :(
            W32Err->Dump(std::cout);
            std::cout << GetFormattedMessage(   ThisModule,
                                                FALSE,
                                                Message,
                                                sizeof(Message)/sizeof(Message[0]),
                                                MSG_PGM_USAGE) << std::endl;
            delete W32Err;
        }   

        Result = 1;
    }
    catch(ProgramException *PrgExp) {
        Result = 1;
        PrgExp->Dump(std::cout);
        delete PrgExp;
    } catch (exception *Exp) {
        Result = 1;
        std::cout << Exp->what() << std::endl;
    }

    return Result;
}


NTSTATUS
QueryCanonicalName(
    IN  PCWSTR   Name,
    IN  ULONG   MaxDepth,
    OUT PWSTR   CanonicalName,
    IN  ULONG   SizeOfBufferInBytes
    )
/*++

Routine Description:

    Resolves the symbolic name to the specified depth. To resolve
    a symbolic name completely specify the MaxDepth as -1

Arguments:

    Name        -   Symbolic name to be resolved
    
    MaxDepth    -   The depth till which the resolution needs to
                    be carried out

    CanonicalName   -   The fully resolved name

    SizeOfBufferInBytes -   The size of the CanonicalName buffer in
                            bytes                           

Return Value:

    Appropriate NT status code

--*/    
{
    UNICODE_STRING      name, canonName;
    OBJECT_ATTRIBUTES   oa;
    NTSTATUS            status;
    HANDLE              handle;
    ULONG               CurrentDepth;

    RtlInitUnicodeString(&name, Name);

    canonName.MaximumLength = (USHORT) (SizeOfBufferInBytes - sizeof(WCHAR));
    canonName.Length = 0;
    canonName.Buffer = CanonicalName;

    if (name.Length >= canonName.MaximumLength) {
        return STATUS_BUFFER_TOO_SMALL;
    }

    RtlCopyMemory(canonName.Buffer, name.Buffer, name.Length);
    canonName.Length = name.Length;
    canonName.Buffer[canonName.Length/sizeof(WCHAR)] = 0;

    for (CurrentDepth = 0; CurrentDepth < MaxDepth; CurrentDepth++) {

        InitializeObjectAttributes(&oa, &canonName, OBJ_CASE_INSENSITIVE, 0, 0);

        status = NtOpenSymbolicLinkObject(&handle,
                                          READ_CONTROL | SYMBOLIC_LINK_QUERY,
                                          &oa);
        if (!NT_SUCCESS(status)) {
            break;
        }

        status = NtQuerySymbolicLinkObject(handle, &canonName, NULL);
        NtClose(handle);

        if (!NT_SUCCESS(status)) {
            return status;
        }
        
        canonName.Buffer[canonName.Length/sizeof(WCHAR)] = 0;
    }

    return STATUS_SUCCESS;
}


#define PRODUCT_NAME_KEY  TEXT("productname")

VOID
GetFriendlyName(
    IN const std::wstring &InfFileName,
    OUT std::wstring &FriendlyName
    )
{
    UINT    ErrorLine = 0;
    BOOL    Status = FALSE;
    HINF    InfHandle = ::SetupOpenInfFile(InfFileName.c_str(),
                            NULL,
                            INF_STYLE_WIN4,
                            &ErrorLine);

    if (InfHandle != INVALID_HANDLE_VALUE) {
        INFCONTEXT  InfContext = {0};
        WCHAR       Buffer[MAX_PATH] = {0};

        //
        // get the key
        //
        Status = SetupFindFirstLine(InfHandle,
                            TEXT("Strings"),
                            PRODUCT_NAME_KEY,
                            &InfContext);

        if (Status) {            
            //
            // If we found the key extract the description
            //
            Status = SetupGetStringField(&InfContext,
                        1,
                        Buffer,
                        ARRAY_SIZE(Buffer),
                        NULL);

            if (Status) {               
                FriendlyName = Buffer;
            }
        }
        
        SetupCloseInfFile(InfHandle);
    }        

    //
    // If we didn't find the description use default description
    //
    if (!Status) {        
        FriendlyName = DEFAULT_NAME;
    }

    FriendlyName = MSFT_PREFIX + FriendlyName;
}