Code Review with Git Patches and Outlook via PowerShell

In the spirit of “simplest thing that works,” my team has a rather low-fidelity approach to code reviews: patch files and e-mail. Nothing fancy, but we find it works rather well. It’s even easier thanks to git format-patch, which lets me easily generate a patch per commit, but I was never able to get send-email to work quite like I wanted. Instead, I whipped together a PowerShell script in a few minutes that does just the trick:

function patch ($ref = 'master..', $Message = '', [switch]$KeepFiles) {
  $patchPaths = $(git format-patch -C -o C:/Temp/Patches $ref)
  if($patchPaths) {
    $outlook = New-Object -ComObject Outlook.Application
    $mail = $outlook.CreateItem(0)
    [void]$mail.Recipients.Add('myteam@mycompany.com')
    $mail.Subject = "Review - $Message"
    $commits = $(git log -C --pretty=format:'%s' --reverse $ref) | foreach {  "<li>$_</li>" }
    $mail.HTMLBody = "<ol style=`"font: 11pt Calibri`">$commits</ol>"
    $patchPaths | foreach {
      [void]$mail.Attachments.Add($_)
      if(!$KeepFiles) { Remove-Item $_ }
    }
    $mail.Display()
  } else {
    Write-Warning 'Nothing to patch!'
  }
}

Usage:

Create patch of everything on current branch since master:

patch -m "Issue 123 - This is neat"

Create patch of last two commits, without message:

patch HEAD~2..

Create patch of everything except the current commit, with message:

patch master..HEAD~1 'Refactoring for Story 234'
Advertisement
Posted in Git, PowerShell. Tags: . Comments Off on Code Review with Git Patches and Outlook via PowerShell

Git-Achievements in PowerShell

Reading through Jason’s post on using Git-Achievements with msysGit, I couldn’t help but get it working with PowerShell. The result is a single PowerShell script added to my Git-Achievements repository, tagged here on the off chance I decided to upload my achievements.

To install posh-git-achievements…

  1. Fork my repository on GitHub (or if you have an existing repository, add me as a remote and pull)
  2. Clone your fork of the repository (into C:\Git\git-achievements, for this example)
  3. Open your PowerShell profile and add the following:
    Set-Alias git C:\Git\git-achievements\git-achievements.ps1
  4. “dot source” your profile to reload it in your current session (or just start a new session):
    . $PROFILE
  5. Check the install:
    git achievements --help

If all goes according to plan, this should unlock your first achievement.

Note that this will pass every git call through a few extra layers, including calls made for the posh-git prompt. But if you can tolerate the performance hit, it’s a rather fun way to expand your working knowledge of Git. Enjoy!

Posted in Git, PowerShell. Tags: . Comments Off on Git-Achievements in PowerShell

posh-git Release v0.2

I just tagged the v0.2 release of posh-git, which you can download here. This is the last release supporing msysgit 1.6.5 and 1.7.0. In this release…

PowerShell Module

Thanks to a contribution from David Muhundro, posh-git now exposes its functions through a module (.psm1). The module exposes a number of functions whose usage can be seen in the example profile.

Tab Expansion Updates

  • TortoiseGit commands: tgit <tab>
  • git-svn operations: git svn <tab>
  • Stash completion for git stash operations: show, apply, drop, pop, branch
  • Branch completion for git reset and git rebase
  • Completion of deleted files for git rm
  • For most commands, tab completion should now work if other command flags are in use. For example, git rebase -i <tab> works as expected.

Thanks to Jeremy Skinner and Mark Embling for their contributions to this release.

Next Steps

The most common complaint about posh-git is performance, which has already been addressed for the next release (available in my master branch). However, the fix requires taking a dependency on msysgit 1.7.1, which has not been officially released yet.  Still, it has been working fine for me.

Beyond that, we still need to address the first two items on my list from the last release…

  • Testing! I’d like to figure out a way to run some integration tests that verify a given repository state renders the expected prompt. If you have suggestions how to approach this, or know how other Git integration projects are tested, please let us know over at the Google Group.
  • Documentation! How to get started, what the project provides, etc.

If you have any other feature requests or find issues, please let us know.

Posted in PowerShell. Tags: , . Comments Off on posh-git Release v0.2

Elegant Inline Debug Tracing

As much fun as it is to step through code with a debugger, I usually prefer to use System.Diagnostics.Debug and Trace with DebugView to see what’s happening in realtime. This is particularly handy to track intermediate results in higher-order functions that you might not be able to step into. However, it’s not always convenient to insert debugging statements amongst the composed expressions of F#, PowerShell or LINQ.

An alternative first came to mind while working in F#:

let dbg x = System.Diagnostics.Debug.WriteLine(x |> sprintf "%A"); x

(Read |> as “as next parameter to”.) We can then use this function anywhere to peek at a value, perhaps an intermediate list in this trivial example:

let data = [1..10]
           |> List.filter (fun i -> i%3 = 0) |> dbg
           |> List.map (fun i -> i*i)

Indeed [3; 6; 9] are traced as multiples of three. Not a particularly convincing example, but it should be pretty easy to imagine a more complex algorithm for which unintrusive tracing would be useful.

This works pretty well with F#’s |> operator to push values forward, but what about C#? Given my posting history, it shouldn’t be hard to guess where I’m going with this…

Extension Methods

So if |> is “as next parameter to”, the . of an extension method call might read “as first parameter to”. So we can implement a roughly equivalent function (sans F#’s nice deep-print formatter "%A") like so:

    public static T Debug<T>(this T value)
    {
        Debug.WriteLine(value);
        return value;
    }

    public static T Dbg<T>(this T value, string category)
    {
        Debug.WriteLine(value, category);
        return value;
    }

I find the optional label handy to keep different traces separate. Looking again, there’s an overload that accepts a category, so we’ll use that instead. So why might this be useful? Maybe we want to log the value assigned within an object initializer:

var q = new SPQuery() {
  Query = GetMyQuery().Debug("Query")
};

Rather than store the query string to a temporary variable or retrieve the property after it’s been set, we can just trace the value inline. Or consider a LINQ example:

var items = from SPListItem item in list.GetItems(q)
            let url = new SPFieldUrlValue(item["URL"] as string)
            where url.Url.Debug("URL").StartsWith(baseUrl, StringComparison.OrdinalIgnoreCase)
            select new
            {
                Title = item.Title.Debug("Title"),
                Description = url.Description,
            };

Here we log all URLs that pass through, even the ones excluded from the result by the predicate. This would be much harder to implement efficiently without inline logging.

This technique works great for simple objects with a useful ToString(), but what about more complex objects? As has often been the answer lately, we can use higher-order functions:

    public static T Dbg<T, R>(this T value, Func<T, R> selector)
    {
        Debug.WriteLine(selector(value));
        return value;
    }

    public static T Dbg<T, R>(this T value, string category, Func<T, R> selector)
    {
        Debug.WriteLine(selector(value), category);
        return value;
    }

Now we can provide a delegate to trace whatever we want without affecting the object itself. For example, we can easily trace a row count for the DataView being returned:

public DataView GetResults()
{
    var myTable = GetDataTable();
    // Process data...
    return myTable.DefaultView.Dbg("Result Count", v => v.Count);
}

I could go on, but you get the idea.

PowerShell Filter

Finally, we can implement similar functionality in PowerShell using a filter with an optional scriptblock parameter:

filter Debug([scriptblock] $sb = { $_ })
{
  [Diagnostics.Debug]::WriteLine((& $sb))
  $_
}

PS > 1..3 | Debug { $_*2 } | %{ $_*$_ }
1
4
9

Which traces 2, 4, 6, as expected.

Update 4/19/2009: Changed functions to use category overloads. And another point to consider: if the value being traced could be null, selector should be designed accordingly to avoid NullReferenceException. There’s nothing worse than bugs introduced by tracing or logging.

SharePoint+PowerShell Leak Workarounds

Zach Rosenfield recently posted about an obscure memory leak issue when working with PowerShell and the SharePoint object model. You can read his post for details but in summary:

  1. PowerShell spawns a new thread for each pipeline (essentially any batch of code that runs together).
  2. SharePoint objects should not be used across multiple threads due to mechanics of the unmanaged heap.
  3. SharePoint objects used across multiple pipelines result in unmanaged heap leaks until PowerShell closes or you run out of memory.

This PowerShell behavior is easy enough to verify:

PS 1> function Get-ThreadId { [Threading.Thread]::CurrentThread.ManagedThreadId }
PS 2> set-alias gt Get-ThreadId
PS 3> gt
6
PS 4> gt
4

So we need to ensure that our SharePoint objects are allocated, used and disposed within the same thread. How can we do this? Zach offers two suggestions, but there are actually several options:

Single Line

PS 5> gt; gt; gt
10
10
10
PS 6> gt; `
>> gt; `
>> gt
>>
3
3
3

Script

PS 7> @"
>> gt
>> gt
>> "@ > gt.ps1
>>
PS 8> .\gt.ps1
8
8

Function

PS 9> function gt2 {
>> gt
>> gt
>> }
>>
PS 10> gt2
4
4

Inline Script Block

PS 11> &{
>> gt
>> gt
>> }
>>
7
7

My Using Function

PS 12> using Microsoft.SharePoint
PS 13> gt; using ($s = [Microsoft.SharePoint.SPSite] 'http://moss') {
>> gt
>> $s.Url
>> gt
>> }
>>
5
5
http://moss
5

Cmdlet + Wrapper Class

Gary Lapointe has a set of PowerShell cmdlets that use wrapper objects to hide SPSite/SPWeb objects unless you specifically ask for them.

PS 14> $spi = Get-SPSite-gl 'http://moss'
PS 15> gt; using ($s = $spi.SPBase) { $s.Url; gt }
8
http://moss
8

The severity of this issue depends on your environment and the kinds of scripts you’re running, but in memory-sensitive and production environments these are definitely some techniques to keep in mind.

PowerShell Get-Type: Simplified Generics

PowerShell doesn’t include any syntactic sugar for dealing with generic objects, so a number of people have posted code snippets online showing how to create generic lists, dictionaries, etc. Interestingly, nobody has pointed out that the real problem isn’t creating the objects—New-Object does that without issue (for the most part)—but simply that it’s tedious to build correct closed type references for generics. This post attempts to address this core problem.

If we leverage the $args array, a trivial solution is quickly discovered:

function TypeOf-Generic ([type] $base) {
  $base.MakeGenericType($args)
}

Which is quite simple to use:

$intList = New-Object (TypeOf-Generic System.Collections.Generic.List int)

However, a bit more code yields a more complete solution:

function global:Get-Type (
    $type = $(throw "Please specify a type")
    )
{
  trap [System.Management.Automation.RuntimeException] { throw ($_.Exception.Message) }

  if($args -and $args.Count -gt 0)
  {
    $types = $args[0] -as [type[]]
    if(-not $types) { $types = [type[]] $args }

    if($genericType = [type] ($type + '`' + $types.Count))
    {
      $genericType.MakeGenericType($types)
    }
  }
  else
  {
    [type] $type
  }
}

