Import von Profilbildern aus einem Fileshare direkt in den SharePoint

Normalerweise ist der übliche Weg die Profilbilder direkt über das AD in den SharePoint zu snychronisieren:

image

In manchen fällen ist das aber nicht möglich, da die Bilder nicht im AD vorgehalten werden sollen. In diesem Fall muss der Import dazu also direkt erfolgen:

image

Bei einem Kunden ist der Fileshare sogar das führende System. Es wird also eine Synchronisation zwischen dem Fileshare und SharePoint benötigt. Das Passende Skript kann im Skript-Galerie heruntergeladen werden:

http://gallery.technet.microsoft.com/scriptcenter/Sync-profile-pictures-from-c6d7608a

Das Skript wird wie folgt aufgerufen:

Import-ProfileImage.ps1 [-path] <Object> [-url] <Object> [[-userProfilePropertyName] <Object>] [[-userProfilePropertyFormatString] <Object>] [-WhatIf] [-Confirm] [<CommonParameters>]

Folgende Parameter werden verwendet:

  • -path: Der Pfad zu dem Fileshare
  • -url: Die URL des MySite-Hosts
  • -userProfilePropertyName: Der Name der Eigenschaft des Profils, aus der sich der Dateiname des Bilds ergibt
  • -userProfilePropertyFormatString: Die Formatierung, die mit der Eigenschaft den Dateinamen des Bilds ergibt (z.B. {0}.jpg).

Das Skript unterstützt auch die Parameter –verbose, –whatif, –confirm und –debug

Wichtig ist, dass der Benutzer genug rechte hat. Er muss Administrator der User Profile Service Application sein. Außerdem benötigt er Besitzerrechte auf der Inhaltsdatenbank des MySite-Hosts (z.B. also der Farm-Admin).

Hier das ganze Skript:

[CmdletBinding(SupportsShouldProcess=$True, ConfirmImpact="Medium")] 
param(
    [Parameter(Mandatory=$true, Position=0, HelpMessage="The UNC path to the folder that contains the images.")]
    [ValidatePattern("^\\\\.*$")]
    $path,

    [Parameter(Mandatory=$true, Position=1, HelpMessage="The url of the mysite host.")]
    [ValidatePattern("\b(https?)://.*")]
    $url,

    [Parameter(Mandatory=$false, Position=2, HelpMessage="The name of the property that contains the key for the images.")]
    $userProfilePropertyName = "EmployeeId",

    [Parameter(Mandatory=$false, Position=3, HelpMessage="The format that is applied to the property that contains the key for the images to construct the image file name.")]
    $userProfilePropertyFormatString = "{0:D8}.jpg"
	)

