Skip to content

Commit

Permalink
Merge pull request #2071 from microsoft/main
Browse files Browse the repository at this point in the history
Release 4-23-24
  • Loading branch information
dpaulson45 authored Apr 23, 2024
2 parents 7e91a6e + bb53898 commit 568069f
Show file tree
Hide file tree
Showing 19 changed files with 1,661 additions and 1,602 deletions.
264 changes: 264 additions & 0 deletions Calendar/CalLogHelpers/CalLogCSVFunctions.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

# ===================================================================================================
# Constants to support the script
# ===================================================================================================

$script:CalendarItemTypes = @{
'IPM.Schedule.Meeting.Request.AttendeeListReplication' = "AttendeeList"
'IPM.Schedule.Meeting.Canceled' = "Cancellation"
'IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}' = "ExceptionMsgClass"
'IPM.Schedule.Meeting.Notification.Forward' = "ForwardNotification"
'IPM.Appointment' = "IpmAppointment"
'IPM.Appointment.MP' = "IpmAppointment"
'IPM.Schedule.Meeting.Request' = "MeetingRequest"
'IPM.CalendarSharing.EventUpdate' = "SharingCFM"
'IPM.CalendarSharing.EventDelete' = "SharingDelete"
'IPM.Schedule.Meeting.Resp' = "RespAny"
'IPM.Schedule.Meeting.Resp.Neg' = "RespNeg"
'IPM.Schedule.Meeting.Resp.Tent' = "RespTent"
'IPM.Schedule.Meeting.Resp.Pos' = "RespPos"
}

# ===================================================================================================
# Functions to support the script
# ===================================================================================================

$ResponseTypeOptions = @{
'0' = "None"
"1" = "Organizer"
'2' = "Tentative"
'3' = "Accept"
'4' = "Decline"
'5' = "Not Responded"
}
<#
.SYNOPSIS
Looks to see if there is a Mapping of ExternalMasterID to FolderName
#>
function MapSharedFolder {
param(
$ExternalMasterID
)
if ($ExternalMasterID -eq "NotFound") {
return "Not Shared"
} else {
$SharedFolders[$ExternalMasterID]
}
}

<#
.SYNOPSIS
Replaces a value of NotFound with a blank string.
#>
function ReplaceNotFound {
param (
$Value
)
if ($Value -eq "NotFound") {
return ""
} else {
return $Value
}
}

<#
.SYNOPSIS
Creates a Mapping of ExternalMasterID to FolderName
#>
function CreateExternalMasterIDMap {
# This function will create a Map of the log folder to ExternalMasterID
$script:SharedFolders = @{}
Write-Verbose "Starting CreateExternalMasterIDMap"

foreach ($ExternalID in $script:GCDO.ExternalSharingMasterId | Select-Object -Unique) {
if ($ExternalID -eq "NotFound") {
continue
}

$AllFolderNames = @($script:GCDO | Where-Object { $_.ExternalSharingMasterId -eq $ExternalID } | Select-Object -ExpandProperty OriginalParentDisplayName | Select-Object -Unique)

if ($AllFolderNames.count -gt 1) {
# We have 2+ FolderNames, Need to find the best one. #remove Calendar
$AllFolderNames = $AllFolderNames | Where-Object { $_ -notmatch 'Calendar' } # This will not work for non-english
}

if ($AllFolderNames.Count -eq 0) {
$SharedFolders[$ExternalID] = "UnknownSharedCalendarCopy"
Write-Host -ForegroundColor red "Found Zero to map to."
}

if ($AllFolderNames.Count -eq 1) {
$SharedFolders[$ExternalID] = $AllFolderNames
Write-Verbose "Found map: [$AllFolderNames] is for $ExternalID"
} else {
# we still have multiple possible Folder Names, need to chose one or combine
Write-Host -ForegroundColor Red "Unable to Get Exact Folder for $ExternalID"
Write-Host -ForegroundColor Red "Found $($AllFolderNames.count) possible folders"

if ($AllFolderNames.Count -eq 2) {
$SharedFolders[$ExternalID] = $AllFolderNames[0] + $AllFolderNames[1]
} else {
$SharedFolders[$ExternalID] = "UnknownSharedCalendarCopy"
}
}
}
Write-Verbose "Created the following Mapping :"
Write-Verbose $SharedFolders
}

<#
.SYNOPSIS
Convert a csv value to multiLine.
#>
function MultiLineFormat {
param(
$PassedString
)
$PassedString = $PassedString -replace "},", "},`n"
return $PassedString.Trim()
}

# ===================================================================================================
# Build CSV to output
# ===================================================================================================

<#
.SYNOPSIS
Builds the CSV output from the Calendar Diagnostic Objects
#>
function BuildCSV {
param(
$Identity
)

Write-Host "Starting to Process Calendar Logs..."
$GCDOResults = @()
$IsFromSharedCalendar = @()
$IsIgnorable = @()
$script:MailboxList = @{}
Write-Host "Creating Map of Mailboxes to CNs..."
CreateExternalMasterIDMap

$ThisMeetingID = $script:GCDO.CleanGlobalObjectId | Select-Object -Unique
$ShortMeetingID = $ThisMeetingID.Substring($ThisMeetingID.length - 6)

ConvertCNtoSMTP

Write-Host "Making Calendar Logs more readable..."
$Index = 0
foreach ($CalLog in $script:GCDO) {
$CalLogACP = $CalLog.AppointmentCounterProposal.ToString()
$Index++
$ItemType = $CalendarItemTypes.($CalLog.ItemClass)
$ShortClientName = @()
$script:KeyInput = $CalLog.ClientInfoString
$ResponseType = $ResponseTypeOptions.($CalLog.ResponseType.ToString())

$ShortClientName = CreateShortClientName($CalLog.ClientInfoString)

$IsIgnorable = SetIsIgnorable($CalLog)

# CleanNotFounds
$PropsToClean = "FreeBusyStatus", "ClientIntent", "AppointmentLastSequenceNumber", "RecurrencePattern", "AppointmentAuxiliaryFlags", "EventEmailReminderTimer", "IsSeriesCancelled", "AppointmentCounterProposal", "MeetingRequestType", "SendMeetingMessagesDiagnostics"
foreach ($Prop in $PropsToClean) {
# Exception objects, etc. don't have these properties.
if ($null -ne $CalLog.$Prop) {
$CalLog.$Prop = ReplaceNotFound($CalLog.$Prop)
}
}

if ($CalLogACP -eq "NotFound") {
$CalLogACP = ''
}

$IsFromSharedCalendar = ($null -ne $CalLog.externalSharingMasterId -and $CalLog.externalSharingMasterId -ne "NotFound")

# Record one row
$GCDOResults += [PSCustomObject]@{
'LogRow' = $Index
'LastModifiedTime' = $CalLog.OriginalLastModifiedTime
'IsIgnorable' = $IsIgnorable
'SubjectProperty' = $CalLog.SubjectProperty
'Client' = $ShortClientName
'ClientInfoString' = $CalLog.ClientInfoString
'TriggerAction' = $CalLog.CalendarLogTriggerAction
'ItemClass' = $CalLog.ItemClass
'ItemVersion' = $CalLog.ItemVersion
'AppointmentSequenceNumber' = $CalLog.AppointmentSequenceNumber
'AppointmentLastSequenceNumber' = $CalLog.AppointmentLastSequenceNumber # Need to find out how we can combine these two...
'Organizer' = $CalLog.From.FriendlyDisplayName
'From' = GetBestFromAddress($CalLog.From)
'FreeBusyStatus' = $CalLog.FreeBusyStatus
'ResponsibleUser' = GetSMTPAddress($CalLog.ResponsibleUserName)
'Sender' = GetSMTPAddress($CalLog.SenderEmailAddress)
'LogFolder' = $CalLog.ParentDisplayName
'OriginalLogFolder' = $CalLog.OriginalParentDisplayName
'SharedFolderName' = MapSharedFolder($CalLog.ExternalSharingMasterId)
'IsFromSharedCalendar' = $IsFromSharedCalendar
'ExternalSharingMasterId' = $CalLog.ExternalSharingMasterId
'ReceivedBy' = $CalLog.ReceivedBy.SmtpEmailAddress
'ReceivedRepresenting' = $CalLog.ReceivedRepresenting.SmtpEmailAddress
'MeetingRequestType' = $CalLog.MeetingRequestType
'StartTime' = $CalLog.StartTime
'EndTime' = $CalLog.EndTime
'TimeZone' = $CalLog.TimeZone
'Location' = $CalLog.Location
'ItemType' = $ItemType
'CalendarItemType' = $CalLog.CalendarItemType
'IsException' = $CalLog.IsException
'RecurrencePattern' = $CalLog.RecurrencePattern
'AppointmentAuxiliaryFlags' = $CalLog.AppointmentAuxiliaryFlags.ToString()
'DisplayAttendeesAll' = $CalLog.DisplayAttendeesAll
'AttendeeCount' = ($CalLog.DisplayAttendeesAll -split ';').Count
'AppointmentState' = $CalLog.AppointmentState.ToString()
'ResponseType' = $ResponseType
'AppointmentCounterProposal' = $CalLogACP
'SentRepresentingEmailAddress' = $CalLog.SentRepresentingEmailAddress
'SentRepresentingSMTPAddress' = GetSMTPAddress($CalLog.SentRepresentingEmailAddress)
'SentRepresentingDisplayName' = $CalLog.SentRepresentingDisplayName
'ResponsibleUserSMTPAddress' = GetSMTPAddress($CalLog.ResponsibleUserName)
'ResponsibleUserName' = $CalLog.ResponsibleUserName
'SenderEmailAddress' = $CalLog.SenderEmailAddress
'SenderSMTPAddress' = GetSMTPAddress($CalLog.SenderEmailAddress)
'ClientIntent' = $CalLog.ClientIntent.ToString()
'NormalizedSubject' = $CalLog.NormalizedSubject
'AppointmentRecurring' = $CalLog.AppointmentRecurring
'HasAttachment' = $CalLog.HasAttachment
'IsCancelled' = $CalLog.IsCancelled
'IsAllDayEvent' = $CalLog.IsAllDayEvent
'IsSeriesCancelled' = $CalLog.IsSeriesCancelled
'CreationTime' = $CalLog.CreationTime
'OriginalStartDate' = $CalLog.OriginalStartDate
'SendMeetingMessagesDiagnostics' = $CalLog.SendMeetingMessagesDiagnostics
'EventEmailReminderTimer' = $CalLog.EventEmailReminderTimer
'AttendeeListDetails' = MultiLineFormat($CalLog.AttendeeListDetails)
'AttendeeCollection' = MultiLineFormat($CalLog.AttendeeCollection)
'CalendarLogRequestId' = $CalLog.CalendarLogRequestId.ToString()
'AppointmentRecurrenceBlob' = $CalLog.AppointmentRecurrenceBlob
'GlobalObjectId' = $CalLog.GlobalObjectId
'CleanGlobalObjectId' = $CalLog.CleanGlobalObjectId
}
}
$script:Results = $GCDOResults

# Automation won't have access to this file - will add code in next version to save contents to a variable
#$Filename = "$($Results[0].ReceivedBy)_$ShortMeetingID.csv";

if ($Identity -like "*@*") {
$ShortName = $Identity.Split('@')[0]
}
$ShortName = $ShortName.Substring(0, [System.Math]::Min(20, $ShortName.Length))
$Filename = "$($ShortName)_$ShortMeetingID.csv"
$FilenameRaw = "$($ShortName)_RAW_$($ShortMeetingID).csv"

Write-Host -ForegroundColor Cyan -NoNewline "Enhanced Calendar Logs for [$Identity] have been saved to : "
Write-Host -ForegroundColor Yellow "$Filename"

Write-Host -ForegroundColor Cyan -NoNewline "Raw Calendar Logs for [$Identity] have been saved to : "
Write-Host -ForegroundColor Yellow "$FilenameRaw"

$GCDOResults | Export-Csv -Path $Filename -NoTypeInformation -Encoding UTF8
$script:GCDO | Export-Csv -Path $FilenameRaw -NoTypeInformation -Encoding UTF8
}
106 changes: 106 additions & 0 deletions Calendar/CalLogHelpers/CalLogInfoFunctions.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

