OM: Как закрыть активные алерты для здоровых мониторов

В Operations Manager иногда бывают ситуации, когда монитор переходит в здоровое состояние, но алерт продолжает висеть во вкладке активных алертов. В большинстве случаев это следствие ошибок оператора. Очень часто бывает, что система уже закрыла алерт, но оператор в это время заполняет поля алерта и возвращает ему прежнее состояние. Такой алерт самостоятельно уже не закроется. Чтобы не искать такие алерты вручную, можно воспользоваться следующим скриптом:

$Server = "ServerName"
$Credentials = Get-Credential
New-SCOMManagementGroupConnection -ComputerName $Server -Credential $Credentials
$ActiveAlerts = Get-SCOMAlert -Criteria "IsMonitorAlert = 1 AND ResolutionState < 255"
foreach ($Alert in $ActiveAlerts)
{
	$MonitoringRule = Get-SCOMMonitor -Id $Alert.MonitoringRuleId
	$MonitoringObject = Get-SCOMMonitoringObject -Id $Alert.MonitoringObjectId
	$MonitorList = New-Object System.Collections.Generic.List[Microsoft.EnterpriseManagement.Configuration.ManagementPackMonitor]
	$MonitorList.Add($MonitoringRule)
	$MonitoringState = $MonitoringObject.GetMonitoringStates($MonitorList)
	if ($MonitoringState.HealthState -eq [Microsoft.EnterpriseManagement.Configuration.HealthState]::Success)
	{
		$Alert
		$Alert | Resolve-SCOMAlert -Comment "The alert was manually closed because the monitor is in healthy state."
	}
}

Необходимо задать переменную $Server, указав имя сервера управления Operations Manager, после чего запустить скрипт.

OM: Windows Server Management Pack Mounted Point Bug

Внимание! В статье описана проблема, возникающая в пакетах управления Microsoft Windows Server 6.0.7292.0.

Буквально на днях обнаружил очень неприятный баг – в консоли SCOM по непонятной мне причине начали отображаться по два инстанса для каждого диска. Единственное, что их отличало друг от друга это наличие обратного слэша и описание устройства,- Logical Fixed Disk и Mounted Disk.

Logical Disc Bug

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

Первое, что я сделал – это отобрал все инстансы класса Microsoft.Windows.LogicalDisk с обратным слэшом через PowerShell:

$baddisk = get-scomclass -name "Microsoft.Windows.LogicalDisk" | Get-SCOMClassInstance | ? {$_.DisplayName -match "\\$"}

Затем, берем любой элемент из массива, например, самый первый и смотрим его ID:

$baddisk[0].Id

В моем случае ID элемента «533a2b61-8852-d031-523a-76fb9a8d0ec3».

Дальше необходимо получить ID правила обнаружения, которое создало этот инстанс. Делаем это через SQL запрос в оперативной базе данных:

SELECT ds.DiscoveryRuleId
FROM [OperationsManager].[dbo].[DiscoverySourceToTypedManagedEntity] dstme
INNER JOIN [OperationsManager].[dbo].[DiscoverySource] ds
ON dstme.DiscoverySourceId = ds.DiscoverySourceId
WHERE dstme.TypedManagedEntityId = '533a2b61-8852-d031-523a-76fb9a8d0ec3'

Мой запрос выдал мне ID «0EBF5558-248D-0B05-51D1-27A126C8E994»

Возвращаемся в PowerShell и смотрим, что это за правило обнаружения:

PowerShell Discovery

Видим, что это правило обнаружения «Microsoft.Windows.Server.2008.MountPoint.Discovery»(Mount Point Discovery Rule). Дальше экспортируем пакет с помощью команды (в моем примере пакет будет экспортирован в корень диска C:):

Get-SCOMManagementPack -Name "Microsoft.Windows.Server.2008.Discovery" | Export-SCOMManagementPack -Path C:\

Открываем его и ищем правило с именем «Microsoft.Windows.Server.2008.MountPoint.Discovery»:

Discovery Rule

Видим, что в качестве источника для данного правила обнаружения используется Data Source модуль с именем «Microsoft.Windows.Server.2008.MountPointDiscovery.ModuleType». Находим его:

Data Source

Содержимое данного модуля – скрипт. Самое интересное находится внизу скрипта. Видим, что данный скрипт опрашивает класс WMI Win32_MountPoint, и выполняет запрос для каждой директории, сравнивая имя директории с именем буквы диска класса Win32_Volume, значения которого записываются в выходные данные обнаружения. НО! Как я уже писал выше, как раз этим скриптом и создаются неверные инстансы дисков – возвращается DeviceID, равное имени буквы диска с обратным слэшом. Если мы опросим класс WMI Win32_Volume и посмотрим скрипт, то увидим, что в строках возвращаются значения Name, которые являются буквой со слэшом.

Data Source Script

Что в данной ситуации нужно сделать? Необходимо создать переопределение (Override) для правила обнаружения «Mount Point Discovery Rule» — переключить флаг «Enabled» в «false» и выполнить команду Remove-SCOMDisabledClassInstance. На данный момент пока нет каких либо хотфиксов как, например, делает это Алексей Журавлев, а сам я его не пишу, т.к. не знаю, что в действительности должно возвращаться, поэтому нужно ждать, пока разработчики исправят эту досадную ошибку.

CM: Еще раз о членстве локальных групп

Давным-давно я (да и не только я) уже писал о том, как можно получить отчет по членам локальных групп. Сегодня, поковырявшись с WMI и оглядываясь назад, решил, что код выглядит достаточно некрасиво, да и сам по себе класс имеет некоторые недостатки. Так, например, если членом локальной группы будет локальный и доменный пользователь с одним и тем же именем, то объект создастся лишь для одной записи. В общем-то сам по себе скрипт поменялся не сильно, а вот класс Win32_LocalGroupMember, если вы уже его использовали, придется пересоздать, т.к. теперь ключевыми свойствами является SID группы и SID учетной записи, что даст абсолютную уникальность каждому объекту в WMI. Также по данному классу теперь можно узнать, является ли пользователь локальным или доменным (MemberLocalAccount), а также SID данного пользователя (MemberSID).

Собственно, сам скрипт:

Set oWMI = GetObject("winmgmts:root\cimv2")
iCimtypeString = 8
iCimtypeBoolean = 11

sClassName = "Win32_LocalGroupMember"
sLocalGroupQuery = "SELECT Domain, Name, SID FROM Win32_Group WHERE LocalAccount=TRUE"
sComputerSystemQuery = "SELECT DomainRole FROM Win32_ComputerSystem"

For Each oSubclass in oWMI.SubclassesOf()
    If oSubclass.Path_.Class = sClassName then oWMI.Get(sClassName).Delete_
Next

Set oClass = oWMI.Get()
oClass.Path_.Class = sClassName
Call oClass.Properties_.add("MemberName", iCimtypeString)
Call oClass.Properties_.add("MemberDomain", iCimtypeString)
Call oClass.Properties_.add("MemberType", iCimtypeString)
Call oClass.Properties_.add("MemberLocalAccount", iCimtypeBoolean)
Call oClass.Properties_.add("MemberSID", iCimtypeString)
Call oClass.Properties_.add("GroupName", iCimtypeString)
Call oClass.Properties_.add("GroupDomain", iCimtypeString)
Call oClass.Properties_.add("GroupSID", iCimtypeString)
Call oClass.Properties_("GroupSID").Qualifiers_.add("key", True)
Call oClass.Properties_("MemberSID").Qualifiers_.add("key", True)
Call oClass.Put_()

Set oClass = oWMI.Get(sClassName).SpawnInstance_

For Each oGroup in oWMI.ExecQuery(sLocalGroupQuery)
	sAssociatorsQuery = "ASSOCIATORS OF {Win32_Group.Domain='"&oGroup.Domain&"',Name='"&oGroup.Name&"'} WHERE Role=GroupComponent"
	For Each oAccount in oWMI.ExecQuery(sAssociatorsQuery)
		oClass.MemberName = oAccount.Name
		oClass.MemberDomain = oAccount.Domain
		oClass.MemberSID = oAccount.SID
		oClass.MemberType = Replace((oAccount.Path_.Class), "Win32_", "")
		oClass.MemberLocalAccount = oAccount.LocalAccount
		oClass.GroupName = oGroup.Name
		oClass.GroupDomain = oGroup.Domain
		oClass.GroupSID = oGroup.SID
		Call oClass.Put_()
	Next
Next

И сам MOF:

//**************************************************************************
//* Class: Win32_LocalGroupMember
//**************************************************************************
 
[ SMS_Report     (TRUE),
  SMS_Group_Name ("Local Group Member"),
  SMS_Class_ID   ("CUSTOM|LocalGroupMember|1.0") ]
class Win32_LocalGroupMember : SMS_Class_Template
{
    [SMS_Report (TRUE)      ] string MemberName;
    [SMS_Report (TRUE)      ] string MemberDomain;
    [SMS_Report (TRUE), key ] string MemberSID;
    [SMS_Report (TRUE)      ] string MemberType;
    [SMS_Report (TRUE)      ] boolean MemberLocalAccount;
    [SMS_Report (TRUE)      ] string GroupName;
    [SMS_Report (TRUE)      ] string GroupDomain;
    [SMS_Report (TRUE), key ] string GroupSID;
};

О том, как опубликовать и использовать в отчетах данный пример можно прочитать в предыдущей статье о членстве групп, все имена свойств класса остались прежними.
Во вложении к статье можно скачать сам скрипт и MOF файл. Не забудьте поменять расширение на .zip.

OM: Небольшой набор функций для работы с MonitoringClass

Это совсем небольшая статья с набором PowerShell скриптов, которые были написаны мной для удобства работы с Operations Manager 2007 R2.

Function Get-MonitoringClassKeyProperty {
	Param(
		[Parameter(Mandatory = $True, ValueFromPipeline = $True)]$MonitoringClass
		)
	Function Get-MonitoringClassKeyPropertyRecursively {
		Param(
			$MonitoringClass
		)
		$MonitoringClass | Get-MonitoringClassProperty | ? {$_.Key}
		If ($MonitoringClass.Hosted) {
			Get-MonitoringClassKeyPropertyRecursively -MonitoringClass $MonitoringClass.FindHostClass()
		}
		If ($MonitoringClass.Base) {
			Get-MonitoringClassKeyPropertyRecursively -MonitoringClass (Get-MonitoringClass -Id $MonitoringClass.Base.Id)
		}
	}
	Get-MonitoringClassKeyPropertyRecursively -MonitoringClass $MonitoringClass | Select-Object -Unique
}

Function Get-MonitoringClassAllProperty {
	Param(
		[Parameter(Mandatory = $True, ValueFromPipeline = $True)]$MonitoringClass
	)
	Function Get-MonitoringBaseClassPropertyRecursively {
		Param (
			$MonitoringClass
		)
		$MonitoringClass | Get-MonitoringClassProperty
		If ($MonitoringClass.Base) {
			Get-MonitoringBaseClassPropertyRecursively -MonitoringClass (Get-MonitoringClass -Id $MonitoringClass.Base.Id | Select-Object -Unique)
		}
	}
	[Array]$MonitoringProperty = Get-MonitoringBaseClassPropertyRecursively -MonitoringClass $MonitoringClass
	$MonitoringProperty += Get-MonitoringClassKeyProperty -MonitoringClass $MonitoringClass
	$MonitoringProperty | Select-Object -Unique
}

Команда Get-MonitoringClassKeyProperty возвращает все ключевые свойства класса.
Команда Get-MonitoringClassAllProperty возвращает все ключевые свойства и свойства родительских классов.

CM: Создание переменной TS — группа локальных администраторов

Совсем небольшой скрипт, который возвращает в переменную Task Sequence «OSDAdministratorsGroup» имя локальной группы администраторов:

Set oTaskSequence = CreateObject("Microsoft.SMS.TSEnvironment")
Set oWMIcimv2 = GetObject("winmgmts:root\cimv2")
Set oQuery = oWMIcimv2.execquery("Select Name from Win32_Group Where SID = 'S-1-5-32-544' and LocalAccount = 1")
For each oAdmGroup in oQuery
	oTaskSequence("OSDAdministratorsGroup") = replace(oAdmGroup.Name,"^.+\\","")
Next

Скопируйте данный код и назовите его, например OSDAdministratorsGroup.vbs и запустите его в одном из шагов Task Sequence.
Для чего нужен данный скрипт? Например, в ходе развертывания ОС вам нужно добавить локальных администраторов. Быстро это можно сделать средствами команды net localgroup, но не всегда известно имя группы администраторов. Самый распространенный случай — в русской версии Администраторы, в английской — Administrators. После того, как скрипт отработал, можно смело ввести команду, например

net localgroup %OSDAdministratorsGroup% /add CONTOSO\sccm_privaccount

и не беспокоиться о том, какая версия ОС у вас установлена.

OSD: «Склеивание» WIM файлов

Решил написать совсем небольшой скрипт, который склеивает WIM-файлы из определенной директории в один WIM-файл. Для работы скрипта требуется Windows AIK, а точнее утилита ImageX.
Merge-WimFile.ps1:

Param (
	[Parameter(mandatory = $true)][string]$Path,
	[Parameter(mandatory = $true)][string]$Destination,
	[Parameter(mandatory = $false)][string]$Filter
)

$Imagex = "$env:ProgramFiles\Windows AIK\Tools\$env:processor_architecture\Imagex.exe"
$WimFiles = Get-ChildItem -Path $Path -Filter $Filter
if (Test-Path $Imagex) {
	foreach ($WimFile in $WimFiles) {
		&$Imagex /Export $WimFile.fullname 1 $Destination ($WimFile.Name -replace $WimFile.Extension)
	}
}
else {
	Write-Error "ImageX.exe not found."
}

В данном скрипте необходимо задать параметры -Path — путь до директории с WIM-файлами и -Destination — путь до WIM-файла, в который будут сохраняться все образы. Можно также задать параметр -Filter, в котором можно задать маску файлов для склеивания. Склеивание образа имеет существенный плюс — размер. В WIM образ файлы попадают в единственном числе и если какой либо файл уже есть в WIM-файле, то на него просто будет создана ссылка.

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

Splited Files
Files before

А вот размер одного WIM-файла, который содержит в себе эти же самые семь образов:
Image Before

Вот как выглядит склеенный загруженный образ в Configuration Manager:
Image in ConfigMgr

Если Вы обратили внимание, то скрипт написан так, что он присваивает к имени образа имя файла. Но у склеенного образа есть и обратная сторона — если образы сильно отличаются друг от друга, то размер файла будет довольно большим, а преимущества никакого не будет и также, в случае, когда контент загружается на локальный диск с точки распространения, а не запускается напрямую с нее, то время на доставку уйдет больше. Выбирать Вам!

SUP: Для тех, кто не любит ждать

Совсем небольшой скрипт для тех случаев, когда ОС нужно обновить, но по какой-то причине установка обновлений не прошла в процессе OSD. Процесс обновления в Configuration Manager требует большего времени, нежели установка обновлений через WSUS, поэтому это наиболее подходящий способ быстро обновиться.

# Путь до исходной папки с обновлениями
$Path = "\\Domain\ConfigMgr\Updates\Microsoft\Windows Server 2008 R2"
$Updates = Get-ChildItem -Path $Path -Filter "*.cab" -Recurse
Foreach ($Update in $Updates) {
	&Dism /Online /Add-Package:$($Update.fullname) /NoRestart
}

В моем случае в качестве пути используется доменный DFS-link. В переменной Path требуется указать путь до папки с обновлениями. Такой трюк пройдет только для ОС Windows Server 2008/Vista SP2 и выше.