Mathias Osterkamp

Specialist – focus development Microsoft technology stack

Migrate User Profile My Links

I like to show how to export and import user profile quick links from one to another SharePoint OnPremise platform.

Problem

SharePoint users can store favorite links directly inside the mysite. If you like to migrate to another SharePoint version or different server maybe you like to transfer these links without copy the database. In general these links are stored in the ProfileDB of your server. There is a smart article about this from Vinods source.

Here a small sample, where you can edit links:

My Links

source

Solution

I wrote a improved script to import and export the data via xml. For export we use the profile manager to get the data and collect for every user all links.

$profileManager = Get-UserProfileManager
$profiles = $profileManager.GetEnumerator() 
$all = $profileManager.Count
$collection = @() 
$num = 0
foreach ($profile in $profiles) { 
    $userProfile = $profileManager.GetUserProfile($profile.AccountName);
    foreach ($link in   $userProfile.QuickLinks.GetItems() ) {
        $myLink = [PSCustomObject]@{
            AccountName  = $profile.AccountName
            Title        = $link.Title
            Url          = $link.Url
            Group        = $link.Group
            PrivacyLevel = $link.PrivacyLevel
            ID           = $link.ID
        }
        $collection += $myLink 
    }
    $num ++
    if (0 -eq ($num % 1000)) {
        Write-Host "User profiles collected $num / $all" -ForegroundColor Gray
    }
}

On the import site we need only recreate this link

$profileManager = Get-UserProfileManager
$userProfile = $profileManager.GetUserProfile($link.AccountName);
$userProfile.QuickLinks.Create($link.Title, $link.Url, "UserSpecified", $link.Group, $link.PrivacyLevel) | Out-Null

Full script:

Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue

Function Export-UserProfileLinks($filePath) {
    Try {
        Write-Host  "Enter Export-UserProfileLinks"  
        $list = Get-UserProfileLinks
        #convert to xml
        $xmlBackup = $list | ConvertTo-FullXML -ObjectName "Link" -RootNodeName "Links"
        #write file
        $xmlBackup | Out-File -FilePath $filePath
    }		
    Catch {
        throw $_.Exception.Message
    }
    Write-Host  "Leave Export-UserProfileLinks" 
}
Function Import-UserProfileLinks($configFilePath) {   
    Try {
        Write-Host  "Enter Import-UserProfileLinks" 
        [xml] $configXml = Get-Content $configFilePath -Encoding UTF8
        foreach ($link in $configXml.UserProfileLinks.UserProfileLink) {
            Edit-UserProfileLink $link 
        }
    }		
    Catch {
        throw $_.Exception.Message
    }
    Write-Host  "Leave Import-UserProfileLinks"
}
Function Get-UserProfileLinks() {
    $profileManager = Get-UserProfileManager
    $profiles = $profileManager.GetEnumerator() 
    $all = $profileManager.Count
    $collection = @() 
    $num = 0
    foreach ($profile in $profiles) { 
        $userProfile = $profileManager.GetUserProfile($profile.AccountName);
        foreach ($link in   $userProfile.QuickLinks.GetItems() ) {
            $myLink = [PSCustomObject]@{
                AccountName  = $profile.AccountName
                Title        = $link.Title
                Url          = $link.Url
                Group        = $link.Group
                PrivacyLevel = $link.PrivacyLevel
                ID           = $link.ID
            }
            $collection += $myLink 
        }
        $num ++
        if (0 -eq ($num % 1000)) {
            Write-Host "User profiles collected $num / $all" -ForegroundColor Gray
        }
    }
    Write-Host "User profiles $num collected"
    return $collection
}
function Edit-UserProfileLink($link) {
    Try {
        $profileManager = Get-UserProfileManager
        $userProfile = $profileManager.GetUserProfile($link.AccountName);
        $userProfile.QuickLinks.Create($link.Title, $link.Url, "UserSpecified", $link.Group, $link.PrivacyLevel) | Out-Null
    }		
    Catch {
        Write-Host  "$($link.AccountName) cannot import $($link.Title)" -ForegroundColor DarkYellow
    }
}
function Get-UserProfileManager() {
    $caURL = (Get-SPWebApplication -IncludeCentralAdministration | Where-Object -FilterScript {
            $_.IsAdministrationWebApplication -eq $true
        }).Url
    $serviceContext = Get-SPServiceContext -Site $caURL 
    return New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager($serviceContext); 
}
function ConvertTo-FullXML {
    [CmdletBinding()]
    param (
        #Object to Input
        [Parameter(ValueFromPipeline)]$InputObject,
        #Name of the root document node. Defaults to "Objects"
        $RootNodeName = "Config",
        $ObjectName = $null
    )
    begin {
        [xml]$Doc = New-Object System.Xml.XmlDocument
        #Add XML Declaration
        $null = $doc.AppendChild($doc.CreateXmlDeclaration("1.0", "UTF-8", $null))
        #Add XML Root Node
        $root = $doc.AppendChild($doc.CreateElement($RootNodeName))
    }
    process {
        if ($null -eq $ObjectName) {
            $elementname = $InputObject.gettype().name
        }
        else {
            $elementname = $ObjectName
        }
        $childObject = $doc.CreateElement($elementname)
        foreach ($propItem in $InputObject.psobject.properties) {
            $propNode = $doc.CreateElement($propItem.Name)
            $propNode.InnerText = $propItem.Value
            $null = $childObject.AppendChild($propNode)
        }
        $null = $root.AppendChild($childObject)
    }
    end {
        return $doc.outerxml
    }
}

To run with on export server

$file = "c:\temp\profilelinks.xml"
Export-UserProfileLinks $file

And recreate it on import server

$file = "c:\temp\profilelinks.xml"
Import-UserProfileLinks $file

Addition

I like to mention there are also some solution to display these links in modern ui sample or sample 2