User Profile Picture Import with Active Directory Import(Dirsync)


UPDATE 8/16/17 My colleague, Josh, wrote a great post on his blog detailing the benefits and considerations when moving to ADI. https://blogs.technet.microsoft.com/spjr/2017/08/14/sharepoint-considerations-when-switching-from-fim-sync-to-ad-import/

SharePoint 2013 introduced Active Directory Import. This is built on a technology called DirSync. There are many advantages with using Active Directory Import. We do not need to start up the User Profile Synchronization service and the syncs are many times faster than FIM.

SharePoint 2016 only has two options: External Identity Provider(MIM) or Active Directory Import.

This script will import profile pictures from Active Directory to SharePoint using DirSync. You can have your pictures imported and use Active Directory Import. This assumes your user profiles are being imported and populated. This will require your account to have Replicate Directory Changes for your domain as you would for SharePoint. I would suggest using the same account that you are using to sync users in SharePoint.

Scroll to the bottom for the entire script. Make sure to create the DNLookup.xml

There is logging and even a write to disk so you can see the pictures that were imported. Logs and the pictures will be written to $location. Here are the variables that will need to be changed to your environment:


$Location = "C:\Dirsync\"
#First time running, just run "DirSync" then "UploadPicture $adusers"
#Update RootDSE to match your domain
$RootDSE = [ADSI]"LDAP://dc=contoso,dc=com"
$site = Get-SpSite http://MySiteHost
$domain = "contoso\"
#This will write the pictures to the folder specified in $location
$write2disk = $true
#LDAP filter that is currently set to pull in users with thumbnailphoto and not disabled users.
$LDAPFilter = "(&(objectCategory=person)(objectclass=user)(thumbnailphoto=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"
#Set $UseDifferentSvcAccount to true to be prompted for a different service account. False will use the user that is running the script to connect to AD.
$UseDifferentSvcAccount = $false

We also need a DNLookup.xml in the $location file. We need the DNLookup just like SharePoint does since DirSync returns DN. Create that file and fill it with this sample data:


<Users>
<UR>
<dn>CN=aa,ou=hh</dn>
<sAMAccountName>PlaceholderAccountDonotdelete</sAMAccountName>
</UR>
<UR>
<dn>CN=bb,ou=hh</dn>
<sAMAccountName>PlaceholderAccountDonotdelete2</sAMAccountName>
</UR>
</Users>

The first time you run this script it will run a full. The cookie.bin will be populated. This will require your account to have Replicate Directory Changes for your domain as you would for SharePoint. I would suggest using the same account that you are using to sync users in SharePoint.

NOTE: We still need to run Update-SPProfilePhotoStore to create the thumbnails

Entire Script:

##Using Dirsync outside of SharePoint to import Profile Pictures
##Author:adamsor
##Version: 1.0
##1.0 Using Profile changes script
##1.1 Improved performance.  Filtered to only include users with thumbnailphoto.  Only pulling thumbnailphoto and sAMAccountName.
##1.1 Summary added.  Logging improved.
Add-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue
$Location = "C:\Dirsync\"
#First time running, just run "DirSync" then "UploadPicture $adusers"
#Update RootDSE to match your domain
$RootDSE = [ADSI]"LDAP://dc=contoso,dc=com"
$site   = Get-SpSite http://MySiteHost
$domain = "contoso\"
#This will write the pictures to the folder specified in $location
$write2disk = $true
#LDAP filter that is currently set to pull in users with thumbnailphoto and not disabled users.
$LDAPFilter = "(&(objectCategory=person)(objectclass=user)(thumbnailphoto=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"
#Set $UseDifferentSvcAccount to true to be prompted for a different service account.  False will use the user that is running the script to connect to AD.
$UseDifferentSvcAccount = $false
#Do not change below this line
$cookiepath = $Location+"cookie.bin"
[xml]$DNlookup = Get-Content -Path $Location"DNLookup.xml"
$log = $Location+"out.log"
$fileloc = $Location+"DNLookup.xml"
$username = $null
$global:ADUsers = $null
#Using default paritionID
$partitionID = "0C37852B-34D0-418E-91C6-2AC25AF4BE5B"
$context  = Get-SPServiceContext($site)
$pm       = new-object Microsoft.Office.Server.UserProfiles.UserProfileManager($context, $true)
$PhotoFolder = $site.RootWeb.GetFolder("User Photos")
$files = $PhotoFolder.Files
If($UseDifferentSvcAccount -eq $true)
{
if ($cred -eq $null) { $cred=(Get-Credential).GetNetworkCredential() }
}
 function Byte2DArrayToString
{
    param([System.DirectoryServices.Protocols.DirectoryAttribute] $attr)
    $len = $attr[0].length
    $val = [string]::Empty
  
    for($i = 0; $i -lt $len; $i++)
    {
         $val += [system.text.encoding]::UTF8.GetChars($attr[0][$i])
    }
    return $val
}
   function Byte2DArrayToBinary
{
    param([System.DirectoryServices.Protocols.DirectoryAttribute] $attr)
   
    $len = $attr[0].length
    #$val = New-Object [Byte] 8
  
    for($i = 0; $i -lt $len; $i++)
    {
         $val += @([byte]::Parse($attr[0][$i]))
    }
    return $val
}
Add-Type -AssemblyName System.DirectoryServices.Protocols
function Dirsync
{
Write-Progress -Activity "Querying AD..." -Status "Please wait."
If (Test-Path $cookiepath –PathType leaf) {[byte[]] $Cookie = Get-Content -Encoding byte –Path $cookiepath}else {$Cookie = $null}
$global:ADUsers = @()
$LDAPConnection = New-Object System.DirectoryServices.Protocols.LDAPConnection($RootDSE.dc)
If($cred -ne $null)
{
$LDAPConnection.Credential=$cred
}
$Request = New-Object System.DirectoryServices.Protocols.SearchRequest($RootDSE.distinguishedName, $LDAPFilter, "Subtree", $null)
$Request.Attributes.Add("thumbnailphoto")
$Request.Attributes.Add("sAMAccountName")
$DirSyncRC = New-Object System.DirectoryServices.Protocols.DirSyncRequestControl($Cookie, [System.DirectoryServices.Protocols.DirectorySynchronizationOptions]::IncrementalValues, [System.Int32]::MaxValue)
$Request.Controls.Add($DirSyncRC) | Out-Null
$Response = $LDAPConnection.SendRequest($Request)
$MoreData = $true
while ($MoreData) {
    $Response.Entries | ForEach-Object {
        write-host $_.distinguishedName
        $global:ADUsers += $_
    }
    ForEach ($Control in $Response.Controls) {
        If ($Control.GetType().Name -eq "DirSyncResponseControl") {
            $Cookie = $Control.Cookie
            $MoreData = $Control.MoreData
        }
    }
    $DirSyncRC.Cookie = $Cookie
    $Response = $LDAPConnection.SendRequest($Request)
}
Set-Content -Value $Cookie -Encoding byte –Path $cookiepath
$global:ADUsers | export-clixml C:\dirsync\aduser.clixml
return $global:adusers
}
Function GetUsername
{
    param($aduser)
    $sam = $aduser.DistinguishedName | dnlookup
    If($sam -ne $null)
    {   
        $username = $domain + $sam
        return $username
    }
return $false
}
Function DnLookup
{
    param([Parameter(ValueFromPipeline=$true)]$DN)
    $lookup=$null
    $lookup=$DNlookup.Users.ur | where {$_.dn -eq $DN}
    If ($lookup -eq $null)
    {
       #$newDN=$DNLookup.CreateElement("UR")
       $olddn = @($DNlookup.users.UR)[0]
       $newDN=$olddn.clone()
       $dsam=$aduser.Attributes["samaccountname"]
       $sam=Byte2DArrayToString -attr $dsam
       $newDN.dn = [string]$DN
       $newDN.samaccountname = [string]$sam
       $DNlookup.Users.AppendChild($newDN)
       $dnlookup.Save($fileloc)
       return $sam
    }
    Return $lookup.samaccountname
}
Function FindUserProfile
{
param([Parameter(ValueFromPipeline=$true)]$username)
$UserProfile = $pm.GetUserProfile($username)
return $userprofile
}
Function write2disk
{
param($filename,$un,$bin)
Try
{
[io.file]::WriteAllBytes($location+$filename,$bin)
}
Catch
{
"$un failed to write picture to disk but the upload probably worked"
}
}
Function UploadPicture
{
    param([Parameter(ValueFromPipeline=$true)]$adusers)
    $date = Get-Date
    [int]$i=1
    [int]$e=0
    [int]$s=0
    $c = $adusers.count
    "New upload started at $date for $($adusers.Count) users" | out-file $log -Append -noclobber
    Foreach ($ADUser in $adusers)
    {
        $decoded=@()
        [int]$p = ($i/$c)*100
        Write-Progress -Activity "Processing User Photos" -CurrentOperation $aduser.DistinguishedName -PercentComplete $p -Status "$i of $c"
        $un= GetUsername $ADUser
        If($un -eq $false)
        {
            "Could not find $($aduser.DistinguishedName)"| out-file $log -Append -noclobber
            $i++
            $e++
            Continue
        }
        try
        {
            $UPAProfile=GetUsername $ADUser | FindUserProfile
           
        }
        catch
        {
            "Could not find User Profile for $un" | out-file $log -Append -noclobber
            $i++
            $e++
            Continue
        }
        If($ADuser.Attributes.thumbnailphoto -eq $false)
        {
            "$un does not have thumbnailphoto update"| out-file $log -Append -noclobber
            $i++
           
            Continue
        }
        $filename = "$($partitionID)_$($UPAProfile.recordid).jpg"
        try
        {
        $photo = $aduser.Attributes.thumbnailphoto
        [byte[]]$bin = Byte2DArrayToBinary -attr $photo
        "Uploading $un"| out-file $log -Append -noclobber
        #Uploading with $true for overwrite.
        $files.add("User Photos/" + $filename,$bin,$true)
        "Upload successful for $un" | out-file $log -Append -noclobber
        $s++
        }
        Catch
        {
        "$un did not upload" | out-file $log -Append -noclobber
        $i++
        $e++
        Continue
        }
        If($write2disk -eq $true)
        {
        write2disk $filename $un $bin
        }
    }
#Summary to the logs
$fdate = Get-Date
$ddate = $fdate - $date
"Summary: Upload completed at $fdate(took $ddate).  $c users imported from AD. $e errors. $s user photos successfully uploaded." | out-file $log -Append -noclobber
}
Dirsync
UploadPicture $ADUsers

Download the zip from here



Comments (0)

Skip to main content