How do I get the computer’s serial number? Consuming Windows Runtime classes in desktop apps, part 1: Raw C++


Getting the computer's serial number used to be an arduous task of getting the system firmware table, and then manually parsing the SMBIOS information looking for the serial number.

Windows 8 introduced the Windows.System.Profile.System­Manufacturers.SmbiosInformation runtime class which parses out the serial number for you.

We can do this the easy way or the hard way. Let's do it the hard way.

From Visual Studio, create a new C++ Console Application that goes like this:

#include <windows.h>
#include <wrl/client.h>
#include <wrl/wrappers/corewrappers.h>
#include <windows.system.profile.systemmanufacturers.h>
#include <roapi.h>
#include <stdio.h> // Horrors! Mixing C and C++!

namespace WRL = Microsoft::WRL;
namespace spsm = ABI::Windows::System::Profile::SystemManufacturers;

using Microsoft::WRL::Wrappers::HString;
using Microsoft::WRL::Wrappers::HStringReference;

int __cdecl wmain(int, wchar_t**)
{
    CCoInitialize init;

    WRL::ComPtr<spsm::ISmbiosInformationStatics> statics;
    HString serialNumber;
    RoGetActivationFactory(HStringReference(
        RuntimeClass_Windows_System_Profile_SystemManufacturers_SmbiosInformation)
                             .Get(), IID_PPV_ARGS(&statics));
    statics->get_SerialNumber(serialNumber.GetAddressOf());
    wprintf(L"Serial number = %ls\n", serialNumber.GetRawBuffer(nullptr));

    return 0;
}

Before building, right-click the Project in Visual Studio and select Properties. From the dialog, make the following change:

  • Configuration Properties, Linker, Inputs, Additional Dependencies: add windowsapp.lib.

Okay, now you can build and run the program, and it'll tell you your system's serial number as recorded in the SMBIOS.

And fortunately you didn't have to go parsing the system firmware table yourself.

This series repeats the above exercise in desktop apps in various languages, each one getting easier than the previous one. Next up is C++/CX.

(The secret goal of this series is to capture how you need to configure your project to get it to build.)

Comments (26)
  1. Danny says:

    “This series repeats the above exercise in desktop apps in various languages…” – when you reach Ada, let me know :P

    1. Rick C says:

      He said they’re supposed to be getting easier, not more tedious.

      I kid, I kid. I learned Ada in college and liked it, mostly, but it struck me, as it might a Kzin, as verbose.

      1. Peter Doubleday says:

        Herbivore!

        1. Buster says:

          Eater of roots and leaves!

  2. camhusmj38 says:

    You missed an even harder way. COM in C – real programmers manually consume vtables.
    (Lies down tries not to think of it again.)

    1. Peter Doubleday says:

      Perverted genius! The very concept of parsing a vtable is … let’s not go there.

    2. skSdnW says:

      Doing it in pure C++ without the WRL stuff is about the same as in C, just without the extra vtbl access and the extra parameter. I have done it in MinGW with a pre-Win8 era SDK and it is not very fun, these new headers files are not meant to be parsed by humans and finding the right things to copy is hard sometimes because of all the templates. MSDN is not helpful either because it hides all the low-level details.

      1. Peter Doubleday says:

        So, doing it in C++ is pretty much the same as doing it in C, but without the extra vtable access.

        Only slight problem with that is that it doesn’t say anything about the vtable access. Which is a very brittle and unstable way to go.

        1. laonianren says:

          COM (upon which WinRT is based) isn’t defined in terms of vtables. A COM interface is just a bunch of pointers in memory laid out according to the COM specification. Not coincidentally, the chosen layout is the same as the obvious layout for simple C++ vtables.

          You could argue that expecting the C++ compiler to emit vtables that match the COM specification is more brittle than coding directly to the specification using C.

    3. Dave says:

      Naah, that’s a method for leedle weemen. Real Men(tm) use the Meltdown vulnerability to read the info straight from kernel memory via a user-space app.
      Raymond, will you be including that in your list of examples?

      1. Nick says:

        Dave, you could have had the same comment without being offensive by saying “Naah, that method is too easy” with your first sentence.

        1. middings says:

          You must read Dave’s comment with Rainier Wolfcastle’s accent.

  3. Entegy says:

    I know you said we’re doing this the hard way, but is there anything stopping a C++ app from querying WMI for this?

    1. Sure, go ahead and query WMI if you like. I was trying to find something simple yet interesting as motivation. You are welcome to invent your own motivation.

      1. 640k says:

        Using wmi should actually be considered the hard way to do it. Almost all calls to wmi can hang your process, and do so as soon as the wmi database has some internal corruption, which is very common on an arbitrary server. With 1000 vms (in the wmi company’s own cloud), a corruption usually happens once a week. Wmi is very brittle technology, you have to wrap the api in robustness that you develop yourself, though another option is of course to ignore the problem and prepare for angry calls from your customers due to hanged apps.

  4. Peter Doubleday says:

    I’m relieved to say, this looks pretty easy. Only one extra dependency?

    Concerning the mixing of C and C++ (headers): I’m not entirely sure that I’ve ever been able to do anything else in Windows. Probably I’ve over-used stdio.h, or it’s a form of insanity, or something. I will say that (networking headers aside) it’s always seemed fairly painless compared to the same in Linux, where from memory you have to juggle the C++ standard headers, the C standard headers, the Linux standard headers, and sometimes even the Posix standard headers. But in either case it’s doable if you’re motivated enough.

    (Of course, you are joking, and this only applies to the output line in this case, and therefore the issue is moot.)

    Looking forward to the Interop version, which I’m guessing will in fact have to deal with more “gotchas” than this version …

    1. kantos says:

      The perfectionist in me gets annoyed because in C++ it should be #include <cstdio> that said it’s not wrong per se to use the C header.

  5. James says:

    I mean there’s an argument to be made that the system serial number shouldn’t be readable from an unprivileged process.

  6. Pietro Gagliardi (andlabs) says:

    Wait, I forget; aren’t programs that call Windows Runtime functions like RoGetActivationFactory() supposed to call RoInitialize(), not CoInitialize()? Though I imagine I could change CCoInitialize into CRoInitialize by just changing the functions…

    1. Darran Rowe says:

      Technically yes, but I think for a desktop application they do the same work.

    2. Medinoc says:

      I’m not sure it’s that simple: CCoInitialize & CoInitialize() initialize as STA (as does OleInitialize()), whereas RoInitialize() apparently only supports MTA.

      …How does the marshaling work for Windows Runtime interfaces?

      1. Darran Rowe says:

        Well, the documentation for this is meh.
        For example, the documentation for Windows::Foundation::Initialize (which calls RoInitialize) states:
        Windows::Foundation::Initialize is changed to create ASTAs instead of classic STAs for the RO_INIT_TYPE value RO_INIT_SINGLETHREADED. Windows::Foundation::Initialize(RO_INIT_SINGLETHREADED) is not supported for desktop applications and will return CO_E_NOTSUPPORTED if called from a process other than a Windows Store app.
        However, the header RoApi only defines RO_INIT_SINGLETHREADED when you are compiling for desktop and it sets the default to RO_INIT_SINGLETHREADED for the desktop.
        _Check_return_
        __inline HRESULT Initialize(_In_ RO_INIT_TYPE initType

        #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
        = RO_INIT_SINGLETHREADED
        #endif // WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
        )
        {
        return RoInitialize(initType);
        }
        And calling Windows::Foundation::Initialize using the default parameters works too. So the documentation is just at odds with the headers in this case.

  7. Zolone says:

    The C way …

    #include
    #include
    #include

    /***
    From windows.system.profile.systemmanufacturers.h

    MIDL_INTERFACE(“080CCA7C-637C-48C4-B728-F9273812DB8E”)
    ISmbiosInformationStatics : IInspectable

    EXTERN_C const IID IID___x_ABI_CWindows_CSystem_CProfile_CSystemManufacturers_CISmbiosInformationStatics;
    ***/

    const IID IID___x_ABI_CWindows_CSystem_CProfile_CSystemManufacturers_CISmbiosInformationStatics =
    {
    0x080CCA7C, 0x637C, 0x48C4, { 0xB7, 0x28, 0xF9, 0x27, 0x38, 0x12, 0xDB, 0x8E }
    };

    //#pragma comment(lib, “windowsapp.lib”)
    #pragma comment(lib, “runtimeobject.lib”)

    int main()
    {
    if (SUCCEEDED(CoInitialize(NULL)))
    {
    TCHAR strBuffer[256];

    __x_ABI_CWindows_CSystem_CProfile_CSystemManufacturers_CISmbiosInformationStatics *statics;

    HSTRING_HEADER hhdr;
    HSTRING smbiosInfo;
    HSTRING serialNumber;

    DWORD dwWritten;

    WindowsCreateStringReference(
    RuntimeClass_Windows_System_Profile_SystemManufacturers_SmbiosInformation,
    ARRAYSIZE(RuntimeClass_Windows_System_Profile_SystemManufacturers_SmbiosInformation) – 1,
    &hhdr,
    &smbiosInfo);

    RoGetActivationFactory(smbiosInfo,
    &IID___x_ABI_CWindows_CSystem_CProfile_CSystemManufacturers_CISmbiosInformationStatics,
    (VOID**)(&statics));

    statics->lpVtbl->get_SerialNumber(statics, &serialNumber);

    WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), strBuffer,
    wsprintf(strBuffer, TEXT(“Serial number = %ls\n”),
    WindowsGetStringRawBuffer(serialNumber, NULL)),
    &dwWritten, NULL);

    WindowsDeleteString(serialNumber);

    statics->lpVtbl->Release(statics);

    CoUninitialize();
    }
    return 0;
    }

    1. Zolone says:

      Should be:

      #include <windows.h>
      #include <roapi.h>
      #include <windows.system.profile.systemmanufacturers.h>

  8. DBJ says:

    I thought (snuggly) this is *much* easier to obtain using WMI ( https://support.microsoft.com/en-us/help/558124 ) but alas it returns “To Be Filled By O.E.M.” … as a serial number on my machine.

  9. Pietro Gagliardi (andlabs) says:

    Actually another question I only now just thought of: what is the run-time (i.e. LoadLibrary()) equivalent of linking to windowsapp.lib or runtimeobject.lib, which admittedly I’m not sure which symbols they provide? Or are they all either in combase.dll or defined in header files?

Comments are closed.

Skip to main content