If “Scheme is Love” and “Lisp is Sin”, then PowerShell is “CLI Heaven”

I found Sriram Krishnan’s blog entry titled "Lisp is sin" an interesting read.  It got me thinking about how the Microsoft Command Shell (renamed to Windows PowerShell) fits into the dynamic scripting language world.  Now the PowerShell designers would probably tell you that it is first and foremost an interactive command line interface (CLI)l.  Yes it does this quite nicely however I think PowerShell  definitely has something to offer programmers in the dynamic scripting language space.  Admittedly I don’t know anything about Scheme and I’ve only dabbled in Lisp and Python.  I’m certainly not suggesting that PowerShell has all the power of these languages.  That said, the more I use the PowerShell scripting language the more I’m growing infatuated with it.  Here’s a few reasons why:
  1. Really convenient to experiment with .NET – there’s no need to create a VS solution/project and no compilation needed.  No more ConsoleApplication37!!
  2. Powerful – I get LINQ like features now for querying object collections (OK so no support for database queries yet)
  3. Simplified handling of arrays (array slicing, dynamically resizeable), regular expressions, hashtables and XML.
  4. Strong typing where I need it.
PowerShell (PS)  is also an interactive shell (the command prompt) which behaves similarly to the Lisp REPL (read-eval-print loop).  PS is based on .NET and easily interacts with .NET types.  These two features combine to make it a very easy way to experiment with .NET.  I wonder if MSH could be the "next Lisp" ahead of C# 3.0 and VB 9.0?  Anyway here are some of the MSH langauges features that I think are really cool.  BTW, all this code is shown as it would execute from the MSH command prompt (C:\>).
 
PS supports dynamic variables like so:
 
C:\> $a = 5.0
C:\> $a = "Hi"
 
However you can also strongly type your variables if you want to:
 
C:\> [double]$a = 5.0
C:\> $a = "Hi"
Cannot convert "Hi" to "System.Double". Error: "Input string was not in
a correct format."
At line:1 char:3
+ $a  <<<< = "Hi"
 
PS has first class numerics support:
 
C:\> 40*52*50.89
105851.2
> for ($i = 0; $i -le 2*[Math]::Pi; $i += ([Math]::Pi / 4.0)) {
>> "{0:F4}" -f [Math]::Sin($i)
>> }
>>
0.0000
0.7071
1.0000
0.7071
0.0000
-0.7071
-1.0000
-0.7071
0.0000
 
How about string processing?
 
C:\> "Hello World" -like "He*"
True
C:\> "Hello World" -replace "world","MSH"
Hello MSH
C:\> "Hello " + "World"
Hello World
C:\> "." * 3
# and of course you have access to all of the .NET string methods
C:\> "Hello World".Split(" ")
Hello
World
 
How about regular expressions?
 
C:\> $regex = "(?<areacode>\d{3})\s+(?<number>\d{3}-\d{4})"
C:\> "Phone: 970 555-1212" -match $regex
True
C:\> $matches
Key                            Value
—                            —–
number                         555-1212
areacode                       970
0                              970 555-1212
 
How’s the array support?  Not too bad at all:
 
C:\> $a = 1,2,3,4,5
C:\> $a
1
2
3
4
5
C:\> [string]$a[1..4]
2 3 4 5
C:\> [string]$a[0,2,4]
1 3 5
C:\> $b = 6,7,8,9,10
C:\> $a += $b
C:\> [string]$a
1 2 3 4 5 6 7 8 9 10
 
Need easy access to hashtables?  No sweat in PS:
 

C:\> $magicNumbers = @{ Pi=3.142; Phi=1.618; e=2.718 }
C:\> $magicNumbers.Pi
3.142

 

You like anonymous methods?  PS has scriptblocks.  Throwing around code like it is data isn’t a problem.

 
C:\> $code = { get-process Win* }
C:\> $code
get-process Win*
C:\> &$code
Handles  NPM(K)    PM(K)      WS(K) VS(M)   CPU(s)     Id ProcessName
——-  ——    —–      —– —–   ——     — ———–
    624      63     7788       7976    62   313.69   1044 winlogon
    141       5     2564       4156    37     3.59   9404 winlogon
    809      22    54744      39904   348   362.33   4652 WINWORD
 
Functions support optional parameters as well as Lisp style keyword and rest parameters.  For example:
C:\> function foo ([string]$label, [int]$width=20) {
>>       "{0,$width}" -f $label
>>       "rest args are $args"
>>   }
>>
C:\> foo -width 10 -label Name 1 2 3
      Name
rest args are 1 2 3
C:\> foo Name
                Name
rest args are
 
How about XML support?
 
C:\> $RssUrl= "http://spaces.msn.com/members/keithhill/feed.rss"
C:\> $blog = [xml](new-object System.Net.WebClient).DownloadString($RssUrl)
C:\> $blog.rss.channel.item[0].title
MSH:Whence Function
 
You .NET programmers out there, wouldn’t reflection at the command line be pretty cool?  Check it out:
 
C:\> [System.IO.File] | get-member -static
   TypeName: System.IO.File
