forked from zjorz/Public-AD-Scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Reset-KrbTgt-Password-For-RWDCs-And-RODCs.ps1
4399 lines (4023 loc) · 236 KB
/
Reset-KrbTgt-Password-For-RWDCs-And-RODCs.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: [email protected]
###
### Paste The Following Quick Link Between The Double Quotes In Browser To Send Mail:
### --> "mailto:Jorge's Script Gallery <[email protected]>?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.
###
<#
.SYNOPSIS
This PoSH Script Resets The KrbTgt Password For RWDCs And RODCs In A Controlled Manner
.VERSION
v2.8, 2020-04-02 (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........: [email protected] ("mailto:Jorge's Script Gallery <[email protected]>?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 NO password reset of the chosen TEST KrbTgt account occurs. Basically this
just checks the status of the objects on scoped DCs. NOTHING is changed. 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 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 simulation mode, which is mode 5 where NO password reset of the chosen PROD KrbTgt account occurs. Basically this
just checks the status of the objects on scoped DCs. NOTHING is changed. Can be scoped for RWDCs and RODCs (single, multiple, all)
- For all scenarios, a real reset mode, which is mode 6 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 this script a DC is reachable/available, if its name is resolvable and connectivity is possible for all of the following ports:
TCP:135 (Endpoint Mapper), TCP:389 (LDAP) and TCP:9839 (AD Web Services)
- 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). It just checks and compares the state of the pwdLastSet attribute
on the source RWDC with other scoped DCs. Nothing is changed/updated!
* 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 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 5, depending on the scope, it uses PROD/REAL krbtgt account(s). It just checks and compares the state of the pwdLastSet attribute
on the source RWDC with other scoped DCs. Nothing is changed/updated!
- In mode 6, 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, 4, 5 or 6, 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, 4, 5 or 6 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
RODC itself is not available, then the RWDC with the PDC FSMO is used as the originating RWDC and the change will eventually replicate
to the RODC
- 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
- N.A.
.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.8, 2020-04-02, Jorge de Almeida Pinto [MVP-EMS]:
- Fixed an issue when the RODC itself is not reachable/available, whereas in that case, the source should be the RWDC with the PDC FSMO
- Checks to make sure both the RWDC with the PDC FSMO role and the nearest RWDC are available. If either one is not available, the script will abort
v2.7, 2020-04-02, Jorge de Almeida Pinto [MVP-EMS]:
- Added DNS name resolution check to the portConnectionCheck function
- To test membership of the administrators group in a remote AD forest the "title" attribute is now used instead of the "displayName" attribute to try to write to it
- Removed usage of $remoteADforest variable and only use the $localADforest variable
- Removed usage of $remoteCredsUsed variable and only use the $adminCrds variable (Was $adminCreds)
- Added a warning if the special purpose krbtgt account 'Krbtgt_AzureAD' is discovered in the AD domain
- If the number of RODCs in the AD domain is 0, then it will not present the options for RODCs
- If the number of RODCs in the AD domain is 1 of more, amd you chose to manually specify the FQDN of RODCs to process, it will present a list of RODCs to choose from
- Operational modes have been changed (WARNING: pay attention to what you choose!). The following modes are the new modes
- 1 - Informational Mode (No Changes At All)
- 2 - Simulation Mode | Temporary Canary Object Created To Test Replication Convergence!
- 3 - Simulation Mode | Use KrbTgt TEST/BOGUS Accounts - No Password Reset/WhatIf Mode!
- 4 - Real Reset Mode | Use KrbTgt TEST/BOGUS Accounts - Password Will Be Reset Once!
- 5 - Simulation Mode | Use KrbTgt PROD/REAL Accounts - No Password Reset/WhatIf Mode!
- 6 - Real Reset Mode | Use KrbTgt PROD/REAL Accounts - Password Will Be Reset Once!
- When choosing RODC Krb Tgt Account scope the following will now occur:
- If the RODC is not reachable, the real source RWDC of the RODC cannot be determined. In that case, the RWDC with the PDC FSMO role is used as the source for the change and replication
- If the RODC is reachable, but the real source RWDC of the RODC is not reachable it cannot be used as the source for the change and replication. In that case, the RWDC with the PDC FSMO role is used as the source for the change and replication
- Sections with '#XXX' have been removed
- Calls using the CMDlet 'Get-ADReplicationAttributeMetadata' (W2K12 and higher) have been replaced with .NET calls to support older OS'es such as W2K8 and W2K8R2. A function has been created to retrieve metadata
- Some parts were rewritten/optimized
v2.6, 2020-02-25, Jorge de Almeida Pinto [MVP-EMS]:
- Removed code that was commented out
- Logging where the script is being executed from
- Updated the function 'createTestKrbTgtADAccount' to also include the FQDN of the RODC for which the Test KrbTgt account is created for better recognition
- In addition to the port 135 (RPC Endpoint Mapper) and 389 (LDAP), the script will also check for port 9389 (AD Web Service) which is used by the ADDS PoSH CMDlets
- Updated script to included more 'try/catch' and more (error) logging, incl. line where it fails, when things go wrong to make troubleshooting easier
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
.EXAMPLE
Execute The Script
.\Reset-KrbTgt-Password-For-RWDCs-And-RODCs.ps1
.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 Title 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"
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) {
# Test To See If The HostName Is Resolvable At All
Try {
[System.Net.Dns]::gethostentry($fqdnServer) | Out-Null
} Catch {
Return "ERROR"
}
$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 {
$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($targetedADdomainRWDCFQDN, $krbTgtSamAccountName, $execDateTimeCustom1, $localADforest, $adminCrds) {
# Determine The DN Of The Default NC Of The Targeted Domain
$targetedADdomainDefaultNC = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And !$adminCrds)) {
Try {
$targetedADdomainDefaultNC = (Get-ADRootDSE -Server $targetedADdomainRWDCFQDN).defaultNamingContext
} Catch {
Logging "" "ERROR"
Logging "Error Connecting To '$targetedADdomainRWDCFQDN' For 'rootDSE'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
If ($localADforest -eq $false -And $adminCrds) {
Try {
$targetedADdomainDefaultNC = (Get-ADRootDSE -Server $targetedADdomainRWDCFQDN -Credential $adminCrds).defaultNamingContext
} Catch {
Logging "" "ERROR"
Logging "Error Connecting To '$targetedADdomainRWDCFQDN' For 'rootDSE' Using '$($adminCrds.UserName)'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
# 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..............: '$targetedADdomainRWDCFQDN'"
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 !$adminCrds)) {
New-ADObject -Type contact -Name $targetObjectToCheckName -Path $containerForTempCanaryObject -DisplayName $targetObjectToCheckName -Description $targetObjectToCheckDescription -Server $targetedADdomainRWDCFQDN
}
If ($localADforest -eq $false -And $adminCrds) {
New-ADObject -Type contact -Name $targetObjectToCheckName -Path $containerForTempCanaryObject -DisplayName $targetObjectToCheckName -Description $targetObjectToCheckDescription -Server $targetedADdomainRWDCFQDN -Credential $adminCrds
}
} Catch {
Logging " --> Temp Canary Object [$targetObjectToCheckDN] FAILED TO BE CREATED on RWDC [$targetedADdomainRWDCFQDN]!..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "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 !$adminCrds)) {
Try {
$targetObjectToCheck = Get-ADObject -LDAPFilter "(&(objectClass=contact)(name=$targetObjectToCheckName))" -Server $targetedADdomainRWDCFQDN
} Catch {
Logging "" "ERROR"
Logging "Error Querying AD Against '$targetedADdomainRWDCFQDN' For Contact Object With 'name=$targetObjectToCheckName'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
If ($localADforest -eq $false -And $adminCrds) {
Try {
$targetObjectToCheck = Get-ADObject -LDAPFilter "(&(objectClass=contact)(name=$targetObjectToCheckName))" -Server $targetedADdomainRWDCFQDN -Credential $adminCrds
} Catch {
Logging "" "ERROR"
Logging "Error Querying AD Against '$targetedADdomainRWDCFQDN' For Contact Object With 'name=$targetObjectToCheckName' Using '$($adminCrds.UserName)'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
If ($targetObjectToCheck) {
$targetObjectToCheckDN = $null
$targetObjectToCheckDN = $targetObjectToCheck.DistinguishedName
Logging " --> Temp Canary Object [$targetObjectToCheckDN] CREATED on RWDC [$targetedADdomainRWDCFQDN]!..." "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: Retrieve The Metadata Of An Object
Function retrieveObjectMetadata($targetedADdomainRWDCFQDN, $ObjectDN, $localADforest, $adminCrds) {
# Get The Metadata Of The Object, And More Specific Of The pwdLastSet Attribute Of That Object BEFORE THE PASSWORD SET
$objectMetadata = $null
$targetedADdomainRWDCContext = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And !$adminCrds)) {
$targetedADdomainRWDCContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("DirectoryServer", $targetedADdomainRWDCFQDN)
}
If ($localADforest -eq $false -And $adminCrds) {
$targetedADdomainRWDCContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("DirectoryServer", $targetedADdomainRWDCFQDN, $($adminCrds.UserName), $($adminCrds.GetNetworkCredential().Password))
}
$targetedADdomainRWDCObject = $null
Try {
$targetedADdomainRWDCObject = [System.DirectoryServices.ActiveDirectory.DomainController]::GetDomainController($targetedADdomainRWDCContext)
$objectMetadata = $targetedADdomainRWDCObject.GetReplicationMetadata($ObjectDN)
} Catch {
Logging "" "ERROR"
If ($localADforest -eq $true -Or ($localADforest -eq $false -And !$adminCrds)) {
Logging "Error Getting Metadata From '$targetedADdomainRWDCFQDN' For Object '$krbTgtObjectBeforeDN'..." "ERROR"
}
If ($localADforest -eq $false -And $adminCrds) {
Logging "Error Getting Metadata From '$targetedADdomainRWDCFQDN' For Object '$krbTgtObjectBeforeDN' Using '$($adminCrds.UserName)'..." "ERROR"
}
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
If ($objectMetadata) {
Return $($objectMetadata.Values)
}
}
### FUNCTION: Reset Password Of AD Account
Function setPasswordOfADAccount($targetedADdomainRWDCFQDN, $krbTgtSamAccountName, $localADforest, $adminCrds) {
# Retrieve The KrgTgt Object In The AD Domain BEFORE THE PASSWORD SET
$krbTgtObjectBefore = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And !$adminCrds)) {
Try {
$krbTgtObjectBefore = Get-ADUser -LDAPFilter "(sAMAccountName=$krbTgtSamAccountName)" -Properties * -Server $targetedADdomainRWDCFQDN
} Catch {
Logging "" "ERROR"
Logging "Error Querying AD Against '$targetedADdomainRWDCFQDN' For User Object With 'sAMAccountName=$krbTgtSamAccountName'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
If ($localADforest -eq $false -And $adminCrds) {
Try {
$krbTgtObjectBefore = Get-ADUser -LDAPFilter "(sAMAccountName=$krbTgtSamAccountName)" -Properties * -Server $targetedADdomainRWDCFQDN -Credential $adminCrds
} Catch {
Logging "" "ERROR"
Logging "Error Querying AD Against '$targetedADdomainRWDCFQDN' For Contact Object With 'sAMAccountName=$krbTgtSamAccountName' Using '$($adminCrds.UserName)'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
# 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
$objectMetadataBefore = $null
$objectMetadataBefore = retrieveObjectMetadata $targetedADdomainRWDCFQDN $krbTgtObjectBeforeDN $localADforest $adminCrds
$objectMetadataBeforeAttribPwdLastSet = $null
$objectMetadataBeforeAttribPwdLastSet = $objectMetadataBefore | Where-Object{$_.Name -eq "pwdLastSet"}
$objectMetadataBeforeAttribPwdLastSetOrgRWDCFQDN = $null
$objectMetadataBeforeAttribPwdLastSetOrgRWDCFQDN = If ($objectMetadataBeforeAttribPwdLastSet.OriginatingServer) {$objectMetadataBeforeAttribPwdLastSet.OriginatingServer} Else {"RWDC Demoted"}
$objectMetadataBeforeAttribPwdLastSetOrgTime = $null
$objectMetadataBeforeAttribPwdLastSetOrgTime = Get-Date $($objectMetadataBeforeAttribPwdLastSet.LastOriginatingChangeTime) -f "yyyy-MM-dd HH:mm:ss"
$objectMetadataBeforeAttribPwdLastSetVersion = $null
$objectMetadataBeforeAttribPwdLastSetVersion = $objectMetadataBeforeAttribPwdLastSet.Version
Logging " --> RWDC To Reset Password On.............: '$targetedADdomainRWDCFQDN'"
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 !$adminCrds)) {
Set-ADAccountPassword -Identity $krbTgtObjectBeforeDN -Server $targetedADdomainRWDCFQDN -Reset -NewPassword $newKrbTgtPasswordSecure
}
If ($localADforest -eq $false -And $adminCrds) {
Set-ADAccountPassword -Identity $krbTgtObjectBeforeDN -Server $targetedADdomainRWDCFQDN -Reset -NewPassword $newKrbTgtPasswordSecure -Credential $adminCrds
}
} Catch {
Logging ""
Logging " --> Setting the new password for [$krbTgtObjectBeforeDN] FAILED on RWDC [$targetedADdomainRWDCFQDN]!..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "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 !$adminCrds)) {
Try {
$krbTgtObjectAfter = Get-ADUser -LDAPFilter "(sAMAccountName=$krbTgtSamAccountName)" -Properties * -Server $targetedADdomainRWDCFQDN
} Catch {
Logging "" "ERROR"
Logging "Error Querying AD Against '$targetedADdomainRWDCFQDN' For User Object With 'sAMAccountName=$krbTgtSamAccountName'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
If ($localADforest -eq $false -And $adminCrds) {
Try {
$krbTgtObjectAfter = Get-ADUser -LDAPFilter "(sAMAccountName=$krbTgtSamAccountName)" -Properties * -Server $targetedADdomainRWDCFQDN -Credential $adminCrds
} Catch {
Logging "" "ERROR"
Logging "Error Querying AD Against '$targetedADdomainRWDCFQDN' For User Object With 'sAMAccountName=$krbTgtSamAccountName' Using '$($adminCrds.UserName)'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
# 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
$objectMetadataAfter = $null
$objectMetadataAfter = retrieveObjectMetadata $targetedADdomainRWDCFQDN $krbTgtObjectAfterDN $localADforest $adminCrds
$objectMetadataAfterAttribPwdLastSet = $null
$objectMetadataAfterAttribPwdLastSet = $objectMetadataAfter | Where-Object{$_.Name -eq "pwdLastSet"}
$objectMetadataAfterAttribPwdLastSetOrgRWDCFQDN = $null
$objectMetadataAfterAttribPwdLastSetOrgRWDCFQDN = If ($objectMetadataAfterAttribPwdLastSet.OriginatingServer) {$objectMetadataAfterAttribPwdLastSet.OriginatingServer} Else {"RWDC Demoted"}
$objectMetadataAfterAttribPwdLastSetOrgTime = $null
$objectMetadataAfterAttribPwdLastSetOrgTime = Get-Date $($objectMetadataAfterAttribPwdLastSet.LastOriginatingChangeTime) -f "yyyy-MM-dd HH:mm:ss"
$objectMetadataAfterAttribPwdLastSetVersion = $null
$objectMetadataAfterAttribPwdLastSetVersion = $objectMetadataAfterAttribPwdLastSet.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.............: '$objectMetadataBeforeAttribPwdLastSetOrgRWDCFQDN'"
If ($krbTgtObjectAfterPwdLastSet -ne $krbTgtObjectBeforePwdLastSet) {
Logging " --> New Originating RWDC..................: '$objectMetadataAfterAttribPwdLastSetOrgRWDCFQDN'"
}
Logging ""
Logging " --> Previous Originating Time.............: '$objectMetadataBeforeAttribPwdLastSetOrgTime'"
If ($krbTgtObjectAfterPwdLastSet -ne $krbTgtObjectBeforePwdLastSet) {
Logging " --> New Originating Time..................: '$objectMetadataAfterAttribPwdLastSetOrgTime'"
}
Logging ""
Logging " --> Previous Version Of Attribute Value...: '$objectMetadataBeforeAttribPwdLastSetVersion'"
If ($krbTgtObjectAfterPwdLastSet -ne $krbTgtObjectBeforePwdLastSet) {
Logging " --> New Version Of Attribute Value........: '$objectMetadataAfterAttribPwdLastSetVersion'"
}
# 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 [$targetedADdomainRWDCFQDN]!..." "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, $adminCrds) {
# Define And Target The root DSE Context
$rootDSE = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And !$adminCrds)) {
Try {
$rootDSE = [ADSI]"LDAP://$targetDCFQDN/rootDSE"
} Catch {
Logging "" "ERROR"
Logging "Error Connecting To '$targetDCFQDN' For 'rootDSE'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
If ($localADforest -eq $false -And $adminCrds) {
Try {
$rootDSE = New-Object System.DirectoryServices.DirectoryEntry(("LDAP://$targetDCFQDN/rootDSE"),$($adminCrds.UserName), $($adminCrds.GetNetworkCredential().password))
} Catch {
Logging "" "ERROR"
Logging "Error Connecting To '$targetDCFQDN' For 'rootDSE' Using '$($adminCrds.UserName)'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
# Perform A Replicate Single Object For The Complete Object
If ($contentScope -eq "Full") {
Try {
$rootDSE.Put("replicateSingleObject",$sourceDCNTDSSettingsObjectDN+":"+$objectDN)
} Catch {
Logging "" "ERROR"
Logging "Replicate Single Object (Full) Failed From '$sourceDCNTDSSettingsObjectDN' To '$targetDCFQDN' For Object '$objectDN'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
# Perform A Replicate Single Object For Obnly The Secrets Of The Object
If ($contentScope -eq "Secrets") {
Try {
$rootDSE.Put("replicateSingleObject",$sourceDCNTDSSettingsObjectDN+":"+$objectDN+":SECRETS_ONLY")
} Catch {
Logging "" "ERROR"
Logging "Replicate Single Object (Secrets Only) Failed From '$sourceDCNTDSSettingsObjectDN' To '$targetDCFQDN' For Object '$objectDN'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
# Commit The Change To The Operational Attribute
Try {
$rootDSE.SetInfo()
} Catch {
Logging "" "ERROR"
Logging "Triggering Replicate Single Object On '$targetDCFQDN' From '$sourceDCNTDSSettingsObjectDN' Failed For Object '$objectDN' Using The '$contentScope' Scope..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
### FUNCTION: Delete/Cleanup Temporary Canary Object
Function deleteTempCanaryObject($targetedADdomainRWDCFQDN, $targetObjectToCheckDN, $localADforest, $adminCrds) {
# 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 !$adminCrds)) {
Remove-ADObject -Identity $targetObjectToCheckDN -Server $targetedADdomainRWDCFQDN -Confirm:$false
}
If ($localADforest -eq $false -And $adminCrds) {
Remove-ADObject -Identity $targetObjectToCheckDN -Server $targetedADdomainRWDCFQDN -Credential $adminCrds -Confirm:$false
}
} Catch {
Logging " --> Temp Canary Object [$targetObjectToCheckDN] FAILED TO BE DELETED on RWDC [$targetedADdomainRWDCFQDN]!..." "ERROR"
Logging " --> Manually delete the Temp Canary Object [$targetObjectToCheckDN] on RWDC [$targetedADdomainRWDCFQDN]!..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "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 !$adminCrds)) {
Try {
$targetObjectToCheck = Get-ADObject -LDAPFilter "(distinguishedName=$targetObjectToCheckDN)" -Server $targetedADdomainRWDCFQDN
} Catch {
Logging "" "ERROR"
Logging "Error Querying AD Against '$targetedADdomainRWDCFQDN' For User Object With 'distinguishedName=$targetObjectToCheckDN'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
If ($localADforest -eq $false -And $adminCrds) {
Try {
$targetObjectToCheck = Get-ADObject -LDAPFilter "(distinguishedName=$targetObjectToCheckDN)" -Server $targetedADdomainRWDCFQDN -Credential $adminCrds
} Catch {
Logging "" "ERROR"
Logging "Error Querying AD Against '$targetedADdomainRWDCFQDN' For User Object With 'distinguishedName=$targetObjectToCheckDN' Using '$($adminCrds.UserName)'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
If (!$targetObjectToCheck) {
Logging " --> Temp Canary Object [$targetObjectToCheckDN] DELETED on RWDC [$targetedADdomainRWDCFQDN]!..." "REMARK"
Logging "" "REMARK"
}
}
### FUNCTION: Check AD Replication Convergence
Function checkADReplicationConvergence($targetedADdomainFQDN, $targetedADdomainSourceRWDCFQDN, $targetObjectToCheckDN, $listOfDCsToCheckObjectOnStart, $listOfDCsToCheckObjectOnEnd, $modeOfOperationNr, $localADforest, $adminCrds) {
# 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
# Type (RWDC Or RODC) Of The DC To Check
$dcToCheckDSType = $null
$dcToCheckDSType = $dcToCheck."DS Type"
# SiteName Of The DC To Check
$dcToCheckSiteName = $null
$dcToCheckSiteName = $dcToCheck."Site Name"
# 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"
# If Mode 3, Simulate Password Reset Of KrbTgt TEST/BOGUS Accounts (No Password Reset/WhatIf Mode)
# If Mode 4, Do A Real Password Reset Of KrbTgt TEST/BOGUS Accounts (Password Reset!)
# If Mode 5, Simulate Password Reset Of KrbTgt PROD/REAL Accounts (No Password Reset/WhatIf Mode)
# If Mode 6, Do A Real Password Reset Of KrbTgt PROD/REAL Accounts (Password Reset!)
If ($modeOfOperationNr -eq 3 -Or $modeOfOperationNr -eq 4 -Or $modeOfOperationNr -eq 5 -Or $modeOfOperationNr -eq 6) {
# Retrieve The Object From The Source Originating RWDC
$objectOnSourceOrgRWDC = $null
If ($localADforest -eq $true -Or ($localADforest -eq $false -And !$adminCrds)) {
Try {
$objectOnSourceOrgRWDC = Get-ADObject -Identity $targetObjectToCheckDN -Properties * -Server $targetedADdomainSourceRWDCFQDN
} Catch {
Logging "" "ERROR"
Logging "Error Querying AD Against '$targetedADdomainSourceRWDCFQDN' For Object '$targetObjectToCheckDN'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
If ($localADforest -eq $false -And $adminCrds) {
Try {
$objectOnSourceOrgRWDC = Get-ADObject -Identity $targetObjectToCheckDN -Properties * -Server $targetedADdomainSourceRWDCFQDN -Credential $adminCrds
} Catch {
Logging "" "ERROR"
Logging "Error Querying AD Against '$targetedADdomainSourceRWDCFQDN' For Object '$targetObjectToCheckDN' Using '$($adminCrds.UserName)'..." "ERROR"
Logging "" "ERROR"
Logging "Exception Type......: $($_.Exception.GetType().FullName)" "ERROR"
Logging "" "ERROR"
Logging "Exception Message...: $($_.Exception.Message)" "ERROR"
Logging "" "ERROR"
Logging "Error On Script Line: $($_.InvocationInfo.ScriptLineNumber)" "ERROR"
Logging "" "ERROR"
}
}
# 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 Or 5 Or 6 Only
If ($modeOfOperationNr -eq 3 -Or $modeOfOperationNr -eq 4 -Or $modeOfOperationNr -eq 5 -Or $modeOfOperationNr -eq 6) {
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 -eq $true) {
# 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 Use The DSA DN Of The Source (Originating) RWDC Of The DC Being Checked