Share via


SharePoint Online: Create Site Columns at site collection level using PowerShell and CSOM

Functional Requirement

Admin wants to create Site Columns at the site collections to their O365 SharePoint Online tenant from any remote machine.

Environment Setup for remote machine

In order to enable SharePoint Online PowerShell Script execution from your remote machine. Click here for setup steps 

Download

Source Code 

Solution Architecture

This tool is responsible for the creation of Site Columns at the site collection level by using an array of Site Columns list. The tool is designed for PowerShell SharePoint Online Tool. This is using CSOM as client object model. The architecture of the tool is depicted as below.

A. Tenant Site

Admin has set up an Office 365 tenant account with Microsoft. The O365 E1, E2, E3 or above plan has been provided. The architecture is based on the O365 mentioned plan.

B. Location Site Collection 

There will be a separate site collection for every identified capability location.

C. Site

There will be a separate subsite for every identified location site collection.

D. Site Column

Site Columns will be created at Site Collection level.

Z. Remote Provisioning Pattern Tool 

Remote Provision pattern tool is responsible for the creation of subsites based on the defined array.

Interface Architecture of Provisioning Pattern

1. InstallProvisioning.ps1

This consists of a specific logic of PowerShell scripting relevant to subsite creation.

#This file consist of function calls to generic functions which are used to deploy Site Provisioning to specified Site Collection
#  .\InstallProvisioning.ps1 -Username "admin@XXX.sharepoint.com" -AdminUrl "https://XXX.sharepoint.com" -siteURL "https://XXX.sharepoint.com/sites/US" -groupName "US"
# Global Parameters Defined
param(
        [string]$Username= "",
    [string]$siteCollectionUrl="",
    [string]$siteURL = "",
        [string[]]$arrSubSite = ("HR", "Finance",  "IT"),
        [string]$groupName = "US",
        [string[]]$arrsiteColumns = ("DeptNumber", "DeptName","DeptLocation"),
    [string[]]$arrcontentTypeName = ("CTFinanancial", "CTAccounts"),        
        [string]$workFolder = (Get-Item (“.”)).FullName,
        [string]$Logfile = "$workFolder\$(gc env:computername).log"
 )
 
#Locate the path where Site Provisioning code files are stored
$workFolder = (Get-Item (“.”)).FullName
 
 
 
. "$workFolder\ProvisioningLibrary.ps1"
 
# These are the Function calls to Execute Generic Site Provisioning Functions 
 
#Creating Site Columns at Sitecollection level by using array of site columns
Add-SPOSiteColumns $Username $AdminPassword $siteCollectionUrl $arrsiteColumns

2. ProvisioningLibrary.ps1

This consists of PowerShell scripting reusable logic for a Provisioning pattern.

#This file consist of generic functions used to deploy Site Provisioning to specified Site Collection
 
#Locate the path where Site Provisioning code files are stored
$workFolder = (Get-Item (".")).FullName
. "$workFolder\CommonLibrary.ps1"
 
