Normalerweise ist der übliche Weg die Profilbilder direkt über das AD in den SharePoint zu snychronisieren:
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:
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.
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.