Use case: Automatically importing user profile pictures to SharePoint 2013 (and 2010)

In last three years I helped numerous customers to install and configure SharePoint 2010 (2013). New User Profile Service application introduced the ability to easily sync User Profile properties from & to Active Directory. In this article I will explain how you can leverage PowerShell to sync user profile photos from file share to SharePoint and Active Directory.

This is how your final result might look like:

The Muppets - Automatically importing user profile pictures to SharePoint 2013 (and 2010)

The problem

Companies often have employee pictures somewhere, in most situations on a file share drive in JPG format. Back in 2010 I found this script and over the years made a number of modifications to it. The script (which you can download below) will iterate through a designated folder and update profile picture for every user in SharePoint (if user exists).

1. Create a folder on your SharePoint server with all the profile pictures
2. Each employee should have a picture in the following format: username.jpg
3. Run the script from SharePoint Management Shell with the following parameters (change these to match your environment):

Update-SPProfilePictures "http://my-sites-host-url" "User Photos" "C:\Photos-Path\" "Your_Domain"

Please note: The script has been tested with English language template of My Site Host. If your  host was deployed in another language this script might not work, or you will have to change User Photos parameter to something else.

Additional features

Once user profile photo is stored in SharePoint, you can easily export it back to Active Directory. Exchange will pick it up from there and, if you are using Outlook (Lync) 2010 or newer, employee pictures will be shown there as well.

Prerequisites, notes and further reading

  • Make sure your SharePoint is up to date with latest patches (currently Feb 2012 CU or later).
  • Make sure you follow Spence Harbar’s instructions on How to Configure User Profile Service in SharePoint 2010 or/and updates for 2013.
  • Make sure profiles are already synced with AD before running the script.
  • My Sites (My Profiles) also need to be configured before you run the script.
  • User running sync must have proper privileges to update Profile properties.
  • You need to run Search crawl before pictures are updated in Search Results.
  • If this process will be automatic, you should probably deny users to update profile pictures on their own.
  • How to configure export of profile pictures.
  • The ideal picture size is 144×144 px for SharePoint. In case you are planning to further export to Active Directory (Outlook & Lnyc) it’s size should be 10kb or less.
  • User performing sync should have the following permissions.

This is the full script, enjoy:

cls
if((Get-PSSnapin | Where {$_.Name -eq "Microsoft.SharePoint.PowerShell"}) -eq $null) {
	Add-PSSnapin Microsoft.SharePoint.PowerShell;
}

#---------------------------------------------------------------------------------
# Default Values
#---------------------------------------------------------------------------------

$spNotFoundMsg = "Unable to connect to SharePoint.  Please verify that the site '$siteUrl' is hosted on the local machine.";

#-----------------------------------------------------
# Load Assemblies
#-----------------------------------------------------

if ([Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") -eq $null)		{ throw $spNotFoundMsg; }
if ([Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server") -eq $null)	{ throw $spNotFoundMsg; }

#-----------------------------------------------------
# Functions
#-----------------------------------------------------

function ToSimpleString([string]$value, [bool]$trim = $true, [bool]$removeSpaces = $true, [bool]$toLower = $true)
{
    if ($value -eq $null) { return [System.String]::Empty; }

    if ($trim)
    {
        $value = $value.Trim();
    }

    if ($removeSpaces)
    {
        $value = $value.Replace(" ", "");
    }

    if ($toLower)
    {
        $value = $value.ToLower();
    }

    return $value;
}

function GetSPSite($url)
{
    [Microsoft.SharePoint.SPSite]$site = New-Object "Microsoft.SharePoint.SPSite" -ArgumentList $url
    return $site;
}

function GetSPWeb($url)
{
    [Microsoft.SharePoint.SPSite]$site = GetSPSite -url $url;
    [Microsoft.SharePoint.SPWeb]$web = $site.OpenWeb();
    return $web
}

function GetSPList($url, $listName)
{
    $listName = (ToSimpleString -value $listName);

    [Microsoft.SharePoint.SPWeb]$web = GetSPWeb -url $url;
    foreach($list in $web.Lists)
    {
        $title = (ToSimpleString -value $list.Title);
        if ($listName -eq $title)
        {
            return $list;
        }
    }
    return $null;
}

function GetSPDocumentLibrary($url, $libraryName)
{
    [Microsoft.SharePoint.SPDocumentLibrary]$lib = [Microsoft.SharePoint.SPDocumentLibrary](GetSPList -url $url -listName $libraryName);
    return $lib;
}

function GetSPFile($libraryInstance, $fileName)
{
    $fileName = (ToSimpleString -value $fileName -removeSpaces $false);

    foreach($file in $libraryInstance.RootFolder.Files)
    {
        $itemName = (ToSimpleString -value $file.Name -removeSpaces $false);
        if ($fileName -eq $itemName)
        {
            return $file;
        }
    }
    return $null;
}

function UploadSPFile([string]$url, [string]$libraryName, [string]$filePath, [System.Text.StringBuilder]$verbose = $null)
{
    try
    {
        [Microsoft.SharePoint.SPDocumentLibrary]$lib = (GetSPDocumentLibrary -url $url -libraryName $libraryName);
        if ($lib -eq $null)
        {
            throw (([string]'Cannot find document library "') + ([string]$libraryName) + ([string]'" at url "') + ([string]$url) + ([string]'"!'));
        }

        $bytes = [System.IO.File]::ReadAllBytes($filePath);
        $fileName = [System.IO.Path]::GetFileName($filePath);

        [Microsoft.SharePoint.SPFile]$file = GetSPFile -libraryInstance $lib -fileName $fileName;

        if ($file -eq $null)
        {
            if ($verbose -ne $null)
            {
                [void]$verbose.AppendLine("Uploading File...");
            }
            $file = $lib.RootFolder.Files.Add($fileName, $bytes);
        }
        else
        {
            if ($verbose -ne $null)
            {
                [void]$verbose.AppendLine("File Exists, overwriting...");
            }
            $file.SaveBinary($bytes);
        }

        if ($verbose -ne $null)
        {
            [void]$verbose.AppendLine(($bytes.Length.ToString()) + ([string]" bytes written!"));
        }

        return $file;
    }
    catch
    {
        if ($verbose -ne $null)
        {
            [void]$verbose.AppendLine(([string]'Error: Upload to document library "') + ([string]$libraryName) + ([string]'" at "') + ([string]$url) + ([string]'" or file "') + ([string]$filePath) + ([string]'" failed!'));
            [void]$verbose.AppendLine([string]'Error: ' + [string]$error[1]);
        }
    }

    return $null;
}

function GetSpContext($url)
{
    [Microsoft.SharePoint.SPSite]$site = GetSPSite -url $url
    return [Microsoft.Office.Server.ServerContext]::GetContext($site);
}

function GetProfileManager($url)
{
	[Microsoft.Office.Server.ServerContext]$ctx = GetSpContext -url $url
    [Microsoft.Office.Server.UserProfiles.UserProfileManager]$upm = New-Object "Microsoft.Office.Server.UserProfiles.UserProfileManager" -ArgumentList $ctx

    return $upm;
}

function GetSPUser($url, $loginName)
{
   [Microsoft.SharePoint.SPWeb]$web = GetSPWeb -url $url
   [Microsoft.SharePoint.SPUser]$user = $web.AllUsers[$loginName]
   return $user;
}

function GetProfilePropertyName($userProfileManager, $propertyName)
{
    $propertyName = (ToSimpleString -value $propertyName);
    $propertyName = $propertyName.Replace("sps-", "");

    foreach($prop in $userProfileManager.Properties)
    {
        [string]$n = (ToSimpleString -value $prop.DisplayName);
        $n = $n.Replace("sps-", "");
        if ($propertyName -eq $n) { return $prop.Name.ToString(); }

        $n = (ToSimpleString -value $prop.Name);
        $n = $n.Replace("sps-", "");
        if ($propertyName -eq $n) { return $prop.Name.ToString(); }
    }

    return $null;
}

#This function is VERY different from [System.IO.Path]::Combine
function CombineUrls([string]$baseUrl, [string]$relUrl)
{
    [System.Uri]$base = New-Object System.Uri($baseUrl, [System.UriKind]::Absolute);
    [System.Uri]$rel = New-Object System.Uri($relUrl, [System.UriKind]::Relative);

    return (New-Object System.Uri($base, $rel)).ToString();
}

function Update-SPProfilePictures([string]$webUrl, [string]$picLibraryName, [string]$localFolderPath, [string]$domain)
{
	#Get web and picture library folder that will store the pictures
	$web = Get-SPWeb $webUrl
	$picFolder = $web.Folders[$picLibraryName]
	if(!$picFolder)
	{
		Write-Host "Picture Library Folder not found"
		return
	 }

	#Attach to local folder and enumerate through all files
	$files = ([System.IO.DirectoryInfo] (Get-Item $localFolderPath)).GetFiles() | ForEach-Object {

		$username = [IO.Path]::GetFileNameWithoutExtension($_.FullName);

		#Create file stream object from file
		$fileStream = ([System.IO.FileInfo] (Get-Item $_.FullName)).OpenRead()
		$contents = new-object byte[] $fileStream.Length
		$fileStream.Read($contents, 0, [int]$fileStream.Length);
		$fileStream.Close();

		write-host "Copying" $_.Name "to" $picLibraryName "in" $web.Title "..."

		#Add file
		$spFile = $picFolder.Files.Add($picFolder.Url + "/" + $_.Name, $contents, $true)
		$spItem = $spFile.Item

		$upm = GetProfileManager -url $webUrl
		$up = $null;
		$up = $upm.GetUserProfile("$domain\$username");

		$picturePropertyName = GetProfilePropertyName -UserProfileManager $upm -PropertyName "PictureUrl";

		if($up -ne $null)
		{
			if (-not [System.String]::IsNullOrEmpty($picturePropertyName))
			{
				$PortraitUrl = CombineUrls -baseUrl $spFile.Web.Url -relUrl $spFile.ServerRelativeUrl;
				Write-Host $PortraitUrl
				$up.get_Item($picturePropertyName).Value = $PortraitUrl;
				$up.Commit();
			}
		}

	}

	Write-Host "Updating User Profile Photo Store..." -foregroundcolor yellow
	Update-SPProfilePhotoStore –MySiteHostLocation $webUrl
	Write-Host "Done" -foregroundcolor green
}

Comments

8 responses to “Use case: Automatically importing user profile pictures to SharePoint 2013 (and 2010)”

  1. […] Create “c:SPSolutionsImportProfileImagesUpdateUserProfiles.ps1″ with the code from SharePoint Use Cases […]

  2. Toni Frankola Avatar
    Toni Frankola

    Did you receive an error or something?

  3. Is there a switch or parameter I can add to the command so it won’t overwrite existing photos? I would like to run this script for any users who do not have a photo but also allow users to upload their own.

  4. I cannot seem to get to work, can anyone tell me what I am missing when trying to get this working with Sharepoint 2013?

    This is what I am doing.
    1. Downloading the full script to a text file, naming it Update-SPProfilePictures.ps1
    2. Putting an image named, username.jpg in c:\Photos-Path folder
    3. Opening the Sharepoint Management Shell with Farm/Site collection administrator account.
    4. Running Update-SPProfilePictures “http://mysites.fakedomain.com” “User Photos” “C:\Photos-Path\” “fakedomain.com”

    When I do the above steps and hit enter, I am brought back to the command prompt without an error or success message. Nothing. No picture is imported.

  5. I cannot seem to get to work, can anyone tell me what I am missing when trying to get this working with Sharepoint 2013?
    This is what I am doing.
    1. Downloading the full script to a text file, naming it Update-SPProfilePictures.ps1
    2. Putting an image named, user_name.jpg in c:\Photos-Path folder
    3. Opening the Sharepoint Management Shell with Farm/Site collection administrator account.
    4. Running Update-SPProfilePictures “http://mysites.fakedomain.com” “User Photos” “C:\Photos-Path\” “fakedomain.com”
    When I do the above steps and hit enter, I am brought back to the command prompt without an error or success message. Nothing. No picture is imported.

  6. Worked first time for me without issue. Many thanks for this awesome script!

  7. Hi,
    Thank you so munch for the script ,I have one issue with my customer that he need the count that how many users pictures are updated and not updated, please help me to get the result

  8. And for forests with multiple domains?