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.