PowerShell: Measuring Download Speeds

Have you ever downloaded a file from the Internet? Probably.

Have you ever downloaded a file with PowerShell? Maybe.

Have you ever wondered how fast your download was going? Sure.

Have you ever wondered how to get that information when you’re downloading a file with PowerShell? Maybe, but you didn’t have a solution until now!

Our web browsers calculate download speeds for us, somehow. Specifically how, I have no clue, but what I do know is that we are more than capable of calculating download speeds using PowerShell.

Downloading a file with PowerShell is ridiculously easy. If you’re using PowerShell version 3.0, you can use the Invoke-WebRequest cmdlet to download a file. If you’re still running v2 though, or you’d just rather have a “proper” way to download a file using the .NET framework, you can use the System.Net.WebClient type. There is a very simple DownloadFile() method, with two parameters: 1) the URL to the file you’re downloading, and 2) the target path on the filesystem where the file will be stored. This method is synchronous, meaning that code execution is blocked until the method has completed, and returned a value (even if that value is [void]).

If we want to calculate the speed of a download, instead of simply downloading a file blindly (with no output), we’ll need to ensure that we can run some calculation code simultaneously with the code that’s actually downloading the file. We could write a separate program to do that, but linking that together with the download code would be too challenging to waste our time working with. Instead, we can use the WebClient.DownloadFileAsync() method to begin the file download asynchronously, and then continue executing code that will calculate the download speed until the download has completed.

Let’s take a look at how to do this:

function Measure-DownloadSpeed {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Please enter a URL to download.")]
        [string] $Url
        ,
        [Parameter(Mandatory = $true, HelpMessage = "Please enter a target path to download to.")]
        [string] $Path
    )

    function Get-ContentLength {
        [CmdletBinding()]
        param (
            [string] $Url
        )

        $Req = [System.Net.HttpWebRequest]::CreateHttp($Url);
        $Req.Method = 'HEAD';
        $Req.Proxy = $null;
        $Response = $Req.GetResponse();
        #Write-Output -InputObject $Response.ContentLength;
        Write-Output -InputObject $Response;
    }

    $FileSize = (Get-ContentLength -Url $Url).ContentLength;

    if (!$FileSize) {
    throw 'Download URL is invalid!';
    }

    # Resolve the fully qualified path to the target file on the filesystem
    # $Path = Resolve-Path -Path $Path;

    if (Test-Path -Path $Path) {
    # throw ('File already exists: {0}' -f $Path);
    }

    # Instantiate a System.Net.WebClient object
    $wc = New-Object System.Net.WebClient;

    # Invoke asynchronous download of the URL specified in the -Url parameter
    $wc.DownloadFileAsync($Url, $Path);

    # While the WebClient object is busy, continue calculating the download rate.
    # This could potentially be broken off into its own function, but hey there's procrastination for that.
    while ($wc.IsBusy) {
    # Get the current time & file size
    #$OldSize = (Get-Item -Path $TargetPath).Length;
    $OldSize = (New-Object -TypeName System.IO.FileInfo -ArgumentList $Path).Length;
    $OldTime = Get-Date;

    # Wait a second
    Start-Sleep -Seconds 1;

    # Get the new time & file size
    $NewSize = (New-Object -TypeName System.IO.FileInfo -ArgumentList $Path).Length;
    $NewTime = Get-Date;

    # Calculate time difference and file size.
    $SizeDiff = $NewSize - $OldSize;
    $TimeDiff = $NewTime - $OldTime;

    # Recalculate download rate based off of actual time difference since
    # we can't assume precisely 1 second time difference due to file IO.
    $UpdatedSize = $SizeDiff / $TimeDiff.TotalSeconds;

    # Write-Host -Object $TimeDiff.TotalSeconds, $SizeDiff, $UpdatedSize;

    Write-Host -Object ("Download speed is: {0:N2}MB/sec" -f ($UpdatedSize/1MB));

    }
}

Measure-DownloadSpeed -Url http://dl.google.com/android/installer_r20.0.1-windows.exe -Path ('{0}\{1}' -f $env:TEMP, 'installer_r20.0.1-windows.exe');