Процесс приготовления будет состоять из следующих этапов:
- получение MAC-адреса сетевого адаптера
- конфигурирование сетевого адаптера
- формирование и отправка "волшебного пакета"
1. Получим MAC-адрес сетевого адаптера (адаптеров), для чего используем WMI-класс Win32_NetworkAdapterConfiguration.
На VBScript код получения MAC-адреса мог бы выглядеть так:
For Each objItem In GetObject("winmgmts:\\.\root\cimv2").ExecQuery _ ("Select MACAddress From Win32_NetworkAdapterConfiguration Where IPEnabled = True") s = s & objItem.MACAddress & vbCrLf Next MsgBox s, vbInformation
На PowerShell все гораздо проще:
(Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter IPEnabled=True | Select-Object -Property MACAddress).MACAddress
Наверное удобнее сразу сохранять полученные MAC-адреса в файл CSV.
Кроме того сделаем наш код более "гибким" - напишем так называемую "продвинутую" функцию, которая будет получать в качестве параметра массив имен компьютеров и возвращать массив MAC-адресов.
function Get-MACAddress { [CmdletBinding()] param ([string[]]$HostName = 'localhost') Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter IPEnabled=True -ComputerName $HostName | ` Select-Object -Property MACAddress } Get-MACAddress -hostName localhost, localhost | ` Export-Csv -Path "mac.csv" -Encoding UTF8 -NoTypeInformation -Append
Теперь представьте себе сколько строчек кода это заняло бы на VBScript.
После отработки нашего "продвинутого" кода получаем примерно такой CSV-файл.
2. Конфигурируем сетевой адаптер (адаптеры) - разрешим ему (им) выводить компьютер из спящего режима, для чего используем утилиту powercfg.exe.
Можно было попробовать обойтись и без утилиты powercfg.exe - использовать WMI-классы MSPower_DeviceWakeEnable и MSNdis_DeviceWakeOnMagicPacketOnly, проживающие в пространстве имен root\wmi, но они не документированы и, как следствие, не поддерживаются Microsoft, поэтому у меня нет уверенности в том, что код, использующий эти классы отработает правильно на любой оси. По крайней мере на 64-битной 7 и 8 - не получилось, хотя в репозитории классы есть. В общем поверьте на слово - лучше использовать powercfg.exe.
Получить справку по использованию утилиты можно стандартным способом - powercfg /?
Команда powercfg /devicequery wake_programmable отображает список устройств, которые могут выводить компьютер из спящего режима.
Выполним команду в PowerShell ISE.
Выведем список имен сетевых адаптеров подключенных к сети, для чего используем WMI-класс Win32_NetworkAdapter.
Значение свойства NetConnectionStatus=2 доступно начиная с Windows XP SP3.
Комбинируем - попробуем получить список сетевых адаптеров подключенных к сети, у которых есть возможность выводить компьютер из спящего режима.
Сразу обернем код в функцию - пригодится.
# функция возвращает массив имен сетевых адаптеров подключенных к сети, # у которых есть возможность выводить компьютер из спящего режима function Get-WakeAdapterName { [CmdletBinding()] param ([string[]]$HostName = 'localhost') $arrWake = Invoke-Command -ComputerName $HostName -ScriptBlock {powercfg /devicequery wake_programmable} | ` Where-Object {$_ -ne ''} $arrAdapters = (Get-WmiObject -Class Win32_NetworkAdapter -Filter NetConnectionStatus=2 -ComputerName $HostName | ` Select-Object -Property Name).Name for ($i=0; $i -lt $arrWake.length; $i++) { for ($j=0; $j -lt $arrAdapters.length; $j++) { if ($arrWake[$i] -eq $arrAdapters[$j]) { Write-Output $arrWake[$i] } } } } Get-WakeAdapterName localhost, localhost
Выполним функцию.
Если у Вас проблема с настройкой PowerShell Remoting - можно выполнить код локально.
# функция возвращает массив имен сетевых адаптеров подключенных к сети, # у которых есть возможность выводить компьютер из спящего режима function Get-WakeAdapterName { [CmdletBinding()] param ([string[]]$HostName = 'localhost') $arrWake = powercfg /devicequery wake_programmable | ` Where-Object {$_ -ne ''} $arrAdapters = (Get-WmiObject -Class Win32_NetworkAdapter -Filter NetConnectionStatus=2 -ComputerName $HostName | ` Select-Object -Property Name).Name for ($i=0; $i -lt $arrWake.length; $i++) { for ($j=0; $j -lt $arrAdapters.length; $j++) { if ($arrWake[$i] -eq $arrAdapters[$j]) { Write-Output $arrWake[$i] } } } } Get-WakeAdapterName localhost, localhost
Лирическое отступление на VBScript. Почему не получить список адаптеров так:
For Each objItem In GetObject("winmgmts:\\.\root\cimv2").ExecQuery _ ("Select MACAddress From Win32_NetworkAdapterConfiguration Where IPEnabled = True") For Each obj In objItem.Associators_(, "Win32_NetworkAdapter") s = s & obj.Name Next Next MsgBox s, vbInformation
или так:
Set objWMI = GetObject("winmgmts:\\.\root\cimv2") For Each objItem In objWMI.ExecQuery _ ("Select MACAddress From Win32_NetworkAdapterConfiguration Where IPEnabled = True") For Each obj In objWMI.ExecQuery _ ("Select * From Win32_NetworkAdapter Where MACAddress = '" & objItem.MACAddress & "' And PhysicalAdapter = True") s = s & obj.Name Next Next MsgBox s, vbInformation
Очевидно: в первом случае отображается виртуальный адаптер Hyper-V, во втором он тоже в списке.
Поэтому использование powercfg.exe + NetConnectionStatus=2 на мой взгляд наиболее подходящий вариант.
Возражения принимаются.
Теперь попробуем отправить полученные функцией Get-WakeAdapterName имена адаптеров по конвейеру утилите powercfg.exe ...
# функция возвращает массив имен сетевых адаптеров подключенных к сети, # у которых есть возможность выводить компьютер из спящего режима function Get-WakeAdapterName { [CmdletBinding()] param ([string[]]$HostName = 'localhost') $arrWake = powercfg /devicequery wake_programmable | ` Where-Object {$_ -ne ''} $arrAdapters = (Get-WmiObject -Class Win32_NetworkAdapter -Filter NetConnectionStatus=2 -ComputerName $HostName | ` Select-Object -Property Name).Name for ($i=0; $i -lt $arrWake.length; $i++) { for ($j=0; $j -lt $arrAdapters.length; $j++) { if ($arrWake[$i] -eq $arrAdapters[$j]) { Write-Output $arrWake[$i] } } } } Get-WakeAdapterName | ForEach-Object {powercfg deviceenablewake "$_"}
... и получаем законное возражение - я не под админом :).
Открываем PowerShell ISE от имени администратора, выполняем скрипт еще раз.
Проверяем свойства адаптера.
Столько движений из-за галочки, которую можно было поставить и вручную :).
На самом деле движения не лишние - на случай конфигурирования нескольких удаленных машин код очень даже уместен.
3. Сформируем и отправим "волшебный пакет".
MAC-адрес моего сетевого адаптера выглядит так: E4:D5:3D:97:D1:8C.
Стало быть в моем случае пакет должен выглядеть следующим образом:
FF FF FF FF FF FF E4 D5 3D 97 D1 8C E4 D5 3D 97
D1 8C E4 D5 3D 97 D1 8C E4 D5 3D 97 D1 8C E4 D5
3D 97 D1 8C E4 D5 3D 97 D1 8C E4 D5 3D 97 D1 8C
E4 D5 3D 97 D1 8C E4 D5 3D 97 D1 8C E4 D5 3D 97
D1 8C E4 D5 3D 97 D1 8C E4 D5 3D 97 D1 8C E4 D5
3D 97 D1 8C E4 D5 3D 97 D1 8C E4 D5 3D 97 D1 8C
E4 D5 3D 97 D1 8C
Пишем функцию отправки "волшебного пакета".
# функция формирования и отправки "волшебного пакета" # получает MAC-адрес (обязательный параметр) и порты удаленного компьютера # (по умолчанию - 0, 7, 9) function Send-Packet { [CmdletBinding()] param ( [Parameter(Mandatory=$True)] [string[]]$Mac, [int32[]]$Ports = @(0,7,9) ) $bmac = $Mac.split(':') | ForEach-Object { [byte]('0x' + $_) } $packet = [byte[]](,0xFF * 6) + $bmac * 16 $broadcast = [System.Net.IPAddress]::Broadcast $UdpClient = New-Object System.Net.Sockets.UdpClient foreach ($port in $Ports) { $UdpClient.Connect($broadcast, $port) Write-Host $("Отправляем пакет на MAC-адрес $Mac порт $port") $UdpClient.Send($packet, $packet.Length) | Out-Null } $UdpClient.Close() } Send-Packet -Mac 'E4:D5:3D:97:D1:8C'
Запустим какой-нибудь сниффер. Я использовал SmartSniff от NirSoft.
Начнем захват пакетов, после чего выполним нашу функцию.
Остановим захват пакетов сниффером и проверим содержание каждого свежего пакета.
То, что доктор прописал. Остается прочитать содержимое файла mac.csv и отправить по конвейеру MAC-адреса в функцию отправки "волшебного пакета".
# функция формирования и отправки "волшебного пакета" # получает MAC-адрес (обязательный параметр) и порты удаленного компьютера # (по умолчанию - 0, 7, 9) function Send-Packet { [CmdletBinding()] param ( [Parameter(Mandatory=$True)] [string[]]$Mac, [int32[]]$Ports = @(0,7,9) ) $bmac = $Mac.split(':') | ForEach-Object { [byte]('0x' + $_) } $packet = [byte[]](,0xFF * 6) + $bmac * 16 $broadcast = [System.Net.IPAddress]::Broadcast $UdpClient = New-Object System.Net.Sockets.UdpClient foreach ($port in $Ports) { $UdpClient.Connect($broadcast, $port) Write-Host $("Отправляем пакет на MAC-адрес $Mac порт $port") $UdpClient.Send($packet, $packet.Length) | Out-Null } $UdpClient.Close() } $csvPath = "$env:USERPROFILE/mac.csv" if (Test-Path $csvPath) { Import-Csv -Path $csvPath | ForEach-Object {Send-Packet -Mac $_.MACAddress} }
Решение готово. В реальных условиях не пробовал пока, поэтому если что не так - сообщите.
Комментариев нет:
Отправить комментарий
Комментарий будет опубликован после модерации