July 2016

Volume 31 Number 7

[Modern Apps]

Build a Wi-Fi Scanner in the UWP

By Frank La

Wi-Fi has over the last decade or so become ubiquitous. Many shops and cafes offer free Wi-Fi to customers for their convenience. Virtually all hotels offer some kind of wireless Internet to their guests. Most of us have wireless networks at home. As few tablets and mobile devices have Ethernet jacks, Wi-Fi has become integral to our modern lives. Beyond that, we rarely give it much thought.

So, questions abound. What about the sheer volume of Wi-Fi networks around us? How many are there? Are they secured? What channel are they on? What are they named? Can we map them? What can we learn from Wi-Fi network metadata?

While walking my dogs recently, I happened to glance at my phone’s Wi-Fi network connection screen and noticed some witty network names. This got me to wonder how many others had chosen to be comical versus practical. Then, I had the idea to map out and scan wireless networks in and around my neighborhood. If I could automate the process, I could even scan and map wireless networks during my commute to work. Ideally, I could have a program running on a Raspberry Pi that would periodically scan wirelessly and record that data to a Web service. This certainly would be more practical than glancing at my phone intermittently.

As it turns out, the Universal Windows Platform (UWP) provides rich access to wireless network data via classes in the Windows.Devices.WiFi namespace. As you know, a UWP app can run not only on phones and PCs, but on Raspberry Pi 2 running Windows 10 IoT Core. Now, I had all I needed to build out my project.

In this column, I’ll explore the basics of scanning Wi-Fi networks using the APIs built right into the UWP.

Windows.Devices.WiFi Namespace

The classes inside the Windows.Devices.WiFi namespace contain everything needed to scan and explore wireless adapters and wireless networks within range. After creating a new UWP project in Visual Studio, add a new class called WifiScanner and add the following property:

public WiFiAdapter WiFiAdapter { get; private set; }

Because it’s possible to have multiple Wi-Fi adapters on a given system, you must pick the Wi-Fi adapter you want to use. The InitializeFirstAdapter method gets the first one enumerated in the system, as shown in Figure 1.

Figure 1 Find the First Wi-Fi Adapter Connected to the System and Initialize It

private async Task InitializeFirstAdapter()
{
  var access = await WiFiAdapter.RequestAccessAsync();
  if (access != WiFiAccessStatus.Allowed)
  {
    throw new Exception("WiFiAccessStatus not allowed");
  }
  else
  {
    var wifiAdapterResults =
      await DeviceInformation.FindAllAsync(WiFiAdapter.GetDeviceSelector());
  if (wifiAdapterResults.Count >= 1)
    {
      this.WiFiAdapter =
        await WiFiAdapter.FromIdAsync(wifiAdapterResults[0].Id);
    }
    else
    {
      throw new Exception("WiFi Adapter not found.");
    }
  }
}

Adding the Wi-Fi Capability

You might notice that there’s a check for access to the Wi-Fi and that the code throws an exception if the RequestAccessAsync method returns false. This is because the app needs to have a device capability to let it scan and connect to Wi-Fi networks. This capability isn’t listed in the Capabilities tab in the manifest properties editor. To add this capability, right-click on the Package.appxmanager file and choose View Code.

You’ll now see the raw XML of the Package.appxmanager file. Inside the Capabilities node, add the following code:

<DeviceCapability Name="wifiControl" />

Now save the file. Your app now has permission to access the Wi-Fi APIs.

Exploring Wireless Networks

With the code to identify a Wi-Fi adapter to work with and permission to access it, the next step is to actually scan for networks. Fortunately, the code to do that is fairly simple; it’s just a call to the ScanAsync method on the WifiAdapter object. Add the following method to the WifiScanner class:

public async Task ScanForNetworks()
{
  if (this.WiFiAdapter != null)
  {
    await this.WiFiAdapter.ScanAsync();
  }
  }

Once ScanAsync runs, the NetworkReport property of the WifiAdapter gets populated. NetworkReport is an instance of WiFiNetworkReport, which contains AvailableNetworks, a List<WiFiAvailableNetwork>. The WiFiAvailableNework object contains numerous data points about a given network. You can find the Service Set Identifier (SSID), signal strength, encryption method and access point uptime, among other data points, all without connecting to the network.

Iterating through the available networks is quite easy: You create a Plain Old CLR Object (POCO) to contain some of the data from the WiFiAvailableNetwork objects, as seen in the following code:

