Friday, January 25, 2019

SharePoint online site migration process across different tenants with both internal users and external users


Recently we have few requests to migrate SharePoint online site from one tenant to another and keep the same permission assignment. The challenge part is the site owners have invited many external users and we would need to keep the content with same permissions. There are seven major steps and here is the detailed procedures with some automation.





1. Original site analysis and report. This will include the following details.
    • Identify the site template - we need to create the destination site with same site template
    • Identify all the security internal AD groups used - we need to create same security groups in destination and add all the internal users
    • Identify all internal users - we need to create them in destination AD if not already and add to site
    • Identify all external users - we need to "add" them to destination site
    • Identify all breaking permission inheritance using Sharegate
Here I'll focus on the last part to get all external users as automated Powershell script to avoid tedious manual process. Other steps I'll provide some automation and tips in separate blogs. Here is the PowerShell to get all external users Email into a file so it can be used for next steps.

# GetUsersPnP.ps1   - Get all external users from a SPO site when passing site url
# $siteUrl                  -SPO site collection url
# $outputFileName  - Output file w/ all external users

param([string]$siteUrl,[string]$outputFileName)

Set-ExecutionPolicy -Scope Process -Confirm:$False -ExecutionPolicy Bypass -Force
Connect-PnPOnline -Url $siteUrl -Credentials (Get-Credential)

$exeUsers = Get-PnPUser |? LoginName -like  "*EXT*"
# If you remove the query, you will get all users on the site and handle internal users in destination site

$outputFileName = ".\Output\" + $outputFileName
$outputFile = $myinvocation.mycommand.path.Replace($MyInvocation.MyCommand.name,"") + $outputFileName

$rows = @()
foreach($exeUser in $exeUsers)
{
    $rows += New-Object -TypeName PSObject -Property @{                                                                                                                       
                                            LoginName = $exeUser.LoginName
                                            Email = $exeUser.Email                             
                                            } | Select-Object LoginName,Email
}

$rows | Export-Csv $outputFile  -NoTypeInformation -Force  -ErrorAction SilentlyContinue


2. Prepare destination site. This will include the following details.
    • Create site collection using the template from step #1
    • Create AD groups with same name as in step #1 and sync to Azure AD in destination
    • Create internal users in destination and add them to the AD groups using by the site
    • Add the security group to the site - this is optional but would be safe to ensure to migrate the groups

You could use PowerShell scripts to automate these steps. I can provide some of them in the future blog.

3. Invite external users from destination site. The purpose is to get all external users to the destination site so the migration will find the external users and migrate the permissions correctly. Since there is no easy to add all external users to the destination tenant and site, you could use the following PowerShell to invite external users and monitor if them accepted. 

The script will use the external user report generated from previous step #1.

# InviteExeUsersfromCSV.ps1 - Invite external users for a SPO site 
# $siteUrl                                  - SPO site collection url
# $outputFileName                   - Output file w/ all external users

# Input Arguments
param([string]$siteUrl,[string]$inputFileName)

Set-ExecutionPolicy -Scope Process -Confirm:$False -ExecutionPolicy Bypass -Force
Add-Type -Path ".\Assembly\Microsoft.SharePoint.Client.dll"
Add-Type -Path ".\Assembly\Microsoft.SharePoint.Client.Runtime.dll"

$inputFileName = ".\Input\" + $inputFileName
$CSVFile = $myinvocation.mycommand.path.Replace($MyInvocation.MyCommand.name,"") + $inputFileName
$users = Import-Csv $CSVFile

if ($users -eq $null) {
  write-host "User invitation file content is empty!"
  return
}

$userName = Read-Host -Prompt "Please enter your user name"
$Secure_String_Pwd = Read-Host -Prompt "Please enter your password" -AsSecureString
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($userName, $Secure_String_Pwd)
$ctx.Credentials = $credentials
# Create request list
$userList = New-Object "System.Collections.Generic.List``1[Microsoft.SharePoint.Client.Sharing.UserRoleAssignment]"

# For each user, set role
ForEach($user in $users)
{
    $userRoleAssignment = New-Object Microsoft.SharePoint.Client.Sharing.UserRoleAssignment
    $userRoleAssignment.UserId = $user.Email
    #$userRoleAssignment.Role = [Microsoft.SharePoint.Client.Sharing.Role]::None
    $userRoleAssignment.Role = [Microsoft.SharePoint.Client.Sharing.Role]::View
    $userList.Add($userRoleAssignment)
    Write-Host "Request for user " $user.Email " to site - " $siteUrl
}

