Private/Get-SiteAccessRowsForObject.ps1
|
function Get-SiteAccessRowsForObject { <# Walk ONE securable's role assignments (a web, list or item - all SecurableObjects) and emit a row per principal that can reach it, classified and, on request, expanded to people. The shared core of the site-centric engine: Get-SiteAccessForWeb calls it for a web, Get-SiteAccessDeep calls it for every subsite, list and item that breaks inheritance. It reports the route each principal holds, not a computed effective level - effective permissions are a per-user question and the subject here is the object. The object's identity (kind, title, url, permissions page) is passed in so the same walk serves every level of the tree. #> param( [Parameter(Mandatory)] $Object, # a SecurableObject with RoleAssignments [Parameter(Mandatory)] [string] $SiteUrl, # the containing web [Parameter(Mandatory)] [string] $SiteTitle, [string] $ObjectKind = 'Site', [string] $ObjectTitle = '', [string] $ObjectUrl = '', [string] $PermUrl = '', [hashtable] $MembershipCache = @{}, [switch] $ExpandMembers ) if (-not $ObjectTitle) { $ObjectTitle = $SiteTitle } if (-not $ObjectUrl) { $ObjectUrl = $SiteUrl } if (-not $PermUrl) { $PermUrl = "$SiteUrl/_layouts/15/user.aspx" } # Local so PSSA sees $Object used - it does not look inside the retry scriptblock. $securable = $Object $assignments = Invoke-WithRetry -Because "role assignments on $ObjectTitle" -Action { Get-PnPProperty -ClientObject $securable -Property RoleAssignments } # One row builder for the group and expanded-person paths. Object-identity fields # are passed in, not closed over, so PSSA sees them used (it does not look inside # script blocks). $newRow = { param($Su, $St, $Kind, $Ot, $Ou, $Pu, $Principal, $PrincipalType, $MemberCount, $Via, $Perm, $RouteType, $Login) [pscustomobject]@{ SiteUrl = $Su SiteTitle = $St Principal = $Principal PrincipalType = $PrincipalType MemberCount = $MemberCount Via = $Via Permission = $Perm RouteType = $RouteType ObjectKind = $Kind ObjectTitle = $Ot ObjectUrl = $Ou PermUrl = $Pu LoginName = $Login } } foreach ($ra in $assignments) { $member = Get-PnPProperty -ClientObject $ra -Property Member # Load sub-properties explicitly - Member loads lazily and StrictMode throws # on an unloaded LoginName/Title/PrincipalType. $mLogin = Get-PnPProperty -ClientObject $member -Property LoginName $mTitle = Get-PnPProperty -ClientObject $member -Property Title $mType = Get-PnPProperty -ClientObject $member -Property PrincipalType if (Test-SystemGroup $mTitle) { continue } $roles = (Get-PnPProperty -ClientObject $ra -Property RoleDefinitionBindings | ForEach-Object { $_.Name }) -join ', ' $c = Get-PrincipalClassification -PrincipalType "$mType" -LoginName $mLogin -Title $mTitle # Resolve a group to its people (count always; the list only when expanding). $memberCount = $null $people = @() if ($c.Kind -eq 'SharePointGroup') { $mem = @(Invoke-WithRetry -Because "members of $mTitle" -Action { Get-PnPGroupMember -Group $mTitle -ErrorAction SilentlyContinue }) $memberCount = $mem.Count if ($ExpandMembers) { $people = $mem | ForEach-Object { [pscustomobject]@{ Upn = ($_.LoginName -split '\|')[-1]; Display = $_.Title } } } } elseif ($c.Kind -eq 'EntraGroup') { $gid = Get-GroupIdFromLogin $mLogin if ($gid) { $eg = Resolve-EntraGroup -GroupId $gid -Cache $MembershipCache -IncludeMembers:$ExpandMembers $memberCount = $eg.Count if ($ExpandMembers) { $people = $eg.Members } } } $isGroup = ($c.Kind -eq 'SharePointGroup' -or $c.Kind -eq 'EntraGroup') if ($ExpandMembers -and $isGroup -and @($people).Count -gt 0) { foreach ($p in @($people)) { $pname = if ("$($p.Display)") { "$($p.Display)" } else { "$($p.Upn)" } & $newRow $SiteUrl $SiteTitle $ObjectKind $ObjectTitle $ObjectUrl $PermUrl $pname 'User' $null $c.Display $roles $c.RouteType "$($p.Upn)" } } else { $via = if ($c.Kind -eq 'User') { 'Direct grant' } else { '' } & $newRow $SiteUrl $SiteTitle $ObjectKind $ObjectTitle $ObjectUrl $PermUrl $c.Display $c.Kind $memberCount $via $roles $c.RouteType $mLogin } } } |