Social Icons

twitter google plus linkedin rss feed

Pages

11.6.15

Migrating SharePoint Users to a New Domain

I have been dreading this type of migration for years... so many years that I already had planned a couple of ways of solving the issue. It finally happened.

Scenario:

Someone decides we need to change the farm from one environment to a new one completely different with a new AD and in a new city.

Well, let's get to it. We have created a new SharePoint farm in the new environment and we have backed up and restored the content databases. We have manually changed the admin of the site collection to the new admin in the new AD in the new farm and we can access the site and see the data. Fantastic.

Fantastic?

The users in the list items are the users from the old farm. And we have several lists with a lot of user fields. And some of our lists have tens or hundreds of thousands of rows. Changing them manually is not an option.

First idea: Go refined and try stsadm -o migrateuser:

Ohh so easy... we change the login name of the user to something else and we are good because the user IDs are still the same... NO.

This is a new domain and we don't have access to the old domain users so the migrateuser parameter throws a nice User not found error.

Second idea: Go berserk and change the strings in the list items

And that worked. Oh the beauty of a simple idea. The process is pure brute force... beautiful in its barbarity... If you have read up to here you are probably desperate for a solution.

Step One:
Get all of the users from the old farm in an XML file or something really high tech (a csv could work too).

static void Main(string[] args)
{
    using (SPSite site = new SPSite(args[0]))
    {
        using (SPWeb web = site.OpenWeb())
        {
            XElement users = new XElement("Users");

            foreach (SPUser user in web.SiteUsers)
            {
                XElement xmlUser = new XElement("User");
                xmlUser.Add(new XAttribute("Name", user.Name));
                xmlUser.Add(new XAttribute("LoginName", user.LoginName));
                xmlUser.Add(new XAttribute("ID", user.ID));

                users.Add(xmlUser);
            }

            users.Save("SiteUsers.xml");
        }
   
    }

    Console.WriteLine("\nProcess finished...");
    Console.ReadLine();
}


Step Two:
Make sure all the users you need are in the new AD. As you have a list in XML you can pass it to someone with privileges in the AD.

Step Three:
Ensure the users in SharePoint, add them to a group with reading permissions and then iterate through all the items in the list changing the users from the old domain to the users in the new one.

static void Main(string[] args)
{
    XElement users = XElement.Load("SiteUsers.xml");
    string newDomain = "XXXXXXXX";

    string ListName = string.Empty;
    if (args.Length == 2) ListName = "Stratex Framework";
    else ListName = args[2];

    //Args are SiteUrl VisitorsGroup ListName
    using (SPSite site = new SPSite(args[0]))
    {
        using (SPWeb web = site.OpenWeb())
        {
            SPList listToUpdate = web.Lists[ListName];

            Dictionary<string, SPUser> NewUsers = new Dictionary<string, SPUser>();

            foreach (XElement user in users.Descendants("User"))
            {
                string LoginName = FixDomain(user.Attribute("LoginName").Value, newDomain);
                SPUser spUser = null;
                try
                {
                    //We try to ensure all the users from the XML file. We'll probably need them
                    spUser = web.EnsureUser(LoginName);
                }
                catch
                { Logger.WriteLine("The user {0} could not be found.", LoginName); }

                if (spUser != null)
                {
                    SPGroup viewers = web.Groups[args[1]];

                    viewers.AddUser(spUser);
                    //Finally we add them to a group with read permissions
                    //We can worry about restricting this further after the migration

                    if (!NewUsers.ContainsKey(LoginName)) NewUsers.Add(user.Attribute("ID").Value, spUser);
                }
            }

            web.Update();


            UpdateUsersInList(listToUpdate, NewUsers);
        }

    }

    Logger.WriteLine("\nProcess finished...");
    Console.ReadLine();
}

