Feeds:
Записи
Комментарии

Archive for Сентябрь 2012

Некоторые атрибуты у объектов могут содержать достаточно большое количество значений. В этой заметке будем рассматривать на примере атрибута member с использованием средств .Net. Предположим,что у нас есть группа с количество членов 6000. Используя акселератор [ADSI],попробуем получить количество членов в группе:

PS >$gr= [ADSI]«LDAP://CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com«
PS >$gr.member.count
1500

Мы получили значение 1500,что не соответствует  6000. Теперь разберемся почему так и как можно обойти это ограничение по умолчанию.

1) Политики LDAP в Active Directory

Чтобы убедиться, что контроллеры домена могут поддерживать заданный уровень  обслуживания , вам нужно указать, эксплуатационных ограничений для ряда Lightweight Directory Access Protocol (LDAP) операций. Эти ограничения предотвращения в конкретных операций отрицательно влияющих на производительность сервера, а также сделать сервер более устойчивым к DDoS-атакам.

Применение LDAP политики осуществляется с помощью объектов класса queryPolicy. Query Policy объекты могут быть созданы в контейнере Query Policy, который является дочерним Directory Service контейнер в  контексте именования Сonfiguration. Например:

cn=Query-Policies, cn=Directory Service, cn=Windows NT, cn=Services,CN=Configuration,DC=forest root

Контроллер домена использует следующие три механизма для применения LDAP политики:

  • Контроллер домена может ссылаться на конкретные LDAP политики. В nTDSASettings объект включает в себя дополнительный атрибут queryPolicyObject, которая содержит различающееся имя(distinguished name,DN)  Query Policy.
  • В отсутствие конкретного query policy, применяемых к контроллеру домена, контроллер домена применяется Query Policy, который был назначен для контроллера домена сайта. В ntDSSiteSettings объект включает в себя дополнительный атрибут queryPolicyObject, которая содержит различающееся имя Запроса Политики.
  • В отсутствие конкретного контроллера домена или сайта Query Policy, контроллер домена использует запросов по умолчанию политики имени по Default-Query Policy.

Для изменения и просмотра Query Policy, мы может воспользоваться несколькими инструментами:

  • ntdsutil
  • dsmgmt
  • оснастка ADSI Editor
  • ldp

Более подробно про параметры Query Policy, можно прочитать:

Нас будет интересовать параметр MaxValRange:

MaxValRange — данное значение определяет количество возвращаемых для атрибута объекта значений, вне зависимости от количества атрибутов объекта или количество объектов в результате поиска. В Windows 2000 для этого параметра установлено значение 1 000. Если количество значений атрибута превышает указанное значение MaxValRange, для получения значений, превышающих значение MaxValRange, необходимо использовать параметр дипазона значений в LDAP. MaxValueRange определяет количество значений, возвращаемых для одного атрибута одного объекта.

ОС контроллера домена Значение по умолчанию
Windows Server 2000* 1000
Windows Server 2003 1500
Windows Server 2003 R2 1500
Windows Server 2008 1500
Windows Server 2008 R2 1500
Windows Server 2012 1500

* — Windows 2000 DCs не поддерживают данную политику и всегда используют значение 1000.

Значение параметра MaxValRange для 2008,2008 R2,2012 жестко ограниченно в 5000.

Это значение можно переопределить,подробнее:

Для переопределения верхнего лимита введенного в Windows Server 2008/R2 и восстановления старого стиля (нет верхнего предела ограничения для LDAP Query Policy в Windows Server 2003), требуется изменить атрибут dSHeuristic в Active Directory. Выполните следующие шаги:

  1. Start ADSI Edit. To do this, open a command prompt in the Support Tools folder, type ADSIEDIT.MSC, press Enter
  2. Right-click CN=Directory Service in the following location, and then click Properties: CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=forest root
  3. Click the Attribute Editor tab, and then locate dSHeuristic in the Attributes list.
    NoteBy default, the value of this attribute is not set.
  4. Click dSHeuristic, and then click Edit.
  5. Type 000000000100000001 in the Value box, and then click OK. See Notebelow.
  6. Restart the Active Directory Domain Service (NTDS) or the domain controller.

Note If a value has already been set for this attribute, incorporate the existing settings into the new value. When you do this, note the following:

  • The tenth character from the left must be 1. Twentieth bit must be 2, and so on.
  • The eighteenth character from the left must be 1.
  • None of the other characters of the existing value should be changed. For instance, if the existing value is 0000002 then the new value should be 000000200100000001

Будьте осторожны с изменением значения MaxValRange, это может вызвать проблемы:

Посмотрим и изменим значение MaxValRange:

C:\Windows\system32\ntdsutil.exe: ldap policies
ldap policy: connect
server connections: connect to server srv-dc
Binding to srv-dc ...
Connected to srv-dc using credentials of locally logged on user.
server connections: q
ldap policy: show values

Policy                          Current(New)

MaxPoolThreads                  4
MaxDatagramRecv                 4096
MaxReceiveBuffer                        10485760
InitRecvTimeout                 120
MaxConnections                  5000
MaxConnIdleTime                 900
MaxPageSize                     1000
MaxBatchReturnMessages                  0
MaxQueryDuration                        120
MaxTempTableSize                        10000
MaxResultSetSize                        262144
MinResultSets                   0
MaxResultSetsPerConn                    0
MaxNotificationPerConn                  5
MaxValRange                     1500

ldap policy: setMaxValRangeto5000
ldap policy: Commit Changes
ldap policy: q
C:\Windows\system32\ntdsutil.exe: q

Теперь опять выполним код:

PS > $gr= [ADSI]"LDAP://CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com"PS > $gr.member.count
5000
Данный способ подходит в случае если у вас точно нет атрибутов с количеством значений 
больше 5000 или используйте способ выше,не поддерживаемый Microsoft.
Поэтому воспользуемся механизмом "range retrieval".

2) Поиск с использованием атрибута Range 

При получении значений многозначного атрибута, Active Directory ограничивает количество значений, которые могут  быть извлечены из одного атрибута в одном поисковом запросе. Максимальное количество значений, которые будут возвращены в одном запросе определяется параметром MaxValRange. Для разрешения всех значений многозначного атрибута, используется механихм  «range retrieval». Этот механизм позволяет клиенту указанное подмножество значений, которые будут получены в поисковом запросе. При выполнении нескольких поисковых запросов, каждый запрос возвращает различное подмножество, полный набор значений атрибута может быть извлечен.

Спецификатор Range для атрибута запроса требуют следующую форму:

range=<low range>-<high range>

<low range> — начинается с нуля и является первый элементом атрибутом

<high range> — является отсчитываемый от нуля индекс извлекаемого последнего значения атрибута.Подстановочный знак (*) может использоваться для <high range> , для указания всех оставшихся записей.

В следующей таблице перечислены примеры диапазон спецификаторы.

Пример Значение
range=0-* Возвращает все значения атрибута. Ограничен параметром MaxValRange(по умолчанию 1500).
range=0-500 Возвращает с 1 по 501 значение атрибута.
range=2-3 Возвращает 3 и 4 значение атрибута.
range=501-* Возвращает с 502 и все оставшиеся значения атрибута.Ограничен параметром MaxValRange(по умолчанию 1500).

Когда сервер Active Directory возвращает значения атрибута member в результате запроса поиска каталога, его поведение  зависит от общего количества значений атрибута для этого объекта, если превышает максимальный предел  значения. Например если список рассылки на сервере Windows 2000 Server содержит 1000 или меньше значения членов, запрос поиска возвращает все значения в один вызов. Однако, если список содержит значения членов 2497, первый вызов функции запросов поиска возвращает атрибут member без значений и дополнительного member; range = 0-999 атрибут, содержащий первые 1000 значений. Для получения следующей группы значений , следует повторить запрос поиска с помощью спецификатора range, который начинается с последнего номера значения, возвращаемого из предыдущего запроса. В этом примере функция запроса поиска будет запрашивать значения атрибута member;range = 1000-* , вернет перую 1000 значений атрибута  и member; range = 1000-1999 вернет следующие 1000 значений атрибута. Этот процесс повторяется до тех пор, пока не будет получено последнее значени. Конец диапазона последней группы, полученных с сервера будет указываться (звездочка) в имени возвращенного атрибута. Для 2497: member;range=0-998 – вернет первую 1000 значений member;range=999-1998 – вернет вторую 1000 значений member;range=1998-* – вернет все оставшиеся значения Для 0-999: member;range=0-* Перейдем к коду на PowerShell для получения значений атрибута member. Для этого воспользуемся классом DirectorySearcher,как одним из вариантов решения, так же можно посмотреть решение с использованием ADO (скрипт предоставил MVP Directory Service Richard Mueller — EnumLargeGroup.ps1).

#Нижний предел

$LowRange=0
#Шаг
#To enable an application to work correctly with all Windows servers, 
#a maximum number of 1000 should be used.

$RangeStep=999

#Создаем бесконечный цикл,выход из которого будет происходить при возникновении
#ошибки,в случае достижения конечного значения атрибута member

while ($true)
{
    #Врехний предел(0-998 и т.д.)

$HighRange=$LowRange+$RangeStep-1

#Атрибут

$Properties="member;range=$LowRange-$HighRange"

#Конструктор для DirectorySearcher, AdsPath - Dn,$Filter -может быть любой                                                   $Searcher=New-ObjectDirectoryServices.DirectorySearcher(
        $AdsPath, $Filter, $Properties, "Base"
    )

    #Получаем значение атрибута membertry {
        $Group=$Searcher.FindOne().Properties

        $Attribute= ($Group.GetEnumerator() | 
            Where {$_.Name -match"member"}).Name

        $Group[$Attribute]
    }

    catch {
         break
    }

    $LowRange+=$RangeStep
}

