Showing posts with label SharePoint Permissions. Show all posts
Showing posts with label SharePoint Permissions. Show all posts

March 3, 2026

How to Audit Specific User Permissions Across All SharePoint Online Sites Using PowerShell

Introduction

Managing user access in SharePoint Online can be challenging – especially when you want to audit permissions for specific users across all site collections.

We use a PowerShell script:

  • Retrieves permissions for multiple users
  • Identifies direct and group-based permissions
  • Scan all accessible SharePoint sites
  • Exports results to an Excel (.xlsx) report

We will guide you step by step on how to configure and run the script.

Overview of the blog in image - Managing SharePoint Online User Permissions with PowerShell

Pre-Requisites:

  1. Admin Credentials:

    You must login using a SharePoint Administrator account.

  2. Azure AD Application Details

    Collect the following values from the Azure AD App Registration:

    • Client ID
    • Tenant ID
  3. Permission to All SharePoint Sites

    Only site you can access will return data.

  4. Users.csv File

    The file contains the list of user email addresses you want to check

    Example Users.csv

    User Email csv file image
  5. Script and CSV File in the Same Folder.

    • GetSpecificUsersPermission.ps1
    • Users.csv

Configure the Script:

  • Open the GetSpecificUsersPermission.ps1 file and update:
  • AdminSiteURL → your admin URL
  • https://yourtenant-admin.sharepoint.com
  • Client ID → your registered application’s Client ID
  • Tenant ID → your Azure AD Tenant ID
  • CSV file name if you changed it.

PowerShell Script: GetSpecificUsersPermission.ps1


# Parameters
$AdminSiteURL = "https://yourtenant-admin.sharepoint.com"
$ClientId = "ClientId"
$TenantId = "TenantId"
$usersCSV = "users.csv"

# Get the folder where this script is located
$scriptFolder = $PSScriptRoot

$ReportOutput = Join-Path -Path $scriptFolder -ChildPath "SpecificUsersPermissionReport.csv"

$UsersCsvPath = Join-Path -Path $scriptFolder -ChildPath $usersCSV

Write-Host "CSV will be saved to: $ReportOutput"
Write-Host "Reading users from: $UsersCsvPath"

$UsersToCheck = Import-Csv -Path $UsersCsvPath | Select-Object -ExpandProperty UserEmail

