Share via


PowerShell und Visual Studio Code in der Dataverse-Web-API verwenden

Dieser Artikel geht näher auf die Themen im Artikel Schnellstart-Web-API mit PowerShell ein und beschreibt erweiterte Funktionen, die mithilfe von PowerShell und Visual Studio Code mit der Dataverse-Web-API für Folgendes verwendet werden können:

Hinweis

Die Anweisungen in diesem Artikel sollten für Windows, Linux und macOS funktionieren. Die Schritte wurden jedoch nur mit Windows getestet. Falls Änderungen erforderlich sind, teilen Sie uns dies bitte über den Abschnitt Feedback am Ende dieses Artikels mit.

Anforderungen

Für den Inhalt dieses Artikels gelten dieselben Voraussetzungen wie für den Artikel Schnellstart-Web-API mit PowerShell.

Installieren Sie Folgendes oder überprüfen Sie, ob es vorhanden ist

Überprüfen der Installation

  1. Öffnen Sie Visual Studio Code.

  2. Wählen Sie im Terminal-Menü Neues Terminal aus.

  3. Wählen Sie im Visual Studio Code-Navigationsbereich das -Symbol für die PowerShell-Erweiterung aus.

  4. Kopieren und fügen Sie das folgende Skript im Terminalfenster von Visual Studio Code aus:

    Write-Host 'PowerShell Version:'$PSVersionTable.PSVersion.ToString()
    Write-Host 'PowerShell Az version:'(Get-InstalledModule Az).Version
    
  5. Drücken Sie die Eingabetaste. Die Ausgabe sollte wie folgt aussehen:

    PowerShell Version: 7.4.0
    PowerShell Az version: 11.1.0
    

Wenn Sie keine derartigen Ergebnisse sehen, installieren oder aktualisieren Sie die Voraussetzungen.

Darüber hinaus brauchen Sie

  • Ein gültiges Benutzerkonto für eine Dataverse-Umgebung
  • Die URL zur Dataverse-Umgebung, mit der Sie eine Verbindung herstellen möchten. Unter Entwicklerressourcen anzeigen erfahren Sie, wie Sie ihn finden. Sie sieht ungefähr so aus: https://yourorg.crm.dynamics.com/, wobei yourorg.crm anders ist.
  • Grundlegende Kenntnisse über die PowerShell-Skriptsprache

Wiederverwendbare Funktionen erstellen

Schnellstart Web-API mit PowerShell bot eine Einführung in die Authentifizierung und den Aufruf der WhoAmI-Funktion mit Visual Studio Code. Für einen Ad-hoc-Test eines oder mehrerer Vorgänge ist dieser Ansatz möglicherweise ausreichend. Wenn Ihre Skripte jedoch komplexer werden, müssen Sie möglicherweise immer wieder denselben Code eingeben.

In diesem Abschnitt beginnen wir mit der Erstellung einer Reihe wiederverwendbarer Funktionen in separaten Dateien, auf die wir über Dot-Sourcing zugreifen können. Verwenden Sie Dot-Sourcing, um eine Datei mit PowerShell-Skripten zu laden, die Funktionen und Variablen enthalten können, die Teil des lokalen Skriptbereichs werden.

Tipp

Vollständig dokumentierte Definitionen dieser Funktionen und mehr finden Sie in unserem PowerApps-Beispiel-Repository auf GitHub unter PowerApps-Samples/dataverse/webapi/PS/

Eine Verbindungsfunktion erstellen

Fügen wir den Code zur Authentifizierung in Dataverse in eine Funktion namens Connect in einer Datei namens Core.ps1 ein, damit wir ihn in einer einzigen Codezeile wiederverwenden können.

  1. Erstellen Sie einen Ordner. In diesem Beispiel erstellen wir einen Ordner in C:\scripts.

  2. Öffnen Sie den Skriptordner mithilfe von Visual Studio Code.

  3. Erstellen Sie im Skriptordner eine Textdatei mit dem Namen Core.ps1.

  4. Kopieren Sie die folgende Connect-Funktion und fügen Sie sie dann in die Core.ps1-Datei ein.

    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/'
    }
    

    Hinweis

    Das Skript fügt die baseURI- und baseHeaders-Variablen mithilfe des $global Bereichsmodifizierer zum globalen Kontext hinzu, sodass sie für andere Skripte in derselben Sitzung verfügbar sind.

  5. Erstellen Sie eine weitere Textdatei mit dem Visual Studio Code namens test.ps1 in Ihrem scripts-Ordner.

  6. Kopieren Sie das folgende Skript in die test.ps1-Datei:

    . $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 oben in der Datei zeigt die Verwendung von Dot-Sourcing an, um das Skript anzuweisen, den Inhalt dieser Datei zu laden.

    Denken Sie daran, den https://yourorg.crm.dynamics.com/-Wert so zu ändern, dass er mit der URL für Ihre Umgebung übereinstimmt.

  7. Drücken Sie F5, um das Skript auszuführen.

    Die Ausgabe sollte in etwa so aussehen:

    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"
    }
    

Eine WhoAmI-Funktion erstellen

Fügen wir den Code zum Aufrufen der WhoAmI-Funktion in eine Funktion namens Get-WhoAmI in einer Datei mit dem Namen CommonFunctions.ps1 ein, damit wir jedes Mal, wenn wir die WhoAmI-Funktion verwenden möchten, nur elf und nicht 100 Zeichen eingeben müssen

  1. Erstellen Sie in Ihrem scripts-Ordner eine neue Textdatei namens CommonFunctions.ps1.

  2. Kopieren Sie die folgende Funktionsdefinition und fügen Sie sie in die CommonFunctions.ps1 ein.

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

    Hinweis

    Diese Funktionsdefinition verwendet eine Technik namens Splatting. Durch Splatting werden Ihre Befehle kürzer und leichter lesbar, da eine Sammlung von Parameterwerten als Einheit an einen Befehl übergeben wird.

  3. Speichern Sie die Datei CommonFunctions.ps1.

  4. Bearbeiten Sie die test.ps1-Datei und ändern Sie den Inhalt so, dass er wie im folgenden Skript aussieht:

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

    Denken Sie daran, den https://yourorg.crm.dynamics.com/-Wert so zu ändern, dass er mit der URL für Ihre Umgebung übereinstimmt.

  5. Drücken Sie F5, um das Skript auszuführen.

    Die Ausgabe sollte genauso aussehen wie zuvor.

Tabellenvorgangsfunktionen erstellen

Legen wir Funktionen zum Ausführen allgemeiner Tabellenvorgänge in eine Datei mit dem Namen TableOperations.ps1 fest, damit wir sie wiederverwenden können.

  1. Erstellen Sie in Ihrem scripts-Ordner eine neue Textdatei namens TableOperations.ps1.

  2. Kopieren Sie die folgende Funktionsdefinitionen und fügen Sie sie in die TableOperations.ps1 ein.

    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
    }
    
    

    Informationen zum Verfassen dieser Anforderungen finden Sie in den folgenden Artikeln:

  3. Speichern Sie die Datei TableOperations.ps1.

  4. Kopieren Sie den folgenden Code und fügen Sie ihn dann in die test.ps1-Datei ein.

    . $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"
    

    Denken Sie daran, den https://yourorg.crm.dynamics.com/-Wert so zu ändern, dass er mit der URL für Ihre Umgebung übereinstimmt.

  5. Drücken Sie F5, um das Skript auszuführen.

    Die Ausgabe sollte in etwa so aussehen:

    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
    

Verarbeiten von Ausnahmen

Bisher haben Sie in diesem Artikel den für Sie bereitgestellten Code kopiert und eingefügt. Wenn Sie aber anfangen, Ihre eigenen Funktionen zu schreiben und zu verwenden, werden Sie auf Fehler stoßen. Diese Fehler stammen dann möglicherweise von Dataverse oder von Ihrem Skript.

Fügen wir eine Hilfsfunktion hinzu, die dabei helfen kann, die Fehlerquelle zu erkennen und relevante Details aus den von Dataverse zurückgegebenen Fehlern zu extrahieren.

  1. Bearbeiten Sie die Core.ps1-Datei, um ihr die folgende Invoke-DataverseCommands-Funktion hinzuzufügen:

    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
          $_
       }
    }
    

    Die Invoke-DataverseCommands-Funktion verwendet das Invoke-Command-Cmdlet, um eine Reihe von Befehlen innerhalb eines try/catch-Blocks zu verarbeiten. Alle von Dataverse zurückgegebenen Fehler sind HttpResponseException-Fehler, daher schreibt der erste catch-Block eine An error occurred calling Dataverse:-Nachricht mit den JSON-Fehlerdaten an das Terminal.

    Die JSON-Daten in $_.ErrorDetails.Message enthalten einige maskierte Unicode-Zeichen. Zum Beispiel: \u0026 statt & und \u0027 statt '. Diese Funktion enthält Code, der diese Zeichen durch unmaskierte Zeichen ersetzt, sodass sie genau mit Fehlern übereinstimmen, die Sie an anderer Stelle sehen.

    Andernfalls werden die Fehler in das Terminalfenster zurückgeschrieben und zwar mit einer Meldung An error occurred in the script:

  2. Speichern Sie die Datei Core.ps1.

  3. Bearbeiten Sie die test.ps1-Datei, um das folgende Skript zu verwenden, das einen ungültigen setName-Parameterwert verwendet. account sollte accounts sein. Dies ist ein häufiger Fehler

    . $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
    
    }
    

    Denken Sie daran, den https://yourorg.crm.dynamics.com/-Wert so zu ändern, dass er mit der URL für Ihre Umgebung übereinstimmt.

  4. Drücken Sie F5, um das Skript auszuführen.

    Die Ausgabe sollte in etwa so aussehen:

    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. Bearbeiten Sie die test.ps1-Datei, um einen Skriptfehler innerhalb des Invoke-DataverseCommands-Blocks auszulösen:

    Invoke-DataverseCommands {
    
       throw 'A script error'
    
    }
    
  6. Drücken Sie F5, um das Skript auszuführen.

    Die Ausgabe sollte fast die gleiche sein, als wäre sie nicht im Invoke-DataverseCommands-Block enthalten:

    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
    

Dataverse-Dienstschutzgrenzwerte verwalten

Die Dataverse-Dienstschutz-API-Grenzwerte helfen dabei, sicherzustellen, dass Dataverse für konsistente Verfügbarkeit und Leistung sorgt. Wenn Clientanwendungen über die Web-API außergewöhnliche Anforderungen an Serverressourcen stellen, gibt Dataverse 429 zu viele Anforderungen-Fehler zurück und die Clientanwendung muss den Vorgang für die in Retry-After-Header angegebene Zeit anhalten.

Der PowerShell MaximumRetryCount-Parameter des Invoke-RestMethod-Cmdlet gibt an, wie oft PowerShell eine Anforderung wiederholt, wenn ein Fehlercode zwischen 400 und einschließlich 599 oder 304 eingeht. Dies bedeutet, dass PowerShell Dataverse-Dienstschutz-429-Fehler erneut versucht, wenn Sie einen Wert für diesen Parameter angeben. Der MaximumRetryCount-Parameter kann mit RetryIntervalSec verwendet werden, um die Anzahl der zu wartenden Sekunden anzugeben. Der Standardwert sind fünf Sekunden. Wenn die Fehlerantwort einen Retry-After-Header für einen 429-Fehler enthält, was bei Dataverse-Dienstschutzfehlern der Fall ist, wird stattdessen dieser Wert verwendet.

Es kann sein, dass nie ein Dienstschutzgrenzwertfehler auftritt, während Sie sich mit der Dataverse-Web-API mit PowerShell vertraut machen. Von Ihnen geschriebene Skripte können zum Senden der großen Anzahl von Anforderungen verwendet werden, die zum Auftreten dieser Fehler erforderlich sind. Sie sollten daher wissen, dass sie auftreten können und wie Sie sie mit PowerShell verwalten können.

Wenn Sie den MaximumRetryCount-Parameter zu jedem Dataverse-Aufruf mit Invoke-RestMethod hinzufügen, versucht PowerShell eine Vielzahl von Fehlern erneut. Wenn Sie jeden Fehler erneut versuchen, werden Ihre Skripte langsamer, insbesondere beim Entwickeln und Testen. Bei jedem Fehler müssten Sie 10 bis 15 Sekunden warten, je nachdem, wie viele Wiederholungen Sie angeben. Ein alternativer Ansatz besteht darin, Invoke-RestMethod in Ihre eigene Methode einzuschließen, die Wiederholungsversuche für bestimmte Fehler verwaltet.

Die folgende Invoke-ResilientRestMethod-Funktion verwendet ein request-Hashtable-Objekt als obligatorischen Parameter und eine boolesche returnHeader-Kennzeichnung, um anzugeben, ob der Antwortheader zurückgegeben werden soll oder nicht. Wenn $returnHeader true ist, wird die Anforderung mithilfe des Invoke-RestMethod-Befehls mit dem Parameter ResponseHeadersVariable gesendet, um die zurückgegebenen Header zu erfassen, und es wird Out-Null verwendet, sodass die Ausgabe, die den leeren Antworttext darstellt, nicht mit der Funktion zurückgegeben wird. Andernfalls sendet die Funktion die Anforderung mit Invoke-RestMethod mithilfe des request-Objekts und gibt den Antworttext zurück.

Wenn Invoke-RestMethod mit einem 429-Fehler fehlschlägt, wird geprüft, ob das request-Objekt eine MaximumRetryCount-Eigenschaft hat. Wenn nicht, wird eine mit dem Wert 3 hinzugefügt. Anschließend wird Invoke-RestMethod unter Verwendung des Anforderungsobjekts und des Retry-After-Antwortheader-Werts erneut versucht. Wenn die returnHeader-Kennzeichnung wahr ist, wird der Antwortheader zurückgegeben. Wenn Invoke-RestMethod mit einem anderen Fehler fehlschlägt, wird die Ausnahme erneut ausgelöst.

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 $_
   }
}

Sie können eine solche Funktion in Ihren wiederverwendbaren Funktionen verwenden. Wenn Funktionen Werte aus dem Header der Antwort zurückgeben müssen, müssen sie den returnHeader-Wert auf $true setzen. Beispielsweise ändert die folgende New-Record-Funktion die Beispielfunktion in Tabellenvorgangsfunktionen erstellen so, dass sie Invoke-ResilientRestMethod anstelle von direkt Invoke-RestMethod verwendet.

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())
}

Andernfalls kann die Invoke-ResilientRestMethod die Invoke-RestMethod wie in diesem Get-Record-Beispiel gezeigt ersetzen:

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
}

Der einzige Unterschied besteht darin, dass Sie die Hashtabelle ($RetrieveRequest) an die Methode übergeben, anstatt Splatting (@RetrieveRequest) zu verwenden. Andernfalls erhalten Sie einen Skriptfehler: A parameter cannot be found that matches parameter name 'Headers'.

Debuggen mithilfe von Fiddler

Fiddler ist ein Web-Debugging-Proxy, der zum Anzeigen des HTTP-Datenverkehrs auf Ihrem Computer verwendet wird. Das Anzeigen dieser Daten ist beim Debuggen von Skripten hilfreich. Standardmäßig sind HTTP-Anforderungen und -Antworten, die mit dem Invoke-RestMethod-Cmdlet gesendet werden, nicht sichtbar, wenn Fiddler verwendet wird.

