Have you ever found yourself in a situation where you are the Farm administrator and you need to add a user (perhaps yourself) as a site collection administrator but you don’t want to be the site collection owner because you need a business user for that, you just want to be added as a site collection administrator? Well, if you’ve tried to use the stsadm command adduser or tried to use the browser to add yourself (or someone else) as the site collection administrator you probably quickly found out that you can’t do it.

For some reason Microsoft chose to not allow farm administrators to be able to add a user as a site administrator – now, this makes no sense because as a farm administrator I have the rights to set any user (including myself) as the site owner, at which point that user is now a site administrator who can now add other users as site administrators. So why didn’t Microsoft make it so that a farm administrator can add users as site administrators even if they themselves are not listed as site administrators?

This was really annoying to me and prevented me from being able to set the security on our site collections without having to go through a lot of hoops. So, in order to get around this issue I decided to create a new command called gl-addsiteadmin. The command is pretty simple, it just takes in a user login, user name, and email and then adds the user as a site admin to the specified site collection. If the user you are trying to add as a site admin is not yourself then I just go ahead and temporarily assign your account as a site owner, add the specified user as a site admin, and then reset the site owner (of course if you are already a site admin then I just simply set the user as a site admin – but that scenario can be handled by the adduser command that already exists).

If you are attempting to add yourself as a site admin (and you don’t want to be a site owner) then I have to use an internal method called AdministratorOperationMode which sets the SPSite object into a special mode that allows administrative functions to be performed. If you are familiar with the SPSiteAdministration object and have ever disassembled it you would see that the constructor of this object calls this internal method so that it can perform admin functions on the SPSite object. What’s really strange is that Microsoft doesn’t expose the resultant SPSite object via the SPSiteAdministration object and the AdministratorOperationMode method is not public.

I hope that one day this changes as the only way to perform admin level functions on the SPSite object is to use reflection to call the method manually (unless of course the SPSiteAdministration object already exposes the require property, which, in this case it does not). Because I’m calling an internal method directly via reflection use of this command could put your environment into an unsupported state according to Microsoft so make sure that you understand what the command is doing and what your support options with Microsoft are.

