Сбор данных об установленных программах во всем AD с Powershell


26 марта 2020


Инвентаризация программ используя Powershell во всем AD

Для сбора списка программ используя Powershell есть много методов. Это может быть win32_product, команда Get-AppxPackage и многие другие. У большинства методов есть два минуса:

  1. Они выполняются очень долго (win32_product может выполняться несколько минут);
  2. Методы возвращают не все установленные программы.

Для обхода этих проблем, в примерах ниже, будет использоваться способ используя ключ реестра. Конечной целью будет получения списка всех программ со всего AD и последующий экспорт CSV (Excel). Так же будет показан способ исключения из списка тех программ, которые являются стандартными для компании.

 

Получение списка программ

Ключ реестра, который содержит все программы следующий:

HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\

Список программ можно получить так:

Get-ChildItem -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'

Получение списка программ в Powershell

Если взглянуть на вывод, то можно подумать, что нам нужно свойство Name, так как оно лучше всего соответствует названию программы, но этот выбор будет некорректный. Вместо значений свойства Name можно увидеть идентификаторы приложения, а вот DisplayName выглядит корректно:

Фильтрация списка программ в Powershell

В примерах ниже я буду получать только значения из колонки Property DisplayName и DisplayVersion. Вы легко можете добавить другие значения, если вас заинтересуют, сами.

Команда, которая вернет только версию и отображаемое имя:

Get-ChildItem -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' | `
    Get-ItemProperty | Select DisplayName, DisplayVersion

Получение свойств из списка программ в Powershell

Примеры скриптов проверяются на Windows Server 2019 и Windows 10. На серверной версии появляются пустые строки связанные с нестандартными объектами:

Форматирование списка программ в Powershell

Я решил их не включать в список программ, так как они относятся к системным  программам установленными вместе с ОС. Скрипт, который исключит их, выглядит так:

# Получение списка установленных программ
$all_programs = Get-ChildItem -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'
# Фильтрация результатов
$all_programs = $all_programs | Get-ItemProperty | `
                Where-Object 'DisplayName' | `
                Select 'DisplayName','DisplayVersion'

Инвентаризация программ с в Powershell

 

Удаленное подключение к компьютерам

Так как скрипт должен подключаться к удаленным компьютерам я добавлю командлет PSRemoting (WinRM). Эта технология нуждается в предварительных настройках. В доменных сетях, при использовании DNS имен компьютеров, может все сработать и без этого. Проверить работу этой возможности с помощью следующего командлета:

Invoke-Command -ComputerName 'localhost' -ScriptBlock {whoami}

Выполнение удаленных программ в Powershell

Если у вас появляются ошибки, то советую проверить, что консоль Powershell запущена от имени администратора и служба включена:

winrm qc
# или
Enable-PSRemoting

Если ошибка осталось - читайте статью "Удаленное управление через Powershell".

Используя только описанные методы у нас получится достаточно сложный скрипт, который будет иметь следующую логику:

Invoke-Command -ComputerName 'Компьютер1','Компьютер2','Компьютер99' -ScriptBlock {'Длинный скрипт'}

Что бы избежать такого сложно читаемого кода я объединю все это в одну функцию-командлет...

 

Создание командлета

Команда, которая вернет список программ с любого компьютера будет выглядеть так:

function Get-Programs {
            # Эта часть принимает имена компьютеров через конвейер (значение по умолчанию localhost)
            [cmdletbinding()]
            Param (
                [parameter(ValueFromPipeline=$True)]
                [string]
                $ComputerName = 'localhost'
            )
            process {
                # Эта команда выполняет подключение и получает список программ
                $all_programs = Invoke-Command -ComputerName $ComputerName `
                                -ScriptBlock {
                                $reg_path = Get-ChildItem `
                                -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'
                                # Форматируем вывод
                                $reg_path | Get-ItemProperty | `
                                Where-Object 'DisplayName' | `
                                Select 'DisplayName','DisplayVersion'
                                }
                # Дополняем наш список именем компьютера с которого он был получен
                foreach ($program in $all_programs){
                    $program | Add-Member -Name 'ComputerName' `
                                          -Value $ComputerName `
                                          -MemberType NoteProperty
                }
                # Возвращаем список
                return $all_programs
            }
}

Если вы планировали добавить больше, а не только имя и версию, то его нужно добавить в месте на скриншоте:

Создание команды возвращающая список установленных программ с Powershell

После этого мы сможем получать наш список следующими способами:

# Через конвейер со множества компьютеров
'localhost','127.0.0.1' | Get-Programs

# С одного компьютера
Get-Programs -ComputerName 'localhost'

# Через цикл
$computers = @('localhost','127.0.0.1')
foreach ($computer in $computers){
    Get-Programs $computer
}

Варианты использования команды возвращающая список установленных программ с Powershell

Отмечу, что команда Get-Programs перестанет работать после завершения сессии пользователя и затем ее понадобится объявлять заново. Если вы хотите что бы они импортировались автоматически почитайте конец статьи "Как создавать команды и функции в Powershell вызывать их и передавать параметры".

 

Исключение программ

Исключить определенные программы мы можем по нескольким причинам. Это могут быть:

  1. Стандартные программы. Например Adobe Reader будет на большинстве компьютеров.
  2. Не несут информационной пользы. В список программ могут входить пакеты обновлений.

Я создал следующий список:

$exclude_programs = @('*Vmware*',
                     '*Visual*2019*')

Использование знака * в названиях говорит, что в этой части строки может находится еще текст, но не обязательно. 

Для исключения программ я сделал еще один командлет, который будет принимать 2 параметра:

  1. Список программ в переменной $Programs
  2. Список исключений в переменной $ExcludeList
function Exclude-Programs {
        [cmdletbinding()]
    Param (
        [parameter(ValueFromPipeline=$True)]
        # Параметр, который будет приниматься через конвейер - массив с программами
        [PSCustomObject]
        $Programs,
        [Array]
        # Список с исключениями
        $ExcludeList
    )
    process {
        # Счетчик, который меняется если найдена программа из исключений
        $is_exluded = 0

        # Новый список программ
        $new_programs_array = @()

        # Два цикла, которые проходят по списку исключений и программ
        foreach ($program in $Programs){
            foreach ($e_program in $ExcludeList){
                # Условие, которая меняет цифру счетчика если программа исключена
                if ($program.DisplayName -like $e_program){
                    $is_exluded += 1
                }
            }

            # Проверка счетчика. 
            # Если счетчик = 0, то программа добавляется в новый список
            # В остальных случаях счетчик обнуляется
            if ($is_exluded -eq 0){
                $new_programs_array += $program
            }
            else {
                $is_exluded = 0
            }
        }
        # Возвращение нового списка
        return $new_programs_array
    }
}

Теперь мы можем исключать программы так:

# Стандартный вывод
'localhost' | Get-Programs

# Объявляем список по которому будем исключать
$exclude_programs = @('*Vmware*',
                     '*Visual*2019*')

# Варианты выполнения

# 1) Через конвейер
'localhost' | Get-Programs | Exclude-Programs -ExcludeList $exclude_programs

# 2) Через параметры
$result = 'localhost' | Get-Programs
Exclude-Programs -Programs $result -ExcludeList $exclude_programs

Исключение программ из списка в Powershell 

 

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

Следующая команда вернет список всех компьютеров которые есть в AD:

Get-ADComputer -Filter *

Получение списка компьютеров из AD в Powershell

Она сработает в случаях если у вас установлен RSAT, импортирован модуль AD или вы на сервере с AD.

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

(Get-ADComputer -Filter *).Name

Получение имен компьютеров из AD в Powershell

Мы можем использовать эту команду без дополнительных параметров, но могут быть ошибки:

$exclude_programs = @('*Vmware*',
                     '*Visual*2019*')

(Get-ADComputer -Filter *).Name | Get-Programs | Exclude-Programs -ExcludeList $exclude_programs

Получение списка программ с удаленных компьютеров в Powershell

