Upgrading User Profile Choice Fields to SharePoint 2010

Posted on Posted in PowerShell Scripting, Scripts

I’m working on an upgrade (database attach) for a client of mine and I ran into something somewhat unexpected with some User Profile properties – properties that used a choice field in 2007 (fields that allowed the administrator to define a list of values that the user could pick from) were migrated to Managed Term Store based fields when upgraded. At first glance this was pretty cool; the only problem was that, though the field was converted to a Managed Term Store based field (essentially a Taxonomy field), the Terms themselves were not migrated and the user-specific values were lost. Now, I may have done something wrong during the upgrade process and if someone knows what I did wrong please let me know – that said, I decided to just throw together a couple quick scripts that I could use to export the field information (along with the users’ values for those fields) and then import the information in the new Farm.

To accomplish this I created two separate scripts, one for the 2007 Farm and one for the new 2010 Farm. The 2007 script loops through all the User Profile properties and, if the property has choices defined then it creates an XML structure that defines the property name and the associated choices; I put this in a function called Get-SPChoiceProperties. It then loops through all User Profiles and, for each identified property, creates another XML structure which defines the property name and associated values along with the account name; I put this in a function called Get-SPUserProperties. I then merge these two XML structures to create a single structure which is saved to disk.

Here’s the complete code which I store in a file called Export-UserProfileProperties.ps1 (note that the function names and script name are not really important  and the code is by no means perfect – I threw this whole thing together *very* quickly so that I could just get this task done and move on to the next task):

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server.UserProfiles") | Out-Null
function Get-SPChoiceProperties() {
    $context = [Microsoft.Office.Server.ServerContext]::Default
    $upm = New-Object Microsoft.Office.Server.UserProfiles.UserProfileConfigManager $context
    $props = $upm.GetProperties()
    [xml]$xml = "<Properties></Properties>"
    foreach ($prop in $props) {
        if ($prop.ChoiceList -eq $null) { continue }
        
        $propXml = $xml.CreateElement("Property")
        $xml.DocumentElement.AppendChild($propXml) | out-null
        $propXml.SetAttribute("Name", $prop.Name)
        $choicesXml = $xml.CreateElement("ChoiceList")
        $propXml.AppendChild($choicesXml) | out-null
        foreach ($choice in $Prop.ChoiceList.GetAllTerms($true)) {
            $choiceXml = $xml.CreateElement("Choice")
            $choicesXml.AppendChild($choiceXml) | out-null
            $choiceXml.InnerText = $choice
        }
    }
    $xml
}
function Get-SPUserProperties([string[]]$propertyNames) {
    $context = [Microsoft.Office.Server.ServerContext]::Default
    $upm = New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager $context
    [xml]$xml = "<Users></Users>"
    foreach ($profile in $upm) {
        $userXml = $xml.CreateElement("User")
        $xml.DocumentElement.AppendChild($userXml) | out-null
        $userXml.SetAttribute("Account", $profile["AccountName"].Value)
        $propertyNames | % { 
            $val = ($profile[$_] -as [string[]]) -join ";"
            $userXml.SetAttribute($_, $val) 
        }
    }
    $xml
}
$propertyXml = Get-SPChoiceProperties
[string[]]$properties = $propertyXml.Properties.Property | % {$_.Name}
$userXml = Get-SPUserProperties $properties

[xml]$xml = "<ProfileData>$($propertyXml.OuterXml)$($userXml.OuterXml)</ProfileData>"
$xml.Save("c:\userdata.xml")

The next thing I needed to do was to create the script for the 2010 Farm. This script had three steps for which I created a separate function for each step:

  1. Create the Term Store Group if not present and, for each User Profile field identified in the exported XML create the Term Set and associated Terms. I store the field name and associated Term Set in a hash table that I can then use for the next steps. The function I created for this is called Create-SPProfileTermSets.
  2. For each User Profile property and associated Term Set returned by step 1, update the property with the new Term Set. The function I created for this is called Set-SPUserProfileProperties.
  3. Loop through all the users defined in the exported XML and, for each user, update the User Profile property with the appropriate Term value. The function I created for this is called Set-SPUserProfileValues.

I then load the exported XML and call these three functions in order, passing in the appropriate information as required. Here’s the completed code (again, it’s rough but it works – well, it met my needs):

function Create-SPProfileTermSets($session, [string]$groupName, [xml]$data) {
    
    $group = $session.DefaultSiteCollectionTermStore.Groups[$groupName]
    if ($group -eq $null) {
        $group = $ts.DefaultSiteCollectionTermStore.CreateGroup($groupName)
        $group.TermStore.CommitAll()
    }
    $termSets = @{}
    $data.ProfileData.Properties.Property | % {
        $name = $_.Name
        $termSet = $group.TermSets[$name]
        if ($termSet -eq $null) {
            $termSet = $group.CreateTermSet($name)
        }
        $termSets += @{$name=$termSet}
        $_.ChoiceList.Choice | % {
            $termValue = $_.Replace("&", "")
            $term = $termSet.Terms[$termValue]
            if ($term -eq $null) {
                Write-Host "Adding $termValue"
                $termSet.CreateTerm($termValue, 1033) | Out-Null
            }
        }
        $group.TermStore.CommitAll()        
    }
    $termSets
}
function Set-SPUserProfileProperties($termSets) {
    $sa = Get-SPServiceApplication | ?{$_.TypeName -eq "User Profile Service Application"}
    $context = [Microsoft.SharePoint.SPServiceContext]::GetContext($sa.ServiceApplicationProxyGroup, [Microsoft.SharePoint.SPSiteSubscriptionIdentifier]::Default)
    $upm = New-Object Microsoft.Office.Server.UserProfiles.UserProfileConfigManager $context
    $properties = $upm.ProfilePropertyManager.GetCoreProperties()
    foreach ($key in $termSets.Keys) {
        $prop = $properties.GetPropertyByName($key)
        $prop.TermSet = $termSets[$key]
        $prop.Commit()
    }
}
function Set-SPUserProfileValues($termSets, [xml]$data) {
    $sa = Get-SPServiceApplication | ?{$_.TypeName -eq "User Profile Service Application"}
    $context = [Microsoft.SharePoint.SPServiceContext]::GetContext($sa.ServiceApplicationProxyGroup, [Microsoft.SharePoint.SPSiteSubscriptionIdentifier]::Default)
    $upm = New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager $context
    
    $data.ProfileData.Users.User | % {
        if ($upm.UserExists($_.Account)) {
            $up = $upm.GetUserProfile($_.Account)
            Write-Host "Evaluating $($_.Account)..."
            foreach ($key in $termSets.Keys) {
                Write-Host "    Setting $key..."
                $prop = $up[$key]
                $prop.Clear()
                [string[]]$term = $_.GetAttribute($key).Split(';')
                $term | % {
                    if (![string]::IsNullOrEmpty($_)) {
                        $prop.Add($termSets[$key].Terms[$_].Name)
                    }
                }
            }
            $up.Commit()
        }
    }
}

$ts = New-Object Microsoft.SharePoint.Taxonomy.TaxonomySession (Get-SPSite "http://<ENTER YOUR SITE URL HERE>"),$true
[xml]$xml = Get-Content C:\userdata.xml
$termSets = Create-SPProfileTermSets $ts "Profile Properties" $xml
Set-SPUserProfileProperties $termSets
Set-SPUserProfileValues $termSets $xml

One note about the Taxonomy Session – notice that I don’t use the Get-SPTaxonomySession cmdlet – this is because I can’t stand how this cmdlet works; when you create a TaxonomySession object using the API you have the ability to force a reload from the database, thereby ignoring locally cached data, but when you use the cmdlet you don’t have this option. This can cause all kinds of issues when you are running scripts/functions like this repeatedly as what is cached may not reflect what is current and this will inevitably cause you headaches. (If you decide to use this code don’t forget to set the URL for the Taxonomy Session).

So that was my adventure for the day – as I said in the beginning, if there’s a way to do the upgrade that negates the need to do any of this please let me know; otherwise, perhaps others will benefit from today’s upgrade headaches :).

3 thoughts on “Upgrading User Profile Choice Fields to SharePoint 2010

  1. THANKS! We are doing an upgrade and have ran into the same issue. I knew I would have to write a script of some sort so you saved me the time. 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *

*