I was doing some pre-migration activities, removing Content Types and un-used lists from SharePoint Online. One of the sites had a lot of lists / libraries and subsites, and I couldn't track down where a particular content type was "in use". So I had a look around to see if I could build something to do this work for me.
In On Prem SharePoint the Server Object Model (SOM) had a SPContentTypeUsage class. Unfortunately, for SharePoint Online its not available in the Client Side Object Model (CSOM).
So I decided to build a script to do the following:
- Connect to a Site Collection
- Examine all the Lists and see if a particular Content Type was associated
- Recurse through all the sub webs.
while it sounds like the script would be doing a lot of work, it only took a few seconds to check around 20 webs, and in the output I found exactly where my CT was being used.
I built the script to work in my office365-devscripts on github library. The library has some baked in goodness that encrypts passwords, downloads CSOM from nuget, manages connections and avoids bloating the scripts with boiler plate code.
Here's the PowerShell script. It won't run as is, as it depends on some modules, but you can download the source from github, or run a `git clone https://github.com/TjWheeler/office365-devscripts'.
PowerShell - Get-ContentTypeUsages.ps1
#Script: Get-ContentTypeUsages.ps1 https://github.com/TjWheeler/office365-devscripts
#Author: Tim Wheeler (http://timwheeler.io)
#Version: 0.3
#Purpose: Recurse the web structure and locate any usages of the content type
#notes:
param(
$env = $(Read-Host "Specify environment name"),
[ValidateSet("Dev","Test","UAT","Prod")]
[String] $environmentType = $(Read-Host "Specify EnvironmentType Dev,Test,UAT,Prod"),
[string] $name = $(Read-Host "Specify name")
)
$InformationPreference = "continue"
&("$PSScriptRoot\Start.ps1")
$scriptStartTime = Get-Date
function FindCTUsages([Microsoft.SharePoint.Client.Web] $web, $name, $contentTypeId)
{
write-host "---- Looking for usages of content type $name in $($web.Url) ----"
$lists = $web.Lists
$context.Load($lists)
Execute-WithRetry $context
foreach($list in $lists)
{
$context.Load($list)
$context.Load($list.ContentTypes)
Execute-WithRetry $context
foreach($ct in $list.ContentTypes)
{
if($ct.Id.StringValue.ToLower().StartsWith($contentTypeId.ToLower()))
{
Write-Host -ForegroundColor Green "Found usage of $name at: $($list.ParentWebUrl) - $($list.Title)"
}
}
}
}
function Get-ContentType($name)
{
write-host "---- Looking for content type $name ----"
$items = $context.Site.RootWeb.ContentTypes
$context.Load($items)
Write-Information "Loading Content Types"
Execute-WithRetry $context
[Array] $filtered = $items | where-object { $_.Name -ieq $name}
if($filtered.Count -eq 0)
{
Write-Warning "$name not found"
}
else {
$ct = $filtered[0]
$context.Load($ct)
Execute-WithRetry $context
return $ct
}
return $null
}
function RecurseWebs([Microsoft.SharePoint.Client.Web] $web, $name, $contentTypeId)
{
$context.Load($web)
Execute-WithRetry $context
FindCTUsages $web $name $contentTypeId
foreach($subWeb in $webs)
{
RecurseWebs $subWeb $name $contentTypeId
}
}
$context = Create-Context $env -environmentType $environmentType
try
{
$ct = Get-ContentType $name
if($ct -eq $null)
{
Write-Error "Could not find $name Content Type"
return
}
Write-Information "Found Content Type $name with ID $($ct.Id.StringValue) "
Write-Information "Recursing Web Structure"
RecurseWebs $context.Site.RootWeb $name $ct.Id.StringValue
}
finally
{
Write-Information "Script started at $scriptStartTime"
Write-Information "Script finished at $(Get-Date)"
Write-Information "Time taken is $(([DateTime]::Now - $scriptStartTime).ToString())"
if($context -ne $null)
{
$context.Dispose()
$context = $null
}
}
Post Image by Gerd Altmann from Pixabay