We looked at a lot of CLI models when designing PowerShell. One of them was netsh. One of the things I had a love/hate relationship with was netsh's use of context. In netsh, you can set context via navigation and then you don't need to provide that context on the command line. For instance you navigate into the FIREWALL and then you perform a set of operations and you don't need to say that you are working on the firewall because it picks it up from the context.
I thought I would experiment with this a little this morning. I don't know if this is useful or not but it shows a couple of interesting scripting techniques so I thought I would share it anyway. This is PUSH-NOUN.PS1. You can push a NOUN context and then you don't need to specify that noun for the first command in a command sequence - you just specify the verb. You type "?" for help, "exit" to exit and "! cmd" to escape and execute a command directly. The examples will make it more clear.
First the code:
# Push-Noun.ps1 # Sets a context which allows you to work on a noun similar to the way NETSH does # WORKS WITH V1 param($noun) while ($true) { Write-Host "[$Noun]> " -NoNewLine $line = $host.UI.ReadLine().trim() switch ($line) { "exit" {return} "quit" {return} "?" {Get-Command "*-$Noun" |ft Verb,Definition -Auto |out-host} {$_.StartsWith("!")} { $Cmd = $_.SubString(1) Invoke-Expression $cmd |out-host } default { $Verb,$args = $Line.Split() $Cmd = "$verb-$Noun $args" Invoke-Expression $cmd |out-host } } }
Now let's run it:
PS> .\push-noun service [service]> ?
Verb Definition ---- ---------- Get Get-Service [[-Name] <String[]>] [-ComputerName <String[]>] [-Include <String New New-Service [-Name] <String> [-BinaryPathName] <String> [-DisplayName <String Restart Restart-Service [-Name] <String[]> [-Force] [-PassThru] [-Include <String[]>] Resume Resume-Service [-Name] <String[]> [-PassThru] [-Include <String[]>] [-Exclude Set Set-Service [-Name] <String> [-DisplayName <String>] [-Description <String>] Start Start-Service [-Name] <String[]> [-PassThru] [-Include <String[]>] [-Exclude Stop Stop-Service [-Name] <String[]> [-Force] [-PassThru] [-Include <String[]>] Suspend Suspend-Service [-Name] <String[]> [-PassThru] [-Include <String[]>]
[service]> get a*
Status Name DisplayName ------ ---- ----------- Running AeLookupSvc Application Experience Running ALG Application Layer Gateway Service Stopped Appinfo Application Information Running AppMgmt Application Management Running AudioEndpointBu... Windows Audio Endpoint Builder Running Audiosrv Windows Audio
[service]> get |where {$_.name -match "^A" -AND $_.Status -eq "stopped"} Status Name DisplayName ------ ---- ----------- Stopped Appinfo Application Information
[service]> !gps *ss
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 673 8 2308 6636 70 6.75 492 csrss 539 7 16044 21460 190 149.98 540 csrss 1633 16 11040 17680 68 67.47 656 lsass 866 5 3976 5720 33 2.56 548 psxss 33 1 312 760 5 0.08 428 smss
[service]> exit PS>
Note that given the mechanisms we have, this only really works with the first command in the sequence. Image that you wanted to do something like:
PS> Get | where {$_.name -match $test} |STOP
Where STOP did not have to specify the NOUN. You really can't do that today. We have been thinking about exposing a TOKEN-NOT-RESOLVED event which would call a user-defined function which would allow you to do runtime fixups. If we had that mechanism in place, you could do this. Hmmmmmm.
Enjoy!
Jeffrey Snover [MSFT] Windows Management Partner Architect Visit the Windows PowerShell Team blog at: http://blogs.msdn.com/PowerShell Visit the Windows PowerShell ScriptCenter at: http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx