Utiliser PowerShell avec les API REST — la pratique d'Invoke-RestMethod
· Go Komura · PowerShell, API REST, Windows, Automatisation, Système métier, Intégration, JSON, Amélioration opérationnelle
« Récupérer les données de commande depuis l’API web du système central pour les reporter dans un tableau Excel interne. » « Interroger chaque matin l’API SaaS de gestion des présences pour produire le planning de présence du jour. » — Parmi les usages professionnels de PowerShell, l’intégration aux API REST est celui qui a le plus nettement progressé ces dernières années. Ce n’est pas au point de justifier l’achat d’un outil dédié, mais le travail manuel ne suit plus. Pour combler cet écart, Invoke-RestMethod est un outil extrêmement puissant.
En revanche, si le faire fonctionner est simple, c’est le passage en exploitation qui devient soudainement difficile — c’est aussi une caractéristique de l’intégration aux API. Le japonais qui se mélange, le contenu des réponses d’erreur illisible, des échecs occasionnels avec un code 429, une connexion qui ne passe pas dans un environnement avec proxy, ou encore Windows PowerShell 5.1 qui se fait bloquer par TLS alors que ce n’est pas le cas ailleurs. Ce sont là autant de problèmes propres aux « systèmes avec un interlocuteur en face ».
Cet article s’adresse aux équipes informatiques et aux développeurs qui appellent des API depuis PowerShell en interne, et organise, dans l’ordre où on en a besoin en pratique, l’authentification, l’envoi et la réception de JSON, la gestion des erreurs, les nouvelles tentatives, la pagination, et jusqu’aux pièges propres à la version 5.1.
Environnement de référence et environnement de validation
| Élément | Contenu |
|---|---|
| Version ciblée | Cet article couvre à la fois Windows PowerShell 5.1 et PowerShell 7. Les fonctionnalités disponibles uniquement à partir de PowerShell 6, comme -SkipHttpErrorCheck, -Authentication, -MaximumRetryCount ou -NoProxy, sont signalées explicitement à chaque occurrence dans le texte (les contraintes propres à 5.1 sont résumées au §8)1 |
| Environnement de validation des exemples | Le code d’exemple distribué en fin d’article a été exécuté et validé sous PowerShell 7.6 (21 tests Pester). Les remarques propres à 5.1 au §8 sont établies à partir des spécifications de la version 5.1 |
| API utilisée dans les exemples | https://api.example.co.jp/... est un point de terminaison fictif. Il ne fonctionnera pas tel quel. Les exemples de réponses JSON sont également fictifs, fournis à des fins d’illustration |
1. La conclusion, d’abord
- Pour une API JSON/XML, utilisez
Invoke-RestMethod. Elle convertit automatiquement la réponse en objet. Si vous avez besoin du code de statut ou des en-têtes, utilisezInvoke-WebRequest, ou bien-StatusCodeVariable/-ResponseHeadersVariable.23 - Passer l’authentification par l’en-tête est la méthode la plus universelle. À partir de PowerShell 6, vous pouvez aussi utiliser
-Authentication Bearer -Token(SecureString).2 - Pour du JSON contenant du japonais, envoyer un tableau d’octets UTF-8 est la méthode la plus sûre. Déclarez explicitement
charset=utf-8dans-ContentType. - La profondeur par défaut de
ConvertTo-Jsonest de 2. Pour un objet fortement imbriqué, si vous ne spécifiez pas-Depth, les niveaux les plus profonds seront tronqués.4 - Les réponses 4xx/5xx deviennent des erreurs bloquantes (terminating error). À partir de PowerShell 7, vous pouvez lire le corps de la réponse via
$_.ErrorDetails.Message. Il est aussi possible d’empêcher la transformation en exception avec-SkipHttpErrorCheck.2 - En cas de code 429, attendez le délai indiqué par
Retry-After. Si vous spécifiez-MaximumRetryCount, le mécanisme intégré s’y adapte automatiquement. Mais la nouvelle tentative intégrée couvre tous les codes 400 à 599 (ainsi que 304) : 401 et 404 sont aussi renvoyés. N’implémentez votre propre logique que si vous avez besoin d’un traitement différencié par code ou d’un backoff exponentiel.2 - Ne réessayez jamais automatiquement une requête non idempotente comme
POST. L’idempotence est la propriété selon laquelle envoyer la même requête plusieurs fois ne change pas le résultat :GET,PUTetDELETEsont idempotents,POSTne l’est pas. Même en cas d’erreur de communication ou de 5xx, le serveur peut avoir déjà traité la requête, et la renvoyer provoquerait un double enregistrement. - La pagination par en-tête
Linkpeut être automatisée avec-FollowRelLink. La pagination par curseur nécessite une boucle maison.2 - Windows PowerShell 5.1 comporte des pièges qui lui sont propres : la nécessité (ou non) de
-UseBasicParsing, l’activation explicite de TLS 1.2, et la gestion de l’encodage.1 - N’utilisez jamais
-SkipCertificateChecken exploitation permanente. La bonne solution consiste à faire reconnaître l’autorité de certification interne.
2. Invoke-RestMethod et Invoke-WebRequest
Commençons par bien cerner la différence.23
Invoke-RestMethod |
Invoke-WebRequest |
|
|---|---|---|
| Traitement de la réponse | Convertit automatiquement le JSON/XML en objet | WebResponseObject (corps brut, en-têtes, code) |
| Usage principal | API REST | Récupération de HTML, consultation du statut ou des en-têtes |
| Code de statut | Obtenu via -StatusCodeVariable (PS7+) |
.StatusCode |
| En-têtes | Obtenus via -ResponseHeadersVariable (PS6+) |
.Headers |
Pour appeler une API, Invoke-RestMethod est le choix de base. Même quand vous avez besoin des en-têtes ou du code, les paramètres de variable dédiés permettent de les récupérer.
$data = Invoke-RestMethod -Uri 'https://api.example.co.jp/v1/orders' `
-Headers @{ Authorization = "Bearer $token" } `
-StatusCodeVariable status -ResponseHeadersVariable headers -TimeoutSec 30
"HTTP $status / Requêtes restantes : $($headers['X-RateLimit-Remaining'])"
$data.items | Select-Object orderId, customerName, amount
3. Transmettre l’authentification
La méthode la plus universelle consiste à l’écrire directement dans l’en-tête ; elle fonctionne de la même façon en 5.1 comme en 7.
# (1) Jeton Bearer (le plus courant)
$headers = @{ Authorization = "Bearer $accessToken"; Accept = 'application/json' }
Invoke-RestMethod -Uri $uri -Headers $headers
# (2) Clé d'API (le nom de l'en-tête dépend de la spécification du fournisseur)
$headers = @{ 'X-Api-Key' = $apiKey }
# (3) Authentification Basic (à partir de PowerShell 6, -Authentication est utilisable)
Invoke-RestMethod -Uri $uri -Authentication Basic -Credential $cred
# (4) Passer le Bearer via -Token (PowerShell 6 et supérieur ; utilisable avec un SecureString)
Invoke-RestMethod -Uri $uri -Authentication Bearer -Token $secureToken
# (5) Certificat client
Invoke-RestMethod -Uri $uri -Certificate $cert
Lorsque vous utilisez -Authentication, PowerShell refuse par défaut toute utilisation en dehors de HTTPS (vous pouvez contourner cela avec -AllowUnencryptedAuthentication, mais comme les identifiants circuleraient alors en clair, vous ne devriez pas le faire).2
Ne jamais écrire de jetons ou de clés d’API directement dans un script est un prérequis absolu. La méthode de stockage avec SecretManagement est détaillée dans « La gestion sécurisée des identifiants sous PowerShell ».
4. Envoyer du JSON — le piège du japonais et de -Depth
Les deux pièges dans lesquels on tombe presque à coup sûr à l’envoi sont le texte corrompu (mojibake) et la troncature de l’imbrication.
Le paramètre -Depth de ConvertTo-Json a une valeur par défaut de 2 ; au-delà de cette profondeur, les niveaux ne sont pas développés et sont remplacés par une chaîne représentant le nom du type, entre autres.4 Spécifiez-le systématiquement pour un corps de requête comportant de l’imbrication.
$body = @{
order = @{
customer = @{ code = 'C001'; name = '株式会社サンプル' } # 3e niveau
lines = @( @{ item = 'A-100'; qty = 3 } )
}
}
# [MAUVAIS] Avec la valeur par défaut -Depth 2, le contenu de customer et lines est perdu
$json = $body | ConvertTo-Json
# [OK] Spécifier une profondeur suffisante
$json = $body | ConvertTo-Json -Depth 10
La parade contre le texte corrompu consiste, de la façon la plus sûre, à convertir en tableau d’octets UTF-8 avant l’envoi.
$json = $body | ConvertTo-Json -Depth 10
$bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
$res = Invoke-RestMethod -Uri $uri -Method Post `
-Headers @{ Authorization = "Bearer $token" } `
-ContentType 'application/json; charset=utf-8' `
-Body $bytes -TimeoutSec 60
Sous PowerShell 7, passer directement la chaîne l’envoie déjà en UTF-8, mais pour un script partagé avec 5.1, convertir en tableau d’octets absorbe les différences entre environnements. Pour les questions générales d’encodage sous Windows, voir « Encodages de caractères et fins de ligne sous Windows ».
Vérifiez l’aller-retour, avant l’envoi et après la réception. À partir du $body ci-dessus, le JSON que l’on souhaite produire a la forme suivante.
{
"order": {
"customer": { "code": "C001", "name": "株式会社サンプル" },
"lines": [ { "item": "A-100", "qty": 3 } ]
}
}
Si vous oubliez de spécifier -Depth, le contenu de customer et lines, qui se trouvent au troisième niveau, ne sera pas développé, et vous n’obtiendrez pas cette forme. Le moyen le plus rapide de vérifier est d’afficher directement $json à l’écran avant l’envoi et de contrôler visuellement si les niveaux sont bien présents (comme il est construit à partir d’une table de hachage, l’ordre des clés dépend de l’ordre d’énumération ; si vous voulez fixer l’ordre, utilisez [ordered]@{}).
Supposons qu’en réponse à cette requête, l’API renvoie ceci (exemple fictif).
{
"orderId": "2026-000123",
"status": "accepted",
"customer": { "code": "C001", "name": "株式会社サンプル" }
}
Comme Invoke-RestMethod transforme automatiquement la réponse en objet, le côté réception peut s’écrire ainsi.
$res.orderId # 2026-000123
$res.customer.name # 株式会社サンプル ── si ceci est corrompu, le problème vient de la réception
Pour diagnostiquer un texte corrompu à la réception, vérifiez si $res.customer.name est lisible. S’il est corrompu, soupçonnez d’abord que le charset n’est pas correctement déclaré dans le Content-Type de la réponse (vous pouvez récupérer les en-têtes avec -ResponseHeadersVariable). Cet aller-retour permet de déterminer si la cause vient de l’émission ou de la réception.
5. Gestion des erreurs — sans pouvoir lire le corps, impossible d’enquêter
Invoke-RestMethod traite les réponses 4xx/5xx comme des erreurs bloquantes (terminating errors). Autrement dit, vous pouvez les capturer avec try/catch, mais la vraie question devient comment lire le corps du message d’erreur renvoyé par l’API.
try {
$res = Invoke-RestMethod -Uri $uri -Method Post -Body $bytes `
-ContentType 'application/json; charset=utf-8' -TimeoutSec 30
}
catch {
$status = $_.Exception.Response.StatusCode # ex. : BadRequest / 400
# À partir de PowerShell 7, le corps de la réponse (message d'erreur de l'API) se trouve ici
$detail = $_.ErrorDetails.Message
Write-Warning "Échec de l'API ($status) : $detail"
throw
}
Point de vérification. $status contient le statut HTTP sous forme de valeur énumérée (comme l’indique le commentaire, il s’affichera par exemple BadRequest), et $detail contient le corps renvoyé par l’API tel quel, sous forme de chaîne. Pour une API qui renvoie du JSON, vous pouvez en extraire les éléments avec $detail | ConvertFrom-Json, ce qui permet de brancher sur le code d’erreur ou le nom du champ. À l’inverse, si $detail reste vide, soit l’API ne renvoie pas de corps, soit vous êtes sous Windows PowerShell 5.1 (en 5.1, il faut lire le flux de réponse soi-même — voir §8).
Pour ne pas perdre de temps à enquêter sur un « HTTP 400 reçu, mais sans savoir pourquoi la requête a été rejetée », concevez le système pour toujours consigner le corps de l’erreur dans les journaux.
Pour un traitement qui doit brancher selon le code de statut, il est plus simple d’utiliser -SkipHttpErrorCheck pour empêcher la transformation en exception (à partir de PowerShell 7).2
$res = Invoke-RestMethod -Uri $uri -SkipHttpErrorCheck -StatusCodeVariable code -TimeoutSec 30
switch ($code) {
200 { $res.items }
404 { Write-Warning "La cible n'existe pas"; @() }
{ $_ -ge 500 } { throw "Erreur côté serveur : $code" }
default { throw "Réponse inattendue : $code" }
}
6. Nouvelles tentatives — le 429 et les échecs temporaires
À partir de PowerShell 6, Invoke-RestMethod dispose de -MaximumRetryCount et -RetryIntervalSec, qui déclenchent une nouvelle tentative en cas d’échec. De plus, si la réponse 429 contient un Retry-After, c’est la valeur de cet en-tête qui est utilisée, et non l’intervalle spécifié.2 Autrement dit, si votre seul besoin est de « attendre comme indiqué en cas de limitation de débit, puis réessayer », la fonctionnalité intégrée suffit.
# Si le seul besoin est de gérer la limitation de débit, cela suffit souvent
Invoke-RestMethod -Uri $uri -Headers $headers -MaximumRetryCount 4 -RetryIntervalSec 5
Cependant, la nouvelle tentative ne concerne pas uniquement le 429. La documentation précise qu’une nouvelle tentative a lieu « en cas de réception d’un code d’échec compris entre 400 et 599 (et 304) ».2 Autrement dit, même des requêtes qui ne seront jamais corrigées quel que soit le nombre d’envois, comme 401 (erreur d’authentification), 403 (droits insuffisants) ou 404 (URL erronée), sont retentées. Si une exécution sans surveillance tourne avec un jeton mal configuré, elle gaspille -MaximumRetryCount × -RetryIntervalSec secondes avant de constater l’échec, et la même requête invalide est envoyée à répétition à l’API. Si vous voulez arrêter immédiatement en cas d’erreur permanente, il faut l’implémentation maison décrite ci-après.
Une fonction de nouvelle tentative maison devient nécessaire dans les cas suivants.
- Vous voulez un traitement différent selon le code de statut (échec immédiat pour la série 400, nouvelle tentative uniquement pour les 5xx, etc.)
- Vous voulez un backoff exponentiel (le mécanisme intégré réessaie à intervalle fixe)
- Vous voulez consigner le corps de l’erreur de l’API dans les journaux en cas d’échec
Voici à quoi cela ressemble.
function Invoke-KsApi {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string] $Uri,
[string] $Method = 'Get',
[object] $Body,
[hashtable] $Headers = @{},
[ValidateRange(1, 10)] [int] $MaxAttempts = 4,
# Si Retry-After indique un délai d'attente supérieur à ceci, on interrompt sans attendre
[ValidateRange(1, 86400)] [int] $MaxWaitSeconds = 300,
# N'autoriser la nouvelle tentative de POST etc. que si l'API prend en charge une clé d'idempotence
[string] $IdempotencyKey
)
# On ne peut retenter que si recevoir deux fois la même requête ne change pas le résultat.
# POST/PATCH peuvent correspondre au cas où « le serveur a réussi mais la réponse n'est pas arrivée »,
# et un simple renvoi entraînerait un double enregistrement
$idempotentMethods = 'Get', 'Head', 'Options', 'Put', 'Delete'
$canRetry = ($Method -in $idempotentMethods) -or $IdempotencyKey
if ($IdempotencyKey) { $Headers = $Headers + @{ 'Idempotency-Key' = $IdempotencyKey } }
for ($attempt = 1; $attempt -le $MaxAttempts; $attempt++) {
$params = @{
Uri = $Uri
Method = $Method
Headers = $Headers
TimeoutSec = 60
SkipHttpErrorCheck = $true # On ne veut pas d'exception, car on souhaite brancher selon le code
StatusCodeVariable = 'code'
ResponseHeadersVariable = 'resHeaders'
}
# Pour pouvoir envoyer $Body même s'il vaut $false, 0 ou une chaîne vide,
# on juge non pas sa valeur booléenne, mais si l'argument a été fourni
if ($PSBoundParameters.ContainsKey('Body')) {
# Passé par le pipeline, un tableau vide @() devient « 0 élément en entrée » et renvoie $null,
# donc on convertit tel quel via -InputObject sans énumération (@() devient [])
$json = ConvertTo-Json -InputObject $Body -Depth 10
$params.Body = [System.Text.Encoding]::UTF8.GetBytes($json)
$params.ContentType = 'application/json; charset=utf-8'
}
# -SkipHttpErrorCheck ne supprime que les réponses d'erreur HTTP.
# Les erreurs de communication sans réponse — délai dépassé, échec de résolution de nom,
# réinitialisation de connexion, erreur TLS, etc. — sont levées comme exceptions ; on les capture ici pour réessayer
try {
$code = $null
$res = Invoke-RestMethod @params
}
catch {
# Pour une requête non idempotente, il se peut que le serveur ait réussi
# alors que seule la réponse n'est pas arrivée. On ne renvoie pas automatiquement, on laisse l'appelant décider
if (-not $canRetry) {
throw "Erreur de communication ($Method n'est pas réessayé ; vérifiez si la requête a déjà été traitée) : $($_.Exception.Message)"
}
if ($attempt -eq $MaxAttempts) { throw }
$wait = [math]::Min([math]::Pow(2, $attempt), 60)
Write-Warning "Erreur de communication : $($_.Exception.Message) ── nouvelle tentative dans $wait s ($attempt/$MaxAttempts)"
Start-Sleep -Seconds $wait
continue
}
if ($code -lt 400) { return $res } # Succès
# Seules les erreurs temporaires méritent une nouvelle tentative. On l'explicite via une liste blanche
# (retenter une erreur permanente comme 405 ou 415 gaspille du temps et
# ne laisse à la fin qu'un message sans rapport de « limite de tentatives atteinte »)
$retryable = @(408, 429, 500, 502, 503, 504)
if ($code -notin $retryable) {
throw "Erreur d'API ($code) : $($res | ConvertTo-Json -Compress -Depth 3)"
}
# Un 5xx peut correspondre à « un échec survenu après traitement côté serveur »,
# donc on ne renvoie pas les requêtes non idempotentes. Idem pour 429 (rien ne garantit qu'il a été rejeté avant traitement)
if (-not $canRetry) {
throw "Erreur d'API ($code). $Method n'est pas réessayé automatiquement : $($res | ConvertTo-Json -Compress -Depth 3)"
}
if ($attempt -eq $MaxAttempts) {
throw "Limite de nouvelles tentatives atteinte ($code) : $($res | ConvertTo-Json -Compress -Depth 3)"
}
# Retry-After peut être renvoyé non seulement en « nombre de secondes » mais aussi au format de date HTTP.
# Le convertir directement en [int] provoque une exception et fait échouer chaque tentative
$wait = $null
$retryAfter = if ($resHeaders) { $resHeaders['Retry-After'] | Select-Object -First 1 }
if ($retryAfter) {
$seconds = 0
$date = [datetime]::MinValue
if ([int]::TryParse($retryAfter, [ref] $seconds)) {
$wait = $seconds
}
elseif ([datetime]::TryParse($retryAfter,
[cultureinfo]::InvariantCulture,
[System.Globalization.DateTimeStyles]::AdjustToUniversal, [ref] $date)) {
# Toujours arrondir la fraction à l'entier supérieur. [int] arrondit (au pair le plus proche),
# ce qui arrondirait 0,4 seconde restante à 0 et renverrait la requête avant le délai du serveur
$remaining = ($date - [datetime]::UtcNow).TotalSeconds
$wait = if ($remaining -gt 0) { [int][math]::Ceiling($remaining) } else { 0 }
}
}
if ($null -eq $wait) {
$wait = [math]::Min([math]::Pow(2, $attempt), 60) # Backoff exponentiel (plafonné à 60 secondes)
}
elseif ($wait -gt $MaxWaitSeconds) {
# Raccourcir arbitrairement le délai indiqué par le serveur pour renvoyer plus tôt ne ferait que
# répéter les 429 jusqu'à la limite. Si le délai est trop long, on le renvoie à l'appelant avec le temps d'attente
throw "Limitation de débit en cours. Le temps d'attente indiqué par le serveur ($wait s) dépasse la limite de $MaxWaitSeconds s : arrêt. Réessayez plus tard ($code)"
}
Write-Warning "HTTP $code ── nouvelle tentative dans $wait s ($attempt/$MaxAttempts)"
Start-Sleep -Seconds $wait
}
}
Vérifiez que les nouvelles tentatives ont bien lieu. Quand cette fonction entre en nouvelle tentative, une ligne Write-Warning s’affiche à chaque essai (sous la forme HTTP 429 ── nouvelle tentative dans 30 s (1/4)). Il y a trois points à surveiller : le nombre de secondes d’attente correspond-il à ce qu’indique le serveur, le nombre de tentatives reste-t-il dans la plage de -MaxAttempts, et une erreur permanente comme 401 ou 404 provoque-t-elle bien une exception immédiate sans nouvelle tentative. Comme l’écran ne conserve rien lors d’une exécution sans surveillance, faites aussi retomber le flux d’avertissement dans les journaux (« En finir avec Write-Host — flux de sortie et conception des journaux sous PowerShell »).
Le deuxième point clé est la gestion de l’idempotence (la propriété selon laquelle envoyer la même requête plusieurs fois ne change pas le résultat). GET ou PUT donnent le même résultat qu’on les reçoive une ou deux fois, mais ce n’est pas le cas de POST. En particulier, dans le cas où « l’enregistrement a réussi côté serveur, mais la communication a été coupée avant que la réponse ne revienne », un renvoi naïf provoque un double enregistrement. Dans l’implémentation ci-dessus, la nouvelle tentative automatique n’est autorisée que pour les méthodes idempotentes ou lorsque l’API prend en charge une clé d’idempotence (Idempotency-Key) ; dans tous les autres cas, l’exécution s’arrête avec un message explicite invitant à vérifier si la requête a déjà été traitée.
Lorsque Retry-After revient sous forme de date HTTP, arrondissez le temps restant à l’entier supérieur. Le cast vers [int] n’est pas une troncature mais un arrondi (à la valeur paire en cas de fraction exactement égale à 0,5) : si le temps restant, une fois déduit le délai de réception de la réponse, passe sous 0,5 seconde, il devient 0. Comme Start-Sleep -Seconds 0 revient immédiatement, cela conduit à renvoyer la requête avant l’heure indiquée par le serveur, qui répondra de nouveau par un 429. Répété sur le nombre de tentatives, cela se termine par une « limite de nouvelles tentatives atteinte ». C’est pour cette raison que l’implémentation ci-dessus intercale [math]::Ceiling.
Ne pas tronquer Retry-After est un autre point important. Si le serveur indique « revenez dans 30 minutes » et que vous renvoyez la requête après seulement 5 minutes sous prétexte d’une limite, vous n’obtiendrez rien d’autre qu’un nouveau 429. Vous aurez épuisé vos tentatives avec des requêtes inutiles, et il ne restera à la fin qu’un message de « limite de nouvelles tentatives atteinte ». Dans l’implémentation ci-dessus, lorsque le délai indiqué dépasse -MaxWaitSeconds, le code échoue immédiatement en indiquant le délai d’attente, plutôt que de patienter moins longtemps que demandé. Pour un traitement par lots, l’appelant peut, sur réception de cette exception, choisir de reporter à la prochaine exécution ou d’attendre exactement le délai indiqué.
Autre point important : les codes à retenter sont explicités via une liste blanche. Avec une approche du type « n’exclure que les erreurs permanentes énumérées », un code oublié dans cette liste (405 Method Not Allowed, 415 Unsupported Media Type, etc.) serait traité comme une erreur temporaire, ce qui répéterait une requête qui ne pourra jamais aboutir et ne laisserait à la fin qu’un message énigmatique de « limite de nouvelles tentatives atteinte ». La bonne approche est de ne retenter que les codes connus pour être temporaires, et de faire échouer immédiatement tous les autres avec le détail de l’erreur renvoyé par l’API. Pour les principes généraux de conception des nouvelles tentatives, voir « Gestion des erreurs et conception des tentatives sous PowerShell ».
7. Pagination
Il est rare qu’une API renvoie l’intégralité des résultats en une seule fois. Il existe principalement deux approches.
(1) L’approche par en-tête Link (adoptée notamment par GitHub) permet de suivre automatiquement la page suivante avec -FollowRelLink.2
# Suit automatiquement la page suivante (on peut aussi spécifier une limite du nombre de pages récupérées)
$all = Invoke-RestMethod -Uri $uri -Headers $headers -FollowRelLink -MaximumFollowRelLink 20
(2) L’approche par curseur/décalage (offset) demande une boucle maison. Notez que le code suivant utilise Invoke-KsApi, définie au §6. Si vous voulez tester uniquement cette section, remplacer Invoke-KsApi -Uri $u -Headers $headers par Invoke-RestMethod -Uri $u -Headers $headers fonctionne aussi (vous perdez simplement les nouvelles tentatives et la consignation du corps d’erreur).
$items = [System.Collections.Generic.List[object]]::new()
$cursor = $null
$page = 0
$maxPages = 100
do {
$page++
# Le séparateur change selon que l'URI d'origine comporte déjà une requête ou non.
# Toujours ajouter ? donnerait .../items?status=active?cursor=...,
# et le serveur verrait le curseur comme faisant partie de la valeur de status.
# Le curseur lui-même est une chaîne opaque (pouvant contenir + & = # etc.), donc toujours l'encoder
$u = if ($cursor) {
$sep = if ($uri.Contains('?')) { '&' } else { '?' }
"$uri$sep" + "cursor=$([uri]::EscapeDataString($cursor))"
}
else { $uri }
$res = Invoke-KsApi -Uri $u -Headers $headers
$items.AddRange([object[]]$res.items)
$cursor = $res.nextCursor
# Toujours signaler une interruption. Sortir silencieusement ferait croire à tort que tous les éléments ont été récupérés
if ($page -ge $maxPages -and $cursor) {
Write-Warning "Limite du nombre de pages ($maxPages) atteinte, arrêt de la pagination. Des éléments peuvent manquer"
break
}
} while ($cursor)
"Nombre récupéré : $($items.Count)"
L’utilisation de List[T] pour $items vise à éviter la recréation du tableau à chaque += (« Où regarder quand un script PowerShell est lent »).
La vérification se fait sur la dernière ligne. Comparez le nombre affiché dans Nombre récupéré : ... avec le total côté API (de nombreuses API exposent un champ comme total dans la réponse) ou avec le nombre affiché dans la console d’administration. En cas d’écart, regardez d’abord si un avertissement d’interruption est apparu. Si le nombre est insuffisant sans qu’aucun avertissement ne soit apparu, soupçonnez que le nom du champ nextCursor ne correspond pas à la spécification.
N’oubliez pas non plus de toujours fixer une limite au nombre de pages. Il arrive réellement qu’un serveur continue de renvoyer le même curseur, ou oublie de vider nextCursor. Sans limite, le script continuerait alors d’envoyer des requêtes sans jamais s’arrêter. Et le fait d’avoir interrompu le traitement doit être signalé par un avertissement visible. Un break silencieux ferait croire à l’appelant qu’un résultat incomplet est en réalité complet.
8. Pièges propres à Windows PowerShell 5.1
Dans les environnements où 5.1 subsiste encore, ce sont les trois points suivants qu’il faut suspecter en premier.1
(1) TLS 1.2 n’est pas activé. Avec les anciens paramètres par défaut, la connexion à une API qui n’accepte que TLS 1.2 ou plus échoue, avec une erreur du type « la connexion sous-jacente a été fermée ».
# Rituel nécessaire sous Windows PowerShell 5.1 (en début de script)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
(2) -UseBasicParsing peut s’avérer nécessaire. Sous 5.1, Invoke-WebRequest utilise par défaut le moteur d’Internet Explorer pour analyser le HTML, ce qui échoue avec un compte où IE n’a jamais été initialisé (un compte de service, par exemple). À partir de PowerShell 6, cette dépendance disparaît, et -UseBasicParsing est simplement ignoré s’il est spécifié.1
(3) -SkipHttpErrorCheck et -Authentication n’existent pas. Sous 5.1, il faut lire soi-même le flux de réponse pour accéder au corps de l’erreur. Si vous travaillez sérieusement l’intégration d’API, adopter PowerShell 7 reste la solution la plus économique (« Différences entre Windows PowerShell 5.1 et PowerShell 7 »).
9. Proxy et certificats
Pour appeler une API sur Internet depuis le réseau interne, franchir le proxy est le premier obstacle. Si vous ne spécifiez pas -Proxy, c’est le proxy configuré dans les paramètres Internet (Options Internet) ou par les variables d’environnement qui est utilisé.2 Autrement dit, « ne rien spécifier » ne signifie pas « ne pas utiliser de proxy ». C’est ce qui explique que le résultat varie selon le compte d’exécution.
| Symptôme | Cause à examiner | Solution |
|---|---|---|
407 Proxy Authentication Required |
Le proxy exige une authentification | Spécifier -ProxyCredential ou -ProxyUseDefaultCredentials avec -Proxy2 |
| Ça fonctionne en local mais échoue uniquement lors du traitement de nuit | Le compte d’exécution récupère des paramètres Internet différents | Spécifier explicitement -Proxy dans le script. Valider avec le compte d’exécution effectif |
| Même les API internes sont absorbées par le proxy | La configuration de proxy par défaut s’applique aussi aux destinations internes | Contourner explicitement avec -NoProxy (PowerShell 6 et supérieur)2 |
| Où stocker les identifiants du proxy | Écrits en dur dans le script | Les récupérer depuis le coffre SecretManagement |
# Spécifie explicitement le proxy et s'authentifie avec les identifiants de l'utilisateur connecté
Invoke-RestMethod -Uri $uri -Proxy 'http://proxy.example.co.jp:8080' -ProxyUseDefaultCredentials
# Passe par un proxy authentifié avec un compte dédié.
# -ProxyCredential s'utilise avec -Proxy. Il ne peut pas être combiné avec -ProxyUseDefaultCredentials
$proxyCred = Get-Secret -Name 'ProxyAccount' # Récupéré depuis le coffre
Invoke-RestMethod -Uri $uri -Proxy 'http://proxy.example.co.jp:8080' -ProxyCredential $proxyCred
# Ne pas faire passer par le proxy les API internes (PowerShell 6 et supérieur)
Invoke-RestMethod -Uri 'https://api.internal.example.local/v1/ping' -NoProxy
-ProxyCredential et -ProxyUseDefaultCredentials supposent tous deux que -Proxy est spécifié, et ne peuvent pas être utilisés ensemble.2 Windows PowerShell 5.1 ne dispose pas de -NoProxy. Si vous voulez, sous 5.1, exclure du proxy uniquement les communications internes, il faut passer par la liste d’exceptions (adresses sans proxy) des paramètres Internet du compte d’exécution.
Lorsque l’exécution se fait avec le compte de service du Planificateur de tâches, les paramètres de proxy peuvent différer de ceux d’une session interactive. C’est le schéma type du « ça fonctionne en local mais échoue uniquement au traitement de nuit ». Validez avec le compte d’exécution effectif (« Les tâches du Planificateur de tâches ne s’exécutent pas »). Ne pas écrire les identifiants du proxy en dur dans le script vaut tout autant que pour les jetons d’API (« La gestion sécurisée des identifiants sous PowerShell »).
N’utilisez -SkipCertificateCheck face à une erreur de certificat que de manière limitée, pour un contournement temporaire en environnement de test. La solution durable consiste à placer le certificat de l’autorité de certification interne dans le magasin des autorités de certification racines de confiance, et à émettre correctement le certificat serveur.
10. Bonnes pratiques (tableau de décision)
| Point | Options | Critère de décision |
|---|---|---|
| Applet de commande | Invoke-RestMethod / Invoke-WebRequest |
Pour une API JSON, la première. En-têtes et code via les variables dédiées23 |
| Authentification | En-tête direct / -Authentication |
Méthode par en-tête si partagé avec 5.1. Stocker les valeurs avec SecretManagement |
| Envoi JSON | Chaîne / Tableau d’octets UTF-8 + charset explicite | Évite le texte corrompu dû aux différences d’environnement |
ConvertTo-Json |
Par défaut / -Depth explicite |
Par défaut 2. Toujours le spécifier en cas d’imbrication4 |
| Erreurs | try/catch seul / consigner aussi le corps | $_.ErrorDetails.Message (PS7). Change la capacité à identifier la cause2 |
| Nombreux embranchements | catch / -SkipHttpErrorCheck + branchement par code |
La seconde option est plus directe pour distinguer les traitements par statut2 |
| Nouvelle tentative | -MaximumRetryCount / maison |
Le mécanisme intégré suit automatiquement le Retry-After du 429. Mais comme il retente tous les codes de 400 à 599, une implémentation maison est nécessaire pour échouer immédiatement sur une erreur permanente2 |
| Nouvelle tentative sur POST | Uniquement avec une clé d’idempotence | L’enregistrement peut avoir réussi même sans réponse reçue ; un renvoi naïf entraîne un double enregistrement |
| Pagination | -FollowRelLink / boucle maison |
La première pour l’approche par en-tête Link2 |
| Environnement 5.1 | Tel quel / TLS 1.2 explicite + envisager PS7 | La plupart des erreurs de connexion viennent du paramétrage TLS1 |
| Erreur de certificat | -SkipCertificateCheck / faire confiance à l’AC interne |
Ne jamais désactiver la vérification en exploitation permanente |
11. Conclusion
- Pour une API JSON,
Invoke-RestMethodest la base. En-têtes et code de statut s’obtiennent via-ResponseHeadersVariable/-StatusCodeVariable. - Envoyez le JSON contenant du japonais sous forme de tableau d’octets UTF-8, en déclarant explicitement
charset=utf-8. OublierConvertTo-Json -Depthentraîne la perte des niveaux imbriqués. - Les 4xx/5xx sont des erreurs bloquantes. Lisez le corps via
$_.ErrorDetails.Messageet consignez-le. Avec de nombreux embranchements,-SkipHttpErrorCheckest plus simple à manier. - Ne réessayez jamais automatiquement une requête non idempotente comme
POST. Une erreur de communication ou un 5xx peut correspondre à un cas déjà traité côté serveur, et renvoyer la requête entraîne un double enregistrement. Pour retenter, un mécanisme de clé d’idempotence est nécessaire. - Face au 429, la base est d’attendre selon
Retry-After. Avec-MaximumRetryCount, ce suivi est assuré nativement. Mais comme le mécanisme intégré retente tous les codes de 400 à 599, une logique de nouvelle tentative maison est nécessaire pour faire échouer immédiatement un 401 ou un 404. Il en va de même quand un traitement différencié par code ou un backoff exponentiel est requis : les erreurs permanentes doivent échouer immédiatement, sans nouvelle tentative. - Pour la pagination,
-FollowRelLinkpour l’approche par en-têteLink, une boucle maison pour l’approche par curseur. N’utilisez pas+=pour agréger les résultats. - Sous 5.1, trois obstacles se dressent : l’activation explicite de TLS 1.2, la dépendance au moteur d’IE, et le manque de fonctionnalités. Si l’intégration d’API doit se poursuivre, adopter PowerShell 7 est la solution la plus rapide.
Téléchargement du code d’exemple
Le code présenté dans cet article est distribué sous une forme directement exécutable. Le paquet contient un appel d’API implémentant nouvelle tentative, idempotence et pagination, ainsi qu’un serveur HTTP de validation.
Télécharger le code d’exemple (zip)
Les exemples de cet article ont été réellement exécutés et validés sous PowerShell 7.6 (21 tests Pester). En exécutant Invoke-SampleTests.ps1, inclus dans le zip, vous pouvez reproduire la même validation chez vous.
# Analyse syntaxique + analyse statique + tests Pester
./Invoke-SampleTests.ps1
Le zip contient un serveur HTTP de validation, ce qui permet de reproduire l’aller-retour en local sans appeler d’API externe. Essayez directement les points de vérification mentionnés au §5 (corps de l’erreur), au §6 (avertissement de nouvelle tentative sur 429) et au §7 (nombre d’éléments récupérés). Vérifier ces trois points avant de pointer vers une véritable API réduit le risque de se retrouver, en production, dans une situation où « on ne comprend pas pourquoi ça ne marche pas ».
Les valeurs de configuration (chemins, noms de serveur, identifiants de locataire, etc.) sont des exemples. Ne les exécutez pas telles quelles en production : adaptez-les à votre propre environnement.
Articles connexes
- Gestion des erreurs et conception des tentatives sous PowerShell — des pièges où try/catch ne suffit pas jusqu’aux bonnes pratiques de code de sortie et de nouvelle tentative
- La gestion sécurisée des identifiants sous PowerShell — bannir les mots de passe en clair des scripts
- Différences entre Windows PowerShell 5.1 et PowerShell 7 — guide pratique de migration des scripts internes
- Où regarder quand un script PowerShell est lent — les points clés des tableaux, du pipeline et des rapprochements
- Introduction à Microsoft Graph PowerShell — gérer Microsoft 365 après l’abandon d’AzureAD et de MSOnline
- N’enveloppez pas HttpClient dans un using — la pratique des communications HTTP dans les applications métier C#
Domaines de conseil associés
合同会社小村ソフト (Komura Software LLC) prend en charge la conception et l’implémentation d’intégrations internes utilisant les API des systèmes centraux ou de SaaS, l’automatisation remplaçant le travail manuel existant par une intégration d’API, ainsi que l’investigation des incidents liés aux communications.
- Développement d’applications métier
- Conseil technique et revue de conception
- Investigation des incidents et analyse des causes
- Contact
Références
-
Microsoft Learn, Differences between Windows PowerShell 5.1 and PowerShell 7.x. Sur les différences de comportement des applets de commande liées au web, les contraintes propres à Windows PowerShell 5.1, et les fonctionnalités ajoutées dans PowerShell 7. Ainsi que sur la spécification de la version de TLS via la propriété ServicePointManager.SecurityProtocol. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Invoke-RestMethod. Sur l’envoi d’une requête vers un point de terminaison REST et la conversion de la réponse JSON/XML en objet PowerShell, la spécification de -Headers / -Body / -ContentType / -Method, -Authentication (Basic / Bearer / OAuth) et -Token, le rejet par défaut de l’authentification en dehors de HTTPS, la désactivation de la levée d’exception pour les 4xx/5xx via -SkipHttpErrorCheck, la récupération via -StatusCodeVariable et -ResponseHeadersVariable, la nouvelle tentative via -MaximumRetryCount / -RetryIntervalSec, la pagination par en-tête Link via -FollowRelLink / -MaximumFollowRelLink, -Proxy / -ProxyCredential / -ProxyUseDefaultCredentials (tous deux supposant -Proxy, et ne pouvant être combinés entre eux), -NoProxy ajouté dans PowerShell 6.0 (contournant le proxy configuré par les paramètres Internet ou les variables d’environnement), ainsi que -SkipCertificateCheck et -TimeoutSec. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20
-
Microsoft Learn, Invoke-WebRequest. Sur le retour de la réponse sous forme de WebResponseObject donnant accès à StatusCode, Headers et Content, sur le fait que Windows PowerShell 5.1 analyse le HTML par défaut avec le moteur d’Internet Explorer (contournable avec -UseBasicParsing), et sur le fait qu’à partir de PowerShell 6 cette dépendance à IE disparaît et que -UseBasicParsing est ignoré. ↩ ↩2 ↩3
-
Microsoft Learn, ConvertTo-Json. Sur la valeur par défaut de -Depth (2) et le fait que les niveaux plus profonds ne sont pas convertis, ainsi que sur la suppression des espaces avec -Compress. ↩ ↩2 ↩3
Articles associés
Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.
Automatiser le déploiement de postes avec winget et PowerShell — Rendre le manuel de procédure exécutable
Ce guide explique comment rendre reproductible la configuration des PC des nouveaux employés : installation d'applications et export/impo...
Où regarder quand un script PowerShell est lent — les points clés sur les tableaux, le pipeline et le rapprochement de données
Ce guide passe en revue les causes classiques de lenteur des scripts PowerShell : pourquoi l'opérateur += sur un tableau devient O(n²), l...
Arrêter d'utiliser Write-Host — Conception des flux de sortie et de la journalisation dans PowerShell
Ce guide présente les six flux de sortie de PowerShell et leurs usages respectifs, les problèmes posés par Write-Host et son bon usage, l...
Le traitement parallèle dans PowerShell — Choisir entre ForEach-Object -Parallel et les jobs
Ce guide présente, d'un point de vue pratique, les différences et les usages respectifs de ForEach-Object -Parallel, Start-ThreadJob et S...
Bien appeler un exe externe depuis PowerShell — les pièges des guillemets d'arguments, du code de sortie et de l'encodage
Appeler robocopy ou un EXE interne depuis PowerShell casse parfois les arguments, empêche de récupérer le code de sortie, ou produit une ...
Sujets associés
Ces pages replacent le sujet dans un contexte plus large de services et de décisions.
Thèmes techniques Windows
Portail des sujets sur le développement Windows, l'analyse des incidents et la valorisation des actifs existants.
Services liés à ce sujet
Cet article est directement lié aux services suivants.
Développement d'applications Windows
Applications métier, intégration d'équipements et outils de communication, des besoins au développement.
Questions fréquentes
Questions souvent posées lors d’une consultation sur le sujet de cet article.
- Comment choisir entre Invoke-RestMethod et Invoke-WebRequest ?
- Si vous manipulez du JSON ou du XML d'API, choisissez Invoke-RestMethod. Il analyse automatiquement le corps de la réponse et le convertit en objet PowerShell, ce qui évite d'avoir à appeler soi-même ConvertFrom-Json. Invoke-WebRequest renvoie la réponse sous forme de HtmlWebResponseObject, donnant accès au code de statut, aux en-têtes et au corps brut. Choisissez Invoke-WebRequest si vous devez consulter le code de statut ou les en-têtes, ou si vous voulez manipuler du HTML tel quel. Notez que depuis PowerShell 6, Invoke-RestMethod dispose de -ResponseHeadersVariable et -StatusCodeVariable, donc si vous n'avez besoin que des en-têtes ou du code, vous pouvez rester avec Invoke-RestMethod et les récupérer ainsi.
- Quand j'envoie du JSON contenant du japonais, il apparaît corrompu côté destinataire.
- C'est parce que la suite d'octets du corps et la déclaration du Content-Type ne concordent pas. La méthode fiable consiste à convertir la chaîne produite par ConvertTo-Json en tableau d'octets UTF-8, à le passer à -Body, et à déclarer explicitement charset=utf-8 dans -ContentType. Sous Windows PowerShell 5.1, passer directement la chaîne peut l'envoyer avec l'encodage par défaut, ce qui rend cette précaution particulièrement utile. Pour un texte corrompu côté réception, soupçonnez de la même façon que le charset de la réponse n'est pas correctement déclaré.
- Quand l'API renvoie un 404 ou un 500, je veux lire le message d'erreur contenu dans le corps de la réponse.
- À partir de PowerShell 7, consultez $_.ErrorDetails.Message dans le bloc catch : le corps de la réponse s'y trouve. Le code de statut est accessible via $_.Exception.Response.StatusCode. En ajoutant -SkipHttpErrorCheck, les 4xx/5xx ne provoquent plus d'exception et sont reçus comme une réponse normale, ce qui facilite un traitement qui branche selon le code de statut. Sous Windows PowerShell 5.1, il faut lire soi-même le flux de réponse, ce qui est une raison supplémentaire de recommander l'utilisation de PowerShell 7.
- L'API me renvoie un 429 (limitation de débit). Comment y faire face ?
- La base consiste à attendre le nombre de secondes indiqué par l'en-tête Retry-After de la réponse avant de réessayer. En l'absence de cet en-tête, élargissez l'intervalle avec un backoff exponentiel (2 secondes, 4 secondes, 8 secondes...). À partir de PowerShell 6, -MaximumRetryCount et -RetryIntervalSec existent, et si la réponse 429 contient un Retry-After, c'est la valeur de cet en-tête qui est utilisée à la place de l'intervalle spécifié : le mécanisme intégré suffit donc si votre seul besoin est de gérer la limitation de débit. N'utilisez une fonction de nouvelle tentative maison que si vous voulez un traitement différent selon le code de statut, ou un backoff exponentiel. Par ailleurs, comme retenter une requête non idempotente comme POST risque un double enregistrement, évitez-le tant qu'il n'existe pas de mécanisme de clé d'idempotence. Fondamentalement, réduire le nombre d'appels lui-même (ne récupérer que les éléments nécessaires, utiliser une API de récupération en masse) reste la solution la plus sûre.
- J'obtiens une erreur avec le certificat auto-signé d'une API interne. Puis-je utiliser -SkipCertificateCheck ?
- Évitez-le en exploitation permanente. Désactiver la vérification du certificat revient à ne plus confirmer que l'interlocuteur est bien celui qu'il prétend être, ce qui laisse une marge à une attaque de l'homme du milieu même sur un réseau interne. La bonne solution consiste à placer le certificat de l'autorité de certification interne dans le magasin des autorités de certification racines de confiance de l'environnement d'exécution, et à émettre correctement le certificat serveur. Même pour un usage temporaire en environnement de test, faites en sorte que ce choix soit explicite via un fichier de configuration ou un paramètre, afin qu'il ne se retrouve jamais mélangé dans un script de production.
Profil de l’auteur
Page de présentation de l’auteur de l’article.
Go Komura
Représentant de KomuraSoft LLC
Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.