atk691
Goto Top

Kontaktliste in Userpostfach importieren via PS

Hallo zusammen,
ich bin auf der Suche nach einer Lösung für folgendes Problem:
Eine Kontaktliste (Telefon), erstellt mittels Get-AdUser , soll in ein oder mehrere Userpostfächer importiert werden. Als separater Kontaktordner.
Diese Liste wird 1x wöchentlich erstellt.
Das eigentliche Ziel: eine Mitarbeitertelefonliste (auch user ohne Postfach) sollen via ActivSysnc allen Smartphones zur Verfügung stehen und immer aktuell.
Bisher wurde das klassisch per Hand gemacht und war (natürlich) nie wirklich aktuell.

Unter diesem Link: xxx.fixthis.ch/blog/post/power-shell_Kontakte-von-AD-filtern-nach-Exchange/
habe ich ein Script gefunden, das genau meinen Anforderungen entspricht.
Allerdings läuft es bei mir nicht und eventuell ist es auch etwas zu "heavy" für meine Ansprüche.
Meine PS-Kenntnisse reichen hier leider nicht aus.

Zu dem Script:
EWS Management API Bibliothek (2.0 + 2.2) ist installiert aber es meldet dennoch immer "EWS Management API DLL fehlt". Die dll ist aber vorhanden (C:\ProgramFiles\Microsoft\Exchange\WebServices\2.0\Microsoft.Exchange.WebServices.dll)
Ein Login auf "https://mail.companydomain.ch/ews/exchange.asmx" funktioniert einwandfrei.

Eventuell kann mich hier jemand unterstützen, bzw. hätte noch einen anderen Lösungsvorschlag face-wink

Danke und Gruss

atk691

Uuups fast vergessen: Exchange 2010 (bald 2016), W2008R2, PS4

Content-Key: 322972

Url: https://administrator.de/contentid/322972

Printed on: April 18, 2024 at 23:04 o'clock

Member: xbast1x
xbast1x Dec 06, 2016 at 07:55:05 (UTC)
Goto Top
Ich wäre auch an einer Lösung interessiert, da ich auch Anfragen zu nicht aktuellen Kontaktlisten erhalte.
Member: Kraemer
Kraemer Dec 06, 2016 at 08:03:10 (UTC)
Goto Top
Unter diesem Link: xxx.fixthis.ch/blog/post/power-shell_Kontakte-von-AD-filtern-nach-Exchange/
Was soll der Quatsch?
Member: atk691
atk691 Dec 06, 2016 at 08:05:46 (UTC)
Goto Top
War/bin mir nicht sicher ob ich vollständige Links einstellen darf !
ersetze xxx mit www
Member: colinardo
Solution colinardo Dec 06, 2016, updated at Dec 09, 2016 at 10:21:01 (UTC)
Goto Top
Servus @atk691,
dazu hatte ich mal vor langer Zeit ein Skript für den EX2010 geschrieben.

In folgendem Skript werden alle aktiven AD-Benutzer ausgelesen und in einen Unterordner des Standard-Kontakte-Ordners namens 'Firmenkontake' jeder aktiven Usermailbox geschrieben. Überprüft wird ob der Ordner schon existiert, wenn nicht wird er angelegt. Dann wird der Inhalt des Ordners einseitig synchronisiert d.h. bereits vorhandene Kontakte werden aktualisiert, neue Angelegt und falls der User in dem Ordner weitere Kontakte anlegt werden diese dort raus gelöscht.

Damit das ganze über die EWS (Exchange Web Services) funktioniert muss man dem User mit dem das Skript ausgeführt wird das Impersonation-Recht erteilen damit dieser Account auf alle Mailboxen zugreifen kann.
Um z.B. dem User 'Administrator' dieses Recht zu erteilen führt man folgenden Befehl in einer EMS aus
New-ManagementRoleAssignment -Name:impersonationAssignmentName -Role:ApplicationImpersonation -User:Administrator
Mehr dazu bitte hier nachlesen:
Configuring Exchange Impersonation in Exchange 2010

Benötigt wird für das Script ebenfalls die Microsoft.Exchange.WebServices.dll die Ihr euch ja bei MS herunterladen könnt. Für das folgende Script ist diese DLL in das Verzeichnis des Scripts zu kopieren, außer man passt den Pfad im Skript an.

Hatte das ganze damals mit Exchange 2010 getestet. Sollte eigentlich noch laufen.

In deinem Fall wäre es auch eigentlich ausreichend die Kontakte in eine einzelne Shared-Mailbox zu transferieren, das lässt sich ja leicht anpassen.
<#
    Sync AD-Users to Contact-Folder in all Mailboxes  EX2010  / (c) @colinardo

   Vor dem Start des Skript ist dem User welcher das Skript später ausführt die Impersonation-Berechtigungzu erteilen:
   In diesem Beispiel der User 'Administrator' (Bitte einmalig in einer EMS ausführen)  

    New-ManagementRoleAssignment -Name:impersonationAssignmentName -Role:ApplicationImpersonation -User:Administrator
#>

if ($PSVersionTable.PSVersion.Major -lt 3){write-host "ERROR: Minimum Powershell Version 3.0 is required!" -F Yellow; return}    

# Active Directory Modul laden
Import-Module ActiveDirectory

# EWS DLL laden
Add-Type -Path "$(Split-Path $MyInvocation.MyCommand.Definition -Parent)\Microsoft.Exchange.WebServices.dll"  

# Mit Exchange verbinden
try{
    $exchange_server = Get-ADObject -LDAPFilter 'objectClass=msExchExchangeServer' -SearchBase (([ADSI]"LDAP://RootDse").configurationNamingContext.ToString()) -Properties networkaddress | select -Expand networkaddress | ?{$_ -match 'ncacn_ip_tcp'} | %{$_.split(":")[1]} | select -First 1  
    if (!$exchange_server){Write-Host "Could not determine Exchange-Server FQDN from AD" -ForegroundColor Red; return}  
    write-host "Creating connection to Exchange-Server '$exchange_server' ..." -ForegroundColor Green  
    $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$exchange_server/powershell" -Authentication Kerberos  
    Import-PSSession $session -DisableNameChecking -AllowClobber | out-null
}catch{
    throw $_
    return
}

# Start Variablen -------------------------------------------------------------------------
# Name des Ordners in dem die Kontake angelegt werden
$foldername = "Firmenkontakte"  
# Überordner festlegen
$parentFolder = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts
# Ende Variablen -------------------------------------------------------------------------

# Custom Propertyset erstellen um einen Kontakt eindeutig zu identifizieren
$propid = [GUID]'{05591a58-e392-4aa9-8cbd-06627d605c95}'  
$propset = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition($propid,'ObjectSID',[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String)  

# EWS Objekt erstellen
$ews = new-object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2)
$ews.Url = [string](Get-WebServicesVirtualDirectory).InternalURL
# Benutze die Credentials mit dem das Skript ausgeführt wird
$ews.UseDefaultCredentials = $true

