Functions/Get-SchoolScheduleMeeting.ps1
function Get-SchoolScheduleMeeting { <# .LINK https://github.com/Sekers/SKYAPI/wiki .LINK Endpoint: https://developer.sky.blackbaud.com/docs/services/school/operations/V1SchedulesMeetingsGet .SYNOPSIS Education Management School API - Returns a list of section meetings for a given date. .DESCRIPTION Education Management School API - Returns a list of section meetings for a given date. When end_date is supplied, a range of meetings between the given dates is returned. If end_date is not supplied, Get-SchoolScheduleMeeting defaults to 30 days from start_date. Additional Notes: - Returned meeting start & end times are in UTC DateTime format. - Returned meeting date is the date of the meeting in the School Time Zone as specified at https://[school_domain_here].myschoolapp.com/app/core#demographics. .PARAMETER start_date Required. Start date of events you want returned. Use ISO-8601 date format (e.g., 2022-04-01). .PARAMETER end_date End date of events you want returned. Use ISO-8601 date format (2022-04-08). If not specified, defaults to 30 days from start_date. .PARAMETER offering_types Can take a single or multiple values as a comma delimited string of integers (defaults to 1 'Academics'). Use Get-SchoolOfferingType to get a list of offering types. .PARAMETER section_ids Comma delimited list of integer values for the section identifiers to return. By default the route returns all sections. .PARAMETER last_modified Filters meetings to sections that were modified on or after the date provided. Use ISO-8601 date format (e.g., 2022-04-01). .PARAMETER SchoolTimeZoneId Indicates the School Time Zone as specified at https://[school_domain_here].myschoolapp.com/app/core#demographics. Get-SchoolScheduleMeeting will try to automatically pull the value from your school envirionment, but if you receive an error, you may have to manually override it with a valid time zone ID. This is required because Blackbaud does not return accurate time zone information from this endpoint. Use 'Get-TimeZone -ListAvailable' to get a list of valid time zone IDs. .EXAMPLE Get-SchoolScheduleMeeting -start_date '2022-11-01' .EXAMPLE Get-SchoolScheduleMeeting -start_date '2022-11-01' -end_date '2022-11-30' -offering_types '1,3' .EXAMPLE Get-SchoolScheduleMeeting -start_date '2022-11-01' | Where-Object -Property faculty_user_id -eq '3154032' | Sort-Object meeting_date, start_time .EXAMPLE $HashArguments = @{ start_date = '2022-11-01' end_date = '2022-11-30' section_ids = '82426521, 93054528' last_modified = '2023-12-09' SchoolTimeZoneId = "Central Standard Time" } Get-SchoolScheduleMeeting @HashArguments .EXAMPLE $Meetings = Get-SchoolScheduleMeeting -start_date '2022-11-01' foreach ($meeting in $Meetings) { "`n--- Meeting Group ---" $meeting.group_name "--- Meeting Date (School Envirionment Time Zone) ---" $meeting.meeting_date "--- Start & End (Local Time) ---" $meeting.start_time.ToLocalTime().DateTime # DateTime Kind of 'Local' $meeting.end_time.ToLocalTime().DateTime # DateTime Kind of 'Local' "--- Start & End (Pacific Standard Time) ---" [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($meeting.start_time, 'Pacific Standard Time') # DateTime Kind of 'Unspecified' [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($meeting.end_time, 'Pacific Standard Time') # DateTime Kind of 'Unspecified' } #> [cmdletbinding()] Param( [Parameter( Position=0, Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [string]$start_date, [Parameter( Position=1, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [string]$end_date, [Parameter( Position=2, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [string]$offering_types, [Parameter( Position=3, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [string]$section_ids, [Parameter( Position=4, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [string]$last_modified, [Parameter( Position=5, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [ValidateScript({ if ((Get-TimeZone -ListAvailable).Id -contains $_) { $true } else { throw "$_ is invalid. Use 'Get-TimeZone -ListAvailable' to get a list of valid time zone IDs." } })] [string]$SchoolTimeZoneId = ((Get-SchoolTimeZone).timezone_name) ) # Set the endpoints $endpoint = 'https://api.sky.blackbaud.com/school/v1/schedules/meetings' $endUrl = '' # Set the response field $ResponseField = "value" # Set the parameters $parameters = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) foreach ($parameter in $PSBoundParameters.GetEnumerator()) { $parameters.Add($parameter.Key,$parameter.Value) } # IMPORTANT NOTE: NO SPACES ALLOWED BETWEEN VALUES FOR 'offering_types' STRING!!!! (e.g., "1,3" is the correct way, NOT "1, 3") # It will still process the query if there is a string, but only return results for the first value. # Remove spaces from 'offering_types' string if included in a comma-separated list. if ($parameters -contains 'offering_types') { $parameters.Remove('offering_types') | Out-Null $parameters.Add('offering_types',$($offering_types.Replace(' ',''))) } # Remove the School Time Zone parameter since we don't pass it on to the API. $parameters.Remove('SchoolTimeZoneId') | Out-Null # Convert SchoolTimeZone to TimeZoneInfo object. $SchoolTimeZone = Get-TimeZone -ListAvailable | Where-Object -Property Id -EQ $SchoolTimeZoneId # Get the SKY API subscription key $sky_api_config = Get-SKYAPIConfig -ConfigPath $sky_api_config_file_path $sky_api_subscription_key = $sky_api_config.api_subscription_key # Grab the security tokens $AuthTokensFromFile = Get-SKYAPIAuthTokensFromFile # Validate Start Date String try {$null = [datetime]$start_date} catch { throw $_ } # If the 'end_date' parameter doesn't exist, then set it to 30 days ahead (the max allowed per call). # It is supposed to default to 30 days, but it doesn't work correctly unless you specify an end date (at least in the beta). # Also, if you put in a larger time limit than 30 days, it sometimes does 31 days or something like that. It's really dumb. [int]$IterationRangeInDays = 30 if ($null -eq $end_date -or $end_date -eq '' -or $end_date -eq 0) { $end_date = (([DateTime]$start_date).AddDays($IterationRangeInDays)).ToString('yyyy-MM-dd') } # Validate End Date String try {$null = [datetime]$end_date} catch { throw $_ } # Initialize Variables $response = $null $DateRangeEnd = [DateTime]$end_date $DateIterationStart = [DateTime]$start_date $DateIterationEnd = $DateIterationStart.AddDays($IterationRangeInDays) $FinalIteration = $false # Iterate $response += do { # Don't go beyond the final end date if ($DateIterationEnd -ge $DateRangeEnd) { $DateIterationEnd = $DateRangeEnd $FinalIteration = $true } # Remove the 'start_date' and 'end_date' parameters. $parameters.Remove('start_date') | Out-Null $parameters.Remove('end_date') | Out-Null # Add the parameters back in with the correct iteration values $parameters.Add('start_date',$DateIterationStart.ToString('yyyy-MM-dd')) $parameters.Add('end_date',$DateIterationEnd.ToString('yyyy-MM-dd')) # Get the Data. # Note: Since PowerShell v6, ConvertTo-Json automatically deserializes strings that contain # an "o"-formatted (roundtrip format) date/time string (e.g., "2023-06-15T13:45:00.123Z") # or a prefix of it that includes at least everything up to the seconds part as [datetime] instances. # Because Blackbaud provides incorrect timezone data we have to adjust this when running in CORE. # So, with PS Core, we need to get the raw JSON and create a CustomPSObject without deserialization. if ($PSVersionTable.PSEdition -EQ 'Desktop') { Get-SKYAPIUnpagedEntity -url $endpoint -endUrl $endUrl -api_key $sky_api_subscription_key -authorisation $AuthTokensFromFile -params $parameters -response_field $ResponseField } else { $response_raw = Get-SKYAPIUnpagedEntity -url $endpoint -endUrl $endUrl -api_key $sky_api_subscription_key -authorisation $AuthTokensFromFile -params $parameters -response_field $ResponseField -ReturnRaw (ConvertFrom-JsonWithoutDateTimeDeserialization -InputObject $response_raw).$ResponseField } # Increase Iteration Range $DateIterationStart = $DateIterationStart.AddDays($IterationRangeInDays + 1) $DateIterationEnd = $DateIterationEnd.AddDays($IterationRangeInDays + 1) } until($FinalIteration -eq $true) # Massage dates in $response because PowerShell automatically converts API calls to date time... # But Blackbaud includes a generic date of '1900-01-01' when returning time which throws off Daylight Saving Time. # Blackbaud also includes a generic time of 'T00:00:00+00:00' when returning dates which throws off stuff too. # Example Output from the API that PowerShell automatically parses: # "start_time": "1900-01-01T09:36:00-05:00" # "end_time": "1900-01-01T10:26:00-05:00" # "meeting_date": "2022-09-06T00:00:00+00:00" $response = foreach ($meeting in $response) { # Strip the time information from the date. $meeting_date = ($meeting.meeting_date -split "T")[0] # Pull the time and combine with the correct date so that daylight saving time is calculated correctly $start_time = ($meeting.start_time -split "T")[1] $start_time = ($start_time -split "-")[0] $start_time = [System.String]::Concat($meeting_date,"T",$start_time) $start_time = ([System.TimeZoneInfo]::ConvertTimeToUtc($start_time, $SchoolTimeZone)) # Convert to UTC, specifying the time zone. $end_time = (($meeting.end_time) -split "T")[1] $end_time = ($end_time -split "-")[0] $end_time = [System.String]::Concat($meeting_date,"T",$end_time) $end_time = ([System.TimeZoneInfo]::ConvertTimeToUtc($end_time, $SchoolTimeZone)) # Convert to UTC, specifying the time zone. # Replace values in array $meeting.start_time = Get-Date $start_time $meeting.end_time = Get-Date $end_time $meeting.meeting_date = $meeting_date # Don't convert to DateTime # Return the array $meeting } return $response } |