Trapping RuntimeException allows us to repackage failed casts without the full ErrorRecord. We get a bit of usage flexibility by accepting our type arguments in either a single array or as the full $args array. With this function, all of the following will return valid types:

$a = Get-Type ([System.Collections.ArrayList])
$b = Get-Type System.Collections.Hashtable
$c = Get-Type System.Collections.Generic.List int
$d = Get-Type System.Collections.Generic.Dictionary int,string
$e = Get-Type System.Collections.Generic.Dictionary int $d

Corresponding to:

System.Collections.ArrayList
System.Collections.Hashtable
System.Collections.Generic.List`1[System.Int32]
System.Collections.Generic.Dictionary`2[System.Int32,System.String]
System.Collections.Generic.Dictionary`2[System.Int32,System.Collections.Generic.Dictionary`2[System.Int32,System.String]]

Having delegated the responsibility of resolving type references, we can update Lee Holmes‘s New-GenericObject:

function New-GenericObject(
    $type = $(throw "Please specify a type"),
    [object[]] $typeParameters = $null,
    [object[]] $constructorParameters = @()
    )
{
    $closedType = (Get-Type $type $typeParameters)
    ,[Activator]::CreateInstance($closedType, $constructorParameters)
}

I’ve included a few extra tweaks:

  1. I replaced [string] $typeName with [object] $type and changed $typeParameters from [string[]] to [object[]], specifically to accept [type] values if they are provided.
  2. If $typeParameters is $null, I assume we were passed a closed type; if not, Get-Type fails accordingly.
  3. $constructorParameters needs a default value to get around this exception:

    Exception calling “CreateInstance” with “2” argument(s): “Ambiguous match found.”

