Работая в компании IT-аутсорса в качестве руководителя 3 линии поддержки, задумался, как автоматизировать подключение сотрудников по RDP, через VPN к серверам десятков клиентов.
Таблички с адресами, паролями и прочими настройками серверов, конечно, хорошо, но поиск клиента и вбивание адресов с аккаунтами занимает довольно существенное время.
Держать все подключения к VPN в Windows не самая лучшая идея, да и при переустановке оного, создавать VPNы тоже не доставляет удовольствие.
Плюс к тому, в большинстве случаев, требуется установить VPN подключение к клиенту без использования шлюза. дабы не гонять весь интернет-трафик через клиента.
Задача, к тому же, осложняется тем, что у некоторых клиентов pptp, у кого-то l2tp, у некоторых несколько подсетей, туннели и т.п.
В результате, для начала был написан скрипты на Powershell для каждого клиента, но позже, узнав, что в Powershell можно использовать Winforms они переродились в некое приложение, написанное с помощью того же Powershell.
До написания этого скрипта-приложения программированием не занимался вообще, разве что лет 20 назад что-то пописывал на VBS в MS Excel и MS Access, поэтому не гарантирую красивость кода и принимаю критику от опытных программистов, как можно было бы сделать красивее.
В Powershell, начиная с Windows 8 и, конечно в Windows 10, появилась прекрасная возможность создавать VPN подключения командой Add-VpnConnection и указывать какие маршруты использовать с этими соединениями командой Add-VpnConnectionRoute, для использования VPN без шлюза.
На основании этих команд и создано данное приложение. Но, обо всем по порядку.
Хранить чувствительные данные (имена, пароли, коды доступа) в публичных облаках небезопасно, выбирайте защищенные места хранения таких данных.
В данном коде таблица в Google Disk представлена для примера, не содержит никаких реальных данный и предназначена для быстрой проверки работоспособности скрипта без раскрытия каких-либо данных автора, которые он не хотел бы разглашать.
Для начала, создаем в удобном вам и безопасном хранилище (в примере в Google Disk) таблицу с именованными столбцами:
Number; Name; VPNname; ServerAddress; RemoteNetwork; VPNLogin; VPNPass; VPNType; l2tpPsk; RDPcomp; RDPuser; RDPpass; DefaultGateway; PortWinbox; WinboxLogin; WinboxPwd; Link; Inform
Основное требование к таблице - свободное конвертирование в формат CSV, либо изначальное хранение формате CSV (знак разделения указывается в переменной comma)
![](https://habrastorage.org/getpro/habr/upload_files/91b/0e5/a4a/91b0e5a4aec4ad34ef63095bd5fd7f83.png)
VPNname – произвольное имя для VPN соединения
ServerAddress – адрес VPN сервера
RemoteNetwork – адреса подсети или подсетей клиента, разделенные «;»
VPNLogin; VPNPass – учетная запись VPN
VPNType -тип VPN (пока используется pptp или l2tp)
l2tpPsk – PSK для l2tp, в случае pptp оставляем пустым
RDPcomp – адрес сервера RPD
RDPuser; RDPpass – учетная запись RPD
DefaultGateway принимает значение TRUE или FALSE и указывает на то, использовать ли «Шлюз по умолчанию» для этого соединения. В 90% случаев = FALSE
PortWinbox; WinboxLogin; WinboxPwd – порт, логин и пароль для Winbox, поскольку у нас большинство клиентов использует Mikrotik)
Link – ссылка на расширенную информацию о компании, например, на диске Google, или в любом другом месте, будет выводиться в информационном поле для быстрого доступа к нужной информации
Inform – примечание
Пример таблицы доступен по ссылке
Number | Name | VPNname | ServerAddress | RemoteNetwork | VPNLogin | VPNPass | VPNType | l2tpPsk | RDPcomp | RDPuser | RDPpass | DefaultGateway | PortWinbox | WinboxLogin | WinboxPwd | Link | Inform |
1 | Тест1 | Test1 | a.b.c.d | 192.168.10.0/24: 10.10.0.0/24 | vpnuser | passWord | pptp | none | 192.168.10.1 | user | passWord | TRUE | 8291 | Admin | Admin | тест | |
2 | Тест2 | Test2 | e.f.j.k | 192.168.2.0/24 | vpnuser | passWord | l2tp | KdoSDtdP | 192.168.2.1 | user | passWord | FALSE | 8291 | Admin | Admin |
Скриншот работающего приложения с затертыми данными:
![Основное окно программы Основное окно программы](https://habrastorage.org/getpro/habr/upload_files/b74/dde/d0c/b74dded0c1605d34d14a368ad38fa322.png)
Далее следует листинг приложения с комментариями и пояснениями. Если интересно, но непонятно, задавайте вопросы, постараюсь прокомментировать
function Get-Clients #Функция принимает строку адреса файла в Google Drive и возвращает в виде массива данных о клиентах
{
param
(
[string]$google_url = ""
)
[string]$xlsFile = $google_url
$csvFile = "$env:temp\clients.csv"
$Comma = ','
Invoke-WebRequest $xlsFile -OutFile $csvFile
$clients = Import-Csv -Delimiter $Comma -Path "$env:temp\clients.csv"
Remove-Item -Path $csvFile
return $clients
}
function Main {
<#
Функция, срабатываемая при запуске скрипта
#>
Param ([String]$Commandline)
#Иннициализируем переменные и присваиваем начальные значения. Здесь же, указываем путь к таблице с клиентами
$Global:Clients = $null
$Global:Current
$Global:CurrentRDPcomp
$Global:google_file = "https://docs.google.com/spreadsheets/d/1O-W1YCM4x3o5W1w6XahCJZpkTWs8cREXVF69gs1dD0U/export?format=csv" # Таблица скачивается сразу в виде csv-файла
$Global:Clients = Get-Clients ($Global:google_file) # Присваиваем значения из таблицы массиву
#Скачиваем Winbox64 во временную папку
$download_url = "https://download.mikrotik.com/winbox/3.27/winbox64.exe"
$Global:local_path = "$env:temp\winbox64.exe"
If ((Test-Path $Global:local_path) -ne $true)
{
$WebClient = New-Object System.Net.WebClient
$WebClient.DownloadFile($download_url, $Global:local_path)
}
#Разрываем все текущие VPN соединения (на всякий случай)
foreach ($item in get-vpnconnection | where { $_.ConnectionStatus -eq "Connected" })
{
Rasdial $item.Name /disconnect
}
#Удаляем все, ранее созданные программой временные соединения, если вдруг не удалились при некорректном закрытии приложения
get-vpnconnection | where { $_.Name -match "tmp" } | Remove-VpnConnection -Force
#Запускаем приложение
Show-MainForm_psf
}
#Собственно, само приложение
function Show-MainForm_psf
{
[void][reflection.assembly]::Load('System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
[void][reflection.assembly]::Load('System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')
#Создаем форму и объекты формы
[System.Windows.Forms.Application]::EnableVisualStyles()
$formКлиентыАльбус = New-Object 'System.Windows.Forms.Form'
$statusbar1 = New-Object 'System.Windows.Forms.StatusBar'
$groupboxTools = New-Object 'System.Windows.Forms.GroupBox'
$buttonPing = New-Object 'System.Windows.Forms.Button'
$buttonВыход = New-Object 'System.Windows.Forms.Button'
$buttonWindox = New-Object 'System.Windows.Forms.Button'
$buttonПеречитатьДанные = New-Object 'System.Windows.Forms.Button'
$buttonPingAll = New-Object 'System.Windows.Forms.Button'
$groupboxRDP = New-Object 'System.Windows.Forms.GroupBox'
$comboboxRDP = New-Object 'System.Windows.Forms.ComboBox'
$textboxRDPLogin = New-Object 'System.Windows.Forms.TextBox'
$textboxRdpPwd = New-Object 'System.Windows.Forms.TextBox'
$buttonПодключитьRDP = New-Object 'System.Windows.Forms.Button'
$groupboxVPN = New-Object 'System.Windows.Forms.GroupBox'
$buttonПодключитьVPN = New-Object 'System.Windows.Forms.Button'
$buttonОтключитьVPN = New-Object 'System.Windows.Forms.Button'
$checkboxШлюзПоумолчанию = New-Object 'System.Windows.Forms.CheckBox'
$richtextboxinfo = New-Object 'System.Windows.Forms.RichTextBox'
$listbox_clients = New-Object 'System.Windows.Forms.ListBox'
$InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'
#----------------------------------------------
# Обработчики событий
#----------------------------------------------
$formКлиентыАльбус_Load = {
#При загрузке формы очистить поле информации и заполнить поле с клиентами (их названиями)
$richtextboxinfo.Clear()
$Global:Clients | ForEach-Object {
[void]$listbox_clients.Items.Add($_.Name)
} # В листбокс добавляем всех наших клиентов по именам и массива при загрузке формы
}
$listbox_clients_SelectedIndexChanged = {
#Прочитать из массива информацию о клиенте при выборе его в поле listbox_clients (массив, как мы помним считан из файла с диска Google)
$statusbar1.Text = 'Выбран клиент: ' + $listbox_clients.SelectedItem.ToString() # Пишем клиента в статусбар
$Global:Current = $Global:Clients.Where({ $_.Name -eq $listbox_clients.SelectedItem.ToString() })
If ($Current.PortWinbox -ne 0) # Если порт Winbox указан, то у клиента Mikrotik, включаем соответствующую кнопку
{
$buttonWindox.Enabled = $true
$buttonWindox.Text = "Winbox"
}
$VPNname = $Global:Current.VPNname + "-tmp" #Добавляем к имени VPN соединения "-tmp" для указания метки временного соединения, чтобы при выходе удалить только их
switch ($Global:Current.VPNType) #В зависимости от типа VPN пишем на кнопке "Подключить pptp VPN" или "Подключить l2tp VPN", если у клиента нет VPN, то пишем "Здесь нет VPN"
{
"pptp" {
$buttonПодключитьVPN.Enabled = $true
$buttonПодключитьVPN.Text = "Подключить pptp VPN"
}
"l2tp" {
$buttonПодключитьVPN.Enabled = $true
$buttonПодключитьVPN.Text = "Подключить l2tp VPN"
}
DEFAULT
{
$buttonПодключитьVPN.Enabled = $false
$buttonПодключитьVPN.Text = "Здесь нет VPN"
}
}
switch ($Global:Current.DefaultGateway) #Смотрим в массиве, используется ли у клиента "Шлюз по-умолчанию" и заполняем соответствующий чекбокс
{
"FALSE"
{ $checkboxШлюзПоумолчанию.Checked = $false }
"Нет"
{ $checkboxШлюзПоумолчанию.Checked = $false }
"TRUE"
{ $checkboxШлюзПоумолчанию.Checked = $true }
"Да"
{ $checkboxШлюзПоумолчанию.Checked = $true }
DEFAULT
{ $checkboxШлюзПоумолчанию.Checked = $false }
}
$VPNStatus = (ipconfig | Select-String $VPNname -Quiet) #Проверяем, не установлено ли уже это VPN соединение?
If ($VPNStatus) #Если установлено, то разблокируем кнопку "Подключить RDP"
{
$buttonПодключитьRDP.Enabled = $true
}
else
{
$buttonПодключитьRDP.Enabled = $false
}
$richtextboxinfo.Clear() #Очищаем информационное поле
# И заполняем информацией о клиенте из массива
$richtextboxinfo.SelectionColor = 'Black'
$richtextboxinfo.Text = "Клиент: " + $Global:Current.Name + [System.Environment]::NewLine + `
"Имя VPN: " + $Global:Current.VPNname + [System.Environment]::NewLine + `
"Тип VPN: " + $Global:Current.VPNType + [System.Environment]::NewLine + `
"Адрес сервера: " + $Global:Current.ServerAddress + [System.Environment]::NewLine + `
"Подсеть клиента: " + $Global:Current.RemoteNetwork + [System.Environment]::NewLine + `
"Адрес сервера RDP: " + $Global:Current.RDPcomp + [System.Environment]::NewLine + [System.Environment]::NewLine + `
"DefaultGateway: " + $Global:Current.DefaultGateway + [System.Environment]::NewLine + [System.Environment]::NewLine + `
"Примечание: " + [System.Environment]::NewLine + $Global:Current.Inform + [System.Environment]::NewLine + `
"Connection '" + $VPNname + "' status is " + $buttonПодключитьRDP.Enabled + [System.Environment]::NewLine
$richtextboxinfo.AppendText($Global:Current.Link)
$RDPServers = $Global:Current.RDPcomp.Split(';') -replace '\s', '' #Считываем и разбираем RDP серверы клиента из строки с разделителем в массив
#Добавляем из в выпадающее поле выбора сервера
$comboboxRDP.Items.Clear()
$comboboxRDP.Text = $RDPServers[0]
foreach ($RDPServer in $RDPServers)
{
$comboboxRDP.Items.Add($RDPServer)
}
#Заполняем поля имени и пароля RDP по умолчанию из таблицы о клиенте (при желании, их можно поменять в окне программы)
$textboxRdpPwd.Text = $Global:Current.RDPpass
$textboxRdpLogin.Text = $Global:Current.RDPuser
} # Форма заполнена, при смене выбранного клиента произойдет перезаполнение полей в соответствии с выбранным клиентом
$buttonWindox_Click = {
#Обработка нажатия кнопки Winbox
If ($Global:Current.PortWinbox -ne 0) #Если порт Winbox заполнен, то открываем скачанный ранее Winbox, подставляем туда имя и пароль к нему и запускаем
{
$runwinbox = "$env:temp\winbox64.exe"
$ServerPort = $Global:Current.ServerAddress + ":" + $Global:Current.PortWinbox
$ServerLogin = " """ + $Global:Current.WinboxLogin + """"
$ServerPass = " """ + $Global:Current.WinboxPwd + """"
$Arg = "$ServerPort $ServerLogin $ServerPass "
Start-Process -filePath $runwinbox -ArgumentList $Arg
}
}
$buttonПодключитьVPN_Click = {
#Обработка нажатия кнопки ПодключитьVPN
$VPNname = $Global:Current.VPNname + "-tmp" #Добавляем к имени VPN соединения "-tmp" для указания метки временного соединения, чтобы при выходе удалить только их
$richtextboxinfo.Clear() #Очищаем информационное поля для вывода туда информации о процессе подключения
$richtextboxinfo.Text = "Клиент: " + $Global:Current.Name + [System.Environment]::NewLine
foreach ($item in get-vpnconnection | where { $_.ConnectionStatus -eq "Connected" }) #Разрываем все установленные соединения
{
$richtextboxinfo.Text = $richtextboxinfo.Text + "Обнаружено активное соединение " + $item.Name + " разрываем его" + [System.Environment]::NewLine
Rasdial $item.Name /disconnect
}
Remove-VpnConnection $VPNname -Force #Удаляем соединение, если ранее оно было создано
$RemoteNetworks = $Global:Current.RemoteNetwork.Split(';') -replace '\s', '' #Считываем и разбираем по строкам в массив список подсетей клиента разделенный ;
switch ($Global:Current.VPNType) #В зависимости от типа VPNа создаем pptp или l2tp соединение
{
"pptp" {
$richtextboxinfo.Text = $richtextboxinfo.Text + "Создаем pptp подключение " + $VPNname + [System.Environment]::NewLine
If ($checkboxШлюзПоумолчанию.Checked -eq $false) #Если не используется "Шлюз по-умолчанию", то создаем VPN соединение без него и прописываем маршруты
{
$Errcon = (Add-VpnConnection -Name $VPNname -ServerAddress $Global:Current.ServerAddress -TunnelType $Global:Current.VPNType -SplitTunneling -Force -RememberCredential -PassThru) #Здесь происходит создание VPN
foreach ($RemoteNetwork in $RemoteNetworks) #Добавляем все подсети клиента к этому VPN
{
$richtextboxinfo.AppendText('Добавляем маршрут к ' + $RemoteNetwork + [System.Environment]::NewLine)
Add-VpnConnectionRoute -ConnectionName $VPNname -DestinationPrefix $RemoteNetwork -PassThru
}
}
else #Если используется "Шлюз по-умолчанию", то создаем VPN соединение с ним и маршруты к клиенту не нужны
{
$Errcon = (Add-VpnConnection -Name $VPNname -ServerAddress $Global:Current.ServerAddress -TunnelType $Global:Current.VPNType -Force -RememberCredential -PassThru)
}
}
"l2tp" {
$richtextboxinfo.Text = $richtextboxinfo.Text + "Создаем l2tp подключение " + $Global:Current.VPNname + [System.Environment]::NewLine
If ($checkboxШлюзПоумолчанию.Checked -eq $false) #Если не используется "Шлюз по-умолчанию", то создаем VPN соединение без него и прописываем маршруты
{
$Errcon = (Add-VpnConnection -Name $VPNname -ServerAddress $Global:Current.ServerAddress -TunnelType $Global:Current.VPNType -L2tpPsk $Global:Current.l2tpPsk -SplitTunneling -Force -RememberCredential -PassThru) #Здесь происходит создание VPN
foreach ($RemoteNetwork in $RemoteNetworks) #Добавляем все подсети клиента к этому VPN
{
$richtextboxinfo.AppendText('Добавляем маршрут к ' + $RemoteNetwork + [System.Environment]::NewLine)
Add-VpnConnectionRoute -ConnectionName $VPNname -DestinationPrefix $RemoteNetwork -PassThru
}
}
else #Если используется "Шлюз по-умолчанию", то создаем VPN соединение с ним и маршруты к клиенту не нужны
{
$Errcon = (Add-VpnConnection -Name $VPNname -ServerAddress $Global:Current.ServerAddress -TunnelType $Global:Current.VPNType -L2tpPsk $Global:Current.l2tpPsk -Force -RememberCredential -PassThru)
}
}
}
$richtextboxinfo.AppendText("Устанавливаем " + $Global:Current.VPNType + " подключение к " + $VPNname + [System.Environment]::NewLine)
$Errcon = Rasdial $VPNname $Global:Current.VPNLogin $Global:Current.VPNPass #Устанавливаем созданное VPN подключение и выводим информацию в поле
$richtextboxinfo.Text = $richtextboxinfo.Text + [System.Environment]::NewLine + $Errcon + [System.Environment]::NewLine
If ((ipconfig | Select-String $VPNname -Quiet)) #Проверяем успешность соединения и, если все удачно, разблокируем кнопку RDP и кнопку "Отключить VPN"
{
$buttonПодключитьRDP.Enabled = $true
$buttonОтключитьVPN.Visible = $true
$buttonОтключитьVPN.Enabled = $true
$statusbar1.Text = $Global:Current.Name + ' подключен'
}
}
$formКлиентыАльбус_FormClosing = [System.Windows.Forms.FormClosingEventHandler]{
#При закрытии формы подчищаем за собой. Разрываем и удаляем все созданные соединения.
foreach ($item in get-vpnconnection | where { $_.ConnectionStatus -eq "Connected" })
{
$richtextboxinfo.Text = $richtextboxinfo.Text + "Обнаружено активное соединение " + $item.Name + " разрываем его" + [System.Environment]::NewLine
Rasdial $item.Name /disconnect
}
$richtextboxinfo.Text = $richtextboxinfo.Text + "Удаляем все временные соединения" + [System.Environment]::NewLine
get-vpnconnection | where { $_.Name -match "tmp" } | Remove-VpnConnection -Force
#Удаляем информацию о RPD-серверах из реестра
$Global:Clients | ForEach-Object {
$term = "TERMSRV/" + $_.RDPcomp
cmdkey /delete:$term
}
}
$buttonПодключитьRDP_Click = {
#Обработка кнопки ПодключитьRDP
$RDPcomp = $comboboxRDP.Text
$RDPuser = $textboxRDPLogin.Text
$RDPpass = $textboxRdpPwd.Text
cmdkey /generic:"TERMSRV/$RDPcomp" /user:"$RDPuser" /pass:"$RDPpass"
mstsc /v:$RDPcomp
}
$buttonОтключитьVPN_Click = {
#При отключении VPN подчищаем за собой и оповещаем о процессе в поле информации
foreach ($item in get-vpnconnection | where { $_.ConnectionStatus -eq "Connected" })
{
$richtextboxinfo.Text = $richtextboxinfo.Text + "Обнаружено активное соединение " + $item.Name + " разрываем его" + [System.Environment]::NewLine
Rasdial $item.Name /disconnect
}
$richtextboxinfo.Text = $richtextboxinfo.Text + "Удаляем все временные соединения" + [System.Environment]::NewLine
get-vpnconnection | where { $_.Name -match "tmp" } | Remove-VpnConnection -Force
$buttonОтключитьVPN.Visible = $false
$buttonПодключитьRDP.Enabled = $false
$statusbar1.Text = $Global:Current.Name + ' отключен'
}
$buttonPingAll_Click={
#Пингуем всех клиентов и оповещаем о результатах
$I=0
$richtextboxinfo.Clear()
$richtextboxinfo.SelectionColor = 'Black'
$clientscount = $Global:Clients.count
$Global:Clients | ForEach-Object {
if ((test-connection -Count 1 -computer $_.ServerAddress -quiet) -eq $True)
{
$richtextboxinfo.SelectionColor = 'Green'
$richtextboxinfo.AppendText($_.Name +' ('+ $_.ServerAddress +') доступен' + [System.Environment]::NewLine)
}
else
{
$richtextboxinfo.SelectionColor = 'Red'
$richtextboxinfo.AppendText($_.Name + ' (' + $_.ServerAddress + ') недоступен (или закрыт ICMP)' + [System.Environment]::NewLine)
}
$richtextboxinfo.ScrollToCaret()
$I = $I + 1
Write-Progress -Activity "Ping in Progress" -Status "$i clients of $clientscount pinged" -PercentComplete ($i/$clientscount*100)
}
$richtextboxinfo.SelectionColor = 'Black'
Write-Progress -Activity "Ping in Progress" -Status "Ready" -Completed
}
$buttonПеречитатьДанные_Click={
#Перечитываем данные из таблицы Google
$Global:Clients = Get-Clients ($Global:google_file)
$listbox_clients.Items.Clear()
$Global:Clients | ForEach-Object {
[void]$listbox_clients.Items.Add($_.Name)
}
}
$buttonВыход_Click = {
#Выход
$formКлиентыАльбус.Close()
}
$richtextboxinfo_LinkClicked=[System.Windows.Forms.LinkClickedEventHandler]{
#Обработка нажатия на ссылку в окне информации
Start-Process $_.LinkText.ToString()
}
$buttonPing_Click={
#Пингуем ip текущего клиента и выводим результат в поле информации
if ((test-connection -Count 1 -computer $Global:Current.ServerAddress -quiet) -eq $True)
{
$richtextboxinfo.AppendText([System.Environment]::NewLine)
$richtextboxinfo.SelectionColor = 'Green'
$richtextboxinfo.AppendText($Global:Current.Name + ' (' + $Global:Current.ServerAddress + ') доступен' + [System.Environment]::NewLine)
}
else
{
$richtextboxinfo.AppendText([System.Environment]::NewLine)
$richtextboxinfo.SelectionColor = 'Red'
$richtextboxinfo.AppendText($Global:Current.Name + ' (' + $Global:Current.ServerAddress + ') недоступен (или закрыт ICMP)' + [System.Environment]::NewLine)
}
}
#----------------------------------------------
#Описание объектов формы
#----------------------------------------------
#
# formКлиентыАльбус
#
$formКлиентыАльбус.Controls.Add($statusbar1)
$formКлиентыАльбус.Controls.Add($groupboxTools)
$formКлиентыАльбус.Controls.Add($groupboxRDP)
$formКлиентыАльбус.Controls.Add($groupboxVPN)
$formКлиентыАльбус.Controls.Add($richtextboxinfo)
$formКлиентыАльбус.Controls.Add($listbox_clients)
$formКлиентыАльбус.AutoScaleDimensions = '6, 13'
$formКлиентыАльбус.AutoScaleMode = 'Font'
$formКлиентыАльбус.AutoSize = $True
$formКлиентыАльбус.ClientSize = '763, 446'
$formКлиентыАльбус.FormBorderStyle = 'FixedSingle'
$formКлиентыАльбус.MaximizeBox = $False
$formКлиентыАльбус.Name = 'formКлиентыАльбус'
$formКлиентыАльбус.SizeGripStyle = 'Hide'
$formКлиентыАльбус.StartPosition = 'CenterScreen'
$formКлиентыАльбус.Text = 'Клиенты Альбус'
$formКлиентыАльбус.add_FormClosing($formКлиентыАльбус_FormClosing)
$formКлиентыАльбус.add_Load($formКлиентыАльбус_Load)
#
# statusbar1
#
$statusbar1.Location = '0, 424'
$statusbar1.Name = 'statusbar1'
$statusbar1.Size = '763, 22'
$statusbar1.TabIndex = 17
#
# groupboxTools
#
$groupboxTools.Controls.Add($buttonPing)
$groupboxTools.Controls.Add($buttonВыход)
$groupboxTools.Controls.Add($buttonWindox)
$groupboxTools.Controls.Add($buttonПеречитатьДанные)
$groupboxTools.Controls.Add($buttonPingAll)
$groupboxTools.Location = '308, 258'
$groupboxTools.Name = 'groupboxTools'
$groupboxTools.Size = '147, 163'
$groupboxTools.TabIndex = 10
$groupboxTools.TabStop = $False
$groupboxTools.Text = 'Tools'
$groupboxTools.UseCompatibleTextRendering = $True
#
# buttonPing
#
$buttonPing.Location = '7, 44'
$buttonPing.Name = 'buttonPing'
$buttonPing.Size = '133, 23'
$buttonPing.TabIndex = 12
$buttonPing.Text = 'Ping'
$buttonPing.UseCompatibleTextRendering = $True
$buttonPing.UseVisualStyleBackColor = $True
$buttonPing.add_Click($buttonPing_Click)
#
# buttonВыход
#
$buttonВыход.Location = '7, 125'
$buttonВыход.Name = 'buttonВыход'
$buttonВыход.Size = '133, 23'
$buttonВыход.TabIndex = 15
$buttonВыход.Text = 'Выход'
$buttonВыход.UseCompatibleTextRendering = $True
$buttonВыход.UseVisualStyleBackColor = $True
$buttonВыход.add_Click($buttonВыход_Click)
#
# buttonWindox
#
$buttonWindox.Enabled = $False
$buttonWindox.Location = '7, 17'
$buttonWindox.Name = 'buttonWindox'
$buttonWindox.Size = '133, 23'
$buttonWindox.TabIndex = 11
$buttonWindox.Text = 'Windox'
$buttonWindox.UseCompatibleTextRendering = $True
$buttonWindox.UseVisualStyleBackColor = $True
$buttonWindox.add_Click($buttonWindox_Click)
#
# buttonПеречитатьДанные
#
$buttonПеречитатьДанные.Location = '7, 98'
$buttonПеречитатьДанные.Name = 'buttonПеречитатьДанные'
$buttonПеречитатьДанные.Size = '133, 23'
$buttonПеречитатьДанные.TabIndex = 14
$buttonПеречитатьДанные.Text = 'Перечитать данные'
$buttonПеречитатьДанные.UseCompatibleTextRendering = $True
$buttonПеречитатьДанные.UseVisualStyleBackColor = $True
$buttonПеречитатьДанные.add_Click($buttonПеречитатьДанные_Click)
#
# buttonPingAll
#
$buttonPingAll.Location = '7, 71'
$buttonPingAll.Name = 'buttonPingAll'
$buttonPingAll.Size = '133, 23'
$buttonPingAll.TabIndex = 13
$buttonPingAll.Text = 'Ping All'
$buttonPingAll.UseCompatibleTextRendering = $True
$buttonPingAll.UseVisualStyleBackColor = $True
$buttonPingAll.add_Click($buttonPingAll_Click)
#
# groupboxRDP
#
$groupboxRDP.Controls.Add($comboboxRDP)
$groupboxRDP.Controls.Add($textboxRDPLogin)
$groupboxRDP.Controls.Add($textboxRdpPwd)
$groupboxRDP.Controls.Add($buttonПодключитьRDP)
$groupboxRDP.Location = '308, 128'
$groupboxRDP.Name = 'groupboxRDP'
$groupboxRDP.Size = '147, 126'
$groupboxRDP.TabIndex = 5
$groupboxRDP.TabStop = $False
$groupboxRDP.Text = 'RDP'
$groupboxRDP.UseCompatibleTextRendering = $True
#
# comboboxRDP
#
$comboboxRDP.FormattingEnabled = $True
$comboboxRDP.Location = '7, 17'
$comboboxRDP.Name = 'comboboxRDP'
$comboboxRDP.Size = '133, 21'
$comboboxRDP.TabIndex = 6
$comboboxRDP.Text = 'IP RDP сервера'
#
# textboxRDPLogin
#
$textboxRDPLogin.Location = '7, 44'
$textboxRDPLogin.Name = 'textboxRDPLogin'
$textboxRDPLogin.Size = '133, 20'
$textboxRDPLogin.TabIndex = 7
$textboxRDPLogin.Text = 'RDP-login'
#
# textboxRdpPwd
#
$textboxRdpPwd.Location = '7, 69'
$textboxRdpPwd.Name = 'textboxRdpPwd'
$textboxRdpPwd.PasswordChar = '*'
$textboxRdpPwd.Size = '133, 20'
$textboxRdpPwd.TabIndex = 8
$textboxRdpPwd.Text = 'RDP-Password'
#
# buttonПодключитьRDP
#
$buttonПодключитьRDP.Enabled = $False
$buttonПодключитьRDP.Location = '7, 94'
$buttonПодключитьRDP.Name = 'buttonПодключитьRDP'
$buttonПодключитьRDP.Size = '133, 20'
$buttonПодключитьRDP.TabIndex = 9
$buttonПодключитьRDP.Text = 'Подключить RDP'
$buttonПодключитьRDP.UseCompatibleTextRendering = $True
$buttonПодключитьRDP.UseVisualStyleBackColor = $True
$buttonПодключитьRDP.add_Click($buttonПодключитьRDP_Click)
#
# groupboxVPN
#
$groupboxVPN.Controls.Add($buttonПодключитьVPN)
$groupboxVPN.Controls.Add($buttonОтключитьVPN)
$groupboxVPN.Controls.Add($checkboxШлюзПоумолчанию)
$groupboxVPN.Location = '308, 27'
$groupboxVPN.Name = 'groupboxVPN'
$groupboxVPN.Size = '147, 98'
$groupboxVPN.TabIndex = 1
$groupboxVPN.TabStop = $False
$groupboxVPN.Text = 'VPN'
$groupboxVPN.UseCompatibleTextRendering = $True
#
# buttonПодключитьVPN
#
$buttonПодключитьVPN.Enabled = $False
$buttonПодключитьVPN.Location = '7, 45'
$buttonПодключитьVPN.Name = 'buttonПодключитьVPN'
$buttonПодключитьVPN.Size = '133, 20'
$buttonПодключитьVPN.TabIndex = 3
$buttonПодключитьVPN.Text = 'Подключить VPN'
$buttonПодключитьVPN.UseCompatibleTextRendering = $True
$buttonПодключитьVPN.UseVisualStyleBackColor = $True
$buttonПодключитьVPN.add_Click($buttonПодключитьVPN_Click)
#
# buttonОтключитьVPN
#
$buttonОтключитьVPN.Enabled = $False
$buttonОтключитьVPN.Location = '7, 67'
$buttonОтключитьVPN.Name = 'buttonОтключитьVPN'
$buttonОтключитьVPN.Size = '133, 20'
$buttonОтключитьVPN.TabIndex = 4
$buttonОтключитьVPN.Text = 'Отключить VPN'
$buttonОтключитьVPN.UseCompatibleTextRendering = $True
$buttonОтключитьVPN.UseVisualStyleBackColor = $True
$buttonОтключитьVPN.Visible = $False
$buttonОтключитьVPN.add_Click($buttonОтключитьVPN_Click)
#
# checkboxШлюзПоумолчанию
#
$checkboxШлюзПоумолчанию.Location = '7, 19'
$checkboxШлюзПоумолчанию.Name = 'checkboxШлюзПоумолчанию'
$checkboxШлюзПоумолчанию.Size = '133, 24'
$checkboxШлюзПоумолчанию.TabIndex = 2
$checkboxШлюзПоумолчанию.Text = 'Шлюз по-умолчанию'
$checkboxШлюзПоумолчанию.TextAlign = 'MiddleRight'
$checkboxШлюзПоумолчанию.UseCompatibleTextRendering = $True
$checkboxШлюзПоумолчанию.UseVisualStyleBackColor = $True
#
# richtextboxinfo
#
$richtextboxinfo.Cursor = 'Default'
$richtextboxinfo.ForeColor = 'WindowText'
$richtextboxinfo.HideSelection = $False
$richtextboxinfo.Location = '461, 27'
$richtextboxinfo.Name = 'richtextboxinfo'
$richtextboxinfo.ReadOnly = $True
$richtextboxinfo.ScrollBars = 'ForcedVertical'
$richtextboxinfo.ShowSelectionMargin = $True
$richtextboxinfo.Size = '290, 394'
$richtextboxinfo.TabIndex = 16
$richtextboxinfo.Text = ''
$richtextboxinfo.add_LinkClicked($richtextboxinfo_LinkClicked)
#
# listbox_clients
#
$listbox_clients.FormattingEnabled = $True
$listbox_clients.Location = '12, 27'
$listbox_clients.Name = 'listbox_clients'
$listbox_clients.Size = '290, 394'
$listbox_clients.TabIndex = 0
$listbox_clients.add_SelectedIndexChanged($listbox_clients_SelectedIndexChanged)
#Сохраняем состояние формы
$InitialFormWindowState = $formКлиентыАльбус.WindowState
#Восстанавливаем состояние при загрузке
$formКлиентыАльбус.add_Load($Form_StateCorrection_Load)
#Очищаем элементы формы при закрытии
$formКлиентыАльбус.add_FormClosed($Form_Cleanup_FormClosed)
#Сохраняем значения элементов при закрытии
$formКлиентыАльбус.add_Closing($Form_StoreValues_Closing)
#Показать форму
return $formКлиентыАльбус.ShowDialog()
}
#Запуск приложения!
Main ($CommandLine)
Скрипт можно запускать как скрипт ps1 или скомпилировать в exe через ps2exe и использовать как полноценное приложение
UPD: скрипт может работать непосредственно в Powershell, достаточно запустить PS от имени Администратора, скопировать и вставить скрипт в окно PS
![](https://habrastorage.org/getpro/habr/upload_files/307/819/f7b/307819f7b9c9630eac197671705b0c04.png)