This repository has been archived by the owner on Mar 8, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 87
/
New-KrbtgtKeys.ps1
3732 lines (3309 loc) · 202 KB
/
New-KrbtgtKeys.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
### Abstract: This PoSH Script Resets The KrbTgt Password For RWDCs And RODCs In A Controlled Manner
###
### Written by: Jorge de Almeida Pinto [MVP-EMS]
### BLOG: http://jorgequestforknowledge.wordpress.com/
### E-Mail Address For Feedback/Questions: scripts.gallery@iamtec.eu
###
### Paste The Following Quick Link Between The Double Quotes In Browser To Send Mail:
### --> "mailto:Jorge's Script Gallery <scripts.gallery@iamtec.eu>?subject=[Script Gallery Feedback:] 'REPLACE-THIS-PART-WITH-SOMETHING-MEANINGFULL'"
###
### For Questions/Feedback:
### --> Please Describe Your Scenario As Best As Possible With As Much Detail As Possible.
### --> If Applicable Describe What Does and Does Not Work.
### --> If Applicable Describe What Should Be/Work Different And Explain Why/How.
### --> Please Add Screendumps.
###
$ver
<#
.SYNOPSIS
This PoSH Script Resets The KrbTgt Password For RWDCs And RODCs In A Controlled Manner
.VERSION
v2.5, 2020-02-17 (UPDATE THE VERSION VARIABLE BELOW)
.AUTHOR
Initial Script/Thoughts.......: Jared Poeppelman, Microsoft
Script Re-Written/Enhanced....: Jorge de Almeida Pinto [MVP Enterprise Mobility And Security, EMS]
Blog..........................: Blog: http://jorgequestforknowledge.wordpress.com/
For Feedback/Questions........: scripts.gallery@iamtec.eu ("mailto:Jorge's Script Gallery <scripts.gallery@iamtec.eu>?subject=[Script Gallery Feedback:] 'REPLACE-THIS-PART-WITH-SOMETHING-MEANINGFULL'")
.DESCRIPTION
This PoSH script provides the following functions:
- Single Password Reset for the KrbTgt account in use by RWDCs in a specific AD domain, using either TEST or PROD KrbTgt accounts
- Single Password Reset for the KrbTgt account in use by an individual RODC in a specific AD domain, using either TEST or PROD KrbTgt accounts
* A single RODC in a specific AD domain
* A specific list of RODCs in a specific AD domain
* All RODCs in a specific AD domain
- Resetting the password/keys of the KrbTgt Account can be done for multiple reasons such as for example:
* From a security perspective as mentioned in https://cloudblogs.microsoft.com/microsoftsecure/2015/02/11/krbtgt-account-password-reset-scripts-now-available-for-customers/
* From an AD recovery perspective as mentioned in https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/ad-forest-recovery-resetting-the-krbtgt-password
- For all scenarios, an informational mode, which is mode 1 with no changes
- For all scenarios, a simulation mode, which is mode 2 where replication is tested through the replication of a temporary canary
object that is created and deleted afterwards. No Password Resets involved here as the temporary canary object is a contact object
- For all scenarios, a simulation mode, which is mode 3 where the password reset of the chosen TEST KrbTgt account is actually executed
and replication of it is monitored through the environment for its duration. Can be scoped for RWDCs and RODCs (single, multiple, all)
- For all scenarios, a real reset mode, which is mode 4 where the password reset of the chosen PROD KrbTgt account is actually executed
and replication of it is monitored through the environment for its duration
- The creation of Test KrbTgt Accounts, which is mode 8
- The deletion of Test KrbTgt Accounts, which is mode 9
Behavior:
- In mode 1 you will always get a list of all RWDCs, and alls RODCs if applicable, in the targeted AD domain that are available/reachable
or not
- In mode 2 it will create the temporary canary object and, depending on the scope, it will check if it exists in the AD database of the
remote DC(s) (RWDC/RODC)
- In mode 3, depending on the scope, it uses TEST/BOGUS krbtgt account(s) to reset the password on an originating RWDC. After that it
checks if pwdLastSet attribute value of the targeted TEST/BOGUS krbtgt account(s) on the remote DC(s) (RWDC/RODC) matches the
pwdLastSet attribute value of the same TEST/BOGUS krbtgt account on the originating RWDC
* For RWDCs it uses the TEST/BOGUS krbtgt account "krbtgt_TEST" (All RWDCs) (= Created when running mode 8)
* For RODCs it uses the TEST/BOGUS krbtgt account "krbtgt_<Numeric Value>_TEST" (RODC Specific) (= Created when running mode 8)
- In mode 4, depending on the scope, it uses PROD/REAL krbtgt account(s) to reset the password on an originating RWDC. After that it
checks if pwdLastSet attribute value of the targeted PROD/REAL krbtgt account(s) on the remote DC(s) (RWDC/RODC) matches the pwdLastSet
attribute value of the same PROD/REAL krbtgt account on the originating RWDC
* For RWDCs it uses the PROD/REAL krbtgt account "krbtgt" (All RWDCs)
* For RODCs it uses the PROD/REAL krbtgt account "krbtgt_<Numeric Value>" (RODC Specific)
- In mode 8, for RWDCs it creates (in disabled state!) the TEST/BOGUS krbtgt account "krbtgt_TEST" and adds it to the AD group
"Denied RODC Password Replication Group". If any RODC exists in the targeted AD domain, it reads the attribute "msDS-KrbTgtLink" of
each RODC computer account to determine the RODC specific krbtgt account and creates (in disabled state!) the TEST/BOGUS krbtgt
account "krbtgt_<Numeric Value>_TEST" and adds it to the AD group "Allowed RODC Password Replication Group"
- In mode 9, for RWDCs it deletes the TEST/BOGUS krbtgt account "krbtgt_TEST" if it exists. If any RODC exists in the targeted AD domain,
it reads the attribute "msDS-KrbTgtLink" of each RODC computer account to determine the RODC specific krbtgt account and deletes the
TEST/BOGUS krbtgt account "krbtgt_<Numeric Value>_TEST" if it exists.
- In mode 2, 3 or 4, if a remote DC (RWDC/RODC) is not available or cannot be reached, there will not be a check against its AD database
to determine if the change made reached it or not.
- In mode 2 when performing the "replicate single object" operation, it will always be for the full object, no matter if the remote DC
is an RWDC or an RODC
- In mode 3 or 4 when performing the "replicate single object" operation, it will always be for the full object, if the remote DC is an
RWDC. If the remote DC is an RODC it will always be for the partial object and more specifically "secrets only"
- When targeting the krbtgt account (TEST/BOGUS or PROD/REAL) in use by all the RWDCs, the originating RWDC is the RWDC with the PDC FSMO
and all other available/reachable RWDCs will be checked against to see if the change has reached them. No RODCs are involved as those
do not use the krbtg account in use by the RWDCs and also do not store/cache its password.
- When targeting the krbtgt account (TEST/BOGUS or PROD/REAL) in use by an RODC, the originating RWDC is the direct replication RWDC if
available/reachable and when not available the RWDC with the PDC FSMO is used as the originating RWDC. Only the RODC that uses the
specific krbtgt account is checked against to see if the change has reached them, but only if the RODCs is available/reachable
- If the operating system attribute of an RODC computer account does not have a value, it is determined to be unknown (not a real RODC),
and therefore something else. It could for example be a Riverbed appliance in "RODC mode".
- The only DC that knows what the real replication partner is of an RODC, is the RODC itself. Only the RODC manages a connection object
that only exists in the AD database of the RODC and does not replicate out to other DCs as RODCs do not support outbound replication.
Therefore, assuming the RODC is available, the CO is looked up in the RODC AD database and from that CO, the "source" server is
determined. In case the RODC is not available or its "source" server is not available, the RWDC with the PDC FSMO is used to reset
the password of the krbtgt account in use by that RODC. If the RODC is available a check will be done against its database, and if
not available the check is skipped
.TODO
- Work out the sections that contain '#XXX'
- Cleanup commented code as it is not used anymore - for next update to occur
.KNOWN ISSUES/BUGS
- When targeting a remote AD forest for which no trust exist with the AD forest the running account belongs to, the public profile of WinRM may be
used. In that case the PSSession for 'Get-GPOReport' may fail due to the default firewall exception only allowing access from remote computers
on the same local subnet. In that case the default 'MaxTicketAge' (default 10 hours) and 'MaxClockSkew' (default 5 minutes) is used instead.
You may see the following error:
[<FQDN TARGET DC>] Connecting to remote server <FQDN TARGET DC> failed with the following error message : WinRM cannot complete the operation.
Verify that the specified computer name is valid, that the computer is accessible over the network, and that a firewall exception for the WinRM
service is enabled and allows access from this computer. By default, the WinRM firewall exception for public profiles limits access to remote
computers within the same local subnet. For more information, see the about_Remote_Troubleshooting Help topic.
+ CategoryInfo : OpenError: (<FQDN TARGET DC>:String) [], PSRemotingTransportException
+ FullyQualifiedErrorId : WinRMOperationTimeout,PSSessionStateBroken
- Although this script can be used in an environment with Windows Server 2000/2003 RWDCs, it is NOT supported to do this. Windows Server
2000/2003 RWDCs cannot do KDC PAC validation using the previous (N-1) krbtgt password. Those RWDCs only attempt that with the current
(N) password. That means that in the subset of KRB AP exchanges where KDC PAC validation is performed, authentication issues could be
experienced because the target server gets a PAC validation error when asking the KDC (domain controller) to validate the KDC signature
of the PAC that is inside the service ticket that was presented by the client to the server. This problem would potentially persist
for the lifetime of the service ticket(s). It is also highly recommended NOT to use products that have reached their end support.
Please upgrade as soon as possible.
- This is not related to this script. When increasing the DFL from Windows Server 2003 to any higher level, the password of the KrbTgt
Account will be reset automatically due to the introduction of AES encryption for Kerberos and the requirement to regenerate new keys
for DES, RC4, AES128, AES256!
.RELEASE NOTES
v2.5, 2020-02-17, Jorge de Almeida Pinto [MVP-EMS]:
- To improve performance, for some actions the nearest RWDC is discovered instead of using the RWDC with the PDC FSMO Role
v2.4, 2020-02-10, Jorge de Almeida Pinto [MVP-EMS]:
- Checked script with Visual Studio Code and fixed all "problems" identified by Visual Studio Code
- Variable "$remoteCredsUsed" is ignored by me, as the problem is due to the part 'Creds' in the variable name
- Variable "$adminCreds" is ignored by me, as the problem is due to the part 'Creds' in the variable name
- Bug Fix: Fixed language specific issue with the groups 'Allowed RODC Password Replication Group' and 'Denied RODC Password Replication Group'
- Added support to execute this script against a remote AD forest, either with or without a trust
v2.3, 2019-02-25, Jorge de Almeida Pinto [MVP-EMS]:
- Bug Fix: Removed the language specific error checking. Has been replaced with another check. This solution also resolved another
issue when checking if a (RW/RO)DC was available or not
v2.2, 2019-02-12, Jorge de Almeida Pinto [MVP-EMS]:
- Bug Fix: Instead of searching for "Domain Admins" or "Enterprise Admins" membership, it resolves the default RIDs of those groups,
combined with the corresponding domain SID, to the actual name of those domain groups. This helps in supporting non-english names
of those domain groups
v2.1, 2019-02-11, Jorge de Almeida Pinto [MVP-EMS]:
- New Feature: Read and display metadata of the KrbTgt accounts before and after to assure it was only updated once!
- Bug Fix: Added a try catch when enumerating details about a specific AD domain that appears not to be available
v2.0, 2018-12-30, Jorge de Almeida Pinto [MVP-EMS]:
- Renamed script to Reset-KrbTgt-Password-For-RWDCs-And-RODCs.ps1
- Full rewrite and major release
- Added possibility to also reset KrbTgt account in use by RODCs
- Added possibility to try this procedure using a temp canary object (contact object)
- Added possibility to try this procedure using a TEST krbtgt accounts and perform password reset on those TEST krbtgt accounts
- Added possibility to create TEST krbtgt accounts if required
- Added possibility to delete TEST krbtgt accounts if required
- Check if an RODC account is indeed in use by a Windows RODC and not something simulating an RODC (e.g. Riverbed)
- Removed dependency for REPADMIN.EXE
- Removed dependency for RPCPING.EXE
- Extensive logging to both screen and file
- Added more checks, such as permissions check, etc.
v1.7, Jared Poeppelman, Microsoft
- Modified rpcping.exe call to use "-u 9 -a connect" parameters to accomodate tighter RPC security settings as specified in
DISA STIG ID: 5.124 Rule ID: SV-32395r1_rule , Vuln ID: V-14254 (thanks Adam Haynes)
v1.6, Jared Poeppelman, Microsoft
- Removed 'finally' block of Get-GPOReport error handling (not a bug, just not needed)
v1.5, Jared Poeppelman, Microsoft
- Renamed script to New-CtmADKrbtgtKeys.ps1
- Added logic for GroupPolicy Powershell module dependency
- Fixed bug of attempting PDC to PDC replication
- Replaced function for password generation
- Renamed functions to use appropriate Powershell verbs
- Added error handling around Get-GpoReport for looking up MaxTicketAge and MaxClockSkew
v1.4, Jared Poeppelman, Microsoft
- First version published on TechNet Script Gallery
.NOTES
- To execute this script, the account running the script MUST be a member of the "Domain Admins" or Administrators group in the
targeted AD domain.
- If the account used is from another AD domain in the same AD forest, then the account running the script MUST be a member of the
"Enterprise Admins" group in the AD forest or Administrators group in the targeted AD domain. For all AD domains in the same
AD forest, membership of the "Enterprise Admins" group is easier as by default it is a member of the Administrators group in
every AD domain in the AD forest
- If the account used is from another AD domain in another AD forest, then the account running the script MUST be a member of the
"Administrators" group in the targeted AD domain. This also applies to any other target AD domain in that same AD forest
- This is due to the reset of the password for the targeted KrbTgt account(s) and forcing (single object) replication between DCs
- Testing "Domain Admins" membership is done through "IsInRole" method as the group is domain specific
- Testing "Enterprise Admins" membership is done through "IsInRole" method as the group is forest specific
- Testing "Administrators" membership cannot be done through "IsInRole" method as the group exist in every AD domain with the same
SID. To still test for required permissions in that case, the value of the Description attribute of the KRBTGT account is copied
into the DisplayName attribute and cleared afterwards. If both those actions succeed it is proven the required permissions are
in place!
#>
### FUNCTION: Logging Data To The Log File
Function Logging($dataToLog, $lineType) {
$datetimeLogLine = "[" + $(Get-Date -format "yyyy-MM-dd HH:mm:ss") + "] : "
Out-File -filepath "$logFilePath" -append -inputObject "$datetimeLogLine$dataToLog"
#Write-Output($datetimeLogLine + $dataToLog)
If ($null -eq $lineType) {
Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Yellow
}
If ($lineType -eq "SUCCESS") {
Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Green
}
If ($lineType -eq "ERROR") {
Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Red
}
If ($lineType -eq "WARNING") {
Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Red
}
If ($lineType -eq "MAINHEADER") {
Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Magenta
}
If ($lineType -eq "HEADER") {
Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor DarkCyan
}
If ($lineType -eq "REMARK") {
Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Cyan
}
If ($lineType -eq "REMARK-IMPORTANT") {
Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Green
}
If ($lineType -eq "REMARK-MORE-IMPORTANT") {
Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Yellow
}
If ($lineType -eq "REMARK-MOST-IMPORTANT") {
Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Red
}
If ($lineType -eq "ACTION") {
Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor White
}
If ($lineType -eq "ACTION-NO-NEW-LINE") {
Write-Host "$datetimeLogLine$dataToLog" -NoNewline -ForeGroundColor White
}
}
### FUNCTION: Test The Port Connection
Function portConnectionCheck($fqdnServer,$port,$timeOut) {
$tcpPortSocket = $null
$portConnect = $null
$tcpPortWait = $null
$tcpPortSocket = New-Object System.Net.Sockets.TcpClient
$portConnect = $tcpPortSocket.BeginConnect($fqdnServer,$port,$null,$null)
$tcpPortWait = $portConnect.AsyncWaitHandle.WaitOne($timeOut,$false)
If(!$tcpPortWait) {
$tcpPortSocket.Close()
Return "ERROR"
} Else {
#$error.Clear()
$ErrorActionPreference = "SilentlyContinue"
$tcpPortSocket.EndConnect($portConnect) | Out-Null
If (!$?) {
Return "ERROR"
} Else {
Return "SUCCESS"
}
$tcpPortSocket.Close()
$ErrorActionPreference = "Continue"
}
}
### FUNCTION: Load Required PowerShell Modules
Function loadPoSHModules($PoSHModule) {
$retValue = $null
If(@(Get-Module | Where-Object{$_.Name -eq $PoSHModule}).count -eq 0) {
If(@(Get-Module -ListAvailable | Where-Object{$_.Name -eq $PoSHModule} ).count -ne 0) {
Import-Module $PoSHModule
Logging "PoSH Module '$PoSHModule' Has Been Loaded..." "SUCCESS"
$retValue = "HasBeenLoaded"
} Else {
Logging "PoSH Module '$PoSHModule' Is Not Available To Load..." "ERROR"
Logging "Aborting Script..." "ERROR"
$retValue = "NotAvailable"
}
} Else {
Logging "PoSH Module '$PoSHModule' Already Loaded..." "SUCCESS"
$retValue = "AlreadyLoaded"
}
Return $retValue
}
### FUNCTION: Test Credentials For Specific Admin Role
Function testAdminRole($adminRole) {
# Determine Current User
$currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
# Check The Current User Is In The Specified Admin Role
(New-Object Security.Principal.WindowsPrincipal $currentUser).IsInRole($adminRole)
}
### FUNCTION: Create Temporary Canary Object
Function createTempCanaryObject($targetedADdomainRWDC, $krbTgtSamAccountName, $execDateTimeCustom1, $localADforest, $remoteCredsUsed, $adminCreds) {
# Determine The DN Of The Default NC Of The Targeted Domain
$targetedADdomainDefaultNC = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$targetedADdomainDefaultNC = (Get-ADRootDSE -Server $targetedADdomainRWDC).defaultNamingContext
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$targetedADdomainDefaultNC = (Get-ADRootDSE -Server $targetedADdomainRWDC -Credential $adminCreds).defaultNamingContext
}
# Determine The DN Of The Users Container Of The Targeted Domain
$containerForTempCanaryObject = $null
$containerForTempCanaryObject = "CN=Users," + $targetedADdomainDefaultNC
# Generate The Name Of The Temporary Canary Object
$targetObjectToCheckName = $null
$targetObjectToCheckName = "_adReplTempObject_" + $krbTgtSamAccountName + "_" + $execDateTimeCustom1
# Specify The Description Of The Temporary Canary Object
$targetObjectToCheckDescription = "...!!!.TEMP OBJECT TO CHECK AD REPLICATION IMPACT.!!!..."
# Generate The DN Of The Temporary Canary Object
$targetObjectToCheckDN = $null
$targetObjectToCheckDN = "CN=" + $targetObjectToCheckName + "," + $containerForTempCanaryObject
Logging " --> RWDC To Create Object On..............: '$targetedADdomainRWDC'"
Logging " --> Full Name Temp Canary Object..........: '$targetObjectToCheckName'"
Logging " --> Description...........................: '$targetObjectToCheckDescription'"
Logging " --> Container For Temp Canary Object......: '$containerForTempCanaryObject'"
Logging ""
# Try To Create The Canary Object In The AD Domain And If Not Successfull Throw Error
Try {
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
New-ADObject -Type contact -Name $targetObjectToCheckName -Path $containerForTempCanaryObject -DisplayName $targetObjectToCheckName -Description $targetObjectToCheckDescription -Server $targetedADdomainRWDC
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
New-ADObject -Type contact -Name $targetObjectToCheckName -Path $containerForTempCanaryObject -DisplayName $targetObjectToCheckName -Description $targetObjectToCheckDescription -Server $targetedADdomainRWDC -Credential $adminCreds
}
} Catch {
Logging " --> Temp Canary Object [$targetObjectToCheckDN] FAILED TO BE CREATED on RWDC [$targetedADdomainRWDC]!..." "ERROR"
Logging "" "ERROR"
}
# Check The Temporary Canary Object Exists And Was created In AD
$targetObjectToCheck = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$targetObjectToCheck = Get-ADObject -LDAPFilter "(&(objectClass=contact)(name=$targetObjectToCheckName))" -Server $targetedADdomainRWDC
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$targetObjectToCheck = Get-ADObject -LDAPFilter "(&(objectClass=contact)(name=$targetObjectToCheckName))" -Server $targetedADdomainRWDC -Credential $adminCreds
}
If ($targetObjectToCheck) {
$targetObjectToCheckDN = $null
$targetObjectToCheckDN = $targetObjectToCheck.DistinguishedName
Logging " --> Temp Canary Object [$targetObjectToCheckDN] CREATED on RWDC [$targetedADdomainRWDC]!..." "REMARK"
Logging "" "REMARK"
}
Return $targetObjectToCheckDN
}
### FUNCTION: Confirm Generated Password Meets Complexity Requirements
# Source: https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/password-must-meet-complexity-requirements
Function confirmPasswordIsComplex($pwd) {
Process {
$criteriaMet = 0
# Upper Case Characters (A through Z, with diacritic marks, Greek and Cyrillic characters)
If ($pwd -cmatch '[A-Z]') {$criteriaMet++}
# Lower Case Characters (a through z, sharp-s, with diacritic marks, Greek and Cyrillic characters)
If ($pwd -cmatch '[a-z]') {$criteriaMet++}
# Numeric Characters (0 through 9)
If ($pwd -match '\d') {$criteriaMet++}
# Special Chracters (Non-alphanumeric characters, currency symbols such as the Euro or British Pound are not counted as special characters for this policy setting)
If ($pwd -match '[\^~!@#$%^&*_+=`|\\(){}\[\]:;"''<>,.?/]') {$criteriaMet++}
# Check If It Matches Default Windows Complexity Requirements
If ($criteriaMet -lt 3) {Return $false}
If ($pwd.Length -lt 8) {Return $false}
Return $true
}
}
### FUNCTION: Generate New Complex Password
Function generateNewComplexPassword([int]$passwordNrChars) {
Process {
$iterations = 0
Do {
If ($iterations -ge 20) {
Logging " --> Complex password generation failed after '$iterations' iterations..." "ERROR"
Logging "" "ERROR"
EXIT
}
$iterations++
$pwdBytes = @()
$rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
Do {
[byte[]]$byte = [byte]1
$rng.GetBytes($byte)
If ($byte[0] -lt 33 -or $byte[0] -gt 126) {
CONTINUE
}
$pwdBytes += $byte[0]
}
While ($pwdBytes.Count -lt $passwordNrChars)
$pwd = ([char[]]$pwdBytes) -join ''
}
Until (confirmPasswordIsComplex $pwd)
Return $pwd
}
}
### FUNCTION: Reset Password Of AD Account
Function setPasswordOfADAccount($targetedADdomainRWDC, $krbTgtSamAccountName, $localADforest, $remoteCredsUsed, $adminCreds) {
# Retrieve The KrgTgt Object In The AD Domain BEFORE THE PASSWORD SET
$krbTgtObjectBefore = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$krbTgtObjectBefore = Get-ADUser -LDAPFilter "(sAMAccountName=$krbTgtSamAccountName)" -Properties * -Server $targetedADdomainRWDC
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$krbTgtObjectBefore = Get-ADUser -LDAPFilter "(sAMAccountName=$krbTgtSamAccountName)" -Properties * -Server $targetedADdomainRWDC -Credential $adminCreds
}
# Get The DN Of The KrgTgt Object In The AD Domain BEFORE THE PASSWORD SET
$krbTgtObjectBeforeDN = $null
$krbTgtObjectBeforeDN = $krbTgtObjectBefore.DistinguishedName
# Get The Password Last Set Value From The KrgTgt Object In The AD Domain BEFORE THE PASSWORD SET
$krbTgtObjectBeforePwdLastSet = $null
$krbTgtObjectBeforePwdLastSet = Get-Date $([datetime]::fromfiletime($krbTgtObjectBefore.pwdLastSet)) -f "yyyy-MM-dd HH:mm:ss"
# Get The Metadata Of The Object, And More Specific Of The pwdLastSet Attribute Of That Object BEFORE THE PASSWORD SET
$metadataObjectBefore = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$metadataObjectBefore = Get-ADReplicationAttributeMetadata $krbTgtObjectBeforeDN -Server $targetedADdomainRWDC
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$metadataObjectBefore = Get-ADReplicationAttributeMetadata $krbTgtObjectBeforeDN -Server $targetedADdomainRWDC -Credential $adminCreds
}
$metadataObjectBeforeAttribPwdLastSet = $null
$metadataObjectBeforeAttribPwdLastSet = $metadataObjectBefore | Where-Object{$_.AttributeName -eq "pwdLastSet"}
$orgRWDCNTDSSettingsObjectDNBefore = $null
$orgRWDCNTDSSettingsObjectDNBefore = $metadataObjectBeforeAttribPwdLastSet.LastOriginatingChangeDirectoryServerIdentity
$metadataObjectBeforeAttribPwdLastSetOrgRWDCFQDN = $null
If ($orgRWDCNTDSSettingsObjectDNBefore) {
# Strip "CN=NTDS Settings," To End Up With The Server Object DN
$orgRWDCServerObjectDNBefore = $null
$orgRWDCServerObjectDNBefore = $orgRWDCNTDSSettingsObjectDNBefore.SubString(("CN=NTDS Settings,").Length)
# Connect To The Server Object DN
$orgRWDCServerObjectObjBefore = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$orgRWDCServerObjectObjBefore = ([ADSI]"LDAP://$targetedADdomainRWDC/$orgRWDCServerObjectDNBefore")
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$orgRWDCServerObjectObjBefore = New-Object System.DirectoryServices.DirectoryEntry(("LDAP://$targetedADdomainRWDC/$orgRWDCServerObjectDNBefore"),$($adminCreds.UserName), $($adminCreds.GetNetworkCredential().password))
}
$metadataObjectBeforeAttribPwdLastSetOrgRWDCFQDN = $orgRWDCServerObjectObjBefore.dnshostname[0]
} Else {
$metadataObjectBeforeAttribPwdLastSetOrgRWDCFQDN = "RWDC Demoted"
}
$metadataObjectBeforeAttribPwdLastSetOrgTime = $null
$metadataObjectBeforeAttribPwdLastSetOrgTime = Get-Date $($metadataObjectBeforeAttribPwdLastSet.LastOriginatingChangeTime) -f "yyyy-MM-dd HH:mm:ss"
$metadataObjectBeforeAttribPwdLastSetVersion = $null
$metadataObjectBeforeAttribPwdLastSetVersion = $metadataObjectBeforeAttribPwdLastSet.Version
Logging " --> RWDC To Reset Password On.............: '$targetedADdomainRWDC'"
Logging " --> sAMAccountName Of KrbTgt Account......: '$krbTgtSamAccountName'"
Logging " --> Distinguished Name Of KrbTgt Account..: '$krbTgtObjectBeforeDN'"
# Specify The Number Of Characters The Generate Password Should Contain
$passwordNrChars = 64
Logging " --> Number Of Chars For Pwd Generation....: '$passwordNrChars'"
# Generate A New Password With The Specified Length (Text)
$newKrbTgtPassword = $null
$newKrbTgtPassword = (generateNewComplexPassword $passwordNrChars).ToString()
# Convert The Text Based Version Of The New Password To A Secure String
$newKrbTgtPasswordSecure = $null
$newKrbTgtPasswordSecure = ConvertTo-SecureString $newKrbTgtPassword -AsPlainText -Force
# Try To Set The New Password On The Targeted KrbTgt Account And If Not Successfull Throw Error
Try {
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
Set-ADAccountPassword -Identity $krbTgtObjectBeforeDN -Server $targetedADdomainRWDC -Reset -NewPassword $newKrbTgtPasswordSecure
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
Set-ADAccountPassword -Identity $krbTgtObjectBeforeDN -Server $targetedADdomainRWDC -Reset -NewPassword $newKrbTgtPasswordSecure -Credential $adminCreds
}
} Catch {
Logging ""
Logging " --> Setting the new password for [$krbTgtObjectBeforeDN] FAILED on RWDC [$targetedADdomainRWDC]!..." "ERROR"
Logging "" "ERROR"
}
# Retrieve The KrgTgt Object In The AD Domain AFTER THE PASSWORD SET
$krbTgtObjectAfter = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$krbTgtObjectAfter = Get-ADUser -LDAPFilter "(sAMAccountName=$krbTgtSamAccountName)" -Properties * -Server $targetedADdomainRWDC
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$krbTgtObjectAfter = Get-ADUser -LDAPFilter "(sAMAccountName=$krbTgtSamAccountName)" -Properties * -Server $targetedADdomainRWDC -Credential $adminCreds
}
# Get The DN Of The KrgTgt Object In The AD Domain AFTER THE PASSWORD SET
$krbTgtObjectAfterDN = $null
$krbTgtObjectAfterDN = $krbTgtObjectAfter.DistinguishedName
# Get The Password Last Set Value From The KrgTgt Object In The AD Domain AFTER THE PASSWORD SET
$krbTgtObjectAfterPwdLastSet = $null
$krbTgtObjectAfterPwdLastSet = Get-Date $([datetime]::fromfiletime($krbTgtObjectAfter.pwdLastSet)) -f "yyyy-MM-dd HH:mm:ss"
# Get The Metadata Of The Object, And More Specific Of The pwdLastSet Attribute Of That Object AFTER THE PASSWORD SET
$metadataObjectAfter = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$metadataObjectAfter = Get-ADReplicationAttributeMetadata $krbTgtObjectAfterDN -Server $targetedADdomainRWDC
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$metadataObjectAfter = Get-ADReplicationAttributeMetadata $krbTgtObjectAfterDN -Server $targetedADdomainRWDC -Credential $adminCreds
}
$metadataObjectAfterAttribPwdLastSet = $null
$metadataObjectAfterAttribPwdLastSet = $metadataObjectAfter | Where-Object{$_.AttributeName -eq "pwdLastSet"}
$orgRWDCNTDSSettingsObjectDNAfter = $null
$orgRWDCNTDSSettingsObjectDNAfter = $metadataObjectAfterAttribPwdLastSet.LastOriginatingChangeDirectoryServerIdentity
$metadataObjectAfterAttribPwdLastSetOrgRWDCFQDN = $null
If ($orgRWDCNTDSSettingsObjectDNAfter) {
# Strip "CN=NTDS Settings," To End Up With The Server Object DN
$orgRWDCServerObjectDNAfter = $null
$orgRWDCServerObjectDNAfter = $orgRWDCNTDSSettingsObjectDNAfter.SubString(("CN=NTDS Settings,").Length)
# Connect To The Server Object DN
$orgRWDCServerObjectObjAfter = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$orgRWDCServerObjectObjAfter = ([ADSI]"LDAP://$targetedADdomainRWDC/$orgRWDCServerObjectDNAfter")
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$orgRWDCServerObjectObjAfter = New-Object System.DirectoryServices.DirectoryEntry(("LDAP://$targetedADdomainRWDC/$orgRWDCServerObjectDNAfter"),$($adminCreds.UserName), $($adminCreds.GetNetworkCredential().password))
}
$metadataObjectAfterAttribPwdLastSetOrgRWDCFQDN = $orgRWDCServerObjectObjAfter.dnshostname[0]
} Else {
$metadataObjectAfterAttribPwdLastSetOrgRWDCFQDN = "RWDC Demoted"
}
$metadataObjectAfterAttribPwdLastSetOrgTime = $null
$metadataObjectAfterAttribPwdLastSetOrgTime = Get-Date $($metadataObjectAfterAttribPwdLastSet.LastOriginatingChangeTime) -f "yyyy-MM-dd HH:mm:ss"
$metadataObjectAfterAttribPwdLastSetVersion = $null
$metadataObjectAfterAttribPwdLastSetVersion = $metadataObjectAfterAttribPwdLastSet.Version
Logging ""
Logging " --> Previous Password Set Date/Time.......: '$krbTgtObjectBeforePwdLastSet'"
If ($krbTgtObjectAfterPwdLastSet -ne $krbTgtObjectBeforePwdLastSet) {
Logging " --> New Password Set Date/Time............: '$krbTgtObjectAfterPwdLastSet'"
}
Logging ""
Logging " --> Previous Originating RWDC.............: '$metadataObjectBeforeAttribPwdLastSetOrgRWDCFQDN'"
If ($krbTgtObjectAfterPwdLastSet -ne $krbTgtObjectBeforePwdLastSet) {
Logging " --> New Originating RWDC..................: '$metadataObjectAfterAttribPwdLastSetOrgRWDCFQDN'"
}
Logging ""
Logging " --> Previous Originating Time.............: '$metadataObjectBeforeAttribPwdLastSetOrgTime'"
If ($krbTgtObjectAfterPwdLastSet -ne $krbTgtObjectBeforePwdLastSet) {
Logging " --> New Originating Time..................: '$metadataObjectAfterAttribPwdLastSetOrgTime'"
}
Logging ""
Logging " --> Previous Version Of Attribute Value...: '$metadataObjectBeforeAttribPwdLastSetVersion'"
If ($krbTgtObjectAfterPwdLastSet -ne $krbTgtObjectBeforePwdLastSet) {
Logging " --> New Version Of Attribute Value........: '$metadataObjectAfterAttribPwdLastSetVersion'"
}
# Check And Confirm If The Password Value Has Been Updated By Comparing The Password Last Set Before And After The Reset
If ($krbTgtObjectAfterPwdLastSet -ne $krbTgtObjectBeforePwdLastSet) {
Logging ""
Logging " --> The new password for [$krbTgtObjectAfterDN] HAS BEEN SET on RWDC [$targetedADdomainRWDC]!..." "REMARK"
Logging "" "REMARK"
}
}
### FUNCTION: Replicate Single AD Object
# INFO: https://msdn.microsoft.com/en-us/library/cc223306.aspx
Function replicateSingleADObject($sourceDCNTDSSettingsObjectDN, $targetDCFQDN, $objectDN, $contentScope, $localADforest, $remoteCredsUsed, $adminCreds) {
# Define And Target The root DSE Context
$rootDSE = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$rootDSE = [ADSI]"LDAP://$targetDCFQDN/rootDSE"
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$rootDSE = New-Object System.DirectoryServices.DirectoryEntry(("LDAP://$targetDCFQDN/rootDSE"),$($adminCreds.UserName), $($adminCreds.GetNetworkCredential().password))
}
# Perform A Replicate Single Object For The Complete Object
If ($contentScope -eq "Full") {
$rootDSE.Put("replicateSingleObject",$sourceDCNTDSSettingsObjectDN+":"+$objectDN)
}
# Perform A Replicate Single Object For Obnly The Secrets Of The Object
If ($contentScope -eq "Secrets") {
$rootDSE.Put("replicateSingleObject",$sourceDCNTDSSettingsObjectDN+":"+$objectDN+":SECRETS_ONLY")
}
# Commit The Change To The Operational Attribute
$rootDSE.SetInfo()
}
### FUNCTION: Delete/Cleanup Temporary Canary Object
Function deleteTempCanaryObject($targetedADdomainRWDC, $targetObjectToCheckDN, $localADforest, $remoteCredsUsed, $adminCreds) {
# Try To Delete The Canary Object In The AD Domain And If Not Successfull Throw Error
Try {
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
Remove-ADObject -Identity $targetObjectToCheckDN -Server $targetedADdomainRWDC -Confirm:$false
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
Remove-ADObject -Identity $targetObjectToCheckDN -Server $targetedADdomainRWDC -Credential $adminCreds -Confirm:$false
}
} Catch {
Logging " --> Temp Canary Object [$targetObjectToCheckDN] FAILED TO BE DELETED on RWDC [$targetedADdomainRWDC]!..." "ERROR"
Logging " --> Manually delete the Temp Canary Object [$targetObjectToCheckDN] on RWDC [$targetedADdomainRWDC]!..." "ERROR"
Logging "" "ERROR"
}
# Retrieve The Temporary Canary Object From The AD Domain And If It Does Not Exist It Was Deleted Successfully
$targetObjectToCheck = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$targetObjectToCheck = Get-ADObject -LDAPFilter "(distinguishedName=$targetObjectToCheckDN)" -Server $targetedADdomainRWDC
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$targetObjectToCheck = Get-ADObject -LDAPFilter "(distinguishedName=$targetObjectToCheckDN)" -Server $targetedADdomainRWDC -Credential $adminCreds
}
If (!$targetObjectToCheck) {
Logging " --> Temp Canary Object [$targetObjectToCheckDN] DELETED on RWDC [$targetedADdomainRWDC]!..." "REMARK"
Logging "" "REMARK"
}
}
### FUNCTION: Check AD Replication Convergence
Function checkADReplicationConvergence($targetedADdomainFQDN, $targetedADdomainSourceRWDCFQDN, $targetObjectToCheckDN, $listOfDCsToCheckObjectOnStart, $listOfDCsToCheckObjectOnEnd, $modeOfOperationNr, $localADforest, $remoteCredsUsed, $adminCreds) {
# Determine The Starting Time
$startDateTime = Get-Date
# Counter
$c = 0
# Boolean To Use In The While Condition
$continue = $true
# The Delay In Seconds Before The Next Check Iteration
$delay = 0.1
While($continue) {
$c++
$oldpos = $host.UI.RawUI.CursorPosition
Logging ""
Logging " =================================================================== CHECK $c ==================================================================="
Logging ""
# Wait For The Duration Of The Configured Delay Before Trying Again
Start-Sleep $delay
# Variable Specifying The Object Is In Sync
$replicated = $true
# For Each DC To Check On The Starting List With All DCs To Check Execute The Following...
ForEach ($dcToCheck in $listOfDCsToCheckObjectOnStart) {
# HostName Of The DC To Check
$dcToCheckHostName = $null
$dcToCheckHostName = $dcToCheck."Host Name"
# Is The DC To Check Also The PDC?
$dcToCheckIsPDC = $null
$dcToCheckIsPDC = $dcToCheck.PDC
# SiteName Of The DC To Check
$dcToCheckSiteName = $null
$dcToCheckSiteName = $dcToCheck."Site Name"
# Type (RWDC Or RODC) Of The DC To Check
$dcToCheckDSType = $null
$dcToCheckDSType = $dcToCheck."DS Type"
# IP Address Of The DC To Check
$dcToCheckIPAddress = $null
$dcToCheckIPAddress = $dcToCheck."IP Address"
# Reachability Of The DC To Check
$dcToCheckReachability = $null
$dcToCheckReachability = $dcToCheck.Reachable
# HostName Of The Source RWDC Of The DC To Check
#$dcToCheckSourceRWDCFQDN = $null
#$dcToCheckSourceRWDCFQDN = $dcToCheck."Source RWDC FQDN"
# DSA DN Of The Source RWDC Of The DC To Check
$dcToCheckSourceRWDCNTDSSettingsObjectDN = $null
$dcToCheckSourceRWDCNTDSSettingsObjectDN = $dcToCheck."Source RWDC DSA"
# When Running Mode 3 (Using TEST/BOGUS KrbTgt Accounts) Or Mode 4 (Using PROD/REAL KrbTgt Accounts)
If ($modeOfOperationNr -eq 3 -Or $modeOfOperationNr -eq 4) {
# Retrieve The Object From The Source Originating RWDC
$objectOnSourceOrgRWDC = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$objectOnSourceOrgRWDC = Get-ADObject -Identity $targetObjectToCheckDN -Properties * -Server $targetedADdomainSourceRWDCFQDN
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$objectOnSourceOrgRWDC = Get-ADObject -Identity $targetObjectToCheckDN -Properties * -Server $targetedADdomainSourceRWDCFQDN -Credential $adminCreds
}
# Retrieve The Password Last Set Of The Object On The Source Originating RWDC
$objectOnSourceOrgRWDCPwdLastSet = $null
$objectOnSourceOrgRWDCPwdLastSet = Get-Date $([datetime]::fromfiletime($objectOnSourceOrgRWDC.pwdLastSet)) -f "yyyy-MM-dd HH:mm:ss"
}
# When The DC To Check Is Also The Source (Originating) RWDC
If ($dcToCheckHostName -eq $targetedADdomainSourceRWDCFQDN) {
Logging " - Contacting DC in AD domain ...[$($dcToCheckHostName.ToUpper())]...(SOURCE RWDC)"
Logging " * DC is Reachable..." "SUCCESS"
# For Mode 2 Only
If ($modeOfOperationNr -eq 2) {
Logging " * Object [$targetObjectToCheckDN] exists in the AD database" "SUCCESS"
}
# For Mode 3 Or 4 Only
If ($modeOfOperationNr -eq 3 -Or $modeOfOperationNr -eq 4) {
Logging " * The new password for Object [$targetObjectToCheckDN] exists in the AD database" "SUCCESS"
}
Logging ""
CONTINUE
}
Logging " - Contacting DC in AD domain ...[$($dcToCheckHostName.ToUpper())]..."
If ($dcToCheckReachability) {
# When The DC To Check Is Reachable
Logging " * DC is Reachable..." "SUCCESS"
# When The DC To Check Is Not The Source (Originating) RWDC
If ($dcToCheckHostName -ne $targetedADdomainSourceRWDCFQDN) {
# As The DSA DN Used The DSA DN Of The Source (Originating) RWDC Of The DC Being Checked
$sourceDCNTDSSettingsObjectDN = $dcToCheckSourceRWDCNTDSSettingsObjectDN
# For Mode 2 Perform A Full Replicate Single Object
If ($modeOfOperationNr -eq 2) {
$contentScope = "Full"
}
# For Mode 3 Or 4
If ($modeOfOperationNr -eq 3 -Or $modeOfOperationNr -eq 4) {
# If The DC Being Checked Is An RWDC Perform A Full Replicate Single Object
If ($dcToCheckDSType -eq "Read/Write") {
$contentScope = "Full"
}
# If The DC Being Checked Is An RODC Perform A Partial Replicate Single Object (Secrets Only)
If ($dcToCheckDSType -eq "Read-Only") {
$contentScope = "Secrets"
}
}
# Execute The Replicate Single Object Function For The Targeted Object To Check
replicateSingleADObject $sourceDCNTDSSettingsObjectDN $dcToCheckHostName $targetObjectToCheckDN $contentScope $localADforest $remoteCredsUsed $adminCreds
}
# For Mode 2 From The DC to Check Retrieve The AD Object Of The Temporary Canary Object That Was Created On The Source (Originating) RWDC
If ($modeOfOperationNr -eq 2) {
$targetObjectToCheck = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$targetObjectToCheck = Get-ADObject -LDAPFilter "(distinguishedName=$targetObjectToCheckDN)" -Server $dcToCheckHostName
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$targetObjectToCheck = Get-ADObject -LDAPFilter "(distinguishedName=$targetObjectToCheckDN)" -Server $dcToCheckHostName -Credential $adminCreds
}
}
# For Mode 3 Or 4 From The DC to Check Retrieve The AD Object Of The Targeted KrbTgt Account (And Its Password Last Set) That Had Its Password Reset On The Source (Originating) RWDC
If ($modeOfOperationNr -eq 3 -Or $modeOfOperationNr -eq 4) {
# Retrieve The Object From The Target DC
$objectOnTargetDC = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$objectOnTargetDC = Get-ADObject -Identity $targetObjectToCheckDN -Properties * -Server $dcToCheckHostName
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$objectOnTargetDC = Get-ADObject -Identity $targetObjectToCheckDN -Properties * -Server $dcToCheckHostName -Credential $adminCreds
}
# Retrieve The Password Last Set Of The Object On The Target DC
$objectOnTargetDCPwdLastSet = $null
$objectOnTargetDCPwdLastSet = Get-Date $([datetime]::fromfiletime($objectOnTargetDC.pwdLastSet)) -f "yyyy-MM-dd HH:mm:ss"
}
} Else {
# When The DC To Check Is Not Reachable
Logging " * DC is NOT reachable..." "ERROR"
}
If ($dcToCheckReachability) {
# When The DC To Check Is Reachable
If ($targetObjectToCheck -Or $objectOnTargetDCPwdLastSet -eq $objectOnSourceOrgRWDCPwdLastSet) {
# If The Target Object To Check Does Exist Or Its Password Last Set Does Match With The Password Last Set Of The Object On The Source (Originating) RWDC
# For Mode 2 Only
If ($modeOfOperationNr -eq 2) {
Logging " * Object [$targetObjectToCheckDN] now does exist in the AD database" "SUCCESS"
}
# For Mode 3 Or 4 Only
If ($modeOfOperationNr -eq 3 -Or $modeOfOperationNr -eq 4) {
Logging " * The new password for Object [$targetObjectToCheckDN] now does exist in the AD database" "SUCCESS"
}
Logging "" "SUCCESS"
# If The DC To Check Does Not Yet Exist On The Ending List With All DCs That Were Checked, Then Add It To The Ending List
If (!($listOfDCsToCheckObjectOnEnd | Where-Object{$_."Host Name" -eq $dcToCheckHostName})) {
# Define The Columns For This DC To Be Filled In
$listOfDCsToCheckObjectOnEndObj = "" | Select-Object "Host Name",PDC,"Site Name","DS Type","IP Address",Reachable,"Source RWDC FQDN",Time
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj."Host Name" = $null
$listOfDCsToCheckObjectOnEndObj."Host Name" = $dcToCheckHostName
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj.PDC = $null
$listOfDCsToCheckObjectOnEndObj.PDC = $dcToCheckIsPDC
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj."Site Name" = $null
$listOfDCsToCheckObjectOnEndObj."Site Name" = $dcToCheckSiteName
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj."DS Type" = $null
$listOfDCsToCheckObjectOnEndObj."DS Type" = $dcToCheckDSType
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj."IP Address" = $null
$listOfDCsToCheckObjectOnEndObj."IP Address" = $dcToCheckIPAddress
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj.Reachable = $null
$listOfDCsToCheckObjectOnEndObj.Reachable = $dcToCheckReachability
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj."Source RWDC FQDN" = $null
$listOfDCsToCheckObjectOnEndObj."Source RWDC FQDN" = $targetedADdomainSourceRWDCFQDN
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj.Time = ("{0:n2}" -f ((Get-Date) - $startDateTime).TotalSeconds)
# Add The Row For The DC To The Table
$listOfDCsToCheckObjectOnEnd += $listOfDCsToCheckObjectOnEndObj
}
} Else {
# If The Target Object To Check Does Not Exist Or Its Password Last Set Does Not Match (Yet) With The Password Last Set Of The Object On The Source (Originating) RWDC
# For Mode 2 Only
If ($modeOfOperationNr -eq 2) {
Logging " * Object [$targetObjectToCheckDN] does NOT exist yet in the AD database" "WARNING"
}
# For Mode 3 Or 4 Only
If ($modeOfOperationNr -eq 3 -Or $modeOfOperationNr -eq 4) {
Logging " * The new password for Object [$targetObjectToCheckDN] does NOT exist yet in the AD database" "WARNING"
}
Logging "" "WARNING"
# Variable Specifying The Object Is Not In Sync
$replicated = $false
}
} Else {
# When The DC To Check Is Not Reachable
Logging " * Unable to connect to DC and check for Object [$targetObjectToCheckDN]..." "ERROR"
Logging "" "WARNING"
# If The DC To Check Does Not Yet Exist On The Ending List With All DCs That Were Checked, Then Add It To The Ending List
If (!($listOfDCsToCheckObjectOnEnd | Where-Object{$_."Host Name" -eq $dcToCheckHostName})) {
# Define The Columns For This DC To Be Filled In
$listOfDCsToCheckObjectOnEndObj = "" | Select-Object "Host Name",PDC,"Site Name","DS Type","IP Address",Reachable,"Source RWDC FQDN",Time
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj."Host Name" = $null
$listOfDCsToCheckObjectOnEndObj."Host Name" = $dcToCheckHostName
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj.PDC = $null
$listOfDCsToCheckObjectOnEndObj.PDC = $dcToCheckIsPDC
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj."Site Name" = $null
$listOfDCsToCheckObjectOnEndObj."Site Name" = $dcToCheckSiteName
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj."DS Type" = $null
$listOfDCsToCheckObjectOnEndObj."DS Type" = $dcToCheckDSType
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj."IP Address" = $null
$listOfDCsToCheckObjectOnEndObj."IP Address" = $dcToCheckIPAddress
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj.Reachable = $null
$listOfDCsToCheckObjectOnEndObj.Reachable = $dcToCheckReachability
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj."Source RWDC FQDN" = $null
$listOfDCsToCheckObjectOnEndObj."Source RWDC FQDN" = $targetedADdomainSourceRWDCFQDN
# Set The Corresponding Value Of The DC In The Correct Column Of The Table
$listOfDCsToCheckObjectOnEndObj.Time = "<Fail>"
# Add The Row For The DC To The Table
$listOfDCsToCheckObjectOnEnd += $listOfDCsToCheckObjectOnEndObj
}
}
}
# If The Object Is In Sync
If ($replicated) {
# Do Not Continue For The DC That Is Being Checked
$continue = $false
} Else {
# Do Continue For The DC That Is Being Checked And Move The Cursor Back To The Initial Position
$host.UI.RawUI.CursorPosition = $oldpos
}
}
# Determine The Ending Time
$endDateTime = Get-Date
# Calculate The Duration
$duration = "{0:n2}" -f ($endDateTime.Subtract($startDateTime).TotalSeconds)
Logging ""
Logging " --> Start Time......: $(Get-Date $startDateTime -format 'yyyy-MM-dd HH:mm:ss')"
Logging " --> End Time........: $(Get-Date $endDateTime -format 'yyyy-MM-dd HH:mm:ss')"
Logging " --> Duration........: $duration Seconds"
Logging ""
# If Mode 2 Was Being Executed, Then Delete The Temp Canary Object On The Source (Originating) RWDC
If ($modeOfOperationNr -eq 2) {
# Retrieve The Temp Canary Object From The Source (Originating) RWDC
$targetObjectToCheck = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$targetObjectToCheck = Get-ADObject -LDAPFilter "(distinguishedName=$targetObjectToCheckDN)" -Server $targetedADdomainSourceRWDCFQDN
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$targetObjectToCheck = Get-ADObject -LDAPFilter "(distinguishedName=$targetObjectToCheckDN)" -Server $targetedADdomainSourceRWDCFQDN -Credential $adminCreds
}
# If The Temp Canary Object Exists On The Source (Originating) RWDC, Then Delete It
If ($targetObjectToCheck) {
# Execute The Deletion Of The Temp Canary Object On The Source (Originating) RWDC
deleteTempCanaryObject $targetedADdomainSourceRWDCFQDN $targetObjectToCheckDN $localADforest $remoteCredsUsed $adminCreds
}
}
# Sort The Ending List With All DCs That Were Checked
$listOfDCsToCheckObjectOnEnd = $listOfDCsToCheckObjectOnEnd | Sort-Object -Property @{Expression = "Time"; Descending = $False} | Format-Table -Autosize
Logging ""
Logging "List Of DCs In AD Domain '$targetedADdomainFQDN' And Their Timing..."
Logging ""
Logging "$($listOfDCsToCheckObjectOnEnd | Out-String)"
Logging ""
}
### FUNCTION: Create Test Krbtgt Accounts
Function createTestKrbTgtADAccount($targetedADdomainRWDC, $krbTgtSamAccountName, $krbTgtUse, $targetedADdomainDomainSID, $localADforest, $remoteCredsUsed, $adminCreds) {
# Determine The DN Of The Default NC Of The Targeted Domain
$targetedADdomainDefaultNC = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$targetedADdomainDefaultNC = (Get-ADRootDSE -Server $targetedADdomainRWDC).defaultNamingContext
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$targetedADdomainDefaultNC = (Get-ADRootDSE -Server $targetedADdomainRWDC -Credential $adminCreds).defaultNamingContext
}
# Determine The DN Of The Users Container Of The Targeted Domain
$containerForTestKrbTgtAccount = $null
$containerForTestKrbTgtAccount = "CN=Users," + $targetedADdomainDefaultNC
# Set The SamAccountName For The Test/Bogus KrbTgt Account
$testKrbTgtObjectSamAccountName = $null
$testKrbTgtObjectSamAccountName = $krbTgtSamAccountName
# Set The Name For The Test/Bogus KrbTgt Account
$testKrbTgtObjectName = $null
$testKrbTgtObjectName = $testKrbTgtObjectSamAccountName
# Set The Description For The Test/Bogus KrbTgt Account
$testKrbTgtObjectDescription = $null
# Set The Description For The Test/Bogus KrbTgt Account For RWDCs
If ($krbTgtUse -eq "RWDC") {
$testKrbTgtObjectDescription = "Test Copy Representing '$($krbTgtSamAccountName.SubString(0,$krbTgtSamAccountName.IndexOf('_TEST')))' - Key Distribution Center Service Account"
}
# Set The Description For The Test/Bogus KrbTgt Account For RODCs
If ($krbTgtUse -eq "RODC") {
$testKrbTgtObjectDescription = "Test Copy Representing '$($krbTgtSamAccountName.SubString(0,$krbTgtSamAccountName.IndexOf('_TEST')))' - Key Distribution Center service account for read-only domain controller"
}
# Generate The DN Of The Test KrbTgt Object
$testKrbTgtObjectDN = $null
$testKrbTgtObjectDN = "CN=" + $testKrbTgtObjectName + "," + $containerForTestKrbTgtAccount
Logging " --> RWDC To Create Object On..............: '$targetedADdomainRWDC'"
Logging " --> Full Name Test KrbTgt Account.........: '$testKrbTgtObjectName'"
Logging " --> Description...........................: '$testKrbTgtObjectDescription'"
Logging " --> Container Test KrbTgt Account.........: '$containerForTestKrbTgtAccount'"
# If The Test/Bogus KrbTgt Account Is Used By RWDCs
If ($krbTgtUse -eq "RWDC") {
$deniedRODCPwdReplGroupRID = "572"
$deniedRODCPwdReplGroupObjectSID = $targetedADdomainDomainSID + "-" + $deniedRODCPwdReplGroupRID
If ($localADforest -eq $true -Or ($localADforest -eq $false -And $remoteCredsUsed -eq $false)) {
$deniedRODCPwdReplGroupObjectName = (Get-ADGroup -Identity $deniedRODCPwdReplGroupObjectSID -Server $targetedADdomainRWDC).Name
}
If ($localADforest -eq $false -And $remoteCredsUsed -eq $true) {
$deniedRODCPwdReplGroupObjectName = (Get-ADGroup -Identity $deniedRODCPwdReplGroupObjectSID -Server $targetedADdomainRWDC -Credential $adminCreds).Name