That being said, I feel that you are very safe with this command as the internal method still preserves all the security checks (it makes sure you are in fact a farm administrator) and it is exactly what the SPSiteAdministration object is doing so I can’t see the use of this as causing any issues (and the reflection call only comes into play if you are attempting to add your own account as a site admin – if you used a different account to run the command to add your account then you’ll never hit the reflection call). The code to do all this is detailed below:

  1public override int Run(string command, StringDictionary keyValues, out string output)
  2{
  3    output = string.Empty;
  4
  5    InitParameters(keyValues);
  6
  7    if (Params["role"].UserTypedIn && Params["group"].UserTypedIn)
  8        throw new ArgumentException(SPResource.GetString("IncompatibleParametersSpecified", new object[] { "role", "group" }));
  9
 10    string url = Params["url"].Value.TrimEnd('/');
 11    string login = Params["userlogin"].Value;
 12    string email = Params["useremail"].Value;
 13    string username = Params["username"].Value;
 14
 15    SPWeb web = null;
 16
 17    using (SPSite site = new SPSite(url))
 18    using (SPSiteAdministration adminSite = new SPSiteAdministration(url))
 19    try
 20    {
 21        web = site.OpenWeb();
 22
 23        login = Utilities.TryGetNT4StyleAccountName(login, web.Site.WebApplication);
 24
 25        if (SPFarm.Local.CurrentUserIsAdministrator() || web.CurrentUser.IsSiteAdmin)
 26        {
 27            // First lets get our user object.
 28            SPUser user = null;
 29            try
 30            {
 31                user = web.AllUsers[login];
 32            }
 33            catch (SPException) { }
 34
 35            if (user == null)
 36            {
 37                web.SiteUsers.Add(login, email, username, string.Empty);
 38                user = web.AllUsers[login];
 39            }
 40            if (user == null)
 41                throw new SPException("User cannot be found.");
 42
 43
 44            if (web.CurrentUser.IsSiteAdmin)
 45            {
 46                // This is the easy part - the calling user is a site admin so we can simply add the user
 47                user.IsSiteAdmin = true;
 48                user.Update();
 49            }
 50            else
 51            {
 52                // This is the hard part - we need to trick the system to allow us to add the user
 53                // (not sure why Microsoft didn't allow farm administrators to add site admins when 
 54                // they can change site owners which produces the same affect but what if you don't
 55                // want to change the site owner?).
 56
 57                try
 58                {
 59                    // Give it a try first (shouldn't work but what the heck)
 60                    user.IsSiteAdmin = true;
 61                    user.Update();
 62                }
 63                catch (SPException)
 64                {
 65                    if (web.CurrentUser.ID != user.ID)
 66                    {
 67                        // If we are adding a user that is not ourselves then we can simply temporarily set
 68                        // ourselves as a site owner, add the user, and then remove ourselves as the site owner.
 69                        // The reason why we can't use this approach if we are setting ourselves as a site admin
 70                        // is that as soon as we attempt to remove ourselves as an owner it will also make it so
 71                        // we are not longer a site admin.
 72
 73                        // Note that we only do this to avoid the reflection call to the internal method at all costs (see below).
 74
 75                        string originalSiteOwner = adminSite.OwnerLoginName;
 76                        adminSite.OwnerLoginName = web.CurrentUser.LoginName;
 77
 78                        // We've changed the properties of the site object so we need to close and re-open our SPWeb object.
 79                        web.Close();
 80                        web.Dispose();
 81
 82                        web = site.OpenWeb();
 83
 84                        // Now that we have a new SPWeb object we can re-retrieve the SPUser object which we now know will exist
 85                        // so no need for null checks as the above already certified that it exists.
 86                        user = web.AllUsers[login];
 87
 88                        user.IsSiteAdmin = true;
 89                        user.Update();
 90
 91                        // Now we need to reset the original owner.
 92                        adminSite.OwnerLoginName = originalSiteOwner;
 93                    }
 94                    else
 95                    {
 96                        // It didn't work because we're not a site admin and we are trying to set ourselves
 97                        // as a site admin which means we can't use the site owner trick from above.
 98                        // Fortunately, Microsoft provides a method that you can call to put the site object
 99                        // into an Admin mode (AdministratorOperationMode).  This is what is called by the
100                        // SPSiteAdministration object's constructor so that farm administrators can set
101                        // the owner and other information.  Unfortunately Microsoft doesn't expose the
102                        // site object via the SPSiteAdministration object nor do they make the AdministratorOperationMode
103                        // method public (it's marked internal).  So, we have to use reflection to set this flag.
104                        // In my opinion this is a safe call to make as the property re-checks all the security
105                        // to make sure that you have appropriate rights to administer the site.  If Microsoft
106                        // would have made the SPSite object available from the SPSiteAdministration object
107                        // this would not be necessary.
108
109                        PropertyInfo adminOpModeProp = site.GetType().GetProperty("AdministratorOperationMode",
110                            BindingFlags.NonPublic |
111                            BindingFlags.Instance |
112                            BindingFlags.InvokeMethod |
113                            BindingFlags.GetProperty);
114                        adminOpModeProp.SetValue(site, true, null);
115
116                        // We've changed the properties of the site object so we need to close and re-open our SPWeb object.
117                        web.Close();
118                        web.Dispose();
119
120                        web = site.OpenWeb();
121
122                        // Now that we have a new SPWeb object we can re-retrieve the SPUser object which we now know will exist
123                        // so no need for null checks as the above already certified that it exists.
124                        user = web.AllUsers[login];
125
126                        user.IsSiteAdmin = true;
127                        user.Update();
128
129                    }
130                }
131            }
132
133            if (Params["role"].UserTypedIn)
134            {
135                SPRoleDefinition roleDefinition = null;
136                try
137                {
138                    roleDefinition = web.RoleDefinitions[Params["role"].Value];
139                }
140                catch (ArgumentException) {}
141
142                if (roleDefinition == null)
143                    throw new SPException("The specified role does not exist.");
144
145                SPRoleDefinitionBindingCollection roleDefinitionBindings = new SPRoleDefinitionBindingCollection();
146                roleDefinitionBindings.Add(roleDefinition);
147                SPRoleAssignment roleAssignment = new SPRoleAssignment(user);
148                roleAssignment.ImportRoleDefinitionBindings(roleDefinitionBindings);
149                web.RoleAssignments.Add(roleAssignment);
150            }
151            else if (Params["group"].UserTypedIn)
152            {
153                SPGroup group = null;
154                try
155                {
156                    group = web.SiteGroups[Params["group"].Value];
157                }
158                catch (ArgumentException) {}
159
160                if (group == null)
161                    throw new SPException("The specified group does not exist.");
162
163                group.AddUser(user);
164            }
165        }
166        else
167        {
168            throw new Exception("You need to be a site collection administrator or farm administrator to set this user as a site administrator.");
169        }
170    }
171    finally
172    {
173        if (web != null)
174            web.Dispose();
175    }
176
177    return 1;
178}

The command I created is detailed below.

Using the command is reasonably similar to using the adduser command. The only real difference is that I’m not requiring a role or group name to be specified and you don’t have to specify the siteadmin switch (naturally). The syntax of the command can be seen below:

C:\>stsadm -help gl-addsiteadmin

stsadm -o gl-addsiteadmin

Adds a user as a site admin (must be a farm administrator or a current site admin).

Parameters:
        -url <url of site collection>
        -userlogin <DOMAIN\user>
        -useremail <someone@example.com>
        -username <display name>
        [-role <role name> / -group <group name>]

Here’s an example of how to set a user as a site administrator and put them in the “Full Control” role (note that it’s usually better to put users in groups then it is to assign roles directly to them):

stsadm –o gl-addsiteadmin -url "http://intranet/" -userlogin "domain\user" -useremail "someone@example.com" -username "Gary Lapointe" -role "Full Control"