From d113d7438799b1ee9dfd968447f306962661436c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B6tz=20Jensen?= Date: Mon, 14 Mar 2022 13:48:19 +0100 Subject: [PATCH 1/4] Version: 0.4.36 --- d365fo.integrations/d365fo.integrations.psd1 | 2 +- d365fo.integrations/d365fo.integrations.psm1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/d365fo.integrations/d365fo.integrations.psd1 b/d365fo.integrations/d365fo.integrations.psd1 index da7ff08..9c7dfe6 100644 --- a/d365fo.integrations/d365fo.integrations.psd1 +++ b/d365fo.integrations/d365fo.integrations.psd1 @@ -3,7 +3,7 @@ RootModule = 'd365fo.integrations.psm1' # Version number of this module. - ModuleVersion = '0.4.34' + ModuleVersion = '0.4.36' # ID used to uniquely identify this module GUID = 'd2667b62-1436-42b3-a840-ab6b4a0e5aa0' diff --git a/d365fo.integrations/d365fo.integrations.psm1 b/d365fo.integrations/d365fo.integrations.psm1 index a8581f9..0ac516c 100644 --- a/d365fo.integrations/d365fo.integrations.psm1 +++ b/d365fo.integrations/d365fo.integrations.psm1 @@ -1,5 +1,5 @@ $script:ModuleRoot = $PSScriptRoot -$script:ModuleVersion = '0.4.34' +$script:ModuleVersion = '0.4.36' $Script:TimeSignals = @{} From c37f34be1a6157166620835e3fe2510ec71f2741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B6tz=20Jensen?= Date: Thu, 9 Feb 2023 06:59:23 +0100 Subject: [PATCH 2/4] Version: 0.4.36 --- d365fo.integrations/d365fo.integrations.psd1 | 2 +- d365fo.integrations/d365fo.integrations.psm1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/d365fo.integrations/d365fo.integrations.psd1 b/d365fo.integrations/d365fo.integrations.psd1 index c387e35..9c7dfe6 100644 --- a/d365fo.integrations/d365fo.integrations.psd1 +++ b/d365fo.integrations/d365fo.integrations.psd1 @@ -3,7 +3,7 @@ RootModule = 'd365fo.integrations.psm1' # Version number of this module. - ModuleVersion = '0.4.35' + ModuleVersion = '0.4.36' # ID used to uniquely identify this module GUID = 'd2667b62-1436-42b3-a840-ab6b4a0e5aa0' diff --git a/d365fo.integrations/d365fo.integrations.psm1 b/d365fo.integrations/d365fo.integrations.psm1 index 3df187c..0ac516c 100644 --- a/d365fo.integrations/d365fo.integrations.psm1 +++ b/d365fo.integrations/d365fo.integrations.psm1 @@ -1,5 +1,5 @@ $script:ModuleRoot = $PSScriptRoot -$script:ModuleVersion = '0.4.35' +$script:ModuleVersion = '0.4.36' $Script:TimeSignals = @{} From 082fae5abb27016c47dfe3c62a1f6dac105c7491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B6tz=20Jensen?= Date: Thu, 9 Feb 2023 07:07:17 +0100 Subject: [PATCH 3/4] Version: 0.4.38 --- d365fo.integrations/d365fo.integrations.psd1 | 2 +- d365fo.integrations/d365fo.integrations.psm1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/d365fo.integrations/d365fo.integrations.psd1 b/d365fo.integrations/d365fo.integrations.psd1 index 9c7dfe6..834e0b6 100644 --- a/d365fo.integrations/d365fo.integrations.psd1 +++ b/d365fo.integrations/d365fo.integrations.psd1 @@ -3,7 +3,7 @@ RootModule = 'd365fo.integrations.psm1' # Version number of this module. - ModuleVersion = '0.4.36' + ModuleVersion = '0.4.38' # ID used to uniquely identify this module GUID = 'd2667b62-1436-42b3-a840-ab6b4a0e5aa0' diff --git a/d365fo.integrations/d365fo.integrations.psm1 b/d365fo.integrations/d365fo.integrations.psm1 index 0ac516c..c1d10bb 100644 --- a/d365fo.integrations/d365fo.integrations.psm1 +++ b/d365fo.integrations/d365fo.integrations.psm1 @@ -1,5 +1,5 @@ $script:ModuleRoot = $PSScriptRoot -$script:ModuleVersion = '0.4.36' +$script:ModuleVersion = '0.4.38' $Script:TimeSignals = @{} From b0fb5938f33205a4d3efaaab2b6060bed27d109a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=B6tz=20Jensen?= Date: Tue, 14 Feb 2023 06:45:37 +0100 Subject: [PATCH 4/4] Fix: Refactor Invoke-D365RestEndpoint to handle 429 --- .../functions/add-d365odataconfig.ps1 | 18 ---- .../functions/get-d365activeodataconfig.ps1 | 18 ---- .../functions/get-d365odataconfig.ps1 | 18 ---- .../functions/import-d365odataentity.ps1 | 1 - .../functions/invoke-d365restendpoint.ps1 | 78 +++++++++++++++- .../functions/remove-d365odataconfig.ps1 | 62 +++++++++++++ .../Invoke-D365RestEndpoint.Tests.ps1 | 42 +++++++-- docs/Add-D365ODataConfig.md | 13 --- docs/Get-D365ActiveODataConfig.md | 13 --- docs/Get-D365ODataConfig.md | 13 --- docs/Invoke-D365RestEndpoint.md | 93 +++++++++++++++++-- 11 files changed, 257 insertions(+), 112 deletions(-) create mode 100644 d365fo.integrations/functions/remove-d365odataconfig.ps1 diff --git a/d365fo.integrations/functions/add-d365odataconfig.ps1 b/d365fo.integrations/functions/add-d365odataconfig.ps1 index 23e606d..669b6ef 100644 --- a/d365fo.integrations/functions/add-d365odataconfig.ps1 +++ b/d365fo.integrations/functions/add-d365odataconfig.ps1 @@ -55,24 +55,6 @@ Tags: Integrations, Integration, Bearer Token, Token, OData, Configuration Author: Mötz Jensen (@Splaxi) - - .LINK - Clear-D365ActiveBroadcastMessageConfig - - .LINK - Get-D365ActiveBroadcastMessageConfig - - .LINK - Get-D365BroadcastMessageConfig - - .LINK - Remove-D365BroadcastMessageConfig - - .LINK - Send-D365BroadcastMessage - - .LINK - Set-D365ActiveBroadcastMessageConfig #> function Add-D365ODataConfig { diff --git a/d365fo.integrations/functions/get-d365activeodataconfig.ps1 b/d365fo.integrations/functions/get-d365activeodataconfig.ps1 index 86f4e72..a1c7568 100644 --- a/d365fo.integrations/functions/get-d365activeodataconfig.ps1 +++ b/d365fo.integrations/functions/get-d365activeodataconfig.ps1 @@ -22,24 +22,6 @@ Tags: OData, Environment, Config, Configuration, ClientId, ClientSecret Author: Mötz Jensen (@Splaxi) - - .LINK - Add-D365BroadcastMessageConfig - - .LINK - Clear-D365ActiveBroadcastMessageConfig - - .LINK - Get-D365BroadcastMessageConfig - - .LINK - Remove-D365BroadcastMessageConfig - - .LINK - Send-D365BroadcastMessage - - .LINK - Set-D365ActiveBroadcastMessageConfig #> function Get-D365ActiveODataConfig { diff --git a/d365fo.integrations/functions/get-d365odataconfig.ps1 b/d365fo.integrations/functions/get-d365odataconfig.ps1 index aaf005b..eb000a1 100644 --- a/d365fo.integrations/functions/get-d365odataconfig.ps1 +++ b/d365fo.integrations/functions/get-d365odataconfig.ps1 @@ -40,24 +40,6 @@ Tags: OData, Environment, Config, Configuration, ClientId, ClientSecret Author: Mötz Jensen (@Splaxi) - - .LINK - Add-D365BroadcastMessageConfig - - .LINK - Clear-D365ActiveBroadcastMessageConfig - - .LINK - Get-D365ActiveBroadcastMessageConfig - - .LINK - Remove-D365BroadcastMessageConfig - - .LINK - Send-D365BroadcastMessage - - .LINK - Set-D365ActiveBroadcastMessageConfig #> function Get-D365ODataConfig { diff --git a/d365fo.integrations/functions/import-d365odataentity.ps1 b/d365fo.integrations/functions/import-d365odataentity.ps1 index 88ba887..2962465 100644 --- a/d365fo.integrations/functions/import-d365odataentity.ps1 +++ b/d365fo.integrations/functions/import-d365odataentity.ps1 @@ -246,7 +246,6 @@ function Import-D365ODataEntity { Invoke-RequestHandler -Method POST -Uri $odataEndpoint.Uri.AbsoluteUri -Headers $headers -ContentType "application/json;charset=$PayloadCharset" -Payload $Payload -RetryTimeout $RetryTimeout if (Test-PSFFunctionInterrupt) { return } - } catch { $messageString = "Something went wrong while importing data through the OData endpoint for the entity: $EntityName" diff --git a/d365fo.integrations/functions/invoke-d365restendpoint.ps1 b/d365fo.integrations/functions/invoke-d365restendpoint.ps1 index 84f3e11..0a7c5dd 100644 --- a/d365fo.integrations/functions/invoke-d365restendpoint.ps1 +++ b/d365fo.integrations/functions/invoke-d365restendpoint.ps1 @@ -32,6 +32,30 @@ The charset has to be a valid http charset like: ASCII, ANSI, ISO-8859-1, UTF-8 + .PARAMETER RetryTimeout + The retry timeout, before the cmdlet should quit retrying based on the 429 status code + + Needs to be provided in the timspan notation: + "hh:mm:ss" + + hh is the number of hours, numerical notation only + mm is the number of minutes + ss is the numbers of seconds + + Each section of the timeout has to valid, e.g. + hh can maximum be 23 + mm can maximum be 59 + ss can maximum be 59 + + Not setting this parameter will result in the cmdlet to try for ever to handle the 429 push back from the endpoint + + .PARAMETER ThrottleSeed + Instruct the cmdlet to invoke a thread sleep between 1 and ThrottleSeed value + + This is to help to mitigate the 429 retry throttling on the OData / Custom Service endpoints + + It makes most sense if you are running things a outer loop, where you will hit the OData / Custom Service endpoints with a burst of calls in a short time + .PARAMETER Tenant Azure Active Directory (AAD) tenant id (Guid) that the D365FO environment is connected to, that you want to access through REST endpoint @@ -89,6 +113,26 @@ The ServiceName used for the import is "UserSessionService/AifUserSessionService/GetUserSessionInfo". The Payload is a valid json string, containing all the needed properties. + .EXAMPLE + PS C:\> $Payload = '{"RateTypeName": "TEST", "FromCurrency": "DKK", "ToCurrency": "EUR", "StartDate": "2019-01-03T00:00:00Z", "Rate": 745.10, "ConversionFactor": "Hundred", "RateTypeDescription": "TEST"}' + PS C:\> Invoke-D365RestEndpoint -ServiceName "UserSessionService/AifUserSessionService/GetUserSessionInfo" -Payload $Payload -RetryTimeout "00:01:00" + + This will invoke the REST endpoint in the Dynamics 365 Finance & Operations environment, and try for 1 minute to handle 429. + First the desired json data is put into the $Payload variable. + The ServiceName used for the import is "UserSessionService/AifUserSessionService/GetUserSessionInfo". + The $Payload variable is passed to the cmdlet. + It will only try to handle 429 retries for 1 minute, before failing. + + .EXAMPLE + PS C:\> $Payload = '{"RateTypeName": "TEST", "FromCurrency": "DKK", "ToCurrency": "EUR", "StartDate": "2019-01-03T00:00:00Z", "Rate": 745.10, "ConversionFactor": "Hundred", "RateTypeDescription": "TEST"}' + PS C:\> Invoke-D365RestEndpoint -ServiceName "UserSessionService/AifUserSessionService/GetUserSessionInfo" -Payload $Payload -ThrottleSeed 2 + + This will invoke the REST endpoint in the Dynamics 365 Finance & Operations environment, and sleep/pause between 1 and 2 seconds. + First the desired json data is put into the $Payload variable. + The ServiceName used for the import is "UserSessionService/AifUserSessionService/GetUserSessionInfo". + The $Payload variable is passed to the cmdlet. + It will use the ThrottleSeed 2 to sleep/pause the execution, to mitigate the 429 pushback from the endpoint. + .NOTES Tags: REST, Endpoint, Custom Service, Services @@ -107,6 +151,10 @@ function Invoke-D365RestEndpoint { [string] $PayloadCharset = "UTF-8", + [Timespan] $RetryTimeout = "00:00:00", + + [int] $ThrottleSeed, + [Alias('$AadGuid')] [string] $Tenant = $Script:ODataTenant, @@ -130,6 +178,28 @@ function Invoke-D365RestEndpoint { ) begin { + if ([System.String]::IsNullOrEmpty($SystemUrl)) { + Write-PSFMessage -Level Verbose -Message "The SystemUrl parameter was empty, using the Url parameter as the OData endpoint base address." -Target $SystemUrl + $SystemUrl = $Url + } + + if ([System.String]::IsNullOrEmpty($Url) -or [System.String]::IsNullOrEmpty($SystemUrl)) { + $messageString = "It seems that you didn't supply a valid value for the Url parameter. You need specify the Url parameter or add a configuration with the Add-D365ODataConfig cmdlet." + Write-PSFMessage -Level Host -Message $messageString -Exception $PSItem.Exception -Target $entityName + Stop-PSFFunction -Message "Stopping because of errors." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', ''))) -ErrorRecord $_ + return + } + + if ($Url.Substring($Url.Length - 1) -eq "/") { + Write-PSFMessage -Level Verbose -Message "The Url parameter had a tailing slash, which shouldn't be there. Removing the tailling slash." -Target $Url + $Url = $Url.Substring(0, $Url.Length - 1) + } + + if ($SystemUrl.Substring($SystemUrl.Length - 1) -eq "/") { + Write-PSFMessage -Level Verbose -Message "The SystemUrl parameter had a tailing slash, which shouldn't be there. Removing the tailling slash." -Target $Url + $SystemUrl = $SystemUrl.Substring(0, $SystemUrl.Length - 1) + } + if (-not $Token) { $bearerParms = @{ Url = $Url @@ -186,7 +256,9 @@ function Invoke-D365RestEndpoint { try { Write-PSFMessage -Level Verbose -Message "Executing http request against the REST endpoint." -Target $($restEndpoint.Uri.AbsoluteUri) - Invoke-RestMethod @params + Invoke-RequestHandler -Method POST -Uri $restEndpoint.Uri.AbsoluteUri -Headers $headers -ContentType "application/json;charset=$PayloadCharset" -Payload $Payload -RetryTimeout $RetryTimeout + + if (Test-PSFFunctionInterrupt) { return } } catch { $messageString = "Something went wrong while importing data through the REST endpoint for the entity: $ServiceName" @@ -195,6 +267,10 @@ function Invoke-D365RestEndpoint { return } + if ($ThrottleSeed) { + Start-Sleep -Seconds $(Get-Random -Minimum 1 -Maximum $ThrottleSeed) + } + Invoke-TimeSignal -End } } \ No newline at end of file diff --git a/d365fo.integrations/functions/remove-d365odataconfig.ps1 b/d365fo.integrations/functions/remove-d365odataconfig.ps1 new file mode 100644 index 0000000..df49dad --- /dev/null +++ b/d365fo.integrations/functions/remove-d365odataconfig.ps1 @@ -0,0 +1,62 @@ + +<# + .SYNOPSIS + Remove an OData config + + .DESCRIPTION + Removes an OData config from the configuration store + + .PARAMETER Name + The name of the OData configuration you are about to remove from the configuration store + + .PARAMETER Temporary + Instruct the cmdlet to only temporarily remove the OData configuration from the configuration store + + .EXAMPLE + PS C:\> Remove-D365ODataConfig -Name "UAT" + + This will create an new OData configuration with the name "UAT". + + .NOTES + Tags: Integrations, Integration, Bearer Token, Token, OData, Configuration + + Author: Mötz Jensen (@Splaxi) +#> + +function Remove-D365ODataConfig { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string] $Name, + + [switch] $Temporary + ) + + $Name = $Name.ToLower() + + if ($Name -match '\*') { + Write-PSFMessage -Level Host -Message "The name cannot contain wildcard character." + Stop-PSFFunction -Message "Stopping because the name contains wildcard character." + return + } + + if (-not ((Get-PSFConfig -FullName "d365fo.integrations.odata.*.name").Value -contains $Name)) { + Write-PSFMessage -Level Host -Message "An OData configuration with that name doesn't exists." + Stop-PSFFunction -Message "Stopping because an OData configuration with that name doesn't exists." + return + } + + $res = (Get-PSFConfig -FullName "d365fo.integrations.active.odata.config.name").Value + + if ($res -eq $Name) { + Write-PSFMessage -Level Host -Message "The active OData configuration is the same as the one you're trying to remove. Please set another configuration as active, before removing this one." + Stop-PSFFunction -Message "Stopping because the active OData configuration is the same as the one trying to be removed." + return + } + + foreach ($config in Get-PSFConfig -FullName "d365fo.integrations.odata.$Name.*") { + Set-PSFConfig -FullName $config.FullName -Value "" + + if (-not $Temporary) { Unregister-PSFConfig -FullName $config.FullName -Scope UserDefault } + } +} \ No newline at end of file diff --git a/d365fo.integrations/tests/functions/Invoke-D365RestEndpoint.Tests.ps1 b/d365fo.integrations/tests/functions/Invoke-D365RestEndpoint.Tests.ps1 index 70f713f..55f2481 100644 --- a/d365fo.integrations/tests/functions/Invoke-D365RestEndpoint.Tests.ps1 +++ b/d365fo.integrations/tests/functions/Invoke-D365RestEndpoint.Tests.ps1 @@ -50,6 +50,32 @@ $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False } + It 'Should have the expected parameter RetryTimeout' { + $parameter = (Get-Command Invoke-D365RestEndpoint).Parameters['RetryTimeout'] + $parameter.Name | Should -Be 'RetryTimeout' + $parameter.ParameterType.ToString() | Should -Be System.TimeSpan + $parameter.IsDynamic | Should -Be $False + $parameter.ParameterSets.Keys | Should -Be '__AllParameterSets' + $parameter.ParameterSets.Keys | Should -Contain '__AllParameterSets' + $parameter.ParameterSets['__AllParameterSets'].IsMandatory | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 3 + $parameter.ParameterSets['__AllParameterSets'].ValueFromPipeline | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False + } + It 'Should have the expected parameter ThrottleSeed' { + $parameter = (Get-Command Invoke-D365RestEndpoint).Parameters['ThrottleSeed'] + $parameter.Name | Should -Be 'ThrottleSeed' + $parameter.ParameterType.ToString() | Should -Be System.Int32 + $parameter.IsDynamic | Should -Be $False + $parameter.ParameterSets.Keys | Should -Be '__AllParameterSets' + $parameter.ParameterSets.Keys | Should -Contain '__AllParameterSets' + $parameter.ParameterSets['__AllParameterSets'].IsMandatory | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 4 + $parameter.ParameterSets['__AllParameterSets'].ValueFromPipeline | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False + $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False + } It 'Should have the expected parameter Tenant' { $parameter = (Get-Command Invoke-D365RestEndpoint).Parameters['Tenant'] $parameter.Name | Should -Be 'Tenant' @@ -58,7 +84,7 @@ $parameter.ParameterSets.Keys | Should -Be '__AllParameterSets' $parameter.ParameterSets.Keys | Should -Contain '__AllParameterSets' $parameter.ParameterSets['__AllParameterSets'].IsMandatory | Should -Be $False - $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 3 + $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 5 $parameter.ParameterSets['__AllParameterSets'].ValueFromPipeline | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False @@ -71,7 +97,7 @@ $parameter.ParameterSets.Keys | Should -Be '__AllParameterSets' $parameter.ParameterSets.Keys | Should -Contain '__AllParameterSets' $parameter.ParameterSets['__AllParameterSets'].IsMandatory | Should -Be $False - $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 4 + $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 6 $parameter.ParameterSets['__AllParameterSets'].ValueFromPipeline | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False @@ -84,7 +110,7 @@ $parameter.ParameterSets.Keys | Should -Be '__AllParameterSets' $parameter.ParameterSets.Keys | Should -Contain '__AllParameterSets' $parameter.ParameterSets['__AllParameterSets'].IsMandatory | Should -Be $False - $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 5 + $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 7 $parameter.ParameterSets['__AllParameterSets'].ValueFromPipeline | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False @@ -97,7 +123,7 @@ $parameter.ParameterSets.Keys | Should -Be '__AllParameterSets' $parameter.ParameterSets.Keys | Should -Contain '__AllParameterSets' $parameter.ParameterSets['__AllParameterSets'].IsMandatory | Should -Be $False - $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 6 + $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 8 $parameter.ParameterSets['__AllParameterSets'].ValueFromPipeline | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False @@ -110,7 +136,7 @@ $parameter.ParameterSets.Keys | Should -Be '__AllParameterSets' $parameter.ParameterSets.Keys | Should -Contain '__AllParameterSets' $parameter.ParameterSets['__AllParameterSets'].IsMandatory | Should -Be $False - $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 7 + $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 9 $parameter.ParameterSets['__AllParameterSets'].ValueFromPipeline | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False @@ -123,7 +149,7 @@ $parameter.ParameterSets.Keys | Should -Be '__AllParameterSets' $parameter.ParameterSets.Keys | Should -Contain '__AllParameterSets' $parameter.ParameterSets['__AllParameterSets'].IsMandatory | Should -Be $False - $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 8 + $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 10 $parameter.ParameterSets['__AllParameterSets'].ValueFromPipeline | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False @@ -149,7 +175,7 @@ $parameter.ParameterSets.Keys | Should -Be '__AllParameterSets' $parameter.ParameterSets.Keys | Should -Contain '__AllParameterSets' $parameter.ParameterSets['__AllParameterSets'].IsMandatory | Should -Be $False - $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 9 + $parameter.ParameterSets['__AllParameterSets'].Position | Should -Be 11 $parameter.ParameterSets['__AllParameterSets'].ValueFromPipeline | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromPipelineByPropertyName | Should -Be $False $parameter.ParameterSets['__AllParameterSets'].ValueFromRemainingArguments | Should -Be $False @@ -159,7 +185,7 @@ Describe "Testing parameterset __AllParameterSets" { <# __AllParameterSets -ServiceName - __AllParameterSets -ServiceName -Payload -PayloadCharset -Tenant -Url -SystemUrl -ClientId -ClientSecret -Token -EnableException -TimeoutSec + __AllParameterSets -ServiceName -Payload -PayloadCharset -RetryTimeout -ThrottleSeed -Tenant -Url -SystemUrl -ClientId -ClientSecret -Token -EnableException -TimeoutSec #> } diff --git a/docs/Add-D365ODataConfig.md b/docs/Add-D365ODataConfig.md index 6debdd9..a118292 100644 --- a/docs/Add-D365ODataConfig.md +++ b/docs/Add-D365ODataConfig.md @@ -193,16 +193,3 @@ Tags: Integrations, Integration, Bearer Token, Token, OData, Configuration Author: Mötz Jensen (@Splaxi) ## RELATED LINKS - -[Clear-D365ActiveBroadcastMessageConfig]() - -[Get-D365ActiveBroadcastMessageConfig]() - -[Get-D365BroadcastMessageConfig]() - -[Remove-D365BroadcastMessageConfig]() - -[Send-D365BroadcastMessage]() - -[Set-D365ActiveBroadcastMessageConfig]() - diff --git a/docs/Get-D365ActiveODataConfig.md b/docs/Get-D365ActiveODataConfig.md index a02ed29..13fbdf3 100644 --- a/docs/Get-D365ActiveODataConfig.md +++ b/docs/Get-D365ActiveODataConfig.md @@ -74,16 +74,3 @@ Tags: OData, Environment, Config, Configuration, ClientId, ClientSecret Author: Mötz Jensen (@Splaxi) ## RELATED LINKS - -[Add-D365BroadcastMessageConfig]() - -[Clear-D365ActiveBroadcastMessageConfig]() - -[Get-D365BroadcastMessageConfig]() - -[Remove-D365BroadcastMessageConfig]() - -[Send-D365BroadcastMessage]() - -[Set-D365ActiveBroadcastMessageConfig]() - diff --git a/docs/Get-D365ODataConfig.md b/docs/Get-D365ODataConfig.md index ad0b340..3884347 100644 --- a/docs/Get-D365ODataConfig.md +++ b/docs/Get-D365ODataConfig.md @@ -111,16 +111,3 @@ Tags: OData, Environment, Config, Configuration, ClientId, ClientSecret Author: Mötz Jensen (@Splaxi) ## RELATED LINKS - -[Add-D365BroadcastMessageConfig]() - -[Clear-D365ActiveBroadcastMessageConfig]() - -[Get-D365ActiveBroadcastMessageConfig]() - -[Remove-D365BroadcastMessageConfig]() - -[Send-D365BroadcastMessage]() - -[Set-D365ActiveBroadcastMessageConfig]() - diff --git a/docs/Invoke-D365RestEndpoint.md b/docs/Invoke-D365RestEndpoint.md index fa70fce..cb4df33 100644 --- a/docs/Invoke-D365RestEndpoint.md +++ b/docs/Invoke-D365RestEndpoint.md @@ -14,8 +14,9 @@ Invoke a REST Endpoint in Dynamics 365 Finance & Operations ``` Invoke-D365RestEndpoint [-ServiceName] [[-Payload] ] [[-PayloadCharset] ] - [[-Tenant] ] [[-Url] ] [[-SystemUrl] ] [[-ClientId] ] - [[-ClientSecret] ] [[-Token] ] [-EnableException] [[-TimeoutSec] ] [] + [[-RetryTimeout] ] [[-ThrottleSeed] ] [[-Tenant] ] [[-Url] ] + [[-SystemUrl] ] [[-ClientId] ] [[-ClientSecret] ] [[-Token] ] + [-EnableException] [[-TimeoutSec] ] [] ``` ## DESCRIPTION @@ -58,6 +59,32 @@ It will get a fresh token, saved it into the token variable and pass it to the c The ServiceName used for the import is "UserSessionService/AifUserSessionService/GetUserSessionInfo". The Payload is a valid json string, containing all the needed properties. +### EXAMPLE 4 +``` +$Payload = '{"RateTypeName": "TEST", "FromCurrency": "DKK", "ToCurrency": "EUR", "StartDate": "2019-01-03T00:00:00Z", "Rate": 745.10, "ConversionFactor": "Hundred", "RateTypeDescription": "TEST"}' +``` + +PS C:\\\> Invoke-D365RestEndpoint -ServiceName "UserSessionService/AifUserSessionService/GetUserSessionInfo" -Payload $Payload -RetryTimeout "00:01:00" + +This will invoke the REST endpoint in the Dynamics 365 Finance & Operations environment, and try for 1 minute to handle 429. +First the desired json data is put into the $Payload variable. +The ServiceName used for the import is "UserSessionService/AifUserSessionService/GetUserSessionInfo". +The $Payload variable is passed to the cmdlet. +It will only try to handle 429 retries for 1 minute, before failing. + +### EXAMPLE 5 +``` +$Payload = '{"RateTypeName": "TEST", "FromCurrency": "DKK", "ToCurrency": "EUR", "StartDate": "2019-01-03T00:00:00Z", "Rate": 745.10, "ConversionFactor": "Hundred", "RateTypeDescription": "TEST"}' +``` + +PS C:\\\> Invoke-D365RestEndpoint -ServiceName "UserSessionService/AifUserSessionService/GetUserSessionInfo" -Payload $Payload -ThrottleSeed 2 + +This will invoke the REST endpoint in the Dynamics 365 Finance & Operations environment, and sleep/pause between 1 and 2 seconds. +First the desired json data is put into the $Payload variable. +The ServiceName used for the import is "UserSessionService/AifUserSessionService/GetUserSessionInfo". +The $Payload variable is passed to the cmdlet. +It will use the ThrottleSeed 2 to sleep/pause the execution, to mitigate the 429 pushback from the endpoint. + ## PARAMETERS ### -ServiceName @@ -121,6 +148,54 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -RetryTimeout +The retry timeout, before the cmdlet should quit retrying based on the 429 status code + +Needs to be provided in the timspan notation: +"hh:mm:ss" + +hh is the number of hours, numerical notation only +mm is the number of minutes +ss is the numbers of seconds + +Each section of the timeout has to valid, e.g. +hh can maximum be 23 +mm can maximum be 59 +ss can maximum be 59 + +Not setting this parameter will result in the cmdlet to try for ever to handle the 429 push back from the endpoint + +```yaml +Type: TimeSpan +Parameter Sets: (All) +Aliases: + +Required: False +Position: 4 +Default value: 00:00:00 +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ThrottleSeed +Instruct the cmdlet to invoke a thread sleep between 1 and ThrottleSeed value + +This is to help to mitigate the 429 retry throttling on the OData / Custom Service endpoints + +It makes most sense if you are running things a outer loop, where you will hit the OData / Custom Service endpoints with a burst of calls in a short time + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 5 +Default value: 0 +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Tenant Azure Active Directory (AAD) tenant id (Guid) that the D365FO environment is connected to, that you want to access through REST endpoint @@ -130,7 +205,7 @@ Parameter Sets: (All) Aliases: $AadGuid Required: False -Position: 4 +Position: 6 Default value: $Script:ODataTenant Accept pipeline input: False Accept wildcard characters: False @@ -145,7 +220,7 @@ Parameter Sets: (All) Aliases: Uri Required: False -Position: 5 +Position: 7 Default value: $Script:ODataUrl Accept pipeline input: False Accept wildcard characters: False @@ -164,7 +239,7 @@ Parameter Sets: (All) Aliases: Required: False -Position: 6 +Position: 8 Default value: $Script:ODataSystemUrl Accept pipeline input: False Accept wildcard characters: False @@ -179,7 +254,7 @@ Parameter Sets: (All) Aliases: Required: False -Position: 7 +Position: 9 Default value: $Script:ODataClientId Accept pipeline input: False Accept wildcard characters: False @@ -194,7 +269,7 @@ Parameter Sets: (All) Aliases: Required: False -Position: 8 +Position: 10 Default value: $Script:ODataClientSecret Accept pipeline input: False Accept wildcard characters: False @@ -211,7 +286,7 @@ Parameter Sets: (All) Aliases: Required: False -Position: 9 +Position: 11 Default value: None Accept pipeline input: False Accept wildcard characters: False @@ -246,7 +321,7 @@ Parameter Sets: (All) Aliases: Required: False -Position: 10 +Position: 12 Default value: 0 Accept pipeline input: False Accept wildcard characters: False