Utiliser PowerShell et Visual Studio Code avec l’API web Dataverse

Cet article développe l’article Démarrage rapide : API web avec PowerShell pour décrire les fonctionnalités avancées utilisant PowerShell et Visual Studio Code avec l’API web Dataverse pour :

Notes

Les instructions de cet article devraient fonctionner pour Windows, Linux et macOS, mais ces étapes n’ont été testées que sous Windows. Si des modifications sont nécessaires, veuillez nous en informer en utilisant la section Commentaires au bas de cet article.

Conditions préalables

Le contenu de cet article présente les mêmes conditions préalables que l’article Démarrage rapide : API web avec PowerShell .

Installer ou vérifier que les éléments suivants sont installés

Vérifier l’installation

  1. Ouvrez Visual Studio Code.

  2. Dans le menu Terminal, sélectionnez Nouveau terminal.

  3. Dans le volet de navigation Visual Studio Code, sélectionnez l’icône pour l’extension PowerShell.

  4. Copiez et collez le script suivant dans la fenêtre du terminal Visual Studio Code :

    Write-Host 'PowerShell Version:'$PSVersionTable.PSVersion.ToString()
    Write-Host 'PowerShell Az version:'(Get-InstalledModule Az).Version
    
  5. Appuyez sur Entrée. La sortie ressemblerait à l’exemple suivant :

    PowerShell Version: 7.4.0
    PowerShell Az version: 11.1.0
    

Si vous ne voyez pas de résultats comme celui-ci, installez ou mettez à jour les prérequis.

Ce dont vous avez besoin

  • Compte d’utilisateur valide pour un environnement Dataverse
  • URL vers l’environnement Dataverse auquel vous souhaitez vous connecter. Voir Afficher les ressources des développeurs pour savoir comment les trouver. Cela ressemble à ceci : https://yourorg.crm.dynamics.com/, où yourorg.crm est différent.
  • Présentation de base du langage de script PowerShell

Créer des fonctions réutilisables

Démarrage rapide : API web avec PowerShell a présenté comment authentifier et appeler la fonction WhoAmI avec Visual Studio Code. Cette approche peut suffire à tester ponctuellement une ou plusieurs opérations. Cependant, à mesure que vos scripts deviennent plus complexes, vous pourriez vous retrouver à taper le même code encore et encore.

Dans cette section, nous commençons à créer un ensemble de fonctions réutilisables dans des fichiers séparés auxquels nous pouvons accéder à l’aide du dot sourcing. Utilisez le dot sourcing pour charger un fichier contenant des scripts PowerShell pouvant contenir des fonctions et des variables qui font partie de la portée du script local.

Conseil

Vous pouvez trouver des définitions entièrement documentées de ces fonctions et bien plus encore dans notre référentiel d’exemples GitHub PowerApps sur PowerApps-Samples/dataverse/webapi/PS/

Créer une fonction Connect

Mettons le code auquel s’authentifier à Dataverse dans une fonction appelée Connect à l’intérieur d’un fichier nommé Core.ps1 afin de pouvoir le réutiliser dans une seule ligne de code.

  1. Créez un dossier. Dans cet exemple, nous créons un dossier dans C:\scripts.

  2. Ouvrez le dossier de scripts à l’aide de Visual Studio Code.

  3. Créez un fichier texte dans le dossier des scripts nommé Core.ps1.

  4. Copiez et collez la fonction Connect suivante dans le fichier Core.ps1.

    function Connect {
       param (
          [Parameter(Mandatory)] 
          [String] 
          $uri
       )
    
       ## Login interactively if not already logged in
       if ($null -eq (Get-AzTenant -ErrorAction SilentlyContinue)) {
          Connect-AzAccount | Out-Null
       }
    
       # Get an access token
       $token = (Get-AzAccessToken -ResourceUrl $uri).Token
    
       # Define common set of headers
       $global:baseHeaders = @{
          'Authorization'    = 'Bearer ' + $token
          'Accept'           = 'application/json'
          'OData-MaxVersion' = '4.0'
          'OData-Version'    = '4.0'
       }
    
       # Set baseURI
       $global:baseURI = $uri + 'api/data/v9.2/'
    }
    

    Notes

    Le script ajoute les variables baseURI et baseHeaders au contexte global à l’aide du $global modificateur de portée afin qu’elles soient disponibles pour d’autres scripts dans la même session.

  5. Créez un autre fichier texte en utilisant le Visual Studio Code nommé test.ps1 dans votre dossier scripts.

  6. Copiez et collez le script suivant dans le fichier test.ps1 :

    . $PSScriptRoot\Core.ps1
    
    Connect 'https://yourorg.crm.dynamics.com/' # change this
    # Invoke WhoAmI Function
    Invoke-RestMethod -Uri ($baseURI + 'WhoAmI') -Method Get -Headers $baseHeaders
    | ConvertTo-Json
    

    . $PSScriptRoot\Core.ps1 en haut du fichier montre l’utilisation du dot sourcing pour demander au script de charger le contenu de ce fichier.

    N’oubliez pas de modifier la valeur https://yourorg.crm.dynamics.com/ pour qu’elle corresponde à l’URL de votre environnement.

  7. Appuyez sur la touche F5 pour exécuter le script.

    Le résultat doit ressembler à ce qui suit :

    PS C:\scripts> . 'C:\scripts\test.ps1'
    {
    "@odata.context": "https://yourorg.crm.dynamics.com/api/data/v9.2/$metadata#Microsoft.Dynamics.CRM.WhoAmIResponse",
    "BusinessUnitId": "3a277578-5996-ee11-be36-002248227994",
    "UserId": "2c2e7578-5996-ee11-be36-002248227994",
    "OrganizationId": "97bf0e8b-aa99-ee11-be32-000d3a106c3a"
    }
    

