Check the internet performance using Powershell?

But of course, you can!  I was tasked to create a script that measure the performance of the internet connection using Powershell. We handle the network for many clients, and this helps us measure and see that our clients' networks deliver the speed that is expected. This way we can notice if they have a problem even before they contact us.

Measure-Command

First, I made a function which measure how long it takes to download a 10MB file. This is because if a client has a slow uplink to internet, it's not a clever idea to choke their already limited speed with large test files.
I found a 10MB file on sunet.se, which is the university network in Sweden, where many clients are. Also, sunet have an exceptionally good, fast, and stable internet connection so I figure they are an excellent choice to test against.

Function Measure-NetworkSpeed{
    # The test file has to be a 10MB file for the math to work. If you want to change sizes, modify the math to match
    $TestFile  = 'https://ftp.sunet.se/mirror/parrotsec.org/parrot/misc/10MB.bin'
    $TempFile  = Join-Path -Path $env:TEMP -ChildPath 'testfile.tmp'
    $WebClient = New-Object Net.WebClient
    $TimeTaken = Measure-Command { $WebClient.DownloadFile($TestFile,$TempFile) } | Select-Object -ExpandProperty TotalSeconds
    $SpeedMbps = (10 / $TimeTaken) * 8
    $Message = "{0:N2} Mbit/sec" -f ($SpeedMbps)
    $Message
}

Measure-NetworkSpeed

As you can see, first I specify what to test against. In this case the 10MB.bin file. Which I put in $TestFile
Next, I specify $WebClient and that I want to use the object Net.WebClient class. Net.WebClient have many methods to use if you want to download stuff. If you want to see what's in the class, you can run $WebClient | Get-Member.
In the $TempFile I specify the path to the TEMP environment where I can download the test file to.
$TimeTaken is where I measure the totalt time it takes to download the test file into the temporary location. $SpeedMbps do some simple math to calculate the Mbps of the download. This is then put into $Message.
Since this is a function, after you loaded the code, you can call Measure-NetworkSpeed and get the result. I highly recommend trying to make your code as functions, as it's very convenient for reuse in other code. I don't always make functions either, as you'll soon see, but mostly I do.

This works well, it's a small file, so it won't drown the connection if you run this in reasonable timeframes. Like once every 15 minutes or so. If the client has a fast connection to the internet, you can run it more often. Remember what you are measuring, you are measuring the speed to the internet from the location where the test machine is placed, you probably don't care about the specific machine you are measuring from. So don't run this from too many locations on the same connection unless you have some specific reason to do so. Usually measuring from one single server is enough.

Here's a link to my GitHub repository for this code: JoakimNordin/Measure-NetworkSpeed (github.com)

Speedtest

The Measure-Command way of measuring the connection have one flaw, and that is that what most people use as sort of the gold standard to measure internet speeds is speedtest.net or similar service. Luckily, I found a fix for that too. Turns out that Ookla speedtest.net have a cli-version. It can be downloaded here: https://www.speedtest.net/nl/apps/cli.  It's a simple speedtest.exe which you can run and get the same functionality as their website. This method has two drawbacks, and one of them is a big one. The first drawback is that you must make sure you have the speedtest.exe, so it's not powershell native if that is important to you. Why it should be, well, maybe, you can't write to the Windows server you want to use or something, I can see where this can complicate things as opposed to just using the Measure-Command method. You can most likely overcome this drawback though.

The bigger drawback is that if you want to test often, and the fact is that speedtest.exe use a lot of data each measurement. So, with this one you must be even more careful, so you don't choke the internet connection. We use this one time per hour, as that's frequent enough to know if a clients' internet is doing fine or not. If you want to measure more often, I will highly recommend you use the Measure-Command method instead, and just live with the fact that the calculated speed is slower than the speedtest way. And to be honest, the Mbps is irrelevant. If you know your client is happy when the number is xxx Mbps, then you know that everything around that number is good. If they complain about sluggish internet when the speed is yyy Mbps, then it's safe to assume that number is when the connection isn't performing well.

We create graphs with the outputs of my scripts, so after running it for a few hours, you will be able to see what's normal and what isn't. Anyway, on to the code.

$Speedtest = & "C:\speedtest.exe" --format=json --accept-license --accept-gdpr
$Speedtest | Out-File "C:\Last.txt" -Force
$Speedtest = $Speedtest | ConvertFrom-Json
 
[PSCustomObject]$SpeedObject = @{
    downloadspeed = [math]::Round($Speedtest.download.bandwidth / 1000000 * 8, 2)
    uploadspeed   = [math]::Round($Speedtest.upload.bandwidth / 1000000 * 8, 2)
    packetloss    = [math]::Round($Speedtest.packetLoss)
    isp           = $Speedtest.isp
    ExternalIP    = $Speedtest.interface.externalIp
    InternalIP    = $Speedtest.interface.internalIp
    UsedServer    = $Speedtest.server.host
    URL           = $Speedtest.result.url
    Jitter        = [math]::Round($Speedtest.ping.jitter)
    Latency       = [math]::Round($Speedtest.ping.latency)
}

# I need the output in one file per desired measurement
$SpeedObject.downloadspeed | Out-File "C:\LastDownloadspeed.txt" -Force
$SpeedObject.uploadspeed | Out-File "C:\LastUploadspeed.txt" -Force
$SpeedObject.packetloss | Out-File "C:\LastPacketloss.txt" -Force
$SpeedObject.Jitter | Out-File "C:\LastJitter.txt" -Force
$SpeedObject.Latency | Out-File "C:\LastLatency.txt" -Force

In my case, to work around some limitations in our monitoring system, I must dump the data into text files. You don't have to do this, which means you can simplify this script a lot. Maybe you don't have a monitoring system. In which case you could just decide at what point do you consider your internet speed to be bad enough to warrant a warning, and just use some math on the download and or upload speed and send yourself a mail with a warning. If you are working for a company, I highly recommend you use a monitoring system that can draw graphs based on input numbers, such as Nagios for example, that way you can see internet performance over time. Anyway, I digress.

I start with creating $Speedtest which runs the speedtest.exe and make the output in json format and then create the text file Last.txt. I don't use this file, but I figured is nice to have in case something breaks. That way I can just see the output and hopefully catch the error. As I write this, I am thinking I should solve it by programming some error checking, but this time I'm too lazy to be honest.


After the file is created, I convert it from Json to an object.
I create the custom objekt $SpeedObject and populate it.
downloadspeed, upoloadspeed use some math to get Mbps in similar fashion as I did in the Measure-Command method, only difference is the number output differ in size.

The rest is self-explanatory. It's just checks in the $Speedtest and populate the object.
Here you could be done and do what you want with the data. In my case, like I mentioned earlier, I must have one text file per measurement I want to use, so I create a text file for each of the measurements the speedtest.exe run created.

And so, now you know two ways to measure internet speed using powershell. If you want to measure speeds within your internal network, you can use iperf in a similar fashion as the Speedtest.exe-method. That's something I might show in a future blogpost.

Here's a link to my GitHub repository for this code: JoakimNordin/Speedtest (github.com)

Joakim Nordin

Joakim Nordin