Dec 08, 2016

Cross SharePoint platform Maintenance Tools using PnP PowerShell

Hi guys,

As a SharePoint consultant, I have often been asked to achieve some boring and repetitive maintenance tasks.

"I want to move all of these 100 documents from this site to that one"

or

"I absolutely want the default value of this field I just created to be replicated on the all the  100 already existing documents in my library"

or

"I changed the default content type of this library, I do not want it to be 'Document' but 'My Super Document' with some uselessful fields in it. I WANT to remove the "Document" content type from the list but I can't since it is still used by the 100 documents in the library"

We will take this last one as our case study, later in this post. If you try to achieve one of those three ONLY with the SharePoint standard user interface. It is possible, but as fast as you can go, I think you have at least for a few hours of work. Imagine if this number of 100 documents was actually 10000?!

As a developer, I am lazy and proud to be so, There must be a way to automate this kind of task.

Use third-party tool

For content migration and some other management tasks, you can use some really great tools like ShareGate. But customers that already pay for servers, SQL Server and SharePoint licenses, and the consultants to help them, sometimes, they are pretty reticent to buy other tools licenses...

Script your own maintenance tasks

powershell

In my developer mind, it then becomes actually a challenge to convert this boring maintenance job into "Develop something to help me doing the job". Moreover, some power users or IT Pros might be able to reuse your scripts later. Whenever I code something, I tend to (over-)split it into reusable and generic parts, sometines I never re-use them... but you never know, one day, maybe...

With this in mind, if I come up with a maintenance task script for a SharePoint OnPrem, I don't want to have to rewrite it a few months later when I want to run the script against SharePoint Online. So let's use what's usable on both platforms: SharePoint PnP PowerShell.

As already mentioned in one of my previous posts, it is a set of Cmdlets that make use of the SharePoint Client Side Object Model (CSOM) to perform tasks. You have .msi packages here (one for each current platform). In the past, with SharePoint 2010 and early 2013, I used to write a lot of automated tasks with the Server-side (SSOM) PowerShell, and the PnP commands makes it very similar to what I used and even easier in some cases. Behind the scenes, it is "just" a wrapper on the PnP Core library features

The case study

Let's take the third real life (almost) case mentioned above. Migrate all items from a Content Type to another one. It is especially useful when you want to change the default content type which is used by all the existing items.

The maintenance task implementation

The scope will target a single list only, it could easily be adapted or re-used to target a set of lists, sites,... The implementation makes intense use of the PnP PowerShell kit, you need to have it installed on your computer to run the following script The Input is then :

  • The target list
  • The source content type
  • The target content type

For simplicity, I will use here the name of the content types as arguments to my script

[CmdletBinding()\] 
Param ( 
  \[Parameter(Mandatory=$True)\] 
  \[string\]$List, 
  \[Parameter(Mandatory=$True)\] 
  \[string\]$SourceContentType, 
  \[Parameter(Mandatory=$True)\] 
  \[string\]$TargetContentType )

Load the target list object and its necessary properties

 # Instantiate the list object 
 $ListObj = Get-PnPList -Identity $List 
 If (!$ListObj) { 
   Throw "The target list cannot be found" 
  } 
  
  Get-PnPProperty -ClientObject $ListObj -Property Title,ContentTypes

Load the source content type object and its necessary properties

  # Instantiate the source content type object 
  $SourceContentTypeObj = Get-PnPContentType -List $ListObj | ? {$_.Name -eq $SourceContentType} 
  If (!$SourceContentTypeObj) { 
    Throw "The specified content type cannot be found in the list" 
  } 
  
  # Ensure the source content type name and id 
  Get-PnPProperty -ClientObject $SourceContentTypeObj -Property Name,Id 

Try to retrieve the target content type from the target list, if not found, try to find it in the site collection and add it to the list

   # Try to retrieve the target content type from the target list 
   $TargetContentTypeObj = Get-PnPContentType -List $ListObj | ?{$_.Name -eq $TargetContentType} 
   # If the target content type does not already exist in the target list, add it 
   If (!$TargetContentTypeObj) { 
     # Retrieve it from the available content types 
     $TargetContentTypeObj = Get-PnPContentType -InSiteHierarchy | ?{$_.Name -eq $TargetContentType} 
     If (!$TargetContentTypeObj) { 
       Throw "The specified content type does not exist either in list or in site collection" 
      } 
      # Add the content type to the target list 
      Add-PnPContentTypeToList -List $ListObj -ContentType $TargetContentTypeObj 
    } 
we need to retrieve all the target items (All the items that have the specified source content type). If no items are found, the script terminates. 
    # Get all items of the target list having the source content type 
    $camlQuery = $("<View> <Query> <Where> <Eq> <FieldRef Name='ContentType'/> <Value Type='Computed'>$SourceContentType</Value> </Eq> </Where> </Query> <ViewFields> <FieldRef Name='Id'/> <FieldRef Name='Title'/> <FieldRef Name='FileLeafRef'/> </ViewFields> </View>")
    
    $itemsToUpdate = Get-PnPListItem -List $ListObj -Query $camlQuery $total = $itemsToUpdate.Count 
    if ($total -eq 0) { 
      Write-Host "No items to migrate" 
      Return 
    } 

We go through the list of retrieved items and call the Set-PnPListItem cmdlet that allows to set the values for the properties of the specified list item (The rest of the code is for displaying a progress bar using Write-Progress built in cmdlet)

$progressStep = 100/$total 
For ($i = 0; $i -lt $total; $i++) { 
  $item = $itemsToUpdate\[$i\] 
  $title = if ($item.Title) {$item.Title} else {$item\["FileLeafRef"\]} 
  $itemInfo = "$title \[$($item.Id)\]" 
  $currentProgress = ($i+1)*$progressStep 
  Try { 
    $dummy = Set-PnPListItem -List $ListObj -Identity $item -ContentType $TargetContentTypeObj 
    Write-Progress -Activity "Migrating Content Types" -Status "Content type of item $itemInfo has been migrated" -PercentComplete $currentProgress 
  } Catch { 
    Write-Progress -Activity "Migrating Content Types" -Status "Content type of item $itemInfo has not been migrated" -PercentComplete $currentProgress 
    Write-Warning "Item $itemInfo cannot be fully updated." 
    Write-Error $_.Error.Message 
    Write-Error $_.Error.StackTrace 
  } 
} 
Write-Progress -Activity "Migrating Content Types" -Status "Operation complete" -PercentComplete 1

Here is what it looks like ;) running_migratecontenttypes

You can find the entire .ps1 one here

As usual, I hope this will help you and give you ideas to write your own maintenance scripts and have fun with tasks that are usually boring Leave your comments !

See ya !

Yannick

Other posts