Category Archives: Content Types

Gooey SharePoint Scripting

Today i’m going to show you how to script Sharepoint through the GUI. Whilst in this example we’ll be running the code on server the same concepts and approach can be used to Script it from any machine that can hit the relevant website…

Our example might seem to be a little forced but it’s based on a real world experience. We had a client who had a fairly complicated Content Type scenario, over 150 Content Types spread over 8 levels of inheritance with untold columns. Then we discovered an issue and needed to publish every single one of those content types. This is the classic example of where PowerShell should be used but awkwardly they’d been burnt with PowerShell publishing before.
As such we had a flat edict, no PowerShell publishing of content types. It must go through the GUI.

A post i’d seen recently by Dr James McCaffrey popped into my head. It was about using PowerShell to automate testing of web applications using PowerShell.
Why not use the same process to automate the publishing of the content types?

The first thing to do is to get ourselves an IE window:

$ie = New-Object -com "Internet Explorer"
#This starts by default in a hidden mode so let's show it
$ie.Visible = $true

This isn’t much use on its’ own so let’s send it to a page. In our case we want to go to the page to publish one of our content types. We know that the publish page itself is an application page that is referenced from a site collection root web with the following URL syntax:

siteCollectionRoot/_layouts/managectpublishing.aspx?ctype=ContentTypeID

Glossing over how to get the ContentTypeID for now we have this:

$pageUrl= "http://sharepoint/sites/cthub/_layouts/managectpublishing.aspx?ctype=0x0100A4CF347707AC054EA9C3735EBDAC1A7C"
$ie.Naviagte($pageUrl)

Now PowerShell moves fast, so we’ll need to wait for Javascript to catch up.

While ($ie.ReadyState -ne 4)
{
	Sleep -Milliseconds 100
}

Now we’re there, let’s get the publish button. Thankfully this button has a consistent ID that we can get using the trusty F12 button in IE.

Image of Identifying an element's ID uwing F12

Identifying an element’s ID uwing F12

The catchily titled “ctl00_PlaceHolderMain_ctl00_RptControls_okButton” button? Depressingly i think i’m starting to see the naming convention behind these ids…

$textBoxID = "ctl00_PlaceHolderMain_ctl00_RptControls_okButton";
#You have to pass the Document into it's own object otherwise it will fail
$document = $ie.Document
$button= $document.getElementByID($buttonID)

And now all we need to do is to click that button:

$button.Click()

Now you might think that we’ve done all we need to do here and slap it into a foreach loop and be done with it. Of course you can’t do that as you need to give IE time to send that request using our good old friend Javascript.
So we wait for the page to re-direct us:

 
While ($ie.locationurl -eq $url)
{
start-sleep -Milliseconds 100
}

Now we can slap it into a foreach loop and with a little bit of work we can come up with something like the code below:

Add-PSSnapin Microsoft.SharePoint.PowerShell -ea SilentlyContinue

#URL for the content type hub to use
$CTHubURL= "https://sharepoint/sites/cthub"

#Get the Content Type hub
$site = Get-SPSite $CTHubURL

#Content Types to publish
$ContentAndColumns = @(
("Document Type 1"),
("Document Type 2"),
("Document Type 3")
)


#Open a new IE window
$ie = New-Object -com "InternetExplorer.Application"

#Make the window visible
$ie.visible = $true

#Loop through the content types and publish them
foreach ($contentTypeName in $ContentTypes)
{
    
    Write-Verbose "Processing $ContentTypeName"
    
    #Content types live at the root web
    $web = $site.rootWeb
    #Get the content type using it's name
    $ct =   $web.ContentTypes[$ContentTypeName]
    #Get the GUID for the CT
    $GUID = $ct.ID.ToString()
    #Get the URL for the page based on the content type hub url, the application page that does publishing and the GUID
    $url = $CTHubURL+ "/_layouts/managectpublishing.aspx?ctype=" + $GUID  
    #Go to the page
    $ie.navigate($url)
    #Wait for the page to finish loading
    while ($ie.ReadyState -ne 4)
     {
        start-sleep -Milliseconds 100
     }
     #The ID of the button to press
    $buttonID = "ctl00_PlaceHolderMain_ctl00_RptControls_okButton"
    $document = $ie.Document
    $btn = $document.getElementByID($buttonID)
    #Push the button
    $btn.click()
    #Wait for the page to be re-directed
     while ($ie.locationurl -eq $url)
     {
        start-sleep -Milliseconds 100
     }
     Write-Verbose "Content Type $contentTypeName published"
}

I don’t know about you but there is something deeply neat about sitting at your desk watching IE do the dull task that you were convinced was going to bring your RSI back with a vengance, and in half the time you could do it.

This example might not be useful for that many people but the concept is intriguing. There’s no reason most of this can’t be done without any code on the server at all, the only time we use it is to get the GUIDs and those can be pre-fetched if needs be. Nor does it need any significant rights, as long as the account you use has permision to get into that site collection and publish content types then that’s all they need.

The logical destination of this is Office 365, the scripts and rules for running them on there are limited and limiting, they have to be. But the beauty of Scripting is that we don’t have to be limited by the detail of code, we can use higher level components and tools to worry about that for us. In this case, the GUI that microsoft were kind enough to provide us for when it’s too awkward to find the PowerShell console.

Adding Content Types to the New button on a document library with PowerShell

Background
I was at a customer site and they wanted to remove a load of document types from the “New” button on their document libraries. I tried using the SPContentType.Hidden = $true parameter but realised that wasn’t the one. I then spent some more time banging my head against it and just did it by hand and moved on.

Another person asked how to do something similar on PowerShell.org (here: http://powershell.org/discuss/viewtopic.php?f=12&t=1407). I had some time and was irked by my failure before hand so I gave it another go. I met some success but thought that since it’s something that annoyed me, and since there’s no easily found PowerShell specific posts about this, it’s worth doing properly and blogging.

It turns out that the new button is determined by SPList.rootFolder.UniqueContentTypeOrder property. This is an ordered list of content types to display, any item in the list must be in the lists’ content types but not vice versa. Modify this and you modify the same property you set in the GUI. Happy days.

The first step is to see if a content type is available in the new button or not:

Is-ContentTypeInNewButton

Function Is-ContentTypeInNewButton {

[CmdletBinding()]
Param ([parameter(Mandatory=$true)][string] $ContentTypeName,
       [parameter(Mandatory=$true)][Microsoft.SharePoint.SPList] $SPList)
BEGIN   {  Write-Verbose "Begining Is-ContentTypeInNewButton" }
       PROCESS{
            #get the uniquecontenttypes from the list root folder
            $rootFolder = $SPList.RootFolder
            $contentTypesInPlace = [Microsoft.SharePoint.SPContentType[]] $rootFolder.UniqueContentTypeOrder
            
            #Check if any of them are the same as the test content type
            $results = $contentTypesInPlace | where { $_.Name -eq $ContentTypeName} 
            if ($results -ne $null)
            {
                Write-Verbose "$ContentTypeName Found"
                return $true
            }
            else
            {
                Write-Verbose "$ContentTypeName Not Found"
                return $false
            }
    }
    
END   {  Write-Verbose "Exiting Is-ContentTypeInNewButton" }
}

Of course there’s a possible gotcha. What if the Content type isn’t even added to the list at all?

Ensure-ContentTypeInList

Function Ensure-ContentTypeInList{

[CmdletBinding()]
Param ( [parameter(Mandatory=$true,ValueFromPipeline=$true)][string] $ContentTypeName,
       [parameter(Mandatory=$true)][Microsoft.SharePoint.SPList] $SPList)

BEGIN   {  Write-Verbose "Begining Ensure-ContentTypeInList" }
PROCESS { 

     #Check to see if the content type is already in the list
     $contentType = $SPList.ContentTypes[$ContentTypeName]
     if ($ContentType -ne $null)
     {
        #Content type already present
        Write-Verbose "$ContentTypeName already present in list"
        Return $true
     }
     else
     {
        Write-Verbose "$ContentTypeName not in list. Attempting to add"
        if (!$SPList.ContentTypesEnabled)
        {
            Write-Verbose "Content Types disabled in list $SPList, Enabling"
            $SPList.ContentTypesEnabled = $true
            $SPList.Update()
        }
         #Add site content types to the list from the site collection root
         $ctToAdd = $SPList.ParentWeb.Site.RootWeb.ContentTypes[$ContentTypeName]
         if($ctToAdd -eq $null)
         {
            Write-Error "Error - Content Type could not be found in the Site Collection"
            #I don't believe this will be called.
            return $false
         }
         $SPList.ContentTypes.Add($ctToAdd) | Out-Null
         $SPList.Update()
         Write-Verbose "$ContentTypeName added to list"
         return $true
     }
    }
END {
     Write-Verbose "Exiting Ensure-ContentTypeInList"
    }
}

Well that’s a start. Now we can tell if the content type already exsits, and can add the content type to the list if it doesn’t, let’s put that into something useful:

Ensure-ContentTypeInNewButton

Function Ensure-ContentTypeInNewButton{

[CmdletBinding()]
Param ( [parameter(Mandatory=$true,ValueFromPipeline=$true)][string] $ContentTypeName,
        [parameter(Mandatory=$true)][Microsoft.SharePoint.SPList] $SPList)
    BEGIN   { 
                Write-Verbose "Begining  Ensure-ContentTypeInNewButton"
                #get the uniquecontenttypes from the list root folder
                $contentTypesInPlace = New-Object 'System.Collections.Generic.List[Microsoft.SharePoint.SPContentType]'
                $contentTypesInPlace = $SPList.RootFolder.UniqueContentTypeOrder
                $dirtyFlag = $false
            }
    PROCESS { 
                
        #Check the content type isn't already present in the content type
        $AlreadyPresent = Is-ContentTypeInNewButton -ContentTypeName $ContentTypeName -SPList $SPList
        if ($AlreadyPresent)
        {
            Write-Verbose "$ContentTypeName is already present in the new button"
        }
        else
        {
            #Check that there really is such a content type
            $ContentTypePresent = Ensure-ContentTypeInList $ContentTypeName $SPList
            #Catch error events
            if ($ContentTypePresent)
            {
                #We now know that the content type is not in the new button and is present in the list. Carry on adding the content type
                
                $ctToAdd = $SPList.ContentTypes[$ContentTypeName]
                
                #add our content type to the unique content type list
                $contentTypesInPlace  =  $contentTypesInPlace + $ctToAdd
                $dirtyFlag = $true
                Write-Verbose "$ContentTypeName queued to add to the new button"
            }
            else
            {
                Write-Error -Message "Content type could not be added to the list."
            }
        }
    }
    End{
        #Set the UniqueContentTypeOrder to the collection we made above
        if ($dirtyFlag)
        {
           $SPList = $SPList.ParentWeb.Lists[$SPList.ID]
            $rootFolder = $SPList.RootFolder
            $rootFolder.UniqueContentTypeOrder = [Microsoft.SharePoint.SPContentType[]]  $contentTypesInPlace
        
             #Update the root folder
             $rootFolder.Update()
             Write-Verbose "ContentType(s) added to the new button in list $($SPList.Name)"
        }
        else
        {
                Write-Verbose "No changes"
        }
         Write-Verbose "Exiting  Ensure-ContentTypeInNewButton"
                
    }
}

Awesome. On the other hand the stuff above didn’t lend itself to testing. I had to go into the GUI each time to remove my content types. So let’s have something to help make unwind our changes:

Remove-ContentTypeFromNewButton

Function Remove-ContentTypeFromNewButton{

[CmdletBinding()]
Param ( [parameter(Mandatory=$true,ValueFromPipeline=$true)][string] $ContentTypeName,
        [parameter(Mandatory=$true)][Microsoft.SharePoint.SPList] $SPList)
    
BEGIN   { Write-Verbose "Begining Remove-ContentTypeFromNewButton" }
PROCESS { 
   
            #Check the content type isn't already present in the content type
            $AlreadyPresent = Is-ContentTypeInNewButton -ContentTypeName $ContentTypeName -SPList $SPList
            if ($AlreadyPresent)
            {
                Write-Verbose "$ContentTypeName is present in the new button - removing"
                
                #get the uniquecontenttypes from the list root folder
                $rootFolder = $SPList.RootFolder
                
                #Get the content types where the names are different to our content type
                $contentTypesInPlace = [System.Collections.ArrayList] $rootFolder.UniqueContentTypeOrder
                $contentTypesInPlace = $contentTypesInPlace | where {$_.Name -ne $contentTypeName}
                
                #Set the UniqueContentTypeOrder to the collection we made above
                $rootFolder.UniqueContentTypeOrder = [Microsoft.SharePoint.SPContentType[]]  $contentTypesInPlace
                
                #Update the root folder
                $rootFolder.Update()
                Write-Verbose "$ContentTypeName removed from the new button in list $($SPList.Name)"
            }
            else
            {
                Write-Verbose "$ContentTypeName is not present in the new button. No further action required."
            }
        }
END     { Write-Verbose "Exiting Remove-ContentTypeFromNewButton" }

}

Done.

So we now have the functions to take a list and content type, run a single command which will add a content type, ensuring it’s added to the new button. Further to that we’ve got some basic help (which WordPress has stripped out), error handling and it’ll take piplines and multiple content types. I love PowerShell.

Tests and examples of code

$CTHubSiteCollectionURL = "http://sharepoint/sites/cthub"
$singleContentType = "AlexB_Document"
$contentTypesToAddToNewButton = @("AlexB_Document1b","AlexB_Docudddment2")

$SPWeb = Get-SPWeb $CTHubSiteCollectionURL
$docLib = $spweb.Lists["TestDocLib"]


Write-Host "Is Content Type $ContentTypeName in the new button already? $(Is-ContentTypeInNewButton $singleContentType $doclib )"
Write-Host "Adding the content type to the new button (using the wonderful Ensure method which won't throw errors if already present)"
Ensure-ContentTypeInNewButton -ContentTypeName $singleContentType -SPList $doclib
Write-Host "Is Content Type $ContentTypeName in the new button already? $(Is-ContentTypeInNewButton $singleContentType $doclib )"
#Victory!

"Removing"
#$contentTypesToUpdate | Remove-ContentTypeFromNewButton -SPList $doclib 
Write-Host "Is Content Type in the new button already? $(Is-ContentTypeInNewButton $singleContentType $doclib )"
#Also Victory!

#Let's try a more interesting example
foreach ($contentTypeName in $contentTypesToAddToNewButton)
{
Write-Host "Is Content Type: $ContentTypeName in the new button already? $(Is-ContentTypeInNewButton $contentTypename $doclib)"
}
Write-Host "Adding the content types to the new button (using the wonderful Ensure method which won't throw errors if already present)"
$contentTypesToAddToNewButton | Ensure-ContentTypeInNewButton -SPList $doclib
foreach ($contentTypeName in $contentTypesToAddToNewButton)
{
Write-Host "Is Content Type: $ContentTypeName in the new button already? $(Is-ContentTypeInNewButton $contentTypename $doclib)"
}
#Victory!

And now i can rest. Any critiques of the powershell welcomed.

Just for those that are interested in the bit that makes this all possible, error handling etc. stripped out:

#Get the Web that holds the list
$SPWeb = Get-SPWeb "http://sharepoint/sites/cthub"
#get the library
$list = $SPWeb.Lists["Shared Documents"]

#Get a content type
$contentType = $docLib.ContentTypes | where { $_.Name -eq "AlexB_Document"}

#Get the root folder object
$rootFolder = $list.RootFolder

#Get the current list of content types available
$contentTypesInPlace = [Microsoft.SharePoint.SPContentType[]] $rootFolder.UniqueContentTypeOrder

#add our content type
$contentTypesInPlace  =  $contentTypesInPlace + $ContentType

#set the list to our new list
$rootFolder.UniqueContentTypeOrder = [Microsoft.SharePoint.SPContentType[]]  $contentTypesInPlace

#Update the folder
$rootFolder.Update()

References:
Thanks to Praveen Battula who’s blog post pointed me in the right direction and has some nice C# for doing a similar task.
http://praveenbattula.blogspot.co.uk/2011/01/change-content-type-order-in-new-button.html
Link to TechNet article on the UniqueContentTypeOrder: http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spfolder.uniquecontenttypeorder(v=office.14).aspx.

Thoughts for the future:
It’d be nice to be able to order the items. Not difficult technically but what would the best way to use such a process be?
It seems you can change the new button for different folders in the hierarchy. That’d be handy

Sorting content types in the new button on a list

As i mentioned in my last post it’d be useful to be able to sort the content types in a document library’s new button. This builds on the examples in the previous post but it can be run on it’s own.

This script will attempt to set a default content type, if one is specified, but if it isn’t or the one listed can’t be found it’ll default to alphabetical.

Function Sort-ContentTypesInNewButton{

[CmdletBinding()]
Param ( [parameter(Mandatory=$false)][string] $DefaultContentTypeName,
[parameter(Mandatory=$true,ValueFromPipeline=$true)][Microsoft.SharePoint.SPList] $SPList)
BEGIN {
Write-Verbose "Begining Sort-ContentTypesInNewButton"
}
PROCESS { 

$rootFolder = $SPList.RootFolder

#Get content types fromt the button
$contentTypesInPlace = New-Object 'System.Collections.Generic.List[Microsoft.SharePoint.SPContentType]'
$contentTypesInPlace = $rootFolder.UniqueContentTypeOrder

#Has a default content type name been specified?
if ($DefaultContentTypeName)
{
$contentType = $contentTypesInPlace | where { $_.Name -eq $DefaultContentTypeName }
if ($contentType -ne $null)
{
#Add the default content type
$sortedListOfContentTypes = New-Object 'System.Collections.Generic.List[Microsoft.SharePoint.SPContentType]'
$sortedListOfContentTypes += $SPList.ContentTypes[$DefaultContentTypeName]

#Remove the default content type from the list
$contentTypesInPlace = $contentTypesInPlace | where {$_.Name -ne $DefaultContentTypeName}
}
else
{
Write-Error "$DefaultContentTypeName was not found in the list, sorting by Name alone"
}
}
else
{
Write-Verbose "No default content type specified"
}

#sort the remaining content types and add the sorted list
foreach ($contentType in $($contentTypesInPlace | Sort-Object -Property Name ))
{
#Add the content types 
$sortedListOfContentTypes = [Microsoft.SharePoint.SPContentType[]] $sortedListOfContentTypes + $contentType
}

$rootFolder.UniqueContentTypeOrder = [Microsoft.SharePoint.SPContentType[]] $sortedListOfContentTypes

#Update the root folder
$rootFolder.Update()
Write-Verbose "ContentType(s) sorted and added to the new button in list $($SPList.Name)"

}
End{
Write-Verbose "Ending Sort-ContentTypesInNewButton"
}

}