Powershell — удобная API построенная на .net. Powershell позволяет пользователям писать скрипты, не упираясь в программирование, при этом получая схожие результаты. Что происходит на КДВП, автор объяснит позже по тексту. Сейчас нам срочно нужно притвориться, что мы программируем на C#.
TL;DR: Postman не нужен, если есть Powershell. Но сперва нужно зайти издалека.
Делаем простой класс
Автор слышал, что крутые программисты делают все через классы и их методы.
Так как PowerShell это позволяет, давайте автор покажет, как можно сложить 1 + 1 притворившись, что мы программируем.
class ClassName {
[string] Sum ($A, $B) {
$Result = $A + $B
return $Result
}
}
Вот наш класс ClassName и его метод Sum. Экземпляр класса можно вызвать ровно так же, как в настоящих языках программирования.
$NewClass = [ClassName]::new()
$NewClass.Sum(1, 1)
Создаем новый экземпляр класса и вызываем метод, всё просто.
Есть ли Void в Powershell
При написании сложных скриптов этот же вопрос вставал у автора. Как сделать функцию, которая будет Void?
Говорят, что можно сделать так:
Get-Date | Out-Null
Однако, | Out-Null так же глушит весь Verbose, ErrorAction и не работает с Invoke-Command.
Если вам нужна функция с [Void] – делайте новый класс, другого выхода нет.
class ClassName {
#Конструктов класса
[void] Start () {
#Создаем экземпляр класса прямо внутри этого же класса.
$q = [ClassName]::new()
$q.GetDate()
}
#Своего рода метод внутри класса
[void] GetDate () {
#А вот тут вызываем еще один метод из .Net
#Просто так, потому что можем
$Result = [DateTime]::UtcNow.ToString()
Write-Host $Result
}
}
Сделать метод приватным внутри одного класса, или вызвать один из методов класса внутри этого же класса в PowerShell невозможно, поэтому приходится лепить такие вот вызовы.
Конструктор класса был добавлен в пример для понимания ограничений языка и такой код писать в целом не стоит.
Вот так мы добились того, что не заглушили Verbose, при этом сделали функцию с Void.
Список методов класса
Скажем, вам нужно сделать программу, возможно, даже на языке, который вы не знаете. Вы знаете, что есть какой-то класс, но методы его плохо документированы.
Перечислить все методы интересующего класса можно так:
#Делаем любовь, а не класс
$Love = [ClassName]::new()
#Выбираем члены интересующего нас класса и записываем в массив.
foreach ($i in $Love | Get-Member -MemberType Method | Select-Object name) {
[array]$array += $i.Name
}
#Вызываем члены класса, если нужно.
$Array | ForEach-Object {
$Love.$_()
}
Отправляем HTTP запросы скриптом (оправдываем КДПВ)
Классами мы можем представлять данные и эти данные конвертировать в разные форматы. К примеру, нам нужно отправить POST запрос на веб сайт в формате JSON.
Сначала мы делаем модель данных и заполним данные в новый экземпляр.
#В качестве модели данных делаем новый класс
class DataModel {
$Data
$TimeStamp
}
#Создаем экземляр класса
$i = [DataModel]::new()
#Заполняем данные
$i.Data = "My Message in string"
$i.TimeStamp = Get-Date
Так выглядит экземпляр класса после заполнения:
PS C:\> $i
Data TimeStamp
---- ---------
My Message in string 30.07.2020 5:51:56
Потом этот экземпляр можно конвертировать в XML или JSON или даже SQL запрос. Остановимся на JSON:
#Конвертируем данные в JSON
$Request = $i | ConvertTo-Json
Так выглядит JSON после его конвертации:
PS C:\> $Request
{
"Data": "My Message in string",
"TimeStamp": "2020-07-30T05:51:56.6588729+03:00"
}
И отправляем:
#Отправляем JSON
Invoke-WebRequest localhost -Body $Request -Method Post -UseBasicParsing
В случае если нужно отправлять один и тот же JSON файл 24/7, можно сохранить его как файл и отправлять уже из файла. К примеру, возьмем этот же самый $Request.
#Сохраняем данные конвертированные ранее в JSON в файл
$Request | Set-Content C:\Users\User\Desktop\YourRequest.json
#Отправляем ранее сохраненный в файл JSON
Invoke-WebRequest localhost -Body (Get-Content C:\Users\User\Desktop\YourRequest.json) -Method Post -UseBasicParsing
Получаем HTTP запросы скриптом (оправдываем КДПВ 2)
Автор терпеть не может Postman, зачем кому-либо нужен Postman, когда есть руки и PowerShell? (Автор предвзято относится к этой программе и его нелюбовь ничем не обоснована.)
Делать свою альтернативу мы будем это с помощью System.Net.HttpListener, то есть мы сейчас запустим настоящий веб сервер из скрипта.
#Создаем новый экземпляр класса
$http = [System.Net.HttpListener]::new()
#Добавляем HTTP префиксы. Их может быть сколько угодно
$http.Prefixes.Add("http:/localhost/")
$http.Prefixes.Add("http://127.0.0.1/")
#Запускаем хттп листенер
$http.Start()
$http.Close()
Так проходит запуск класса.
Экземпляр класса был создан и его процесс запустился, мы можем слушать от него вывод. Вывод представлен как System.Net.HttpListener.GetContext. В это примере мы принимаем и конвертируем только POST запрос.
while ($http.IsListening) {
#GetContext нужен для получения сырых данных из HttpListener
$context = $http.GetContext()
#Определяем тип запроса с помощью Request.HttpMethod
if ($context.Request.HttpMethod -eq 'POST') {
#Читаем сырые данные из GetContext
#Для каждого отдельного запроса создаем свой конвейер
[System.IO.StreamReader]::new($context.Request.InputStream).ReadToEnd() | ForEach-Object {
#С помощью System.Web.HttpUtility делаем urlDecore, иначе кириллица превращается в руны
$DecodedContent = [System.Web.HttpUtility]::UrlDecode($_)
#Конвертируем прилетевшие данные в нужный нам формат
$ConvertedForm = $DecodedContent | ConvertFrom-Json -ErrorAction SilentlyContinue
#Cконвертированные данные отображаем таблицой
$ConvertedForm | Format-Table
}
}
}
Готовый скрипт
С помощью этого скрипта можно принимать запросы:
#Создаем новый экземпляр класса
$http = [System.Net.HttpListener]::new()
#Добавляем HTTP префиксы. Их может быть сколько угодно
$http.Prefixes.Add("http://localhost/")
$http.Prefixes.Add("http://127.0.0.1/")
#Запускаем веб листенер
$http.Start()
if ($http.IsListening) {
Write-Host "Скрипт запущен"
}
while ($http.IsListening) {
#GetContext нужен для получения сырых данных из HttpListener
$context = $http.GetContext()
#Определяем тип запроса с помощью Request.HttpMethod
if ($context.Request.HttpMethod -eq 'POST') {
#Читаем сырые данные из GetContext
#Для каждого отдельного запроса создаем свой конвейер
[System.IO.StreamReader]::new($context.Request.InputStream).ReadToEnd() | ForEach-Object {
#С помощью System.Web.HttpUtility делаем urlDecore, иначе кириллица превращается в руны
$DecodedContent = [System.Web.HttpUtility]::UrlDecode($_)
#Конвертируем прилетевшие данные в нужный нам формат
$ConvertedForm = $DecodedContent | ConvertFrom-Json -ErrorAction SilentlyContinue
#Cконвертированные данные отображаем таблицей
$ConvertedForm | Format-Table
}
#Отвечаем клиенту 200 OK и закрываем стрим.
$context.Response.Headers.Add("Content-Type", "text/plain")
$context.Response.StatusCode = 200
$ResponseBuffer = [System.Text.Encoding]::UTF8.GetBytes("")
$context.Response.ContentLength64 = $ResponseBuffer.Length
$context.Response.OutputStream.Write($ResponseBuffer, 0, $ResponseBuffer.Length)
$context.Response.Close()
}
#Cконвертированные данные отображаем таблицей
$http.Close()
break
}
Данные будут автоматичеки конвертироваться из JSON и выводиться в терминал.
Автор надеется, что вы выбросите Postman, так же, как и GIT с GUI.
lair
Вот когда автор расскажет, как в его поделке авторизоваться в удаленном сервисе через OAuth 2, или, что веселее, послать запрос, подписанный по требованиям AWS, тогда и поговорим.
Tihon_V
Попробуйте HTTPie c батарейкой для AWS авторизации.
tdemin
Добавлю: вместо
Invoke-WebRequest
можно использоватьInvoke-RestMethod
, он за нас умеет делать сериализацию/десериализацию тела запроса/ответа в JSON/XML и обратно в PS-объекты, а еще имеет набор заготовленных параметров для типичных RESTful API.https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod?view=powershell-7
karb0f0s
Люблю и ненавижу PowerShell. С одной стороны позволяет с легкостью автоматизировать даже сложные процессы, с другой совершенно идиотский и неконсистентный синтаксис с динамической типизацией.
Например, работа с коллекцией через
foreach(obj in $list) {...}
и через$list | Foreach-Object {...}
разнится и зачастую непредсказуемо, если внутри блока использовать continue, break или return или код внутри блока выкидывает исключение.За двойные стандарты вызова функций авторов PowerShell нужно отправить в несгораемый котел: почему одним функциям нужно передавать параметр через
Set-Value -Parameter "Hi"
, а другим через"string".Split(";")
? Эта неконсистентость только усиливается классами, пример которых представлен в статье. Если я объявляю функцию модуля, то она будет вызваться какGet-Something
, но если я создаю класс, то это ужеClassInstance.Get($something)
. Я кучу часов потратил на отладку неработающего кода, из-за того, что скопировал функцию из объявленияInvoke-Something($a = "aaa", $b = "bbb)
и забыл убрать скобки!Еще одна болезнь — толерантность к пустым переменным. Я плачу по strict режиму, чтобы нельзя было использовать необъявленные и неинициализированные переменные. Куча времени уходит на поимку фантомов, после того, как в одном месте изменяется название переменной, а ниже по пайпу она продолжает использоваться как ни в чем не бывало. Еще хуже с этим дела обстоят в PowerShell ISE, который будет хранить временное значение переменной до перезапуска, а вы будете себе рвать волосы на голове, не понимая почему значение переменной $MuVar не меняется, хотя выше по коду $MyVar давно имеет новое значение.
Alexey2005
После Bash, где написание скриптов подобно заплыву по бездонному океану граблей, PowerShell кажется потрясающе логичным и продуманным.
tagirb
Но таким скуучным… :)
vadiminter9
В добавок к вашему списку еще хочется добавить то как он возвращает значения из функций.
This function returns the number 4 as a string, right? Wrong! It returns an array with the values “1”,”2”,”3”,”4”. The reason is that the strings “1”,”2” and “3” are not saved into a variable, so the output of those strings goes into the PowerShell pipeline, which collects the outputs of each line and returns all the values as an array.
Charg
Потому что в powershell ключевое слово return выполняет другую функцию. Непривычно но не более.
Из документации:
evr1ka
Первый же код такой вывод про $NewClass.Sum(1, 1):
При создании конвейера произошла ошибка.
+ CategoryInfo: NotSpecified: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId: RuntimeException
Milein
То ли у меня лыжи не те, то ли ещё чего, но вообще не представляю как хотябы среднего размера запросы отправлять через командную строку.
Вот нужно в середине JSON в 10 строк поменять пару строк. А после отправки поменять ещё раз. И ещё раз.
Идти файл редактировать каждый раз?
Или в командной строке ковырять мульти-строковую команду?
А выравнивать JSON руками что ли тогда?
Короче куча проблем, которые Postman и ему подобные решают легко.
karb0f0s
А зачем строки менять? PowerShell десериализует JSON в объект и достаточно будет поменять свойство нужных полей, а потом сериализовать и отправить ответ:
Причем неважно откуда будет получен исходный объект — прочитан из файла, из запроса или сформирован через переменные PowerShell.
Milein
В смысле зачем строки менять? Затем чтобы новый запрос отправить.
Вот пусть я сначала создаю с JSON как у вас, затем второй объект у которого 10 значений в массиве Param4, затем у третьего объекта есть Param5 типа объект с несколькими полей. Который я вставлю из внутренностей ответа на другой запрос.
Причём это мне нужно только сегодня. Это задача на один раз, никакой автоматизации не нужно. Мутить пайплайн который всё это сделает не нужно.
Так вот, как вы будете это делать в командной строке?
Добавлено:
И вот то что вы породили через изменение параметра объекта это угар. Напишу я коллеге: «смотри, чо у нас происходит». А он мне: «интереснько. а ну-ка кинь мне курл». А я что ему?
NLO
НЛО прилетело и опубликовало эту надпись здесь
impwx
А можно взять LINQPad и писать маленькие скрипты на обычном C#, при этом пользуясь отличным визуализатором. По сравнению с PowerShell визуального мусора на порядок меньше
karb0f0s
Окей гугл: как пройтись по всем сайтовым коллекциям SharePoint и почистить в них корзину из LINQPad? Как в несколько шагов организовать миграцию для SQL Server по аналогии с dbatools?
Может для программиста и проще накидать скетч на C#, а для администратора проще написать скрипт на повершеле. Тем более, что управление инфраструктурой Microsoft построено вокруг PowerShell.
impwx
Понятное дело, что есть задачи, для которых есть готовое решение только в виде cmdlet'ов. Я скорее про то, что большинство примеров из статьи легко переписать на C#, и при этом код станет короче и менее шумным.
remzalp
Спасибо, чудесная вещь! Впервые встретил.