Поделиться через


Where or where has my Where-Object gone?

If you have done any remote runspace creation against Exchange 2010 you have undoubtly run into this error message:

"Script block literals are not allowed in restricted language mode or a Data section"

or

"The term 'Where-Object' is not recoginized as the name of a cmdlet, function, script file or operable program.  Check the spelling of the name, or if a path was included, verify that the path is correct and try again."

This is because the Exchange Powershell runspace is a constrained runspace.  This means that full language mode is not supported and therefore commands that contain scripts to be evaluated are not allowed.  The workaround to this is to execute your Exchange related cmdlets on the server and then run the script based cmdlets on the client.  If this sounds like you are downloading extra data, then you are right. However, this is what the Exchange Management Shell does.  Furthermore, when you retrieve data back from the server for a particular cmdlet the data is a serialized representation of the object.  It may not be the entire object depending on the serialization level.  If you are truly concerned about this just make your pipeline more succinct.  For example, instead of using:

Get-ExchangeServer | ? {$_.Name -eq 'MyServer'}

use

Get-ExchangeServer -Server 'MyServer'

I suppose that some Exchange cmdlets don't have the ability to filter the dataset easily or it just makes more sense to filter the data using the Where-Object.  For those cases you can use a hybrid approach of runspaces hosted in your application ("local runspaces") and runspaces hosted in the Exchange remote end point.  Here is some sample code to demonstrate what I am talking about.

private static Collection<PSObject> ApplyFilter(Collection<PSObject> unfilteredData, ScriptBlock sb)

{

Runspace rnspc = null;

Powershell = null;

Collection<PSObject> retVal = null;

using (rnspc = RunspaceFactory.CreateRunspace())

  {

    using (psh = Powershell.Create())

    {

      psh.AddCommand("Where-Object");

      psh.AddParameter("FilterScript", sb);

      retVal = psh.Invoke(unfilteredData);

    }

  }

}

And thus the callsite would look like this:

filteredResults = ApplyFilter(results, ScriptBlock.Create("$_.Alias -eq 'John'");

In this method I take a Collection of Type PSObject which is the unfiltered data that I got back from a previous call to a cmdlet running in the Exchange Powershell remote endpoint.   The method takes the collection and uses it as input to a call to the Where-Object runnning in a "local runspace" created on the fly.  It also uses the passed in ScriptBlock as a parameter to filter on.  The method returns the results back to the caller.

The ForEach-Object cmdlet should suffer the same fate as the Where-Object cmdlet since it too requires a script block that is evaluated at runtime.

Lastly, you could implement your own For or While Loop and bypass the use of Powershell completely.  This may give you more power over the iterator but you sacrifice the consistent "Powershellness" of your code.

Comments

  • Anonymous
    November 08, 2012
    Thanks Dave for the post..This has helped me re-solve the issue I was facing.

  • Anonymous
    February 08, 2013
    Excellent post and great idea for filtering the results without iteration.

  • Anonymous
    December 17, 2013
    Great post, Dave! Thank you a lot. This is exactly what I need to filter get-journalrule details using the Management Shell.

  • Anonymous
    August 17, 2015
    The proper phrase is "Where, oh where..."

  • Anonymous
    August 19, 2015
    Thanks Steve! I posted this in 2011 and you are the first person to notice that. I have corrected it.

  • Anonymous
    March 12, 2018
    Thanks Dave! Unique and priceless article :-)