Wir suchten nach einer Möglichkeit, ausgewählte Kalendereinträge verschiedener Mitarbeitender zuverlässig mit einem SharePoint-Kalender zu synchronisieren. Wie wir das gelöst haben, worauf man achten sollte und welche Stolpersteine auftauchten, lest ihr hier.
Überblick
Das Ziel: Nur Termine mit einer klar definierten Kennzeichnung sollten aus mehreren persönlichen Outlook-Kalendern in einen zentralen SharePoint-Kalender übernommen werden. Der Abgleich musste regelmäßig laufen, damit Teamleitungen stets aktuelle Urlaubs- oder Abwesenheitspläne vor sich haben.
Anforderungen
- Synchronisieren bestimmter Kalendereinträge unterschiedlicher Personen mit einem SharePoint-Kalender.
- Vergangene Termine der letzten X Wochen und zukünftige Termine der kommenden Y Wochen berücksichtigen.
- Die Synchronisation alle zwei Stunden per Skript automatisch anstoßen.
Technische Umsetzung
Da der Abgleich ohne Benutzerinteraktion laufen sollte, entschieden wir uns für ein PowerShell-Skript. Über EWS lesen wir die Outlook-Kalendereinträge samt Kategorien aus, filtern sie und schreiben passende Einträge in SharePoint.
Kategorien und Benutzer sammeln
Um festzulegen, welche Termine übertragen werden, gibt es zwei Optionen: Kategorien oder ein Textpräfix. Wir haben uns für Kategorien entschieden, weil sie nur einmal angelegt werden und Tippfehler ausgeschlossen sind. Die Benutzer, deren Kalender berücksichtigt werden, kommen aus einer Active-Directory-Gruppe.
$identities = Get-ADGroupMember Outlooktermine_Mitarbeiter -Recursive |
Get-ADUser -Properties * |
Select-Object Mail
SharePoint-Kalender vorbereiten
Bevor neue Termine eingetragen werden, löschen wir im SharePoint-Kalender alle Items, die im relevanten Zeitraum liegen. So vermeiden wir Dubletten und stellen sicher, dass verschobene oder gelöschte Termine korrekt übernommen werden.
$cal = $web.lists.getbytitle('Name of your SharePoint calendar here')
$ctx.load($cal)
$ctx.ExecuteQuery()
$query = "<Query>
<Where>
<And>
<Geq>
<FieldRef Name='EventDate' />
<Value IncludeTimeValue='TRUE' Type='DateTime'>" + $startDate + "</Value>
</Geq>
<Leq>
<FieldRef Name='EndDate' />
<Value IncludeTimeValue='TRUE' Type='DateTime'>" + $endDate + "</Value>
</Leq>
</And>
</Where>
</Query>"
$items = [Microsoft.SharePoint.Client.ListItemCollection]$cal.GetItems($query)
$ctx.load($items)
$ctx.ExecuteQuery()
Termine filtern und übertragen
Nun laufen wir alle Benutzer durch, lesen deren Kalendereinträge aus und übertragen nur jene mit der gewünschten Kategorie in den SharePoint-Kalender.
$categories = @("Urlaub", "Sonderurlaub")
foreach ($identity in $identities) {
$appointments = Get-CalendarInformation -Identity $identity.Mail -weeksBefore $weeksBefore -weeksAfter $weeksAfter
foreach ($appointment in $appointments) {
$hasCategory = $false
foreach ($appCategory in $appointment.Categories) {
if ($categories -contains $appCategory) {
$hasCategory = $true
break
}
}
if ($hasCategory) {
Add-AppointmentIntoSPCalendar -Appointment $appointment
}
}
}
PowerShell-Funktion Get-CalendarInformation
Das Herzstück ist eine Funktion, die den Outlook-Kalender eines Postfachs liest, die Zeitspanne begrenzt und die benötigten Eigenschaften lädt.
function Get-CalendarInformation {
<#
.SYNOPSIS
A PowerShell function to list calendar information
.DESCRIPTION
Long description
.EXAMPLE
PS C:\> Get-CalendarInformation -Identity "xxx" -weeksBefore 5 -weeksAfter 2
Lists calendar information from the last 5 and the next 2 weeks
#>
[CmdletBinding()]
param (
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
$Identity,
[Parameter(Mandatory)]
[int]
$weeksBefore,
[Parameter()]
[int]
$weeksAfter,
[Parameter()]
[System.Management.Automation.CredentialAttribute()]
[pscredential]
$Credential
)
begin {
$libPath = $PSScriptRoot + "\libs\Microsoft.Exchange.WebServices.dll"
Add-Type -Path $libPath
}
process {
$Service = [Microsoft.Exchange.WebServices.Data.ExchangeService]::new()
if ($PSBoundParameters.ContainsKey('Credential')) {
$Service.Credentials = [System.Net.NetworkCredential]::new($Credential.UserName, $Credential.Password)
}
else {
$Service.UseDefaultCredentials = $true
}
$Service.Url = "https://xx.xxx.at/EWS/Exchange.asmx"
$FolderId = New-Object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar, $Identity)
try {
$Folder = [Microsoft.Exchange.WebServices.Data.CalendarFolder]::Bind($Service, $FolderId)
}
catch {
Write-Host $_.Exception.Message "Name:" $FolderId.Mailbox.Address -ForegroundColor Red
return $null
}
$startDate = [datetime]::Now.AddDays(-7 * $weeksBefore)
if ($null -eq $startDate) {
$startDate = [datetime]::Now
}
$endDate = [datetime]::Now.AddDays(7 * $weeksAfter)
if ($null -eq $endDate) {
$endDate = [datetime]::Now
}
$View = [Microsoft.Exchange.WebServices.Data.CalendarView]::new($startDate, $endDate)
$View.PropertySet = [Microsoft.Exchange.WebServices.Data.PropertySet]::new(
[Microsoft.Exchange.WebServices.Data.AppointmentSchema]::Subject,
[Microsoft.Exchange.WebServices.Data.AppointmentSchema]::Start,
[Microsoft.Exchange.WebServices.Data.AppointmentSchema]::End,
[Microsoft.Exchange.WebServices.Data.AppointmentSchema]::Organizer,
[Microsoft.Exchange.WebServices.Data.AppointmentSchema]::DateTimeCreated
)
$BodyPropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$BodyPropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text
$FolderItems = $Service.FindItems($Folder.Id, $View)
[void]$Service.LoadPropertiesForItems($FolderItems, $BodyPropertySet)
return $FolderItems
}
}
Mit diesem Setup lassen sich Outlook-Kalender mittels PowerShell und EWS automatisiert mit SharePoint synchronisieren. Welche weiteren Szenarien sich mit PowerShell abdecken lassen, liest du in unserem Blog.
Habt ihr Fragen oder Interesse am kompletten Skript? Meldet euch gern, indem ihr auf den Button klickt!
Umsetzung
Da die Einträge alle 2 Stunden synchronisiert werden sollen, haben wir uns dazu entschieden es mit einem PowerShell Skript umzusetzen. Um herauszufinden welche Termine synchronisiert werden sollen, gibt es 2 Möglichkeiten:
- Termine mit Kategorien versehen (Jeder User muss sich die Kategorien selbst anlegen)
- Termine mit einem bestimmten Präfix synchronisieren
Wir haben uns für Möglichkeit 1. Entschieden, da man so nur einmal die Kategorien anlegen muss. Dadurch fallen mögliche Fehler durch falsche Präfixe und Tippfehler weg. Die Farbe der Kategorie spielt dabei keine Rolle. Ein Nachteil hierbei ist, dass es keine Möglichkeit gibt, den User davon abzuhalten Kategorien zu bearbeiten bzw. zu löschen.
Die Kalendereinträge inklusive Kategorien, werden mittels EWS ausgelesen.
Zuerst müssen deren Kalender synchronisiert werden soll aus der AD-Gruppe ausgelesen werden.
$identities = Get-ADGroupMember Outlooktermine_Mitarbeiter -Recursive | Get-ADUser -Properties * | Select-Object Mail
Nun müssen alle SPItems aus dem Kalender gelöscht werden, bei denen das Startdatum zwischen dem angegebenen Start und Enddatum liegt.
#*************************************
Get calendar and items
#*************************************
$cal = $web.lists.getbytitle(‘Name of your Sharepoint calendar here’)
$ctx.load($cal)
$ctx.ExecuteQuery()
$query = ”
$items = [Microsoft.SharePoint.Client.ListItemCollection]$cal.GetItems($query) $ctx.load($items) $ctx.ExecuteQuery()
Als nächstes geht man jeden Benutzer der AD-Gruppe durch und liest dessen Kalendereinträge aus.
Hat einer dieser Einträge die gesuchte Kategorie, wird dieser Eintrag in den SharePoint Kalender übernommen.
$categories = @{“Urlaub”, “Sonderurlaub”} foreach ($identity in $identities) { # Termine auslesen $appointments = Get-CalendarInformation -Identity $identity.Mail -weeksBefore $weeksBefore -weeksAfter $weeksAfter
foreach ($appointment in $appointments) {
# Überprüfen, ob der Termin der richtigen Kategorie zugeordnet ist
$hasCategory = $false
foreach ($appCategory in $appointment.Categories) {
if ($null -ne $categories\[$appCategory\]) {
$hasCategory = $true
}
break
}
if ($hasCategory) {
# Termine in SharePoint anlegen
Add-AppointmentIntoSPCalendar -Appointment $appointment
}
}
}
Kalenderinformationen auslesen
function Get-CalendarInformation { <# .SYNOPSIS A PowerShell function to list calendar information .DESCRIPTION Long description .EXAMPLE PS C:\> Get-CalendarInformation -Identity “xxx” -weeksBefore 5 -weeksAfter 2 Lists CalenderInformation from the Last 5 and the next 2 Weeks .NOTES #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] $Identity,
\[Parameter(Mandatory)\]
\[int\]
$weeksBefore,
\[Parameter()\]
\[int\]
$weeksAfter,
\[Parameter()\]
\[System.Management.Automation.CredentialAttribute()\]
\[pscredential\]
$Credential
)
begin {
$libPath = $PSScriptRoot + "\\libs\\Microsoft.Exchange.WebServices.dll"
Add-Type -Path $libPath
}
process {
$Service = \[Microsoft.Exchange.WebServices.Data.ExchangeService\]::new()
if ($PSBoundParameters.ContainsKey('Credential')) {
$Service.Credentials = \[System.Net.NetworkCredential\]::new($Credential.UserName, $Credential.Password)
}
else {
$Service.UseDefaultCredentials = $true
}
$Service.Url = "https://xx.xxx.at/EWS/Exchange.asmx"
$FolderId = New-Object Microsoft.Exchange.WebServices.Data.FolderId(\[Microsoft.Exchange.WebServices.Data.WellKnownFolderName\]::Calendar, $Identity)
try {
$Folder = \[Microsoft.Exchange.WebServices.Data.CalendarFolder\]::Bind($Service, $FolderId)
}
catch {
Write-Host $\_.Exception.Message "Name:" $FolderId.Mailbox.Address -ForegroundColor Red
return $null
}
$startDate = \[datetime\]::Now.AddDays(-7 \* $weeksBefore)
if ($null -eq $startDate) {
$startDate = \[datetime\]::Now
}
$endDate = \[datetime\]::Now.AddDays(7 \* $weeksAfter)
if ($null -eq $endDate) {
$endDate = \[datetime\]::Now
}
$View = \[Microsoft.Exchange.WebServices.Data.CalendarView\]::new($startDate, $endDate)
$View.PropertySet = \[Microsoft.Exchange.WebServices.Data.PropertySet\]::new(\[Microsoft.Exchange.WebServices.Data.AppointmentSchema\]::Subject,
\[Microsoft.Exchange.WebServices.Data.AppointmentSchema\]::Start,
\[Microsoft.Exchange.WebServices.Data.AppointmentSchema\]::End,
\[Microsoft.Exchange.WebServices.Data.AppointmentSchema\]::Organizer,
\[Microsoft.Exchange.WebServices.Data.AppointmentSchema\]::DateTimeCreated)
$BodyPropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet(\[Microsoft.Exchange.WebServices.Data.BasePropertySet\]::FirstClassProperties)
$BodyPropertySet.RequestedBodyType = \[Microsoft.Exchange.WebServices.Data.BodyType\]::Text
$FolderItems = $Service.FindItems($Folder.Id, $View)
\[Void\]$Service.LoadPropertiesForItems($FolderItems, $BodyPropertySet)
return $FolderItems
}
end {
}
}
Nun müssen nur noch alle gefundenen Termine mit der entsprechenden Kategorie in den SP Kalender hinzufügen werden.
Mithilfe dieses kleinen PS-Skripts ist es möglich die Kalendereinträge aus Outlook mittels EWS im SharePoint zu synchronisieren.
Was man sonst noch alles mit PowerShell anstellen kann, kannst du in unserem Blog nachlesen.
Habt ihr Fragen oder Interesse an dem kompletten Script – einfach bei uns melden, indem du auf den Button klickst!