PneumaticTube Update

Last year around this time I created a little command-line application to upload files to Dropbox from Windows. Since then I've added a few features, so I figured it was time to post an update.

The biggest change is support for chunked uploading. Dropbox requires chunked uploading for files larger than 150MB, and even for smaller files it's a nice feature to have because you can get progress updates on your upload. This feature took a little work, because the original library PneumaticTube was based on, DropNet, didn't have any async support and its successor, DropNetRT didn't have any chunked uploading support. But after a little porting work, PneumaticTube can handle uploading large files just fine and now has a couple of different progress indicator options.

The other big feature change is the ability to upload a whole folder to Dropbox. This makes PT a much more convenient tool for backup operations. Right now it only supports the top level of a folder (no recursion), but that may change in the future.

There've also been several bug fixes. One of these bugs was somewhat surprising: the progress indicators during chunked uploading caused crashes under PowerShell ISE. I'm posting a little more about this bug because info about it was scarce on Google, and maybe this will help someone else who has a similar problem.

In order to show upload progress indicators in-place (rather than writing "X % Complete" on a new line over and over again), I use Console.SetCursorPosition to move the cursor back to the beginning of the line and overwrite the old value. It turns out that the PowerShell console in PS ISE isn't a real console; among the differences between the standard terminal and ISE is the fact that ISE doesn't support SetCursorPosition or other cursor operations. Calls to those methods will throw IOExceptions.

PneumaticTube now handles this case by trying to access Console.CursorTop in the constructor of each progress display implementation. If access to CursorTop throws an exception, the progress display classes stop trying to report progress using those methods. This keeps scripts which call PneumaticTube under ISE (or another console which doesn't support those cursor operations) from failing. I also added command line switches for disabling progress reporting entirely as a workaround for other possible console support issues.

If you want to give PneumaticTube a try, you can download it directly from the releases page or install it using chocolatey. Happy uploading!

An Even Better PowerShell Forecast

After posting the last update to my PowerShell weather script, I was looking at the sheer awkwardness of the pre-made SOAP request. Basically, in order to send a SOAP request to the web service, I just kept a ready-made SOAP envelope in a file alongside the script, prepped it with some simple search-and-replace for the parameters, and used Invoke-WebRequest to POST it.

Here's the old code:

$uri = "http://graphical.weather.gov/xml/SOAP_server/ndfdXMLserver.php"
$lat = "40.019444"
$lon = "-105.292778"
$start =  Get-Date -format "yyyy-MM-dd"

$body = Get-Content .\ndfd.soap
$body = $body.Replace("[lat]", $lat).Replace("[lon]", $lon).Replace("[start]", $start)

[xml]$envelope = Invoke-WebRequest $uri -Method post -ContentType "text/xml" -Body $body
[xml]$weather = $envelope.Envelope.Body.NDFDgenByDayResponse.dwmlByDayOut.'#text'

It's awkward and very lazy, but it works. Still, it was bothering me. After a little more research, it turns out that if you've got a WSDL, you can just generate a proxy straight out of PowerShell (much the same way you'd generate proxy classes to work with a SOAP service from Visual Studio). The cmdlet you need is New-WebServiceProxy (naturally). Here's the updated code using New-WebServiceProxy and dumping the pre-made SOAP request:

$uri = "http://graphical.weather.gov/xml/DWMLgen/wsdl/ndfdXML.wsdl"
$lat = "40.0269"
$lon = "-105.251"Po
$start =  Get-Date -format "yyyy-MM-dd"

$Proxy = New-WebServiceProxy -uri $URI -namespace WebServiceProxy

[xml]$weather = $Proxy.NDFDgenByDay($lat, $lon, $start, 7, "e", "Item24hourly")

Just generate the proxy and call the method. Notice that this also means I get to drop the awkward line of code which extracted the XML I cared about from the return SOAP envelope. Much cleaner.

A Better PowerShell Forecast

In my last post, I wrote about getting the forecast from the National Weather Service from the command line in PowerShell. But the final version of the script I left off with wasn't that great; it basically just wrote a bunch of strings out to the console, which isn't a very PowerShell-esque way to do things. PowerShell likes objects, and it especially likes when we can commands like Where-Object and Select-Object to do interesting things with them.

So, let's update that script a bit so we get objects with properties back, instead of just strings. While we're at it, we can fix a bug with the old version - it's only checking the high temperature value for nil, and the web service we're calling can return nil for any of these values.

Here's the old version of the output loop:

for($i = 0; $i -lt 7; $i++){
    if(-not $days[0].value[$i].nil) {
        ("High of " + $days[0].value[$i] + ", low of " + $days[1].value[$i] + ", " + $days[2].value[$i].'weather-summary')
    }
}

Here's the new version:

$startDate = (Get-Date)

for($i = 0; $i -lt 7; $i++){
    $dayOfWeek = (($startDate).AddDays($i).DayOfWeek).ToString()

    $high = $days[0].value[$i]
    if($high.nil) { $high = "[unknown]"}

    $low = $days[1].value[$i]
    if($low.nil) { $low = "[unknown]" }

    $condition = $days[2].value[$i].'weather-summary'
    if($condition.nil) { $condition = "[unknown]"}

    New-Object -TypeName PSObject -Property ([ordered]@{"Day" = $dayOfWeek; "High" = $high; "Low" = $low; "Conditions" = $condition})
}

Let's break that down a bit.

First, we're adding $dayOfWeek to so that we can label the days; it makes the output nicer and helps me when I forget which day of the week it is.

For each of the other values we want to return (the high, low, and conditions) we're checking to see if the XML value returned by the service was nil; if so, we're just going to set the value to 'unknown' and move on.

The last line is the new and interesting bit. PowerShell lets you create custom objects on the fly with the New-Object cmdlet. And one really convenient option for that cmdlet is -Property, which lets you pass in a hash which will defined the properties of the new object. So, by calling New-Object in a loop like this, the output of the script is a list of objects which all have the properties Day, High, Low, and Conditions.

Even better, PowerShell 3 supports ordered hashes (notice the ordered keyword at the front of the hash). This forces the hash (and the object generated from it) to preserve the order in which we declared the properties.

The upshot of this is that when we run the script, the output looks like this:

Day                          High                         Low                          Conditions
---                          ----                         ---                          ----------
Sunday                       57                           34                           Chance Rain Showers
Monday                       58                           36                           Mostly Sunny
Tuesday                      73                           39                           Mostly Sunny
Wednesday                    79                           44                           Mostly Sunny
Thursday                     70                           40                           Partly Sunny
Friday                       67                           41                           Partly Sunny
Saturday                     70                           42                           Partly Sunny

And since each of those is an object, we can now pipe the output through other cmdlets, which allows things like:

.\forecast.ps1 | Where-Object {$_.Low -gt 40}

This command is piping the output through Where-Object and only returning the days with a Low greater than 40 degrees:

Day                          High                         Low                          Conditions
---                          ----                         ---                          ----------
Wednesday                    79                           44                           Mostly Sunny
Friday                       67                           41                           Partly Sunny
Saturday                     70                           42                           Partly Sunny

Much more useful. For extra fun, try .\forecast.ps1 | Out-GridView. Once you turn your script outputs into objects, you can do nearly anything with them.