Arise, Sir O.

I'm happy to say that the powers that be in Microsoft have deemed me MVP worthy - I am now an official Microsoft Most Valued Professional in Windows Server Admin Frameworks for 2008, more specifically for my open source work and public support of Microsoft's most excellent object-oriented interactive shell, PowerShell during the last 18 months.

MVP_Horizontal_BlueOnly

I've just returned from an extended computational absence, so hopefully I can get back to churning out code and the odd blog post.

My PowerPoint slides from PowerShell Virtual User Group #2

In case anyone is interested, here are my slides in PowerPoint PPTX format from the most recent PS Virtual User Group. It covers the new Path handling infrastructure for PscxCmdlets in the upcoming PowerShell Community Extensions 1.2 and some brief information on my PowerShell Eventing snap-in for PowerShell.

psvug2_oisin_grehan.zip (152 KB)

(updated to a zip: it appears DasBlog will not serve pptx files?)

Why AppDomains are not a Magic Bullet

As knowledge of PowerShell increases for those new to .NET, there comes a point when people start to notice some shortcomings of the Assembly loading/unloading mechanisms of the 2.0 CLR. Namely, once you load an assembly into PowerShell to use it, you can't unload it again. The only way to remove it from memory is to restart PowerShell. Eventually, you might read something about how Assemblies can be loaded into AppDomains, and AppDomains themselves can be unloaded. This is true, but for the most part it is not much use in PowerShell unless the Types in question where specifically designed with this in mind. For those of you who understand enough of what I'm talking about to get this far without going "huh?", the following script will demonstrate some of the issues at hand:

