You can configure SharePoint 2016 to import Active Directory properties to user profiles via Central Admin > Manage Service Applications > User Profile Service Application > Configure Synchronization Connections.
The synchronization connection will import numerous Active Directory properties. Unfortunately, user photos are not one of the properties. I have no idea why- it's an AD property like anything else, except it's binary, not text.
The Install Microsoft Identity Manager for User Profiles in SharePoint Server 2016 TechNet article explicitly states the built in Active Directory Import "does not import user photos" and recommends Microsoft Identity Management Server (MIM) instead.
The article states the built in Active Directory Import has the advantage of "not need[ing] a separate server installation," which implies the MIM server should be installed on a separate server.
I was not happy with the prospect of creating another VM to install MIM just to sync user photos. So I wrote a PowerShell script instead that syncs user photos from AD to SharePoint. Run this script on a SharePoint server.
First, install the AD PowerShell module.
Install-WindowsFeature RSAT-AD-PowerShell
Then run the script.
# Import AD module and set constants.
Import-Module ActiveDirectory
$domain = 'YourDomain\\' # Escape the backslash character
$mySitesUrl = "https://mysites.yourdomain.com"
$photoLibraryName = "User Photos"
$photoLibraryFolderName = "Profile Pictures"
$global:photoPropertyName = "PictureURL"
$global:directory = $PSScriptRoot + "\User Photos"
function SyncPhoto($web, $photoLibraryFolder, $username, $userProfile)
{
Write-Host -NoNewline "Searching for $username in Active Directory... "
$user = $(try {Get-ADUser $username -properties thumbnailPhoto} catch {$null})
if ($user)
{
Write-Host "found."
if ($user.thumbnailPhoto)
{
Write-Host -NoNewline "Downloading user photo... "
# Download photo from Active Directory.
$photoFilename = $username + ".jpg"
$photoPath = $global:directory + "\" + $photoFilename
$user.thumbnailPhoto | Set-Content ($photoPath) -Encoding byte
Write-Host "done."
Write-Host -NoNewline "Uploading photo to SharePoint..."
# Upload photo to SharePoint My Sites.
$photoUrl = $photoLibraryFolder.Url + "/" + $photoFilename
$photoAbsoluteUrl = $mySitesUrl + "/" + $photoUrl
$photo = Get-Item $photoPath
$photoStream = $photo.OpenRead()
$spFile = $photoLibraryFolder.Files.Add($photoUrl, [System.IO.Stream]$photoStream, $true)
Write-Host "done."
$photoStream.Close()
# Update user profile
Write-Host -NoNewline "Updating user profile..."
$userProfile[$global:photoPropertyName].Value = $photoAbsoluteUrl
$userProfile.Commit()
Write-Host "done."
}
else
{
Write-Host "No photo found."
}
}
else
{
Write-Host "not found."
}
Write-Host ""
}
# Create local photos directory.
New-Item -ItemType Directory -Force -Path $directory | Out-Null
# Locate photo library in SharePoint My Sites.
$site = Get-SPSite -Identity $mySitesUrl
$web = $site.RootWeb
$photoLibrary = $web.Lists[$photoLibraryName]
$photoLibraryFolder = $web.GetFolder($photoLibraryName + "/" + $photoLibraryFolderName)
# Iterate over all SharePoint user profiles.
$serviceContext = Get-SPServiceContext -Site $site
$userProfileManager = New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager($serviceContext)
$userProfiles = $userProfileManager.GetEnumerator()
while ($userProfiles.MoveNext())
{
$userProfile = $userProfiles.Current
$username = $userProfile.AccountName -replace $domain -replace ""
$photoUrl= $userProfile[$global:photoPropertyName]
if ($username)
{
SyncPhoto $web $photoLibraryFolder $username $userProfile
}
}