Tag Archives: PowerShell

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

Create A Document Library Based on a Content Type

Something that’s been winding me up for quite a while is the time needed to create a library, enable content types, add a content type to it, remove the OOB ‘Document’ Type and then finally create a new view that includes the fields on the content type.

It’s not much in the grand scheme of things. Probably about 5 minutes a time. But if you’re creating one, you’ll probably be creating another, and another, and another.

So, let’s do something about that, this script should do everything in the description above. Plus let’s throw in a flag that’ll let us create a list at the same time instead of just a Document Library.

Function Create-DocumentLibraryBasedOnContentType
{

[CmdletBinding()]
Param ([parameter(Mandatory=$true)][string] $ContentTypeName,
       [parameter(Mandatory=$true)][string] $documentLibraryName,
       [parameter(Mandatory=$true)][string] $SPWebURL,
       [parameter(Mandatory=$false)][switch] $IsAList,
       [parameter(Mandatory=$false)][switch] $ShowOnQuickLaunch)
    BEGIN   
        {
            
            Write-Verbose "Beginning Create-DocumentLibraryBasedOnContentType"
            
            $SPWeb = Get-SPWeb $SPWebURL
            $SPSite = $SPWeb.Site
            if ($SPWeb.Lists[$documentLibraryName] -ne $null)
            {
                Write-Error "Library already exists. Aborting" -ErrorAction Stop
            }
            #The default behaviour is to create a document library
            if ($IsAList)
            {
                $listTemplate = [Microsoft.SharePoint.SPListTemplateType]::GenericList
                $listOrLibrary = "List"
            }
            else
            {
                $listTemplate = [Microsoft.SharePoint.SPListTemplateType]::DocumentLibrary
                $listOrLibrary = "Document Library"
            }
        }
    PROCESS
        {
            #get the content Type
            Write-Verbose "Getting content type $contentTypeName in site $siteURL"

            #Confirm that the content type exists.
            $contentType = $SPSite.RootWeb.ContentTypes | where {$_.Name -eq $contentTypeName}

            ##Abort if not found.
            if ($contentType -eq $null)
            {
                Write-Error "$ContentTypeName not found in site collection. Aborting" -ErrorAction Stop
            }
            else
            {
                #Create a library
                Write-Verbose "Creating Default $list"
                
                
                #Create the document Library, stripping out the spaces in the URL for cleanliness
                $libGuid =   $SPWeb.Lists.Add($documentLibraryName.Replace(' ',''),'',$listTemplate) 
                $docLib = $SPWeb.Lists[$libGUID]
                
                Write-Verbose "$listOrLibrary Created, updating settings"
                #Correct the title to appear without spaces
                $docLib.Title = $documentLibraryName
              
                #Add to the quick launch if relevant.
                if($ShowOnQuickLaunch)
                {
                    Write-Verbose "Adding to quick launch"
                    $docLib.OnQuickLaunch = $true
                }
                
                ##Allow modification of content types
                $docLib.ContentTypesEnabled = $true
                
                ##add content type
                 Write-Verbose "Adding the content type"
                $docLibInSitu = $doclib.ContentTypes.Add($contentType)
                
                ##remove 'document'
                if ($IsAList)
                {
                    $doclib.ContentTypes["Item"].Delete()
                }
                else
                {
                    $doclib.ContentTypes["Document"].Delete()
                }
                ##create view and set to default
                Write-Verbose "Creating Default View"
                $newView = $docLib.Views[0].clone("Default", 30, $true, $true)
                $newView.Update()
            }
        }
    END {
            $docLib.Update()
            $SPSite.Dispose()
            $SPWeb.Dispose()
            Write-Verbose "Ending Create-DocumentLibraryBasedOnContentType" 
        }
}

And the script to run it:

$contentTypeName = "AlexB_Document1b"
$siteURL = "http://SharePoint/sites/Subscriber/"
$docLibName = "Test DocLib"

Create-DocumentLibraryBasedOnContentType -ContentTypeName $contentTypeName -documentLibraryName $docLibName -SPWebURL $siteURL -Verbose -List -ShowOnQuickLaunch

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"
}

}