private static void UpdateUsersInList(SPList list, Dictionary<string, SPUser> NewUsers)
{
    int itemsInList = list.ItemCount;
    Logger.WriteLine("Updating users at {0}. {1} items.", list.Title, itemsInList.ToString());

    SPQuery qry = new SPQuery();
    qry.ViewAttributes = "Scope=\"RecursiveAll\"";
    SPListItemCollection allItems = list.GetItems(qry);
    int count = 0;

    UpdateCount(count++, itemsInList);

    foreach (SPListItem item in allItems)
    {
        try
        {
            bool changed = false;
            SPFieldCollection allFields;
            //If the item has content type it has usualy less fields
            if (item.ContentType == null)
                allFields = item.Fields;
            else
                allFields = item.ContentType.Fields;

            foreach (SPField field in allFields)
            {
                if (field is SPFieldUser)
                    changed = ChangeUserToNewDomain(item, field, NewUsers) || changed;
            }

            changed = ChangeUserToNewDomain(item, item.Fields.GetFieldByInternalName("Author"), NewUsers) || changed;
            changed = ChangeUserToNewDomain(item, item.Fields.GetFieldByInternalName("Editor"), NewUsers) || changed;

            if (changed) item.SystemUpdate(false); //if the item has not been changed we won't update it to save time
        }
        catch (Exception ex) { Logger.WriteLine("Failed to update item {0}. Exception {1}", item.Title, ex.Message); }

        UpdateCount(count++, itemsInList);
    }

    UpdateCount(count++, 0);
}

private static bool ChangeUserToNewDomain(SPListItem item, SPField field, Dictionary<string, SPUser> NewUsers)
{
    bool changed = false;
    string fieldContent = item[field.InternalName] == null ? null : item[field.InternalName].ToString();

    if (string.IsNullOrEmpty(fieldContent)) return false;

    List<string> oldUserIds = GetUserIDs(fieldContent.Split(new string[] { ";#" }, StringSplitOptions.RemoveEmptyEntries));

    if (oldUserIds.Count == 1)
    {   //The field has only one user in it
        SPFieldUserValue foundUser = FindUser(NewUsers, oldUserIds[0]);

        if (foundUser != null)
        {
            item[field.InternalName] = foundUser;
            changed = true;
        }
    }
    else if (oldUserIds.Count > 1)
    {   //The field has several users in it
        SPFieldUserValueCollection usersInField = new SPFieldUserValueCollection();
        foreach (string oldUser in oldUserIds)
        {
            SPFieldUserValue foundUser = FindUser(NewUsers, oldUser);

            if (foundUser != null)
                usersInField.Add(foundUser);
        }

        if (usersInField.Count > 0)
        {
            item[field.InternalName] = usersInField;
            changed = true;
        }
    }
            
            
    return changed;
}

private static List<string> GetUserIDs(string[] UserTokens)
{   //We do not care about the login name. The ID is gold
    List<string> result = new List<string>();

    if (UserTokens.Length > 0)
    {
        for (int i = 0; i < UserTokens.Length; i++)
        {
            int id;

            if (i % 2 == 0 && int.TryParse(UserTokens[i], out id))
                result.Add(id.ToString());
        }
    }

    return result;
}

private static SPFieldUserValue FindUser(Dictionary<string, SPUser> NewUsers, string oldUser)
{
    SPUser foundUser = null;

    if (NewUsers.ContainsKey(oldUser)) foundUser = NewUsers[oldUser];
    else
    {
        //If we can't find the ID of the user we will still try with the login or even with the Display Name
        foreach (SPUser newUser in NewUsers.Values)
        {
            if (newUser.Name == oldUser || newUser.LoginName == oldUser) { foundUser = newUser; break; }
        }
    }

    if (foundUser != null)
        return new SPFieldUserValue(foundUser.ParentWeb, foundUser.ID, foundUser.Name);
    else
        return null;
}

private static string FixDomain(string loginName, string newDomain)
{
    //Here we change the users from XXXXX\\User to YYYYY\\User
    //The source domain was claim based
    if (loginName.Contains("|")) loginName = loginName.Split('|')[1];

    string[] tokens = loginName.Split('\\');

    tokens[0] = newDomain;

    return string.Join("\\", tokens);
}

private static void UpdateCount(int currentItem, int itemsInList)
{
    int percentage;

    if (currentItem == 0) percentage = 0;
    else if (itemsInList == 0) percentage = 100;
    else
    {
        //We will only change the value every 10 times to make the process faster.
        if (currentItem % 10 != 0) return;
        percentage = currentItem * 100 / itemsInList;
    }
    Console.Write("\r");
    if (percentage >= 0 && percentage < 10)
        Console.Write("  ");
    else if (percentage >= 10 && percentage < 100)
        Console.Write(" ");

    Console.Write("{0}%", percentage);
}

This is a first prototype that has worked as expected but it's not fully tested (by far) if you need it you can use it as a base to develop your own tool.

The one who possesses the strings has the power.

No comments:

Post a Comment

10.6.15

Why Windows 10 will be a game changer?

Do you remember having to meet at your friend's house to ask him to lend you his Zanac cassette tape?

Do you remember buying a double cassette player to be able to copy the game just for testing purposes?

The floppy disks arrived then and we had those huge bendy disks of 5 1/4 they were really fast and their capacity was amazing but they didn't change the fact that you needed to go to your friend's house to get a copy of Alley Cat.

Then the internet arrived. And you still had to go to your friend's house to get the diskette if Ishar because it took much less time to get to the other side of the planet walking than downloading 1MB.

But the internet was a game changer. After some time you completely forgot about 3,5 diskettes and you would only visit your friend to get the CD of Dungeon Keeper II or something like that.

And some time later (today) you can download 60GB virtual machines in minutes. It looks like the moment proper internet arrived I stopped enjoying computers...

Windows 10 is similar when it comes to changing the paradigm of the distribution of the software and I will explain my point.


Most of the programs and games for desktops were created for Windows and not for Linux or Mac and there's a good reason for that. The market share of the windows desktop made working for any other platform a waste of money.

The the market places arrived. And instead of going out to the scary internet to download you programs you would go to a supervised environment where you could download your programs safely.

BUT

When it comes to desktops only a "ridiculous" 16.45% of the computers (W8 + 8.1) can execute market applications while the vast majority of desktops (W7 + XP) a 72.36% are still stuck in the old paradigm. (source)

And the question comes again... Why develop an application for the 16% of the computers when, for the same amount of money I can create the same app for the 88.81% (given that W8 and 8.1 are compatible with the old desktop code)? It's a no brainer.

And here comes Windows 10.

Offering Windows 10 as a free upgrade will surely convince most of the users out there with W7, 8 and 8.1 to get the latest bits and with them the ability to be clients of this new market.

Not only that. Given that Windows 10 apps will also work in any hardware capable of executing W10 the target audience will be not only increased but probably multiplied.

You will develop an app once and it will be downloadable by the desktop users, but also by the Windows Phone users, the XBox users, the Raspberry Pi users... the Hololens users! you name it.


The estimations of Microsoft about this is that in a couple of years there will be one billion W10 devices out there and then the question will be again:

Why would anyone develop for any other platform?

No comments:

Post a Comment

4.6.15

Geeky T-Shirt For Free... Count me on Xamarin!

I firmly believe that once the majority of the desktop computers of the world are capable of running Microsoft Store applications (Windows 10 will be released in July 29) the natural way for developing anything will be using the Microsoft Store I usually get excited whenever Microsoft says anything, excited or angry, but mostly excited because I am quite a naïve guy.

But even though, in the same way some android and IOS developers port their applications to the Microsoft Store, we might need to port our applications to other platforms, the other markets will be ridiculously small, but still.


I have been waiting for the right opportunity to give Xamarin a go and now, for a free t-shirt that we are exploring new platforms for a new tablet project, seems like the right moment.

The installation process in my Surface 3 Pro has not been easy. I struggled to make it run in VS2015 and started using the Xamarin Studio. I had one small issue with XS, nothing that 30 second in the Xamarin forums could not fix. All looked good, but then when I tried starting the application in the virtual devices I couldn't. Using real hardware was not an option because I only have Windows Phones at home 6.1 to 10.

I found the solution getting and installing the Xamarin Player, after that which also installed VBox, I downloaded and configured a Nexus 4 VM.

In the last step after you select which t-shirt you want you need to go to the code and do one small change. Even though the change was small it made me feel more inclined to hack through the code and modify it to do something else.



I can't wait to get the t-shirt and be one of the LINQ do the process if you want to know what i mean here.

Oh and the app has a lot of code that you will probably reuse too... another gift!



No comments:

Post a Comment