Showing posts with label PnP PowerShell. Show all posts
Showing posts with label PnP PowerShell. Show all posts

Wednesday, February 27, 2019

Different ways to apply SharePoint field customizer extension to existing multiple columns


SharePoint Extensions can be deployed to SharePoint Online, and you can use modern JavaScript tools and libraries to build them. The Field Customizer Extension allows modifying the Views of the field in a list view. It can be used to override the field representation in the list. Field Customizer can be used with site columns or directly to the field inside a list. Microsoftarticle shows the detailed step to create the Field Customizer Extension but with limited instruction to apply it to existing list columns. Here are multiple ways to apply Field Customizer Extension to existing list columns.

The way to do this is to ClientSiteComponentId GUID attribute of Field Customizer for the existing field. There are few ways to implement this.

1. The first way is to use CSOM PowerShell to update ClientSiteComponentId attribute.

$siteURL = "https://mycompany.sharepoint.com/sites/Harry-PM-Testing"

$userId = "admin@mycompany.onmicrosoft.com"
$pwd = Read-Host -Prompt "Please enter your password" -AsSecureString
$creds = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($userId,$pwd)

$listTitle = "Order"
$existingColumnName = "SPFxExeColumn"
$ClientSideComponentId = "acd4d36c-a4ad-123c-a123-93862bd52123"

$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)
$ctx.Credentials = $creds 

try{
    $lists = $ctx.web.Lists
    $list = $lists.GetByTitle($listTitle)
    $listItems = $list.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
    $ctx.load($listItems)
    $ctx.load($list.Fields)
    $ctx.executeQuery()

    $field = $list.Fields.GetByInternalNameOrTitle($existingColumnName)
    $guid = [GUID]$ClientSideComponentId
    $field.ClientSideComponentId=$guid
    $field.update()
    $ctx.Load($field)
    $ctx.executeQuery()

}
catch{
    write-host "$($_.Exception.Message)" - foregroundcolore red
}   


2. The second is to use REST API like mentioned here.

3. The third easiest way is to use the PnP PowerShell like below.

$siteURL = "https://ionisdevops.sharepoint.com/sites/Harry-PM-Testing"

$listTitle = "Order"
$existingColumnName = "SPFxExeColumn"
$ClientSideComponentId = "acd4d36c-a4ad-449c-a312-93862bd52376"
  
Connect-PnPOnline -Url $siteurl  

$guid = [GUID]$ClientSideComponentId
Set-PnPField -List $listTitle -Identity $existingColumnName -Values @{ClientSideComponentId=$guid} -UpdateExistingLists

You can apply the same Field Customizer to as many existing fields as you like as long as the field has all the required schema as the the original list. The fields do not need to be the same type like "Number" since it will not matter.


If you need to apply other SPFx extension like custom action to whole tenant, please refer to other blog.




Tuesday, February 5, 2019

How to fix SharePoint 2013 workflow missing issue on SharePoint online site?

If users could not find the out of box SharePoint 2013 workflow on SharePoint online site, here are two different ways to verify and fix the issue.

The first way is to click "Setting"->"Workflow Settings"->"Work Flows"->"Workflow Health" and then activate "The workflow service store feature" as described here.

If you would to fix on multiple site collections, here are the PowerShell script you can run against the site.

1. Install SharePoint online PnP PowerShell package.

Install-Module SharePointPnPPowerShellOnline

2. Check the WorkflowServiceStore site collection feature

Connect-PnPOnline -Url <Site URL>
Get-PnPFeature -Identity 2c63df2b-ceab-42c6-aeff-b3968162d4b1

3. Enable the WorkflowServiceStore site collection feature

Enable-PnPFeature -Identity 2c63df2b-ceab-42c6-aeff-b3968162d4b1 -Force

That's it.

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.