At Christian Aid we recently upgraded from Sharepoint 2003 to Microsoft Office Sharepoint Server 2007 (commonly abbreviated to MOSS 2007) which has definitely meant improvements in a number of areas, but administration is still a royal pain in the ass. Some of this is down to the rather busy admin web pages (here I think the Rule of 7 actually would have been a useful principle for Microsoft to follow), and some down to the information architecture we rolled out in Sharepoint 2003.
Sharepoint Architecture
Sharepoint is organised into the following key objects:

At Christian Aid we have opted to create a new Site Collection for each Team Site – the team site is effectively the root and only site in a Site Collection. We are beginning to see a number of problems with this model. Before I get into those, let me explain the reasons why we originally chose this path.
I think that Microsoft’s intention has from the out been that when creating a Sharepoint Farm, the administrators create a number of site collections, each storing their content in a Content Database. All team sites would then be created under this limited number of Site Collections. This model allows for a certain degree of granularity in backup/restore. I say a certain degree because since your content is stored in a database, when restoring a lost file it is necessary to restore the whole content database. If you have a single content database, this may mean restoring 100+ gigabytes in order to get a 2kb file! This is one of the reasons that Christian Aid opted to set up a Site Collection for each team site, the idea being that you could have a larger number of smaller content databases making the restore process less of a headache.
Another reason for our Site Collection = Team Site model is that permissions in Sharepoint are inherited from the Site Collection. In Sharepoint 2003, if you wanted to have a different set of Permission Groups or Permission Levels for different team sites, one way of achieving this was to use different site collections.
Inheritance problems
It is this inheritance issue that is now biting us in the ass. We have 160+ site collections, and since this is the level from which permissions are inherited, if we want to make a change across the board there are 160+ places to make the change. No small problem.
The problem is compounded because, in my thinking, Sharepoint actually has two security models. One is geared towards Administration of the Farm, the other more towards User level access control, including administration of sites. The Site Collection object kind of sits in a grey area between the two. Here is my understanding of security in Sharepoint 2007:

