Monthly Archives: April 2013

Managed Metadata columns fail to sync between SharePoint and client applications

This issue seems to be cropping up a lot at the moment, one possible fix is below.

Symptoms:

When you set a Managed Metadata Service (MMS) column in SharePoint these values are pushed down to the office document and will be visible on the Document Information Panel (DIP). When these values are changed in an office document however these MMS column changes are not updated in the SharePoint item. Non MMS fields (i.e. Single Line of Text, Choice, Number etc.) are correctly synced. If you close and re-open the office document, even from another computer, any changes made in office to the MMS values will still remain as you set them in the DIP. However as normal any changes to the values in SharePoint will be pushed down to the office document overwriting any values in the DIP.

In summary: SharePoint can write to the office document but MMS values in the document cannot be written to SharePoint by office.

Note: If text, choice or other non MMS fields are not being synced when you save the document then this is probably unrelated to your issue.

Where has this been seen:

We’ve seen it in at least two SharePoint 2010 SP1 environments in the last week, with farms using varying CUs. No obvious cause has been identified.
The main example is in office, at least word and Excel. This has also been seen with Harmon.ie where it is impossible to set the MMS value, it is probable other systems may be effected.

Solution:

Add and remove a MMS column from each list. You can confirm that this fixes your issue by performing a manual update of a single list and then run a bulk correction using PowerShell. Note that you will need to test and re-create any faulty Site Templates.

Cause

Not known at this time, it appears to be related to the document parser. It appears that in some cases the document Parser process fails on MMS values. The value in Word is maintained in the document’s xml fields but is not correctly udpated (at least in our tests) with the correct namespace for the term or termID.
It seems that by adding a new MMS column the issues with the other columns is corrected, we believe this might be due to some version or synchronisation process but have not tracked down the root cause.

Manual steps

In your list or library, open the list settings.

Image of library ribbon with Library Settings highlighted

Library Settings

Click on ‘Create Column’

Image of Create Column highlighted within Library Settings

Create Column

Enter a name, here we will use ‘DummyColumn’ and select ‘Managed Metadata’

Column creation process with Name and Type highlighted

Create Column (specify type and name)

Select a value in the MMS

Image of Managed metadata value selected in column creation

Select Managed Metadata Value

Click OK.

At this point you should be able to confirm that the MMS field is now synchronised between Office and SharePoint. You can then delete the column.

Note: If the process fails then delete the column anyway, unless you’re selling childrens accessories then it will probably be of little use.

Programatic

This can be scritped in several ways but the primary method will be on server PowerShell. An example script is shown below:

<#
Author: Alex Brassington (Trinity Expert Systems)
Date: 26/04/2013
Description:
Adds and removes an MMS colummn to every library in the white list for all sites in a web application. This is to
fix the office => SharePoint managed metadata service sync field issues.
This can be run with either a white list of lists/libraries to update or without, in which case all document libraries will be updated. It is possible that this only needs to run on one document library per site but i have not yet been able to confirm or refute that.
#>

Add-PSSnapin Microsoft.SharePoint.PowerShell -ea SilentlyContinue

#Reference to the content type hub to be used for the MMS Column    
$CTHubURL= "http://sharepoint/sites/cthub"

#Site Collection to modify
$SCURL = "http://sharepoint/sites/cthub"

#Name of the MMS instance to use
$MMSInstance = "Managed Metadata Service"


#A 'white list' of libraries to process. Note that this currently contains 'DOcuments' which should be handled as a special case.
$librariesToCheck =
(
    "Documents",
    "Entertainment",
    "Project Documents",
    "Management Information"
)

    #Setup the termstore object
    $contentTypeHub = Get-SPSite $contentTypeHubURL
    $session = New-Object Microsoft.SharePoint.Taxonomy.TaxonomySession($contentTypeHub)
    $termStore = $session.TermStores | ? {$_.Name -eq $MMSInstance}
    $group = $termStore.Groups["Demo Terms"]
    $termSet = $group.Termsets["Condiments"]


Function Update-LibrariesInSiteCollection ()
{
[CmdletBinding()]
    Param (
    [Parameter(Position=0,Mandatory=$true,ValueFromPipeLine=$true)][string]$siteURL, 
    [Parameter(Position=1,Mandatory=$true)][Microsoft.SharePoint.Taxonomy.TermSet]$termSet,
    [Parameter(Position=2,Mandatory=$true)][Microsoft.SharePoint.Taxonomy.TermStore]$termStore,
    [Parameter(Position=3,Mandatory=$false)][string]$errorFile,
    [Parameter(Position=4,Mandatory=$false)][string[]]$librariesToCheck
    )
    
    
    #No change required, only used internally
    $columnName = "TempColumn"
    
    #Get the SharePoint Site Collection to process
    $site = Get-SPSite $siteURL
    Write-Verbose "Updating Site Collection $($site.URL)"
    foreach ($web in $site.AllWebs)
    {
        Write-Verbose "Updating Web $($web.URL)"
        
        #If there's a list of folders to use as a whitelist then use them
        if ($librariesToCheck)
        {
            Write-Verbose "Updating libraries based on provided White list"
            $lists = $web.Lists | ? {$librariesToCheck -contains $_}
        }
        else
        {
            #If not then process all libraries.
            Write-Verbose "Updating all document libraries only"
            $lists = $web.Lists | ? {$_.BaseType -eq "DocumentLibrary"}
        }
        
        foreach ($list in $lists)
        {
            Write-Verbose "Updating list $($list.Title)"
            try
            {
                #Create a new taxonomy field
                $taxField = $list.fields.CreateNewField("TaxonomyFieldType", $columnName)
                
                #set the term store ID and the termset ID 
                $taxField.SspId = $termStore.Id
                $taxField.TermSetId = $termSet.Id
                
                #Add the column to the list
                $list.Fields.Add($taxField) | Out-Null
                $list.Update()
                
                #Remove the column
                $column = $list.fields[$columnName]
                $column.Delete()
                Write-Verbose "List Complete $($list.Title)"
            }
            catch
            {
                Write-Error "Error encountered on List: $($list.Title)"
            }
        }
    $web.Dispose()
    }
    
    #If a file path was given then write out the error log.
    if ($errorFile)
    {
        $error >> $errorFile
    }
    #Dispose of the site collection
    $site.Dispose()
}

Update-LibrariesInSiteCollection -siteURL $SCURL -termSet $termSet -termStore $termStore -errorFile $ErrorPath -Verbose

My thanks to my colleague Paul Hunt (aka Cimares) who found the fix that we scripted above.

Tweaking exercise

One of my colleagues needed a PowerShell script to report on the SharePoint Site Collection Quotas in use on all sites, as well as how much of the site was being used.
Not being a PowerShell or SharePoint expert they asked for a second opinion, since I had an hour an a half of train journey they got a bit more than they expected.

The original Script:

$t = [Microsoft.SharePoint.Administration.SPWebService]::ContentService.quotatemplates
$tFound = $false
$webApp = Get-SPWebApplication | %{$_.Sites} | Get-SPSite -Limit ALL
$webApp | fl Url, @{n=”Storage Used/1MB”;e={[int]($_.Usage.Storage/1MB)}},
@{n=”Storage Available Warning/1MB”; e={[int](($_.Quota).StorageWarningLevel/1MB)}},
@{n=”Storage Available Maximum/1MB”; e={[int](($_.Quota).StorageMaximumLevel/1MB)}},
@{n=”Sandboxed Resource Points Warning”;e={[int](($_.Quota).UserCodeWarningLevel)}},
@{n=”Sandboxed Resource Points Maximum”;e={[int](($_.Quota).UserCodeMaximumLevel)}},
@{n=”Quota Name”; e={ foreach($qt in $t){if($qt.QuotaId -eq [int](($_.Quota).QuotaID)){$qt.Name; $tFound = $true}} if($tFound -eq $false){“No Template Applied”}$tFound=$false;}} >> c:quotaoutput.txt
if($parent) {$webApp.Dispose(); $t.Dispose()}

First. Scripts are written by humans, for humans. Computers might use them but they are meant for us. There’s also a direct correlation between consistency of indenting and code quality. That monolithic block has to go.

$t = [Microsoft.SharePoint.Administration.SPWebService]::ContentService.quotatemplates
$tFound = $false
 

$webApp = Get-SPWebApplication | %{$_.Sites} | Get-SPSite -Limit ALL
$webApp | fl Url, 
	@{n=”Storage Used/1MB”;e={[int]($_.Usage.Storage/1MB)}},
	@{n=”Storage Available Warning/1MB”; e={[int](($_.Quota).StorageWarningLevel/1MB)}},
	@{n=”Storage Available Maximum/1MB”; e={[int](($_.Quota).StorageMaximumLevel/1MB)}},
	@{n=”Sandboxed Resource Points Warning”;e={[int](($_.Quota).UserCodeWarningLevel)}},
	@{n=”Sandboxed Resource Points Maximum”;e={[int](($_.Quota).UserCodeMaximumLevel)}},
	@{n=”Quota Name”; e={ 
	        foreach($qt in $t)
	        {
	            if($qt.QuotaId -eq [int](($_.Quota).QuotaID))
	            {
	                 $qt.Name; 
	                 $tFound = $true
	            }
	        } 
	        if($tFound -eq $false)
	        {
	            “No Template Applied”
	        }
	    $tFound=$false;
	    }
	} >> c:PSoutput.txt
 
if($parent)
 {
	$webApp.Dispose(); 
	$t.Dispose()
}

At this point we can actually work out what happens. A collection of site collections are fetched, then we iterate through each of them, capturing bits of information, and then try to work out if the site has a quota and if so what it is called.

You might already have spotted the second item in there. If not here’s a hint, we’re getting a Collection of Site Collections.

Not a WebApplication, nor even a collection of them.

Note to self and others: Always use meaningful names.

I was slightly confused when I first read this as I assumed the names were meaningful. It took me a second, and a run through in debug mode, to convince myself otherwise.

So, let’s correct that name to something more meaningful. We’re in a simple scenario so we can use something short but descriptive like ‘AllSites’. While we’re there let’s also tidy up that $t to $templates

$templates = [Microsoft.SharePoint.Administration.SPWebService]::ContentService.quotatemplates
$tFound = $false
 
$AllSites= Get-SPWebApplication | %{$_.Sites} | Get-SPSite -Limit ALL
$AllSites| fl Url, 
	@{n=”Storage Used/1MB”;e={[int]($_.Usage.Storage/1MB)}},
	@{n=”Storage Available Warning/1MB”; e={[int](($_.Quota).StorageWarningLevel/1MB)}},
	@{n=”Storage Available Maximum/1MB”; e={[int](($_.Quota).StorageMaximumLevel/1MB)}},
	@{n=”Sandboxed Resource Points Warning”;e={[int](($_.Quota).UserCodeWarningLevel)}},
	@{n=”Sandboxed Resource Points Maximum”;e={[int](($_.Quota).UserCodeMaximumLevel)}},
	@{n=”Quota Name”; e={ 
	        foreach($qt in $templates)
	        {
	            if($qt.QuotaId -eq [int](($_.Quota).QuotaID))
	            {
	                 $qt.Name; 
	                 $tFound = $true
	            }
	        } 
	        if($tFound -eq $false)
	        {
	            “No Template Applied”
	        }
	    $tFound=$false;
	    }
	} >> c:PSoutput.txt
 
if($parent)
 {
	$AllSites.Dispose(); 
	$template.Dispose()
}

Now, that makes it a bit nicer to read. The mislabeled variable is a big hint to our next item, return values from cmdlets. Let’s look at this one line:

