How do I obtain the computer manufacturer’s name via C++?


The way to get the computer manufacturer and other information is to ask WMI. WMI is much easier to use via scripting, but maybe you want to do it from C++. Fortunately, MSDN takes you through it step by step and even puts it together into a sample program.

But I'm going to write the code myself anyway.

Today's Little Program extracts the computer name, manufacturer, and model from WMI. Remember that Little Programs do little or no error checking.

And the smart pointer library we'll use is (rolls dice) _com_ptr_t!

#include <windows.h>
#include <stdio.h>
#include <ole2.h>
#include <oleauto.h>
#include <wbemidl.h>
#include <comdef.h>

_COM_SMARTPTR_TYPEDEF(IWbemLocator, __uuidof(IWbemLocator));
_COM_SMARTPTR_TYPEDEF(IWbemServices, __uuidof(IWbemServices));
_COM_SMARTPTR_TYPEDEF(IWbemClassObject, __uuidof(IWbemClassObject));
_COM_SMARTPTR_TYPEDEF(IEnumWbemClassObject, __uuidof(IEnumWbemClassObject));

// CCoInitialize class incorporated by reference

Those include files and macros set things up so we can use _com_ptr_t to access WBEM interfaces.

_bstr_t GetProperty(IWbemClassObject *pobj, PCWSTR pszProperty)
{
  _variant_t var;
  pobj->Get(pszProperty, 0, &var, nullptr, nullptr);
  return var;
}

void PrintProperty(IWbemClassObject *pobj, PCWSTR pszProperty)
{
 printf("%ls = %ls\n", pszProperty,
  static_cast<PWSTR>(GetProperty(pobj, pszProperty)));
}

The first helper function retrieves a string property from a WBEM object. The second one prints it. (Exercise: Why do we need the static_cast?)

int __cdecl main(int, char**)
{
 CCoInitialize init;

 IWbemLocatorPtr spLocator;
 CoCreateInstance(CLSID_WbemLocator, nullptr, CLSCTX_ALL,
  IID_PPV_ARGS(&spLocator));

 IWbemServicesPtr spServices;
 spLocator->ConnectServer(_bstr_t(L"root\\cimv2"),
  nullptr, nullptr, 0, 0, nullptr, nullptr, &spServices);

 CoSetProxyBlanket(spServices, RPC_C_AUTHN_DEFAULT,
  RPC_C_AUTHZ_DEFAULT, COLE_DEFAULT_PRINCIPAL,
  RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE,
  0, EOAC_NONE);

 IEnumWbemClassObjectPtr spEnum;
 spServices->ExecQuery(_bstr_t(L"WQL"),
  _bstr_t(L"select * from Win32_ComputerSystem"),
   WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
   nullptr, &spEnum);

 IWbemClassObjectPtr spObject;
 ULONG cActual;
 while (spEnum->Next(WBEM_INFINITE, 1, &spObject, &cActual)
                                    == WBEM_S_NO_ERROR) {
  PrintProperty(spObject, L"Name");
  PrintProperty(spObject, L"Manufacturer");
  PrintProperty(spObject, L"Model");
 }

 return 0;
}

And here is the actual guts of the program.

We initialize COM but we do not call Co­Initialize­Security because the checklist notes that the call sets the default security for the entire process, which would be a global solution to a local problem. Now, in this case, we are in control of the process, but I'm doing it this way because I know people are going to copy/paste the code (hopefully after adding some error checking), and the local solution is more appropriate in the general case.

The next step in the cookbook is creating a connection to a WMI namespace. We create a WbemLocator and connect it to the desired namespace.

Step three in the cookbook is setting the security context on the interface, which is done with the amusingly-named function Co­Set­Proxy­Blanket.

Once we have a connection to the server, we can ask it for all (*) the information from Win32_Computer­System.

We know that there is only one computer in the query, but I'm going to write a loop anyway, because somebody who copies this code may issue a query that contains multiple results.

For each object, we print its Name, Manufacturer, and Model.

And that's it.

Comments (30)
  1. SI says:

    Cast is to convert from the bstr_t class to WCHAR, but shouldn't the format string then be L"…"?

  2. John Doe says:

    @SI, the L"…" would be necessary for wprintf. Anyway, you nailed the exercise: msdn.microsoft.com/…/btdzb8eb.aspx

  3. kinokijuf says:

    Especially since most mobo manufacturers put “System manufacturer” in that field (and “System Product Name” in the model field). The motherboard manufacturer field is more reliable.

  4. SI says:

    Ok, today I found out that PWSTR is basically TCHAR*, so it depends on UNICODE being defined.

  5. Rick C says:

    "we can ask it for all (*) the information"

    Nitpicker's corner:  Today there is no nitpicker's corner.

  6. > %ls always means an ANSI value

    copy/paste error: should read "%ls always means a Unicode value"

  7. SI says:

    We had exactly the opposite problem, our codebase is littered with (const char*)bstr_t(…) wrappers around unicode status messages to convert them to ansi for vsprintf logging calls, instead of using %ls directly.

  8. J. Peterson says:

    30-some lines of code to…retrieve a string???

    [It will probably take you even more lines of code to get the number of unread messages in the user's Yahoo inbox, and that's just a 32-bit integer! -Raymond]
  9. > I found out that PWSTR is basically TCHAR*, so it depends on UNICODE being defined

    Hmm… there seems to be some confusion about how L"" and "" etc. work.

    printf always uses "…"; this is always ANSI. wprintf always uses L"…"; this is always Unicode.

    _tprintf is either printf or wprintf, depending on whether UNICODE/_UNICODE is defined. This always uses TEXT("…").

    Consider the following:

    printf("foo"); // OK

    wprintf(L"foo"); // OK

    _tprintf(TEXT("foo")); // OK

    The other six possibilities (e.g. wprintf(TEXT("foo"))) are all either compiler or stylistic errors.

    %ls corresponds to a value which is a Unicode string; %hs corresponds to a value which is an ANSI string. %s by itself corresponds to a string **which is of the same type as the format string itself** (regardless of whether UNICODE/_UNICODE is defined.)

    Let us suppose that the string we are trying to print contains some non-ANSI characters, e.g.: Contosó. Consider the following:

    printf("%s", "foo"); // OK; %s in an ANSI format string means an ANSI value

    printf("%hs", "foo"); // OK; %hs always means an ANSI value

    printf("%ls", L"foo"); // OK; %ls always means an ANSI value

    printf("%ls", L"Contosó"); // Iffy; prints "Contoso", Unicode value is downconverted to ANSI format string

    wprintf(L"%s", L"foo"); // OK

    wprintf(L"%s", L"foo"); // OK

    wprintf(L"%hs", "foo"); // OK

    wprintf(L"%ls", L"Contosó"); // OK (prints Contosó)

    So I think Raymond's printf("%ls", (LPCWSTR)GetPropertyValue(…)) is iffy, because any Unicode data in the property value would be downconverted to ANSI. I would prefer wprintf(L"%ls", (LPCWSTR)GetPropertyValue(…)).

  10. SI says:

    I overlooked the l in the %ls, that makes much more sense than the PWSTR forcing the compiler to use the char* operator due to current mode. But if we are converting it down to ansi anyhow, why not use the const char* operator present in the bstr_t class, which caches the copy?

    [Because I didn't know about that operator. I had always treated _bstr_t as simply a wrapper around a BSTR without any added functionality. -Raymond]
  11. skSdnW says:

    @Maurits [MSFT]: wprintf(L"%s", L"foo"); // OK

    This will not work on a POSIX system, %s is _always_ char* there. The MS implementation is much less painful to work with and allows both TCHAR types to build from the same source.

  12. 640k says:

    @skSdnW: If you want portable code, or escape bloated COM, you have to use DMI. Then POSIX may be relevant.

  13. ulric says:

    ho, the static cast is necessary not because of wchar/tchar/char issues (BSTR is an OLESTR is a WCHAR always ) but because printf is a variable parameter list and the conversion for bstr_t to use to push on the stack is ambiguous.  would not have been a problem if it were a BSTR directly

  14. Deduplicator says:

    @skSdnW: Well, there's little reason to fiddle with UTF16 on POSIX systems. UTF8 is the variable byte encoding of choice, and UTF32 handles full codepoints, so no need to take any compromise solution which is neither ascii-compatible nor fixed width. But if you really have to, you can use the proper defines…

  15. ErikF says:

    @J. Peterson: If you only need to retrieve WMI data, use the right tool for the job (PowerShell)! In other news, you can write a Windows program in assembly code, but it will take many more lines of code than C.

  16. laonianren says:

    This is mildly off-topic.

    WMI CIM Studio (which lets you browse and modify WMI objects) is implemented as a web page containing ActiveX controls.  The only application that can host this (as far as I know) is Internet Explorer.  Unfortunately, it has stopped working in IE 11.

    Does anyone know either a) how to get it to work or b) an alternative WMI browser?

  17. Joshua says:

    @laonianren: Host the ActiveX browser control in a vb6 app and use that to load the page.

  18. T says:

    This is pretty neat, though, is it not optimistic to expect a PC to have a manufacturer? So many PCs are built by their owners anyway! It returns this on my system:

    Name = T-PC

    Manufacturer = To Be Filled By O.E.M.

    Model = To Be Filled By O.E.M.

    [The people who ask this question are typically IT professionals who want to inventory all the computers in their company. Those computers are unlikely to be home-built. -Raymond]
  19. T says:

    @Raymond, that makes sense! Next part: how to modify this information :).

  20. skSdnW says:

    @Deduplicator: Who said anything about UTF16? wchar_t is usually 32bit on other systems.

    The point is, working with printf functions where %s does not match the type of the format string is annoying if the code is going to be used on Windows and POSIX…

  21. Myria says:

    While we're on the subject of Unicode, how about also using wmain instead of main? =)

    extern "C" int __cdecl wmain(int argc, wchar_t **argv, wchar_t **envp)

    [The program takes no command line arguments. Who cares! (Don't forget people: Little Program.) -Raymond]
  22. Engywuck says:

    Well, we have seven "To Be Filled By O.E.M." for both Manufacturer and Model plus 5 for only Model (there Manufacturer = Mainboard Manufacturer). All bought at a local vendor (not everyone buys dell-only ;-))

    But it sure would be nice if at least the big ones could get their names consistent: "HP", "Hewlett-Packard", "Hewlett-Packard Company", the same for Dell, Siemens etc. So you need an additional step consolidating anyway, plus regular maintenance when they come up with something different.

    To the program: just exec() wmiq and parse the result string. No need to handle wmi yourself! ;-)

  23. Medinoc says:

    @skSdnW: The worst part under POSIX (or really, under the ISO/IEC C standard) is not that %s is "always ANSI, all the time" (equivalent to %hs). The worst is that there is NO WAY AT ALL to specify a string argument "The same width as the function"!

    Quite frankly, I think that part of the C standard is dumb and I was flabbergasted when I discovered it. The Windows way is much easier to use (especially with the late addition to the C++ standard that says L"blah" "blah" is no longer a string width mismatch, invaluable when working with macros).

  24. Joshua says:

    [It will probably take you even more lines of code to get the number of unread messages in the user's Yahoo inbox, and that's just a 32-bit integer! -Raymond]

    I'm pretty sure that's 6 lines.

    int r;

    char buf[4096];

    snprintf(buf, 4096, "wget -O – https://… ", username, stored_password);

    FILE *f = popen(buf, "r");

    fscanf(f, "%d", &r);

    return r;

    [I think it's more complicated than that nowadays. And isn't forking a wget a bit of a cheat? If not, then I can do it in one line of C++: system("wmic computersystem get name, manufacturer, model"); ⟨drops mic⟩ -Raymond]
  25. Gabe says:

    Joshua: I'm pretty sure you must first log in with OAuth, then parse the response, and send the appropriate cookie in your inbox query. The most complicated part is probably parsing the response to the authentication request.

  26. Joshua says:

    I didn't think I was going to answer, but Gabe did so I will. The last time I looked up the call for any such thing OAuth didn't even exist.

    [If not, then I can do it in one line of C++: system("wmic computersystem get name, manufacturer, model"); -Raymond]

    The only reason I think you cheated is you didn't parse the result.

    [I would think that invoking an external program to do the heavy lifting violates the spirit of the phrase "in C++". -Raymond]
  27. Myria says:

    [The program takes no command line arguments. Who cares! (Don't forget people: Little Program.) -Raymond]

    Butbutbut it saves 512 bytes and starts up unnoticeably faster!…… =)

    I just make it a habit to use wmain / wWinMain so I don't mess up for real programs. =^-^=

    C:Projectstemptests>echo extern "C" int __cdecl main() { return 0; } > unicode.cpp

    C:Projectstemptests>cl /MT /Ox /nologo unicode.cpp > nul

    C:Projectstemptests>ls -l unicode.exe

    -rwxrwxrwx   1 user     group       36864 Jan  8 14:33 unicode.exe

    C:Projectstemptests>echo extern "C" int __cdecl wmain() { return 0; } > unicode.cpp

    C:Projectstemptests>cl /MT /Ox /nologo unicode.cpp > nul

    C:Projectstemptests>ls -l unicode.exe

    -rwxrwxrwx   1 user     group       36352 Jan  8 14:34 unicode.exe

  28. Total spirit violator says:

    int main() {

       std::cout << "Plz run yr favourite Yahoo! mail reader and enter the number of Inbox messages, followed by the Enter key: ";

       char buf[4]; gets(buf); std::cout << "Result: " << buf << std::endl;

    }

    Note that the first line can be omitted by taking advantage of long file name support: name the program the same as the intro line (remove/replace unsupported characters) and its usage is self-documenting. Additionally the program becomes even more flexible and re-usable since the name of the program (or shortcuts to it) can be changed to reflect the desired task. Finally, this snippet does not include external code executed and human motor/brain activity from computer start to user entry.

  29. Evan says:

    @Total spirit violator:

    I like (1) the use of gets, one of the worst functions ever; (2) the assumption that the number of inbox messages is at most 3 digits (definitely not true for many people); (3) the juxtaposition of gets with c++ iostreams.

  30. Gabe says:

    Evan: Sure, 3 chars are allocated to the buffer, but what's really going to happen if there are more? Crash on exit? At that point the program has already done its job.

Comments are closed.