-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathGet-TokenSizeReport.ps1
599 lines (522 loc) · 28.5 KB
/
Get-TokenSizeReport.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
<#
This script will enumerate all enabled user accounts in a Domain, calculate their estimated Token
Size and create two reports in CSV format:
1) A report of all users with an estimated token size greater than or equal to the number defined
by the $TokensSizeThreshold variable.
2) A report of the top x users as defined by the $TopUsers variable.
Syntax:
- To run the script against all enabled user accounts in the current domain:
Get-TokenSizeReport.ps1
- To run the script against all enabled user accounts of a trusted domain:
Get-TokenSizeReport.ps1 -TrustedDomain:mytrusteddomain.com
- To run the script against 1 user account:
Get-TokenSizeReport.ps1 -AccountName:<samaccountname>
Script Name: Get-TokenSizeReport.ps1
Release 2.1
Modified by Jeremy@jhouseconsulting.com 01/12/2014
Written by Jeremy@jhouseconsulting.com 13/12/2013
Original script was derived from the CheckMaxTokenSize.ps1 written by Tim Springston [MS] on 7/19/2013
http://gallery.technet.microsoft.com/scriptcenter/Check-for-MaxTokenSize-520e51e5
Re-wrote the script to be more efficient and provide a report for all users in the
Domain.
References:
- Microsoft KB327825: Problems with Kerberos authentication when a user belongs to many groups
http://support.microsoft.com/kb/327825
- Microsoft KB243330: Well-known security identifiers in Windows operating systems
http://support.microsoft.com/kb/243330
- Microsoft KB328889: Users who are members of more than 1,015 groups may fail logon authentication
http://support.microsoft.com/kb/328889
- Microsoft KB938118: How to use Group Policy to add the MaxTokenSize registry entry to multiple computers
http://support.microsoft.com/kb/938118
- Microsoft Blog: Managing Token Bloat:
http://blogs.dirteam.com/blogs/sanderberkouwer/archive/2013/05/22/common-challenges-when-managing-active-directory-domain-services-part-2-unnecessary-complexity-and-token-bloat.aspx
Although it's not documented in KB327825 or any other Microsoft references, I also add the number of
global groups outside the user's account domain that the user is a member of to the "d" calculation of
the TokenSize. Whilst the Microsoft methodology is to add universal groups from other domains, it is
possible to add global groups too. Therefore it's important to capture this and correctly include it in
the calculation.
My calculations consider the BuiltIn groups as Domain Local groups, which means I'm allowing 40 bytes
per BuiltIn group that the user is a member of. Others seem to only allow 8 bytes in their calculations.
However depending on the length of the SID, BuiltIn groups are actually either 16 or 28 bytes in reality.
Therefore, whilst I may be overcompensating for some of the groups, others are always underestimating.
If we wanted to be as accurate as possible we can calculate the byte length of each SID and then add a
further 16 bytes for associated attributes and information. Most user and group SIDs are 28 bytes in
length.
Refer to the following thread to get a full understanding how this needs to be calculated for complete
acuracy. You also need to understand how the token is managed in 4KB blocks. It starts to make sense
when you tie together the comments from Paul Bergson, Richard Mueller and Marcin Polich:
- https://social.technet.microsoft.com/Forums/windowsserver/en-US/a7035740-355f-4a8c-a434-27583e3b6075/probably-a-maxtokensize-problem-but?forum=winserverDS
There is some further good information on SIDs published by Philipp Fockeler:
- http://www.selfadsi.org/deep-inside/microsoft-sid-attributes.htm
For users with large tokens consider reducing direct and transitive (nested) group memberships.
Larger environments that have evolved over time also have a tendancy to suffer from Circular Group
Nesting and sIDHistory.
It's important to note that with Windows 2012 Active Directory, compression enhancements were added to
the KDC functionality. Therefore the formula used in this script does not apply to a pure Windows Server
2012.
On the odd ocasion I was receiving the following error:
- Exception calling "FindByIdentity" with "2" argument(s): "Multiple principals contain
a matching Identity."
- There seemed to be a known bug in .NET 4.x when passing two arguments to the FindByIdentity() method.
- The solution was to either use a machine with .NET 3.5 or re-write the script to pass
three arguments as per the Get-UserPrincipal function provided in the following Scripting Guy article:
- http://blogs.technet.com/b/heyscriptingguy/archive/2009/10/08/hey-scripting-guy-october-8-2009.aspx
This function passes the Context Type, FQDN Domain Name and Parent OU/Container.
- Other references:
- http://richardspowershellblog.wordpress.com/2008/05/27/account-management-member-of/
- http://www.powergui.org/thread.jspa?threadID=20194
I have also seen the following error:
- Exception calling "GetAuthorizationGroups" with "0" argument(s): "An error (1301) occurred while
enumerating the groups. The group's SID could not be resolved."
- Other references:
- http://richardspowershellblog.wordpress.com/2008/05/27/account-management-member-of/
- https://groups.google.com/forum/#!topic/microsoft.public.adsi.general/jX3wGd0JPOo
- http://lucidcode.com/2013/02/18/foreign-security-groups-in-active-directory/
Added the tokenGroups attribute to get all nested groups as I could not achieve 100% reliability using
the GetAuthorizationGroups() method. Could not afford for it to start failing after running for hours
in large environments.
- References:
- http://www.msxfaq.de/code/tokengroup.htm
- http://www.msxfaq.de/tools/dumpticketsize.htm
There are important differences between using the GetAuthorizationGroups() method versus the tokenGroups
attribute that need to be understood. Aside from the unreliability of GetAuthorizationGroups(), when push
comes to shove you get different results depending on which method you use, and what you want to achieve.
- The tokenGroups attribute only contains the actual "Active Directory" principals, which are groups and
siDHistory.
- However, tokenGroups does not reveal cross-forest/domain group memberships. The tokenGroups attribute
is constructed by Active Directory on request, and this depends on the availability of a Global Catalog
server: http://msdn.microsoft.com/en-us/library/ms680275(VS.85).aspx
- The GetAuthorizationGroups() method also returns the well-known security identifiers of the local
system (LSALogonUser) for the user running the script, which will include groups such as:
- Everyone (S-1-1-0)
- Authenticated Users (S-1-5-11)
- This Organization (S-1-5-15)
- Low Mandatory Level (S-1-16-4096)
This will vary depending on where you're running the script from and in what user context. The result
is still consistent, as it adds the same overhead to each user. But this is misleading.
- GetAuthorizationGroups() will return cross-forest/domain group memberships, but cannot resolve them
because they contain a ForeignSecurityPrincipal. It therefore fails as documented above.
- GetAuthorizationGroups() does not contain siDHistory.
In my view you would use the tokenGroups attribute to collate a consistent and accurate user report across
the environment, whereas the GetAuthorizationGroups() method could be used in a logon script to calucate
the token of the user together with the system they are logging on to. The actual calculation of the token
size adds the estimated value for ticket overhead anyway, hence the reason why using the tokenGroups
attribute provides a consistent result for all users.
If you wanted an accurate token size per user per system and GetAuthorizationGroups() method continues to
prove to be unreliable, you could use the tokenGroups attribute together with the addition of the output
from the "whoami /groups" command to get all the well-known groups and label needed to calculate the
complete local token.
Microsoft also has a tool called Tokensz.exe that could also be used in a logon script. It can be downloded
from here: http://www.microsoft.com/download/en/details.aspx?id=1448
To be completed:
- Some further research and testing needs to be completed with the code that retrieves the tokenGroups
attribute to validate performance between the GetInfoEx method or RefreshCache method.
- Work out how to report on cross-forest/domain group memberships as neither tokenGroups or
GetAuthorizationGroups() can achieve this.
#>
#-------------------------------------------------------------
param([String]$AccountName,[String]$TrustedDomain)
#-------------------------------------------------------------
# Set this to the OU structure where the you want to search to
# start from. Do not add the Domain DN. If you leave it blank,
# the script will start from the root of the domain.
$OUStructureToProcess = ""
# Set this value to the number of users with large tokens that
# you want to report on.
$TopUsers = 200
# Set this to the size in bytes that you want to capture the user
# information for the report.
$TokensSizeThreshold = 1000
# Set this value to true if you want to output to the console
$ConsoleOutput = $True
# Set this value to true if you want a summary output to the
# console when the script has completed.
$OutputSummary = $True
# Set this value to true to use the tokenGroups attribute
$UseTokenGroups = $True
# Set this value to true to use the GetAuthorizationGroups() method
$UseGetAuthorizationGroups = $False
# Set this to the delimiter for the CSV output
$Delimiter = ","
# Set this to remove the double quotes from each value within the
# CSV.
$RemoveQuotesFromCSV = $False
# Set this value to true if you want to see the progress bar.
$ProgressBar = $True
#-------------------------------------------------------------
Function Get-UserPrincipal($cName, $cContainer, $userName)
{
$dsam = "System.DirectoryServices.AccountManagement"
$rtn = [reflection.assembly]::LoadWithPartialName($dsam)
$cType = "domain" #context type
$iType = "SamAccountName"
$dsamUserPrincipal = "$dsam.userPrincipal" -as [type]
$principalContext = new-object "$dsam.PrincipalContext"($cType,$cName,$cContainer)
$dsamUserPrincipal::FindByIdentity($principalContext,$iType,$userName)
} # end Get-UserPrincipal
Function Test-DotNetFrameWork35
{
Test-path -path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5'
} #end Test-DotNetFrameWork35
Function Test-DotNetFrameWork4
{
Test-path -path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full'
} #end Test-DotNetFrameWork4
If(-not(Test-DotNetFrameWork35) -AND -not(Test-DotNetFrameWork4)) { "Requires at least .NET Framework 3.5" ; exit }
$invalidChars = [io.path]::GetInvalidFileNamechars()
$datestampforfilename = ((Get-Date -format s).ToString() -replace "[$invalidChars]","-")
# Get the script path
$ScriptPath = {Split-Path $MyInvocation.ScriptName}
$ReferenceFile = $(&$ScriptPath) + "\KerberosTokenSizeReport-$($datestampforfilename).csv"
$ReferenceFileTopUsers = $(&$ScriptPath) + "\KerberosTokenSizeReport-TopUsers-$($datestampforfilename).csv"
if (Test-Path -path $ReferenceFile) {
remove-item $ReferenceFile -force -confirm:$false
}
if (Test-Path -path $ReferenceFileTopUsers) {
remove-item $ReferenceFileTopUsers -force -confirm:$false
}
if ([String]::IsNullOrEmpty($TrustedDomain)) {
# Get the Current Domain Information
$domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
} else {
$context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("domain",$TrustedDomain)
Try {
$domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($context)
}
Catch [exception] {
write-host -ForegroundColor red $_.Exception.Message
Exit
}
}
# Get AD Distinguished Name
$DomainDistinguishedName = $Domain.GetDirectoryEntry() | select -ExpandProperty DistinguishedName
If ($OUStructureToProcess -eq "") {
$ADSearchBase = $DomainDistinguishedName
} else {
$ADSearchBase = $OUStructureToProcess + "," + $DomainDistinguishedName
}
$arrayoftopusers = @()
$TotalUsersProcessed = 0
$UserCount = 0
$GroupCount = 0
$LargestTokenSize = 0
$TotalGoodTokens = 0
$TotalTokensBetween8and12K = 0
$TotalLargeTokens = 0
$TotalVeryLargeTokens = 0
If ([String]::IsNullOrEmpty($AccountName)) {
# Create an LDAP search for all enabled users
$ADFilter = "(&(objectClass=user)(objectcategory=person)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
$ProcessSingleAccount = $False
} Else {
# Create an LDAP search for all enabled users
$ADFilter = "(&(objectClass=user)(objectcategory=person)(samaccountname=$AccountName))"
$ProcessSingleAccount = $True
$TokensSizeThreshold = 65335
$OutputSummary = $False
}
# There is a known bug in PowerShell requiring the DirectorySearcher
# properties to be in lower case for reliability.
$ADPropertyList = @("distinguishedname","samaccountname","useraccountcontrol","objectsid","sidhistory","primarygroupid","lastlogontimestamp","memberof")
$ADScope = "SUBTREE"
$ADPageSize = 1000
$ADSearchRoot = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$($ADSearchBase)")
$ADSearcher = New-Object System.DirectoryServices.DirectorySearcher
$ADSearcher.SearchRoot = $ADSearchRoot
$ADSearcher.PageSize = $ADPageSize
$ADSearcher.Filter = $ADFilter
$ADSearcher.SearchScope = $ADScope
if ($ADPropertyList) {
foreach ($ADProperty in $ADPropertyList) {
[Void]$ADSearcher.PropertiesToLoad.Add($ADProperty)
}
}
$Users = $ADSearcher.Findall()
$UserCount = $users.Count
if ($UserCount -ne 0) {
$Users | ForEach-Object {
#$_.Properties
#$_.Properties.propertynames
$lastLogonTimeStamp = ""
$lastLogon = ""
$UserDN = $_.Properties.distinguishedname[0]
$samAccountName = $_.Properties.samaccountname[0]
If (($_.Properties.lastlogontimestamp | Measure-Object).Count -gt 0) {
$lastLogonTimeStamp = $_.Properties.lastlogontimestamp[0]
$lastLogon = [System.DateTime]::FromFileTime($lastLogonTimeStamp)
if ($lastLogon -match "1/01/1601") {$lastLogon = "Never logged on before"}
} else {
$lastLogon = "Never logged on before"
}
$OU = $_.GetDirectoryEntry().Parent
$OU = $OU -replace ("LDAP:\/\/","")
# Get user SID
$arruserSID = New-Object System.Security.Principal.SecurityIdentifier($_.Properties.objectsid[0], 0)
$userSID = $arruserSID.Value
# Get the SID of the Domain the account is in
$AccountDomainSid = $arruserSID.AccountDomainSid.Value
# Get User Account Control & Primary Group by binding to the user account
# ADSI Requires that / Characters be Escaped with the \ Escape Character
$UserDN = $UserDN.Replace("/", "\/")
$objUser = [ADSI]("LDAP://" + $UserDN)
$UACValue = $objUser.useraccountcontrol[0]
$primarygroupID = $objUser.PrimaryGroupID
# Primary group can be calculated by merging the account domain SID and primary group ID
$primarygroupSID = $AccountDomainSid + "-" + $primarygroupID.ToString()
$primarygroup = [adsi]("LDAP://<SID=$primarygroupSID>")
$primarygroupname = $primarygroup.name
$objUser = $null
# Get SID history
$SIDCounter = 0
if ($_.Properties.sidhistory -ne $null) {
foreach ($sidhistory in $_.Properties.sidhistory) {
$SIDHistObj = New-Object System.Security.Principal.SecurityIdentifier($sidhistory, 0)
#Write-Host -ForegroundColor green $SIDHistObj.Value "is in the SIDHistory."
$SIDCounter++
}
$SIDHistObj = $null
}
$TotalUsersProcessed ++
If ($ProgressBar) {
Write-Progress -Activity 'Processing Users' -Status ("Username: {0}" -f $samAccountName) -PercentComplete (($TotalUsersProcessed/$UserCount)*100)
}
# Use TokenGroups Attribute
If ($UseTokenGroups) {
$UserAccount = [ADSI]"$($_.Path)"
$UserAccount.GetInfoEx(@("tokenGroups"),0) | Out-Null
$ErrorActionPreference = "continue"
$error.Clear()
$groups = $UserAccount.GetEx("tokengroups")
if ($Error) {
Write-Warning " Tokengroups not readable"
$Groups=@() #empty enumeration
}
$GroupCount = 0
# Note that the tokengroups includes all principals, which includes siDHistory, so we need
# to subtract the sIDHistory count to correctly report on the number of groups in the token.
$GroupCount = $groups.count - $SIDCounter
$SecurityDomainLocalScope = 0
$SecurityGlobalInternalScope = 0
$SecurityGlobalExternalScope = 0
$SecurityUniversalInternalScope = 0
$SecurityUniversalExternalScope = 0
foreach($token in $groups) {
$GroupSIDBytes = 0
$GroupSIDHistoryCount = 0
$principal = New-Object System.Security.Principal.SecurityIdentifier($token,0)
$GroupSid = $principal.value
#$group = $principal.Translate([System.Security.Principal.NTAccount])
#$group.value
$grp = [ADSI]"LDAP://<SID=$GroupSid>"
if ($grp.Path -ne $null) {
$grpdn = $grp.distinguishedName.tostring().ToLower()
$grouptype = $grp.groupType.psbase.value
$ValidGroup = $False
switch -exact ($GroupType) {
"-2147483646" {
# Global security scope
if ($GroupSid -match $AccountDomainSid)
{
$SecurityGlobalInternalScope++
} else {
# Global groups from others.
$SecurityGlobalExternalScope++
}
$ValidGroup = $True
}
"-2147483644" {
# Domain Local scope
$SecurityDomainLocalScope++
$ValidGroup = $True
}
"-2147483643" {
# Domain Local BuiltIn scope
$SecurityDomainLocalScope++
$ValidGroup = $True
}
"-2147483640" {
# Universal security scope
if ($GroupSid -match $AccountDomainSid)
{
$SecurityUniversalInternalScope++
} else {
# Universal groups from others.
$SecurityUniversalExternalScope++
}
$ValidGroup = $True
}
}
If ($ValidGroup) {
#write-host "$($grpdn)"
$GroupSIDBytes = $GroupSIDBytes + $principal.BinaryLength + 16
#write-host " - Number of bytes in SID: $($principal.BinaryLength + 16) (incliding overhead)"
If (($grp.sidhistory.psbase.value | Measure-Object).Count -gt 0) {
$grp.sidhistory.psbase.value | ForEach-Object {
$GroupSIDHistoryCount = $GroupSIDHistoryCount + 1
}
}
#write-host " - Number of SIDs in SIDHistory: $GroupSIDHistoryCount"
}
}
}
}
# Use GetAuthorizationGroups() Method
If ($UseGetAuthorizationGroups) {
$userPrincipal = Get-UserPrincipal -userName $SamAccountName -cName $domain -cContainer "$OU"
$GroupCount = 0
$SecurityDomainLocalScope = 0
$SecurityGlobalInternalScope = 0
$SecurityGlobalExternalScope = 0
$SecurityUniversalInternalScope = 0
$SecurityUniversalExternalScope = 0
# Use GetAuthorizationGroups() for Indirect Group MemberShip, which includes all Nested groups and the Primary group
Try {
$groups = $userPrincipal.GetAuthorizationGroups() | select SamAccountName, GroupScope, SID
$GroupCount = $groups.count
foreach ($group in $groups) {
$GroupSid = $group.SID.value
#$group
switch ($group.GroupScope)
{
"Local" {
# Domain Local & Domain Local BuildIn scope
$SecurityDomainLocalScope++
}
"Global" {
# Global security scope
if ($GroupSid -match $AccountDomainSid) {
$SecurityGlobalInternalScope++
} else {
# Global groups from others.
$SecurityGlobalExternalScope++
}
}
"Universal" {
# Universal security scope
if ($GroupSid -match $AccountDomainSid) {
$SecurityUniversalInternalScope++
} else {
# Universal groups from others.
$SecurityUniversalExternalScope++
}
}
}
}
}
Catch {
write-host "Error with the GetAuthorizationGroups() method: $($_.Exception.Message)" -ForegroundColor Red
}
}
If ($ConsoleOutput) {
Write-Host -ForegroundColor green "Checking the token of user $SamAccountName in domain $domain"
Write-Host -ForegroundColor green "There are $GroupCount groups in the token."
Write-Host -ForegroundColor green "- $SecurityDomainLocalScope are domain local security groups."
Write-Host -ForegroundColor green "- $SecurityGlobalInternalScope are domain global scope security groups inside the users domain."
Write-Host -ForegroundColor green "- $SecurityGlobalExternalScope are domain global scope security groups outside the users domain."
Write-Host -ForegroundColor green "- $SecurityUniversalInternalScope are universal security groups inside the users domain."
Write-Host -ForegroundColor green "- $SecurityUniversalExternalScope are universal security groups outside the users domain."
Write-host -ForegroundColor green "The primary group is $primarygroupname."
Write-host -ForegroundColor green "There are $SIDCounter SIDs in the users SIDHistory."
Write-Host -ForegroundColor green "The current userAccountControl value is $UACValue."
}
$TrustedforDelegation = $false
if ((($UACValue -bor 0x80000) -eq $UACValue) -OR (($UACValue -bor 0x1000000) -eq $UACValue)) {
$TrustedforDelegation = $true
}
# Calculate the current token size, taking into account whether or not the account is trusted for delegation or not.
$TokenSize = 1200 + (40 * ($SecurityDomainLocalScope + $SecurityGlobalExternalScope + $SecurityUniversalExternalScope + $SIDCounter)) + (8 * ($SecurityGlobalInternalScope + $SecurityUniversalInternalScope))
if ($TrustedforDelegation -eq $false) {
If ($ConsoleOutput) {
Write-Host -ForegroundColor green "Token size is $Tokensize and the user is not trusted for delegation."
}
} else {
$TokenSize = 2 * $TokenSize
If ($ConsoleOutput) {
Write-Host -ForegroundColor green "Token size is $Tokensize and the user is trusted for delegation."
}
}
If ($TokenSize -le 12000) {
$TotalGoodTokens ++
If ($TokenSize -gt 8192) {
$TotalTokensBetween8and12K ++
}
} elseIf ($TokenSize -le 48000) {
$TotalLargeTokens ++
} else {
$TotalVeryLargeTokens ++
}
If ($TokenSize -gt $LargestTokenSize) {
$LargestTokenSize = $TokenSize
$LargestTokenUser = $SamAccountName
}
If ($TokenSize -ge $TokensSizeThreshold) {
$obj = New-Object -TypeName PSObject
$obj | Add-Member -MemberType NoteProperty -Name "Domain" -value $domain
$obj | Add-Member -MemberType NoteProperty -Name "SamAccountName" -value $SamAccountName
$obj | Add-Member -MemberType NoteProperty -Name "TokenSize" -value $TokenSize
$obj | Add-Member -MemberType NoteProperty -Name "Memberships" -value $GroupCount
$obj | Add-Member -MemberType NoteProperty -Name "DomainLocal" -value $SecurityDomainLocalScope
$obj | Add-Member -MemberType NoteProperty -Name "GlobalInternal" -value $SecurityGlobalInternalScope
$obj | Add-Member -MemberType NoteProperty -Name "GlobalExternal" -value $SecurityGlobalExternalScope
$obj | Add-Member -MemberType NoteProperty -Name "UniversalInternal" -value $SecurityUniversalInternalScope
$obj | Add-Member -MemberType NoteProperty -Name "UniversalExternal" -value $SecurityUniversalExternalScope
$obj | Add-Member -MemberType NoteProperty -Name "SIDHistory" -value $SIDCounter
$obj | Add-Member -MemberType NoteProperty -Name "UACValue" -value $UACValue
$obj | Add-Member -MemberType NoteProperty -Name "TrustedforDelegation" -value $TrustedforDelegation
$obj | Add-Member -MemberType NoteProperty -Name "LastLogon" -value $lastLogon
$arrayoftopusers += $obj
# PowerShell V2 doesn't have an Append parameter for the Export-Csv cmdlet. Out-File does, but it's
# very difficult to get the formatting right, especially if you want to use quotes around each item
# and add a delimeter. However, we can easily do this by piping the object using the ConvertTo-Csv,
# Select-Object and Out-File cmdlets instead.
if ($PSVersionTable.PSVersion.Major -gt 2) {
$obj | Export-Csv -Path "$ReferenceFile" -Append -Delimiter $Delimiter -NoTypeInformation -Encoding ASCII
} Else {
if (!(Test-Path -path $ReferenceFile)) {
$obj | ConvertTo-Csv -NoTypeInformation -Delimiter $Delimiter | Select-Object -First 1 | Out-File -Encoding ascii -filepath "$ReferenceFile"
}
$obj | ConvertTo-Csv -NoTypeInformation -Delimiter $Delimiter | Select-Object -Skip 1 | Out-File -Encoding ascii -filepath "$ReferenceFile" -append -noclobber
}
If ($ProcessSingleAccount -eq $False) {
# Manage an array of the top X users as per the $TopUsers variable.
$arrayoftopusers | Sort-Object TokenSize -descending | select-object -first $TopUsers | out-null
}
}
If ($ConsoleOutput -AND $ProcessSingleAccount -eq $False) {
$percent = "{0:P}" -f ($TotalUsersProcessed/$UserCount)
write-host -ForegroundColor green "Processed $TotalUsersProcessed of $UserCount user accounts = $percent complete."
Write-host " "
}
}
If ($OutputSummary) {
Write-Host -ForegroundColor green "Summary:"
Write-Host -ForegroundColor green "- Processed $UserCount user accounts."
Write-Host -ForegroundColor green "- $TotalGoodTokens have a calculated token size of less than or equal to 12000 bytes."
If ($TotalGoodTokens -gt 0) {
Write-Host -ForegroundColor green " - These users are good."
}
If ($TotalTokensBetween8and12K -gt 0) {
Write-Host -ForegroundColor green " - Although $TotalTokensBetween8and12K of these user accounts have tokens above 8K and should therefore be reviewed."
}
Write-Host -ForegroundColor green "- $TotalLargeTokens have a calculated token size larger than 12000 bytes."
If ($TotalLargeTokens -gt 0) {
Write-Host -ForegroundColor green " - These users will be okay if you have increased the MaxTokenSize to 48000 bytes.`n - Consider reducing direct and transitive (nested) group memberships."
}
Write-Host -ForegroundColor red "- $TotalVeryLargeTokens have a calculated token size larger than 48000 bytes."
If ($TotalVeryLargeTokens -gt 0) {
Write-Host -ForegroundColor red " - These users will have problems. Do NOT increase the MaxTokenSize beyond 48000 bytes.`n - Reduce the direct and transitive (nested) group memberships."
}
Write-Host -ForegroundColor green "- $LargestTokenUser has the largest calculated token size of $LargestTokenSize bytes in the $domain domain."
}
If ($ProcessSingleAccount -eq $False) {
# Write the $arrayoftopusers to a CSV.
$arrayoftopusers | export-csv -notype -path "$ReferenceFileTopUsers" -Delimiter $Delimiter
}
# Remove the quotes from the output file.
If ($RemoveQuotesFromCSV) {
(get-content "$ReferenceFile") |% {$_ -replace '"',""} | out-file "$ReferenceFile" -Fo -En ascii
If ($ProcessSingleAccount -eq $False) {
(get-content "$ReferenceFileTopUsers") |% {$_ -replace '"',""} | out-file "$ReferenceFileTopUsers" -Fo -En ascii
}
}
}