Friday, October 11, 2013

PowerShell script to create Content Types

Continuing with the same scenario/example described in my previous post PowerShell Script to create Site Columns, here is a powershell script which creates site content types in bulk by reading the input from a list or a .csv file.

The pre-req for this script is to create site columns that will be assigned to these content types. You can use existing ones too if they are already created. In case, you wish to create empty content types without adding any site columns, please leave the "Site Columns" column blank. You will see errors in the output window when you run the script but they can be safely ignored.

So lets dive in..

As mentioned earlier; the script can read input from either a list or a .csv file. My input looks something like this. This time I went with the data list approach in contract with the creation of site columns, coz I wanted to give a group of users the ability to define what content types they wish to use for their set of documents.
You can choose to change the properties/columns as per your requirement but do not forget to tweak the code appropriately
 (or)


 



 
 





 
Please note, I did not populate  the Document Template column in my list entries for this example, however if you wish to do so, the input must be in the form of http://servername/sitecollectionname/subweb/documentlibrary/documentname.fileextension

# Description
#          This script is used to create content types by reading data from a SharePoint Data List.
#          The data list must have the columns
#                                    Content Type Name                  Single line of text           Required
#                                    Group                                           Single line of text           Optional
#                                    Parent                                          Single line of text           Required
#                                    Description                                 Multiple lines of text     Optional
#                                    Read Only                                   Yes/No                              Optional
#                                    Update Child Content Types   Yes/No                             Optional
#                                    Division                                       Choice                               Required
#                                    Site Columns                              Multiple lines of text     Optional
#                                    Document Template                 Hyperlink or Picture      Optional

# To input data from a csv file(Optional)
# Get the CSV File location which contains the input values. Change value in "" to match appropriate location
$inputFile = "C:\/*Folder lcoation*/\ContentTypesToBeAdded.csv"
#Import values from the CSV
$ctToBeAdded = import-csv $inputFile
# To input Data from data list
# Get the web in which the input data list is located
$listWeb = Get-SPWeb "/*Url of web*/"
 
# Get the List which stores the input values. Change the value in "" to match appropriate list name
$list = $listWeb.Lists["Content types to be Created"]
# Get the list items
$listItems = $list.Items
# Create an array to store the field links to all site columns that will be added to the content type
$fieldLinksArray = @()
# for each list item in the data list, do the following
foreach($item in $listItems)
{
   # Get the SPSite object for the site collection the content types are created in 
    $ctToBeAddedSite = new-Object Microsoft.SharePoint.SPSite "/*Url of Site Collection*/"
    # If the content types are created at a site collection level - Get the root web
    $ctToBeAddedWeb= $ctToBeAddedSite.RootWeb
   
    # If the script is used for creating web content types, get the rootweb first and store in a seperate variable say "$ctToBeAddedRootWeb" and the use the Get-SPWeb cmdlet to get the url of subweb
    # The reason why you need another variable to store the root web is if your parent content type is a built in content type for eg: Document, it can only be accessed from the rootweb contenttypes collection folder 
    # $ctToBeAddedWeb = Get-SPWeb "/*Url of sub web*/"
    # Parent content type
    # Note: If you are adding the content types to a subweb, use
    # $parentContentType= $ctToBeAddedRootWeb.ContentTypes[$item[$list.Fields["Parent"]]]
    $parentContentType = $ctToBeAddedWeb.ContentTypes[$item[$list.Fields["Parent"]]]
    # Content Type collection the new content type will be added to
    $ctToBeAddedCollection = $ctToBeAddedWeb.ContentTypes
    # Name of content type to be added   
    $ctToBeAddedName = $item[$list.Fields["Content Type Name"]]
    # Names of site columns to be added seperated by ','
    $ctSiteColumns = $item[$list.Fields["Site Columns"]]

    # When adding site columns to a content type; reading the input from a string with values separated by comma, for some reason, PowerShell skips the last value (still researching on this error).
    # You will notice that the array will be built with all the values in it, but the last value is always skipped when the field links are added.
    # To avoid this, my code adds a "," at the end of the input string and treat the last value as a blank.
    # This step can be avoided by adding a "," at the end of the string in the input list
    # Check if the input string ends with a comma, if it doesn’t append a “,”  to the end of the string.
    if($ctSiteColumns.EndsWith(",") -eq $false)
    {
        # Append a comma at the end of the string
        $ctSiteColumnsFinalString = $ctSiteColumns + ","
    }
    else # Keep the input as is
    {
        $ctSiteColumnsFinalString = $ctSiteColumns
    }   

     # Store all site columns in an array by splitting values at "," 
    $ctSiteColumnsArray = $ctSiteColumnsFinalString.Split(",")
    # for each site column in the array; do the following   
    foreach($siteColumn in $ctSiteColumnsArray)
    {
        # Get the SPField object
        $ctSiteColumnToBeAdded = $ctToBeAddedWeb.Fields[$siteColumn]       
        # If it exists
        if($ctSiteColumnToBeAdded -ne $null)
        {
            # Create a field link to the site column
            $fieldLink = new-object Microsoft.SharePoint.SPFieldLink $ctSiteColumnToBeAdded
        
            # Add it to the field link array
            $fieldLinksArray += ,$fieldLink
        }
        else # Site column does not exist
        {
            write-host "The Site Column:" $siteColumn "to be added to content type:" $ctToBeAddedName "does not exist"
        }        
    }
 
    # Check if the content type exists   
    if($ctToBeAddedWeb.ContentTypes[$item[$list.Fields["Content Type Name"]]] -eq $null)
    {
       # Create a new content type
        $newContentType = new-object Microsoft.SharePoint.SPContentType($parentContentType,$ctToBeAddedCollection,$ctToBeAddedName)

        # Add the content type to the content type collection
        $ctToBeAddedCollection.Add($newContentType)    
        # Set the content type properties
        $newContentType.Group = $item[$list.Fields["Group"]]
        $newContentType.Description = $item[$list.Fields["Description"]]
        [boolean]$newContentType.ReadOnly = [System.Convert]::ToBoolean($item[$list.Fields["Read Only"]])
        #Setting this property is optional. I chose to store the documents in a separate library, create a hyperlink column called “Document Template” in my list/.csv file and then attach the link to the content type
        $newContentType.DocumentTemplate = $item[$list.Fields["Document Template"]]
      
       # Foreach field link in the array
       foreach($link in $fieldLinksArray)
      {
           # Add the column to the new content type
           $newContentType.FieldLinks.Add($link)
      }

      # Update the content type definition that is stored in the database and update all content types that inherit from this content type.
        $newContentType.Update([System.Convert]::ToBoolean($item[$list.Fields["Update Child Content Types"]]))
   }
    else # Content type already exists
    {
       write-host "Content Type:" $item[$list.Fields["Content Type Name"]] "already exists"
    }
}
# Dispose the web and site ** Important **
$ctToBeAddedWeb.Dispose()
$listWeb.Dispose()
$ctToBeAddedSite.Dispose()

