Инвентаризация компьютеров в домене. Лень-двигатель прогресса

Всех приветствую.
Недавно начальство попросило меня подумать над вопросом о сборе информации о комплектации компьютеров у нас в домене. Сначала просьба была только на счет процессоров памяти и жестких дисков. Первая мысль — хождение по отделам и просьба освободить компьютер на минутку. В случае с 1 компьютером не сложно, но если их 1500. Мысли были направленны в сторону PowerShell.

Для начала надо было извлечь список всех компьютеров в домене. В данном примере мой домен Test.lan. Импортируем весь этот список в файл AllComputers.csv.
Для этого не забываем добавить модуль АД для PS. У меня на рабочем месте он прописан в профиле, и вам советую сделать тоже самое:

import-module activedirectory
get-ADcomputer -Filter * |
Where-Object {$a=$_.name; $_.DistinguishedName -ne "CN=$a,OU=Disable,DC=Test,DC=lan"} |
Sort-Object name | Select-Object name | Export-csv C:\Invent\AllComputers.csv -NoTypeInformation


Здесь надо пояснить, что у меня в домене есть папка Disable, где располагаются учетные записи всех отключенных компьютеров. Если они отключены, то какой смысл к ним стучаться. По этому эту папку мы исключаем из поиска.

Всем понятно, что далеко не все компьютеры, которые находятся в домене, включены, работают или вообще имеют место быть. По этому перед тем, как перейти к проверке, мы проверяем связь с ним. Конечно можно этого не делать, если у вас 100 компьютеров. А если у вас 2000 компьютеров, потеря времени при количестве выключенных компьютеров порядка 800 съест у вас не мало времени. Так же стоит сразу вспомнить компьютеры, к которым у нас нет доступа. Так же смысла стучаться в их дверь нет.

import-csv c:\Invent\AllComputers.csv | foreach {
$a=$_.name
if ((Test-connection $a -count 2 -quiet) -eq "True")
{
if ((Get-WmiObject -computername $a Win32_OperatingSystem) -eq $null)
{


Многие могут возразить:
«Для чего такие сложности? Зачем сначала делать список а потом импортировать его. Не легче ли сразу?»
Согласен, легче. Но иметь перед глазами список компьютеров, согласитесь, приятно. К тому же, список пронумерован. И Вы всегда знаете сколько у вас компьютеров в АД.

Для теста соединения выбрал cmdlet Test-connection с параметром -quiet, дабы нам не выдавались строчки с разной информацией, а просто выдавался ответ: True or False. При чем мы сокращаем количество запросов с 2 до 4.
Если мы постучимся к компьютеру с WMI запросом, а прав на такое действие у нас нет, получим кучу строчек красного цвета с ошибками. Так что сразу отфильтровываем такие компьютеры пробным WMI запросом

Любую информацию (наверное, почти любую) можно узнать, если залезть в WMI объекты, а PS позволяет это делать просто на ура. Так что я сразу углубился в поиски нужных WMI объектов.
Здесь можно поглядеть все классы с их атрибутами.
Убедившись, что компьютер в сети, что доступ у нас к нему есть, смотрим его внутренности:

Write-Host "Проверка компьютера " -ForeGroundColor Green $a
"Компьютер" | out-file c:\Invent\Comp\$a.txt
Get-WmiObject -computername $a Win32_OperatingSystem |
select-object csname, caption, Serialnumber, csdVersion |
ft @{Label="Сетевое имя"; Expression={$_.CSname}},
@{label="Наименование"; Expression={$_.caption}},
@{label="Версия"; Expression={$_.csdVersion}},
@{label="Серийный номер"; Expression={$_.SerialNumber}} -auto -wrap |
out-file c:\Invent\Comp\$a.txt -append
Get-WmiObject -computername $a Win32_ComputerSystemProduct | select-object UUID |
ft UUID -autosize | out-file c:\Invent\Comp\$a.txt -append
"Процессор" | out-file c:\Invent\Comp\$a.txt -append
Get-WmiObject -computername $a Win32_Processor | select-object name, SocketDesignation, Description |
ft @{label="Имя"; Expression={$_.name}},
@{label="Разъем"; Expression={$_.SocketDesignation}},
@{label="Описание"; Expression={$_.Description}} -auto -wrap | out-file c:\Invent\Comp\$a.txt -append
"Материнская плата" | out-file c:\Invent\Comp\$a.txt -append
Get-WmiObject -computername $a Win32_BaseBoard | select-object Manufacturer, Product, SerialNumber |
ft @{label="Производитель"; Expression={$_.manufacturer}},
@{label="Модель"; Expression={$_.Product}},
@{label="Серийный номер"; Expression={$_.SerialNumber}} -auto -wrap |
out-file c:\Invent\Comp\$a.txt -append
"Жесткие диски" | out-file c:\Invent\Comp\$a.txt -append
Get-WmiObject -computername $a Win32_DiskDrive | select-object Model, Partitions, Size, interfacetype |
ft @{Label="Модель"; Expression={$_.Model}},
@{Label="Количество разделов"; Expression={$_.Partitions}},
@{Label="Размер (гб)"; Expression={($_.Size/1GB).tostring("F00")}},
@{Label="Интерфейс"; Expression={$_.interfaceType}} -auto -wrap |
out-file c:\Invent\Comp\$a.txt -append
"Логические диски" | out-file c:\Invent\Comp\$a.txt -append
Get-WmiObject -computername $a Win32_LogicalDisk -Filter "DriveType=3" | select-object DeviceID, FileSystem, Size, FreeSpace |
ft @{Label="Наименование"; Expression={$_.DeviceID}},
@{Label="Файловая система"; Expression={$_.FileSystem}},
@{Label="Размер (гб)"; Expression={($_.Size/1GB).tostring("F00")}},
@{Label="Свободное место (гб)"; Expression={($_.FreeSpace/1GB).tostring("F00")}} -auto -wrap |
out-file c:\Invent\Comp\$a.txt -append
"Оперативная память" | out-file c:\Invent\Comp\$a.txt -append
Get-WmiObject -computername $a Win32_Physicalmemory | Select-Object capacity, DeviceLocator |
ft @{Label="Размер (мб)"; Expression={($_.capacity/1MB).tostring("F00")}},
@{Label="Расположение"; Expression={$_.DeviceLocator}} -auto -wrap |
out-file c:\Invent\Comp\$a.txt -append
"Видеокарта" | out-file c:\Invent\Comp\$a.txt -append
Get-WmiObject -computername $a Win32_videoController |
Select-Object name, AdapterRAM, VideoProcessor |
ft @{Label="Наименование"; Expression={$_.name}},
@{Label="Объем памяти (мб)"; Expression={($_.AdapterRAM/1MB).tostring("F00")}},
@{Label="Видеопроцессор"; Expression={$_.VideoProcessor}} -auto -wrap |
out-file c:\Invent\Comp\$a.txt -append
"Сетевая карта" | out-file c:\Invent\Comp\$a.txt -append
$OS=Get-WmiObject -computername $a Win32_OperatingSystem | foreach {$_.caption}
if ($OS -eq "Microsoft Windows 2000 Professional")
{
Get-WmiObject -computername $a Win32_NetworkAdapterConfiguration -Filter "DHCPEnabled=True" |
Select-Object caption,MACaddress |
ft @{Label="Наименование"; Expression={$_.caption}},
@{Label="MAC адрес"; Expression={$_.MACAddress}} -auto -wrap |
out-file c:\Invent\Comp\$a.txt -append
}
else
{
Get-WmiObject -computername $a Win32_NetworkAdapter -Filter "NetConnectionStatus>0" |
Select-Object name, AdapterType, MACAddress |
ft @{Label="Наименование"; Expression={$_.name}},
@{Label="MAC адрес"; Expression={$_.MACAddress}},
@{Label="Тип"; Expression={$_.AdapterType}} -auto -wrap |
out-file c:\Invent\Comp\$a.txt -append
}
}


Вся информация о компьютере падает в текстовый файл.
Здесь стоит остановить внимание на 2 моментах.
Во-первых, строчку Write-Host "Проверка компьютера " -ForeGroundColor Green $a я делал исключительно для себя, ибо приятно знать чем занимается в данный момент PowerShell.
Во-вторых, для чего мы перед проверкой сетевой карты проверяем ОС.
К сожалению, Windows 2000 не отвечает на запрос Win32_NetworkAdapter, по этому к ней мы применяем запрос Win32_NetworkAdapterConfiguration. Почему не оставить только последний? Можно оставить, но Win32_NetworkAdapter выдает такой атрибут, как Name, когда в его аналоге только Caption. Мелочь, а приятно.
Так же можете видеть, что при проверки сетевой карты, мы проверяем ее на работоспособность, иначе мы получим список еще из нескольких карт, которые в данный момент Вас не интересуют. Вам это надо?

Вот в принципе и все.
Скрипт можно доработать до своих потребностей.
Так что теперь начальство вряд ли напугает вас инвентаризацией компьютеров.
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 19

    0
    Прикольная такая лень, мне нравится. Из предложений: проверять доступность компьютера не по расположению его в соответствующем ou, а по последнему общению с ним (у QAD такая штука есть, гуглится по obsolete computer account) — так несколько надежнее, ибо перемещение — ручная операция :)
    А по выводу — интересно все это выгружать сразу в csv, например, для дальнейшего анализа в виде таблицы.
      0
      Еще, кстати, можно развернуть powershell на всех машинах групповой политикой и крутить скриптами локально, выкладывая результаты на сетевой диск — тогда точно всем достанется :) единственная проблема — с линухами не катит никак, как, впрочем, и оригинальный скрипт…
        0
        Локально то возможно, но с подписыванием скриптов у меня пока дела обстоят худо. Да и парк наших машин оставляет желать лучшего :)
          0
          З.Ы. В csv можно все собрать, но понимаешь, либо весь вывод отправлять разом, ибо в export-csv нет волшебной команды -append. Хотя возможно я чего то не знаю :)
            0
            Подписывание скриптов лечится политикой.
        0
        Microsoft Assessment and Planning Toolkit
          +2
          System center, prtg,… :)
            +1
            System center денег стоит. MAPT бесплатен. Про prtg не слыхал. Но вообще такого софта вагон.
              +3
              ocs inventory, бесплатно, быстро, наглядно.
              +1
              вот PRTG классная вещь.
            0
            Только я сначала прочитал заголовок как «Инвентаризация компьютеров в доме»?
              +2
              Только Вы.
                0
                Читаю хабр и понимаю, что у Кэпа был бы тут самый большой рейтинг.
              0
              Хоть приложение и старенькое — юзал на предыдущей работе CheckCFG — легко интегрируется в логон скрипты и умеет делать очень много полезных штук. Есть вроде даже скрипты для Линукса…
                +4
                >>Здесь надо пояснить, что у меня в домене есть папка Disable, где располагаются учетные записи всех отключенных компьютеров. Если они отключены, то какой смысл к ним стучаться. По этому эту папку мы исключаем из поиска.

                Тю, есть решение гораздо проще (хоть и строчек кода больше):

                =Cut=
                function Get-ADComputers ()
                {
                # $strFilter = "(objectCategory=Computer)"
                #get AD computers without disabled accounts
                $strFilter = "(&(objectCategory=computer)(!userAccountControl:1.2.840.113556.1.4.803:=2))"

                $objDomain = New-Object System.DirectoryServices.DirectoryEntry

                $objSearcher = New-Object System.DirectoryServices.DirectorySearcher
                $objSearcher.SearchRoot = $objDomain
                $objSearcher.PageSize = 1000
                $objSearcher.Filter = $strFilter

                $colProplist = «name»

                foreach ($i in $colPropList)
                {
                $null = $objSearcher.PropertiesToLoad.Add($i)
                }

                $colResults = $objSearcher.FindAll()
                foreach ($objResult in $colResults)
                {
                $objItem = $objResult.Properties;
                [string]$str = $objItem.name
                $str
                }
                }
                =ENDCut=

                Не требует модуля ActiveDirectory, кстати, а значит и работает в PSv1 (для меня это было критично одно время)

                >>Get-WmiObject -computername $a Win32_Processor
                Если процессоров больше одного, информация будет дублироваться, для записи в текстовый файл — не критично, при запихивании результатов в таблицу — будут проблемы.
                То же относится к памяти, дискам, томам и сетевым картам.

                >>Get-WmiObject -computername $a Win32_Physicalmemory
                Пропущен параметр MemoryType, для преобразования в читаемый вариант можно прогнать через тупой свитч:
                switch( $wmiW32PM[0].MemoryType )
                {
                0 {$strRAMType = «Unknown»}
                1 {$strRAMType = «Other»}
                2 {$strRAMType = «DRAM»}
                9 {$strRAMType = «RAM»}
                17 {$strRAMType = «SDRAM»}
                19 {$strRAMType = «RDRAM»}
                20 {$strRAMType = «DDR»}
                21 {$strRAMType = «DDR-2»}
                default {$strRAMType = $_}
                } #end switch
                (для DDR-3 встречалось как 22 так и 23, адаптировать по месту, некоторые маловерятные я выкинул)

                Так же не наблюдаю инвентаризации ОС, через класс Win32_OperatingSystem, в котором есть следующие полезные свойства:
                Caption, Version, Description, InstallDate, lastbootuptime, SerialNumber, CSDVersion

                Это может выглядеть вот так:
                =CUT=

                Get-WmiObject Win32_OperatingSystem -computername $a | `
                Select-Object Caption, Version, Description, InstallDate, lastbootuptime, SerialNumber, CSDVersion | `
                ft @{Label=«Наименование»; Expression={$_.Caption}}, `
                @{Label=«Версия ОС»; Expression={$_.Version}}, `
                @{Label=«Описание ПК»; Expression={$_.Description}}, `
                @{Label=«Дата установки»; Expression={[System.Management.ManagementDateTimeconverter]::ToDateTime($_.InstallDate)}},
                @{Label=«Время последнего запуска»; Expression={[System.Management.ManagementDateTimeconverter]::ToDateTime($_.lastbootuptime)}},
                @{Label=«Серийный номер ОС»; Expression={($_.SerialNumber)}},
                @{Label=«Установленный пакет обновлений»; Expression={($_.CSDVersion)}}

                =ENDCUT=

                Говоря об инвентаризации ОС полезно воспользоваться следующей функцией и получить ключ продукта установленной ОС:

                =CUT=

                function Get-WindowsProductKey {
                param ($strComputer)

                $hklm = 2147483650
                $key = «SOFTWARE\Microsoft\Windows NT\CurrentVersion\»
                $value = «DigitalProductId»
                $wmi2 = "\\"+$strComputer+"\root\default:stdRegProv"
                $wmi = [wmiclass]$wmi2
                $rpk = ($wmi.GetBinaryValue($hklm,$key,$value)).uValue
                if ($rpk)#if rpk present — do math
                {
                # $rpk = (Get-ItemProperty «HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\» -Name DigitalProductId).DigitalProductId
                $i = 28
                $rpkOffset = 52
                $PossibleChars = «BCDFGHJKMPQRTVWXY2346789»
                do {
                $Accumulator = 0
                $j = 14
                do {
                $Accumulator = $Accumulator * 256
                $Accumulator = $rpk[$j + $rpkOffset] + $Accumulator
                $Accumulator / 24 -match "^\d*" | Out-Null
                $rpk[$j + $rpkOffset] = $matches[0] -band 255
                $Accumulator = $Accumulator % 24
                $j--
                } while ($j -ge 0)
                $i--
                $ProductKey = $PossibleChars.Substring($Accumulator, 1) + $ProductKey
                if ((29 — $i) % 6 -eq 0 -and $i -ne -1) {
                $i--
                $ProductKey = "-" + $ProductKey
                }
                } while ($i -ge 0)
                $ProductKey

                } # end of if rpk
                else
                {break}
                }

                =ENDCut=
                (да, код уродский, но лень приводить в порядок)

                Таким же образом можно получить версию Internet Explorer установленного в системе:

                =Cut=
                function Get-InternetExplorerVersionWMI {
                param ($strComputer)

                $hklm = 2147483650
                $key = «SOFTWARE\Microsoft\Internet Explorer\»
                $value = «Version»
                $wmi2 = "\\"+$strComputer+"\root\default:stdRegProv"
                $wmi = [wmiclass]$wmi2
                $rpk = ($wmi.GetStringValue($hklm,$key,$value)).sValue
                $rpk
                }

                =ENDCut=

                Так же непонятна конструкция
                >> $OS=Get-WmiObject -computername $a Win32_OperatingSystem | foreach {$_.caption}

                Вполне можно обойтись более элегантым (и чуть более быстрым)
                $OS = (Get-WmiObject -computername $a Win32_OperatingSystem).caption

                или совсем быстрым
                $OS = (get-wmiobject -query «SELECT Caption FROM Win32_OperatingSystem» -computername $a).Caption

                Плюс всё это хозяйство можно писать не в отдельные текстовые файлы, а вполне в табличку, которую потом через export-csv выгрузить.
                А при желании — через System.Net.WebClient и выгрузить на FTP.
                  0
                  Объяснюсь про Disable. Домен достался мне по наследству, по этому когда я только начинал заниматься дизайном домена, эта папка появился по необходимости собирать там отключенные учетные записи компьютеров. Все таки удобнее держать их всех в одной куче, нежели разбросанными по домену. Да я согласен с Вашим предложением по отсеву отключенных учетных записей. Но, так как лично у меня они лежат все в 1 месте, мне легче было сделать таким образом :)

                  Что касается разных свойств, будь то процессор, память и тому подобное — я опять же с Вами согласен, но мне в моем задании этой информации было достаточно :)

                  На счет непонятной конструкции — пути PowerShella неисповедимы :)

                  Что касается csv файла, не скрою, что сначала хотел выгружать все туда. Но к сожалению до конца с этим не разобрался, и остановился на текстовых файлах. Буду развиваться дальше :)
                    0
                    >>Объяснюсь про Disable
                    Да не надо было, и так понятно. Тем более в моих подотчётных доменах тоже есть такой OU ;)

                    >>Что касается разных свойств
                    Ну я и уточнил, что текстовика этого достаточно.
                    По поводу выгрузки в табличку — есть тупой вариант чтобы не связываться с CSV:

                    ==CUT==
                    $computers = «COMP1»,«SERVER2»,«NOTEBOOK3»
                    $table = @{}

                    $computers | % `
                    {
                    $cpu = «Intel Itanium » + (Get-Random 3000) + «Mhz»
                    $ram = [string](Get-Random 2048) + «MB RDRAM»
                    $table[$_] = @{«cpu»=$cpu;«ram»=$ram}
                    }

                    $random = Get-Random 2048
                    $builder = New-Object System.Text.StringBuilder
                    [void]$builder.AppendLine( «Name;RAM;CPU» )
                    $computers | % { [void]$builder.AppendLine( $_ + ";" + $table[$_].ram +";"+ $table[$_].cpu ) }

                    $builder.ToString()
                    ==ENDCUT==

                    Надеюсь будет понятно куда что.

                    >>На счет непонятной конструкции — пути PowerShella неисповедимы :)
                    Просто в Поше много путей, не все они оптимальны ;)
                  0
                  >> Для этого не забываем добавить модуль АД для PS.
                  Вот подробная инструкция, как это делать: www.mikepfeiffer.net/2010/01/how-to-install-the-active-directory-module-for-windows-powershell/
                    0
                    В стародавние времена инвентаризацию деал с помощью Fpinger, у него есть какой-то модуль который достаточно запихать в логон скрипт или через psexec прогнать по всем машинам. На выходе получались такие красивые бумажки с табличками и непонятными буковками, которые так любит руководство.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое