Сквозь порты на оборудовании к пользовательским машинам

в 8:46, , рубрики: powershell, Регулярные выражения, системное администрирование, метки: , ,

Доброе время суток, читатели.

Данный пост повествует Вам о том, как с помощью PowerShell мы опять смогли немного облегчить нам жизнь и автоматизировать поиск оборудования и портов, на которых сидят компьютеры пользователей. Это необходимо в тот момент, когда надо пробросить vlan`ы (ну или просто для информации).
Сквозь порты на оборудовании к пользовательским машинам"

Предыстория

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

Используя Putty, я попробовал проделать все это вручную, все получилось и я начал думать как все это автоматизировать. Да не просто автоматизировать, а сделать это с помощью PowerShell. Почему именно PoSh? В то время я на него сильно запал (хотя я и сейчас с него не слезаю), можно было сделать это в чем-нибудь другом, но мне очень сильно хотелось сделать это через PoSh.

Так как подключаться к оборудованию требовалось через Telnet и SSH (в основном, через Telnet, так как на тот момент SSH был не везде, но об этом позже) я провел немало времени в интернете, что бы узнать как PowerShell может работать с этими протоколами.
Тогда я обратился к 2 механизмам подключения:

  1. использование plink.exe (из Putty)
  2. Netcmdlets фирмы /n software

Использовав преимущественно plink.exe у меня получилось нечто, что трудно было назвать произведением искусства. Это было огромный, нагроможденный скрипт, который совершенно никому не хотелось показывать. А уж тем более писать об этом статью тут.
И так как оно работало (периодически, я им даже пользовался), я отложил его оптимизацию в долгий ящик и занялся другими делами.

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

Vader, rise!

Самым важным из произошедшего было то, что обновили прошивки у коммутаторов, и теперь стало возможно подключаться к ним по SSH. Что же, отлично, значит забиваем на telnet.
За этим последовало еще 1 изменение: раз telnet больше не нужен, мы можем не использовать plink.exe (из Putty) и вся слава достается NetCmdlets.
Пообщавшись не мало с техподдержкой этой фирмы, наконец я разобрался в устройстве этих орудий труда, и начал работу.

Что мне было нужно:

  1. Проверять правильность ввода IP адреса компьютера
  2. Проверять наличие компьютера в сети
  3. Подключаться к каждому оборудованию последовательно, начиная с самого главного, и выяснять на каких портах висит наше устройство
  4. Показать все это пользователю, что бы он (т.е. я) остался доволен

NetCmdlets

Так как я хочу сказать спасибо людям, которые их сделали, небольшое отступление и пару слов об этих командлетах:
Почитать про них и скачать их можно здесь. Скачать можно как триальную версию (на 30 дней) так и полную (за 100$ на один компьютер). Я пользовался триальной версией, т.к. после окончании срока ее можно просто переустановить.
Недавно на ресурсе powershellmagazine.com раздавались эти командлеты бесплатно, я успел ухватить. Так что будьте на чеку! Но вернемся к нашим баранам.
Выбрав набор этих cmdletов, я приступил к их изучению. Этот набор достаточно большой, так что я не буду описывать все, что там есть. остановлюсь на 2:

  • Сonnect-SSh
  • Invoke-SSh

Можно обойтись только Invoke-SSh, но тогда у вас не будет постоянной сессии с устройством (т.е., скажем, выполнить команды в «conf t» на устройстве уже будет нельзя). По этому командлетом Сonnect-SSh мы создаем подключение к устройству, а командлетом Invoke-SSh выполняем команды. Все достаточно просто.
Важным моментом является то, что эти командлеты могут работать вместе с командлетом Get-Credential, в которую Вы можете записать учетные данные на подключение к оборудованию (примеры увидите в скрипте), т.е. учетные данные не хранятся в открытом виде в скрипте (как это у меня было с использованием plink.exe). Я не параноик, но перестраховаться люблю.

Парсинг вывода и регулярные выражения

Меня ожидал очень романтичный парсинг вывода оболочки Cisco iOS, т.к командлет Invoke-SSH показывает вывод в столбце Text, т.е. передав в переменную вывод, мы получаем большой текст.
Здесь нам приходят на помощь регулярные выражения. Но кто парсил большие тексты, знает, что достать нужный фрагмент оттуда довольно не просто, но, благодаря PowerShell, мы с блеском вышли из этой ситуации.
В данный момент читай PowerShell in Action 2-nd Edition. Тому, кто изучает PoSh, я настоятельно рекомендую эту книгу. И в ней я прочел о такой вещи, как named regexp. Суть в том, что при PowerShell содержит переменную $Mathces, в которую заносятся все совпадения при использовании регулярных выражений:

PS [13] > $Matches
PS [14] > $a="ass 123 saa"
PS [15] > $a -match "d+"
True
PS [16] > $Matches

Name                           Value                                           
----                           -----                                           
0                              123 

Но это еще не все! Вся плюшка в том, что если мы добавим ?<Numbers> в наше выражение, мы получим следующее:

PS [17] > $a -match "(?<Numbers>d+)"
True
PS [18] > $Matches

Name                           Value                                           
----                           -----                                           
Numbers                        123                                             
0                              123                                             

PS [19] > $Matches.Numbers
123

Таким образом мы можем обращаться напрямую к результатам совпадений и обойти парсинг такой замечательной вещью (ф топку split!). Не знаю как для вас, а для меня это было открытие (например, на русских ресурсах по PowerShell об этом ничего нет), при котором я был готов визжать как школьница.
Интересно, в других языках такое есть?

Сам скрипт

Собственно, сам скрипт.

## Функция вывода информации о конечном оборудовании
Function LastSwitch
    {
    "IP адрес свича: {0}, порт {1}" -f $IpSwitch,$Port
    If ($CiscoPhone) {"Компьютер подключен через Cisco IP Phone"}
    Read-Host "Press Enter for continue..."
    break   
    }
##Проверяем правильность ввода
For (;;)
    {
    $ip=Read-host "Enter ip"
    If ($ip -match "(d{1,3}.){3}d{1,3}") {break}
    else {Write-Warning "Invalid IP address! Try again..."}
    }
##Проверяем на наличие компьютера в сети
if ((test-connection $ip -quiet) -ne "True") 
    {
    Write-Warning "Компьютер не в сети!"
    Read-Host "Press Enter to continue..."
    break
    }
$cred = get-credential Admin ##Учетная запись для подключения к устройствам
$IpSwitch = "10.138.30.1" ##Родоначальник наших коммутаторов
$MAC = $null
$CiscoPhone = $false
For (;;)
{
$conn = Connect-ssh -Server $IpSwitch -Credential $cred -ShellPrompt "#" -Force
Invoke-SSH -Connection $conn -Command "terminal length 0" | out-null
Invoke-SSH -Connection $conn -Command "ping $ip" | out-null
If (!$MAC)
    {
    ## Получаем МАС адрес устройства
    ((Invoke-SSH -Connection $conn -Command "sh arp | i $ip ").text | 
        Where-Object {$_ -match "w"}) -match "(?<MAC>w{4}.w{4}.w{4})"
    $MAC = $Matches.MAC
    }
## Находим порт, на котором висит оборудование
(((Invoke-SSH -Connection $conn -Command "sh mac address-table address $MAC").Text | 
    where-object {$_ -match $mac}) | 
    Select-Object -First 1) -match "(?<port>((D{2}d{1,3})/|Po)(d{1,3})(/d{1,3})?)" | out-null
$port = $Matches.Port
## Проверяем количество оборудования подключенного к порту
$portInfo = (Invoke-SSH -Connection $conn -Command "show mac address-table interface $port").Text | 
    where-object {$_ -match $port}
If (($portInfo | measure).Count -eq 1) {LastSwitch}
## Информация о порте
$DetailPortInfo = (Invoke-SSH -Connection $conn -Command "sh cdp neighbors $port detail" -Force).Text
## Если в инфомрации о порту есть запись "Cisco IP phone", значит компьютер подключен чезез IP телефон.
If ($DetailPortInfo -match "Cisco IP phone") {$CiscoPhone = $true; LastSwitch}
(($DetailPortInfo | where-object {$_ -match "IP address: (d{1,3}.){3}d{1,3}"}) | 
    Select-Object -First 1) -match "(?<ip>(d{1,3}.){3}d{1,3})" | out-null
"IP адрес свича: {0}, порт {1}" -f $IpSwitch,$Port
$IpSwitch = $Matches.IP
Disconnect-SSH $conn
}

Небольшие комментарии:

  • Учетные данные мы получаем с помощью команды Get-Credential. Кому лень вводить каждый раз учетные данные, можно сделать автоматически, поместив зашифрованный пароль в текстовый файл. Подробнее об этом можно прочитать тут
  • На наших коммутаторах стоит ограничение на вывод информации за 1 раз (т.е. листать может с помощью пробела (аналогия из PowerShell: get-help | more)). Нас это не устраивает, т.к. Invoke-SSH не умеет листать, и в итоге мы будем получать ошибку. Для решения данной проблемы, мы отключаем это на время сессии с помощью команды terminal length 0
  • ((D{2}d{1,3})/|Po)(d{1,3})(/d{1,3})? — регулярное выражение, призванное нами для нахождения портов. Оно ловит такие порты как: Fa5, Gi0/2, Te2/0/8, Po255 и т.п. (если вдруг что то забыл, пишите, я подправлю)
  • Некоторые компьютеры у нас могут быть подключены через Cisco IP phone, по этому пришлось добавить еще пару строк, что бы это выяснять
  • Так как нам не нужен вывод лишней информации (от Invoke-SSH), этот вывод мы дружно отправляем в никуда (out-null)

Автор: uksus

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js