Um den HTTP-Datenverkehr mit Fiddler anzuzeigen, legen Sie den Invoke-RestMethod-Proxy-Parameter auf die URL fest, die auf Ihrem lokalen Computer als Fiddler-Proxy konfiguriert ist. Die URL lautet standardmäßig http://127.0.0.1:8888. Ihrer kann anders sein.

Wenn Sie beispielsweise die WhoAmI-Funktion mit dem -Proxy-Parameter aufrufen, während Fiddler Datenverkehr erfasst:

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

In Fiddler können Sie alle Details sehen:

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"}

Wenn Fiddler nicht ausgeführt wird, erhalten Sie eine Fehlermeldung wie diese:

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

Wenn Sie sich dafür entscheiden, alle Ihre Invoke-RestMethod-Aufrufe über eine einzige Funktion weiterzuleiten, wie zum Beispiel die Invoke-ResilientRestMethod, die unter Dataverse-Dienstschutzgrenzwerte verwalten beschrieben ist, können Sie einige Variablen in der Core.ps1-Datei festlegen, um diese Option an einem einzigen Ort zu konfigurieren.

# <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'

Anschließend können Sie in Ihrer zentralisierten Funktion den -Proxy-Parameter durch Splatting mit der $request-Hashtabelle nur beim Debuggen mit Fiddler festlegen.

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

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

   ...

Erfahren Sie mehr über die Erfassung von Webdatenverkehr mit Fiddler

Das CSDL-$metadata-Dokument für die Dataverse-Web-API herunterladen

Die CSDL $metadata sind die Quelle der Wahrheit über Dataverse-Web-API-Funktionen. Sie können sie in einem Browser anzeigen. Eventuell ist es jedoch einfacher, die Datei herunterzuladen und innerhalb von Visual Studio Code anzuzeigen. Das folgende Skript ist eine modifizierte Version des Skripts, das in Schnellstart-Web-API mit PowerShell eingeführt wurde. Der Unterschied besteht darin, dass das Invoke-WebRequest-Cmdlet verwendet wird, das besser zum Herunterladen eines XML-Dokuments geeignet ist.

$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. Kopieren Sie das Skript.
  2. Bearbeiten Sie die $environmentUrl- und $writeFileTo-Variablen je nach Ihren Anforderungen.
  3. Führen Sie das Skript im Visual Studio Code aus.

Das CSDL-$metadata-Dokument für die Dataverse-Web-API wird im Visual Studio Code geöffnet.

Sie erhalten wahrscheinlich eine Benachrichtigung mit der Meldung:

Aus Leistungsgründen wurden Dokumentsymbole auf 5.000 Elemente begrenzt. Wenn ein neuer Grenzwert festgelegt wird, schließen Sie bitte diese Datei und öffnen Sie sie erneut, um die Dokumentsymbole neu zu berechnen.

Die Benachrichtigung bietet die Möglichkeit, den xml.symbols.maxItemsComputed-Grenzwert der Visual Studio-XML-Erweiterung zu ändern. Für die meisten CSDL-$metadata-Dokumente der Dataverse-Web-API sollte es ausreichen, den Grenzwert auf 500.000 festzulegen.

Problembehandlung

Dieser Abschnitt enthält einige Hinweise zu Problemen, auf die Sie möglicherweise stoßen.

Fehlerdialog: Verbinden Sie ENOENT\\.\pipe\<RANDOM_text> mit der Schaltfläche „Launch.json öffnen“

Dieser Fehler kann manchmal beim Debuggen mit Visual Studio Code auftreten. Beheben Sie diesen wie folgt:

  1. Wählen Sie Ansicht > Befehlspalette ... aus dem Visual Studio Code-Menü oder drücken Sie Strg+Umschalttaste+P.
  2. Geben Sie restart ein und wählen Sie Powershell: Restart session aus. Weitere Informationen finden Sie unter PowerShell/vscode-powershell-GitHub-Issue 4332.

Nächste Schritte,

Erfahren Sie mehr über Dataverse-Web-API-Funktionen durch Verständnis der Servicedokumente.

Überprüfen Sie den Beispielcode und führen Sie ihn aus.