Delete all alerts for a user in Sharepoint with this PowerShell script

When a user leaves an organisation, their account is usually cleaned up – either by disabling or deleting it.  In Sharepoint this cleaning up is not automatic – generally Sharepoint hangs on to user accounts so that information about that user is still linked to the documents the user worked on. One of the side effects of this is that any alerts that user may have set up will remain, at least in Sharepoint 2007.  This leads to a lot of undeliverable messages going to Site Administrators.  Manually removing the alerts for a user requires visiting each site that has alerts, going to the site settings, User Alerts, finding the user and deleting their alerts.  I wrote the following PowerShell script to remove all alerts for a given user in one step. The script requires my supporting functions script (Download SPFunctions) – you need to save this in the same directory as the script below.  It works in a similar way to my Edit-SPAdmins script – it allows you to select a Web Application via GUID (don’t worry, it lists all the Web Applications and their GUIDs), and then loops through all the site collections and sites, removing the users alerts as it goes. So – on with the script:

# Remove alerts for a given user from all SharePoint sites in a web application
# Usage:  delete-spalert.ps1  
# Example:  delete-spalert.ps1 DOMAINUser

# Validate arguments
if ($args.Count -lt 1)
{
    Write-Warning "Usage: alerts.ps1 "
	Write-Warning "Username must be in DOMAINuser format"
    exit
}

# check that arguement is in correct format including domain
if ($args[0] -inotmatch '^w+[\]w+$')
{
    Write-Warning "Username must be in DOMAINUser format"
    exit
}

# Load Sharepoint functions
.SPFunctions.ps1;

# store arguement in variable
$myuser = $args[0]

# Preamble
cls;
write-host "###################################################################################

This application adds or removes alerts for a user in a specified 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)"
Write-Host "You will be removing alerts for the following user: $myuser"

$continue = Read-Host -Prompt "Continue? (y|n)>";
switch($continue){
	"y" {break;}
	default {Write-Host "Exiting..." -ForegroundColor red; return;}
}

$alertcount = 0

# For each site collection remove all alerts for the given user
$time = Measure-Command {
    foreach ($site in $webapp.Sites)
    {
        # get the collection of webs
        $webs = $site.AllWebs
        foreach ($web in $webs)
        {
            # get the alerts
            $alerts = $web.Alerts

            # if more than 0 alerts, iterate through these alerts to see if there is one for the user
            if ($alerts.Count -gt 0)
            {
                $myalerts = @()
                $mysites += $web.Url
                foreach ($alert in $alerts)
                {
                    if ($alert.User.LoginName -eq $myuser)
                    {
                        echo "Deleting $($alert.Title)"
                        $myalerts += $alert
                        $alertcount++
                    }
                }

                # now we have alerts for this site, we can delete them
                foreach ($alertdel in $myalerts)
                {
                    $alerts.Delete($alertdel.ID)
                }
            }
        }
    }
}

echo "Deleted $alertcount alerts in $($time.TotalSeconds) seconds"

Getting User Profile Properties out of Sharepoint and into a table

One of the shortcomings of Sharepoint 2007 is the lack of a tabular view of User Profile Properties.  This would be really useful, so I wrote a PowerShell script which gets specified profile properties for every user and writes them into a delimited file.

First up however, you need a list of the profile property names so you know what to select.  The following PowerShell script will display a table showing the internal name used by Sharepoint, and the property name displayed in the Sharepoint UI:

# Outputs a list of User profile names - both the internal name, and the name displayed in Sharepoint

