Compartilhar via


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 Communications

  • Anonymous
    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-PowerShell

  • Anonymous
    January 01, 2003
    [link] Marco

  • Anonymous
    January 01, 2003
    Let's face it, PowerShell does a bunch of magic for you. If we've done our job right, this magic

  • Anonymous
    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.mspx

  • Anonymous
    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.mspx

  • Anonymous
    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