Saturday, 17 December 2011

Downloading files with PowerShell

I have created various small scripts and programs to downloading files from the Internet mostly using PHP/cURL and .NET. However, it turns out the easiest way to do this is with PowerShell so I rewrote my .NET application. Here's what the simple version looks like it looks like:

 function getfile($url, $filename)  
 {  
   $wc = New-Object System.Net.WebClient  
   try  
   {  
     $wc.DownloadFile($url, $filename)  
   }  
   catch [System.Net.WebException]  
   {  
     Write-Host("Cannot download $url")  
   }   
   finally  
   {    
     $wc.Dispose()  
   }  
 }  
To download a file, just specify the URL from which to download and the target location on disk and then call the function like this:

 $url = "http://msdn.microsoft.com/en-us/library/windows/desktop/aa973757(v=vs.85).aspx"  
 $filename = "d:\msdn.htm"  
 getfile $url $filename  
The function is quite straightforward. A WebClient object is instantiated and it's DownloadFile method called with the source URL and target path. However, this will cause the function to block and freeze PowerShell console till the file is downloaded. There won't be any kind of download progress notification either. In my original application, I called the DownloadFileAsync method and tracked the progress of the download by handling the DownloadProgressChanged and DownloadFileCompleted events. The same can be accomplished in PowerShell in fewer (albeit more convoluted) lines of code. Here's the asynchronous version of getfile:

 function getfile($url, $filename)  
 {  
   $wc = New-Object System.Net.WebClient  
   Register-ObjectEvent -InputObject $wc -EventName DownloadProgressChanged -SourceIdentifier WebClient.DownloadProgressChanged -Action { Write-Progress -Activity "Downloading: $($EventArgs.ProgressPercentage)% Completed" -Status $url -PercentComplete $EventArgs.ProgressPercentage; }    
   Register-ObjectEvent -InputObject $wc -EventName DownloadFileCompleted -SourceIdentifier WebClient.DownloadFileComplete -Action { Write-Host "Download Complete - $filename"; Unregister-Event -SourceIdentifier WebClient.DownloadProgressChanged; Unregister-Event -SourceIdentifier WebClient.DownloadFileComplete; }  
   try  
   {  
     $wc.DownloadFileAsync($url, $filename)  
   }  
   catch [System.Net.WebException]  
   {  
     Write-Host("Cannot download $url")  
   }   
   finally  
   {    
     $wc.Dispose()  
   }  
 }  
The call to DownloadFile has been replaced by DownloadFileAsync. The real difference is in how events are handled. PowerShell registers events individually through the Register-ObjectEvent command. The arguments used in this example are:

  • InputObject: the source object that raises the events, in this case $wc, the WebClient instance
  • EventName: the name of the event, I have used the DownloadProgressChanged and DownloadFileCompleted
  • SourceIdentifier: a unique identifier for a particular event registration or subscription. In this example, the SourceIdentifier is used to unregister the events
  • Action: a semi-colon delimited list of PowerShell commands that are executed when the event is raised. The Action for DownloadProgressChanged shows a text based progress bar as well as the percentage of download completed using the Write-Progress command. The Action for DownloadFileCompleted completed shows a download complete message and then unregisters both events.
Here's what the function looks like in action:








Friday, 9 December 2011

Getting system uptime with PowerShell

Finding out how long your system has been powered on (minus the time it was hibernating) shouldn't be difficult, and it isn't. All I had to was call the TickCount property of the System.Environment .Net class which returned the number of milliseconds since the last restart. In PowerShell, this would be:

 [System.Environment]::TickCount  
I divided the TickCount by 3600000 to get the number of hours since last boot-up, enclosed the line in a function and added it my functions.psm1 file:
 function uptime()  
 {  
   [System.Environment]::TickCount/3600000  
 }  

Everything was fine for about 25 days of continuous operation. After that, every time I typed 'uptime', I would get a negative value. Although it was immediately clear that 25 days translated to about 2 billion milliseconds which is more then long-type TickCount could hold, it brought me to square one. The solution to the problem I had thought solved had to be redesigned.

Before I go any further, for those who don't want to know how I came about it and what it does, here's the final solution:

 function uptime()  
 {    
   $pc = New-Object System.Diagnostics.PerformanceCounter("System", "System Up Time")  
   $upSeconds = 0.0  
   $totalSleepTime = 0  
   $sleepFor = 100  
   do  
   {  
     $upSeconds = $pc.NextValue()  
     $totalSleepTime = $totalSleepTime + $sleepFor  
     sleep -m $sleepFor  
   }  
   while(($upSeconds -eq 0) -or ($totalTime -eq 1000))  
   $pc.Close()  
   $upMinutes = [Math]::Floor($upSeconds / 60)  
   $upHours = [Math]::Floor($upMinutes / 60)  
   $remainingMinutes = $upMinutes % 60  
   [string]::Format("{0:0} Hours {1:0} Minutes", $upHours, $remainingMinutes)  
 }