Friday, September 14, 2012

Sharepoint 2010 - Change Domain Membership



Hello,

Couple of days ago I had to change domain membership of a single server Sharepoint 2010 farm with separate database server. Browsing the Internet I found little data about this and most recommendations were based on backup/reinstall/restore procedure.

However, I thought I would give it a try with plain domain membership change and with switching Sharepoint 2010 service accounts. I encountered many problems and I will mentioned some of them here. Here's the procedure:
  • Perform full backup of Sharepoint 2010 farm from Sharepoint Central Administration
  • Create new service accounts in the destination domain
  • Make sure you know your Sharepoint 2010 farm passphrase which you entered when you initially provisioned the farm
  • Change the domain membership of  SQL database server first (in my case the SQL database was running under LocalSystem account so I had no issues with that)
  • Give the future farm account from the new domain sysadmin permissions to the SQL database engine (actually only security admin and dbcreator permissions are necessary)
  • At this point your Sharepoint is not working
  • Run stsadm -o setconfig db with the -connect switch to connect to your Sharepoint configuration database. You will have to use your new domain farm credentials here.
  • After this step, the Central Administration site should be working, however, your Sharepoint box is still in the old domain. In my case I had the domain trust established between the old and new domains.
  • Create the new Sharepoint managed accounts by selecting the accounts from the new domain: Central Administration > Security > General Security > Configure Managed Accounts
  • Change the service accounts to reflect the newly added managed accounts: Central Administration > Security > Change Service Accounts
  • Add your farm account to the local administrators group on the Sharepoint server
  • Change Sharepoint box domain membership
  • At this point, your Sharepoint sites should be accessible. However, in my case they were not working and I received 404 not found message. I realized that after I reconnected the Sharepoint farm to the configuration database, custom solutions that these sites were using were not available any more. Thankfully, I had a full farm backup and managed to restore only the farm solutions. I redeployed the solutions from the Central Administration and the sites worked!
  • At one point, after a couple of iisresets and server restarts I received "The trial period has expired" error message when I opened the Sharepoint sites. Running Sharepoint Configuration Wizard again solved this issue.
  • Looking at the "Central Administration > Manage services on service" I saw only a couple of services listed while I know there should be more. Running Install-SPService from Powershell re-registered these services. This is important step for Sharepoint Service Applications to work properly.
  • Almost all Service Applications were started and I could access the management pages for them except the two most important ones, User Profile Synchronization service and Search service. No matter what I did I could not fix them or even restore them. I ended up creating and provisioning the new services from powershell. There aren't any user generated data in these services so recreating them was not a big issue.
 Here are a few links that helped me solve problems with provisioning new service applications:
This one helped me to solve SharePoint Server Search instance reporting "Service is offline" when trying to start/provision.
http://msdnrss.thecoderblogs.com/2011/06/unable-to-create-a-search-service-application-errors-were-encountered-during-the-configuration-of-the-search-application/

This is actually about multitenancy, but has some excellent code snippets that helped me provision User Profile Synchronization and Search service.
http://www.harbar.net/articles/sp2010mt5.aspx

I truly hope that these steps will help someone avoid the pain I suffered :)




Change from SharePoint Server name to Fully Qualified Domain Name (FQDN)

It is generally best practice to use fully qualified domain names when creating web applications in Windows SharePoint Services 3.0 and Microsoft Office SharePoint server 2007.
I am a bit excused in our environment since I was not hired when the servers and web applications were installed.
Prior to going Live with a SharePoint solution for our global company, the URLs would need to change from local server names to Fully Qualified Domain Names (FQDN). This is however not as much trouble as it seems.
1: Change Alternate Access Mappings in Central Administration
Go to Central Administration -> Operations -> Global Configuration -> Alternate access mappings
Click Edit Public URLs.
Click Alternate Access Mapping Collection drop down list in the top right corner and choose a web application.
There is 1 entry for each web application and editing them all could be a good idea, but generally changing the url to central admin is not needed.
It is generally considered bad practice to give access to Central Administration over the Internet, but I still change the Url to it. I just don’t open up the port in the firewall. I just like to have uniform addresses.
Enter the FQDN instead of the servername.
You have several possibilities on how to go about this and what you choose generally comes down to how you want users to access the site. Our site is set up so that all users access it using our FQDN. You can’t enter it using hostname and ports. This is done so that when using google analytics you only need to setup 1 site.
If you want to access the website using both servername and FQDN:
Enter the external url under Internet and leave the servername in the default field.
If you want to access the website using only FQDN:
- Edit the default field to reflect the FQDN.
2: Change bindings in Internet Information Services (IIS) Manager
First we will recap on what the choices are when you create a web application.
In order for IIS and SharePoint to differentiate the web applications either hostname or portnumber must be unique for each web application. Offcourse both can be unique.
This means you can only host one web application that uses port 80 unless you use hostnames.
Later on if you want to create another website without hostname you will need to use another port number or use a hostname.
Normally everyone uses hostnames for normal websites and port 80, but portnumbers for central admin, mysites and SSP.
When you originally created the web application, then the bindings in IIS depends on how you created the website.
                                                           Hostname                         Portnumber
With port number only           Empty                                 80
With hostname and port         code-journey.com        80
Generally all websites should be hosted on port 80. It would be odd to have to write google.com:8080 to access google.
Hosting them all on port 80 means you need to use hostnames.
If you want to only access the web application using the FQDN then edit the bindings, and make sure the FQDN is entered under hostname. Also make sure there is the only item in the Bindings list.
If you want to access the web application using both servername and FQDN then just make sure only the port is entered in the Bindings or alternatively that there is a binding for both the servername and the FQDN.
Don’t panic when you can’t log in anymore using the old or new address. Read on.
3: Edit Local Loopback Settings
You have probably noticed by now that you are prompted for a login no matter what you type in as credentials now. That’s by design as you now log in to a local IIS site that has another adress than the servername. To fix it read this post
If you mess up the address to central admin and can’t access it, even after this fix, take a look at the binding, and then you can change the URL for it. Read this post.
4: IISRESET
Do an IISRESET now and make sure that you can access all the sites you changes url for.
If you can’t access some of them, check the bindings, and then check the Alternate Access Mappings.
5: Change SSP settings
Go to Central Administration -> SharedServices1 (or whatever you called it) ->
User Profiles and My Sites -> My Site settings
Change the urls for Preferred Search Center and Personal Site Services to use the FQDN.
If you have set up any mysite trusted locations go change those too.
If you are using BDC at all, then go to Excel Services Trusted File Locations and edit each line.
Update: 
I should probably have mentioned that the domain you put in as the new address should be one your server can resolve.
If you are doing this on a developer machine then changes are it doesn’t know the domain. Add it to either the DNS service you might be running, or add the domain to your local hosts file in Windows.
Default path is:  C:\Windows\System32\drivers\etc\hosts (the file has no extention)
Add this to it:
127.0.0.1        your-non-existing-domain-name
If you have UAC running, then you might need to open notepad up as administrator and then browse to the file.
You should now be good to go with your new SharePoint Url.

Moving SharePoint to a new active directory domain

Let's pretend that your company has been bought or you bought another company that has SharePoint installed and already being used. The active directory team decides that they want to duplicate the current usernames and copy them to a new active directory domain. No problem they think. As you will soon find out, SharePoint doesn't like that idea without some migration of users. If you are fortunate enough to be able to convert all the users at once and never use the old domain in the meantime then you are in luck. Microsoft in Service Pack 2 for SharePoint has included functionality to make you job easier. stsadm command line utility should do it for you. Just use the migrateuser option. Call that for each user and you should be in good shape. If however, you do not know what users will be migrated to the new domain, and when, and if the old domain will still be used, need a back out plan, etc then this strategy may not work unless you want to be fielding calls from users and migrating them as they have users. This is hardly a proactive approach. HttpModules come to the rescue. SharePoint is an asp.net application, so it has web.config. Web.config files allow us to add custom HttpModules to SharePoint. HttpModules essentially allow us to handle events before the application does. So, what I propose is to look at the username and domain coming in. If the domain of the current user is the same as what is in SharePoint database (see the UserInfo table in the SharePoint SITES database) then we do nothing. If however they are different then we migrate the SharePoint User information to be the current user's domain. This does a couple of things. First, it makes it so both old and new domains can access SharePoint. Second, it allows users to be migrated when ever they hit the site so we don't have to know when they will be migrated, rollback if the active directory team decides to roll back their plan, etc. It is a flexible plan for all, and best of all it is a proactive approach. As it turns out SharePoint has a SharePoint.dll that allows us to call this migration tool from c#. So, all we need to do is create a MS Visual Studio 2003 class library, reference the SharePoint.dll in the project, sign out dll, install our new HttpModule into SharePoint and we are done. To install the HttpModule into SharePoint, there are a couple of places we need to make changes. The appSettings will need to be customized to work in your environment, HttpModules section will need to be changed to use your PublicKeyToken and version number. Make the following changes C:\Program Files\Common Files\Microsoft Shared\web server extensions\60\TEMPLATE\LAYOUTS\web.config
  
    
  
  
    
 
    
    
    
    
 
    
  
  
    
    
    
    
  
 
 
Depending on where you set the web root for SharePoint Portal Server 2003, you will want to edit the web.config there. Check IIS if you are not sure. It is the location that your IIS website points to. C:\Inetpub\SharePoint\web.config
  
    
 
    
    
    
    
 
    
  
  
    
    
    
    
  
Our dll must be Signed in order for SharePoint to accept it. Signing a dll is beyond the scope of this blog, but there are lots of good articles. Just google it. ;) I do recommend you hard code the version number (at least during devlelopment). If you don't you will need to change the two web.configs above everytime you redeploy. If you have a development server with Visual Studio 2003 on it you can develop directly on the server. This includes debugging. I recommend it if you can do it. If not, you can develop on your local machine, copy the dll to the server after it builds. In either case, you will want to uninstall the previous dll from the gac, then install the new one, then reset IIS (unless you change the version everytime) because IIS doesn't see that the dll in the gac changed since the version didn't change. Here is the command line way to uninstall, and install our dll into the gac. C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\gacutil /u AuthenticationMapper C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\gacutil -I "c:\AuthenticationMapper.dll" C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\gacutil -l AuthenticationMapper iisreset I recommend calling iisreset as well. I don't think it is necessary if you change the version everytime, but I didn't so I needed iisreset so that IIS would see the new dll. Here is the class you will need in your project. This code works, but error handling is left to you to decide how to handle things. There are references to Config.Instance.xxx. This is a class that just accesses the web.config. So, yes, you can access the web.config of the application we are plugging into (in this case SharePoint).
start code
 
