Автоматизация ввода в SecureCRT с помощью скриптов

У сетевых инженеров часто возникает задача, выполнить copy/past определенных фрагментов из блокнота в консоль. Копировать приходиться обычно несколько параметров: Username/Password и что-либо еще. Ускорить этот процесс позволяет использование скриптов. НО задачи написание скрипта и выполнение скрипта суммарно должны занять меньше времени, чем настройка вручную, иначе скрипты бесполезны.

Для чего нужна эта статья. Эта статья из цикла Fast Start и направлена на экономию времени сетевым инженерам, при настройке оборудования (одной задачи) на нескольких устройствах. Используется ПО SecureCRT и встроенный функционал выполнение скриптов.

Содержание


Введение


В программу SecureCRT из коробки встроен механизм выполнения скриптов. Для чего нужны скрипты в терминале:

  • Автоматизированный ввод и вывод, и минимальная проверка корректности ввода/вывода.
  • Ускорить выполнение рутинных задач — уменьшение пауз между настройкой оборудования. (Де факто уменьшение пауз, вызванных временем для выполнения действий по copy/past на одном и том же оборудовании, при 3-х и более фрагментов команд для применения на оборудовании.)


В данном документе рассмотрены задачи:

  • Создание простейших скриптов.
  • Запуск скриптов на SecureCRT.
  • Примеры использования простых и продвинутых скриптов. (Практика из реальной жизни.)


Создание простейших скриптов.


Простейшие скрипты используют всего две команды Send и WaitForString. Этого функционала хватит на 90% (и более) выполняемых задач.

Скрипты могут работать на языке Python, JS, VBS (Visual Basic), Perl и т. д.

Python

# $language = "Python"
# $interface = "1.0"
def main():
  crt.Screen.Synchronous = True
  crt.Screen.Send("\r")
  crt.Screen.WaitForString("name")
  crt.Screen.Send("admin\r")
  crt.Screen.WaitForString("Password:")
  crt.Screen.Send("Password")
  crt.Screen.Synchronous = False
main()


Обычно файл с расширением »*.py»

VBS

# $language = "VBScript"
# $interface = "1.0"
Sub Main
  crt.Screen.Synchronous = True
  crt.Screen.Send vbcr
  crt.Screen.WaitForString "name"
  crt.Screen.Send "cisco" & vbcr
  crt.Screen.WaitForString "assword"
  crt.Screen.Send "cisco" & vbcr
  crt.Screen.Synchronous = False
End Sub


Обычно файл с расширением »*.vbs»

Создание скрипта с помощью записи скрипта.


Позволяет автоматизировать процесс написания скрипта. Вы запускаете запись скрипта. SecureCRT записывает команды и последующий ответ оборудования и выводит Вам готовый скрипт.

а. Запустить запись скрипта:
Меню SecureCRT => Script => Start Recording Script
б. Выполнить действия с консолью (выполнить действия по настройке в CLI).
в. Закончить запись скрипта:
Меню SecureCRT => Script => Stop Recording Script…
Сохранить файл со скриптом.

Пример выполненных команд и сохраненного скрипта:

n2pagiv3jkemqmbj2674drkj5jc.png


Запуск скриптов на SecureCRT.


После создания/редактирования скрипта, возникает закономерный вопрос: Как применить скрипт?
Существует несколько способов:

  • Запуск вручную из меню Script
  • Автоматический запуск после подключения (logon script)
  • Автоматический logon без использования скрипта
  • Запуск вручную с помощью кнопки в SecureCRT (кнопку еще предстоит создать и добавить в SecureCRT)


Запуск вручную из меню Script


Меню SecureCRT => Script => Run…
— Последние 10 скриптов запоминаются и доступны для быстрого запуска:
Меню SecureCRT => Script => 1 «Имя файла со скриптом»
Меню SecureCRT => Script => 2 «Имя файла со скриптом»
Меню SecureCRT => Script => 3 «Имя файла со скриптом»
Меню SecureCRT => Script => 4 «Имя файла со скриптом»
Меню SecureCRT => Script => 5 «Имя файла со скриптом»

