Ещё одна захватывающая история про фокусы с удалёнными хостами в домене. Сегодня коснёмся утилиты PsExec из состава вот этого пакета. При всей популярности утилиты, я с ней сталкиваюсь достаточно редко. В основном для удалённого доступа использую изкоробочные решения, но в данном случае никаких более доступных и простых альтернатив не было. Итак, попробуем запустить программу с графическим интерфейсом на компьютере пользователя в его сеансе, а не где-то под капотом в виде очередного безликого процесса.
В уютном мирке тонких клиентов и удалённых сеансов у каждого пользователя при включении компьютера запускается лаунчер, заменяющий Проводник операционной системы. Обычно на форме лаунчера всего пара кнопок, ведущие куда-то в дебри сервера удалённых приложений или терминальных сессий.
Звучит (да и смотрится вполне) красиво, но в этой истории лаунчер располагался на сетевом ресурсе, который в один прекрасный момент ушёл в оффлайн. Вот здесь-то и начинаются приключения: пока идут работы по возвращению сетевого ресурса в строй, нужно вывести на машине хотя бы окно mstsc.
В случае с PsExec потребуется вот такая функция:
function RemoteRunGUI
{
Param([Parameter(Mandatory=$true)]$ComputerName,
[Parameter(Mandatory=$true)]$Command)
$getuser = quser /server:$ComputerName | ForEach-Object { $_.Trim() -replace "\s+", "," -replace ">","" }
$parse = $getuser | ConvertFrom-Csv
$username = $parse.ПОЛЬЗОВАТЕЛЬ
$idsession = $parse.ID
Start-Process -FilePath $PSScriptRoot\psexec.exe -ArgumentList "\\$ComputerName -i $idsession -d -s $Command" -NoNewWindow | Out-Null
}
Минусы метода — надо иметь рядом со скриптом тот самый PsExec, а ещё утилита запускает процессы от имени системы удалённого хоста. С последним надо быть особенно аккуратным и понимать последствия.
Если пользователи проходят авторизацию по смарт-картам — приготовьтесь к тому, что mstsc не пропустит пользователей дальше окна ввода пин-кода, ведь локальная система не будет иметь ни малейшего представления о том, кто и куда пытается попасть.
Часть функции, которая про quser и каскад переменных, начиная с $getuser, позаимствована отсюда и изменений не претерпела.
Можно пойти дальше и скачать PsExec, если скрипт его не нашёл:
$exists = Test-Path -Path $PSScriptRoot\psexec.exe
if (!$exists)
{
$link = "https://download.sysinternals.com/files/PSTools.zip"
$filename = ([uri]$link).Segments[-1]
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Start-BitsTransfer -Source $link -Destination $PSScriptRoot\$filename
Expand-Archive $PSScriptRoot\$filename -DestinationPath $PSScriptRoot\temp -Force
Get-ChildItem $PSScriptRoot\temp -Filter "psexec.exe" -File -Recurse | Move-Item -Destination $PSScriptRoot -Force
Remove-Item -Path $PSScriptRoot\temp -Recurse -Force
Remove-Item -Path $PSScriptRoot\$filename -Force
}
Логика конструкции проста: не нашли файл, скачали архив, распаковали во временную папку, переместили нужный файл к скрипту, остальное удалили.
А теперь все куплеты вместе:
# Заголовок окна консоли #
[System.Console]::Title = "Запуск программы с GUI на удалённом хосте"
# Функция запуска программы с GUI на удалённом хосте #
function RemoteRunGUI
{
Param([Parameter(Mandatory=$true)]$ComputerName,
[Parameter(Mandatory=$true)]$Command)
$getuser = quser /server:$ComputerName | ForEach-Object { $_.Trim() -replace "\s+", "," -replace ">","" }
$parse = $getuser | ConvertFrom-Csv
$username = $parse.ПОЛЬЗОВАТЕЛЬ
$idsession = $parse.ID
Start-Process -FilePath $PSScriptRoot\psexec.exe -ArgumentList "\\$ComputerName -i $idsession -d -s $Command" -NoNewWindow | Out-Null
}
# Скачать PSExec.exe #
$exists = Test-Path -Path $PSScriptRoot\psexec.exe
if (!$exists)
{
$link = "https://download.sysinternals.com/files/PSTools.zip"
$filename = ([uri]$link).Segments[-1]
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Start-BitsTransfer -Source $link -Destination $PSScriptRoot\$filename
Expand-Archive $PSScriptRoot\$filename -DestinationPath $PSScriptRoot\temp -Force
Get-ChildItem $PSScriptRoot\temp -Filter "psexec.exe" -File -Recurse | Move-Item -Destination $PSScriptRoot -Force
Remove-Item -Path $PSScriptRoot\temp -Recurse -Force
Remove-Item -Path $PSScriptRoot\$filename -Force
}
# Запросить у пользователя имя компьютера #
$compname = Read-Host "Имя компьютера"
# Команда для выполнения на удалённом хосте #
$command = "mstsc"
# Вызов функции #
RemoteRunGUI -ComputerName $compname -Command $command
# Ожидание действия #
Read-Host
Добавил переменные $compname и $command для сопровождения параметров функции RemoteRunGUI. И, само собой, никто не мешает запихнуть функцию в перебор и подсовывать имена хостов из любого массива. Например, откуда-нибудь из Active Directory:
Get-ADComputer -Filter * -SearchBase "OU=PC,OU=DEVICES,DC=local,DC=domain,DC=ru" | Select -ExpandProperty Name
Перебор организуем стандартными методами и в итоге получаем:
# Заголовок окна консоли #
[System.Console]::Title = "Запуск программы с GUI на удалённом хосте"
# Импортируем модуль для работы с Active Directory #
Import-Module ActiveDirectory
# Функция запуска программы с GUI на удалённом хосте #
function RemoteRunGUI
{
Param([Parameter(Mandatory=$true)]$ComputerName,
[Parameter(Mandatory=$true)]$Command)
$getuser = quser /server:$ComputerName | ForEach-Object { $_.Trim() -replace "\s+", "," -replace ">","" }
$parse = $getuser | ConvertFrom-Csv
$username = $parse.ПОЛЬЗОВАТЕЛЬ
$idsession = $parse.ID
Start-Process -FilePath $PSScriptRoot\psexec.exe -ArgumentList "\\$ComputerName -i $idsession -d -s $Command" -NoNewWindow | Out-Null
}
# Скачать PSExec.exe #
$exists = Test-Path -Path $PSScriptRoot\psexec.exe
if (!$exists)
{
$link = "https://download.sysinternals.com/files/PSTools.zip"
$filename = ([uri]$link).Segments[-1]
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Start-BitsTransfer -Source $link -Destination $PSScriptRoot\$filename
Expand-Archive $PSScriptRoot\$filename -DestinationPath $PSScriptRoot\temp -Force
Get-ChildItem $PSScriptRoot\temp -Filter "psexec.exe" -File -Recurse | Move-Item -Destination $PSScriptRoot -Force
Remove-Item -Path $PSScriptRoot\temp -Recurse -Force
Remove-Item -Path $PSScriptRoot\$filename -Force
}
# Команда для выполнения на удалённом хосте #
$command = "mstsc"
# Получим список компьютеров из подразделения Active Directory #
$pcs = Get-ADComputer -Filter * -SearchBase "OU=PC,OU=DEVICES,DC=local,DC=domain,DC=ru" | Select -ExpandProperty Name
foreach($pc in $pcs)
{
# Вызов функции для переменной $pc #
RemoteRunGUI -ComputerName $pc -Command $command
}
# Ожидание действия #
Read-Host
В принципе, завершающий Read-Host тут и не нужен, но иногда уж очень хочется видеть результат работы скрипта.
Теперь дело за малым: вернуть доступ к сетевому ресурсу, безопасности ради ребутнуть на фиг хосты, куда дотянулась функция RemoteRunGUI, чтобы все могли работать по спокойному и привычному распорядку.