Finding If a Computer Is a Laptop

I’ve ran recently across this question: how to find (using C++) if a computer is a laptop? That is possible with WMI and many answers (such as this) point to the Win32_SystemEnclosure class. This class has a member called ChassisTypes, which is an array of integers indicating possible chassis types. At least one of them should indicate a laptop. However, there might be several problems with this solution. First, there are several values for “laptops”:

  • 8 – Portable
  • 9 – Laptop
  • 10 – Notebook

Different machines might return different values. And more important, this property might not be defined on all computers. A more reliable solution is explained in this TechNet article Finding Computers That Are Laptops. The solution described there suggests checking for several properties:

  • Win32_SystemEnclosure, ChassisTypes(1)=10.
  • Win32_Battery or Win32_PortableBattery.
  • Win32_PCMCIAController
  • Win32_DriverVXD.Name = “pccard”
  • Win32_ComputerSystem.Manufacturer
  • Win32_ComputerSystem.Model

The following code shows how one can query for the chassis types using C++. Run queries for the other properties to make sure you are running on a laptop.

#define _WIN32_DCOM

#include <iostream>
using namespace std;

#include <comdef.h>
#include <Wbemidl.h>

#pragma comment(lib, "wbemuuid.lib")

class WMIQuery
{
   IWbemLocator* m_pLocator;
   IWbemServices* m_pServices;

public:
   WMIQuery():
      m_pLocator(NULL),
      m_pServices(NULL)
   {
   }

   bool Initialize()
   {
      // Obtain the initial locator to WMI
      HRESULT hr = ::CoCreateInstance(
         CLSID_WbemLocator,             
         0, 
         CLSCTX_INPROC_SERVER, 
         IID_IWbemLocator, (LPVOID *) &m_pLocator);

      if (FAILED(hr))
      {
         cerr << "Failed to create IWbemLocator object. Err code = 0x" << hex << hr << endl;
         return false;
      }

      // Connect to WMI through the IWbemLocator::ConnectServer method
      // Connect to the root\cimv2 namespace with the current user 
      hr = m_pLocator->ConnectServer(
         _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
         NULL,                    // User name. NULL = current user
         NULL,                    // User password. NULL = current
         0,                       // Locale. NULL indicates current
         NULL,                    // Security flags.
         0,                       // Authority (e.g. Kerberos)
         0,                       // Context object 
         &m_pServices             // pointer to IWbemServices proxy
         );

      if (FAILED(hr))
      {
         cerr << "Could not connect. Error code = 0x" << hex << hr << endl;
         m_pLocator->Release();
         m_pLocator = NULL;
         return false;
      }

      // Set security levels on the proxy
      hr = ::CoSetProxyBlanket(
         m_pServices,                 // Indicates the proxy to set
         RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
         RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
         NULL,                        // Server principal name 
         RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx 
         RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
         NULL,                        // client identity
         EOAC_NONE                    // proxy capabilities 
         );

      if (FAILED(hr))
      {
         cerr << "Could not set proxy blanket. Error code = 0x" << hex << hr << endl;
         m_pServices->Release();
         m_pServices = NULL;
         m_pLocator->Release();
         m_pLocator = NULL;
         return false;
      }

      return true;
   }

   IEnumWbemClassObject* Query(LPCTSTR strquery)
   {
      IEnumWbemClassObject* pEnumerator = NULL;
      HRESULT hr = m_pServices->ExecQuery(
         bstr_t("WQL"), 
         bstr_t(strquery),
         WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, 
         NULL,
         &pEnumerator);

      if (FAILED(hr))
      {
         cerr << "Query for operating system name failed. Error code = 0x" << hex << hr << endl;
         return NULL;
      }

      return pEnumerator;
   }

   ~WMIQuery()
   {
      if(m_pServices != NULL)
      {
         m_pServices->Release();
         m_pServices = NULL;
      }

      if(m_pLocator != NULL)
      {
         m_pLocator->Release();
         m_pLocator = NULL;
      }
   }
};