foreach (var availableNetwork in report.AvailableNetworks)
{
  WiFiSignal wifiSignal = new WiFiSignal()
  {
    MacAddress = availableNetwork.Bssid,
    Ssid = availableNetwork.Ssid,
    SignalBars = availableNetwork.SignalBars,
    ChannelCenterFrequencyInKilohertz =
      availableNetwork.ChannelCenterFrequencyInKilohertz,
    NetworkKind = availableNetwork.NetworkKind.ToString(),
    PhysicalKind = availableNetwork.PhyKind.ToString()
  };
}

Building the UI

While I intend for the app to run without a UI in the final project, it’s useful for development and troubleshooting to see the networks within range and the metadata associated with them. It’s also useful for developers who might not have a Raspberry Pi at the moment, but still want to follow along. As shown in Figure 2, the XAML for the project is straightforward and there’s a multiline TextBox to store the output of the scan.

Figure 2 The XAML for the UI

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
  <Grid.RowDefinitions>
    <RowDefinition Height="60"/>
      <RowDefinition Height="60"/>
      <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
  <TextBlock FontSize="36" Grid.RowSpan="2" >WiFi Scanner</TextBlock>
  <StackPanel Name="spButtons" Grid.Row="1" Orientation="Horizontal">
    <Button Name="btnScan" Click="btnScan_Click" Grid.Row="1">Scan For
      Networks</Button>
  </StackPanel>
  <TextBox Name="txbReport" TextWrapping="Wrap" AcceptsReturn="True"
    Grid.Row="2"></TextBox>
  </Grid>
</Page>

Capturing Location Data

In order to provide additional value, each scan of the wireless network should also note the location of the scan. This will make it possible to provide interesting insights and data visualizations later. Fortunately, adding location to UWP apps is simple. You will, however, need to add the Location capability to your app. You can do that by double-clicking on the Package.appxmanifest file in Solution Explorer, clicking on the Capabilities tab, and checking the Location checkbox in the Capabilities list.

The following code will retrieve the location using the APIs built into the UWP:

Geolocator geolocator = new Geolocator();
Geoposition position = await geolocator.GetGeopositionAsync();

Now that you have a location, you’ll want to store the location data. Following is the WiFiPointData class, which stores location data along with information about networks found at the location:

public class WiFiPointData
{
  public DateTimeOffset TimeStamp { get; set; }
  public double Latitude { get; set; }
  public double Longitude { get; set; }
  public double Accuracy { get; set; }
  public List<WiFiSignal> WiFiSignals { get; set; }
  public WiFiPointData()
  {
    this.WiFiSignals = new List<WiFiSignal>();
  }
}

At this time, it’s important to note that unless your device has a GPS device, then the app requires a Wi-Fi connection to the Internet in order to resolve location. Without an onboard GPS sensor, you’ll have to have a mobile hotspot and make sure that your laptop or Raspberry Pi 2 is connected to it. This also means that the reported location will be less accurate. For more information on best practices for creating location-aware UWP, please refer to the Windows Dev Center article, “Guidelines for Location-Aware Apps,” at bit.ly/1P0St0C.

Scanning Repeatedly

For a scanning-and-mapping-while-driving scenario, the app needs to periodically scan for Wi-Fi networks. To accomplish this, you’ll need to use a DispatchTimer to scan for Wi-Fi networks at regular intervals. If you’re not familiar with how DispatchTimer works, please refer to the documentation at bit.ly/1WPMFcp.

It’s important to note that a Wi-Fi scan can take up to several seconds, depending on your system. The following code sets up a DispatchTimer to fire an event every 10 seconds, more than enough time for even the slowest system:

DispatcherTimer timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 10);
timer.Tick += Timer_Tick;
timer.Start();

Every 10 seconds, the timer will run the code in the Timer_Tick method. The following code scans for Wi-Fi networks and then appends the results to the TextBox in the UI:

private async void Timer_Tick(object sender, object e)
{
  StringBuilder networkInfo = await RunWifiScan();
  this.txbReport.Text = this.txbReport.Text + networkInfo.ToString();
}

Reporting Scan Results

As mentioned previously, once the ScanAsync method is called, the results of the scan are stored in a List<WiFiAvailableNetwork>. All it takes to get to those results is to iterate through the list. The code in Figure 3 does just that and places the results into an instance of the WiFiPointData class.

Figure 3 Code to Iterate Through All the Networks Found During a Scan

