Wednesday, August 2, 2017

PowerShell script for listing master pages in all sites of Sharepoint Online site collection via CSOM

If you maintain big site collection in Sharepoint Online it will be useful to know what master pages are used in sub sites. The following script recursively iterates through all sub sites in specific site collection and prints master pages. Also it prints whether or not site master page and custom master page are inherited from the parent web:

   1: param(
   2:     [string]$siteUrl,
   3:     [string]$login,
   4:     [string]$password
   5: )
   6:  
   7: $currentDir = Convert-Path(Get-Location)
   8: $dllsDir = resolve-path($currentDir +
   9:     "\Microsoft.SharePointOnline.CSOM.16.1.6420.1200\lib\net45")
  10:  
  11: [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllsDir,
  12:     "Microsoft.SharePoint.Client.dll"))
  13: [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllsDir,
  14:     "Microsoft.SharePoint.Client.Runtime.dll"))
  15:  
  16: if (-not $siteUrl)
  17: {
  18:     Write-Host "Specify site url in siteUrl parameter" -foregroundcolor red
  19:     return
  20: }
  21:  
  22: if (-not $login)
  23: {
  24:     Write-Host "Specify user name in login parameter" -foregroundcolor red
  25:     return
  26: }
  27:  
  28: if (-not $password)
  29: {
  30:     Write-Host "Specify user password in password parameter" -foregroundcolor red
  31:     return
  32: }
  33:  
  34: <#
  35: .Synopsis
  36:     Facilitates the loading of specific properties of a Microsoft.SharePoint.Client.ClientObject object or Microsoft.SharePoint.Client.ClientObjectCollection object.
  37: .DESCRIPTION
  38:     Replicates what you would do with a lambda expression in C#. 
  39:     For example, "ctx.Load(list, l => list.Title, l => list.Id)" becomes
  40:     "Load-CSOMProperties -object $list -propertyNames @('Title', 'Id')".
  41: .EXAMPLE
  42:     Load-CSOMProperties -parentObject $web -collectionObject $web.Fields -propertyNames @("InternalName", "Id") -parentPropertyName "Fields" -executeQuery
  43:     $web.Fields | select InternalName, Id
  44: .EXAMPLE
  45:    Load-CSOMProperties -object $web -propertyNames @("Title", "Url", "AllProperties") -executeQuery
  46:    $web | select Title, Url, AllProperties
  47: #>
  48: function Load-CSOMProperties {
  49:     [CmdletBinding(DefaultParameterSetName='ClientObject')]
  50:     param (
  51:         # The Microsoft.SharePoint.Client.ClientObject to populate.
  52:         [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = "ClientObject")]
  53:         [Microsoft.SharePoint.Client.ClientObject]
  54:         $object,
  55:  
  56:         # The Microsoft.SharePoint.Client.ClientObject that contains the collection object.
  57:         [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = "ClientObjectCollection")]
  58:         [Microsoft.SharePoint.Client.ClientObject]
  59:         $parentObject,
  60:  
  61:         # The Microsoft.SharePoint.Client.ClientObjectCollection to populate.
  62:         [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1, ParameterSetName = "ClientObjectCollection")]
  63:         [Microsoft.SharePoint.Client.ClientObjectCollection]
  64:         $collectionObject,
  65:  
  66:         # The object properties to populate
  67:         [Parameter(Mandatory = $true, Position = 1, ParameterSetName = "ClientObject")]
  68:         [Parameter(Mandatory = $true, Position = 2, ParameterSetName = "ClientObjectCollection")]
  69:         [string[]]
  70:         $propertyNames,
  71:  
  72:         # The parent object's property name corresponding to the collection object to retrieve (this is required to build the correct lamda expression).
  73:         [Parameter(Mandatory = $true, Position = 3, ParameterSetName = "ClientObjectCollection")]
  74:         [string]
  75:         $parentPropertyName,
  76:  
  77:         # If specified, execute the ClientContext.ExecuteQuery() method.
  78:         [Parameter(Mandatory = $false, Position = 4)]
  79:         [switch]
  80:         $executeQuery
  81:     )
  82:  
  83:     begin { }
  84:     process {
  85:         if ($PsCmdlet.ParameterSetName -eq "ClientObject") {
  86:             $type = $object.GetType()
  87:         } else {
  88:             $type = $collectionObject.GetType() 
  89:             if ($collectionObject -is [Microsoft.SharePoint.Client.ClientObjectCollection]) {
  90:                 $type = $collectionObject.GetType().BaseType.GenericTypeArguments[0]
  91:             }
  92:         }
  93:  
  94:         $exprType = [System.Linq.Expressions.Expression]
  95:         $parameterExprType = [System.Linq.Expressions.ParameterExpression].MakeArrayType()
  96:         $lambdaMethod = $exprType.GetMethods() | ? { $_.Name -eq "Lambda" -and $_.IsGenericMethod -and $_.GetParameters().Length -eq 2 -and $_.GetParameters()[1].ParameterType -eq $parameterExprType }
  97:         $lambdaMethodGeneric = Invoke-Expression "`$lambdaMethod.MakeGenericMethod([System.Func``2[$($type.FullName),System.Object]])"
  98:         $expressions = @()
  99:  
 100:         foreach ($propertyName in $propertyNames) {
 101:             $param1 = [System.Linq.Expressions.Expression]::Parameter($type, "p")
 102:             try {
 103:                 $name1 = [System.Linq.Expressions.Expression]::Property($param1, $propertyName)
 104:             } catch {
 105:                 Write-Error "Instance property '$propertyName' is not defined for type $type"
 106:                 return
 107:             }
 108:             $body1 = [System.Linq.Expressions.Expression]::Convert($name1, [System.Object])
 109:             $expression1 = $lambdaMethodGeneric.Invoke($null, [System.Object[]] @($body1, [System.Linq.Expressions.ParameterExpression[]] @($param1)))
 110:  
 111:             if ($collectionObject -ne $null) {
 112:                 $expression1 = [System.Linq.Expressions.Expression]::Quote($expression1)
 113:             }
 114:             $expressions += @($expression1)
 115:         }
 116:  
 117:  
 118:         if ($PsCmdlet.ParameterSetName -eq "ClientObject") {
 119:             $object.Context.Load($object, $expressions)
 120:             if ($executeQuery) { $object.Context.ExecuteQuery() }
 121:         } else {
 122:             $newArrayInitParam1 = Invoke-Expression "[System.Linq.Expressions.Expression``1[System.Func````2[$($type.FullName),System.Object]]]"
 123:             $newArrayInit = [System.Linq.Expressions.Expression]::NewArrayInit($newArrayInitParam1, $expressions)
 124:  
 125:             $collectionParam = [System.Linq.Expressions.Expression]::Parameter($parentObject.GetType(), "cp")
 126:             $collectionProperty = [System.Linq.Expressions.Expression]::Property($collectionParam, $parentPropertyName)
 127:  
 128:             $expressionArray = @($collectionProperty, $newArrayInit)
 129:             $includeMethod = [Microsoft.SharePoint.Client.ClientObjectQueryableExtension].GetMethod("Include")
 130:             $includeMethodGeneric = Invoke-Expression "`$includeMethod.MakeGenericMethod([$($type.FullName)])"
 131:  
 132:             $lambdaMethodGeneric2 = Invoke-Expression "`$lambdaMethod.MakeGenericMethod([System.Func``2[$($parentObject.GetType().FullName),System.Object]])"
 133:             $callMethod = [System.Linq.Expressions.Expression]::Call($null, $includeMethodGeneric, $expressionArray)
 134:             
 135:             $expression2 = $lambdaMethodGeneric2.Invoke($null, @($callMethod, [System.Linq.Expressions.ParameterExpression[]] @($collectionParam)))
 136:  
 137:             $parentObject.Context.Load($parentObject, $expression2)
 138:             if ($executeQuery) { $parentObject.Context.ExecuteQuery() }
 139:         }
 140:     }
 141:     end { }
 142: }
 143:  
 144: function CheckMasterPage($ctx, $web)
 145: {
 146:     Load-CSOMProperties -object $web -propertyNames @("MasterUrl", "CustomMasterUrl",
 147:         "Webs", "AllProperties")
 148:     $ctx.ExecuteQuery()
 149:     
 150:     ($web.Url) | Out-File "log.txt" -Append
 151:     ("    " + $web.MasterUrl) | Out-File "log.txt" -Append
 152:     ("    " + $web.AllProperties["__InheritsMasterUrl"]) | Out-File "log.txt" -Append
 153:     ("    " + $web.CustomMasterUrl) | Out-File "log.txt" -Append
 154:     ("    " + $web.AllProperties["__InheritsCustomMasterUrl"]) | Out-File "log.txt" -Append
 155:     
 156:     foreach ($w in $web.Webs)
 157:     {
 158:         CheckMasterPage $ctx $w
 159:     }
 160: }
 161:  
 162: $securePassword = ConvertTo-SecureString $password -AsPlainText -Force
 163: $credentials =
 164:     New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($login,
 165:         $securePassword)    
 166: $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)
 167: $ctx.AuthenticationMode =
 168:     [Microsoft.SharePoint.Client.ClientAuthenticationMode]::Default
 169: $ctx.Credentials = $credentials
 170: $ctx.Load($ctx.Site)
 171: $ctx.Load($ctx.Web)
 172: $ctx.ExecuteQuery()
 173:  
 174: CheckMasterPage $ctx $ctx.Web

Script uses CSOM v.16.1.6420.1200, but you may use other versions of course – download it and save in the script folder (you may need to change path from where assemblies are loaded – lines 8-9). Also it uses helper utility function Load-CSOMProperties (credits go to Gary Lapointe) which is analogue of ClientContext.Load function in C# which allows to specify which properties should be loaded via lambda expressions (in PowerShell there are no lambdas, so we have to use helper function). Script itself is quite simple – it recursively iterates through all sub sites and prints master pages (lines 144-160). Hope it will help someone.

No comments:

Post a Comment