<#
.SYNOPSIS
Checks if a set of Calendar Logs is from the Organizer.
#>
function SetIsOrganizer {
param(
$CalLogs
)
[bool] $IsOrganizer = $false

foreach ($CalLog in $CalLogs) {
if ($CalendarItemTypes.($CalLog.ItemClass) -eq "IpmAppointment" -and
$CalLog.ExternalSharingMasterId -eq "NotFound" -and
($CalLog.ResponseType -eq "1" -or $CalLogs.ResponseType -eq "Organizer")) {
$IsOrganizer = $true
Write-Verbose "IsOrganizer: [$IsOrganizer]"
return $IsOrganizer
}
}
Write-Verbose "IsOrganizer: [$IsOrganizer]"
return $IsOrganizer
}

<#
.SYNOPSIS
Checks if a set of Calendar Logs is from a Resource Mailbox.
#>
function SetIsRoom {
param(
$CalLogs
)
[bool] $IsRoom = $false
# Simple logic is if RBA is running on the MB, it is a Room MB, otherwise it is not.
foreach ($CalLog in $CalLogs) {
if ($CalendarItemTypes.($CalLog.ItemClass) -eq "IpmAppointment" -and
$CalLog.ExternalSharingMasterId -eq "NotFound" -and
$CalLog.Client -eq "ResourceBookingAssistant" ) {
$IsRoom = $true
return $IsRoom
}
}
return $IsRoom
}

