Three Ways to Find Files

Let’ say we have to implement a function that search a folder to recursively find files having an extension from a given list of extensions. This article shows three possible implementations: one using FindFirstFile and FindNextFile WinAPI functions, one using CFileFind MFC class and one using Filesystem Library.

Find files using FindFirstFile and FindNextFile WinAPI functions

Althugh this implementation uses some ATL stuff to make things easier, it appears still quite complicated.

void demo::FindFiles(const CString& strRootPath,       // initial path to search
                     const std::list<CString> listExt, // a list of file extensions
                     std::list<CString>& listFiles)    // output list of files found
{
    CString strFileToFind = strRootPath;
    ATLPath::Append(CStrBuf(strFileToFind, MAX_PATH), _T("*.*"));

    WIN32_FIND_DATA findData = { 0 };
    HANDLE hFileFind = ::FindFirstFile(strFileToFind, &findData);
    if (INVALID_HANDLE_VALUE != hFileFind)
    {
        do
        {
            CString strFileName = findData.cFileName;
            if ((strFileName == _T(".")) || (strFileName == _T("..")))
                continue;

            CString strFilePath = strRootPath;
            ATLPath::Append(CStrBuf(strFilePath, MAX_PATH), strFileName);
            if (ATLPath::IsDirectory(strFilePath))
            {
                // call recursive
                FindFiles(strFilePath, listExt, listFiles);
            }
            else
            {
                CString strExt = ATLPath::FindExtension(strFilePath);
                strExt.TrimLeft(_T('.'));

                if (IsStringInListNoCase(strExt, listExt))
                {
                    listFiles.push_back(strFilePath);
                }
            }

        } while (::FindNextFile(hFileFind, &findData));

        ::FindClose(hFileFind);
    }
}

Find files using CFileFind MFC class

Using CFileFind MFC class can make code a little bit shorter and the programmer’s life easier.

void demo::FindFiles(const CString& strRootPath,
                     const std::list<CString> listExt,
                     std::list<CString>& listFiles)
{
    CString strFileToFind = strRootPath + _T("\\*.*");

    CFileFind fileFind;
    BOOL bMoreFiles = fileFind.FindFile(strFileToFind);
    while (bMoreFiles)
    {
        bMoreFiles = fileFind.FindNextFile();
        if (fileFind.IsDots())
            continue;

        CString strFilePath = fileFind.GetFilePath();
        if (fileFind.IsDirectory())
        {
            // call recursive
            FindFiles(strFilePath, listExt, listFiles);
        }
        else
        {
            int nPos = strFilePath.ReverseFind(_T('.'));
            if (-1 != nPos)
            {
                CString strExt = strFilePath.Right(strFilePath.GetLength() - nPos - 1);
                if (IsStringInListNoCase(strExt, listExt))
                {
                    listFiles.push_back(strFilePath);
                }
            }
        }
    }
}

Note: IsStringInListNoCase searches a list for a string, not case sensitive. You can find its implementation in the attached demo solution.

Find files using Filesystem Library

Here it is:

void demo::FindFiles(const CString& strRootPath,       // initial path to search
                     const std::list<CString> listExt, // a list of file extensions
                     std::list<CString>& listFiles)    // output list of files found
{
    _FSPFX path root_path(strRootPath.GetString());
    _FSPFX recursive_directory_iterator end;
    _FSPFX recursive_directory_iterator iter(root_path);
    while (iter != end)
    {
        const auto& path = iter->path();
        CString strExt = path.extension().c_str();
        strExt.TrimLeft(_T('.'));
        if (IsStringInListNoCase(strExt, listExt))
        {
            listFiles.push_back(path.c_str());
        }
        ++iter;
    }
}

Hurray, we did it in just few lines of code! ๐Ÿ™‚
Of course, it can be used in MFC, ATL, Win32 and Console applications, as well.

Demo solution

Download: Find-Files-Samples.zip

The demo solution contains three projects, each having one of the above implementations.

Resources and related articles

Listing Processes โ€“ Using Windows Management Instrumentation (WMI)

Using Win32_Process WMI class

A previous article shows how to use Win32_PhysicalMedia WMI class to get physical drive info. We can write something similar for getting a list of running processes. All we have to do is to replace the WQL query and get specific properties for Win32_Process. However, to simplify the things, I wrote few C++ wrapper classes over the WMI stuff.
Here is a brief description for each one:

  • CWMIConnection – opens and keeps a connection to WMI namespace.
  • CWMIQuery – executes WQL (SQL for WMI) queries and navigates through results.
  • CWMIWin32_Process – is derived from CWMIQuery and is specialized for Win32_Process.
  • CWMIValue – a class that gets user readable strings from different CIM types.

The implementation details can be found in the attached demo project.
Let’s show now just a usage example, that fills a list-view control with info about running processes.

void CDemoDlg::_FillProcessesList()
{
    // clear listview control
    m_listProcesses.DeleteAllItems();

    try
    {
        // Open WMI connection
        CWMIConnection wmiConnection;
        wmiConnection.Open(L"ROOT\\CIMV2");

        // Query Win32_Process
        CWMIWin32_Process wmiQuery(wmiConnection);
        wmiQuery.Open();

        // Fill the list
        int nItem = 0;
        while(wmiQuery.MoveNext())
        {
            m_listProcesses.InsertItem(nItem, NULL);

            m_listProcesses.SetItemText(nItem, ePID, WMI_GETPROPERTYSTR(wmiQuery, Handle));
            m_listProcesses.SetItemText(nItem, eSessionID, WMI_GETPROPERTYSTR(wmiQuery, SessionId));
            m_listProcesses.SetItemText(nItem, eImageName, WMI_GETPROPERTYSTR(wmiQuery, Caption));
            m_listProcesses.SetItemText(nItem, eCommandLine, WMI_GETPROPERTYSTR(wmiQuery, CommandLine));
            // ...

            // NOTE: This is just for demo purpose and can be completed. 
            // For a compelte list of Win32_Process properties, see MSDN documentation.
            // https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process
            nItem++;
        }
    }
    catch(COleException* e)
    {
        e->ReportError();
        e->Delete();
    }
}

Demo project

Download: Listing_Processes_Using_WMI.zip

The demo project is a simple MFC dialog-based application that uses WMI wrapper classes to list and get info about running processes.

Using WMI - Demo Application

Resources

Listing Processes โ€“ Using Remote Desktop Services API

Using Remote Desktop Services API

We can call WTSEnumerateProcesses function to get information about the active processes on a specified Remote Desktop Session Host server. However, if pass WTS_CURRENT_SERVER_HANDLE in first argument (server handle), we can enumerate and get info about processes which are running on local machine.
The following example calls WTSEnumerateProcesses function, then fills an array of WTS_PROCESS_INFO structures.

// Windows Terminal Server API header & lib 
#include <wtsapi32.h&gt;
#pragma comment(lib, "Wtsapi32.lib")

DWORD RDSAPI_EnumProcesses(CArray<WTS_PROCESS_INFO>& arrProcInfo)
{
    // clear output array
    arrProcInfo.RemoveAll();

    // enumerate processes
    HANDLE hServer = WTS_CURRENT_SERVER_HANDLE; // local machine
    PWTS_PROCESS_INFO pProcessInfo = NULL;
    DWORD dwCount = 0;
    if (!::WTSEnumerateProcesses(hServer, 0, 1, &pProcessInfo, &dwCount))
    {
        return ::GetLastError();
    }

    // fill output array
    arrProcInfo.SetSize(dwCount);
    for (DWORD dwIndex = 0; dwIndex < dwCount; dwIndex++)
    {
        arrProcInfo[dwIndex] = pProcessInfo[dwIndex];
    }

    // free the memory allocated in WTSEnumerateProcesses
    ::WTSFreeMemory(pProcessInfo);
    return NO_ERROR;
}

If the target OS is Windows Vista or newer, we can alternatively use WTSEnumerateProcessesEx which can get additional info in WTS_PROCESS_INFO_EX structures.
Here is an example that enumerates processes, then fills an array of application-defined structures, (CProcessInfoEx) which contain the following info:

  • the process identifier;
  • the identifier of session associated with the process;
  • the name of the executable file associated with the process;
  • the user account name;
  • the user domain name;
  • the number of threads in the process;
  • the number of handles in the process;
  • the page file usage of the process, in bytes;
  • the peak page file usage of the process, in bytes;
  • the working set size of the process, in bytes;
  • the peak working set size of the process, in bytes;
  • the time, in milliseconds, the process has been running in user mode;
  • the time, in milliseconds, the process has been running in kernel mode.
DWORD RDSAPI_EnumProcessesEx(CArray<CProcessInfoEx>& arrProcInfo)
{
    // clear output array
    arrProcInfo.RemoveAll();

    // init WTSEnumerateProcessesEx parameters
    HANDLE hServer = WTS_CURRENT_SERVER_HANDLE; // get local machine processes
    DWORD dwLevel = 1;                          // get array of WTS_PROCESS_INFO_EX
    DWORD dwSessionID = WTS_ANY_SESSION;        // get processes in all sessions
    DWORD/ dwCount = 0;                         // returns the number of processes
    PWTS_PROCESS_INFO_EX  pProcessInfo = NULL;  // output data

    if (!::WTSEnumerateProcessesEx(hServer, &dwLevel, dwSessionID, (LPTSTR*)&pProcessInfo, &dwCount))
    {
        return ::GetLastError();
    }

    // fill output array
    arrProcInfo.SetSize(dwCount);
    for (DWORD dwIndex = 0; dwIndex < dwCount; dwIndex++)
    {
        CProcessInfoEx processInfoEx(pProcessInfo[dwIndex]); 
        arrProcInfo[dwIndex] = processInfoEx;
    }

    // free the memory allocated in WTSEnumerateProcessesEx
    if (!::WTSFreeMemoryEx(WTSTypeProcessInfoLevel1, pProcessInfo, dwCount))
    {
        return ::GetLastError();
    }
    return NO_ERROR;
}