using System;
using System.Web;
using System.IO;
using System.Security;
using System.Data.SqlClient;
using System.Data;
using Microsoft.SharePoint.Administration;
using System.Collections;
using System.Web.Mail;
using System.Diagnostics;
namespace AuthenticationMapper
{
   /// 
   /// Summary description for Class1.
   /// 
   public class AuthenticationMapper : IHttpModule
   {
      // IHttpModule members
      public void Init(HttpApplication httpApp)
      {
         httpApp.AuthenticateRequest += new EventHandler(this.OnAuthenticateRequest);
      }
      public void Dispose()
      {
         // Usually, nothing has to happen here...
      }
      // event handlers 
      public void OnAuthenticateRequest(object o, EventArgs ea)
      {
 
         string url = Url;
         bool neededMigration = false;
         try
         {
            if (User == null) throw new Exception("User was null");
          
            if (!url.ToUpper().EndsWith("SPSCRAWL.ASMX") && !url.ToUpper().EndsWith("SITEDATA.ASMX"))
            {
               string currentLogon = User.Identity.Name;
               // if it isn't the Network Service making request then try to migrate it
               // I think the Network Service is for the SharePoint indexer
               if (currentLogon.ToUpper() != @"NT AUTHORITY\NETWORK SERVICE")
               {
                  string currentUsername = this.CurrentUserNameOnly(User.Identity.Name);
                
                  string[] sharepointLogons = GetSharePointLogon(currentUsername);
                  for (int i=0; i
                  {
                     try
                     {
                        // if Current User credentials are different from the ones in SharePoint
                        // then migrate user in SharePoint to match the Current User credentials (i.e. change domain)
                        if (sharepointLogons[i].ToUpper() !=  currentLogon.ToUpper())
                        {
                           MigrateUser(currentLogon, sharepointLogons[i]);
                           neededMigration = true;
                        }
                     }
                     catch (Exception ex)
                     {
                        // handle error here
                     }
                  }
               }
            }
         }
         catch (Exception ex)
         {
            // handle error here
         }
         // sometimes the security context or something gets messed up (on certain pages) after the migration.
         // Redirect seems to fix it.
         if (neededMigration)
         {
            HttpContext.Current.Response.Redirect(url);
         }
      }
      
      // update references to sharepointLogon in SharePoint to be currentLogon
      public void MigrateUser(string currentLogon, string sharepointLogon)
      {
         Impersonation su = new Impersonation();
         if(su.ImpersonateUser(Config.Instance.PrivilegedUserName, Config.Instance.PrivilegedUserDomain, Config.Instance.PrivilegedUserPassword)) 
         {
            // Migrate user to new domain.
            // For more info: http://support.microsoft.com/kb/896593
            SPGlobalAdmin wss = new SPGlobalAdmin();
       
            wss.AllowUnsafeUpdates = true; // we need this so the update will be allowed
            bool enforceSidHistory = false;
            wss.MigrateUserAccount(sharepointLogon, currentLogon, enforceSidHistory);
            //Insert your code that runs under the security context of a specific user here.
            su.UnImpersonation();
         }
         else
         {
            throw new Exception("Could not impersonate a user that can run stsadm.exe.");
         }
      }
    
      // username should not include domain
      public string[] GetSharePointLogon(string username)
      {
         ArrayList logons = new ArrayList();
         SqlDataReader reader;
       
         string connStr = Config.Instance.SharePointSiteDatabaseConnectionString;
         using (SqlConnection conn = new SqlConnection(connStr))
         {
            conn.Open();
            // SharePoint doesn't actually every delete a user, it just marks it as deleted. If the user is added again, then it is "undeleted" instead of added.
            string sql = string.Format(@"select distinct tp_login from dbo.UserInfo where tp_deleted = 0 and tp_Login like '%\{0}'", username);
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.CommandType = CommandType.Text;
            reader = cmd.ExecuteReader();
            using (reader)
            {
               while (reader.Read())
               {
                  logons.Add(Convert.ToString(reader[0]));
               }
            }
         }
         return (string[])logons.ToArray(typeof(string));
      }
      public string CurrentUserNameOnly(string logon)
      {
         return logon.Split(new char[]{'\\'})[1];
      }
      public string CurrentDomainOnly(string logon)
      {
         return logon.Split(new char[]{'\\'})[0];
      }
    
 
 
      public System.Security.Principal.IPrincipal User
      {
         get
         {
            return HttpContext.Current.User;
         }
      }
     
      public string Url
      {
         get
         {
            string val = HttpContext.Current.Request.Url.ToString();
            return val;
         }
      }   
}
}
end code

No comments:

Post a Comment