Name                 MemberType Definition
—-                 ———- ———-
AppendAllText        Method     static System.Void AppendAllText(String path…
AppendText           Method     static System.IO.StreamWriter AppendText(Str…
Copy                 Method     static System.Void Copy(String sourceFileNam…
Create               Method     static System.IO.FileStream Create(String pa…
CreateText           Method     static System.IO.StreamWriter CreateText(Str…
Decrypt              Method     static System.Void Decrypt(String path)
Delete               Method     static System.Void Delete(String path)
Encrypt              Method     static System.Void Encrypt(String path)
Equals               Method     static System.Boolean Equals(Object objA, Ob…
<snip>
 
How about LINQ / SQL style functionality?  Check out PowerShell’s select-object, where-object, group-object and measure-object cmdlets:
 
# Count the number of blog posts in the feed for 2005
C:\>  $blog.rss.channel.item |
>>      where {([DateTime]$_.pubDate).Year -eq 2005} |
>>      measure-object
>>

Count    : 21
Average  :
Sum      :
Max      :
Min      :
Property :
 
and
 
# Show me all the versions of the MS CRT loaded and what processes
# are using them.
C:\>  get-process | select processname -expand Modules -ea SilentlyContinue |
>>      where {$_.ModuleName -like "msvc*.dll"} |
>>      group {$_.ModuleName} | format-list
 
 
Name   : MSVCP71.dll
Count  : 12
Group  : {CCAPP, CCEVTMGR, CCSETMGR, EDICT, iexplore, msimn, msn, NAVAPSVC,
         NPFMNTOR, SPBBCSvc, WeatherDataClient, WINWORD}
Values : {MSVCP71.dll}
<snip>
 
That’s just an inkling of what’s in the PowerShell language.  There are the typical looping constructs like for, foreach, do, while.  There are conditional constructs like if and switch.  BTW the switch is quite powerful allowing you to switch on regular expressions.  And there’s support for multiple variable scopes (global, script, local and private).  For more info on PowerShell, check out the Getting Started Guide at:
 
 
Of course PowerShell isn’t perfect and could benefit from a few more capabilities IMO.  🙂  Here’s what I think it needs to round it out and perhaps help it take off like Python and Ruby:
 
1. Support for "by ref" parameters – this is a somewhat major problem since there are a number of handy .NET methods you just can’t call (<primitive numeric>.TryParse for example).  Updated: Fixed in release candidate 1.
 
2. A keyword for loading assemblies based on either a display name, fully qualified name or a path.  You can do this today but it is a bit verbose – [System.Reflection.Assembly]::LoadWithPartialName("System.Net").  The following would be preferable IMHO:
 
load System.Net
load "Microsoft.TeamFoundation.Client, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
load C:\Test\MyAssembly.dll
 
3. Need a way to import types from a specified namespaces as well as allow us to specify namespace aliases e.g.:
 
using System.Net
using System.Runtime.Serialization.Formatters.Binary
using Tfc=Microsoft.TeamFoundation.Client
 
4. More array slicing features.  The range operator is nice but I want to be able to reference with last index without having to find the array size e.g.:
 
$a = 1,2,3,4,5
[string]$a[..-1]
1 2 3 4
[string]$a[2..]
3 4 5
 
5. A native syntax for hooking event handlers.  I would suggest using C#’s += and -= and since these already exist in the language you don’t have to add any more special operators.  BTW I would also like to be able to specify a function name when hooking an event instead of just a script block e.g.:
 
function ClickHandler (this, ea) { write-host "Button clicked" }
$b = new System.Windows.Forms.Button
$b += ClickHandler
 
6. A friendlier way to specify generic parameters e.g.:
 
$regexs = new-object Collections.Generic.Dictionary[string, regex]
 
7. Need a way to save out the shell state (current variables, functions, aliases, etc) and then easily reload it into another shell similar to the way you can save and reload a FASL file in Lisp.
 
8. A minor quibble but one of the advantages of a scripting language should be terseness and in this reqard I am missing C#’s tertiary (?:) and null coalescing (??) operators.  I would love to be able to do this in MSH:
 
$ProjDir = $env:ProjectRootDir ?? C:\Projects
 
9. Type browsing using get-member isn’t as easy to do as it should be.  For instance, if you can’t easily create an instance of an object you can’t view its instance members with this cmdlet.  The cmdlet is geared towards inspecting the object passed to it via the pipeline.  It seriously needs a -TypeName parameter so that you don’t need to create an instance object to pass to the cmdlet e.g.:
 
C:\> get-member -TypeName System.Environment
 
10. A way to define .NET types.  Right now PowerShell is a CLS consumer language and not an extender language.  I can understand why the team hasn’t put this in for V1 and may not ever put it in.  OTOH if they really want to the scripting language part of PS to take off, I think it needs this ability.  Then again like the team says, this product is first and foremost a command shell.
 
If you dig command line interfaces, PowerShell is a little slice of heaven for the power user in you.   🙂 
 
This entry was posted in PowerShell. Bookmark the permalink.

4 Responses to If “Scheme is Love” and “Lisp is Sin”, then PowerShell is “CLI Heaven”

  1. Unknown says:

    7. Need a way to save out the shell state (current variables, functions, aliases, etc) and then easily reload it into another shell similar to the way you can save and reload a FASL file in Lisp.——————————————————–Do you mean "export-console" and "msh -MSHConsoleFile" command?

  2. Keith says:

    Export-Console doesn\’t save out any new shell variables or functions so it wouldn\’t work for what I had in mind.

  3. Unknown says:

    Keith,
    I think you like this one 😉
     
    read this script with MSH in mind
     
    Forgive me, lambda, for I have sinned…
    http://bdonlan.livejournal.com/7058.html
     
    gr /\\/\\o\\/\\/

Leave a reply to Keith Cancel reply