Créer une fonction WhoAmI

Mettons le code pour appeler la fonction WhoAmI dans une fonction appelée Get-WhoAmI à l’intérieur d’un fichier nommé CommonFunctions.ps1 afin que nous puissions taper simplement 11 caractères au lieu de 100 à chaque fois que vous souhaitez utiliser la fonction WhoAmI

  1. Créez un fichier texte nommé CommonFunctions.ps1 dans votre dossier scripts.

  2. Copiez et collez la définition de fonction suivante dans CommonFunctions.ps1.

    function Get-WhoAmI{
    
       $WhoAmIRequest = @{
          Uri = $baseURI + 'WhoAmI'
          Method = 'Get'
          Headers = $baseHeaders
       }
    
       Invoke-RestMethod @WhoAmIRequest
    }
    

    Notes

    Cette définition de fonction utilise une technique appelée Splatting. Le Splatting rend vos commandes plus courtes et plus faciles à lire, car il transmet une collection de valeurs de paramètres à une commande en tant qu’unité.

  3. Enregistrez le fichier CommonFunctions.ps1.

  4. Modifiez le fichier test.ps1, changez le contenu pour qu’il ressemble au script suivant :

    . $PSScriptRoot\Core.ps1
    . $PSScriptRoot\CommonFunctions.ps1
    
    Connect 'https://yourorg.crm.dynamics.com/' # change this
    # Invoke WhoAmI Function
    Get-WhoAmI | ConvertTo-Json
    

    N’oubliez pas de modifier la valeur https://yourorg.crm.dynamics.com/ pour qu’elle corresponde à l’URL de votre environnement.

  5. Appuyez sur la touche F5 pour exécuter le script.

    La sortie devrait ressembler exactement à ce qu’elle était auparavant.

Créer des fonctions d’opérations de table

Mettons les fonctions pour effectuer des opérations de table courantes dans un fichier nommé TableOperations.ps1 afin que nous puissions les réutiliser.

  1. Créez un fichier texte nommé TableOperations.ps1 dans votre dossier scripts.

  2. Copiez et collez les définitions de fonction suivantes dans TableOperations.ps1.

    function Get-Records {
       param (
          [Parameter(Mandatory)] 
          [String] 
          $setName,
          [Parameter(Mandatory)] 
          [String] 
          $query
       )
       $uri = $baseURI + $setName + $query
       # Header for GET operations that have annotations
       $getHeaders = $baseHeaders.Clone()
       $getHeaders.Add('If-None-Match', $null)
       $getHeaders.Add('Prefer', 'odata.include-annotations="*"')
       $RetrieveMultipleRequest = @{
          Uri     = $uri
          Method  = 'Get'
          Headers = $getHeaders
       }
       Invoke-RestMethod @RetrieveMultipleRequest
    }
    
    function New-Record {
       param (
          [Parameter(Mandatory)] 
          [String] 
          $setName,
          [Parameter(Mandatory)] 
          [hashtable]
          $body
       )
       $postHeaders = $baseHeaders.Clone()
       $postHeaders.Add('Content-Type', 'application/json')
    
       $CreateRequest = @{
          Uri     = $baseURI + $setName
          Method  = 'Post'
          Headers = $postHeaders
          Body    = ConvertTo-Json $body
       }
       Invoke-RestMethod @CreateRequest -ResponseHeadersVariable rh | Out-Null
       $url = $rh['OData-EntityId']
       $selectedString = Select-String -InputObject $url -Pattern '(?<=\().*?(?=\))'
       return [System.Guid]::New($selectedString.Matches.Value.ToString())
    }
    
    function Get-Record {
       param (
          [Parameter(Mandatory)] 
          [String] 
          $setName,
          [Parameter(Mandatory)] 
          [Guid] 
          $id,
          [String] 
          $query
       )
       $uri = $baseURI + $setName
       $uri = $uri + '(' + $id.Guid + ')' + $query
       $getHeaders = $baseHeaders.Clone()
       $getHeaders.Add('If-None-Match', $null)
       $getHeaders.Add('Prefer', 'odata.include-annotations="*"')
       $RetrieveRequest = @{
          Uri     = $uri
          Method  = 'Get'
          Headers = $getHeaders
       }
       Invoke-RestMethod @RetrieveRequest
    }
    
    function Update-Record {
       param (
          [Parameter(Mandatory)] 
          [String] 
          $setName,
          [Parameter(Mandatory)] 
          [Guid] 
          $id,
          [Parameter(Mandatory)] 
          [hashtable]
          $body
       )
       $uri = $baseURI + $setName
       $uri = $uri + '(' + $id.Guid + ')'
       # Header for Update operations
       $updateHeaders = $baseHeaders.Clone()
       $updateHeaders.Add('Content-Type', 'application/json')
       $updateHeaders.Add('If-Match', '*') # Prevent Create
       $UpdateRequest = @{
          Uri     = $uri
          Method  = 'Patch'
          Headers = $updateHeaders
          Body    = ConvertTo-Json $body
       }
       Invoke-RestMethod @UpdateRequest
    }
    
    function Remove-Record {
       param (
          [Parameter(Mandatory)] 
          [String]
          $setName,
          [Parameter(Mandatory)] 
          [Guid] 
          $id
       )
       $uri = $baseURI + $setName
       $uri = $uri + '(' + $id.Guid + ')'
       $DeleteRequest = @{
          Uri     = $uri
          Method  = 'Delete'
          Headers = $baseHeaders
       }
       Invoke-RestMethod @DeleteRequest
    }
    
    

    Pour obtenir des informations sur la façon de composer ces requêtes, consultez les articles suivants :

  3. Enregistrez le fichier TableOperations.ps1.

  4. Copiez et collez le code suivant dans le fichier test.ps1.

    . $PSScriptRoot\Core.ps1
    . $PSScriptRoot\CommonFunctions.ps1
    . $PSScriptRoot\TableOperations.ps1
    
    Connect 'https://yourorg.crm.dynamics.com/' # change this
    
    # Retrieve Records
    Write-Host 'Retrieve first three account records:'
    (Get-Records `
       -setName accounts `
       -query '?$select=name&$top=3').value | 
    Format-Table -Property name, accountid
    
    # Create a record
    Write-Host 'Create an account record:'
    $newAccountID = New-Record `
       -setName accounts `
       -body @{
          name                = 'Example Account'; 
          accountcategorycode = 1 # Preferred
       }
    Write-Host "Account with ID $newAccountID created"
    
    # Retrieve a record
    Write-Host 'Retrieve the created record:'
    Get-Record `
       -setName  accounts `
       -id $newAccountID.Guid '?$select=name,accountcategorycode' |
    Format-List -Property name,
    accountid,
    accountcategorycode,
    accountcategorycode@OData.Community.Display.V1.FormattedValue
    
    # Update a record
    Write-Host 'Update the record:'
    $updateAccountData = @{
       name                = 'Updated Example account';
       accountcategorycode = 2; #Standard
    }
    Update-Record `
       -setName accounts `
       -id $newAccountID.Guid `
       -body $updateAccountData
    Write-Host 'Retrieve the updated the record:'
    Get-Record `
       -setName accounts `
       -id  $newAccountID.Guid `
       -query '?$select=name,accountcategorycode' |
    Format-List -Property name,
    accountid,
    accountcategorycode,
    accountcategorycode@OData.Community.Display.V1.FormattedValue
    
    # Delete a record
    Write-Host 'Delete the record:'
    Remove-Record `
       -setName accounts `
       -id $newAccountID.Guid
    Write-Host "The account with ID $newAccountID was deleted"
    

    N’oubliez pas de modifier la valeur https://yourorg.crm.dynamics.com/ pour qu’elle corresponde à l’URL de votre environnement.

  5. Appuyez sur la touche F5 pour exécuter le script.

    Le résultat doit ressembler à ce qui suit :

    PS C:\scripts> . 'C:\scripts\test.ps1'
    Retrieve first three account records:
    
    name                     accountid
    ----                     ---------
    Fourth Coffee (sample)   d2382248-cd99-ee11-be37-000d3a9b7981
    Litware, Inc. (sample)   d4382248-cd99-ee11-be37-000d3a9b7981
    Adventure Works (sample) d6382248-cd99-ee11-be37-000d3a9b7981
    
    Create an account record:
    Account with ID  a2c3ebc2-39a8-ee11-be37-000d3a8e8e07 created
    Retrieve the created record:
    
    name                                                          : Example Account
    accountid                                                     : a2c3ebc2-39a8-ee11-be37-000d3a8e8e07
    accountcategorycode                                           : 1
    accountcategorycode@OData.Community.Display.V1.FormattedValue : Preferred Customer
    
    Update the record:
    
    Retrieve the updated the record:
    
    name                                                          : Updated Example account
    accountid                                                     : a2c3ebc2-39a8-ee11-be37-000d3a8e8e07
    accountcategorycode                                           : 2
    accountcategorycode@OData.Community.Display.V1.FormattedValue : Standard
    
    Delete the record:
    
    The account with ID  a2c3ebc2-39a8-ee11-be37-000d3a8e8e07 was deleted
    

