Interesting Side Effects of the Move-SPUser PowerShell Cmdlet

Recently, I was doing some user migration work in a SharePoint 2010 environment and I ran into something pretty interesting.  The important details of this work are as follows:

    • Two separate domains exist, one is production and one is test
    • Both domains share the same name (ie:
    • Same users exist in both domains (ie: user ‘contoso\spmonkey’ exists in both the production domain and the test domain)
    • Production data was duplicated from the production SharePoint  to the test SharePoint environment by attaching backups of the production content databases to equivalent Web Applications in the test environment.
    • Appropriate content database ownership/rights have been granted, sites in the content database are discoverable.


So far, so good.  Had this been all one domain, two separate farms, users would be able to begin testing content and validating the data migration was successful.  Here’s where this migration gains a little in complexity.

    • An entry is typically populated into the UserInfo table of the content database in two scenarios
    1. If a user is directly granted permission to a SharePoint site, either via SharePoint group or permission level directly
    2. If a user is granted access to a SharePoint site through an Active Directory group AND browses to the SharePoint site
      • You could actually populate user information into the UserInfo table using the object model.  Todd Carter has some posts about this subject, including a tool.
      • This is a good thing, it allows us to have certain profile information available within the content database in which the site resides – like user display names, which we use all over the place, and the user’s SID, which we can use while we calculate the rights a user has to a certain object.
    • The SID for each user is different in the test domain than it is in the production domain.  The production SID is stored in the UserInfo table
      • Because of this, SharePoint is unable to calculate rights properly for the user logging into the site – so it seems.  SharePoint is actually calculating rights just fine.  An account with the same SID as the authenticated user account has never been granted access to any resources
      • This is ok.  We have several tools for this (stsadm –o migrateuser, or the Move-SPUser PowerShell Cmdlet, for example)

Even at this point, we’re still ok.  We’re all SharePoint professionals who have dealt with domain Migrations before, or an account that has been deleted and recreated – so this is nothing new.  So now we have two viable options to get out of this mess.

    • We can manually use Move-SPUser or stsadm –o migrateuser on each user account
      • This works well if you have 10 users, it doesn’t work so well when you have 10,000 users
    • We can leverage PowerShell to enumerate all users in a particular scope (web application, site collection, etc.) and use a ForEach loop to perform a Move-SPUser Cmdlet against all user objects.
    • We can write a custom .NET application to retrieve all user objects in a particular scope and use the object model to populate the user’s new SID.
    • You could violate your EULA and render your environment unsupportable by writing a custom .NET application that replaces the user SID in the userinfo table with the value from the current domain.  DO NOT DO THIS EVER.  It seems simple, it seems to be the least resource intensive option, but it’s a HORRIBLE idea.

With our 3 viable options outlined above, (#4 not being viable), I chose to use the 2nd option and wrote a PowerShell script to do the work for me.  This worked great until the Move-SPUser Cmdlet was used against the Object Cache accounts.  Everybody remembers those accounts right?  In case that doesn’t ring a bell, maybe “Portal Super User” and “Portal Super Reader” accounts will sound more familiar.  As soon as we got past executing the Move-SPUser Cmdlet against these two accounts, even valid migrated accounts which worked earlier in the migration process received the SharePoint access denied page.  Even the site collection administrator account was denied access.  Even accounts granted full control via web application policies was denied access.  For anybody who has seen this issue before, this is immediately identifiable as a web application which is missing entries for the Object Cache accounts.  Using PowerShell, we can verify that the Portal Super User and the Portal Super Reader properties for the web application do exist and have the correct account configured.  What was peculiar is that previous policies for web application had been removed as a result of running the PowerShell script.

Further testing indicates that whenever you run Move-SPUser against a user account, any web application policies defined for that account are removed from all web applications.  The exception in this case appears to be for the “Search Crawling Account”.  Possibly any policy where the user display name does not match the display name for the user in Active Directory – but that requires further testing.  What happened in my case is that once the Move-SPUser Cmdlet was executed against the Object Cache accounts, the policies for the web applications granting full read and full control to these accounts was removed.  Further to this, the full read policy granted to NT Authority\Local Service was also removed.

The fix for this issue was actually pretty simple.  As part of the script, I initially record the Object Cache accounts to a variable.  At the end of the script, once all user accounts have been migrated using the Move-SPUser Cmdlet, I set the Object Cache accounts to their former values (this step is optional, because they don’t actually get erased) and configure web application policies to grant the appropriate permission levels to both of these accounts, as well as the NT Authority\Local Service account.  I will post the set of scripts I used for this once they are sanitized (probably a day or two).

The lesson from this blog is:
Be careful when you use the Move-SPUser Cmdlet.  It can and will erase any web application policies for any user accounts against which this Cmdlet is run.

Now that we all know about this, it’s pretty simple to plan for this and to work around this issue. 

Go ahead and test this out for yourself – I’d be very happy to hear if this does not reproduce in your environment, as it’s easily reproducible in 100% of the environments in which I’ve tested.

        Comments (10)

        1. Louis says:

          Can you give me the script snippet on how to save the current object cache accounts?


        2. I'll put this into a script and try to have a new post put up tonight – but if this is a time sensitive request, here are the lines you'll need

          $SuperUserAccount = $TargetWebApplication.Properties["portalsuperuseraccount"]

          $SuperUser = get-spuser $SuperUserAccount.tostring() -web $TargetWebApplication.url.tostring()

          $SuperReaderAccount = $TargetWebApplication.Properties["portalsuperreaderaccount"]

          $SuperReader = get-spuser $SuperReaderAccount.tostring() -web $TargetWebApplication.url.tostring()

          Then it's just a matter of outputting them somewhere (console, file, etc.)

        3. Louis,

          For now I've uploaded a script that allows you to generate a text-based report of all object cache user accounts.  I've detailed the reason I've done this in a blog post  I created tonight:


          Once I get the Move-SPUser PowerShell script sorted out to work in environments with multiple web applications, I'll include a proper script that does the user migration and retains all object cache user accounts for all web applications.

        4. Rune Mariboe says:

          It sounds like something else is at play here…

          Anyway, how did your cache accounts end up in the UserInfo table? Hopefully, no one has ever logged on using these account, as that defeats the whole purpose of having them..?

        5. Customer environments do not always adhere to best practice, and often times you'll find things in an undesired state.  In any case, if any account you end up running 'Move-SPUser' against happens to be listed as an object cache account, it will be removed as an object cache account.

        6. DubaStep says:

          Let me try logging in before posting a comment…anyways, I came across this after running a migration command and thinking "oh crap, what did I break?"  Luckily, it wasn't anything.  But I can answer how the object cache accounts got there.  It is because they are doing their intended jobs.  They are cache accounts, so they will access the site collection.  I verified this by running a Get-SPUser on a web application I know these accounts have never logged into and they were there.  So the key to take from all of this is remember to remove your object cache accounts from the list of users you retrieved from the user info table before running Move-SPUser against said list.

        7. Jason Zhang at Toronto says:

          can you post the scrip use forEach loop to mirage all odldoaminusers  newDomainuseraccount  ?  can the script read from an input file (including oldAccount , Newaccount ) ?

        8. Andres Ortiz says:

          Hi, please can you post the scripts? Thanks

        9. Surya says:

          Hi, Could you please provide the script as our company AD team is migrating approximately 15000 users from one domain to other in batches. From SharePoint prospective, we have 8 web application in our SP2013 environment and the script will be very helpful to migrate users within SharePoint.

          Could you please provide the script. Thank you.

        10. Surya says:

          Hi, Could you please provide script as our company AD team is migrating approximately 15000 users from one domain to other in batches. From SharePoint prospective, we have 8 web applications in our SP2013 environment and the script will be very helpful to migrate user within SharePoint.

          Could you please provide script at the earliest. Thank you.

        Skip to main content