Я написал функцию для более удобного использования.

Function Get-GroupMember
{
    [CmdletBinding()]
    Param (
	    [Parameter(Mandatory=$true,ValueFromPipeline=$true,
			ValueFromPipelineByPropertyName=$true)]
	    [Alias("DN","DistinguishedName")]
	    $Name,
	    [uint32]$HighRange,
	    [uint32]$LowRange = 0,
	    [switch]$CountMembers,
	    [string]$Server
    )

    begin {
        $RangeStep = 999
		$Filter = "(&(objectClass=Group)(objectCategory=Group))"
    }

	    process {
		$LowRange = [int]$PSBoundParameters["LowRange"]

		if($LowRange -gt 0) {$LowRange -= 1}
		if($HighRange -gt 0) {$HighRange -= 1}

		$Members = @()

		if($Server) {
			$AdsPath = "LDAP://$Server/$Name"
		}
		else {
			$AdsPath = "LDAP://$Name"
		}

		$IsExists = $false

		if([DirectoryServices.DirectoryEntry]::Exists($AdsPath)) {
			if($HighRange) {
			    while ($LowRange -lt $HighRange)
			    {
			        $MiddleRange = $LowRange + $RangeStep -1
					if($MiddleRange -gt $HighRange) {
						 $Properties = "member;range=$LowRange-$HighRange"
					}
					else {
					 	$Properties = "member;range=$LowRange-$MiddleRange"
					}

			        $Searcher = New-Object DirectoryServices.DirectorySearcher(
						$AdsPath, $Filter, $Properties, "Base"
					)

			        try {
				        $Group = $Searcher.FindOne().Properties

						$Attribute = ($Group.GetEnumerator() |
								Where {$_.Name -match "member"}).Name

						Write-Verbose "$Name - $Attribute"
				        $Members += $Group[$Attribute]
			        }

					catch {
			            break
			        }

			        $LowRange  += $RangeStep
			    }
			}

			else {
				while ($true)
			    {
			        $HighRange = $LowRange + $RangeStep -1
			        $Properties = "member;range=$LowRange-$HighRange"

			        $Searcher = New-Object DirectoryServices.DirectorySearcher(
						$AdsPath, $Filter, $Properties, "Base"
					)

			        try {
				        $Group = $Searcher.FindOne().Properties

						$Attribute = ($Group.GetEnumerator() |
								Where {$_.Name -match "member"}).Name

						Write-Verbose "$Name - $Attribute"
				        $Members += $Group[$Attribute]
			        }

					catch {
			            break
			        }

			        $LowRange  += $RangeStep
			    }
			}

			$IsExists = $true
		}

		else {
			Write-Host "The path $Name is invalid" -ForegroundColor Yellow
		}

		if ($IsExists)
		{
			if($CountMembers){
				$Members.Count
			}

			else {
				$Members
			}
		}
    }
}

Примеры работы функции:

1) Получить количество членов в группе


PS > Get-GroupMember "CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com" -CountMembers
6000
PS > "CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com" | Get-GroupMember -CountMembers
6000

PS > Get-GroupMember "CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com" -CountMembers -Verbose
VERBOSE: CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com - member;range=0-998
VERBOSE: CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com - member;range=999-1997
VERBOSE: CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com - member;range=1998-2996
VERBOSE: CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com - member;range=2997-3995
VERBOSE: CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com - member;range=3996-4994
VERBOSE: CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com - member;range=4995-5993
VERBOSE: CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com - member;range=5994-*
6000

2)Получить первые 500 значений


PS > Get-GroupMember "CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com" -HighRange 500 -CountMembers
500
PS > Get-GroupMember "CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com" -HighRange 500 -CountMembers
-Verbose
VERBOSE: CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com - member;range=0-499
500

3)Получить значения с 1000 по 3000


PS > Get-GroupMember "CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com" -LowRange 1000 -HighRange 3000 -CountMembers -Verbose
VERBOSE: CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com - member;range=999-1997
VERBOSE: CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com - member;range=1998-2996
VERBOSE: CN=Big,OU=Groups,OU=Contoso,DC=contoso,DC=com - member;range=2997-2999
2001

PS. Оба метода являются громоздкими,но более производительными нежели командлеты из модуля для Active Directory. Командлеты Get-ADGroupMember,Get-ADObject не страдают ограничением на количество возвращаемых значений. Поэтому советую использовать данные командлеты.

Реклама

Read Full Post »