SHIFT

--- Sjoerd Hooft's InFormation Technology ---

User Tools

Site Tools


Sidebar

Recently Changed Pages:

View All Pages


View All Tags


LinkedIn




WIKI Disclaimer: As with most other things on the Internet, the content on this wiki is not supported. It was contributed by me and is published “as is”. It has worked for me, and might work for you.
Also note that any view or statement expressed anywhere on this site are strictly mine and not the opinions or views of my employer.


Pages with comments

View All Comments

sharepointstsadmmigrateuser

Sharepoint User Domain Migration using Stsadm

While doing an AD migration we ran into a very annoying error while trying to migrate the sharepoint users. If you are a little bit familiar with Sharepoint you would know that all users are known by a unique id in the sharepoint database, and also by their domain. Now if a user changes from his or her domain, he or she would lose access to their sharepoint data. Even if you'll use the SID history option. That is of course not what you'd want so there is a migration option to change all ownerships from one use to another, stsadm -o migrateuser. However, while testing this command we ran into some errors and it took us months to find out what went wrong. It turned out users were connected to orphaned sites, so we had to clean up these orphaned sites before we could migrate these users. In this article I'll show you everything I can remember from these errors and issues, the fix, the migration scripts and some information to collect permissions from sharepoint.

Success and Errors

While manually testing the migration we got these success and failure messages.

This is what we got if successful:

PS C:\> stsadm -o migrateuser -oldlogin OLDDOM\sjoerd -newlogin NEWDOM\sjoerd

Operation completed successfully.

This is what we got if the command failed:

PS C:\> stsadm -o migrateuser -oldlogin OLDDOM\sjoerd -newlogin NEWDOM\sjoerd
Cannot complete this action.

Please try again.

If you use the migration script below the error code would be:

-2147467259

Setting Sharepoint Log Level

At first you should always set the loglevel to verbose to check if something obvious is going on.

This is how you set the loglevel to verbose:

=== logging level verbose
PS C:\> stsadm -o setlogginglevel -tracelevel verbose

Ans this is how to set it back to default when you're done:

stsadm -o setlogginglevel -default

Logfiles can be found here:

c:\Program Files\Common Files\Microsoft Shared\web server extensions\12\LOGS

However, nothing useful would be found in the logfiles, no really specific related errors that would give a quick clue.

Fixing the Error

After some months of troubleshooting without real results and not being able to fix even one user we brought in Microsoft Tech Support on site. They found we never had any maintenance on our system, and that we had some orphaned sites. After deleting these orphaned sites (in test environment) some users migrated successfully. So we discussed a migration path including the cleanup os orphaned sites and ended up with a 99,9% success ratio. These are the steps we did to come to that success.

Pre Upgrade Check

First you have to run a preupgradecheck. This will do an orphaned sites check and will tell you (among other things) which sites are orphaned:

stsasm -o preupgradecheck

When the command is finished (might take a while) a webpage will be shown with all found information. At the bottom of the website the orphaned sites are shown. The page will be saved in this locations:

C\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\LOGS

The information you'll get will just show you the database name, but that is the information you need to run the enumallwebs option, which will tell you the site ids.

Enum All Webs

Now run this command to enumerate all sites in the found databases:

stsadm -o enumallwebs -databasename <database name>  > c:\enumallwebs_dbname.txt

As said before, the databasename in the command is from the output of the preupgradecheck. The output you get from the enumallwebs command looks like this:

<Sites Count="3">
  <Site Id="e04240bd-f716-4d1a-8485-001d940df0da" OwnerLogin="OLDDOM\user1" InSiteMap="False">
    <Webs Count="1">
      <Web Id="83eda8d1-a537-4a1f-b9af-af94631ad642" Url="/personal/user2" LanguageId="1033" TemplateName="SPSPERS#0" TemplateId="21" />
    </Webs>
  </Site>
  <Site Id="d8acb129-1370-4d97-8045-002a1a921c2a" OwnerLogin="OLDDOM\user3" InSiteMap="True">
    <Webs Count="1">
      <Web Id="0319531c-631f-4e3c-b8a1-b73a0ebcdbfe" Url="/personal/user1" LanguageId="1033" TemplateName="SPSPERS#0" TemplateId="21" />
    </Webs>
  </Site>
  <Site Id="362c7399-6d82-4a4a-aaaf-0048d42aa676" OwnerLogin="OLDDOM\user4" InSiteMap="True">
    <Webs Count="1">
      <Web Id="ef06e315-6c1f-4795-aa57-e4cfe92c56e6" Url="/personal/user5" LanguageId="1033" TemplateName="SPSPERS#0" TemplateId="21" />
    </Webs>
  </Site>
</Sites>

The information you need is in the Site Id line, if “InSiteMap” is “False” the site is orphaned and needs to be deleted. You need to collect all site id's for all orphaned sites from this output file, and delete them.

Delete Orphaned Site

Delete the found orphaned sites using this command:

stsadm -o deletesite -force -siteid <siteid in enumallwebs> -databaseserver <database server name> -databasename <database name> -force

for example:

stsadm -o deletesite -siteid e04240bd-f716-4d1a-8485-001d940df0da -databaseserver SPDB01 -databasename WSS_INTRANET -force

As mentioned before:

  • Siteid is from the enumallwebs output
  • Databasename is from the preupgradecheck

Cleanup Databases

Then you need to run a cleanup command to delete any old database that is around. If you want to can show these first:

  • stsadm -o sync -listolddatabases <days>
    • Use zero “0” days
  • stsadm -o sync -deleteolddatabases <days>
    • Use zero “0” days

Repeat

Now, from my experience you're not ready yet. You need to repeat these steps until the preupgradecheck says there are no more orphaned sites. This usually takes two times, I only had to run these steps three times.

Migration Script

When you have no more orphaned sites you can migrate all users with this script which will make a logfile of any failure you might still have:

# Sharepoint Script
 
# Script variables
$timestamp = Get-Date -format "yyyyMMdd-HH.mm"
$startdir = "C:\"
$importcsv = "$startdir\usermig.csv"
$errorcsv = "$startdir\errorcsv-$timestamp.csv"
 
# First, Get All AD users From CSV file
$allusers = Import-Csv $importcsv
 
# Create CSV file for error reporting
# Define csv table
$myTable = @()
$teller = 1
 
ForEach ($importuser in $allusers){
 
  $UserReporting = "" | Select OLDDOMUser,ReturnValue
 
  $currentuser = $importuser.samaccountname
  $newtime = Get-Date -format "yyyyMMdd-HH.mm"
  Write-Host "This is user $teller"
  Write-Host "$newtime : Now processing $currentuser"
  $UserReporting.OLDDOMUser = $currentuser
 
  STSADM -o migrateuser -oldlogin OLDDOM\$currentuser -newlogin NEWDOM\$currentuser
 
  $UserReporting.ReturnValue = $LastExitCode
  $teller ++ 
 
$myTable += $UserReporting
}
 
$myTable | Export-csv -NoTypeInformation $errorcsv

CSV Input File

SamAccountName
user1
user2
user3
user4
user5
user6
user7

CSV Output File

"OLDDOMUser","ReturnValue"
"user1","0"
"user2","0"
"user3","0"
"user4","0"
"user5","0"
"user6","0"
"user7","0"

Sharepoint Permissions

Even though you now have migrated all users, you haven't migrated any groups. There is no command to do that, this is a manual step in the migration. We found it useful to run a script to check for any permission that are assigned to groups (or users that haven't been migrated yet).

The Script

###########################################################
#DisplaySPWebApp6.ps1 -URL <string> -searchScope <string> -userToFind <string>
#
#Author: Brian Jackett
#Last Modified Date: Jan. 12, 2009
#
#Supply Traverse the entire web app site by site to display
# hierarchy and users with permissions to site.
###########################################################
 
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
 
#DECLARE VARIABLES
[string]$siteUrl = $args[0]
[string]$searchScope = $args[1]
[string]$userToFind = $args[2]
 
#DECLARE CONSTANTS
$BUFFER_CHARS = "   "
 
function DetermineSpaceBuffer #-iterations <int>
{
  [string]$spaceBuffer = ""
  for($i = 0; $i -lt $args[0]; $i++)
  {$spaceBuffer += $BUFFER_CHARS}
 
  return $spaceBuffer
}
 