#This function used to create site columns to the specific site collection
function Add-SPOSiteColumns{
 
# Catch the parameters received through the function call
param(
      [Parameter(Mandatory=$true,Position=1)]
      [string]$Username,
      [Parameter(Mandatory=$true,Position=2)]
      [string]$AdminPassword,
      [Parameter(Mandatory=$true,Position=3)]
      [string]$Url,
      [Parameter(Mandatory=$true,Position=4)]
      [string[]]$arrsiteColumns
     )
     
# Creating client context object
    $context = New-Object Microsoft.SharePoint.Client.ClientContext($Url)  
    $context.credentials = $SPOCredentials
    $fields = $context.web.fields
    $context.load($fields)
      
#Send the request containing all operations to the server
    try{
        $context.executeQuery()
    LogProcess "Loaded all the Site Columns from the site Successfully" "Green"
    }
    catch{
    LogProcess "$($_.Exception.Message)" "Red"
    }
      
#Loop through each entry and create the columnGroup
    foreach ($column in $arrsitecolumns){
        #check if column already exists
        foreach($field in $fields){
            if ($field.internalname -eq $column.name){
                $columnExists = 1
            }
            else{
                $columnExists = 0
            }       
        }
 
#Executes if Site Column does not exist already        
        if ($columnExists -eq 0){
            #create XML entry for a new field
            $fieldAsXML = "<Field Type='Text'
            DisplayName='$column'
            Name='$column' 
            Group='$groupName'/>"
              
            #See tips below for info about fieldOptions
            $fieldOption = [Microsoft.SharePoint.Client.AddFieldOptions]::AddFieldInternalNameHint
            $field = $fields.AddFieldAsXML($fieldAsXML, $true, $fieldOption)
            $context.load($field)
              
            #Send the request containing all operations to the server
            try{
                $context.executeQuery()
        LogProcess "Site Column : $column created Successfully" "Green"
            }
            catch{
        LogProcess "$($_.Exception.Message)" "Red"
            }
        }
        else{
    LogProcess "WARNING: The Site Column : $column already exists." "yellow"
        }
    }
}
 
 
#This function used to create Content Type to the specific site collection
function Add-SPOContentType{
 
# Catch the parameters received through the function call
param(
    [Parameter(Mandatory=$true,Position=1)]
    [string]$Username,
    [Parameter(Mandatory=$true,Position=2)]
    $AdminPassword,
    [Parameter(Mandatory=$true,Position=3)]
    [string]$Url,
    [Parameter(Mandatory=$true,Position=4)]
    [string[]]$arrsiteColumns,
    [Parameter(Mandatory=$true,Position=5)]
    [string[]]$arrcontentTypeName
     )
 
# Local Variables
    $contentTypeGroup = "$groupName"
    $parentContentTypeID = "0x0101"
      
# Creating client context object
    $context = New-Object Microsoft.SharePoint.Client.ClientContext($Url)
    $context.credentials = $SPOCredentials
    $fields = $context.web.fields
    $contentTypes = $context.web.contenttypes
    $context.load($fields)
    $context.load($contentTypes)
      
# Send the request containing all operations to the server
    try{
        $context.executeQuery()
    LogProcess "Loaded all the Fields and Content Types from the site Successfully" "Green"
    }
    catch{
    LogProcess "$($_.Exception.Message)" "Red"
    }
          
# Loop through all content types to verify it doesn't exist
    foreach ($contentType in $contentTypes){
    for($i = 0; $i-le $arrcontentTypeName.Length; $i++){
        if ($contentType.name -eq $arrcontentTypeName[$i]){
            LogProcess "WARNING: The content type $($contentType.name) already exists." "Yellow"
            $contentTypeExists = $true
        }
        else{
            $contentTypeExists = $false
        }
      }
    }
          
# Create content type if it doesnt exist based on specified Content Type ID
    if($contentTypeExists -eq $false){
        # load parent content type
        $parentContentType = $contentTypes.GetByID($parentContentTypeID)
        $context.load($parentContentType)
          
        # Send the request containing all operations to the server
        try{
            $context.executeQuery()
        LogProcess "Loaded parent Content Type Successfully" "Green"
        }
        catch{
        LogProcess "$($_.Exception.Message)" "Red"
        }
          
      # Create Content Type using ContentTypeCreationInformation object (ctci)
      foreach ($contenttypename in $arrcontentTypeName){
        $ctci = new-object Microsoft.SharePoint.Client.ContentTypeCreationInformation
        $ctci.Name = $contenttypename
        $ctci.ParentContentType = $parentContentType
        #$ctci.Id = $ContentTypeID
        $ctci.group = $contentTypeGroup       
        $ctci = $contentTypes.add($ctci)       
        $context.load($ctci)
          
        # Send the request containing all operations to the server
        try{
            $context.executeQuery()
        LogProcess "Content Type : $contenttypename created Successfully " "Green"
        }
        catch{
            LogProcess "$($_.Exception.Message)" "Red"
        }
        
          
        # Get the new content type object
        #$newContentType = $context.web.contenttypes.getbyid($ctci.Name)
          
        # Loop through all the columns that needs to be added
        foreach ($column in $arrsiteColumns){
            $field = $fields.GetByInternalNameOrTitle($column)
            #create FieldLinkCreationInformation object (flci)
            $flci = new-object Microsoft.SharePoint.Client.FieldLinkCreationInformation
            $flci.Field = $field
            $addContentType = $ctci.FieldLinks.Add($flci)
            LogProcess "Added Site Columns: $column to the array Successfully" "Green"
        }     
          
        #$newContentType.Update($true)
         $ctci.Update($true)
        }
        # send the request containing all operations to the server
        try{
            $context.executeQuery()
        LogProcess "Added Site Columns : $arrsiteColumns to Content Types : $arrcontentTypeName Successfully" "Green"
        }
        catch{
            LogProcess "$($_.Exception.Message)" "Red"
        }
    }
}
 
