Ознакомьтесь с нашей политикой обработки персональных данных
  • ↓
  • ↑
  • ⇑
 
Записи с темой: powershell (список заголовков)
07:07 

Саша это я, да
Писал тут несколько постов про expect-подобное решение для винды.
Думаю, следует подвести итог и укомплектовать все описанное в более простую форму.

Еще раз напомню, о чем шла речь. Причиной для всему послужил тот факт, что самые доступные решения, реализующие функционал expect для Windowsб, имели свои, для меня критические, недостатки. Сильнее всего угнетало отсутствие функции interact. Я использовал собственное решение, которое заключалось в совместном использовании C# класса, оболочки PowerShell и консольного приложения plink. В итоге получилось нечто с купированными по сравнению с традиционным expect'ом возможностями , однако вполне пригодное для автоматизации управления сетевыми устройствами. Плюс, что немаловажно, поддержка interact режима.

В общем, что нужно сделать, чтобы оно работало:

  1. Установить PowerShell, если он не был установлен. (Сначала, возможно, потребуется установить .NET Framework 2.0, а потом сам PowerShell).
  2. Установить plink. Автоматически устанавливется вместе с putty, но можно скачать отдельно. Путь к plink.exe обязательно прописать в переменную среды %PATH%.
  3. Скачать отсюда ps скрипт. Его нужно будет подключать ко всем вашим expect скриптам, используя следующую строку:


За примерами и подробностями в этот pdf.

@темы: .NET Framework, C#, PowerShell, Windows, expect, interact, plink, putty

07:27 

Сказ про Plink, Expect и PowerShell. Часть III: PowerShell!

Саша это я, да
Итак, в предыдущей части мы написали класс на C#, который предоставляет нам следующие методы:
  • Spawn - запуск консольного приложения. По сути это запуск всего одного единственного консольного приложения - plink. Больше для наших целей нам и не надо.
  • Expect - ожидание в выводе определенной последовательности символов. Возвращает true, если последовательность обнаружена, falst - если по истечению таймаута искомая строка не появилась.
  • Send - посылка в поток ввода процесса последовательности символов.
  • Interact - передача управления вводом и выводом пользователю.
  • Close - освобождение ресурсов.

Создадим для каждого из них удобную функцию-оболочку на PowerShell. Для того, чтобы использовать функции написанного нами класса, необходимо включить его в ps скрипте с помощью командлета Add-Type. При этом наш класс может быть уже скомпилированным в виде библиотеки. В этом случае синтаксис такой:

  1. Add-Type -Path $customDll
  2. #$customDll - путь к библиотеке

С другой стороны мы можем вставить исходный текст класса непосредственно в скрипт:

  1. $source = @"
  2. //Исходный код класса
  3. "@
  4. Add-Type -Language CSharp -TypeDefinition $source

Решите сами, какой метод вам больше по душе.
Для начала создадим объект нашего класса, с которым мы будем работать. Одного объекта нам вполне хватит. Также добавим переменную глобальной области видимости $ExpectTimeout, которая будет определять таймаут для всех вызовов метода Expect.

  1. [Zh.ZhExpect] $plink = New-Object Zh.ZhExpect
  2. [int32] $ExpectTimeout = 5
  3. #Функция Spawn-Plink. Запускает plink с заданными аргументами (адрес узла, порт, протокол, пароль, логин)
  4. function Spawn-Plink {
  5. param (
  6. [parameter(Mandatory=$true)]
  7. [alias("h")]
  8. [string]$remoteHost,
  9. [alias("p")]
  10. [string]$port = "",
  11. [alias("pr")]
  12. [string]$proto = "ssh",
  13. [alias("l")]
  14. [string]$login = "",
  15. [alias("pw")]
  16. [string]$password = ""
  17. )
  18. $argumentList = "-$proto"
  19. if ($port.length -gt 0) {
  20. $argumentList = $argumentList + " -P $port"
  21. }
  22. #Аргументы -l и -pw могут быть заданы только для ssh
  23. if ($proto -eq "ssh") {
  24. if ($login.length -gt 0) { $argumentList = $argumentList + " -l $login" }
  25. if ($password.length -gt 0) { $argumentList = $argumentList + " -pw $password" }
  26. }
  27. $argumentList = $argumentList + " " + $remoteHost
  28. $sсriрt:plink.Spawn("plink", $argumentList)
  29. }
  30. #Функция Expect. Вызывает метод plink.Expect и, если он возвращает true, выполняет заданный блок кода $action.
  31. function Expect {
  32. param(
  33. [parameter(Mandatory=$true, Position=1)]
  34. [alias("e")]
  35. [String] $ExpectString,
  36. [parameter(Position=2)]
  37. [alias("a")]
  38. [Scriptblock] $action,
  39. [alias("t")]
  40. [Int32] $timeOut = $sсriрt:ExpectTimeout
  41. [alias("ncl")]
  42. [switch]
  43. [bool] $doNotCleanReceivedText = $false
  44. )
  45. if ($sсriрt:plink.Expect($ExpectString, $timeOut, !$doNotCleanReceivedText)) {
  46. $action.Invoke()
  47. }
  48. }
  49. #Остальные функции в коментариях не нуждаются в виду своей простоты
  50. function Send {
  51. param([parameter(Mandatory=$true)]
  52. [string]$SendString
  53. )
  54. $sсriрt:plink.Send($SendString)
  55. }
  56. function Interact {
  57. param([parameter(Mandatory=$true)]
  58. [string]$stopString
  59. )
  60. $sсriрt:plink.Interact($stopString)
  61. }
  62. function Close-Plink {
  63. $sсriрt:plink.Close()
  64. }