<#
.SYNOPSIS
Checks if a set of Calendar Logs is from a Recurring Meeting.
#>
function SetIsRecurring {
param(
$CalLogs
)
Write-Host -ForegroundColor Yellow "Looking for signs of a recurring meeting."
[bool] $IsRecurring = $false
# See if this is a recurring meeting
foreach ($CalLog in $CalLogs) {
if ($CalendarItemTypes.($CalLog.ItemClass) -eq "IpmAppointment" -and
$CalLog.ExternalSharingMasterId -eq "NotFound" -and
($CalLog.CalendarItemType.ToString() -eq "RecurringMaster" -or
$CalLog.IsException -eq $true)) {
$IsRecurring = $true
Write-Verbose "Found recurring meeting."
return $IsRecurring
}
}
Write-Verbose "Did not find signs of recurring meeting."
return $IsRecurring
}

<#
.SYNOPSIS
Checks to see if the Calendar Log is Ignorable.
Many updates are not interesting in the Calendar Log, marking these as ignorable. 99% of the time this is correct.
#>
function SetIsIgnorable {
param(
$CalLog
)

if ($CalLog.ItemClass -eq "(Occurrence Deleted)") {
return "Ignorable"
} elseif ($ShortClientName -like "TBA*SharingSyncAssistant" -or
$ShortClientName -eq "CalendarReplication" -or
$CalendarItemTypes.($CalLog.ItemClass) -eq "SharingCFM" -or
$CalendarItemTypes.($CalLog.ItemClass) -eq "SharingDelete") {
return "Sharing"
} elseif ($ShortClientName -like "EBA*" -or
$ShortClientName -like "TBA*" -or
$ShortClientName -eq "LocationProcessor" -or
$ShortClientName -eq "GriffinRestClient" -or
$ShortClientName -eq "RestConnector" -or
$ShortClientName -eq "ELC-B2" -or
$ShortClientName -eq "TimeService" ) {
return "Ignorable"
} elseif ($CalLog.ItemClass -eq "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}" ) {
return "Exception"
} elseif (($CalendarItemTypes.($CalLog.ItemClass) -like "*Resp*" -and $CalLog.CalendarLogTriggerAction -ne "Create" ) -or
$CalendarItemTypes.($CalLog.ItemClass) -eq "AttendeeList" ) {
return "Cleanup"
} else {
return "False"
}
}
Loading

0 comments on commit 568069f

Please sign in to comment.