diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cb4057..4a2059a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog for SKYAPI PowerShell Module +## [0.4.0](https://github.com/Sekers/SKYAPI/tree/0.4.0) - (2025-01-22) + +### Fixes + +- Resolved bug in PS Core (PS Desktop was fine) when calling Get-SchoolScheduleMeeting if one of the 30-day iterations comes back empty. + +### Features +- Sample script for Google Faculty Calendar Imports now supports meeting exclusions. +- Improved error handling. Some errors that can often be transient now retry using an exponential backoff rather than just retrying up to 5 times with a 5-second delay in-between. This should reduce how often scripts fail by increasing the retry count from 5 to 7 and increasing the max wait time from the first try to the last retry from 20 seconds to just over 5 minutes. The exponential backup delay is as follows (in seconds): + + | Failure Count | Wait Time Before Next Retry | Total Wait Time | + | --- | --- | --- | + | 1 | 5 | 5 | + | 2 | 10 | 15 | + | 3 | 20 | 35 | + | 4 | 40 | 75 | + | 5 | 80 | 155 | + | 6 | 160 | 315 | + | 7 | n/a (throws error) | 315 | + +### Other + +- Disabled progress bar in function scope when calling Invoke-WebRequest or Invoke-RestMethod. This improves performance due to a bug in some versions of PowerShell. It was eventually fixed in Core (v6.0.0-alpha.13) but still is around in Desktop. More Information: https://github.com/PowerShell/PowerShell/pull/2640 +- Removed the unimplemented 'MiniHTTPServer' alternate method of capturing authentication as this would be overkill and is unnecessary. +- Removed the 'LegacyIEControl' alternate method of capturing authentication as it is no longer supported by Blackbaud. + +Author: [**@Sekers**](https://github.com/Sekers) + +--- ## [0.3.11](https://github.com/Sekers/SKYAPI/tree/0.3.11) - (2024-10-14) ### Features @@ -7,14 +36,15 @@ - BREAKING CHANGE: Updated the included [Microsoft Edge WebView2 control](https://www.nuget.org/packages/Microsoft.Web.WebView2) to version [1.0.2792.45](https://www.nuget.org/packages/Microsoft.Web.WebView2/1.0.2792.45). Note that the minimum .NET Framework version requirement for .NET WebView2 has been updated from .NET Framework 4.5 to .NET Framework 4.6.2 (this affects Windows PowerShell Desktop only, not PowerShell Core). - New Endpoint: [Get-SchoolAdmissionCandidate](https://developer.sky.blackbaud.com/docs/services/school/operations/V1AdmissionsCandidatesGet) - New Endpoint: [Get-SchoolAdmissionStatus](https://developer.sky.blackbaud.com/docs/services/school/operations/V1AdmissionsStatusGet) -- New Endpoint: [Get-SchoolActivityRoster](https://developer.sky.blackbaud.com/api#api=school&operation=V1ActivitiesRostersGet) -- New Endpoint: [Get-SchoolAdvisoryRoster](https://developer.sky.blackbaud.com/api#api=school&operation=V1AdvisoriesRostersGet) -- New Endpoint: [Get-SchoolRoster](https://developer.sky.blackbaud.com/api#api=school&operation=V1AcademicsRostersGet) +- New Endpoint (Currently Beta): [Get-SchoolActivityRoster](https://developer.sky.blackbaud.com/api#api=school&operation=V1ActivitiesRostersGet) +- New Endpoint (Currently Beta): [Get-SchoolAdvisoryRoster](https://developer.sky.blackbaud.com/api#api=school&operation=V1AdvisoriesRostersGet) +- New Endpoint (Currently Beta): [Get-SchoolRoster](https://developer.sky.blackbaud.com/api#api=school&operation=V1AcademicsRostersGet) - Added new parameters to Set-SchoolUserRelationship: resides_with, do_not_contact, primary, & comments - Updates to error handling as the Blackbaud SKY API now returns at least 4 different types of error message formats. - New sample script: Blackbaud SIS Teacher Schedules to ICS ### Other + - Updated links for built-in help to new endpoint documentation URLs. - Updated authorize URI in various places due to updates on the API end (the old URI still works since it is forwarded to the new one). - Minor example and built-in help updates, clarifications, and typo fixes. @@ -37,6 +67,7 @@ Author: [**@Sekers**](https://github.com/Sekers) - New Endpoint: [New-SchoolUserAddress](https://developer.sky.blackbaud.com/docs/services/school/operations/V1UsersByUser_idAddressesPost) ### Other + - Updated links in README to Blackbaud API documentation as the documentation website had slightly changed structure. - A few minor example and built-in help updates, clarifications, and typo fixes. - Removed the 'links' parameter from New-SchoolUserPhone as Blackbaud never actually implemented this feature in the endpoint and updated their documentation. See the [February 28, 2023 Education Management API changelog](https://developer.blackbaud.com/skyapi/support/changelog/bbem) for further information. @@ -51,9 +82,10 @@ Author: [**@Sekers**](https://github.com/Sekers) - New Endpoint: [Get-SchoolSession](https://developer.sky.blackbaud.com/docs/services/school/operations/V1SessionsGet) - New Endpoint: [Get-SchoolResourceBoard](https://developer.sky.blackbaud.com/docs/services/school/operations/V1ContentResourcesGet) - 'Get-SchoolAssignmentBySection' & 'Get-SchoolScheduleMeeting' now allow spaces in the types/offering_types parameters. -- New Example Script: [Blackbaud SIS Teacher Schedules to Google Calendar CSVs](https://github.com/Sekers/SKYAPI/tree/master/Sample_Usage_Scripts/Blackbaud%20SIS%20Teacher%20Schedules%20to%20Google%20Calendar%20CSVs). Creates importable Google Calendar schedules for faculty from the Blackbaud School Envirionment. +- New Example Script: [Blackbaud SIS Teacher Schedules to Google Calendar CSVs](https://github.com/Sekers/SKYAPI/tree/master/Sample_Usage_Scripts/Blackbaud%20SIS%20Teacher%20Schedules%20to%20Google%20Calendar%20CSVs). Creates importable Google Calendar schedules for faculty from the Blackbaud School Environment. ### Other + - Built-in help updates regarding necessary permissions to access some endpoints (Blackbaud [loosened the requirements](https://developer.blackbaud.com/skyapi/support/changelog/bbem?_ga=2.83020246.1587108373.1678131781-1653312318.1663684217) on a bunch of general information ones). Author: [**@Sekers**](https://github.com/Sekers) diff --git a/SKYAPI/Functions/Connect-SKYAPI.ps1 b/SKYAPI/Functions/Connect-SKYAPI.ps1 index 65c72dd..21df08f 100644 --- a/SKYAPI/Functions/Connect-SKYAPI.ps1 +++ b/SKYAPI/Functions/Connect-SKYAPI.ps1 @@ -25,9 +25,6 @@ Function Connect-SKYAPI Let's you specify how you want to authenticate if authentication is necessary: - EdgeWebView2 (default): Opens a web browser window using Microsoft Edge WebView2 for authentication. Requires the WebView2 Runtime to be installed. If not installed, will prompt for automatic installation. - - LegacyIEControl: Opens a web browser window using the old Internet Explorer control. This is no longer supported by Blackbaud. - - MiniHTTPServer: Alternate method of capturing the authentication using your user account's default web browser - and listening for the authentication response using a temporary HTTP server hosted by the module. .PARAMETER ReturnConnectionInfo Returns connection information after performing function. @@ -38,7 +35,7 @@ Function Connect-SKYAPI .EXAMPLE Connect-SKYAPI -ForceReauthentication -ClearBrowserControlCache .EXAMPLE - Connect-SKYAPI -ForceReauthentication -AuthenticationMethod MiniHTTPServer + Connect-SKYAPI -ForceReauthentication -AuthenticationMethod EdgeWebView2 .EXAMPLE Connect-SKYAPI -ForceRefresh .EXAMPLE @@ -66,7 +63,7 @@ Function Connect-SKYAPI Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] - [ValidateSet('EdgeWebView2','MiniHTTPServer','LegacyIEControl')] + [ValidateSet('EdgeWebView2')] [string]$AuthenticationMethod, [parameter( @@ -111,7 +108,7 @@ Function Connect-SKYAPI process { - # Set the Necesasary Configuration Variables + # Set the Necessary Configuration Variables $sky_api_config = Get-SKYAPIConfig -ConfigPath $sky_api_config_file_path $client_id = $sky_api_config.client_id $client_secret = $sky_api_config.client_secret @@ -159,7 +156,7 @@ Function Connect-SKYAPI { # Run Invoke Command and Catch Responses [int]$InvokeCount = 0 - [int]$MaxInvokeCount = 5 + [int]$MaxInvokeCount = 7 do { $InvokeCount += 1 @@ -173,7 +170,7 @@ Function Connect-SKYAPI { # Process Invoke Error $LastCaughtError = ($_) - $NextAction = SKYAPICatchInvokeErrors($_) + $NextAction = SKYAPICatchInvokeErrors -InvokeErrorMessageRaw $_ -InvokeCount $InvokeCount -MaxInvokeCount $MaxInvokeCount # Just in case the token was refreshed by the error catcher, update the $AuthTokensFromFile variable $AuthTokensFromFile = Get-SKYAPIAuthTokensFromFile diff --git a/SKYAPI/Functions/Get-SchoolActivityRoster.ps1 b/SKYAPI/Functions/Get-SchoolActivityRoster.ps1 index 1b153d3..da12868 100644 --- a/SKYAPI/Functions/Get-SchoolActivityRoster.ps1 +++ b/SKYAPI/Functions/Get-SchoolActivityRoster.ps1 @@ -23,7 +23,7 @@ function Get-SchoolActivityRoster Limits rosters returned to the school level specified. .PARAMETER section_ids - Limits roters returned to the sections specified. Provide comma-delimited list of section_id values. + Limits rosters returned to the sections specified. Provide comma-delimited list of section_id values. .PARAMETER last_modified Limits rosters returned to sections that were modified on or after the date provided. Use ISO-8601 date format (e.g., 2022-04-01). diff --git a/SKYAPI/Functions/Get-SchoolAdmissionCandidate.ps1 b/SKYAPI/Functions/Get-SchoolAdmissionCandidate.ps1 index 903149f..84e3ba5 100644 --- a/SKYAPI/Functions/Get-SchoolAdmissionCandidate.ps1 +++ b/SKYAPI/Functions/Get-SchoolAdmissionCandidate.ps1 @@ -83,7 +83,7 @@ function Get-SchoolAdmissionCandidate # Grab the security tokens $AuthTokensFromFile = Get-SKYAPIAuthTokensFromFile - # Get data for one or more school levels + # Get data if ($ReturnRaw) { $response = Get-SKYAPIUnpagedEntity -url $endpoint -api_key $sky_api_subscription_key -authorisation $AuthTokensFromFile -params $parameters -ReturnRaw diff --git a/SKYAPI/Functions/Get-SchoolAdvisoryRoster.ps1 b/SKYAPI/Functions/Get-SchoolAdvisoryRoster.ps1 index fbe3caf..fabdf14 100644 --- a/SKYAPI/Functions/Get-SchoolAdvisoryRoster.ps1 +++ b/SKYAPI/Functions/Get-SchoolAdvisoryRoster.ps1 @@ -23,7 +23,7 @@ function Get-SchoolAdvisoryRoster Limits rosters returned to the school level specified. .PARAMETER section_ids - Limits roters returned to the sections specified. Provide comma-delimited list of section_id values. + Limits rosters returned to the sections specified. Provide comma-delimited list of section_id values. .PARAMETER last_modified Limits rosters returned to sections that were modified on or after the date provided. Use ISO-8601 date format (e.g., 2022-04-01). diff --git a/SKYAPI/Functions/Get-SchoolAssignmentBySection.ps1 b/SKYAPI/Functions/Get-SchoolAssignmentBySection.ps1 index af1b18a..5bf17f0 100644 --- a/SKYAPI/Functions/Get-SchoolAssignmentBySection.ps1 +++ b/SKYAPI/Functions/Get-SchoolAssignmentBySection.ps1 @@ -124,7 +124,7 @@ function Get-SchoolAssignmentBySection # Grab the security tokens $AuthTokensFromFile = Get-SKYAPIAuthTokensFromFile - # Get data for one or more school levels + # Get data for one or more section IDs foreach ($uid in $Section_ID) { if ($ReturnRaw) diff --git a/SKYAPI/Functions/Get-SchoolCycleBySection.ps1 b/SKYAPI/Functions/Get-SchoolCycleBySection.ps1 index d65a969..37ed31e 100644 --- a/SKYAPI/Functions/Get-SchoolCycleBySection.ps1 +++ b/SKYAPI/Functions/Get-SchoolCycleBySection.ps1 @@ -90,7 +90,7 @@ function Get-SchoolCycleBySection # Grab the security tokens $AuthTokensFromFile = Get-SKYAPIAuthTokensFromFile - # Get data for one or more school levels + # Get data for one or more section IDs foreach ($uid in $Section_ID) { if ($ReturnRaw) diff --git a/SKYAPI/Functions/Get-SchoolRoster.ps1 b/SKYAPI/Functions/Get-SchoolRoster.ps1 index 309e0d6..abc060e 100644 --- a/SKYAPI/Functions/Get-SchoolRoster.ps1 +++ b/SKYAPI/Functions/Get-SchoolRoster.ps1 @@ -23,7 +23,7 @@ function Get-SchoolRoster Limits rosters returned to the school level specified. .PARAMETER section_ids - Limits roters returned to the sections specified. Provide comma-delimited list of section_id values. + Limits rosters returned to the sections specified. Provide comma-delimited list of section_id values. .PARAMETER last_modified Limits rosters returned to sections that were modified on or after the date provided. Use ISO-8601 date format (e.g., 2022-04-01). diff --git a/SKYAPI/Functions/Get-SchoolScheduleMeeting.ps1 b/SKYAPI/Functions/Get-SchoolScheduleMeeting.ps1 index ed62c21..95ab811 100644 --- a/SKYAPI/Functions/Get-SchoolScheduleMeeting.ps1 +++ b/SKYAPI/Functions/Get-SchoolScheduleMeeting.ps1 @@ -33,7 +33,7 @@ function Get-SchoolScheduleMeeting 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, + Get-SchoolScheduleMeeting will try to automatically pull the value from your school environment, 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. @@ -59,7 +59,7 @@ function Get-SchoolScheduleMeeting { "`n--- Meeting Group ---" $meeting.group_name - "--- Meeting Date (School Envirionment Time Zone) ---" + "--- Meeting Date (School Environment Time Zone) ---" $meeting.meeting_date "--- Start & End (Local Time) ---" $meeting.start_time.ToLocalTime().DateTime # DateTime Kind of 'Local' @@ -186,14 +186,14 @@ function Get-SchoolScheduleMeeting } # Initialize Variables - $response = $null + $response = [System.Collections.Generic.List[Object]]::new() $DateRangeEnd = [DateTime]$end_date $DateIterationStart = [DateTime]$start_date $DateIterationEnd = $DateIterationStart.AddDays($IterationRangeInDays) $FinalIteration = $false # Iterate - $response += do + do { # Don't go beyond the final end date if ($DateIterationEnd -ge $DateRangeEnd) @@ -218,12 +218,21 @@ function Get-SchoolScheduleMeeting if ($PSVersionTable.PSEdition -EQ 'Desktop') { - Get-SKYAPIUnpagedEntity -url $endpoint -endUrl $endUrl -api_key $sky_api_subscription_key -authorisation $AuthTokensFromFile -params $parameters -response_field $ResponseField + $response_objects = 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 + $response_objects = (ConvertFrom-JsonWithoutDateTimeDeserialization -InputObject $response_raw).$ResponseField + } + + # Only return a response to the list if there's data. + if ($null -ne $response_objects) + { + foreach ($response_object in $response_objects) + { + $response.Add($response_object) + } } # Increase Iteration Range diff --git a/SKYAPI/Functions/Get-SchoolSectionByTeacher.ps1 b/SKYAPI/Functions/Get-SchoolSectionByTeacher.ps1 index 484a040..56c579b 100644 --- a/SKYAPI/Functions/Get-SchoolSectionByTeacher.ps1 +++ b/SKYAPI/Functions/Get-SchoolSectionByTeacher.ps1 @@ -73,7 +73,7 @@ function Get-SchoolSectionByTeacher # Grab the security tokens $AuthTokensFromFile = Get-SKYAPIAuthTokensFromFile - # Get data for one or more school levels + # Get data for one or more teacher IDs foreach ($uid in $Teacher_ID) { diff --git a/SKYAPI/Functions/Get-SchoolSession.ps1 b/SKYAPI/Functions/Get-SchoolSession.ps1 index 00a2e59..cd121f1 100644 --- a/SKYAPI/Functions/Get-SchoolSession.ps1 +++ b/SKYAPI/Functions/Get-SchoolSession.ps1 @@ -72,7 +72,7 @@ function Get-SchoolSession # Grab the security tokens $AuthTokensFromFile = Get-SKYAPIAuthTokensFromFile - # Get data for one or more school levels + # Get data if ($ReturnRaw) { $response = Get-SKYAPIUnpagedEntity -url $endpoint -api_key $sky_api_subscription_key -authorisation $AuthTokensFromFile -params $parameters -ReturnRaw diff --git a/SKYAPI/Functions/Get-SchoolStudentBySection.ps1 b/SKYAPI/Functions/Get-SchoolStudentBySection.ps1 index 56b01d0..6103e91 100644 --- a/SKYAPI/Functions/Get-SchoolStudentBySection.ps1 +++ b/SKYAPI/Functions/Get-SchoolStudentBySection.ps1 @@ -53,7 +53,7 @@ function Get-SchoolStudentBySection # Grab the security tokens $AuthTokensFromFile = Get-SKYAPIAuthTokensFromFile - # Get data for one or more school levels + # Get data for one or more section IDs foreach ($uid in $Section_ID) { if ($ReturnRaw) diff --git a/SKYAPI/SKYAPI.psd1 b/SKYAPI/SKYAPI.psd1 index b4c484d..916c573 100644 --- a/SKYAPI/SKYAPI.psd1 +++ b/SKYAPI/SKYAPI.psd1 @@ -12,7 +12,7 @@ RootModule = 'SKYAPI.psm1' # Version number of this module. -ModuleVersion = '0.3.11' +ModuleVersion = '0.4.0' # Supported PSEditions CompatiblePSEditions = @('Desktop','Core') diff --git a/SKYAPI/SKYAPI.psm1 b/SKYAPI/SKYAPI.psm1 index 41ac2a6..6179ac3 100644 --- a/SKYAPI/SKYAPI.psm1 +++ b/SKYAPI/SKYAPI.psm1 @@ -86,6 +86,11 @@ Function Get-SKYAPIAuthToken [CmdletBinding()] Param($grant_type,$client_id,$redirect_uri,$client_secret,$authCode,$token_uri) + # Disable Progress Bar in Function Scope When Calling Invoke-WebRequest or Invoke-RestMethod. + # This improves performance due to a bug in some versions of PowerShell. It was eventually fixed in Core (v6.0.0-alpha.13) but still is around in Desktop. + # More Information: https://github.com/PowerShell/PowerShell/pull/2640 + $ProgressPreference = 'SilentlyContinue' + #Build token request $AuthorizationPostRequest = 'grant_type=' + $grant_type + '&' + 'redirect_uri=' + [System.Web.HttpUtility]::UrlEncode($redirect_uri) + '&' + @@ -112,6 +117,11 @@ Function Get-SKYAPIAccessToken [CmdletBinding()] Param($grant_type,$client_id,$redirect_uri,$client_secret,$authCode,$token_uri) + # Disable Progress Bar in Function Scope When Calling Invoke-WebRequest or Invoke-RestMethod. + # This improves performance due to a bug in some versions of PowerShell. It was eventually fixed in Core (v6.0.0-alpha.13) but still is around in Desktop. + # More Information: https://github.com/PowerShell/PowerShell/pull/2640 + $ProgressPreference = 'SilentlyContinue' + #Build token request $AuthorizationPostRequest = 'grant_type=' + $grant_type + '&' + 'redirect_uri=' + [System.Web.HttpUtility]::UrlEncode($redirect_uri) + '&' + @@ -173,55 +183,6 @@ function Resolve-SKYAPIMemberChain } } -# Helper to make sure Browser Emulation/Compatibility Mode is Off When Using the WebBrowser Control. -# This function will set the Internet Explorer emulation mode for the running executable. This allows the WebBrowser control to support newer html features and improves compatibility with modern websites. -# Modified from https://www.sapien.com/blog/2020/11/05/a-simple-fix-for-problems-with-windows-forms-webbrowser/ (see also https://bchallis.wordpress.com/2020/10/17/problems-with-the-windows-forms-webbrowser-control-and-a-simple-way-to-fix-it/) -function Set-SKYAPIWebBrowserEmulation -{ - param - ( - [ValidateNotNullOrEmpty()] - [string] - $ExecutableName = [System.IO.Path]::GetFileName([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName) - ) - - #region Get IE Version - $valueNames = 'svcVersion', 'svcUpdateVersion', 'Version', 'W2kVersion' - - $version = 0; - for ($i = 0; $i -lt $valueNames.Length; $i++) - { - $objVal = [Microsoft.Win32.Registry]::GetValue('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer', $valueNames[$i], '0') - $strVal = [System.Convert]::ToString($objVal) - if ($strVal) - { - $iPos = $strVal.IndexOf('.') - if ($iPos -gt 0) - { - $strVal = $strVal.Substring(0, $iPos) - } - - $res = 0; - if ([int]::TryParse($strVal, [ref]$res)) - { - $version = [Math]::Max($version, $res) - } - } - } - - if ($version -lt 7) - { - $version = 7000 - } - else - { - $version = $version * 1000 - } - #endregion - - [Microsoft.Win32.Registry]::SetValue('HKEY_CURRENT_USER\SOFTWARE\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION', $ExecutableName, $version) -} - Function Show-SKYAPIOAuthWindow { Param( @@ -237,15 +198,15 @@ Function Show-SKYAPIOAuthWindow Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] - [ValidateSet('','EdgeWebView2','MiniHTTPServer','LegacyIEControl')] # Allows null to be passed + [ValidateSet('','EdgeWebView2')] # Allows null to be passed [string]$AuthenticationMethod, [parameter( - Position=2, - Mandatory=$false, - ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true)] - [switch]$ClearBrowserControlCache + Position=2, + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true)] + [switch]$ClearBrowserControlCache ) # If Edge WebView 2 is the Authentication Method & the runtime not installed - https://developer.microsoft.com/en-us/microsoft-edge/webview2/ @@ -284,7 +245,7 @@ Function Show-SKYAPIOAuthWindow Write-Warning "Microsoft Edge WebView2 Runtime is not installed and is required for browser-based authentication. Please install the runtime and try again." $PromptNoWebView2Runtime_Title = "Options" $PromptNoWebView2Runtime_Message = "Enter your choice:" - $PromptNoWebView2Runtime_Choices = [System.Management.Automation.Host.ChoiceDescription[]]@("&Download & install the Edge WebView2 runtime", "&Try alternative method (beta)", "&Cancel & exit") + $PromptNoWebView2Runtime_Choices = [System.Management.Automation.Host.ChoiceDescription[]]@("&Download & install the Edge WebView2 runtime", "&Cancel & exit") $PromptNoWebView2Runtime_Default = 0 $PromptNoWebView2Runtime_Selection = $host.UI.PromptForChoice($PromptNoWebView2Runtime_Title,$PromptNoWebView2Runtime_Message,$PromptNoWebView2Runtime_Choices,$PromptNoWebView2Runtime_Default) @@ -323,9 +284,6 @@ Function Show-SKYAPIOAuthWindow Write-Host "Retrying Authentication...`n" } 1 { - $AuthenticationMethod = "MiniHTTPServer" - } - 2 { Write-Host "Exiting..." Exit } @@ -335,85 +293,6 @@ Function Show-SKYAPIOAuthWindow switch ($AuthenticationMethod) { - MiniHTTPServer # TODO - { - Write-Host "`nUsing this option will attempt to authenticate using an alternate method by building a mini webserver in PowerShell. Continue?" - $PromptMiniWebserver_Title = "Options" - $PromptMiniWebserver_Message = "Enter your choice:" - $PromptMiniWebserver_Choices = [System.Management.Automation.Host.ChoiceDescription[]]@("&Load temporary HTTP server", "&Cancel & exit") - $PromptMiniWebserver_Default = 0 - $PromptMiniWebserver_Selection = $host.UI.PromptForChoice($PromptMiniWebserver_Title,$PromptMiniWebserver_Message,$PromptMiniWebserver_Choices,$PromptMiniWebserver_Default) - - switch($PromptMiniWebserver_Selection) - { - 0 { - Write-Warning "Sorry. The mini webserver authentication feature is not yet implemented." - Write-Host "Exiting..." - Exit - } - 1 { - Write-Host "Exiting..." - Exit - } - } - } - LegacyIEControl - { - Set-SKYAPIWebBrowserEmulation - - if ($ClearBrowserControlCache) - { - # Try to clear IE cache - # More info: https://superuser.com/questions/450014/clearmytracksbyprocess-all-options - # Using 4351 (0x10FF) to clear all + files and settings stored by add-ons. Convert Hex to Decimal. - # // This magic value is the combination of the following bitflags: - # // #define CLEAR_HISTORY 0x0001 // Clears history - # // #define CLEAR_COOKIES 0x0002 // Clears cookies - # // #define CLEAR_CACHE 0x0004 // Clears Temporary Internet Files folder - # // #define CLEAR_CACHE_ALL 0x0008 // Clears offline favorites and download history - # // #define CLEAR_FORM_DATA 0x0010 // Clears saved form data for form auto-fill-in - # // #define CLEAR_PASSWORDS 0x0020 // Clears passwords saved for websites - # // #define CLEAR_PHISHING_FILTER 0x0040 // Clears phishing filter data - # // #define CLEAR_RECOVERY_DATA 0x0080 // Clears webpage recovery data - # // #define CLEAR_PRIVACY_ADVISOR 0x0800 // Clears tracking data - # // #define CLEAR_SHOW_NO_GUI 0x0100 // Do not show a GUI when running the cache clearing - # // - # // Bitflags available but not used in this magic value are as follows: - # // #define CLEAR_USE_NO_THREAD 0x0200 // Do not use multithreading for deletion - # // #define CLEAR_PRIVATE_CACHE 0x0400 // Valid only when browser is in private browsing mode - # // #define CLEAR_DELETE_ALL 0x1000 // Deletes data stored by add-ons - # // #define CLEAR_PRESERVE_FAVORITES 0x2000 // Preserves cached data for "favorite" websites - Write-Warning "Note: You may have to close PowerShell and start a new session for clearing the IE cache to take effect." - Start-Process -FilePath 'RunDll32.exe' -ArgumentList 'InetCpl.cpl, ClearMyTracksByProcess 4351' -Wait - $ClearBrowserControlCache = $false - } - - Add-Type -AssemblyName System.Windows.Forms - - $form = New-Object -TypeName System.Windows.Forms.Form -Property @{Width=600;Height=800} - $web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{Width=584;Height=760;Url=($url)} - $DocComp = { - $Global:uri = $web.Url.AbsoluteUri - if ($Global:Uri -match "error=[^&]*|code=[^&]*") {$form.Close() } - } - $web.ScriptErrorsSuppressed = $true - $web.Add_DocumentCompleted($DocComp) - - $form.Controls.Add($web) - $form.Add_Shown({$form.Activate()}) - $form.ShowDialog() | Out-Null - - # Parse Return URL - $queryOutput = [System.Web.HttpUtility]::ParseQueryString($web.Url.Query) - $output = @{} - foreach($key in $queryOutput.Keys){ - $output["$key"] = $queryOutput[$key] - } - - # Dispose Form & IE WebBrowser Control - $web.Dispose() - $form.Dispose() - } default # EdgeWebView2 { # Set EdgeWebView2 Control Version to Use @@ -539,26 +418,26 @@ Function Get-SKYAPINewTokens [CmdletBinding()] Param( [parameter( - Position=0, - Mandatory=$false, - ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true)] - [string]$sky_api_tokens_file_path, + Position=0, + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true)] + [string]$sky_api_tokens_file_path, [parameter( - Position=1, - Mandatory=$false, - ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true)] - [ValidateSet('','EdgeWebView2','MiniHTTPServer','LegacyIEControl')] # Allows null to be passed - [string]$AuthenticationMethod, + Position=1, + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true)] + [ValidateSet('','EdgeWebView2')] # Allows null to be passed + [string]$AuthenticationMethod, [parameter( - Position=2, - Mandatory=$false, - ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true)] - [switch]$ClearBrowserControlCache + Position=2, + Mandatory=$false, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true)] + [switch]$ClearBrowserControlCache ) # Set the Necessary Config Variables @@ -600,9 +479,56 @@ Function Get-SKYAPINewTokens | Out-File -FilePath $sky_api_tokens_file_path -Force } -# Handle Common Errors > https://developer.blackbaud.com/skyapi/docs/resources/in-depth-topics/handle-common-errors -function SKYAPICatchInvokeErrors($InvokeErrorMessageRaw) +function Get-ExponentialBackoffDelay +{ + [CmdletBinding()] + Param( + [parameter( + Position=0, + Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true)] + [int]$InitialDelay, + + [parameter( + Position=1, + Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true)] + [int]$InvokeCount + ) + + # Return the delay time. + return ($InitialDelay * [Math]::Pow(2, $InvokeCount - 1)) # Initial delay times 2 to the power of $InvokeCount minus 1. +} + +# Handle Common Errors > https://developer.blackbaud.com/skyapi/docs/in-depth-topics/handle-common-errors +function SKYAPICatchInvokeErrors { + [CmdletBinding()] + Param( + [parameter( + Position=0, + Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true)] + $InvokeErrorMessageRaw, + + [parameter( + Position=1, + Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true)] + [int]$InvokeCount, + + [parameter( + Position=2, + Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true)] + [int]$MaxInvokeCount + ) + # Convert From JSON try { @@ -637,64 +563,124 @@ function SKYAPICatchInvokeErrors($InvokeErrorMessageRaw) # Try and handle the error message. Switch ($StatusCodeorError) { - invalid_client # You usually see this error when providing an invalid . + invalid_client # You usually, but not always, see this error when providing an invalid client id. { # We will display the error, try again and handle the issue later. Write-Warning $InvokeErrorMessageRaw + + # Check if we've hit the max invoke count and if so, throw the error. + if ($InvokeCount -ge $MaxInvokeCount) + { + throw $InvokeErrorMessageRaw + } + 'retry' } invalid_grant # You usually, but not always, see this error when providing an invalid, expired, or previously used authorization code. { # We will display the error, try again and handle the issue later. Write-Warning $InvokeErrorMessageRaw + + # Check if we've hit the max invoke count and if so, throw the error. + if ($InvokeCount -ge $MaxInvokeCount) + { + throw $InvokeErrorMessageRaw + } + 'retry' } - 400 # Bad request. Usually means that data in the initial request is invalid or improperly formatted. + 400 # Bad Request. Usually means that data in the initial request is invalid or improperly formatted. { throw $InvokeErrorMessageRaw } - 401 # Unauthorized Request. Could mean that the authenticated user does not have rights to access the requested data or does not have permission to edit a given record or record type. An unauthorized request also occurs if the authorization token expires or if the authorization header is not supplied. + 401 # Unauthorized. Could mean that the authenticated user does not have rights to access the requested data or does not have permission to edit a given record or record type. An unauthorized request also occurs if the authorization token expires or if the authorization header is not supplied. { + # Check if we've hit the max invoke count and if so, throw the error. + if ($InvokeCount -ge $MaxInvokeCount) + { + throw $InvokeErrorMessageRaw + } + # This can happens if the token has expired so we will try to refresh and then run the invoke again. Connect-SKYAPI -ForceRefresh 'retry' } - 429 # Rate limit is exceeded. Try again in 1 seconds. Technically, the number of seconds is returned in the 'Retry-After' header, but I think it's best not to wait longer. + 403 # Forbidden. The request failed because the user in whose context the API is being called either does not have permission to perform the operation itself, or does not have permission to access the data being requested. You may also see this response when the API quota associated with your subscription has been met. + { + # In addition to 429 rate limits (per second limit), SKY API also employs a quota limit to manage API traffic over a broader period of time. If this this limit is reached, requests return the 403 (Forbidden) status code with retry-after headers that indicate how long to wait before retrying an API request. Similar to the 429 responses, it is recommended to wait and retry after the time period in the retry-after header. + # TODO: Check for '403 - Quota Exceeded' response from the API because this is a different type of 403 error and means the broad period (as opposed to per-second) quota is exceeded and not a real "Forbidden" error. + throw $InvokeErrorMessageRaw + } + 404 # Not Found. The requested resource could not be found. You may be trying to access a record that does not exist, or you may have supplied an invalid URL. + { + throw $InvokeErrorMessageRaw + } + 415 # Unsupported Media Type. The request failed because the correct Content-Type header was not provided on the request. For endpoints that accept JSON in the request body, you must use the Content-Type header application/json. { - # Sleep for 1 second and return the try command. + throw $InvokeErrorMessageRaw + } + 429 # Too Many Requests. Rate limit is exceeded. Try again in 1 seconds. Technically, the number of seconds is returned in the 'Retry-After' header, but the standard throttle is 10 calls per second. See: https://developer.blackbaud.com/skyapi/docs/in-depth-topics/api-request-throttling + { + # Check if we've hit the max invoke count and if so, throw the error. + if ($InvokeCount -ge $MaxInvokeCount) + { + throw $InvokeErrorMessageRaw + } + + # Sleep for 1 second and return the retry action command. Start-Sleep -Seconds 1 'retry' } - 500 # Internal Server Error. + 500 # Internal Server Error. An unexpected error has occurred on the SKY API side. You should never receive this response, but if you do let Blackbaud Support know. { - # Sleep for 5 seconds and return the try command. I don't know if this is a good length, but it seems reasonable since we try 5 times before failing. - # The other option would be to use the exponential backoff method where You can periodically retry a failed request over an increasing amount of time to handle errors - # related to rate limits, network volume, or response time. For example, you might retry a failed request after one second, then after two seconds, and then after four seconds. - Start-Sleep -Seconds 5 + # Check if we've hit the max invoke count and if so, throw the error. + if ($InvokeCount -ge $MaxInvokeCount) + { + throw $InvokeErrorMessageRaw + } + + # Exponential backoff + $SleepTime = Get-ExponentialBackoffDelay -InitialDelay 5 -InvokeCount $InvokeCount + Start-Sleep -Seconds $SleepTime 'retry' } - 503 # The service is currently unavailable. + 503 # Service Unavailable. The service is currently unavailable. One or more API services are not available. This is usually a temporary condition caused by an unexpected outage or due to planned downtime. Check the Issues page (https://status.blackbaud.com/?svcid=skydev) for more information. { - # Sleep for 5 seconds and return the try command. I don't know if this is a good length, but it seems reasonable since we try 5 times before failing. - # The other option would be to use the exponential backoff method where You can periodically retry a failed request over an increasing amount of time to handle errors - # related to rate limits, network volume, or response time. For example, you might retry a failed request after one second, then after two seconds, and then after four seconds. - Start-Sleep -Seconds 5 + # Check if we've hit the max invoke count and if so, throw the error. + if ($InvokeCount -ge $MaxInvokeCount) + { + throw $InvokeErrorMessageRaw + } + + # Exponential backoff + $SleepTime = Get-ExponentialBackoffDelay -InitialDelay 5 -InvokeCount $InvokeCount + Start-Sleep -Seconds $SleepTime 'retry' } 504 # Gateway Time-out. { - # Sleep for 5 seconds and return the try command. I don't know if this is a good length, but it seems reasonable since we try 5 times before failing. - # The other option would be to use the exponential backoff method where You can periodically retry a failed request over an increasing amount of time to handle errors - # related to rate limits, network volume, or response time. For example, you might retry a failed request after one second, then after two seconds, and then after four seconds. - Start-Sleep -Seconds 5 + # Check if we've hit the max invoke count and if so, throw the error. + if ($InvokeCount -ge $MaxInvokeCount) + { + throw $InvokeErrorMessageRaw + } + + # Exponential backoff + $SleepTime = Get-ExponentialBackoffDelay -InitialDelay 5 -InvokeCount $InvokeCount + Start-Sleep -Seconds $SleepTime 'retry' } - 'An exception occured. Please contact Support.' # Random exception. Often transient. + 'An exception occurred. Please contact Support.' # Random exception. Often transient. { - # Sleep for 5 seconds and return the try command. I don't know if this is a good length, but it seems reasonable since we try 5 times before failing. - # The other option would be to use the exponential backoff method where You can periodically retry a failed request over an increasing amount of time to handle errors - # related to rate limits, network volume, or response time. For example, you might retry a failed request after one second, then after two seconds, and then after four seconds. - Start-Sleep -Seconds 5 + # Check if we've hit the max invoke count and if so, throw the error. + if ($InvokeCount -ge $MaxInvokeCount) + { + throw $InvokeErrorMessageRaw + } + + # Exponential backoff + $SleepTime = Get-ExponentialBackoffDelay -InitialDelay 5 -InvokeCount $InvokeCount + Start-Sleep -Seconds $SleepTime 'retry' } default @@ -737,9 +723,14 @@ Function Get-SKYAPIUnpagedEntity $Request.Query = $params.ToString() } + # Disable Progress Bar in Function Scope When Calling Invoke-WebRequest or Invoke-RestMethod. + # This improves performance due to a bug in some versions of PowerShell. It was eventually fixed in Core (v6.0.0-alpha.13) but still is around in Desktop. + # More Information: https://github.com/PowerShell/PowerShell/pull/2640 + $ProgressPreference = 'SilentlyContinue' + # Run Invoke Command and Catch Responses [int]$InvokeCount = 0 - [int]$MaxInvokeCount = 5 + [int]$MaxInvokeCount = 7 do { $InvokeCount += 1 @@ -784,7 +775,7 @@ Function Get-SKYAPIUnpagedEntity { # Process Invoke Error $LastCaughtError = ($_) - $NextAction = SKYAPICatchInvokeErrors($_) + $NextAction = SKYAPICatchInvokeErrors -InvokeErrorMessageRaw $_ -InvokeCount $InvokeCount -MaxInvokeCount $MaxInvokeCount # Just in case the token was refreshed by the error catcher, update these $AuthTokensFromFile = Get-SKYAPIAuthTokensFromFile @@ -836,9 +827,14 @@ Function Get-SKYAPIPagedEntity # Create records array $allRecords = @() + # Disable Progress Bar in Function Scope When Calling Invoke-WebRequest or Invoke-RestMethod. + # This improves performance due to a bug in some versions of PowerShell. It was eventually fixed in Core (v6.0.0-alpha.13) but still is around in Desktop. + # More Information: https://github.com/PowerShell/PowerShell/pull/2640 + $ProgressPreference = 'SilentlyContinue' + # Run Invoke Command and Catch Responses [int]$InvokeCount = 0 - [int]$MaxInvokeCount = 5 + [int]$MaxInvokeCount = 7 do { $InvokeCount += 1 @@ -917,7 +913,7 @@ Function Get-SKYAPIPagedEntity { # Process Invoke Error $LastCaughtError = ($_) - $NextAction = SKYAPICatchInvokeErrors($_) + $NextAction = SKYAPICatchInvokeErrors -InvokeErrorMessageRaw $_ -InvokeCount $InvokeCount -MaxInvokeCount $MaxInvokeCount # Just in case the token was refreshed by the error catcher, update these $AuthTokensFromFile = Get-SKYAPIAuthTokensFromFile @@ -958,10 +954,15 @@ Function Remove-SKYAPIEntity if ($null -ne $params -and $params -ne '') { $Request.Query = $params.ToString() } - + + # Disable Progress Bar in Function Scope When Calling Invoke-WebRequest or Invoke-RestMethod. + # This improves performance due to a bug in some versions of PowerShell. It was eventually fixed in Core (v6.0.0-alpha.13) but still is around in Desktop. + # More Information: https://github.com/PowerShell/PowerShell/pull/2640 + $ProgressPreference = 'SilentlyContinue' + # Run Invoke Command and Catch Responses [int]$InvokeCount = 0 - [int]$MaxInvokeCount = 5 + [int]$MaxInvokeCount = 7 do { $InvokeCount += 1 @@ -991,7 +992,7 @@ Function Remove-SKYAPIEntity { # Process Invoke Error $LastCaughtError = ($_) - $NextAction = SKYAPICatchInvokeErrors($_) + $NextAction = SKYAPICatchInvokeErrors -InvokeErrorMessageRaw $_ -InvokeCount $InvokeCount -MaxInvokeCount $MaxInvokeCount # Just in case the token was refreshed by the error catcher, update these $AuthTokensFromFile = Get-SKYAPIAuthTokensFromFile @@ -1032,9 +1033,14 @@ function Submit-SKYAPIEntity # Build Body $PostRequest = $params | ConvertTo-Json + # Disable Progress Bar in Function Scope When Calling Invoke-WebRequest or Invoke-RestMethod. + # This improves performance due to a bug in some versions of PowerShell. It was eventually fixed in Core (v6.0.0-alpha.13) but still is around in Desktop. + # More Information: https://github.com/PowerShell/PowerShell/pull/2640 + $ProgressPreference = 'SilentlyContinue' + # Run Invoke Command and Catch Responses [int]$InvokeCount = 0 - [int]$MaxInvokeCount = 5 + [int]$MaxInvokeCount = 7 do { $InvokeCount += 1 @@ -1065,7 +1071,7 @@ function Submit-SKYAPIEntity { # Process Invoke Error $LastCaughtError = ($_) - $NextAction = SKYAPICatchInvokeErrors($_) + $NextAction = SKYAPICatchInvokeErrors -InvokeErrorMessageRaw $_ -InvokeCount $InvokeCount -MaxInvokeCount $MaxInvokeCount # Just in case the token was refreshed by the error catcher, update these $AuthTokensFromFile = Get-SKYAPIAuthTokensFromFile @@ -1106,9 +1112,14 @@ function Update-SKYAPIEntity # Build Body $PatchRequest = $params | ConvertTo-Json + # Disable Progress Bar in Function Scope When Calling Invoke-WebRequest or Invoke-RestMethod. + # This improves performance due to a bug in some versions of PowerShell. It was eventually fixed in Core (v6.0.0-alpha.13) but still is around in Desktop. + # More Information: https://github.com/PowerShell/PowerShell/pull/2640 + $ProgressPreference = 'SilentlyContinue' + # Run Invoke Command and Catch Responses [int]$InvokeCount = 0 - [int]$MaxInvokeCount = 5 + [int]$MaxInvokeCount = 7 do { $InvokeCount += 1 @@ -1139,7 +1150,7 @@ function Update-SKYAPIEntity { # Process Invoke Error $LastCaughtError = ($_) - $NextAction = SKYAPICatchInvokeErrors($_) + $NextAction = SKYAPICatchInvokeErrors -InvokeErrorMessageRaw $_ -InvokeCount $InvokeCount -MaxInvokeCount $MaxInvokeCount # Just in case the token was refreshed by the error catcher, update these $AuthTokensFromFile = Get-SKYAPIAuthTokensFromFile diff --git a/Sample_Usage_Scripts/@SKYAPI Module/SKYAPI_Examples.ps1 b/Sample_Usage_Scripts/@SKYAPI Module/SKYAPI_Examples.ps1 index a011148..6224005 100644 --- a/Sample_Usage_Scripts/@SKYAPI Module/SKYAPI_Examples.ps1 +++ b/Sample_Usage_Scripts/@SKYAPI Module/SKYAPI_Examples.ps1 @@ -37,14 +37,11 @@ "AuthenticationMethod" parameter let's you specify how you want to authenticate if authentication is necessary: - EdgeWebView2 (default): Opens a web browser window using Microsoft Edge WebView2 for authentication. Requires the WebView2 Runtime to be installed. If not installed, will prompt for automatic installation. - - LegacyIEControl: Opens a web browser window using the old Internet Explorer control. This is no longer supported by Blackbaud. - - MiniHTTPServer: Alternate method of capturing the authentication using your user account's default web browser - and listening for the authentication response using a temporary HTTP server hosted by the module. #> # Connect-SKYAPI # Connect-SKYAPI -ForceReauthentication # Connect-SKYAPI -ForceReauthentication -ClearBrowserControlCache -# Connect-SKYAPI -ForceReauthentication -AuthenticationMethod MiniHTTPServer +# Connect-SKYAPI -ForceReauthentication -AuthenticationMethod EdgeWebView2 # Connect-SKYAPI -ForceRefresh # Connect-SKYAPI -ReturnConnectionInfo @@ -331,7 +328,7 @@ # { # "`n--- Meeting Group ---" # $meeting.group_name -# "--- Meeting Date (School Envirionment Time Zone) ---" +# "--- Meeting Date (School Environment Time Zone) ---" # $meeting.meeting_date # "--- Start & End (Local Time) ---" # $meeting.start_time.ToLocalTime().DateTime # DateTime Kind of 'Local' diff --git a/Sample_Usage_Scripts/Blackbaud SIS Teacher Schedules to Google Calendar CSVs/Create Importable Faculty Google Calendar CSVs.ps1 b/Sample_Usage_Scripts/Blackbaud SIS Teacher Schedules to Google Calendar CSVs/Create Importable Faculty Google Calendar CSVs.ps1 index 4974b8e..44b85e1 100644 --- a/Sample_Usage_Scripts/Blackbaud SIS Teacher Schedules to Google Calendar CSVs/Create Importable Faculty Google Calendar CSVs.ps1 +++ b/Sample_Usage_Scripts/Blackbaud SIS Teacher Schedules to Google Calendar CSVs/Create Importable Faculty Google Calendar CSVs.ps1 @@ -2,7 +2,7 @@ # OVERVIEW # ############ -# Creates importable Google Calendar schedules for faculty from the Blackbaud School Envirionment. +# Creates importable Google Calendar schedules for faculty from the Blackbaud School Environment. # Outputs CSV files, one for each teacher. # Teachers can manually import as needed. Throw them in a shared Google Drive folder or somewhere else for easy access. @@ -30,6 +30,9 @@ $StartDate = '2023-01-01' # Format as YYYY-MM-DD. $EndDate = '2023-06-30' # Format as YYYY-MM-DD. $OfferingTypes = '1,3' # Defaults to 1 (Academics) if not specified. Use 'Get-SchoolOfferingType' to get a list of offering types. +# Import Meetings To Ignore Settings +$MeetingsToIgnore = Get-Content -Path "$PSScriptRoot\meetings_to_ignore.json" | ConvertFrom-Json + ################################# # DO NOT MODIFY BELOW THIS LINE # ################################# @@ -74,11 +77,22 @@ $HashArguments = @{ } $Meetings = Get-SchoolScheduleMeeting @HashArguments +# Remove Meetings That Should Be Ignored +[array]$MeetingsFilterProperties = ($MeetingsToIgnore | Get-Member -MemberType NoteProperty).Name +foreach ($meetingsFilterProperty in $MeetingsFilterProperties) +{ + $MeetingsFilterPropertyValues = $MeetingsToIgnore.($meetingsFilterProperty) + foreach ($meetingsFilterPropertyValue in $MeetingsFilterPropertyValues) + { + $Meetings = $Meetings | Where-Object -Property $($meetingsFilterProperty) -NotMatch $meetingsFilterPropertyValue + } +} + +# Get Teachers With Meetings [array]$Teachers = foreach ($meeting in $Meetings) { [PSCustomObject]@{faculty_user_id = $meeting.faculty_user_id; faculty_name = $meeting.faculty_name} } - $Teachers = $Teachers | Sort-Object -Property faculty_name -Unique # Create Events CSV for Each Returned Teacher diff --git a/Sample_Usage_Scripts/Blackbaud SIS Teacher Schedules to Google Calendar CSVs/meetings_to_ignore.json b/Sample_Usage_Scripts/Blackbaud SIS Teacher Schedules to Google Calendar CSVs/meetings_to_ignore.json new file mode 100644 index 0000000..53d559a --- /dev/null +++ b/Sample_Usage_Scripts/Blackbaud SIS Teacher Schedules to Google Calendar CSVs/meetings_to_ignore.json @@ -0,0 +1,6 @@ +{ + "group_name": [ + "Homeroom", + "Ignore This Event" + ] +} \ No newline at end of file diff --git a/Sample_Usage_Scripts/Blackbaud SIS Teacher Schedules to ICS/Create Importable Faculty Calendar ICSs.ps1 b/Sample_Usage_Scripts/Blackbaud SIS Teacher Schedules to ICS/Create Importable Faculty Calendar ICSs.ps1 index 9a0abb3..5d1f2f7 100644 --- a/Sample_Usage_Scripts/Blackbaud SIS Teacher Schedules to ICS/Create Importable Faculty Calendar ICSs.ps1 +++ b/Sample_Usage_Scripts/Blackbaud SIS Teacher Schedules to ICS/Create Importable Faculty Calendar ICSs.ps1 @@ -2,7 +2,7 @@ # OVERVIEW # ############ -# Creates importable ICS Calendar schedules for faculty from the Blackbaud School Envirionment. +# Creates importable ICS Calendar schedules for faculty from the Blackbaud School Environment. # Outputs ICS files, one for each teacher. # Teachers can manually import as needed. Throw them in a shared folder or somewhere else for easy access. diff --git a/Tests/TestAPICallErrors_CustomJSON.ps1 b/Tests/TestAPICallErrors_CustomJSON.ps1 new file mode 100644 index 0000000..eba1055 --- /dev/null +++ b/Tests/TestAPICallErrors_CustomJSON.ps1 @@ -0,0 +1,39 @@ +# CODE FOR TESTING SKYAPI Module Error Handling With Custom JSON + +$ErrorActionPreference = "Stop" + +# Import the module +# Normally this module would be installed and your command would simply be: +# Import-Module SKYAPI +Import-Module "$PSScriptRoot\..\SKYAPI\SKYAPI.psm1" + +# Set custom properties +Set-SKYAPIConfigFilePath -Path "$PSScriptRoot\sky_api_config.json" # The location where you placed your Blackbaud SKY API configuration file. +Set-SKYAPITokensFilePath -Path "$env:USERPROFILE\SKYAPI\skyapi_key.json" # The location where you want the access and refresh tokens to be stored. + +# Stop on Errors +$ErrorActionPreference = "Stop" + +# Connect to Blackbaud SKY API +Connect-SKYAPI + +$JSONError = @" +{ + "errors": { + "Message": "Error converting value \"panda\" to type 'FuzzyDate'. Path 'birthdate' line 7, position 26.", + "error_code": 500, + "RawMessage": "Error converting value \"panda\" to type 'FuzzyDate'. Path 'birthdate' line 7, position 26." + } +} +"@ + +# Error Testing +$InvokeErrorMessageRaw = [PSCustomObject]@{ + 'KeyTesting' = 1 # Need at least one non-null key to create a hash object. + 'ErrorDetails' = [PSCustomObject]@{ + 'Message' = $JSONError + } +} + +# Invoke Error Testing +SKYAPICatchInvokeErrors -InvokeErrorMessageRaw $ErrorTestRawObject -InvokeCount 1 -MaxInvokeCount 7 \ No newline at end of file diff --git a/Tests/TestAPICallErrors.ps1 b/Tests/TestAPICallErrors_RateLimit.ps1 similarity index 100% rename from Tests/TestAPICallErrors.ps1 rename to Tests/TestAPICallErrors_RateLimit.ps1