Contents of this directory is archived and no longer updated.

Примечание: данный пост перепечатан в связи с закрытием бложиков на spaces.live.com, как имеющий какую-то ценность для автора и/или читателей.


Ричард Сиддэвей (Richard Siddaway) в своём блоге ведёт на мой взгляд интересный цикл постов Windows 2000 Scripting Guide (W2KSG) с применением PowerShell и WMI. В них рассказываются достаточно интересные и полезные возможности классов WMI для сбора различных сведений как программной части системы, так и аппаратной. Так же недавно на форуме TechNet была поднята (да, на форумах всегда найдутся археологи, которые выкопают темы полу- и годичной давности, а то и ещё старше :) ) тема про скрипт, который бы собрал данные об аппаратной составляющей компьютеров. Подобные темы периодически всплывают на различных форумах. Я подумал, что неплохо было бы решить данный вопрос с помощью PowerShell.

Итак, отправной точкой для меня послужила ссылка на Computer System Hardware Classes, где я посмотрел какие классы можно применить. Изучив весь список я отобрал лишь самые необходимые для решения задачи классы, а именно:

 

Достаточно сходить по ссылкам и можно посмотреть множество свойств каждого класса, которые детально описывают себя. Но при прочтении очень важно следить за поддерживаемыми свойствами в ОС, которые были выпущены до Windows Vista/2008. Я старался эти моменты учитывать, чтобы получить оптимальную совместимость как с предыдущими ОС, так и с текущими.

Ничего сверхсложного в этом нету, сперва я определил требуемые классы WMI в переменные и определил набор необходимых свойств каждого класса следующим образом:

$OS = gwmi  Win32_OperatingSystem | Select Caption, OSArchitecture, 
OtherTypeDescription, ServicePackMajorVersion, CSName, TotalVisibleMemorySize
$CPU = gwmi  Win32_Processor | Select Architecture, DeviceID, Name
$RAM = gwmi  Win32_MemoryDevice | Select DeviceID, 
StartingAddress, EndingAddress
...

После определения всех классов WMI и переменных я начал писать секцию вывода. Например, вывод имени компьютера и ОС, под которой он управляется:

"Computer Name: `n`t" + $OS.CSName + "`n"
"Operating System: `n`t" + $OS.Caption + " " + $OS.OtherTypeDescription + $OS.OSArchitecture + "`n"

Здесь можно не обращать на знаки регулярных выражений, т.к. они несут только одну функцию, а именно - удобное для воспроиятия форматирование вывода. Т.е. первой строкой пойдёт название поля Computer Name, после чего будет переход на новую строку (`n). Чтобы все строчки не сливались я значения полей отделил табулятором (`t). И в конце так же добавил знак возврата каретки (`n), чтобы отделить между собой поля. Вывод в данном случае будет таким:

Computer Name:
        THOR


Operating System:
        MicrosoftR Windows VistaT Business  32-bit


Service Pack:
        Service Pack 1 installed

Т.е. поля идут с левого края и между собой отделены двумя пустыми строками. А значения полей отделены табулятором относительно того же левого края. Мне кажется, что такой выход весьма читабелен и данные не сливаются в кучу. Когда у поля только одно значение - всё просто. Но когда значений возможно несколько, то задача форматирования выхода таких данных стала для меня небольшой проблемой. Почему небольшой - потому что ответ посмотрел в блоге у Ричарда, а именно в посте W2KSG: Free Disk Space. Суть заключается в очень простом: берётся переменная с массивом данных и по конвейеру при помощи команды Format-Table подготавливается табличный выход и с указанием основного элемента, по которому будет этот выход формироваться. Чтобы лучше понять этот процесс я покажу его на примере вывода сведений о процессоре. Сейчас многопроцессорные системы не редкость и скрипт должен поддерживать показ сведений о более чем одном процессоре:

[user name] $CPU = gwmi  Win32_Processor | Select Architecture, DeviceID, Name
[user name] "Processors:"
Processors:
[user name] $CPU | ft DeviceID, @{Label = "Architecture"; Expression = {switch ($_.Architecture) {
>> "0" {"x86"}; "1" {"MIPS"}; "2" {"Alpha"}; "3" {"PowerPC"}; "6" {"Intel Itanium"}; "9" {"x64"}}}},
>> @{Label = "Model"; Expression = {$_.name}} -AutoSize
>>

DeviceID Architecture Model
-------- ------------ -----
CPU0     x86          Intel(R) Pentium(R) 4 CPU 2.60GHz
CPU1     x86          Intel(R) Pentium(R) 4 CPU 2.60GHz


[user name]

К сожалению, ноутбук у меня однопроцессорный, поэтому эту часть скрипта я запустил на стационаре (в котором, кстати, тоже только 1 физический процессор. Но технология Hyper-Threading эмулирует именно настоящую мультипроцессорность, а не логические ядра, как это сделано в многоядерных процессорах). Итак, давайте разберём строку:

$CPU | ft DeviceID, @{
    Label = "Architecture"; Expression = {
        switch ($_.Architecture) {
            "0" {"x86"}; "1" {"MIPS"}; "2" {"Alpha"}; "3" {"PowerPC"}; "6" {"Intel Itanium"}; "9" {"x64"}
        }
    }
}, @{Label = "Model"; Expression = {$_.name}} -AutoSize

Переменная $CPU содержит сведения о всех установленных процессорах в системе. Я эту переменную передал по конвейеру сразу на команду форматирования - ft (сокращённый алиас от Format-Table) и указал по какому свойству форматировать (DeviceID, который перечисляет ID номера всех процессоров начиная с 0). А дальше я использовал несколько хэш-таблиц для отображения дополнительных свойств каждого объекта. Параметр Label задаёт название новой графе, а Expression указывается значение параметра (как Architecture и Model). Если посмотреть справку по классу Win32_Processor, то можно увидеть, что свойство Architecture содержит лишь числовое значение (от 0 до 9) и в справке приведена расшифровка этих значений. Числовые значения в данном случае, согласитесь, не самый читабельный вариант. Поэтому в Expression я вместил конструкцию Switch, которая автоматически будет числовым значениям сопоставлять понятные текстовые значения. И если свойство Architecture вернёт числовое значение 0, то Switch в нашем случае сопоставит ему более понятное значение x86. И в последней строке я добавил ещё одну хэш-таблицу, которая добавляет ещё одну графу - Model, которая будет содержать свойство Name класса Win32_Processor - название процессора.

Такое форматирование выхода я делал для каждого поля, которое может содержать несколько значений. Например, объём каждого установленного модуля памяти:

$RAM = gwmi  Win32_MemoryDevice | Select DeviceID, StartingAddress, EndingAddress

DeviceID будет содержать номер каждого установленного модуля памяти начиная с нулевого ряда (разбор понятий Ряд и Банк памяти выходит за рамки данного поста) или первого слота. StartingAddress и EndingAddress показывают адресное пространство, за которое отвечает каждый модуль начиная от первого байта. И простым вычитанием начального адреса из конечного мы получим ёмкость каждого модуля:

$RAM | ft DeviceID, @{Label = "Module Size(MB)"; Expression = {
    (($_.endingaddress - $_.startingaddress) / 1KB).ToString("F00")}
} -AutoSize

Так же, как и с процессорами я содержимое переменной $RAM перенаправил по конвейеру на форматирование по столбцу DeviceID. И через запятую добавил ещё одну хэш-таблицу, которая будет показывать объём каждого модуля. Если просто произвести операцию вычитания, то мы получим объём памяти в килобайтах. Чтобы показать объём в мегабайтах я просто разделил полученную разность на килобайты - 1KB (Очень удобная штука :) ). Здесь важно было не ошибиться, т.к. по логике может показаться, что нужно делать на 1MB, чтобы получить размер в мегабайтах. Но, как я уже сказал выше, у нас разность будет уже в килобайтах. Поэтому, чтобы получить в мегабайты, то нам нужно разделить только на 1KB. И посмотрим, что мы будем иметь на выходе:

[vPodans] $RAM = gwmi  Win32_MemoryDevice | Select DeviceID, StartingAddress, EndingAddress
[vPodans] $RAM | ft DeviceID, @{Label = "Module Size(MB)"; Expression = {(($_.endingaddress - 
$_.startingaddress) / 1KB).ToString("F00")}} -AutoSize

DeviceID        Module Size(MB)
--------        ---------------
Memory Device 0 1024
Memory Device 1 1024

[vPodans]

Вот так мы получили номера модулей памяти и объём каждого из них. После деления у нас не получится целое число, поэтому я сконвертировал это число в строку (ToString) и указал количество знаков после запятой - 0 знаков (F00). В данном случае дробное число просто округляется до ближайшего целого числа. Количество знаков после запятой можно изменить, например указав F01, которое округлит число до ближайшего целого числа с точностью до 1 знака после запятой. Это всё не я придумал, а честно взял из поста W2KSG: Free Disk Space :)

Вот, в принципе, я рассказал о всех используемых в скрипте приёмах, которые доступны в PowerShell и при дальнейшей разработке скрипта я только выдёргивал нужные свойства каждого класса и формировал более-менее приличный и понятный вывод. Хотя в итоге я добавил ещё 2 вещи:

  1. Оформил скрипт в функцию Get-HwInfo и с возможностью передачи в неё имён компьютеров
  2. Каждый WMI класс позволяет собирать сведения не только с локальных комьютеров, но и с удалённых. Имя далённого компьютера указывается через параметр -ComputerName при вызове команды Get-WMIObject, например: $CPU = gwmi  Win32_Processor -ComputerName $computers | Select Architecture, DeviceID, Name

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

######################################################## 
# Get-HwInfo.ps1 
# Version 1.0 
# 
# Getting basic information about systems hardware 
# 
# Vadims Podans (c) 2008 
# http://vpodans.spaces.live.com/ 
########################################################

function Get-HwInfo ($computers = ".") {
    $OS = gwmi  Win32_OperatingSystem -ComputerName $computers | Select Caption, OSArchitecture,
        OtherTypeDescription, ServicePackMajorVersion, CSName, TotalVisibleMemorySize
    $CPU = gwmi  Win32_Processor -ComputerName $computers | Select Architecture, DeviceID, Name
    $RAM = gwmi  Win32_MemoryDevice -ComputerName $computers | Select DeviceID,
        StartingAddress, EndingAddress
    $MB = gwmi  Win32_BaseBoard -ComputerName $computers | Select Manufacturer, Product, Version
    $VGA = gwmi  Win32_VideoController -ComputerName $computers | Select Name, AdapterRam
    $HDD = gwmi  Win32_DiskDrive -ComputerName $computers | select Model, Size
    $Volumes = gwmi  Win32_LogicalDisk -Filter "MediaType = 12" -ComputerName $computers | Select DeviceID,
        Size, FreeSpace
    $CD = gwmi Win32_CDROMDrive | Select Id, Name, MediaType
    $NIC = gwmi Win32_NetworkAdapter -ComputerName $computers | ?{$_.NetConnectionID -ne $null}
    Write-Host "Computer Name: `n`t" $OS.CSName `
    `n"Operating System: `n`"
 $OS.Caption  "  $OS.OtherTypeDescription $OS.OSArchitecture `

    `nService Pack: `n`" Service
 Pack OS.ServicePackMajorVersion " in installed `
    `nProcessors:
    $CPU | ft DeviceID, @{
        Label = Architecture; Expression = {
            switch ($_.Architecture) {
                0 {x86}; 1 {MIPS}; 2 {Alpha}; 3 {PowerPC}; 6 {Intel
 Itanium}; 9 {x64}
            }
        }
    }, @{Label = Model; Expression = {$_.name}} -AutoSize
    Write-Host Physical Memory:    $RAM | ft DeviceID, @{Label = "ModuModule Size(MB)xpression = {
        (($_.endingaddress - $_.startingaddress) / 1KB).tostring("F00"F00)}} -AutoSize
    Write-Host Total Memory: `n`"
 ($OS.TotalVisibleMemorySize / 1KB).tostring() " M)  `
  `

    `nMotherBoard: 
 `

    `n`Vendor: 
 $MB.Manufacturer `

    `n`Model:  
 $MB.Product `

    `n`Version: 
 $MB.Version `

    `nVideocontroller:
 `

    `n`Model: 
 $VGA.Name `

    `n`Video RAM: 
 ($VGA.AdapterRam/1MB).tostring(B`n" ) 
    `n `

    `nHarddDisks:

    $HDD | ft Model, @{Label=(GB)"; Expressi; Expression = {($_.Size/1GB).tostring(AutoS)}} -AutoSize
    Write-Host itions:"
    $Vol
    $Volumes | ft DeviceID, @{Label=(GB)"; Expressi; Expression={($_.Size/1GB).ToString(
    )}},
        @{Label=GB)"; Expressi; Expression={($_.FreeSpace/1GB).tostring(AutoS)}} -AutoSize
    $CD | ft Id, @{Label = e"; Expressi; Expression = {$_.MediaType}},
        @{Label = xpressi; Expression = {$_.Name}} -AutoSize
    Write-Host apters:"
    $NIC
    $NIC | ft NetConnectionID, @{
        Label=tus"; Expressi; Expression = {
            switch ($_.NetConnectionStatus) {
                onn {ted"}
       }
                ect {g"}
       }
                ect {"}
       }
                onn {ting"}
       }
                war {not present"}
       }
                war {disabled"}
       }
                war {malfunction"}
       }
                a d {connected"}
       }
                ent {ating"}
       }
                ent {ation succeeded"}
       }
                hent {ation failed"}
       }
                alid {ddress"}
       }
                dent {ls required"}
       }
            }
        }
    }, @{Label=ressi; Expression={$_.name}}
}

Здесь видно, что в конце я снова применил конструкцию Switch, которая расшифровывает числовое значение статуса сетевого адаптера в его текстовое значение.

Согласен, что скрипт выглядит не очень опрятно, но в PowerGUI он выглядит вполне сносно. На основе данного скрипта каждый может его с лёгкостью расширить и изменить под свои нужды, я лишь старался показать образец решения задачи, а так же показал некоторые интересные приёмы в PowerShell. Кстати говоря, данный скрипт можно отправить в HTML формат командой ConvertTo-Html. Это будет удобно, когда потребуется собрать подобные сведения с нескольких компьютеров. Тогда HTML формат будет весьма полезен для последующего анализа. На сегодня всё, а теперь спать.


Share this article:

Comments:

Comments are closed.