Некоторые атрибуты у объектов могут содержать достаточно большое количество значений. В этой заметке будем рассматривать на примере атрибута 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, можно прочитать:
- Просмотр и установка политики LDAP в Active Directory с помощью программы Ntdsutil.exe
- LDAP Policy в Active Directory
- 3.1.1.3.4.6 LDAP Policies
Нас будет интересовать параметр 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. Выполните следующие шаги:
- Start ADSI Edit. To do this, open a command prompt in the Support Tools folder, type ADSIEDIT.MSC, press Enter
- 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
- Click the Attribute Editor tab, and then locate dSHeuristic in the Attributes list.
NoteBy default, the value of this attribute is not set. - Click dSHeuristic, and then click Edit.
- Type 000000000100000001 in the Value box, and then click OK. See Notebelow.
- 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 отлично от значения по умолчанию
Посмотрим и изменим значение 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 не страдают ограничением на количество возвращаемых значений. Поэтому советую использовать данные командлеты.
Добавить комментарий