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)  
 }  

Monday, 28 November 2011

PowerShell functions, scripts and aliases

Monday 28/11/2011 – 8:00 PM    


 

While I have been using PowerShell for years now, I have only used it as a literal replacement for the command prompt without benefiting from the additional functionality that PowerShell brings. However, in the past year or so, I have been come to see the light and have been using PowerShell for doing things that are either difficult or impossible to do in the command prompt.


 

Here's a list of functions, scripts and aliases that I use on frequent or semi-frequent basis. I am very interested in knowing if they can benefit someone else. I also welcome suggestions on improving and expanding them.


 

S#

Item

Type

Commands

Description

Examples

1

mf

Function

function mf($path)

{

ni –type file $path

}

Make a file

mf d:\a.txt

2

uptime

Function

function uptime()

{

[string]::Format( "{0:0} Hours", [System.Environment]::TickCount/3600000)

}

Get number of hours since machine was last restarted

uptime

3

fs

Function

function fs()

{

    $disks = Get-WmiObject -ComputerName "." -Class Win32_LogicalDisk

    

    foreach ($disk in $disks)

    {

        if ($disk.DriveType -eq 3)

        {

            [string]::Format("Drive Letter: {0:N0}", $disk.DeviceID)

            [string]::Format("Volume Name: {0:N0}", $disk.VolumeName)

            [string]::Format("Total Size: {0:N0} MB", $disk.Size/(1024*1024))

            [string]::Format("Free Space: {0:N0} MB", $disk.FreeSpace/(1024*1024))

            echo ""

        }

    }

}

Display the Drive Letter, Volume Name, Total Size and Free Space of all hard drive partitions

fs

Thursday, 24 November 2011

Working with Mercurial and CodePlex

Thursday 24/11/2011 – 3:49 PM

There is an MSDN article on using TortoiseHg with CodePlex but it doesn't say anything about working with Hg via command line.

Saturday, 12 November 2011

Saving Custom Aliases in Notepad++ using NppExec

NPPExec has a rather convulated way of doing this. Here's what you need to do:
  1. Start by creating a text file for the commands. I named mine 'NppStartScript.txt' and saved it in the 'scripts' folder. Here's what the folder structure should look like:$(NPP_DIRECTORY)\scripts\NppStartScript.txt where $(NPP_DIRECTORY) represents your Notepad++ application folder.
  2. Locate the 'NppExec.ini' file. It should be in the '$(NPP_DIRECTORY)\plugins\config' folder. Open the file, add the following configuration setting at the bottom of the file (separated from the rest of the settings with a line break) and save the file:
    [Options]
    ScriptNppStart=$(NPP_DIRECTORY)\scripts\NppStartScript.txt
  3. Create the 'NppStartScript.txt', type your command(s) and save the file. In your case it would be:npe_cmdalias alias = Full command
  4. "Full command" should be a npp_exec directive to a file which will contain the commands that you want to execute repeatedly. Here's what my run python command looks like: npe_cmdalias rpy = npp_exec "$(NPP_DIRECTORY)\scripts\rpy.txt"
  5. The "rpy.txt" contains the command to invoke the python interpreter on the file that is open in the current Notepad++ tab. Here's what I have written: D:\Portable Apps\Python\python.exe "$(FULL_CURRENT_PATH)"
  6. Restart Notepad++, open the NPPExec console and type your alias. It should run the command that you specified