AD-User zu Outlook Kontakte konvertieren

Dank der globalen Adressliste können wir im Handy problemlos nach den Kontaktdaten von anderen AD-Usern suchen. Das bringt uns aber nichts, wenn wir von einem dieser User angerufen werden, denn das Handy schaut nicht in der GAL, sondern nur in den Kontakten die auf das Handy synchronisiert wurden. Somit weiß man nicht wer einen anruft, außer man hat die Nummer selbst ins Outlook oder ins Handy eingetragen.

Die Lösung: Alle AD-User zu Outlook Kontakte machen. Und bei der Gelegenheit nehmen wir etwaige AD-Bilder gleich mit.

Dieses Skript kann man dann einfach jede Nacht laufen lassen:

  • Es leert den Kontakte Ordner im Outlook den es beim letzten mal angelegt hat
  • Exportiert die Bilder aus dem AD
  • Erzeugt die Kontakte für jeden User neu

Hierdurch ist sichergestellt, dass keine alten User in den Kontakten rum dümpeln.

# Hier kommen alles User rein, die ihr Outlook nicht upgedatet haben wollen.
# Es ist der SAMAccountName einzutragen, das heißt der Login-Name.
# Z.B. mmustermann für Max Mustermann oder ssorglos für Susi Sorglos, etc...
# Es ist für jeden User eine neue Zeile unter der letzten $wollen_nicht +=... Zeile zu erstellen.
# Die Syntax ist: $wollen_nicht += "SAMACCOUNTNAME"
# Also z.B.: $wollen_nicht += "mmustermann"

  $wollen_nicht = @()
# $wollen_nicht += "SAMAccountName" --> Als Beispiel, bei kopieren das # nicht mit kopieren, sonst greift es nicht.

# So soll der Ordner der Kontakte im Outlook heißen
$OutlookFoldername = "Contoso-Mitarbeiter"

#export the thmbnailphotos from AD to local storage
$thumbnailPhotoPath = "C:\Scripts\thumbnailPhoto"
$thumbnailPhotoPathResized = "C:\scripts\thumbnailPhoto_resized"