В заключение приведу два простых примера скриптов:

  1. param (
  2. [string]$h = "",
  3. [string]$runCommandAndExit = ""
  4. )
  5. #подключаем наш файл с функциями
  6. . F:\zh\scripts\powershell\bin\Expect.ps1
  7. #
  8. Spawn-Plink -remoteHost $h -proto telnet
  9. Expect 'User name:' { Send "root`r" }
  10. Expect 'User password:' { Send "password`r" }
  11. Expect 'TERMINAL>' { Send "en`r" }
  12. Expect 'TERMINAL#' {
  13. Send "co te`r"
  14. if ($runCommandAndExit -ne "") {
  15. Start-Sleep -Seconds 1
  16. Send "$runCommandAndExitr"
  17. }
  18. }
  19. Expect 'TERMINAL(config)#' { Interact "___" }
  20. Close-Plink

  1. . F:\zh\scripts\powershell\bin\expect.ps1
  2. Spawn-Plink -proto telnet -port 4001 -remoteHost 192.168.60.60
  3. Start-Sleep -Seconds 2
  4. Send "`r`r"
  5. Expect 'user id :' { Send "sysadmin`r" }
  6. Expect 'password:' { Send "sysadmin`r" }
  7. Expect '=>' { Interact "___" }
  8. #Переходим к управлению устройством
  9. Close-Plink


Сказ про Plink, Expect и PowerShell. Предисловие
Сказ про Plink, Expect и PowerShell. Часть I: Простое решение
Сказ про Plink, Expect и PowerShell. Часть II: Нам нужен multithreading (Начало)
Сказ про Plink, Expect и PowerShell. Часть II: Нам нужен multithreading (Продолжение)

@темы: plink, Windows, Powershell, Expect

12:15 

Сказ про Plink, Expect и PowerShell. Часть I: Простое решение

Саша это я, да
В первой части речь пойдет о самом незатейливом способе реализации автоматического управления устройствами. Несмотря на свою простоту и множество ограничений этот метод, тем не менее, позволит нам вводить команды на устройстве также и собственноручно.

Немного о процессах

.NET Framework предоставляет возможность перенаправлять стандартные потоки (вывода, ввода, ошибок) запускаемых процессов. Однако не все приложения используют их. Например, такой фокус не сработает с telnet и openssh. Таким образом, количество консольных приложений, с которыми мы сможем работать, ограничено. К счастью plink, консольный вариант Putty, подходит для наших нужд как нельзя лучше. Помимо использования стандартных потоков, plink предоставляет нам функции клиентов telnet, ssh и rlogin. Существует несколько неприятных моментов, которые, опять-таки к счастью, можно решить с помощью подручных инструментов, но об этом чуть позже.

Простое решение

Существует достаточно простой способ автоматически выполнять команды на удаленном устройстве. Для этого нужно перед запуском plink перенаправить его поток ввода с помощью метода RedirectStandardInput. Для этого нам даже не понадобится C#, т.к. PowerShell имеет доступ к объектам .NET Framework. Рассмотрим код PowerShell:

  1. $ps = New-Object -TypeName System.Diagnostics.Process
  2. $ps.StartInfo.UseShellExecute = $false
  3. $ps.StartInfo.RedirectStandardInput = $true
  4. $ps.StartInfo.FileName = "plink"
  5. $ps.StartInfo.Arguments = "-telnet 172.30.20.5"

  6. [void]$ps.Start()

  7. $myStreamWriter = $ps.StandardInput

  8. Start-Sleep -m 500
  9. $myStreamWriter.Write("root`r")
  10. Start-Sleep -m 500
  11. $myStreamWriter.Write("password`r")
  12. Start-Sleep -m 500
  13. $myStreamWriter.Write("enable`r")
  14. Start-Sleep -m 500
  15. $myStreamWriter.Write("exit`r")

  16. $myStreamWriter.Close();

  17. if (!$ps.HasExited) { $ps.Kill() }


Разберем код по строкам:
1:создаем объект .NET Framework класса System.Diagnostics.Process.
2-5: свойство StartInfo содержит параметры процесса, которые нужно указать перед его запуском.
2: чтобы появилась возможность перенаправлять потоки, свойству UseShellExecute необходимо присвоить значение false.
4-5: здесь, я полагаю, всё очевидно. Указываем, что запускать и с какими аргументами. Если путь к plink.exe не прописан в $env:path, необходимо указать полный путь.
7: запускаем процесс. [void] используется для того, чтобы PowerShell не выводил на экран возвращаемое методом Start() значение (True). В качестве альтернативы можно использовать $ps.Start() | Out-Null.
9: после старта процесса мы можем получить доступ к стандартному потоку ввода с помощью свойства StandardInput нашего объекта Process, которое ссылается на объект класса StreamWriter.
11-18: Отправка команд в поток ввода plink.
11: Перед отправкой каждой команды будем делать небольшую паузу (полсекунды) для того, чтобы устройство успело подготовиться и отправить нам приглашение (в данном случае - запрос имени пользователя).
12: Используем метод Write для записи в поток. При этом посылаемая последовательность должна заканчиваться символом возврата каретки. В случае PowerShell это `r. Также можно использовать метод WriteLine() - тогда управляющие символы нам не потребуются.
20: Отправка команд завершена, закрываем поток ввода.
22: Если последняя отправленная команда не инициировала корректного окончания сессии, то после завершения работы скрипта сам процесс останется в фоне. В данной строке мы с помощью свойства HasExited проверяем состояние процесса, и в случае если он все еще незавершен, убиваем его методом Kill().

Двигаемся дальше. Что будет, если мы случайно присвоим свойству FileName некорректное значение (например, "plik";)? Правильно, будет вызвано исключение. Мы с вами люди приличные и было бы неплохо, если бы такие моменты были нами учтены. Поэтому добавим следующую строку в наш скрипт:

trap{ Write-Host ("Error: Не найден путь к файлу " + $ps.StartInfo.FileName + "."); exit 1 }

Она выведет сообщение о некорректных данных и завершит скрипт.

А что если мы хотим, скажем, только автоматически залогониться на устройстве, а затем взять всё управление в свои руки? Для этого нам послужит следующий код:

  1. do { $myStreamWriter.Write([Console]::ReadKey($true).KeyChar) }
  2. while (!$ps.HasExited)


Цикл будет существовать вплоть до завершения процесса. На каждой итерации с помощью метода [Console]::ReadKey($true) считывается каждый отдельный символ. Единственный аргумент, установленный в true, отключает локальный вывод на экран вводимых символов (устройство и так будет отсылать их обратно, так что они будут выведены экран). ReadKey возвращает объект System.ConsoleKeyInfo, свойство KeyChar которого представляет собой введенный символ. Его-то мы в конечном итоге и отпраляем устройству с помощью $myStreamWriter.Write().

В итоге скрипт примет следующий вид:

  1. $ps = New-Object -TypeName System.Diagnostics.Process
  2. $ps.StartInfo.UseShellExecute = $false
  3. $ps.StartInfo.RedirectStandardInput = $true
  4. $ps.StartInfo.FileName = "plink"
  5. $ps.StartInfo.Arguments = "-telnet 172.30.20.5"

  6. trap{ Write-Host ("Error: Не найден путь к файлу " + $ps.StartInfo.FileName + "."); exit 1 }

  7. [void]$ps.Start()

  8. $myStreamWriter = $ps.StandardInput

  9. Start-Sleep -m 500
  10. $myStreamWriter.Write("root`r")
  11. Start-Sleep -m 500
  12. $myStreamWriter.Write("password`r")
  13. Start-Sleep -m 500
  14. $myStreamWriter.Write("enable`r")

  15. #Перед началом ввода очистим экран
  16. Clear-Host

  17. do { $myStreamWriter.Write([Console]::ReadKey($true).KeyChar) }
  18. while (!$ps.HasExited)

  19. $myStreamWriter.Close();

  20. if (!$ps.HasExited) { $ps.Kill() }

Возможно в процессе тестирования вы отметили прескорбный факт - нет реакции на нажатие клавиш со стрелками. В действительности это вполне решаемая проблема: если ReadKey считывает нажатие одной из этих клавиш, то мы должны отправить на вход процесса определенную специальную последовательность:

  1. do {

  2. $key = Console.ReadKey($true)

  3. if ( $key.Key -eq [System.ConsoleKey]::UpArrow ) { $myStreamWriter.Write("\x1b[A") }
  4. elseif ( $key.Key -eq [System.ConsoleKey]::DownArrow ) { $myStreamWriter.Write("\x1b[B") }
  5. elseif ( $key.Key -eq [System.ConsoleKey]::LeftArrow ) { $myStreamWriter.Write("\x1b[D") }
  6. elseif ( $key.Key -eq [System.ConsoleKey]::RightArrow ) { $myStreamWriter.Write("\x1b[C") }
  7. else { $myStreamWriter.Write($key.KeyChar) }

  8. } while (!$ps.HasExited)





NoteЕсли с помощью вашего скрипта вы пробовали подключаться к шеллу Линукса и выполняли ping к какому-нибудь узлу, то могли заметить, что стандартное прерывание выполнения команды по нажатию CTRL-C работает не так, как вы того ожидали. В plink эта комбинация используется для завершения работы. Я не знаю, как можно обойти эту проблему, если вы привыкли использовать стандартный эмулятор терминала и не готовы от него отказываться. Могу лишь сказать, что в ConEmu для этой цели отлично срабатывает комбинация CTRL-SHIFT-C.


Окончательный вариант кода

Сказ про Plink, Expect и PowerShell. Предисловие
Сказ про Plink, Expect и PowerShell. Часть II: Нам нужен multithreading (Начало)
Сказ про Plink, Expect и PowerShell. Часть II: Нам нужен multithreading (Продолжение)
Сказ про Plink, Expect и PowerShell. Часть III: PowerShell!

@темы: RedirectStandardInput, Putty, PowerShell, Plink, .NET Framework

12:14 

Сказ про Plink, Expect и PowerShell. Предисловие

Саша это я, да
Полгода назад у руководства на моей работе случилось внеплановое обострение ФГМ, которое в результате недлительного брожения приняло форму приказа "Об ограничении точек доступа в интернет сотрудников филиала". О причинах и предпосылках этого очередного рецидива упоминать я, пожалуй, не стану, т.к. некоторые пункты вышеозначенной бумажки и без того вызвали всеобщую печаль. Лично меня больше всего огорчил пассаж о запрете на использование на АРМ сотрудников Unix подобных ОСей.

Как бы то ни было, был я вынужден в итоге переползти на седьмой маздай. И, надо признать, он оказался не так плох. Мне удалось реализовать большую часть всех удобных фич, к которым я уже успел привыкнуть на своем любовно настраиваемом Awesome WM. Больше поразил своими возможностями боевой тандем PowerShell и ConEmu. Про PowerShell и широту его возможностей, я думаю, все в курсе. ConEmu - это лучший эмулятор терминала для винды. В нем есть практически все, что необходимо пользователю, который привык к таким штукам на Linux. В частности, это вкладки, возможность запуска любых консольных приложений в них (даже Putty!) и хоткеи всех раскрасок и мастей (в том числе глобальные - можно, например, назначить сочетание клавиш на сворачивание/разворачивание окна терминала). Да и вообще, там куча всевозможных настроек и фич.

Между тем, я тут не просто разглагольствую о том, какой наш маздай крутой, если навешать на него 100500 сторонних приложений, а постепенно подвожу к самой сути всей этой писанины. Но наберитесь терпения, мне уже самому надоело ходить вокруг да около.

Я работаю в телекоме в секторе IP телефонии, и за нашим отделом числится несколько сотен абонентских VoIP шлюзов. Закономерной в таких условиях ситуацией является периодическое возникновение потребности изменения одного или целого ряда параметров на всех устройствах. Никакой системы управления шлюзами у нас нет, поэтому вся настройка производится вручную через web или ssh серверы, запущенные на устройствах. Пока я сидел на Linux, панацеей были скрипты, написанные на Expect - расширении для языка TCL, которое реализует автоматизацию взаимодействия с консольными приложениями (например, клиентами ftp, telnet или ssh). Кроме VoIP шлюзов, в нашем зоопарке присутствует целое множество различных VoIP серверов, которые в совокупности представляют собой один большой Softswitch. Плюс несколько узкоспециализированных отдельных серверных машин. Держать в собственной памяти все IP адреса и пароли доступа к CLI интерфейсам, согласитесь, не очень удобно. Поэтому у подавляющей части сотрудников отдела на АРМ установлены эмуляторы терминалов, которые позволяют сохранять настройки отдельных подключений. Как правило, это SecureCRT, вариант который я лично отмел по трем причинам, а именно: а) этот продукт является платным, а наша контора его не покупала; б) у меня уже есть ConEmu и нет желания плодить несколько эмуляторов, каждый под отдельные нужды; в) я считаю интерфейс SecureCRT не самым удобным в использовании. С условием использования expect скриптов все функции SecureCRT, как мне изначально казалось, можно легко перенести на ConEmu. Вкратце, я представлял себе этот процесс следующим образом:
  1. На диске создается структура каталогов, подобная дереву SecureCRT. Каждый из листьев дерева будет представлять собой expect-скрипт для доступа к конкретному устройству.
  2. Каждый expect-скрипт выполняет следующие действия: подключается к устройству по telnet или ssh, вводит имя пользователя и пароль, после чего выполняет команду Interact, которая передает брозды управления устройством пользователю.
  3. Для автоматизации создания самих скриптов должен также быть написан специальный скрипт, который должен в качестве входящих данных принимать параметры соединения (протокол соединения, IP адрес, порт, имя пользователя, пароль, тип устройства). Удобства ради прописываем путь к скрипту в $PATH. В качестве языка программирования я предполагал использовать PowerShell (нужно, наверное, использовать мощношелл раз уж мы сидим на винде).
  4. В настройках ConEmu устанавливаем хоткей, который создает новую вкладку с оболочкой (cmd или powershell) в корне нашей структуры каталогов.

Expect TCL на Windows

Здесь начались неприятности. Большая часть запросов "expect scripts windows" в гугле указывала на сайт ActiveState и их продукт ActiveTcl с расширением Expect. Недолго думая, я скачал, установил и приступил к написанию тестовых скриптов. Практически сразу выяснилось, что команда interact по какой-то причине не работает. Help дал объяснения:

exp_interact [string1 body1] ... [stringn [bodyn]]
Not currently implemented on Windows. On Unix systems, this command gives control of the current process to the user, so that keystrokes are sent to the current process, and the stdout and stderr of the current process are returned.


Пришлось искать альтернативы:
  1. Использование PowerShell для автоматизации взаимодействия с устройствами. Самый удобоваримый с точки зрения нативности вариант.
  2. Использование Cygwin. Linux на Windows. Извращение, да, но работать должно, тем более это второе по популярности решение.
  3. Activestate Perl с модулем Expect - по всей видимости имеет те же проблемы, что и ActiveTcl.
  4. Lua с модулем Expect.
  5. Strawberry Perl с модулем Expect.
  6. Python с модулем pexpect.
Наиболее интересным сразу показался первый вариант, хоть он и подразумевал дичайшие пляски с бубном (и это оказалось правдой). В действительности, как выяснилось, это решение предполагает использование также .NET Framework и C#. Остальные варианты мной даже не рассматривались, их я оставил на крайний случай. К слову, следующим по привлекательности был Lua. Еще, наверное, следует заметить, что беглый гуглеж по оставшимся вариантам выявил факт, что большинство из них также обеспечат вас как бубном, так и танцами.
Наконец, я подошел к моменту, когда можно четко сформулировать задачу и таки приступить к её решению.

Постановка задачи

Сказ про Plink, Expect и PowerShell. Часть I: Простое решение
Сказ про Plink, Expect и PowerShell. Часть II: Нам нужен multithreading (Начало)
Сказ про Plink, Expect и PowerShell. Часть II: Нам нужен multithreading (Продолжение)
Сказ про Plink, Expect и PowerShell. Часть III: PowerShell!

@темы: Windows, Powershell, Expect, ConEmu, C#, .NET Framework

Выдох Вэйдера

главная