Автоматизация инвентаризации парка ПК: PowerShell-скрипт для сбора системной информации

от автора

Привет, Хабр! Типичная ситуация: начальство требует полный отчёт по всему парку техники или только устроились в организацию с крупным парком машин, нужно сформировать понимание, с чем вы работаете. Вручную обходить 100+ рабочих мест — совсем не вариант.

В статье я поделюсь PowerShell-скриптом, который:

  • Сам обойдет все машины в сети

  • Соберёт подробную информацию о конфигурации

  • Сгенерирует удобные для восприятия HTML-отчёты

  • Складирует всё в вашу сетевую папку для удобства

Скрипт будет полезен системным администраторам, инженерам ТП, аудиторам и тем, кто устал вручную обходить все машины в организации.

Возможности скрипта

Скрипт собирает комплексную информацию о системе:

  • Информация об операционной системе (версия, архитектура, время работы)

  • Данные о BIOS и производителе оборудования Аппаратное обеспечение:

  • Процессор (модель, ядра/потоки, загрузка)

  • Оперативная память (объем, модули, использование)

  • Графические процессоры (модель, память, драйверы)

  • Накопители (модель, разделы, свободное пространство) Сетевые адаптеры:

  • Активные сетевые адаптеры

  • IP и MAC-адреса

  • Скорость соединения Доп информация:

  • Версия PowerShell

  • Логические диски и процент их заполнения

Назначение скрипта

Скрипт выполняет следующие задачи:

  • Собирает основную конфигурацию с удалённых машин

  • Сохраняет данные в структурированном HTML-формате

  • Централизованно хранит отчёты в вашей сетевой папке

  • Автоматически удаляет устаревшие отчёты

  • Логирует все этапы работы

Архитектура решения

Скрипт имеет следующую структуру:

  1. Контроллер — основной скрипт, который запускается на машине админа

  2. Агенты — функции, выполняемые на удалённых компьютерах

Детальный разбор компонентов

Я разберу лишь основные функции скрипта, тк нагружать вас во многом бесполезным кодом — нет смысла. Полный скрипт:

Скрытый текст
$centralLogsFolder = "\\сервер\общая_папка\Logs" $maxReportsPerComputer = 5 $logFile = Join-Path $centralLogsFolder "InventoryScript_$(Get-Date -Format 'yyyyMMdd').log"  # Функция для логирования function Write-Log {     param(         [string]$message,         [string]$level = "INFO"     )     $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"     $logEntry = "[$timestamp][$level] $message"     Add-Content -Path $logFile -Value $logEntry     Write-Host $logEntry -ForegroundColor $(if ($level -eq "ERROR") { "Red" } elseif ($level -eq "WARNING") { "Yellow" } else { "White" }) }  # Создаём основную папку для логов, если её нет if (!(Test-Path $centralLogsFolder)) {     try {         New-Item -ItemType Directory -Path $centralLogsFolder -Force | Out-Null         Write-Log "Создана центральная папка для логов: $centralLogsFolder"     }     catch {         Write-Log "Не удалось создать центральную папку для логов: $_" -level "ERROR"         exit 1     } }  # Получаем список компьютеров try {     # Способ 1: Из Active Directory (раскомментируйте нужный)     # $computers = Get-ADComputer -Filter * | Select-Object -ExpandProperty Name          # Способ 2: Из файла     $computers = Get-Content "C:\path\to\computers.txt" | Where-Object { $_ -notmatch '^\s*#' -and $_ -ne '' }          # Способ 3: Вручную указать список     # $computers = @("COMPUTER1", "COMPUTER2", "COMPUTER3")      if (-not $computers) {         Write-Log "Не найдено компьютеров для обработки" -level "WARNING"         exit 1     }          Write-Log "Получен список компьютеров для обработки: $($computers.Count) шт." } catch {     Write-Log "Не удалось получить список компьютеров: $_" -level "ERROR"     exit 1 }  # Запрашиваем учётные данные $cred = Get-Credential -Message "Введите учётные данные с правами администратора на целевых компьютерах" if (-not $cred) {     Write-Log "Не введены учётные данные" -level "ERROR"     exit 1 }  # Функция для удалённого сбора информации function Get-RemoteSystemInventory {     param(         [string]$computerName,         [string]$outputDir,         [int]$maxReports,         [pscredential]$credential     )          $session = $null     try {         # Проверяем доступность компьютера         if (-not (Test-Connection -ComputerName $computerName -Count 2 -Quiet)) {             Write-Log "Компьютер $computerName недоступен по сети" -level "WARNING"             return         }          # Создаём сессию PSRemoting         $sessionParams = @{             ComputerName = $computerName             Credential = $credential             ErrorAction = 'Stop'         }                  # Пробуем подключиться (добавляем проверку для WinRM)         try {             $session = New-PSSession @sessionParams         }         catch {             # Если WinRM отключен, пробуем через WMI             Write-Log "Не удалось подключиться к $computerName через PSRemoting, пробуем WMI" -level "WARNING"                          $scriptBlock = {                 param($outputDir, $maxReports)                 # Встроенный код функции Get-SystemInventory (см. ниже)                 # ... (всё содержимое функции Get-SystemInventory)             }              Invoke-Command -ComputerName $computerName -Credential $credential -ScriptBlock $scriptBlock -ArgumentList $outputDir, $maxReports -ErrorAction Stop             Write-Log "Успешно собраны данные с $computerName через WMI" -level "INFO"             return         }          # Подготовка аргументов         $remoteOutputDir = Join-Path $outputDir $computerName         $arguments = @($remoteOutputDir, $maxReports)          # Выполняем команду на удалённом компьютере         $result = Invoke-Command -Session $session -ScriptBlock ${function:Get-SystemInventory} -ArgumentList $arguments -ErrorAction Stop                  Write-Log "Успешно собраны данные с компьютера $computerName" -level "INFO"     }     catch {         Write-Log "Ошибка при работе с компьютером $computerName : $_" -level "ERROR"     }     finally {         if ($session) {             Remove-PSSession -Session $session -ErrorAction SilentlyContinue         }     } }  # Основная функция сбора данных (будет выполняться на удалённых машинах) function Get-SystemInventory {     param(         [string]$outputDir,         [int]$maxReports     )          try {         $computerName = $env:COMPUTERNAME         $fileName = "Inventory_$(Get-Date -Format 'yyyyMMdd-HHmmss').html"         $fullOutputDir = Join-Path $outputDir $computerName          # Создаём папку и чистим старые отчёты         if (!(Test-Path $fullOutputDir)) {              New-Item -ItemType Directory -Path $fullOutputDir -Force | Out-Null          }                  Get-ChildItem $fullOutputDir -Filter "Inventory_*.html" |              Sort-Object CreationTime -Descending |              Select-Object -Skip $maxReports |              Remove-Item -Force -ErrorAction SilentlyContinue          # Собираем основные данные         $os = Get-CimInstance Win32_OperatingSystem         $cpu = Get-CimInstance Win32_Processor         $mem = Get-CimInstance Win32_PhysicalMemory         $totalGB = [math]::Round(($mem | Measure-Object -Property Capacity -Sum).Sum /1GB, 2)          # Получаем данные о загрузке CPU         $cpuUsage = "Ошибка получения данных"         try {             $cpuUsage = (Get-Counter '\Processor(_Total)\% Processor Time' -ErrorAction Stop).CounterSamples.CookedValue.ToString("N2")         } catch {             try {                 $cpuUsage = (Get-WmiObject Win32_Processor | Measure-Object -Property LoadPercentage -Average).Average.ToString("N2")             } catch {                 $cpuUsage = "Ошибка получения данных"             }         }          $gpus = Get-CimInstance Win32_VideoController         $networkAdapters = Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | ForEach-Object {             $ipAddress = (Get-NetIPAddress -InterfaceIndex $_.ifIndex -AddressFamily IPv4).IPAddress             [PSCustomObject]@{                 Name = $_.Name                 Interface = $_.InterfaceDescription                 Status = $_.Status                 Speed = "$([math]::Round($_.Speed/1MB, 2)) Mbps"                 MAC = $_.MacAddress                 IP = $ipAddress             }         }          $disks = Get-Disk | ForEach-Object {             $partitions = Get-Partition -DiskNumber $_.DiskNumber | ForEach-Object {                 $vol = Get-Volume -Partition $_                 $freeSpacePercent = if ($vol.Size -gt 0) { [math]::Round(($vol.SizeRemaining / $vol.Size) * 100, 2) } else { 0 }                 $freeSpaceClass = if ($freeSpacePercent -lt 10) { "err" } elseif ($freeSpacePercent -lt 20) { "warn" } else { "" }                 "<span class='$freeSpaceClass'>$($_.DriveLetter) $([math]::Round($_.Size/1GB))GB ($([math]::Round($vol.SizeRemaining/1GB))GB свободно, $freeSpacePercent%)</span>"             }             [PSCustomObject]@{                 Model = $_.FriendlyName                 Size = [math]::Round($_.Size/1GB)                 Health = $_.HealthStatus                 Partitions = $partitions -join '<br>'             }         }          # Генерируем HTML (как в вашем оригинальном скрипте)         $html = @" <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Системный отчет - $computerName</title> <style> body{font-family:Segoe UI;margin:20px} h1,h2{color:#0066cc} table{width:100%;border-collapse:collapse} th,td{padding:8px;text-align:left;border-bottom:1px solid #ddd} .warn{color:orange} .err{color:red} .info{color:#0066cc} .disk-info{background-color:#f9f9f9} .gpu-info{background-color:#f0f8ff} .network-info{background-color:#fff8f0} </style> </head> <body> <h1>Отчет о системе - $computerName</h1> <p><b>Дата:</b> $(Get-Date)</p> <p><b>Время работы системы:</b> $([math]::Round($os.LastBootUpTime.Subtract((Get-Date)).TotalHours * -1, 2)) часов</p>  <h2>ОС</h2> <table class="os-info"> <tr><td>Название</td><td>$($os.Caption)</td></tr> <tr><td>Версия</td><td>$($os.Version)</td></tr> <tr><td>Архитектура</td><td>$($os.OSArchitecture)</td></tr> <tr><td>Версия BIOS</td><td>$( (Get-CimInstance Win32_BIOS).Name )</td></tr> <tr><td>Производитель системы</td><td>$( (Get-CimInstance Win32_ComputerSystem).Manufacturer )</td></tr> <tr><td>Модель системы</td><td>$( (Get-CimInstance Win32_ComputerSystem).Model )</td></tr> </table>  <h2>Процессор</h2> <table> <tr><td>Модель</td><td>$($cpu.Name)</td></tr> <tr><td>Ядер/Потоков</td><td>$($cpu.NumberOfCores)/$($cpu.NumberOfLogicalProcessors)</td></tr> <tr><td>Тактовая частота</td><td>$([math]::Round($cpu.MaxClockSpeed/1000, 2)) GHz</td></tr> <tr><td>Загрузка CPU</td><td>$cpuUsage%</td></tr> </table>  <h2>Память</h2> <table> <tr><td>Всего</td><td>${totalGB}GB</td></tr> <tr><td>Модули</td><td>$($mem.Count)x$([math]::Round($mem[0].Capacity/1GB))GB $($mem[0].Manufacturer)</td></tr> <tr><td>Используется</td><td>$([math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory)/1MB, 2))GB / ${totalGB}GB ($([math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory)/$os.TotalVisibleMemorySize*100, 2))%)</td></tr> </table>  <h2>Графические процессоры</h2> <table class="gpu-info"> <tr><th>Модель</th><th>Память</th><th>Драйвер</th><th>Разрешение</th></tr> "@  foreach ($gpu in $gpus) {     $gpuMemory = if ($gpu.AdapterRAM -gt 0) { "$([math]::Round($gpu.AdapterRAM/1GB))GB" } else { "N/A" }     $html += "<tr><td>$($gpu.Name)</td><td>$gpuMemory</td><td>$($gpu.DriverVersion)</td><td>$($gpu.CurrentHorizontalResolution)x$($gpu.CurrentVerticalResolution)</td></tr>" }  $html += @" </table>  <h2>Диски</h2> <table class="disk-info"> <tr><th>Модель</th><th>Размер</th><th>Состояние</th><th>Разделы</th></tr> "@  foreach ($d in $disks) {     $healthClass = if ($d.Health -ne "Healthy") {"warn"} else {""}     $html += "<tr><td>$($d.Model)</td><td>$($d.Size)GB</td><td class='$healthClass'>$($d.Health)</td><td>$($d.Partitions)</td></tr>" }  $html += @" </table>  <h2>Сетевые адаптеры</h2> <table class="network-info"> <tr><th>Имя</th><th>Интерфейс</th><th>Скорость</th><th>MAC</th><th>IP-адрес</th></tr> "@  foreach ($adapter in $networkAdapters) {     $html += "<tr><td>$($adapter.Name)</td><td>$($adapter.Interface)</td><td>$($adapter.Speed)</td><td>$($adapter.MAC)</td><td>$($adapter.IP)</td></tr>" }  $html += @" </table>  <h2>Дополнительная информация</h2> <table> <tr><td>Версия PowerShell</td><td>$($PSVersionTable.PSVersion)</td></tr> <tr><td>Логические диски</td><td>$( (Get-PSDrive | Where-Object { $_.Provider -like "*FileSystem*" } | ForEach-Object { "$($_.Name) ($([math]::Round($_.Used/1GB, 2))GB/$([math]::Round($_.Free/1GB, 2))GB" })) -join ', ' )</td></tr> </table>  </body> </html> "@          $html | Out-File "$fullOutputDir\$fileName" -Encoding UTF8         return "Отчет создан: \\$($env:COMPUTERNAME)\$($fullOutputDir.Replace(':','$'))\$fileName"     }     catch {         return "Ошибка при создании отчета: $_"     } }  # Основной цикл обработки компьютеров foreach ($computer in $computers) {     Write-Log "Начинаем обработку компьютера: $computer"          try {         $remoteOutputDir = Join-Path $centralLogsFolder $computer         Get-RemoteSystemInventory -computerName $computer -outputDir $centralLogsFolder -maxReports $maxReportsPerComputer -credential $cred     }     catch {         Write-Log "Критическая ошибка при обработке компьютера $computer : $_" -level "ERROR"     }          Write-Log "Завершена обработка компьютера: $computer" }  Write-Log "Скрипт завершил работу" -level "INFO"

1. Настройка централизованного хранения логов

$centralLogsFolder = "\\сервер\общая_папка\Logs" $maxReportsPerComputer = 5 $logFile = Join-Path $centralLogsFolder "InventoryScript_$(Get-Date -Format 'yyyyMMdd').log" 

Скрипт создаёт единое хранилище отчётов в сетевой папке, что позволяет:

  • Централизованно управлять отчётами

  • Вести детальный лог выполнения

2. Функция логирования

function Write-Log {     param([string]$message, [string]$level = "INFO")     $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"     $logEntry = "[$timestamp][$level] $message"     Add-Content -Path $logFile -Value $logEntry     Write-Host $logEntry -ForegroundColor $(if ($level -eq "ERROR") { "Red" } elseif ($level -eq "WARNING") { "Yellow" } else { "White" }) } 

Плюсы реализации:

  • Несколько уровней логирования (INFO, WARNING, ERROR)

  • Вывод в консоль и запись в файл (для удобства)

  • Визуальное выделение ошибок (каждой свой цвет, тоже для удобства)

3. Получение списка компьютеров

В скрипте я указал 3 способа получения списка рабочих машин:

# Из Active Directory $computers = Get-ADComputer -Filter * | Select-Object -ExpandProperty Name  # Из текстового файла $computers = Get-Content "C:\path\to\computers.txt" | Where-Object { $_ -notmatch '^\s*#' -and $_ -ne '' }  # Заданный список # $computers = @("COMPUTER1", "COMPUTER2", "COMPUTER3") 

В текущей версии активирован способ 2 — чтение из файла. Можно переписать под CSV-файл.

4. Механизм удалённого выполнения

Основная функция Get-RemoteSystemInventory реализует несколько важных особенностей:

Отказоустойчивость:

  • Проверка доступности компьютера перед подключением

  • Автоматическое переключение между PSRemoting и WMI

  • Обработка ошибок

if (-not (Test-Connection -ComputerName $computerName -Count 2 -Quiet)) {     Write-Log "Компьютер $computerName недоступен по сети" -level "WARNING"     return }

Гибкое подключение:

try {     $session = New-PSSession @sessionParams } catch {     Write-Log "Не удалось подключиться через PSRemoting, пробуем WMI" -level "WARNING"     Invoke-Command -ComputerName $computerName -Credential $credential -ScriptBlock $scriptBlock -ArgumentList $outputDir, $maxReports }

5. Функция сбора данных (Get-SystemInventory)

Эта функция выполняется на удалённых компьютерах и собирает:

Основные системные данные:

  • Общую инфу о системе

  • Данные об ОС

  • Характеристики процессора

  • Информацию о памяти

  • Данные о видеокартах

  • Состояние дисков

  • Сетевые настройки

  • Дополнительную информацию

Пример обработки дисков:

$disks = Get-Disk | ForEach-Object {     $partitions = Get-Partition -DiskNumber $_.DiskNumber | ForEach-Object {         $vol = Get-Volume -Partition $_         $freeSpacePercent = if ($vol.Size -gt 0) { [math]::Round(($vol.SizeRemaining / $vol.Size) * 100, 2) } else { 0 }         $freeSpaceClass = if ($freeSpacePercent -lt 10) { "err" } elseif ($freeSpacePercent -lt 20) { "warn" } else { "" }         "<span class="$freeSpaceClass">$($_.DriveLetter) $([math]::Round($_.Size/1GB))GB ($([math]::Round($vol.SizeRemaining/1GB))GB свободно, $freeSpacePercent%)</span>"     }     ... }

6. Управление отчётами

Скрипт автоматически чистит старые отчёты, оставляя только указанное кол-во:

Get-ChildItem $fullOutputDir -Filter "Inventory_*.html" |      Sort-Object CreationTime -Descending |      Select-Object -Skip $maxReports |      Remove-Item -Force -ErrorAction SilentlyContinue

Как выглядит отчёт:

ОС, процессор, память

ОС, процессор, память
Графические процессоры, диски, сетевые адаптеры, дополнительная информация

Графические процессоры, диски, сетевые адаптеры, дополнительная информация

Практическое применение

Я делал скрипт для формирования понимания о парке машин, коим сейчас владею, но это далеко не весь перечень потенциально применения:

  1. Инвентаризации парка машин — удобный сбор данных о конфигурациях;

  2. Выявления проблем — обнаружение нехватки места на дисках и тд.;;

  3. Документирования инфраструктуры — создание базы знаний о системе. Скрипт можно улучшить и добавить генерацию сводных отчётов по всем компьютерам.

Заключение

Скрипт, описанный в статье — крайне полезный инструмент, который может помочь сэкономить много времени. Его можно допиливать под свои нужды: добавить сбор софта, интеграцию с БД или сводную аналитику. Если тема будет интересна — разберу в следующих статьях возможные улучшения кода.

P.S. Я запустил свою группу в Телеграмм, буду рад видеть всех, кому интересен процесс написания скриптов и автоматизация в мире IT. Также, там можно найти этот и другие скрипты.


ссылка на оригинал статьи https://habr.com/ru/articles/923744/