Now, let’s fill a listview control with the processes info grabbed by RDSAPI_EnumProcessesEx.

void CDemoDlg::_FillProcessesList()
{
    // clear listview control
    m_listProcesses.DeleteAllItems();

    // get an array of CProcessInfoEx
    CArray<CProcessInfoEx> arrProcInfo;
    VERIFY(NO_ERROR == RDSAPI_EnumProcessesEx(arrProcInfo));

    // fill the listview control
    const INT_PTR nSize = arrProcInfo.GetSize();
    for (INT_PTR nItem = 0; nItem < nSize; nItem++)
    {
        // add new item to listview control
        m_listProcesses.InsertItem(nItem, NULL);

        // set list imtem text
        const CProcessInfoEx& processInfoEx = arrProcInfo.ElementAt(nItem);
        m_listProcesses.SetItemText(nItem, ePID, processInfoEx.GetProcessIdStr());
        m_listProcesses.SetItemText(nItem, eSessionID, processInfoEx.GetSessionIdStr());
        m_listProcesses.SetItemText(nItem, eImageName, processInfoEx.GetProcessNameStr());
        m_listProcesses.SetItemText(nItem, eUserName, processInfoEx.GetUserNameStr());
        m_listProcesses.SetItemText(nItem, eDomain, processInfoEx.GetDomainNameStr());
        m_listProcesses.SetItemText(nItem, eThreads, processInfoEx.GetNumberOfThreadsStr());
        m_listProcesses.SetItemText(nItem, eHandles, processInfoEx.GetHandleCountStr());
        m_listProcesses.SetItemText(nItem, ePagefileUsage, processInfoEx.GetPagefileUsageStr());
        m_listProcesses.SetItemText(nItem, ePeakPagefileUsage, processInfoEx.GetPeakPagefileUsageStr());
        m_listProcesses.SetItemText(nItem, eWorkingSetSize, processInfoEx.GetWorkingSetSizeStr());
        m_listProcesses.SetItemText(nItem, ePeakWorkingSetSize, processInfoEx.GetPeakWorkingSetSizeStr());
        m_listProcesses.SetItemText(nItem, eUserTime, processInfoEx.GetUserTimeStr());
        m_listProcesses.SetItemText(nItem, eKernelTime, processInfoEx.GetKernelTimeStr());
    }
}

More implementation details can be found in the attached demo project.

Notes

  • WTSEnumerateProcesses function requires at least Windows XP or Windows Server 2003;
  • WTSEnumerateProcessesEx function requires at least Windows 7 or Windows Server 2008 R2;
  • WTS prefix comes from Windows Terminal Services which is the former name of Remote Desktop Services.

Demo Project

The demo project is a simple MFC dialog-based application that uses the above functions.

Download: Listing_Processes_Using_RDS_API.zip (23 KB)

Using Remote Desktop Services API - Demo Application

References


Listing Processes โ€“ Using Tool Help Library

Tool Help Library makes it easy to enumerate and get info about processes, threads, modules, and heaps. Let’s use it to get a list of running processes.

Using Tool Help Library

First, take a snapshot by passing TH32CS_SNAPPROCESS flag to CreateToolhelp32Snapshot function. Next, pass the snapshot handle to Process32First, then to Process32Next to get running processes info in a list of PROCESSENTRY32 structures.

#include <Tlhelp32.h>

DWORD CDemoDlg::ToolHelp_EnumProcesses(CList<PROCESSENTRY32>& listProcInfo)
{
    DWORD dwRet = NO_ERROR;

    // take a snapshot of processes
    DWORD dwFlags = TH32CS_SNAPPROCESS;
    HANDLE hSnapshot = ::CreateToolhelp32Snapshot(dwFlags, 0);
    if(INVALID_HANDLE_VALUE == hSnapshot)
    {
        return ::GetLastError();
    }

    PROCESSENTRY32 processEntry = {0};
    processEntry.dwSize = sizeof(PROCESSENTRY32);
    // get info for each process in the snapshot
    if(::Process32First(hSnapshot, &processEntry))
    {
        do
        {   
            listProcInfo.AddTail(processEntry);
        }
        while(::Process32Next(hSnapshot, &processEntry));
    }
    else
    {
        dwRet = ::GetLastError();
    }
    ::CloseHandle(hSnapshot);
    return dwRet;
}

Next example fills a listview control with the following info of each process.

  • PID (process identifier);
  • process image name (the name of the executable file);
  • parent PID;
  • number of threads started by the process;
  • base priority of any threads created by this process.
void CDemoDlg::_FillProcessesList()
{
    // clear listview control
    m_listProcesses.DeleteAllItems();

    // get a list of PROCESSENTRY32 structures
    CList<PROCESSENTRY32> listProcInfo;
    VERIFY(NO_ERROR == ToolHelp_EnumProcesses(listProcInfo));

    // fill the listview control
    POSITION pos = listProcInfo.GetHeadPosition();
    int nIndex = 0;
    while(NULL != pos)
    {
        // get next process from the list
        const PROCESSENTRY32& procInfo = listProcInfo.GetNext(pos);

        // format info strings
        CString strPID, strImageName, strParentPID, strThreads, strPriority;
        strPID.Format(_T("%u"), procInfo.th32ProcessID);
        strImageName = procInfo.szExeFile;
        strParentPID.Format(_T("%u"), procInfo.th32ParentProcessID);
        strThreads.Format(_T("%u"), procInfo.cntThreads);
        strPriority.Format(_T("%d"), procInfo.pcPriClassBase);

        // set listview item text
        m_listProcesses.InsertItem(nIndex, strPID);
        m_listProcesses.SetItemText(nIndex, 1, strImageName);
        m_listProcesses.SetItemText(nIndex, 2, strParentPID);
        m_listProcesses.SetItemText(nIndex, 3, strThreads);
        m_listProcesses.SetItemText(nIndex++, 4, strPriority);
    }
}

Demo project

Download: Listing_Processes_Using_Tool_Help_Library.zip (21 KB)

The demo project is a simple MFC dialog-based application that uses the above functions.

Using Tool Help Library - Demo Application

Resources

Listing Processes โ€“ Using PSAPI

A previous article shows how to list running processes from command line. Next, we’ll see how to get a list of processes in our own programs. Let’s start by using Process Status API (PSAPI).

Using PSAPI

Here is an example that calls EnumProcesses PSAPI function, then fills an array with the found process identifiers.

#include <Psapi.h>
#pragma comment(lib, "Psapi.lib")
// ...

DWORD CDemoDlg::PSAPI_EnumProcesses(CDWordArray& arrProcessIds, 
                        DWORD dwMaxProcessCount)
{
    DWORD dwRet = NO_ERROR;
    arrProcessIds.RemoveAll();

    DWORD *pProcessIds = new DWORD[dwMaxProcessCount];
    DWORD cb = dwMaxProcessCount * sizeof(DWORD);
    DWORD dwBytesReturned = 0;

    // call PSAPI EnumProcesses
    if(::EnumProcesses(pProcessIds, cb, &dwBytesReturned))
    {
        // fill the array with returned process IDs
        const int nSize = dwBytesReturned / sizeof(DWORD);
        arrProcessIds.SetSize(nSize);
        for(int nIndex = 0; nIndex < nSize; nIndex++)
        {
            arrProcessIds[nIndex] = pProcessIds[nIndex];
        }
    }
    else
    {
        dwRet = ::GetLastError();
    }
    delete []pProcessIds;
    return dwRet;
}

Once having the process identifiers, we can call OpenProcess to get process handles, then use the handles in other functions which get info about processes. Next example fills a listview control with process identifiers, names and image file paths.

void CDemoDlg::_FillProcessesList()
{
    // clear the listvie control
    m_listProcesses.DeleteAllItems();

    // get an array of process IDs
    CDWordArray arrProcessIds;
    VERIFY(NO_ERROR == PSAPI_EnumProcesses(arrProcessIds, 1024));
    const int nSize = arrProcessIds.GetSize();

    // fill the listview control
    for(int nIndex = 0; nIndex < nSize; nIndex++)
    {
        DWORD dwProcessId = arrProcessIds[nIndex];
        // get the process handle
        HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION,
                                  FALSE, dwProcessId);
        if(NULL != hProcess)
        {
            // insert new item in the list
            const int nItemCount = m_listProcesses.GetItemCount();
            m_listProcesses.InsertItem(nItemCount, NULL);

            // format process ID
            CString strProcessID;
            strProcessID.Format(_T("%u"), dwProcessId);

            // get executable file path (in device form)
            CString strImageFileName;
            ::GetProcessImageFileName(hProcess,
                        CStrBuf(strImageFileName, MAX_PATH), MAX_PATH);

            // get image name
            CString strImageName = ::PathFindFileName(strImageFileName);

            // set subitems text
            m_listProcesses.SetItemText(nItemCount, 0, strProcessID);
            m_listProcesses.SetItemText(nItemCount, 1, strImageName);
            m_listProcesses.SetItemText(nItemCount, 2, strImageFileName);

            // close the process handle
            ::CloseHandle(hProcess);
        }
    }
}

Demo project

Download: Listing_Processes_Using_PSAPI.zip (19 KB)

The demo project is a simple MFC dialog-based application that uses the above functions.

Using PSAPI - Demo Application

Resources

Get Physical Drive Serial Number โ€“ Part 2

The previous article shows how to get manufacturer-provided serial number for a physical drive by calling DeviceIoControl function.
Now, let’s see how can it be made by using WMI (Windows Management Instrumentation).

Get serial number by using Win32_PhysicalMedia WMI class

To get the physical drive serial number by using Win32_PhysicalMedia class, follow these steps:

  1. Initialize COM.
        HRESULT hr = ::CoInitializeEx(0, COINIT_MULTITHREADED);
  2. Set the default process security level.
        hr =  ::CoInitializeSecurity(
            NULL,                        // Security descriptor    
            -1,                          // COM negotiates authentication service
            NULL,                        // Authentication services
            NULL,                        // Reserved
            RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication level for proxies
            RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation level for proxies
            NULL,                        // Authentication info
            EOAC_NONE,                   // Additional capabilities of the client or server
            NULL);                       // Reserved
  3. Create a connection to WMI namespace.
        // Initialize the IWbemLocator interface
        CComPtr<IWbemLocator> pIWbemLocator;
        hr = ::CoCreateInstance(CLSID_WbemLocator, 0, 
            CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pIWbemLocator);
        // ...
    
        // Call IWbemLocator::ConnectServer for connecting to WMI 
        CComPtr<IWbemServices> pIWbemServices;
        hr = pIWbemLocator->ConnectServer(L"ROOT\\CIMV2",
            NULL, NULL, 0, NULL, 0, 0, &pIWbemServices);
  4. Set the security levels on WMI connection.
        hr = ::CoSetProxyBlanket(
            pIWbemServices, 
            RPC_C_AUTHN_WINNT,
            RPC_C_AUTHZ_NONE, 
            NULL, 
            RPC_C_AUTHN_LEVEL_CALL,
            RPC_C_IMP_LEVEL_IMPERSONATE,
            NULL,
            EOAC_NONE);
  5. Execute a WQL (WMI Query Language) query to get a list of physical media. Each list element contains a tag as unique identifier (e.g. PHYSICALDRIVE0)  and the manufacturer-provided serial number.
        const BSTR szQueryLanguage = L"WQL";
        const BSTR szQuery =  L"SELECT Tag, SerialNumber FROM Win32_PhysicalMedia";
        CComPtr<IEnumWbemClassObject> pIEnumWbemClassObject;
        hr = pIWbemServices->ExecQuery(
            szQueryLanguage,                                       // Query language
            szQuery,                                               // Query
            WBEM_FLAG_FORWARD_ONLY|WBEM_FLAG_RETURN_IMMEDIATELY,   // Flags
            NULL,                                                  // Context
            &pIEnumWbemClassObject);                               // Enumerator
  6. Get each enumerator element until find the desired physical drive. For detailed code, see the complete demo application, below.

Putting all together with some helpful ATL stuff, we can make now a simple console demo application.

// ConsoleWMI.cpp : Defines the entry point for the console application.
//
#include <atlbase.h>
#include <atlstr.h>
#include <comutil.h>
#include <wbemidl.h>

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

void GetPhysicalDriveSerialNumber(UINT nDriveNumber IN, CString& strSerialNumber OUT);

int _tmain(int argc, _TCHAR* argv[])
{
    CString strResult;
    try
    {
        // 1. Initialize COM 
        // http://msdn.microsoft.com/en-us/library/windows/desktop/aa390885(v=vs.85).aspx
        HRESULT hr = ::CoInitializeEx(0, COINIT_MULTITHREADED); 

        ATLENSURE_SUCCEEDED(hr);

        CString strSerialNumber;
        UINT nDriveNumber = 0;
        GetPhysicalDriveSerialNumber(nDriveNumber, strSerialNumber);
        strResult.Format(_T("Serial number for drive #%u is %s"), 
            nDriveNumber, strSerialNumber);
    }
    catch(CAtlException& e)
    {
        strResult.Format(_T("Get serial number failure. Error code: 0x%08X"), 
            (HRESULT)e);
    }

    // Show result
    ::MessageBox(NULL, strResult, _T("Serial number demo"), MB_OK);

    // Uninitialize COM
    ::CoUninitialize();
    return 0;
}

void GetPhysicalDriveSerialNumber(UINT nDriveNumber IN, CString& strSerialNumber OUT)
{
    strSerialNumber.Empty();

    // Format physical drive path (may be '\\.\PhysicalDrive0', '\\.\PhysicalDrive1' and so on).
    CString strDrivePath;
    strDrivePath.Format(_T("\\\\.\\PhysicalDrive%u"), nDriveNumber);

    // 2. Set the default process security level 
    // http://msdn.microsoft.com/en-us/library/windows/desktop/aa393617(v=vs.85).aspx
    HRESULT hr =  ::CoInitializeSecurity(
        NULL,                        // Security descriptor    
        -1,                          // COM negotiates authentication service
        NULL,                        // Authentication services
        NULL,                        // Reserved
        RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication level for proxies
        RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation level for proxies
        NULL,                        // Authentication info
        EOAC_NONE,                   // Additional capabilities of the client or server
        NULL);                       // Reserved

    ATLENSURE_SUCCEEDED(hr);

    // 3. Create a connection to WMI namespace
    // http://msdn.microsoft.com/en-us/library/windows/desktop/aa389749(v=vs.85).aspx

    // 3.1. Initialize the IWbemLocator interface
    CComPtr<IWbemLocator> pIWbemLocator;
    hr = ::CoCreateInstance(CLSID_WbemLocator, 0, 
        CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pIWbemLocator);

    ATLENSURE_SUCCEEDED(hr);

    // 3.2. Call IWbemLocator::ConnectServer for connecting to WMI 
    CComPtr<IWbemServices> pIWbemServices;
    hr = pIWbemLocator->ConnectServer(L"ROOT\\CIMV2",
        NULL, NULL, 0, NULL, 0, 0, &pIWbemServices);

    ATLENSURE_SUCCEEDED(hr);

    // 4. Set the security levels on WMI connection
    // http://msdn.microsoft.com/en-us/library/windows/desktop/aa393619(v=vs.85).aspx
    hr = ::CoSetProxyBlanket(
        pIWbemServices, 
        RPC_C_AUTHN_WINNT,
        RPC_C_AUTHZ_NONE, 
        NULL, 
        RPC_C_AUTHN_LEVEL_CALL,
        RPC_C_IMP_LEVEL_IMPERSONATE,
        NULL,
        EOAC_NONE);

    ATLENSURE_SUCCEEDED(hr);

    // 5. Execute a WQL (WMI Query Language) query to get physical media info
    const BSTR szQueryLanguage = L"WQL";
    const BSTR szQuery =  L"SELECT Tag, SerialNumber FROM Win32_PhysicalMedia";
    CComPtr<IEnumWbemClassObject> pIEnumWbemClassObject;
    hr = pIWbemServices->ExecQuery(
        szQueryLanguage,                                       // Query language
        szQuery,                                               // Query
        WBEM_FLAG_FORWARD_ONLY|WBEM_FLAG_RETURN_IMMEDIATELY,   // Flags
        NULL,                                                  // Context
        &pIEnumWbemClassObject);                               // Enumerator

    ATLENSURE_SUCCEEDED(hr);

    // 6. Get each enumerator element until find the desired physical drive 
    ULONG uReturn = 0;
    while(pIEnumWbemClassObject)
    {
        CComPtr<IWbemClassObject> pIWbemClassObject;
        hr = pIEnumWbemClassObject->Next(WBEM_INFINITE, 1, &pIWbemClassObject, &uReturn);
        if(0 == uReturn || FAILED(hr))
            break;

        variant_t vtTag;           // unique tag, e.g. '\\.\PHYSICALDRIVE0'
        variant_t vtSerialNumber;  // manufacturer-provided serial number

        hr = pIWbemClassObject->Get(L"Tag", 0, &vtTag, NULL, NULL);
        ATLENSURE_SUCCEEDED(hr);

        CString strTag(vtTag.bstrVal);
        if(!strTag.CompareNoCase(strDrivePath)) // physical drive found
        {
            hr = pIWbemClassObject->Get(L"SerialNumber", 0, &vtSerialNumber, NULL, NULL);
            ATLENSURE_SUCCEEDED(hr);
            strSerialNumber = vtSerialNumber.bstrVal; // get the serial number
            break;
        }
    }
}

See also

Resources

Later edit

We can simplify a little bit the above example by adding WHERE clause in the WQL query.

void GetPhysicalDriveSerialNumber(UINT nDriveNumber IN, CString& strSerialNumber OUT)
{
    strSerialNumber.Empty();

    // Format physical drive path (may be '\\\\.\\PhysicalDrive0', '\\\\.\\PhysicalDrive1' and so on). 
    // Note: backslash is used as escape in WQL, so we need to double each one.
    CStringW strDrivePath;
    strDrivePath.Format(_T("\\\\\\\\.\\\\PhysicalDrive%u"), nDriveNumber);

    // ...
    // ... [the same code as in previous example]
    // ...

    // 5. Execute a WQL (WMI Query Language) query to get the wanted phisical drive serial number
    const BSTR szQueryLanguage = L"WQL";
    CStringW strQuery;
    strQuery.Format(L"SELECT SerialNumber FROM Win32_PhysicalMedia WHERE Tag=\"%s\"", 
        strDrivePath);

    CComPtr<IEnumWbemClassObject> pIEnumWbemClassObject;
    hr = pIWbemServices->ExecQuery(
        szQueryLanguage,                                       // Query language
        (BSTR)strQuery.GetString(),                            // Query
        WBEM_FLAG_FORWARD_ONLY|WBEM_FLAG_RETURN_IMMEDIATELY,   // Flags
        NULL,                                                  // Context
        &pIEnumWbemClassObject);                               // Enumerator

    ATLENSURE_SUCCEEDED(hr);

   // 6. Get first enumerator element. If exists, get the serial number.
    ULONG uReturn = 0;
    CComPtr<IWbemClassObject> pIWbemClassObject;
    hr = pIEnumWbemClassObject->Next(WBEM_INFINITE, 1, &pIWbemClassObject, &uReturn);

    if(WBEM_S_NO_ERROR == hr)
    {
        variant_t vtSerialNumber;  // manufacturer-provided serial number
        hr = pIWbemClassObject->Get(L"SerialNumber", 0, &vtSerialNumber, NULL, NULL);
        ATLENSURE_SUCCEEDED(hr);

        strSerialNumber = vtSerialNumber.bstrVal; // assign serial number to output parameter
    }
    else
    {
        AtlThrow(hr);
    }
}

Get Physical Drive Serial Number – Part 1

One frequently asked question is “how to (programmatically) get the serial number of a physical drive?” or “how to find my hard disk serial number?“.
One first simple attempt may be to call GetVolumeInformation. However, this function retrieves a serial number which is assigned by the operating system to a volume when it is formatted. It’s not what we want.
To get the serial number assigned to the hard disk (or another type of physical drive) by the manufacturer, we have to find other ways, like for example calling DeviceIoControl function or using Win32_PhysicalMedia WMI class.
Let’s begin with the first one.

Get serial number by using DeviceIoControl

To get the serial number of a physical drive, we can call DeviceIoControl with IOCTL_STORAGE_QUERY_PROPERTY control code.
Just follow these steps:

  1. Call CreateFile function to get a handle to physical drive. First argument (lpFileName) may be \\.\PhysicalDrive0, \\.\PhysicalDrive1, \\.\PhysicalDrive2… for drive #0, #1, #2, and so on.
        // Format physical drive path (may be '\\.\PhysicalDrive0', '\\.\PhysicalDrive1' and so on).
        CString strDrivePath;
        strDrivePath.Format(_T("\\\\.\\PhysicalDrive%u"), nDriveNumber);
    
        // Get a handle to physical drive
        HANDLE hDevice = ::CreateFile(strDrivePath, 0, FILE_SHARE_READ|FILE_SHARE_WRITE,
            NULL, OPEN_EXISTING, 0, NULL);
  2. Set the STORAGE_PROPERTY_QUERY input data structure.
        // Set the input data structure
        STORAGE_PROPERTY_QUERY storagePropertyQuery;
        ZeroMemory(&storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY));
        storagePropertyQuery.PropertyId = StorageDeviceProperty;
        storagePropertyQuery.QueryType = PropertyStandardQuery;
  3. Call DeviceIoControl once for retrieving necessary size, then allocate the output buffer.
        // Get the necessary output buffer size
        STORAGE_DESCRIPTOR_HEADER storageDescriptorHeader = {0};
        DWORD dwBytesReturned = 0;
        if(! ::DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY,
            &storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY),
            &storageDescriptorHeader, sizeof(STORAGE_DESCRIPTOR_HEADER),
            &dwBytesReturned, NULL))
        {
            // handle error, do cleanup and return
        }
    
        // Alloc the output buffer
        const DWORD dwOutBufferSize = storageDescriptorHeader.Size;
        BYTE* pOutBuffer = new BYTE[dwOutBufferSize];
        ZeroMemory(pOutBuffer, dwOutBufferSize);
  4. Call DeviceIoControl twice to get the storage device descriptor. The output buffer points to a STORAGE_DEVICE_DESCRIPTOR structure, followed by additional info like vendor ID, product ID, serial number, and so on. The serial number is a null-terminated ASCII string located at SerialNumberOffset bytes counted form the beginning of the output buffer.
        // Get the storage device descriptor
        if(! ::DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY,
                &storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY),
                pOutBuffer, dwOutBufferSize,
                &dwBytesReturned, NULL))
        {
            // handle error, do cleanup and return
        }
    
        // Now, the output buffer points to a STORAGE_DEVICE_DESCRIPTOR structure
        // followed by additional info like vendor ID, product ID, serial number, and so on.
        STORAGE_DEVICE_DESCRIPTOR* pDeviceDescriptor = (STORAGE_DEVICE_DESCRIPTOR*)pOutBuffer;
        const DWORD dwSerialNumberOffset = pDeviceDescriptor->SerialNumberOffset;
        if(dwSerialNumberOffset != 0)
        {
            // Finally, get the serial number
            strSerialNumber = CString(pOutBuffer + dwSerialNumberOffset);
        }

Now let’s put all together in an MFC sample application which gets then displays the serial number for physical drive #0.

DWORD GetPhysicalDriveSerialNumber(UINT nDriveNumber IN, CString& strSerialNumber OUT)
{
    DWORD dwRet = NO_ERROR;
    strSerialNumber.Empty();

    // Format physical drive path (may be '\\.\PhysicalDrive0', '\\.\PhysicalDrive1' and so on).
    CString strDrivePath;
    strDrivePath.Format(_T("\\\\.\\PhysicalDrive%u"), nDriveNumber);

    // Get a handle to physical drive
    HANDLE hDevice = ::CreateFile(strDrivePath, 0, FILE_SHARE_READ|FILE_SHARE_WRITE,
        NULL, OPEN_EXISTING, 0, NULL);

    if(INVALID_HANDLE_VALUE == hDevice)
        return ::GetLastError();

    // Set the input data structure
    STORAGE_PROPERTY_QUERY storagePropertyQuery;
    ZeroMemory(&storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY));
    storagePropertyQuery.PropertyId = StorageDeviceProperty;
    storagePropertyQuery.QueryType = PropertyStandardQuery;

    // Get the necessary output buffer size
    STORAGE_DESCRIPTOR_HEADER storageDescriptorHeader = {0};
    DWORD dwBytesReturned = 0;
    if(! ::DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY,
        &storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY),
        &storageDescriptorHeader, sizeof(STORAGE_DESCRIPTOR_HEADER),
        &dwBytesReturned, NULL))
    {
        dwRet = ::GetLastError();
        ::CloseHandle(hDevice);
        return dwRet;
    }

    // Alloc the output buffer
    const DWORD dwOutBufferSize = storageDescriptorHeader.Size;
    BYTE* pOutBuffer = new BYTE[dwOutBufferSize];
    ZeroMemory(pOutBuffer, dwOutBufferSize);

    // Get the storage device descriptor
    if(! ::DeviceIoControl(hDevice, IOCTL_STORAGE_QUERY_PROPERTY,
            &storagePropertyQuery, sizeof(STORAGE_PROPERTY_QUERY),
            pOutBuffer, dwOutBufferSize,
            &dwBytesReturned, NULL))
    {
        dwRet = ::GetLastError();
        delete []pOutBuffer;
        ::CloseHandle(hDevice);
        return dwRet;
    }

    // Now, the output buffer points to a STORAGE_DEVICE_DESCRIPTOR structure
    // followed by additional info like vendor ID, product ID, serial number, and so on.
    STORAGE_DEVICE_DESCRIPTOR* pDeviceDescriptor = (STORAGE_DEVICE_DESCRIPTOR*)pOutBuffer;
    const DWORD dwSerialNumberOffset = pDeviceDescriptor->SerialNumberOffset;
    if(dwSerialNumberOffset != 0)
    {
        // Finally, get the serial number
        strSerialNumber = CString(pOutBuffer + dwSerialNumberOffset);
    }

    // Do cleanup and return
    delete []pOutBuffer;
    ::CloseHandle(hDevice);
    return dwRet;
}
void CPhysDrivesDialog::OnButtonGetDriveSerialNumber()
{
    UpdateData();

    DWORD dwRet = GetPhysicalDriveSerialNumber(m_nDriveNumber, m_strSerialNumber);
    if(NO_ERROR != dwRet)
    {
        CString strError;
        strError.Format(_T("GetPhysicalDriveSerialNumber failed. Error: %u"), dwRet);
        AfxMessageBox(strError);
    }

    UpdateData(FALSE);
}

Notes

  • The above example intentionally shows a “flat” global function, just for learning purpose. Of course, each one may improve it by using a more object-oriented approach.
  • May notice that STORAGE_DEVICE_DESCRIPTOR gets more info than serial number (device type, vendor ID, product ID, and so on). This is a subject of improving, as well.
  • Next article will describe how to get serial number using WMI (Windows Management Instrumentation).

See also

Resources

Listing Processes – Part 1: Introduction

Many users know about Task Manager that allows, between others, to quickly list the currently running processes and get info about each one. Some advanced users may also deal with Windows Resource Monitor, for real-time monitoring the running processes. Other ones may use command line tools lile Tasklist or Tlist as well as complex third-party applications, like Sysinternals Process Explorer and Process Monitor.

However, let’s say that we have to get a list of running processes and obtain additional info about them in our own application. This series of articles presents several methods for doing that in C/C++ programs, by using Windows API or other libraries. But first, let’s have a brief look at command-line tools.

Tasklist

Tasklist is a command-line utility tool, shipped with Windows XP (except home edition) and newer Windows versions. What you have to do is to open cmd console window and run tasklist.exe. Here are few examples:

  • Displays info about all processes in LIST format
    tasklist /v /fo LIST
  • Displays services running under svchost.exe
    tasklist /fi "Imagename eq svchost.exe" /svc
  • Displays the modules loaded in Internet Explorer
    tasklist /m /fi "imagename eq iexplore.exe"
  • Displays tasklist help.
    tasklist /?

For a complete reference, see the link under Resources section.

Tlist

Tlist isn’t shipped with Windows operating system but is included in Debugging Tools for Windows suite that can be downloaded from Microsoft site.
Examples:

  • Displays the command line that started each process
    C:\Program Files\Debugging Tools for Windows>tlist /c
  • Displays the services that run in each process
    C:\Program Files\Debugging Tools for Windows>tlist /s

Also, for a complete reference see the link, below.

Can we use these tools in our own program?

The answer is yes. We can launch tasklist.exe or tlist.exe with CreateProcess, then use a pipe to catch the output, then… But that may be a real overkill, so let’s forget it!
Next articles will show how to directly use APIs to list processes.

Resources