Archive for the ‘InfoTech’ Category

Getting User Profile Properties out of Sharepoint and into a table


2010
07.05

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 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!

Download Sharepoint User Profile PowerShell scripts here (zip file).

Notes on Windows Network Load Balancing


2010
06.29

Network Load Balancing simply round robins between servers, and makes sure that any client goes to the same host (called affinity) so that the client always reaches the same host in case it has a session on it.

As such, any problems with SharePoint related to performance will not have anything to do with NLB, as all it does is decide which host a client will send requests to.  Rather such problems would be down to one of the web front ends responding slowing – usually down to the application pool needing to free up memory, which should happen automatically after a while.  Where NLB confuses things is that you can’t easily tell which WFE a client machine is talking to.

If NLB is not working, the only likely symptom would be that no requests to the NLB IP address would work.  If you can ping your NLB IP address, then NLB is working.

You can check Network Load Balancing by going to Start > Administrative Tools and running Network Load Balancing (most likely you have this on your PC).  You need to connect to one of the nodes involved.  You can then see the status of the nodes. This should always be checked first in case one of the nodes has stopped responding on the NLB address).  You should also use this interface to drainstop a node before taking it offline for any reason – that way you get as graceful as possible a handover of affinities.

Update: It seems that in certain circumstances, NLB can come apart under load.  We recently switched from two high end Cisco core switches to two stacks of three Dell PowerConnect 6048 switches.  Since then NLB works, but as soon as we get moderate load coming to the NLB address, Sharepoint grinds to a halt.  Dell are chasing this, and we are promised a fix soon – until then no NLB – we can’t roll back to our Cisco switches.  Christian Aid isn’t such a huge organisation (~800 staff worldwide), but heavy users of Sharepoint for file storage – seems enough to break NLB.  I think it is something to do with the multicast hack Microsoft use to make NLB work – but I’d better wait to hear back from Dell and get it working before I can say for sure.

Dell Switches and VMware


2010
04.14

We recently swapped out our Cisco 6500 core switch, replacing it with a much cheaper pair of Dell PowerConnect 6248 stacks.  One of the first issues that came up after the swap over was that VMware High Availability stopped working.  The error message was that each VMware host could not communicate with its isolation address.

By default VMware hosts ping their default gateway to determine whether or not they are isolated from the cluster.  However, if you are running a pair of Dell PowerConnect switches using VRRP for redundancy, the VRRP IP address is not pingable (I’m not sure why Dell set this up – this isn’t the case with Cisco’s HSRP equivalent).  So the solution to the problem was to nominate another device in the same subnet as the Service Console as an isolation address and force VMware to use this.  We nominated our monitoring server, as we attempt to keep this running 24/7.  You need to go to the Advanced Settings for VMware HA, and set das.isolationaddress to the IP of the device you will be pinging, and then create a new variable das.usedefaultisolationaddress and set that to false.  After doing this, disable HA, then once that has completed, reenable.

HA should work fine after that.

Where to set Sharepoint Admin Permissions


2009
06.30

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!

Orchestra of dusty old computer equipment


2009
04.20

Don’t “donate” it to the third world to slowly biodegrade over millenia.  Teach it to sing:

Sharepoint Global Site Collection Administrator editor using PowerShell


2009
02.06

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.

On with the script: (Download Edit-SPAdmins.ps1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#######
#
#	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
####################################################################################
#
#	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
Get-SPWebApplications | ft name, id;
 
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;
}
 
$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 CAID\username
		e.g. DOMAIN\user1,DOMAIN\user2,DOMAIN\user3 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] + "@christian-aid.org";
					$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 CAID\username
		Write-Host "Please give a comma delimited list of users to remove from the site collection administrators
		e.g. DOMAIN\user1,DOMAIN\user2,DOMAIN\user3" -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.

Simple Sharepoint Denial of Service Attack


2008
12.17

If someone can guess the name of your Sharepoint service account they can easily make a denial of service attack on your Sharepoint installation.

In Sharepoint 2007 this is particularly easy as the option to login as another user is available on every page.