Complications in the Site Collection permissions are that the Site Collection administrators cannot be AD or Sharepoint Groups. AD Users only. Why this is the case I don’t know. If you want to have centralised admin, and your staff changes then you will have to edit the Site Collection Administrators in all of your Site Collections. Ouch! It would be so much nicer to just add/remove users from an AD group.
Choices
Given our Sharepoint install is so large (over a terabyte of data, over a million documents), it isn’t a simple matter to rearchitect it. Many readers will point out that the upgrade was the perfect opportunity to do this – however for a few reasons the organisation wasn’t ready for big changes.
The next option is to find a way of centrally administering all these Site Collections. There are a number of admin tools out there – Quest’s Sharepoint Administrator and Avepoint spring to mind. However these tools are pricey, and you may not use the functionality frequently enough to justify the cost.
It is also possible to programme SharePoint using C#. For me this would mean a string of learning opportunities. I want something cheap and relatively quick.
PowerShell and stsadmn to the rescue
Microsoft’s new PowerShell is able to interact with the Sharepoint Object API and the scripting language is relatively simple, especially if you have ever done any *nix shell scripting. So after reading a few extremely handy blog entries – (links at end of post) I settled down to write some PowerShell scripts to centrally make changes to our Site Collection permissions.
The first issue I hit was the complexity of the Sharepoint Object Model. The naming convention runs a bit contrary to Sharepoint’s nomenclature – a Site Collection is of the SPSite object, whereas a Site is a SPWeb. Some objects are tied to the Microsoft.SharePoint namespace, while others are part of Microsoft.Office.Server namespace which is documented in a completely different place! It is possible to figure all this out, but it is a bit of a Choose Your Own Adventure experience, flicking between the different objects documentation on MSDN and trying to get a grip on how it all relates. I found printing out key pages very useful!
One epiphany for me was that it was possible to call stsadmn commands from PowerShell. Instead of trying to figure out how to set a Site Collection Owner via the object model, you can instead use PowerShell to iterate through SharePoint object collections (i.e. Site Collection, Web Applications, Sites etc.) and make changes using stsadmn.
So after all that waffling, here are the scripts.
Requirements
The following scripts should be installed to a server with SharePoint installed, and ran as the SharePoint system user – I’m not clear that this last bit is always necessary, but it was the only way I could get these things to run. Use Run As to open PowerShell as the System User and you are good to go.
Supporting functions
The following script contains functions that will be used by the other scripts. It also calls the assemblies required for PowerShell to interact with the SharePoint object model: (Download SPFunctions)
# Load SharePoint assemblies [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server") ##################################################################################### # # Sharepoint basic functions # ##################################################################################### ####### # # Get-SPFarm # # Gets the Farm on this server # ####### function global:Get-SPFarm{ return [Microsoft.SharePoint.Administration.SPFarm]::Local } ####### # # Get-SPWebApps # # Gets all the web apps on a farm # ####### function global:Get-SPWebApps{ Get-SPFarm |% {$_.Services} | where {'$_.TYPEName -eq "Windows Sharepoint Services Web Application"'} |% {$_.WebApplications} |% {Write-Output $_} } ####### # # List-SPWebApp-GUID # # Lists the Web Applications on a farm, and their GUIDS # (which can be used to grab a single Web App). # ####### function global:List-SPWebApp-GUID { Get-SPWebApplication | Select Id, DisplayName | Format-Table -AutoSize } ####### # # Get-SPWebApp # # Gets a specific web app by its GUID # Hint: use List-SPWebApp-GUID to list all the GUIDs # ####### function global:Get-SPWebApp($guid){ # If no guid sent get all the web applications for the farm if($guid -eq $null){ Get-SPFarm |% {$_.Services} | where {'$_.TYPEName -eq "Windows Sharepoint Services Web Application"'} |% {$_.WebApplications} |% {Write-Output $_} } else { $myfarm = Get-SPFarm; return $myfarm.GetObject($guid); } } ######## # # Get-SPWeb # # Gets a single site by its URL # ######## function global:Get-SPWeb($url,$site) { if($site -ne $null -and $url -ne $null){"Url OR Site can be given"; return} #if SPSite is not given, we have to get it... if($site -eq $null){ $site = Get-SPSite($url); } #Output 1 or more sites... if($url -eq $null){ for($i=0; $i -lt $s.AllWebs.Count;$i++){ Write-Output $s.AllWebs[$i]; ##Send through Pipeline $s.Dispose(); ##ENFORCED DISPOSAL!!! } }else{ Write-Output $site.OpenWeb() } } ######## # # Check-GUID # # Checks that a given string is the format of a GUID (but doesn't verify that the guid is valid) # ######### function global:Check-GUID($guid){ $regex = "^({{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}}{0,1})$"; if ($guid -match $regex){ return $true; } else { return $false; } } ######## # # Get-SPSite # # Gets a single Site Collection by its URL # ######## function global:Get-SPSite($url){ return New-Object Microsoft.SharePoint.SPSite($url); } ######## # # Get-UserProfileConfigManager # # Returns UserProfileConfigManager object used for managing MOSS User Profiles # ######## function global:Get-UserProfileConfigManager([string]$PortalURL){ # Get portal context object $site = Get-SPSite($PortalURL); $servercontext = [Microsoft.Office.Server.ServerContext]::GetContext($site); $site.Dispose(); # Return the UserProfileConfigManager New-Object Microsoft.Office.Server.UserProfiles.UserProfileConfigmanager($servercontext); } ######## # # Get-MemberGroupManager # # Gets the MemberGroupManager object for a portal - this can be used to make changes to membership groups # ######## function global:Get-MemberGroupManager($PortalURL){ $ugm = Get-UserProfileManager($PortalURL);$m $ugm.GetMemberGroups(); } ######## # # Get-UserProfileManager # # Gets a UserProfileManager object for a specific portal. This can be used to make changes to # user profiles # ######## function global:Get-UserProfileManager($PortalURL){ # Get portal context object $site = Get-SPSite($PortalURL); $servercontext = [Microsoft.Office.Server.ServerContext]::GetContext($site); $site.Dispose(); # Return the UserProfileManager object New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager($servercontext); } ######## # # Delete-MemberGroup # # Removes a MemberGroup from the given Portal URL # ######## function global:Delete-MemberGroup($PortalURL){ $mm = Get-MemberGroupManager($PortalURL); $name = Read-Host "MailNickName"; foreach ($m in $mm){ if ($m.MailNickName -eq $name){ Write-Host "Deleting " $m.MailNickName; $m.Delete(); } } }
These functions are mainly based on those described on Zach Rosenfield’s blog. Any errors are obviously mine. Save this as SPFunctions.ps1.
Update Owner and Secondary Owner of Site Collection
This script zings through all the site collections on the selected farm, and updates the Owner and/or the Secondary owner. Note that it calls the SPFunctions.ps1 file above. (Download edit-spowners)
####### # # edit-spowners # # Sets the owner and secondary login for all site collections in a web application # # TODO: Check userinput and handle errors gracefully # ####### .SPFunctions.ps1; #################################################################################### # # Main script # #################################################################################### # Preamble cls; write-host "################################################################################### This application updates the owner and secondary owner of all site collections for the given Web Application. Here is a list of all Web Applications on the farm" -foregroundcolor yellow; # List the GUIDs of all web apps on this farm List-SPWebApp-GUID; write-host "Please copy the GUID for the web application you wish to use by selecting then clicking the right mouse button." -foregroundcolor yellow; write-host " Note that the Central Admin site collection has no name, but does have a GUID! Don't select this one by accident! " -foregroundcolor red; Write-Host "You can paste the GUID by clicking the Right mouse button again" -ForegroundColor yellow; # Prompt user for GUID to use $guid = Read-Host -Prompt "GUID"; $guid = $guid.Trim(); # Check GUID for correct format Write-Host "Checking GUID"; switch(Check-GUID($guid)){ "False" { Write-Host "Error: Invalid GUID. Exiting..." -ForegroundColor red; return;} } # Get the specified web app $webapp = Get-SPWebApp($guid); # Check that webapp has been got and if not inform user Write-Host "The following Web App will be affected: " $webapp.name; # List out the site collections that will be changed and prompt to continue Write-Host "The following site collections will be affected" -ForegroundColor yellow; $webapp.Sites | ft url, owner, secondarycontact; $continue = Read-Host -Prompt "Continue? (y|n)>"; switch($continue){ "y" {break;} default {Write-Host "Exiting..." -ForegroundColor red; return;} } # Ask user to specify username for owner $owner = Read-Host -Prompt "Username of new Owner 1) Use DOMAINusername format to set a new Owner 2) Leave blank to leave the Owner alone Owner> "; # Note STSADM checks validity of usernames # Ask user to specify username for secondary owner $second = Read-Host -Prompt "Username of new Secondary Contact 1) Use DOMAINusername format to set a new Secondary Contact 2) Type none to set the Secondary Contact as blank 3) Leave blank to leave the Secondary Contact alone Secondary Contact> "; # Check username is correct format or empty string # Create the appropriate STADM option to set the owner and/or secondary contact for each site switch($owner){ "" {$own_param = ''} default {$own_param = "-ownerlogin `"$owner`""} } switch($second){ "" {$sec_param = ''} "none" {$sec_param = "-secondarylogin `"`""} default {$sec_param = "-secondarylogin `"$second`""} } # For each site collection echo the url and make the update (will generate success message) $time = Measure-Command { foreach ($site in $webapp.Sites) { $command = "stsadm -o siteowner -url `"" + $site.url + "`" $own_param $sec_param"; Write-Host $command; Invoke-Expression $command; $count++; } } Write-Host "$count Site Collections updated in" $time.TotalSeconds "seconds." -ForegroundColor yellow; ############################## End of script #############################
This makes good use of the stsadmn trick – note you have to build your command as a string then call it using Invoke-Expression.
Adding/removing users from Site Collection Administrators
I’ll be using a similar technique to create a script to add and remove users from the Site Collection Administrators list on each site. Watch this space.
Handy links
- SharePoint PowerShell for Beginners – on The Silverlightness of SharePoint blog
- PowerShell and SharePoint – Oh Yes – on Adventures in SPWonderland
- Managing SharePoint with PowerShell – on Zach Resenfield’s blog
[…] with the previous script, you will need to save the supporting functions script and invoke it before running this […]
how do I run this script?
the update-spadmins to update the site collection administrators. thanks
i receive the following error
The term ‘Get-SPWebApplication’ is not recognized as a cmdlet, function, operab
le program, or script file. Verify the term and try again.
At C:powershellscriptsSPSiteCollectAdmin.ps1:27 char:21
+ Get-SPWebApplication <<<< | ft name, id;
when i run powershell.exe c:powershellscriptsSPSiteCollectAdmin.ps1 when i’m in c:
any ideas?
Thanks
Have you saved the preceding script in the same directory?
I’ve made some minor changes to the scripts – but these were to make the scripts a little clearer, and wouldn’t change your case.
To test the function in question, run the SPFunction.ps1 script, then try Get-SPWebApplications | ft name, id
If that returns a table with the name and GUID of each Web Application, then the script should work for you.
Thanks for the response 🙂
Yes, they are both in the same directory.
C:PowerShellScriptsSPFunctions.ps1
C:PowerShellScriptsSPSiteCollectAdmin.ps1
PS C:> powershell.exe c:powershellscriptsSPFunctions.ps1
An empty pipe element is not permitted.
At C:powershellscriptsSPFunctions.ps1:35 char:27
+ |% <<<
An empty pipe element?
I’ve updated both posts with powershell scripts to include downloadable ps1 files – I think the rendering of the pages might have corrupted some of the scripts so they don’t work correctly. Using the downloaded files should work better.
[…] with the previous script, you will need to save the supporting functions script (Download SPFunctions) in the same […]
Hi Ducan,
No doubt your block is very helpful.
Can you please post a scipt to create a new webapplication & a new site collection in MOSS 2007 using powershell?
I have gone through a number of blogs but didn’t find any helpful information.
I’m not likely to ever have to do this myself, especially since we are moving to SharePoint Online which is SharePoint 2013 under the hood.
I’d recommend taking a good look at the objects in SharePoint 2007 – a useful list of equivalent commands and objects for SharePoint 2007 is here. Get-Help for any specific command is usually quite helpful.
[…] with the previous script, you will need to save the supporting functions script in the same directory. IMPORTANT: You must […]