# Eigenschaft der User welche abgerufen werden
$user_properties =  @('DisplayName','EmailAddress','Company','facsimileTelephoneNumber','givenName','initials','l','mailNickname','name','pager','postalCode','sn','streetAddress','telephoneNumber','HomePhone','MobilePhone','title','co','State','Department','Office','wwwHomePage','Manager')  

# Aktivierte Benutzer abrufen welche als Kontake in den Mailboxen angelegt werden
$users = Get-ADUser -Filter{ObjectCategory -eq "Person" -and objectClass -eq "user" -and Enabled -eq $true} -Properties $user_properties  

# Funktion zum Prüfen von Eigenschaften
function Get-PropValue($prop){return @{$true=$prop;$false=$null}[$prop -ne ""]}  

# Für alle aktivierten Usermailboxen ausführen
Get-Mailbox -RecipientTypeDetails UserMailbox -Filter "IsMailBoxEnabled -eq 'True'" | %{  
    write-host "Updating Mailbox '$($_.Name)'." -ForegroundColor Blue -BackgroundColor White  
    $ews.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, [string]$_.PrimarySmtpAddress)
    # Nach dem Ordner "Firmenkontakte" im Default Kontakte-Ordner suchen  
    $view = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1)
    $view.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly,[Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName)
    $view.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow
    $filter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$foldername)
    $result = $ews.FindFolders($parentFolder,$filter,$view)
    
    if ($result.TotalCount){
        # Ordner wurde gefunden
        $targetFolder = $result.Folders
    }else{
        # Ordner existiert noch nicht, lege ihn an
        write-host "Creating the contact folder '$foldername' in Mailbox." -ForegroundColor Green  
        $targetFolder = New-Object Microsoft.Exchange.WebServices.Data.ContactsFolder($ews)
        $targetFolder.DisplayName = $foldername
        $targetFolder.Save($parentFolder)
    }
    
    # Items im Ordner auflisten
    $view = New-Object Microsoft.Exchange.WebServices.Data.ItemView(10000)
    $view.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly,[Microsoft.Exchange.WebServices.Data.ContactSchema]::DisplayName,$propset)
    $result = $ews.FindItems($targetFolder.Id,$view)

    $targetSIDs =  $result | %{$_.ExtendedProperties | ?{$_.PropertyDefinition.PropertySetId -eq $propid} | select -Expand Value}

    # überschüssige Kontakte die vom User dort angelegt wurden im Ordner löschen
    $result | ?{$_.ExtendedProperties.Value -notin $users.SID -or $_.ExtendedProperties.Count -eq 0 } | %{
        write-host "Removing abandoned contact '$($_.DisplayName)' in '$foldername.'" -ForegroundColor Yellow  
        $_.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
    }
    
    # Vorhandene Kontakte aktualisieren und neue erstellen
    foreach ($user in $users){
        if ($user.SID -in $targetSIDs){
            $isupdate = $true
            $contact = $result | ?{($_.ExtendedProperties | ?{$_.PropertyDefinition.PropertySetId -eq $propid}).Value -eq $user.SID}
            write-host "Updating contact '$($user.Name)'." -ForegroundColor Green  
        }else{
            $isupdate = $false
            $contact = new-object Microsoft.Exchange.WebServices.Data.Contact($ews)
            write-host "Creating new contact '$($user.Name)'." -ForegroundColor Green  
        }

        $contact.GivenName = Get-PropValue $user.GivenName
        $contact.Surname = Get-PropValue $user.sn
        $contact.Initials = Get-PropValue $user.Initials
        $contact.DisplayName = Get-PropValue $user.DisplayName
        $contact.CompanyName = Get-PropValue $user.Company
        $contact.OfficeLocation = Get-PropValue $user.Office
        $contact.Department = Get-PropValue $user.Department
        $contact.JobTitle = Get-PropValue $user.Title
        $contact.BusinessHomePage = Get-PropValue $user.wwwHomePage
        $contact.Manager = Get-PropValue $user.Manager

        $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::HomePhone] = Get-PropValue $user.HomePhone
        $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = Get-PropValue $user.telephoneNumber
        $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::MobilePhone] = Get-PropValue $user.MobilePhone
        $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::Pager] = Get-PropValue $user.Pager
        $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessFax] = Get-PropValue $user.facsimileTelephoneNumber
        
        $privateAddress = New-Object Microsoft.Exchange.WebServices.Data.PhysicalAddressEntry
        $privateAddress.Street = Get-PropValue $user.streetAddress
        $privateAddress.City = Get-PropValue $user.l
        $privateAddress.PostalCode = Get-PropValue $user.postalCode
        $privateAddress.State = Get-PropValue $user.State
        $privateAddress.CountryOrRegion = Get-PropValue $user.co
        $contact.PhysicalAddresses[[Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::Home] = $privateAddress
        $contact.PostalAddressIndex = [Microsoft.Exchange.WebServices.Data.PhysicalAddressIndex]::Home

        $contact.EmailAddresses['EMailAddress1'] = Get-PropValue $user.EMailAddress  
        $contact.FileAsMapping = [Microsoft.Exchange.WebServices.Data.FileAsMapping]::SurnameCommaGivenName

        if ($isupdate){
            $contact.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
        }else{
            # Schreibe die SID des Users in die custom extended property des Kontakts (Dient zur eindeutigen Identifikation des Kontakts)
            $contact.SetExtendedProperty($propset,[string]$user.SID)
            $contact.Save($targetFolder.id)
        }
    }
}

# Exchange Session beenden
Remove-PSSession $session
Grüße Uwe

Falls der Beitrag gefällt, seid so nett und unterstützt mich durch eine kleine Spende / If you like my contribution please support me and donate

edit (08.12.2016 13:00): Anpassung im Skript für die eindeutige Identifizierung der Kontakte über eine Custom Extended Property anstatt des selten genutzten Attributs Generation.
edit (09.12.2016 11:15): Prüfung der Kontakteigenschaften auf Nullwerte
Mitglied: 131381
131381 Dec 06, 2016 updated at 18:53:15 (UTC)
Goto Top
@colinardo Cool, das ist mal was richtig nützliches face-smile

Habs gerade mal auf meinem Test-Exchange ausprobiert, läuft echt gut.

Habe sowas ähnliches für einen Kunden zu realisieren, da kommt gleich mal eine Spende zu Dir face-wink

HERZLICHES DANKE von meiner Seite!

Gruß mikrotik
Member: colinardo
colinardo Dec 06, 2016 updated at 18:59:41 (UTC)
Goto Top
Zitat von @131381:
HERZLICHES DANKE von meiner Seite!
Gerne, keine Ursache face-smile
da kommt gleich mal eine Spende zu Dir
Thanks man face-smile!!

Schönen Abend wünsche ich.
Grüße Uwe
Member: atk691
atk691 Dec 06, 2016 at 20:15:40 (UTC)
Goto Top
Wow, mit sowas habe ich nicht gerechnet face-smile
Werde es morgen gleich mal umsetzen. Hoffe ich komme mit den Variablen klar.
Wie ich sehe, wird der Ordner "Firmenkontakte" sogar bereinigt. Somit würden ausgetretene Mitarbeiter auch entfernt - genial !
Ich bedanke mich sehr herzlich bei Dir , Uwe.
Werde nach der Umsetzung sofort berichten und natürlich ebenfalls spenden.
Diese Arbeit muss man schliesslich belohnen face-wink

Gruss
atk691
Member: atk691
atk691 Dec 07, 2016 at 07:44:21 (UTC)
Goto Top
So, habe es mal vorsichtig angetestet.
Allerdings nur bis Zeile 45. Hat Fehlerfrei funktioniert face-smile
hinzugefügt habe ich in Zeile 44 noch die die searchbase (will es nicht direkt ab root laufen lassen)

Das es auf alle Mailboxen zugreift, finde ich inzwischen recht gut.
Allerdings möchte ich schon zu testzwecken erst mal nur in eine Usermailbox schreiben. Das wird wohl in Zeile 47/48 gemacht. Nur wie genau ?

Gruss
atk691
Member: colinardo
Solution colinardo Dec 07, 2016 updated at 08:34:03 (UTC)
Goto Top
Zitat von @atk691:
So, habe es mal vorsichtig angetestet.
Wofür gibt's VMs ?! face-wink
Allerdings nur bis Zeile 45. Hat Fehlerfrei funktioniert face-smile
Bis dahin passiert ja auch nichts weltbewegendes face-smile
Das es auf alle Mailboxen zugreift, finde ich inzwischen recht gut.
Allerdings möchte ich schon zu testzwecken erst mal nur in eine Usermailbox schreiben. Das wird wohl in Zeile 47/48 gemacht. Nur wie genau ?
Einfach eine beliebigen Filter mit WHERE-Objekt dazwischen schalten oder die Mailbox direkt angeben
Es reicht Zeile 57 auf folgendes zu ändern.
Get-Mailbox 'maxmuster' | %{   
Ein Blick in die Doku zu Get-Mailbox hätte dazu eigentlich auch gereicht.
Member: atk691
atk691 Dec 07, 2016 at 09:03:53 (UTC)
Goto Top
Zitat von @atk691:
Bis dahin passiert ja auch nichts weltbewegendes face-smile
Nunja, immerhin erfolgen hier die Abfragen. Also Test des Connectors. Das war vorher ja mein Problem.
> Get-Mailbox 'maxmuster' | %{   
> 
Ein Blick in die Doku zu Get-Mailbox hätte dazu eigentlich auch gereicht.
Richtig, aber dieses doch recht umfangreiche Script hat mich etwas nervös gemacht face-smile
Da wäre ein Fehler doch unter Umständen fatal.

Habe es jetzt auf meine Box laufen lassen : PERFEKT !

Ohne jetzt schleimen zu wollen:
Man sieht am Code doch gleich den PS -Profi.
Klar und deutlich strukturiert und kommentiert.face-smileface-smile

Nochmals vielen, vielen Dank

Gruss
atk691
Member: atk691
atk691 Dec 07, 2016 at 10:17:00 (UTC)
Goto Top
Auf die Gefahr hin, dass man mich für blöd erklärt:

Ich kriege weitere Filter nicht gebacken.
Zeile 44 möchte ich mit filter telephonenumber und mobile erweitern.
So das nur user mit Festanschluss oder Handy berücksichtigt werden.
Dann kann ich das Thema abschliessen face-smile

Dank und Gruss
atk691
Member: Kraemer
Kraemer Dec 07, 2016 at 10:22:27 (UTC)
Goto Top
Zitat von @atk691:

Auf die Gefahr hin, dass man mich für blöd erklärt:

Ich kriege weitere Filter nicht gebacken.
Zeile 44 möchte ich mit filter telephonenumber und mobile erweitern.
So das nur user mit Festanschluss oder Handy berücksichtigt werden.
Dann kann ich das Thema abschliessen face-smile

Dank und Gruss
atk691
Sorry - aber das ist in meinen Augen schon dreist. Für so etwas bezahlt man üblicherweise Leute...
Member: atk691
atk691 Dec 07, 2016 updated at 10:30:18 (UTC)
Goto Top
Dachte mir schon das sowas kommt, kein Problem, werde den Syntax schon hinkriegen.
Im übrigen, "bezahlt" ist schon erledigt face-smile (wenn auch nur geringfügig)

Gruss
atk691

P.S. "Kraemer:
Du hast dich 2x in diesem Thread gemeldet. Beide mal nicht sonderlich produktiv.face-wink
Aber irgenwie wirst Du ja Level 2 erreicht haben.
Nix für ungut face-wink
Member: atk691
atk691 Dec 07, 2016 at 10:54:04 (UTC)
Goto Top
letzter Kommentar:
-and telephoneNumber -Like"*" -or MobilePhone -Like "*" in Zeile 44 eingefügt und das gewünschte Ergebnis ist da face-smile

Gruss
atk691
Member: Kraemer
Kraemer Dec 07, 2016 at 11:10:32 (UTC)
Goto Top
Zitat von @atk691:
Du hast dich 2x in diesem Thread gemeldet. Beide mal nicht sonderlich produktiv.face-wink
Aber irgenwie wirst Du ja Level 2 erreicht haben.
Nix für ungut face-wink
das sagt der richtige. Ist ja viel einfacher sich blöd zu stellen und andere machen zu lassen - naja streng genommen dies zu versuchen. Wie man sieht war es nämlich auch für dich ein leichtes, das Skript anzupassen.
Und wenn du dann mal irgendwann zu einer Lösung beigetragen hast, dann denke ich evtl. auch mal darüber nach, ob ich mich von dir kritisieren lasse...
Nix für ungut
Member: colinardo
Solution colinardo Dec 07, 2016 updated at 13:03:55 (UTC)
Goto Top
Zitat von @atk691:

letzter Kommentar:
-and telephoneNumber -Like"*" -or MobilePhone -Like "*" in Zeile 44 eingefügt und das gewünschte Ergebnis ist da face-smile
Richtiger wäre eher:
$users = Get-ADUser -Filter{ObjectCategory -eq "Person" -and objectClass -eq "user" -and Enabled -eq $true} -Properties $user_properties | ?{$_.telephoneNumber -ne $null -or $_.MobilePhone -ne $null}  
Oder alternativ auch über den LDAP-Filter:
$users = Get-AdUser -LDAPFilter "(&(ObjectCategory=Person)(ObjectClass=User)(!userAccountControl:1.2.840.113556.1.4.803:=2)(|(mobile=*)(telephoneNumber=*)))" -Properties $user_properties  

Und vielen Dank dir ebenfalls für deine Spende. Sie ist soeben einer Krebsstiftung hier vor Ort zu Gute gekommen face-smile

Schöne Restwoche
Grüße Uwe
Member: atk691
atk691 Dec 07, 2016 at 14:32:35 (UTC)
Goto Top
Wie gesagt, das übersteigt meinen PS-Horizont face-sad
Habe deine Version eingepflegt - und - wen wunderts? - es läuft einwandfrei face-smile

Das Thema hat mich doch recht stark motiviert mich in PS zu vertiefen.

Vielen Dank Uwe

Gruss
atk691
Member: xbast1x
xbast1x Dec 09, 2016 at 07:12:46 (UTC)
Goto Top
@colinardo

Wenn ich Benutzer nur aus einer bestimmten OU in die Liste eintragen möchte, wie stelle ich das an? Wird der Filter OU mit in Zeile 83 angehangen?

Gruß xbast1x
Member: colinardo
colinardo Dec 09, 2016 at 07:34:32 (UTC)
Goto Top
Zitat von @xbast1x:

@colinardo

Wenn ich Benutzer nur aus einer bestimmten OU in die Liste eintragen möchte, wie stelle ich das an? Wird der Filter OU mit in Zeile 83 angehangen?

In Zeile 50 legst du fest welche Kontakte angelegt werden. Geb dort einfach zusätzlich den Parameter -SearchBase 'OU=Marketing,DC=domain,DC=de' mit dem DN deiner OU an, fertig.

Grüße Uwe
Member: xbast1x
xbast1x Dec 09, 2016 updated at 07:40:59 (UTC)
Goto Top
Perfekt, merci ;)

Das gleiche könnte ich auch machen wenn ich nur eine bestimmte OU befüllen möchte. Das wäre dann die Zeile 53 korrekt?

Ich habe dir einen kleinen Obolus überlassen.
Member: colinardo
colinardo Dec 09, 2016 updated at 10:19:16 (UTC)
Goto Top
ACHTUNG an alle die das Script bereits verwenden, es wurde an einigen Stellen auf den aktuellen Stand gebracht.

Anfragen für persönliche Anpassungen nehme ich gegen eine entsprechende Aufwandsentschädigung gerne per PN entgegen.

Danke für euer Verständnis.
Grüße Uwe
Member: colinardo
Solution colinardo Dec 17, 2016, updated at Sep 12, 2023 at 14:20:25 (UTC)
Goto Top
Hier nur noch als Ergänzung eine aktuelle Variante des Scripts welches auch auf Exchange 2013 und 2016 lauffähig ist.
<#
   Sync AD-Users to Contact-Folder in all Mailboxes / (c) @colinardo
   Vor dem Start des Skript ist dem User welcher das Skript später ausführt die Impersonation-Berechtigungzu erteilen:
   In diesem Beispiel der User 'Administrator' (Bitte einmalig in einer EMS ausführen)  
   New-ManagementRoleAssignment -Name:impersonationAssignmentName -Role:ApplicationImpersonation -User:Administrator
#>

if ($PSVersionTable.PSVersion.Major -lt 3){write-host "ERROR: Minimum Powershell Version 3.0 is required!" -F Yellow; return}    

# Active Directory Modul laden
Import-Module ActiveDirectory

# EWS DLL laden
Add-Type -Path "$(Split-Path $MyInvocation.MyCommand.Definition -Parent)\Microsoft.Exchange.WebServices.dll"  

# Mit Exchange verbinden
try{
    $exchange_server = Get-ADObject -LDAPFilter 'objectClass=msExchExchangeServer' -SearchBase (([ADSI]"LDAP://RootDse").configurationNamingContext.ToString()) -Properties networkaddress | select -Expand networkaddress | ?{$_ -match 'ncacn_ip_tcp'} | %{$_.split(":")[1]} | select -First 1  
    if (!$exchange_server){Write-Host "Could not determine Exchange-Server FQDN from AD" -ForegroundColor Red; return}  
    write-host "Creating connection to Exchange-Server '$exchange_server' ..." -ForegroundColor Green  
    $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$exchange_server/powershell" -Authentication Kerberos  
    Import-PSSession $session -DisableNameChecking -AllowClobber | out-null
}catch{
    throw $_
    return
}

# Start Variablen -------------------------------------------------------------------------
# Name des Ordners in dem die Kontake angelegt werden
$foldername = "Firmenkontakte"  
# Überordner festlegen
$parentFolder = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts
# Ende Variablen -------------------------------------------------------------------------

# Allen Zertifikaten vertrauen
Add-Type @"  
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@  
# Trust all certs policy dem ServicePointManager zuweisen
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

# Custom Propertyset erstellen um einen Kontakt eindeutig zu identifizieren
$propid = [GUID]'{05591a58-e392-4aa9-8cbd-06627d605c95}'  
$propset = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition($propid,'ObjectSID',[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String)  

# EWS Objekt erstellen
$ews = new-object Microsoft.Exchange.WebServices.Data.ExchangeService
$ews.Url = [string](Get-WebServicesVirtualDirectory).InternalURL
# Benutze die Credentials mit dem das Skript ausgeführt wird
$ews.UseDefaultCredentials = $true

# Eigenschaft der User welche abgerufen werden
$user_properties =  @('DisplayName','EmailAddress','Company','facsimileTelephoneNumber','givenName','initials','l','mailNickname','name','pager','postalCode','sn','streetAddress','telephoneNumber','HomePhone','MobilePhone','title','co','State','Department','Office','wwwHomePage','Manager')  

# Aktivierte Benutzer abrufen welche als Kontake in den Mailboxen angelegt werden
$users = Get-ADUser -Filter{ObjectCategory -eq "Person" -and objectClass -eq "user" -and Enabled -eq $true} -Properties $user_properties  

# Funktion zum Prüfen von Eigenschaften
function Get-PropValue($prop){return (@{$true=$prop;$false=$null}[![string]::IsNullOrEmpty($prop)])}

# Für alle aktivierten Usermailboxen ausführen
Get-Mailbox -RecipientTypeDetails UserMailbox -Filter "IsMailBoxEnabled -eq 'True'" | %{  
    write-host "Updating Mailbox '$($_.Name)'." -ForegroundColor Blue -BackgroundColor White  
    $ews.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, [string]$_.PrimarySmtpAddress)
    # Nach dem Ordner "Firmenkontakte" im Default Kontakte-Ordner suchen  
    $view = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1)
    $view.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly,[Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName)
    $view.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow
    $filter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$foldername)
    $result = $ews.FindFolders($parentFolder,$filter,$view)
    
    if ($result.TotalCount){
        # Ordner wurde gefunden
        $targetFolder = $result.Folders
    }else{
        # Ordner existiert noch nicht, lege ihn an
        write-host "Creating the contact folder '$foldername' in Mailbox." -ForegroundColor Green  
        try{
            $targetFolder = New-Object Microsoft.Exchange.WebServices.Data.ContactsFolder($ews)
            $targetFolder.DisplayName = $foldername
            $targetFolder.Save($parentFolder)
        }catch{
            Write-Host $_ -F Red
            return
        }
    }
    
    # Items im Ordner auflisten
    $view = New-Object Microsoft.Exchange.WebServices.Data.ItemView(10000)
    $view.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly,[Microsoft.Exchange.WebServices.Data.ContactSchema]::DisplayName,$propset)
    $result = $ews.FindItems($targetFolder.Id,$view)

    $targetSIDs =  $result | %{$_.ExtendedProperties | ?{$_.PropertyDefinition.PropertySetId -eq $propid} | select -Expand Value}

    # überschüssige Kontakte die vom User dort angelegt wurden im Ordner löschen
    $result | ?{$_.ExtendedProperties.Value -notin $users.SID -or $_.ExtendedProperties.Count -eq 0 } | %{
        write-host "Removing abandoned contact '$($_.DisplayName)' in '$foldername.'" -ForegroundColor Yellow  
        $_.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
    }
    
    # Vorhandene Kontakte aktualisieren und neue erstellen
    foreach ($user in $users){

        try{
            if ($user.SID -in $targetSIDs){
                $isupdate = $true
                $contact = $result | ?{($_.ExtendedProperties | ?{$_.PropertyDefinition.PropertySetId -eq $propid}).Value -eq $user.SID}
                $contact.Load()
                write-host "Updating contact '$($user.Name)'." -ForegroundColor Green  
            }else{
                $isupdate = $false
                $contact = new-object Microsoft.Exchange.WebServices.Data.Contact($ews)
                write-host "Creating new contact '$($user.Name)'." -ForegroundColor Green  
            }
            $contact.GivenName = Get-PropValue $user.GivenName
            $contact.Surname = Get-PropValue $user.sn
            $contact.Initials = Get-PropValue $user.Initials
            $contact.DisplayName = Get-PropValue $user.DisplayName
            $contact.CompanyName = Get-PropValue $user.Company
            $contact.OfficeLocation = Get-PropValue $user.Office
            $contact.Department = Get-PropValue $user.Department
            $contact.JobTitle = Get-PropValue $user.Title
            $contact.BusinessHomePage = Get-PropValue $user.wwwHomePage
            $contact.Manager = Get-PropValue $user.Manager
            
            $number = ""  
            $numberFound = $contact.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::HomePhone,[ref]$number)
            $prop = Get-PropValue $user.HomePhone
            if (($numberFound -and $number -ne $user.HomePhone) -or (!$numberFound -and $prop -ne $null)){
                $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::HomePhone] = @{$true=$prop;$false=[char]129}[$prop -ne $null]
            }
            $numberFound = $contact.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone,[ref]$number)
            $prop = Get-PropValue $user.telephoneNumber
            if (($numberFound -and $number -ne $user.telephoneNumber) -or (!$numberFound -and $prop -ne $null)){
                $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = @{$true=$prop;$false=[char]129}[$prop -ne $null]
            }
            $numberFound = $contact.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::MobilePhone,[ref]$number)
            $prop = Get-PropValue $user.MobilePhone
            if (($numberFound -and $number -ne $user.MobilePhone) -or (!$numberFound -and $prop -ne $null)){
                $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::MobilePhone] =  @{$true=$prop;$false=[char]129}[$prop -ne $null]
            }
            $numberFound = $contact.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::Pager,[ref]$number)
            $prop = Get-PropValue $user.Pager
            if (($numberFound -and $number -ne $user.Pager) -or (!$numberFound -and $prop -ne $null) ){
                $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::Pager] = @{$true=$prop;$false=[char]129}[$prop -ne $null]
            }

            $numberFound = $contact.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessFax,[ref]$number)
            $prop = Get-PropValue $user.facsimileTelephoneNumber
            if (($numberFound -and $number -ne $user.facsimileTelephoneNumber) -or (!$numberFound -and $prop -ne $null)){
                 $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessFax] =  @{$true=$prop;$false=[char]129}[$prop -ne $null]
            }
        
            
            $privateAddress = New-Object Microsoft.Exchange.WebServices.Data.PhysicalAddressEntry
            $privateAddress.Street = Get-PropValue $user.streetAddress
            $privateAddress.City = Get-PropValue $user.l
            $privateAddress.PostalCode = Get-PropValue $user.postalCode
            $privateAddress.State = Get-PropValue $user.State
            $privateAddress.CountryOrRegion = Get-PropValue $user.co
            $contact.PhysicalAddresses[[Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::Home] = $privateAddress
            $contact.PostalAddressIndex = [Microsoft.Exchange.WebServices.Data.PhysicalAddressIndex]::Home

            $contact.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1] = Get-PropValue $user.EMailAddress
            $contact.FileAsMapping = [Microsoft.Exchange.WebServices.Data.FileAsMapping]::SurnameCommaGivenName
            
            if ($isupdate){
                $contact.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
            }else{
                # Schreibe die SID des Users in die custom extended property des Kontakts (Dient zur eindeutigen Identifikation des Kontakts)
                $contact.SetExtendedProperty($propset,[string]$user.SID)
                $contact.Save($targetFolder.id)
            }
         }catch{
            Write-host $_ -F Red
            return
         }
    }
}

# Exchange Session beenden
Remove-PSSession $session

Wünsche euch allen schon einmal ein frohes Weihnachtsfest.
Grüße Uwe
Member: atk691
atk691 Dec 20, 2016 at 09:06:30 (UTC)
Goto Top
Perfekt face-smile
hatte schon die Befürchtung, dass ich die Anpassungen an Exchange 2016 nicht gebacken kriege.

Vielen Dank Uwe

Gruss
atk691
Member: patrickebert
patrickebert Nov 08, 2017 updated at 20:14:18 (UTC)
Goto Top
Super Skript Uwe,ich habe das Skript gerade schnell mal durchgelesen.
Doch eine Frage habe ich diesbezüglich
Erstellst du hier erst einen Öffentlichen Kontakte Ordner aus den User Eigenschaften und schreibst ihn dann in den Kontakte Ordner der User?
Die Frage kommt daher, da ich schon einen Öffentlichen Kontakte Ordner habe, welcher über unserer Datenbank abgeglichen wird
Und ich dies dann mit den Userproperties auslesen lassen könnte.

Gruß
Patrick
Member: colinardo
colinardo Nov 08, 2017 at 21:29:49 (UTC)
Goto Top
Hallo Patrick,
Erstellst du hier erst einen Öffentlichen Kontakte Ordner aus den User Eigenschaften und schreibst ihn dann in den Kontakte Ordner der User?
Nein, bei diesem Skript wird in jeder Mailbox der User unterhalb deren Default Kontakte Ordners ein weiterer Ordner erstellt in dem die Kontakte dann landen und bei weiteren Durchläufen dann nur noch aktualisiert/ergänzt/gelöscht.

Grüße Uwe
Member: patrickebert
patrickebert Nov 09, 2017 at 09:44:32 (UTC)
Goto Top
Hey Uwe,
Vielen Dank für deine Info, habe es mir nun, da ich mehr Zeit habe, mal genauer durchgelesen und verstehe eigentlich alles was du machst.
Da ich aber nicht aus der AD auslesen will, sondern von einer Datenbank abfragen will. Ist mir ein Element noch nicht ganz klar, was er da genau macht und daher die Frage kannst du mir den Scriptschnipsel erläutern?
 $numberFound = $contact.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::Pager,[ref]$number)
            $prop = Get-PropValue $user.Pager
            if (($numberFound -and $number -ne $user.Pager) -or (!$numberFound -and $prop -ne $null) ){
                $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::Pager] = @{$true=$prop;$false=[char]129}[$prop -ne $null]

$True und $False sind ja eigentlich Konstanten und es sieht hier so aus als ob du diese neu definierst.
[char]129 ist das Telefonsymbol in AscII
Grüße
Member: colinardo
colinardo Nov 09, 2017 at 10:07:44 (UTC)
Goto Top
Das sind Workarounds für die Update-Prozedur bei Kontakten, da ansonsten Fehler beim Update auftreten und leere Felder hier zu Fehlern führen, deswegen das Füllen des Feldes mit einem nicht sichtbaren Zeichen.
Member: RedRocks
RedRocks Nov 23, 2018 at 08:48:09 (UTC)
Goto Top
Hallo Zusammen,

funkioniert dieser Script auch für den Exchange Online?
Member: colinardo
colinardo Nov 23, 2018 updated at 09:08:16 (UTC)
Goto Top
Zitat von @RedRocks:

Hallo Zusammen,

funkioniert dieser Script auch für den Exchange Online?
Servus.
Ohne Anpassung, nein, dazu sind diverse Änderungen nötig. zumal ja erstens der Exchange aus dem AD ermittelt wird, die User aus dem lokalen AD kommen etc. pp.

Grüße Uwe
Member: WilliWillsWissen19
WilliWillsWissen19 May 07, 2019 updated at 07:21:52 (UTC)
Goto Top
Moin,

ich habe das soweit bei mir laufen lassen und nur für einen Testaccount in Zeile 71 eingetragen "Get-Mailbox 'Timo Test' | %{".
Leider bekomme ich nach kurzer Laufzeit folgenden Fehler:

Creating connection to Exchange-Server 'Server.Domain.intern' ...
Updating Mailbox 'Timo Test'.
% : Exception calling "FindFolders" with "3" argument(s): "The request failed. The remote server returned an error: (400) Bad Request."
At C:\_Tools\Scripte\Kontakte-in-Useradressbuch.ps1:71 char:27
Get-Mailbox 'timo test' | %{

CategoryInfo : NotSpecified: ( [ForEach-Object], MethodInvocationException
FullyQualifiedErrorId : ServiceRequestException,Microsoft.PowerShell.Commands.ForEachObjectCommand

Wir nutzen Exchange 2016 mit CU 12 (Version 15.1 ‎(Build 1713.5)‎), insgesamt 4 Systeme mit 2 DAG Clustern (2 Server pro Standort in einem DAG)

Danke für einen Gedankenanschubser...

Grüße
Der Willi
Member: atk691
atk691 May 08, 2019 at 16:53:32 (UTC)
Goto Top
Ohne das sich jetzt dein komplettes script kenne: Daran denken, dass es in der Exchange-shell und mit Adminrechten laufen muss.
Deine Errors sehen schon ein wenig nach Berechtigungspoblemen aus face-wink

Gruss
atk691
Member: WilliWillsWissen19
WilliWillsWissen19 May 09, 2019 at 06:24:49 (UTC)
Goto Top
@atk691
Danke für den Ratschlag, das habe ich bereits überprüft. face-smile

Die Exchange-Shell ist unter dem Domänen-Administrator über Rechtsklick, als Administrator ausführen gestartet. Soweit sollte es passen. Die erforderlichen Rechte hat der Domänen-Admin auch um im Exchange "arbeiten" zu dürfen.

Wir haben wie gesagt 2 DAG in 2 Standorten. Jeder Standort hat seine eigene DAG.

DAG Standort A:
Exchangeserver1-A
Exchangeserver2-A

DAG Standort B:
Exchangeserver1-B
Exchangeserver2-B

Die Mailbox von "Timo Test" liegt im Standort A. Wenn ich das Skript im Standort B ausführe, kommt der bereits geschriebene Fehler.
In Standort A läuft das Skript zwar etwas weiter, dann kommt aber diese Meldung:

Creating connection to Exchange-Server 'exchangeserver1-a.domäne.intern' ...
Updating Mailbox 'Timo Test'.
Creating the contact folder 'Firmenkontakte' in Mailbox.
Exception calling "Save" with "1" argument(s): "The request failed. The remote server returned an error: (400) Bad Request."

Hier mal das eingesetzte Skript: (es wurden noch Änderungen vorgenommen und entsprechend kommentiert.)
<#
   Sync AD-Users to Contact-Folder in all Mailboxes / (c) @colinardo
   Vor dem Start des Skript ist dem User welcher das Skript später ausführt die Impersonation-Berechtigungzu erteilen:
   In diesem Beispiel der User 'Administrator' (Bitte einmalig in einer EMS ausführen)  
   New-ManagementRoleAssignment -Name:impersonationAssignmentName -Role:ApplicationImpersonation -User:Administrator
#>

if ($PSVersionTable.PSVersion.Major -lt 3){write-host "ERROR: Minimum Powershell Version 3.0 is required!" -F Yellow; return}    

# Active Directory Modul laden
Import-Module ActiveDirectory

# EWS DLL laden
#Original auskommentiert, da das Sktipt in einem anderen Pfad liegt.
#Add-Type -Path "$(Split-Path $MyInvocation.MyCommand.Definition -Parent)\Microsoft.Exchange.WebServices.dll"  
Add-Type -Path "D:\Exchange\V15\Bin\Microsoft.Exchange.WebServices.dll"  

# Mit Exchange verbinden
try{
    #$exchange_server = Get-ADObject -LDAPFilter 'objectClass=msExchExchangeServer' -SearchBase (([ADSI]"LDAP://RootDse").configurationNamingContext.ToString()) -Properties networkaddress | select -Expand networkaddress | ?{$_ -match 'ncacn_ip_tcp'} | %{$_.split(":")[1]} | select -First 1  
 #Server Manuell angegeben
    $exchange_server = "exchangeserver1-b.domäne.intern"  
    if (!$exchange_server){Write-Host "Could not determine Exchange-Server FQDN from AD" -ForegroundColor Red; return}  
    write-host "Creating connection to Exchange-Server '$exchange_server' ..." -ForegroundColor Green  
    $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$exchange_server/powershell" -Authentication Kerberos  
    Import-PSSession $session -DisableNameChecking -AllowClobber | out-null
}catch{
    throw $_
    return
}

# Start Variablen -------------------------------------------------------------------------
# Name des Ordners in dem die Kontake angelegt werden
$foldername = "Firmenkontakte"  
# Überordner festlegen
$parentFolder = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts
# Ende Variablen -------------------------------------------------------------------------

# Allen Zertifikaten vertrauen
Add-Type @"  
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
            ServicePoint srvPoint, X509Certificate certificate,
            WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@  
# Trust all certs policy dem ServicePointManager zuweisen
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

# Custom Propertyset erstellen um einen Kontakt eindeutig zu identifizieren
$propid = [GUID]'{05591a58-e392-4aa9-8cbd-06627d605c95}'  
$propset = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition($propid,'ObjectSID',[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String)  

# EWS Objekt erstellen
$ews = new-object Microsoft.Exchange.WebServices.Data.ExchangeService
$ews.Url = [string](Get-WebServicesVirtualDirectory).InternalURL
# Benutze die Credentials mit dem das Skript ausgeführt wird
$ews.UseDefaultCredentials = $true

# Eigenschaft der User welche abgerufen werden
$user_properties =  @('DisplayName','EmailAddress','Company','facsimileTelephoneNumber','givenName','initials','l','mailNickname','name','pager','postalCode','sn','streetAddress','telephoneNumber','HomePhone','MobilePhone','title','co','State','Department','Office','wwwHomePage','Manager')  

# Aktivierte Benutzer abrufen welche als Kontake in den Mailboxen angelegt werden
$users = Get-ADUser -Filter{ObjectCategory -eq "Person" -and objectClass -eq "user" -and Enabled -eq $true} -Properties $user_properties  

# Funktion zum Prüfen von Eigenschaften
function Get-PropValue($prop){return (@{$true=$prop;$false=$null}[![string]::IsNullOrEmpty($prop)])}

# Für alle aktivierten Usermailboxen ausführen
    #Für Testzwecke abgeändert
        #Get-Mailbox -RecipientTypeDetails UserMailbox -Filter "IsMailBoxEnabled -eq 'True'" | %{  
Get-Mailbox 'timo test' | %{  
    write-host "Updating Mailbox '$($_.Name)'." -ForegroundColor Blue -BackgroundColor White  
    $ews.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, [string]$_.PrimarySmtpAddress)
    # Nach dem Ordner "Firmenkontakte" im Default Kontakte-Ordner suchen  
    $view = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1)
    $view.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly,[Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName)
    $view.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow
    $filter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$foldername)
    #$result = $ews.FindFolders($parentFolder,$filter,$view)
    
    if ($result.TotalCount){
        # Ordner wurde gefunden
        $targetFolder = $result.Folders
    }else{
        # Ordner existiert noch nicht, lege ihn an
        write-host "Creating the contact folder '$foldername' in Mailbox." -ForegroundColor Green  
        try{
            $targetFolder = New-Object Microsoft.Exchange.WebServices.Data.ContactsFolder($ews)
            $targetFolder.DisplayName = $foldername
            $targetFolder.Save($parentFolder)
        }catch{
            Write-Host $_ -F Red
            return
        }
    }
    
    # Items im Ordner auflisten
    $view = New-Object Microsoft.Exchange.WebServices.Data.ItemView(10000)
    $view.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::IdOnly,[Microsoft.Exchange.WebServices.Data.ContactSchema]::DisplayName,$propset)
    $result = $ews.FindItems($targetFolder.Id,$view)

    $targetSIDs =  $result | %{$_.ExtendedProperties | ?{$_.PropertyDefinition.PropertySetId -eq $propid} | select -Expand Value}

    # überschüssige Kontakte die vom User dort angelegt wurden im Ordner löschen
    $result | ?{$_.ExtendedProperties.Value -notin $users.SID -or $_.ExtendedProperties.Count -eq 0 } | %{
        write-host "Removing abandoned contact '$($_.DisplayName)' in '$foldername.'" -ForegroundColor Yellow  
        $_.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
    }
    
    # Vorhandene Kontakte aktualisieren und neue erstellen
    foreach ($user in $users){

        try{
            if ($user.SID -in $targetSIDs){
                $isupdate = $true
                $contact = $result | ?{($_.ExtendedProperties | ?{$_.PropertyDefinition.PropertySetId -eq $propid}).Value -eq $user.SID}
                $contact.Load()
                write-host "Updating contact '$($user.Name)'." -ForegroundColor Green  
            }else{
                $isupdate = $false
                $contact = new-object Microsoft.Exchange.WebServices.Data.Contact($ews)
                write-host "Creating new contact '$($user.Name)'." -ForegroundColor Green  
            }
            $contact.GivenName = Get-PropValue $user.GivenName
            $contact.Surname = Get-PropValue $user.sn
            $contact.Initials = Get-PropValue $user.Initials
            $contact.DisplayName = Get-PropValue $user.DisplayName
            $contact.CompanyName = Get-PropValue $user.Company
            $contact.OfficeLocation = Get-PropValue $user.Office
            $contact.Department = Get-PropValue $user.Department
            $contact.JobTitle = Get-PropValue $user.Title
            $contact.BusinessHomePage = Get-PropValue $user.wwwHomePage
            $contact.Manager = Get-PropValue $user.Manager
            
            $number = ""  
            $numberFound = $contact.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::HomePhone,[ref]$number)
            $prop = Get-PropValue $user.HomePhone
            if (($numberFound -and $number -ne $user.HomePhone) -or (!$numberFound -and $prop -ne $null)){
                $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::HomePhone] = @{$true=$prop;$false=[char]129}[$prop -ne $null]
            }
            $numberFound = $contact.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone,[ref]$number)
            $prop = Get-PropValue $user.telephoneNumber
            if (($numberFound -and $number -ne $user.telephoneNumber) -or (!$numberFound -and $prop -ne $null)){
                $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessPhone] = @{$true=$prop;$false=[char]129}[$prop -ne $null]
            }
            $numberFound = $contact.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::MobilePhone,[ref]$number)
            $prop = Get-PropValue $user.MobilePhone
            if (($numberFound -and $number -ne $user.MobilePhone) -or (!$numberFound -and $prop -ne $null)){
                $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::MobilePhone] =  @{$true=$prop;$false=[char]129}[$prop -ne $null]
            }
            $numberFound = $contact.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::Pager,[ref]$number)
            $prop = Get-PropValue $user.Pager
            if (($numberFound -and $number -ne $user.Pager) -or (!$numberFound -and $prop -ne $null) ){
                $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::Pager] = @{$true=$prop;$false=[char]129}[$prop -ne $null]
            }

            $numberFound = $contact.PhoneNumbers.TryGetValue([Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessFax,[ref]$number)
            $prop = Get-PropValue $user.facsimileTelephoneNumber
            if (($numberFound -and $number -ne $user.facsimileTelephoneNumber) -or (!$numberFound -and $prop -ne $null)){
                 $contact.PhoneNumbers[[Microsoft.Exchange.WebServices.Data.PhoneNumberKey]::BusinessFax] =  @{$true=$prop;$false=[char]129}[$prop -ne $null]
            }
        
            
            $privateAddress = New-Object Microsoft.Exchange.WebServices.Data.PhysicalAddressEntry
            $privateAddress.Street = Get-PropValue $user.streetAddress
            $privateAddress.City = Get-PropValue $user.l
            $privateAddress.PostalCode = Get-PropValue $user.postalCode
            $privateAddress.State = Get-PropValue $user.State
            $privateAddress.CountryOrRegion = Get-PropValue $user.co
            $contact.PhysicalAddresses[[Microsoft.Exchange.WebServices.Data.PhysicalAddressKey]::Home] = $privateAddress
            $contact.PostalAddressIndex = [Microsoft.Exchange.WebServices.Data.PhysicalAddressIndex]::Home

            $contact.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1] = Get-PropValue $user.EMailAddress
            $contact.FileAsMapping = [Microsoft.Exchange.WebServices.Data.FileAsMapping]::SurnameCommaGivenName
            
            if ($isupdate){
                $contact.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
            }else{
                # Schreibe die SID des Users in die custom extended property des Kontakts (Dient zur eindeutigen Identifikation des Kontakts)
                $contact.SetExtendedProperty($propset,[string]$user.SID)
                $contact.Save($targetFolder.id)
            }
         }catch{
            Write-host $_ -F Red
            return
         }
    }
}

# Exchange Session beenden
Remove-PSSession $session

Es scheint als stehe ich auf dem Schlauch..

Grüße
Der Willi
Member: colinardo
colinardo Jul 23, 2020 updated at 14:54:18 (UTC)
Goto Top
Servus,
auch wenn der Thread schon einige Zeit auf dem Buckel hat, nur noch als Ergänzung für die die hier vorbei schlendern und vielleicht etwas leicht abweichendes suchen.

Ein Powershell-Skript aus meiner Feder das einen öffentlichen Ordner in die User-Mailboxen One-Way synchronisiert:

Also Öffentlicher Ordner => Kontakt-Ordner der User-Mailbox (bestehende Kontakte bleiben natürlich erhalten)

Exchange öffentliche Ordner auf Smartphones übertragen

Grüße Uwe
Member: desmu74
desmu74 Sep 12, 2023 at 14:17:18 (UTC)
Goto Top
Hallo,

hat schon ma jemand das Script:"Sync AD-Users to Contact-Folder in all Mailboxes"

erfolgreich mit dem Exchange 2019 getestet?
Unter 2013 lief bisher alles perfekt und nun gibt es diverse Fehlermeldungen.

zB.:
% : Ausnahme beim Aufrufen von "FindFolders" mit 3 Argument(en): "The request failed. Die zugrunde liegende Verbindung wurde geschlossen: Unerwarteter Fehler beim Senden.."

back-to-top... entTypeDetails UserMailbox -Filter "IsMailBoxEnabled -eq 'True'" | %{

Member: prinzjulius
prinzjulius Sep 13, 2023 at 14:43:32 (UTC)
Goto Top
Guten Tag,

bei mir seit einigen Tagen das gleiche Problem auf einem Exchange 2016. Bisher lief es problemlos. Folgende Ausgabe:

% : Ausnahme beim Aufrufen von "FindFolders" mit 3 Argument(en):  "The request failed. Der Remoteserver hat einen Fehler zurückgegeben: (403) Unzulässig."  
In C:\PS-Skripte\MLKontakte.ps1:79 Zeichen:23
+ Get-Mailbox 'FooBar' | %{  
+                       ~~
    + CategoryInfo          : NotSpecified: (:) [ForEach-Object], MethodInvocationException
    + FullyQualifiedErrorId : ServiceRequestException,Microsoft.PowerShell.Commands.ForEachObjectCommand

Bisher konnte ich das Problem nicht lösen.

Beste Grüße
JPR
Member: colinardo
colinardo Sep 13, 2023 updated at 15:56:53 (UTC)
Goto Top
Servus @prinzjulius und @desmu74, willkommen auf Administrator.de!
Habe zur Zeit leider kein lokales Exchange-System für Test mehr im Zugriff. Der letzten Fehlermeldung zu Folge könnte es aber sein das die minimale EWS-Schemaversion in den letzten Wochen auf den Servern per Update angehoben wurde. Wenn das der Fall ist hilft eventuell im Konstruktor des EWS-Objects die minimale Schema-Version zu erhöhen:
$ews = new-object Microsoft.Exchange.WebServices.Data.ExchangeService ([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2016)
Wie gesagt, ich kann zur Zeit leider bezüglich "OnPremise Exchange" keinen weiteren Support leisten da die Umstände es leider nicht zulassen.

Grüße Uwe
Member: prinzjulius
prinzjulius Sep 14, 2023 at 08:11:16 (UTC)
Goto Top
@colinardo Vielen Dank für die schnelle Hilfe.
Das hat zwar noch nicht geholfen, aber ist vielleicht ein Ansatz.

Grüße
JPR
Member: desmu74
desmu74 Sep 14, 2023 at 08:42:22 (UTC)
Goto Top
Hallo zusammen,

mit einer neuen Testinstallation von Exchange 2019 ( alles standard mit selbstsignierten Zertifikaten ) + DC funktioniert das Script. Ich bin grad auf der Suche was bei der Migration von EX2013 auf EX2019 schief gelaufen ist.

Grüße
Member: desmu74
desmu74 Sep 14, 2023 at 10:48:58 (UTC)
Goto Top
Hallo,

nachdem nun einige Updates auf dem Testsystem gelaufen sind, gab es wieder Fehler beim ausführen des Scripts.
Abhilfe schaffte ein Reg Eintrag vom Typ REG_MULTI_SZ unter:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0

BackConnectionHostNames

hier wurden alle FQDN`s vom Server hinterlegt

Wie hier beschrieben

Ich hoffe es hilft euch.

Grüße
Member: colinardo
colinardo Sep 14, 2023 updated at 10:57:10 (UTC)
Goto Top
Ah ja OK, wenn das ganze lokal auf dem Exchange selbst läuft, richtig, da war ja mal was mit Loopback und Kerberos, die Erinnerung kommt wieder in die grauen Zellen face-wink.

Danke für das Teilen der Info 👍 !

Grüße Uwe
Mitglied: 139689
139689 Oct 03, 2023 at 16:00:31 (UTC)
Goto Top
@colinardo

Möchte mich recht herzlich für das Script bedanken! Damit konnte ich, im Gegensatz zu vielen anderen Beispielen im Netz, tatsächlich ein Ergebnis erzielen!
Nur noch die User besser filtern und die Automatisierung herstellen, scharf schalten und es dürfte laufen...

Getestet auf Exchange 2019 on-premise mit aktuellstem CU.
Das mit dem Loopback war auch hier ein Thema.

Wenn ich Paypal grad aktiv nutzen würde, wäre sicher eine Spende da...