Nie wiem jak Wam, ale mi się taki klimat podoba. Niestandardowe tło z logiem firmy, zdjęcie pracownika na ekranie logowania przy koncie użytkownika…to sprawia, że jak ktoś patrzy na takie środowisko z boku to myśli, że wdrażał to jakiś profesjonalista, który dba o wszystkie szczegóły i w sumie w mojej pracy to jest to, do czego dążę, czyli być najlepszym w swoim rodzaju. Okej, ale dosyć lizania własnych jajek, przejdźmy do konkretów, czyli do tego jak to wdrożyć – zdjęcia użytkowników w AD i w Exchange 2019.
Exchange to taki dodatek do zdjęć użytkowników – tutaj ponownie poczta wygląda profesjonalnie w organizacji, gdy jest wszystko tak poukładane. Efekt w OWA (Outlook Web Access) wygląda tak:
Do tego będą nam potrzebne zdjęcia naszych użytkowników i te zdjęcia muszą być kwadratowe, czyli np. być rozmiaru 500x500px. Nie ma znaczenia czy te zdjęcie jest duże czy małe (byle nie za małe, bo wtedy będzie pikseloza, a nie twarze użytkowników) dlatego, bo i tak te zdjęcia zostaną w pewnym stopniu skompresowane. Zdjęcia, które ja wykorzystałem są z Unsplash i przedstawiają modelów w moim testowym środowisku. Do wycięcia zdjęć można użyć program wg własnego uznania, np. IrfanView czy Adobe Photoshop. Następnie takie pliki użytkowników należy jakoś nazwać. Najlepszym rozwiązaniem jest nazwanie plików imieniem i nazwiskiem, ponieważ wtedy możemy je wykorzystać lepiej w programie, którym będziemy przypisywać zdjęcia do użytkowników w AD, przykład: Radosław.Serba.jpg
. Ja skorzystałem z nazwy sAMAccountName, czyli w moim przypadku rserba.jpg
(niestety ta opcja nie pozwala na automatyczne dopasowanie obrazków do kont użytkowników w CodeTwo Active Directory Photos.
Zdjęcia w AD
Ogólnie zdjęcie użytkownika jest przechowywane w atrybucie użytkownika thumbnailPhoto
i w plaintext można zobaczyć tylko w krótkim fragmencie:
Get-ADUser -Identity Administrator -Properties thumbnailPhoto
DistinguishedName : CN=Administrator,CN=Users,DC=serba,DC=local
Enabled : True
GivenName :
Name : Administrator
ObjectClass : user
ObjectGUID : 9dfaf3bb-fcc9-4f09-be3f-b35b95f7f278
SamAccountName : Administrator
SID : S-1-5-21-723521058-2419329218-2805022534-500
Surname :
thumbnailPhoto : {137, 80, 78, 71...}
UserPrincipalName : [email protected]
W takim razie jak ustawić taki atrybut? Są dwa rozwiązania: przez PowerShell lub za pomocą fajnej aplikacji zrobionej przez CodeTwo.
PowerShell:
Set-ADUser rserba -Replace @{thumbnailPhoto=([byte[]](Get-Content "C:\zdjecia\rserba.jpg" -Encoding byte))}
CodeTwo Active Directory Photos:
Sytuacja jest prosta: instalujemy program z linka, odpalamy, wybieramy OU, w którym chcemy ustawić zdjęcia i zaznaczamy konta, w których chcemy ustawić avatarki, a następnie klikamy Import.
Tutaj wszystko zależy od tego jaki mamy schemat nazw plików obrazków. Jeśli trzymaliśmy się schematu, który proponowałem na początku to wystarczy, że pomiędzy {First name}
i {Last name}
wstawimy kropkę, więc pole będzie wypełnione tak:{First name}.{Last name}
Po tym możemy kliknąć przycisk Automatch >.
Co prawda, ja tu już zdjęcia mam, ale to, co mamy tutaj na ekranie to scenariusz, w którym zdjęcia mogły nie być dopasowane i musimy je dopasować ręcznie, więc wystarczy przeciągnąć obrazki z prawej strony do odpowiedniego użytkownika, a następnie kliknąć Next >. Po tym wystarczy sprawdzić, czy zmiany, które chcemy zastosować nam pasują i zaznaczyć checkbox Apply the above settings to all selected images. Wartość jest ustawiona na 100 KB nieprzypadkowo, ponieważ to jest limit wielkości obrazka przechowywany w atrybcie thumbnailPhoto
użytkownika.
Gdy to zatwierdzimy to zdjęcia się zaimportują i jedną część mamy gotową. Drugą częścią jest podpięcie gotowego skryptu, który będzie pobierał zdjęcia użytkowników przy wylogowywaniu się. Bez niego obrazki będą się importowały nam na maszynach tylko i wyłącznie, gdy posiadamy na nich lokalne uprawnienia administracyjne, czyli w sumie będzie to działać tylko dla adminów. Mowa tutaj o skrypcie, który także jest na stronie CodeTwo:
[CmdletBinding(SupportsShouldProcess = $true)]Param()
function Test-Null($InputObject) { return !([bool]$InputObject) }
Function ResizeImage() {
param([String]$ImagePath, [Int]$Quality = 90, [Int]$targetSize, [String]$OutputLocation)
Add-Type -AssemblyName "System.Drawing"
$img = [System.Drawing.Image]::FromFile($ImagePath)
$CanvasWidth = $targetSize
$CanvasHeight = $targetSize
#Encoder parameter for image quality
$ImageEncoder = [System.Drawing.Imaging.Encoder]::Quality
$encoderParams = New-Object System.Drawing.Imaging.EncoderParameters(1)
$encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter($ImageEncoder, $Quality)
# get codec
$Codec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where { $_.MimeType -eq 'image/jpeg' }
#compute the final ratio to use
$ratioX = $CanvasWidth / $img.Width;
$ratioY = $CanvasHeight / $img.Height;
$ratio = $ratioY
if ($ratioX -le $ratioY) {
$ratio = $ratioX
}
$newWidth = [int] ($img.Width * $ratio)
$newHeight = [int] ($img.Height * $ratio)
$bmpResized = New-Object System.Drawing.Bitmap($newWidth, $newHeight)
$graph = [System.Drawing.Graphics]::FromImage($bmpResized)
$graph.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$graph.Clear([System.Drawing.Color]::White)
$graph.DrawImage($img, 0, 0, $newWidth, $newHeight)
#save to file
$bmpResized.Save($OutputLocation, $Codec, $($encoderParams))
$bmpResized.Dispose()
$img.Dispose()
}
#get sid and photo for current user
$user = ([ADSISearcher]"(&(objectCategory=User)(SAMAccountName=$env:username))").FindOne().Properties
$user_photo = $user.thumbnailphoto
$user_sid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
Write-Verbose "Updating account picture for $($user.displayname)..."
#continue if an image was returned
If ((Test-Null $user_photo) -eq $false) {
Write-Verbose "Success. Photo exists in Active Directory."
#set up image sizes and base path
$image_sizes = @(32, 40, 48, 96, 192, 200, 240, 448)
$image_mask = "Image{0}.jpg"
$image_base = "C:\ProgramData\AccountPictures"
#set up registry
$reg_base = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AccountPicture\Users\{0}"
$reg_key = [string]::format($reg_base, $user_sid)
$reg_value_mask = "Image{0}"
If ((Test-Path -Path $reg_key) -eq $false) { New-Item -Path $reg_key }
#save images, set reg keys
ForEach ($size in $image_sizes) {
#create hidden directory, if it doesn't exist
$dir = $image_base + "\" + $user_sid
If ((Test-Path -Path $dir) -eq $false) { $(mkdir $dir).Attributes = "Hidden" }
#save photo to disk, overwrite existing files
$file_name = ([string]::format($image_mask, $size))
$pathtmp = $dir + "\_" + $file_name
$path = $dir + "\" + $file_name
Write-Verbose " saving: $file_name"
$user_photo | Set-Content -Path $pathtmp -Encoding Byte -Force
ResizeImage $pathtmp $size $size $path
Remove-Item $pathtmp
#save the path in registry, overwrite existing entries
$name = [string]::format($reg_value_mask, $size)
$value = New-ItemProperty -Path $reg_key -Name $name -Value $path -Force
}
Write-Verbose "Done."
}
else { Write-Error "No photo found in Active Directory for $env:username" }
Taki skrypt zapisujemy w pliku, ja nazwałem go UserProfilePicture.ps1
i umieściłem go w folderze NETLOGON
. Ten folder jest synchronizowany pomiędzy wszystkimi kontrolerami domeny. Następnie należy stworzyć politykę GPO, w której definiujemy ten skrypt jako skrypt uruchamiany przy wylogowaniu użytkownika, więc należy przejść do Konfiguracja użytkownika > Ustawienia systemu Windows > Skrypty (logowanie/wylogowywanie) > Wylogowywanie.
Następnie, w zakładce Skrypty PowerShell należy wskazać ścieżkę do pliku. Jeśli plik jest umieszczony bezpośrednio w NETLOGON, ścieżka powinna być //<nazwa-domeny>/NETLOGON/<nazwa-skryptu>
i tak też jest w moim przypadku.
Po dodaniu można zapisać i zamknąć wszystkie okna, a następnie poczekać, aż polityki się zaaktualizują na komputerach. Po tym kwestia zdjęć na komputerach jest załatwiona.
Zdjęcia w Exchange 2019
Obstawiam, że sposób działania zdjęć jest mniej więcej taki sam w starszych wersjach Exchange 2019 (mam na myśli Exchange 2016 i 2013, bo w 2010 zmiany są ponoć spore). Do wrzucenia zdjęć możemy wykorzystać poniższy skrypt, lecz będziemy w nim musieli zmienić główną gałąź naszego OU, w którym przechowujemy użytkowników (linia 3) oraz ścieżkę do folderu, w którym przechowujemy pliki (linia 7). Poniżej opcja skryptu dla wykorzystywania nazw sAMAccountName:
Import-Module activedirectory
$users = Get-ADUser -Filter "ObjectClass -eq 'user'" -SearchBase 'OU=it.supra.tf,DC=serba,DC=local'
foreach($user in $users)
{
$samaccountname = $user.SamAccountName
Set-UserPhoto -Identity $user.SamAccountName -PictureData ([System.IO.File]::ReadAllBytes("C:\Users\rserba.SERBA\Desktop\it.supra.tf\$samaccountname.jpg")) -Confirm:$false
}
Ponadto druga opcja z imionami i nazwiskami oddzielonymi kropką:
Import-Module activedirectory
$users = Get-ADUser -Filter "ObjectClass -eq 'user'" -SearchBase 'OU=it.supra.tf,DC=serba,DC=local'
foreach($user in $users)
{
$firstname = $user.firstname
$lastname = $user.lastname
Set-UserPhoto -Identity $user.SamAccountName -PictureData ([System.IO.File]::ReadAllBytes("C:\Users\rserba.SERBA\Desktop\it.supra.tf\$firstname.$lastname.jpg")) -Confirm:$false
}
Taki skrypt odpalamy w Exchange Management Shell. Jeśli popełniliśmy błędy w nazwie plików lub one nie istnieją, zostaniemy o tym poinformowani przez skrypt:
[PS] C:\Users\rserba.SERBA\desktop>.\UpdateExchangePictureProfile.ps1
Exception calling "ReadAllBytes" with "1" argument(s): "Nie można odnaleźć pliku 'C:\Users\rserba.SERBA\Desktop\it.supra.tf\ferexio.jpg'."
At C:\Users\rserba.SERBA\desktop\UpdateExchangePictureProfile.ps1:7 char:5
+ Set-UserPhoto -Identity $user.SamAccountName -PictureData ([Syste ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : FileNotFoundException
Exception calling "ReadAllBytes" with "1" argument(s): "Nie można odnaleźć pliku 'C:\Users\rserba.SERBA\Desktop\it.supra.tf\kszymocha.jpg'."
At C:\Users\rserba.SERBA\desktop\UpdateExchangePictureProfile.ps1:7 char:5
+ Set-UserPhoto -Identity $user.SamAccountName -PictureData ([Syste ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : FileNotFoundException
Efekty widać na początku posta 😊