Output:






 
 
 
 
 
 
 
 
 
 
 
 
 
 
 



 
 
 
Sources:
1. Google.com

Wednesday, October 2, 2013

PowerShell Script to create Site Columns

Hello!
In many scenarios, like setting up a new SharePoint environment or upgrading to a new version or taxonomy conversions, developers are tasked with the creation of ten's to hundereds of site columns in a site collection/web.

So to make this easier for us, I came up with a powershell way to input data into a location and create site columns by reading the input data. The input location can be a sharepoint data list or a .csv file based on the requirement. (I personally prefer working with spreadsheets, but for other's convenience, I will include code for both)

My input spreadsheet looks something like this. You can add/remove any properties as per your requirement or comfort, but do not forget the change the code accordingly (If you use a data list, the list must be populated with the same columns and appropriate values)
Choice fields and managed metadata fields are treated as special cases in my script. The reason being, metadata columns require the intialization of the taxonomy session, term store, groups, sets etc. before the field can be created and choice columns require the declaration of a string collection to which the choice values can be added.
 
# Desription:
# This script is used to create site columns in a site collection
# The following types are created using this script
# Web UI Type                      SPFieldType
# Single line of text                             Text
# Multiple lines of text                        Note
# Choice (menu to choose from)         Choice
# Number (1, 1.0, 100)                        Number
# Currency ($, ¥, €)                             Currency
# Date and Time                                 DateTime
# Yes/No (check box)                         Boolean
# Person or Group                               User
# Hyperlink or Picture                         URL
# Managed Metadata                          Managed Metadata


#### Parameters ####
# To input data from a csv file
# Get the CSV File location which contains the input values. Change value in "" to match appropriate location
$inputFile = "C:\/*Folder lcoation*/\SiteColumnsToBeAdded.csv"
#Import values from the CSV
$siteColumnsToBeAdded = import-csv $inputFile
# To input Data from data list (Optional)
# Get the web in which the input data list is located
$listWeb = Get-SPWeb "/* URL of web where the list is located*/"
# Get the List which stores the input values. Change the value in "" to match appropriate list name
$inputList = $listWeb.Lists["Site Columns List"]
# Get the list items
$siteColumnsToBeAdded = $inputList.Items
# For each row in the csv file/each list item, create a new site column based on the name,description and other properties
foreach($siteColumn in $siteColumnsToBeAdded)
{
       # Get the Site Collection where the site columns will be created.
      
$site = new-object Microsoft.SharePoint.SPSite "/* URL of Site collection*/"
       # Get the Root Web of the site collection. If the columns are created a a sub-web level use the cmdlet Get-SPWeb "" to get the url of the subweb.
      
$web = $site.RootWeb #Get-SPWeb "/* URL of web*/"
       # Get the name of the site column from csv file
      
$name = $siteColumn.Name
       # Check if the field does not exist already
       if($web.Fields[$name] -eq $null)
      {
           
# Get the type of the field
            $type = $siteColumn.Type

           
# To create a managed metadata field; do the following
           
if($type -eq "Managed Metadata")
            {
                      # Get the Taxonomy session of your site collection
                      $session = new-object Microsoft.SharePoint.Taxonomy.TaxonomySession($site)
                      # Get the Metadata service used by the agency. Change value in "" to match appropriate name
                      $termStore = $session.TermStores["Test Managed Metadata Service"]
                      # Get the term store group which stores the term sets you want to retrieve. Change  value in "" to match appropriate name
                      $termStoreGroup = $termStore.Groups["Test Taxonomy"]

                      # Get the term set you want to associate with this field.
                      $termSet = $termStoreGroup.TermSets[$siteColumn.TermSet]
                      # In most cases, the anchor on the managed metadata field is set to a lower level under the root term of the term set. In such cases, specify the term in the spreadsheet and do the following
                      if($siteColumn.TermSet -ne $siteColumn.Term)
                      {
                                #Get all terms under term set
                                $terms = $termSet.GetAllTerms()

                               #Get the term to map the column to
                               $term = $terms | Where-Object {$_.Name -eq $siteColumn.Term}

                               #Get the GUID of the term to map the metadata column anchor to
                               $termID = $term.Id
                        }
                     else # In cases when you want to set the anchor at the root of the term set, leave the  value as blank. Empty guids will error out when you run the script but will accomplish what you need to do i.e. set the anchor at the root of the termset
                       {
                                $termID = [System.GUID]::empty
                       }
                     # Create the new managed metadata field
                     $newSiteColumn = $web.Fields.CreateNewField("TaxonomyFieldType", $name)

                     # Update the properties of the new field.
                     $newSiteColumn.SspId = $termSet.TermStore.ID
                     $newSiteColumn.TermSetId = $termSet.Id
                     $newSiteColumn.AnchorId = $termID
                     $newSiteColumn.AllowMultipleValues = $siteColumn.AllowMultipleValues
                     # Add the the new column to the Site collection's Root web's Fields Collection
                     $web.Fields.Add($newSiteColumn)

                     # Update the web
                     $web.Update()
         }
         elseif($type -eq "Choice") # To create a choice field; do the following
        {
                    # Build a string array with the choice values separating the values at ","
                    $choiceFieldChoices = @($siteColumn.Choices.Split(","))

                    # Declare a new empty String collection
                     $stringColl = new-Object System.Collections.Specialized.StringCollection

                    # Add the choice fields from array to the string collection
                    $stringColl.AddRange($choiceFieldChoices)

                   
# Create a new choice field and add it to the web using overload method
                      SPFieldCollection.Add method (String, SPFieldType, Boolean, Boolean, StringCollection)

                    $newSiteColumn = $web.Fields.Add($name,[Microsoft.SharePoint.SPFieldType]::$type, $siteColumn.Required, $false, $stringColl)
                    # Update the web
                    $web.Update()
         }
         
else # For any other type of field; do the following
         {
                     # Create the new field and add it to the web
                     $newSiteColumn = $web.Fields.CreateNewField([Microsoft.SharePoint.SPFieldType]::$type, $name)
                     $web.Fields.Add($newSiteColumn)
                     $web.update()
           }
write-host "The following site column has been created:" $name
          # You will need to call a new instance of the created site column; direct use of the              $newSiteColumn will result in errors
          
$sc = $null
          $sc = $web.Fields[$name]

          # Add or remove any properties here
          $sc.Description = $siteColumn.Description
          $sc.Group = $siteColumn.Group
        # Boolean values must be converted before they are assigned in PowerShell.
[boolean]$sc.ShowInNewForm = [System.Convert]::ToBoolean($siteColumn.ShowInNewForm)
[boolean]$sc.ShowInDisplayForm = [System.Convert]::ToBoolean($siteColumn.ShowInDisplayForm)
[boolean]$sc.ShowInEditForm = [System.Convert]::ToBoolean($siteColumn.ShowInEditForm)
[boolean]$sc.Hidden = [System.Convert]::ToBoolean($siteColumn.Hidden)
[boolean]$sc.Required = [System.Convert]::ToBoolean($siteColumn.Required)
          
$sc.DefaultValue = $siteColumn.DefaultValue

          
# Update the site column
         
$sc.Update()
          # Update the web
         
$web.Update()
    }
   
else # If the site column already exists
    {
               
write-host "The following site column already exists:" $name
     }
}
# Dispose the web and site ** Important **
$web.Dispose()
$site.Dispose()     
 
Output:


Errors/Notes:
1. If the term store values do not match, you might see some null value errors.
2. All the field types must be entered in the input as described in the beginning of the script. Else the script will fail.
3. When the term column is left empty with an intention to set the anchor to the root of term set, the following runtime error will showup. This is expected and can be safely ignored 
Sources:
1. Google
3. http://msdn.microsoft.com/en-us/library/Microsoft.SharePoint.SPFieldCollection_methods(v=office.14).aspx
4. http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spfieldtype.aspx