Monday, December 7, 2015

Find everywhere a Column or Site Column is used

Ever needed to remove a Site Column that had at one point been published with a Content Type from the Content Type Hub?  This can be a real pain since there is no easy way to find everywhere that Site Column had ever been used, even where it has been directly added to a list or library that doesn't use the Content Type.  That's just one scenario where the PowerShell script below can come in handy. Once found it may also be helpful to quickly delete the column everywhere it is used or selectively in some places.

The script below does all that. Here is an overview first:
  • Iterates over all lists and libraries in all Webs in all Site Collections in a SharePoint Online tenant (excluding the Admin Portal and My Sites) to find a column that matches the SCName variable. If found it outputs the details of that occurrence of the column.  If the DeleteColumns variable is set to $true the column will automatically be deleted or in the case where the PromptBeforeDelete variable is also set to $true then you are prompted to optionally delete the column where it was found.
  • Input variables:
    • $script:SCName: string representing a Site Column's display name, internal name OR GUID
    • $tenantUrl: Tenant's root SharePoint site URL (a string)
    • $AdminUrl: Tenant's Admin Portal URL (a string)
    • $script:Debug: show debug or other non-critical error messages ($false or $true)
    • $script:DeleteColumns: delete the column from lists or libraries ($false or $true)
    • $script:PromptBeforeDelete: ask whether to delete the column each time it is found ($false or $true)
    • $user: account used to log in to the tenant admin portal and all site collections
    • $spocmdletsPath: local file system path to the folder where the spocmdlets were installed
    • $loadcsompropertiesPath: local file system path to the folder where the loadl-csomproperties script is located
  • Output: list of SharePoint lists or libraries where a column was found that matched the input string. Output list returns the following details for each column found:
    • Site URL
    • List/Library Name
    • Column's display name
    • Column's internal name
    • Column's GUID
  • Requirements/Dependencies
  • Future enhancements
    • Output to CSV
    • Create as a function with parameters or parameterized ps1
    • Download and load dependencies on the fly as needed
Script:

 #Configurable Settings  
 $user = "adminuser@tenanturl.com" #This user will be used to connect to site and create library. Pwd will be prompted for during execution.  
 $spocmdletsPath = "C:\downloads\SPO-Powershell\SPOAutomationCmdlets\SPOCmdlets" #  
 $loadcsompropertiesPath = "C:\downloads\SPScripts\Load-CSOMProperties.ps1"  
 $AdminUrl = "https://tenant-admin.sharepoint.com/"  
 $tenantUrl ="https://tenant.sharepoint.com"  
 $script:SCName = "Coffee"  
 $script:Debug = $false  
 $script:DeleteColumns = $false  
 $script:PromptBeforeDelete = $true  
   
 #Import-Module SPOCmdlets needed for Get-SPOContext, New-SPOList & Add-SPOListCT cmdlets  
 Import-Module $spocmdletsPath  
 Add-Type –Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"   
 Add-Type –Path "C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"  
 $loadcsompropertiesPath  
   
   
 function ConvertFrom-SecureToPlain {  
     
   param( [Parameter(Mandatory=$true)][System.Security.SecureString] $SecurePassword)  
     
   # Create a "password pointer"  
   $PasswordPointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)  
     
   # Get the plain text version of the password  
   $PlainTextPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto($PasswordPointer)  
     
   # Free the pointer  
   [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($PasswordPointer)  
     
   # Return the plain text password  
   #[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($PlainTextPassword))  
   $PlainTextPassword  
 }  
   
   
 $SecureString = $null  
 $r = $null  
   
 $SecureString = Read-Host -AsSecureString 'Enter password for'$user  
 $r = ConvertFrom-SecureToPlain($SecureString)  
 $SecurePassword = $r | ConvertTo-SecureString -AsPlainText -Force  
 $script:Credentials = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $user, $SecurePassword  
   
   
 Connect-SPOService -Url $AdminUrl -Credential $Credentials  
   
 #Get all non admin or My or Public site collections  
 $sites = Get-SPOSite | where {$_.url -Match "$tenantUrl/*"}  
 Disconnect-SPOService  
   
 $:Credentials = New-Object -TypeName Microsoft.SharePoint.Client.SharePointOnlineCredentials -argumentlist $user, $SecurePassword  
 [System.Collections.ArrayList]$libraries = New-Object System.Collections.ArrayList  
   
 function script:GetListsThatUseSC($web,$libraries){  
     
   $Context = New-Object Microsoft.SharePoint.Client.ClientContext($web.url)  
   $Context.Credentials = $Credentials   
   $context.RequestTimeOut = 5000 * 60 * 10;  
   $webI = $Context.Web  
   #$context.Load($webI)  
   Load-CSOMProperties -object $webI -propertyNames @("Title", "Url", "Webs")  
   $Lists = $webI.Lists  
   $Context.Load($Lists)  
   try {   
     $Context.ExecuteQuery()   
     #Load-CSOMProperties -object $Lists -propertyNames @("AllProperties", "Url", "Title")  
     Load-CSOMProperties -parentObject $webI -collectionObject $webI.Lists -propertyNames @("Id", "Title", "Fields") -parentPropertyName "Lists"   
     $Context.ExecuteQuery()   
     if($Debug)  
     {  
       Write-Progress -Activity $webI.url -PercentComplete 100 #"("$webI.Lists.Count "lists)" #-foregroundcolor black -backgroundcolor green -NoNewLine  
         
     }  
     #iterate over each list/library  
     [int]$listIndex = 1  
     [int]$listTotal = [convert]::ToInt32($Lists.Count,10)  
     foreach( $list in $Lists )  
     {  
       [int]$percComplete = ($listIndex/$listTotal)*100  
         
   
       Write-Progress -Activity $webI.url -PercentComplete $percComplete  
       #$libraries.Add($list.Title)  
       foreach($field in $list.Fields)  
       {  
         if (($field.Title -eq $SCName) -or ($field.InternalName -eq $SCName) -or ($field.Id -eq $SCName))  
         {  
           Write-Host $web.Url">"$list.Title":"$field.Title","$field.Id","$field.InternalName #-foregroundcolor black -backgroundcolor green   
           #Delete the column?  
           if($DeleteColumns)  
           {  
             if($PromptBeforeDelete){  
               $d = Read-Host "Delete column? [Y]es or [N]o"  
               if($d.ToLower() -eq "y")  
               {  
                 $Field.DeleteObject()  
                 $Context.ExecuteQuery()  
               }  
             }else  
             {  
               $Field.DeleteObject()  
               $Context.ExecuteQuery()  
             }  
           }  
         }  
       }  
       $listIndex = $listIndex+1  
     }  
   }  
   catch {   
     if($Debug)  
     {  
       Write-Host "Not able to authenticate to SharePoint Online $_.Exception.Message" -foregroundcolor black -backgroundcolor Red  
     }  
   }   
     
  #iterate over all columns  
   
  #log library and lists/library URL where site column is used  
     
  #iterate over each subweb  
  try{  
     if($webI.Webs.Count > 0)  
     {  
       foreach($sub in $webI.Webs)  
       {  
         GetListsThatUseSC($sub, $libraries)  
       }  
     }  
  }catch{  
     if($Debug)  
     {  
       Write-Host "Subwebs not found or could not be loaded: $_.Exception.Message" -foregroundcolor black -backgroundcolor Red  
     }  
  }  
   return $libraries  
   
 }     
   
 #iterate of each Site Collection   
 foreach ($site in $sites)  
 {  
   GetListsThatUseSC($site, $libraries)  
 }  
   
 #$libraries  
   
 #clear pwd  
 $r = $null