Friday, June 12, 2015

SharePoint 2013: Working with Move-SPUser and Set-SPUser when migrating users

Print this posting

This posting consolidates my notes with regard to migrating user accounts from old SharePoint instances to new ones. I had the need to cleanup and migrate a subset of users from a legacy, Internet-facing SharePoint 2007 farm, using a dedicated instance of Active Directory Lightweight Directory Services (ADLDS), all co-located on a single server. User account maintenance over the years had not be consistent nor robust and as a result user account formats were inconsistent and also reflected changing domain implementations.  Looking through the site collection's AllUsers list, one would see varying user account userLogin formats, such as:
  • i:0#.f|OLDDOMAIN1|First.Last
  • i:0#.f|OLDDOMAIN2|First.Last
  • i:0#.f|OLDDOMAIN3|First.Last
  • [machine name]\First.Last
Even the display names of these user accounts frequently contained varying domain names and other odd formats.  I also found some duplication and special character usage. There was no relationship between the old ADLDS and the new AD authentication services, and so performing a simple user migration was not possible.  Alot of user accounts had never experienced a login event, and many others had not been used in over a year.  Given the formatting of the old user accounts, additional effort was needed to cleanup the user account formatting in SharePoint, and then appropriately map the user accounts to their new implementations in AD.

Migrating Old User Accounts to New Domain

To do this, I first migrated the content DB using the usual pathway of: 2007 --> 2010 --> 2013, where both the intermediary 2010 and final 2013 farm web applications were configured as standard claims-based authentication web applications.  After the content DB was satisfactorily migrated and the site brought up, I reviewed the list of users in the old ADLDS, drafting a list of which ones really needed to be migrated.  To draft this list, I pulled user account information via repadmin, and then I coordinated with legacy site stakeholders, providing them lists of the old accounts for them to review.  I also pulled user account lastlogins to better identify and determine user accounts useful to migrate.  Over the course of several rounds of stakeholder review and comment, a final list was drafted.

Next, I used this basic script to pull the list of users in the migrated site collection.  The migrated content was contained in a single content database, a single web application, containing a single site collection. So, I only needed to look at one AllUsers list, like so:
$web=Get-SPWeb [new web application URL] $web.AllUsers | Select-Object DisplayName, ID, userLogin | Sort-Object DisplayName | Format-table -auto
This list revealed the extent of what needed to be done.  About a third of what was listed here needed to be migrated.  Because the usernames were so varied, it wasn't clear to me that writing a loop function would be the most time-effective approach.  Instead, I copied the target user accounts into Excel, and then used standard Excel string manipulation tools to generate the following set of statements for each user account to be migrated:
$User=Get-SPUser -Identity "i:0#.f|OLDDOMAIN|First.Last" -Web "[new web app URL]" Move-SPUser -Identity $User -NewAlias "NEWDOMAIN\First.Last" -IgnoreSID -confirm:$false
In some cases, users had two accounts, due to what appear to have been misspellings or a customization request by a user, such as to shorten a name to a letter or two.  In such cases, I had to create two sets of user account migration steps, each set moving the legacy user account to the same destination account:
$User=Get-SPUser -Identity "i:0#.f|OLDDOMAIN|First.Last" -Web "[new web app URL]" Move-SPUser -Identity $User -NewAlias "NEWDOMAIN\First.Last" -IgnoreSID -confirm:$false $User=Get-SPUser -Identity "i:0#.f|OLDDOMAIN|F.Last" -Web "[new web app URL]" Move-SPUser -Identity $User -NewAlias "NEWDOMAIN\First.Last" -IgnoreSID -confirm:$false
I then incorporated these statements into PowerShell script and executed.  It was useful to have it as a script because in this way I could re-execute the script over multiple trial migration runs - each fresh content database migration bringing with it the old user accounts untouched, and a portion of these would need to be re-mapped to their counterparts in AD.

Cleaning Up User Account Display Names

Another aspect of migrating the old user accounts involved cleaning up the user account DisplayName.  Here again I found a variety of formats and special characters and here again I used Excel string manipulation tools to quickly draft a set of statements for each user account needing cleanup:
$name=Get-SPUser -Identity "i:0#.f|OLDDOMAIN|First.Last" -Web "[New web app URL]" Set-SPUser -Identity $name -Web "[new web app URL]" -DisplayName "First Last"
These statements I also placed into a PowerShell script for re-use during subsequent trial migration runs.

  • Thanks to Trevor Seward for his assistance in this posting that helped me to better understand some aspects of the Move-SPUser command.
  • The Move-SPUser NewAlias value must be in Pre-Windows 2000 format.  This means 20 characters or less. Otherwise, you will see errors like, "Move-SPUser : The user does not exist or is not unique."  You can determine the pre-Windows 2000 username currently set for the user by going to the account in Active Directory Users and Computers control panel, and then selecting the Account tab.
  • The Move-SPUser NewAlias value must be of a user account that exists within the new AD domain.  For example, if you are moving a user to DOMAIN\First.Last, then DOMAIN\First.Last must be a valid AD account already created in the domain.  If this user account does not exist, you will see an error like, "Move-SPUser : The user does not exist or is not unique." 
  • You need to refresh the $web object to see the changes.  This is because executing the Get-SPWeb command effectively takes a snapshot of the site collection and stores it in the $web object (as shown above).  If you want to see your changes reflected in the AllUsers list, you need to re-execute Get-SPWeb.


Akshit Mathur said...

Nice post,

You said : Don't use claims-based authentication format for the Move-SPUser NewAlias value ? Why? any specific reason. I tested with the claim prefix attached to username and it worked fine.

Al said...

Mathur - you are correct and thank you for pointing this out. I have removed that bullet from the Notes. After writing this post, I continued to investigate claims-based authentication and username storage in SharePoint and found that the default appears to be to store username in claims-based format. Please see this article for more current discussion on this topic: SharePoint 2013: PowerShell automation of user and group reconfigurations.

Anonymous said...

Just wanted to let you know that I was using the move-spuser cmdlet for a migrated user (from DomainA to DomainB). First I located the old username and all the site collections that had it in the respective user list. The old user name was in claims format. That went well. However I used a non-claims string as the target account name. Then I ran the move-spuser but it failed with an Object not set to an instance error. But in the process it stripped out the old domain account so that I no longer could get to the old domain account....:( But it never transferred permissions because of the error. I am not sure if the missing claims prefix was what caused it to error but it did a half-execution and left me neither here nor there. Luckily I had the list of site collections saved off and so I manually went and adjusted the permissions to the best of my ability. Anyway, just wanted to say that it seems like the move-spuser seems to be a one shot operation. If it fails, it doesn't rollback. So just be careful to get it all correct before hitting Enter.

Al said...

After executing $User=Get-SPUser, did you check the $User object to verify it contained what you expect?

Akshit Mathur said...

Hey Me Again,

I have faced a new issue, where I successfully migrated a user from Domain1 to Domain2 (as I thought, because powershell displayed no error message when moved).

I checked the data associated to that user, and it seems everything had worked good.

Now, some time after when user logged in he was not able to see any of the work he has done, no items whatsoever.

So I went in to check and saw that too. The data is all present in list/library with that user Display name , but once I click on that user it throws an error of User Not found with a different ID associated to that user.

Why did it happen? Is this related to User Profile Sync? because the old user was not deleted from AD nor from the site collection.

The data is not visible to old user nor the new user now.Data is associated to another user with a different ID.

I search this user through get-Spuser with $web.Users[$userID]. It displayed nothing. Then tried with $web.SiteUsers[$userID], then one user came up not related just some random user.

I want to know how can I fix this user now, so that the new domain account gets associated with the data he have.

For future move commands also, do I need to remove the old doamin user from site collection as well as from AD (Mark it as locked Inactive).

Please let me know if you have any idea what to do here.

Robert Kirchhof said...

I'm having the same kind of issue. After migrating the user their permissions are gone.

Al said...

Check to make sure that you adequately mapped the accounts. One typo can through this off. Make sure that every step of the process works by executing one commandlet at a time, each time following up by interrogating the object to verify it contains the expected value, before proceeding to the next step. NOTE: before engaging in this process, backup the content database(s)! This way, you can test and test again fresh each time. Even better, get copies of the production databases, mount them to your dev farm (you do have a dev farm?) and then test. I easily did over fifty tests, repeating the processes in part and whole until I was completely satisfied I understood every step and obtained the expected results at every step and then performed the production run. Trying testing just one user account at a time.

Akshit Mathur said...

@Robert : Was the old domain account removed from AD.

To overcome the permission issue, I had to add the old domain account into user profile exclusions. So that this user never sync again.

This was my scenerio since I do not want to remove the old user. Ideally we should remove the old user once move is completed.

Check for user inside user information list, that the moved user is still visible or not. Give permission
to account and check again if all the associated data is intact.