SharePoint Automation Gary Lapointe – Founding Partner, Aptillon, Inc.

29Apr/1028

Discovering Who Has Access to SharePoint 2010 Securable Objects

I've talked on several occasions about how we can easily use the SharePoint 2010 object model (OM) to discover who has access to a securable object (SPWeb, SPList, or SPListItem) and the fact that we can use the same mechanisms within PowerShell to create useful security/audit reports. On some of those occasions I've shown a version of a PowerShell script which gives you a dump to the screen or a text file of every securable object and who has access to it and how they were given access to it - today I'd like to share a new version of that script.

Before we get to the actual script let's first talk about how to get the information. All securable objects have a method named GetUserEffectivePermissionInfo which is defined in the abstract base class SPSecurableObject (in 2007 this method was defined directly on the SPWeb, SPList, and SPListItem objects). This method returns back an SPPermissionInfo object which we can use to inspect the various role definition bindings and corresponding permission levels.

Once we have the permission details we simple loop through the SPRoleAssignments objects via the RoleAssignments property. This will give us information about how the user is given access to the resource. Next we look at the RoleDefinitionBindings property which returns back a collection of SPRoleDefinition objects that tell us about the type of access granted (e.g., Full Control, etc.).

I then take all this information, stick it in a hash table which I then use to create a new object which gets written to the pipeline.

So with that, let's take a look at the code:

function Get-SPUserEffectivePermissions(
    [object[]]$users, 
    [Microsoft.SharePoint.SPSecurableObject]$InputObject) {
    
    begin { }
    process {
        $so = $InputObject
        if ($so -eq $null) { $so = $_ }
        
        if ($so -isnot [Microsoft.SharePoint.SPSecurableObject]) {
            throw "A valid SPWeb, SPList, or SPListItem must be provided."
        }
        
        foreach ($user in $users) {
            # Set the users login name
            $loginName = $user
            if ($user -is [Microsoft.SharePoint.SPUser] -or $user -is [PSCustomObject]) {
                $loginName = $user.LoginName
            }
            if ($loginName -eq $null) {
                throw "The provided user is null or empty. Specify a valid SPUser object or login name."
            }
            
            # Get the users permission details.
            $permInfo = $so.GetUserEffectivePermissionInfo($loginName)
            
            # Determine the URL to the securable object being evaluated
            $resource = $null
            if ($so -is [Microsoft.SharePoint.SPWeb]) {
                $resource = $so.Url
            } elseif ($so -is [Microsoft.SharePoint.SPList]) {
                $resource = $so.ParentWeb.Site.MakeFullUrl($so.RootFolder.ServerRelativeUrl)
            } elseif ($so -is [Microsoft.SharePoint.SPListItem]) {
                $resource = $so.ParentList.ParentWeb.Site.MakeFullUrl($so.Url)
            }

            # Get the role assignments and iterate through them
            $roleAssignments = $permInfo.RoleAssignments
            if ($roleAssignments.Count -gt 0) {
                foreach ($roleAssignment in $roleAssignments) {
                    $member = $roleAssignment.Member
                    
                    # Build a string array of all the permission level names
                    $permName = @()
                    foreach ($definition in $roleAssignment.RoleDefinitionBindings) {
                        $permName += $definition.Name
                    }
                    
                    # Determine how the users permissions were assigned
                    $assignment = "Direct Assignment"
                    if ($member -is [Microsoft.SharePoint.SPGroup]) {
                        $assignment = $member.Name
                    } else {
                        if ($member.IsDomainGroup -and ($member.LoginName -ne $loginName)) {
                            $assignment = $member.LoginName
                        }
                    }
                    
                    # Create a hash table with all the data
                    $hash = @{
                        Resource = $resource
                        "Resource Type" = $so.GetType().Name
                        User = $loginName
                        Permission = $permName -join ", "
                        "Granted By" = $assignment
                    }
                    
                    # Convert the hash to an object and output to the pipeline
                    New-Object PSObject -Property $hash
                }
            }
        }
    }
    end {}
}

Great - we've got the code - so now you're probably asking, "how the heck do I use it?" Well the first thing you need to do is save it to a file, let's call it SecurityReport.ps1 and we'll put it in the root of the C drive. Once saved we can load it in memory using the following:

C:\ PS> . .\SecurityReport.ps1

Now for the fun stuff :) . The examples I'm going to show will build off of each other and will eventually conclude with an example that gives me a report for all users and all securable objects throughout the entire farm. The first example I want to show is how to retrieve a report for a single user and a single web (we'll reuse the $user variable throughout the script so I'll only define it once here):

$user = "sp2010\siteowner2"
Get-SPWeb http://portal | Get-SPUserEffectivePermissions $user | Out-GridView -Title "Web Permissions for $user"

Running this command will generate a grid view as shown here:

image

Note that I could have just as easily saved the results to a CSV file which I could then open in Excel using the Export-Csv cmdlet:

Get-SPWeb http://portal | Get-SPUserEffectivePermissions $user | Export-Csv -NoTypeInformation -Path c:\perms.csv

For this next example I'm going to show the permissions for the same user for ALL webs throughout the entire farm (note that this won't include lists or items):

Get-SPSite -Limit All | Get-SPWeb | Get-SPUserEffectivePermissions $user | Out-GridView -Title "All Web Permissions for $user"

Now I want to get the permissions for the same user for all lists throughout the entire farm:

Get-SPSite -Limit All | Get-SPWeb | %{$_.Lists | Get-SPUserEffectivePermissions $user} | Out-GridView -Title "List Permissions for $user"

Now we're going to get nice and deep and show the permissions for every single item throughout the entire farm (probably don't want to run this on any front-end servers):

Get-SPSite -Limit All | Get-SPWeb | %{$_.Lists | %{$_.Items | Get-SPUserEffectivePermissions $user}} | Out-GridView -Title "Item Permissions for $user"

So now that I've shown you how to get the individual securable objects results throughout the farm for a single user let's now go ahead and stitch them together into one report:

Get-SPSite -Limit All | ForEach-Object {
    $site = $_
    $webPermissions += $site | Get-SPWeb –Limit All | Get-SPUserEffectivePermissions $user
    $listPermissions += $site | Get-SPWeb –Limit All | %{$_.Lists | Get-SPUserEffectivePermissions $user}
    $itemPermissions += $site | Get-SPWeb –Limit All | %{$_.Lists | %{$_.Items | Get-SPUserEffectivePermissions $user}}
    $site.Dispose();
}
$webPermissions + $listPermissions + $itemPermissions | Out-GridView -Title "Web, List, and Item Permissions for $user"

In this example I'm simply performing the same calls but appending to an array of objects and then dumping the combination of those arrays to the grid. Note that in this case I'm calling $site.Dispose() but below I'll be using the SPAssignmentCollection to dispose of objects - keep reading for an explanation.

So now lets take it one step further and see how we can get the same reports but this time for every user. We'll start with webs again - in this example we'll get the permissions for all users for a given site:

$gc = Start-SPAssignment
$site = $gc | Get-SPSite http://portal
$site | Get-SPWeb –Limit All | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName) | Out-GridView -Title "Web Permissions for All Users In $($site.Url)"
$gc | Stop-SPAssignment

As you can see I'm basically using the SiteUsers property from the root web and passing the login name for each user into the function. Note that here I'm using the Start-SPAssignment and Stop-SPAssignment cmdlets - that's because I'm using the SPSite object after the pipeline execution finishes (as opposed to the above) so I need to make sure it gets disposed (I could just as easily called Dispose on the object as I did above but I'm attempting to demonstrate when/why you'd use the assignment collections).

Now lets see the lists:

$gc = Start-SPAssignment
$site = $gc | Get-SPSite http://portal
$site | Get-SPWeb –Limit All | %{$_.Lists | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)} | Out-GridView -Title "List Permissions for All Users in $($site.Url)"
$gc | Stop-SPAssignment

Starting to see a pattern? Let's take a look at the list items now:

$gc = Start-SPAssignment
$site = $gc | Get-SPSite http://portal
$site | Get-SPWeb –Limit All | %{$_.Lists | %{$_.Items | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)}} | Out-GridView -Title "Item Permissions for All Users in $($site.Url)"
$gc | Stop-SPAssignment

Great! So now lets piece this last bit together so we can see the permissions for all webs, lists, and list items for every user within a single site collection:

$gc = Start-SPAssignment
$site = $gc | Get-SPSite http://portal
$webPermissions = $site | Get-SPWeb –Limit All | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)
$listPermissions = $site | Get-SPWeb –Limit All | %{$_.Lists | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)}
$itemPermissions = $site | Get-SPWeb –Limit All | %{$_.Lists | %{$_.Items | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)}}
$webPermissions + $listPermissions + $itemPermissions Out-GridView -Title "Web, List, and Item Permissions for All Users in $($site.Url)"
$gc | Stop-SPAssignment

Alright, we're almost done - let's now stitch this all together and generate a single report showing all permissions for all securable objects (webs, lists, and list items) for every user within every site collection:

Get-SPSite -Limit All | ForEach-Object {
    $site = $_
    $webPermissions += $site | Get-SPWeb –Limit All | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)
    $listPermissions += $site | Get-SPWeb –Limit All | %{$_.Lists | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)}
    $itemPermissions += $site | Get-SPWeb –Limit All | %{$_.Lists | %{$_.Items | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)}}
    $site.Dispose();
}
$webPermissions + $listPermissions + $itemPermissions | Out-GridView -Title "Web, List, and Item Permissions for All Users in All Sites"

Note in this last example, as I did previously when looping through all site collections, I'm calling the Dispose() method inside the ForEach-Object script block. I do this because objects wouldn't otherwise get disposed until the pipeline execution has finished and because it's continuing to iterate so the pipeline has not yet completed. If I used the assignment collection I wouldn't get a disposal until after I'm done iterating which would be too late - I want to dispose right when I'm done with the individual SPSite objects to avoid out of memory errors.

Reporting on who has access to what is one of the things I get asked about most frequently so hopefully this code sample and corresponding examples will prove to be useful to people. One possible area of improvement to the script would be to accommodate groups being passed in - right now I'm only considering users; and of course you could easily turn the example usages into functions. As always, if anyone has any feedback (bugs, improvements, etc.) please post here so that myself and others may benefit.

Comments (28) Trackbacks (3)
  1. Gary,
    This looks very good for 2010 which we are implementing at my company starting Jan 2011. However, could this powershell script be altered to do the same thing for MOSS 2007? Moreover, if you can get the security objects, could one also make this replace/copy the security (permissions)? For instance, johndoe needs all of janedoe’s permissions. If we know janedoe’s security with your cmdlet, we should be able to add or replace that security for johndoe, right?

    Why I ask…we have just combined several AD domains into one new AD domain and all of the users are being replicated to the new domain and they will still need their same SharePoint permissions. Because funds are tight (no 3rd party tool), I am trying to define a way to code/script the process of replacing or copying security for DOMAIN1\john to DOMAIN2\john on the same farm. I guess, the first thing I should ask is does that sound doable?
    Thanks in advance for you thoughts.

  2. The script could be modified to work with 2007. If you needed to export security settings and re-import I would do it a bit differently (see my gl-exportlistsecurity/gl-importlistsecurity commands for a starting place). That said for your specific need you may be able to just use the out of the box migrate user command: http://technet.microsoft.com/en-us/library/cc262141(office.12).aspx.

  3. Gary thanks for the tip. I went with the MigrateUser command. I thought based on the name that it implied moving users across farms or versions of farms. I never thought it would be migrate the user across domains. So I had not given it any more thought. The MigrateUser command is working perfectly so far.

    Thanks again.

  4. After importing the functions and trying to run the code for generating reports for:

    Get-SPSite -Limit All | ForEach-Object {
    $site = $_
    $webPermissions += $site | Get-SPWeb | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)
    $listPermissions += $site | Get-SPWeb | %{$_.Lists | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)}
    $itemPermissions += $site | Get-SPWeb | %{$_.Lists | %{$_.Items | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)}}
    $site.Dispose();
    }
    $webPermissions + $listPermissions + $itemPermissions | Out-GridView

    I receive an error: Exception calling “GetUserEffectivePermissionInfo” with “1″ argument9s): “Attempted to perform an unauthorized operation”
    $perminfo=$so.GetUserEffectivePermissionINfo <<<< ($loginName)

    Would you be able to provide me with some assistance with this error?

  5. this is Insane, in a good way.!
    Thanks just what i was looking for!

  6. How can I run a script for just a specific site or subsite?

  7. This is one of the best posts on the subject I have seen so thanks for that.

    I have one question though, is it possible to restrict this to output certain permissions only, specifically excluding READ and LIMITED ACCESS from the report.

    Our profiles database contains about 8,000 accounts all with read access or “Style Resource Reader” somewhere. This means that “$site.RootWeb.SiteUsers” is typically quite huge and the output not very helpful.

    I’m trying to run a report to list all webs and users with edit/owner permissions (full control/contribute). If I could get this to only show those two permissions I’m there I think.

    I want to something like this:
    $webPermissions = $site | Get-SPWeb –Limit All | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName) [*but don't output if is Read or Limited Access / Only output if full control or contribute*]]

    This may be a simple question but I am a bit of a powershell novice.

    Thanks again.
    Hugh

  8. It’s not very elegant but I achieved what I wanted. I added an if statement to the permissions for loop to flag items that include the full control permission. This flag is then used later to decide whether to add the role assignment to the hash table and output.

    if ($definition.Name -eq “Full Control”) {
    $permName += $definition.Name
    # Set flag to say found “Full Control” so add this item to the output
    $FullControlFound=”TRUE”
    }

    Thanks

  9. How can I limit this to a set selection of resources or subsites. Also is there a way to batch this out to call the initial sharepoint powershell and then to call an additional script from that? Right now I have a batch file that runs:

    C:\Windows\System32\WindowsPowerShell\v1.0\PowerShell.exe -NoExit ” & ‘ C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\CONFIG\POWERSHELL\Registration\\sharepoint.ps1 ‘ ”

    from there I run
    cd \
    . .\SecurityReport.ps1
    $gc = Start-SPAssignment
    $site = $gc | Get-SPSite http://portal/
    $site | Get-SPWeb –Limit All | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName) | Export-Csv c:\perms.csv
    $gc | Stop-SPAssignment

    which all works but I want to make it a single click for a non-powershell person and to limit which subsites are derived, http://portal/site1 and http://portal/site2 for example.

  10. First – thanks for this script. There looks to be a bug when determining the URL of a SPListItem. The line that reads “$resource = $so.ParentList.ParentWeb.Site.MakeFullUrl($so.Url)” does not work for items in nested webs. This is due to the $so.Url being a site-relative Url, not a server relative one (which is what “MakeFullUrl” expects. As a workaround, replace the line with ” $resource = $so["EncodedAbsUrl"] ” .

    Thanks again for this script. Saved quite a bit of time.

  11. Folders aren’t included in this. From the sp-list documentation: “The Items property returns all the files in a document library, including files in subfolders, but not the folders themselves. In a document library, folders are not considered items. “.

    So you need another line with
    $folderPermissions = $site | Get-SPWeb –Limit All | %{$_.Lists | %{$_.Folders | Get-SPUserEffectivePermissions ($site.RootWeb.SiteUsers | select LoginName)}}

    Add $folderPermissions to $itemPermissions and all the rest and you’re good.

  12. Nice script Gary! I tried running the “stitch” script on my test farm (~20 site collection, ~800 subsites, +500 mysites)… it never finished. I left it running over 24 hrs and still didn’t return anything. It works if I run the web or list only scripts.

    Any idea?

  13. Nice script..i am looking for doing the same thing but using c# language and not Powershell..how to convert these lines of code to c# ?…i want to show report on a sharepoint application page..thank you

  14. I’m totally new to powershell so this question might sound stupid, but do i have to enter a username or spsiteurl or something to get the maincode to work?

  15. Gary thanks for the script.

    I wanted to let you know that someone did make a copy of entire post on CodePlex without any credit given to you. Here is the CodePlex URL https://sp2010userperm.codeplex.com.

    I have left appropriate review on CodePlex hope the author realize this and give enough credit to you.

    • Thanks for the heads up. I’ve sent the guy an email and hopefully he does the right thing. Unfortunately codeplex’s process for getting copyright violators shut down is less than ideal so I’m hoping I don’t have to go that route.


Leave a comment

CAPTCHA Image

*