All the attacker needs to do is try and login as the service account.  If they get the password wrong enough times to get the account locked, your whole Sharepoint site will stop working!

What can you do about this?

  • Use an obscure username for your account – security through obscurity is never the best approach
  • Increase the number of wrong password attempts before an account is locked (or remove this setting altogether)
  • Remove the Login as another user

The last step is absolutely essential if your Sharepoint site is available on the public internet.  It is relatively easy for a malicious person to identify your public site as a Sharepoint site.  Luckily our public web site is not built on Sharepoint!

How much of a threat?

It’s denial of service so the damage is proportionate to how long it takes you to recover, and what a loss of access means to your organisation.

How to recover?

You’ll need to reenable the Service account through active directory.

Administering Sharepoint – PowerShell is your friend


2008
12.17

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.ps1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#####################################################################################
# Load Required Assemblies
#####################################################################################
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server")
#####################################################################################
#
# Sharepoint basic functions
#
#####################################################################################
 
#######
#
#	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-SPWebApplications | ft name, id;
}
 
#######
#
#	Get-SPWebApplications
#
#	Gets all the web apps on a farm
#
#######
 
function global:Get-SPWebApplications{
	Get-SPFarm |% {$_.Services} | where {
                        '$_.TYPEName -eq "Windows Sharepoint Services Web Application"'}
                         |% {$_.WebApplications} |% {Write-Output $_}
}
 
#######
#
#	Get-SPFarm
#
#	Gets the Farm on this server
#
#######
 
function global:Get-SPFarm{
	return [Microsoft.SharePoint.Administration.SPFarm]::Local
}
 
#######
#
#	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);
	}
}
 
########
#
#	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. (Download Edit-SPOwners.ps1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#######
#
#	update-spadmins
#
#	Sets the owner and secondary login for all site collections in a web application
#
#######
 
.\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
Get-SPWebApplications | ft name, id;
 
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 DOMAIN\username 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 DOMAIN\username 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

One Laptop Per Child


2007
11.27

Negreponte and his laptopThe One Laptop Per Child project is complaining that “Politics is stifling” their aims.

Apparently some governments are questioning the need for these laptops – something that OLPC is putting down to a lack of “big thinking”.  My experience of working on IT projects in developing countries tells me that as clever as they are, these folk from MIT have not learnt the lessons of many who have gone before.

While the concept of equipping a school in a developing country sounds on the surface as a great idea, one thing seems constantly forgotten.

The teachers at schools are not asking for this.  They are looking for basic and cheap things such as blackboards, chalk, classroom furniture and those old fashioned things called books.  Electricity for lighting would be nice.

These are the things that schools in developing countries ask for.  When they take delivery of unrequested laptops who takes responsibility for these.  Who rewrites the curriculum to teach the basics of using these laptops.  Which lessons are dropped to make time for this?

The big and bold thinking would give the people in developing countries a voice in this matter, and the boffins at MIT the humility to listen to those voices when they don’t concurr with their enthusiasm for technology.

Top Tip: Delay Your Email


2007
07.30

Bugs in your mail client (and you yourself) can cause messages to be sent unfinished, without attachments, or with tone and content you regret immediatly on clicking send.  A simple rule that delays sending of your mail by only one minute can help avoid all these email faux pas.

Here’s how to fix Outlook 2003:

  • Tools > Rules and Alerts
  • New Rule
  • Start from a blank rule.
  • Check messages after sending. Next
  • Don’t select anything (apply to all messages) Next (accept the prompt)
  • Defer delivery to a number of minutes.  1 min is the minimum – should be enough to catch the accidental sends and the ones you immediately regret :-)  Next
  • Don’t add any expections.  Next
  • Name the rule something like “Delay send for 1 minute”.  Tick turn on rule.
  • Finish.

Initially you may find waiting a minute to see your message leave your outbox stressful.  Relax.  Remember – you used to have to wait for a courier even for the most urgent messages.  If it’s REALY urgent, why are you sending an email.  Get on the phone.


Bear