Powershell again. Pipes, and "This is not an output"
My experiences with PowerShell continue and every so often realize that I never really understood something I thought I'd "got" some while back
I've spent most of my time trying not to use the WRITE cmdlets to send output to the screen. After all if I want to write "Hello, World" to the screen I can have a function
function hw
{ 'Hello, World' }
No need for a Print, Write, Echo or any other command: in PowerShell an output with nowhere else to go ends up on the screen. If I want formatted output FORMAT-LIST and FORMAT-TABLE do a great job, especially since I "discovered" calculated fields. Since I always used to get my queries in Access to do loads of work, shoving the work onto FORMAT-TABLE seems natural enough, even if the syntax is little weird. The field is actually written as a hash table @{ label="Text", Expression={some code} }. Of course "Some code" can do anything you want and what it returns goes in the table. So I had a bright idea over the weekend. I can have a menu using format-table. Here the code
Function Choose-Process
{$global:Counter=-1
$proc=Get-Process
Format-Table -inputobject $Proc @{ Label = "ID"; Expression={($global:counter++) }} , processName
$proc[(read-host "Which one ?")]
So I set a counter, get a list of processes, and then format a table, in formatting the table I output and increment the counter; the I ask the user to input a number to choose one and that gives me the offset into the array of processes of the one to return. When I run it here's what I get:
PS C:\Users\Jamesone> choose-proc
ID ProcessName
-- -----------
0 audiodg
1 CcmExec
Which one ?: 1
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
744 29 13128 10560 92 2204 CcmExec
Of course I want to save this. So I type in $proc=choose-proc and I get
Which one ?:
What the ... ? Where did my menu go ? And the answer is: the menu was OUTPUT. Where did I tell PowerShell that format-table wasn't to go to standard output but $proc[offset] was ? I didn't so both ended up being stored in the result.
Anyone who's explored PowerShell's providers will have found there is one for variables. Now I get it ... $varName= MyFunction is actually MyFunction > Variable:varName. This behaviour can work for or against you depending on the situation, and here I have to use a for loop and use WRITE-HOST to force output to go the screen [Update: no I don't need to. See Jeffrey's comment below]
I guess I was still thinking in Basic, where the last line of your function usually returns the result. PowerShell's Outputs are different, they go down the "Standard Output" pipe unless told otherwise and standard output ends up on the screen if the "end of pipe" is left Open; just like you'd expect from a Shell. And since I was quoting my university lecturers a little while ago here's something which was written on the board in our first programming class.
It is practically impossible to teach good programming style to students that have had prior exposure to BASIC; as potential programmers they are mentally mutilated beyond hope of regeneration.
( Edsger Dijkstra )
Technorati tags: Microsoft, Powershell
Comments
Anonymous
January 01, 2003
I mentioned my weekend of PowerShell , and that I've been working on scripts for the Office CommunicationsAnonymous
January 01, 2003
I've been showing PowerShell on the roadshow, and Steve warning me about it becoming the "Look-how-clever-I-am-with-PowerShellAnonymous
January 01, 2003
[link] MarcoAnonymous
January 01, 2003
Let's face it, PowerShell does a bunch of magic for you. If we've done our job right, this magicAnonymous
January 01, 2003
Jeffrey, thanks for your comments and your kind words over on the Powershell team blog. Matt. The function would now go Function choose-process {$global:Counter=-1 $proc=Get-Process Format-Table -autosize -inputobject $Proc @{ Label = "ID"; Expression={($global:counter++) }} , processName | out-host $proc[ int[].Split(",")] } note: I've revised this comment to included Jeffery's multi-select - since I've just written something which actually uses it :-)Anonymous
January 01, 2003
Fantastic Hecks. thanks for sharing.Anonymous
August 29, 2007
I don't think you need a FOR loop to fix this, the OUT-HOST should suffice. Just do Format-Table <blah blah> | Out-Host ; And you'll be all set. By the way, when I ran this on my machine, I decided to add -AutoSize to the Format-Table - that is much nicer to deal with. Cheers! 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.mspxAnonymous
August 29, 2007
BTW - if you wanted to allow multiple processes to be chosen you could do this: $proc[ [int[]](Read-Host "Which ones? ").Split(",")] See if you like that. 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.mspxAnonymous
August 29, 2007
This is really cool! I've been wanting something like this for a while, but just haven't thought it out yet. After reviewing all the comments & having more time to think about it, do you have a "better version" written now that you can share with us? Thanks!Anonymous
September 04, 2007
I have a set of such functions that basically do the equivalent of SHIFT/CTRL selecting from a list with a mouse. The trick works with just about any Powershell output really. Here's one for selecting directory items:
Pick-ChildItem.ps1
param ($path, [array]$index, [switch]$force) if ($force) {$switch = "-force"} $global:items = (invoke-expression "get-childitem '$path*' $switch" | sort-object @{expression={$.PSIsContainer}; Descending=$true}, @{expression={$.Name}; Ascending=$true}) if ($index -ne $null) { $index | %{$} | %{$items[$ - 1]} } else {$items | %{$i=1}{$; $i++} | format-table @{label="ID"; expression={$i}}, Name, @{label="Path"; expression={ if ($.PSISContainer) {"<Dir> $"} else {$.directoryname}}} -auto }
usage: new-alias -name pci -value c:ScriptsPick-ChildItem.ps1 pci . <- returns all items in current directory pci . 4 <- returns fourth item in list pci . (1..6),(47..50) <- returns all items in ID ranges