Delegates

PowerShell recursion with delegate functions, iterate all lists in all webs




There are many ways to iterate a collection in PowerShell. I just really like using delegate functions. This approach is not native PowerShell but utilises the .NET Action class as a function parameter. Using a delegate function approach, it is possible to create a recursive loop that can be very easily reused in the future just by providing an alternative Action.

The example code I provide below demonstrates how to create a delegate function in PowerShell, how to write a function that accepts one as a parameter, and provides some ready made samples for iterating SharePoint objects, specifically all webs or all lists. I am using some specific SharePoint objects in these samples, however the fundamental pattern can be used to effectively iterate any recursive structure.

foreachDecendentWeb : perform an action on every web below the provided web
foreachListInWeb : perform an action on every list in the provided web
foreachListInWebAndAllDecendentWebs : perform an action on every list in the current and all decendent webs

Delegates

Some notes

The below script references ‘TopOfScript.ps1’, it is specifically related to calling SharePoint CSOM from PowerShell. Read about it here on sharepointnutsandbolts.

Making the call, providing the delegate

# See Chris O'Brien's blog on using CSOM in PowerShell for
# more information on 'TopOfScript'. In essence this just 
# defines the $clientContext object.
# http://www.sharepointnutsandbolts.com/2013/12/Using-CSOM-in-PowerShell-scripts-with-Office365.html
. .\TopOfScript.ps1

# Load root web
$rootWeb = $clientContext.Site.RootWeb
$clientContext.Load($rootWeb)
$clientContext.ExecuteQuery()

# Define the work that you want done here
[Action[Microsoft.SharePoint.Client.List]]$printListName = 
{
  param([Parameter(Mandatory=$true)]$list)
  Write-Host "$($list.Title)"
}

# Kick it off!
foreachListInWebAndAllDecendentWebs $rootWeb $printListName

The utility scipts, recursive functions accepting delegate parameters

function foreachDecendentWeb(
  [Parameter(Mandatory=$true)]
  [Microsoft.SharePoint.Client.Web]$web, 
  [Parameter(Mandatory=$true)]
  [Action[Microsoft.SharePoint.Client.Web]]$webAction)
{
  # Load child webs
  $context = $web.Context
  $childWebs = $web.Webs
  $context.Load($childWebs)
  $context.ExecuteQuery()
  
  # Iterate child webs
  foreach($childWeb in $childWebs)
  {
    # Perform action on child web
    $webAction.Invoke($childWeb)

    # Iterate child-child webs
    foreachDecendentWeb $childWeb $webAction
  }
}

function foreachListInWeb(
  [Parameter(Mandatory=$true)]
  [Microsoft.SharePoint.Client.Web]$web,
  [Parameter(Mandatory=$true)]
  [Action[Microsoft.SharePoint.Client.List]]$listAction)
{
  # Load lists
  $context = $web.Context
  $lists = $web.Lists
  $context.Load($lists)
  $context.ExecuteQuery()

  # Iterate lists
  $listCount = 0
  foreach($list in $lists)
  {
    # Perform action on child web
    $listAction.Invoke($list)
    $listCount++
  }
  return $listCount
}

function foreachListInWebAndAllDecendentWebs(
  [Parameter(Mandatory=$true)]
  [Microsoft.SharePoint.Client.Web]$web,
  [Parameter(Mandatory=$true)]
  [Action[Microsoft.SharePoint.Client.List]]$action)
{
  # Define per-list action
  [Action[Microsoft.SharePoint.Client.List]]$perListAction = 
  {
    param([Parameter(Mandatory=$true)]$list)
    $action.Invoke($list)
  }

  # Define per-web action
  [Action[Microsoft.SharePoint.Client.Web]]$perWebAction = 
  {
    param([Parameter(Mandatory=$true)]$web)
    foreachListInWeb $web $perListAction
  }

  # Perform action on root web
  $perWebAction.Invoke($rootWeb)

  # Perform action on every decendent web
  foreachDecendentWeb $rootWeb $perWebAction
}

Paul.




Leave a Reply

Your email address will not be published.