Если не учитывать особенности вашей инфраструктуры, то причины в основном 3:

  1. Компьютер выключен.
  2. Компьютер отключен как учетная запись.
  3. На указанном компьютере не настроен WinRM (PSRemoting), не открыт порт или нет нужных привилегий на такую команду.

О третьей причине мы уже говорили. Что касается первых двух мы можем их решить.

В случае если отключены учетные записи компьютеров, то отфильтровать их можно так:

Get-ADComputer -Filter {Enabled -eq $True}

Фильтрация компьютеров из AD в Powershell

Выключенные компьютеры можно проверить через команду Test-NetConnection (аналог ping):

# Получение имен компьютеров
$computers = (Get-ADComputer -Filter {Enabled -eq $True}).Name

# Пустой список, который будет содержать компьютеры онлайн и оффлайн
$computers_online = @()
$computers_offline = @()

foreach ($computer in $computers){
    # Проверка доступности компьютера 1 ICMP пакетом и исключение Warning сообщений
    $result = Test-NetConnection `
        -ComputerName $computer `
        -Hops 1 `
        -WarningAction Ignore `
        -ErrorAction SilentlyContinue
    # Если пинг прошел, он будет добавлен в список online, иначе в offline
    if ($result.PingSucceeded -eq $True){
        $computers_online += $computer
    }
    else {
        $computers_offline += $computer
    }
}
Write-Output "Компьютеры онлайн: "$computers_online
Write-Output "Компьютеры оффлайн: "$computers_offline

Проверка компьютеров AD в сети с Powershell

Плюсы метода выше в том, что вы получите список компьютеров, которые оффлайн. Минусы в том, что вы не можете изменить время возвращения ответа на ICMP пакет, а оно будет длиться 3-7 секунд.

И последний пример - это использовать возможность игнорирования ошибок через -ErrorAction SilentlyContinue. Этот пример самый быстрый, но вы не получите список исключенных компьютеров в чистом виде:

(Get-ADComputer -Filter *).Name | Get-Programs -ErrorAction SilentlyContinue -ErrorVariable computers_offline

# Вывод ошибок
$computers_offline

Использование переменной ErrorVariable в Powershell

 

Экспорт данных в CSV и открытие в Excel

Команда, которая эскортирует данные, называется Export-CSV. Если вы не исключаете программы, то можете выполнить ее так:

'127.0.0.1','localhost' | Get-Programs -ErrorAction SilentlyContinue -ErrorVariable computers_offline | Export-Csv -Path 'C:\programs.csv' -NoTypeInformation

В нашем случае файл будет сохранен на диске C, под именем programs.csv. При появлении ошибок при экспорте с кодировками, делиметром (разделителем), до записью вы можете попробовать исправить прочитав эту статью "Powershell экспорт и запись в CSV файл".

Если вы исключаете программы, то можете выполнить ее так:

$exclude_programs = @('*Vmware*',
                     '*Visual*2019*')

$all_computers = (Get-ADComputer -Filter *).Name
$all_programs = $all_computers | Get-Programs -ErrorAction SilentlyContinue -ErrorVariable computers_offline
$excluded = $all_programs | Exclude-Programs -ExcludeList $exclude_programs
Export-Csv -InputObject $excluded -Path 'C:\programs.csv' -NoTypeInformation

Открыв Excel и выбрав следующий пункт выберете файл в проводнике:

Импорт CSV из Powershell в Excel

В новом окне посмотреть на разделитель, который используется, и нажать "Далее":

Импорт CSV из Powershell в Excel

Выбрать разделитель (будет либо запятая или точка-запятая):

Импорт CSV из Powershell в Excel

Результат будет примерно следующим:

Получение списка программ в Powershell с использованием AD

Если выделить шапку (как в примере выше) и нажать "Фильтр", то сможете более удобно фильтровать результат.

...

Теги: #powershell #ad #инвентаризация


Популярные тэги
О блоге
Этот блог представляет собой конспекты выученного материала, преобретенного опыта и лучшие практики в системном администрировании и программировании.