Hasuniqueroleassignments Power Shell Script

In SharePoint Online, most of the times, a Site Owner would like to get subsites report of their site collection.  Here is a sample PowerShell script which uses CSOM to get all users with Full control permission in all the subsites (recursively) under the given Site Collection and outputs in an Excel sheet.  The script can be modified as needed to get other properties of a subsite.

 

Add-Type –Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"

Add-Type –Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"

 

$siteURL = Read-Host -Prompt "Enter Site Collection URL"

$username = Read-Host -Prompt "Enter User Name"

$password = Read-Host -Prompt "Password for $username" -AsSecureString

[Microsoft.SharePoint.Client.ClientContext]$clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($siteURL)

$clientContext.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $password)

 

$excel = New-Object -ComObject Excel.Application

$excel.Visible = $true

$workbook = $excel.Workbooks.Add()

$sheet = $workbook.ActiveSheet

$counter = 1

$sheet.cells.Item($counter,1) = 'Site URL'

$sheet.cells.Item($counter,2) = 'Unique Permission'

$sheet.cells.Item($counter,3) = 'Users with Full Control Permission'

$sheet.cells.Item($counter,1).font.bold = $True

$sheet.cells.Item($counter,2).font.bold = $True

$sheet.cells.Item($counter,3).font.bold = $True

$sheet.cells.Item($counter,1).Interior.ColorIndex = 8

$sheet.cells.Item($counter,2).Interior.ColorIndex = 8

$sheet.cells.Item($counter,3).Interior.ColorIndex = 8

 

Function Invoke-LoadMethod() {

param(

[Microsoft.SharePoint.Client.ClientObject]$Object = $(throw "Please provide a Client Object"),

[string]$PropertyName

)

$ctx = $Object.Context

$load = [Microsoft.SharePoint.Client.ClientContext].GetMethod("Load")

$type = $Object.GetType()

$clientLoad = $load.MakeGenericMethod($type)

$Parameter = [System.Linq.Expressions.Expression]::Parameter(($type), $type.Name)

$Expression = [System.Linq.Expressions.Expression]::Lambda(

[System.Linq.Expressions.Expression]::Convert(

[System.Linq.Expressions.Expression]::PropertyOrField($Parameter,$PropertyName),

[System.Object]

),

$($Parameter)

)

$ExpressionArray = [System.Array]::CreateInstance($Expression.GetType(), 1)

$ExpressionArray.SetValue($Expression, 0)

$clientLoad.Invoke($ctx,@($Object,$ExpressionArray))

}

function Get-SPOWebs(){

param(

$Url = $(throw "Please provide a Site Collection Url"),

$Credential = $(throw "Please provide a Credentials")

)

$context = New-Object Microsoft.SharePoint.Client.ClientContext($Url)

$context.Credentials = $Credential

$web = $context.Web

$context.Load($web)

$context.Load($web.Webs)

$context.ExecuteQuery()

foreach($web in $web.Webs)

{

Get-SPOWebs -Url $web.Url -Credential $Credential

Invoke-LoadMethod -Object $web -PropertyName "HasUniqueRoleAssignments"

$context.ExecuteQuery()

$outputString = ''

$siteGroups = $web.SiteGroups

$context.Load($web.AssociatedOwnerGroup)

$context.ExecuteQuery()

$ownersGroup = $siteGroups.GetByName($web.AssociatedOwnerGroup.Title)

$context.Load($ownersGroup)

$context.ExecuteQuery()

$ownersGroupUsers = $ownersGroup.Users

$context.Load($ownersGroupUsers)

$context.ExecuteQuery()

$ownersGroupUsers | ForEach-Object {$outputString += $_.Email + ';' }

$rows = $sheet.UsedRange.Rows.Count + 1

$sheet.cells.Item($rows ,1) = $web.Url

$sheet.cells.Item($rows ,2) = $web.HasUniqueRoleAssignments.ToString()

$sheet.cells.Item($rows ,3) = $outputString

$outputString = ''

}

}

Get-SPOWebs -Url $siteURL -Credential $clientContext.Credentials

 

★★★★★

★★★★

★★★

★★

How to load additional CSOM properties in a PowerShell script

We can use CSOM (client-side object model) in different programming and scripting languages to manage SharePoint On-Premises and SharePoint Online objects, as you know loading additional properties in client side object is very challenging task. In .NET C#, we can easily load specific values using Lambda Expressions with SharePoint CSOM API, but we should have proper knowledge to write lambda expression in Windows PowerShell.

In this post, I am going to share how to use GitHub open source library Load-CSOMProperties.ps1 to fetch extra properties in SharePoint CSOM API. It includes the following steps.
  1. Copy the required Powershell script code from this GitHub location Load-CSOMProperties.ps1 or you also copy the same script code at the end of this post.
  2. Once you copied the script, open Notepad (or Text Document) and paste the copied script, then save the file with .ps1 extension (Ex: Load-CSOMProperties.ps1).
  3. Now you can load the saved Powershell script file in Powershell console by just entering the file path. Ex: C:\Scripts\Load-CSOMProperties.ps1
  4. Once you loaded the Load-CSOMProperties.ps1 file in Powershell console, you can call the function Load-CSOMProperties anywhere in your script to load extra properties for any client side object.

Example 1:

The below powershell script load the additional properties 'Url' and 'Title' of the object $web along with default properties.
C:\Scripts\Load-CSOMProperties.ps1 $web = $ctx.Web Load-CSOMProperties -object $web -propertyNames @("AllProperties", "Url", "Title") $ctx.ExecuteQuery()

Example 2:

The below powershell script load the property HasUniqueRoleAssignments in every list item and check if the item has any unique permission entry or not.
C:\Scripts\Load-CSOMProperties.ps1 foreach($listItem in $allItems) { Load-CSOMProperties -object $listItem -propertyNames @("HasUniqueRoleAssignments"); $ctx.ExecuteQuery(); if($listItem.HasUniqueRoleAssignments -eq $true) { Write-Host $listItem["FileRef"] } }

Source of Load-CSOMProperties.ps1:

<# .Synopsis Facilitates the loading of specific properties of a Microsoft.SharePoint.Client.ClientObject object or Microsoft.SharePoint.Client.ClientObjectCollection object. .DESCRIPTION Replicates what you would do with a lambda expression in C#. For example, "ctx.Load(list, l => list.Title, l => list.Id)" becomes "Load-CSOMProperties -object $list -propertyNames @('Title', 'Id')". .EXAMPLE Load-CSOMProperties -parentObject $web -collectionObject $web.Fields -propertyNames @("InternalName", "Id") -parentPropertyName "Fields" -executeQuery $web.Fields | select InternalName, Id .EXAMPLE Load-CSOMProperties -object $web -propertyNames @("Title", "Url", "AllProperties") -executeQuery $web | select Title, Url, AllProperties #> function global:Load-CSOMProperties { [CmdletBinding(DefaultParameterSetName='ClientObject')] param ( # The Microsoft.SharePoint.Client.ClientObject to populate. [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = "ClientObject")] [Microsoft.SharePoint.Client.ClientObject] $object, # The Microsoft.SharePoint.Client.ClientObject that contains the collection object. [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = "ClientObjectCollection")] [Microsoft.SharePoint.Client.ClientObject] $parentObject, # The Microsoft.SharePoint.Client.ClientObjectCollection to populate. [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1, ParameterSetName = "ClientObjectCollection")] [Microsoft.SharePoint.Client.ClientObjectCollection] $collectionObject, # The object properties to populate [Parameter(Mandatory = $true, Position = 1, ParameterSetName = "ClientObject")] [Parameter(Mandatory = $true, Position = 2, ParameterSetName = "ClientObjectCollection")] [string[]] $propertyNames, # The parent object's property name corresponding to the collection object to retrieve (this is required to build the correct lamda expression). [Parameter(Mandatory = $true, Position = 3, ParameterSetName = "ClientObjectCollection")] [string] $parentPropertyName, # If specified, execute the ClientContext.ExecuteQuery() method. [Parameter(Mandatory = $false, Position = 4)] [switch] $executeQuery ) begin { } process { if ($PsCmdlet.ParameterSetName -eq "ClientObject") { $type = $object.GetType() } else { $type = $collectionObject.GetType() if ($collectionObject -is [Microsoft.SharePoint.Client.ClientObjectCollection]) { $type = $collectionObject.GetType().BaseType.GenericTypeArguments[0] } } $exprType = [System.Linq.Expressions.Expression] $parameterExprType = [System.Linq.Expressions.ParameterExpression].MakeArrayType() $lambdaMethod = $exprType.GetMethods() | ? { $_.Name -eq "Lambda" -and $_.IsGenericMethod -and $_.GetParameters().Length -eq 2 -and $_.GetParameters()[1].ParameterType -eq $parameterExprType } $lambdaMethodGeneric = Invoke-Expression "`$lambdaMethod.MakeGenericMethod([System.Func``2[$($type.FullName),System.Object]])" $expressions = @() foreach ($propertyName in $propertyNames) { $param1 = [System.Linq.Expressions.Expression]::Parameter($type, "p") try { $name1 = [System.Linq.Expressions.Expression]::Property($param1, $propertyName) } catch { Write-Error "Instance property '$propertyName' is not defined for type $type" return } $body1 = [System.Linq.Expressions.Expression]::Convert($name1, [System.Object]) $expression1 = $lambdaMethodGeneric.Invoke($null, [System.Object[]] @($body1, [System.Linq.Expressions.ParameterExpression[]] @($param1))) if ($collectionObject -ne $null) { $expression1 = [System.Linq.Expressions.Expression]::Quote($expression1) } $expressions += @($expression1) } if ($PsCmdlet.ParameterSetName -eq "ClientObject") { $object.Context.Load($object, $expressions) if ($executeQuery) { $object.Context.ExecuteQuery() } } else { $newArrayInitParam1 = Invoke-Expression "[System.Linq.Expressions.Expression``1[System.Func````2[$($type.FullName),System.Object]]]" $newArrayInit = [System.Linq.Expressions.Expression]::NewArrayInit($newArrayInitParam1, $expressions) $collectionParam = [System.Linq.Expressions.Expression]::Parameter($parentObject.GetType(), "cp") $collectionProperty = [System.Linq.Expressions.Expression]::Property($collectionParam, $parentPropertyName) $expressionArray = @($collectionProperty, $newArrayInit) $includeMethod = [Microsoft.SharePoint.Client.ClientObjectQueryableExtension].GetMethod("Include") $includeMethodGeneric = Invoke-Expression "`$includeMethod.MakeGenericMethod([$($type.FullName)])" $lambdaMethodGeneric2 = Invoke-Expression "`$lambdaMethod.MakeGenericMethod([System.Func``2[$($parentObject.GetType().FullName),System.Object]])" $callMethod = [System.Linq.Expressions.Expression]::Call($null, $includeMethodGeneric, $expressionArray) $expression2 = $lambdaMethodGeneric2.Invoke($null, @($callMethod, [System.Linq.Expressions.ParameterExpression[]] @($collectionParam))) $parentObject.Context.Load($parentObject, $expression2) if ($executeQuery) { $parentObject.Context.ExecuteQuery() } } } end { } }

0 comments

Leave a Reply

Your email address will not be published. Required fields are marked *