Cross-Assembly Generics

A user on Stack Overflow found an interesting quirk when trying to instantiate a generic SortedDictionary in PowerShell. The problem is easy to reproduce:

PS 1> $sdt = Get-Type System.Collections.Generic.SortedDictionary string,string
PS 2> $sdt.FullName
System.Collections.Generic.SortedDictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToke
n=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
PS 3> $sd = New-Object $sdt
New-Object : Cannot find type [System.Collections.Generic.SortedDictionary`2[System.String,System.String]]: make sure t
he assembly containing this type is loaded.
At line:1 char:17
+ $sd = New-Object  <<<< $sdt

Yet the type works fine with New-GenericObject:

PS 4> $sd = New-GenericObject $sdt
PS 5> $sd.GetType().FullName
System.Collections.Generic.SortedDictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToke
n=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

As the answer on Stack Overflow points out, SortedDictionary and System.String live in different assemblies (System.dll and mscorlib.dll, respectively), which apparently confuses the PowerShell type converter. And because New-Object expects -typeName to be a string, it automatically casts our [type] object (which has the necessary assembly information) back to [string]:

PS 6> [string] $sdt
System.Collections.Generic.SortedDictionary`2[System.String,System.String]

If we pass in the type’s FullName instead, everything works as expected:

PS 7> $sd2 = New-Object $sdt.FullName
PS 8> $sd2.GetType().FullName
System.Collections.Generic.SortedDictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToke
n=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

Note that this behavior extends to all assemblies, not just within the System namespace:

PS 11> $l = New-Object (Get-Type System.Collections.Generic.List ([Microsoft.SharePoint.SPWeb]))
New-Object : Cannot find type [System.Collections.Generic.List`1[Microsoft.SharePoint.SPWeb]]: make sure the assembly c
ontaining this type is loaded.
At line:1 char:16
+ $l = New-Object  <<<< (Get-Type System.Collections.Generic.List ([Microsoft.SharePoint.SPWeb]))
PS 12> $l = New-Object (Get-Type System.Collections.Generic.List ([Microsoft.SharePoint.SPWeb])).FullName
PS 13> $l.GetType().FullName
System.Collections.Generic.List`1[[Microsoft.SharePoint.SPWeb, Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral,
 PublicKeyToken=71e9bce111e9429c]]

If someone on the PowerShell team reads this, it would be awesome if New-Object had a parameter set that accepted a [type] instead of a [string]. Thanks in advance!

PowerShell Coalesce and PowerShellASP Query String Parameters

I had fun initially with PowerShellASP, but its sealed classes prevented me from doing much useful with it in SharePoint. I really need to take a look at Andre de Cavaignac‘s PowerShellPages project, though the GPL license is a bit restrictive for my taste. Maybe I’ll just build my own…

Anyway, I actually had a practical use for PoShASP today, and in working with it I decided there needed to be a better way to handle QueryString values. The resulting function is fairly straight-forward, and supports typed and optional parameters:

<% QSParam w x,int,10 y,double z,zzz %>
w = <%= $w %>
x = <%= $x %>
y = <%= $y %>
z = <%= $z %>

With no query string, we get the following:

w =
x = 10
y =
z = zzz

However, if I use a query string like w=42&x=oops&y=3.14&z=pi, the valid values fall through:

w = 42
x = 10
y = 3.14
z = pi

Since ‘oops’ isn’t an [int], we get the default. But first…

PowerShell Coalesce

One of the most underused C# operators is the coalesce operator (??), which evaluates to the right-hand value if the left-hand value is null. PowerShell doesn’t include such an operator (to my knowledge), but we can certainly build a function to do the job. I found an implementation contributed anonymously in the comments here:

function Coalesce-Args {
    ([object[]]($args | ?{$_}) + ,$null)[0]
}

Which can be simplified to this:

function Coalesce-Args {
  (@($args | ?{$_}) + $null)[0]
}
Set-Alias ?? Coalesce-Args

The array subexpression @( ... ) always returns an array, to which we append $null in case the array is empty. Taking the first element yields the desired result. I also define an alias (??) for an easily remembered shorthand, though the usage is different from the binary C# operator. With our new function, my original QueryString pattern:

if( !($n = $Request.QueryString["n"] -as [int]) ) { $n = 10 }

Can be rewritten as:

$n = ?? ($Request.QueryString["n"] -as [int]) 10

QSParam Function

Now that we have Coalesce-Args, we can implement QSParam:

function QSParam {
  $QueryString = $Request.QueryString
  $args | ForEach-Object {
    $arg = @($_)
    $varName = $arg[0];
    $varType = $arg[1] -as [type];

    $qsValue = $QueryString[$varName]
    if ($varType -eq $null) {
      $varValue = Coalesce-Args $qsValue $arg[2] $arg[1]
    } else {
      $varValue = Coalesce-Args ($qsValue -as $varType) `
                                ($arg[2] -as $varType)
    }

    Set-Variable $varName $varValue -Scope script
  }
}

If  PowerShell can’t cast the second value as a type, we use it as a default value. If a third value is specified, that takes priority over the second value (which is how you can specify a default value that would otherwise be confused for a type: QSParam rhymesWithSquid,$null,guid). Finally, we assign the result to a script-scoped variable so we don’t have to dot source the function call.

And in case you don’t follow the PowerShell team blog (or missed the post), Channel 9’s Expert-to-Expert with Erik Meijer and Jeffrey Snover is definitely worth watching if you’re at all interested in the language internals and other geeky stuff. Jeffrey’s enthusiasm for the language is infectious.

PowerShell Dispose-All

With all the talk of Dispose safety in object model code, it’s easy to forget that those same objects need to be cleaned up in PowerShell as well. Sure, SharePoint will clean everything up when the thread ends, but my PowerShell doesn’t get closed all that often, leaving open SPRequests for extended periods of time. To combat that, I wrote a quick function that will clean up all my IDisposable variables for me:

function Dispose-All {
    Get-Variable -exclude Runspace |
        Where-Object {
            $_.Value -is [System.IDisposable]
        } |
        Foreach-Object {
            $_.Value.Dispose()
            Remove-Variable $_.Name
        }
}

For some reason, I don’t think it would be a good idea to Dispose() the current Runspace… (Edit: Apparently I define $Runspace in a long-forgotten part of my profile script, so don’t be worried if you don’t have that defined.)

Of course this isn’t foolproof, as variables can be overwritten and not all IDisposables will have been assigned to variables in the first place, but you could certainly adapt your shell behavior to make the most out of a catch-all cleanup like this.

Multi-Purpose PowerShell Using Function

It’s always bothered me that there isn’t a clean way to deal with IDisposables in PowerShell. It seems Adam Weigert came to the same conclusion and implemented a using function much like the statement found in C# and VB. Note that he also makes use of his implementation of PowerShell try..catch..finally, which is pretty slick. Meanwhile, I’m told Raymond Mitchell has his own using function that he uses to load assemblies, which certainly makes sense to me.

I figure the next evolution is to provide a generic using that covers all the bases:

function using {
    param (
        $inputObject = $(throw "The parameter -inputObject is required."),
        [ScriptBlock] $scriptBlock
    )

    if ($inputObject -is [string]) {
        if (Test-Path $inputObject) {
            [system.reflection.assembly]::LoadFrom($inputObject)
        } elseif($null -ne (
              new-object System.Reflection.AssemblyName($inputObject)
              ).GetPublicKeyToken()) {
            [system.reflection.assembly]::Load($inputObject)
        } else {
            [system.reflection.assembly]::LoadWithPartialName($inputObject)
        }
    } elseif ($inputObject -is [System.IDisposable] -and $scriptBlock -ne $null) {
        Try {
            &$scriptBlock
        } -Finally {
            if ($inputObject -ne $null) {
                $inputObject.Dispose()
            }
            Get-Variable -scope script |
                Where-Object {
                    [object]::ReferenceEquals($_.Value.PSBase, $inputObject.PSBase)
                } |
                Foreach-Object {
                    Remove-Variable $_.Name -scope script
                }
        }
    } else {
        $inputObject
    }
}

Some notes on the code:

  • If $inputObject is a string, I assume it’s an assembly reference…
    • If the string is a path, load as a path
    • Rather than parse the string, I figure the framework knows best; the presence of a PublicKeyToken means it’s probably a full assembly name
    • I considered adding support for this alternative to LoadWithPartialName, but I don’t feel like managing a global “assembly map”; the deprecated shortcut will have to do for now
  • If $inputObject is IDisposable and a script block was supplied…
    • Wrap script execution in Try..Finally to make sure we get to Dispose()
    • Here I disagree with Adam – if the PSObject’s Dispose method was overridden, we should assume it was done for good reason (more on this in a later post) and that the override will respect the object’s disposability.
    • After disposal, I thought it might be nice to take the variable out of scope like C#/VB. Using -scope script will look at variables in the scope where our function was called, and since we don’t know what $inputObject was named before it was passed in, I just compare references instead.
  • Otherwise just punt the object along in the pipeline

Usage

Loading assemblies is pretty straightforward:

using System.Windows.Forms
using 'System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
using C:\Dev\DisposeTest\bin\Debug\DisposeTest.dll

To test IDisposable handling, I use a simple disposable object which returns its hash code in ToString(), instantiated by a helper function:

function new-disp { new-object DisposeTest.Disposable }

To verify that variable scope is handled properly, we need two test scripts. gv is an alias for Get-Variable.

NestedTest.ps1

gv x
using ($x = new-disp) { gv x }
gv x

UsingTest.ps1

$x = 'X'
.\NestedTest.ps1
using ($y = new-disp) { gv y }
gv y

From the behavior in C#/VB, we expect that the object being ‘used’ will only be available within the scope of the script block. So when we enter NestedTest.ps1, we should see the $x remains ‘X’, inherited from the parent scope, both before and after the using statement. Similarly, we expect $y will not be accessible outside of the using block:

SharePoint Example

using Microsoft.SharePoint
using ($s = new-object Microsoft.SharePoint.SPSite('http://localhost/')) {
  $s.AllWebs | %{ using ($_) { $_ | select Title, Url } }
}
if($s -eq $null) { 'Success!' }

It’s not exceedingly friendly for interactive mode, particularly for tab completion, but it should aid script readability.

PowerShellASP Applied: JSON Factory

Now that we can use PowerShellASP with SharePoint, what can we do with it? Well without master page or web part support, we can’t easily integrate PoShASP content directly into the SharePoint experience. Fortunately, we can leverage PowerShell in other ways. An easy example is generating JSON, perhaps from the PowerShell standby Get-Process:

<% $Response.ContentType = "application/json; charset=utf-8" %>
<% $Response.Cache.SetCacheability([System.Web.HttpCacheability]'NoCache') %>
<% $Response.Cache.SetExpires([DateTime]::MinValue) %>
<% if( !($n = $Request.QueryString["n"] -as [int]) ) { $n = 10 } %>
{"processes":[
  <% [string]::Join(",`n  ",
  (get-process | sort ws -desc | select -first $n | foreach {
  "{'ID':$($_.ID),'Name':'$($_.Name)','WS':$($_.ws)}" })) %>
]}

After we set the JSON ContentType and cache policy, we try to fetch an “n” query parameter. If n is missing or not a number, we default to 10. Now we can start building our JSON object. To build our array, we use System.String‘s static Join method (the backtick is PowerShell’s escape character, so `n is a newline). Join‘s string[] parameter is provided by a PowerShell pipeline that fetches our system processes, sorts by working set size, selects the top $n and then iterates through those process objects, returning a string of JSON with our desired properties. Note also that PoShASP seems to require an empty line at the end of the file, otherwise “]}” is ignored. Our output will look something like this:

{"processes":[
  {'ID':1672,'Name':'sqlservr','WS':506028032},
  {'ID':3296,'Name':'devenv','WS':230555648},
  {'ID':4720,'Name':'w3wp','WS':181100544},
  {'ID':376,'Name':'services','WS':121995264},
  {'ID':3372,'Name':'w3wp','WS':102522880}
]}

Let’s save our JSON endpoint as %12%\TEMPLATE\LAYOUTS\Get-Process.json.ps1x, which we can test by visiting http://server/_layouts/Get-Process.json.ps1x?n=5. Now it’s AJAX time! Since not everyone can use the ASP.NET AJAX Extensions, we’ll just do it the old-fashioned way. Starting with a Content Editor Web Part on a page of your choice, set the source to the following:

<div id="d_procs">
<img src="/_layouts/images/GEARS_AN.GIF"
  alt="Loading..." align="center" />
</div>
<script>
function getXmlHttpRequestObject() {
 if (window.XMLHttpRequest) {
  return new XMLHttpRequest();
 } else if(window.ActiveXObject) {
  return new ActiveXObject("Microsoft.XMLHTTP");
 } else {
  document.getElementById('d_procs').innerHTML =
  'Cound not create XmlHttpRequest Object.';
 }
}

var r = getXmlHttpRequestObject();
var c = 0;
var mTimer;

function getProcs() {
 if (r.readyState == 4 || r.readyState == 0) {
  r.open("GET", '/_layouts/Get-Process.json.ps1x?n=5', true);
  r.onreadystatechange = handleReceiveProcs;
  r.send(null);
 }
}

function handleReceiveProcs() {
 if (r.readyState == 4) {
  var d = document.getElementById('d_procs');
  var response = eval("(" + r.responseText + ")");
  var p = response.processes;
  var t = "<table width=\"100%\">";
  t += "<tr><th>ID</th><th>Name</th><th>WS(MB)</th></tr>";
  for(i=0;i < p.length; i++) {
   t += '<tr><td>'+p[i]['ID']+'</td>';
   t += '<td>'+p[i]['Name']+'</td/>';
   t += '<td align=\"right\">';
   t += (p[i]['WS']/(1024*1024.0)).toFixed(2);
   t += '</td/></tr>';
  }
  t += "</table><p>Refresh count: "+(++c)+"</p>";
  d.innerHTML = t;
  mTimer = setTimeout('getProcs();', 3000); // 3-sec refresh
 }
}

_spBodyOnLoadFunctionNames.push('getProcs');
</script>

I haven’t put much effort into cleaning up the code, but it does the job (in FF2 and IE7, at least):

With PowerShell’s ease of development and specialized adapters, this is just the tip of the iceberg. What other real-time data would you find useful?