$AllSites= Get-SPWebApplication | %{$_.Sites} | Get-SPSite -Limit ALL

Let’s work through what this does. First we get all the WebApplications in the farm, then for each of those we get their sites, then for each of those sites we run the Get-SPSite -Limit All comand for that single site.
Wait, what?
Yup, we get a collection of all the sites and then we step through each and fetch it again. It’s almost surprising it works until you realise just how clever the PowerShell compiler is at converting types.
In fact, all three lines following are equivalent:

	$sites = Get-SPWebApplication | % { $_.Sites} | Get-SPSite –Limit All
	$sites = Get-SPWebApplication | % { $_.Sites} 
	$sites = Get-SPSite –Limit All

Why make things more complicated than they need to be? Let’s go with the last one.

$templates = [Microsoft.SharePoint.Administration.SPWebService]::ContentService.quotatemplates
$tFound = $false

$AllSites =  Get-SPSite -Limit ALL
$AllSites| fl Url, 
	@{n=”Storage Used/1MB”;e={[int]($_.Usage.Storage/1MB)}},
	@{n=”Storage Available Warning/1MB”; e={[int](($_.Quota).StorageWarningLevel/1MB)}},
	@{n=”Storage Available Maximum/1MB”; e={[int](($_.Quota).StorageMaximumLevel/1MB)}},
	@{n=”Sandboxed Resource Points Warning”;e={[int](($_.Quota).UserCodeWarningLevel)}},
	@{n=”Sandboxed Resource Points Maximum”;e={[int](($_.Quota).UserCodeMaximumLevel)}},
	@{n=”Quota Name”; e={ 
	        foreach($qt in $templates)
	        {
	            if($qt.QuotaId -eq [int](($_.Quota).QuotaID))
	            {
	                 $qt.Name; 
	                 $tFound = $true
	            }
	        } 
	        if($tFound -eq $false)
	        {
	            “No Template Applied”
	        }
	    $tFound=$false;
	    }
	} >> c:PSoutput.txt
 
if($parent)
 {
	$AllSites.Dispose(); 
	$template.Dispose()
}

That’s better, but looking down the script there’s another item that has probably grabbed your notice. What the heck is $parent?
I have my suspicions it is orriginally from a 2007 PowerShell script, back when we were still using WSS 3, STSADM, PowerShell V1.0 and dinosaur attacks were listed on the risk register.
Either way this has no place here, if we’re executing in Strict mode (which we should be) then the script won’t even compile. If we’re not then it’ll never fire as $null evaluates to $false.
That’s probably for the best really as $AllSites, being a collection, doesn’t have a .Dispose() method and nor does $template.
Let’s just blow that away completely.

$templates = [Microsoft.SharePoint.Administration.SPWebService]::ContentService.quotatemplates
$tFound = $false

$AllSites =  Get-SPSite -Limit ALL
$AllSites| fl Url, 
	@{n=”Storage Used/1MB”;e={[int]($_.Usage.Storage/1MB)}},
	@{n=”Storage Available Warning/1MB”; e={[int](($_.Quota).StorageWarningLevel/1MB)}},
	@{n=”Storage Available Maximum/1MB”; e={[int](($_.Quota).StorageMaximumLevel/1MB)}},
	@{n=”Sandboxed Resource Points Warning”;e={[int](($_.Quota).UserCodeWarningLevel)}},
	@{n=”Sandboxed Resource Points Maximum”;e={[int](($_.Quota).UserCodeMaximumLevel)}},
	@{n=”Quota Name”; e={ 
	        foreach($qt in $templates)
	        {
	            if($qt.QuotaId -eq [int](($_.Quota).QuotaID))
	            {
	                 $qt.Name; 
	                 $tFound = $true
	            }
	        } 
	        if($tFound -eq $false)
	        {
	            “No Template Applied”
	        }
	    $tFound=$false;
	    }
	} >> c:PSoutput.txt

That’s better still. Sleeker and more readable. On the other hand that .Dispose method should be ringing some bells, as you all know SharePoint is infamous for not properly releasing memory for the key components. Without the .Dispose method the objects will sit in memory until the PowerShell session ends.

In C# we’d have ‘using’ blocks but they don’t really exist in PowerShell. Here we use the pipeline, anything that’s run in a pipeline is disposed of at the end by default.

It just so happens that our $AllSites object is only used once after being declared, by rolling that into the pipeline we can make use of this wonderful feature and streamline our code further!

$templates = [Microsoft.SharePoint.Administration.SPWebService]::ContentService.quotatemplates
$tFound = $false
 
Get-SPSite -Limit ALL| fl Url, 
	@{n=”Storage Used/1MB”;e={[int]($_.Usage.Storage/1MB)}},
	@{n=”Storage Available Warning/1MB”; e={[int](($_.Quota).StorageWarningLevel/1MB)}},
	@{n=”Storage Available Maximum/1MB”; e={[int](($_.Quota).StorageMaximumLevel/1MB)}},
	@{n=”Sandboxed Resource Points Warning”;e={[int](($_.Quota).UserCodeWarningLevel)}},
	@{n=”Sandboxed Resource Points Maximum”;e={[int](($_.Quota).UserCodeMaximumLevel)}},
	@{n=”Quota Name”; e={ 
	        foreach($qt in $templates)
	        {
	            if($qt.QuotaId -eq [int](($_.Quota).QuotaID))
	            {
	                 $qt.Name; 
	                 $tFound = $true
	            }
	        } 
	        if($tFound -eq $false)
	        {
	            “No Template Applied”
	        }
	    $tFound=$false;
	    }
	} >> c:PSoutput.txt

Of course, that doesn’t work because of the aforementioned crapness of SharePoint and it’s memory handling. I’m working on a longer post on how to deal with it but for now just remember to kill your sessions as soon as you can.

So, if you run this you get a nice text file with a rubbish name dumped out at the end. The format might look something like this:

Url : http://mysites:8080
Storage Used/1MB : 2
Storage Available Warning/1MB : 0
Storage Available Maximum/1MB : 0
Sandboxed Resource Points Warning : 100
Sandboxed Resource Points Maximum : 300
Quota Name : No Template Applied

Url : http://sharepoint
Storage Used/1MB : 7
Storage Available Warning/1MB : 0
Storage Available Maximum/1MB : 0
Sandboxed Resource Points Warning : 100
Sandboxed Resource Points Maximum : 300
Quota Name : No Template Applied

I’m liking the data but if you’ve got hundreds of sites that’s going to be a nightmare to go through. It just so happens we can make use of one of the lesser known, but highly awesome, PowerShell features to help us here.

As we all know the world floats on Excel and if we’re honest that’s where this data’s going anyway for us to sort. Let’s dump it out into a CSV file, now we could re-write the format-list statement to dump the stuff out in strings and then concatenate our hearts out.
Or we can change two things, swap fl out for Select and insert ConvertTo-CSV.

$templates = [Microsoft.SharePoint.Administration.SPWebService]::ContentService.quotatemplates
$tFound = $false
 
Get-SPSite -Limit ALL| Select Url, 
	@{n=”Storage Used/1MB”;e={[int]($_.Usage.Storage/1MB)}},
	@{n=”Storage Available Warning/1MB”; e={[int](($_.Quota).StorageWarningLevel/1MB)}},
	@{n=”Storage Available Maximum/1MB”; e={[int](($_.Quota).StorageMaximumLevel/1MB)}},
	@{n=”Sandboxed Resource Points Warning”;e={[int](($_.Quota).UserCodeWarningLevel)}},
	@{n=”Sandboxed Resource Points Maximum”;e={[int](($_.Quota).UserCodeMaximumLevel)}},
	@{n=”Quota Name”; e={ 
	        foreach($qt in $templates)
	        {
	            if($qt.QuotaId -eq [int](($_.Quota).QuotaID))
	            {
	                 $qt.Name; 
	                 $tFound = $true
	            }
	        } 
	        if($tFound -eq $false)
	        {
	            “No Template Applied”
	        }
	    $tFound=$false;
	    }
	} | ConvertTo-CSV >> c:PSoutput-CSV.CSV

That turns our text output into something like this:

#TYPE Selected.Microsoft.SharePoint.SPSite
“Url”,”Storage Used/1MB”,”Storage Available Warning/1MB”,”Storage Available Maximum/1MB”,”Sandboxed Resource Points Warning”,”Sandboxed Resource Points Maximum”,”Quota Name”
“http://mysites:8080″,”2″,”0″,”0″,”100″,”300″,”No Template Applied”
“http://sharepoint”,”7″,”0″,”0″,”100″,”300″,”No Template Applied”
“http://sharepoint/sites/CTHub”,”3″,”0″,”0″,”100″,”300″,”No Template Applied”
“http://sharepoint/sites/sync”,”3″,”0″,”0″,”100″,”300″,”No Template Applied”
“http://sharepoint/sites/TechNet”,”2″,”0″,”0″,”100″,”300″,”No Template Applied”

A hell of a lot uglier but with a little Excel care and attention it’s sortable, filterable and fit for use in a report.

What if we’re not going to be using Excel but we are going to be inspecting by eye, isn’t there a better format there? Well yes there is, you can use the ConvertTo-HTML option and that’ll turn the entire lot into a fully formed HTML file for you. With a modicum of genius and/or a particulary epic book by Don Jones you can add your own CSS and Jquery.

That works, but if this is going to be run more than once I don’t’ want my files overwriting the old ones, or even worse appending (as the script above will do, talk about confusing!)

Let’s slap a date stamp onto our output file:

$outputFolder = "C:"
$path = $outputFolder + "Output-" + (Get-Date -Format "dd-MM-yyyy") + ".txt"

Yes, I’m a Brit, we will use the only sensible date format in this blog.

With a slight modification we’re now here:


$outputFolder = "C:\Results\"
$path = $outputFolder + "Output-" + (Get-Date -Format "dd-MM-yyyy") + ".csv"

$templates = [Microsoft.SharePoint.Administration.SPWebService]::ContentService.quotatemplates
$tFound = $false
 
Get-SPSite -Limit ALL| Select Url, 
	@{n=”Storage Used/1MB”;e={[int]($_.Usage.Storage/1MB)}},
	@{n=”Storage Available Warning/1MB”; e={[int](($_.Quota).StorageWarningLevel/1MB)}},
	@{n=”Storage Available Maximum/1MB”; e={[int](($_.Quota).StorageMaximumLevel/1MB)}},
	@{n=”Sandboxed Resource Points Warning”;e={[int](($_.Quota).UserCodeWarningLevel)}},
	@{n=”Sandboxed Resource Points Maximum”;e={[int](($_.Quota).UserCodeMaximumLevel)}},
	@{n=”Quota Name”; e={ 
	        foreach($qt in $templates)
	        {
	            if($qt.QuotaId -eq [int](($_.Quota).QuotaID))
	            {
	                 $qt.Name; 
	                 $tFound = $true
	            }
	        } 
	        if($tFound -eq $false)
	        {
	            “No Template Applied”
	        }
	    $tFound=$false;
	    }
	} | ConvertTo-CSV >>  $path

We’ve turned a script that shouldn’t even run into something that’s more legible, probably faster (more to come on this I hope), more efficient and giving more useful results.

What haven’t we done? We haven’t touched on the, frankly brutal, RAM leaks which are the massive elephant in the room. This script will make your server cry, if it’s a really large farm then it might even impact the stability or performance of your CA box or wherever you run it. If you’ve got thousands of site collections I recommend running this out of hours with Task manager open and a hand hovering over Ctrl + C.

What next? Elephant hunting and SPAssignments

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