Притворяемся что пишем на C#, но только на Powershell

jsycnamvumej2vknuwh2jqsibe4.png


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.

oug5kh6sjydt9llengsiebnp40w.png

© Habrahabr.ru