BEGIN {
    #Load SPSnapin and assemblies...
    $ver = $host | select version
    if ($ver.Version.Major -gt 1) {$host.Runspace.ThreadOptions = "ReuseThread"} 
    if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) {
        Add-PSSnapin "Microsoft.SharePoint.PowerShell";
    }
    
    [void][System.Reflection.Assembly]::Load("Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")
    if ([Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Server") -eq $null)	{ throw "The assembly 'Microsoft.Office.Server' could not be loaded." }
    if ([Reflection.Assembly]::LoadWithPartialName("System.Drawing") -eq $null)	{ throw "The assembly 'System.Drawing' could not be loaded." }

    #Validate parameters...
    if (-not (Test-Path $path)){
        Write-Error "The path '$path' is not valid or accassable."
        exit
    }
    Write-Verbose "Path '$path' verified..."
    Get-SPSite $url | Out-Null
    Write-Verbose "Url '$url' verified..."
    Write-Verbose "Begin import of images from '$path' to '$url'..."
    $gc = Start-SPAssignment
    try{ Start-Transcript -path .\Import.log -append -ErrorAction Ignore }catch{}
}

PROCESS {

    # The sizes for thumbnail photos
    [int]$largeThumbnailSize = 300 
    [int]$mediumThumbnailSize = 72 
    [int]$smallThumbnailSize = 48

    # User photos folder in my site host
    $profilePictureFolder = "User%20Photos/Profile%20Pictures"

    # PictureUrl PropertyName
    $profilePictureUrlPropertyName = "PictureUrl"

    # Creates the thumbnails and upload them to SharePoint
    function Create-Thumbnail{    
        param([System.Drawing.Bitmap]$original, [int]$idealWidth, [int]$idealHeight, [Microsoft.SharePoint.SPFolder]$folder, [string]$fileName)   

        [int]$width = $original.Width
	    [int]$height = $original.Height

		if ($width -gt 0 -and $height -gt 0)
		{
			$y = 0
			$x = 0
			$num = 0.35
			$num2 = 0.5
			[int]$width2 = 0
			[int]$height2 = 0

			if (($idealWidth -eq $largeThumbnailSize -and $idealHeight -eq $largeThumbnailSize -and $width -lt $idealWidth -and $height -lt $idealHeight) -or ($width -eq $idealWidth -and $height -eq $idealHeight)){
				$width2 = ($idealWidth = $width)
				$height2 = ($idealHeight = $height)
				$x = ($y = 0)
			}else{
				if ($idealWidth -eq $largeThumbnailSize -and $idealHeight -eq $largeThumbnailSize) {
					[double]$num3 = [double]$width / [double]$height
					[double]$num4 = [double]$idealWidth / [double]$idealHeight
					if ($num3 -lt $num4) {
						$idealWidth = [int][Math]::Round([double]$idealWidth * $num3)
					}else{
						$idealHeight = [int][Math]::Round([double]$idealHeight / $num3)
					}
					$x = ($y = 0)
					$width2 = $width
					$height2 = $height
				}else{
					if ($width -lt $height){
						$y = [int]([double]($height - $width) * $num)
						$height2 = $width
						$width2 = $width
					}else{
						$x = [int]([double]($width - $height) * $num2)
						$height2 = $height
						$width2 = $height
					}
				}
			}

            $bitmap = New-Object "Drawing.Bitmap" -ArgumentList @($idealWidth, $idealHeight, [Drawing.Imaging.PixelFormat]::Format32bppArgb)
            $graphics = [Drawing.Graphics]::FromImage($bitmap)
			
			$destRect = New-Object "Drawing.Rectangle" -ArgumentList @(0, 0, $idealWidth, $idealHeight)
			$srcRect = New-Object "Drawing.Rectangle" -ArgumentList @($x, $y, $width2, $height2)
			$graphics.CompositingMode = [Drawing.Drawing2D.CompositingMode]::SourceCopy
			$graphics.CompositingQuality = [Drawing.Drawing2D.CompositingQuality]::HighQuality
			$graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
			$graphics.DrawImage($original, $destRect, $srcRect, [Drawing.GraphicsUnit]::Pixel)
            
            $stream = new-object "IO.MemoryStream"
            $bitmap.Save($stream, [Drawing.Imaging.ImageFormat]::Jpeg)
						
			$stream.Position = 0L
			$file = $folder.Files.Add($fileName, $stream, $true)
					
			$stream.Close()
            $graphics.Dispose()
            $bitmap.Dispose()	

		}
	}

    # Remove the domain from the accountname
    function Create-FileBaseName{
        param([string]$accountName)

        $split = $accountName.LastIndexOf('\')
        return $accountName.Substring($split, $accountName.Length - $split).TrimStart('\') 
    }

    # Delete a file from SharePoint
    function Delete-SPFile{
        param([Microsoft.SharePoint.SPWeb]$web, [string]$url)

        $file = $web.GetFile($url)
        if ($file.Exists){
            $file.Delete()
        }
    }

    $site = $gc | Get-SPSite $url
    $ctx = [Microsoft.Office.Server.ServerContext]::GetContext($site);
    $upm = New-Object "Microsoft.Office.Server.UserProfiles.UserProfileManager" -ArgumentList $ctx

    $web = $site.OpenWeb()
	$picFolder = $web.GetFolder($profilePictureFolder)
    $lastImport = (get-date).AddDays(-1)

    $picFolder.Url
    if (-not $picFolder.Exists){
        throw "The folder for uploading Images does not exist or is not accassable."
        exit
    }

    $userProfiles = $upm.GetEnumerator()

    $userProfiles | %{
        Write-Verbose "Begin processing file '$($_.AccountName)' at '$(get-date -displayhint Time -Format "HH:mm:ss")'..."

        [int]$employeeId = $_.get_Item($userProfilePropertyName).Value

        Write-Verbose "EmployeeID: $employeeId"

        $imgFileName = $userProfilePropertyFormatString -f $employeeId
        $imgPath = Join-Path $path $imgFileName
        $skip = $false
        if (-not (Test-Path $imgPath)){
            
            
            # Check if the user has a foto in sharepoint and remove it
            if ($_.get_Item($profilePictureUrlPropertyName).Value){
                $imgUrl = $_.get_Item($profilePictureUrlPropertyName).Value
                Write-Host "Image '$imgPath' does not exist. Delete all images in SharePoint and reset Profile."
                if ($PSCmdlet.ShouldProcess("SPFile '$imgUrl'", "Delete")){
                    Delete-SPFile $web $imgUrl
                }
                if ($PSCmdlet.ShouldProcess("SPFile '$($imgUrl.Replace("_MThumb.jpg", "_SThumb.jpg"))'", "Delete")){
                    Delete-SPFile $web $imgUrl.Replace("_MThumb.jpg", "_SThumb.jpg")
                }
                if ($PSCmdlet.ShouldProcess("SPFile '$($imgUrl.Replace("_MThumb.jpg", "_LThumb.jpg"))'", "Delete")){
                    Delete-SPFile $web $imgUrl.Replace("_MThumb.jpg", "_LThumb.jpg")
                }

                if ($PSCmdlet.ShouldProcess("Update", "Property 'PictureUrl' in Profile '$($_.AccountName)'")){
                    $_.get_Item($profilePictureUrlPropertyName).Value = $null
				    $_.Commit()
                }
            }else{
                Write-Host "Image '$imgPath' does not exist. Skip user."
            }

        }else{
            # Get Image
            $img = Get-Item $imgPath
            
            #check if user already has a image...
            if ($_.get_Item($profilePictureUrlPropertyName).Value){
                $lastWriteTime = $img.LastWriteTime

                if (($lastWriteTime -lt $lastImport)){
                    Write-Debug "The image was modified the last time at '$lastwriteTime'. Skip Profile..."
                    $skip = $true
                }else{
                    Write-Debug "The image was modified the last time at '$lastwriteTime'. Upload new image..."
                }
            }

            if (-not $skip){
                if ($PSCmdlet.ShouldProcess("Image '$imgPath' to '$($picFolder.Url)'", "Upload")){
                    
                    $fileStream = $img.OpenRead()
                    $contents = new-object byte[] $fileStream.Length
                    $readBytes = $fileStream.Read($contents, 0, [int]$fileStream.Length)

                    Write-Debug "$readBytes bytes read."

                    $bitmap = New-Object "Drawing.Bitmap" -ArgumentList @($fileStream, $true)

                    $accountBaseName = Create-FileBaseName $_.AccountName
                    Create-Thumbnail $bitmap $largeThumbnailSize $largeThumbnailSize $picFolder "$($accountBaseName)_LThumb.jpg"
                    Create-Thumbnail $bitmap $mediumThumbnailSize $mediumThumbnailSize $picFolder "$($accountBaseName)_MThumb.jpg"
                    Create-Thumbnail $bitmap $smallThumbnailSize  $smallThumbnailSize $picFolder "$($accountBaseName)_SThumb.jpg"

                    $bitmap.Dispose()
                    $fileStream.Close()
                }

                if ($PSCmdlet.ShouldProcess("Property 'PictureUrl' in Profile '$($_.AccountName)'", "Update")){
                    $pictureUrl = "{0}/{1}/{2}_MThumb.jpg" -f $picFolder.ParentWeb.Site.Url, $picFolder.Url,$accountBaseName
                    Write-Debug "PictureUrl: $pictureUrl"
				    $_.get_Item($profilePictureUrlPropertyName).Value = $pictureUrl
				    $_.Commit()
                }

                Write-Host "Uploaded image '$imgPath' and updated profile '$($_.AccountName)'."
            }    

        }
        Write-Verbose "End processing file '$($_.AccountName)' at '$(get-date -displayhint Time -Format "HH:mm:ss")'..."
    }
}


END {
    Write-Verbose "End-Import of Images from '$path' to '$url'..."
    try{ Stop-Transcrip }catch{}
    Stop-SPAssignment $gc
}

Zu bemerken ist ev. noch das vorgehen. Die Dateien werden direkt vom Skript in die Thumbnails mit den drei Größen umgewandelt und hochgeladen.

Damit die Synchronisation funktioniert, sollte das Skript als geplanter Task jeden Tag ausgeführt werden. Der geplante Task sollte mit erhöhten Privilegien laufen.

image

Das das Aufrufen eines Skriptes mit Parametern in einem geplanten Task ja nicht immer so trivial ist, hier noch die Anleitung. Folgende Parameter müssen für die Activity eingegeben werden:

Program/script: %SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe

Add arguments (optional): -Command “& c:\scripts\Import-ProfileImage.ps1 –path <p> –url <url> –userProfilePropertyName <name> –userProfilePropertyFormatString <format>”

Start in: C:\scripts

C:\Scripts ist natürlich nur ein Beispiel. Das Skript kann an jedem beliebigen Ort liegen.

image

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s