Автоматический запуск после подключения (logon script)


Настройки автоматического скрипта логирования настраиваются для сохраненной сессии: Connection => Logon Actions => Logon script

409vbewwwww7qiwhylwry04bkqg.png


Автоматический logon без использования скрипта


Существует возможность автоматического ввода имени пользователя пароля без написания скрипта, используя только встроенный функционал SecureCRT. В настройках соединения «Connection» => Logon Actions => Automate logon — необходимо заполнить несколько связок — которые подразумевают собой пары: «Ожидаемый текст» + «Посылаемые символы на этот текст» таких пар может быть много. (Пример: 1-я пара ожидание ввода имени пользователя, вторая ожидание ввода пароля, третья ожидание приглашения в привилегированный режим, четвертая пароль от привилегированного режима.)

Пример автоматического logon на Cisco ASA:

elnb7yegiuxjuhdrbistcbid51a.png


Запуск вручную с помощью кнопки в SecureCRT (кнопку еще предстоит создать и добавить в SecureCRT)


В SecureCRT можно задать скрипт — кнопке. Кнопка добавляется на специально созданную для этой цели панель.

а. Добавляем панель в интерфейс: Меню SecureCRT => View => Button Bar
б. Добавляем кнопку на панель и добавляем скрипт. — Правой кнопкой мышки щелкаем на панель Button Bar и в контекстном меню выбираем пункт «New button…».
в. В диалоговом окне «Map Button» в поле «Action» Выбираем действие (function) «Run Script».
Указываем подпись к кнопке. Цвет для иконки кнопки. Заканчиваем настройки нажатием Ok.

zuogarfvnqiaro8sellqybklwoc.png

Примечание:

Панель с кнопками очень полезный функционал.

1. Есть возможность при Logon к определенной сессии указывать какую панель по умолчанию открывать к этой вкладке.

2. Есть возможность для стандартных действий с оборудованием задать заранее заданные действия: показать show version, show running-config, сохранить конфигурацию.

qetau8yu46_7zbc7kqxyarmu3cq.png


К данным кнопкам не привязан ни какой скрипт. Только строка с действиями:

x_z5f7cs8ai-o8rbwuha9w6gtyk.png


Настройка — чтобы при переключении на сессию открывалась необходимая панель с кнопками производиться в настройках сессии:

bqrfio6rdwn1muryrnzfwcq-sg8.png


Для заказчика имеет смысл настроить индивидуальные скрипты для Login и переход на панель с частыми командами для вендора.

1skij8p_fwu7g72moyiabkysj0e.png


При нажатии на кнопку Go Cisco — панель переключается на Cisco Button Bar.

w45thmjjraidjseaoqqbrzxk9jm.png


Примеры использования простых и продвинутых скриптов. (Практика из реальной жизни.)


Простых скриптов хватает почти на все случае жизни. Но один раз мне понадобилось чуть усложнить скрипт — для ускорения работы. Это усложнение — всего лишь запрашивало дополнительные данные в диалоговом окне у пользователя.

Запрос данных у пользователя с помощью диалогового окна


У меня в скрипте запросов данных было 2. Это Hostname и 4-й октет IP адреса. Для выполнения этого действия — я загуглил как это сделать и нашел на официальном сайте SecureCRT (vandyke). — функционал называется prompt.

	crt.Screen.WaitForString("-Vlanif200]")
	hostnamestr = crt.Dialog.Prompt("Enter hostname:", "hostname", "", False)
	ipaddressstr = crt.Dialog.Prompt("Enter ip address:", "ip", "", False)
	crt.Screen.Send("ip address 10.10.10.")
	crt.Screen.Send(ipaddressstr)
	crt.Screen.Send(" 23\r")
	crt.Screen.Send("quit\r")
	crt.Screen.Send("sysname ")
	crt.Screen.Send(hostnamestr)
	crt.Screen.Send("\r") 


Эта часть скрипта запрашивала Hostname и цифры из последнего октета. Т. к. оборудования было 15 шт. И данные были представлены в таблице, то я копировал из таблицы значения и вставлял в диалоговые окна. Дальше скрипт работал самостоятельно.

FTP копирование на сетевое оборудование.


Данный скрипт запускал у меня командное окно (shell) и копировал данные по FTP. По завершению закрывал сессию. Использовать блокнот для этого невозможно, т. к. копирование идет очень долго и данные в буфере FTP не будут столько храниться:

# $language = "Python"
# $interface = "1.0"

# Connect to a telnet server and automate the initial login sequence.
# Note that synchronous mode is enabled to prevent server output from
# potentially being missed.

def main():
	crt.Screen.Synchronous = True
	crt.Screen.Send("ftp 192.168.1.1\r")
	crt.Screen.WaitForString("Name")
	crt.Screen.Send("admin\r")
	crt.Screen.WaitForString("Password:")
	crt.Screen.Send("Password\r")
	crt.Screen.WaitForString("ftp")
	crt.Screen.Send("binary\r")
	crt.Screen.WaitForString("ftp")
	crt.Screen.Send("put S5720LI-V200R011SPH016.pat\r")
	crt.Screen.WaitForString("ftp")
	crt.Screen.Send("quit\r")
	crt.Screen.Synchronous = False
main()


Ввод username/password с помощью скрипта


У одного заказчика доступ к сетевому оборудованию напрямую был закрыт. Зайти на оборудование можно было сначала подключившись к Шлюзу по умолчанию, а с него потом на оборудование подключенное к нему. Для подключения использовался ssh клиент встроенный в IOS/ПО оборудования. Соответственно имя пользователя и пароль запрашивались в консоли. С помощью скрипта ниже, имя пользователя и пароль вводились автоматически:

# $language = "Python"
# $interface = "1.0"

# Connect to a telnet server and automate the initial login sequence.
# Note that synchronous mode is enabled to prevent server output from
# potentially being missed.

def main():
	crt.Screen.Synchronous = True
	crt.Screen.Send("snmpadmin\r")
	crt.Screen.WaitForString("assword:")
	crt.Screen.Send("Password\r")
	crt.Screen.Synchronous = False
main()


Примечание: Скрипта было 2. Один для учетной записи администратора, второй для учетной записи eSIGHT.

Скрипт с возможностью напрямую дописывать данные во время выполнения скрипта.


Задачей было добавить на всем сетевом оборудовании статический маршрут. Но шлюз в интернет на каждом оборудовании был свой (и он отличался от шлюза по умолчанию). Следующий скрипт выводил таблицу маршрутизации, вводил в режим конфигурирования недописывал до конца команду (IP адрес шлюза в интернет) — эту часть дописывал я. После того как я нажимал Enter, скрипт продолжал выполнение команды.

# $language = "Python"
# $interface = "1.0"

# Connect to a telnet server and automate the initial login sequence.
# Note that synchronous mode is enabled to prevent server output from
# potentially being missed.

def main():
	crt.Screen.Synchronous = True
	crt.Screen.Send("Zdes-mogla-bit-vasha-reklama\r")
	crt.Screen.WaitForString("#")
	crt.Screen.Send("show run | inc ip route\r")
	crt.Screen.WaitForString("#")
	crt.Screen.Send("conf t\r")
	crt.Screen.WaitForString("(config)#")
	crt.Screen.Send("ip route 10.10.10.8 255.255.255.252 ")
	crt.Screen.WaitForString("(config)#")
	crt.Screen.Send("end\r")
	crt.Screen.WaitForString("#")
	crt.Screen.Send("copy run sta\r")
	crt.Screen.WaitForString("[startup-config]?")
	crt.Screen.Send("\r")
	crt.Screen.WaitForString("#")
	crt.Screen.Send("exit\r")
	crt.Screen.Synchronous = False
