Second Life of a Hungarian SharePoint Geek

June 17, 2014

How to restrict the properties and Filter the results of Client Object Model requests sent from PowerShell – The alternative solution without expression trees

Filed under: Managed Client OM, PowerShell, SP 2013 — Tags: , , — Peter Holpar @ 23:24

Recently I blogged about how to implement the Where and Include functionality of the Client Object Model requests sent from PowerShell. That is great, however I don’t really like the idea to create Expression trees from scratch manually and call a lot of generic methods using Reflection to achieve this goal.

Isn’t there a simpler solution out there? Yes, there is.

In the solution illustrated below I split the functionality into two separate components. I create a static helper class that includes all of the linguistic features that are problematic to achieve form the standard PowerShell toolset.

Note: The sample below includes the Take function beyond the Where and Include methods, so only the first matching child element will be returned.

  1. public static class QueryHelper
  2. {
  3.     public static IQueryable<Field> FilterFields(this FieldCollection fields)
  4.     {
  5.         return fields.Where(f => f.TypeAsString == "Guid").Take(1).Include(f => f.Id, f => f.InternalName);
  6.     }
  7. }

The second component calls the helper method introduced above. This code contains only simple client object model features, all of the available via simple PowerShell method calls.

  1. ClientContext ctx = new ClientContext("http://sp2013&quot;);
  2. var list = ctx.Web.Lists.GetByTitle("Images");
  3. var fieldsQuery = list.Fields;
  4. var fields = ctx.LoadQuery(QueryHelper.FilterFields(fieldsQuery));
  5. ctx.ExecuteQuery();

In the next step we translate the second component to PowerShell, however we leave the C# code for the first part. From the C# code we build a temporary assembly in runtime, and call the helper method from PowerShell.

I’ve created two alternative solutions, in the first one, the code is compiled via the native Add-Type PowerShell cmdlet:

  1. $url = "http://sp2013&quot;
  2.  
  3. $referencedAssemblies = (
  4.     "Microsoft.SharePoint.Client, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c",
  5.     "Microsoft.SharePoint.Client.Runtime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c",
  6.     "System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
  7. $sourceCode = @"
  8. using Microsoft.SharePoint.Client;
  9. using System.Collections.Generic;
  10. using System.Linq;
  11. public static class QueryHelper
  12. {
  13.   public static IQueryable<Field> FilterFields(this FieldCollection fields)
  14.   {
  15.     return fields.Where(f => f.TypeAsString == "Guid").Take(1).Include(f => f.Id, f => f.InternalName);
  16.   }
  17. }
  18. "@
  19.  
  20. Add-Type -ReferencedAssemblies $referencedAssemblies -TypeDefinition $sourceCode -Language CSharp;
  21. Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"
  22. Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
  23.  
  24. $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($url)
  25. $listFields = $ctx.Web.Lists.GetByTitle("Images").Fields
  26. $fieldQuery = [QueryHelper]::FilterFields($listFields)
  27. $fields = $ctx.LoadQuery($fieldQuery)
  28. $ctx.ExecuteQuery()
  29.  
  30. $fields | % { Write-Host $_.InternalName: $_.Id }

In the second one, the code is compiled directly via the CompileAssemblyFromSource method of the CSharpCodeProvider class:

  1. $url = "http://sp2013&quot;
  2.  
  3. Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"
  4. Add-Type -Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"
  5.  
  6. $sourceCode = @"
  7. using Microsoft.SharePoint.Client;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. public static class QueryHelper
  11. {
  12.   public static IQueryable<Field> FilterFields(this FieldCollection fields)
  13.   {
  14.     return fields.Where(f => f.TypeAsString == "Guid").Take(1).Include(f => f.Id, f => f.InternalName);
  15.   }
  16. }
  17. "@
  18.  
  19. $parameters = New-Object System.CodeDom.Compiler.CompilerParameters
  20. $runtimeCompiler = New-Object Microsoft.CSharp.CSharpCodeProvider
  21. # supress command output by casting the command to void
  22. [void]$parameters.ReferencedAssemblies.Add("System.Core.dll");
  23. [void]$parameters.ReferencedAssemblies.Add("C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll");
  24. [void]$parameters.ReferencedAssemblies.Add("C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll");
  25. $parameters.GenerateExecutable = $False
  26. $parameters.GenerateInMemory = $True
  27. $parameters.IncludeDebugInformation = $False
  28. $errCount = $compRes.Errors.Count
  29. $compRes = $runtimeCompiler.CompileAssemblyFromSource($parameters, $sourceCode)
  30. Write-Host Build error count: $errCount
  31.  
  32. If ($errCount -eq 0)
  33. {
  34.   $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($url)
  35.   $listFields = $ctx.Web.Lists.GetByTitle("Images").Fields
  36.   $fieldQuery = [QueryHelper]::FilterFields($listFields)
  37.   $fields = $ctx.LoadQuery($fieldQuery)
  38.   $ctx.ExecuteQuery()
  39.  
  40. $fields | % { $_.InternalName }
  41. }

The request (as checked using Fiddler) now includes the necessary filters,

image

and the response includes only a single entry, and the two public properties (Id and InternalName) we requested.

image

The output on the console:

image

Note: Based on my experience, the temporary assemblies are cached in the PowerShell process. That means if you alter the C# code, you should restart the PowerShell console to let the changes get reflected.

Conclusion: I prefer this approach to the former ones, because we can write the lambda expressions as we got used to in C#. There is no need to write overcomplicated Expression method calls.

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: