Skip to content

Instantly share code, notes, and snippets.

@tommck
Last active August 31, 2023 15:29
Show Gist options
  • Save tommck/7bf09e95da369ecd488ed4f0f7c15a88 to your computer and use it in GitHub Desktop.
Save tommck/7bf09e95da369ecd488ed4f0f7c15a88 to your computer and use it in GitHub Desktop.
$emailMessageSubject = "OutlookSync Meetings";
$syncRetryIntervalInSeconds = 10;
Function Send-MeetingsEmail() {
param (
$StartDate = (Get-Date -Hour 0 -Minute 00 -Second 00),
$EndDate = (Get-Date -Hour 0 -Minute 00 -Second 00).AddDays(14),
[Parameter(Mandatory = $true)]
[string]
$ExcludedCategory,
[switch]
$SkipFocusTime,
[Parameter(Mandatory = $true)]
[string]
$Email,
[switch]
$IncludeBody,
[switch]
$DryRun
)
$ErrorActionPreference = 'Stop'
Add-Type -assembly "Microsoft.Office.Interop.Outlook" | Out-Null
$outlook = New-Object -ComObject outlook.application
Send-MeetingsEmailInternal `
-outlook $outlook `
-StartDate $StartDate `
-EndDate $EndDate `
-ExcludedCategory $ExcludedCategory `
-SkipFocusTime $SkipFocusTime `
-Email $Email `
-IncludeBody $IncludeBody `
-DryRun $DryRun
}
Function Import-Meetings() {
param(
[string]
[Parameter(Mandatory = $true)]
$FileName,
[string]
[Parameter(Mandatory = $true)]
$Recipient,
[int]
$ReminderMinutes = 5,
[switch]
$AddReminders,
[string]
[Parameter(Mandatory = $true)]
$Category,
# Make meetings private
[switch]
$Private,
# Ability to delete all meetings in date range with the same category
[switch]
$DeleteExistingInCategory,
[switch]
$DryRun
)
$ErrorActionPreference = 'Stop'
Add-Type -assembly "Microsoft.Office.Interop.Outlook" | Out-Null
$ol = New-Object -ComObject Outlook.Application
Import-MeetingsInternal `
-Category $Category `
-outlook $ol `
-FileName $FileName `
-Recipient $Recipient `
-ReminderMinutes $ReminderMinutes `
-AddReminders $AddReminders `
-Private $Private `
-DeleteExistingInCategory $DeleteExistingInCategory `
-DryRun $DryRun
}
Function Get-Meetings() {
param (
[Parameter(Mandatory = $true)]
$Outlook, # TODO: Type?
[datetime]
$StartDate,
[datetime]
$EndDate,
[string]
$ExcludedCategory,
[boolean]
$SkipFocusTime
)
$ErrorActionPreference = 'Stop'
$namespace = $Outlook.GetNameSpace("MAPI")
$olFolders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -as [type]
Write-Host "Gathering From $($StartDate.ToShortDateString()) To $($EndDate.ToShortDateString())..."
$folder = $namespace.getDefaultFolder($olFolders::olFolderCalendar)
$filter = "[Start] >= '" `
+ $StartDate.ToString("g") `
+ "' AND [End] <= '"`
+ $EndDate.ToString("g") + "'";
if ($SkipFocusTime) {
$filter += " AND [Subject] <> 'Focus time'"
}
$calItems = $folder.Items;
$calItems.IncludeRecurrences = $true;
$calItems.Sort("[Start]")
$filteredRecurring = $calItems.restrict($filter)
$recurringToUse = $filteredRecurring
if ($ExcludedCategory) {
$recurringToUse = $recurringToUse `
| Where-Object { -not $_.Categories.Contains($ExcludedCategory) }
}
$recurringToUse
}
Function Send-MeetingsEmailInternal() {
param (
$outlook,
[Parameter(Mandatory = $true)]
[datetime]
$StartDate,
[Parameter(Mandatory = $true)]
[datetime]
$EndDate,
[Parameter(Mandatory = $true)]
[string]
$ExcludedCategory,
[boolean]
$SkipFocusTime,
[Parameter(Mandatory = $true)]
[string]
$Email,
[boolean]
$IncludeBody
)
$array = Get-Meetings $outlook $StartDate $EndDate $ExcludedCategory $SkipFocusTime
$items = @();
foreach ($appt in $array) {
$items += @{
Subject = $appt.Subject
Start = $appt.Start.ToString()
End = $appt.End.ToString()
IsRecurring = $false
ResponseStatus = $appt.ResponseStatus
Body = if ($IncludeBody) { $appt.Body } else { $null }
}
}
$json = @{
StartDate = $StartDate.ToString()
EndDate = $EndDate.ToString()
Meetings = $items
} | ConvertTo-Json
$outFile = New-Item -Path "meetings.json" -ItemType File -Force
$json | Out-File $outFile;
$newMail = $outlook.CreateItem('olMailItem');
$newMail.To = $Email;
$newMail.Subject = $emailMessageSubject;
$newMail.Body = "Save the attachment to sync things";
$attachmentTypes = "Microsoft.Office.Interop.Outlook.OlAttachmentType" -as [type]
$newMail.Attachments.Add($outFile.FullName, $attachmentTypes.olByValue)
$newMail.Send();
}
Function Import-MeetingsInternal() {
param(
$outlook,
[string]
[Parameter(Mandatory = $true)]
$FileName,
[string]
[Parameter(Mandatory = $true)]
$Recipient,
[int]
$ReminderMinutes = 5,
[boolean]
$AddReminders,
[string]
[Parameter(Mandatory = $true)]
$Category,
# Make meetings private
[boolean]
$Private,
# Ability to delete all meetings in date range with the same category
[boolean]
$DeleteExistingInCategory,
[boolean]
$DryRun
)
$contents = (Get-Content $FileName | Out-String | ConvertFrom-Json)
$start = [DateTime]::Parse($contents.StartDate)
$end = [DateTime]::Parse($contents.EndDate).AddDays(1)
# get meetings for date range
$existingMeetings = Get-Meetings -Outlook $outlook -StartDate $start -EndDate $end
if ($DeleteExistingInCategory) {
$catMeetings = $existingMeetings | `
Where-Object { $_.Categories -contains $Category };
# TODO: Add "Are You Sure?"
if ($catMeetings) {
Write-Host "CAT MEETINGS:"
foreach ($m in $catMeetings) {
Write-Host -ForegroundColor Yellow "Deleting Meeting '$($m.Subject)'"
if (!$DryRun) {
try {
$m.Delete();
}
catch {
Write-Error "Error Deleting Item:", $_
}
}
}
}
}
Write-Host "Creating New Appointments"
# TODO: filter recurring - need to use "Pattern" stuff for those
$nonRecurring = $contents.Meetings | Where-Object { $_.IsRecurring -eq $false }
foreach ($appt in $nonRecurring) {
# FIRST - Check for the existing meeting by Subject + Date
$existing = $existingMeetings | `
Where-Object { $_.Subject -eq $appt.Subject -and [DateTime]::Parse($_.Start).Date -eq [DateTime]::Parse($appt.Start.ToString()).Date } | `
Select-Object -First 1
if (!$existing) {
$sub = $appt.Subject
Write-Host "Importing Meeting '$sub'", $(If ($Private) { "as Private" } else { "" })
if (!$DryRun) {
$meeting = $outlook.CreateItem('olAppointmentItem')
$meeting.Subject = $sub
$meeting.Categories = $Category
$meeting.Body = if ($appt.Body) { $appt.Body } else { 'Placeholder' }
$meeting.Location = ''
$meeting.ReminderSet = $AddReminders
$meeting.Importance = 1
$meeting.MeetingStatus = [Microsoft.Office.Interop.Outlook.OlMeetingStatus]::olMeeting
$meeting.Recipients.Add($Recipient)
$meeting.ReminderMinutesBeforeStart = $ReminderMinutes
$meeting.Start = [DateTime]::Parse($appt.Start.ToString())
$meeting.End = [DateTime]::Parse($appt.End)
$meeting.ResponseStatus = $appt.ResponseStatus -as [Microsoft.Office.Interop.outlook.OlResponseStatus]
if ($Private) {
$meeting.Sensitivity = [Microsoft.Office.Interop.Outlook.OlSensitivity]::olPrivate;
}
$meeting.Send()
}
}
}
Write-Host "Finished Importing"
}
function Sync-OutlookMeetings() {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]
$ExternalCategory,
[switch]
$SkipFocusTime,
[Parameter(Mandatory = $true)]
[string]
$ExternalEmail,
[string]
[Parameter(Mandatory = $true)]
$InternalEmail,
[switch]
$IncludeBody,
[int]
$ReminderMinutes = 5,
[switch]
$AddReminders,
# Make meetings private
[switch]
$Private,
# Ability to delete all meetings in date range with the same category
[switch]
$DeleteExistingInCategory,
[switch]
$DryRun
)
$ErrorActionPreference = 'Stop'
# $CommandName = $PSCmdlet.MyInvocation.InvocationName;
# $ParameterList = (Get-Command -Name $CommandName).Parameters;
# Write-Host -ForegroundColor Yellow "CHECKING PARAMS"
# foreach ($Parameter in $ParameterList) {
# Get-Variable -Name $Parameter.Values.Name -ErrorAction SilentlyContinue;
# }
$StartDate = (Get-Date -Hour 0 -Minute 00 -Second 00)
$EndDate = $StartDate.AddDays(14)
# get outlook instance
Add-Type -assembly "Microsoft.Office.Interop.Outlook" | Out-Null
$outlook = New-Object -ComObject outlook.application
# send outlook meetings to external recipient
Send-MeetingsEmailInternal `
-outlook $outlook `
-StartDate $StartDate `
-EndDate $EndDate `
-ExcludedCategory $ExternalCategory `
-SkipFocusTime $SkipFocusTime `
-Email $ExternalEmail `
-IncludeBody $IncludeBody
# watch the outlook inbox for a return email from the external system
$email = Get-SyncEmailMessage($outlook)
# save attachment as temp file (it's 1-based)
$attachment = $email.Attachments[1]
# $email.Attachments | Where-Object {$_.FileName -eq $attachmentFileName}
Write-Host "Processing attachment $($attachment.FileName)"
$tempFile = Join-Path "$ENV:Temp" "meetingsToSync.json"
$attachment.SaveAsFile($tempFile);
# import those meetings.
Import-MeetingsInternal `
-Category $ExternalCategory `
-outlook $outlook `
-FileName $tempFile `
-Recipient $InternalEmail `
-ReminderMinutes $ReminderMinutes `
-AddReminders $AddReminders `
-Private $Private `
-DeleteExistingInCategory $DeleteExistingInCategory `
-DryRun $DryRun
# Cleanup
$email.UnRead = $false
$email.Delete()
Remove-item $tempFile
}
Function Get-SyncEmailMessage() {
param(
$outlook
)
# TODO: verify sender, etc.
$namespace = $outlook.GetNameSpace("MAPI")
$inbox = $namespace.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox)
$email = $null
do {
Write-Host -ForegroundColor Cyan "Checking Inbox for new message"
$email = $inbox.Items | Where-Object { $_.Subject -like $emailMessageSubject } | Select-Object -First 1
if (-not $email) {
Write-Host -ForegroundColor Cyan "Sleeping for $syncRetryIntervalInSeconds seconds..."
Start-Sleep -Seconds $syncRetryIntervalInSeconds
}
# TODO: Bail out after X retries.
}
until ($email)
$email
}
Export-ModuleMember -Function Send-MeetingsEmail, Import-Meetings, Sync-OutlookMeetings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment