image

Несмотря на заложенные в PowerShell механизмы, благодаря которым вполне можно обходиться без обращений к WMI, большинство предпочитает идти по пути наименьшего сопротивления. Но вот незадача, простому пользователю попасть в чертоги WMI невозможно, а в некоторых случаях WMI поддразнивает самого системного администратора демонстрацией фиги при обращении к одному из своих классов, — прецеденты были и неоднократно, так что всецело уповать на WMI не стоит, да и знание альтернативных подходов к решению некоторых задач не будут лишними.

Справедливости ради стоит заметить, что определенные задачи могут быть вовсе решены на командном языке, однако со все более возрастающей популярностью PowerShell у некоторых складывается ложное впечатление, дескать, командная строка де-факто мертва. На самом деле командная строка хоть и отходит на второй план, списывать ее со счетов смысла не имеет, но не в виду привычки, а возможности в некоторых случаях упростить решение задачи. Например, как партийные MVP получают путь до интернет-обозревателя используемого по умолчанию:

PS C:\> ([Regex]'(?<=\").*(?=\")').Match(
>> (gp Registry::HKCR\http\shell\open\command).'(default)'
>> ).Value
C:\Program Files\Internet Explorer\iexplore.exe
PS C:\>

С учетом встроенных в командную строку assoc и ftype количество кода можно было бы сократить:

PS C:\> (cmd /c ftype (cmd /c assoc .htm).Split('=')[1]).Split('"')[1]
C:\Program Files\Internet Explorer\iexplore.exe
PS C:\>

Рассмотрим другой пример. Допустим, требуется получить uptime системы — задача, которая на первый взгляд неизбежно ведет к использованию WMI.

$w = gwmi Win32_OperatingSystem
'up {0} day{1}, {2:D2}:{3:D2}:{4:D2}' -f ($u = (Get-Date) - $w.ConvertToDateTime(
  $w.LastBootUptime
)).Days, $(if ($u.Days -gt 1) {'s'}), $u.Hours, $u.Minutes, $u.Seconds

Ежели кто помнит такую штуку в командной строке, как typeperf, тот очевидно знает о счетчике «Время работы системы», приведение показаний которого к удобочитаемому виду будет выглядеть примерно следующим образом.

@echo off
  setlocal & chcp 1251>nul
    for /f "tokens=2 delims=," %%i in (
      'typeperf "\Система\Время работы системы" -sc 1 ^| findstr /rc:"\:"'
    ) do set "sec=%%i"
    set "sec=%sec:"=%"
    for /f "tokens=1 delims=." %%i in ("%sec%") do set "sec=%%i"
    set /a "ss=sec%%60", "sec/=60", "mm=sec%%60", "sec/=60", "hh=sec%%24", "dd=sec/24"
    if %hh% lss 10 set "hh=0%hh%"
    if %mm% lss 10 set "mm=0%mm%"
    if %ss% lss 10 set "ss=0%ss%"
    echo.up %dd% days, %hh%:%mm%:%ss%
  endlocal & chcp 866>nul
exit /b

В переводе на PowerShell это будет выглядеть как:

$pc = New-Object Diagnostics.PerformanceCounter('System', 'System Up Time', $true)
[void]$pc.NextValue()
'up {0} day{1}, {2:D2}:{3:D2}:{4:D2}' -f (
  $u = [TimeSpan]::FromSeconds($pc.NextValue())
).Days, $(if ($u.Days -gt 1) {'s'}), $u.Hours, $u.Minutes, $u.Seconds

Таким образом мы поймали сразу двух зайцев: обошли WMI и убедились в том, что пренебрегать командной строкой также не стоит, а если не убедились, то во всяком случае взяли ее на карандаш. К слову, есть и другие способы получения uptime (один из которых был приведен в первой части).

linux uptime
C# REPL в Linux: чем не PowerShell?

Вообще, сама по себе тема поиска альтернативных решений довольно обширна и вряд ли представляется возможным объять ее в каком-то небольшом обзоре, поэтому логичнее все свести к некой алгоритмической базе, способной стать отправной точкой в изысканиях. Но, как бы парадоксально это ни звучало, никаких алгоритмов в данном случае нет и быть не может, все в сущности своидится к чтению документации, узучению сборок платформы .NET с помощью дизассемблера вроде ILDASM и экспериментам с кодом. Если пояснять это на каких-то определенных примерах… хотелось бы сократить написание имен типов, тем самым снизив количество набираемых символов? Есть, конечно, ISE и Vim с автодополнением, но все же? В предыдущей части упомяналиcь ускорители типов — самая пора задействовать их.

Set-Content function:_from {
  param(
    [Parameter(Mandatory=$true, Position=0)]
    [String]$NameSpace,
    
    [Parameter(Mandatory=$true, Position=1)]
    [ScriptBlock]$Import
  )
  
  $ret = $null
  $arr = [accelerators]::Get.Keys
  
  [Management.Automation.PSParser]::Tokenize($Import, [ref]$ret) | % {
    if ($_.Type -match 'Command' -and $arr -notcontains $_.Content) {
      [accelerators]::Add($_.Content, ("$($NameSpace).$($_.Content)"))
    }
  }
}


Поместив эту функцию также в профиль, получим что-то вроде Python импорта типов:

PS C:\> _from Runtime.InteropServices -import {
>> Marshal; HandleRef; GCHandle;
>> }
>>
PS C:\> [GCHandle]

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    GCHandle                                 System.ValueType

PS C:\>

Согласитесь, гораздо проще и приятней, нежели набивать:

PS C:\> [Runtime.InteropServices.GCHandle]
...
PS C:\>

Комментарии ()