Многие из вас наверняка работают с разнообразными инфраструктурами, используя REST API. А поскольку все более широкие слои населения для автоматизации рутинных задач осваивают PowerShell, то почему бы и не начать применять его для работы с REST API?
Сегодня вашему вниманию предлагается перевод статьи Адама Бертрама, большого поклонника автоматизации, автора сайта adamtheautomator.com, в которой он показывает на примере, как это можно сделать.
Итак, добро пожаловать под кат.

В качестве примера API у нас будет выступать swagger petstore API.
Начну, как и положено в приличных руководствах, с требований к системе. Данный пример создавался с Windows PowerShell v5.1 — проверьте, что у вас он есть. Возможно, что пример будет работать также на v6 и выше, возможно, что на Mac или на Linux ОС — но это не точно (не тестировалось).
До того, как начать писать собственно скрипт, нужно подготовить файловую структуру для нашего модуля. Тут, конечно, каждый волен делать что ему нравится, а мы для простоты создадим простейшую структуру из двух файлов и одной папки:
Создаем папку PetStore в каталоге Source на диске C:
Затем создадим файл манифеста для нашего модуля — это файл .psd1 с метаданными, где вы сможете прописать все требования к модулю и всю информацию о нём. Вообще-то никто вам не запретит создать такой файл вручную, но можно сделать это и с помощью командлета New-ModuleManifest. Само собой, нужно будет указать необходимые вам параметры.
Создаем файл манифеста под названием Petstore.psd1 в нашей новой папке на диске C:
Файл собственно модуля с расширением .psm1 как раз и будет содержать все нужные нам функции. В любом PowerShell-редакторе создадим новый файл и положим его на диск C.
Строго говоря, это необязательная операция, но будет хорошим тоном в файле .psm1 прописать основной идентификатор ресурса (base URI) для API-эндпойнтов как переменную — это многое упростит, в том числе и в будущем (переход от версии к версии). Ну а в качестве формата данных будем использовать JSON.
Получится вот так:
Затем мы сможем использовать эту переменную в любой функции с вызовом
Invoke-RestMethod:
Для начала создадим функцию, которая будет получать от эндпойнта https://petstore.swagger.io/v2/pet/ данные о питомцах. Назовем ее Get-PetstorePet:
Мы определили параметр PetId так, чтобы он соответствовал определенному параметру REST API для этого эндпойнта.
Примечание: Полный перечень параметров для данного примера эндпойнта можно найти тут.
Используя ValueFromPipeline и ValueFromPipelineByPropertyName в нашей функции, можно передавать ей параметры из пайплайна:
Для этого выполняем команду Import-Module:
После чего можно будет запускать выполнение нашей функции Get-PetstorePet:
Как разъясняется в документации для API, чтобы добавить нового питомца, нужно написать тело запроса в формате JSON. Ну и логично, что для добавления нового питомца мы будем использовать функцию Add-PetstorePet.
Поскольку PowerShell умеет в объекты, мы не будем валить всё в кучу, а создадим хэш-таблицу, которую затем преобразуем в JSON:
Это куда проще и короче, чем создавать всеобъемлющий JSON. На основе нашей хэш-таблички определим параметры, которые понадобятся нам для команды:
Поскольку блок Begin ровно такой же, как и у предыдущей команды, мы можем его переиспользовать, слегка изменив URI (т.к. параметр PetID тут уже не нужен):
Затем определим блок Process, который создаст post-запрос, используя формат JSON, и отправит его к REST API-эндпойнту, задействуя Invoke-RestMethod.
Заметьте, что мы удалили у $BodyObject свойства со значением null — ибо некоторые REST API не любят, когда им пытаются скормить такие значения.
Наша функция добавления питомца будет выглядеть в итоге так:
Снова импортируем модуль и запускаем нашу функцию:
Теперь мы освоились с функциями и можем на основе этого подхода создавать функции для других методов, работающих с REST API.
Например, на базе функции Add-PetstorePet можно создать функцию удаления Remove-PetstorePet — для этого надо дать ей соответствующее название и заменить метод, который вызывается командой Invoke-RestMethod, на метод
Поскольку параметры были настроены на прием pipeline input, вы можете спокойно организовать пайп питомцев в функцию Remove-PetstorePet:
Как вы наверняка заметили, при создании модуля PowerShell для REST API многое завязано на повторное использование. Поскольку многие REST API весьма схожи с swagger API, то вполне можно переиспользовать модуль для работы с разными API. Поддержка же пайплайна еще более упрощает работу.
Ну а если у вас тоже есть чем поделиться из своей практики автоматизации, добро пожаловать в комментарии.
Сегодня вашему вниманию предлагается перевод статьи Адама Бертрама, большого поклонника автоматизации, автора сайта adamtheautomator.com, в которой он показывает на примере, как это можно сделать.
Итак, добро пожаловать под кат.

В качестве примера API у нас будет выступать swagger petstore API.
Начну, как и положено в приличных руководствах, с требований к системе. Данный пример создавался с Windows PowerShell v5.1 — проверьте, что у вас он есть. Возможно, что пример будет работать также на v6 и выше, возможно, что на Mac или на Linux ОС — но это не точно (не тестировалось).
Готовим папку и файлы для нового модуля
До того, как начать писать собственно скрипт, нужно подготовить файловую структуру для нашего модуля. Тут, конечно, каждый волен делать что ему нравится, а мы для простоты создадим простейшую структуру из двух файлов и одной папки:
- В файле манифеста PowerShell (.psd1) будет храниться информация о новом модуле.
- В файле собственно модуля PowerShell (.psm1) будут храниться все наши командлеты.
- Ну и папка, в которой будут лежать оба эти файла.
Создаем папку PetStore в каталоге Source на диске C:
New-Item -Type Directory -Path C:\Source\PetStore\
Затем создадим файл манифеста для нашего модуля — это файл .psd1 с метаданными, где вы сможете прописать все требования к модулю и всю информацию о нём. Вообще-то никто вам не запретит создать такой файл вручную, но можно сделать это и с помощью командлета New-ModuleManifest. Само собой, нужно будет указать необходимые вам параметры.
Создаем файл манифеста под названием Petstore.psd1 в нашей новой папке на диске C:
# Use splatting technique to make it more readable # Не забывайте про комментарии к коду для пущей читабельности $ModuleManifestParams = @{ Path = "C:\Source\PetStore\PetStore.psd1" # Notice that the psd1 file has the same name as the folder it resides in # Файл манифеста имеет такое же имя, что и папка Guid = [GUID]::NewGuid().Guid # A unique GUID for the module for identification # GUID нового модуля Author = "Your Name Here" # Optional - кто автор (необязательно) CompanyName = "Company Name here" # Optional - компания (необязательно) ModuleVersion = "0.0.1" # Semantic versioning as recommended best practice for PowerShell modules # Проставим номер версии модуля, как рекомендуется в лучших домах (семантическое версионирование, т.е. по смыслу) Description = "A PowerShell module to interact with the HttpBin API" # A short description of what the module does # Кратко опишем, для чего нужен модуль } # Run New-ModuleManifest with the splatted parameters # А теперь запустим командлет с указанными параметрами для создания манифеста New-ModuleManifest @ModuleManifestParams
Файл собственно модуля с расширением .psm1 как раз и будет содержать все нужные нам функции. В любом PowerShell-редакторе создадим новый файл и положим его на диск C.
Прописываем URI для API-эндпойнтов
Строго говоря, это необязательная операция, но будет хорошим тоном в файле .psm1 прописать основной идентификатор ресурса (base URI) для API-эндпойнтов как переменную — это многое упростит, в том числе и в будущем (переход от версии к версии). Ну а в качестве формата данных будем использовать JSON.
Получится вот так:
$script:PetStoreBaseUri = "https://petstore.swagger.io/v2" $script:PetstoreInvokeParams = @{ ContentType = "application/json" }
Затем мы сможем использовать эту переменную в любой функции с вызовом
Invoke-RestMethod:
PS51> Invoke-RestMethod -Uri "$script:PetStoreBaseUri/pet/<petid>" @script:PetstoreInvokeParams
Создаем первую функцию
Для начала создадим функцию, которая будет получать от эндпойнта https://petstore.swagger.io/v2/pet/ данные о питомцах. Назовем ее Get-PetstorePet:
Function Get-PetstorePet { [cmdletbinding()] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [int64]$PetID ) Begin { # Using the module-scoped API endpoint URI to create a function-specific URL to query # Используем наш URI, заданный для модуля, чтобы создать для данной функции URL, куда будут идти запросы $Uri = "$script:PetStoreBaseUri/pet/{petId}" } Process { # Create a temporary variable from the Uri variable # Создаем временную переменную из нашей переменной URI, содержащую параметр PetId $TempUri = $Uri -replace "\{petId\}", $PetId # Call the API endpoint using the $TempUri # Используем эту временную переменную для обращения к API-эндпойнту Invoke-RestMethod -Uri $TempUri -Method Get @script:PetStoreInvokeParams } }
Мы определили параметр PetId так, чтобы он соответствовал определенному параметру REST API для этого эндпойнта.
Примечание: Полный перечень параметров для данного примера эндпойнта можно найти тут.
Используя ValueFromPipeline и ValueFromPipelineByPropertyName в нашей функции, можно передавать ей параметры из пайплайна:
# Define pet id's to fetch # Указываем идентификаторы питомцев, которые нам нужны $PetIds = @(100,101,102,103) $PetIds | Get-PetstorePet
Импортируем модуль
Для этого выполняем команду Import-Module:
PS51> Import-Module C:\Source\Petstore
После чего можно будет запускать выполнение нашей функции Get-PetstorePet:
PS51> Get-PetstorePet -PetId 123
Добавляем новую запись
Как разъясняется в документации для API, чтобы добавить нового питомца, нужно написать тело запроса в формате JSON. Ну и логично, что для добавления нового питомца мы будем использовать функцию Add-PetstorePet.
Поскольку PowerShell умеет в объекты, мы не будем валить всё в кучу, а создадим хэш-таблицу, которую затем преобразуем в JSON:
$BodyJson = @{ id = $PetId category = @{ id = $CategoryId name = $CategoryName } name = $PetName photoUrls = $PhotoUrls tags = $Tags status = $Status } | ConvertTo-Json
Это куда проще и короче, чем создавать всеобъемлющий JSON. На основе нашей хэш-таблички определим параметры, которые понадобятся нам для команды:
param( # Id of the pet that you'll create # ID питомца, которого будем создавать [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [Alias("Id")] # This is an alias so that we can use it with pipeline properly by piping output from other Functions in this module # Это алиас, который мы сможем использовать в пайплайне, забирая то, что получается на выходе у всяких разных функций в нашем модуле [int64]$PetId, # Category ID of the pet to create # ID категории, к которой будет относиться новый питомец [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [int64]$CategoryId, [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string]$CategoryName, # Name of the pet # Имя питомца [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string]$PetName, # Urls to photos of the pet # URLs для фото питомца [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string[]]$PhotoUrls, # Objects with tags containing Id and Name properties # Объекты с тэгами, содержащими ID и Name [Parameter(ValueFromPipelineByPropertyName)] [PSCustomObject[]]$Tags, # Status of the pet "Available","Taken" etc. # Статус питомца (Доступен, Пристроен и пр.) [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string]$Status )
Поскольку блок Begin ровно такой же, как и у предыдущей команды, мы можем его переиспользовать, слегка изменив URI (т.к. параметр PetID тут уже не нужен):
Begin { $Uri = "$script:PetStoreBaseUri/pet" }
Затем определим блок Process, который создаст post-запрос, используя формат JSON, и отправит его к REST API-эндпойнту, задействуя Invoke-RestMethod.
Process { # Create the JSON that we'll post to the endpoint # Создаем JSON для отправки post-запроса на эндпойнт $BodyObject = @{ id = $PetId category = $Category name = $PetName photoUrls = $PhotoUrls tags = $Tags status = $Status } # This will remove properties with a null value since it may mess around with some API's # Удаляем свойства, у которых значение равно null, чтобы не мешали $BodyObject.psobject.properties | ? {$_.Value -eq $Null} | Foreach { $BodyObject.psobject.properties.Remove($_.Name) } $BodyJson = $BodyObject | ConvertTo-Json # Call the API endpoint using the $TempUri # Обращаемся к эндпойнту, используя $TempUri Invoke-RestMethod -Uri $Script:Uri -Method POST -Body $BodyJson @PetStoreInvokeParams }
Заметьте, что мы удалили у $BodyObject свойства со значением null — ибо некоторые REST API не любят, когда им пытаются скормить такие значения.
Наша функция добавления питомца будет выглядеть в итоге так:
Function Add-PetstorePet { [cmdletbinding()] param( # Id of the pet that you'll create # ID питомца, которого будем создавать [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [Alias("Id")] # This is an alias so that we can use it with pipeline properly by piping output from other Functions in this module # Это алиас, который мы сможем использовать в пайплайне, забирая то, что получается на выходе у всяких разных функций в нашем модуле [int64]$PetId, # hash containing Id and name of category # Имя и ID категории, к которой будет относиться новый питомец, берем из хэш-таблички [Parameter(ValueFromPipelineByPropertyName)] [hashtable]$Category, # Name of the pet # Имя питомца [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string]$PetName, # Urls to photos of the pet # URLs для фото питомца [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [string[]]$PhotoUrls, # Objects with tags containing Id and Name properties # Объекты с тэгами, содержащими ID и Name [Parameter(ValueFromPipelineByPropertyName)] [PSCustomObject[]]$Tags, # Status of the pet "Available","Taken" etc. # Статус питомца (Доступен, Пристроен и пр.) [Parameter(ValueFromPipelineByPropertyName)] [string]$Status ) Begin { $Uri = "$script:PetStoreBaseUri/pet" } Process { # Create the JSON that we'll post to the endpoint # Создаем JSON для отправки post-запроса на эндпойнт $BodyObject = @{ id = $PetId category = $Category name = $PetName photoUrls = $PhotoUrls tags = $Tags status = $Status } $BodyObject.psobject.properties | ? {$_.Value -eq $Null} | Foreach { $BodyObject.psobject.properties.Remove($_.Name) } $BodyJson = $BodyObject | ConvertTo-Json # Call the API endpoint using the $TempUri # Обращаемся к эндпойнту, используя $TempUri Invoke-RestMethod -Uri $Uri -Method POST -Body $BodyJson @script:PetStoreInvokeParams } }
Снова импортируем модуль и запускаем нашу функцию:
PS51> Import-Module C:\Source\Petstore -Force PS51> Add-PetstorePet -PetName Fido -PetId 1 -PhotoUrls "http://somesite.com/picture.jpg" PS51> Get-PetstorePet -PetId 1
Пишем новые функции
Теперь мы освоились с функциями и можем на основе этого подхода создавать функции для других методов, работающих с REST API.
Например, на базе функции Add-PetstorePet можно создать функцию удаления Remove-PetstorePet — для этого надо дать ей соответствующее название и заменить метод, который вызывается командой Invoke-RestMethod, на метод
DELETE, как показано ниже:Function Remove-PetstorePet { [cmdletbinding()] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias("Id")] [int64]$PetId ) Begin { $Uri = "$script:PetStoreBaseUri/pet/{petId}" } Process { # Create a temporary variable from the Uri variable containing the PetId parameter # Создаем временную переменную из нашей переменной URI $TempUri = $Uri -replace "\{petId\}", $PetId # Call the API endpoint using the $TempUri # Обращаемся к эндпойнту, используя $TempUri Invoke-RestMethod -Uri $TempUri -Method DELETE @script:PetStoreInvokeParams # NOTICE how we replaced GET with DELETE # Обратите внимание, что мы заменили тут GET на DELETE } }
Поскольку параметры были настроены на прием pipeline input, вы можете спокойно организовать пайп питомцев в функцию Remove-PetstorePet:
PS51> Get-PetstorePet -PetId 1 | Remove-PetstorePet
В заключение
Как вы наверняка заметили, при создании модуля PowerShell для REST API многое завязано на повторное использование. Поскольку многие REST API весьма схожи с swagger API, то вполне можно переиспользовать модуль для работы с разными API. Поддержка же пайплайна еще более упрощает работу.
Ну а если у вас тоже есть чем поделиться из своей практики автоматизации, добро пожаловать в комментарии.
