James O'Neill's blog

Windows Platform, Virtualization and PowerShell with a little Photography for good measure.

Powershell again. Pipes, and "This is not an output"

Powershell again. Pipes, and "This is not an output"

  • Comments 10
  • Likes

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: ,
Comments
  • <p>[link] Marco</p>

  • <p>I don't think you need a FOR loop to fix this, the OUT-HOST should suffice. &nbsp;Just do </p> <p>Format-Table &lt;blah blah&gt; | Out-Host ;</p> <p>And you'll be all set. &nbsp;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.</p> <p>Cheers!</p> <p>Jeffrey Snover [MSFT]</p> <p>Windows Management Partner Architect</p> <p>Visit the Windows PowerShell Team blog at: &nbsp; &nbsp;<a rel="nofollow" target="_new" href="http://blogs.msdn.com/PowerShell">http://blogs.msdn.com/PowerShell</a></p> <p>Visit the Windows PowerShell ScriptCenter at: &nbsp;<a rel="nofollow" target="_new" href="http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx">http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx</a></p>

  • <p>BTW - if you wanted to allow multiple processes to be chosen you could do this:</p> <p>$proc[ [int[]](Read-Host &quot;Which ones? &quot;).Split(&quot;,&quot;)]</p> <p>See if you like that.</p> <p>Jeffrey Snover [MSFT]</p> <p>Windows Management Partner Architect</p> <p>Visit the Windows PowerShell Team blog at: &nbsp; &nbsp;<a rel="nofollow" target="_new" href="http://blogs.msdn.com/PowerShell">http://blogs.msdn.com/PowerShell</a></p> <p>Visit the Windows PowerShell ScriptCenter at: &nbsp;<a rel="nofollow" target="_new" href="http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx">http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx</a></p>

  • <p>Let&amp;#39;s face it, PowerShell does a bunch of magic for you. If we&amp;#39;ve done our job right, this magic</p>

  • <p>This is really cool! &nbsp;I've been wanting something like this for a while, but just haven't thought it out yet.</p> <p>After reviewing all the comments &amp; having more time to think about it, do you have a &quot;better version&quot; written now that you can share with us? &nbsp;</p> <p>Thanks!</p>

  • <P>Jeffrey, thanks for your comments and your kind words over on the Powershell team blog.</P> <P>Matt. The function would now go</P> <P>Function choose-process</P> <P>{$global:Counter=-1</P> <P>$proc=Get-Process</P> <P>Format-Table -autosize -inputobject $Proc @{ Label = "ID"; Expression={($global:counter++) }} , processName | out-host</P> <P>$proc[ [int[]](Read-Host "Which ones? ").Split(",")] }</P> <P><STRONG>note:</STRONG> I've revised this comment to included Jeffery's multi-select - since I've just written something which actually uses it :-) </P>

  • <p>I have a set of such functions that basically do the equivalent of SHIFT/CTRL selecting from a list with a mouse. &nbsp;The trick works with just about any Powershell output really. &nbsp;Here's one for selecting directory items:</p> <p>-------------</p> <p>## Pick-ChildItem.ps1 ##</p> <p>param ($path, [array]$index, [switch]$force)</p> <p>if ($force) {$switch = &quot;-force&quot;}</p> <p>$global:items = (invoke-expression &quot;get-childitem '$path\*' $switch&quot; | </p> <p> &nbsp; sort-object &nbsp;@{expression={$_.PSIsContainer}; Descending=$true},</p> <p> &nbsp; @{expression={$_.Name}; Ascending=$true})</p> <p>if ($index -ne $null) {</p> <p> &nbsp; $index | %{$_} | %{$items[$_ - 1]}</p> <p>}</p> <p>else {$items | %{$i=1}{$_; $i++} | </p> <p> &nbsp; format-table @{label=&quot;ID&quot;; expression={$i}}, Name,</p> <p> &nbsp; @{label=&quot;Path&quot;; expression={</p> <p> &nbsp; &nbsp; if ($_.PSISContainer) {&quot;&lt;Dir&gt; $_&quot;} </p> <p> &nbsp; &nbsp; else {$_.directoryname}}} -auto</p> <p>}</p> <p>-------------</p> <p>usage:</p> <p>new-alias -name pci -value c:\Scripts\Pick-ChildItem.ps1</p> <p>pci . &lt;- returns all items in current directory</p> <p>pci . 4 &lt;- returns fourth item in list</p> <p>pci . (1..6),(47..50) &lt;- returns all items in ID ranges</p>

  • <p>Fantastic Hecks. thanks for sharing. </p>

  • <p>I mentioned my weekend of PowerShell , and that I've been working on scripts for the Office Communications</p>

  • <p>I've been showing PowerShell on the roadshow, and Steve warning me about it becoming the &amp;quot;Look-how-clever-I-am-with-PowerShell</p>

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment