Skip to content

Tool that bulk imports or removes User pictures

Simple Tool that bulk imports or removes user pictures, based on AD Group Membership.
If a user is in both groups, the picture will be removed!
Idea based on my old tool to import Active Directory pictures.
They are a bit to tiny, so I use Exchange now to make them look better in Exchange and Skype.

The tool will not check the pictures. As long as a picture exists, it will try to import it. Then the Exchange Server might reject the import, and you will get a warning. Same for users with non existing Mailboxes: The Tool will not check anything for the User before it tries to import the picture.

The Pictures should be in the JPG format and not larger than 648x648px. With a depth of 24 bits, each picture has around 200-250 kilobytes. Keep that in mind if you load a big bunch of pictures.

The following formats are used:

  • 48x48px – Active Directory thumbnailPhoto
  • 96x96px – Outlook, Outlook Web Access, Lync/Skype and SharePoint
  • 648x648px – Lync/Skype Clients and Lync/Skype Web App


If you use small pictures (great idea to save space), The servers will then upscale the picture and look a bit crappy in Skype!

By the way: Outlook might show all pictures with rounded edges, so avoid the corner usage. Similar to the picture in the About Box bellow.

All Pictures are down scaled. And if needed, the picture will be cropped to fit. That might look crappy, so use a proper format.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
#requires -Version 2.0 -Modules ActiveDirectory
<#
		.SYNOPSIS
		Tool that bulk imports or removes User pictures, based on AD Group Membership
	
		.DESCRIPTION
		Tool that bulk imports or removes User pictures, based on AD Group Membership
		If a user is in both groups, the picture will be removed!
		Idea based on my old tool to import Active Directory pictures.
		They are a bit to tiny, so I use Exchange now to make them look better in Exchange and Skype.
	
		.PARAMETER AddGroup
		Active Directory Group with users that would like to have a picture.
		For all Members of this group, the Tool will try to set an image.
	
		.PARAMETER RemGroup
		Active Directory Group with users that would like have have the picture removed.
		For all Members of this group, the Tool will try to remove the existing image (If set).
	
		.PARAMETER PictureDir
		Directory that contains the picures
	
		.PARAMETER Extension
		Extension of the pictures
	
		.PARAMETER workaround
		Workaround for Exchange 2016 on Windows Server 2016
	
		.PARAMETER UPNDomain
		The default Domain, to add to the UPN
	
		.EXAMPLE
		# Use the Groups 'ADDPIXX' and 'NOPIXX' to Set/Remove the User Pictures
		# There was an Issue with the User joerg.hochwald (Possible Picture Problem!
		PS C:\> .\.\Set-AllUserPictures.ps1 -AddGroup 'ADDPIXX' -RemGroup 'NOPIXX' -PictureDir 'c:\upixx\' -workaround -UPNDomain 'jhochwald.com'

		WARNING: Unable to set Image c:\upixx\joerg.hochwald.jpg for User joerg.hochwald

		.EXAMPLE
		# Use the Groups 'ADDPIXX' and 'NOPIXX' to Set/Remove the User Pictures
		# There was an Issue with the User jane.doe - Check that this user has a provissioned Mailbox (on Prem or Cloud)
		PS C:\> .\.\Set-AllUserPictures.ps1 -AddGroup 'ADDPIXX' -RemGroup 'NOPIXX' -PictureDir 'c:\upixx\' -workaround -UPNDomain 'jhochwald.com'

		WARNING: Unable to handle jane.doe - Check that this user has a valid Mailbox!

		.EXAMPLE
		# Use the Groups 'ADDPIXX' and 'NOPIXX' to Set/Remove the User Pictures - Everything went well
		PS C:\> .\.\Set-AllUserPictures.ps1 -AddGroup 'ADDPIXX' -RemGroup 'NOPIXX' -PictureDir 'c:\upixx\' -workaround -UPNDomain 'jhochwald.com'

		WARNING: Unable to handle jane.doe - Check that this user has a valid Mailbox!

		.NOTES
		TODO: There is no logging! Only the Exchange RBAC logging is in use
		TODO: A few error handlers are still missing

		If a user is in both groups, the picture will be removed!
		Verbose could be very verbose. This is due to the fact, that the complete Exchange logging will be shown!

		There are a few possibilities for Warnings and Errors. (Mostly for missing things)

		Disclaimer: The code is provided 'as is,' with all possible faults, defects or errors, and without warranty of any kind.

		Author: Joerg Hochwald

		License: http://unlicense.org

		.LINK
		Author http://jhochwald.com

		.Link
		License http://unlicense.org
#>
param
(
	[Parameter(Mandatory = $true,
			ValueFromPipeline = $true,
			ValueFromPipelineByPropertyName = $true,
			Position = 1,
	HelpMessage = 'Active Directory Group with users that would like to have a picture')]
	[ValidateNotNullOrEmpty()]
	[Alias('positive')]
	[string]
	$AddGroup,
	[Parameter(Mandatory = $true,
			ValueFromPipeline = $true,
			ValueFromPipelineByPropertyName = $true,
			Position = 2,
	HelpMessage = 'Active Directory Group with users that would like have have the picture removed.')]
	[ValidateNotNullOrEmpty()]
	[string]
	$RemGroup,
	[Parameter(Mandatory = $true,
			ValueFromPipeline = $true,
			ValueFromPipelineByPropertyName = $true,
			Position = 3,
	HelpMessage = 'Directory that contains the picures')]
	[ValidateNotNullOrEmpty()]
	[Alias('PixxDir')]
	[string]
	$PictureDir,
	[Parameter(ValueFromPipeline = $true,
			ValueFromPipelineByPropertyName = $true,
	Position = 5)]
	[Alias('defaultDomain')]
	[string]
	$UPNDomain,
	[Parameter(ValueFromPipeline = $true,
			ValueFromPipelineByPropertyName = $true,
	Position = 4)]
	[ValidateSet('png', 'jpg', 'gif', 'bmp')]
	[ValidateNotNullOrEmpty()]
	[string]
	$Extension = 'jpg',
	[switch]
	$workaround = $false
)

BEGIN
{
	if ($workaround)
	{
		# Unsupported Workaround accoring to https://hochwald.net/workaround-for-get-help-issue-with-exchange-2016-on-windows-server-2016/
		Add-PSSnapin -Name Microsoft.Exchange.Management.PowerShell.SnapIn
	}
	
	# Cleanup
	$AddUserPixx = $null
	$NoUserPixx = $null
	
	# Check the source directory string and fix it if needed
	if (-not ($PictureDir).EndsWith('\'))
	{
		# Fix it
		$PictureDir = $PictureDir + '\'
		
		$paramWriteVerbose = @{
			Message = 'Fixed the Source Directory String!'
		}
		Write-Verbose @paramWriteVerbose
	}
	
	try
	{
		$paramGetADGroupMember = @{
			Identity      = $AddGroup
			ErrorAction   = 'Stop'
			WarningAction = 'SilentlyContinue'
		}
		$AddUserPixx = (Get-ADGroupMember @paramGetADGroupMember | Select-Object -Property samaccountname)
	}
	catch
	{
		$paramWriteError = @{
			Message     = "Unable to find $AddGroup"
			ErrorAction = 'Stop'
		}
		Write-Error @paramWriteError
		
		return
	}
	
	try
	{
		$paramGetADGroupMember = @{
			Identity      = $RemGroup
			ErrorAction   = 'Stop'
			WarningAction = 'SilentlyContinue'
		}
		$NoUserPixx = (Get-ADGroupMember @paramGetADGroupMember | Select-Object -Property samaccountname)
	}
	catch
	{
		$paramWriteError = @{
			Message     = "Unable to find $AddGroup"
			ErrorAction = 'Stop'
		}
		Write-Error @paramWriteError
		
		return
	}
	
	function Test-ValidEmail
	{
		<#
				.SYNOPSIS
				Simple Function to check if a String is a valid Mail
	
				.DESCRIPTION
				Simple Function to check if a String is a valid Mail and return a Bool
	
				.PARAMETER address
				Address String to Check
	
				.EXAMPLE
				# Not a valid String
				PS C:\> Test-ValidEmail -address 'Joerg.Hochwald'
				False
	
				.EXAMPLE
				# Valid String
				PS C:\> Test-ValidEmail -address '[email protected]'
				True
	
				.NOTES
				Disclaimer: The code is provided 'as is,' with all possible faults, defects or errors, and without warranty of any kind.
		
				Author: Joerg Hochwald
		
				License: http://unlicense.org
	
				.LINK
				Author http://jhochwald.com
	
				.Link
				License http://unlicense.org
		#>
	
		[OutputType([bool])]
		param
		(
			[Parameter(Mandatory = $true,
			HelpMessage = 'Address String to Check')]
			[ValidateNotNullOrEmpty()]
			[string]
			$address
		)
	
		($address -as [mailaddress]).Address -eq $address -and $address -ne $null
	}
}

PROCESS
{
	if (-not ($AddUserPixx.samaccountname))
	{
		$paramWriteVerbose = @{
			Message = "The AD Group $AddGroup has no members."
		}
		Write-Verbose @paramWriteVerbose
	}
	else
	{
		# Add a counter
		$AddUserPixxCount = (($AddUserPixx.samaccountname).count)
		
		$paramWriteVerbose = @{
			Message = "The AD Group $AddGroup has $AddUserPixxCount members."
		}
		Write-Verbose @paramWriteVerbose
		
		foreach ($AddUser in $AddUserPixx.samaccountname)
		{
			if (($NoUserPixx.samaccountname) -notcontains $AddUser)
			{
				# Check the UPN and Fix it, if possible
				if (-not (Test-ValidEmail($AddUser)))
				{
					if (-not ($UPNDomain))
					{
						# Whoopsie
						$paramWriteError = @{
							Message     = 'UPN Default Domain not set but needed!'
							ErrorAction = 'Stop'
						}
						Write-Error @paramWriteError
					}
					else
					{
						# Let us fix this
						$AddUserUPN = ($AddUser + '@' + $UPNDomain)
					}
				}
				
				# Build the Full Image Path
				$SingleUserPicture = ($PictureDir + $AddUser + '.' + $Extension)
				
				# Check if Picture exists
				$paramTestPath = @{
					Path          = $SingleUserPicture
					ErrorAction   = 'Stop'
					WarningAction = 'SilentlyContinue'
				}
				
				If (Test-Path @paramTestPath)
				{
					try
					{
						$paramSetUserPhoto = @{
							Identity      = $AddUserUPN
							PictureData   = ([IO.File]::ReadAllBytes($SingleUserPicture))
							Confirm       = $false
							ErrorAction   = 'Stop'
							WarningAction = 'SilentlyContinue'
						}
						
						$null = (Set-UserPhoto @paramSetUserPhoto)
					}
					catch
					{
						$paramWriteWarning = @{
							Message     = "Unable to set Image $SingleUserPicture for User $AddUser"
							ErrorAction = 'SilentlyContinue'
						}
						Write-Warning @paramWriteWarning
					}
				}
				else
				{
					$paramWriteWarning = @{
						Message     = "The Image $SingleUserPicture for User $AddUser was not found"
						ErrorAction = 'SilentlyContinue'
					}
					Write-Warning @paramWriteWarning
				}
			}
			else
			{
				$paramWriteVerbose = @{
					Message = "Sorry, User $AddUser is member of $AddGroup and $RemGroup"
				}
				Write-Verbose @paramWriteVerbose
			}
		}
	}
	
	if (-not ($NoUserPixx.samaccountname))
	{
		$paramWriteVerbose = @{
			Message = "The AD Group $RemGroup has no members."
		}
		Write-Verbose @paramWriteVerbose
	}
	else
	{
		# Add a counter
		$NoUserPixxCount = (($NoUserPixx.samaccountname).count)
		
		$paramWriteVerbose = @{
			Message = "The AD Group $RemGroup has $NoUserPixxCount members."
		}
		Write-Verbose @paramWriteVerbose
		
		foreach ($NoUser in $NoUserPixx.samaccountname)
		{
			# Check the UPN and Fix it, if possible
			if (-not (Test-ValidEmail($NoUser)))
			{
				if (-not ($UPNDomain))
				{
					# Whoopsie
					$paramWriteError = @{
						Message     = 'UPN Default Domain not set but needed!'
						ErrorAction = 'Stop'
					}
					Write-Error @paramWriteError
				}
				else
				{
					# Let us fix this
					$NoUserUPN = ($NoUser + '@' + $UPNDomain)
				}
			}
			
			$paramSetUserPhoto = @{
				Identity      = $NoUserUPN
				Confirm       = $false
				ErrorAction   = 'Stop'
				WarningAction = 'SilentlyContinue'
			}
			
			try 
			{
				$null = (Remove-UserPhoto @paramSetUserPhoto)
			}
			catch 
			{
				$paramWriteWarning = @{
					Message     = "Unable to handle $NoUser - Check that this user has a valid Mailbox!"
					ErrorAction = 'SilentlyContinue'
				}
				Write-Warning @paramWriteWarning
			}
		}
	}
}

END
{
	# Cleaniup
	$AddUserPixx = $null
	$NoUserPixx = $null
	$AddUserPixxCount = $null
	$NoUserPixxCount = $null

	# Do a garbage collection: Call the .NET function to cleanup some stuff
	$null = ([GC]::Collect())
}

There is also a Gist for that.

The Script should be well documented.

Please note: This is an early version and there are a few things still missing!

More Info about Picture Handling: TechNet

This content is older than 1 year. It might be outdated.
Published inPowerShell

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *