0

Good day everyone,

I need to copy documents and metadata from a library in site collection A (legacy) to another one in site collection B (brand new) via PowerShell. Lucky me, there's is only the native metadata but the SPFieldUser ones (Author, Editor) give me a hard time.

Of course, there are many values related to people who since left the company and have their account disabled/deleted. And as the source and target site collections are different, they don't rely on the same User Information List, making things really tricky to migrate them.

What I've tried so far :

I have no issue to copy files and folders themselves. For the sake of readability, I'll just display the method copying metadata from $srcItem (the original SPListItem in sitecoll A) to $tgtItem (its copy in sitecoll B).

1. The "brute-force" way

function Copy-Metadata($srcItem, $tgtItem) {
    $tgtItem["Created"]  = $srcItem["Created"] 
    $tgtItem["Author"]   = $srcItem["Author"]
    $tgtItem["Modified"] = $srcItem["Modified"]
    $tgtItem["Editor"]   = $srcItem["Editor"]
    $tgtItem.Update();
}

Of course that doesn't work properly, because User IDs don't match between both sitecolls.

2. The "classic" way

function Copy-Metadata($srcItem, $tgtItem) {
    $tgtItem["Created"]  = $srcItem["Created"] 
    $tgtItem["Author"]   = $tgtItem.Web.EnsureUser((new-object Microsoft.SharePoint.SPFieldUserValue($srcItem.Web, $srcItem["Author"])).User.LoginName)
    $tgtItem["Modified"] = $srcItem["Modified"]
    $tgtItem["Editor"]   = $tgtItem.Web.EnsureUser((new-object Microsoft.SharePoint.SPFieldUserValue($srcItem.Web, $srcItem["Editor"])).User.LoginName)
    $tgtItem.Update();
}

Works great for active users, but raises the following exception for orphan ones : Exception calling "EnsureUser" with "1" argument(s): "The specified user domain\goneuser could not be found.

3. The "desperate" way

function Copy-Metadata($srcItem, $tgtItem) {
    $tgtItem["Created"]  = $srcItem["Created"] 
    $tgtItem["Author"]   = Build-SPFieldUserValue $srcItem $tgtItem "Author"
    $tgtItem["Modified"] = $srcItem["Modified"]
    $tgtItem["Editor"]   = Build-SPFieldUserValue $srcItem $tgtItem "Editor"
    $tgtItem.Update();
}

function Build-SPFieldUserValue ($srcItem, $tgtItem, $fieldInternalName) {
    $srcUser = (new-object Microsoft.SharePoint.SPFieldUserValue($srcItem.Web, $srcItem[$fieldInternalName])).User
    try {
        $tgtUser = $tgtItem.Web.EnsureUser($srcUser.LoginName)
    }
    catch {
        New-SPUser -UserAlias $srcUser.LoginName -DisplayName $srcUser.Name -Web $tgtItem.Web.Url
        $tgtUser = $tgtItem.Web.EnsureUser($srcUser.LoginName)
    }
    return $tgtUser
}

Basically, I'm trying to explicitly add the orphan user in the target sitecoll cache, but it fails too as the New-SPUser command interrogates Active Directory, hence raising the following exception : New-SPUser : The specified user domain\goneuser could not be found.

[EDIT following Zdeněk Vinduška's answer] 4. Adding user as a SPListItem in SiteUserInfoList

$userItem = $tgtItem.Web.SiteUserInfoList.Items.Add()
$userItem["Name"] = "domain\goneuser"
$userItem.Update()

Throws Exception calling "Update" with "0" argument(s): "Cannot complete this action. Please try again."

I'm now out of ideas. Does anyone know how to achieve this ? Thank you very much.

0

Users are stored in the All Users table - Probably you could try create orphan users here in User Information List. From the first site to the new site. (This List is Site Based)

https://zimmergren.net/sharepoints-hidden-user-list-user-information-list/

  • Hi Zdeněk and thanks for the idea. I tried to add a user as a SPListItem in the cache list (see edit), but failed. – Berthim Jan 16 at 10:43
  • I mean to create a copy from UIL Site1 to UIL Site2, not to create a record like you did. – Zdeněk Vinduška Jan 16 at 10:52
0

I suggest that you use a default user as a fallback instead of creating a user within the user list which has no representative in the Active Directory.

If you'd use a migration tool like Sharegate or AvePoint this is how they do it.

[Edit] Let's elaborate a bit.

The main issue is that the user of an old list item is no longer available.

When you call EnsureUser() SharePoint will look into the sites user information list to see if the user is present. If not it will check in the Active Directory and if it finds the user it will create an item in the user information list.

If EnsureUser cannot resolve the user an error will be thrown.

I've used ILSpy to look at this function in the SharePoint binaries.

You should not tamper with SharePoints internal lists and by doing so you might get other issues down the road. There is timer jobs managing the user information list and if it goes out of sync there is a possibility that you won't get any updates from the User Profile Service to the user information list anymore and you'll have to resolve that using stsadm -o sync -listolddatabases/-deleteolddatabases and what that will do to your non-existing users in that list I don't actually know - but I'd never go down that road.

Therefor you should use a default user that is present in the Active Directory as an fallback if EnsureUser fails to find one.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.