try
{
    # Send invites
    $message = "Please accept this invite to our SharePoint site. Thanks!"
    $res =[Microsoft.SharePoint.Client.Sharing.WebSharingManager]::UpdateWebSharingInformation($ctx, $ctx.Web, $userList, $true, $message, $true, $true)
    $ctx.ExecuteQuery()
    $Success = $res.Status
    $StatusMessage = $res.message
    Write-Host "Send invite Status " $Success
}
catch
{
    Write-Host "Error to invite users"
    $hasError = ($error[0] | out-string)
}

4. Finalize destination site. This will include the following steps.
    • Identify remaining external users who have not accepted the invitation
    • Identify the using mapping for migration
    • Add migration system account as site collection in destination site

You could set the invitation expiration days to 10 days for example (default is 90 days). After 10 days, identify any external users who have not accepted the invitation. You could use the following script similar to the one used step #1 run against destination site. The new script will report external users with flag "UserJoined" as "Y" or "N". 

You can compare and identify those users who have not accepted the invitation. You could either re-invite or map to replace them as either owner or migration system account during Sharegate migration.


# Input Arguments
param([string]$siteUrl,[string]$outputFileName, [string]$inputFileName)


################################################################################
#Set of test input data
#$siteUrl = "https://mycompany.sharepoint.com/sites/testsite"
#$outputFileName = ".\Output\NewExeUseresPnP.csv"
#$inputFileName = ".\Input\ExeUseresPnP.csv"
################################################################################

Set-ExecutionPolicy -Scope Process -Confirm:$False -ExecutionPolicy Bypass -Force
Connect-PnPOnline -Url $siteUrl -Credentials (Get-Credential)

$exeUsers = Get-PnPUser |? LoginName -like  "*EXT*"

$outputFileName = ".\Output\" + $outputFileName
$outputFile = $myinvocation.mycommand.path.Replace($MyInvocation.MyCommand.name,"") + $outputFileName


$inputFileName = ".\Input\" + $inputFileName
$inputFile = $myinvocation.mycommand.path.Replace($MyInvocation.MyCommand.name,"") + $inputFileName
$invitedUsers = Import-Csv $inputFile
$invitedUsers
$rows = @()

ForEach($user in $invitedUsers)
{
    $userJoined ="N"
    foreach($exeUser in $exeUsers)
    {
         if($exeUser.Email -eq $user.Email){
            $userJoined ="Y"
         }  
    }

    $newLoginName = "N/A"
    if($userJoined -eq "Y"){
        $newLoginName = $exeUser.LoginName
    }

    $rows += New-Object -TypeName PSObject -Property @{                                                                                                                              

                                                LoginName = $user.LoginName
                                                Email = $user.Email                                          
                                                UserJoined = $userJoined
                                                NewLoginName = $newLoginName
                                                } | Select-Object LoginName,Email,UserJoined, NewLoginName
}

$rows | Export-Csv $outputFile  -NoTypeInformation -Force  -ErrorAction SilentlyContinue

Write-host "Completed the site user report in " $outputFile -foregroundcolor black -backgroundcolor green


5. Content migration w/ permissions and user mapping. This will include the following steps.
    • Sharegate migration or other tools
    • Migrate whole site collection with permission mappping in previous step

 6. Make original site read only.
After content migrated, we should make the original site ready only so no updates will be made to the site. The easy way to do this is to apply the site policy as described here. Here are detailed steps.
    • Activate Site Policy site collection feature
    • Create one Site Policy and select "The site collection will be ready only when it closed"
    • In Site Closure and Deletion, associate the policy
    • In Site Closure and Deletion, select "Close this site now"
Your site will be ready only like below and you could archive or delete in the future. You could also add redirect if you need.




7. Clean up destination site and UAT. Here are the steps.
    • Make a backup of the site
    • Delete external users that should not be in the visitor group
    • Remove migration system account as site collection admin
    • Complete UAT
    • Remove the backup
    • Set up redirect from original site
    • Archive or delete original site

After content migrated, it's better to remove the external users invited to the SharePoint groups. In this case, all of them are invited to visitor group. Since we normally invite to the individual library, folder, or file, they should not have all view permission to whole site. This final step is to verify original site any external should not be in the visitor group, then remove them form the destination site.

Now you should have the site migrated to another tenant with permission mapped as original site.


No comments:

Post a Comment