Before you run this script, please disable PowerTab or any other SnapIns that may load the WinForms assembly into the current AppDomain. In short, this script creates a Form object in a child AppDomain, examines the current AppDomain for the WinForms assembly. It then attempts to manipulate the Form and again examines the current AppDomain for the WinForms assembly.

  1. # full qualified display name to WinForms assembly   
  2. $assembly = "System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"  
  3.   
  4. function IsWinFormsLoaded() {   
  5.     $loaded = [appdomain]::currentdomain.getassemblies()   
  6.     $winforms = $loaded | ? { $_.fullname -like "system.windows*" }   
  7.     return ($winforms -ne $null)       
  8. }   
  9.   
  10. if (-not (IsWinFormsLoaded)) {   
  11.     "Creating child AppDomain..."  
  12.     $child = [appdomain]::Createdomain("child",$null,$null)   
  13.   
  14.     # create a remote instance of a WinForms Form in a child AppDomain   
  15.     "Creating remote WinForms Form in child AppDomain... "  
  16.     $handle = $child.CreateInstance($assembly"System.Windows.Forms.Form")   
  17.   
  18.     # examine returned ObjectHandle   
  19.     "Returned object is a {0}" -f $handle.GetType()   
  20.     $handle | gm # dump methods   
  21.   
  22.     # Did WinForms get pulled into our AppDomain?   
  23.     "Is Windows Forms loaded in this AppDomain? {0}" -f (IsWinFormsLoaded)   
  24.   
  25.     # attempt to manipulate remote object, so unwrap   
  26.     "Unwrapping, examining methods..."  
  27.     $form = $handle.Unwrap()   
  28.     $form | gm | select -first 10   
  29.   
  30.     # is Windows Forms loaded now?   
  31.     "Is Windows Forms loaded in this AppDomain? {0}" -f (IsWinFormsLoaded)   
  32.   
  33. else {   
  34.     write-warning "System.Windows.Forms is already loaded. Please disable PowerTab or other SnapIns that may load System.Windows.Forms and restart PowerShell."  
  35. }  

Hopefully this will clear up any outstanding questions. I'll post more information about this later, or possibly add to this post.

appdomains.ps1 (1.33 KB)

Foreground/Background Swappable Downloads in PowerShell

Here's another interesting use for my PowerShell Eventing Snap-In, where I'm simulating unix-style foreground/background tasks. In this case, the task is a large download using System.Net.WebClient. Just dot-source the script below and start a download using the Start-Download function, passing the url to the large file and the local path where to save your file (be sure to fully qualify the save path). The download will start immediately and show a progress bar with bytes remaining and a percentage, however you can hit Ctrl+C at any time and the download will continue in the background. You can get back to PowerShell tasks, and bring back up the progress bar by invoking Show-Progress at any time. Use Stop-Download to cancel the currently active download. Only one download can be active at a time, but this could easily be extended to support a pool of downloads (using multiple WebClient objects).

downloads.ps1 (1.85 KB)

  1. #requires -pssnapin pseventing  
  2.  
  3. function Stop-Download {  
  4.     $wc.CancelAsync()  
  5. }  
  6.  
  7. function Start-Download {  
  8.     param(  
  9.         [uri]$Url = $(throw "need Url."),  
  10.         [string]$Path = $(throw "Need save path.")  
  11.     )  
  12.  
  13.     # initialise webclient  
  14.     if (-not $global:wc) {  
  15.         $global:wc = New-Object System.Net.WebClient  
  16.         $var = get-variable wc  
  17.         Connect-EventListener -Variable $var `  
  18.             -EventName DownloadProgressChanged, DownloadFileCompleted         
  19.     }  
  20.  
  21.     if ($wc.IsBusy) {  
  22.         Write-Warning "Currently busy: Please cancel current download or wait until it is completed." 
  23.         return 
  24.     }  
  25.       
  26.     $wc.DownloadFileAsync($url, $path, $path) # last is userstate  
  27.     Write-Host "Download started. Ctrl+C to continue in background." 
  28.     Show-Progress  
  29. }  
  30.  
  31. function Show-Progress {  
  32.     if (-not $wc.IsBusy) {  
  33.         # possibly already completed, clear queue  
  34.         get-event | Out-Null 
  35.         "No active download." 
  36.         return 
  37.     }  
  38.       
  39.     Start-KeyHandler -CaptureCtrlC  
  40.       
  41.     trap [exception] {  
  42.         Stop-KeyHandler  
  43.         Write-Warning "error" 
  44.         $_ 
  45.         return 
  46.     }  
  47.       
  48.     $exiting = $false 
  49.       
  50.       
  51.     while (-not $exiting) {       
  52.         $events = @(Get-Event -Wait)          
  53.         $event = $null;  
  54.           
  55.         # skip to last event  
  56.         foreach ($event in $events) {  
  57.             if ($event.Name -eq "CtrlC") {  
  58.                 break;  
  59.             }  
  60.         }         
  61.           
  62.         switch ($event.Name) {  
  63.             "DownloadProgressChanged" {  
  64.                 $r = $event.args.BytesReceived  
  65.                 $t = $event.args.TotalBytesToReceive  
  66.                 $activity = "Downloading $($event.args.userstate)" 
  67.                 $p = [math]::Ceiling(([double]$r / $t) * 100)  
  68.                 Write-Progress -Activity $activity -PercentComplete $p `  
  69.                     -Status ("{0} of {1} byte(s)" -f $r,$t)  
  70.             }  
  71.             "DownloadFileCompleted" {  
  72.                 Write-Progress -Activity DownloadComplete -Completed  
  73.                 "Complete." 
  74.                 $exiting = $true 
  75.             }  
  76.             "CtrlC" {  
  77.                 "Switching to background downloading. Show-Progress to move to foreground." 
  78.                 $exiting = $true 
  79.             }  
  80.         }         
  81.     }  
  82.     Stop-KeyHandler  

PowerShell Patterns #2

Hmmm... that old typographical oddity "the quick brown fox jumps over the lazy dog," does it really use all letters from "A" to "Z?"

  1. [char]"a" .. [char]"z" | ? { "the quick brown fox jumps over the lazy dog".getenumerator() -contains $_ } | % { [char]$_

I guess it's true then.

Using Windows Forms Controls in PowerShell #1: ListBox

Using Windows Forms controls in PowerShell is a tricky thing, because it only supports very simple event listboxhandling. However, some time ago I wrote a Snap-In to allow anyone to work with .NET events, even asynchronous ones. You can download it from my PSEventing CodePlex project; it comes with full help via the normal PowerShell mechanisms of -? and get-help. Examples are also available online on how to use the project for more complex scenarios. Here's simple demonstration of autogenerating a listbox control filled with choices given as arguments to a simple script:

  1. # as simple as 1,2,3 :-)  
  2. $choice = .\get-choice.ps1 "one","two","three" 

And here is the source to the get-choice.ps1 script itself. This requires PSEventing 1.0 or 1.1 Beta.

  1. #requires -pssnapin pseventing  
  2.  
  3. # http://www.codeplex.com/pseventing (1.0 or 1.1)  
  4.  
  5. param([string[]]$choices = $(throw "please supply a string array of choices"))  
  6.  
  7. if ($choices.length -eq 0) {  
  8.     Write-Warning "cannot be a zero length array." 
  9.     return 
  10. }  
  11.  
  12. # initialize form  
  13. $form = new-object windows.forms.form  
  14. $form.Text = "Choose..." 
  15. $form.MinimizeBox = $false 
  16. $form.MaximizeBox = $false 
  17. $form.AutoSize = $true 
  18. $form.AutoSizeMode = "GrowAndShrink" 
  19.  
  20. # initialize listbox  
  21. $listbox = new-object windows.forms.listbox  
  22. $choices | % { [void]$listbox.items.add($_) }  
  23.  
  24. $form.controls.Add($listbox)  
  25.  
  26. # catch a choice in the listbox (remove -verbose for quiet mode)  
  27. Connect-EventListener -VariableName listbox -EventName SelectedIndexChanged -Verbose  
  28.  
  29. # catch someone closing the form (remove -verbose for quiet mode)  
  30. Connect-EventListener -VariableName form -EventName Closed -Verbose  
  31.  
  32. $form.Show()  
  33.  
  34. # wait for an event while performing sendmessage pumping (or ctrl+c to exit)  
  35. $event = Get-Event -Wait  
  36.  
  37. # don't pollute pipeline (remove in production)  
  38. write-host ($event | ft -auto | out-string)  
  39.  
  40. $form.Dispose()  
  41.  
  42. $choice = $listbox.SelectedItem  
  43.  
  44. # clean up; event listeners will be automatically unhooked ;-)  
  45. $form = $null 
  46. $listbox = $null 
  47.  
  48. # return chosen item, or $null if the form was closed  
  49. if ($event.Name -eq "SelectedIndexChanged") {  
  50.     $choice 
  51. } else {  
  52.     $null # cancelled  
Have fun!

PowerShell Patterns #1

It's very easy sometimes to look at the PowerShell grammar and partition it into two distinct styles: pipeline and traditional imperative script. In fact, it took a number of weeks of playing around before I realised that this is leaving out a large portion of patterns that employ a fusion of these two styles. For example, Let's iterate over an array of numbers, skipping null elements; first, using the pipeline style:

  1. $arr = @(1,2,3,$null,5)  
  2.  
  3. $arr | where-object { $_ -ne $null } | foreach-object { $_

Then, using a traditional VBScript style, using the foreach keyword, as opposed to the foreach-object cmdlet:

  1. $arr = @(1,2,3,$null,5)  
  2.  
  3. foreach ($elem in $arr) {  
  4.     if ($elem -ne $null) {  
  5.         $elem 
  6.     }  

And finally, a fusion of the two styles whereby I insert a pipeline into the expression, and also take advantage of the fact that $null will evaluate to $false, thereby skipping the need to test with the -eq operator (update: Keith Hill reminded me that both 0 and "" will also evaluate to $false, so I added an explicit -ne $null):

  1. $arr = @(1,2,3,$null,5)  
  2.  
  3. foreach ($elem in $arr | where-object {$_ -ne $null} ) {  
  4.     $elem 

As Mr. Snover is fond of saying, this is a great example of PowerShell's pithiness. PithyShell at its best!

Import-Excel companion to Import-CSV

Well, this is not exactly rocket science, but it helped me out today with some spreadsheets tomfoolery. It generates PSCustomObjects with properties named after the header row in the Excel sheet (you need a header row for this to work), just like Import-Csv. The parameters should be self-explanatory: filename and worksheet.

param (
    [
string]$filename = $(throw "need a filename, e.g. c:\temp\test.xls"),
   
[string]$worksheet
)

if (-not (Test-Path $filename)) {
    throw "Path '$filename' does not exist."
    exit
}

if (-not $worksheet) {
   
Write-Warning "Defaulting to Sheet1 in workbook."
   
$worksheet = "Sheet1"
}

# resolve relative paths
$filename = Resolve-Path $filename

# assume header row (HDR=YES)
$connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data" +
    "
Source=${filename};Extended Properties=`"Excel 8.0;HDR=YES`"";

$connection = New-Object data.OleDb.OleDbConnection $connectionString;
$connection.Open();
$command = New-Object data.OleDb.OleDbCommand "select * from [$worksheet`$]"

$command.connection = $connection
$reader
= $command.ExecuteReader("CloseConnection")

if ($reader.HasRows) {
   
# cache field names
   
$fields = @()
   
$count = $reader.FieldCount

    for ($i = 0; $i -lt $count; $i++) {
        $fields += $reader.GetName($i)
    }

    while ($reader.read()) {

        trap [exception] {
            Write-Warning "Error building row."
            break;
        }

        # needs to match field count
        $values = New-Object object[] $count

        # cache row values
        $reader.GetValues($values)

        $row = New-Object psobject
        $fields | foreach-object -begin {$i = 0} -process {
            $row | Add-Member -MemberType noteproperty -Name $fields[$i
                -Value $values[$i]; $i++
        }
        $row # emit row
    }
}

 

New blog on PowershellCommunity.org

It's been a really busy few weeks in work, but I'm finally starting to find some time to put together some weightier posts on PowerShell. By this stage, everyone is pretty good at hacking together Cmdlets, and to be honest, Cmdlets are where it's at for most applications. However, I think the provider system is pretty powerful and deserves a chance to become a more equal partner to its Cmdlet brethren. The bewildering array of methods and base classes at your disposal is a little intimidating, and let's be honest, not everyone's provider ideas can be compared to an Access Database provider model.

My new blog is reachable via http://oisin.powershell.com/ (thanks Klumsy!) and is a redirect to my spot on the new community site, http://www.powershellcommunity.org/ . The first post will cover PowerShell paths in all their guises.

 

Non CLS-Compliant Types in PowerShell

Someone on the newsgroup ran into an issue whereby a instance of a non CLS Compliant type was fed to PowerShell's default formatter. The type in question had two properties with the same name, but in different casing. I'm pretty much just going to carbon-copy the qestion and answer here as I feel this is worth preserving in the annals of blogdom.

> I am in the process of writing a script to document SharePoint
> installations.  When I try to get information about the content database
> I get this error.

> "out-lineoutput : The field/property: "Id" for type:
> "Microsoft.SharePoint.Administration.SPContentDatabase" differs only in
> case from the field/property: "ID". Failed to use non CLS compliant type."

> Here is some of the PowerShell Script that I am using

> [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
> $site = New-Object Microsoft.SharePoint.SPSite("http://localhost")
> $site.ContentDatabase

> It is the final line that is giving me the error.  I can get other
> properties without error.  Can someone enlighten me on this error
> message and let me know if there is a way to work around it.

> Thanks
> Jerry

Hi Jerry,

This is a tricky one for people very new to powershell without any real experience of the .NET framework. One of the rules of a so-called "CLS compliant type" (cls = common lanaguage specification) is that you should not expose any public members that only differ in casing. The SPContentDatabase type has two properties, "Id" and "ID." This is perfectly legal in C# since case in important in that language and the compiler sees them as different as chalk and cheese. However, in VB.NET, case is not important and as such it [vb] has no native mechanism to differentiate between the two. The same goes for PowerShell's grammar. So, how do we get around this?

First, you grab a handle to the method directly by asking for it from the Type itself:

PS> $idMethod1 = [Microsoft.SharePoint.Administration.SPContentDatabase].getmethod("get_ID")

and for the second version,

PS> $idMethod2 = [Microsoft.SharePoint.Administration.SPContentDatabase].getmethod("get_Id")

Note the differing case for get_Id and get_ID. Btw, Properties like ID in .NET are actually served by special accessor methods prefixed with get_ and set_, hence the prefixes. Next, you need a way to invoke those methods on the instance itself: this is done by using the Invoke method on the MethodInfo object representing the method itself. You pass the instance to the Invoke method telling it that the method is "instance" (e.g. not static/shared) and "public" and it will call that method for you on the instance, returning the value, if any.

PS> $result = $idMethod1.Invoke($site.ContentDatabase, "instance,public", $null, $null, $null)

I know from the SharePoint documenation, that this property returns a guid. Also, due to another oversight, Posh does not know how to format Guids, so you must call ToString() on the result to get the value back or you'll be greeted by a blank line from powershell's formatter.

PS> $result.ToString()
1ade149d-2049-4668-b555-4b7be9374c65

Bizarrely, these two properties return the same guid, but hey, I'll let someone from the sharepoint team answer that one. Anyway, you should have enough information here to extract the properties that you need.

So, that's the interesting portion out of the way. Another way to do this might be to create a custom view for the SPContentDatabase type omitting the duplicated property and use the Update-FormatData cmdlet to activate it. This may not work though, as the non-cls compliancy issue may rear it's ugly head before posh's formatter gets this far.

Hope this helps,

- Oisin / x0n.

About the author

Irish, PowerShell MVP, .NET/ASP.NET/SharePoint Developer, Budding Architect. Developer. Montrealer. Opinionated. Montreal, Quebec.

Month List

Page List