#DECLARE FUNCTIONS
function DrillDownADGroup #-group <[AD]DirectoryEntry> -depth <int>
{
  [string]$spaceBuffer = DetermineSpaceBuffer $args[1]
  $domain = $args[0].Name.substring(0, $args[0].Name.IndexOf("\") + 1)
  $groupName = $args[0].Name.Remove(0, $args[0].Name.IndexOf("\") + 1)
 
  #BEGIN - CODE ADAPTED FROM SCRIPT CENTER SAMPLE CODE REPOSITORY
  #http://www.microsoft.com/technet/scriptcenter/scripts/powershell/search/users/srch106.mspx
 
  #GET AD GROUP FROM DIRECTORY SERVICES SEARCH
  $strFilter = "(&(objectCategory=Group)(name="+($groupName)+"))"
  $objDomain = New-Object System.DirectoryServices.DirectoryEntry
  $objSearcher = New-Object System.DirectoryServices.DirectorySearcher
  $objSearcher.SearchRoot = $objDomain
  $objSearcher.Filter = $strFilter
 
  #
  $colProplist = ("name","member")
  foreach ($i in $colPropList)
  {
    $catcher = $objSearcher.PropertiesToLoad.Add($i)
  }
  $colResults = $objSearcher.FindAll()
 
  #END - CODE ADAPTED FROM SCRIPT CENTER SAMPLE CODE REPOSITORY
 
 
  foreach ($objResult in $colResults)
  {
    foreach ($member in $objResult.Properties.member)
    {
      $indMember = [adsi] "LDAP://$member"
 
      #ATTEMPT TO GET AD OBJECT TYPE FOR USER, NOT WORKING RIGHT NOW
      #$user = $indMember.PSBase
      #$user.Properties
 
      $fullUserName = $domain + ($indMember.Name)
      DisplayADEntry $fullUserName ($args[1])
    }
  }
}
 
function DisplaySPGroupMembers #-group <SPGroup> -depth <int>
{
  [string]$spaceBuffer = DetermineSpaceBuffer $args[1]
 
  if($args[0].Users -ne $Null)
  {
    #START SHAREPOINT USERS ENTITY
    Write-Output $spaceBuffer"<SPUsers>"
 
    foreach($user in $args[0].Users)
    {
      DisplayADEntry $user ($args[1] + 1)
    }
 
    #END SHAREPOINT USERS ENTITY
    Write-Output $spaceBuffer"</SPUsers>"
  }
}
 
function DisplayADEntry #-user/group <SPUser> -depth <int>
{
  #FILTER RESULTS IF LOOKING FOR SPECIFIC USER
  if($args[0].IsDomainGroup -eq "True")
  {
    $outputText = "$spaceBuffer$BUFFER_CHARS<Group>" + ($args[0])
    Write-Output $outputText
    DrillDownADGroup $args[0] ($args[1])
    $outputText = "$spaceBuffer$BUFFER_CHARS</Group>" 
    Write-Output $outputText
  }
  else
  {
    #USER FOUND AS A CHILD OF AN EMBEDDED AD GROUP
    if(($userToFind -ne "" -and ($userToFind.ToUpper() -eq $args[0].LoginName.ToUpper() -or $userToFind.ToUpper() -eq $args[0].ToUpper())) -or $userToFind -eq "")
    {
      $outputText = "$spaceBuffer$BUFFER_CHARS<User>" + ($args[0]) + "</User>"
      Write-Output $outputText
    }
  }
}
 
function DetermineUserAccess #-web <SPWeb> -depth <int>
{
  [string]$spaceBuffer = DetermineSpaceBuffer $args[1]
 
  #START SHAREPOINT GROUPS ENTITY
  Write-Output "$spaceBuffer<SPGroups>"
 
  foreach($perm in $args[0].Permissions)
  {
    #CHECK IF MEMBER IS AN ACTIVE DIRECTORY ENTRY OR SHAREPOINT GROUP
    if($perm.XML.Contains('MemberIsUser="True"') -eq "True")
    {
      DisplayADEntry $perm.Member ($args[1] + 1)
    }
    #IS A SHAREPOINT GROUP
    else
    {
      $outputText = "$spaceBuffer$BUFFER_CHARS<SPGroup>" + ($perm.Member)
      Write-Output $outputText
      DisplaySPGroupMembers $perm.Member ($args[1] + 2)
      Write-Output "$spaceBuffer$BUFFER_CHARS</SPGroup>"
    }
  }
 
  #END SHAREPOINT GROUPS ENTITY
  Write-Output "$spaceBuffer</SPGroups>"
}
 
function DisplayWebApplication #-webApp <SPWebApplication>
{
  [string]$spaceBuffer = DetermineSpaceBuffer $args[1]
 
  #START WEB APPLICATION ENTITY
  $outputText = "$spaceBuffer<Web Application>" + ($args[0].Name)
  Write-Output $outputText
 
  if($args[0].Sites -ne $Null)
  {
    #START CONTAINED SITE COLLECTIONS ENTITY
    Write-Output "$spaceBuffer$BUFFER_CHARS<Site Collections>"
 
    foreach($spSiteColl in $args[0].Sites)
    {
      DisplaySiteCollection $spSiteColl ($args[1] + 2)
      $spSiteColl.Dispose()
    }
 
    #END CONTAINED SITE COLLECTIONS ENTITY
    Write-Output "$spaceBuffer$BUFFER_CHARS</SiteCollections>"
  }
 
  #END WEB APPLICATION ENTITY
  "$spaceBuffer</Web Application>"
}
 
function DisplaySiteCollection #-siteColl <SPSiteCollection> -depth <int>
{
  [string]$spaceBuffer = DetermineSpaceBuffer $args[1]
  $sc = $args[0].OpenWeb()
 
  #START SITE COLLECTION ENTITY
  $outputText = "$spaceBuffer<Site Collection>" + ($sc.URL)
  Write-Output $outputText
 
  if($sc -ne $Null)
  {
    #START CONTAINED SITES ENTITY
    Write-Output "$spaceBuffer$BUFFER_CHARS<Sites>"
 
    foreach ($spWeb in $sc)
    {
      DisplayWeb $spWeb ($args[1] + 2)
      $spWeb.Dispose()
    }
 
    #END CONTAINED SITES ENTITY
    Write-Output "$spaceBuffer$BUFFER_CHARS</Sites>"
  }
 
  #END SITE COLLECTION ENTITY
  Write-Output "$spaceBuffer</Site Collection>"
 
  #CLEANUP SITE COLLECTION VARIABLE
  $sc.Dispose()
}
 
function DisplayWeb #-web <SPWeb> -depth <int> -parentWeb <SPWeb>
{
  [string]$spaceBuffer = DetermineSpaceBuffer $args[1]
 
  #START SITE ENTITY
  $outputText = "$spaceBuffer<Site>" + ($args[0].URL)
  Write-Output $outputText
 
  if($args[0].HasUniquePerm -eq "True")
  {
    DetermineUserAccess $args[0] ($args[1] + 1)
  }
  else
  {
    Write-Output "$spaceBuffer<!--Inherits from parent>"
  }
 
 
  if($args[0].Webs -ne $Null)
  {
    #START CONTAINED SUBSITES ENTITY
    Write-Output "$spaceBuffer$BUFFER_CHARS<Subsites>"
 
    #RECURSIVELY SEARCH SUBWEBS
    foreach ($spSubWeb in $args[0].Webs)
    {
      DisplayWeb $spSubWeb ($args[1] + 2)
      $spSubWeb.Dispose()
    }
    #END CONTAINED SUBSITES ENTITY
    Write-Output "$spaceBuffer$BUFFER_CHARS</Subsites>"
  }
 
  #END SITE ENTITY
  Write-Output "$spaceBuffer</Site>"
}
 
function DisplayMissingParametersMessage
{
  #Write-Output "You are missing a parameter for 'Site URL'"
  $script:siteURL = Read-Host "Enter Site URL"
}
 
############
# MAIN
############
 
#IF MISSING PARM FOR SITE URL, ASK FOR INPUT TO FILL
if($args.length -eq 0)
{
  DisplayMissingParametersMessage
}
 
$rootSite = New-Object Microsoft.SharePoint.SPSite($siteUrl)
$spWebApp = $rootSite.WebApplication
 
 
Write-Output "<Web Applications>"
 
#IF SEARCH SCOPE SPECIFIED FOR SITE, ONLY SEARCH SITE
if($searchScope -eq "-site")
{
  DisplaySiteCollection $rootSite 1
}
#ELSE SEARCH ENTIRE WEB APP
else
{
  DisplayWebApplication $spWebApp 1
}
Write-Output "</Web Applications>"
 
 
#CLEANUP
$rootSite.Dispose()

Starting and Redirection

The script should be started on your SharePoint management server. Also, output is directly written to the screen. You can redirect the output to a textfile, but then it doesnt't accept the requested parameters. You can however redirect the output and let the script request the url to search:

PS C:\> .\SP_Display-WebApp6.ps1 > permissions.txt
Enter Site URL: http://intranetsite

Resources

You could leave a comment if you were logged in.
sharepointstsadmmigrateuser.txt · Last modified: 2021/09/24 00:25 (external edit)