[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server")
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server.UserProfiles")
# Function:          Get-UserProfileConfigManager
# Description:       return a UserProfileConfigManager object which is used for management of MOSS User Profiles
# Parameters:        SSPName          Shared Service Provider Name    

Function global:Get-UserProfileConfigManager($SSPName)
{
$ServerContext = [Microsoft.Office.Server.ServerContext]::GetContext($SSPName);
new-object Microsoft.Office.Server.UserProfiles.UserProfileConfigmanager($servercontext)
}

$cm=Get-UserProfileConfigManager("SharedServices");
$cm.getProperties() | ft name,displayname

The output from this can be used to determine the names of the properties you want to use in the next script.  To use this script, update the  $arProperties list with the property names you need.  By default this saves the results to a file UserProfiles.csv in the directory from which you run the script.  You can then import this into Excel or whatever.

# Outputs a delimited file with specified user profile properties for each user in Sharepoint

# Create array of desired properties
$arProperties = 'UserName','FirstName','LastName','Title','WorkEmail','WorkPhone','Manager','AlternateContact','RoleDescription','PictureURL';
# Specify output file
$outfile = 'UserProfiles.csv';
#Specify delimiter character (i.e. not one that might appear in your user profile data)
$delim = '^';
# Specify Shared Service Provider that contains the user profiles.
$SSP = "SharedServices";

[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server")
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server.UserProfiles")

# Function:          Get-UserProfiles
# Description:       return a UserProfileManager object containing all user profiles
# Parameters:        SSPName          SSPName

Function global:Get-UserProfiles($SSPName)
{
	$ServerContext = [Microsoft.Office.Server.ServerContext]::GetContext($SSPName);
	$UPManager = new-object Microsoft.Office.Server.UserProfiles.UserProfileManager($ServerContext);
	return $UPManager.GetEnumerator();
}
$profiles = Get-UserProfiles($SSP);

#Initialise Output file with headings
$header = [string]::join($delim,$arProperties);
Write-Output $header | Out-File $outfile

#Output the specified properties for each
$profiles | ForEach-Object {
	foreach($p in $arProperties){
		# Get the property name and add it to a new array, which will be used to construct the result string
		$arProfileProps += $_.Item($p);
	}
	$results = [string]::join($delim,$arProfileProps);
	# Get rid of any newlines that may be in there.
	$CleanResults = $results.Replace("`n",'');
	Write-Output $CleanResults
	Remove-Variable -Name arProfileProps
} | Out-File -Append $outfile

The next stage of development would be to pipe the output of the first script into the second, instead of setting up a list of desired properties – it is probably more useful to just grab everything.

I’m sure this could be written much more gracefully, but I can’t work out how to iterate through the UserProfile.Item array/object.  Any suggestions gratefully received!

Where to set Sharepoint Admin Permissions

There are a lot of places in Sharepoint where you can assign permissions.  This is intended as a summary of where administrative level permissions can be assigned.

Site Collection Users and Groups

This is set per site collection.  Super user and regular user permissions to children of the site collection are set here.  These permissions can be inherited by an site collection child, including sub-sites.  Giving a user Full Control here does not give Site Collection Admin level control as might be assumed, but does give enough functionality for many things.

Site Collection Administrators

This can be set through the Site Collection Site Settings if you are already a Site Collection Administrator, or owner or secondary contact – i.e. existing site collection administators can add other people.  You cannot add AD groups to this list, so Site Collection Administrators must be listed individually.  Owners and Secondary Contacts are automatically Site Collection Administrators whether they are listed as such or not.

Site Collection Administrators is the highest level permission that can be given to a site collection.  A Farm Administrator does not automatically inherit this permission (but see below) but can add themselves to the a specific Site Collection’s Administrators list through Central Administration > Application management > Site Collection Administrators.

Policy for Web Application

Here you can give Site Collection Administrator type permissions to all Site Collections in a Web Application (e.g. http://division).  This can be useful for setting up admin accounts.

SSP Users and Groups

Access to some, but not all, SSP functions can be assigned via the SSP Users and groups page.

SSP Site Collection Administrators

If you are not on this list, you cannot access the Search Usage Report

SSP Web Application Policy

As for any other site collection, you can add an AD group as Site Collection Administrator by adding the group to this list.

Personalisation Services Permissions

Set up specific users who need to manage profiles and MySites in here.  You also need to set up these users under the Policy for Web Application for your MySites Web Application or they won’t be able to admin mysites fully.

Business Data Catalog Permissions

If you use these, then you need permissions in here for administration.

Central Admin Site Users and Groups

Some functionality can be given here, but mainly you will be able to see the menu, but not do anything.

Central Admin Site Site Collection Administrators

Here you get the real power over Sharepoint.

Policy for Central Admin Web Application

Again – useful for assigning admin permissions by AD group.

Phew – that is a lot of places, and possibly not an exhaustive list.  Does anyone actually need this level of granularity?  Even if that is the case, could this have been made easier through better thought out admin pages, and better labelling?  I am sure that Donald Norman and Edward Tufte would have a lot to say about Sharepoint admin!

Sharepoint Global Site Collection Administrator editor using PowerShell

I finally got round to writing a PowerShell script which allows you to globally add or remove users from Site Collection Administrators.  I had expected to be able to do this using stsadm commands, but it turns out you cannot use stsadm to remove a Site Collection Administrator (although you can add an administrator this way – and the Microsoft documentation suggestions suggests removal works too – it doesnt!).  Instead I had to delve into the Sharepoint object model and figure out where on earth the Site Administrators are set.  Turns out it is in the root site (aka Web in the code) of the site collection, which has a SiteAdministrators property – a collection of SPUser objects.  These need the IsSiteAdmin property setting to false.  That’s how the script works.

The script will also list out the Site Collection Administrators for all Site Collections in a Web Application, which is useful.  Just run the script, then quit it after you have the information you need.  At some point I’ll update the script so this list is formatted in a nicer way.

As with the previous script, you will need to save the supporting functions script in the same directory.  IMPORTANT: You must edit the value of $email-domain at line 13 to match the domain (bit after @) that your users email addresses use.

On with the script:

#######
#
#	edit-spadmins
#
#	Adds or removes site administrators from all site collections in a given web application
#
#	TODO: Check userinput and handle errors gracefully
#
#######
.SPFunctions.ps1;

##  !! Edit the email domain to whatever your users email addresses end with (after the @)
$email-domain = "dunxd.com"

####################################################################################
#
#	Main script
#
####################################################################################
# Preamble
cls;
write-host "###################################################################################

This application adds or removes users from all site collections in
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 (and their admins) that will be changed and prompt to continue
Write-Host "The following site collections will be affected" -ForegroundColor yellow;
foreach ($site in $webapp.Sites){
	Write-Host $site.url;
	$web = $site.openweb();
	$web.SiteAdministrators | ft LoginName, id;
}

$continue = Read-Host -Prompt "Continue? (y|n)>";
switch($continue){
	"y" {break;}
	default {Write-Host "Exiting..." -ForegroundColor red; return;}
}

# Is this operataion adding or removing users from Site Collection Administrators
Write-Host "Are you adding or removing users from Site Collection Administrators?" -ForegroundColor yellow;
$operation = Read-Host -Prompt "(add|remove)";

switch($operation){
	"add" {
		#Write-Host "The adding functionality has not been written yet" -ForegroundColor red; return;
		Write-Host "Please give a comma delimited list of usernames in format DOMAINusername
		e.g. DOMAINuser1,DOMAINuser2,DOMAINuser3 etc." -ForegroundColor yellow;
		$usernames = Read-Host -Prompt "Users";
		#Split the list on the commas
		$username_array = $usernames.Split(",");

		$time = Measure-Command {
			foreach ($site in $webapp.Sites){
				foreach ($user in $username_array){
					$domain_username = $user.Split("");
					$email = $domain_username[1] + $email-domain;
					$command = "stsadm -o adduser -url " + $site.url + " -userlogin $user -useremail $email -Role `"Full Control`" -username $user -siteadmin";
					Write-Host $command;
					Invoke-Expression $command;
					$count_users++;
				}
				$count_sites++;
			}
		}
		Write-Host $count_users "Admin Accounts added to" $count_sites "in" $time.TotalSeconds "seconds." -ForegroundColor yellow;
	}
	"remove" {
		$count_users = 0;
		# Which user(s) do we want to remove from the Site Collection Administrators list?
		# Collect a comma delimited list of usernames in format DOMAINusername
		Write-Host "Please give a comma delimited list of users to remove from the site collection administrators
		e.g. DOMAINuser1,DOMAINuser2,DOMAINuser3" -ForegroundColor yellow;
		$usernames = Read-Host -Prompt "Users";
		#Split the list on the commas
		$username_array = $usernames.Split(",");
		$time = Measure-Command {
			foreach ($site in $webapp.Sites){
				$web = $site.openweb();
				$admins = $web.SiteAdministrators;
				foreach ($user in $admins){
					# Check to see whether the user name is in the $username-array
					if ($username_array -contains $user.LoginName){
						$user.IsSiteAdmin = $FALSE;
						$user.Update();
						Write-Host "Removed " $user.LoginName " from Site Administrators on " $web.URL;
						$count_users++
					}
				}
				$count_sites++
			}
		}
		Write-Host $count_users "Admin Accounts removed from" $count_sites "in" $time.TotalSeconds "seconds." -ForegroundColor yellow;
	}
	default {Write-Host "You must type add or remove!  Exiting..." -ForegroundColor red; return;}
}
##############################      End of script     #############################

Hope you find that useful.  Take a look through the code to get some ideas for accessing SPUser objects via PowerShell.  I’m sure this is going to be handy in future.

Administering Sharepoint – PowerShell is your friend

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:

Sharepoint Objects
Sharepoint 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:

Sharepoint Security Model
Sharepoint Security Model

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