Gérer les exceptions

Jusqu’à présent, dans cet article, vous avez copié et collé le code qui vous était fourni. Mais lorsque vous commencerez à écrire vos propres fonctions et à les utiliser, vous rencontrerez des erreurs. Lorsque ces erreurs se produisent, elles peuvent provenir de Dataverse ou de votre script.

Ajoutons une fonction d’assistance qui peut aider à détecter la source des erreurs et à extraire les détails pertinents des erreurs renvoyées par Dataverse.

  1. Modifiez le fichier Core.ps1 pour ajouter la fonction Invoke-DataverseCommands suivante :

    function Invoke-DataverseCommands {
       param (
          [Parameter(Mandatory)] 
          $commands
       )
       try {
          Invoke-Command $commands
       }
       catch [Microsoft.PowerShell.Commands.HttpResponseException] {
          Write-Host "An error occurred calling Dataverse:" -ForegroundColor Red
          $statuscode = [int]$_.Exception.StatusCode;
          $statusText = $_.Exception.StatusCode
          Write-Host "StatusCode: $statuscode ($statusText)"
          # Replaces escaped characters in the JSON
          [Regex]::Replace($_.ErrorDetails.Message, "\\[Uu]([0-9A-Fa-f]{4})", 
             {[char]::ToString([Convert]::ToInt32($args[0].Groups[1].Value, 16))} )
    
       }
       catch {
          Write-Host "An error occurred in the script:" -ForegroundColor Red
          $_
       }
    }
    

    La fonction Invoke-DataverseCommands utilise l’applet de commande Invoke-Command pour traiter un ensemble de commandes dans un bloc try/catch. Toutes les erreurs renvoyées par Dataverse sont des erreurs HttpResponseException, donc le premier bloc catch écrit un message An error occurred calling Dataverse: au terminal avec les données d’erreur JSON.

    Les données JSON dans $_.ErrorDetails.Message contiennent des caractères Unicode d’échappement. Par exemple : \u0026 au lieu de & et \u0027 au lieu de '. Cette fonction inclut du code qui remplace ces caractères par les caractères sans échappement afin qu’ils correspondent exactement aux erreurs que vous voyez ailleurs.

    Sinon, les erreurs sont réécrites dans la fenêtre du terminal avec un message : An error occurred in the script:

  2. Enregistrez le fichier Core.ps1.

  3. Modifiez le fichier test.ps1 pour utiliser le script suivant qui utilise une valeur de paramètre setName non valide. account doit être accounts. C’est une erreur courante

    . $PSScriptRoot\Core.ps1
    . $PSScriptRoot\CommonFunctions.ps1
    . $PSScriptRoot\TableOperations.ps1
    
    Connect 'https://yourorg.crm.dynamics.com/' # change this
    
    Invoke-DataverseCommands {
    
       # Retrieve Records
       Write-Host 'Retrieve first three account records:'
          (Get-Records `
          -setName account `
          -query '?$select=name&$top=3').value | 
       Format-Table -Property name, accountid
    
    }
    

    N’oubliez pas de modifier la valeur https://yourorg.crm.dynamics.com/ pour qu’elle corresponde à l’URL de votre environnement.

  4. Appuyez sur la touche F5 pour exécuter le script.

    Le résultat doit ressembler à ce qui suit :

    PS C:\scripts> . 'C:\scripts\test.ps1'
    Retrieve first three account records:
    An error occurred calling Dataverse:
    StatusCode: 404 (NotFound)
    
    {
    "error": {
       "code": "0x80060888",
       "message": "Resource not found for the segment 'account'."
       }
    }
    
  5. Modifiez le fichier test.ps1 pour générer une erreur de script dans le bloc Invoke-DataverseCommands :

    Invoke-DataverseCommands {
    
       throw 'A script error'
    
    }
    
  6. Appuyez sur la touche F5 pour exécuter le script.

    Le résultat devrait être presque le même que s’il n’était pas inclus dans le bloc Invoke-DataverseCommands :

    PS C:\scripts> . 'C:\scripts\test.ps1'
    An error occurred in the script:
    Exception: C:\scripts\test.ps1:8:4
    Line |
       8 |     throw 'A script error'
         |     ~~~~~~~~~~~~~~~~~~~~~~
         | A script error
    

Gérer les limites de protection de service Dataverse

Les limites de l’API de protection des services Dataverse permettent de garantir que Dataverse propose une disponibilité et des performances constantes. Lorsque les applications clientes sollicitent énormément les ressources du serveur à l’aide de l’API web, Dataverse renvoie 429 erreurs de requêtes trop nombreuses et l’application cliente doit suspendre les opérations pendant toute la durée spécifié dans l’en-tête Retry-After.

L’applet de commande Invoke-RestMethod PowerShell Paramètre MaximumRetryCount spécifie combien de fois PowerShell réessaye une demande lorsqu’un code d’échec est compris entre 400 et 599 inclus ou que le code 304 est reçu. Cela signifie que PowerShell réessaye en cas d’erreur de type 429 de protection de services Dataverse lorsque vous incluez une valeur pour ce paramètre. Le paramètre MaximumRetryCount peut être utilisé avec RetryIntervalSec pour spécifier le nombre de secondes à attendre. La valeur par défaut est 5 secondes. Si la réponse à l’erreur inclut un en-tête Retry-After pour une erreur 429, comme les erreurs de protection de services Dataverse se produisent, cette valeur est utilisée à la place.

Vous ne rencontrerez peut-être jamais d’erreur de limite de protection de service pendant que vous apprenez à utiliser l’API web Dataverse avec PowerShell. Les scripts que vous écrivez peuvent être utilisés pour envoyer le grand nombre de requêtes nécessaires pour rencontrer ces erreurs. Vous devez donc savoir qu’elles peuvent se produire et savoir comment les gérer à l’aide de PowerShell.

Si vous ajoutez le paramètre MaximumRetryCount à chaque appel Dataverse en utilisant Invoke-RestMethod, PowerShell réessaye un large éventail d’erreurs. Réessayer chaque erreur ralentit vos scripts, en particulier lors du développement et des tests. Vous devrez attendre 10 à 15 secondes à chaque fois qu’une erreur se produit, selon le nombre de tentatives que vous spécifiez. Une approche alternative consiste à encapsuler la Invoke-RestMethod dans votre propre méthode qui gère les tentatives pour des erreurs spécifiques.

La fonction Invoke-ResilientRestMethod suivante prend un objet de table de hachage request en tant que paramètre obligatoire et un indicateur returnHeader booléen pour indiquer s’il faut renvoyer l’en-tête de réponse ou pas. Lorsque $returnHeader a la valeur true, elle envoie la requête à l’aide de la commande Invoke-RestMethod avec le paramètre ResponseHeadersVariable pour capturer les en-têtes renvoyés, et utilise Out-Null afin que la sortie qui représente le corps de la réponse vide ne soit pas renvoyée avec la fonction. Sinon, la fonction envoie la requête en utilisant Invoke-RestMethod avec l’objet request et renvoie le corps de la réponse.

Si Invoke-RestMethod échoue avec une erreur 429, elle vérifie si l’objet request a une propriété MaximumRetryCount. Sinon, elle en ajoute une avec une valeur de 3. Elle réessaie ensuite Invoke-RestMethod en utilisant l’objet de requête et la valeur de l’en-tête de réponse Retry-After. Si l’indicateur returnHeader est défini sur True, elle renvoie l’en-tête de réponse. Si Invoke-RestMethod échoue avec une autre erreur, elle renvoie l’exception.

function Invoke-ResilientRestMethod {
   param (
      [Parameter(Mandatory)] 
      $request,
      [bool]
      $returnHeader
   )
   try {
      if ($returnHeader) {
         Invoke-RestMethod @request -ResponseHeadersVariable rhv | Out-Null
         return $rhv
      }
      Invoke-RestMethod @request
   }
   catch [Microsoft.PowerShell.Commands.HttpResponseException] {
      $statuscode = $_.Exception.Response.StatusCode
      # 429 errors only
      if ($statuscode -eq 'TooManyRequests') {
         if (!$request.ContainsKey('MaximumRetryCount')) {
            $request.Add('MaximumRetryCount', 3)
            # Don't need - RetryIntervalSec
            # When the failure code is 429 and the response includes the Retry-After property in its headers, 
            # the cmdlet uses that value for the retry interval, even if RetryIntervalSec is specified
         }
         # Will attempt retry up to 3 times
         if ($returnHeader) {
            Invoke-RestMethod @request -ResponseHeadersVariable rhv | Out-Null
            return $rhv
         }
         Invoke-RestMethod @request
      }
      else {
         throw $_
      }
   }
   catch {
      throw $_
   }
}

Vous pouvez utiliser une fonction comme celle-ci dans vos fonctions réutilisables. Lorsque les fonctions doivent renvoyer des valeurs de l’en-tête de la réponse, elles doivent définir la valeur returnHeader sur $true. Par exemple, la fonction suivante New-Record modifie l’exemple de fonction dans Fonctions d’opérations de création de table pour l’utiliser Invoke-ResilientRestMethod au lieu de Invoke-RestMethod directement.

function New-Record {
   param (
      [Parameter(Mandatory)] 
      [String] 
      $setName,
      [Parameter(Mandatory)] 
      [hashtable]
      $body
   )
   $postHeaders = $baseHeaders.Clone()
   $postHeaders.Add('Content-Type', 'application/json')
   
   $CreateRequest = @{
      Uri     = $environmentUrl + 'api/data/v9.2/' + $setName
      Method  = 'Post'
      Headers = $postHeaders
      Body    = ConvertTo-Json $body

   }
   # Before: 
   # Invoke-RestMethod @CreateRequest -ResponseHeadersVariable rh | Out-Null

   # After:
   $rh = Invoke-ResilientRestMethod -request $CreateRequest -returnHeader $true
   $url = $rh['OData-EntityId']
   $selectedString = Select-String -InputObject $url -Pattern '(?<=\().*?(?=\))'
   return [System.Guid]::New($selectedString.Matches.Value.ToString())
}

Sinon, Invoke-ResilientRestMethod peut remplacer le Invoke-RestMethod comme indiqué dans cet exemple Get-Record :

function Get-Record {
   param (
      [Parameter(Mandatory)] 
      [String] 
      $setName,
      [Parameter(Mandatory)] 
      [Guid] 
      $id,
      [String] 
      $query
   )
   $uri = $environmentUrl + 'api/data/v9.2/' + $setName
   $uri = $uri + '(' + $id.Guid + ')' + $query
   $getHeaders = $baseHeaders.Clone()
   $getHeaders.Add('If-None-Match', $null)
   $getHeaders.Add('Prefer', 'odata.include-annotations="*"')
   $RetrieveRequest = @{
      Uri     = $uri
      Method  = 'Get'
      Headers = $getHeaders
   }
   # Before:
   # Invoke-RestMethod @RetrieveRequest

   # After: 
   Invoke-ResilientRestMethod $RetrieveRequest
}

La seule différence est que vous transmettez la table de hachage ($RetrieveRequest) à la méthode au lieu d’utiliser le splatting (@RetrieveRequest). Sinon, vous obtiendrez une erreur de script : A parameter cannot be found that matches parameter name 'Headers'.

Déboguer avec Fiddler

Fiddler est un proxy de débogage Web utilisé pour afficher le trafic HTTP sur votre ordinateur. L’affichage de ces données est utile lors du débogage des scripts. Par défaut, les requêtes et réponses HTTP envoyées à l’aide de l’applet de commande Invoke-RestMethod ne seront pas visibles à l’aide de Fiddler.

Pour afficher le trafic HTTP à l’aide de Fiddler, définissez le Invoke-RestMethod paramètre Proxy sur l’URL configurée comme proxy Fiddler sur votre ordinateur local. Par défaut, l’URL est http://127.0.0.1:8888. Le vôtre peut être différent.

Par exemple, si vous appelez la fonction WhoAmI avec le paramètre -Proxy défini pendant que Fiddler capture le trafic :

Invoke-RestMethod `
   -Uri ($environmentUrl + 'api/data/v9.2/WhoAmI') `
   -Method Get `
   -Headers $baseHeaders `
   -Proxy 'http://127.0.0.1:8888'

Dans Fiddler, vous pourrez voir tous les détails :

GET https://yourorg.api.crm.dynamics.com/api/data/v9.2/WhoAmI HTTP/1.1
Host: yourorg.api.crm.dynamics.com
OData-MaxVersion: 4.0
Accept: application/json
Authorization: Bearer [REDACTED]
OData-Version: 4.0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.22631; en-US) PowerShell/7.4.0
Accept-Encoding: gzip, deflate, br


HTTP/1.1 200 OK
Cache-Control: no-cache
Allow: OPTIONS,GET,HEAD,POST
Content-Type: application/json; odata.metadata=minimal
Expires: -1
Vary: Accept-Encoding
x-ms-service-request-id: 7341c0c1-3343-430b-98ea-292567ed4776
Set-Cookie: ARRAffinity=f60cbee43b7af0a5f322e7ce57a018546ed978f67f0c11cbb5e15b02ddb091a915134d20c556b0b34b9b6ae43ec3f5dcdad61788de889ffc592af7aca85fc1c508DC0FC94CB062A12107345846; path=/; secure; HttpOnly
Set-Cookie: ReqClientId=4fc95009-0b3d-4a19-b223-0d80745636ac; expires=Sun, 07-Jan-2074 21:10:42 GMT; path=/; secure; HttpOnly
Set-Cookie: orgId=648e8efd-db86-466e-a5bc-a4d5eb9c52d4; expires=Sun, 07-Jan-2074 21:10:42 GMT; path=/; secure; HttpOnly
x-ms-service-request-id: 1ee13aa7-47f3-4a75-95fa-2916775a1f79
Strict-Transport-Security: max-age=31536000; includeSubDomains
REQ_ID: 1ee13aa7-47f3-4a75-95fa-2916775a1f79
CRM.ServiceId: framework
AuthActivityId: 0b562cc3-56f6-44f0-a26e-4039cfc4be6a
x-ms-dop-hint: 48
x-ms-ratelimit-time-remaining-xrm-requests: 1,200.00
x-ms-ratelimit-burst-remaining-xrm-requests: 5999
OData-Version: 4.0
X-Source: 110212218438874147222728177124203420477168182861012399121919014511175711948418152
Public: OPTIONS,GET,HEAD,POST
Set-Cookie: ARRAffinity=f60cbee43b7af0a5f322e7ce57a018546ed978f67f0c11cbb5e15b02ddb091a915134d20c556b0b34b9b6ae43ec3f5dcdad61788de889ffc592af7aca85fc1c508DC0FC94CB062A12107345846; path=/; secure; HttpOnly
X-Source: 2302101791355821068628523819830862152291172232072372448021147103846182145238216119
Date: Sun, 07 Jan 2024 21:10:42 GMT
Content-Length: 277

{"@odata.context":"https://yourorg.api.crm.dynamics.com/api/data/v9.2/$metadata#Microsoft.Dynamics.CRM.WhoAmIResponse","BusinessUnitId":"1647bf36-e90a-4c4d-9b61-969d57ce7a66","UserId":"24e34f5e-7f1a-43fe-88da-7e4b862d51ad","OrganizationId":"648e8efd-db86-466e-a5bc-a4d5eb9c52d4"}

Si Fiddler ne fonctionne pas, vous obtiendrez une erreur comme celle-ci :

Invoke-RestMethod: C:\scripts\test.ps1:8:1
Line |
   8 |  Invoke-RestMethod `
     |  ~~~~~~~~~~~~~~~~~~~
     | No connection could be made because the target machine actively refused it.

Si vous choisissez d’acheminer tous vos appels Invoke-RestMethod via une seule fonction, comme la Invoke-ResilientRestMethod décrite dans Gérer les limites de protection de services Dataverse, vous pouvez définir certaines variables dans le fichier Core.ps1 pour configurer cette option dans un seul emplacement.

# <a name="set-to-true-only-while-debugging-with-fiddler"></a>Set to true only while debugging with Fiddler
$debug = $true
# <a name="set-this-value-to-the-fiddler-proxy-url-configured-on-your-computer"></a>Set this value to the Fiddler proxy URL configured on your computer
$proxyUrl = 'http://127.0.0.1:8888'

Ensuite, au sein de votre fonction centralisée, vous pouvez définir le paramètre -Proxy en utilisant le splatting avec la table de hachage $request uniquement lors du débogage avec Fiddler.

function Invoke-ResilientRestMethod {
   param (
      [Parameter(Mandatory)] 
      $request,
      [bool]
      $returnHeader
   )

   if ($debug) {
      $request.Add('Proxy', $proxyUrl)
   }

   ...

Découvrir comment capturer le trafic Web avec Fiddler

Télécharger le document $metadata CSDL de l’API web Dataverse

Les métadonnées $CSDL sont la source de vérité sur les capacités de l’API web Dataverse. Vous pouvez l’afficher dans un navigateur, mais il vous sera peut-être plus facile de télécharger le fichier et de l’afficher dans Visual Studio Code. Le script suivant est une version modifiée du script introduit dans Démarrage rapide : API web avec PowerShell. La différence est qu’il utilise l’applet de commande Invoke-WebRequest, qui est plus appropriée pour télécharger un document XML.

$environmentUrl = 'https://yourorg.crm.dynamics.com/' # change this
$writeFileTo =  'C:\temp\yourorg.xml' # change this

## <a name="login-if-not-already-logged-in"></a>Login if not already logged in
if ($null -eq (Get-AzTenant -ErrorAction SilentlyContinue)) {
   Connect-AzAccount | Out-Null
}
# <a name="get-an-access-token"></a>Get an access token
$token = (Get-AzAccessToken -ResourceUrl $environmentUrl).Token
# <a name="common-headers"></a>Common headers
$xmlHeaders = @{
   'Authorization'    = 'Bearer ' + $token
   'Accept'           = 'application/xml'
   'OData-MaxVersion' = '4.0'
   'OData-Version'    = '4.0'
}

$doc = [xml](Invoke-WebRequest `
      -Uri ($environmentUrl + 'api/data/v9.2/$metadata?annotations=true') `
      -Method 'Get' `
      -Headers $xmlHeaders ).Content

$StringWriter = New-Object System.IO.StringWriter
$XmlWriter = New-Object System.XMl.XmlTextWriter $StringWriter
$xmlWriter.Formatting = 'indented'
$xmlWriter.Indentation = 2
$doc.WriteContentTo($XmlWriter)
$XmlWriter.Flush()
$StringWriter.Flush()
Set-Content -Path $writeFileTo -Value $StringWriter.ToString()
code $writeFileTo
  1. Copiez le script.
  2. Modifiez les variables $environmentUrl et $writeFileTo en fonction de vos besoins.
  3. Exécutez le script dans Visual Studio Code.

Le document $metadata CSDL de l’API web Dataverse s’ouvre dans Visual Studio Code.

Vous recevrez probablement une notification indiquant :

Pour des raisons de performances, les symboles des documents ont été limités à 5 000 éléments. Si une nouvelle limite est définie, veuillez fermer et rouvrir ce fichier pour recalculer les symboles du document.

La notification donne la possibilité de modifier la limite xml.symbols.maxItemsComputed de l’extension XML Visual Studio. Pour la plupart des documents $metadata CSDL de l’API web Dataverse, définir la limite sur 500 000 devrait suffire.

Résolution des problèmes

Cette section contient des conseils sur les problèmes que vous pourriez rencontrer.

Boîte de dialogue d’erreur : connectez ENOENT\\.\pipe\<RANDOM_text> avec le bouton Ouvrir ’launch.json’

Cette erreur peut se produire parfois lors du débogage à l’aide de Visual Studio Code. Pour résoudre :

  1. Sélectionnez Afficher > Palette de commandes... dans le menu Visual Studio Code ou appuyez sur Ctrl+Maj+P.
  2. Saisissez restart et sélectionnez Powershell: Restart session. Consultez PowerShell/vscode-powershell Problème GitHub 4332 pour plus d’informations.

Étapes suivantes

En savoir plus sur les fonctionnalités de l’API web Dataverse en comprenant les documents de service.

Examinez et exécutez un exemple de code.