Command line

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.

Advertisements
Articles

Hello world!

Hello, I’ve already found out that it’d be nice to come out of my private den and meet some subscribers :-] And there’s no single reason for doing that: practicing language, learning new things, comparing wordpress to own platform 😛 … Don’t be thrifty in your critics.

Have a nice reading!