foreach (var availableNetwork in report.AvailableNetworks)
{
  WiFiSignal wifiSignal = new WiFiSignal()
  {
    MacAddress = availableNetwork.Bssid,
    Ssid = availableNetwork.Ssid,
    SignalBars = availableNetwork.SignalBars,
    NetworkKind = availableNetwork.NetworkKind.ToString(),
    PhysicalKind = availableNetwork.PhyKind.ToString(),
    Encryption = availableNetwork.SecuritySettings.NetworkEncryptionType.ToString()
  };
  wifiPoint.WiFiSignals.Add(wifiSignal);
  }

In order to make the UI simple while still providing for rich data analysis, you can convert the WiFiPointData to a Comma Separated Value (CSV) format and set the text of the TextBox in the UI. CSV is a relatively simple format that can be imported into Excel and Power BI for analysis. The code to convert WiFiPointData is shown in Figure 4.

Figure 4 Converting WiFiPointData

private StringBuilder CreateCsvReport(WiFiPointData wifiPoint)
{
  StringBuilder networkInfo = new StringBuilder();
  networkInfo.AppendLine("MAC,SSID,SignalBars,Type,Lat,Long,Accuracy,Encryption");
  foreach (var wifiSignal in wifiPoint.WiFiSignals)
  {
    networkInfo.Append($"{wifiSignal.MacAddress},");
    networkInfo.Append($"{wifiSignal.Ssid},");
    networkInfo.Append($"{wifiSignal.SignalBars},");
    networkInfo.Append($"{wifiSignal.NetworkKind},");
    networkInfo.Append($"{wifiPoint.Latitude},");
    networkInfo.Append($"{wifiPoint.Longitude},");
    networkInfo.Append($"{wifiPoint.Accuracy},");
    networkInfo.Append($"{wifiSignal.Encryption}");
    networkInfo.AppendLine();
  }
  return networkInfo;
}

Visualizing the Data

Naturally, I couldn’t wait to set up my cloud service to display and visualize the data. Accordingly, I took the CSV data generated by the app and copied and pasted that into a text file. I then made sure to save the file with a .CSV extension. Next, I imported the data into Power BI Desktop. Power BI Desktop is a free download from powerbi.microsoft.com that makes it easy to visualize and explore data.

To import the data from the app, click on Get Data on the Power Bi Desktop splash screen.

On the following screen, choose CSV and then click Connect. In the file picker dialog, choose the CSV file with the data copied and pasted out of the app.

Once that loads, you’ll then see a list of fields on the right hand side of the screen. While a full tutorial on Power BI Desktop is beyond the scope of this article, it doesn’t take much skill to produce a visualization that shows the location of Wi-Fi networks, their SSIDs and the encryption protocols they employ, as shown in Figure 5.

Power BI Visualization of Data the Wi-Fi Scanner App Collected
Figure 5 Power BI Visualization of Data the Wi-Fi Scanner App Collected

Amazingly, about one-third of networks are completely unencrypted. While some of these are guest networks set up at various businesses, some are not.

Practical Applications

While the original intent was merely to measure the technical savvy and wit of my neighbors, this project has some rather interesting practical uses. The ability to easily and automatically map Wi-Fi signal strength and location has interesting applications. What could a city do if each city bus were outfitted with an IoT device with this app running on it? Cities could measure the prevalence of Wi-Fi networks and correlate that data with neighborhood income data. Elected officials could then make informed policy decisions based on that data. If a community provides public Wi-Fi across town or in certain areas, then the signal strength could be measured in real time without the added cost of sending technicians around. Cities could also determine where unsecured networks were prevalent and create targeted awareness programs to increase community cyber security.

On a smaller scale, the ability to quickly scan Wi-Fi network metadata comes in handy when setting up your own network. Many routers offer users the chance to modify the channel on which they broadcast. A great example of this is an app called “Wi-Fi Analyzer” (bit.ly/25ovZ0Q), which, among other things, displays the strength and frequency of nearby wireless networks. This comes in handy when setting up a Wi-Fi network in a new location.

Wrapping Up

Copying and pasting text data from the UI will not scale. Furthermore, if the goal is to run the app on an IoT device without any type of display, then the app needs to send data to the cloud without any UI. In my next month’s column, you’ll learn how to set up a cloud service to take in all this data. Additionally, you’ll learn how to deploy the solution to a Raspberry Pi 2 running Windows IoT Core.


Frank La Vigne is a technology evangelist on the Microsoft Technology and Civic Engagement team, where he helps users leverage technology in order to create a better community. He blogs regularly at FranksWorld.com and has a YouTube channel called Frank’s World TV (youtube.com/FranksWorldTV).

Thanks to the following technical experts for reviewing this article: Rachel Appel, Robert Bernstein and Jose Luis Manners


Discuss this article in the MSDN Magazine forum