From 0999c4055e0a957e0670f323f3b1b0301778cc0f Mon Sep 17 00:00:00 2001 From: Shane Ferrell Date: Thu, 25 Jan 2024 11:19:51 -0800 Subject: [PATCH 1/5] Add Exception Option, update TimeLine --- .../Get-CalendarDiagnosticObjectsSummary.ps1 | 67 +++++++++++++++---- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 index 9ebbad1f5b..4b9dc54a1e 100644 --- a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 +++ b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 @@ -15,7 +15,10 @@ # The MeetingID of the meeting to query. # # .PARAMETER TrackingLogs -# Include specific tracking logs in the output. +# Include specific tracking logs in the output. Only useable with the MeetingID parameter. +# +# .PARAMETER Exceptions +# Include Exception objects in the output. Only useable with the MeetingID parameter. # # .EXAMPLE # Get-CalendarDiagnosticObjectsSummary.ps1 -Identity someuser@microsoft.com -MeetingID 040000008200E00074C5B7101A82E008000000008063B5677577D9010000000000000000100000002FCDF04279AF6940A5BFB94F9B9F73CD @@ -33,6 +36,7 @@ param ( [Parameter(Mandatory, ParameterSetName = 'MeetingID', Position = 1)] [string]$MeetingID, [switch]$TrackingLogs, + [switch]$Exceptions, [Parameter(Mandatory, ParameterSetName = 'Subject', Position = 1)] [string]$Subject @@ -633,6 +637,8 @@ function SetIsIgnorable { $CalLog.CalendarLogTriggerAction -ne "Create" ) -or $CalendarItemTypes.($CalLog.ItemClass) -eq "AttendeeList") { return "Resp" + } elseif ($CalLog.ItemClass -eq "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}") { + return "Exception" } elseif ($ShortClientName -like "EBA*" -or $ShortClientName -like "TBA*" -or $ShortClientName -eq "LocationProcessor" -or @@ -640,7 +646,7 @@ function SetIsIgnorable { $ShortClientName -eq "RestConnector" -or $ShortClientName -eq "ELC-B2" -or $ShortClientName -eq "TimeService" ) { - return "True" + return "Ignorable" } else { return "False" } @@ -959,7 +965,8 @@ function BuildTimeline { [Array]$Header = ("Subject: " + ($script:GCDO[0].NormalizedSubject) + " | MeetingID: "+ ($script:GCDO[0].CleanGlobalObjectId)) MeetingSummary -Time "Calendar Log Timeline for Meeting with" -MeetingChanges $Header MeetingSummary -Time "Initial Message Values" -Entry $script:GCDO[0] -LongVersion - $MeetingTimeLine = $Results | Where-Object { $_.IsIgnorable -eq "False" } + # Ignorable and items from Shared Calendars are not included in the TimeLine. + $MeetingTimeLine = $Results | Where-Object { $_.IsIgnorable -eq "False" -and $_.IsFromSharedCalendar -eq $False } foreach ($CalLog in $MeetingTimeLine) { [bool] $MeetingSummaryNeeded = $False @@ -1062,7 +1069,7 @@ function BuildTimeline { } if ($CalLog.MeetingRequestType -ne $PreviousCalLog.MeetingRequestType) { - [Array]$TimeLineText = "The Meeting Request Type changed from [$($PreviousCalLog.MeetingRequestType)] to: [$($CalLog.MeetingRequestType)]" + [Array]$TimeLineText = "The Meeting Request Type changed from [$($PreviousCalLog.MeetingRequestType.Value)] to: [$($CalLog.MeetingRequestType.Value)]" MeetingSummary -Time " " -MeetingChanges $TimeLineText } @@ -1109,7 +1116,7 @@ function BuildTimeline { Create { if ($CalLog.IsOrganizer) { if ($CalLog.IsException -eq $True) { - $Output1 = "A new Exception $($CalLog.MeetingRequestType.Value) Meeting Request was created with $($CalLog.Client)" + $Output1 = "A new Exception $($CalLog.MeetingRequestType.Value) Meeting Request was created with $($CalLog.Client) for [$($CalLog.StartTime)]." } else { $Output1 = "A new $($CalLog.MeetingRequestType.Value) Meeting Request was created with $($CalLog.Client)" } @@ -1127,8 +1134,19 @@ function BuildTimeline { [array] $Output = "The user Forwarded a Meeting Request with $($CalLog.Client)." } else { if ($CalLog.Client -eq "Transport") { - [array] $Output = "Transport delivered a new Meeting Request from $($CalLog.SentRepresentingDisplayName)." - [bool] $MeetingSummaryNeeded = $True + if ($CalLog.IsException -eq $True) { + [array] $Output = "[$($CalLog.SentRepresentingDisplayName)] send a new Meeting Request via Transport for an exception starting on [$($CalLog.StartTime)]." + [bool] $MeetingSummaryNeeded = $True + } else { + [array] $Output = "[$($CalLog.SentRepresentingDisplayName)] send a new Meeting Request via Transport." + [bool] $MeetingSummaryNeeded = $True + } + } elseif ($CalLog.Client -eq "CalendarRepairAssistant") { + if ($CalLog.IsException -eq $True) { + [array] $Output = "CalendarRepairAssistant Created a new Meeting Request to repair an inconsistency with an exception starting on [$($CalLog.StartTime)]." + } else { + [array] $Output = "CalendarRepairAssistant Created a new Meeting Request to repair an inconsistency." + } } else { if ($CalLog.IsException -eq $True) { [array] $Output = "[$($CalLog.ResponsibleUser)] Created a new Meeting Request with $($CalLog.Client) for an exception starting on [$($CalLog.StartTime)]." @@ -1140,7 +1158,7 @@ function BuildTimeline { } } Update { - [array] $Output = "$($CalLog.ResponsibleUser) updated on the $($CalLog.MeetingRequestType) Meeting Request with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] Updated on the $($CalLog.MeetingRequestType.Value) Meeting Request with $($CalLog.Client)." } MoveToDeletedItems { if ($CalLog.ResponsibleUser -eq "Calendar Assistant") { @@ -1150,7 +1168,7 @@ function BuildTimeline { } } default { - [array] $Output = "[$($CalLog.ResponsibleUser)] Deleted the $($CalLog.MeetingRequestType) Meeting Request with $($CalLog.Client)." + [array] $Output = "[$($CalLog.ResponsibleUser)] Deleted the $($CalLog.MeetingRequestType.Value) Meeting Request with $($CalLog.Client)." } } } @@ -1217,10 +1235,10 @@ function BuildTimeline { } else { switch ($CalLog.Client) { Transport { - [array] $Output = "$($CalLog.Client) added a new Tentative Meeting from $($CalLog.SentRepresentingDisplayName) to the Calendar." + [array] $Output = "Transport added a new Tentative Meeting from [$($CalLog.SentRepresentingDisplayName)] to the Calendar." } ResourceBookingAssistant { - [array] $Output = "$($CalLog.Client) added a new Tentative Meeting from $($CalLog.SentRepresentingDisplayName) to the Calendar." + [array] $Output = "ResourceBookingAssistant added a new Tentative Meeting from [$($CalLog.SentRepresentingDisplayName)] to the Calendar." } default { [array] $Output = "[$($CalLog.ResponsibleUser)] created the Meeting with $($CalLog.Client)." @@ -1231,7 +1249,7 @@ function BuildTimeline { Update { switch ($CalLog.Client) { Transport { - [array] $Output = "Transport $($CalLog.TriggerAction)d the meeting." + [array] $Output = "[$($CalLog.SentRepresentingDisplayName)] made changed which caused Transport to $($CalLog.TriggerAction) the meeting." } LocationProcessor { [array] $Output = "" @@ -1239,6 +1257,9 @@ function BuildTimeline { ResourceBookingAssistant { [array] $Output = "ResourceBookingAssistant $($CalLog.TriggerAction)d the Meeting." } + CalendarRepairAssistant { + [array] $Output = "CalendarRepairAssistant $($CalLog.TriggerAction)d the Meeting to repair an inconsistency." + } default { if ($CalLog.ResponsibleUser -eq "Calendar Assistant") { [array] $Output = "The Exchange System $($CalLog.TriggerAction)d the meeting via the Calendar Assistant." @@ -1264,7 +1285,7 @@ function BuildTimeline { SoftDelete { switch ($CalLog.Client) { Transport { - [array] $Output = "Transport $($CalLog.TriggerAction)d the Meeting." + [array] $Output = "[$($CalLog.SentRepresentingDisplayName)] made changed which caused Transport to $($CalLog.TriggerAction) the Meeting." } LocationProcessor { [array] $Output = "" @@ -1302,7 +1323,11 @@ function BuildTimeline { Cancellation { switch ($CalLog.Client) { Transport { - [array] $Output = "Transport $($CalLog.TriggerAction)d the Meeting Cancellation from $($CalLog.SentRepresentingDisplayName)." + if ($CalLog.IsException -eq $True) { + [array] $Output = "[$($CalLog.SentRepresentingDisplayName)] $($CalLog.TriggerAction)d a Meeting Cancellation delivered by Transport for the exception starting on [$($CalLog.StartTime)]" + } else { + [array] $Output = "[$($CalLog.SentRepresentingDisplayName)] $($CalLog.TriggerAction)d a Meeting Cancellation delivered by Transport." + } } default { if ($CalLog.IsException -eq $True) { @@ -1520,6 +1545,20 @@ if (-not ([string]::IsNullOrEmpty($Subject)) ) { if ($script:GCDO.count -gt 0) { Write-Host "Found $($script:GCDO.count) CalLogs with MeetingID [$MeetingID]." + if ($Exceptions.IsPresent) { + Write-Host -ForegroundColor Cyan "Looking for Exception Logs..." + #collect Exception Logs + $ExceptionLogs = @() + + $ExceptionLogs = $script:GCDO | Where-Object { $_.ItemClass -like 'IPM.Appointment.*' } | Sort-Object ItemVersion | ForEach-Object { + Write-Host -ForegroundColor Cyan "Getting Exception Logs for [$($_.ItemId.ObjectId)]" + Get-CalendarDiagnosticObjects -Identity $ID -ItemIds $_.ItemId.ObjectId -ShouldFetchRecurrenceExceptions $true -CustomPropertyNames $CustomPropertyNameList + } + } + + $TempLogs = $script:GCDO + ($ExceptionLogs | Where-Object { $_.ItemClass -notlike "IPM.Appointment*" }) | Select-Object *, @{n='OrgTime'; e= { [DateTime]::Parse($_.OriginalLastModifiedTime.ToString()) } } | Sort-Object OrgTime + $script:GCDO = $TempLogs + BuildCSV -Identity $ID BuildTimeline -Identity $ID } else { From d78916886b9a5310eb3473c75616d7f86321cb5c Mon Sep 17 00:00:00 2001 From: Shane Ferrell Date: Thu, 25 Jan 2024 14:53:44 -0800 Subject: [PATCH 2/5] Improve Exception logic --- .../Get-CalendarDiagnosticObjectsSummary.ps1 | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 index 4b9dc54a1e..8217ad378d 100644 --- a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 +++ b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 @@ -1423,7 +1423,7 @@ function CheckIdentities { if (Get-Command -Name Get-Mailbox -ErrorAction SilentlyContinue) { Write-Host "Validated connection to Exchange Online." } else { - Write-Error "Get-Mailbox cmdlet not found. Please validate that you are running this script from an Exchange Management Shell and try again." + Write-Error "Get-Mailbox cmdlet not found. Please validate that you are running this script from an Exchange Management Shell and try again." Write-Host "Look at Import-Module ExchangeOnlineManagement and Connect-ExchangeOnline." exit } @@ -1537,8 +1537,6 @@ if (-not ([string]::IsNullOrEmpty($Subject)) ) { } elseif (-not ([string]::IsNullOrEmpty($MeetingID))) { foreach ($ID in $ValidatedIdentities) { - #$script:GCDO = $script:InitialCDOs; # use the CalLogs that we already have, since there is only one. - # $script:InitialCDOs = @(); # clear the Initial CDOs. Write-DashLineBoxColor "Looking for CalLogs from [$ID] with passed in MeetingID." Write-Verbose "Running: Get-CalendarDiagnosticObjects -Identity [$ID] -MeetingID [$MeetingID] -CustomPropertyNames $CustomPropertyNameList -WarningAction Ignore -MaxResults $LogLimit -ResultSize $LogLimit -ShouldBindToItem $true;" $script:GCDO = GetCalendarDiagnosticObjects -Identity $ID -MeetingID $MeetingID @@ -1549,15 +1547,23 @@ if (-not ([string]::IsNullOrEmpty($Subject)) ) { Write-Host -ForegroundColor Cyan "Looking for Exception Logs..." #collect Exception Logs $ExceptionLogs = @() + $LogToExamine = @() + $LogToExamine = $script:GCDO | Where-Object { $_.ItemClass -like 'IPM.Appointment*' } | Sort-Object ItemVersion - $ExceptionLogs = $script:GCDO | Where-Object { $_.ItemClass -like 'IPM.Appointment.*' } | Sort-Object ItemVersion | ForEach-Object { - Write-Host -ForegroundColor Cyan "Getting Exception Logs for [$($_.ItemId.ObjectId)]" - Get-CalendarDiagnosticObjects -Identity $ID -ItemIds $_.ItemId.ObjectId -ShouldFetchRecurrenceExceptions $true -CustomPropertyNames $CustomPropertyNameList + Write-Host -ForegroundColor Cyan "Found $($LogToExamine.count) CalLogs to examine for Exception Logs." + Write-Host -ForegroundColor Cyan "`t Ignore the next [$($LogToExamine.count)] warnings..." + + $ExceptionLogs = foreach ($Log in $LogToExamine) { + Write-Verbose "Getting Exception Logs for [$($Log.ItemId.ObjectId)]" + Get-CalendarDiagnosticObjects -Identity $ID -ItemIds $Log.ItemId.ObjectId -ShouldFetchRecurrenceExceptions $true -CustomPropertyNames $CustomPropertyNameList } - } + $ExceptionLogs = $ExceptionLogs | Where-Object { $_.ItemClass -notlike "IPM.Appointment*" } + Write-Host -ForegroundColor Cyan "Found $($ExceptionLogs.count) Exception Logs, adding them into the CalLogs." - $TempLogs = $script:GCDO + ($ExceptionLogs | Where-Object { $_.ItemClass -notlike "IPM.Appointment*" }) | Select-Object *, @{n='OrgTime'; e= { [DateTime]::Parse($_.OriginalLastModifiedTime.ToString()) } } | Sort-Object OrgTime - $script:GCDO = $TempLogs + $script:GCDO = $script:GCDO + $ExceptionLogs | Select-Object *, @{n='OrgTime'; e= { [DateTime]::Parse($_.OriginalLastModifiedTime.ToString()) } } | Sort-Object OrgTime + $LogToExamine = $null + $ExceptionLogs = $null + } BuildCSV -Identity $ID BuildTimeline -Identity $ID From a28919cf5e06412d5881db29bf8ac2905348b7c5 Mon Sep 17 00:00:00 2001 From: Shane Ferrell Date: Thu, 25 Jan 2024 15:05:13 -0800 Subject: [PATCH 3/5] Fix TimeLine output header when from shared meeting --- Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 index 8217ad378d..eddc5cab12 100644 --- a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 +++ b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 @@ -837,14 +837,6 @@ function BuildCSV { Write-Host -ForegroundColor Cyan -NoNewline "Calendar Logs for [$Identity] have been saved to :" Write-Host -ForegroundColor Yellow "$Filename" $GCDOResults | Export-Csv -Path $Filename -NoTypeInformation -Encoding UTF8 - - $MeetingTimeLine = $Results | Where-Object { $_.IsIgnorable -eq "False" } - Write-Host "`n`n`nThis is the meetingID $ThisMeetingID`nThis is Short MeetingID $ShortMeetingID" - if ($MeetingTimeLine.count -eq 0) { - Write-Host "All CalLogs are Ignorable, nothing to create a timeline with, displaying initial values." - } else { - Write-Host "Found $($script:GCDO.count) Log entries, only the $($MeetingTimeLine.count) Non-Ignorable entries will be analyzed in the TimeLine." - } } function MultiLineFormat { @@ -968,6 +960,13 @@ function BuildTimeline { # Ignorable and items from Shared Calendars are not included in the TimeLine. $MeetingTimeLine = $Results | Where-Object { $_.IsIgnorable -eq "False" -and $_.IsFromSharedCalendar -eq $False } + Write-Host "`n`n`nThis is the meetingID $ThisMeetingID`nThis is Short MeetingID $ShortMeetingID" + if ($MeetingTimeLine.count -eq 0) { + Write-Host "All CalLogs are Ignorable, nothing to create a timeline with, displaying initial values." + } else { + Write-Host "Found $($script:GCDO.count) Log entries, only the $($MeetingTimeLine.count) Non-Ignorable entries will be analyzed in the TimeLine." + } + foreach ($CalLog in $MeetingTimeLine) { [bool] $MeetingSummaryNeeded = $False [bool] $AddChangedProperties = $False From 6344fcf5cf78ccd6c3a3e5c2f9b6c19ed467e093 Mon Sep 17 00:00:00 2001 From: Shane Ferrell Date: Fri, 26 Jan 2024 17:16:29 -0800 Subject: [PATCH 4/5] Fix mapping of Outlook for Android --- Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 index eddc5cab12..666308ad78 100644 --- a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 +++ b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 @@ -562,6 +562,8 @@ function CreateShortClientName { $ShortClientName = "TimeService" } elseif ($ClientInfoString -like "*AppId=48af08dc-f6d2-435f-b2a7-069abd99c086*") { $ShortClientName = "RestConnector" + } elseif ($ClientInfoString -like "*Client=OutlookService;Outlook-Android*") { + $ShortClientName = "OutlookAndroid" } elseif ($ClientInfoString -like "*GriffinRestClient*") { $ShortClientName = "GriffinRestClient" } elseif ($ClientInfoString -like "*MacOutlook*") { From 0452a012435c18cad02e5c2972c70f552f6a4be1 Mon Sep 17 00:00:00 2001 From: Shane Ferrell Date: Mon, 29 Jan 2024 12:12:09 -0800 Subject: [PATCH 5/5] Use ForEach-Object --- Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 index 666308ad78..abdb8ac603 100644 --- a/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 +++ b/Calendar/Get-CalendarDiagnosticObjectsSummary.ps1 @@ -1554,10 +1554,11 @@ if (-not ([string]::IsNullOrEmpty($Subject)) ) { Write-Host -ForegroundColor Cyan "Found $($LogToExamine.count) CalLogs to examine for Exception Logs." Write-Host -ForegroundColor Cyan "`t Ignore the next [$($LogToExamine.count)] warnings..." - $ExceptionLogs = foreach ($Log in $LogToExamine) { - Write-Verbose "Getting Exception Logs for [$($Log.ItemId.ObjectId)]" - Get-CalendarDiagnosticObjects -Identity $ID -ItemIds $Log.ItemId.ObjectId -ShouldFetchRecurrenceExceptions $true -CustomPropertyNames $CustomPropertyNameList + $ExceptionLogs = $LogToExamine | ForEach-Object { + Write-Verbose "Getting Exception Logs for [$($_.ItemId.ObjectId)]" + Get-CalendarDiagnosticObjects -Identity $ID -ItemIds $_.ItemId.ObjectId -ShouldFetchRecurrenceExceptions $true -CustomPropertyNames $CustomPropertyNameList } + # Remove the IPM.Appointment logs as they are already in the CalLogs. $ExceptionLogs = $ExceptionLogs | Where-Object { $_.ItemClass -notlike "IPM.Appointment*" } Write-Host -ForegroundColor Cyan "Found $($ExceptionLogs.count) Exception Logs, adding them into the CalLogs."