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.

No comments:

Post a Comment