#This function used to create sub site to the specific site collection
function Add-SPOSubsite{
 
# Catch the parameters received through the function call
param(
    [Parameter(Mandatory=$true,Position=1)]
    [string]$Username,
    [Parameter(Mandatory=$true,Position=2)]
    $AdminPassword,
    [Parameter(Mandatory=$true,Position=3)]
    [string]$Url,
    [Parameter(Mandatory=$true,Position=4)]
    [string]$SubSite
     )
 
# Creating client context object
    $context = New-Object Microsoft.SharePoint.Client.ClientContext($Url)      
    $context.credentials = $SPOCredentials
    $rootWeb = $Context.Web
    $Context.Load($rootWeb)
    $childWebs = $rootWeb.Webs
    $Context.Load($childWebs)
    try{
        $Context.executeQuery()
    LogProcess "Loaded all Subsites Successfully" "Green"
       }
    catch{
    LogProcess "$($_.Exception.Message)" "Red"
       }
     
#Create Subsite
    $WCI = New-Object Microsoft.SharePoint.Client.WebCreationInformation
    $WCI.WebTemplate = "BDR#0"
    $WCI.Description = "Finance"
    $WCI.Title = "Finance"
    $WCI.Url = "Finance"
    $WCI.Language = "1033"
 
    foreach($subweb in $childwebs.Title){       
    if($subweb -eq "$SubSite"){
     $columnExists = 1
         LogProcess "WARNING: Subsite $SubSite already Exists" "Yellow"
    }
    else
    {
         $columnExists = 0
    }  
}
if ($columnExists -eq 0){
     try{
        $Sub = $Context.Web.Webs.Add($WCI)
                $context.executeQuery()
        LogProcess "Subsite $SubSite created Successfully" "Green"
            }
            catch{
        LogProcess "$($_.Exception.Message)" "Red"
            }   
    }
}
 
