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

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.

August 8, 2024

Resolving Power Automate Flow Trigger Issues when Migrating Items to a SharePoint List

Introduction:

Automation in SharePoint using Power Automate can significantly streamline processes and enhance productivity. However, certain scenarios, such as bulk item migrations, can lead to unintended behaviors. This blog post will share a recent problem encountered with an automated flow triggered by item creation in a SharePoint list. We will also walk you through the steps we took to resolve this issue effectively.

Problem:

We had set up an automated flow in Power Automate that trigger whenever a new item is created in a SharePoint list. Before migrating many items, we turned off this flow to prevent it from triggering for each migrated item. After completing the migration and reactivating the flow, we observed an unexpected behavior, the flow triggered for all the newly migrated items due to data stored in the browser cache. This resulted in uneven and unwanted executions of the flow.

Steps to the solution:

To avoid this issue in future migrations, we followed an approach that ensures the flow does not trigger unintentionally during bulk item migrations. Here are the steps:


Step 1: Export the Flow

Before starting the migration process, export the existing flow to your local machine. This step ensures that you have a backup of the flow configuration and can easily restore it later.

  1. Open Power Automate.
  2. Navigate to the flow which you want to export.
  3. Select the flow and open it.
  4. Select "Export", then click on the "Package (.zip)"
  5. In the opened "Package details" section, Give the Name of the package.
  6. Click on the exported .zip file and download it to your computer.

     



Step 2: Delete the Existing Flow

With the flow exported and safely stored, proceed to delete the existing flow. This step prevents any triggers from occurring during the migration process.
  1. Navigate to the flow which you want to delete.
  2. Click on “Delete” and it will ask for confirmation. 


Step 3: Migrate the Items

Now that the flow has been deleted, proceed with migrating the items to the SharePoint list. Since the flow is not present, it will not trigger during this process.


Step 4: Recreate the Flow by Importing the Zip File

After completing the migration, recreate the flow by importing the previously exported .zip file. This ensures that the flow is restored to its original configuration.
  1. Navigate to the location where you want to Import.
  2. Click on "Import" -> Import Package (Legacy).
  3. Upload the .zip file that you exported earlier; it will take some time to complete the process.
  4. In Review Package Content – click on the "Update" link from IMPORT SETUP.
  5. It will open an "Import setup" panel, in the Setup field we need to select the "Create as new" option and then click on the Save button.
  6. Click on the Import button. [Note: It will take some time to Import the file, so please do not navigate away]
  7. Once the Import is completed then you will get a successfully imported message.


Step 5: Verify and Reactivate the Flow Once the flow is successfully imported, verify its configuration to ensure everything is set up correctly. Then, reactivate the flow.
  1. Locate the newly imported flow.
  2. Ensure the configuration is correct and click on "Turn on" to reactivate it. 

Conclusion:

By following these steps, you can effectively manage your automated flows in Power Automate and avoid unintended triggers during bulk item migrations. Exporting and deleting the flow before migration, and then recreating it afterward, ensures that your automation behaves as expected and maintains data integrity. This method provides a reliable solution to prevent the issues caused by browser cache and uneven flow triggering.

Implementing this approach will save you time and effort, ensuring a smooth migration process without disrupting your automated workflows.






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