# Connect to Admin Center
$AdminConnection = Connect-PnPOnline -Url $AdminSiteURL -ClientId $ClientId `
-Tenant $TenantId -Interactive -ReturnConnection

# Get all site collections
$Sites = Get-PnPTenantSite -Connection $AdminConnection

$Results = @()

foreach ($Site in $Sites) {
	Write-Host "Processing site: $($Site.Url)" -ForegroundColor Cyan

	# Connect to the actual site
	$SiteConnection = Connect-PnPOnline -Url $Site.Url -ClientId $ClientId `
	-Tenant $TenantId

	# Get the root web and role assignments
	$Web = Get-PnPWeb -Connection $SiteConnection -Includes `
	RoleAssignments, HasUniqueRoleAssignments

	Get-PnPProperty -ClientObject $Web -Property RoleAssignments

	foreach ($RoleAssignment in $Web.RoleAssignments) {

		Get-PnPProperty -ClientObject $RoleAssignment -Property `
		RoleDefinitionBindings, Member

		# Direct user
		if ($RoleAssignment.Member.PrincipalType -eq "User") {

			$UserEmail = ($RoleAssignment.Member.LoginName -split '\|')[-1]

			if ($UsersToCheck -contains $UserEmail) {

				$Results += [PSCustomObject]@{
					SiteURL           = $Site.Url
					UserOrGroupName   = $RoleAssignment.Member.Title
					Type              = "Direct User"
					PermissionLevels  = ($RoleAssignment.RoleDefinitionBindings |
										  Select -ExpandProperty Name) -join ", "
				}
			}
		}

		# SharePoint Group
		elseif ($RoleAssignment.Member.PrincipalType -eq "SharePointGroup") {

			try {
				$Group = Get-PnPGroup -Identity $RoleAssignment.Member.Title `
				-Includes Users -Connection $SiteConnection

				foreach ($User in $Group.Users) {

					$UserEmail = ($User.LoginName -split '\|')[-1]

					if ($UsersToCheck -contains $UserEmail) {

						$Results += [PSCustomObject]@{
							SiteURL           = $Site.Url
							UserOrGroupName   = $User.Title
							Type              = "User (via Group: $($RoleAssignment.Member.Title))"
							PermissionLevels  = ($RoleAssignment.RoleDefinitionBindings |
												  Select -ExpandProperty Name) -join ", "
						}
					}
				}
			}
			catch {
				Write-Warning "Cannot access group $($RoleAssignment.Member.Title) in site $($Site.Url). Skipping."
			}
			finally {
				if ($SiteConnection) {
					Disconnect-PnPOnline -Connection $SiteConnection
				}
			}
		}
	}
}

if ($AdminConnection) {
	Disconnect-PnPOnline
}

# Export results to CSV
$Results | Export-Csv -Path $ReportOutput -NoTypeInformation

Write-Host "Report generated successfully at $ReportOutput"
	

How to Run the Script:

  1. Open PowerShell
  2. Navigate to your script folder:

    Cd “C:\YourFolder”

  3. Run the script:

    .\GetSpecificUsersPermission.ps1

  4. Enter your admin credentials
  5. Complete browser authentication
  6. Wait for the report to be generated

Output Report:

The script generates an Excel report containing:

  • Site URL
  • User Email
  • Direct Permissions
  • Group memberships
  • Role definitions
  • Permission levels

You will find the .xlsx report in the same folder as the script

Conclusion:

This approach gives SharePoint administrators a quick and efficient way to:

  • Audit Permissions.
  • Verify access.
  • View direct & group permissions.
  • Export clean reports for governance.
  • Just update the script, run it and your Excel report is ready.

If you have any questions you can reach out our SharePoint Consulting team here .

April 17, 2025

Custom PowerShell Script to Copy Site Groups, Permissions, and Settings Without Migration Tool

Introduction 

Migrating permissions, site groups, and settings from a SharePoint Hub Site to an associated site is a common task, especially in large organizations. While available migration tools can make this easy, they often come at a cost. 

In this blog post, we’ll walk through a custom PowerShell script that automates this process without using any migration tool.  

What This Script Does 

  • Connects to both Hub Site and Associated Site 
  • Copies custom permission levels 
  • Copies site groups with users 
  • Applies appropriate role assignments 
  • Updates sharing capabilities 
  • Streamlines migration process for SharePoint Online environments 

Script Parameters 

Param ( 
    [string] $ClientId = $(Throw "Please provide ClientId"), 
    [string] $HubSiteURL = $(Throw "Please provide HubSiteURL"), 
    [string] $AdminSiteURL = $(Throw "Please provide AdminSiteURL"), 
    [string] $ClientSecret = $(Throw "Please provide ClientSecret"), 
    [string] $AssociatedSiteURL = $(Throw "Please provide Associated Site URL") 
) 

You’ll need to register an Azure AD App with appropriate SharePoint API permissions and provide its ClientId and ClientSecret. This ensures secure authentication without using stored credentials. 

Authentication and Setup 

$HubSiteConnection = Connect-PnPOnline -Url $HubSiteURL -ClientId $ClientId -ClientSecret $ClientSecret -ReturnConnection 
$AssociatedSiteConnection = Connect-PnPOnline -Url $AssociatedSiteURL -ClientId $ClientId -ClientSecret $ClientSecret -ReturnConnection

This part of the script connects to both the Hub Site and the Associated Site using PnP PowerShell, returning secure connections for use in the following functions. 

Updating Sharing Capability 

function Update-ExternalSharing { 
    param ( 
        [string] $AssociatedSiteURL, 
        [string] $AdminSiteURL 
    ) 
 
    Connect-PnPOnline -Url $AdminSiteURL -ClientId $ClientId -ClientSecret $ClientSecret 
    Set-PnPTenantSite -Url $AssociatedSiteURL -SharingCapability ExternalUserSharingOnly 
}
This function sets the sharing capability of the associated site to allow external users to access the site — but only if they are authenticated. This is done by updating the SharingCapability property of the site to ExternalUserSharingOnly. 

Copying Permission Levels 

function Copy-PermissionLevels { 
    param ( 
        $HubSiteConnection, 
        $AssociatedSiteConnection 
    ) 
 
    $AllPermissionLevels = Get-PnPRoleDefinition -Connection $HubSiteConnection 
 
    foreach ($PermissionLevel in $AllPermissionLevels) { 
        if (-not $PermissionLevel.Hidden) { 
            $ExistingPermission = Get-PnPRoleDefinition -Identity $PermissionLevel.Name -Connection $AssociatedSiteConnection -ErrorAction SilentlyContinue 
            if (!$ExistingPermission) { 
                $selectedPermissions = New-Object Microsoft.SharePoint.Client.BasePermissions 
                [Enum]::GetValues([Microsoft.SharePoint.Client.PermissionKind]) | ForEach-Object { 
                    if ($PermissionLevel.BasePermissions.Has($_)) { 
                        $selectedPermissions.Set($_) 
                    } 
                } 
 
                $newRole = Add-PnPRoleDefinition -RoleName $PermissionLevel.Name -Description $PermissionLevel.Description -Connection $AssociatedSiteConnection 
                $newRole.BasePermissions = $selectedPermissions 
                $newRole.Update() 
                Invoke-PnPQuery -Connection $AssociatedSiteConnection 
            } 
        } 
    } 
} 
This function copies non-hidden permission levels from the hub site to the associated site. It checks if the permission already exists to avoid duplication and applies the same Base Permissions.  Copying Groups and Users 
function Copy-HubSiteGroups { 
    param ( 
        $HubSiteName, 
        $AssociatedSiteName, 
        $HubSiteConnection, 
        $AssociatedSiteConnection 
    ) 
 
    $GroupMappings = @( 
        @{ Source = "$HubSiteName Owners"; Destination = "$AssociatedSiteName Owners" }, 
        @{ Source = "$HubSiteName Members"; Destination = "$AssociatedSiteName Members" }, 
        @{ Source = "$HubSiteName Visitors"; Destination = "$AssociatedSiteName Visitors" } 
    ) 
 
    foreach ($Mapping in $GroupMappings) { 
        $SourceGroup = Get-PnPGroup -Identity $Mapping.Source -Connection $HubSiteConnection 
        $DestinationGroup = Get-PnPGroup -Identity $Mapping.Destination -Connection $AssociatedSiteConnection -ErrorAction SilentlyContinue 
 
        if (-not $DestinationGroup) { 
            New-PnPGroup -Title $Mapping.Destination -Connection $AssociatedSiteConnection 
        } 
 
        $SourceGroup.Users | ForEach-Object { 
            $LoginName = if ($_ -like "*#ext#*") { $_.Email } else { $_.LoginName } 
            Add-PnPGroupMember -Group $Mapping.Destination -LoginName $LoginName -Connection $AssociatedSiteConnection 
        } 
    } 
} 

This function replicates SharePoint default groups (Owners, Members, Visitors) from the hub to the associated site. It handles both internal and external users and ensures group membership is preserved. 

Main Function

To simplify script execution, we can wrap all functional calls inside a Main function:

function Main {
    # Connect to the Hub Site and Associated Site using PnP PowerShell
    $HubSiteConnection = Connect-PnPOnline -Url $HubSiteURL -ClientId $ClientId -ClientSecret $ClientSecret -ReturnConnection
    $AssociatedSiteConnection = Connect-PnPOnline -Url $AssociatedSiteURL -ClientId $ClientId -ClientSecret $ClientSecret -ReturnConnection

    # Update external sharing settings before proceeding
    Update-ExternalSharing -AdminSiteURL $AdminSiteURL -AssociatedSiteURL $AssociatedSiteURL

    # Copy custom permission levels from Hub Site to Associated Site
    Copy-PermissionLevels -HubSiteConnection $HubSiteConnection -AssociatedSiteConnection $AssociatedSiteConnection

    # Extract names of sites to match default group names
    $HubSiteName = ($HubSiteURL -split "/")[-1]
    $AssociatedSiteName = ($AssociatedSiteURL -split "/")[-1]

    # Copy site groups and their users
    Copy-HubSiteGroups -HubSiteName $HubSiteName -AssociatedSiteName $AssociatedSiteName -HubSiteConnection $HubSiteConnection -AssociatedSiteConnection $AssociatedSiteConnection
}

Main
Running the Script To run this PowerShell script, first make sure you’ve saved it with a .ps1 extension — for example, name it CopyGroupsAndPermissions.ps1. 

Once saved, navigate to the directory where the script is stored using PowerShell and run the following command (make sure to replace the placeholder values with your actual credentials and URLs): 

./CopyGroupsAndPermissions.ps1 ` 
    -ClientId "xxxx-xxxx-xxxx-xxxx" ` 
    -HubSiteURL "https://yourtenant.sharepoint.com/sites/hubsite" ` 
    -ClientSecret "your-client-secret" ` 
    -AdminSiteURL "https://yourtenant-admin.sharepoint.com" ` 
    -AssociatedSiteURL "https://yourtenant.sharepoint.com/sites/associatedsite" 
  

Final Thoughts 

This script is ideal for scenarios where: 

  • You need to replicate security and structure across multiple sites 
  • You want to avoid third-party tools like ShareGate 

If you're working on managing large SharePoint environments with many associated sites, this can save time and reduce manual effort.

If you have any questions you can reach out our SharePoint Consulting team here.