В этом посте я опишу свой велосипед по сбору сведений об аппаратном обеспечении компьютеров. У меня это организовано с помощью скрипта на powershell, базы данных MySQL и системы мониторинга icinga2. Пока не буду описывать то, что связано с icinga2, так как это тема отдельной статьи.

Скрипт берет данный из wmi как с локальной так и с удаленной машины. Основной плюс моего решения, это то, что в скрипте жестко не прописаны классы и параметры которые нужно собирать, они берутся из базы данных. Добавлять параметры которые, мы хотим собирать можно практически на лету (естественно до запуска скрипта). Это решение было подсмотрено у WSUS, структуру таблиц я так же попытался сделать идентичной WSUS.

Совет дня. В WSUS можно включить инвентаризацию установив CollectClientInventory = True, в таблице tbConfigurationC. После чего клиенты будут отправлять на сервер сведения об аппаратном и программном обеспечении. Правда встроенного интерфейса для просмотра этих данных нет.

Машины идентифицируется по "Win32_ComputerSystemProduct.UUID-Win32_OperatingSystem.SerialNumber". Другого подходящего способа однозначно идентифицировать компьютер пока не нашел (имя компьютера может меняться, операционная система переустанавливаться, а UUID или серийный номер BIOS могут быть пустыми). Можно использовать GUID из реестра, который используется для крипто системы Windows, но с его получением на удаленных машинах могут быть проблемы.

В скрипте используется драйвер для работы с MySQL.

Скриншоты моего интерфейса - iciwebface. Отчет об аппаратных характеристиках:

Скрипт и дамп базы данных:

Скрипт сбора и отправки сведений в базу данных

```powershell
<#
icinga2scripts
Version 1.0
Description: Скрипт для Icinga 2 - Информация о железе рабочей станции

Pavel Satin (c) 2016
pslater.ru@gmail.com
#>
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

$returnStateOK = 0
$returnStateWarning = 1
$returnStateCritical = 2
$returnStateUnknown = 3

#$ErrorActionPreference = "SilentlyContinue"

$connString = "Server=192.168.0.209;Uid=inventory_user;Pwd=password;database=inventory;charset=utf8"

if ((Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain) {
    $myFQDN = (Get-WmiObject win32_computersystem).DNSHostName+"."+(Get-WmiObject win32_computersystem).Domain

} else {
    $myFQDN = (Get-WmiObject win32_computersystem).DNSHostName

}

#Проверка аргументов
if ( $args[0] -ne $Null) {
    $ComputerName = $args[0]
} else {
    $ComputerName = "localhost"
}

if ( $args[1] -ne $Null) {
     $myFQDN = $args[1]
}




#Функция выполнения MySQL запроса
function run-MySQLQuery {
    Param(
        [Parameter(
            Mandatory = $true,
            ParameterSetName = '',
            ValueFromPipeline = $true)]
            [string]$query,
        [Parameter(
            Mandatory = $true,
            ParameterSetName = '',
            ValueFromPipeline = $true)]
            [string]$connectionString
        )

        try {
            # load MySQL driver and create connection
            Write-Verbose "Create Database Connection"
            # You could also could use a direct Link to the DLL File
            $mySQLDataDLL = "C:\ProgramData\icinga2\Scripts\icinga2\MySQL.Data.dll"
            [void][system.reflection.Assembly]::LoadFrom($mySQLDataDLL)
            #[void][System.Reflection.Assembly]::LoadWithPartialName("MySql.Data")
            $connection = New-Object MySql.Data.MySqlClient.MySqlConnection
            $connection.ConnectionString = $ConnectionString
            Write-Verbose "Open Database Connection"
            $connection.Open()
            
            # Run MySQL Querys
            Write-Verbose "Run MySQL Querys"
            $command = New-Object MySql.Data.MySqlClient.MySqlCommand($query, $connection)
            $dataAdapter = New-Object MySql.Data.MySqlClient.MySqlDataAdapter($command)
            $dataSet = New-Object System.Data.DataSet
            $recordCount = $dataAdapter.Fill($dataSet, "data")
            $dataSet.Tables["data"] # | Format-Table
        }        
        catch {
            Write-Host "Could not run MySQL Query" $Error[0] 
        }    
        Finally {
            Write-Verbose "Close Connection"
            $connection.Close()
        }

} #Конец функции Run-MySQLQuery


#########################################################################################
#Замер времени исполнения скрипта
$watch = [System.Diagnostics.Stopwatch]::StartNew()
$watch.Start() #Запуск таймера

if ($ComputerName -eq ".") {
    $result = $true
    $addUUID = $myFQDN
} elseif ( $ComputerName -eq "localhost" ) {
    $result = $true
    $addUUID = $myFQDN
    $ComputerName = $myFQDN
} else {
    $addUUID = $ComputerName
    $myFQDN = $ComputerName
    $result = Test-Connection -ComputerName $ComputerName -Count 2 -Quiet
}


$myFQDN = $myFQDN.ToLower()


if ($result) {

    $ComputerUUID = get-wmiobject Win32_ComputerSystemProduct -computername $ComputerName | Select-Object -ExpandProperty UUID
    $OSSerial = get-wmiobject Win32_OperatingSystem -computername $ComputerName | Select-Object -ExpandProperty SerialNumber

    $ComputerUUID = $ComputerUUID + "-" + $OSSerial

    if ( $ComputerUUID -eq "00000000-0000-0000-0000-000000000000") {
        $ComputerUUID = $ComputerUUID + "-" + $addUUID
    } elseif ( $ComputerUUID -eq $Null ) {
        $ComputerUUID = "00000000-0000-0000-0000-000000000000-" + $addUUID
    }


    #Write-Host "Обновляем данные системы в tbComputerTarget"
    ############################################################################

    $sQLquery = "SELECT Name, ComputerTargetId FROM tbComputerTarget WHERE ComputerTargetId='$ComputerUUID'"
    $rQLquery = run-MySQLQuery -connectionString $connString -query $sQLquery
    [string]$LastReportedInventoryTime = get-date -Format "yyyy-MM-dd HH:mm:ss"

    if ($rQLquery.ComputerTargetId -ne $Null) {

        $sQLquery = "UPDATE tbComputerTarget SET Name='$myFQDN', LastReportedInventoryTime='$LastReportedInventoryTime' WHERE ComputerTargetId='$ComputerUUID'"
        $rQLquery = run-MySQLQuery -connectionString $connString -query $sQLquery


    } else {

        $sQLquery = "INSERT INTO tbComputerTarget ( ComputerTargetId, Name, LastReportedInventoryTime ) VALUES ( '$ComputerUUID', '$myFQDN', '$LastReportedInventoryTime' )"
        $rQLquery = run-MySQLQuery -connectionString $connString -query $sQLquery

    }
    ##########################################################################


    #Выбираем только включенные классы
    $sQLquery = "SELECT ClassID, Name, Namespace, Enabled FROM tbInventoryClass WHERE Enabled = 1"
    $rQLquery = run-MySQLQuery -connectionString $connString -query $sQLquery

    $recordCount = 0

    foreach ($row in $rQLquery) {



        [string]$Win32ClassName = $row.Name
        $Win32ClassID = $row.ClassID
        
        #Удаляем старые записи этого класса
        $sQLqueryDel = "DELETE FROM tbComputerInventory WHERE (ComputerTargetId='$ComputerUUID' AND ClassID = $Win32ClassID)"
        $rQLqueryDel = run-MySQLQuery -connectionString $connString -query $sQLqueryDel
        
        
        ##Отладочный вывод названия обрабатываемого класса ###############################################################
        #Write-Host $Win32ClassName

        [string]$Win32Namespace = $row.Namespace
        $computerClassI = Get-WMIObject -Namespace $Win32Namespace -Class $Win32ClassName -Computer $ComputerName

        $InstanceId = 0

        
        foreach ($computerClass in $computerClassI) {
            $InstanceId = $InstanceId + 1
        
            $sQLqueryProp = "SELECT PropertyID, ClassID, Name FROM tbInventoryProperty WHERE ClassID='$Win32ClassID'"
            $rQLqueryProp = run-MySQLQuery -connectionString $connString -query $sQLqueryProp
                
            foreach ($rowProp in $rQLqueryProp) {
                $PropertyID = $rowProp.PropertyID
                $PropertyName = $rowProp.Name
                $Value = $computerClass.$PropertyName
        
                $sQLDings = "INSERT INTO tbComputerInventory ( ComputerTargetId, ClassID, PropertyID, Value, InstanceId )
                        VALUES ( '$ComputerUUID', '$Win32ClassID', '$PropertyID', '$Value', '$InstanceId' )"
                    
                run-MySQLQuery -connectionString $connString -query $sQLDings
                $recordCount ++

            }
        }


    }


    Write-Host "Inserted $recordCount entries in the database"

    $watch.Stop() #Остановка таймера
    Write-Host $watch.Elapsed #Время выполнения
    Write-Host (Get-Date)
    [System.Environment]::Exit($returnStateOK)

} #End if test-connection result
else {
    Write-Host "Host $ComputerName is not available."
    [System.Environment]::Exit($returnStateUnknown)
}

```

Скрипт сбора и отправки сведений о ПО в базу данных

```powershell
<#
icinga2scripts
Version 1.0
Description: Скрипт для Icinga 2 - Информация о софте рабочей станции

Pavel Satin (c) 2017
pslater.ru@gmail.com
#>
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

$returnStateOK = 0
$returnStateWarning = 1
$returnStateCritical = 2
$returnStateUnknown = 3

#$ErrorActionPreference = "SilentlyContinue"

$connString = "Server=192.168.0.209;Uid=inventory_user;Pwd=password;database=inventory;charset=utf8"


if ((Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain) {
    $myFQDN = (Get-WmiObject win32_computersystem).DNSHostName+"."+(Get-WmiObject win32_computersystem).Domain

} else {
    $myFQDN = (Get-WmiObject win32_computersystem).DNSHostName
    
}

#Проверка аргументов
if ( $args[0] -ne $Null) {
    $ComputerName = $args[0]
} else {
    $ComputerName = "localhost"
}

if ( $args[1] -ne $Null) {
     $myFQDN = $args[1]
}



#Функция выполнения MySQL запроса
function run-MySQLQuery {
    Param(
        [Parameter(
            Mandatory = $true,
            ParameterSetName = '',
            ValueFromPipeline = $true)]
            [string]$query,   
        [Parameter(
            Mandatory = $true,
            ParameterSetName = '',
            ValueFromPipeline = $true)]
            [string]$connectionString
        )

        try {
            # load MySQL driver and create connection
            Write-Verbose "Create Database Connection"
            # You could also could use a direct Link to the DLL File
            $mySQLDataDLL = "C:\ProgramData\icinga2\Scripts\icinga2\MySQL.Data.dll"
            [void][system.reflection.Assembly]::LoadFrom($mySQLDataDLL)
            #[void][System.Reflection.Assembly]::LoadWithPartialName("MySql.Data")
            $connection = New-Object MySql.Data.MySqlClient.MySqlConnection
            $connection.ConnectionString = $ConnectionString
            Write-Verbose "Open Database Connection"
            $connection.Open()
            
            # Run MySQL Querys
            Write-Verbose "Run MySQL Querys"
            $command = New-Object MySql.Data.MySqlClient.MySqlCommand($query, $connection)
            $dataAdapter = New-Object MySql.Data.MySqlClient.MySqlDataAdapter($command)
            $dataSet = New-Object System.Data.DataSet
            $recordCount = $dataAdapter.Fill($dataSet, "data")
            $dataSet.Tables["data"] # | Format-Table
        }        
        catch {
            Write-Host "Could not run MySQL Query" $Error[0] 
        }    
        Finally {
            Write-Verbose "Close Connection"
            $connection.Close()
        }

} #Конец функции Run-MySQLQuery


#########################################################################################
#Замер времени исполнения скрипта
$watch = [System.Diagnostics.Stopwatch]::StartNew()
$watch.Start() #Запуск таймера

if ($ComputerName -eq ".") {
    $result = $true
    $addUUID = $myFQDN
} elseif ( $ComputerName -eq "localhost" ) {
    $result = $true
    $addUUID = $myFQDN
    $ComputerName = $myFQDN
} else {
    $addUUID = $ComputerName
    $myFQDN = $ComputerName
    $result = Test-Connection -ComputerName $ComputerName -Count 2 -Quiet
}


$myFQDN = $myFQDN.ToLower()

if ($result) {

    $ComputerUUID = get-wmiobject Win32_ComputerSystemProduct -computername $ComputerName | Select-Object -ExpandProperty UUID
    $OSSerial = get-wmiobject Win32_OperatingSystem -computername $ComputerName | Select-Object -ExpandProperty SerialNumber

    $ComputerUUID = $ComputerUUID + "-" + $OSSerial

    if ( $ComputerUUID -eq "00000000-0000-0000-0000-000000000000") {
        $ComputerUUID = $ComputerUUID + "-" + $addUUID
    } elseif ( $ComputerUUID -eq $Null ) {
        $ComputerUUID = "00000000-0000-0000-0000-000000000000-" + $addUUID
    }


    #Write-Host "Обновляем данные системы в tbComputerTarget"
    ############################################################################

    $sQLquery = "SELECT Name, ComputerTargetId FROM tbComputerTarget WHERE ComputerTargetId='$ComputerUUID'"
    $rQLquery = run-MySQLQuery -connectionString $connString -query $sQLquery
    [string]$LastReportedInventoryTime = get-date -Format "yyyy-MM-dd HH:mm:ss"

    
    $sQLqueryS = "SELECT ClassID, Name, Namespace, Enabled FROM tbInventoryClass WHERE Name = 'Win32_Product'"
    $rQLqueryS = run-MySQLQuery -connectionString $connString -query $sQLqueryS
    
    foreach ($row in $rQLqueryS) {
        $ClassID = $row.ClassID
        [string]$Win32ClassName = $row.Name

    }
    


    if ($rQLquery.ComputerTargetId -ne $Null) {

        $sQLquery = "UPDATE tbComputerTarget SET Name='$myFQDN', LastReportedSoftInventoryTime='$LastReportedInventoryTime' WHERE ComputerTargetId='$ComputerUUID'"
        $rQLquery = run-MySQLQuery -connectionString $connString -query $sQLquery

    
        $sQLqueryDel = "DELETE FROM tbComputerSoftInventory WHERE ComputerTargetId='$ComputerUUID'"
        $rQLqueryDel = run-MySQLQuery -connectionString $connString -query $sQLqueryDel

    } else {

        $sQLquery = "INSERT INTO tbComputerTarget ( ComputerTargetId, Name, LastReportedSoftInventoryTime ) VALUES ( '$ComputerUUID', '$myFQDN', '$LastReportedInventoryTime' )"
        $rQLquery = run-MySQLQuery -connectionString $connString -query $sQLquery

    }
    ##########################################################################




    $recordCount = 0


    $computerClassI = Get-WMIObject -Class $Win32ClassName -Computer $ComputerName | Sort-Object InstallDate Descending
    $InstanceId = 0
    #$SnapshotId = 0

    $sQLqueryProp = "SELECT PropertyID, ClassID, Name FROM tbInventoryProperty WHERE ClassID='$ClassID'"
    $rQLqueryProp = run-MySQLQuery -connectionString $connString -query $sQLqueryProp

    foreach ($computerClass in $computerClassI) {

        $InstanceId = $InstanceId + 1

        foreach ($rowProp in $rQLqueryProp) {
            if ($rowProp.Name -eq "Name") {
                $PropertyName = $rowProp.PropertyID
                $Value = $computerClass.Name
                $Value = $Value -replace "[']",""

                $sQLDings = "INSERT INTO tbComputerSoftInventory ( ComputerTargetId, PropertyID, Value, InstanceId )
                    VALUES ( '$ComputerUUID', '$PropertyName', '$Value', '$InstanceId' )"
                
                run-MySQLQuery -connectionString $connString -query $sQLDings
                $recordCount ++

            } elseif ($rowProp.Name -eq "Version") {
                $PropertyName = $rowProp.PropertyID
                $Value = $computerClass.Version

                $sQLDings = "INSERT INTO tbComputerSoftInventory ( ComputerTargetId, PropertyID, Value, InstanceId )
                    VALUES ( '$ComputerUUID', '$PropertyName', '$Value', '$InstanceId' )"
                
                run-MySQLQuery -connectionString $connString -query $sQLDings
                $recordCount ++

            } elseif ($rowProp.Name -eq "Vendor") {
                $PropertyName = $rowProp.PropertyID
                $Value = $computerClass.Vendor
    
                $sQLDings = "INSERT INTO tbComputerSoftInventory ( ComputerTargetId, PropertyID, Value, InstanceId )
                    VALUES ( '$ComputerUUID', '$PropertyName', '$Value', '$InstanceId' )"
                
                run-MySQLQuery -connectionString $connString -query $sQLDings
                $recordCount ++
                
            } elseif ($rowProp.Name -eq "InstallDate") {
                $PropertyName = $rowProp.PropertyID
                $Value = $computerClass.InstallDate
    
                $sQLDings = "INSERT INTO tbComputerSoftInventory ( ComputerTargetId, PropertyID, Value, InstanceId )
                    VALUES ( '$ComputerUUID', '$PropertyName', '$Value', '$InstanceId' )"
                
                run-MySQLQuery -connectionString $connString -query $sQLDings
                $recordCount ++
                
            } elseif ($rowProp.Name -eq "IdentifyingNumber") {
                $PropertyName = $rowProp.PropertyID
                $Value = $computerClass.IdentifyingNumber
    
                $sQLDings = "INSERT INTO tbComputerSoftInventory ( ComputerTargetId, PropertyID, Value, InstanceId )
                    VALUES ( '$ComputerUUID', '$PropertyName', '$Value', '$InstanceId' )"
                
                run-MySQLQuery -connectionString $connString -query $sQLDings
                $recordCount ++
                
            }

        }

    }




    Write-Host "Inserted $recordCount entries in the database"

    $watch.Stop() #Остановка таймера
    Write-Host $watch.Elapsed #Время выполнения
    Write-Host (Get-Date)

    [System.Environment]::Exit($returnStateOK)

} #End if test-connection result
else {
    Write-Host "Host $ComputerName is not available."
    [System.Environment]::Exit($returnStateUnknown)

}


```

Дамп MySQL для создания нужных таблиц
```sql
-- Adminer 4.2.5 MySQL dump

SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';

DROP TABLE IF EXISTS `tbComputerInventory`;
CREATE TABLE `tbComputerInventory` (
  `id` int(5) NOT NULL AUTO_INCREMENT,
  `ComputerTargetId` varchar(255) NOT NULL,
  `ClassID` int(5) NOT NULL,
  `PropertyID` int(5) NOT NULL,
  `Value` varchar(256) NOT NULL,
  `InstanceId` int(5) NOT NULL DEFAULT '1',
  PRIMARY KEY (`id`),
  KEY `ClassID` (`ClassID`),
  KEY `PropertyID` (`PropertyID`),
  KEY `ComputerTargetId` (`ComputerTargetId`),
  CONSTRAINT `tbComputerInventory_ibfk_2` FOREIGN KEY (`PropertyID`) REFERENCES `tbInventoryProperty` (`PropertyID`),
  CONSTRAINT `tbComputerInventory_ibfk_3` FOREIGN KEY (`ClassID`) REFERENCES `tbInventoryClass` (`ClassID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `tbComputerSoftInventory`;
CREATE TABLE `tbComputerSoftInventory` (
  `id` int(5) NOT NULL AUTO_INCREMENT,
  `ComputerTargetId` varchar(255) NOT NULL,
  `PropertyID` int(5) NOT NULL,
  `Value` varchar(256) NOT NULL,
  `InstanceId` int(5) NOT NULL,
  `SnapshotId` int(5) DEFAULT NULL,
  `SnapshotDate` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `ComputerTargetId` (`ComputerTargetId`),
  KEY `PropertyID` (`PropertyID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `tbComputerTarget`;
CREATE TABLE `tbComputerTarget` (
  `id` int(5) NOT NULL AUTO_INCREMENT,
  `ComputerTargetId` varchar(255) NOT NULL,
  `Name` varchar(256) NOT NULL,
  `LastReportedInventoryTime` datetime DEFAULT NULL,
  `LastReportedSoftInventoryTime` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `ComputerTargetId` (`ComputerTargetId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `tbInventoryClass`;
CREATE TABLE `tbInventoryClass` (
  `ClassID` int(5) NOT NULL,
  `Name` varchar(256) NOT NULL,
  `Namespace` varchar(256) NOT NULL DEFAULT 'root\\cimv2',
  `Enabled` int(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`ClassID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `tbInventoryProperty`;
CREATE TABLE `tbInventoryProperty` (
  `PropertyID` int(5) NOT NULL,
  `ClassID` int(5) NOT NULL,
  `Name` varchar(256) NOT NULL,
  `Type` varchar(10) NOT NULL,
  PRIMARY KEY (`PropertyID`),
  KEY `ClassID` (`ClassID`),
  CONSTRAINT `tbInventoryProperty_ibfk_1` FOREIGN KEY (`ClassID`) REFERENCES `tbInventoryClass` (`ClassID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP VIEW IF EXISTS `vwComputerInventory`;
CREATE TABLE `vwComputerInventory` (`Name` varchar(256), `PropertyID` int(5), `Value` varchar(256), `InstanceId` int(5));


DROP VIEW IF EXISTS `vwComputerSoftInventory`;
CREATE TABLE `vwComputerSoftInventory` (`Name` varchar(256), `PropertyID` int(5), `Value` varchar(256), `InstanceId` int(5));


DROP TABLE IF EXISTS `vwComputerInventory`;
CREATE ALGORITHM=UNDEFINED SQL SECURITY DEFINER VIEW `vwComputerInventory` AS select `tbComputerTarget`.`Name` AS `Name`,`tbComputerInventory`.`PropertyID` AS `PropertyID`,`tbComputerInventory`.`Value` AS `Value`,`tbComputerInventory`.`InstanceId` AS `InstanceId` from (`tbComputerInventory` join `tbComputerTarget` on((`tbComputerInventory`.`ComputerTargetId` = `tbComputerTarget`.`ComputerTargetId`)));

DROP TABLE IF EXISTS `vwComputerSoftInventory`;
CREATE ALGORITHM=UNDEFINED SQL SECURITY DEFINER VIEW `vwComputerSoftInventory` AS select `tbComputerTarget`.`Name` AS `Name`,`tbComputerSoftInventory`.`PropertyID` AS `PropertyID`,`tbComputerSoftInventory`.`Value` AS `Value`,`tbComputerSoftInventory`.`InstanceId` AS `InstanceId` from (`tbComputerSoftInventory` join `tbComputerTarget` on((`tbComputerSoftInventory`.`ComputerTargetId` = `tbComputerTarget`.`ComputerTargetId`)));

-- 2017-12-23 14:52:36

```