WiFi Scanning Part 1:Scanning on Windows

I’ve enjoyed my experiments with making my own WiFi based location system. I’ll be writing on it more, but before I do I wanted to turn some attention to the WiFi scanning itself. This is fairly easy to do on both Windows and Android. I won’t be discussing iOS because at the time of this writing, iOS doesn’t allow user applications to perform WiFi scanning (the devices do it themselves and support WiFi based location, but do not expose the lower level functionality to the developers). In this first post I discuss WiFi scanning on Windows.

WiFi Scanning on Windows

Windows in my opinion was the easiest system on which to perform the scanning. An application initiates the scan and the operating system takes care of most of the rest. Is the application tries to retrieve the information a bit later, it’s there. While some might be tempted to request a scan, add a delay, and then retrieve the results, don’t do this. There a number of reasons why, including you can’t really know how long the scan will actually take. Windows also allows a callback function to be registered to receive notifications on operations. It is better to register a callback to be notified when the WiFi scanning is complete. You can see the full source code for how to perform the scanning here. Most of the rest of this is an explanation of the code.

Wireless operations start with requesting a HANDLE that is used to track request and operations. The Windows function WlanOpenHandle() will return this handle. Hold onto it until your application is either closing or nolonger needs to perform wireless operations. When you are done with the HANDLE, release it with WlanCloseHandle().

Once you have your HANDLE, use it to register a notification callback with WlanRegisterNotification. When you want to unregister a callback, call this same function again passing NULL in place of the callback function.

    if (ERROR_SUCCESS == WlanOpenHandle(2, nullptr, &version, &context.wlanHandle))
        result = WlanRegisterNotification(context.wlanHandle, WLAN_NOTIFICATION_SOURCE_ACM, 
                                          TRUE, (WLAN_NOTIFICATION_CALLBACK)WlanNotificationCallback, 
                                          &context, NULL, NULL);
        // Other wireless operations go here.
         WlanRegisterNotification(context.wlanHandle, WLAN_NOTIFICATION_SOURCE_ACM, 
                                  TRUE, NULL, NULL, NULL, NULL);
         WlanCloseHandle(context.wlanHandle, NULL);

Enumerating the Wireless Adapters

I’ll talk in detail about the implementation of the callback function in a moment. A device could have 0 or more wireless adapters. We could request a wireless scan on each of the adapters. For my sample program, it will perform a scan on each adapter one at a time. We can get a list of all the wireless adapters in a single call. The function accepts the address of a variable that will hold a pointer to the returned data. The call to WLanEnumInterfaces takes care of allocating the memory for this information. When we are done with it, we need to deallocate the memory ourselves with a call wo WlanFreeMemory. Enumerating through the array, each element has a property named isState. If the state is equal to the constant wlan_interface_state_connected then the wireless adapter is connected to a network. I’m only scanning when an adapter is being used and connected to a network. My reasons for this is that I ended up using this in diagnostics of some connectivity problems on some remote machines and I was only interested in the adapters being used.

The actual scanning is performed in the call to WlanScan. After the call, I reset a Windows Event object (created earlier in the program, but unused until now) and then wait for the object to have a signaled state with the function WaitForSingleObject. If you are familiar with Windows synchronization objects, then take note this is how I am coordinating code in the main thread with the callback.

if (ERROR_SUCCESS == (result = WlanEnumInterfaces(context.wlanHandle, NULL, &interfaceList)))
    std::cout << "Host, BSSID, Access Point Name, Frequency, RSSI, Capabilities, Rateset, Host Timestamp, Timestamp, BSS Type" << std::endl;

    for (int i = 0; i < (int)interfaceList->dwNumberOfItems; i++)
        PWLAN_INTERFACE_INFO wirelessInterface;
        wirelessInterface = (WLAN_INTERFACE_INFO*)&interfaceList->InterfaceInfo[i];
        if (wirelessInterface->isState == wlan_interface_state_connected)
            context.interfaceGuid = wirelessInterface->InterfaceGuid;
             if (ERROR_SUCCESS != (result = WlanScan(context.wlanHandle, &context.interfaceGuid, NULL, NULL, NULL)))
                std::cout << "Scan failed" << std::endl;
                retVal = 1;
                WaitForSingleObject(context.scanCompleteEvent, INFINITE);

For those not familiar, the call to WaitForSingleObject will cause the code to block until some other thread calls SetEvent on the same object. The callback that I registered will call SetEvent after it has received and process the scan information. This frees the main code to continue its processing.

Receiving the Response

I’m primary interested in printing out some attributes about each access point that is found in a format that is CSV friendly. If the notification received is for a WLAN_NOTIFICATION_SOURCE_ACM event, then that means that the scan information is available. A call to WlanGetNetworkBssList returns the information in a structure in memory allocated for us. After we get done processing this information, we need to release the memory with WlanFreeMemory(). Most of what I do with the information is direct printing of the values. I do have a function to format the BSSID information as a colon delimited string of hexadecimal digits. Information on the capabilities for the access points is stored in bit fields, which I extract and print as string. After iterating through each item in the returned information and printing the comma delimited fields, I call SetEvent so that the main thread can continue executing.

void WlanNotificationCallback(PWLAN_NOTIFICATION_DATA notificationData, PVOID contextData)
    DWORD result;
    PWlanCallbackContext context = (PWlanCallbackContext)contextData;
    switch (notificationData->NotificationSource)

        result = WlanGetNetworkBssList(context->wlanHandle, &context->interfaceGuid,
            NULL /*&pConnectInfo->wlanAssociationAttributes.dot11Ssid */,
            TRUE, NULL, &pBssList);
        if (ERROR_SUCCESS == result)
            for (auto i = 0; i < pBssList->dwNumberOfItems; ++i)
                auto item = pBssList->wlanBssEntries[i];
                std::cout << context->ComputerName << ", ";
                std::cout << FormatBssid(item.dot11Bssid) << ", ";
                std::cout << item.dot11Ssid.ucSSID << ", ";
                std::cout << item.ulChCenterFrequency << ", ";
                std::cout << item.lRssi << ", ";
                std::cout << ((item.usCapabilityInformation & 0x01) ? "[+ESS]" : "[-ESS]");
                std::cout << ((item.usCapabilityInformation & 0x02) ? "[+IBSS]" : "[-IBSS]");
                std::cout << ((item.usCapabilityInformation & 0x04) ? "[+CF_Pollable]" : "[-CF_Pollable]");
                std::cout << ((item.usCapabilityInformation & 0x08) ? "[+CF_PollRequest]" : "[-CF_PollRequest]");
                std::cout << ((item.usCapabilityInformation & 0x10) ? "[+Privacy]" : "[-Privacy]");
                std::cout << ", ";
                for (int k = 0; k < item.wlanRateSet.uRateSetLength; ++k)
                    std::cout << "[" << item.wlanRateSet.usRateSet[k] << "]";
                std::cout << ", ";
                std::cout << item.ullHostTimestamp << ", " << item.ullTimestamp << ", ";
                switch (item.dot11BssType)
                case dot11_BSS_type_infrastructure: std::cout << "infastructure"; break;
                case dot11_BSS_type_independent: std::cout << "independend"; break;
                case dot11_BSS_type_any:std::cout << "any"; break;
                default: std::cout << "";

                std::cout << std::endl;



That’s everything that is needed to scan for WiFi information on Windows. If you would like to see the full source code for a console program that performs these steps, I have it posted on GitHub here.

The information is printed to standard output where it can be viewed. When I need to save it, I direct standard output to a file. Many utilities support this format. I’ve used Excel, Sheets, and SQL Server Bulk Insert for processing this information.

I’m working on an explanation for how to use the same functionality on Android. That will come to this space in a couple of weeks with working code being made available on GitHub.

Mastodon: @j2inet@masto.ai
Instagram: @j2inet
Facebook: @j2inet
YouTube: @j2inet
Telegram: j2inet
Twitter: @j2inet

Posts may contain products with affiliate links. When you make purchases using these links, we receive a small commission at no extra cost to you. Thank you for your support.

Win32/C++ Application Base


Win32 is necessary for development in many current technologies.  Some of my current projects utilize Win32/C++ applications.  Many of them use a similar pattern for starting the application.  Initializing the window that hosts these applications is not particularly interesting, but it is a foundation block for some of my applications that I will share in the future.

In Win32 UI programs there will be at least one function defined that is known as being of type WNDPROC.  A WNDPROC is a callback function.  It is the primary function through which Windows will communicate to an application various events.  When a user moves their mouse over an application WNDPROC is called.  When the user presses a key WNDPROC is called.  When an application is being notified to render its UI again WNDPROC is called.  The call signature for WNDPROC is shown below.

  _In_ HWND   hwnd,
  _In_ UINT   uMsg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam

The first argument in this function is a handle to the window that is the intended recipient of the message.  An application could have more than one window.  But an application with only one window will have the same value.  The second argument is a numerical value that identifies the event or message being sent.  The constants that define possible vales generally are prefixed by WM_ (meaning Windows Message).  Examples of such values include: WM_COMMAND, which is generally the result of a button being clicked; WM_PAINT, which tells the application to redraw its UI; WM_SIZE, meaning that the window has been resized; and WM_CLOSE, meaning that there was a request to close the window.

Because of the central position that the WNDPROC function plays in receiving notifications from the operating system, if all responses to the operating system were handled here the function would quickly grow big.  I have seen this done, and it can get to be a nightmare on shared projects.  One way to deal with this is to have the WNDPROC function act only as a method that routes these incoming messages to other functions that handle the method.  A simple way to implement this is with a switch statement where each case does a minimal amount of work before passing the message on to a function specifically made to handle the message.

That is the solution used in the projects I plan to share in the future.  I created an abstract base class named AppWindow that creates an application window; has a few methods for handling certain Windows messages; and has case statements for calling those methods in response to a Windows message.

Windows communicates these messages to an application through a message queue.  An application must retrieve the next message to begin processing it.  If an application does not retrieve any messages for a certain period of time, then Windows assumes the application is locked up or busy and may give you a prompt to either terminate or wait for the program.  To keep messages going through the queue an application must implement a message pump.  In a simple implementation of a message pump two functions are called in a loop.  GetMessage which receives a MSG struct containing the information on a message and DispatchMessage to have the message handed off to the application’s WNDPROC for processing. 

If there are no messages available, calling GetMessage will result in the application waiting until there is a message.  During this wait, the main thread of an application will not use any CPU bandwidth.  In some applications, such as games or other applications that are continuously performing UI updates, using GetMessage is undesirable since it can cause the UI thread to wait.  In such applications, PeekMessage can be used instead. With PeekMessage, unlike GetMessage, if there are no messages available PeekMessage will not block.  Its return value indicates whether or not a new message was available.  If there are no messages to be processed the application can perform other tasks.

An application may modify a message within the time that it was retrieved with PeekMessage or GetMessage and when it is passed off through DispatchMessage.  You will see the use of a method named TranslateMessage which despite its name, does not perform the modification of any messages.  Instead, it creates new messages in response to certain virtual key messages and adds these to the message queue.  For the programs that I present, the specifics of what it does are not important and I will not be modifying any messages in the message pump.

I do not use any of the traditional Win32 UI elements in the programs that I will be sharing (buttons, labels, text boxes, list boxes, etc.) but those elements could easily be created within the application.  If I were making a framework for such controls, I would probably make classes for each one.  My base class does have a CreateButton and CreateLabel method that are used for debug purposes.

The AppWindow.h file

This class is abstract and cannot be directly initialized.  So, there first must be a derived class.  The only methods that the derived class must absolutely implement are the constructor and the method GetWindowsClassName().  The constructor must accept a HINSTANCE argument and pass it to the AppWindow base class constructor. GetWindowClassName() must return a unique string.  This string is going to be used for registering the window class.

Full implementation of a derived window

To make an application that runs, all that is left to do is create a derived application window, call its Init() method (which is where it actually creates its Window) and call its message pump.


Running the application will result in a window showing a label and a button.  It does not do anything yet, which is what is desired.  The code samples to be posted in the future, require that there be an initialized window (which this provides).  The entirety of the code can be found on GitHub.

Relevant Github Commit

Latest Version