Powershell, tools

Simple interactive console menu in Powershell

Personally I am a kind of console-freak. If possible, I would do everything in terminal. But in my case loving terminal is not about remembering all the different switches and options of console commands, but rather presenting things in a simple, readable manner, without fireworks. And most of all, navigation must be fast and comfortable. That’s why I prefer choosing options from simple menu than getting through command documentation and juggling with switches/parameters. That’s the course set out by yeoman for example.

I tried to find an easy way of building command-line menus but I couldn’t so I ended up with my own solution. It’s based on Powershell. I’m sharing it here in case you wanted to use it as well.

As we all know a picture is worth a thousand words:
example1

You can still find the code on github: https://github.com/chrisseroka/ps-menu/

The script is really short but the best way you can use it is to install it
with PowerShellGet from https://www.powershellgallery.com/

install-module -name ps-menu

You can also copy-paste the code from ps-menu.psm1 file in case you wanted to have everything in one place.

Advertisements
Powershell

Linq Any() equivalent in Powershell

Introduction

Some people like it, some not, but for me Powershell syntax is really great. Anything you want to do can be done in few commands, even in one (readable!) line of code. But there in our journey moments that we are surprised why inventors of the language haven’t added some basic functionalities. One of the examples is the “Any” operator.

If you use LINQ you might have met yourself using expressions like

 
var items = GetItems(); 
if (items.Any(x => x.ID == 999)) 
{ 
   //Do action here 
} 

You are used to things like that and want to copy these habitats into Powershell and… Nothing. There’s no “any” operator here. Of course we have the operator called “-contains” but it only compares objects, so you cannot use it to check for example if any process running has Handle == 123

It’s esspecially annoying when you are using Select-Object Cmdlet

 
$ids = Import-Csv "test.csv" | select ID 
$result = $ids -contains 10 
#never true, because we only have collection of objects with one #property "ID" set, not collection of numbers 

It would be nice in this case to use expression like:

 
$result = $ids -any {$_.ID -eq 10} 

But immediatelly we get the error:

 
You must provide a value expression on the right-hand side of the '-' operator. At :line:1 char:17 + $result = $ids -a <<<< ny {$_.Id -eq 712} 

One of the working version of the solution may look like that

@($ids | ?{$_.ID -eq 10}).Length -gt 0 

We select the object with Where-Object alias and converts result into an array to obtain its length and check if it is greater than 0. Is it readable at all? Moreover it has one more disadvantage: It counts all the elements so it is not the real and efficient “any” operator. What if you have big csv file with Ids – everytime you need to iterate through its elements to count it and say if it’s greater than 0.

Solution 1:

While investigating the problem on the Web I’ve found a solution here:

 
function Test-Any() { 
   begin { 
      $any = $false 
   } 
   process { 
      $any = $true 
   } 
   end { 
      $any 
   } 
} 

The function look very clear and could be helpful. Now our expression with Ids looks better

 
$result = $ids | ?{$_.Id -eq 123} | Test-Any() 

Function works fine, but if you set breakpoint into the line 6: “$any = $true” you will see that the expression is invoked for every source item in the pipeline (every element that has Id = 123). It’s still not what we want to have.

Solution 2:

Finally after some research I’ve managed to write my own implementation:

 
function any { 
   param( $FilterScript= $null ) 
   process { 
      if ($FilterScript| Invoke-Expression){ 
         $true; 
         break 
      } 
   } 
   end { 
      $false 
   } 
} 

Here is the sample invocation of such function:

 $result = $ids | any {$_.Id -eq 123} 

Now it serves me as I wanted. Not only makes it operation run efficient but gets rid of the where-object clause. Like the previous one, the “any” function is a pipeline function that processes the input stream. As the one and only parameter it gets $FilterScript – it is our condition to check on the data. If the source has no elements the function returns $false in the “END” block but as if the source stream is not empty, the function invokes the script (condition) we provided as a parameter and if it’s true – it returns $true and the most important thing – BREAKs the pipeline. If the condition is satisfied on the beginning of the input the result is returned immediatelly.

Unfortunatelly it does still have one small & specific disadvantage – BREAK instruction prevents the actual and all the following functions in the pipeline from invoking END block, so if your pipeline depends on it you must be aware of this fact. Not always is it a problem because you would not probably add any more function to the pipeline (If you have an idea – I’m really interested in).

I suggest you to include your ways of dealing with the lack of the any operator. Or maybe you know how to make END block invoke? I’d be pleased to be informed.