PowerShell — AD GPO — PcDescription

Иногда руководство, а чаще сами администраторы хотят знать на какой машине залогинился конкретный пользователь.  Это может быть полезно, когда нужно удаленно подключиться к пользователю, который не знает имени своего компьютера, или даже для сбора информации о компьютерах в домене. Ну в общем очень популярная задача.

Все реализовывают ее по-своему. Я видел даже вариант с логон-скриптом, который пишет информацию о логоне в текстовый файл, что, на мой взгляд, является самым отвратительным и ненадежным вариантом.
Я несколько раз выполнял эту задачу в разных организациях и с каждым разом реализация становилась все интереснее, но самая последняя реализация сегодня раскрыта не будет, т.к. она включает в себя и powershell, БД, веб-интерфейс, т.е. содержит слишком много компонентов для описания в статье. Может когда-нибудь напишу.

Сегодня я расскажу о двух более простых вариантах:

  1. Логон-скрипт PowerShell
    Этот вариант проще, но у него есть существенный недостаток: скрипт выполняется при логоне, и, соответственно, первую информацию мы увидим только после перелогона пользователя, а ждать этого можно и несколько месяцев
  2. Планировщик задач + PowerShell модуль + VBS скрипт-стартер
    Этот вариант сложнее в развертывании, т.к. он содержит больше компонентов. И  вдобавок необходим vbs, т.к. если запускать powershell-скрипты непосредственно из планировщика под залогинившимся пользователем, то при каждом входе будет мигать окно консоли.

В обоих случаях данные будут записываться поле Description объекта Computer (можно использовать и другие поля, например info, но предварительно нужно нагуглить на msdn длину этого поля), соответственно необходимо дать разрешения пользователям домена на запись в это поле. Также нужно не забыть разрешить пользователям домена выполнять скрипты через GPO.

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

Делегируем права на запись в поле

Надеюсь, нескольких скриншотов должно хватить:

1

2

3

4

5

Теперь пользователи домена имеют право на запись description компьютеров, находящихся в OrganizationalUnit WorkStation.

Ну и несколько слов о самой реализации 🙂

1. Логон-скрипт PowerShell

$USERDNSDOMAIN = $env:USERDNSDOMAIN
$COMPUTERNAME = $env:COMPUTERNAME
$ObjectHash = @{}
$ObjectHash.UserName = $env:USERNAME
$ObjectHash.UserDomain = $USERDNSDOMAIN
$ObjectHash.DateTime = "{0:dd.MM.yyyy-HH:mm:ss}" -f $(Get-Date)
$ObjectHash.Console = ([Boolean]$((query.exe SESSION | findstr.exe ">console") -match $env:USERNAME)).ToString()
$ObjectHash.PSversion = $Host.Version.Major.ToString()
$ObjectHash.MACAddress = (Get-WmiObject Win32_NetworkAdapterConfiguration -Filter "IPEnabled = $True" | sort IPConnectionMetric | select -First 1).MACAddress
$ObjectHash.Memory = [string]$((Get-WmiObject CIM_PhysicalMemory | measure -Property Capacity -Sum).Sum/1GB)+"GB"
$ObjectHash.Processor = (Get-WmiObject Win32_Processor).Name
$ObjectHash.Battery = (Get-WmiObject Win32_Battery).DeviceID
$ObjectHash.BiosSN = (Get-WmiObject Win32_BIOS).SerialNumber
$ObjectHash.SystemManufacturer = (Get-WmiObject Win32_ComputerSystem).Manufacturer
$ObjectHash.SystemModel = (Get-WmiObject Win32_ComputerSystem).Model

$select = "UserName,UserDomain,DateTime,Console,PSversion,MACAddress,Memory,Processor,Battery,BiosSN,SystemManufacturer,SystemModel" -split ","

if([int]$Host.Version.Major -ge 3){
$Description = New-Object -TypeName PSCustomObject -Property $ObjectHash | select $select | ConvertTo-Json -Compress
} else {
	$Description = '{' + $(($select | % {'"'+"$($_)"+'"'+":"+'"'+"$($ObjectHash.$($_))"+'"'}) -join ",") + '}'
	}

if(!$Description){$Description = "Null"}

$DirectoryEntry = New-Object System.DirectoryServices.DirectoryEntry
$USERDNSDOMAIN_DN = $DirectoryEntry.distinguishedName

$Root = [ADSI]"LDAP://$USERDNSDOMAIN/$USERDNSDOMAIN_DN"
$Root
$SearchPC = new-object System.DirectoryServices.DirectorySearcher($Root)
$SearchPC.Filter = "(&(objectClass=computer)(Name=$COMPUTERNAME))"
$Computer = [ADSI]$($SearchPC.FindOne().Path)
$Computer.put("Description",$Description)
$Computer.put("info",$Description)
# Если на любом этапе в скрипте возникла ошибка, не изменяем описание компьютера в AD
if($error.count -ge 1){exit}
# Если не было ни одной ошибки, изменяем описание компьютера в AD
$Computer.SetInfo()
exit

