Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 4-23-24 #2071

Merged
merged 14 commits into from
Apr 23, 2024
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
Loading