Function Set-ImageSize{
   
	[CmdletBinding(
    	SupportsShouldProcess=$True,
        ConfirmImpact="Low"
    )]		
	Param(
		[parameter(Mandatory=$true,
			ValueFromPipeline=$true,
			ValueFromPipelineByPropertyName=$true)]
		[Alias("Image")]	
		[String[]]$FullName,
		[String]$Destination = $(Get-Location),
		[Switch]$Overwrite,
		[Int]$WidthPx,
		[Int]$HeightPx,
		[Int]$DPIWidth,
		[Int]$DPIHeight,
		[Switch]$FixedSize,
		[Switch]$RemoveSource
	)
	
	Begin{
		[void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
		#[void][reflection.assembly]::loadfile( "C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Drawing.dll")
	}
	
	Process{

		Foreach($ImageFile in $FullName){
			If(Test-Path $ImageFile){
				$OldImage = new-object System.Drawing.Bitmap $ImageFile
				$OldWidth = $OldImage.Width
				$OldHeight = $OldImage.Height
				
				if($WidthPx -eq $Null){
					$WidthPx = $OldWidth
				}
				
				if($HeightPx -eq $Null){
					$HeightPx = $OldHeight
				}
				
				if($FixedSize){
					$NewWidth = $WidthPx
					$NewHeight = $HeightPx
				}else{
					if($OldWidth -lt $OldHeight){
						$NewWidth = $WidthPx
						[int]$NewHeight = [Math]::Round(($NewWidth*$OldHeight)/$OldWidth)
						
						if($NewHeight -gt $HeightPx){
							$NewHeight = $HeightPx
							[int]$NewWidth = [Math]::Round(($NewHeight*$OldWidth)/$OldHeight)
						}
					}else{
						$NewHeight = $HeightPx
						[int]$NewWidth = [Math]::Round(($NewHeight*$OldWidth)/$OldHeight)
						
						if($NewWidth -gt $WidthPx){
							$NewWidth = $WidthPx
							[int]$NewHeight = [Math]::Round(($NewWidth*$OldHeight)/$OldWidth)
						}						
					}
				}

				$ImageProperty = Get-ItemProperty $ImageFile				
				$SaveLocation = Join-Path -Path $Destination -ChildPath ($ImageProperty.Name)

				If(!$Overwrite){
					If(Test-Path $SaveLocation){
						$Title = "A file already exists: $SaveLocation"
							
						$ChoiceOverwrite = New-Object System.Management.Automation.Host.ChoiceDescription "&Overwrite"
						$ChoiceCancel = New-Object System.Management.Automation.Host.ChoiceDescription "&Cancel"
						$Options = [System.Management.Automation.Host.ChoiceDescription[]]($ChoiceCancel, $ChoiceOverwrite)		
						If(($host.ui.PromptForChoice($Title, $null, $Options, 1)) -eq 0){
							Write-Verbose "Image '$ImageFile' exist in destination location - skiped"
							Continue
						} #End If ($host.ui.PromptForChoice($Title, $null, $Options, 1)) -eq 0
					} #End If Test-Path $SaveLocation
				} #End If !$Overwrite	
				
				$NewImage = new-object System.Drawing.Bitmap $NewWidth,$NewHeight

				$Graphics = [System.Drawing.Graphics]::FromImage($NewImage)
				$Graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
				$Graphics.DrawImage($OldImage, 0, 0, $NewWidth, $NewHeight) 

				$ImageFormat = $OldImage.RawFormat
				$OldImage.Dispose()
				if($DPIWidth -and $DPIHeight){
					$NewImage.SetResolution($DPIWidth,$DPIHeight)
				} #End If $DPIWidth -and $DPIHeight
				
				$NewImage.Save($SaveLocation,$ImageFormat)
				$NewImage.Dispose()
				Write-Verbose "Image '$ImageFile' was resize from $($OldWidth)x$($OldHeight) to $($NewWidth)x$($NewHeight) and save in '$SaveLocation'"
				
				If($RemoveSource){
					Remove-Item $Image -Force
					Write-Verbose "Image source '$ImageFile' was removed"
				} #End If $RemoveSource
			}
		}

	} #End Process
	
	End{}
}

Function Copy-OrgContactsToUserMailboxContacts{
   
	param(
		$Mailbox,
		$FolderName,
		$Users,
		$thumbnailPhotoPath
	)

	
	$EwsUrl = ([array](Get-WebServicesVirtualDirectory))[0].InternalURL.AbsoluteURI

	$ContactMapping=@{
		"FirstName" = "GivenName";
		"LastName" = "Surname";
		"Company" = "CompanyName";
		"WindowsEmailAddress" = "Email:EmailAddress1";
		"Phone" = "Phone:BusinessPhone";
		"Fax" = "Phone:BusinessFax";
		"MobilePhone" = "Phone:MobilePhone";
		"Manager" = "Manager";
		"Title" = "JobTitle";
		"WebPage" = "BusinessHomePage";
		"AssistantName" = "AssistantName";
		"Office" = "OfficeLocation";
		"Department" = "Department";
	}

	$UserMailbox = Get-Mailbox $Mailbox

	if (!$UserMailbox){
		throw "Mailbox $($Mailbox) not found";
		exit;
	}
	$EmailAddress = $UserMailbox.PrimarySMTPAddress

	# Load EWS Managed API
	[void][Reflection.Assembly]::LoadFile("E:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll");

	$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
	$service.UseDefaultCredentials = $true;
	$service.URL = New-Object Uri($EwsUrl);

	# Search for an existing copy of the Folder to store Org contacts 
	$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
	$RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
	$RootFolder.Load()

	$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
	$FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
	$ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where {$_.DisplayName -eq $FolderName}
	if ($ContactsFolderSearch){
		# Empty if found
		$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
		$ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
		$ContactsFolder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true)
	}else{
		# Create new contacts folder
		$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
		$ContactsFolder = New-Object Microsoft.Exchange.WebServices.Data.ContactsFolder($service);
		$ContactsFolder.DisplayName = $FolderName
		$ContactsFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
		# Search for the new folder instance
		$RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
		$RootFolder.Load()
		$FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
		$ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where {$_.DisplayName -eq $FolderName}
		$ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
	}

	# Add contacts

	foreach ($ContactItem in $Users){
		$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress);
		
		$ExchangeContact = New-Object Microsoft.Exchange.WebServices.Data.Contact($service);
		if ($ContactItem.FirstName -and $ContactItem.LastName){
			$ExchangeContact.NickName = $ContactItem.LastName + ", " + $ContactItem.FirstName;
		}elseif ($ContactItem.FirstName -and !$ContactItem.LastName){
			$ExchangeContact.NickName = $ContactItem.FirstName;
		}elseif (!$ContactItem.FirstName -and $ContactItem.LastName){
			$ExchangeContact.NickName = $ContactItem.LastName;
		}elseif (!$ContactItem.FirstName -and !$ContactItem.LastName){
			$ExchangeContact.NickName = $ContactItem.DisplayName;
			$ContactItem.FirstName = $ContactItem.DisplayName;
		}


		#Set profile picture from exported thumbnailphoto, if empty then skip
		$ContactPictureFile = $thumbnailPhotoPath + "\" + $ContactItem.SamAccountName + ".jpg"
		if ((Test-Path -Path $ContactPictureFile) -eq $true){
			$ExchangeContact.SetContactPicture($ContactPictureFile);
		}

		$ExchangeContact.DisplayName = $ExchangeContact.NickName;
		$ExchangeContact.FileAs = $ExchangeContact.NickName;
		
		# This uses the Contact Mapping above to save coding each and every field, one by one. Instead we look for a mapping and perform an action on
		# what maps across. As some methods need more "code" a fake multi-dimensional array (seperated by :'s) is used where needed.
		foreach ($Key in $ContactMapping.Keys){
			# Only do something if the key exists
			
			if ($ContactItem.$Key){
				# Will this call a more complicated mapping?
				
				if ($ContactMapping[$Key] -like "*:*"){
					# Make an array using the : to split items.
					
					$MappingArray = $ContactMapping[$Key].Split(":")
					# Do action
					
					switch ($MappingArray[0]){
						"Email"{
							$ExchangeContact.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::($MappingArray[1])] = $ContactItem.$Key.ToString();
						}"Phone"{
							$ExchangeContact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::($MappingArray[1])] = $ContactItem.$Key;
						}
					}                
				}elseif($ContactMapping[$Key] -like "Manager"){

					$ExchangeContact.($ContactMapping[$Key]) = $ContactItem.$Key.Name
				
			
				}elseif($ContactMapping[$Key] -like "JobTitle"){

					$Title = $ContactItem.$Key
					if($Title -like ".*"){
							$Title = $Title.SubString(1, ($Title.Length - 1))
					}
					
					$ExchangeContact.($ContactMapping[$Key]) = $Title
								
				
				}else{
				
					$ExchangeContact.($ContactMapping[$Key]) = $ContactItem.$Key;            
				}
			}    
		}
		# Save the contact    
		$ExchangeContact.Save($ContactsFolder.Id);
		
		# Provide output that can be used on the pipeline
		$Output_Object = New-Object Object;
		$Output_Object | Add-Member NoteProperty FileAs $ExchangeContact.FileAs;
		$Output_Object | Add-Member NoteProperty GivenName $ExchangeContact.GivenName;
		$Output_Object | Add-Member NoteProperty Surname $ExchangeContact.Surname;
		$Output_Object | Add-Member NoteProperty EmailAddress1 $ExchangeContact.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1]
		$Output_Object;
	}
}


# Module laden
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
Import-Module ActiveDirectory

# Ggf. Ordner erstellen
if ((Test-Path -Path $thumbnailPhotoPath) -eq $true){
    Remove-Item -Path $thumbnailPhotoPath -Force -Recurse
    New-Item -Path $thumbnailPhotoPath -ItemType directory -Force
}else{
    New-Item -Path $thumbnailPhotoPath -ItemType directory -Force
}

# Kurz warten
sleep(5)

# Alle User einlesen die ein Bild im AD haben
# Dann für jeden User das Bild exportieren
$bilderuser = Get-ADUser -Filter * -Properties thumbnailphoto | where {$_.thumbnailphoto}
for ($i = 0; $i -lt $bilderuser.Count; $i++){
	$newpath = $thumbnailPhotoPath + "\" + $bilderuser[$i].SamAccountName + ".jpg"
	$bilderuser[$i].thumbnailphoto | Set-Content -Path $newpath -Encoding Byte
}


# Die Bilder auf eine neue Größe anpassen, weil Outlook diese sonst nicht anzeigen kann
$45kb = 40960 # 40KB in Byte

# Hilfs-Counter, weil es auf Grund von mathematischen Rundungen passieren kann, dass er nicht genau die richtige Größe trifft...
$count = 0

while(1){

	if($count -ge 10){
		echo "Abbruch"
		break;
	}

	echo "Los geht's"
	$bilder = gci "$thumbnailPhotoPath\*.jpg"
	$zugrossebilder = @()
	
	foreach ($bild in $bilder){

		$size = $bild.Length

		if($size -gt $45kb){
			$zugrossebilder += $bild
		}
	}

	if($zugrossebilder.Count -ge 1){

		if (!(Test-Path $thumbnailPhotoPathResized)){
			New-Item -Path $thumbnailPhotoPathResized -ItemType directory -Force
		}

		foreach($zugrossesbild in $zugrossebilder){
			
			$size = $zugrossesbild.Length
			$sizeratio = $size / $45kb
			$dateiname = $zugrossesbild.Versioninfo.Filename
			$image = New-Object -ComObject Wia.ImageFile
			$image.LoadFile($dateiname)
			$oldHeight = $image.Height
			$oldWidth = $image.Width
			$oldPixel = $oldHeight * $oldWidth
			$newPixel = $oldPixel / $sizeratio
			
			$faktor = [math]::Sqrt($newPixel / ($oldHeight * $oldWidth))

			$newWidth = $OldWidth * $faktor
			$newWidth = [math]::Round($newWidth, 0)
			
			$newHeight = $OldHeight * $faktor
			$newHeight = [math]::Round($newHeight, 0)
			
			Set-ImageSize -Fullname $dateiname -WidthPx $newWidth -HeightPx $newHeight -Destination $thumbnailPhotoPathResized
			echo $dateiname
			
			[System.Runtime.Interopservices.Marshal]::ReleaseComObject($image)
			Remove-Variable image
		}
		
		sleep(1)
		
		Move-Item -Path "$thumbnailPhotoPathResized\*.*" -Destination $thumbnailPhotoPath -Force
		Remove-Item $thumbnailPhotoPathResized -Force -Recurse		
		
		sleep(1)
		$count++
		
	}else{
		break;
	}
}


# Alle User die eine E-Mail Adresse und Rufnummer haben werden eingelesen
$users = @()
$Users += get-user -Filter {WindowsEmailAddress -ne $null -and (MobilePhone -ne $null -or Phone -ne $null)} | select SamAccountName

$newUsers = @()
foreach($newUser in $Users){
	$newUsers += get-ADuser $newUser.SAMAccountName
}

# Da wir ein Array mit dem Attribut SAMAccountName haben, soll das weg und nur ein Text-Array erstellt werden
$umschrieb = @()
foreach ($user in $newUsers){
	$umschrieb += $user.SAMAccountName
}
$user = $null

# Jetzt werden alle o.g. User aus der Liste gelöscht
foreach ($user in $wollen_nicht){
	$umschrieb = $umschrieb -ne $user
}

# Leere Einträge rausnehmen
$umschrieb = $umschrieb | ? {$_}

$Users = @()
$newUsers = $newUsers | ? {$_.SamAccountName}
$User = $null

foreach($User in $newUsers){
	$Users += get-user $User.SamAccountName | select *
}


# Für alle User die übrig bleiben wird das Script ausgeführt
foreach ($Mailbox in $umschrieb){
	echo "Jetz kommt: $Mailbox"
	Copy-OrgContactsToUserMailboxContacts -Mailbox $Mailbox -Foldername $OutlookFoldername -Users $Users -thumbnailPhotoPath $thumbnailPhotoPath
	echo ""
	echo "$Mailbox ist fertig!"
	echo "---------------------------------------------"
	echo "---------------------------------------------"
}

if ((Test-Path -Path $thumbnailPhotoPath) -eq $true){
	Remove-Item -Path $thumbnailPhotoPath -Force -Recurse
}

exit