How to Use SharePoint PNP to Create a Site Inventory Script

Content inventory scripts are essential for me, my teammates, and my clients. Often though, I find myself re-writing scripts because Client A wants these fields, and Client B wants those fields. To save me the headache of writing a new script every time, I wanted an inventory script that retrieves every field. I was tired of using CSOM scripts for inventory, so instead decided to try using PNP for inventory. Now instead of writing a new script every time, all I need to do is delete columns in Excel instead.

I’ve used PNP for provisioning, but never for inventory. I found that by using PNP the inventory script was much quicker to write and was much more powerful.

Here’s the script in its glory. It will work on SharePoint Online, 2013, and 2016. Before you can run it, you’ll need to install PNP. This blog walks through how to do that.

Param(

[parameter(Mandatory=$true)]
 [string]$sitecollection
)
#update credentials based on your system. If in doubt, remove credentials param
Connect-PnPOnline -Url $sitecollection -Credentials 'outlook.office365.com'

#create variables for output file
$date = get-date -format yyyy-MM-dd
$time = get-date -format HH-mm-ss
$outputfilename = "SiteInventory" + $date + "_" + $time + ".csv"
$outputpath = "c:\scripts\"+$outputfilename

Write-Host "Connected to SharePoint site:" $sitecollection -ForegroundColor Green

function InventorySite($web){
#Create array variables to store data
$fieldvalues = $null
$fieldvalues = @()
$array = $null
$array = @()
#additional vars
$siteurl = $site.url
$weburl = $web.url
 write-host "Performing inventory on " $weburl -foregroundcolor Yellow
 #is this the root or not. If not, get lists
 
 if($weburl -eq $siteUrl){
 $lists = get-pnplist
 }
 else { 
 $lists = get-pnplist -web $web
 
 }
 
 #go through every list
 foreach ($list in $lists)
 { 
 write-host "List:" $list.title -foregroundcolor White
 #get all items
 #if this is the root, don't use web property. if subsite, use web
 if($weburl -eq $siteUrl){
 $items = get-pnplistitem -list $list -PageSize 1000
 }
 else { 
 $items = get-pnplistitem -list $list -Web $web -PageSize 1000
 } 
 #go through every item
 foreach ($item in $items)
 {
 $obj = New-Object PSObject
 $fieldvalues = $item.FieldValues 
 foreach ($field in $fieldvalues){
 $keys = @()
 $values = @()
 #work on the keys aka field names
 foreach ($key in $field.keys){
 $keys += $key
 }
 #work on the field values
 foreach ($value in $field.values){
 #if lookup value, do this
 if($value.lookupvalue -ne $null){
 $values += $value.lookupvalue
 }
 #not a lookup
 else{$values += $value}
 }
 #add values to obj array
 For ($j = 0; $j -lt $keys.count;$j++)
 {
 $obj | Add-Member -MemberType noteproperty -Name ($keys[$j]).tostring() -Value $values[$j] 
 $array += $obj
 } 
 #close field in fields
 }
 
 #close item in items
 }
 #close list in lists
 }
 $filter = $array | Get-Unique -AsString
 $filter | Export-Csv $outputpath -noType -Encoding UTF8 -Append -Force
}

#load site collection, and lists at site collection
$site = Get-PnPSite -includes RootWeb.Lists
#call inventory function
InventorySite $site

#load subsites
$subwebs = Get-PnPSubWebs -Recurse
#call inventory function for each subsite
foreach ($web in $subwebs){
 InventorySite $web
}
write-host "Script complete! Exported file to " $outputpath -foregroundcolor green

#references
#https://veenstra.me.uk/2017/11/03/microsoft-teams-get-all-your-team-sites-using-pnp-powershell/
#https://blog.kloud.com.au/2018/02/01/quick-start-guide-for-pnp-powershell/
#https://github.com/SharePoint/PnP-PowerShell/blob/master/Documentation/readme.md

The script was originally written to run on a site collection. However, it bombs on large lists, and if there are a lot of subsites (on get-pnplistitem and get-pnpsubwebs -recurse respectively).

To get around this and run an inventory on very large site collections, in PowerShell I ran the following.

Connect-pnponline -site https://sitecollection

$subsites = Get-pnpsubwebs

$subssites | export-csv c:\scripts\subsites.csv

 

This gets all of the first-level subsites, and outputs them to a CSV file.

I then used a concatenate function to build the syntax to run the script so it looks something this this:

.\pnp.ps1 -sitecollection https://tenant.sharepoint.com/sites/projects/subsitenumberone

Then I took the value of the calculated column and pasted it into PowerShell and let it run. It’s a poor-man’s batch job, but it is effective.

If you choose to go this route, make sure you update the -Credentials parameter on Connect-PNPOnline to represent a credential on your system.

*UPDATE – 4/18/18* – I’ve explored the issues I ran into above. I’ve fixed the large list query by adding a page size. Second, I identified the issue with getting a lot of subsites. This is only an issue when you have provider hosted apps (i.e. Nintex). These can be filtered out in the script, but I’ll create a new post when I solve that. I’ve also logged this as a bug.