Поскольку это логон-скрипт и его достаточно распространить через политику и ждать 🙂

2. Планировщик задач + PowerShell модуль + VBS скрипт-стартер

Нам нужен VBS скрипт, про который я рассказывал в одной из моих прошлых статей

CommandString = "powershell.exe -NoLogo -NoProfile -WindowStyle Hidden -Command"
For Each Argument In WScript.Arguments
     CommandString = CommandString & " " & Argument
Next
REM CommandString = CommandString & " > c:\temp\test.txt"
REM WScript.Echo CommandString
set WshShell = WScript.CreateObject("WScript.Shell")
call WshShell.Run(CommandString,0,1)
WScript.Quit

Самое простое — это с помощью политики раскидать скрипт в System32, тогда им будет удобно пользоваться.

6

За тем нужно раскатить PowerShell-модуль PcDescription по машинам, это делается также, только в конце пути указываем * для копирования всех файлов

7

А теперь, конечно же тоже через GPO, создадим задание в планировщике

8

9

Напишу словами:

Программа или сценарий:

wscript.exe

Добавить аргументы:

PSCommandSilentlyRun.vbs Import-Module PcDescription; Set-PcDescription

Теперь, если у нас настроено удаленное управление, мы можем форсировать распространение групповой политики на все компьютеры домена:

Invoke-AsWorkflow -PSComputerName (Get-ADComputer -Filter *).DNSHostName -Expression {gpupdate}

И наблюдать за тем, как поле описания компьютеров заполняется данными.

Конечно в модуль я добавил функцию для выгрузки данных. Таким образом в модуле у нас 2 функции: Set-PcDescription, который запускается планировщиком при входе пользователя и Get-PcDescription для выборки данных, запускаемый админами интерактивно.

Пример работы:

PS> Get-PcDescription


UserName               : sasha
UserDomain             : DOMAIN.LOCAL
DateTime               : 05.08.2017-22:57:53
Console                : False
PSversion              : 4
MACAddress             : 00:1D:4A:B0:85:71
Memory                 : 8GB
Processor              : AMD Opteron(tm) Processor 4171 HE
Battery                :
BiosSN                 : 0000-0000-1581-8186-8769-9221-39
SystemManufacturer     : Microsoft Corporation
SystemModel            : Virtual Machine
Name                   : TS
OperatingSystem        : Windows Server 2012 R2 Datacenter
OperatingSystemVersion : 6.3 (9600)
CanonicalName          : domain.local/WorkStation/TEST

UserName               : olya
UserDomain             : DOMAIN.LOCAL
DateTime               : 05.08.2017-13:26:56
Console                : False
PSversion              : 2
MACAddress             : 51:AF:63:24:B7:A5
Memory                 : 2GB
Processor              : Intel(R) Celeron(R) CPU 1017U @ 1.60GHz
Battery                :
BiosSN                 : US00206137
SystemManufacturer     : LENOVO
SystemModel            : 10115
Name                   : WS45
OperatingSystem        : Windows 7 Профессиональная
OperatingSystemVersion : 6.1 (7601)
CanonicalName          : domain.local/WorkStation/WS45

UserName               : vasya
UserDomain             : DOMAIN.LOCAL
DateTime               : 05.08.2017-15:40:55
Console                : True
PSversion              : 4
MACAddress             : 00:35:AB:65:C1:F0
Memory                 : 4GB
Processor              : AMD A8-6410 APU with AMD Radeon R5 Graphics
Battery                :
BiosSN                 : VS81826243
SystemManufacturer     : LENOVO
SystemModel            : 10139
Name                   : WS41
OperatingSystem        : Windows 8.1 Корпоративная
OperatingSystemVersion : 6.3 (9600)
CanonicalName          : domain.local/WorkStation/WS41

UserName               : masha
UserDomain             : DOMAIN.LOCAL
DateTime               : 05.08.2017-11:32:55
Console                : True
PSversion              : 5
MACAddress             : C8:F2:38:7E:05:92
Memory                 : 4GB
Processor              : Intel(R) Pentium(R) CPU 3825U @ 1.90GHz
Battery                :
BiosSN                 : P9017JXU
SystemManufacturer     : LENOVO
SystemModel            : F0B400UKRK
Name                   : WS122
OperatingSystem        : Windows 10 Pro (Registered Trademark)
OperatingSystemVersion : 10.0 (14393)
CanonicalName          : domain.local/WorkStation/WS122



PS>

Ну и сам модуль, правда для адаптации для PowerShell v2 пришлось прибегнуть к небольшим костылям (к сожалению он еще используется).

Файл модуля — PSM1:

Function Set-PcDescription {
	$USERDNSDOMAIN = $env:USERDNSDOMAIN
	$COMPUTERNAME = $env:COMPUTERNAME
	$ObjectHash = @{}
	$ObjectHash.UserName = $env:USERNAME
	$ObjectHash.UserDomain = $USERDNSDOMAIN
	$ObjectHash.DateTime = "{0:dd.MM.yyyy-HH:mm:ss}" -f $(Get-Date)
	$ObjectHash.Console = ([Boolean]$((query.exe SESSION | findstr.exe ">console") -match $env:USERNAME)).ToString()
	$ObjectHash.PSversion = $Host.Version.Major.ToString()
	$ObjectHash.MACAddress = (Get-WmiObject Win32_NetworkAdapterConfiguration -Filter "IPEnabled = $True" | sort IPConnectionMetric | select -First 1).MACAddress
	$ObjectHash.Memory = [string]$((Get-WmiObject CIM_PhysicalMemory | measure -Property Capacity -Sum).Sum/1GB)+"GB"
	$ObjectHash.Processor = (Get-WmiObject Win32_Processor).Name
	$ObjectHash.Battery = (Get-WmiObject Win32_Battery).DeviceID
	$ObjectHash.BiosSN = (Get-WmiObject Win32_BIOS).SerialNumber
	$ObjectHash.SystemManufacturer = (Get-WmiObject Win32_ComputerSystem).Manufacturer
	$ObjectHash.SystemModel = (Get-WmiObject Win32_ComputerSystem).Model

	$select = "UserName,UserDomain,DateTime,Console,PSversion,MACAddress,Memory,Processor,Battery,BiosSN,SystemManufacturer,SystemModel" -split ","

	if([int]$Host.Version.Major -ge 3){
	$Description = New-Object -TypeName PSCustomObject -Property $ObjectHash | select $select | ConvertTo-Json -Compress
	} else {
		$Description = '{' + $(($select | % {'"'+"$($_)"+'"'+":"+'"'+"$($ObjectHash.$($_))"+'"'}) -join ",") + '}'
		}

	if(!$Description){$Description = "Null"}

	$DirectoryEntry = New-Object System.DirectoryServices.DirectoryEntry
	$USERDNSDOMAIN_DN = $DirectoryEntry.distinguishedName

	$Root = [ADSI]"LDAP://$USERDNSDOMAIN/$USERDNSDOMAIN_DN"
	$Root
	$SearchPC = new-object System.DirectoryServices.DirectorySearcher($Root)
	$SearchPC.Filter = "(&(objectClass=computer)(Name=$COMPUTERNAME))"
	$Computer = [ADSI]$($SearchPC.FindOne().Path)
	$Computer.put("Description",$Description)
	$Computer.SetInfo()

}

Function Get-PcDescription {
	Get-ADComputer -Filter * -Property Description,OperatingSystemVersion,OperatingSystem,CanonicalName | % {
		if ($_.Description) {
			ConvertFrom-Json -InputObject $_.Description |
			Add-Member -MemberType:NoteProperty -Name:Name -Value $_.Name -Force -PassThru |
			Add-Member -MemberType:NoteProperty -Name:OperatingSystem -Value $_.OperatingSystem -Force -PassThru |
			Add-Member -MemberType:NoteProperty -Name:OperatingSystemVersion -Value $_.OperatingSystemVersion -Force -PassThru |
			Add-Member -MemberType:NoteProperty -Name:CanonicalName -Value $_.CanonicalName -Force -PassThru
		}
	}
}

Файл манифеста — PSD1 — к моему удивлению PowerShell2 не понимает манифест с директивой RootModule, поэтому манифест тоже прикладываю.

#
# Module manifest for module 'domain local'
#

@{

# Script module or binary module file associated with this manifest.
# RootModule = 'PcDescription.psm1'

# Version number of this module.
ModuleVersion = '1.0'

# ID used to uniquely identify this module
GUID = 'e26eee34-34e4-4677-8a10-fce715f24182'

# Author of this module
Author = 'Saw-Friendship'

# Company or vendor of this module
CompanyName = 'domain.local'

# Copyright statement for this module
Copyright = 'Saw-Friendship'

# Description of the functionality provided by this module
Description = 'Module for domain.local'

# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '2.0'

# Name of the Windows PowerShell host required by this module
# PowerShellHostName = ''

# Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = ''

# Minimum version of Microsoft .NET Framework required by this module
# DotNetFrameworkVersion = ''

# Minimum version of the common language runtime (CLR) required by this module
# CLRVersion = ''

# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''

# Modules that must be imported into the global environment prior to importing this module
  RequiredModules = ''

# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()

# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()

# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()

# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()

# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
NestedModules = @('PcDescription.psm1')

# Functions to export from this module
FunctionsToExport = '*'

# Cmdlets to export from this module
# CmdletsToExport = ''

# Variables to export from this module
VariablesToExport = '*'

# Aliases to export from this module
AliasesToExport = '*'

# List of all modules packaged with this module
# ModuleList = @()

# List of all files packaged with this module
# FileList = @()

# Private data to pass to the module specified in RootModule/ModuleToProcess
# PrivateData = ''

# HelpInfo URI of this module
# HelpInfoURI = ''

# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''

}

Если кто-то забыл или не знал как собирать модуль:
Модуль psm1 и манифест psd1 сохраняем под одним и тем же именем PcDescription в папке с тем же именем PcDescription, модуль готов!

Кажется это все, что необходимо, но, если что-то не понятно, задавайте вопросы в комментариях!

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s