int _tmain(int argc, _TCHAR* argv[])
{
   HRESULT hres;

   // Initialize COM.
   hres =  ::CoInitializeEx(0, COINIT_MULTITHREADED); 
   if (FAILED(hres))
   {
      cout << "Failed to initialize COM library. Error code = 0x" << hex << hres << endl;
      return 1;
   }

   // Set general COM security levels
   hres =  ::CoInitializeSecurity(
      NULL, 
      -1,                          // COM authentication
      NULL,                        // Authentication services
      NULL,                        // Reserved
      RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication 
      RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation  
      NULL,                        // Authentication info
      EOAC_NONE,                   // Additional capabilities 
      NULL                         // Reserved
      );

   if (FAILED(hres))
   {
      cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl;
      ::CoUninitialize();
      return 1;
   }
   else
   {
      WMIQuery query;
      if(query.Initialize())
      {
         IEnumWbemClassObject* pEnumerator = query.Query(_T("SELECT * FROM Win32_SystemEnclosure"));

         if(pEnumerator != NULL)
         {
            // Get the data from the query
            IWbemClassObject *pclsObj;
            ULONG uReturn = 0;

            while (pEnumerator)
            {
               HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);

               if(0 == uReturn)
               {
                  break;
               }

               VARIANT vtProp;

               hr = pclsObj->Get(L"Name", 0, &vtProp, 0, 0);
               wcout << "Name:    " << vtProp.bstrVal << endl;

               hr = pclsObj->Get(L"ChassisTypes", 0, &vtProp, 0, 0);
               wcout << "Chassis: ";
               SAFEARRAY* parrValues = NULL;
               
               if (vtProp.vt & VT_ARRAY)
               {
                  if (VT_BYREF & vtProp.vt)
                     parrValues = *vtProp.pparray;
                  else
                     parrValues = vtProp.parray;
               }

               if (parrValues != NULL)
               {
                  SAFEARRAYBOUND arrayBounds[1];
                  arrayBounds[0].lLbound = 0;
                  arrayBounds[0].cElements = 0;

                  SafeArrayGetLBound(parrValues, 1, &arrayBounds[0].lLbound);
                  SafeArrayGetUBound(parrValues, 1, (long*)&arrayBounds[0].cElements);
                  arrayBounds[0].cElements -= arrayBounds[0].lLbound;
                  arrayBounds[0].cElements += 1;

                  if (arrayBounds[0].cElements > 0)
                  {
                     for (ULONG i = 0; i < arrayBounds[0].cElements; i++)
                     {
                        LONG lIndex = (LONG)i;
                        INT item;

                        HRESULT hr = ::SafeArrayGetElement(parrValues, &lIndex, &item);

                        if(SUCCEEDED(hr))
                        {
                           LPCTSTR szType = NULL;
                           switch(item)
                           {
                           case 1: szType = _T("Other"); break;
                           case 2: szType = _T("Unknown"); break;
                           case 3: szType = _T("Desktop"); break;
                           case 4: szType = _T("Low Profile Desktop"); break;
                           case 5: szType = _T("Pizza Box"); break;
                           case 6: szType = _T("Mini Tower"); break;
                           case 7: szType = _T("Tower"); break;
                           case 8: szType = _T("Portable"); break;
                           case 9: szType = _T("Laptop"); break;
                           case 10:szType = _T("Notebook"); break;
                           case 11:szType = _T("Hand Held"); break;
                           case 12:szType = _T("Docking Station"); break;
                           case 13:szType = _T("All in One"); break;
                           case 14:szType = _T("Sub Notebook"); break;
                           case 15:szType = _T("Space-Saving"); break;
                           case 16:szType = _T("Lunch Box"); break;
                           case 17:szType = _T("Main System Chassis"); break;
                           case 18:szType = _T("Expansion Chassis"); break;
                           case 19:szType = _T("SubChassis"); break;
                           case 20:szType = _T("Bus Expansion Chassis"); break;
                           case 21:szType = _T("Peripheral Chassis"); break;
                           case 22:szType = _T("Storage Chassis"); break;
                           case 23:szType = _T("Rack Mount Chassis"); break;
                           case 24:szType = _T("Sealed-Case PC"); break;
                           }
                           wcout << szType;
                           if(i+1 < arrayBounds[0].cElements)
                              wcout << ", ";
                        }
                     }

                     wcout << endl;
                  }
               }

               VariantClear(&vtProp);

               pclsObj->Release();
            }

            pEnumerator->Release();
         }
      }
   }

   ::CoUninitialize();

   return 0;
}

On my laptop, the program output was:

Name: System Enclosure
Chassis: Notebook

8 Replies to “Finding If a Computer Is a Laptop”

  1. The file names in the include statements are missing, so I can’t compile this and learn from your efforts. Could you update this please?

  2. Thanks. This is a learning

    Was there a specific reason you need not make use of WMI query was of extracting the data? i have done in C# and found it shorter

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.