main()


В данном скрипте в строчке: crt.Screen.Send («ip route 10.10.10.8 255.255.255.252 ») не дописан IP адрес шлюза и отсутствует символ перевода каретки. Скрипт ждет появления следующей строчки с символами »(config)#» Данные символы появлялись после ввода мной ip адреса и enter.

Заключение:


При написании скрипта и выполнении, должно непременно выполняться правило: Время написания скрипта и выполнения скрипта ни когда не должно быть больше, времени теоретически затраченного на выполнения этой же работы вручную (copy/paste из блокнота, написание и отладка playbook для ansible, написание и отладка скрипта python). Т. е. использование скрипта должно экономить время, а не тратить время на одноразовую автоматизацию процессов (Т. е. когда скрипт уникальный и повторения больше не будет). Но если скрипт уникальный и автоматизация со скриптом и написание/отладка скрипта занимает времени меньше чем выполнение любым другим способом (ansible, command window), то скрипт — лучшее решение.
Отладка скрипта. Скрипт растет постепенно, отладка происходит на обкате на первом, втором, третьем устройстве и к четвертому скрипт скорее всего будет полностью рабочим.

Запуск скрипта (с вводом username+password) с помощью мышки обычно происходит быстрее, чем копирование из блокнота Username и Password. Но не безопасно с точки зрения безопасности.
Другой (реальный) пример когда использование скрипта: Прямого доступа на сетевое оборудование у Вас нет. Но есть необходимость донастроить все сетевое оборудование (завести в систему мониторинга, донастроить дополнительный Username/password/snmpv3username/password). Есть доступ когда вы заходите на Core коммутатор, с него открываете SSH на другое оборудование. Почему нельзя использовать средства Ansible. — Потому что мы упираемся в ограничение по количеству допустимых одновременных сессий на сетевом оборудовании (line vty 0 4, user-interface vty 0 4) (другой вопрос как в Ansible заводить разное оборудование с одним и тем же SSH первым хопом).

Скрипт уменьшает время при длительных операциях — например копирование файлов по FTP. После окончания копирования скрипт сразу начинает работать. Человеку потребуется увидеть окончание копирования, потом осознать окончание копирования, потом ввести соответствующие команды. Скрипт это делает объективно быстрее.

Скрипты применимы там, где невозможно использовать средства массовой доставки данных: Консоль. Или когда часть данных для оборудования уникальна: hostname, management ip address. Или когда писать программу и отладку к ней сложнее, чем дописать данные полученные с оборудования во время работы скрипта. — Пример со скриптом по прописыванию маршрута, когда на каждом оборудование IP адрес интернет провайдера свой. (Мои коллеги писали такие скрипты — когда DMVPN spoke было за 3 сотни. Необходимо было изменить настройки DMVPN).

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

А. Вставил консольный кабель в устройство.
Б. Запустил скрипт
В. Дождался выполнения скрипта
Г. Переткнул консольный кабель в следующее устройство.
Д. Если свитч не последний, перейти к пункту Б.

Итого по итогам работы скрипта:

  • на оборудовании задан начальный пароль.
  • введен Username
  • введен уникальный IP адрес устройства.


P.S. операцию пришлось повторять. Т. к. по Default ssh оказался не настроен/выключен. (Да это моя ошибка.)

Использованные источники.


1. О создании скриптов
2. Примеры скриптов

Приложение 1: Примеры скриптов.


Пример длинного скрипта, с двумя запросами: Hostname и IP адресом. Создавался для преднастройки оборудования через консоль (9600 бод). А также для подготовки подключения оборудования к сети.

# $language = "Python"
# $interface = "1.0"

# Connect to a telnet server and automate the initial login sequence.
# Note that synchronous mode is enabled to prevent server output from
# potentially being missed.

def main():
	crt.Screen.Synchronous = True
	crt.Screen.Send("\r")
	crt.Screen.WaitForString("name")
	crt.Screen.Send("admin\r")
	crt.Screen.WaitForString("Password:")
	crt.Screen.Send("Password\r")
	crt.Screen.Send("sys\r")
	crt.Screen.WaitForString("]")
	crt.Screen.Send("interface Vlanif 1\r")
	crt.Screen.WaitForString("Vlanif1]")
	crt.Screen.Send("undo ip address\r")
	crt.Screen.Send("shutdown\r")
	crt.Screen.Send("vlan 100\r")
	crt.Screen.Send(" description description1\r")
	crt.Screen.Send(" name description1\r")
	crt.Screen.Send("vlan 110\r")
	crt.Screen.Send(" description description2\r")
	crt.Screen.Send(" name description2\r")
	crt.Screen.Send("vlan 120\r")
	crt.Screen.Send(" description description3\r")
	crt.Screen.Send(" name description3\r")
	crt.Screen.Send("vlan 130\r")
	crt.Screen.Send(" description description4\r")
	crt.Screen.Send(" name description4\r")
	crt.Screen.Send("vlan 140\r")
	crt.Screen.Send(" description description5\r")
	crt.Screen.Send(" name description5\r")
	crt.Screen.Send("vlan 150\r")
	crt.Screen.Send(" description description6\r")
	crt.Screen.Send(" name description6\r")
	crt.Screen.Send("vlan 160\r")
	crt.Screen.Send(" description description7\r")
	crt.Screen.Send(" name description7\r")
	crt.Screen.Send("vlan 170\r")
	crt.Screen.Send(" description description8\r")
	crt.Screen.Send(" name description8\r")               
	crt.Screen.Send("vlan 180\r")
	crt.Screen.Send(" description description9\r")
	crt.Screen.Send(" name description9\r")
	crt.Screen.Send("vlan 200\r")
	crt.Screen.Send(" description description10\r")
	crt.Screen.Send(" name description10\r")
	crt.Screen.Send("vlan 300\r")
	crt.Screen.Send(" description description11\r")
	crt.Screen.Send(" name description11\r")
	crt.Screen.Send("quit\r")
	crt.Screen.WaitForString("]")
	crt.Screen.Send("stp region-configuration\r")
	crt.Screen.Send("region-name desc\r")
	crt.Screen.Send("active region-configuration\r")
	crt.Screen.WaitForString("mst-region]")
	crt.Screen.Send("quit\r")
	crt.Screen.Send("stp instance 0 priority 57344\r")
	crt.Screen.WaitForString("]")
	crt.Screen.Send("interface range GigabitEthernet 0/0/1 to GigabitEthernet 0/0/42\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("description Users\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("port link-type hybrid\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("voice-vlan 100 enable\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("voice-vlan legacy enable\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("port hybrid pvid vlan 120\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("port hybrid tagged vlan 100\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("port hybrid untagged vlan 120\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("stp edged-port enable\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("trust 8021p\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("storm-control broadcast min-rate 1000 max-rate 1500\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("storm-control multicast min-rate 1000 max-rate 1500\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("storm-control action block\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("storm-control enable trap\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("quit\r")
	crt.Screen.Send("interface range GigabitEthernet 0/0/43 to GigabitEthernet 0/0/48\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("description Printers\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("port link-type access\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("port default vlan 130\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("stp edged-port enable\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("trust 8021p\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("storm-control broadcast min-rate 1000 max-rate 1500\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("storm-control multicast min-rate 1000 max-rate 1500\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("storm-control action block\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("storm-control enable trap\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("quit\r")
	crt.Screen.Send("interface range XGigabitEthernet 0/0/1 to XGigabitEthernet 0/0/2\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("description uplink\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("port link-type trunk\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("port trunk allow-pass vlan 100 110 120 130 140 150 160 170 180 200\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("port trunk allow-pass vlan 300\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("storm-control broadcast min-rate 1000 max-rate 1500\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("storm-control multicast min-rate 1000 max-rate 1500\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("storm-control action block\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("storm-control enable trap\r")
	crt.Screen.WaitForString("port-group]")
	crt.Screen.Send("quit\r")
	crt.Screen.Send("ntp-service unicast-server 10.10.10.4\r")
	crt.Screen.Send("ntp-service unicast-server 10.10.10.2\r")
	crt.Screen.Send("ntp-service unicast-server 10.10.10.134\r")
	crt.Screen.Send("ip route-static 0.0.0.0 0.0.0.0 10.10.10.254\r")
	crt.Screen.Send("interface Vlanif 200\r")
	crt.Screen.WaitForString("-Vlanif200]")
	crt.Screen.Send("\r")
	crt.Screen.WaitForString("-Vlanif200]")
	crt.Screen.Send("\r")
	crt.Screen.WaitForString("-Vlanif200]")
	crt.Screen.Send("\r")
	crt.Screen.WaitForString("-Vlanif200]")
	crt.Screen.Send("\r")
	crt.Screen.WaitForString("-Vlanif200]")
	crt.Screen.Send("\r")
	crt.Screen.WaitForString("-Vlanif200]")
	crt.Screen.Send("\r")
	crt.Screen.WaitForString("-Vlanif200]")
	crt.Screen.Send("\r")
	crt.Screen.WaitForString("-Vlanif200]")
        hostnamestr = crt.Dialog.Prompt("Enter hostname:", "hostname", "", False)
        ipaddressstr = crt.Dialog.Prompt("Enter ip address:", "ip", "", False)
	crt.Screen.Send("ip address 10.10.10.")
	crt.Screen.Send(ipaddressstr)
	crt.Screen.Send(" 24\r")
	crt.Screen.Send("quit\r")
	crt.Screen.Send("sysname ")
	crt.Screen.Send(hostnamestr)
	crt.Screen.Send("\r")
	crt.Screen.WaitForString("]")
	crt.Screen.Synchronous = False
main()


Такие скрипты обычно не нужны, но количество оборудования — 15 шт. Позволило ускорить настройку. Дальше настраивать оборудование было быстрее с помощью SecureCRT Command window.

Настройка учетной записи для ssh.


Другой пример. Настройка также через консоль.

# $language = "Python"
# $interface = "1.0"

# Connect to a telnet server and automate the initial login sequence.
# Note that synchronous mode is enabled to prevent server output from
# potentially being missed.

def main():
	crt.Screen.Synchronous = True
	crt.Screen.Send("\r")
	crt.Screen.WaitForString("name")
	crt.Screen.Send("admin\r")
	crt.Screen.WaitForString("Password:")
	crt.Screen.Send("Password\r")
	crt.Screen.WaitForString(">")
	crt.Screen.Send("sys\r")
	crt.Screen.Send("stelnet server enable\r")
	crt.Screen.Send("aaa\r")
	crt.Screen.Send("local-user admin service-type terminal ftp http ssh\r")
	crt.Screen.Send("quit\r")
	crt.Screen.Send("user-interface vty 0 4\r")
	crt.Screen.Send("authentication-mode aaa\r")
	crt.Screen.Send("quit\r")
	crt.Screen.Send("quit\r")
	crt.Screen.Synchronous = False
main()


О SecureCRT:
Платное ПО: от 99$ (самая маленькая цена только на SecureCRT на один год)
Официальный сайт
1 раз покупается лицензия на ПО, с поддержкой (для обновления), потом ПО используется с этой лицензией неограниченное время.

Работает на операционных системах Mac OS X и Windows.

Есть поддержка скриптов (эта статья)
Есть Command Window
Serial/Telnet/SSH1/SSH2/Shell Операционной системы

© Habrahabr.ru