#This function used to associate Content Type to specific document library
function Add-SPOAddContentTypeToList {
 
# Catch the parameters received through the function call
param(
    [Parameter(Mandatory=$true,Position=1)]
    [string]$Username,
    [Parameter(Mandatory=$true,Position=2)]
    $AdminPassword,
    [Parameter(Mandatory=$true,Position=3)]
    [string]$Url,
    [Parameter(Mandatory=$true,Position=4)]
    [string[]]$arrcontentTypeName
     )
 
# Local Variables
    $listTitle = "Document Record"
    $listDescription = "This is Testing Library"
    $listTemplate = 101
 
# Creating client context object
    $context = New-Object Microsoft.SharePoint.Client.ClientContext($Url)
    $context.credentials = $SPOCredentials
    $web = $context.Web
    #$web1 = $web.Webs
    $site = $context.Site 
    $context.Load($web)
    #$context.Load($web1)
    $context.Load($site)
 
try
    {
     $context.ExecuteQuery()
     LogProcess "Authentcated and Connected to SharePoint Online Tenant site $Url Successfully" "Green"
    }
    catch
    {
     LogProcess "Not able to authenticate to SharePoint Online $_.Exception.Message" "Red"
     return
    }
 
#Getting all the list information 
    $Lists = $web.Lists
    $Context.Load($Lists)
    $ContentTypes = $web.AvailableContentTypes
    $Context.Load($ContentTypes)
    try
    {
     $Context.ExecuteQuery()
     LogProcess "Successfully retrived all the List and Content Type information from the site" "Green"
    }
    catch
    {
     LogProcess "Error while getting the List and Content Type information from the site" "Red"
    }
 
    # send the request containing all operations to the server
    try{
        $context.executeQuery()
    LogProcess "Loaded all the Content Types and Lists from the site Successfully" "Green"
    }
    catch{
    LogProcess "$($_.Exception.Message)" "Red"
    }
 
 #Loop through all Document library to verify it doesn't exist
    foreach ($list in $Lists){
        if ($list.Title -eq  $listTitle){
        LogProcess "WARNING: The list : $listTitle already exists." "Yellow"
            $listExists = $true
        }
        else{
           $listExists = $false
        }
    }
 
# create list if it doesnt exist
    if($listExists -eq $false){
       #create list using ListCreationInformation object (lci)
    $lci = New-Object Microsoft.SharePoint.Client.ListCreationInformation
    $lci.title = $listTitle
    $lci.description = $listDescription
    $lci.TemplateType = $listTemplate
    $lci = $lists.add($lci)
    $context.load($lci)
    #send the request containing all operations to the server
    try{
        $context.executeQuery()
    LogProcess "Created List : $listTitle Successfully" "Green"
    }
    catch{
    LogProcess "info: $($_.Exception.Message)" "Red"
    } 
 
# get the new list object
   $newList = $context.Web.Lists.GetByTitle($lci.title)
 
# loop through all the Content Types that needs to be added  
   # create Content Type using ContentTypeCreationInformation object (ctci)
   $ctci = new-object Microsoft.SharePoint.Client.ContentTypeCreationInformation
   foreach($ct in $ContentTypes){
    $CTNAME = $ct.Name
     for($i = 0; $i-le $arrcontentTypeName.Length; $i++){
        if($CTNAME -eq $arrcontentTypeName[$i]){
      $ctci = $context.Web.AvailableContentTypes.GetById($ct.Id)
      $newList.ContentTypesEnabled=$true
      $addList = $newList.ContentTypes.AddExistingContentType($ctci)               
      $newList.Update()
     } 
    }
   } 
          
# send the request containing all operations to the server
        try{
            $context.executeQuery()
        LogProcess "Added ContentTypes : $arrcontentTypeName to List : $listTitle Successfully " "Green"
        }
        catch{
        LogProcess "info: $($_.Exception.Message)" "Red"
        }
  }  
}

3. CommonLibrary.ps1

This consists of PowerShell scripting reusable logic that can be used across the different projects.

# This file consist of Reusable Components amongst all the generic functions that are used to deploy Site Provisioning to specified Site Collection
 
# Paths to SDK. Please verify location on your computer
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"
 
# Accept the Password of the admin site
$AdminPassword=Read-Host -Prompt "Password" -AsSecureString
$SPOCredentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Username, $AdminPassword)
 
#This funtion Logs the entire activity of the script to console and write it to $Logfile
Function LogProcess
{
   Param ([string]$logstring,
          [string]$color
         ) 
   
   if($color -eq "Red")
   {
    $now = Get-Date -f "yyyy-MM-dd HH:mm:ss"  
    Add-content $Logfile -value "Error Details: $($now) $($logstring)"
   }
   else
   {
    $now = Get-Date -f "yyyy-MM-dd HH:mm:ss"  
    write-host $logstring -foregroundcolor $color
    Add-content $Logfile -value "$($now) $($logstring)"
   }     
}