Browse Source

adding compondent scaffolder

Peter Alcock 8 months ago
parent
commit
976ddb96f9
100 changed files with 17743 additions and 0 deletions
  1. 252 0
      active-directory-lab-hybrid-adfs/.gitignore
  2. 21 0
      active-directory-lab-hybrid-adfs/LICENSE
  3. 96 0
      active-directory-lab-hybrid-adfs/README.md
  4. 27 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs.sln
  5. BIN
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC.zip
  6. 345 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/adDSCConfiguration.ps1
  7. 91 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Assert-HADC.ps1
  8. 92 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Assert-ParentChildDomains.ps1
  9. 601 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADCommon/MSFT_xADCommon.ps1
  10. 4 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADCommon/MSFT_xADCommon.psm1
  11. 521 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADComputer/MSFT_xADComputer.psm1
  12. 20 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADComputer/MSFT_xADComputer.schema.mof
  13. 25 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADComputer/en-US/MSFT_xADComputer.psd1
  14. 347 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomain/MSFT_xADDomain.psm1
  15. 13 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomain/MSFT_xADDomain.schema.mof
  16. 224 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.psm1
  17. 11 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.schema.mof
  18. 226 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/MSFT_xADDomainDefaultPasswordPolicy.psm1
  19. 17 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/MSFT_xADDomainDefaultPasswordPolicy.schema.mof
  20. 398 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1
  21. 13 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.schema.mof
  22. 492 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADGroup/MSFT_xADGroup.psm1
  23. 19 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADGroup/MSFT_xADGroup.schema.mof
  24. 221 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADOrganizationalUnit/MSFT_xADOrganizationalUnit.psm1
  25. 12 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADOrganizationalUnit/MSFT_xADOrganizationalUnit.schema.mof
  26. 39 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADRecycleBin/Examples/xActiveDirectory_xADRecycleBin.ps1
  27. 189 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADRecycleBin/MSFT_xADRecycleBin.psm1
  28. 8 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADRecycleBin/MSFT_xADRecycleBin.schema.mof
  29. 6 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADRecycleBin/ResourceDesignerScripts/GeneratexADRecycleBinSchema.ps1
  30. 1017 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADUser/MSFT_xADUser.psm1
  31. 50 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADUser/MSFT_xADUser.schema.mof
  32. 185 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xWaitForADDomain/MSFT_xWaitForADDomain.psm1
  33. 9 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xWaitForADDomain/MSFT_xWaitForADDomain.schema.mof
  34. 23 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/HADCConfiguration.psd1
  35. 21 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/LICENSE
  36. 17 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Misc/New-ADDomainTrust.ps1
  37. BIN
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/PSGetModuleInfo.xml
  38. 24 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/ParentChildConfig.psd1
  39. 1164 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/README.md
  40. 530 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADCommon.Tests.ps1
  41. 532 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADComputer.Tests.ps1
  42. 406 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADDomain.Tests.ps1
  43. 245 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADDomainController.Tests.ps1
  44. 289 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADDomainDefaultPasswordPolicy.Tests.ps1
  45. 539 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADGroup.Tests.ps1
  46. 293 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADOrganizationalUnit.Tests.ps1
  47. 455 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADUser.Tests.ps1
  48. 175 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xWaitForADDomain.Tests.ps1
  49. 66 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/xActiveDirectory.psd1
  50. 898 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/xActiveDirectory_TechNetDocumentation.html
  51. 163 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/CommonResourceHelper.psm1
  52. 628 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsCertificationAuthority/MSFT_xAdcsCertificationAuthority.psm1
  53. 27 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsCertificationAuthority/MSFT_xAdcsCertificationAuthority.schema.mof
  54. 13 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsCertificationAuthority/en-us/MSFT_xAdcsCertificationAuthority.strings.psd1
  55. 262 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsOnlineResponder/MSFT_xAdcsOnlineResponder.psm1
  56. 7 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsOnlineResponder/MSFT_xAdcsOnlineResponder.schema.mof
  57. 13 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsOnlineResponder/en-us/MSFT_xAdcsOnlineResponder.strings.psd1
  58. 281 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsWebEnrollment/MSFT_xAdcsWebEnrollment.psm1
  59. 9 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsWebEnrollment/MSFT_xAdcsWebEnrollment.schema.mof
  60. 13 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsWebEnrollment/en-us/MSFT_xAdcsWebEnrollment.strings.psd1
  61. 258 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Examples/Config-SetupActiveDirectory.ps1
  62. 21 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/LICENSE
  63. BIN
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/PSGetModuleInfo.xml
  64. 204 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/README.md
  65. 34 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Integration/IntegrationTestsCommon.psm1
  66. 141 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Integration/MSFT_xAdcsCertificationAuthority.Tests.ps1
  67. 11 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Integration/MSFT_xAdcsCertificationAuthority_Install.config.ps1
  68. 11 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Integration/MSFT_xAdcsCertificationAuthority_Uninstall.config.ps1
  69. 383 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Unit/MSFT_xAdcsCertificationAuthority.Tests.ps1
  70. 252 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Unit/MSFT_xAdcsOnlineResponder.Tests.ps1
  71. 259 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Unit/MSFT_xAdcsWebEnrollment.Tests.ps1
  72. 87 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/xAdcsDeployment.psd1
  73. 241 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/CertificateCommon/CertificateCommon.psm1
  74. 6 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/CertificateCommon/en-us/CertificateCommon.strings.psd1
  75. 661 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xCertReq/MSFT_xCertReq.psm1
  76. 16 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xCertReq/MSFT_xCertReq.schema.mof
  77. 22 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xCertReq/en-us/MSFT_xCertReq.strings.psd1
  78. 278 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xCertificateImport/MSFT_xCertificateImport.psm1
  79. 9 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xCertificateImport/MSFT_xCertificateImport.schema.mof
  80. 11 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xCertificateImport/en-us/MSFT_xCertificateImport.strings.psd1
  81. 325 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xPfxImport/MSFT_xPfxImport.psm1
  82. 11 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xPfxImport/MSFT_xPfxImport.schema.mof
  83. 11 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xPfxImport/en-us/MSFT_xPfxImport.strings.psd1
  84. 725 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/PDT/PDT.psm1
  85. 10 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/PDT/en-us/PDT.strings.psd1
  86. 54 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Examples/Sample_xCertReq_RequestAltSSLCert.ps1
  87. 52 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Examples/Sample_xCertReq_RequestSSLCert.ps1
  88. 22 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Examples/Sample_xCertificateImport_MinimalUsage.ps1
  89. 51 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Examples/Sample_xPfxImport_IIS_WebSite.ps1
  90. 41 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Examples/Sample_xPfxImport_MinimalUsage.ps1
  91. 22 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/LICENSE
  92. BIN
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/PSGetModuleInfo.xml
  93. 367 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/ReadMe.md
  94. 109 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xCertReq.Integration.Tests.ps1
  95. 47 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xCertReq.config.ps1
  96. 119 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xCertificateImport.Integration.Tests.ps1
  97. 17 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xCertificateImport_add.config.ps1
  98. 17 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xCertificateImport_remove.config.ps1
  99. 135 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xPfxImport.Integration.Tests.ps1
  100. 19 0
      active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xPfxImport_add.config.ps1

+ 252 - 0
active-directory-lab-hybrid-adfs/.gitignore

@@ -0,0 +1,252 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml

+ 21 - 0
active-directory-lab-hybrid-adfs/LICENSE

@@ -0,0 +1,21 @@
+    MIT License
+
+    Copyright (c) Microsoft Corporation. All rights reserved.
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in all
+    copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+    SOFTWARE

+ 96 - 0
active-directory-lab-hybrid-adfs/README.md

@@ -0,0 +1,96 @@
+# Azure Active Directory Hybrid ADFS Lab
+## Creates full AD/CA/ADFS/WAP environment with Azure AD Connect installed
+## Quick Start
+
+Description | Link
+--- | ---
+Full deploy - AD, ADFS, WAP | <a href="https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Factive-directory-lab-hybrid-adfs%2Fmaster%2Flab-hybrid-adfs%2FNoClientDeploy.json" target="_blank"><img src="http://azuredeploy.net/deploybutton.png"/></a>
+Full deploy - AD, ADFS, WAP, _with client machines*_ | <a href="https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure-Samples%2Factive-directory-lab-hybrid-adfs%2Fmaster%2Flab-hybrid-adfs%2FFullDeploy.json" target="_blank"><img src="http://azuredeploy.net/deploybutton.png"/></a>
+
+## Details
+* Deploys the following infrastructure:
+ * Virtual Network
+  * 3 subnets: AD, DMZ, Client
+  * 3 Network Security Groups:
+    * AD - permits AD traffic, RDP incoming to network; limits DMZ access
+    * Client - permissive; restricts traffic to DMZ
+    * DMZ - restrictive; permits 443 traffic to Internal, RDP from internal, very limited traffic from Internal, no traffic to Internet or Internal
+  * Public IP Address for each node
+  * AD VM
+	* DSC installs AD, CA roles, generates certificate for use by ADFS and WAP
+    * Certificate is based on the public IP/DNS of the WAP deployment
+    * Split-brain DNS on the DC is configured for the ADFS URL
+    * The Azure vNet is updated with a custom DNS entry pointing to the DC
+    * Test users are created in the local AD by passing in an array. There is an array sample set as the default value in the deployment template.
+    * Azure Active Directory Connect is installed and available to configure.
+  * ADFS VM
+	* DSC installs ADFS Role, pulls and installs cert from CA on the DC
+    * CustomScriptExtension configures the ADFS farm
+    * For unique testing scenarios, multiple distinct farms may be specified
+    * Azure Active Directory Connect is installed and available to configure.
+  * WAP VM - one for each ADFS VM
+	* DSC installs WAP role
+    * CustomScriptExtension copies and installs the cert from the DC and connects to the ADFS farm
+
+## Notes
+* _A template is included for Client creation via MSDN images. You will need to update the URL to point to your images. Images must be named "OSImage_Win&lt;version&gt;"._ You are responsible for validating your licensing rights for client MSDN images in your subscription.
+* The NSGs defined are for reference, but they aren't production-ready as holes are also opened for RDP to each VM directly, and public IPs are allocated for each VM as well
+* One VM size is specified for all VMs
+* Managed disks are used for all VMs - no storage accounts are created for diagnostics
+* The root CA cert is usually updated automatically to domain-joined clients within hours. To accelerate this, an easy workaround is to reboot the client VM.
+* In the AD DSC template, there is a commented draft of some code to push the ADFS FQDN out as an "Intranet Zone" site to the client machines - we've punted on that for now, so you will have to do this manually on client VMs in order to get ADFS SSO.
+
+## Caveats
+* There has been an intermittent bug with regards to the script block that enables RDP access for non-admin (test) users within deployed client VMs (when the client deploy script is used). It doesn't affect the base lab functionality, but if you see an error regarding 'ConfigRDPUsers' during deployment, the AD admin may need to enable non-admin RDP access before a test user can login to one of the client VMs to try client SSO scenarios.
+
+## NOTICE/WARNING
+* This template is explicitely designed for a lab environment. A few compromises were made, especially with regards to credential passing to DSC and script automation, that WILL result in clear text passwords being left behind in the DSC/scriptextension package folders, Azure log folders, and system event logs on the resulting VMs. 
+
+## Bonus
+The "deploy.ps1" file above can be downloaded and run locally against this repo, and offers a few additional features:
+* After the deployment completes, it will create a folder on your desktop with the name of the resource group
+* It will then create an RDP connectoid in that folder for each server and client that was deployed.
+* It will then create an HTTP shortcut to the ADFS WAP endpoint for testing and confirming the deployment.
+
+The deploy script master has a [line](https://github.com/Azure-Samples/active-directory-lab-hybrid-adfs/blob/master/lab-hybrid-adfs/deploy.ps1#L48) that allows you to separate your specific variables from the master via dot-sourcing. Here's a sample dot-sourced variable overrides file:
+```powershell
+#Login if necessary
+$AzureSub = "My Azure Subscription"try { $ctx=Get-AzureRmContext -ErrorAction Stop }
+catch { Login-AzureRmAccount }
+if ($ctx.SubscriptionName -ne $AzureSub) { Set-AzureRmContext -SubscriptionName $AzureSub }
+
+#DEPLOYMENT OPTIONS
+    $Branch                  = "master"
+
+    $VNetAddrSpace2ndOctet   = "2"
+    $RGName                  = "TestRG$VNetAddrSpace2ndOctet"
+    $DeployRegion            = "West US 2"
+    $userName                = "localAdmin"
+    $secpasswd               = "CrazyP@ssword"
+    $adDomainName            = "aadpoctest.com"
+    #$clientsToDeploy         = @("10-1511","10-1607","10-1703","7","8")
+    $clientsToDeploy         = @("7")
+    $clientImageBaseResource = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/ImageRG/providers/Microsoft.Compute/images/"
+    $AdfsFarmCount           = "1";
+    $AssetLocation           = "https://raw.githubusercontent.com/Azure-Samples/active-directory-lab-hybrid-adfs/$Branch/lab-hybrid-adfs/"
+
+    $usersArray              = @(
+                                @{ "FName"= "Bob"; "LName"= "Jones"; "SAM"= "bjones" },
+                                @{ "FName"= "Bill"; "LName"= "Smith"; "SAM"= "bsmith" },
+                                @{ "FName"= "Mary"; "LName"= "Phillips"; "SAM"= "mphillips" },
+                                @{ "FName"= "Sue"; "LName"= "Jackson"; "SAM"= "sjackson" }
+                            );
+    $defaultUserPassword     = "P@ssw0rd"
+    $RDPWidth                = 1680
+    $RDPHeight               = 1050
+
+#END DEPLOYMENT OPTIONS
+
+```
+
+ 
+## Contributing
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
+
+

+ 27 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs.sln

@@ -0,0 +1,27 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Files", "Files", "{5C157692-6690-4426-AAA2-3747F923FEB9}"
+	ProjectSection(SolutionItems) = preProject
+		README.md = README.md
+	EndProjectSection
+EndProject
+Project("{151D2E53-A2C4-4D7D-83FE-D05416EBD58E}") = "lab-hybrid-adfs", "lab-hybrid-adfs\lab-hybrid-adfs.deployproj", "{8F3307B1-DC7F-4D1A-A719-82A29C8850AC}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{8F3307B1-DC7F-4D1A-A719-82A29C8850AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8F3307B1-DC7F-4D1A-A719-82A29C8850AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8F3307B1-DC7F-4D1A-A719-82A29C8850AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8F3307B1-DC7F-4D1A-A719-82A29C8850AC}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal

BIN
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC.zip


+ 345 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/adDSCConfiguration.ps1

@@ -0,0 +1,345 @@
+$DscWorkingFolder = $PSScriptRoot
+
+configuration DomainController
+{
+   param
+   (
+        [Parameter(Mandatory)]
+        [String]$Subject,
+
+        [Parameter(Mandatory)]
+        [Int]$ADFSFarmCount,
+
+        [Parameter(Mandatory)]
+        [System.Management.Automation.PSCredential]$AdminCreds,
+
+        [Parameter(Mandatory)]
+        [String]$ADFSIPAddress,
+
+		[Parameter(Mandatory)]
+		[Object]$usersArray,
+
+		[Parameter(Mandatory)]
+		[System.Management.Automation.PSCredential]$UserCreds,
+
+        [Int]$RetryCount=20,
+        [Int]$RetryIntervalSec=30
+    )
+    
+    $wmiDomain      = Get-WmiObject Win32_NTDomain -Filter "DnsForestName = '$( (Get-WmiObject Win32_ComputerSystem).Domain)'"
+    $shortDomain    = $wmiDomain.DomainName
+    $DomainName     = $wmidomain.DnsForestName
+    $ComputerName   = $wmiDomain.PSComputerName
+    $CARootName     = "$($shortDomain.ToLower())-$($ComputerName.ToUpper())-CA"
+    $CAServerFQDN   = "$ComputerName.$DomainName"
+
+	# NOTE: see adfsDeploy.json variable block to see how the internal IP is constructed 
+	#       (punting and reproducing logic here)
+	$adfsNetworkArr         = $ADFSIPAddress.Split('.')
+	$adfsStartIpNodeAddress = [int]$adfsNetworkArr[3]
+	$adfsNetworkString      = "$($adfsNetworkArr[0]).$($adfsNetworkArr[1]).$($adfsNetworkArr[2])."
+
+    $CertPw         = $AdminCreds.Password
+    $ClearPw        = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($CertPw))
+	$ClearDefUserPw = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($UserCreds.Password))
+
+    Import-DscResource -ModuleName xComputerManagement,xNetworking,xSmbShare,xAdcsDeployment,xCertificate,PSDesiredStateConfiguration
+
+    [System.Management.Automation.PSCredential]$DomainCreds = New-Object System.Management.Automation.PSCredential ("${shortDomain}\$($Admincreds.UserName)", $Admincreds.Password)
+    
+    Node 'localhost'
+    {
+        LocalConfigurationManager
+        {
+            DebugMode = 'All'
+            RebootNodeIfNeeded = $true
+        }
+
+        WindowsFeature ADCS-Cert-Authority
+        {
+            Ensure = 'Present'
+            Name = 'ADCS-Cert-Authority'
+        }
+
+        WindowsFeature RSAT-ADCS-Mgmt
+        {
+            Ensure = 'Present'
+            Name = 'RSAT-ADCS-Mgmt'
+        }
+
+        File SrcFolder
+        {
+            DestinationPath = "C:\src"
+            Type = "Directory"
+            Ensure = "Present"
+        }
+
+        xSmbShare SrcShare
+        {
+            Ensure = "Present"
+            Name = "src"
+            Path = "C:\src"
+            FullAccess = @("Domain Admins","Domain Computers")
+            ReadAccess = "Authenticated Users"
+            DependsOn = "[File]SrcFolder"
+        }
+
+        xADCSCertificationAuthority ADCS
+        {
+            Ensure = 'Present'
+            Credential = $DomainCreds
+            CAType = 'EnterpriseRootCA'
+            DependsOn = '[WindowsFeature]ADCS-Cert-Authority'             
+        }
+
+        WindowsFeature ADCS-Web-Enrollment
+        {
+            Ensure = 'Present'
+            Name = 'ADCS-Web-Enrollment'
+            DependsOn = '[WindowsFeature]ADCS-Cert-Authority','[WindowsFeature]ADCS-Cert-Authority'
+        }
+
+        xADCSWebEnrollment CertSrv
+        {
+            Ensure = 'Present'
+            IsSingleInstance = 'Yes'
+            Credential = $DomainCreds
+            DependsOn = '[WindowsFeature]ADCS-Web-Enrollment','[xADCSCertificationAuthority]ADCS'
+        }
+
+		Script ExportRoot
+		{
+			SetScript = {
+							$arr       = $($using:DomainName).split('.')
+							$d         = $($using:shortDomain).ToLower()
+							$c         = $($using:ComputerName).ToUpper()
+							$shortname = "$d-$c-CA"
+                            $rootName  = "CN={0}, {1}" -f $shortname, [string]::Join(", ", ($arr | % { "DC={0}" -f $_ }))
+
+							$rootcert  = Get-ChildItem Cert:\LocalMachine\CA | where {$_.Subject -eq "$rootName"}
+							if ($rootcert -eq $null) {
+							    Write-Verbose "ERROR: ROOT CERT `"$rootName`" NOT FOUND, cancelling cert export"
+							} else {
+								$root      = if ($rootcert.GetType().BaseType.Name -eq "Array") {$rootCert[0]} else {$rootCert}
+								Export-Certificate -FilePath "c:\src\$shortname.cer" -Cert $root
+							}
+
+						}
+			TestScript = {
+					$arr       = $($using:DomainName).split('.')
+					$d         = $($using:shortDomain).ToLower()
+					$c         = $($using:ComputerName).ToUpper()
+					$shortname = "$d-$c-CA"
+					return Test-Path "C:\src\$shortname.cer"
+			}
+			GetScript = { @{} }
+			DependsOn = '[xADCSWebEnrollment]CertSrv'
+		}
+
+        for($instance=1; $instance -le $ADFSFarmCount; $instance++) {
+
+			xCertReq "SSLCert$instance"
+			{
+				CARootName                = "$CARootName"
+				CAServerFQDN              = "$ComputerName.$DomainName"
+				Subject                   = "$Subject" -f $instance
+				KeyLength                 = 2048
+				Exportable                = $true
+				ProviderName              = '"Microsoft RSA SChannel Cryptographic Provider"'
+				OID                       = '1.3.6.1.5.5.7.3.1'
+				KeyUsage                  = '0xa0'
+				CertificateTemplate       = 'WebServer'
+				AutoRenew                 = $true
+				Credential                = $DomainCreds
+				DependsOn                 = '[xADCSWebEnrollment]CertSrv'
+			}
+
+			Script "SaveCert$instance"
+			{
+				SetScript  = {
+								 $s = $using:subject;
+								 $s = $s -f $using:instance
+								 write-verbose "subject = $s";
+								 $cert = Get-ChildItem Cert:\LocalMachine\My | where {$_.Subject -eq "CN=$s"}
+								 Export-PfxCertificate -FilePath "c:\src\$s.pfx" -Cert $cert -Password (ConvertTo-SecureString $Using:ClearPw -AsPlainText -Force)
+							 }
+
+				GetScript  = { @{ 
+									$s = $using:subject;
+									$s = $s -f $using:instance
+									Result = (Get-Content "C:\src\$s.pfx") } 
+								}
+				TestScript = {
+								$s = $using:subject;
+								$s = $s -f $using:instance
+								return Test-Path "C:\src\$s.pfx" 
+							 }
+				DependsOn  = "[xCertReq]SSLCert$instance"
+			}
+
+			Script "UpdateDNS$instance"
+			{
+				SetScript  = {
+								$NodeAddr  = ([int]$($using:instance) + [int]$($using:adfsStartIpNodeAddress)) - 1
+								$IPAddress = "$($using:adfsNetworkString)$NodeAddr"
+
+								$s        = $using:subject;
+								$s        = $s -f $using:instance
+								$ZoneName = $s
+								$Zone     = Add-DnsServerPrimaryZone -Name $ZoneName -ReplicationScope Forest -PassThru
+								$rec      = Add-DnsServerResourceRecordA -ZoneName $ZoneName -Name "@" -AllowUpdateAny -IPv4Address $IPAddress
+							 }
+
+				GetScript =  { @{} }
+				TestScript = { 
+					$s        = $using:subject;
+					$s        = $s -f $using:instance
+					$ZoneName = $s
+					$Zone = Get-DnsServerZone -Name $ZoneName -ErrorAction SilentlyContinue
+					return ($Zone -ine $null)
+				}
+			}
+		}
+
+        Script InstallAADConnect
+        {
+            SetScript = {
+                $AADConnectDLUrl="https://download.microsoft.com/download/B/0/0/B00291D0-5A83-4DE7-86F5-980BC00DE05A/AzureADConnect.msi"
+                $exe="$env:SystemRoot\system32\msiexec.exe"
+
+                $tempfile = [System.IO.Path]::GetTempFileName()
+                $folder = [System.IO.Path]::GetDirectoryName($tempfile)
+
+                $webclient = New-Object System.Net.WebClient
+                $webclient.DownloadFile($AADConnectDLUrl, $tempfile)
+
+                Rename-Item -Path $tempfile -NewName "AzureADConnect.msi"
+                $MSIPath = $folder + "\AzureADConnect.msi"
+
+                Invoke-Expression "& `"$exe`" /i $MSIPath /qn /passive /forcerestart"
+            }
+
+            GetScript =  { @{} }
+            TestScript = { 
+                return Test-Path "$env:TEMP\AzureADConnect.msi" 
+            }
+        }
+
+        Script CreateOU
+        {
+            SetScript = {
+                $wmiDomain = Get-WmiObject Win32_NTDomain -Filter "DnsForestName = '$( (Get-WmiObject Win32_ComputerSystem).Domain)'"
+                $segments = $wmiDomain.DnsForestName.Split('.')
+                $path = [string]::Join(", ", ($segments | % { "DC={0}" -f $_ }))
+                New-ADOrganizationalUnit -Name "OrgUsers" -Path $path
+            }
+            GetScript =  { @{} }
+            TestScript = { 
+                $test=Get-ADOrganizationalUnit -Server "$using:ComputerName.$using:DomainName" -Filter 'Name -like "OrgUsers"' -ErrorAction SilentlyContinue
+                return ($test -ine $null)
+            }
+        }
+
+        Script AddTestUsers
+        {
+            SetScript = {
+                $wmiDomain = Get-WmiObject Win32_NTDomain -Filter "DnsForestName = '$( (Get-WmiObject Win32_ComputerSystem).Domain)'"
+                $mailDomain=$wmiDomain.DnsForestName
+                $server="$($wmiDomain.PSComputerName).$($wmiDomain.DnsForestName)"
+                $segments = $wmiDomain.DnsForestName.Split('.')
+                $OU = "OU=OrgUsers, {0}" -f [string]::Join(", ", ($segments | % { "DC={0}" -f $_ }))
+                
+                $folder=$using:DscWorkingFolder
+
+				$clearPw = $using:ClearDefUserPw
+				$Users = $using:usersArray
+
+                foreach ($User in $Users)
+                {
+                    $Displayname = $User.'FName' + " " + $User.'LName'
+                    $UserFirstname = $User.'FName'
+                    $UserLastname = $User.'LName'
+                    $SAM = $User.'SAM'
+                    $UPN = $User.'FName' + "." + $User.'LName' + "@" + $Maildomain
+                    #$Password = $User.'Password'
+                    $Password = $clearPw
+                    "$DisplayName, $Password, $SAM"
+                    New-ADUser `
+                        -Name "$Displayname" `
+                        -DisplayName "$Displayname" `
+                        -SamAccountName $SAM `
+                        -UserPrincipalName $UPN `
+                        -GivenName "$UserFirstname" `
+                        -Surname "$UserLastname" `
+                        -Description "$Description" `
+                        -AccountPassword (ConvertTo-SecureString $Password -AsPlainText -Force) `
+                        -Enabled $true `
+                        -Path "$OU" `
+                        -ChangePasswordAtLogon $false `
+                        -PasswordNeverExpires $true `
+                        -server $server `
+                        -EmailAddress $UPN
+                }
+            }
+            GetScript =  { @{} }
+            TestScript = { 
+				$Users = $using:usersArray
+                $samname=$Users[0].'SAM'
+                $user = get-aduser -filter {SamAccountName -eq $samname} -ErrorAction SilentlyContinue
+                return ($user -ine $null)
+            }
+            DependsOn  = '[Script]CreateOU'
+        }
+		
+        #using service credentials for ADFS for now
+		Script AddTools
+        {
+            SetScript  = {
+				# Install AAD Tools
+					md c:\temp -ErrorAction Ignore
+					Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
+
+					#Install-Module -Name Azure -AllowClobber -Force
+					#Install-Module -Name AzureRM -AllowClobber -Force
+
+					Install-Module -Name MSOnline -Force
+
+					Install-Module -Name AzureAD -Force
+
+					Install-Module -Name AzureADPreview -AllowClobber -Force
+                }
+
+            GetScript =  { @{} }
+            TestScript = { 
+                $key=Get-Module -Name MSOnline -ListAvailable
+                return ($key -ine $null)
+            }
+            #Credential = $DomainCreds
+            #PsDscRunAsCredential = $DomainCreds
+
+            DependsOn = '[xADCSWebEnrollment]CertSrv'
+        }
+		
+
+        <#
+        Script UpdateAdfsSiteGPO
+        {
+            SetScript = {
+                $SiteName = $using:Subject
+                $TargetGPO = Get-GPO -Name "Default Domain Policy"
+                $ZoneName = '1'
+                $TargetHive = 'HKEY_LOCAL_MACHINE'
+                $BaseKey = 'Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings'
+                $Key = "$($TargetHive)\$($BaseKey)\ZoneMapKey"
+
+                Set-GPRegistryValue -Guid $TargetGPO.Id -Additive -Key $Key -ValueName $SiteName -Type "String" -Value "1" | Out-Null
+            }
+            GetScript =  { @{} }
+            TestScript = { 
+                $CurrKey = Get-GPRegistryValue -Guid $TargetGPO.Id -Key $Key -ValueName $SiteName -ErrorAction SilentlyContinue
+                return ($CurrKey -ine $null)
+            }
+        }
+        #>
+    }
+}

+ 91 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Assert-HADC.ps1

@@ -0,0 +1,91 @@
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingComputerNameHardcoded', '')]
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
+param()
+
+# A configuration to Create High Availability Domain Controller
+$secpasswd = ConvertTo-SecureString "Adrumble@6" -AsPlainText -Force
+$domainCred = New-Object System.Management.Automation.PSCredential ("sva-dscdom\Administrator", $secpasswd)
+$safemodeAdministratorCred = New-Object System.Management.Automation.PSCredential ("sva-dscdom\Administrator", $secpasswd)
+$localcred = New-Object System.Management.Automation.PSCredential ("Administrator", $secpasswd)
+$redmondCred = Get-Credential -Message "Enter Credentials for DNS Delegation: "
+$newpasswd = ConvertTo-SecureString "Adrumble@7" -AsPlainText -Force
+$userCred = New-Object System.Management.Automation.PSCredential ("sva-dscdom\Administrator", $newpasswd)
+
+configuration AssertHADC
+{
+    Import-DscResource -ModuleName xActiveDirectory
+
+    Node $AllNodes.Where{$_.Role -eq "Primary DC"}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = "Present"
+            Name = "AD-Domain-Services"
+        }
+
+        xADDomain FirstDS
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domaincred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DnsDelegationCredential = $redmondCred
+            DependsOn = "[WindowsFeature]ADDSInstall"
+        }
+
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.DomainName
+            DomainUserCredential = $domaincred
+            RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = "[xADDomain]FirstDS"
+        }
+
+        xADUser FirstUser
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domaincred
+            UserName = "dummy"
+            Password = $userCred
+            Ensure = "Present"
+            DependsOn = "[xWaitForADDomain]DscForestWait"
+        }
+
+    }
+
+    Node $AllNodes.Where{$_.Role -eq "Replica DC"}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = "Present"
+            Name = "AD-Domain-Services"
+        }
+
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.DomainName
+            DomainUserCredential = $domaincred
+        RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = "[WindowsFeature]ADDSInstall"
+        }
+
+        xADDomainController SecondDC
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domaincred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DependsOn = "[xWaitForADDomain]DscForestWait"
+        }
+    }
+}
+
+$config = Invoke-Expression (Get-content $PSScriptRoot\HADCconfiguration.psd1 -Raw)
+AssertHADC -configurationData $config
+
+$computerName1 = "sva-dsc1"
+$computerName2 = "sva-dsc2"
+
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName $computerName1 -Path $PSScriptRoot\AssertHADC -Credential $localcred
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName $computerName2 -Path $PSScriptRoot\AssertHADC -Credential $localcred
+

+ 92 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Assert-ParentChildDomains.ps1

@@ -0,0 +1,92 @@
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingComputerNameHardcoded', '')]
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
+param()
+
+$secpasswd = ConvertTo-SecureString "Adrumble@6" -AsPlainText -Force
+$domainCred = New-Object System.Management.Automation.PSCredential ("sva-dscdom\Administrator", $secpasswd)
+$safemodeAdministratorCred = New-Object System.Management.Automation.PSCredential ("sva-dscdom\Administrator", $secpasswd)
+$localcred = New-Object System.Management.Automation.PSCredential ("Administrator", $secpasswd)
+$redmondCred = Get-Credential -Message "Enter Credentials for DNS Delegation: "
+$newpasswd = ConvertTo-SecureString "Adrumble@7" -AsPlainText -Force
+$userCred = New-Object System.Management.Automation.PSCredential ("sva-dscdom\Administrator", $newpasswd)
+
+configuration AssertParentChildDomains
+{
+
+    Import-DscResource -ModuleName xActiveDirectory
+
+    Node $AllNodes.Where{$_.Role -eq "Parent DC"}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = "Present"
+            Name = "AD-Domain-Services"
+        }
+
+        xADDomain FirstDS
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domaincred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DnsDelegationCredential = $redmondCred
+            DependsOn = "[WindowsFeature]ADDSInstall"
+        }
+
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.DomainName
+            DomainUserCredential = $domaincred
+        RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = "[xADDomain]FirstDS"
+        }
+
+        xADUser FirstUser
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domaincred
+            UserName = "dummy"
+            Password = $userCred
+            Ensure = "Present"
+            DependsOn = "[xWaitForADDomain]DscForestWait"
+        }
+
+    }
+
+    Node $AllNodes.Where{$_.Role -eq "Child DC"}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = "Present"
+            Name = "AD-Domain-Services"
+        }
+
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.ParentDomainName
+            DomainUserCredential = $domaincred
+        RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = "[WindowsFeature]ADDSInstall"
+        }
+
+        xADDomain ChildDS
+        {
+            DomainName = $Node.DomainName
+            ParentDomainName = $Node.ParentDomainName
+            DomainAdministratorCredential = $domaincred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DependsOn = "[xWaitForADDomain]DscForestWait"
+        }
+    }
+}
+
+$config = Invoke-Expression (Get-content $PSScriptRoot\ParentChildconfig.psd1 -Raw)
+AssertParentChildDomains -configurationData $config
+
+$computerName1 = "sva-dsc1"
+$computerName2 = "sva-dsc2"
+
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName $computerName1 -Path $PSScriptRoot\AssertParentChildDomains -Credential $localcred
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName $computerName2 -Path $PSScriptRoot\AssertParentChildDomains -Credential $localcred
+

+ 601 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADCommon/MSFT_xADCommon.ps1

@@ -0,0 +1,601 @@
+data localizedString
+{
+    # culture="en-US"
+    ConvertFrom-StringData @'
+        RoleNotFoundError              = Please ensure that the PowerShell module for role '{0}' is installed
+        MembersAndIncludeExcludeError  = The '{0}' and '{1}' and/or '{2}' parameters conflict. The '{0}' parameter should not be used in any combination with the '{1}' and '{2}' parameters.
+        MembersIsNullError             = The Members parameter value is null. The '{0}' parameter must be provided if neither '{1}' nor '{2}' is provided.
+        MembersIsEmptyError            = The Members parameter is empty.  At least one group member must be provided.
+        IncludeAndExcludeConflictError = The member '{0}' is included in both '{1}' and '{2}' parameter values. The same member must not be included in both '{1}' and '{2}' parameter values.
+        IncludeAndExcludeAreEmptyError = The '{0}' and '{1}' parameters are either both null or empty.  At least one member must be specified in one of these parameters.
+
+        CheckingMembers                = Checking for '{0}' members.
+        MembershipCountMismatch        = Membership count is not correct. Expected '{0}' members, actual '{1}' members.
+        MemberNotInDesiredState        = Member '{0}' is not in the desired state.
+        RemovingDuplicateMember        = Removing duplicate member '{0}' definition.
+        MembershipInDesiredState       = Membership is in the desired state.
+        MembershipNotDesiredState      = Membership is NOT in the desired state.
+        CheckingDomain                 = Checking for domain '{0}'.
+        CheckingSite                   = Checking for site '{0}'.
+'@
+}
+
+# Internal function to assert if the role specific module is installed or not
+function Assert-Module
+{
+    [CmdletBinding()]
+    param
+    (
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [System.String] $ModuleName = 'ActiveDirectory'
+    )
+
+    if (-not (Get-Module -Name $ModuleName -ListAvailable))
+    {
+        $errorId = '{0}_ModuleNotFound' -f $ModuleName;
+        $errorMessage = $localizedString.RoleNotFoundError -f $moduleName;
+        ThrowInvalidOperationError -ErrorId $errorId -ErrorMessage $errorMessage;
+    }
+} #end function Assert-Module
+
+# Internal function to test whether computer is a member of a domain
+function Test-DomainMember {
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param ( )
+    $isDomainMember = [System.Boolean] (Get-CimInstance -ClassName Win32_ComputerSystem -Verbose:$false).PartOfDomain;
+    return $isDomainMember;
+}
+
+
+# Internal function to get the domain name of the computer
+function Get-DomainName {
+    [CmdletBinding()]
+    [OutputType([System.String])]
+    param ( )
+    $domainName = [System.String] (Get-CimInstance -ClassName Win32_ComputerSystem -Verbose:$false).Domain;
+    return $domainName;
+} # function Get-DomainName
+
+# Internal function to build domain FQDN
+function Resolve-DomainFQDN {
+    [CmdletBinding()]
+    param (
+        [Parameter(Mandatory)]
+        [OutputType([System.String])]
+        [System.String] $DomainName,
+
+        [Parameter()] [AllowNull()]
+        [System.String] $ParentDomainName
+    )
+    $domainFQDN = $DomainName
+    if ($ParentDomainName)
+    {
+        $domainFQDN = '{0}.{1}' -f $DomainName, $ParentDomainName;
+    }
+    return $domainFQDN
+}
+
+## Internal function to test/ domain availability
+function Test-ADDomain
+{
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [System.String] $DomainName,
+
+        [Parameter()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Credential
+    )
+    Write-Verbose -Message ($localizedString.CheckingDomain -f $DomainName);
+    $ldapDomain = 'LDAP://{0}' -f $DomainName;
+    if ($PSBoundParameters.ContainsKey('Credential'))
+    {
+        $domain = New-Object DirectoryServices.DirectoryEntry($ldapDomain, $Credential.UserName, $Credential.GetNetworkCredential().Password);
+    }
+    else
+    {
+        $domain = New-Object DirectoryServices.DirectoryEntry($ldapDomain);
+    }
+    return ($null -ne $domain);
+}
+
+# Internal function to get an Active Directory object's parent Distinguished Name
+function Get-ADObjectParentDN
+{
+    <#
+        Copyright (c) 2016 The University Of Vermont
+        All rights reserved.
+
+        Redistribution and use in source and binary forms, with or without modification, are permitted provided that
+        the following conditions are met:
+
+        1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
+           following disclaimer.
+        2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
+           following disclaimer in the documentation and/or other materials provided with the distribution.
+        3. Neither the name of the University nor the names of its contributors may be used to endorse or promote
+           products derived from this software without specific prior written permission.
+
+        THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+        LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+        IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+        CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+        OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+        CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+        THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+        http://www.uvm.edu/~gcd/code-license/
+    #>
+    [CmdletBinding()]
+    [OutputType([System.String])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [System.String]
+        $DN
+    )
+
+    # https://www.uvm.edu/~gcd/2012/07/listing-parent-of-ad-object-in-powershell/
+    $distinguishedNameParts = $DN -split '(?<![\\]),';
+    $distinguishedNameParts[1..$($distinguishedNameParts.Count-1)] -join ',';
+
+} #end function GetADObjectParentDN
+
+# Internal function that validates the Members, MembersToInclude and MembersToExclude combination
+# is valid. If the combination is invalid, an InvalidArgumentError is raised.
+function Assert-MemberParameters
+{
+    [CmdletBinding()]
+    param
+    (
+        [ValidateNotNull()]
+        [System.String[]]
+        $Members,
+
+        [ValidateNotNull()]
+        [System.String[]]
+        $MembersToInclude,
+
+        [ValidateNotNull()]
+        [System.String[]]
+        $MembersToExclude,
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ModuleName = 'xActiveDirectory'
+    )
+
+    if($PSBoundParameters.ContainsKey('Members'))
+    {
+        if($PSBoundParameters.ContainsKey('MembersToInclude') -or $PSBoundParameters.ContainsKey('MembersToExclude'))
+        {
+            # If Members are provided, Include and Exclude are not allowed.
+            $errorId = '{0}_MembersPlusIncludeOrExcludeConflict' -f $ModuleName;
+            $errorMessage = $localizedString.MembersAndIncludeExcludeError -f 'Members','MembersToInclude','MembersToExclude';
+            ThrowInvalidArgumentError -ErrorId $errorId -ErrorMessage $errorMessage;
+        }
+
+        if ($Members.Length -eq 0) # )
+        {
+            $errorId = '{0}_MembersIsNull' -f $ModuleName;
+            $errorMessage = $localizedString.MembersIsNullError -f 'Members','MembersToInclude','MembersToExclude';
+            ThrowInvalidArgumentError -ErrorId $errorId -ErrorMessage $errorMessage;
+        }
+    }
+
+    if ($PSBoundParameters.ContainsKey('MembersToInclude'))
+    {
+        $MembersToInclude = [System.String[]] @(Remove-DuplicateMembers -Members $MembersToInclude);
+    }
+
+    if ($PSBoundParameters.ContainsKey('MembersToExclude'))
+    {
+        $MembersToExclude = [System.String[]] @(Remove-DuplicateMembers -Members $MembersToExclude);
+    }
+
+    if (($PSBoundParameters.ContainsKey('MembersToInclude')) -and ($PSBoundParameters.ContainsKey('MembersToExclude')))
+    {
+        if (($MembersToInclude.Length -eq 0) -and ($MembersToExclude.Length -eq 0))
+        {
+            $errorId = '{0}_EmptyIncludeAndExclude' -f $ModuleName;
+            $errorMessage = $localizedString.IncludeAndExcludeAreEmptyError -f 'MembersToInclude', 'MembersToExclude';
+            ThrowInvalidArgumentError -ErrorId $errorId -ErrorMessage $errorMessage;
+        }
+
+        # Both MembersToInclude and MembersToExlude were provided. Check if they have common principals.
+        foreach ($member in $MembersToInclude)
+        {
+            if ($member -in $MembersToExclude)
+            {
+                $errorId = '{0}_IncludeAndExcludeConflict' -f $ModuleName;
+                $errorMessage = $localizedString.IncludeAndExcludeConflictError -f $member, 'MembersToInclude', 'MembersToExclude';
+                ThrowInvalidArgumentError -ErrorId $errorId -ErrorMessage $errorMessage;
+            }
+        }
+    }
+
+} #end function Assert-MemberParameters
+
+## Internal function to remove duplicate strings (members) from a string array
+function Remove-DuplicateMembers
+{
+    [CmdletBinding()]
+    [OutputType([System.String[]])]
+    param
+    (
+        [System.String[]] $Members
+    )
+
+    Set-StrictMode -Version Latest
+
+    $destIndex = 0;
+    for([int] $sourceIndex = 0 ; $sourceIndex -lt $Members.Count; $sourceIndex++)
+    {
+        $matchFound = $false;
+        for([int] $matchIndex = 0; $matchIndex -lt $destIndex; $matchIndex++)
+        {
+            if($Members[$sourceIndex] -eq $Members[$matchIndex])
+            {
+                # A duplicate is found. Discard the duplicate.
+                Write-Verbose -Message ($localizedString.RemovingDuplicateMember -f $Members[$sourceIndex]);
+                $matchFound = $true;
+                continue;
+            }
+        }
+
+        if(!$matchFound)
+        {
+            $Members[$destIndex++] = $Members[$sourceIndex].ToLowerInvariant();
+        }
+    }
+
+    # Create the output array.
+    $destination = New-Object -TypeName System.String[] -ArgumentList $destIndex;
+
+    # Copy only distinct elements from the original array to the destination array.
+    [System.Array]::Copy($Members, $destination, $destIndex);
+
+    return $destination;
+
+} #end function RemoveDuplicateMembers
+
+# Internal function to test whether the existing array members match the defined explicit array
+# members, the included members are present and the exlcuded members are not present.
+function Test-Members
+{
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param
+    (
+        ## Existing array members
+        [AllowNull()]
+        [System.String[]]
+        $ExistingMembers,
+
+        ## Explicit array members
+        [AllowNull()]
+        [System.String[]]
+        $Members,
+
+        ## Compulsory array members
+        [AllowNull()]
+        [System.String[]]
+        $MembersToInclude,
+
+        ## Excluded array members
+        [AllowNull()]
+        [System.String[]]
+        $MembersToExclude
+    )
+
+    if ($PSBoundParameters.ContainsKey('Members'))
+    {
+        if ($null -eq $Members -or (($Members.Count -eq 1) -and ($Members[0].Length -eq 0)))
+        {
+            $Members = @();
+        }
+        Write-Verbose ($localizedString.CheckingMembers -f 'Explicit');
+        $Members = [System.String[]] @(Remove-DuplicateMembers -Members $Members);
+        if ($ExistingMembers.Count -ne $Members.Count)
+        {
+            Write-Verbose -Message ($localizedString.MembershipCountMismatch -f $Members.Count, $ExistingMembers.Count);
+            return $false;
+        }
+
+        foreach ($member in $Members)
+        {
+            if ($member -notin $ExistingMembers)
+            {
+                Write-Verbose -Message ($localizedString.MemberNotInDesiredState -f $member);
+                return $false;
+            }
+        }
+    } #end if $Members
+
+    if ($PSBoundParameters.ContainsKey('MembersToInclude'))
+    {
+        if ($null -eq $MembersToInclude -or (($MembersToInclude.Count -eq 1) -and ($MembersToInclude[0].Length -eq 0)))
+        {
+            $MembersToInclude = @();
+        }
+        Write-Verbose -Message ($localizedString.CheckingMembers -f 'Included');
+        $MembersToInclude = [System.String[]] @(Remove-DuplicateMembers -Members $MembersToInclude);
+        foreach ($member in $MembersToInclude)
+        {
+            if ($member -notin $ExistingMembers)
+            {
+                Write-Verbose -Message ($localizedString.MemberNotInDesiredState -f $member);
+                return $false;
+            }
+        }
+    } #end if $MembersToInclude
+
+    if ($PSBoundParameters.ContainsKey('MembersToExclude'))
+    {
+        if ($null -eq $MembersToExclude -or (($MembersToExclude.Count -eq 1) -and ($MembersToExclude[0].Length -eq 0)))
+        {
+            $MembersToExclude = @();
+        }
+        Write-Verbose -Message ($localizedString.CheckingMembers -f 'Excluded');
+        $MembersToExclude = [System.String[]] @(Remove-DuplicateMembers -Members $MembersToExclude);
+        foreach ($member in $MembersToExclude)
+        {
+            if ($member -in $ExistingMembers)
+            {
+                Write-Verbose -Message ($localizedString.MemberNotInDesiredState -f $member);
+                return $false;
+            }
+        }
+    } #end if $MembersToExclude
+
+    Write-Verbose -Message $localizedString.MembershipInDesiredState;
+    return $true;
+
+} #end function Test-Membership
+
+function ConvertTo-TimeSpan
+{
+    [CmdletBinding()]
+    [OutputType([System.TimeSpan])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [ValidateNotNullOrEmpty()]
+        [System.UInt32]
+        $TimeSpan,
+
+        [Parameter(Mandatory)]
+        [ValidateSet('Seconds','Minutes','Hours','Days')]
+        [System.String]
+        $TimeSpanType
+    )
+    $newTimeSpanParams = @{ };
+    switch ($TimeSpanType)
+    {
+        'Seconds' { $newTimeSpanParams['Seconds'] = $TimeSpan }
+        'Minutes' { $newTimeSpanParams['Minutes'] = $TimeSpan }
+        'Hours' { $newTimeSpanParams['Hours'] = $TimeSpan }
+        'Days' { $newTimeSpanParams['Days'] = $TimeSpan }
+    }
+    return (New-TimeSpan @newTimeSpanParams)
+} #end function ConvertTo-TimeSpan
+
+<#
+    .SYNOPSIS
+        Converts a System.TimeSpan into the number of seconds, mintutes, hours or days.
+    .PARAMETER TimeSpan
+        TimeSpan to convert into an integer
+    .PARAMETER TimeSpanType
+        Convert timespan into the total number of seconds, minutes, hours or days.
+    .EXAMPLE
+        $Get-ADDefaultDomainPasswordPolicy
+
+        ConvertFrom-TimeSpan
+#>
+function ConvertFrom-TimeSpan
+{
+    [CmdletBinding()]
+    [OutputType([System.Int32])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [ValidateNotNullOrEmpty()]
+        [System.TimeSpan]
+        $TimeSpan,
+
+        [Parameter(Mandatory)]
+        [ValidateSet('Seconds','Minutes','Hours','Days')]
+        [System.String]
+        $TimeSpanType
+    )
+    switch ($TimeSpanType)
+    {
+        'Seconds' { return $TimeSpan.TotalSeconds -as [System.UInt32] }
+        'Minutes' { return $TimeSpan.TotalMinutes -as [System.UInt32] }
+        'Hours' { return $TimeSpan.TotalHours -as [System.UInt32] }
+        'Days' { return $TimeSpan.TotalDays -as [System.UInt32] }
+    }
+} #end function ConvertFrom-TimeSpan
+
+<#
+    .SYNOPSIS
+        Returns common AD cmdlet connection parameter for splatting
+    .PARAMETER CommonName
+        When specified, a CommonName overrides theUsed by the xADUser cmdletReturns the Identity as the Name key. For example, the Get-ADUser, Set-ADUser and
+        Remove-ADUser cmdlets take an Identity parameter, but the New-ADUser cmdlet uses the
+        Name parameter.
+    .PARAMETER UseNameParameter
+        Returns the Identity as the Name key. For example, the Get-ADUser, Set-ADUser and
+        Remove-ADUser cmdlets take an Identity parameter, but the New-ADUser cmdlet uses the
+        Name parameter.
+    .EXAMPLE
+        $getADUserParams = Get-CommonADParameters @PSBoundParameters
+
+        Returns connection parameters suitable for Get-ADUser using the splatted cmdlet
+        parameters.
+    .EXAMPLE
+        $newADUserParams = Get-CommonADParameters @PSBoundParameters -UseNameParameter
+
+        Returns connection parameters suitable for New-ADUser using the splatted cmdlet
+        parameters.
+#>
+function Get-ADCommonParameters
+{
+    [CmdletBinding()]
+    [OutputType([System.Collections.Hashtable])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [ValidateNotNullOrEmpty()]
+        [Alias('UserName','GroupName','ComputerName')]
+        [System.String]
+        $Identity,
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $CommonName,
+
+        [Parameter()]
+        [ValidateNotNull()]
+        [Alias('DomainAdministratorCredential')]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Credential,
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [Alias('DomainController')]
+        [System.String]
+        $Server,
+
+        [Parameter()]
+        [System.Management.Automation.SwitchParameter]
+        $UseNameParameter,
+
+        [Parameter()]
+        [System.Management.Automation.SwitchParameter]
+        $PreferCommonName,
+
+        ## Catch all to enable splatted $PSBoundParameters
+        [Parameter(ValueFromRemainingArguments)]
+        $RemainingArguments
+    )
+
+    if ($UseNameParameter)
+    {
+        if ($PreferCommonName -and ($PSBoundParameters.ContainsKey('CommonName')))
+        {
+            $adConnectionParameters = @{ Name = $CommonName; }
+        }
+        else {
+            $adConnectionParameters = @{ Name = $Identity; }
+        }
+    }
+    else
+    {
+        if ($PreferCommonName -and ($PSBoundParameters.ContainsKey('CommonName')))
+        {
+            $adConnectionParameters = @{ Identity = $CommonName; }
+        }
+        else {
+            $adConnectionParameters = @{ Identity = $Identity; }
+        }
+    }
+
+    if ($Credential)
+    {
+        $adConnectionParameters['Credential'] = $Credential;
+    }
+
+    if ($Server)
+    {
+        $adConnectionParameters['Server'] = $Server;
+    }
+
+    return $adConnectionParameters;
+} #end function Get-ADCommonParameters
+
+function ThrowInvalidOperationError
+{
+    [CmdletBinding()]
+    param
+    (
+        [Parameter(Mandatory)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ErrorId,
+
+        [Parameter(Mandatory)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ErrorMessage
+    )
+
+    $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $ErrorMessage;
+    $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation;
+    $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $ErrorId, $errorCategory, $null;
+    throw $errorRecord;
+}
+
+function ThrowInvalidArgumentError
+{
+    [CmdletBinding()]
+    param
+    (
+        [Parameter(Mandatory)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ErrorId,
+
+        [Parameter(Mandatory)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ErrorMessage
+    )
+
+    $exception = New-Object -TypeName System.ArgumentException -ArgumentList $ErrorMessage;
+    $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument;
+    $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $ErrorId, $errorCategory, $null;
+    throw $errorRecord;
+
+} #end function ThrowInvalidArgumentError
+
+## Internal function to test site availability
+function Test-ADReplicationSite
+{
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [System.String] $SiteName,
+
+        [Parameter(Mandatory)]
+        [System.String] $DomainName,
+
+        [Parameter()]
+        [System.Management.Automation.PSCredential]
+        $Credential
+    )
+
+    Write-Verbose -Message ($localizedString.CheckingSite -f $SiteName);
+    
+    $existingDC = "$((Get-ADDomainController -Discover -DomainName $DomainName -ForceDiscover).HostName)";
+
+    try
+    {
+        $site = Get-ADReplicationSite -Identity $SiteName -Server $existingDC -Credential $Credential;
+    }
+    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
+    {
+        return $false;
+    }
+
+    return ($null -ne $site);
+}

+ 4 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADCommon/MSFT_xADCommon.psm1

@@ -0,0 +1,4 @@
+## Import all .ps1 files to enable testing using the unit test framework
+Get-ChildItem -Path $PSScriptRoot -Include '*.ps1' -Recurse | ForEach-Object {
+    . $PSItem.FullName;
+}

+ 521 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADComputer/MSFT_xADComputer.psm1

@@ -0,0 +1,521 @@
+$moduleRoot = Split-Path -Path $MyInvocation.MyCommand.Path -Parent
+#region LocalizedData
+$culture = 'en-us'
+if (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath $PSUICulture))
+{
+    $culture = $PSUICulture
+}
+$importLocalizedDataParams = @{
+    BindingVariable = 'LocalizedData'
+    Filename = 'MSFT_xADComputer.psd1'
+    BaseDirectory = $moduleRoot
+    UICulture = $culture
+}
+Import-LocalizedData @importLocalizedDataParams
+#endregion
+
+## Create a property map that maps the DSC resource parameters to the
+## Active Directory computer attributes.
+$adPropertyMap = @(
+    @{ Parameter = 'ComputerName'; ADProperty = 'cn'; }
+    @{ Parameter = 'Location'; }
+    @{ Parameter = 'DnsHostName'; }
+    @{ Parameter = 'ServicePrincipalNames'; }
+    @{ Parameter = 'UserPrincipalName'; }
+    @{ Parameter = 'DisplayName'; }
+    @{ Parameter = 'Path'; ADProperty = 'distinguishedName'; }
+    @{ Parameter = 'Description'; }
+    @{ Parameter = 'Enabled'; }
+    @{ Parameter = 'Manager'; ADProperty = 'managedBy'; }
+)
+
+
+function Get-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Collections.Hashtable])]
+    param
+    (
+        # Common Name
+        [Parameter(Mandatory)]
+        [System.String] $ComputerName,
+
+        [ValidateSet('Present', 'Absent')]
+        [System.String] $Ensure = 'Present',
+
+        [ValidateNotNull()]
+        [System.String] $UserPrincipalName,
+
+        [ValidateNotNull()]
+        [System.String] $DisplayName,
+
+        [ValidateNotNull()]
+        [System.String] $Path,
+
+        [ValidateNotNull()]
+        [System.String] $Location,
+
+        [ValidateNotNull()]
+        [System.String] $DnsHostName,
+
+        [ValidateNotNull()]
+        [System.String[]] $ServicePrincipalNames,
+
+        [ValidateNotNull()]
+        [System.String] $Description,
+
+        ## Computer's manager specified as a Distinguished Name (DN)
+        [ValidateNotNull()]
+        [System.String] $Manager,
+
+        [ValidateNotNull()]
+        [System.String] $RequestFile,
+
+        [ValidateNotNull()]
+        [System.Boolean] $Enabled = $true,
+
+        [ValidateNotNull()]
+        [System.String] $DomainController,
+
+        ## Ideally this should just be called 'Credential' but is here for consistency with xADUser
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $DomainAdministratorCredential
+    )
+
+    Assert-Module -ModuleName 'ActiveDirectory';
+    Import-Module -Name 'ActiveDirectory' -Verbose:$false;
+
+    try
+    {
+        $adCommonParameters = Get-ADCommonParameters @PSBoundParameters;
+
+        $adProperties = @();
+        ## Create an array of the AD property names to retrieve from the property map
+        foreach ($property in $adPropertyMap)
+        {
+
+            if ($property.ADProperty)
+            {
+                $adProperties += $property.ADProperty;
+            }
+            else
+            {
+                $adProperties += $property.Parameter;
+            }
+        }
+
+        Write-Verbose -Message ($LocalizedData.RetrievingADComputer -f $ComputerName);
+        $adComputer = Get-ADComputer @adCommonParameters -Properties $adProperties;
+        Write-Verbose -Message ($LocalizedData.ADComputerIsPresent -f $ComputerName);
+        $Ensure = 'Present';
+    }
+    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
+    {
+        Write-Verbose -Message ($LocalizedData.ADComputerNotPresent -f $ComputerName);
+        $Ensure = 'Absent';
+    }
+    catch
+    {
+        Write-Error -Message ($LocalizedData.RetrievingADComputerError -f $ComputerName);
+        throw $_;
+    }
+
+    $targetResource = @{
+        ComputerName      = $ComputerName;
+        DistinguishedName = $adComputer.DistinguishedName; ## Read-only property
+        SID               = $adComputer.SID; ## Read-only property
+        Ensure            = $Ensure;
+        DomainController  = $DomainController;
+        RequestFile    = $RequestFile;
+    }
+
+    ## Retrieve each property from the ADPropertyMap and add to the hashtable
+    foreach ($property in $adPropertyMap)
+    {
+        $propertyName = $property.Parameter;
+        if ($propertyName -eq 'Path') {
+            ## The path returned is not the parent container
+            if (-not [System.String]::IsNullOrEmpty($adComputer.DistinguishedName))
+            {
+                $targetResource['Path'] = Get-ADObjectParentDN -DN $adComputer.DistinguishedName;
+            }
+        }
+        elseif ($property.ADProperty)
+        {
+            ## The AD property name is different to the function parameter to use this
+            $targetResource[$propertyName] = $adComputer.($property.ADProperty);
+        }
+        else
+        {
+            ## The AD property name matches the function parameter
+            if ($adComputer.$propertyName -is [Microsoft.ActiveDirectory.Management.ADPropertyValueCollection])
+            {
+                $targetResource[$propertyName] = $adComputer.$propertyName -as [System.String[]];
+            }
+            else
+            {
+                $targetResource[$propertyName] = $adComputer.$propertyName;
+            }
+        }
+    }
+    return $targetResource;
+
+} #end function Get-TargetResource
+
+
+function Test-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param
+    (
+        # Common Name
+        [Parameter(Mandatory)]
+        [System.String] $ComputerName,
+
+        [ValidateSet('Present', 'Absent')]
+        [System.String] $Ensure = 'Present',
+
+        [ValidateNotNull()]
+        [System.String] $UserPrincipalName,
+
+        [ValidateNotNull()]
+        [System.String] $DisplayName,
+
+        [ValidateNotNull()]
+        [System.String] $Path,
+
+        [ValidateNotNull()]
+        [System.String] $Location,
+
+        [ValidateNotNull()]
+        [System.String] $DnsHostName,
+
+        [ValidateNotNull()]
+        [System.String[]] $ServicePrincipalNames,
+
+        [ValidateNotNull()]
+        [System.String] $Description,
+
+        ## Computer's manager specified as a Distinguished Name (DN)
+        [ValidateNotNull()]
+        [System.String] $Manager,
+
+        [ValidateNotNull()]
+        [System.String] $RequestFile,
+
+        [ValidateNotNull()]
+        [System.Boolean] $Enabled = $true,
+
+        [ValidateNotNull()]
+        [System.String] $DomainController,
+
+        ## Ideally this should just be called 'Credential' but is here for backwards compatibility
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $DomainAdministratorCredential
+    )
+
+    $targetResource = Get-TargetResource @PSBoundParameters;
+    $isCompliant = $true;
+
+    if ($Ensure -eq 'Absent')
+    {
+        if ($targetResource.Ensure -eq 'Present')
+        {
+            Write-Verbose -Message ($LocalizedData.ADComputerNotDesiredPropertyState -f `
+                                    'Ensure', $PSBoundParameters.Ensure, $targetResource.Ensure);
+            $isCompliant = $false;
+        }
+    }
+    else
+    {
+        ## Add ensure and enabled as they may not be explicitly passed and we want to enumerate them
+        $PSBoundParameters['Ensure'] = $Ensure;
+        $PSBoundParameters['Enabled'] = $Enabled;
+
+        foreach ($parameter in $PSBoundParameters.Keys)
+        {
+            if ($targetResource.ContainsKey($parameter))
+            {
+                ## This check is required to be able to explicitly remove values with an empty string, if required
+                if (([System.String]::IsNullOrEmpty($PSBoundParameters.$parameter)) -and
+                    ([System.String]::IsNullOrEmpty($targetResource.$parameter)))
+                {
+                    # Both values are null/empty and therefore we are compliant
+                }
+                elseif ($parameter -eq 'ServicePrincipalNames')
+                {
+                    $testMembersParams = @{
+                        ExistingMembers = $targetResource.ServicePrincipalNames -as [System.String[]];
+                        Members = $ServicePrincipalNames;
+                    }
+                    if (-not (Test-Members @testMembersParams))
+                    {
+                        $existingSPNs = $testMembersParams['ExistingMembers'] -join ',';
+                        $desiredSPNs = $ServicePrincipalNames -join ',';
+                        Write-Verbose -Message ($LocalizedData.ADComputerNotDesiredPropertyState -f `
+                                                'ServicePrincipalNames', $desiredSPNs, $existingSPNs);
+                        $isCompliant = $false;
+                    }
+                }
+                elseif ($PSBoundParameters.$parameter -ne $targetResource.$parameter)
+                {
+                    Write-Verbose -Message ($LocalizedData.ADComputerNotDesiredPropertyState -f `
+                                            $parameter, $PSBoundParameters.$parameter, $targetResource.$parameter);
+                    $isCompliant = $false;
+                }
+            }
+        } #end foreach PSBoundParameter
+    }
+
+    if ($isCompliant)
+    {
+        Write-Verbose -Message ($LocalizedData.ADComputerInDesiredState -f $ComputerName)
+        return $true
+    }
+    else
+    {
+        Write-Verbose -Message ($LocalizedData.ADComputerNotInDesiredState -f $ComputerName)
+        return $false
+    }
+
+} #end function Test-TargetResource
+
+
+function Set-TargetResource
+{
+    [CmdletBinding()]
+    param
+    (
+        # Common Name
+        [Parameter(Mandatory)]
+        [System.String] $ComputerName,
+
+        [ValidateSet('Present', 'Absent')]
+        [System.String] $Ensure = 'Present',
+
+        [ValidateNotNull()]
+        [System.String] $UserPrincipalName,
+
+        [ValidateNotNull()]
+        [System.String] $DisplayName,
+
+        [ValidateNotNull()]
+        [System.String] $Path,
+
+        [ValidateNotNull()]
+        [System.String] $Location,
+
+        [ValidateNotNull()]
+        [System.String] $DnsHostName,
+
+        [ValidateNotNull()]
+        [System.String[]] $ServicePrincipalNames,
+
+        [ValidateNotNull()]
+        [System.String] $Description,
+
+        ## Computer's manager specified as a Distinguished Name (DN)
+        [ValidateNotNull()]
+        [System.String] $Manager,
+
+        [ValidateNotNull()]
+        [System.String] $RequestFile,
+
+        [ValidateNotNull()]
+        [System.Boolean] $Enabled = $true,
+
+        [ValidateNotNull()]
+        [System.String] $DomainController,
+
+        ## Ideally this should just be called 'Credential' but is here for backwards compatibility
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $DomainAdministratorCredential
+    )
+
+    $targetResource = Get-TargetResource @PSBoundParameters;
+
+    ## Add ensure and enabled as they may not be explicitly passed and we want to enumerate them
+    $PSBoundParameters['Ensure'] = $Ensure;
+    $PSBoundParameters['Enabled'] = $Enabled;
+
+    if ($Ensure -eq 'Present')
+    {
+        if ($targetResource.Ensure -eq 'Absent') {
+            ## Computer does not exist and needs creating
+            if ($RequestFile)
+            {
+                ## Use DJOIN to create the computer account as well as the ODJ Request file.
+                Write-Verbose -Message ($LocalizedData.ODJRequestStartMessage -f `
+                        $DomainName,$ComputerName,$RequestFile)
+
+                # This should only be performed on a Domain Member, so detect the Domain Name.
+                $DomainName = Get-DomainName
+                $DJoinParameters = @(
+                    '/PROVISION'
+                    '/DOMAIN',$DomainName
+                    '/MACHINE',$ComputerName )
+                if ($PSBoundParameters.ContainsKey('Path'))
+                {
+                    $DJoinParameters += @( '/MACHINEOU',$Path )
+                } # if
+
+                if ($PSBoundParameters.ContainsKey('DomainController'))
+                {
+                    $DJoinParameters += @( '/DCNAME',$DomainController )
+                } # if
+
+                $DJoinParameters += @( '/SAVEFILE',$RequestFile )
+                $Result = & djoin.exe @DjoinParameters
+
+                if ($LASTEXITCODE -ne 0)
+                {
+                    $errorId = 'ODJRequestError'
+                    $errorMessage = $($LocalizedData.ODJRequestError `
+                        -f $LASTEXITCODE,$Result)
+                    ThrowInvalidOperationError -ErrorId $errorId -ErrorMessage $errorMessage
+                } # if
+
+                Write-Verbose -Message ($LocalizedData.ODJRequestCompleteMessage -f `
+                        $DomainName,$ComputerName,$RequestFile)
+            }
+            else
+            {
+                ## Create the computer account using New-ADComputer
+                $newADComputerParams = Get-ADCommonParameters @PSBoundParameters -UseNameParameter;
+                if ($PSBoundParameters.ContainsKey('Path'))
+                {
+                    Write-Verbose -Message ($LocalizedData.UpdatingADComputerProperty -f 'Path', $Path);
+                    $newADComputerParams['Path'] = $Path;
+                }
+                Write-Verbose -Message ($LocalizedData.AddingADComputer -f $ComputerName);
+                New-ADComputer @newADComputerParams;
+            } # if
+            ## Now retrieve the newly created computer
+            $targetResource = Get-TargetResource @PSBoundParameters;
+        }
+
+        $setADComputerParams = Get-ADCommonParameters @PSBoundParameters;
+        $replaceComputerProperties = @{};
+        $removeComputerProperties = @{};
+        foreach ($parameter in $PSBoundParameters.Keys)
+        {
+            ## Only check/action properties specified/declared parameters that match one of the function's
+            ## parameters. This will ignore common parameters such as -Verbose etc.
+            if ($targetResource.ContainsKey($parameter))
+            {
+                if ($parameter -eq 'Path' -and ($PSBoundParameters.Path -ne $targetResource.Path))
+                {
+                    ## Cannot move computers by updating the DistinguishedName property
+                    $adCommonParameters = Get-ADCommonParameters @PSBoundParameters;
+                    ## Using the SamAccountName for identity with Move-ADObject does not work, use the DN instead
+                    $adCommonParameters['Identity'] = $targetResource.DistinguishedName;
+                    Write-Verbose -Message ($LocalizedData.MovingADComputer -f `
+                                            $targetResource.Path, $PSBoundParameters.Path);
+                    Move-ADObject @adCommonParameters -TargetPath $PSBoundParameters.Path;
+                }
+                elseif ($parameter -eq 'ServicePrincipalNames')
+                {
+                    Write-Verbose -Message ($LocalizedData.UpdatingADComputerProperty -f `
+                                            'ServicePrincipalNames', ($ServicePrincipalNames -join ','));
+                    $replaceComputerProperties['ServicePrincipalName'] = $ServicePrincipalNames;
+                }
+                elseif ($parameter -eq 'Enabled' -and ($PSBoundParameters.$parameter -ne $targetResource.$parameter))
+                {
+                    ## We cannot enable/disable an account with -Add or -Replace parameters, but inform that
+                    ## we will change this as it is out of compliance (it always gets set anyway)
+                    Write-Verbose -Message ($LocalizedData.UpdatingADComputerProperty -f `
+                                            $parameter, $PSBoundParameters.$parameter);
+                }
+                elseif ($PSBoundParameters.$parameter -ne $targetResource.$parameter)
+                {
+                    ## Find the associated AD property
+                    $adProperty = $adPropertyMap | Where-Object { $_.Parameter -eq $parameter };
+
+                    if ([System.String]::IsNullOrEmpty($adProperty))
+                    {
+                        ## We can't do anything with an empty AD property!
+                    }
+                    elseif ([System.String]::IsNullOrEmpty($PSBoundParameters.$parameter))
+                    {
+                        ## We are removing properties
+                        ## Only remove if the existing value in not null or empty
+                        if (-not ([System.String]::IsNullOrEmpty($targetResource.$parameter)))
+                        {
+                            Write-Verbose -Message ($LocalizedData.RemovingADComputerProperty -f `
+                                                    $parameter, $PSBoundParameters.$parameter);
+                            if ($adProperty.UseCmdletParameter -eq $true)
+                            {
+                                ## We need to pass the parameter explicitly to Set-ADComputer, not via -Remove
+                                $setADComputerParams[$adProperty.Parameter] = $PSBoundParameters.$parameter;
+                            }
+                            elseif ([System.String]::IsNullOrEmpty($adProperty.ADProperty))
+                            {
+                                $removeComputerProperties[$adProperty.Parameter] = $targetResource.$parameter;
+                            }
+                            else
+                            {
+                                $removeComputerProperties[$adProperty.ADProperty] = $targetResource.$parameter;
+                            }
+                        }
+                    } #end if remove existing value
+                    else
+                    {
+                        ## We are replacing the existing value
+                        Write-Verbose -Message ($LocalizedData.UpdatingADComputerProperty -f `
+                                                $parameter, $PSBoundParameters.$parameter);
+                        if ($adProperty.UseCmdletParameter -eq $true)
+                        {
+                            ## We need to pass the parameter explicitly to Set-ADComputer, not via -Replace
+                            $setADComputerParams[$adProperty.Parameter] = $PSBoundParameters.$parameter;
+                        }
+                        elseif ([System.String]::IsNullOrEmpty($adProperty.ADProperty))
+                        {
+                            $replaceComputerProperties[$adProperty.Parameter] = $PSBoundParameters.$parameter;
+                        }
+                        else
+                        {
+                            $replaceComputerProperties[$adProperty.ADProperty] = $PSBoundParameters.$parameter;
+                        }
+                    } #end if replace existing value
+                }
+
+            } #end if TargetResource parameter
+        } #end foreach PSBoundParameter
+
+        ## Only pass -Remove and/or -Replace if we have something to set/change
+        if ($replaceComputerProperties.Count -gt 0)
+        {
+            $setADComputerParams['Replace'] = $replaceComputerProperties;
+        }
+        if ($removeComputerProperties.Count -gt 0)
+        {
+            $setADComputerParams['Remove'] = $removeComputerProperties;
+        }
+
+        Write-Verbose -Message ($LocalizedData.UpdatingADComputer -f $ComputerName);
+        [ref] $null = Set-ADComputer @setADComputerParams -Enabled $Enabled;
+    }
+    elseif (($Ensure -eq 'Absent') -and ($targetResource.Ensure -eq 'Present'))
+    {
+        ## User exists and needs removing
+        Write-Verbose ($LocalizedData.RemovingADComputer -f $ComputerName);
+        $adCommonParameters = Get-ADCommonParameters @PSBoundParameters;
+        [ref] $null = Remove-ADComputer @adCommonParameters -Confirm:$false;
+    }
+
+} #end function Set-TargetResource
+
+## Import the common AD functions
+$adCommonFunctions = Join-Path `
+    -Path (Split-Path -Path $PSScriptRoot -Parent) `
+    -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.ps1';
+. $adCommonFunctions;
+
+Export-ModuleMember -Function *-TargetResource

+ 20 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADComputer/MSFT_xADComputer.schema.mof

@@ -0,0 +1,20 @@
+[ClassVersion("1.0.0.0"), FriendlyName("xADComputer")]
+class MSFT_xADComputer : OMI_BaseResource
+{
+    [Key, Description("Specifies the name of the computer")] String ComputerName;
+    [Write, Description("Specifies the location of the computer, such as an office number")] String Location;
+    [Write, Description("Specifies the fully qualified domain name (FQDN) of the computer")] String DnsHostName;
+    [Write, Description("Specifies the service principal names for the computer account")] String ServicePrincipalNames[];
+    [Write, Description("Specifies the UPN assigned to the computer account")] String UserPrincipalName;
+    [Write, Description("Specifies the display name of the computer")] String DisplayName;
+    [Write, Description("Specifies the X.500 path of the Organizational Unit (OU) or container where the computer is located")] String Path;
+    [Write, Description("Specifies a description of the computer object")] String Description;
+    [Write, Description("Specifies if the computer account is enabled")] Boolean Enabled;
+    [Write, Description("Specifies the user or group Distinguished Name that manages the computer object")] String Manager;
+    [Write, Description("Specifies the Active Directory Domain Services instance to connect to perform the task")] String DomainController;
+    [Write, Description("Specifies the user account credentials to use to perform the task"), EmbeddedInstance("MSFT_Credential")] String DomainAdministratorCredential;
+    [Write, Description("Specifies the full path to the Offline Domain Join Request file to create.")] String RequestFile;
+    [Write, ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] String Ensure;
+    [Read, Description("Returns the X.500 path of the computer object")] String DistinguishedName;
+    [Read, Description("Returns the security identifier of the computer object")] String SID;
+};

+ 25 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADComputer/en-US/MSFT_xADComputer.psd1

@@ -0,0 +1,25 @@
+# culture="en-US"
+ConvertFrom-StringData @'
+    RoleNotFoundError                 = Please ensure that the PowerShell module for role '{0}' is installed.
+    RetrievingADComputerError         = Error looking up Active Directory computer '{0}'.
+
+    RetrievingADComputer              = Retrieving Active Directory computer '{0}' ...
+    CreatingADDomainConnection        = Creating connection to Active Directory domain ...
+    ADComputerIsPresent               = Active Directory computer '{0}' is present.
+    ADComputerNotPresent              = Active Directory computer '{0}' was NOT present.
+    ADComputerNotDesiredPropertyState = Computer '{0}' property is NOT in the desired state. Expected '{1}', actual '{2}'.
+    ADComputerInDesiredState          = Active Directory computer '{0}' is in the desired state.
+    ADComputerNotInDesiredState       = Active Directory computer '{0}' is NOT in the desired state.
+
+    AddingADComputer                  = Adding Active Directory computer '{0}'.
+    RemovingADComputer                = Removing Active Directory computer '{0}'.
+    UpdatingADComputer                = Updating Active Directory computer '{0}'.
+    UpdatingADComputerProperty        = Updating computer property '{0}' with/to '{1}'.
+    RemovingADComputerProperty        = Removing computer property '{0}' with '{1}'.
+    MovingADComputer                  = Moving computer from '{0}' to '{1}'.
+    RenamingADComputer                = Renaming computer from '{0}' to '{1}'.
+
+    ODJRequestStartMessage=Attempting to create the ODJ request file '{2}' for computer '{1}' in Domain '{0}'.
+    ODJRequestCompleteMessage=The ODJ request file '{2}' for computer '{1}' in Domain '{0}' has been provisioned successfully.
+    ODJRequestError=Error {0} occured provisioning the computer using ODJ- {1}.
+'@

+ 347 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomain/MSFT_xADDomain.psm1

@@ -0,0 +1,347 @@
+# Localized messages
+data localizedData
+{
+    # culture="en-US"
+    ConvertFrom-StringData @'
+        RoleNotFoundError                    = Please ensure that the PowerShell module for role '{0}' is installed.
+        InvalidDomainError                   = Computer is a member of the wrong domain?!
+        ExistingDomainMemberError            = Computer is already a domain member. Cannot create a new '{0}' domain?
+        InvalidCredentialError               = Domain '{0}' is available, but invalid credentials were supplied.
+                                             
+        QueryDomainWithLocalCredential       = Computer is a domain member; querying domain '{0}' using local credential ...
+        QueryDomainWithCredential            = Computer is a workgroup member; querying for domain '{0}' using supplied credential ...
+        DomainFound                          = Active Directory domain '{0}' found.
+        DomainNotFound                       = Active Directory domain '{0}' cannot be found.
+        CreatingChildDomain                  = Creating domain '{0}' as a child of domain '{1}' ...
+        CreatedChildDomain                   = Child domain '{0}' created.
+        CreatingForest                       = Creating AD forest '{0}' ...
+        CreatedForest                        = AD forest '{0}' created.
+        ResourcePropertyValueIncorrect       = Property '{0}' value is incorrect; expected '{1}', actual '{2}'.
+        ResourceInDesiredState               = Resource '{0}' is in the desired state.
+        ResourceNotInDesiredState            = Resource '{0}' is NOT in the desired state.
+        RetryingGetADDomain                  = Attempt {0} of {1} to call Get-ADDomain failed, retrying in {2} seconds.
+        UnhandledError                       = Unhandled error occured, detail here: {0}
+        FaultExceptionAndDomainShouldExist = ServiceModel FaultException detected and domain should exist, performing retry...
+'@
+}
+
+<#
+    .SYNOPSIS
+        Retrieves the name of the file that tracks the status of the xADDomain resource with the
+        specified domain name.
+
+    .PARAMETER DomainName
+        The domain name of the xADDomain resource to retrieve the tracking file name of.
+
+    .NOTES
+        The tracking file is currently output to the environment's temp directory.
+        
+        This file is NOT removed when a configuration completes, so if another call to a xADDomain
+        resource with the same domain name occurs in the same environment, this file will already
+        be present.
+        
+        This is so that when another call is made to the same resource, the resource will not
+        attempt to promote the machine to a domain controller again (which would cause an error).
+        
+        If the resource should be promoted to a domain controller once again, you must first remove
+        this file from the environment's temp directory (usually C:\Temp).
+
+        If in the future this functionality needs to change so that future configurations are not
+        affected, $env:temp should be changed to the resource's cache location which is removed
+        after each configuration.
+        ($env:systemRoot\system32\Configuration\BuiltinProvCache\MSFT_xADDomain)
+#>
+function Get-TrackingFilename {
+    [OutputType([String])]
+    [CmdletBinding()]
+    param(
+        [Parameter(Mandatory = $true)]
+        [String]
+        $DomainName
+    )
+
+    return Join-Path -Path ($env:temp) -ChildPath ('{0}.xADDomain.completed' -f $DomainName)
+}
+
+function Get-TargetResource
+{
+    [OutputType([System.Collections.Hashtable])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [String] $DomainName,
+
+        [Parameter(Mandatory)]
+        [PSCredential] $DomainAdministratorCredential,
+
+        [Parameter(Mandatory)]
+        [PSCredential] $SafemodeAdministratorPassword,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $ParentDomainName,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $DomainNetBIOSName,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [PSCredential] $DnsDelegationCredential,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $DatabasePath,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $LogPath,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $SysvolPath
+    )
+    
+    Assert-Module -ModuleName 'ADDSDeployment';
+    $domainFQDN = Resolve-DomainFQDN -DomainName $DomainName -ParentDomainName $ParentDomainName;
+    $isDomainMember = Test-DomainMember;
+
+    $retries = 0
+    $maxRetries = 5
+    $retryIntervalInSeconds = 30
+    $domainShouldExist = (Test-Path (Get-TrackingFilename -DomainName $DomainName))
+    do {            
+    try
+    {
+        if ($isDomainMember) {
+            ## We're already a domain member, so take the credentials out of the equation
+            Write-Verbose ($localizedData.QueryDomainADWithLocalCredentials -f $domainFQDN);
+            $domain = Get-ADDomain -Identity $domainFQDN -ErrorAction Stop;
+        }
+        else {
+            Write-Verbose ($localizedData.QueryDomainWithCredential -f $domainFQDN);
+            $domain = Get-ADDomain -Identity $domainFQDN -Credential $DomainAdministratorCredential -ErrorAction Stop;
+        }
+
+        ## No need to check whether the node is actually a domain controller. If we don't throw an exception,
+        ## the domain is already UP - and this resource shouldn't run. Domain controller functionality
+        ## should be checked by the xADDomainController resource?
+        Write-Verbose ($localizedData.DomainFound -f $domain.DnsRoot);
+        
+        $targetResource = @{
+            DomainName = $domain.DnsRoot;
+            ParentDomainName = $domain.ParentDomain;
+            DomainNetBIOSName = $domain.NetBIOSName;
+        }
+        
+        return $targetResource;
+    }
+    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
+    {
+        $errorMessage = $localizedData.ExistingDomainMemberError -f $DomainName;
+        ThrowInvalidOperationError -ErrorId 'xADDomain_DomainMember' -ErrorMessage $errorMessage;
+    }
+    catch [Microsoft.ActiveDirectory.Management.ADServerDownException]
+    {
+        Write-Verbose ($localizedData.DomainNotFound -f $domainFQDN)
+        $domain = @{ };
+        # will fall into retry mechanism
+    }
+    catch [System.Security.Authentication.AuthenticationException]
+    {
+        $errorMessage = $localizedData.InvalidCredentialError -f $DomainName;
+        ThrowInvalidOperationError -ErrorId 'xADDomain_InvalidCredential' -ErrorMessage $errorMessage;
+    }
+    catch
+    {
+        $errorMessage = $localizedData.UnhandledError -f ($_.Exception | Format-List -Force | Out-String)
+        Write-Verbose $errorMessage
+
+        if ($domainShouldExist -and ($_.Exception.InnerException -is [System.ServiceModel.FaultException]))
+        {
+            Write-Verbose $localizedData.FaultExceptionAndDomainShouldExist
+            # will fall into retry mechanism
+        } else {
+            ## Not sure what's gone on here!
+            throw $_
+        }
+    }
+
+    if($domainShouldExist) {
+        $retries++
+        Write-Verbose ($localizedData.RetryingGetADDomain -f $retries, $maxRetries, $retryIntervalInSeconds)
+        Start-Sleep -Seconds ($retries * $retryIntervalInSeconds)
+    }
+
+    } while ($domainShouldExist -and ($retries -le $maxRetries) )
+
+} #end function Get-TargetResource
+
+function Test-TargetResource
+{
+    [OutputType([System.Boolean])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [String] $DomainName,
+
+        [Parameter(Mandatory)]
+        [PSCredential] $DomainAdministratorCredential,
+
+        [Parameter(Mandatory)]
+        [PSCredential] $SafemodeAdministratorPassword,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $ParentDomainName,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $DomainNetBIOSName,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [PSCredential] $DnsDelegationCredential,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $DatabasePath,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $LogPath,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $SysvolPath
+    )
+
+    $targetResource = Get-TargetResource @PSBoundParameters
+    $isCompliant = $true;
+
+    ## The Get-Target resource returns .DomainName as the domain's FQDN. Therefore, we
+    ## need to resolve this before comparison.
+    $domainFQDN = Resolve-DomainFQDN -DomainName $DomainName -ParentDomainName $ParentDomainName
+    if ($domainFQDN -ne $targetResource.DomainName)
+    {
+        $message = $localizedData.ResourcePropertyValueIncorrect -f 'DomainName', $domainFQDN, $targetResource.DomainName;
+        Write-Verbose -Message $message;
+        $isCompliant = $false;   
+    }
+    
+    $propertyNames = @('ParentDomainName','DomainNetBIOSName');
+    foreach ($propertyName in $propertyNames)
+    {
+        if ($PSBoundParameters.ContainsKey($propertyName))
+        {
+            $propertyValue = (Get-Variable -Name $propertyName).Value;
+            if ($targetResource.$propertyName -ne $propertyValue)
+            {
+                $message = $localizedData.ResourcePropertyValueIncorrect -f $propertyName, $propertyValue, $targetResource.$propertyName;
+                Write-Verbose -Message $message;
+                $isCompliant = $false;        
+            }
+        }
+    }
+        
+    if ($isCompliant)
+    {
+        Write-Verbose -Message ($localizedData.ResourceInDesiredState -f $domainFQDN);
+        return $true;
+    }
+    else
+    {
+        Write-Verbose -Message ($localizedData.ResourceNotInDesiredState -f $domainFQDN);
+        return $false;
+    }
+
+} #end function Test-TargetResource
+
+function Set-TargetResource
+{
+    param
+    (
+        [Parameter(Mandatory)]
+        [String] $DomainName,
+
+        [Parameter(Mandatory)]
+        [PSCredential] $DomainAdministratorCredential,
+
+        [Parameter(Mandatory)]
+        [PSCredential] $SafemodeAdministratorPassword,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $ParentDomainName,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $DomainNetBIOSName,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [PSCredential] $DnsDelegationCredential,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $DatabasePath,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $LogPath,
+
+        [Parameter()] [ValidateNotNullOrEmpty()]
+        [String] $SysvolPath
+    )
+
+    # Debug can pause Install-ADDSForest/Install-ADDSDomain, so we remove it.
+    [ref] $null = $PSBoundParameters.Remove("Debug");
+    ## Not entirely necessary, but run Get-TargetResouece to ensure we raise any pre-flight errors.
+    $targetResource = Get-TargetResource @PSBoundParameters;
+    
+    $installADDSParams = @{
+        SafeModeAdministratorPassword = $SafemodeAdministratorPassword.Password;
+        NoRebootOnCompletion = $true;
+        Force = $true;
+    }
+    
+    if ($PSBoundParameters.ContainsKey('DnsDelegationCredential'))
+    {
+        $installADDSParams['DnsDelegationCredential'] = $DnsDelegationCredential;
+        $installADDSParams['CreateDnsDelegation'] = $true;
+    }
+    if ($PSBoundParameters.ContainsKey('DatabasePath'))
+    {
+        $installADDSParams['DatabasePath'] = $DatabasePath;
+    }
+    if ($PSBoundParameters.ContainsKey('LogPath'))
+    {
+        $installADDSParams['LogPath'] = $LogPath;
+    }
+    if ($PSBoundParameters.ContainsKey('SysvolPath'))
+    {
+        $installADDSParams['SysvolPath'] = $SysvolPath;
+    }
+    
+    if ($PSBoundParameters.ContainsKey('ParentDomainName'))
+    {
+        Write-Verbose -Message ($localizedData.CreatingChildDomain -f $DomainName, $ParentDomainName);
+        $installADDSParams['Credential'] = $DomainAdministratorCredential
+        $installADDSParams['NewDomainName'] = $DomainName
+        $installADDSParams['ParentDomainName'] = $ParentDomainName
+        $installADDSParams['DomainType'] = 'ChildDomain';
+        if ($PSBoundParameters.ContainsKey('DomainNetBIOSName'))
+        {
+            $installADDSParams['NewDomainNetbiosName'] = $DomainNetBIOSName;
+        }
+        Install-ADDSDomain @installADDSParams;
+        Write-Verbose -Message ($localizedData.CreatedChildDomain);
+    }
+    else
+    {
+        Write-Verbose -Message ($localizedData.CreatingForest -f $DomainName);
+        $installADDSParams['DomainName'] = $DomainName;
+        if ($PSBoundParameters.ContainsKey('DomainNetbiosName'))
+        {
+            $installADDSParams['DomainNetbiosName'] = $DomainNetBIOSName;
+        }
+        Install-ADDSForest @installADDSParams;
+        Write-Verbose -Message ($localizedData.CreatedForest -f $DomainName); 
+    }  
+
+    "Finished" | Out-File -FilePath (Get-TrackingFilename -DomainName $DomainName) -Force
+
+    # Signal to the LCM to reboot the node to compensate for the one we
+    # suppressed from Install-ADDSForest/Install-ADDSDomain
+    $global:DSCMachineStatus = 1
+
+} #end function Set-TargetResource
+
+## Import the common AD functions
+$adCommonFunctions = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.ps1';
+. $adCommonFunctions;
+
+Export-ModuleMember -Function *-TargetResource;

+ 13 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomain/MSFT_xADDomain.schema.mof

@@ -0,0 +1,13 @@
+[ClassVersion("1.0.1.0"), FriendlyName("xADDomain")]
+class MSFT_xADDomain : OMI_BaseResource
+{
+    [Key, Description("Name of the domain to which the user will be added")] String DomainName;
+    [Required, Description("Credentials used to query for domain existence"), EmbeddedInstance("MSFT_Credential")] String DomainAdministratorCredential;
+    [Required, Description("Password for the administrator account when the computer is started in Safe Mode"), EmbeddedInstance("MSFT_Credential")] String SafemodeAdministratorPassword;
+    [Write, Description("Fully qualified name of the parent domain")] String ParentDomainName;
+    [Write, Description("NetBIOS name for the new domain")] String DomainNetbiosName;
+    [Write, Description("Credential used for creating DNS delegation"), EmbeddedInstance("MSFT_Credential")] String DnsDelegationCredential;
+    [Write, Description("Path to a directory that contains the domain database")] String DatabasePath;
+    [Write, Description("Path to a directory for the log file that will be written")] String LogPath;
+    [Write, Description("Path to a directory where the Sysvol file will be written")] String SysvolPath;
+};

+ 224 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.psm1

@@ -0,0 +1,224 @@
+#
+# xADDomainController: DSC resource to install a domain controller in Active
+# Directory.
+#
+
+function Get-TargetResource
+{
+    [OutputType([System.Collections.Hashtable])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [String]$DomainName,
+
+        [Parameter(Mandatory)]
+        [PSCredential]$DomainAdministratorCredential,
+
+        [Parameter(Mandatory)]
+        [PSCredential]$SafemodeAdministratorPassword,
+
+        [String]$DatabasePath,
+
+        [String]$LogPath,
+
+        [String]$SysvolPath,
+
+        [String]$SiteName
+    )
+
+    $returnValue = @{
+        DomainName = $DomainName
+        Ensure = $false
+    }
+
+    try
+    {
+        Write-Verbose -Message "Resolving '$($DomainName)' ..."
+        $domain = Get-ADDomain -Identity $DomainName -Credential $DomainAdministratorCredential
+        if ($domain -ne $null)
+        {
+            Write-Verbose -Message "Domain '$($DomainName)' is present. Looking for DCs ..."
+            try
+            {
+                $dc = Get-ADDomainController -Identity $env:COMPUTERNAME -Credential $DomainAdministratorCredential
+                Write-Verbose -Message "Found domain controller '$($dc.Name)' in domain '$($dc.Domain)'."
+                if ($dc.Domain -eq $DomainName)
+                {
+                    Write-Verbose -Message "Current node '$($dc.Name)' is already a domain controller for domain '$($dc.Domain)'."
+
+                    $serviceNTDS     = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters'
+                    $serviceNETLOGON = Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters'
+
+                    $returnValue.Ensure       = $true
+                    $returnValue.DatabasePath = $serviceNTDS.'DSA Working Directory'
+                    $returnValue.LogPath      = $serviceNTDS.'Database log files path'
+                    $returnValue.SysvolPath   = $serviceNETLOGON.SysVol -replace '\\sysvol$', ''
+                    $returnValue.SiteName     = $dc.Site
+                }
+            }
+            catch
+            {
+                if ($error[0]) {Write-Verbose $error[0].Exception}
+                Write-Verbose -Message "Current node does not host a domain controller."
+            }
+        }
+    }
+    catch [System.Management.Automation.CommandNotFoundException]
+    {
+        if ($error[0]) {Write-Verbose $error[0].Exception}
+        Write-Verbose -Message "Current node is not running AD WS, and hence is not a domain controller."
+    }
+    $returnValue
+}
+
+function Set-TargetResource
+{
+    param
+    (
+        [Parameter(Mandatory)]
+        [String]$DomainName,
+
+        [Parameter(Mandatory)]
+        [PSCredential]$DomainAdministratorCredential,
+
+        [Parameter(Mandatory)]
+        [PSCredential]$SafemodeAdministratorPassword,
+
+        [String]$DatabasePath,
+
+        [String]$LogPath,
+
+        [String]$SysvolPath,
+
+        [String]$SiteName
+    )
+
+    # Debug can pause Install-ADDSDomainController, so we remove it.
+    $parameters = $PSBoundParameters.Remove("Debug");
+    $targetResource = Get-TargetResource @PSBoundParameters
+
+    if ($targetResource.Ensure -eq $false)
+    {
+        ## Node is not a domain controllr so we promote it
+        Write-Verbose -Message "Checking if domain '$($DomainName)' is present ..."
+        $domain = $null;
+        try
+        {
+            $domain = Get-ADDomain -Identity $DomainName -Credential $DomainAdministratorCredential
+        }
+        catch
+        {
+            if ($error[0]) {Write-Verbose $error[0].Exception}
+            throw (New-Object -TypeName System.InvalidOperationException -ArgumentList "Domain '$($DomainName)' could not be found.")
+        }
+
+        Write-Verbose -Message "Verified that domain '$($DomainName)' is present, continuing ..."
+        $params = @{
+            DomainName = $DomainName
+            SafeModeAdministratorPassword = $SafemodeAdministratorPassword.Password
+            Credential = $DomainAdministratorCredential
+            NoRebootOnCompletion = $true
+            Force = $true
+        }
+        if ($DatabasePath -ne $null)
+        {
+            $params.Add("DatabasePath", $DatabasePath)
+        }
+        if ($LogPath -ne $null)
+        {
+            $params.Add("LogPath", $LogPath)
+        }
+        if ($SysvolPath -ne $null)
+        {
+            $params.Add("SysvolPath", $SysvolPath)
+        }
+        if ($SiteName -ne $null -and $SiteName -ne "")
+        {
+            $params.Add("SiteName", $SiteName)
+        }
+
+        Install-ADDSDomainController @params
+        Write-Verbose -Message "Node is now a domain controller for '$($DomainName)'."
+
+        # Signal to the LCM to reboot the node to compensate for the one we
+        # suppressed from Install-ADDSDomainController
+        $global:DSCMachineStatus = 1
+    }
+    elseif ($targetResource.Ensure)
+    {
+        ## Node is a domain controller. We check if other properties are in desired state
+        if ($PSBoundParameters["SiteName"] -and $targetResource.SiteName -ne $SiteName)
+        {
+            ## DC is not in correct site. Move it.
+            Write-Verbose "Moving Domain Controller from '$($targetResource.SiteName)' to '$SiteName'"
+            Move-ADDirectoryServer -Identity $env:COMPUTERNAME -Site $SiteName -Credential $DomainAdministratorCredential
+        }
+    }
+}
+
+function Test-TargetResource
+{
+    [OutputType([System.Boolean])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [String]$DomainName,
+
+        [Parameter(Mandatory)]
+        [PSCredential]$DomainAdministratorCredential,
+
+        [Parameter(Mandatory)]
+        [PSCredential]$SafemodeAdministratorPassword,
+
+        [String]$DatabasePath,
+
+        [String]$LogPath,
+
+        [String]$SysvolPath,
+
+        [String]$SiteName
+    )
+
+    if ($PSBoundParameters.SiteName)
+    {
+        if (-not (Test-ADReplicationSite -SiteName $SiteName -DomainName $DomainName -Credential $DomainAdministratorCredential))
+        {
+            throw (New-Object -TypeName System.InvalidOperationException -ArgumentList "Site '$($SiteName)' could not be found.")
+        }
+    }
+
+    $isCompliant = $true
+
+    try
+    {
+        $parameters = $PSBoundParameters.Remove("Debug");
+
+        $existingResource = Get-TargetResource @PSBoundParameters
+        $isCompliant = $existingResource.Ensure
+
+        if ([System.String]::IsNullOrEmpty($SiteName))
+        {
+            #If SiteName is not specified confgiuration is compliant
+        }
+        elseif ($existingResource.SiteName -ne $SiteName)
+        {
+            Write-Verbose "Domain Controller Site is not in a desired state. Expected '$SiteName', actual '$($existingResource.SiteName)'"
+            $isCompliant = $false
+        }
+    }
+    catch
+    {
+        if ($error[0]) {Write-Verbose $error[0].Exception}
+        Write-Verbose -Message "Domain '$($DomainName)' is NOT present on the current node."
+        $isCompliant = $false
+    }
+
+    $isCompliant
+
+}
+
+## Import the common AD functions
+$adCommonFunctions = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.ps1'
+. $adCommonFunctions
+
+Export-ModuleMember -Function *-TargetResource

+ 11 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.schema.mof

@@ -0,0 +1,11 @@
+[ClassVersion("1.0.1.0"), FriendlyName("xADDomainController")]
+class MSFT_xADDomainController : OMI_BaseResource
+{
+    [Key] String DomainName;
+    [Required, EmbeddedInstance("MSFT_Credential")] String DomainAdministratorCredential;
+    [Required, EmbeddedInstance("MSFT_Credential")] String SafemodeAdministratorPassword;
+    [write] String DatabasePath;
+    [write] String LogPath;
+    [write] String SysvolPath;
+    [write] String SiteName;
+};

+ 226 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/MSFT_xADDomainDefaultPasswordPolicy.psm1

@@ -0,0 +1,226 @@
+# Localized messages
+data localizedData
+{
+    # culture="en-US"
+    ConvertFrom-StringData @'
+        RoleNotFoundError              = Please ensure that the PowerShell module for role '{0}' is installed.
+        QueryingDomainPasswordPolicy   = Querying Active Directory domain '{0}' default password policy.
+        UpdatingDomainPasswordPolicy   = Updating Active Directory domain '{0}' default password policy.
+        SettingPasswordPolicyValue     = Setting password policy '{0}' property to '{1}'.
+        ResourcePropertyValueIncorrect = Property '{0}' value is incorrect; expected '{1}', actual '{2}'.
+        ResourceInDesiredState         = Resource '{0}' is in the desired state.
+        ResourceNotInDesiredState      = Resource '{0}' is NOT in the desired state.
+'@
+}
+
+## List of changeable policy properties
+$mutablePropertyMap = @(
+    @{ Name = 'ComplexityEnabled'; }
+    @{ Name = 'LockoutDuration'; IsTimeSpan = $true; }
+    @{ Name = 'LockoutObservationWindow'; IsTimeSpan = $true; }
+    @{ Name = 'LockoutThreshold'; }
+    @{ Name = 'MinPasswordAge'; IsTimeSpan = $true; }
+    @{ Name = 'MaxPasswordAge'; IsTimeSpan = $true; }
+    @{ Name = 'MinPasswordLength'; }
+    @{ Name = 'PasswordHistoryCount'; }
+    @{ Name = 'ReversibleEncryptionEnabled'; }
+)
+
+function Get-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Collections.Hashtable])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [System.String] $DomainName,
+        
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String] $DomainController,
+        
+        [Parameter()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Credential      
+    )
+    Assert-Module -ModuleName 'ActiveDirectory';
+    
+    $PSBoundParameters['Identity'] = $DomainName;
+    $getADDefaultDomainPasswordPolicyParams = Get-ADCommonParameters @PSBoundParameters;
+    Write-Verbose -Message ($localizedData.QueryingDomainPasswordPolicy -f $DomainName);
+    $policy = Get-ADDefaultDomainPasswordPolicy @getADDefaultDomainPasswordPolicyParams;
+    $targetResource = @{
+        DomainName = $DomainName;
+        ComplexityEnabled = $policy.ComplexityEnabled;
+        LockoutDuration = ConvertFrom-Timespan -Timespan $policy.LockoutDuration -TimeSpanType Minutes;
+        LockoutObservationWindow = ConvertFrom-Timespan -Timespan $policy.LockoutObservationWindow -TimeSpanType Minutes;
+        LockoutThreshold = $policy.LockoutThreshold;
+        MinPasswordAge = ConvertFrom-Timespan -Timespan $policy.MinPasswordAge -TimeSpanType Minutes;
+        MaxPasswordAge = ConvertFrom-Timespan -Timespan $policy.MaxPasswordAge -TimeSpanType Minutes;
+        MinPasswordLength = $policy.MinPasswordLength;
+        PasswordHistoryCount = $policy.PasswordHistoryCount;
+        ReversibleEncryptionEnabled = $policy.ReversibleEncryptionEnabled;
+    }
+    return $targetResource;
+} #end Get-TargetResource
+
+function Test-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [System.String] $DomainName,
+        
+        [Parameter()]
+        [System.Boolean] $ComplexityEnabled,
+        
+        [Parameter()]
+        [System.UInt32] $LockoutDuration,
+        
+        [Parameter()]
+        [System.UInt32] $LockoutObservationWindow,
+        
+        [Parameter()]
+        [System.UInt32] $LockoutThreshold,
+        
+        [Parameter()]
+        [System.UInt32] $MinPasswordAge,
+        
+        [Parameter()]
+        [System.UInt32] $MaxPasswordAge,
+        
+        [Parameter()]
+        [System.UInt32] $MinPasswordLength,
+        
+        [Parameter()]
+        [System.UInt32] $PasswordHistoryCount,
+        
+        [Parameter()]
+        [System.Boolean] $ReversibleEncryptionEnabled,
+        
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String] $DomainController,
+        
+        [Parameter()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Credential        
+    )
+    $getTargetResourceParams = @{
+        DomainName = $DomainName;
+    }
+    if ($PSBoundParameters.ContainsKey('Credential'))
+    {
+        $getTargetResourceParams['Credential'] = $Credential;
+    }
+    if ($PSBoundParameters.ContainsKey('DomainController'))
+    {
+        $getTargetResourceParams['DomainController'] = $DomainController;
+    }
+    $targetResource = Get-TargetResource @getTargetResourceParams;
+    
+    $inDesiredState = $true;
+    foreach ($property in $mutablePropertyMap)
+    {
+        $propertyName = $property.Name;
+        if ($PSBoundParameters.ContainsKey($propertyName))
+        {
+            $expectedValue = $PSBoundParameters[$propertyName];
+            $actualValue = $targetResource[$propertyName];
+            if ($expectedValue -ne $actualValue)
+            {
+                $valueIncorrectMessage = $localizedData.ResourcePropertyValueIncorrect -f $propertyName, $expectedValue, $actualValue;
+                Write-Verbose -Message $valueIncorrectMessage;
+                $inDesiredState = $false;
+            }
+        }
+    }
+
+    if ($inDesiredState)
+    {
+        Write-Verbose -Message ($localizedData.ResourceInDesiredState -f $DomainName);
+        return $true;
+    }
+    else
+    {
+        Write-Verbose -Message ($localizedData.ResourceNotInDesiredState -f $DomainName);
+        return $false;
+    }
+} #end Test-TargetResource
+
+function Set-TargetResource
+{
+    [CmdletBinding()]
+    param
+    (
+        [Parameter(Mandatory)]
+        [System.String] $DomainName,
+        
+        [Parameter()]
+        [System.Boolean] $ComplexityEnabled,
+        
+        [Parameter()]
+        [System.UInt32] $LockoutDuration,
+        
+        [Parameter()]
+        [System.UInt32] $LockoutObservationWindow,
+        
+        [Parameter()]
+        [System.UInt32] $LockoutThreshold,
+        
+        [Parameter()]
+        [System.UInt32] $MinPasswordAge,
+        
+        [Parameter()]
+        [System.UInt32] $MaxPasswordAge,
+        
+        [Parameter()]
+        [System.UInt32] $MinPasswordLength,
+        
+        [Parameter()]
+        [System.UInt32] $PasswordHistoryCount,
+        
+        [Parameter()]
+        [System.Boolean] $ReversibleEncryptionEnabled,
+        
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String] $DomainController,
+        
+        [Parameter()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Credential        
+    )
+    Assert-Module -ModuleName 'ActiveDirectory';
+    $PSBoundParameters['Identity'] = $DomainName;
+    $setADDefaultDomainPasswordPolicyParams = Get-ADCommonParameters @PSBoundParameters;
+
+    foreach ($property in $mutablePropertyMap)
+    {
+        $propertyName = $property.Name;
+        if ($PSBoundParameters.ContainsKey($propertyName))
+        {
+            $propertyValue = $PSBoundParameters[$propertyName];
+            if ($property.IsTimeSpan -eq $true)
+            {
+                $propertyValue = ConvertTo-TimeSpan -TimeSpan $propertyValue -TimeSpanType Minutes;
+            }
+            $setADDefaultDomainPasswordPolicyParams[$propertyName] = $propertyValue;
+            Write-Verbose -Message ($localizedData.SettingPasswordPolicyValue -f $propertyName, $propertyValue);
+        }
+    }
+
+    Write-Verbose -Message ($localizedData.UpdatingDomainPasswordPolicy -f $DomainName);
+    [ref] $null = Set-ADDefaultDomainPasswordPolicy @setADDefaultDomainPasswordPolicyParams;
+} #end Set-TargetResource
+
+## Import the common AD functions
+$adCommonFunctions = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.ps1';
+. $adCommonFunctions;
+
+Export-ModuleMember -Function *-TargetResource;

+ 17 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/MSFT_xADDomainDefaultPasswordPolicy.schema.mof

@@ -0,0 +1,17 @@
+[ClassVersion("1.0.0"), FriendlyName("xADDomainDefaultPasswordPolicy")]
+class MSFT_xADDomainDefaultPasswordPolicy : OMI_BaseResource
+{
+    [Key, Description("Name of the domain to which the password policy will be applied")] String DomainName;
+    [Write, Description("Whether password complexity is enabled for the default password policy")] Boolean ComplexityEnabled;
+    [Write, Description("Length of time that an account is locked after the number of failed login attempts (minutes)")] UInt32 LockoutDuration;
+    [Write, Description("Maximum time between two unsuccessful login attempts before the counter is reset to 0 (minutes)")] UInt32 LockoutObservationWindow;
+    [Write, Description("Number of unsuccessful login attempts that are permitted before an account is locked out")] UInt32 LockoutThreshold;
+    [Write, Description("Minimum length of time that you can have the same password (minutes)")] UInt32 MinPasswordAge;
+    [Write, Description("Maximum length of time that you can have the same password (minutes)")] UInt32 MaxPasswordAge;
+    [Write, Description("Minimum number of characters that a password must contain")] UInt32 MinPasswordLength;
+    [Write, Description("Number of previous passwords to remember")] UInt32 PasswordHistoryCount;
+    [Write, Description("Whether the directory must store passwords using reversible encryption")] Boolean ReversibleEncryptionEnabled;
+    [Write, Description("Active Directory domain controller to enact the change upon")] String DomainController;    
+    [Write, Description("Credentials used to access the domain"), EmbeddedInstance("MSFT_Credential")] String Credential;
+};
+

+ 398 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1

@@ -0,0 +1,398 @@
+# Localized messages
+data LocalizedData
+{
+    # culture="en-US"
+    ConvertFrom-StringData @'
+MissingRoleMessage        = Please ensure that the {0} role is installed 
+
+CheckingTrustMessage      = Checking if Trust between {0} and {1} exists ...
+TestTrustMessage          = Trust is {0} between source and target domains and it should be {1} 
+RemovingTrustMessage      = Removing trust between {0} and {1} domains ...
+DeleteTrustMessage        = Trust between specified domains is now absent                          
+AddingTrustMessage        = Adding domain trust between {0} and {1}  ...
+SetTrustMessage           = Trust between specified domains is now present
+
+CheckPropertyMessage      = Checking for {0} between domains ...
+DesiredPropertyMessage    = {0} between domains is set correctly
+NotDesiredPropertyMessage = {0} between domains is not correct. Expected {1}, actual {2}
+SetPropertyMessage        = {0} between domains is set
+'@
+}
+
+function Get-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Collections.Hashtable])]
+    param
+    (
+        [parameter(Mandatory)]
+        [String]$SourceDomainName,
+
+        [parameter(Mandatory)]
+        [String]$TargetDomainName,
+
+        [parameter(Mandatory)]
+        [PSCredential]$TargetDomainAdministratorCredential,
+
+        [parameter(Mandatory)]
+        [ValidateSet("External","Forest")]
+        [String]$TrustType,
+
+        [parameter(Mandatory)]
+        [ValidateSet("Bidirectional","Inbound","Outbound")]
+        [String]$TrustDirection,
+
+        [ValidateSet("Present","Absent")]
+        [String]$Ensure = 'Present'
+    )
+
+#region Input Validation
+
+    # Load the .NET assembly
+    try
+    {
+        Add-type -AssemblyName System.DirectoryServices
+    }
+    # If not found, means ADDS role is not installed
+    catch
+    {
+        $missingRoleMessage = $($LocalizedData.MissingRoleMessage) -f 'AD-Domain-Services' 
+        New-TerminatingError -errorId ActiveDirectoryRoleMissing -errorMessage $missingRoleMessage -errorCategory NotInstalled
+    }
+
+#endregion
+
+    try
+    {
+        switch ($TrustType)
+        {
+            'External' {$DomainOrForest = 'Domain'}
+            'Forest' {$DomainOrForest = 'Forest'}
+        }
+        # Create the target object
+        $trgDirectoryContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext($DomainOrForest,$TargetDomainName, $TargetDomainAdministratorCredential.UserName, $TargetDomainAdministratorCredential.GetNetworkCredential().Password)
+        $trgDomain = ([type]"System.DirectoryServices.ActiveDirectory.$DomainOrForest")::"Get$DomainOrForest"($trgDirectoryContext)
+        # Create the source object
+        $srcDirectoryContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext($DomainOrForest,$SourceDomainName)
+        $srcDomain = ([type]"System.DirectoryServices.ActiveDirectory.$DomainOrForest")::"Get$DomainOrForest"($srcDirectoryContext)
+
+        # Find trust betwen source & destination.
+        $trust = $srcDomain.GetTrustRelationship($trgDomain)
+
+        $Ensure = 'Present'
+    }
+    catch
+    {
+        $Ensure = 'Absent'
+    }
+
+    # return a credential object without password
+    $CIMCredential = New-CimInstance -ClassName MSFT_Credential -ClientOnly `
+                                     -Namespace root/microsoft/windows/desiredstateconfiguration `
+                                     -Property @{
+                                                  UserName = [string]$TargetDomainAdministratorCredential.UserName
+                                                  Password = [string]$null
+                                                }
+
+    @{
+        SourceDomainName = $SourceDomainName
+        TargetDomainName = $TargetDomainName
+        Ensure           = $Ensure
+        TrustType        = $trust.TrustType
+        TrustDirection   = $trust.TrustDirection
+        TargetDomainAdministratorCredential = $CIMCredential
+    }
+
+}
+
+function Set-TargetResource
+{
+    [CmdletBinding()]
+    param
+    (
+        [parameter(Mandatory)]
+        [String]$SourceDomainName,
+
+        [parameter(Mandatory)]
+        [String]$TargetDomainName,
+
+        [parameter(Mandatory)]
+        [PSCredential]$TargetDomainAdministratorCredential,
+
+        [parameter(Mandatory)]
+        [ValidateSet("External","Forest")]
+        [String]$TrustType,
+
+        [parameter(Mandatory)]
+        [ValidateSet("Bidirectional","Inbound","Outbound")]
+        [String]$TrustDirection,
+
+        [ValidateSet("Present","Absent")]
+        [String]$Ensure = 'Present'
+    )
+
+    if($PSBoundParameters.ContainsKey('Debug')){$null = $PSBoundParameters.Remove('Debug')}
+    Validate-ResourceProperties @PSBoundParameters -Apply
+}
+
+function Test-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param
+    (
+        [parameter(Mandatory)]
+        [String]$SourceDomainName,
+
+        [parameter(Mandatory)]
+        [String]$TargetDomainName,
+
+        [parameter(Mandatory)]
+        [PSCredential]$TargetDomainAdministratorCredential,
+
+        [parameter(Mandatory)]
+        [ValidateSet("External","Forest")]
+        [String]$TrustType,
+
+        [parameter(Mandatory)]
+        [ValidateSet("Bidirectional","Inbound","Outbound")]
+        [String]$TrustDirection,
+
+        [ValidateSet("Present","Absent")]
+        [String]$Ensure = 'Present'
+    )
+
+#region Input Validation
+
+    # Load the .NET assembly
+    try
+    {
+        Add-type -AssemblyName System.DirectoryServices
+    }
+    # If not found, means ADDS role is not installed
+    catch
+    {
+        $missingRoleMessage = $($LocalizedData.MissingRoleMessage) -f 'AD-Domain-Services' 
+        New-TerminatingError -errorId ActiveDirectoryRoleMissing -errorMessage $missingRoleMessage -errorCategory NotInstalled
+    }
+
+#endregion
+
+    if($PSBoundParameters.ContainsKey('Debug')){$null = $PSBoundParameters.Remove('Debug')}
+    Validate-ResourceProperties @PSBoundParameters
+}
+
+#region Helper Functions
+function Validate-ResourceProperties
+{
+    [Cmdletbinding()]
+    param
+    (
+        [parameter(Mandatory)]
+        [String]$SourceDomainName,
+
+        [parameter(Mandatory)]
+        [String]$TargetDomainName,
+
+        [parameter(Mandatory)]
+        [PSCredential]$TargetDomainAdministratorCredential,
+
+        [parameter(Mandatory)]
+        [ValidateSet("External","Forest")]
+        [String]$TrustType,
+
+        [parameter(Mandatory)]
+        [ValidateSet("Bidirectional","Inbound","Outbound")]
+        [String]$TrustDirection,
+
+        [ValidateSet("Present","Absent")]
+        [String]$Ensure = 'Present',
+
+        [Switch]$Apply
+    )
+
+    try
+    {
+        $checkingTrustMessage = $($LocalizedData.CheckingTrustMessage) -f $SourceDomainName,$TargetDomainName
+        Write-Verbose -Message $checkingTrustMessage
+
+        switch ($TrustType)
+        {
+            'External' {$DomainOrForest = 'Domain'}
+            'Forest' {$DomainOrForest = 'Forest'}
+        }
+        # Create the target object
+        $trgDirectoryContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext($DomainOrForest,$TargetDomainName, $TargetDomainAdministratorCredential.UserName, $TargetDomainAdministratorCredential.GetNetworkCredential().Password)
+        $trgDomain = ([type]"System.DirectoryServices.ActiveDirectory.$DomainOrForest")::"Get$DomainOrForest"($trgDirectoryContext)
+        # Create the source object
+        $srcDirectoryContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext($DomainOrForest,$SourceDomainName)
+        $srcDomain = ([type]"System.DirectoryServices.ActiveDirectory.$DomainOrForest")::"Get$DomainOrForest"($srcDirectoryContext)
+
+        # Find trust
+        try
+        {
+            # Find trust betwen source & destination.
+            $trust = $srcDomain.GetTrustRelationship($TargetDomainName)
+        
+            $TestTrustMessage = $($LocalizedData.TestTrustMessage) -f 'present',$Ensure
+            Write-Verbose -Message $TestTrustMessage
+
+            if($Ensure -eq 'Present')
+            {
+                #region Test for trust direction
+
+                $CheckPropertyMessage = $($LocalizedData.CheckPropertyMessage) -f 'trust direction'
+                Write-Verbose -Message $CheckPropertyMessage
+             
+                # Set the trust direction if not correct
+                if($trust.TrustDirection -ne $TrustDirection)
+                {
+                    $notDesiredPropertyMessage = $($LocalizedData.NotDesiredPropertyMessage) -f 'Trust direction',$TrustDirection,$trust.TrustDirection
+                    Write-Verbose -Message $notDesiredPropertyMessage
+
+                    if($Apply)
+                    {
+                        $srcDomain.UpdateTrustRelationship($trgDomain,$TrustDirection)
+
+                        $setPropertyMessage = $($LocalizedData.SetPropertyMessage) -f 'Trust direction'
+                        Write-Verbose -Message $setPropertyMessage
+                    }
+                    else
+                    {
+                        return $false
+                    }
+                } # end trust direction is not correct
+            
+                # Trust direction is correct
+                else
+                {
+                    $desiredPropertyMessage = $($LocalizedData.DesiredPropertyMessage) -f 'Trust direction'
+                    Write-Verbose -Message $desiredPropertyMessage
+                }
+                #endregion trust direction
+             
+                #region Test for trust type
+
+                $CheckPropertyMessage = $($LocalizedData.CheckPropertyMessage) -f 'trust type'
+                Write-Verbose -Message $CheckPropertyMessage
+             
+                # Set the trust type if not correct
+                if($trust.TrustType-ne $TrustType)
+                {
+                    $notDesiredPropertyMessage = $($LocalizedData.NotDesiredPropertyMessage) -f 'Trust type',$TrustType,$trust.TrustType
+                    Write-Verbose -Message $notDesiredPropertyMessage
+
+                    if($Apply)
+                    {
+                        # Only way to fix the trust direction is to delete it and create again
+                        # TODO: Add a property to ask user permission to delete an existing trust
+                        $srcDomain.DeleteTrustRelationship($trgDomain)
+                        $srcDomain.CreateTrustRelationship($trgDomain,$TrustDirection)
+
+                        $setPropertyMessage = $($LocalizedData.SetPropertyMessage) -f 'Trust type'
+                        Write-Verbose -Message $setPropertyMessage
+                    }
+                    else
+                    {
+                        return $false
+                    }
+                } # end trust type is not correct
+            
+                # Trust type is correct
+                else
+                {
+                    $desiredPropertyMessage = $($LocalizedData.DesiredPropertyMessage) -f 'Trust type'
+                    Write-Verbose -Message $desiredPropertyMessage
+                }
+
+                #endregion Test for trust type
+
+                # If both trust type and trust direction are correct, return true
+                if(-not $Apply)
+                {
+                    return $true
+                }                
+            } # end Ensure -eq present
+ 
+            # If the trust should be absent, remove the trust
+            else
+            {                                                    
+                if($Apply)
+                {
+                    $removingTrustMessage = $($LocalizedData.RemovingTrustMessage) -f $SourceDomainName,$TargetDomainName
+                    Write-Verbose -Message $removingTrustMessage
+
+                    $srcDomain.DeleteTrustRelationship($trgDomain)
+
+                    $deleteTrustMessage = $LocalizedData.DeleteTrustMessage
+                    Write-Verbose -Message $deleteTrustMessage
+                }
+                else
+                {
+                    return $false
+                }
+            } # end Ensure -eq absent
+        } # end find trust
+
+        # Trust does not exist between source and destination
+        catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException]
+        {
+            $TestTrustMessage = $($LocalizedData.TestTrustMessage) -f 'absent',$Ensure
+            Write-Verbose -Message $TestTrustMessage
+
+            if($Ensure -eq 'Present')
+            {
+                if($Apply)
+                {
+                    $addingTrustMessage = $($LocalizedData.AddingTrustMessage) -f $SourceDomainName,$TargetDomainName
+                    Write-Verbose -Message $addingTrustMessage
+            
+                    $srcDomain.CreateTrustRelationship($trgDomain,$TrustDirection)
+
+                    $setTrustMessage = $LocalizedData.SetTrustMessage
+                    Write-Verbose -Message $setTrustMessage
+                }
+                else
+                {
+                    return $false
+                }
+            } # end Ensure -eq Present
+            else
+            {
+                if(-not $Apply)
+                {
+                    return $true
+                }
+            }
+        } # end no trust
+    }# end getting directory object
+    catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException]
+    {
+        throw
+    }
+}
+
+# Internal function to throw terminating error with specified errroCategory, errorId and errorMessage
+function New-TerminatingError
+{
+    [CmdletBinding()]
+    param
+    (
+        [Parameter(Mandatory)]
+        [String]$errorId,
+        
+        [Parameter(Mandatory)]
+        [String]$errorMessage,
+
+        [Parameter(Mandatory)]
+        [System.Management.Automation.ErrorCategory]$errorCategory
+    )
+    
+    $exception = New-Object System.InvalidOperationException $errorMessage 
+    $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null
+    throw $errorRecord
+}
+
+#endregion
+
+Export-ModuleMember -Function *-TargetResource

+ 13 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.schema.mof

@@ -0,0 +1,13 @@
+
+[ClassVersion("1.0.1.0"), FriendlyName("xADDomainTrust")]
+class MSFT_xADDomainTrust : OMI_BaseResource
+{
+    [Write, Description("Should this resource be present or absent"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
+    [Required, EmbeddedInstance("MSFT_Credential"), Description("Credentials to authenticate to the target domain")] String TargetDomainAdministratorCredential;
+    [Key, Description("Name of the AD domain that is being trusted")] String TargetDomainName;
+    [Required, Description("Type of trust"), ValueMap{"External","Forest"}, Values{"External","Forest"}] String TrustType;
+    [Required, Description("Direction of trust"), ValueMap{"Bidirectional","Inbound","Outbound"}, Values{"Bidirectional","Inbound","Outbound"}] String TrustDirection;
+    [Key, Description("Name of the AD domain that is requesting the trust")] String SourceDomainName;
+};
+
+

+ 492 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADGroup/MSFT_xADGroup.psm1

@@ -0,0 +1,492 @@
+# Localized messages
+data LocalizedData
+{
+    # culture="en-US"
+    ConvertFrom-StringData @'
+        RetrievingGroupMembers         = Retrieving group membership based on '{0}' property.
+        GroupMembershipInDesiredState  = Group membership is in the desired state.
+        GroupMembershipNotDesiredState = Group membership is NOT in the desired state.
+
+        AddingGroupMembers             = Adding '{0}' member(s) to AD group '{1}'.
+        RemovingGroupMembers           = Removing '{0}' member(s) from AD group '{1}'.
+        AddingGroup                    = Adding AD Group '{0}'
+        UpdatingGroup                  = Updating AD Group '{0}'
+        RemovingGroup                  = Removing AD Group '{0}'
+        MovingGroup                    = Moving AD Group '{0}' to '{1}'
+        GroupNotFound                  = AD Group '{0}' was not found
+        NotDesiredPropertyState        = AD Group '{0}' is not correct. Expected '{1}', actual '{2}'
+        UpdatingGroupProperty          = Updating AD Group property '{0}' to '{1}'
+'@
+}
+
+function Get-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Collections.Hashtable])]
+    param
+    (
+        [parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $GroupName,
+
+        [ValidateSet('DomainLocal','Global','Universal')]
+        [System.String]
+        $GroupScope = 'Global',
+
+        [ValidateSet('Security','Distribution')]
+        [System.String]
+        $Category = 'Security',
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Path,
+
+        [ValidateSet("Present", "Absent")]
+        [System.String]
+        $Ensure = "Present",
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Description,
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $DisplayName,
+
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Credential,
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $DomainController,
+
+        [System.String[]]
+        $Members,
+
+        [System.String[]]
+        $MembersToInclude,
+
+        [System.String[]]
+        $MembersToExclude,
+
+        [ValidateSet('SamAccountName','DistinguishedName','SID','ObjectGUID')]
+        [System.String]
+        $MembershipAttribute = 'SamAccountName',
+
+        ## This must be the user's DN
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ManagedBy,
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Notes
+    )
+    Assert-Module -ModuleName 'ActiveDirectory';
+    $adGroupParams = Get-ADCommonParameters @PSBoundParameters;
+    try {
+        $adGroup = Get-ADGroup @adGroupParams -Property Name,GroupScope,GroupCategory,DistinguishedName,Description,DisplayName,ManagedBy,Info;
+        Write-Verbose -Message ($LocalizedData.RetrievingGroupMembers -f $MembershipAttribute);
+        ## Retrieve the current list of members, returning the specified membership attribute
+        $adGroupMembers = (Get-ADGroupMember @adGroupParams).$MembershipAttribute;
+        $targetResource = @{
+            GroupName = $adGroup.Name;
+            GroupScope = $adGroup.GroupScope;
+            Category = $adGroup.GroupCategory;
+            Path = Get-ADObjectParentDN -DN $adGroup.DistinguishedName;
+            Description = $adGroup.Description;
+            DisplayName = $adGroup.DisplayName;
+            Members = $adGroupMembers;
+            MembersToInclude = $MembersToInclude;
+            MembersToExclude = $MembersToExclude;
+            MembershipAttribute = $MembershipAttribute;
+            ManagedBy = $adGroup.ManagedBy;
+            Notes = $adGroup.Info;
+            Ensure = 'Absent';
+        }
+        if ($adGroup)
+        {
+            $targetResource['Ensure'] = 'Present';
+        }
+    }
+    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
+        Write-Verbose ($LocalizedData.GroupNotFound -f $GroupName);
+        $targetResource = @{
+            GroupName = $GroupName;
+            GroupScope = $GroupScope;
+            Category = $Category;
+            Path = $Path;
+            Description = $Description;
+            DisplayName = $DisplayName;
+            Members = @();
+            MembersToInclude = $MembersToInclude;
+            MembersToExclude = $MembersToExclude;
+            MembershipAttribute = $MembershipAttribute;
+            ManagedBy = $ManagedBy;
+            Notes = $Notes;
+            Ensure = 'Absent';
+        }
+    }
+    return $targetResource;
+} #end function Get-TargetResource
+
+function Test-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param (
+        [parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $GroupName,
+
+        [ValidateSet('DomainLocal','Global','Universal')]
+        [System.String]
+        $GroupScope = 'Global',
+
+        [ValidateSet('Security','Distribution')]
+        [System.String]
+        $Category = 'Security',
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Path,
+
+        [ValidateSet("Present", "Absent")]
+        [System.String]
+        $Ensure = "Present",
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Description,
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $DisplayName,
+
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Credential,
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $DomainController,
+
+        [System.String[]]
+        $Members,
+
+        [System.String[]]
+        $MembersToInclude,
+
+        [System.String[]]
+        $MembersToExclude,
+
+        [ValidateSet('SamAccountName','DistinguishedName','SID','ObjectGUID')]
+        [System.String]
+        $MembershipAttribute = 'SamAccountName',
+
+        ## This must be the user's DN
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ManagedBy,
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Notes
+    )
+    ## Validate parameters before we even attempt to retrieve anything
+    $assertMemberParameters = @{};
+    if ($PSBoundParameters.ContainsKey('Members') -and -not [system.string]::IsNullOrEmpty($Members))
+    {
+        $assertMemberParameters['Members'] = $Members;
+    }
+    if ($PSBoundParameters.ContainsKey('MembersToInclude') -and -not [system.string]::IsNullOrEmpty($MembersToInclude))
+    {
+        $assertMemberParameters['MembersToInclude'] = $MembersToInclude;
+    }
+    if ($PSBoundParameters.ContainsKey('MembersToExclude') -and -not [system.string]::IsNullOrEmpty($MembersToExclude))
+    {
+        $assertMemberParameters['MembersToExclude'] = $MembersToExclude;
+    }
+    Assert-MemberParameters @assertMemberParameters -ModuleName 'xADDomain' -ErrorAction Stop;
+
+    $targetResource = Get-TargetResource @PSBoundParameters;
+    $targetResourceInCompliance = $true;
+    if ($targetResource.GroupScope -ne $GroupScope)
+    {
+        Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'GroupScope', $GroupScope, $targetResource.GroupScope);
+        $targetResourceInCompliance = $false;
+    }
+    if ($targetResource.Category -ne $Category)
+    {
+        Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'Category', $Category, $targetResource.Category);
+        $targetResourceInCompliance = $false;
+    }
+    if ($Path -and ($targetResource.Path -ne $Path))
+    {
+        Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'Path', $Path, $targetResource.Path);
+        $targetResourceInCompliance = $false;
+    }
+    if ($Description -and ($targetResource.Description -ne $Description))
+    {
+        Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'Description', $Description, $targetResource.Description);
+        $targetResourceInCompliance = $false;
+    }
+    if ($DisplayName -and ($targetResource.DisplayName -ne $DisplayName))
+    {
+        Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'DisplayName', $DisplayName, $targetResource.DisplayName);
+        $targetResourceInCompliance = $false;
+    }
+    if ($ManagedBy -and ($targetResource.ManagedBy -ne $ManagedBy))
+    {
+        Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'ManagedBy', $ManagedBy, $targetResource.ManagedBy);
+        $targetResourceInCompliance = $false;
+    }
+    if ($Notes -and ($targetResource.Notes -ne $Notes))
+    {
+        Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'Notes', $Notes, $targetResource.Notes);
+        $targetResourceInCompliance = $false;
+    }
+    ## Test group members match passed membership parameters
+    if (-not (Test-Members @assertMemberParameters -ExistingMembers $targetResource.Members))
+    {
+        Write-Verbose -Message $LocalizedData.GroupMembershipNotDesiredState;
+        $targetResourceInCompliance = $false;
+    }
+    if ($targetResource.Ensure -ne $Ensure)
+    {
+        Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'Ensure', $Ensure, $targetResource.Ensure);
+        $targetResourceInCompliance = $false;
+    }
+    return $targetResourceInCompliance;
+} #end function Test-TargetResource
+
+function Set-TargetResource
+{
+    [CmdletBinding()]
+    param
+    (
+        [parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $GroupName,
+
+        [ValidateSet('DomainLocal','Global','Universal')]
+        [System.String]
+        $GroupScope = 'Global',
+
+        [ValidateSet('Security','Distribution')]
+        [System.String]
+        $Category = 'Security',
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Path,
+
+        [ValidateSet("Present", "Absent")]
+        [System.String]
+        $Ensure = "Present",
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Description,
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $DisplayName,
+
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Credential,
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $DomainController,
+
+        [System.String[]]
+        $Members,
+
+        [System.String[]]
+        $MembersToInclude,
+
+        [System.String[]]
+        $MembersToExclude,
+
+        [ValidateSet('SamAccountName','DistinguishedName','SID','ObjectGUID')]
+        [System.String]
+        $MembershipAttribute = 'SamAccountName',
+
+        ## This must be the user's DN
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ManagedBy,
+
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Notes
+
+    )
+    Assert-Module -ModuleName 'ActiveDirectory';
+    $adGroupParams = Get-ADCommonParameters @PSBoundParameters;
+
+    try {
+        $adGroup = Get-ADGroup @adGroupParams -Property Name,GroupScope,GroupCategory,DistinguishedName,Description,DisplayName,ManagedBy,Info;
+
+        if ($Ensure -eq 'Present') {
+
+            $setADGroupParams = $adGroupParams.Clone();
+            $setADGroupParams['Identity'] = $adGroup.DistinguishedName;
+
+            # Update existing group properties
+            if ($Category -ne $adGroup.GroupCategory)
+            {
+                Write-Verbose ($LocalizedData.UpdatingGroupProperty -f 'Category', $Category);
+                $setADGroupParams['GroupCategory'] = $Category;
+            }
+            if ($GroupScope -ne $adGroup.GroupScope)
+            {
+                ## Cannot change DomainLocal to Global or vice versa directly. Need to change them to a Universal group first!
+                Set-ADGroup -Identity $adGroup.DistinguishedName -GroupScope Universal;
+                Write-Verbose ($LocalizedData.UpdatingGroupProperty -f 'GroupScope', $GroupScope);
+                $setADGroupParams['GroupScope'] = $GroupScope;
+            }
+            if ($Description -and ($Description -ne $adGroup.Description))
+            {
+                Write-Verbose ($LocalizedData.UpdatingGroupProperty -f 'Description', $Description);
+                $setADGroupParams['Description'] = $Description;
+            }
+            if ($DisplayName -and ($DisplayName -ne $adGroup.DisplayName))
+            {
+                Write-Verbose ($LocalizedData.UpdatingGroupProperty -f 'DisplayName', $DisplayName);
+                $setADGroupParams['DisplayName'] = $DisplayName;
+            }
+            if ($ManagedBy -and ($ManagedBy -ne $adGroup.ManagedBy))
+            {
+                Write-Verbose ($LocalizedData.UpdatingGroupProperty -f 'ManagedBy', $ManagedBy);
+                $setADGroupParams['ManagedBy'] = $ManagedBy;
+            }
+            if ($Notes -and ($Notes -ne $adGroup.Info))
+            {
+                Write-Verbose ($LocalizedData.UpdatingGroupProperty -f 'Notes', $Notes);
+                $setADGroupParams['Replace'] = @{ Info = $Notes };
+            }
+            Write-Verbose ($LocalizedData.UpdatingGroup -f $GroupName);
+            Set-ADGroup @setADGroupParams;
+
+            # Move group if the path is not correct
+            if ($Path -and ($Path -ne (Get-ADObjectParentDN -DN $adGroup.DistinguishedName))) {
+                Write-Verbose ($LocalizedData.MovingGroup -f $GroupName, $Path);
+                $moveADObjectParams = $adGroupParams.Clone();
+                $moveADObjectParams['Identity'] = $adGroup.DistinguishedName
+                Move-ADObject @moveADObjectParams -TargetPath $Path;
+            }
+
+            Write-Verbose -Message ($LocalizedData.RetrievingGroupMembers -f $MembershipAttribute);
+            $adGroupMembers = (Get-ADGroupMember @adGroupParams).$MembershipAttribute;
+            if (-not (Test-Members -ExistingMembers $adGroupMembers -Members $Members -MembersToInclude $MembersToInclude -MembersToExclude $MembersToExclude))
+            {
+                ## The fact that we're in the Set method, there is no need to validate the parameter
+                ## combination as this was performed in the Test method
+                if ($PSBoundParameters.ContainsKey('Members') -and -not [system.string]::IsNullOrEmpty($Members))
+                {
+                    # Remove all existing first and add explicit members
+                    $Members = Remove-DuplicateMembers -Members $Members;
+                    # We can only remove members if there are members already in the group!
+                    if ($adGroupMembers.Count -gt 0)
+                    {
+                        Write-Verbose -Message ($LocalizedData.RemovingGroupMembers -f $adGroupMembers.Count, $GroupName);
+                        Remove-ADGroupMember @adGroupParams -Members $adGroupMembers -Confirm:$false;
+                    }
+                    Write-Verbose -Message ($LocalizedData.AddingGroupMembers -f $Members.Count, $GroupName);
+                    Add-ADGroupMember @adGroupParams -Members $Members;
+                }
+                if ($PSBoundParameters.ContainsKey('MembersToInclude') -and -not [system.string]::IsNullOrEmpty($MembersToInclude))
+                {
+                    $MembersToInclude = Remove-DuplicateMembers -Members $MembersToInclude;
+                    Write-Verbose -Message ($LocalizedData.AddingGroupMembers -f $MembersToInclude.Count, $GroupName);
+                    Add-ADGroupMember @adGroupParams -Members $MembersToInclude;
+                }
+                if ($PSBoundParameters.ContainsKey('MembersToExclude') -and -not [system.string]::IsNullOrEmpty($MembersToExclude))
+                {
+                    $MembersToExclude = Remove-DuplicateMembers -Members $MembersToExclude;
+                    Write-Verbose -Message ($LocalizedData.RemovingGroupMembers -f $MembersToExclude.Count, $GroupName);
+                    Remove-ADGroupMember @adGroupParams -Members $MembersToExclude -Confirm:$false;
+                }
+            }
+        }
+        elseif ($Ensure -eq 'Absent')
+        {
+            # Remove existing group
+            Write-Verbose ($LocalizedData.RemovingGroup -f $GroupName);
+            Remove-ADGroup @adGroupParams -Confirm:$false;
+        }
+    }
+    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
+    {
+        ## The AD group doesn't exist
+        if ($Ensure -eq 'Present')
+        {
+
+            Write-Verbose ($LocalizedData.GroupNotFound -f $GroupName);
+            Write-Verbose ($LocalizedData.AddingGroup -f $GroupName);
+
+            $adGroupParams = Get-ADCommonParameters @PSBoundParameters -UseNameParameter;
+            if ($Description)
+            {
+                $adGroupParams['Description'] = $Description;
+            }
+            if ($DisplayName)
+            {
+                $adGroupParams['DisplayName'] = $DisplayName;
+            }
+            if ($ManagedBy)
+            {
+                $adGroupParams['ManagedBy'] = $ManagedBy;
+            }
+            if ($Path)
+            {
+                $adGroupParams['Path'] = $Path;
+            }
+            ## Create group
+            $adGroup = New-ADGroup @adGroupParams -GroupCategory $Category -GroupScope $GroupScope -PassThru;
+
+            ## Only the New-ADGroup cmdlet takes a -Name parameter. Refresh
+            ## the parameters with the -Identity parameter rather than -Name
+            $adGroupParams = Get-ADCommonParameters @PSBoundParameters
+
+            if ($Notes) {
+                ## Can't set the Notes field when creating the group
+                Write-Verbose ($LocalizedData.UpdatingGroupProperty -f 'Notes', $Notes);
+                $setADGroupParams = $adGroupParams.Clone();
+                $setADGroupParams['Identity'] = $adGroup.DistinguishedName;
+                Set-ADGroup @setADGroupParams -Add @{ Info = $Notes };
+            }
+
+            ## Add the required members
+            if ($PSBoundParameters.ContainsKey('Members') -and -not [system.string]::IsNullOrEmpty($Members))
+            {
+                $Members = Remove-DuplicateMembers -Members $Members;
+                Write-Verbose -Message ($LocalizedData.AddingGroupMembers -f $Members.Count, $GroupName);
+                Add-ADGroupMember @adGroupParams -Members $Members;
+            }
+            elseif ($PSBoundParameters.ContainsKey('MembersToInclude') -and -not [system.string]::IsNullOrEmpty($MembersToInclude))
+            {
+                $MembersToInclude = Remove-DuplicateMembers -Members $MembersToInclude;
+                Write-Verbose -Message ($LocalizedData.AddingGroupMembers -f $MembersToInclude.Count, $GroupName);
+                Add-ADGroupMember @adGroupParams -Members $MembersToInclude;
+            }
+
+        }
+    } #end catch
+} #end function Set-TargetResource
+
+## Import the common AD functions
+$adCommonFunctions = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.ps1';
+. $adCommonFunctions;
+
+Export-ModuleMember -Function *-TargetResource;

+ 19 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADGroup/MSFT_xADGroup.schema.mof

@@ -0,0 +1,19 @@
+[ClassVersion("1.0.1.0"), FriendlyName("xADGroup")]
+class MSFT_xADGroup : OMI_BaseResource
+{
+    [Key, Description("Name of the Active Directory group")] String GroupName;
+    [Write, Description("Active Directory group scope"), ValueMap{"DomainLocal","Global","Universal"}, Values{"DomainLocal","Global","Universal"}] String GroupScope;
+    [Write, Description("Active Directory group category"), ValueMap{"Security","Distribution"}, Values{"Security","Distribution"}] String Category;
+    [Write, Description("Location of the group within Active Directory expressed as a Distinguished Name")] String Path;
+    [Write, Description("Should this resource be present or absent"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
+    [Write, Description("Description of the Active Directory group")] String Description;
+    [Write, Description("Display name of the Active Directory group")] String DisplayName;
+    [Write, Description("Credentials used to enact the change upon"), EmbeddedInstance("MSFT_Credential")] String Credential;
+    [Write, Description("Active Directory domain controller to enact the change upon")] String DomainController;
+    [Write, Description("Active Directory group membership should match membership exactly")] String Members[];
+    [Write, Description("Active Directory group should include these members")] String MembersToInclude[];
+    [Write, Description("Active Directory group should NOT include these members")] String MembersToExclude[];
+    [Write, Description("Active Directory attribute used to perform membership operations"), ValueMap{"SamAccountName","DistinguishedName","ObjectGUID","SID"}, Values{"SamAccountName","DistinguishedName","ObjectGUID","SID"}] String MembershipAttribute;
+    [Write, Description("Active Directory managed by attribute specified as a DistinguishedName")] String ManagedBy;
+    [Write, Description("Active Directory group notes field")] String Notes;
+};

+ 221 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADOrganizationalUnit/MSFT_xADOrganizationalUnit.psm1

@@ -0,0 +1,221 @@
+# Localized messages
+data LocalizedData
+{
+    # culture="en-US"
+    ConvertFrom-StringData @'
+        RoleNotFoundError        = Please ensure that the PowerShell module for role '{0}' is installed
+        RetrievingOU             = Retrieving OU '{0}'.
+        UpdatingOU               = Updating OU '{0}'
+        DeletingOU               = Deleting OU '{0}'
+        CreatingOU               = Creating OU '{0}'
+        OUInDesiredState         = OU '{0}' exists and is in the desired state
+        OUNotInDesiredState      = OU '{0}' exists but is not in the desired state
+        OUExistsButShouldNot     = OU '{0}' exists when it should not exist
+        OUDoesNotExistButShould  = OU '{0}' does not exist when it should exist
+'@
+}
+
+function Get-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Collections.Hashtable])]
+    param
+    (    
+        [parameter(Mandatory)] 
+        [System.String] $Name,
+
+        [parameter(Mandatory)] 
+        [System.String] $Path
+    )
+    
+    Assert-Module -ModuleName 'ActiveDirectory';
+    Write-Verbose ($LocalizedData.RetrievingOU -f $Name)
+    $ou = Get-ADOrganizationalUnit -Filter { Name -eq $Name } -SearchBase $Path -SearchScope OneLevel -Properties ProtectedFromAccidentalDeletion, Description
+
+    $targetResource = @{
+        Name = $Name
+        Path = $Path
+        Ensure = if ($null -eq $ou) { 'Absent' } else { 'Present' }
+        ProtectedFromAccidentalDeletion = $ou.ProtectedFromAccidentalDeletion
+        Description = $ou.Description
+    }
+    return $targetResource
+
+} # end function Get-TargetResource
+
+function Test-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param
+    (    
+        [parameter(Mandatory)] 
+        [System.String] $Name,
+
+        [parameter(Mandatory)] 
+        [System.String] $Path,
+        
+        [ValidateSet('Present', 'Absent')]
+        [System.String]
+        $Ensure = 'Present',
+
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Credential,
+
+        [ValidateNotNull()]
+        [System.Boolean] $ProtectedFromAccidentalDeletion = $true,
+
+        [ValidateNotNull()]
+        [System.String] $Description = ''
+    )
+
+    $targetResource = Get-TargetResource -Name $Name -Path $Path
+    
+    if ($targetResource.Ensure -eq 'Present')
+    {
+        if ($Ensure -eq 'Present')
+        {
+            ## Organizational unit exists
+            if ([System.String]::IsNullOrEmpty($Description)) {
+                $isCompliant = (($targetResource.Name -eq $Name) -and
+                                    ($targetResource.Path -eq $Path) -and
+                                        ($targetResource.ProtectedFromAccidentalDeletion -eq $ProtectedFromAccidentalDeletion))
+            }
+            else {
+                $isCompliant = (($targetResource.Name -eq $Name) -and
+                                    ($targetResource.Path -eq $Path) -and
+                                        ($targetResource.ProtectedFromAccidentalDeletion -eq $ProtectedFromAccidentalDeletion) -and
+                                            ($targetResource.Description -eq $Description))
+            }
+
+            if ($isCompliant)
+            {
+                Write-Verbose ($LocalizedData.OUInDesiredState -f $targetResource.Name)
+            }
+            else
+            {
+                Write-Verbose ($LocalizedData.OUNotInDesiredState -f $targetResource.Name)
+            }
+        }
+        else
+        {
+            $isCompliant = $false
+            Write-Verbose ($LocalizedData.OUExistsButShouldNot -f $targetResource.Name)
+        }
+    }
+    else
+    {
+        ## Organizational unit does not exist
+        if ($Ensure -eq 'Present')
+        {
+            $isCompliant = $false
+            Write-Verbose ($LocalizedData.OUDoesNotExistButShould -f $targetResource.Name)
+        }
+        else
+        {
+            $isCompliant = $true
+            Write-Verbose ($LocalizedData.OUInDesiredState -f $targetResource.Name)
+        }
+    }
+
+    return $isCompliant
+
+} #end function Test-TargetResource
+
+function Set-TargetResource
+{
+    [CmdletBinding()]
+    param
+    (    
+        [parameter(Mandatory)] 
+        [System.String] $Name,
+
+        [parameter(Mandatory)] 
+        [System.String] $Path,
+        
+        [ValidateSet('Present', 'Absent')]
+        [System.String]
+        $Ensure = 'Present',
+
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Credential,
+
+        [ValidateNotNull()]
+        [System.Boolean] $ProtectedFromAccidentalDeletion = $true,
+
+        [ValidateNotNull()]
+        [System.String] $Description = ''
+    )
+
+    Assert-Module -ModuleName 'ActiveDirectory';
+    $targetResource = Get-TargetResource -Name $Name -Path $Path
+    
+    if ($targetResource.Ensure -eq 'Present')
+    {
+        $ou = Get-ADOrganizationalUnit -Filter { Name -eq $Name } -SearchBase $Path -SearchScope OneLevel
+        if ($Ensure -eq 'Present')
+        {
+            Write-Verbose ($LocalizedData.UpdatingOU -f $targetResource.Name)
+            $setADOrganizationalUnitParams = @{
+                Identity = $ou
+                Description = $Description
+                ProtectedFromAccidentalDeletion = $ProtectedFromAccidentalDeletion
+            }
+            if ($Credential)
+            {
+                $setADOrganizationalUnitParams['Credential'] = $Credential
+            }
+            Set-ADOrganizationalUnit @setADOrganizationalUnitParams
+        }
+        else
+        {
+            Write-Verbose ($LocalizedData.DeletingOU -f $targetResource.Name)
+            if ($targetResource.ProtectedFromAccidentalDeletion)
+            {
+                $setADOrganizationalUnitParams = @{
+                    Identity = $ou
+                    ProtectedFromAccidentalDeletion = $ProtectedFromAccidentalDeletion
+                }
+                if ($Credential)
+                {
+                    $setADOrganizationalUnitParams['Credential'] = $Credential
+                }
+                Set-ADOrganizationalUnit @setADOrganizationalUnitParams
+            }
+
+            $removeADOrganizationalUnitParams = @{
+                Identity = $ou
+            }
+            if ($Credential)
+            {
+                $removeADOrganizationalUnitParams['Credential'] = $Credential
+            }
+            Remove-ADOrganizationalUnit @removeADOrganizationalUnitParams
+        }
+    }
+    else
+    {
+        Write-Verbose ($LocalizedData.CreatingOU -f $targetResource.Name)
+        $newADOrganizationalUnitParams = @{
+            Name = $Name
+            Path = $Path
+            Description = $Description
+            ProtectedFromAccidentalDeletion = $ProtectedFromAccidentalDeletion
+        }
+        if ($Credential) {
+            $newADOrganizationalUnitParams['Credential'] = $Credential
+        }
+        New-ADOrganizationalUnit @newADOrganizationalUnitParams
+    }
+
+} #end function Set-TargetResource
+
+## Import the common AD functions
+$adCommonFunctions = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.ps1';
+. $adCommonFunctions;
+
+Export-ModuleMember -Function *-TargetResource

+ 12 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADOrganizationalUnit/MSFT_xADOrganizationalUnit.schema.mof

@@ -0,0 +1,12 @@
+[ClassVersion("1.0.0.0"), FriendlyName("xADOrganizationalUnit")] 
+class MSFT_xADOrganizationalUnit : OMI_BaseResource
+{
+    [Key, Description("The name of OU")] string Name;
+    [Key, Description("Specifies the X500 path of the OU or container where the new object is created")] string Path;
+   
+    [Write, ValueMap{"Present", "Absent"}, Values{"Present", "Absent"}] string Ensure;
+    [Write, EmbeddedInstance("MSFT_Credential"),Description("The credential to be used to perform the operation on Active Directory")] string Credential;
+    [Write, Description("Defaults to True")] boolean ProtectedFromAccidentalDeletion;
+    [Write, Description("The description of the OU")] string Description;
+};
+

+ 39 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADRecycleBin/Examples/xActiveDirectory_xADRecycleBin.ps1

@@ -0,0 +1,39 @@
+Configuration Example_xADRecycleBin
+{
+Param(
+    [parameter(Mandatory = $true)]
+    [System.String]
+    $ForestFQDN,
+
+    [parameter(Mandatory = $true)]
+    [System.Management.Automation.PSCredential]
+    $EACredential 
+)
+
+    Import-DscResource -Module xActiveDirectory
+
+    Node $AllNodes.NodeName
+    {
+        xADRecycleBin RecycleBin
+        {
+           EnterpriseAdministratorCredential = $EACredential
+           ForestFQDN = $ForestFQDN
+        }
+    }
+}
+
+$ConfigurationData = @{
+    AllNodes = @(
+        @{
+            NodeName = '2012r2-dc'
+            PSDscAllowPlainTextPassword = $true
+        }
+    )
+}
+
+Example_xADRecycleBin -EACredential (Get-Credential contoso\administrator) -ForestFQDN 'contoso.com' -ConfigurationData $ConfigurationData
+
+Start-DscConfiguration -Path .\Example_xADRecycleBin -Wait -Verbose -WhatIf
+
+Start-DscConfiguration -Path .\Example_xADRecycleBin -Wait -Verbose
+

+ 189 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADRecycleBin/MSFT_xADRecycleBin.psm1

@@ -0,0 +1,189 @@
+function Get-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Collections.Hashtable])]
+    param
+    (
+        [parameter(Mandatory = $true)]
+        [System.String]
+        $ForestFQDN,
+
+        [parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        $EnterpriseAdministratorCredential
+    )
+
+    Try
+    {
+        # AD cmdlets generate non-terminating errors.
+        $ErrorActionPreference = 'Stop'
+
+        $RootDSE = Get-ADRootDSE -Server $ForestFQDN -Credential $EnterpriseAdministratorCredential
+        $RecycleBinPath = "CN=Recycle Bin Feature,CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,$($RootDSE.configurationNamingContext)"
+        $msDSEnabledFeature = Get-ADObject -Identity "CN=Partitions,$($RootDSE.configurationNamingContext)" -Property msDS-EnabledFeature -Server $ForestFQDN -Credential $EnterpriseAdministratorCredential |
+            Select-Object -ExpandProperty msDS-EnabledFeature
+
+        If ($msDSEnabledFeature -contains $RecycleBinPath) {
+            $RecycleBinEnabled = $True
+        } Else {
+            $RecycleBinEnabled = $False
+        }
+    }
+
+    Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException],[Microsoft.ActiveDirectory.Management.ADServerDownException] {
+        Write-Error -Message "Cannot contact forest $ForestFQDN. Check the spelling of the Forest FQDN and make sure that a domain contoller is available on the network."
+        Throw $_
+    }
+    Catch [System.Security.Authentication.AuthenticationException] {
+        Write-Error -Message "Credential error. Check the username and password used."
+        Throw $_
+    }
+    Catch {
+        Write-Error -Message "Unhandled exception getting Recycle Bin status for forest $ForestFQDN."
+        Throw $_
+    }
+
+    Finally {
+        $ErrorActionPreference = 'Continue'
+    }
+
+    $returnValue = @{
+        ForestFQDN = $ForestFQDN
+        RecycleBinEnabled = $RecycleBinEnabled
+        ForestMode = $RootDSE.forestFunctionality.ToString()
+    }
+
+    $returnValue
+}
+
+
+function Set-TargetResource
+{
+    [CmdletBinding(SupportsShouldProcess=$true)]
+    param
+    (
+        [parameter(Mandatory = $true)]
+        [System.String]
+        $ForestFQDN,
+
+        [parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        $EnterpriseAdministratorCredential
+    )
+
+
+    Try
+    {
+        # AD cmdlets generate non-terminating errors.
+        $ErrorActionPreference = 'Stop'
+
+        $Forest = Get-ADForest -Identity $ForestFQDN -Server $ForestFQDN -Credential $EnterpriseAdministratorCredential
+
+        # Check minimum forest level and throw if not
+        If (($Forest.ForestMode -as [int]) -lt 4) {
+            Write-Verbose -Message "Forest functionality level $($Forest.ForestMode) does not meet minimum requirement of Windows2008R2Forest or greater."
+            Throw "Forest functionality level $($Forest.ForestMode) does not meet minimum requirement of Windows2008R2Forest or greater."
+        }
+
+        If ($PSCmdlet.ShouldProcess($Forest.RootDomain, "Enable Active Directory Recycle Bin")) {
+            Enable-ADOptionalFeature 'Recycle Bin Feature' -Scope ForestOrConfigurationSet `
+                -Target $Forest.RootDomain -Server $Forest.DomainNamingMaster `
+                -Credential $EnterpriseAdministratorCredential `
+                -Verbose
+        }
+    }
+
+    Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException],[Microsoft.ActiveDirectory.Management.ADServerDownException] {
+        Write-Error -Message "Cannot contact forest $ForestFQDN. Check the spelling of the Forest FQDN and make sure that a domain contoller is available on the network."
+        Throw $_
+    }
+    Catch [System.Security.Authentication.AuthenticationException] {
+        Write-Error -Message "Credential error. Check the username and password used."
+        Throw $_
+    }
+    Catch {
+        Write-Error -Message "Unhandled exception setting Recycle Bin status for forest $ForestFQDN."
+        Throw $_
+    }
+
+    Finally {
+        $ErrorActionPreference = 'Continue'
+    }
+
+}
+
+
+function Test-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param
+    (
+        [parameter(Mandatory = $true)]
+        [System.String]
+        $ForestFQDN,
+
+        [parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        $EnterpriseAdministratorCredential
+    )
+
+    Try {
+        # AD cmdlets generate non-terminating errors.
+        $ErrorActionPreference = 'Stop'
+
+        $RootDSE = Get-ADRootDSE -Server $ForestFQDN -Credential $EnterpriseAdministratorCredential
+        $RecycleBinPath = "CN=Recycle Bin Feature,CN=Optional Features,CN=Directory Service,CN=Windows NT,CN=Services,$($RootDSE.configurationNamingContext)"
+        $msDSEnabledFeature = Get-ADObject -Identity "CN=Partitions,$($RootDSE.configurationNamingContext)" -Property msDS-EnabledFeature -Server $ForestFQDN -Credential $EnterpriseAdministratorCredential |
+            Select-Object -ExpandProperty msDS-EnabledFeature
+
+        If ($msDSEnabledFeature -contains $RecycleBinPath) {
+            Write-Verbose "Active Directory Recycle Bin is enabled."
+            Return $True
+        } Else {
+            Write-Verbose "Active Directory Recycle Bin is not enabled."
+            Return $False
+        }
+    }
+
+    Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException],[Microsoft.ActiveDirectory.Management.ADServerDownException] {
+        Write-Error -Message "Cannot contact forest $ForestFQDN. Check the spelling of the Forest FQDN and make sure that a domain contoller is available on the network."
+        Throw $_
+    }
+    Catch [System.Security.Authentication.AuthenticationException] {
+        Write-Error -Message "Credential error. Check the username and password used."
+        Throw $_
+    }
+    Catch {
+        Write-Error -Message "Unhandled exception testing Recycle Bin status for forest $ForestFQDN."
+        Throw $_
+    }
+
+    Finally {
+        $ErrorActionPreference = 'Continue'
+    }
+
+
+}
+
+
+Export-ModuleMember -Function *-TargetResource
+
+<#
+Test syntax:
+
+$cred = Get-Credential contoso\administrator
+
+# Valid Domain
+Get-TargetResource -ForestFQDN contoso.com -EnterpriseAdministratorCredential $cred
+Test-TargetResource -ForestFQDN contoso.com -EnterpriseAdministratorCredential $cred
+Set-TargetResource -ForestFQDN contoso.com -EnterpriseAdministratorCredential $cred -WhatIf
+
+# Invalid Domain
+Get-TargetResource -ForestFQDN contoso.cm -EnterpriseAdministratorCredential $cred
+Test-TargetResource -ForestFQDN contoso.cm -EnterpriseAdministratorCredential $cred
+Set-TargetResource -ForestFQDN contoso.cm -EnterpriseAdministratorCredential $cred -WhatIf
+#>
+
+
+

+ 8 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADRecycleBin/MSFT_xADRecycleBin.schema.mof

@@ -0,0 +1,8 @@
+[ClassVersion("1.0.0.0"), FriendlyName("xADRecycleBin")]
+class MSFT_xADRecycleBin : OMI_BaseResource
+{
+    [Key] String ForestFQDN;
+    [Required, EmbeddedInstance("MSFT_Credential")] String EnterpriseAdministratorCredential;
+    [Read] String RecycleBinEnabled;
+    [Read] String ForestMode;
+};

+ 6 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADRecycleBin/ResourceDesignerScripts/GeneratexADRecycleBinSchema.ps1

@@ -0,0 +1,6 @@
+New-xDscResource -Name MSFT_xADRecycleBin -FriendlyName xADRecycleBin -ModuleName xActiveDirectory -Path . -Force -Property @(
+    New-xDscResourceProperty -Name ForestFQDN -Type String -Attribute Key
+    New-xDscResourceProperty -Name EnterpriseAdministratorCredential -Type PSCredential -Attribute Required
+    New-xDscResourceProperty -Name RecycleBinEnabled -Type Boolean -Attribute Read
+    New-xDscResourceProperty -Name ForestMode -Type String -Attribute Read
+)

+ 1017 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADUser/MSFT_xADUser.psm1

@@ -0,0 +1,1017 @@
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')]
+param()
+
+# Localized messages
+data LocalizedData
+{
+    # culture="en-US"
+    ConvertFrom-StringData @'
+        RoleNotFoundError              = Please ensure that the PowerShell module for role '{0}' is installed.
+        RetrievingADUserError          = Error looking up Active Directory user '{0}' ({0}@{1}).
+        PasswordParameterConflictError = Parameter '{0}' cannot be set to '{1}' when the '{2}' parameter is specified.
+
+        RetrievingADUser               = Retrieving Active Directory user '{0}' ({0}@{1}) ...
+        CreatingADDomainConnection     = Creating connection to Active Directory domain '{0}' ...
+        CheckingADUserPassword         = Checking Active Directory user '{0}' password ...
+        ADUserIsPresent                = Active Directory user '{0}' ({0}@{1}) is present.
+        ADUserNotPresent               = Active Directory user '{0}' ({0}@{1}) was NOT present.
+        ADUserNotDesiredPropertyState  = User '{0}' property is NOT in the desired state. Expected '{1}', actual '{2}'.
+
+        AddingADUser                   = Adding Active Directory user '{0}'.
+        RemovingADUser                 = Removing Active Directory user '{0}'.
+        UpdatingADUser                 = Updating Active Directory user '{0}'.
+        SettingADUserPassword          = Setting Active Directory user password.
+        UpdatingADUserProperty         = Updating user property '{0}' with/to '{1}'.
+        RemovingADUserProperty         = Removing user property '{0}' with '{1}'.
+        MovingADUser                   = Moving user from '{0}' to '{1}'.
+        RenamingADUser                 = Renaming user from '{0}' to '{1}'.
+'@
+}
+
+## Create a property map that maps the DSC resource parameters to the
+## Active Directory user attributes.
+$adPropertyMap = @(
+    @{ Parameter = 'CommonName'; ADProperty = 'cn'; }
+    @{ Parameter = 'UserPrincipalName'; }
+    @{ Parameter = 'DisplayName'; }
+    @{ Parameter = 'Path'; ADProperty = 'distinguishedName'; }
+    @{ Parameter = 'GivenName'; }
+    @{ Parameter = 'Initials'; }
+    @{ Parameter = 'Surname'; ADProperty = 'sn'; }
+    @{ Parameter = 'Description'; }
+    @{ Parameter = 'StreetAddress'; }
+    @{ Parameter = 'POBox'; }
+    @{ Parameter = 'City'; ADProperty = 'l'; }
+    @{ Parameter = 'State'; ADProperty = 'st'; }
+    @{ Parameter = 'PostalCode'; }
+    @{ Parameter = 'Country'; ADProperty = 'c'; }
+    @{ Parameter = 'Department'; }
+    @{ Parameter = 'Division'; }
+    @{ Parameter = 'Company'; }
+    @{ Parameter = 'Office'; ADProperty = 'physicalDeliveryOfficeName'; }
+    @{ Parameter = 'JobTitle'; ADProperty = 'title'; }
+    @{ Parameter = 'EmailAddress'; ADProperty = 'mail'; }
+    @{ Parameter = 'EmployeeID'; }
+    @{ Parameter = 'EmployeeNumber'; }
+    @{ Parameter = 'HomeDirectory'; }
+    @{ Parameter = 'HomeDrive'; }
+    @{ Parameter = 'HomePage'; ADProperty = 'wWWHomePage'; }
+    @{ Parameter = 'ProfilePath'; }
+    @{ Parameter = 'LogonScript'; ADProperty = 'scriptPath'; }
+    @{ Parameter = 'Notes'; ADProperty = 'info'; }
+    @{ Parameter = 'OfficePhone'; ADProperty = 'telephoneNumber'; }
+    @{ Parameter = 'MobilePhone'; ADProperty = 'mobile'; }
+    @{ Parameter = 'Fax'; ADProperty = 'facsimileTelephoneNumber'; }
+    @{ Parameter = 'Pager'; }
+    @{ Parameter = 'IPPhone'; }
+    @{ Parameter = 'HomePhone'; }
+    @{ Parameter = 'Enabled'; }
+    @{ Parameter = 'Manager'; }
+    @{ Parameter = 'PasswordNeverExpires'; UseCmdletParameter = $true; }
+    @{ Parameter = 'CannotChangePassword'; UseCmdletParameter = $true; }
+)
+
+function Get-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Collections.Hashtable])]
+    param
+    (
+        ## Name of the domain where the user account is located (only used if password is managed)
+        [Parameter(Mandatory)]
+        [System.String] $DomainName,
+
+        # Specifies the Security Account Manager (SAM) account name of the user (ldapDisplayName 'sAMAccountName')
+        [Parameter(Mandatory)]
+        [System.String] $UserName,
+
+        ## Specifies a new password value for an account
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Password,
+
+        ## Specifies whether the user account is created or deleted
+        [ValidateSet('Present', 'Absent')]
+        [System.String] $Ensure = 'Present',
+
+        ## Specifies the common nane assigned to the user account (ldapDisplayName 'cn')
+        [ValidateNotNull()]
+        [System.String] $CommonName = $UserName,
+
+        ## Specifies the UPN assigned to the user account (ldapDisplayName 'userPrincipalName')
+        [ValidateNotNull()]
+        [System.String] $UserPrincipalName,
+
+        ## Specifies the display name of the object (ldapDisplayName 'displayName')
+        [ValidateNotNull()]
+        [System.String] $DisplayName,
+
+        ## Specifies the X.500 path of the Organizational Unit (OU) or container where the new object is created
+        [ValidateNotNull()]
+        [System.String] $Path,
+
+        ## Specifies the user's given name (ldapDisplayName 'givenName')
+        [ValidateNotNull()]
+        [System.String] $GivenName,
+
+        ## Specifies the initials that represent part of a user's name (ldapDisplayName 'initials')
+        [ValidateNotNull()]
+        [System.String] $Initials,
+
+        ## Specifies the user's last name or surname (ldapDisplayName 'sn')
+        [ValidateNotNull()]
+        [System.String] $Surname,
+
+        ## Specifies a description of the object (ldapDisplayName 'description')
+        [ValidateNotNull()]
+        [System.String] $Description,
+
+        ## Specifies the user's street address (ldapDisplayName 'streetAddress')
+        [ValidateNotNull()]
+        [System.String] $StreetAddress,
+
+        ## Specifies the user's post office box number (ldapDisplayName 'postOfficeBox')
+        [ValidateNotNull()]
+        [System.String] $POBox,
+
+        ## Specifies the user's town or city (ldapDisplayName 'l')
+        [ValidateNotNull()]
+        [System.String] $City,
+
+        ## Specifies the user's or Organizational Unit's state or province (ldapDisplayName 'st')
+        [ValidateNotNull()]
+        [System.String] $State,
+
+        ## Specifies the user's postal code or zip code (ldapDisplayName 'postalCode')
+        [ValidateNotNull()]
+        [System.String] $PostalCode,
+
+        ## Specifies the country or region code for the user's language of choice (ldapDisplayName 'c')
+        [ValidateNotNull()]
+        [System.String] $Country,
+
+        ## Specifies the user's department (ldapDisplayName 'department')
+        [ValidateNotNull()]
+        [System.String] $Department,
+
+        ## Specifies the user's division (ldapDisplayName 'division')
+        [ValidateNotNull()]
+        [System.String] $Division,
+
+        ## Specifies the user's company (ldapDisplayName 'company')
+        [ValidateNotNull()]
+        [System.String] $Company,
+
+        ## Specifies the location of the user's office or place of business (ldapDisplayName 'physicalDeliveryOfficeName')
+        [ValidateNotNull()]
+        [System.String] $Office,
+
+        ## Specifies the user's title (ldapDisplayName 'title')
+        [ValidateNotNull()]
+        [System.String] $JobTitle,
+
+        ## Specifies the user's e-mail address (ldapDisplayName 'mail')
+        [ValidateNotNull()]
+        [System.String] $EmailAddress,
+
+        ## Specifies the user's employee ID (ldapDisplayName 'employeeID')
+        [ValidateNotNull()]
+        [System.String] $EmployeeID,
+
+        ## Specifies the user's employee number (ldapDisplayName 'employeeNumber')
+        [ValidateNotNull()]
+        [System.String] $EmployeeNumber,
+
+        ## Specifies a user's home directory path (ldapDisplayName 'homeDirectory')
+        [ValidateNotNull()]
+        [System.String] $HomeDirectory,
+
+        ## Specifies a drive that is associated with the UNC path defined by the HomeDirectory property (ldapDisplayName 'homeDrive')
+        [ValidateNotNull()]
+        [System.String] $HomeDrive,
+
+        ## Specifies the URL of the home page of the object (ldapDisplayName 'wWWHomePage')
+        [ValidateNotNull()]
+        [System.String] $HomePage,
+
+        ## Specifies a path to the user's profile (ldapDisplayName 'profilePath')
+        [ValidateNotNull()]
+        [System.String] $ProfilePath,
+
+        ## Specifies a path to the user's log on script (ldapDisplayName 'scriptPath')
+        [ValidateNotNull()]
+        [System.String] $LogonScript,
+
+        ## Specifies the notes attached to the user's accoutn (ldapDisplayName 'info')
+        [ValidateNotNull()]
+        [System.String] $Notes,
+
+        ## Specifies the user's office telephone number (ldapDisplayName 'telephoneNumber')
+        [ValidateNotNull()]
+        [System.String] $OfficePhone,
+
+        ## Specifies the user's mobile phone number (ldapDisplayName 'mobile')
+        [ValidateNotNull()]
+        [System.String] $MobilePhone,
+
+        ## Specifies the user's fax phone number (ldapDisplayName 'facsimileTelephoneNumber')
+        [ValidateNotNull()]
+        [System.String] $Fax,
+
+        ## Specifies the user's home telephone number (ldapDisplayName 'homePhone')
+        [ValidateNotNull()]
+        [System.String] $HomePhone,
+
+         ## Specifies the user's pager number (ldapDisplayName 'pager')
+        [ValidateNotNull()]
+        [System.String] $Pager,
+
+        ## Specifies the user's IP telephony phone number (ldapDisplayName 'ipPhone')
+        [ValidateNotNull()]
+        [System.String] $IPPhone,
+
+        ## Specifies the user's manager specified as a Distinguished Name (ldapDisplayName 'manager')
+        [ValidateNotNull()]
+        [System.String] $Manager,
+
+        ## Specifies if the account is enabled (default True)
+        [ValidateNotNull()]
+        [System.Boolean] $Enabled = $true,
+
+        ## Specifies whether the account password can be changed
+        [ValidateNotNull()]
+        [System.Boolean] $CannotChangePassword,
+
+        ## Specifies whether the password of an account can expire
+        [ValidateNotNull()]
+        [System.Boolean] $PasswordNeverExpires,
+
+        ## Specifies the Active Directory Domain Services instance to use to perform the task.
+        [ValidateNotNull()]
+        [System.String] $DomainController,
+
+        ## Specifies the user account credentials to use to perform this task. Ideally this should just be called 'Credential' but is here for backwards compatibility
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $DomainAdministratorCredential,
+
+        ## Specifies the authentication context type when testing user passwords #61
+        [ValidateSet('Default','Negotiate')]
+        [System.String] $PasswordAuthentication = 'Default'
+    )
+
+    Assert-Module -ModuleName 'ActiveDirectory';
+
+    try
+    {
+        $adCommonParameters = Get-ADCommonParameters @PSBoundParameters;
+
+        $adProperties = @();
+        ## Create an array of the AD propertie names to retrieve from the property map
+        foreach ($property in $adPropertyMap)
+        {
+            if ($property.ADProperty)
+            {
+                $adProperties += $property.ADProperty;
+            }
+            else
+            {
+                $adProperties += $property.Parameter;
+            }
+        }
+
+        Write-Verbose -Message ($LocalizedData.RetrievingADUser -f $UserName, $DomainName);
+        $adUser = Get-ADUser @adCommonParameters -Properties $adProperties;
+        Write-Verbose -Message ($LocalizedData.ADUserIsPresent -f $UserName, $DomainName);
+        $Ensure = 'Present';
+    }
+    catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
+    {
+        Write-Verbose -Message ($LocalizedData.ADUserNotPresent -f $UserName, $DomainName);
+        $Ensure = 'Absent';
+    }
+    catch
+    {
+        Write-Error -Message ($LocalizedData.RetrievingADUserError -f $UserName, $DomainName);
+        throw $_;
+    }
+
+    $targetResource = @{
+        DomainName        = $DomainName;
+        Password          = $Password;
+        UserName          = $UserName;
+        DistinguishedName = $adUser.DistinguishedName; ## Read-only property
+        Ensure            = $Ensure;
+        DomainController  = $DomainController;
+    }
+
+    ## Retrieve each property from the ADPropertyMap and add to the hashtable
+    foreach ($property in $adPropertyMap)
+    {
+        if ($property.Parameter -eq 'Path') {
+            ## The path returned is not the parent container
+            if (-not [System.String]::IsNullOrEmpty($adUser.DistinguishedName))
+            {
+                $targetResource['Path'] = Get-ADObjectParentDN -DN $adUser.DistinguishedName;
+            }
+        }
+        elseif ($property.ADProperty)
+        {
+            ## The AD property name is different to the function parameter to use this
+            $targetResource[$property.Parameter] = $adUser.($property.ADProperty);
+        }
+        else
+        {
+            ## The AD property name matches the function parameter
+            $targetResource[$property.Parameter] = $adUser.($property.Parameter);
+        }
+    }
+    return $targetResource;
+
+} #end function Get-TargetResource
+
+function Test-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param
+    (
+        ## Name of the domain where the user account is located (only used if password is managed)
+        [Parameter(Mandatory)]
+        [System.String] $DomainName,
+
+        # Specifies the Security Account Manager (SAM) account name of the user (ldapDisplayName 'sAMAccountName')
+        [Parameter(Mandatory)]
+        [System.String] $UserName,
+
+        ## Specifies a new password value for an account
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Password,
+
+        ## Specifies whether the user account is created or deleted
+        [ValidateSet('Present', 'Absent')]
+        [System.String] $Ensure = 'Present',
+
+        ## Specifies the common nane assigned to the user account (ldapDisplayName 'cn')
+        [ValidateNotNull()]
+        [System.String] $CommonName = $UserName,
+
+        ## Specifies the UPN assigned to the user account (ldapDisplayName 'userPrincipalName')
+        [ValidateNotNull()]
+        [System.String] $UserPrincipalName,
+
+        ## Specifies the display name of the object (ldapDisplayName 'displayName')
+        [ValidateNotNull()]
+        [System.String] $DisplayName,
+
+        ## Specifies the X.500 path of the Organizational Unit (OU) or container where the new object is created
+        [ValidateNotNull()]
+        [System.String] $Path,
+
+        ## Specifies the user's given name (ldapDisplayName 'givenName')
+        [ValidateNotNull()]
+        [System.String] $GivenName,
+
+        ## Specifies the initials that represent part of a user's name (ldapDisplayName 'initials')
+        [ValidateNotNull()]
+        [System.String] $Initials,
+
+        ## Specifies the user's last name or surname (ldapDisplayName 'sn')
+        [ValidateNotNull()]
+        [System.String] $Surname,
+
+        ## Specifies a description of the object (ldapDisplayName 'description')
+        [ValidateNotNull()]
+        [System.String] $Description,
+
+        ## Specifies the user's street address (ldapDisplayName 'streetAddress')
+        [ValidateNotNull()]
+        [System.String] $StreetAddress,
+
+        ## Specifies the user's post office box number (ldapDisplayName 'postOfficeBox')
+        [ValidateNotNull()]
+        [System.String] $POBox,
+
+        ## Specifies the user's town or city (ldapDisplayName 'l')
+        [ValidateNotNull()]
+        [System.String] $City,
+
+        ## Specifies the user's or Organizational Unit's state or province (ldapDisplayName 'st')
+        [ValidateNotNull()]
+        [System.String] $State,
+
+        ## Specifies the user's postal code or zip code (ldapDisplayName 'postalCode')
+        [ValidateNotNull()]
+        [System.String] $PostalCode,
+
+        ## Specifies the country or region code for the user's language of choice (ldapDisplayName 'c')
+        [ValidateNotNull()]
+        [System.String] $Country,
+
+        ## Specifies the user's department (ldapDisplayName 'department')
+        [ValidateNotNull()]
+        [System.String] $Department,
+
+        ## Specifies the user's division (ldapDisplayName 'division')
+        [ValidateNotNull()]
+        [System.String] $Division,
+
+        ## Specifies the user's company (ldapDisplayName 'company')
+        [ValidateNotNull()]
+        [System.String] $Company,
+
+        ## Specifies the location of the user's office or place of business (ldapDisplayName 'physicalDeliveryOfficeName')
+        [ValidateNotNull()]
+        [System.String] $Office,
+
+        ## Specifies the user's title (ldapDisplayName 'title')
+        [ValidateNotNull()]
+        [System.String] $JobTitle,
+
+        ## Specifies the user's e-mail address (ldapDisplayName 'mail')
+        [ValidateNotNull()]
+        [System.String] $EmailAddress,
+
+        ## Specifies the user's employee ID (ldapDisplayName 'employeeID')
+        [ValidateNotNull()]
+        [System.String] $EmployeeID,
+
+        ## Specifies the user's employee number (ldapDisplayName 'employeeNumber')
+        [ValidateNotNull()]
+        [System.String] $EmployeeNumber,
+
+        ## Specifies a user's home directory path (ldapDisplayName 'homeDirectory')
+        [ValidateNotNull()]
+        [System.String] $HomeDirectory,
+
+        ## Specifies a drive that is associated with the UNC path defined by the HomeDirectory property (ldapDisplayName 'homeDrive')
+        [ValidateNotNull()]
+        [System.String] $HomeDrive,
+
+        ## Specifies the URL of the home page of the object (ldapDisplayName 'wWWHomePage')
+        [ValidateNotNull()]
+        [System.String] $HomePage,
+
+        ## Specifies a path to the user's profile (ldapDisplayName 'profilePath')
+        [ValidateNotNull()]
+        [System.String] $ProfilePath,
+
+        ## Specifies a path to the user's log on script (ldapDisplayName 'scriptPath')
+        [ValidateNotNull()]
+        [System.String] $LogonScript,
+
+        ## Specifies the notes attached to the user's accoutn (ldapDisplayName 'info')
+        [ValidateNotNull()]
+        [System.String] $Notes,
+
+        ## Specifies the user's office telephone number (ldapDisplayName 'telephoneNumber')
+        [ValidateNotNull()]
+        [System.String] $OfficePhone,
+
+        ## Specifies the user's mobile phone number (ldapDisplayName 'mobile')
+        [ValidateNotNull()]
+        [System.String] $MobilePhone,
+
+        ## Specifies the user's fax phone number (ldapDisplayName 'facsimileTelephoneNumber')
+        [ValidateNotNull()]
+        [System.String] $Fax,
+
+        ## Specifies the user's home telephone number (ldapDisplayName 'homePhone')
+        [ValidateNotNull()]
+        [System.String] $HomePhone,
+
+         ## Specifies the user's pager number (ldapDisplayName 'pager')
+        [ValidateNotNull()]
+        [System.String] $Pager,
+
+        ## Specifies the user's IP telephony phone number (ldapDisplayName 'ipPhone')
+        [ValidateNotNull()]
+        [System.String] $IPPhone,
+
+        ## Specifies the user's manager specified as a Distinguished Name (ldapDisplayName 'manager')
+        [ValidateNotNull()]
+        [System.String] $Manager,
+
+        ## Specifies if the account is enabled (default True)
+        [ValidateNotNull()]
+        [System.Boolean] $Enabled = $true,
+
+        ## Specifies whether the account password can be changed
+        [ValidateNotNull()]
+        [System.Boolean] $CannotChangePassword,
+
+        ## Specifies whether the password of an account can expire
+        [ValidateNotNull()]
+        [System.Boolean] $PasswordNeverExpires,
+
+        ## Specifies the Active Directory Domain Services instance to use to perform the task.
+        [ValidateNotNull()]
+        [System.String] $DomainController,
+
+        ## Specifies the user account credentials to use to perform this task. Ideally this should just be called 'Credential' but is here for backwards compatibility
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $DomainAdministratorCredential,
+
+        ## Specifies the authentication context type when testing user passwords #61
+        [ValidateSet('Default','Negotiate')]
+        [System.String] $PasswordAuthentication = 'Default'
+    )
+
+    Assert-Parameters @PSBoundParameters;
+    $targetResource = Get-TargetResource @PSBoundParameters;
+    $isCompliant = $true;
+
+    if ($Ensure -eq 'Absent')
+    {
+        if ($targetResource.Ensure -eq 'Present')
+        {
+            Write-Verbose -Message ($LocalizedData.ADUserNotDesiredPropertyState -f 'Ensure', $PSBoundParameters.Ensure, $targetResource.Ensure);
+            $isCompliant = $false;
+        }
+    }
+    else
+    {
+        ## Add common name, ensure and enabled as they may not be explicitly passed and we want to enumerate them
+        $PSBoundParameters['Ensure'] = $Ensure;
+        $PSBoundParameters['Enabled'] = $Enabled;
+
+        foreach ($parameter in $PSBoundParameters.Keys)
+        {
+            if ($parameter -eq 'Password')
+            {
+                $testPasswordParams = @{
+                    Username = $UserName;
+                    Password = $Password;
+                    DomainName = $DomainName;
+                    PasswordAuthentication = $PasswordAuthentication;
+                }
+                if ($DomainAdministratorCredential)
+                {
+                    $testPasswordParams['DomainAdministratorCredential'] = $DomainAdministratorCredential;
+                }
+                if (-not (Test-Password @testPasswordParams))
+                {
+                    Write-Verbose -Message ($LocalizedData.ADUserNotDesiredPropertyState -f 'Password', '<Password>', '<Password>');
+                    $isCompliant = $false;
+                }
+            }
+            # Only check properties that are returned by Get-TargetResource
+            elseif ($targetResource.ContainsKey($parameter))
+            {
+                ## This check is required to be able to explicitly remove values with an empty string, if required
+                if (([System.String]::IsNullOrEmpty($PSBoundParameters.$parameter)) -and ([System.String]::IsNullOrEmpty($targetResource.$parameter)))
+                {
+                    # Both values are null/empty and therefore we are compliant
+                }
+                elseif ($PSBoundParameters.$parameter -ne $targetResource.$parameter)
+                {
+                    Write-Verbose -Message ($LocalizedData.ADUserNotDesiredPropertyState -f $parameter, $PSBoundParameters.$parameter, $targetResource.$parameter);
+                    $isCompliant = $false;
+                }
+            }
+        } #end foreach PSBoundParameter
+    }
+
+    return $isCompliant;
+
+} #end function Test-TargetResource
+
+function Set-TargetResource
+{
+    [CmdletBinding()]
+    param
+    (
+        ## Name of the domain where the user account is located (only used if password is managed)
+        [Parameter(Mandatory)]
+        [System.String] $DomainName,
+
+        # Specifies the Security Account Manager (SAM) account name of the user (ldapDisplayName 'sAMAccountName')
+        [Parameter(Mandatory)]
+        [System.String] $UserName,
+
+        ## Specifies a new password value for an account
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Password,
+
+        ## Specifies whether the user account is created or deleted
+        [ValidateSet('Present', 'Absent')]
+        [System.String] $Ensure = 'Present',
+
+        ## Specifies the common nane assigned to the user account (ldapDisplayName 'cn')
+        [ValidateNotNull()]
+        [System.String] $CommonName = $UserName,
+
+        ## Specifies the UPN assigned to the user account (ldapDisplayName 'userPrincipalName')
+        [ValidateNotNull()]
+        [System.String] $UserPrincipalName,
+
+        ## Specifies the display name of the object (ldapDisplayName 'displayName')
+        [ValidateNotNull()]
+        [System.String] $DisplayName,
+
+        ## Specifies the X.500 path of the Organizational Unit (OU) or container where the new object is created
+        [ValidateNotNull()]
+        [System.String] $Path,
+
+        ## Specifies the user's given name (ldapDisplayName 'givenName')
+        [ValidateNotNull()]
+        [System.String] $GivenName,
+
+        ## Specifies the initials that represent part of a user's name (ldapDisplayName 'initials')
+        [ValidateNotNull()]
+        [System.String] $Initials,
+
+        ## Specifies the user's last name or surname (ldapDisplayName 'sn')
+        [ValidateNotNull()]
+        [System.String] $Surname,
+
+        ## Specifies a description of the object (ldapDisplayName 'description')
+        [ValidateNotNull()]
+        [System.String] $Description,
+
+        ## Specifies the user's street address (ldapDisplayName 'streetAddress')
+        [ValidateNotNull()]
+        [System.String] $StreetAddress,
+
+        ## Specifies the user's post office box number (ldapDisplayName 'postOfficeBox')
+        [ValidateNotNull()]
+        [System.String] $POBox,
+
+        ## Specifies the user's town or city (ldapDisplayName 'l')
+        [ValidateNotNull()]
+        [System.String] $City,
+
+        ## Specifies the user's or Organizational Unit's state or province (ldapDisplayName 'st')
+        [ValidateNotNull()]
+        [System.String] $State,
+
+        ## Specifies the user's postal code or zip code (ldapDisplayName 'postalCode')
+        [ValidateNotNull()]
+        [System.String] $PostalCode,
+
+        ## Specifies the country or region code for the user's language of choice (ldapDisplayName 'c')
+        [ValidateNotNull()]
+        [System.String] $Country,
+
+        ## Specifies the user's department (ldapDisplayName 'department')
+        [ValidateNotNull()]
+        [System.String] $Department,
+
+        ## Specifies the user's division (ldapDisplayName 'division')
+        [ValidateNotNull()]
+        [System.String] $Division,
+
+        ## Specifies the user's company (ldapDisplayName 'company')
+        [ValidateNotNull()]
+        [System.String] $Company,
+
+        ## Specifies the location of the user's office or place of business (ldapDisplayName 'physicalDeliveryOfficeName')
+        [ValidateNotNull()]
+        [System.String] $Office,
+
+        ## Specifies the user's title (ldapDisplayName 'title')
+        [ValidateNotNull()]
+        [System.String] $JobTitle,
+
+        ## Specifies the user's e-mail address (ldapDisplayName 'mail')
+        [ValidateNotNull()]
+        [System.String] $EmailAddress,
+
+        ## Specifies the user's employee ID (ldapDisplayName 'employeeID')
+        [ValidateNotNull()]
+        [System.String] $EmployeeID,
+
+        ## Specifies the user's employee number (ldapDisplayName 'employeeNumber')
+        [ValidateNotNull()]
+        [System.String] $EmployeeNumber,
+
+        ## Specifies a user's home directory path (ldapDisplayName 'homeDirectory')
+        [ValidateNotNull()]
+        [System.String] $HomeDirectory,
+
+        ## Specifies a drive that is associated with the UNC path defined by the HomeDirectory property (ldapDisplayName 'homeDrive')
+        [ValidateNotNull()]
+        [System.String] $HomeDrive,
+
+        ## Specifies the URL of the home page of the object (ldapDisplayName 'wWWHomePage')
+        [ValidateNotNull()]
+        [System.String] $HomePage,
+
+        ## Specifies a path to the user's profile (ldapDisplayName 'profilePath')
+        [ValidateNotNull()]
+        [System.String] $ProfilePath,
+
+        ## Specifies a path to the user's log on script (ldapDisplayName 'scriptPath')
+        [ValidateNotNull()]
+        [System.String] $LogonScript,
+
+        ## Specifies the notes attached to the user's accoutn (ldapDisplayName 'info')
+        [ValidateNotNull()]
+        [System.String] $Notes,
+
+        ## Specifies the user's office telephone number (ldapDisplayName 'telephoneNumber')
+        [ValidateNotNull()]
+        [System.String] $OfficePhone,
+
+        ## Specifies the user's mobile phone number (ldapDisplayName 'mobile')
+        [ValidateNotNull()]
+        [System.String] $MobilePhone,
+
+        ## Specifies the user's fax phone number (ldapDisplayName 'facsimileTelephoneNumber')
+        [ValidateNotNull()]
+        [System.String] $Fax,
+
+        ## Specifies the user's home telephone number (ldapDisplayName 'homePhone')
+        [ValidateNotNull()]
+        [System.String] $HomePhone,
+
+         ## Specifies the user's pager number (ldapDisplayName 'pager')
+        [ValidateNotNull()]
+        [System.String] $Pager,
+
+        ## Specifies the user's IP telephony phone number (ldapDisplayName 'ipPhone')
+        [ValidateNotNull()]
+        [System.String] $IPPhone,
+
+        ## Specifies the user's manager specified as a Distinguished Name (ldapDisplayName 'manager')
+        [ValidateNotNull()]
+        [System.String] $Manager,
+
+        ## Specifies if the account is enabled (default True)
+        [ValidateNotNull()]
+        [System.Boolean] $Enabled = $true,
+
+        ## Specifies whether the account password can be changed
+        [ValidateNotNull()]
+        [System.Boolean] $CannotChangePassword,
+
+        ## Specifies whether the password of an account can expire
+        [ValidateNotNull()]
+        [System.Boolean] $PasswordNeverExpires,
+
+        ## Specifies the Active Directory Domain Services instance to use to perform the task.
+        [ValidateNotNull()]
+        [System.String] $DomainController,
+
+        ## Specifies the user account credentials to use to perform this task. Ideally this should just be called 'Credential' but is here for backwards compatibility
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $DomainAdministratorCredential,
+
+        ## Specifies the authentication context type when testing user passwords #61
+        [ValidateSet('Default','Negotiate')]
+        [System.String] $PasswordAuthentication = 'Default'
+    )
+
+    Assert-Parameters @PSBoundParameters;
+    $targetResource = Get-TargetResource @PSBoundParameters;
+
+    ## Add common name, ensure and enabled as they may not be explicitly passed
+    $PSBoundParameters['Ensure'] = $Ensure;
+    $PSBoundParameters['Enabled'] = $Enabled;
+
+    if ($Ensure -eq 'Present')
+    {
+        if ($targetResource.Ensure -eq 'Absent') {
+            ## User does not exist and needs creating
+            $newADUserParams = Get-ADCommonParameters @PSBoundParameters -UseNameParameter;
+            if ($PSBoundParameters.ContainsKey('Path'))
+            {
+                $newADUserParams['Path'] = $Path;
+            }
+            Write-Verbose -Message ($LocalizedData.AddingADUser -f $UserName);
+            New-ADUser @newADUserParams -SamAccountName $UserName;
+            ## Now retrieve the newly created user
+            $targetResource = Get-TargetResource @PSBoundParameters;
+        }
+
+        $setADUserParams = Get-ADCommonParameters @PSBoundParameters;
+        $replaceUserProperties = @{};
+        $removeUserProperties = @{};
+        foreach ($parameter in $PSBoundParameters.Keys)
+        {
+            ## Only check/action properties specified/declared parameters that match one of the function's
+            ## parameters. This will ignore common parameters such as -Verbose etc.
+            if ($targetResource.ContainsKey($parameter))
+            {
+                if ($parameter -eq 'Path' -and ($PSBoundParameters.Path -ne $targetResource.Path))
+                {
+                    ## Cannot move users by updating the DistinguishedName property
+                    $adCommonParameters = Get-ADCommonParameters @PSBoundParameters;
+                    ## Using the SamAccountName for identity with Move-ADObject does not work, use the DN instead
+                    $adCommonParameters['Identity'] = $targetResource.DistinguishedName;
+                    Write-Verbose -Message ($LocalizedData.MovingADUser -f $targetResource.Path, $PSBoundParameters.Path);
+                    Move-ADObject @adCommonParameters -TargetPath $PSBoundParameters.Path;
+                }
+                elseif ($parameter -eq 'CommonName' -and ($PSBoundParameters.CommonName -ne $targetResource.CommonName))
+                {
+                    ## Cannot rename users by updating the CN property directly
+                    $adCommonParameters = Get-ADCommonParameters @PSBoundParameters;
+                    ## Using the SamAccountName for identity with Rename-ADObject does not work, use the DN instead
+                    $adCommonParameters['Identity'] = $targetResource.DistinguishedName;
+                    Write-Verbose -Message ($LocalizedData.RenamingADUser -f $targetResource.CommonName, $PSBoundParameters.CommonName);
+                    Rename-ADObject @adCommonParameters -NewName $PSBoundParameters.CommonName;
+                }
+                elseif ($parameter -eq 'Password')
+                {
+                    $adCommonParameters = Get-ADCommonParameters @PSBoundParameters;
+                    Write-Verbose -Message ($LocalizedData.SettingADUserPassword -f $UserName);
+                    Set-ADAccountPassword @adCommonParameters -Reset -NewPassword $Password.Password;
+                }
+                elseif ($parameter -eq 'Enabled' -and ($PSBoundParameters.$parameter -ne $targetResource.$parameter))
+                {
+                    ## We cannot enable/disable an account with -Add or -Replace parameters, but inform that
+                    ## we will change this as it is out of compliance (it always gets set anyway)
+                    Write-Verbose -Message ($LocalizedData.UpdatingADUserProperty -f $parameter, $PSBoundParameters.$parameter);
+                }
+                elseif ($PSBoundParameters.$parameter -ne $targetResource.$parameter)
+                {
+                    ## Find the associated AD property
+                    $adProperty = $adPropertyMap | Where-Object { $_.Parameter -eq $parameter };
+
+                    if ([System.String]::IsNullOrEmpty($adProperty))
+                    {
+                        ## We can't do anything is an empty AD property!
+                    }
+                    elseif ([System.String]::IsNullOrEmpty($PSBoundParameters.$parameter))
+                    {
+                        ## We are removing properties
+                        ## Only remove if the existing value in not null or empty
+                        if (-not ([System.String]::IsNullOrEmpty($targetResource.$parameter)))
+                        {
+                            Write-Verbose -Message ($LocalizedData.RemovingADUserProperty -f $parameter, $PSBoundParameters.$parameter);
+                            if ($adProperty.UseCmdletParameter -eq $true)
+                            {
+                                ## We need to pass the parameter explicitly to Set-ADUser, not via -Remove
+                                $setADUserParams[$adProperty.Parameter] = $PSBoundParameters.$parameter;
+                            }
+                            elseif ([System.String]::IsNullOrEmpty($adProperty.ADProperty))
+                            {
+                                $removeUserProperties[$adProperty.Parameter] = $targetResource.$parameter;
+                            }
+                            else
+                            {
+                                $removeUserProperties[$adProperty.ADProperty] = $targetResource.$parameter;
+                            }
+                        }
+                    } #end if remove existing value
+                    else
+                    {
+                        ## We are replacing the existing value
+                        Write-Verbose -Message ($LocalizedData.UpdatingADUserProperty -f $parameter, $PSBoundParameters.$parameter);
+                        if ($adProperty.UseCmdletParameter -eq $true)
+                        {
+                            ## We need to pass the parameter explicitly to Set-ADUser, not via -Replace
+                            $setADUserParams[$adProperty.Parameter] = $PSBoundParameters.$parameter;
+                        }
+                        elseif ([System.String]::IsNullOrEmpty($adProperty.ADProperty))
+                        {
+                            $replaceUserProperties[$adProperty.Parameter] = $PSBoundParameters.$parameter;
+                        }
+                        else
+                        {
+                            $replaceUserProperties[$adProperty.ADProperty] = $PSBoundParameters.$parameter;
+                        }
+                    } #end if replace existing value
+                }
+
+            } #end if TargetResource parameter
+        } #end foreach PSBoundParameter
+
+        ## Only pass -Remove and/or -Replace if we have something to set/change
+        if ($replaceUserProperties.Count -gt 0)
+        {
+            $setADUserParams['Replace'] = $replaceUserProperties;
+        }
+        if ($removeUserProperties.Count -gt 0)
+        {
+            $setADUserParams['Remove'] = $removeUserProperties;
+        }
+
+        Write-Verbose -Message ($LocalizedData.UpdatingADUser -f $UserName);
+        [ref] $null = Set-ADUser @setADUserParams -Enabled $Enabled;
+    }
+    elseif (($Ensure -eq 'Absent') -and ($targetResource.Ensure -eq 'Present'))
+    {
+        ## User exists and needs removing
+        Write-Verbose ($LocalizedData.RemovingADUser -f $UserName);
+        $adCommonParameters = Get-ADCommonParameters @PSBoundParameters;
+        [ref] $null = Remove-ADUser @adCommonParameters -Confirm:$false;
+    }
+
+} #end function Set-TargetResource
+
+# Internal function to validate unsupported options/configurations
+function Assert-Parameters
+{
+    [CmdletBinding()]
+    param
+    (
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential] $Password,
+
+        [ValidateNotNull()]
+        [System.Boolean] $Enabled = $true,
+
+        [Parameter(ValueFromRemainingArguments)]
+        $IgnoredArguments
+    )
+
+    ## We cannot test/set passwords on disabled AD accounts
+    if (($PSBoundParameters.ContainsKey('Password')) -and ($Enabled -eq $false))
+    {
+        $throwInvalidArgumentErrorParams = @{
+            ErrorId = 'xADUser_DisabledAccountPasswordConflict';
+            ErrorMessage = $LocalizedData.PasswordParameterConflictError -f 'Enabled', $false, 'Password';
+        }
+        ThrowInvalidArgumentError @throwInvalidArgumentErrorParams;
+    }
+
+} #end function Assert-Parameters
+
+# Internal function to test the validity of a user's password.
+function Test-Password
+{
+    [CmdletBinding()]
+    param
+    (
+        [Parameter(Mandatory)]
+        [System.String] $DomainName,
+
+        [Parameter(Mandatory)]
+        [System.String] $UserName,
+
+        [Parameter(Mandatory)]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $Password,
+
+        [ValidateNotNull()]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.CredentialAttribute()]
+        $DomainAdministratorCredential,
+
+        ## Specifies the authentication context type when testing user passwords #61
+        [Parameter(Mandatory)]
+        [ValidateSet('Default','Negotiate')]
+        [System.String] $PasswordAuthentication
+    )
+
+    Write-Verbose -Message ($LocalizedData.CreatingADDomainConnection -f $DomainName);
+    Add-Type -AssemblyName 'System.DirectoryServices.AccountManagement';
+
+    if ($DomainAdministratorCredential)
+    {
+        $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext(
+                                [System.DirectoryServices.AccountManagement.ContextType]::Domain,
+                                $DomainName,
+                                $DomainAdministratorCredential.UserName,
+                                $DomainAdministratorCredential.GetNetworkCredential().Password
+                            );
+    }
+    else
+    {
+        $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext(
+                                [System.DirectoryServices.AccountManagement.ContextType]::Domain,
+                                $DomainName,
+                                $null,
+                                $null
+                            );
+    }
+    Write-Verbose -Message ($LocalizedData.CheckingADUserPassword -f $UserName);
+
+    if ($PasswordAuthentication -eq 'Negotiate')
+    {
+        return $principalContext.ValidateCredentials(
+            $UserName,
+            $Password.GetNetworkCredential().Password,
+            [System.DirectoryServices.AccountManagement.ContextOptions]::Negotiate -bor
+                [System.DirectoryServices.AccountManagement.ContextOptions]::Signing -bor
+                    [System.DirectoryServices.AccountManagement.ContextOptions]::Sealing
+        );
+    }
+    else
+    {
+        ## Use default authentication context
+        return $principalContext.ValidateCredentials(
+            $UserName,
+            $Password.GetNetworkCredential().Password
+        );
+    }
+
+} #end function Test-Password
+
+## Import the common AD functions
+$adCommonFunctions = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.ps1';
+. $adCommonFunctions;
+
+Export-ModuleMember -Function *-TargetResource

+ 50 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xADUser/MSFT_xADUser.schema.mof

@@ -0,0 +1,50 @@
+[ClassVersion("1.0.1.0"), FriendlyName("xADUser")]
+class MSFT_xADUser : OMI_BaseResource
+{
+    [Key, Description("Name of the domain where the user account is located (only used if password is managed)")] String DomainName;
+    [Key, Description("Specifies the Security Account Manager (SAM) account name of the user (ldapDisplayName 'sAMAccountName')")] String UserName;
+    [Write, Description("Specifies a new password value for the account"), EmbeddedInstance("MSFT_Credential")] String Password;
+    [Write, Description("Specifies whether the user account is created or deleted"), ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] String Ensure;
+    [Write, Description("Specifies the common nane assigned to the user account (ldapDisplayName 'cn')")] String CommonName;
+    [Write, Description("Specifies the UPN assigned to the user account (ldapDisplayName 'userPrincipalName')")] String UserPrincipalName;
+    [Write, Description("Specifies the display name of the object (ldapDisplayName 'displayName')")] String DisplayName;
+    [Write, Description("Specifies the X.500 path of the Organizational Unit (OU) or container where the new object is created")] String Path;
+    [Write, Description("Specifies the user's given name (ldapDisplayName 'givenName')")] String GivenName;
+    [Write, Description("Specifies the initials that represent part of a user's name (ldapDisplayName 'initials')")] String Initials;
+    [Write, Description("Specifies the user's last name or surname (ldapDisplayName 'sn')")] String Surname;
+    [Write, Description("Specifies a description of the object (ldapDisplayName 'description')")] String Description;
+    [Write, Description("Specifies the user's street address (ldapDisplayName 'streetAddress')")] String StreetAddress;
+    [Write, Description("Specifies the user's post office box number (ldapDisplayName 'postOfficeBox')")] String POBox;
+    [Write, Description("Specifies the user's town or city (ldapDisplayName 'l')")] String City;
+    [Write, Description("Specifies the user's or Organizational Unit's state or province (ldapDisplayName 'st')")] String State;
+    [Write, Description("Specifies the user's postal code or zip code (ldapDisplayName 'postalCode')")] String PostalCode;
+    [Write, Description("Specifies the country or region code for the user's language of choice (ldapDisplayName 'c')")] String Country;
+    [Write, Description("Specifies the user's department (ldapDisplayName 'department')")] String Department;
+    [Write, Description("Specifies the user's division (ldapDisplayName 'division')")] String Division;
+    [Write, Description("Specifies the user's company (ldapDisplayName 'company')")] String Company;
+    [Write, Description("Specifies the location of the user's office or place of business (ldapDisplayName 'physicalDeliveryOfficeName')")] String Office;
+    [Write, Description("Specifies the user's title (ldapDisplayName 'title')")] String JobTitle;
+    [Write, Description("Specifies the user's e-mail address (ldapDisplayName 'mail')")] String EmailAddress;
+    [Write, Description("Specifies the user's employee ID (ldapDisplayName 'employeeID')")] String EmployeeID;
+    [Write, Description("Specifies the user's employee number (ldapDisplayName 'employeeNumber')")] String EmployeeNumber;
+    [Write, Description("Specifies a user's home directory path (ldapDisplayName 'homeDirectory')")] String HomeDirectory;
+    [Write, Description("Specifies a drive that is associated with the UNC path defined by the HomeDirectory property (ldapDisplayName 'homeDrive')")] String HomeDrive;
+    [Write, Description("Specifies the URL of the home page of the object (ldapDisplayName 'wWWHomePage')")] String HomePage;
+    [Write, Description("Specifies a path to the user's profile (ldapDisplayName 'profilePath')")] String ProfilePath;
+    [Write, Description("Specifies a path to the user's log on script (ldapDisplayName 'scriptPath')")] String LogonScript;
+    [Write, Description("Specifies the notes attached to the user's accoutn (ldapDisplayName 'info')")] String Notes;
+    [Write, Description("Specifies the user's office telephone number (ldapDisplayName 'telephoneNumber')")] String OfficePhone;
+    [Write, Description("Specifies the user's mobile phone number (ldapDisplayName 'mobile')")] String MobilePhone;
+    [Write, Description("Specifies the user's fax phone number (ldapDisplayName 'facsimileTelephoneNumber')")] String Fax;
+    [Write, Description("Specifies the user's home telephone number (ldapDisplayName 'homePhone')")] String HomePhone;
+    [Write, Description("Specifies the user's pager number (ldapDisplayName 'pager')")] String Pager;
+    [Write, Description("Specifies the user's IP telephony phone number (ldapDisplayName 'ipPhone')")] String IPPhone;
+    [Write, Description("Specifies the user's manager specified as a Distinguished Name (ldapDisplayName 'manager')")] String Manager;
+    [Write, Description("Specifies if the account is enabled (default True)")] Boolean Enabled;
+    [Write, Description("Specifies whether the account password can be changed")] Boolean CannotChangePassword;
+    [Write, Description("Specifies whether the password of an account can expire")] Boolean PasswordNeverExpires;
+    [Write, Description("Specifies the Active Directory Domain Services instance to use to perform the task.")] String DomainController;
+    [Write, Description("Specifies the user account credentials to use to perform this task"), EmbeddedInstance("MSFT_Credential")] String DomainAdministratorCredential;
+    [Write, Description("Specifies the authentication context type used when testing passwords"), ValueMap{"Default","Negotiate"},Values{"Default","Negotiate"}] String PasswordAuthentication;
+    [Read, Description("Returns the X.500 path of the object")] String DistinguishedName;
+};

+ 185 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xWaitForADDomain/MSFT_xWaitForADDomain.psm1

@@ -0,0 +1,185 @@
+function Get-TargetResource
+{
+    [OutputType([System.Collections.Hashtable])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [String]$DomainName,
+
+        [PSCredential]$DomainUserCredential,
+
+        [UInt64]$RetryIntervalSec = 60,
+
+        [UInt32]$RetryCount = 10,
+        
+        [UInt32]$RebootRetryCount = 0
+
+    )
+
+    if($DomainUserCredential)
+    {
+        $convertToCimCredential = New-CimInstance -ClassName MSFT_Credential -Property @{Username=[string]$DomainUserCredential.UserName; Password=[string]$null} -Namespace root/microsoft/windows/desiredstateconfiguration -ClientOnly
+    }
+    else
+    {
+        $convertToCimCredential = $null
+    }
+    
+    $domain = Get-Domain -DomainName $DomainName -DomainUserCredential $DomainUserCredential
+         
+   
+    $returnValue = @{
+        DomainName = $domain.Name
+        DomainUserCredential = $convertToCimCredential
+        RetryIntervalSec = $RetryIntervalSec
+        RetryCount = $RetryCount
+        RebootRetryCount = $RebootRetryCount
+    }
+    
+    $returnValue
+}
+
+
+function Set-TargetResource
+{
+    param
+    (
+        [Parameter(Mandatory)]
+        [String]$DomainName,
+
+        [PSCredential]$DomainUserCredential,
+
+        [UInt64]$RetryIntervalSec = 60,
+
+        [UInt32]$RetryCount = 10,
+        
+        [UInt32]$RebootRetryCount = 0
+
+    )
+
+    $rebootLogFile = "$env:temp\xWaitForADDomain_Reboot.tmp"
+    
+    for($count = 0; $count -lt $RetryCount; $count++)
+    {
+        $domain = Get-Domain -DomainName $DomainName -DomainUserCredential $DomainUserCredential
+         
+        if($domain)
+        {
+            if($RebootRetryCount -gt 0)
+            {
+                Remove-Item $rebootLogFile -ErrorAction SilentlyContinue
+            }
+            
+            break;
+        }
+        else 
+        {
+            Write-Verbose -Message "Domain $DomainName not found. Will retry again after $RetryIntervalSec sec"
+            Start-Sleep -Seconds $RetryIntervalSec
+            Clear-DnsClientCache
+        }    
+    }
+
+    if(-not $domain) 
+    {
+        if($RebootRetryCount -gt 0)
+        {
+            [UInt32]$rebootCount = Get-Content $RebootLogFile -ErrorAction SilentlyContinue
+            
+            if($rebootCount -lt $RebootRetryCount)
+            {
+                $rebootCount = $rebootCount + 1
+                Write-Verbose -Message  "Domain $DomainName not found after $count attempts with $RetryIntervalSec sec interval. Rebooting.  Reboot attempt number $rebootCount of $RebootRetryCount."
+                Set-Content -Path $RebootLogFile -Value $rebootCount
+                $global:DSCMachineStatus = 1
+            }
+            else 
+            {
+                throw "Domain '$($DomainName)' NOT found after $RebootRetryCount Reboot attempts."     
+            }
+
+            
+        }
+        else
+        {
+            throw "Domain '$($DomainName)' NOT found after $RetryCount attempts."
+        }
+    }
+}
+
+function Test-TargetResource
+{
+    [OutputType([System.Boolean])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [String]$DomainName,
+
+        [PSCredential]$DomainUserCredential,
+
+        [UInt64]$RetryIntervalSec = 60,
+
+        [UInt32]$RetryCount = 10,
+        
+        [UInt32]$RebootRetryCount = 0
+
+    )
+    
+    $rebootLogFile = "$env:temp\xWaitForADDomain_Reboot.tmp"
+    
+    $domain = Get-Domain -DomainName $DomainName -DomainUserCredential $DomainUserCredential
+   
+    if($domain)
+    {
+        if($RebootRetryCount -gt 0)
+        {
+            Remove-Item $rebootLogFile -ErrorAction SilentlyContinue
+        }
+            
+        $true
+    }
+    else 
+    {
+        $false
+    }    
+}
+
+
+
+function Get-Domain
+{
+    [OutputType([PSObject])]
+    param
+    (
+        [Parameter(Mandatory)]
+        [String]$DomainName,
+
+        [PSCredential]$DomainUserCredential
+
+    )
+    Write-Verbose -Message "Checking for domain $DomainName ..."
+  
+    if($DomainUserCredential)
+    {
+        $context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $DomainName, $DomainUserCredential.UserName, $DomainUserCredential.GetNetworkCredential().Password)
+    }
+    else
+    {
+        $context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain',$DomainName)
+    }
+    
+    try 
+    {
+        $domain = ([System.DirectoryServices.ActiveDirectory.DomainController]::FindOne($context)).domain.ToString()
+        Write-Verbose -Message "Found domain $DomainName"
+        $returnValue = @{
+            Name = $domain
+        }
+    
+       $returnValue
+    }
+    catch
+    {
+        Write-Verbose -Message "Domain $DomainName not found"
+    }
+}

+ 9 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/DSCResources/MSFT_xWaitForADDomain/MSFT_xWaitForADDomain.schema.mof

@@ -0,0 +1,9 @@
+[ClassVersion("1.0.1.0"), FriendlyName("xWaitForADDomain")]
+class MSFT_xWaitForADDomain : OMI_BaseResource
+{
+    [Key] String DomainName;
+    [Write, EmbeddedInstance("MSFT_Credential")] String DomainUserCredential;
+    [Write] uint64 RetryIntervalSec;
+    [Write] uint32 RetryCount;
+    [Write] uint32 RebootRetryCount;
+};

+ 23 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/HADCConfiguration.psd1

@@ -0,0 +1,23 @@
+@{
+    AllNodes = @(
+
+        @{
+            Nodename = "sva-dsc1"
+            Role = "Primary DC"
+            DomainName = "sva-dscdom.nttest.microsoft.com"
+            PSDscAllowPlainTextPassword = $true
+        RetryCount = 20 
+        RetryIntervalSec = 30 
+        },
+
+        @{
+            Nodename = "sva-dsc2"
+            Role = "Replica DC"
+            DomainName = "sva-dscdom.nttest.microsoft.com"
+            PSDscAllowPlainTextPassword = $true
+        RetryCount = 20 
+        RetryIntervalSec = 30 
+        }
+    )
+}
+

+ 21 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Microsoft Corporation.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 17 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Misc/New-ADDomainTrust.ps1

@@ -0,0 +1,17 @@
+$Properties = @{
+                SourceDomain    = New-xDscResourceProperty -Name SourceDomainName -Type String -Attribute Key `
+                                                           -Description 'Name of the AD domain that is requesting the trust'
+                TargetDomain    = New-xDscResourceProperty -Name TargetDomainName -Type String -Attribute Key `
+                                                           -Description 'Name of the AD domain that is being trusted'
+                TargetAdminCred = New-xDscResourceProperty -Name TargetDomainAdministratorCredential -Type PSCredential -Attribute Required `
+                                                           -Description 'Credentials to authenticate to the target domain'
+                TrustDirection  = New-xDscResourceProperty -Name TrustDirection -Type String -Attribute Required -ValidateSet 'Bidirectional','Inbound','Outbound' `
+                                                           -Description 'Direction of trust'
+                TrustType       = New-xDscResourceProperty -Name TrustType -Type String -Attribute Required -ValidateSet 'CrossLink','External','Forest','Kerberos','ParentChild','TreeRoot','Unknown' `
+                                                           -Description 'Type of trust'
+                Ensure    = New-xDscResourceProperty -Name Ensure -Type String -Attribute Write -ValidateSet 'Present','Absent' `
+                                                     -Description 'Should this resource be present or absent'
+                
+            }
+New-xDscResource -Name MSFT_xADDomainTrust -Property $Properties.Values -Path . -ModuleName xActiveDirectory -FriendlyName xADDomainTrust -Force
+

BIN
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/PSGetModuleInfo.xml


+ 24 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/ParentChildConfig.psd1

@@ -0,0 +1,24 @@
+@{
+    AllNodes = @(
+
+        @{
+            Nodename = "sva-dsc1"
+            Role = "Parent DC"
+            DomainName = "sva-dscdom.nttest.microsoft.com"
+            PSDscAllowPlainTextPassword = $true
+        RetryCount = 50 
+        RetryIntervalSec = 30 
+        },
+
+        @{
+            Nodename = "sva-dsc2"
+            Role = "Child DC"
+            DomainName = "sva-dscchild"
+            ParentDomainName = "sva-dscdom.nttest.microsoft.com"
+            PSDscAllowPlainTextPassword = $true    
+        RetryCount = 50 
+        RetryIntervalSec = 30        
+        }
+    )
+}
+

+ 1164 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/README.md

@@ -0,0 +1,1164 @@
+[![Build status](https://ci.appveyor.com/api/projects/status/p4jejr60jrgb8ity/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/xactivedirectory/branch/master)
+
+# xActiveDirectory
+
+The **xActiveDirectory** DSC resources allow you to configure and manage Active Directory.
+Note: these resources do not presently install the RSAT tools.
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
+
+## Contributing
+Please check out common DSC Resource [contributing guidelines](https://github.com/PowerShell/DscResources/blob/master/CONTRIBUTING.md).
+
+## Description
+
+The **xActiveDirectory** module contains the **xADComputer, xADDomain, xADDomainController, xADUser, xWaitForDomain, xADDomainTrust, xADRecycleBin, xADGroup, xADOrganizationalUnit and xADDomainDefaultPasswordPolicy** DSC Resources.
+These DSC Resources allow you to configure new domains, child domains, and high availability domain controllers, establish cross-domain trusts and manage users, groups and OUs.
+
+## Resources
+
+* **xADComputer** creates and manages Active Directory computer accounts.
+* **xADDomain** creates new Active Directory forest configurations and new Active Directory domain configurations.
+* **xADDomainController** installs and configures domain controllers in Active Directory.
+* **xADDomainDefaultPasswordPolicy** manages an Active Directory domain's default password policy.
+* **xADDomainTrust** establishes cross-domain trusts.
+* **xADGroup** modifies and removes Active Directory groups.
+* **xADOrganizationalUnit** creates and deletes Active Directory OUs.
+* **xADUser** modifies and removes Active Directory Users.
+* **xWaitForDomain** waits for new, remote domain to setup.
+(Note: the RSAT tools will not be installed when these resources are used to configure AD.)
+
+### **xADDomain**
+
+* **DomainName**: Name of the domain.
+ * If no parent name is specified, this is the fully qualified domain name for the first domain in the forest.
+* **ParentDomainName**: Fully qualified name of the parent domain (optional).
+* **DomainAdministratorCredential**: Credentials used to query for domain existence.
+ * __Note: These are NOT used during domain creation.__
+(AD sets the local admin credentials as new domain administrator credentials during setup.)
+* **SafemodeAdministratorPassword**: Password for the administrator account when the computer is started in Safe Mode.
+* **DnsDelegationCredential**: Credential used for creating DNS delegation (optional).
+* **DomainNetBIOSName**: Specifies the NetBIOS name for the new domain (optional).
+ * If not specified, then the default is automatically computed from the value of the DomainName parameter.
+* **DatabasePath**: Specifies the fully qualified, non-Universal Naming Convention (UNC) path to a directory on a fixed disk of the local computer that contains the domain database (optional).
+* **LogPath**: Specifies the fully qualified, non-UNC path to a directory on a fixed disk of the local computer where the log file for this operation will be written (optional).
+* **SysvolPath**: Specifies the fully qualified, non-UNC path to a directory on a fixed disk of the local computer where the Sysvol file will be written. (optional)
+
+### **xADDomainController**
+
+* **DomainName**: The fully qualified domain name for the domain where the domain controller will be present.
+* **DomainAdministratorCredential**: Specifies the credential for the account used to install the domain controller.
+* **SafemodeAdministratorPassword**: Password for the administrator account when the computer is started in Safe Mode.
+* **DatabasePath**: Specifies the fully qualified, non-Universal Naming Convention (UNC) path to a directory on a fixed disk of the local computer that contains the domain database (optional).
+* **LogPath**: Specifies the fully qualified, non-UNC path to a directory on a fixed disk of the local computer where the log file for this operation will be written (optional).
+* **SysvolPath**: Specifies the fully qualified, non-UNC path to a directory on a fixed disk of the local computer where the Sysvol file will be written. (optional)
+* **SiteName**: Specify the name of an existing site where new domain controller will be placed. (optional)
+
+### **xADUser**
+
+* **DomainName**: Name of the domain to which the user will be added.
+ * The Active Directory domain's fully-qualified domain name must be specified, i.e. contoso.com.
+ * This parameter is used to query and set the user's account password.
+* **UserName**: Specifies the Security Account Manager (SAM) account name of the user.
+ * To be compatible with older operating systems, create a SAM account name that is 20 characters or less.
+ * Once created, the user's SamAccountName and CN cannot be changed.
+* **Password**: Password value for the user account.
+ * _If the account is enabled (default behaviour) you must specify a password._
+ * _You must ensure that the password meets the domain's complexity requirements._
+* **Ensure**: Specifies whether the given user is present or absent (optional).
+ * If not specified, this value defaults to Present.
+* **DomainController**: Specifies the Active Directory Domain Services instance to connect to (optional).
+ * This is only required if not executing the task on a domain controller.
+* **DomainAdministratorCredential**: User account credentials used to perform the task (optional).
+ * This is only required if not executing the task on a domain controller or using the -DomainController parameter.
+* **CommonName**: Specifies the user's CN of the user account (optional).
+ * If not specified, this defaults to the ___UserName___ value.
+* **UserPrincipalName**: Each user account has a user principal name (UPN) in the format [user]@[DNS-domain-name] &#40;optional&#41;.
+* **DisplayName**: Specifies the display name of the user object (optional).
+* **Path**: (optional).
+* **GivenName**: Specifies the user's first or given name (optional).
+* **Initials**: Specifies the initials that represent part of a user's name (optional).
+* **Surname**: Specifies the user's last name or surname (optional).
+* **Description**: Specifies a description of the user object (optional).
+* **StreetAddress**: Specifies the user's street address (optional).
+* **POBox**: Specifies the user's post office box number (optional).
+* **City**: Specifies the user's town or city (optional).
+* **State**: Specifies the user's state or province (optional).
+* **PostalCode**: Specifies the user's postal code or zip code (optional).
+* **Country**: Specifies the country or region code for the user's language of choice (optional).
+ * This should be specified as the country's two character ISO-3166 code.
+* **Department**: Specifies the user's department (optional).
+* **Division**: Specifies the user's division (optional).
+* **Company**: Specifies the user's company (optional).
+* **Office**: Specifies the location of the user's office or place of business (optional).
+* **JobTitle**: Specifies the user's job title (optional).
+* **EmailAddress**: Specifies the user's e-mail address (optional).
+* **EmployeeID**: Specifies the user's employee ID (optional).
+* **EmployeeNumber**: Specifies the user's employee number (optional).
+* **HomeDirectory**: Specifies a user's home directory path (optional).
+* **HomeDrive**: Specifies a drive that is associated with the UNC path defined by the HomeDirectory property (optional).
+ * The drive letter is specified as "[DriveLetter]:" where [DriveLetter] indicates the letter of the drive to associate.
+ * The [DriveLetter] must be a single, uppercase letter and the colon is required.
+* **HomePage**: Specifies the URL of the home page of the user object (optional).
+* **ProfilePath**: Specifies a path to the user's profile (optional).
+ * This value can be a local absolute path or a Universal Naming Convention (UNC) path.
+* **LogonScript**: Specifies a path to the user's log on script (optional).
+ * This value can be a local absolute path or a Universal Naming Convention (UNC) path.
+* **Notes**: (optional).
+* **OfficePhone**: Specifies the user's office telephone number (optional).
+* **MobilePhone**: Specifies the user's mobile phone number (optional).
+* **Fax**: Specifies the user's fax phone number (optional).
+* **Pager**: Specifies the user's pager number (optional).
+* **IPPhone**: Specifies the user's IP telephony number (optional).
+* **HomePhone**: Specifies the user's home telephone number (optional).
+* **Enabled**: Specifies if an account is enabled (optional).
+ * An enabled account requires a password.
+* **Manager**: Specifies the user's manager (optional).
+ * This value can be specified as a DN, ObjectGUID, SID or SamAccountName.
+* **PasswordNeverExpires**: Specifies whether the password of an account can expire (optional).
+ * If not specified, this value defaults to False.
+* **CannotChangePassword**: Specifies whether the account password can be changed (optional).
+ * If not specified, this value defaults to False.
+* **PasswordAuthentication**: Specifies the authentication context used when testing users' passwords (optional).
+ * The 'Negotiate' option supports NTLM authentication - which may be required when testing users' passwords when Active Directory Certificate Services (ADCS) is deployed.
+
+### **xWaitForADDomain**
+
+* **DomainName**: Name of the remote domain.
+* **RetryIntervalSec**: Interval to check for the domain's existence.
+* **RetryCount**: Maximum number of retries to check for the domain's existence.
+
+### **xADDomainTrust**
+
+* **Ensure**: Specifies whether the domain trust is present or absent
+* **TargetDomainAdministratorCredential**: Credentials to authenticate to the target domain
+* **TargetDomainName**: Name of the AD domain that is being trusted
+* **TrustType**: Type of trust
+* **TrustDirection**: Direction of trust, the values for which may be Bidirectional,Inbound, or Outbound
+* **SourceDomainName**: Name of the AD domain that is requesting the trust
+
+### **xADRecycleBin**
+The xADRecycleBin DSC resource will enable the Active Directory Recycle Bin feature for the target forest.
+This resource first verifies that the forest mode is Windows Server 2008 R2 or greater.  If the forest mode
+is insufficient, then the resource will exit with an error message.  The change is executed against the
+Domain Naming Master FSMO of the forest.
+(Note: This resource is compatible with a Windows 2008 R2 or above target node. )
+* **ForestFQDN**:  Fully qualified domain name of forest to enable Active Directory Recycle Bin.
+* **EnterpriseAdministratorCredential**:  Credential with Enterprise Administrator rights to the forest.
+* **RecycleBinEnabled**:  Read-only. Returned by Get.
+* **ForestMode**:  Read-only. Returned by Get.
+
+### **xADGroup**
+The xADGroup DSC resource will manage groups within Active Directory.
+
+* **GroupName**: Name of the Active Directory group to manage.
+* **Category**: This parameter sets the GroupCategory property of the group.
+ * Valid values are 'Security' and 'Distribution'.
+ * If not specified, it defaults to 'Security'.
+* **GroupScope**: Specifies the group scope of the group.
+ * Valid values are 'DomainLocal', 'Global' and 'Universal'.
+ * If not specified, it defaults to 'Global'.
+* **Path**: Path in Active Directory to place the group, specified as a Distinguished Name (DN).
+* **Description**: Specifies a description of the group object (optional).
+* **DisplayName**: Specifies the display name of the group object (optional).
+* **Members**: Specifies the explicit AD objects that should comprise the group membership (optional).
+ * If not specified, no group membership changes are made.
+ * If specified, all undefined group members will be removed the AD group.
+ * This property cannot be specified with either 'MembersToInclude' or 'MembersToExclude'.
+* **MembersToInclude**: Specifies AD objects that must be in the group (optional).
+ * If not specified, no group membership changes are made.
+ * If specified, only the specified members are added to the group.
+ * If specified, no users are removed from the group using this parameter.
+ * This property cannot be specified with the 'Members' parameter.
+* **MembersToExclude**: Specifies AD objects that _must not_ be in the group (optional).
+ * If not specified, no group membership changes are made.
+ * If specified, only those specified are removed from the group.
+ * If specified, no users are added to the group using this parameter.
+ * This property cannot be specified with the 'Members' parameter.
+* **MembershipAttribute**: Defines the AD object attribute that is used to determine group membership (optional).
+ * Valid values are 'SamAccountName', 'DistinguishedName', 'ObjectGUID' and 'SID'.
+ * If not specified, it defaults to 'SamAccountName'.
+ * You cannot mix multiple attribute types.
+* **ManagedBy**: Specifies the user or group that manages the group object (optional).
+ * Valid values are the user's or group's DistinguishedName, ObjectGUID, SID or SamAccountName.
+* **Notes**: The group's info attribute (optional).
+* **Ensure**: Specifies whether the group is present or absent.
+ * Valid values are 'Present' and 'Absent'.
+ * It not specified, it defaults to 'Present'.
+* **DomainController**: An existing Active Directory domain controller used to perform the operation (optional).
+ * If not running on a domain controller, this is required.
+* **Credential**: User account credentials used to perform the operation (optional).
+ * If not running on a domain controller, this is required.
+
+### **xADOrganizationalUnit**
+The xADOrganizational Unit DSC resource will manage OUs within Active Directory.
+* **Name**: Name of the Active Directory organizational unit to manage.
+* **Path**: Specified the X500 (DN) path of the organizational unit's parent object.
+* **Description**: The OU description property (optional).
+* **ProtectedFromAccidentalDeletion**: Valid values are $true and $false. If not specified, it defaults to $true.
+* **Ensure**: Specifies whether the OU is present or absent. Valid values are 'Present' and 'Absent'. It not specified, it defaults to 'Present'.
+* **Credential**: User account credentials used to perform the operation (optional). Note: _if not running on a domain controller, this is required_.
+
+### **xADDomainDefaultPasswordPolicy**
+The xADDomainDefaultPasswordPolicy DSC resource will manage an Active Directory domain's default password policy.
+* **DomainName**: Name of the domain to which the password policy will be applied.
+* **ComplexityEnabled**: Whether password complexity is enabled for the default password policy.
+* **LockoutDuration**: Length of time that an account is locked after the number of failed login attempts (minutes).
+* **LockoutObservationWindow**: Maximum time between two unsuccessful login attempts before the counter is reset to 0 (minutes).
+* **LockoutThreshold**: Number of unsuccessful login attempts that are permitted before an account is locked out.
+* **MinPasswordAge**: Minimum length of time that you can have the same password (minutes).
+* **MaxPasswordAge**: Maximum length of time that you can have the same password (minutes).
+* **MinPasswordLength**: Minimum number of characters that a password must contain.
+* **PasswordHistoryCount**: Number of previous passwords to remember.
+* **ReversibleEncryptionEnabled**: Whether the directory must store passwords using reversible encryption.
+* **DomainController**: An existing Active Directory domain controller used to perform the operation (optional).
+* **Credential**: User account credentials used to perform the operation (optional).
+
+### **xADComputer**
+The xADComputer DSC resource will manage computer accounts within Active Directory.
+* **ComputerName**: Specifies the name of the computer to manage.
+* **Location**: Specifies the location of the computer, such as an office number (optional).
+* **DnsHostName**: Specifies the fully qualified domain name (FQDN) of the computer (optional).
+* **ServicePrincipalNames**: Specifies the service principal names for the computer account (optional).
+* **UserPrincipalName** :Specifies the UPN assigned to the computer account (optional).
+* **DisplayName**: "Specifies the display name of the computer (optional).
+* **Path**: Specifies the X.500 path of the container where the computer is located (optional).
+* **Description**: Specifies a description of the computer object (optional).
+* **Enabled**: Specifies if the computer account is enabled (optional).
+* **Manager**: Specifies the user or group Distinguished Name that manages the computer object (optional).
+ * Valid values are the user's or group's DistinguishedName, ObjectGUID, SID or SamAccountName.
+* **DomainController**: Specifies the Active Directory Domain Services instance to connect to perform the task (optional).
+* **DomainAdministratorCredential**: Specifies the user account credentials to use to perform the task (optional).
+* **RequestFile**: Specifies the full path to the Offline Domain Join Request file to create (optional).
+* **Ensure**: Specifies whether the computer account is present or absent.
+ * Valid values are 'Present' and 'Absent'.
+ * It not specified, it defaults to 'Present'.
+* **DistinguishedName**: Returns the X.500 path of the computer object (read-only).
+* **SID**: Returns the security identifier of the computer object (read-only).
+
+Note: An ODJ Request file will only be created when a computer account is first created in the domain.
+Setting an ODJ Request file path for a configuration that creates a computer account that already exists will not cause the file to be created.
+
+## Versions
+
+### Unreleased
+
+### 2.16.0.0
+
+* xAdDomainController: Update to complete fix for SiteName being required field.
+* xADDomain: Added retry logic to prevent FaultException to crash in Get-TargetResource on subsequent reboots after a domain is created because the service is not yet running. This error is mostly occur when the resource is used with the DSCExtension on Azure. 
+
+### 2.15.0.0
+* xAdDomainController: Fixes SiteName being required field.
+
+### 2.14.0.0
+* xADDomainController: Adds Site option.
+* xADDomainController: Populate values for DatabasePath, LogPath and SysvolPath during Get-TargetResource.
+
+### 2.13.0.0
+* Converted AppVeyor.yml to pull Pester from PSGallery instead of Chocolatey
+* xADUser: Adds 'PasswordAuthentication' option when testing user passwords to support NTLM authentication with Active Directory Certificate Services deployments
+* xADUser: Adds descriptions to user properties within the schema file.
+* xADGroup: Fixes bug when updating groups when alternate Credentials are specified.
+
+### 2.12.0.0
+* xADDomainController: Customer identified two cases of incorrect variables being called in Verbose output messages. Corrected.
+* xADComputer: New resource added.
+* xADComputer: Added RequestFile support.
+* Fixed PSScriptAnalyzer Errors with v1.6.0.
+
+### 2.11.0.0
+* xWaitForADDomain: Made explicit credentials optional and other various updates
+
+### 2.10.0.0
+
+* xADDomainDefaultPasswordPolicy: New resource added.
+* xWaitForADDomain: Updated to make it compatible with systems that don't have the ActiveDirectory module installed, and to allow it to function with domains/forests that don't have a domain controller with Active Directory Web Services running.
+* xADGroup: Fixed bug where specified credentials were not used to retrieve existing group membership.
+* xADDomain: Added check for Active Directory cmdlets.
+* xADDomain: Added additional error trapping, verbose and diagnostic information.
+* xADDomain: Added unit test coverage.
+* Fixes CredentialAttribute and other PSScriptAnalyzer tests in xADCommon, xADDomin, xADGroup, xADOrganizationalUnit and xADUser resources.
+
+### 2.9.0.0
+
+* xADOrganizationalUnit: Merges xADOrganizationalUnit resource from the PowerShell gallery
+* xADGroup: Added Members, MembersToInclude, MembersToExclude and MembershipAttribute properties.
+* xADGroup: Added ManagedBy property.
+* xADGroup: Added Notes property.
+* xADUser: Adds additional property settings.
+* xADUser: Adds unit test coverage.
+
+### 2.8.0.0
+* Added new resource: xADGroup
+* Fixed issue with NewDomainNetbiosName parameter.
+
+### 2.7.0.0
+
+* Added DNS flush in retry loop
+* Bug fixes in xADDomain resource
+
+### 2.6.0.0
+
+* Removed xDscResourceDesigner tests (moved to common tests)
+
+### 2.5.0.0
+
+* Updated xADDomainTrust and xADRecycleBin tests
+
+### 2.4.0.0
+
+* Added xADRecycleBin resource
+* Minor fixes for xADUser resource
+
+### 2.3
+
+* Added xADRecycleBin.
+* Modified xADUser to include a write-verbose after user is removed when Absent.
+* Corrected xADUser to successfully create a disabled user without a password.
+
+### 2.2
+
+* Modified xAdDomain and xAdDomainController to support Ensure as Present / Absent, rather than True/False.
+Note: this may cause issues for existing scripts.
+* Corrected return value to be a hashtable in both resources.
+
+### 2.1.0.0
+
+* Minor update: Get-TargetResource to use domain name instead of name.
+
+### 2.0.0.0
+
+* Updated release, which added the resource:
+    * xADDomainTrust
+
+### 1.0.0.0
+
+* Initial release with the following resources:
+    * xADDomain, xADDomainController, xADUser, and xWaitForDomain
+
+
+## Examples
+
+### Create a highly available Domain using multiple domain controllers
+In the following example configuration, a highly available domain is created by adding a domain controller to an existing domain.
+This example uses the xWaitForDomain resource to ensure that the domain is present before the second domain controller is added.
+
+```powershell
+# A configuration to Create High Availability Domain Controller
+Configuration AssertHADC
+{
+   param
+    (
+        [Parameter(Mandatory)]
+        [pscredential]$safemodeAdministratorCred,
+        [Parameter(Mandatory)]
+        [pscredential]$domainCred,
+        [Parameter(Mandatory)]
+        [pscredential]$DNSDelegationCred,
+        [Parameter(Mandatory)]
+        [pscredential]$NewADUserCred
+    )
+    Import-DscResource -ModuleName xActiveDirectory
+    Node $AllNodes.Where{$_.Role -eq "Primary DC"}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = "Present"
+            Name = "AD-Domain-Services"
+        }
+        xADDomain FirstDS
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domainCred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DnsDelegationCredential = $DNSDelegationCred
+            DependsOn = "[WindowsFeature]ADDSInstall"
+        }
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.DomainName
+            DomainUserCredential = $domainCred
+            RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = "[xADDomain]FirstDS"
+        }
+        xADUser FirstUser
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domainCred
+            UserName = "dummy"
+            Password = $NewADUserCred
+            Ensure = "Present"
+            DependsOn = "[xWaitForADDomain]DscForestWait"
+        }
+    }
+    Node $AllNodes.Where{$_.Role -eq "Replica DC"}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = "Present"
+            Name = "AD-Domain-Services"
+        }
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.DomainName
+            DomainUserCredential = $domainCred
+            RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = "[WindowsFeature]ADDSInstall"
+        }
+        xADDomainController SecondDC
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domainCred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DnsDelegationCredential = $DNSDelegationCred
+            DependsOn = "[xWaitForADDomain]DscForestWait"
+        }
+    }
+}
+# Configuration Data for AD
+$ConfigData = @{
+    AllNodes = @(
+        @{
+            Nodename = "dsc-testNode1"
+            Role = "Primary DC"
+            DomainName = "dsc-test.contoso.com"
+            CertificateFile = "C:\publicKeys\targetNode.cer"
+            Thumbprint = "AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8"
+            RetryCount = 20
+            RetryIntervalSec = 30
+        },
+        @{
+            Nodename = "dsc-testNode2"
+            Role = "Replica DC"
+            DomainName = "dsc-test.contoso.com"
+            CertificateFile = "C:\publicKeys\targetNode.cer"
+            Thumbprint = "AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8"
+            RetryCount = 20
+            RetryIntervalSec = 30
+        }
+    )
+}
+AssertHADC -configurationData $ConfigData `
+-safemodeAdministratorCred (Get-Credential -Message "New Domain Safe Mode Admin Credentials") `
+-domainCred (Get-Credential -Message "New Domain Admin Credentials") `
+-DNSDelegationCred (Get-Credential -Message "Credentials to Setup DNS Delegation") `
+-NewADUserCred (Get-Credential -Message "New AD User Credentials")
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName "dsc-testNode1" -Path $PSScriptRoot\AssertHADC `
+-Credential (Get-Credential -Message "Local Admin Credentials on Remote Machine")
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName "dsc-testNode2" -Path $PSScriptRoot\AssertHADC `
+-Credential (Get-Credential -Message "Local Admin Credentials on Remote Machine")
+# A configuration to Create High Availability Domain Controller
+
+Configuration AssertHADC
+{
+
+   param
+    (
+        [Parameter(Mandatory)]
+        [pscredential]$safemodeAdministratorCred,
+
+        [Parameter(Mandatory)]
+        [pscredential]$domainCred,
+
+        [Parameter(Mandatory)]
+        [pscredential]$DNSDelegationCred,
+
+        [Parameter(Mandatory)]
+        [pscredential]$NewADUserCred
+    )
+
+    Import-DscResource -ModuleName xActiveDirectory
+
+    Node $AllNodes.Where{$_.Role -eq "Primary DC"}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = "Present"
+            Name = "AD-Domain-Services"
+        }
+
+        xADDomain FirstDS
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domainCred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DnsDelegationCredential = $DNSDelegationCred
+            DependsOn = "[WindowsFeature]ADDSInstall"
+        }
+
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.DomainName
+            DomainUserCredential = $domainCred
+            RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = "[xADDomain]FirstDS"
+        }
+
+        xADUser FirstUser
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domainCred
+            UserName = "dummy"
+            Password = $NewADUserCred
+            Ensure = "Present"
+            DependsOn = "[xWaitForADDomain]DscForestWait"
+        }
+
+    }
+
+    Node $AllNodes.Where{$_.Role -eq "Replica DC"}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = "Present"
+            Name = "AD-Domain-Services"
+        }
+
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.DomainName
+            DomainUserCredential = $domainCred
+            RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = "[WindowsFeature]ADDSInstall"
+        }
+
+        xADDomainController SecondDC
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domainCred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DnsDelegationCredential = $DNSDelegationCred
+            DependsOn = "[xWaitForADDomain]DscForestWait"
+        }
+    }
+}
+
+# Configuration Data for AD
+
+$ConfigData = @{
+    AllNodes = @(
+        @{
+            Nodename = "dsc-testNode1"
+            Role = "Primary DC"
+            DomainName = "dsc-test.contoso.com"
+            CertificateFile = "C:\publicKeys\targetNode.cer"
+            Thumbprint = "AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8"
+            RetryCount = 20
+            RetryIntervalSec = 30
+        },
+
+        @{
+            Nodename = "dsc-testNode2"
+            Role = "Replica DC"
+            DomainName = "dsc-test.contoso.com"
+            CertificateFile = "C:\publicKeys\targetNode.cer"
+            Thumbprint = "AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8"
+            RetryCount = 20
+            RetryIntervalSec = 30
+        }
+    )
+}
+
+AssertHADC -configurationData $ConfigData `
+-safemodeAdministratorCred (Get-Credential -Message "New Domain Safe Mode Admin Credentials") `
+-domainCred (Get-Credential -Message "New Domain Admin Credentials") `
+-DNSDelegationCred (Get-Credential -Message "Credentials to Setup DNS Delegation") `
+-NewADUserCred (Get-Credential -Message "New AD User Credentials")
+
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName "dsc-testNode1" -Path $PSScriptRoot\AssertHADC `
+-Credential (Get-Credential -Message "Local Admin Credentials on Remote Machine")
+
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName "dsc-testNode2" -Path $PSScriptRoot\AssertHADC `
+-Credential (Get-Credential -Message "Local Admin Credentials on Remote Machine")
+```
+
+
+### Create a child domain under a parent domain
+
+In this example, we create a domain, and then create a child domain on another node.
+
+```powershell
+# Configuration to Setup Parent Child Domains
+
+Configuration AssertParentChildDomains
+{
+    param
+    (
+        [Parameter(Mandatory)]
+        [pscredential]$safemodeAdministratorCred,
+
+        [Parameter(Mandatory)]
+        [pscredential]$domainCred,
+
+        [Parameter(Mandatory)]
+        [pscredential]$DNSDelegationCred,
+
+        [Parameter(Mandatory)]
+        [pscredential]$NewADUserCred
+    )
+
+    Import-DscResource -ModuleName xActiveDirectory
+
+    Node $AllNodes.Where{$_.Role -eq "Parent DC"}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = "Present"
+            Name = "AD-Domain-Services"
+        }
+
+        xADDomain FirstDS
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domainCred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DnsDelegationCredential = $DNSDelegationCred
+            DependsOn = "[WindowsFeature]ADDSInstall"
+        }
+
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.DomainName
+            DomainUserCredential = $domainCred
+            RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = "[xADDomain]FirstDS"
+        }
+
+        xADUser FirstUser
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domaincred
+            UserName = "dummy"
+            Password = $NewADUserCred
+            Ensure = "Present"
+            DependsOn = "[xWaitForADDomain]DscForestWait"
+        }
+
+    }
+
+    Node $AllNodes.Where{$_.Role -eq "Child DC"}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = "Present"
+            Name = "AD-Domain-Services"
+        }
+
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.ParentDomainName
+            DomainUserCredential = $domainCred
+            RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = "[WindowsFeature]ADDSInstall"
+        }
+
+        xADDomain ChildDS
+        {
+            DomainName = $Node.DomainName
+            ParentDomainName = $Node.ParentDomainName
+            DomainAdministratorCredential = $domainCred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DependsOn = "[xWaitForADDomain]DscForestWait"
+        }
+    }
+}
+
+$ConfigData = @{
+
+    AllNodes = @(
+        @{
+            Nodename = "dsc-testNode1"
+            Role = "Parent DC"
+            DomainName = "dsc-test.contoso.com"
+            CertificateFile = "C:\publicKeys\targetNode.cer"
+            Thumbprint = "AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8"
+            RetryCount = 50
+            RetryIntervalSec = 30
+        },
+
+        @{
+            Nodename = "dsc-testNode2"
+            Role = "Child DC"
+            DomainName = "dsc-child"
+            ParentDomainName = "dsc-test.contoso.com"
+            CertificateFile = "C:\publicKeys\targetNode.cer"
+            Thumbprint = "AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8"
+            RetryCount = 50
+            RetryIntervalSec = 30
+        }
+    )
+}
+
+AssertParentChildDomains -configurationData $ConfigData `
+-safemodeAdministratorCred (Get-Credential -Message "New Domain Safe Mode Admin Credentials") `
+-domainCred (Get-Credential -Message "New Domain Admin Credentials") `
+-DNSDelegationCred (Get-Credential -Message "Credentials to Setup DNS Delegation") `
+-NewADUserCred (Get-Credential -Message "New AD User Credentials")
+
+
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName "dsc-testNode1" -Path $PSScriptRoot\AssertParentChildDomains `
+-Credential (Get-Credential -Message "Local Admin Credentials on Remote Machine")
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName "dsc-testNode2" -Path $PSScriptRoot\AssertParentChildDomains `
+-Credential (Get-Credential -Message "Local Admin Credentials on Remote Machine")
+```
+
+### Create a cross-domain trust
+
+In this example, we setup one-way trust between two domains.
+
+```powershell
+Configuration Sample_xADDomainTrust_OneWayTrust
+{
+    param
+    (
+        [Parameter(Mandatory)]
+        [String]$SourceDomain,
+        [Parameter(Mandatory)]
+        [String]$TargetDomain,
+
+        [Parameter(Mandatory)]
+        [PSCredential]$TargetDomainAdminCred,
+        [Parameter(Mandatory)]
+        [String]$TrustDirection
+    )
+    Import-DscResource -module xActiveDirectory
+    Node $AllNodes.Where{$_.Role -eq 'DomainController'}.NodeName
+    {
+        xADDomainTrust trust
+        {
+            Ensure                              = 'Present'
+            SourceDomainName                    = $SourceDomain
+            TargetDomainName                    = $TargetDomain
+            TargetDomainAdministratorCredential = $TargetDomainAdminCred
+            TrustDirection                      = $TrustDirection
+            TrustType                           = 'External'
+        }
+    }
+}
+$config = @{
+    AllNodes = @(
+        @{
+            NodeName      = 'localhost'
+            Role          = 'DomainController'
+            # Certificate Thumbprint that is used to encrypt/decrypt the credential
+            CertificateID = 'B9192121495A307A492A19F6344E8752B51AC4A6'
+        }
+    )
+}
+Sample_xADDomainTrust_OneWayTrust -configurationdata $config `
+                                  -SourceDomain safeharbor.contoso.com `
+                                  -TargetDomain corporate.contoso.com `
+                                  -TargetDomainAdminCred (get-credential) `
+                                  -TrustDirection 'Inbound'
+# Configuration to Setup Parent Child Domains
+
+configuration AssertParentChildDomains
+{
+    param
+    (
+        [Parameter(Mandatory)]
+        [pscredential]$safemodeAdministratorCred,
+
+        [Parameter(Mandatory)]
+        [pscredential]$domainCred,
+
+        [Parameter(Mandatory)]
+        [pscredential]$DNSDelegationCred,
+
+        [Parameter(Mandatory)]
+        [pscredential]$NewADUserCred
+    )
+
+    Import-DscResource -ModuleName xActiveDirectory
+
+    Node $AllNodes.Where{$_.Role -eq "Parent DC"}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = "Present"
+            Name = "AD-Domain-Services"
+        }
+
+        xADDomain FirstDS
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domainCred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DnsDelegationCredential = $DNSDelegationCred
+            DependsOn = "[WindowsFeature]ADDSInstall"
+        }
+
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.DomainName
+            DomainUserCredential = $domainCred
+            RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = "[xADDomain]FirstDS"
+        }
+
+        xADUser FirstUser
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domaincred
+            UserName = "dummy"
+            Password = $NewADUserCred
+            Ensure = "Present"
+            DependsOn = "[xWaitForADDomain]DscForestWait"
+        }
+
+    }
+
+    Node $AllNodes.Where{$_.Role -eq "Child DC"}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = "Present"
+            Name = "AD-Domain-Services"
+        }
+
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.ParentDomainName
+            DomainUserCredential = $domainCred
+            RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = "[WindowsFeature]ADDSInstall"
+        }
+
+        xADDomain ChildDS
+        {
+            DomainName = $Node.DomainName
+            ParentDomainName = $Node.ParentDomainName
+            DomainAdministratorCredential = $domainCred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DependsOn = "[xWaitForADDomain]DscForestWait"
+        }
+    }
+}
+
+$ConfigData = @{
+
+    AllNodes = @(
+        @{
+            Nodename = "dsc-testNode1"
+            Role = "Parent DC"
+            DomainName = "dsc-test.contoso.com"
+            CertificateFile = "C:\publicKeys\targetNode.cer"
+            Thumbprint = "AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8"
+            RetryCount = 50
+            RetryIntervalSec = 30
+        },
+
+        @{
+            Nodename = "dsc-testNode2"
+            Role = "Child DC"
+            DomainName = "dsc-child"
+            ParentDomainName = "dsc-test.contoso.com"
+            CertificateFile = "C:\publicKeys\targetNode.cer"
+            Thumbprint = "AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8"
+            RetryCount = 50
+            RetryIntervalSec = 30
+        }
+    )
+}
+
+AssertParentChildDomains -configurationData $ConfigData `
+-safemodeAdministratorCred (Get-Credential -Message "New Domain Safe Mode Admin Credentials") `
+-domainCred (Get-Credential -Message "New Domain Admin Credentials") `
+-DNSDelegationCred (Get-Credential -Message "Credentials to Setup DNS Delegation") `
+-NewADUserCred (Get-Credential -Message "New AD User Credentials")
+
+
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName "dsc-testNode1" -Path $PSScriptRoot\AssertParentChildDomains `
+-Credential (Get-Credential -Message "Local Admin Credentials on Remote Machine")
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName "dsc-testNode2" -Path $PSScriptRoot\AssertParentChildDomains `
+-Credential (Get-Credential -Message "Local Admin Credentials on Remote Machine")
+ ```
+
+### Enable the Active Directory Recycle Bin
+
+In this example, we enable the Active Directory Recycle Bin.
+
+```powershell
+Configuration Example_xADRecycleBin
+{
+Param(
+    [parameter(Mandatory = $true)]
+    [System.String]
+    $ForestFQDN,
+
+    [parameter(Mandatory = $true)]
+    [System.Management.Automation.PSCredential]
+    $EACredential
+)
+
+    Import-DscResource -Module xActiveDirectory
+
+    Node $AllNodes.NodeName
+    {
+        xADRecycleBin RecycleBin
+        {
+           EnterpriseAdministratorCredential = $EACredential
+           ForestFQDN = $ForestFQDN
+        }
+    }
+}
+
+$ConfigurationData = @{
+    AllNodes = @(
+        @{
+            NodeName = '2012r2-dc'
+        }
+    )
+}
+
+Example_xADRecycleBin -EACredential (Get-Credential contoso\administrator) -ForestFQDN 'contoso.com' -ConfigurationData $ConfigurationData
+
+Start-DscConfiguration -Path .\Example_xADRecycleBin -Wait -Verbose
+```
+
+### Create an Active Directory group
+
+In this example, we add an Active Directory group to the default container (normally the Users OU).
+
+```powershell
+configuration Example_xADGroup
+{
+Param(
+    [parameter(Mandatory = $true)]
+    [System.String]
+    $GroupName,
+
+    [ValidateSet('DomainLocal','Global','Universal')]
+    [System.String]
+    $Scope = 'Global',
+
+    [ValidateSet('Security','Distribution')]
+    [System.String]
+    $Category = 'Security',
+
+    [ValidateNotNullOrEmpty()]
+    [System.String]
+    $Description
+)
+
+    Import-DscResource -Module xActiveDirectory
+
+    Node $AllNodes.NodeName
+    {
+        xADGroup ExampleGroup
+        {
+           GroupName = $GroupName
+           GroupScope = $Scope
+           Category = $Category
+           Description = $Description
+           Ensure = 'Present'
+        }
+    }
+}
+
+Example_xADGroup -GroupName 'TestGroup' -Scope 'DomainLocal' -Description 'Example test domain local security group' -ConfigurationData $ConfigurationData
+
+Start-DscConfiguration -Path .\Example_xADGroup -Wait -Verbose
+```
+
+### Create an Active Directory OU
+
+In this example, we add an Active Directory organizational unit to the 'example.com' domain root.
+
+```powershell
+configuration Example_xADOrganizationalUnit
+{
+Param(
+    [parameter(Mandatory = $true)]
+    [System.String]
+    $Name,
+
+    [parameter(Mandatory = $true)]
+    [System.String]
+    $Path,
+
+    [System.Boolean]
+    $ProtectedFromAccidentalDeletion = $true,
+
+    [ValidateNotNull()]
+    [System.String]
+    $Description = ''
+)
+
+    Import-DscResource -Module xActiveDirectory
+
+    Node $AllNodes.NodeName
+    {
+        xADOrganizationalUnit ExampleOU
+        {
+           Name = $Name
+           Path = $Path
+           ProtectedFromAccidentalDeletion = $ProtectedFromAccidentalDeletion
+           Description = $Description
+           Ensure = 'Present'
+        }
+    }
+}
+
+Example_xADOrganizationalUnit -Name 'Example OU' -Path 'dc=example,dc=com' -Description 'Example test organizational unit' -ConfigurationData $ConfigurationData
+
+Start-DscConfiguration -Path .\Example_xADOrganizationalUnit -Wait -Verbose
+
+```
+
+### Configure Active Directory Domain Default Password Policy
+
+In this example, we configure an Active Directory domain's default password policy to set the minimum password length and complexity.
+
+```powershell
+configuration Example_xADDomainDefaultPasswordPolicy
+{
+    Param
+    (
+        [parameter(Mandatory = $true)]
+        [System.String]
+        $DomainName,
+
+        [parameter(Mandatory = $true)]
+        [System.Boolean]
+        $ComplexityEnabled,
+
+        [parameter(Mandatory = $true)]
+        [System.Int32]
+        $MinPasswordLength,
+    )
+
+    Import-DscResource -Module xActiveDirectory
+
+    Node $AllNodes.NodeName
+    {
+        xADDomainDefaultPasswordPolicy 'DefaultPasswordPolicy'
+        {
+           DomainName = $DomainName
+           ComplexityEnabled = $ComplexityEnabled
+           MinPasswordLength = $MinPasswordLength
+        }
+    }
+}
+
+Example_xADDomainDefaultPasswordPolicy -DomainName 'contoso.com' -ComplexityEnabled $true -MinPasswordLength 8
+
+Start-DscConfiguration -Path .\Example_xADDomainDefaultPasswordPolicy -Wait -Verbose
+
+```
+
+### Create an Active Directory Computer Account
+
+In this example, we create a 'NANO-001' computer account in the 'Server' OU of the 'example.com' Active Directory domain.
+
+```powershell
+configuration Example_xADComputerAccount
+{
+    Param
+    (
+        [parameter(Mandatory = $true)]
+        [System.String]
+        $DomainController,
+
+        [parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        $DomainCredential,
+
+        [parameter(Mandatory = $true)]
+        [System.String]
+        $ComputerName,
+
+        [parameter(Mandatory = $true)]
+        [System.String]
+        $Path
+    )
+
+    Import-DscResource -Module xActiveDirectory
+
+    Node $AllNodes.NodeName
+    {
+        xADComputer "$ComputerName"
+        {
+           DomainController = $DomainController
+           DomainAdministratorCredential = $DomainCredential
+           ComputerName = $ComputerName
+           Path = $Path
+        }
+    }
+}
+
+Example_xADComputerAccount -DomainController 'DC01' `
+    -DomainCredential (Get-Credential -Message "Domain Credentials") `
+    -ComputerName 'NANO-001' `
+    -Path 'ou=Servers,dc=example,dc=com' `
+    -ConfigurationData $ConfigurationData
+
+Start-DscConfiguration -Path .\Example_xADComputerAccount -Wait -Verbose
+
+```
+
+### Create an Active Directory Computer Account and an ODJ Request File
+
+In this example, we create a 'NANO-200' computer account in the 'Nano' OU of the 'example.com' Active Directory domain as well as creating an Offline Domain Join Request file.
+
+```powershell
+configuration Example_xADComputerAccountODJ
+{
+    Param
+    (
+        [parameter(Mandatory = $true)]
+        [System.String]
+        $DomainController,
+
+        [parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        $DomainCredential,
+
+        [parameter(Mandatory = $true)]
+        [System.String]
+        $ComputerName,
+
+        [parameter(Mandatory = $true)]
+        [System.String]
+        $Path,
+
+        [parameter(Mandatory = $true)]
+        [System.String]
+        $RequestFile
+    )
+
+    Import-DscResource -Module xActiveDirectory
+
+    Node $AllNodes.NodeName
+    {
+        xADComputer "$ComputerName"
+        {
+           DomainController = $DomainController
+           DomainAdministratorCredential = $DomainCredential
+           ComputerName = $ComputerName
+           Path = $Path
+           RequestFile = $RequestFile
+        }
+    }
+}
+
+Example_xADComputerAccountODJ -DomainController 'DC01' `
+    -DomainCredential (Get-Credential -Message "Domain Credentials") `
+    -ComputerName 'NANO-200' `
+    -Path 'ou=Nano,dc=example,dc=com' `
+    -RequestFile 'd:\ODJFiles\NANO-200.txt' `
+    -ConfigurationData $ConfigurationData
+
+Start-DscConfiguration -Path .\Example_xADComputerAccount -Wait -Verbose
+
+```

+ 530 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADCommon.Tests.ps1

@@ -0,0 +1,530 @@
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
+param()
+
+$Global:DSCModuleName      = 'xActiveDirectory' # Example xNetworking
+$Global:DSCResourceName    = 'MSFT_xADCommon' # Example MSFT_xFirewall
+
+#region HEADER
+[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path))
+Write-Host $moduleRoot -ForegroundColor Green;
+if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $Global:DSCModuleName `
+    -DSCResourceName $Global:DSCResourceName `
+    -TestType Unit
+#endregion
+
+# Begin Testing
+try
+{
+
+    #region Pester Tests
+
+    # The InModuleScope command allows you to perform white-box unit testing on the internal
+    # (non-exported) code of a Script Module.
+    InModuleScope $Global:DSCResourceName {
+
+        #region Pester Test Initialization
+
+        #endregion
+
+        #region Function ResolveDomainFQDN
+        Describe "$($Global:DSCResourceName)\Resolve-DomainFQDN" {
+
+            It 'Returns "DomainName" when "ParentDomainName" not supplied' {
+                $testDomainName = 'contoso.com';
+                $testParentDomainName = $null;
+
+                $result = Resolve-DomainFQDN -DomainName $testDomainName -ParentDomainName $testParentDOmainName;
+
+                $result | Should Be $testDomainName;
+            }
+
+            It 'Returns compound "DomainName.ParentDomainName" when "ParentDomainName" supplied' {
+                $testDomainName = 'subdomain';
+                $testParentDomainName = 'contoso.com';
+
+                $result = Resolve-DomainFQDN -DomainName $testDomainName -ParentDomainName $testParentDomainName;
+
+                $result | Should Be "$testDomainName.$testParentDomainName";
+            }
+
+        }
+        #endregion
+
+        #region Function TestDomainMember
+        Describe "$($Global:DSCResourceName)\Test-DomainMember" {
+
+            It 'Returns "True" when domain member' {
+                Mock Get-CimInstance { return @{ Name = $env:COMPUTERNAME; PartOfDomain = $true; } }
+
+                Test-DomainMember | Should Be $true;
+            }
+
+            It 'Returns "False" when workgroup member' {
+                Mock Get-CimInstance { return @{ Name = $env:COMPUTERNAME; } }
+
+                Test-DomainMember | Should Be $false;
+            }
+
+        }
+        #endregion
+
+        #region Function Get-DomainName
+        Describe "$($Global:DSCResourceName)\Get-DomainName" {
+
+            It 'Returns exepected domain name' {
+                Mock Get-CimInstance { return @{ Name = $env:COMPUTERNAME; Domain = 'contoso.com'; } }
+
+                Get-DomainName | Should Be 'contoso.com';
+            }
+
+        }
+        #endregion
+
+        #region Function Assert-Module
+        Describe "$($Global:DSCResourceName)\Assert-Module" {
+
+            It 'Does not throw when module is installed' {
+                $testModuleName = 'TestModule';
+                Mock Get-Module -ParameterFilter { $Name -eq $testModuleName } { return $true; }
+
+                { Assert-Module -ModuleName $testModuleName } | Should Not Throw;
+            }
+
+            It 'Throws when module is not installed' {
+                $testModuleName = 'TestModule';
+                Mock Get-Module -ParameterFilter { $Name -eq $testModuleName } { }
+
+                { Assert-Module -ModuleName $testModuleName } | Should Throw;
+            }
+
+        }
+        #endregion
+
+        #region Function Assert-Module
+        Describe "$($Global:DSCResourceName)\Get-ADObjectParentDN" {
+
+            It "Returns CN object parent path" {
+                Get-ADObjectParentDN -DN 'CN=Administrator,CN=Users,DC=contoso,DC=com' | Should Be 'CN=Users,DC=contoso,DC=com';
+            }
+
+            It "Returns OU object parent path" {
+                Get-ADObjectParentDN -DN 'CN=Administrator,OU=Custom Organizational Unit,DC=contoso,DC=com' | Should Be 'OU=Custom Organizational Unit,DC=contoso,DC=com';
+            }
+
+        }
+        #endregion
+
+        #region Function Remove-DuplicateMembers
+        Describe "$($Global:DSCResourceName)\Remove-DuplicateMembers" {
+
+            It 'Removes one duplicate' {
+                $members = Remove-DuplicateMembers -Members 'User1','User2','USER1';
+
+                $members.Count | Should Be 2;
+                $members -contains 'User1' | Should Be $true;
+                $members -contains 'User2' | Should Be $true;
+            }
+
+            It 'Removes two duplicates' {
+                $members = Remove-DuplicateMembers -Members 'User1','User2','USER1','USER2';
+
+                $members.Count | Should Be 2;
+                $members -contains 'User1' | Should Be $true;
+                $members -contains 'User2' | Should Be $true;
+            }
+
+            It 'Removes double duplicates' {
+                $members = Remove-DuplicateMembers -Members 'User1','User2','USER1','user1';
+
+                $members.Count | Should Be 2;
+                $members -contains 'User1' | Should Be $true;
+                $members -contains 'User2' | Should Be $true;
+            }
+
+        }
+        #endregion
+
+        #region Function Test-Members
+        Describe "$($Global:DSCResourceName)\Test-Members" {
+
+            It 'Passes when nothing is passed' {
+                Test-Members -ExistingMembers $null | Should Be $true;
+            }
+
+            It 'Passes when there are existing members but members are required' {
+                $testExistingMembers = @('USER1', 'USER2');
+
+                Test-Members -ExistingMembers $testExistingMembers | Should Be $true;
+            }
+
+            It 'Passes when existing members match required members' {
+                $testExistingMembers = @('USER1', 'USER2');
+                $testMembers = @('USER2', 'USER1');
+
+                Test-Members -ExistingMembers $testExistingMembers -Members $testMembers | Should Be $true;
+            }
+
+            It 'Fails when there are no existing members and members are required' {
+                $testExistingMembers = @('USER1', 'USER2');
+                $testMembers = @('USER1', 'USER3');
+
+                Test-Members -ExistingMembers $null -Members $testMembers | Should Be $false;
+            }
+
+            It 'Fails when there are more existing members than the members required' {
+                $testExistingMembers = @('USER1', 'USER2', 'USER3');
+                $testMembers = @('USER1', 'USER3');
+
+                Test-Members -ExistingMembers $null -Members $testMembers | Should Be $false;
+            }
+
+            It 'Fails when there are more existing members than the members required' {
+                $testExistingMembers = @('USER1', 'USER2');
+                $testMembers = @('USER1', 'USER3', 'USER2');
+
+                Test-Members -ExistingMembers $null -Members $testMembers | Should Be $false;
+            }
+
+            It 'Fails when existing members do not match required members' {
+                $testExistingMembers = @('USER1', 'USER2');
+                $testMembers = @('USER1', 'USER3');
+
+                Test-Members -ExistingMembers $testExistingMembers -Members $testMembers | Should Be $false;
+            }
+
+            It 'Passes when existing members include required member' {
+                $testExistingMembers = @('USER1', 'USER2');
+                $testMembersToInclude = @('USER2');
+
+                Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should Be $true;
+            }
+
+            It 'Passes when existing members include required members' {
+                $testExistingMembers = @('USER1', 'USER2');
+                $testMembersToInclude = @('USER2', 'USER1');
+
+                Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should Be $true;
+            }
+
+            It 'Fails when existing members is missing a required member' {
+                $testExistingMembers = @('USER1');
+                $testMembersToInclude = @('USER2');
+
+                Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should Be $false;
+            }
+
+            It 'Fails when existing members is missing a required member' {
+                $testExistingMembers = @('USER1', 'USER3');
+                $testMembersToInclude = @('USER2');
+
+                Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should Be $false;
+            }
+
+            It 'Fails when existing members is missing a required members' {
+                $testExistingMembers = @('USER3');
+                $testMembersToInclude = @('USER1', 'USER2');
+
+                Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should Be $false;
+            }
+
+            It 'Passes when existing member does not include excluded member' {
+                $testExistingMembers = @('USER1');
+                $testMembersToExclude = @('USER2');
+
+                Test-Members -ExistingMembers $testExistingMembers -MembersToExclude $testMembersToInclude | Should Be $true;
+            }
+
+            It 'Passes when existing member does not include excluded members' {
+                $testExistingMembers = @('USER1');
+                $testMembersToExclude = @('USER2', 'USER3');
+
+                Test-Members -ExistingMembers $testExistingMembers -MembersToExclude $testMembersToInclude | Should Be $true;
+            }
+
+            It 'Passes when existing members does not include excluded member' {
+                $testExistingMembers = @('USER1', 'USER2');
+                $testMembersToExclude = @('USER3');
+
+                Test-Members -ExistingMembers $testExistingMembers -MembersToExclude $testMembersToInclude | Should Be $true;
+            }
+        }
+        #endregion
+
+        #region Function Assert-MemberParameters
+        Describe "$($Global:DSCResourceName)\Assert-MemberParameters" {
+
+            It "Throws if 'Members' is specified but is empty" {
+                { Assert-MemberParameters -Members @() } | Should Throw 'The Members parameter value is null';
+            }
+
+            It "Throws if 'Members' and 'MembersToInclude' are specified" {
+                { Assert-MemberParameters -Members @('User1') -MembersToInclude @('User1') } | Should Throw 'parameters conflict';
+            }
+
+            It "Throws if 'Members' and 'MembersToExclude' are specified" {
+                { Assert-MemberParameters -Members @('User1') -MembersToExclude @('User2') } | Should Throw 'parameters conflict';
+            }
+
+            It "Throws if 'MembersToInclude' and 'MembersToExclude' contain the same member" {
+                { Assert-MemberParameters -MembersToExclude @('user1') -MembersToInclude @('USER1') } | Should Throw 'member must not be included in both';
+            }
+
+            It "Throws if 'MembersToInclude' and 'MembersToExclude' are empty" {
+                { Assert-MemberParameters -MembersToExclude @() -MembersToInclude @() } | Should Throw 'At least one member must be specified';
+            }
+
+        }
+        #endregion
+
+        #region Function ConvertTo-Timespan
+        Describe "$($Global:DSCResourceName)\ConvertTo-Timespan" {
+
+            It "Returns 'System.TimeSpan' object type" {
+                $testIntTimeSpan = 60;
+
+                $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Minutes;
+
+                $result -is [System.TimeSpan] | Should Be $true;
+            }
+
+            It "Creates TimeSpan from seconds" {
+                $testIntTimeSpan = 60;
+
+                $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Seconds;
+
+                $result.TotalSeconds | Should Be $testIntTimeSpan;
+            }
+
+            It "Creates TimeSpan from minutes" {
+                $testIntTimeSpan = 60;
+
+                $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Minutes;
+
+                $result.TotalMinutes | Should Be $testIntTimeSpan;
+            }
+
+            It "Creates TimeSpan from hours" {
+                $testIntTimeSpan = 60;
+
+                $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Hours;
+
+                $result.TotalHours | Should Be $testIntTimeSpan;
+            }
+
+            It "Creates TimeSpan from days" {
+                $testIntTimeSpan = 60;
+
+                $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Days;
+
+                $result.TotalDays | Should Be $testIntTimeSpan;
+            }
+
+        }
+        #endregion
+
+        #region Function ConvertTo-Timespan
+        Describe "$($Global:DSCResourceName)\ConvertFrom-Timespan" {
+
+            It "Returns 'System.UInt32' object type" {
+                $testIntTimeSpan = 60;
+                $testTimeSpan = New-TimeSpan -Seconds $testIntTimeSpan;
+
+                $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Seconds;
+
+                $result -is [System.UInt32] | Should Be $true;
+            }
+
+            It "Converts TimeSpan to total seconds" {
+                $testIntTimeSpan = 60;
+                $testTimeSpan = New-TimeSpan -Seconds $testIntTimeSpan;
+
+                $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Seconds;
+
+                $result | Should Be $testTimeSpan.TotalSeconds;
+            }
+
+            It "Converts TimeSpan to total minutes" {
+                $testIntTimeSpan = 60;
+                $testTimeSpan = New-TimeSpan -Minutes $testIntTimeSpan;
+
+                $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Minutes;
+
+                $result | Should Be $testTimeSpan.TotalMinutes;
+            }
+
+            It "Converts TimeSpan to total hours" {
+                $testIntTimeSpan = 60;
+                $testTimeSpan = New-TimeSpan -Hours $testIntTimeSpan;
+
+                $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Hours;
+
+                $result | Should Be $testTimeSpan.TotalHours;
+            }
+
+            It "Converts TimeSpan to total days" {
+                $testIntTimeSpan = 60;
+                $testTimeSpan = New-TimeSpan -Days $testIntTimeSpan;
+
+                $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Days;
+
+                $result | Should Be $testTimeSpan.TotalDays;
+            }
+
+        }
+        #endregion
+
+        #region Function Get-ADCommonParameters
+        Describe "$($Global:DSCResourceName)\Get-ADCommonParameters" {
+
+            It "Returns 'System.Collections.Hashtable' object type" {
+                $testIdentity = 'contoso.com';
+
+                $result = Get-ADCommonParameters -Identity $testIdentity;
+
+                $result -is [System.Collections.Hashtable] | Should Be $true;
+            }
+
+            It "Returns 'Identity' key by default" {
+                $testIdentity = 'contoso.com';
+
+                $result = Get-ADCommonParameters -Identity $testIdentity;
+
+                $result['Identity'] | Should Be $testIdentity;
+            }
+
+            It "Returns 'Name' key when 'UseNameParameter' is specified" {
+                $testIdentity = 'contoso.com';
+
+                $result = Get-ADCommonParameters -Identity $testIdentity -UseNameParameter;
+
+                $result['Name'] | Should Be $testIdentity;
+            }
+
+            foreach ($identityParam in @('UserName','GroupName','ComputerName')) {
+                It "Returns 'Identity' key when '$identityParam' alias is specified" {
+                    $testIdentity = 'contoso.com';
+                    $getADCommonParameters = @{
+                        $identityParam = $testIdentity;
+                    }
+
+                    $result = Get-ADCommonParameters @getADCommonParameters;
+
+                    $result['Identity'] | Should Be $testIdentity;
+                }
+            }
+
+            It "Returns 'Identity' key by default when 'Identity' and 'CommonName' are specified" {
+                $testIdentity = 'contoso.com';
+                $testCommonName = 'Test Common Name';
+
+                $result = Get-ADCommonParameters -Identity $testIdentity -CommonName $testCommonName;
+
+                $result['Identity'] | Should Be $testIdentity;
+            }
+
+            It "Returns 'Identity' key with 'CommonName' when 'Identity', 'CommonName' and 'PreferCommonName' are specified" {
+                $testIdentity = 'contoso.com';
+                $testCommonName = 'Test Common Name';
+
+                $result = Get-ADCommonParameters -Identity $testIdentity -CommonName $testCommonName -PreferCommonName;
+
+                $result['Identity'] | Should Be $testCommonName;
+            }
+
+            It "Returns 'Identity' key with 'Identity' when 'Identity' and 'PreferCommonName' are specified" {
+                $testIdentity = 'contoso.com';
+
+                $result = Get-ADCommonParameters -Identity $testIdentity -PreferCommonName;
+
+                $result['Identity'] | Should Be $testIdentity;
+            }
+
+            it "Returns 'Name' key when 'UseNameParameter' and 'PreferCommonName' are supplied" {
+                $testIdentity = 'contoso.com';
+                $testCommonName = 'Test Common Name';
+
+                $result = Get-ADCommonParameters -Identity $testIdentity -UseNameParameter -CommonName $testCommonName -PreferCommonName;
+
+                $result['Name'] | Should Be $testCommonName;
+            }
+
+            It "Does not return 'Credential' key by default" {
+                $testIdentity = 'contoso.com';
+
+                $result = Get-ADCommonParameters -Identity $testIdentity;
+
+                $result.ContainsKey('Credential') | Should Be $false;
+            }
+
+            It "Returns 'Credential' key when specified" {
+                $testIdentity = 'contoso.com';
+                $testCredential = [System.Management.Automation.PSCredential]::Empty;
+
+                $result = Get-ADCommonParameters -Identity $testIdentity -Credential $testCredential;
+
+                $result['Credential'] | Should Be $testCredential;
+            }
+
+            It "Does not return 'Server' key by default" {
+                $testIdentity = 'contoso.com';
+
+                $result = Get-ADCommonParameters -Identity $testIdentity;
+
+                $result.ContainsKey('Server') | Should Be $false;
+            }
+
+            It "Returns 'Server' key when specified" {
+                $testIdentity = 'contoso.com';
+                $testServer = 'testserver.contoso.com';
+
+                $result = Get-ADCommonParameters -Identity $testIdentity -Server $testServer;
+
+                $result['Server'] | Should Be $testServer;
+            }
+
+            It "Converts 'DomainAdministratorCredential' parameter to 'Credential' key" {
+                $testIdentity = 'contoso.com';
+                $testCredential = [System.Management.Automation.PSCredential]::Empty;
+
+                $result = Get-ADCommonParameters -Identity $testIdentity -DomainAdministratorCredential $testCredential;
+
+                $result['Credential'] | Should Be $testCredential;
+            }
+
+            It "Converts 'DomainController' parameter to 'Server' key" {
+                $testIdentity = 'contoso.com';
+                $testServer = 'testserver.contoso.com';
+
+                $result = Get-ADCommonParameters -Identity $testIdentity -DomainController $testServer;
+
+                $result['Server'] | Should Be $testServer;
+            }
+
+            It 'Accepts remaining arguments' {
+                $testIdentity = 'contoso.com';
+
+                $result = Get-ADCommonParameters -Identity $testIdentity -UnexpectedParameter 42;
+
+                $result['Identity'] | Should Be $testIdentity;
+            }
+
+        }
+        #endregion
+
+    }
+    #endregion
+}
+finally
+{
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}

+ 532 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADComputer.Tests.ps1

@@ -0,0 +1,532 @@
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
+param()
+
+$Global:DSCModuleName      = 'xActiveDirectory' # Example xNetworking
+$Global:DSCResourceName    = 'MSFT_xADComputer' # Example MSFT_xFirewall
+
+#region HEADER
+# Unit Test Template Version: 1.1.0
+[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path))
+if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $Global:DSCModuleName `
+    -DSCResourceName $Global:DSCResourceName `
+    -TestType Unit
+#endregion HEADER
+
+
+# Begin Testing
+try
+{
+
+    #region Pester Tests
+
+    # The InModuleScope command allows you to perform white-box unit testing on the internal
+    # (non-exported) code of a Script Module.
+    InModuleScope $Global:DSCResourceName {
+
+        $testPresentParams = @{
+            ComputerName = 'TESTCOMPUTER';
+            Ensure = 'Present';
+        }
+
+        $testAbsentParams = $testPresentParams.Clone();
+        $testAbsentParams['Ensure'] = 'Absent';
+
+        $fakeADComputer = @{
+            DistinguishedName = "CN=$($testPresentParams.ComputerName),CN=Computers,DC=contoso,DC=com";
+            Enabled = $true;
+            Name = $testPresentParams.ComputerName;
+            SamAccountName = '{0}$' -f $testPresentParams.ComputerName;
+            SID = 'S-1-5-21-1409167834-891301383-2860967316-1143';
+            ObjectClass = 'computer';
+            ObjectGUID = [System.Guid]::NewGuid();
+            UserPrincipalName = 'TESTCOMPUTER@contoso.com';
+            ServicePrincipalNames = @('spn/a','spn/b');
+            Location = 'Test location';
+            DnsHostName = '{0}.contoso.com' -f $testPresentParams.ComputerName;
+            DisplayName = $testPresentParams.ComputerName;
+            Description = 'Test description';
+            ManagedBy = 'CN=Manager,CN=Users,DC=contoso,DC=com';
+        }
+
+        $testDomainController = 'TESTDC';
+        $testCredential = [System.Management.Automation.PSCredential]::Empty;
+
+        #region Function Get-TargetResource
+        Describe "$($Global:DSCResourceName)\Get-TargetResource" {
+
+            It "Returns a 'System.Collections.Hashtable' object type" {
+                Mock Get-ADComputer { return [PSCustomObject] $fakeADComputer; }
+
+                $adUser = Get-TargetResource @testPresentParams;
+
+                $adUser -is [System.Collections.Hashtable] | Should Be $true;
+            }
+
+            It "Returns 'Ensure' is 'Present' when user account exists" {
+                Mock Get-ADComputer { return [PSCustomObject] $fakeADComputer; }
+
+                $adUser = Get-TargetResource @testPresentParams;
+
+                $adUser.Ensure | Should Be 'Present';
+            }
+
+            It "Returns 'Ensure' is 'Absent' when user account does not exist" {
+                Mock Get-ADComputer { throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException }
+
+                $adUser = Get-TargetResource @testPresentParams;
+
+                $adUser.Ensure | Should Be 'Absent';
+            }
+
+            It "Calls 'Get-ADComputer' with 'Server' parameter when 'DomainController' specified" {
+                Mock Get-ADComputer -ParameterFilter { $Server -eq $testDomainController } -MockWith { return [PSCustomObject] $fakeADComputer; }
+
+                Get-TargetResource @testPresentParams -DomainController $testDomainController;
+
+                Assert-MockCalled Get-ADComputer -ParameterFilter { $Server -eq $testDomainController } -Scope It;
+            }
+
+            It "Calls 'Get-ADComputer' with 'Credential' parameter when 'DomainAdministratorCredential' specified" {
+                Mock Get-ADComputer -ParameterFilter { $Credential -eq $testCredential } -MockWith { return [PSCustomObject] $fakeADComputer; }
+
+                Get-TargetResource @testPresentParams -DomainAdministratorCredential $testCredential;
+
+                Assert-MockCalled Get-ADComputer -ParameterFilter { $Credential -eq $testCredential } -Scope It;
+            }
+
+        }
+        #endregion
+
+        #region Function Test-TargetResource
+        Describe "$($Global:DSCResourceName)\Test-TargetResource" {
+
+            $testStringProperties = @(
+                'Location',
+                'DnsHostName',
+                'UserPrincipalName',
+                'DisplayName',
+                'Path',
+                'Description',
+                'Manager'
+            );
+            $testArrayProperties = @(
+                'ServicePrincipalNames'
+            );
+            $testBooleanProperties = @(
+                'Enabled'
+            );
+
+            It "Passes when computer account does not exist and 'Ensure' is 'Absent'" {
+                Mock Get-TargetResource { return $testAbsentParams }
+
+                Test-TargetResource @testAbsentParams | Should Be $true;
+            }
+
+            It "Passes when computer account exists and 'Ensure' is 'Present'" {
+                Mock Get-TargetResource { return $testPresentParams }
+
+                Test-TargetResource @testPresentParams | Should Be $true;
+            }
+
+            It "Fails when computer account does not exist and 'Ensure' is 'Present'" {
+                Mock Get-TargetResource { return $testAbsentParams }
+
+                Test-TargetResource @testPresentParams | Should Be $false;
+            }
+
+            It "Fails when computer account exists, and 'Ensure' is 'Absent'" {
+                Mock Get-TargetResource { return $testPresentParams }
+
+                Test-TargetResource @testAbsentParams | Should Be $false;
+            }
+
+            foreach ($testParameter in $testStringProperties) {
+
+                It "Passes when computer account '$testParameter' matches AD account property" {
+                    $testParameterValue = 'Test Parameter String Value';
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADComputer[$testParameter] = $testParameterValue;
+                        return $validADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $true;
+                }
+
+                It "Fails when computer account '$testParameter' does not match incorrect AD account property value" {
+                    $testParameterValue = 'Test Parameter String Value';
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $invalidADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $invalidADComputer[$testParameter] = $testParameterValue.Substring(0, ([System.Int32] $testParameterValue.Length/2));
+                        return $invalidADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $false;
+                }
+
+                It "Fails when computer account '$testParameter' does not match empty AD account property value" {
+                    $testParameterValue = 'Test Parameter String Value';
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $invalidADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $invalidADComputer[$testParameter] = '';
+                        return $invalidADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $false;
+                }
+
+                It "Fails when computer account '$testParameter' does not match null AD account property value" {
+                    $testParameterValue = 'Test Parameter String Value';
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $invalidADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $invalidADComputer[$testParameter] = $null;
+                        return $invalidADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $false;
+                }
+
+                It "Passes when empty computer account '$testParameter' matches empty AD account property" {
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADComputer[$testParameter] = '';
+                        return $validADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $true;
+                }
+
+                It "Passes when empty computer account '$testParameter' matches null AD account property" {
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADComputer[$testParameter] = $null;
+                        return $validADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $true;
+                }
+
+            } #end foreach test string property
+
+            foreach ($testParameter in $testArrayProperties) {
+
+                It "Passes when computer account '$testParameter' matches empty AD account property" {
+                    $testParameterValue = @();
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADComputer[$testParameter] = $testParameterValue;
+                        return $validADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $true;
+                }
+
+                It "Passes when computer account '$testParameter' matches single AD account property" {
+                    $testParameterValue = @('Entry1');
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADComputer[$testParameter] = $testParameterValue;
+                        return $validADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $true;
+                }
+
+                It "Passes when computer account '$testParameter' matches multiple AD account property" {
+                    $testParameterValue = @('Entry1','Entry2');
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADComputer[$testParameter] = $testParameterValue;
+                        return $validADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $true;
+                }
+
+                It "Fails when computer account '$testParameter' does not match AD account property count" {
+                    $testParameterValue = @('Entry1','Entry2');
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADComputer[$testParameter] = @('Entry1');
+                        return $validADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $false;
+                }
+
+                It "Fails when computer account '$testParameter' does not match AD account property name" {
+                    $testParameterValue = @('Entry1');
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADComputer[$testParameter] = @('Entry2');
+                        return $validADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $false;
+                }
+
+                It "Fails when computer account '$testParameter' does not match empty AD account property" {
+                    $testParameterValue = @('Entry1');
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADComputer[$testParameter] = @();
+                        return $validADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $false;
+                }
+
+                It "Fails when empty computer account '$testParameter' does not match AD account property" {
+                    $testParameterValue = @();
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADComputer[$testParameter] = @('ExtraEntry1');
+                        return $validADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $false;
+                }
+
+            } #end foreach test string property
+
+            foreach ($testParameter in $testBooleanProperties) {
+
+                It "Passes when computer account '$testParameter' matches AD account property" {
+                    $testParameterValue = $true;
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADComputer[$testParameter] = $testParameterValue;
+                        return $validADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $true;
+                }
+
+                It "Fails when computer account '$testParameter' does not match AD account property value" {
+                    $testParameterValue = $true;
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $invalidADComputer = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $invalidADComputer[$testParameter] = -not $testParameterValue;
+                        return $invalidADComputer;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $false;
+                }
+
+            } #end foreach test boolean property
+
+        }
+        #endregion
+
+        #region Function Set-TargetResource
+        Describe "$($Global:DSCResourceName)\Set-TargetResource" {
+
+            $testStringProperties = @(
+                'Location',
+                'DnsHostName',
+                'UserPrincipalName',
+                'DisplayName',
+                'Description'
+                # Manager is translated to ManagedBy
+            );
+
+            $testArrayProperties = @(
+                'ServicePrincipalNames'
+            );
+            $testBooleanProperties = @(
+                'Enabled'
+            );
+
+            It "Calls 'New-ADComputer' when 'Ensure' is 'Present' and the account does not exist" {
+                $newComputerName = 'NEWCOMPUTER'
+                $newAbsentParams = $testAbsentParams.Clone();
+                $newAbsentParams['ComputerName'] = $newComputerName;
+                $newPresentParams = $testPresentParams.Clone();
+                $newPresentParams['ComputerName'] = $newComputerName;
+                Mock New-ADComputer -ParameterFilter { $Name -eq $newComputerName } -MockWith { }
+                Mock Set-ADComputer { }
+                Mock Get-TargetResource -ParameterFilter { $ComputerName -eq $newComputerName } -MockWith { return $newAbsentParams; }
+
+                Set-TargetResource @newPresentParams;
+
+                Assert-MockCalled New-ADComputer -ParameterFilter { $Name -eq $newComputerName } -Scope It;
+            }
+
+            It "Calls 'New-ADComputer' when 'Ensure' is 'Present' and the account does not exist, RequestFile is set, DJOIN OK" {
+                $newComputerName = 'NEWCOMPUTER'
+                $newAbsentParams = $testAbsentParams.Clone();
+                $newAbsentParams['ComputerName'] = $newComputerName;
+                $newPresentParams = $testPresentParams.Clone();
+                $newPresentParams['ComputerName'] = $newComputerName;
+                $newPresentParams['RequestFile'] = 'c:\ODJTest.txt';
+                Mock New-ADComputer -ParameterFilter { $Name -eq $newComputerName } -MockWith { }
+                Mock djoin.exe -MockWith { $LASTEXITCODE = 0; 'OK' }
+                Mock Set-ADComputer { }
+                Mock Get-TargetResource -ParameterFilter { $ComputerName -eq $newComputerName } -MockWith { return $newAbsentParams; }
+
+                Set-TargetResource @newPresentParams;
+
+                Assert-MockCalled New-ADComputer -ParameterFilter { $Name -eq $newComputerName } -Scope It -Exactly 0;
+                Assert-MockCalled djoin.exe -Exactly 1;
+            }
+
+            It "Calls 'New-ADComputer' with 'Path' when specified" {
+                $newComputerName = 'NEWCOMPUTER'
+                $newAbsentParams = $testAbsentParams.Clone();
+                $newAbsentParams['ComputerName'] = $newComputerName;
+                $newPresentParams = $testPresentParams.Clone();
+                $newPresentParams['ComputerName'] = $newComputerName;
+                $targetPath = 'OU=Test,DC=contoso,DC=com';
+                Mock New-ADComputer -ParameterFilter { $Path -eq $targetPath } -MockWith { }
+                Mock Set-ADComputer { }
+                Mock Get-TargetResource -ParameterFilter { $ComputerName -eq $newComputerName } -MockWith { return $newAbsentParams; }
+
+                Set-TargetResource @newPresentParams -Path $targetPath;
+
+                Assert-MockCalled New-ADComputer -ParameterFilter { $Path -eq $targetPath } -Scope It;
+            }
+
+            It "Calls 'Move-ADObject' when 'Ensure' is 'Present', the computer account exists but Path is incorrect" {
+                $testTargetPath = 'OU=NewPath,DC=contoso,DC=com';
+                Mock Set-ADComputer { }
+                Mock Get-ADComputer {
+                    $duffADComputer = $fakeADComputer.Clone();
+                    $duffADComputer['DistinguishedName'] = 'CN={0},OU=WrongPath,DC=contoso,DC=com' -f $testPresentParams.ComputerName;
+                    return $duffADComputer;
+                }
+                Mock Move-ADObject -ParameterFilter { $TargetPath -eq $testTargetPath } -MockWith { }
+
+                Set-TargetResource @testPresentParams -Path $testTargetPath;
+
+                Assert-MockCalled Move-ADObject -ParameterFilter { $TargetPath -eq $testTargetPath } -Scope It;
+            }
+
+            foreach ($testParameter in $testStringProperties) {
+
+                It "Calls 'Set-ADComputer' with 'Remove' when '$testParameter' is `$null" {
+                    Mock Get-ADComputer { return $fakeADComputer; }
+                    Mock Set-ADComputer -ParameterFilter { $Remove.ContainsKey($testParameter) } { }
+
+                    $setTargetResourceParams = $testPresentParams.Clone();
+                    $setTargetResourceParams[$testParameter] = '';
+                    Set-TargetResource @setTargetResourceParams;
+
+                    Assert-MockCalled Set-ADComputer -ParameterFilter { $Remove.ContainsKey($testParameter) } -Scope It -Exactly 1;
+                }
+
+                It "Calls 'Set-ADComputer' with 'Replace' when existing '$testParameter' is not `$null" {
+                    Mock Get-ADComputer { return $fakeADComputer; }
+                    Mock Set-ADComputer -ParameterFilter { $Replace.ContainsKey($testParameter) } { }
+
+                    $setTargetResourceParams = $testPresentParams.Clone();
+                    $setTargetResourceParams[$testParameter] = 'NewStringValue';
+                    Set-TargetResource @setTargetResourceParams;
+
+                    Assert-MockCalled Set-ADComputer -ParameterFilter { $Replace.ContainsKey($testParameter) } -Scope It -Exactly 1;
+                }
+
+            } #end foreach string parameter
+
+            It "Calls 'Set-ADComputer' with 'Remove' when 'Manager' is `$null" {
+                ## Manager translates to AD attribute 'managedBy'
+                Mock Get-ADComputer { return $fakeADComputer; }
+                Mock Set-ADComputer -ParameterFilter { $Remove.ContainsKey('ManagedBy') } { }
+
+                $setTargetResourceParams = $testPresentParams.Clone();
+                $setTargetResourceParams['Manager'] = '';
+                Set-TargetResource @setTargetResourceParams;
+
+                Assert-MockCalled Set-ADComputer -ParameterFilter { $Remove.ContainsKey('ManagedBy') } -Scope It -Exactly 1;
+            }
+
+            It "Calls 'Set-ADComputer' with 'Replace' when existing 'Manager' is not `$null" {
+                ## Manager translates to AD attribute 'managedBy'
+                Mock Get-ADComputer { return $fakeADComputer; }
+                Mock Set-ADComputer -ParameterFilter { $Replace.ContainsKey('ManagedBy') } { }
+
+                $setTargetResourceParams = $testPresentParams.Clone();
+                $setTargetResourceParams['Manager'] = 'NewValue';
+                Set-TargetResource @setTargetResourceParams;
+
+                Assert-MockCalled Set-ADComputer -ParameterFilter { $Replace.ContainsKey('ManagedBy') } -Scope It -Exactly 1;
+            }
+
+            It "Calls 'Set-ADComputer' with 'Enabled' = 'True' by default" {
+                Mock Get-ADComputer { return $fakeADComputer; }
+                Mock Set-ADComputer -ParameterFilter { $Enabled -eq $true } { }
+
+                $setTargetResourceParams = $testPresentParams.Clone();
+                $setTargetResourceParams[$testParameter] = -not $fakeADComputer.$testParameter;
+                Set-TargetResource @setTargetResourceParams;
+
+                Assert-MockCalled Set-ADComputer -ParameterFilter { $Enabled -eq $true } -Scope It -Exactly 1;
+            }
+
+            It "Calls 'Set-ADComputer' with 'ServicePrincipalNames' when specified" {
+                $testSPNs = @('spn/a','spn/b');
+                Mock Get-ADComputer { return $fakeADComputer; }
+                Mock Set-ADComputer -ParameterFilter { $Replace.ContainsKey('ServicePrincipalName') } { }
+
+                Set-TargetResource @testPresentParams -ServicePrincipalNames $testSPNs;
+
+                Assert-MockCalled Set-ADComputer -ParameterFilter { $Replace.ContainsKey('ServicePrincipalName') } -Scope It -Exactly 1;
+            }
+
+            It "Calls 'Remove-ADComputer' when 'Ensure' is 'Absent' and computer account exists" {
+                Mock Get-ADComputer { return $fakeADComputer; }
+                Mock Remove-ADComputer -ParameterFilter { $Identity.ToString() -eq $testAbsentParams.ComputerName } -MockWith { }
+
+                Set-TargetResource @testAbsentParams;
+
+                Assert-MockCalled Remove-ADComputer -ParameterFilter { $Identity.ToString() -eq $testAbsentParams.ComputerName } -Scope It;
+            }
+
+        }
+        #endregion
+    }
+    #endregion
+}
+finally
+{
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}

+ 406 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADDomain.Tests.ps1

@@ -0,0 +1,406 @@
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
+param()
+
+$Global:DSCModuleName      = 'xActiveDirectory' # Example xNetworking
+$Global:DSCResourceName    = 'MSFT_xADDomain' # Example MSFT_xFirewall
+
+#region HEADER
+[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path))
+Write-Host $moduleRoot -ForegroundColor Green;
+if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $Global:DSCModuleName `
+    -DSCResourceName $Global:DSCResourceName `
+    -TestType Unit
+#endregion
+
+
+# Begin Testing
+try
+{
+
+    #region Pester Tests
+
+    # The InModuleScope command allows you to perform white-box unit testing on the internal
+    # (non-exported) code of a Script Module.
+    InModuleScope $Global:DSCResourceName {
+
+        #region Pester Test Initialization
+
+        $correctDomainName = 'present.com';
+        $incorrectDomainName = 'incorrect.com';
+        $missingDomainName = 'missing.com';
+        $testAdminCredential = New-Object System.Management.Automation.PSCredential 'DummyUser', (ConvertTo-SecureString 'DummyPassword' -AsPlainText -Force);
+        $invalidCredential = New-Object System.Management.Automation.PSCredential 'Invalid', (ConvertTo-SecureString 'InvalidPassword' -AsPlainText -Force);
+
+        $testDefaultParams = @{
+            DomainAdministratorCredential = $testAdminCredential;
+            SafemodeAdministratorPassword = $testAdminCredential;
+        }
+
+        #endregion
+
+        #region Function Get-TargetResource
+        Describe "$($Global:DSCResourceName)\Get-TargetResource" {
+
+            Mock Assert-Module -ParameterFilter { $ModuleName -eq 'ADDSDeployment' } { }
+
+            It 'Calls "Assert-Module" to check "ADDSDeployment" module is installed' {
+                Mock Get-ADDomain { }
+                $result = Get-TargetResource @testDefaultParams -DomainName $correctDomainName;
+
+                Assert-MockCalled Assert-Module -ParameterFilter { $ModuleName -eq 'ADDSDeployment' } -Scope It;
+            }
+
+            It 'Returns "System.Collections.Hashtable" object type' {
+                Mock Get-ADDomain { }
+                $result = Get-TargetResource @testDefaultParams -DomainName $correctDomainName;
+
+                $result -is [System.Collections.Hashtable] | Should Be $true;
+            }
+
+            It 'Calls "Get-ADDomain" without credentials if domain member' {
+                Mock Test-DomainMember { $true; }
+                Mock Get-ADDomain -ParameterFilter { $Credential -eq $null } {  }
+
+                $result = Get-TargetResource @testDefaultParams -DomainName $correctDomainName;
+
+                Assert-MockCalled Get-ADDomain -ParameterFilter { $Credential -eq $null } -Scope It;
+            }
+
+            It 'Throws "Invalid credentials" when domain is available but authentication fails' {
+                Mock Get-ADDomain -ParameterFilter { $Identity.ToString() -eq $incorrectDomainName } -MockWith {
+                    Write-Error -Exception (New-Object System.Security.Authentication.AuthenticationException);
+                }
+                ## Match operator is case-sensitive!
+                { Get-TargetResource @testDefaultParams -DomainName $incorrectDomainName } | Should Throw 'invalid credentials';
+            }
+
+            It 'Throws "Computer is already a domain member" when is already a domain member' {
+                Mock Get-ADDomain -ParameterFilter { $Identity.ToString() -eq $incorrectDomainName } -MockWith {
+                    Write-Error -Exception (New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException);
+                }
+
+                { Get-TargetResource @testDefaultParams -DomainName $incorrectDomainName } | Should Throw 'Computer is already a domain member';
+            }
+
+            It 'Does not throw when domain cannot be located' {
+                Mock Get-ADDomain -ParameterFilter { $Identity.ToString() -eq $missingDomainName } -MockWith {
+                    Write-Error -Exception (New-Object Microsoft.ActiveDirectory.Management.ADServerDownException);
+                }
+
+                { Get-TargetResource @testDefaultParams -DomainName $missingDomainName } | Should Not Throw;
+            }
+
+        }
+        #endregion
+
+        #region Function Test-TargetResource
+        Describe "$($Global:DSCResourceName)\Test-TargetResource" {
+
+            $correctDomainName = 'present.com';
+            $correctChildDomainName = 'present';
+            $correctDomainNetBIOSName = 'PRESENT';
+            $incorrectDomainName = 'incorrect.com';
+            $parentDomainName = 'parent.com';
+            $testAdminCredential = New-Object System.Management.Automation.PSCredential 'DummyUser', (ConvertTo-SecureString 'DummyPassword' -AsPlainText -Force);
+
+            $testDefaultParams = @{
+                DomainAdministratorCredential = $testAdminCredential;
+                SafemodeAdministratorPassword = $testAdminCredential;
+            }
+
+            $stubDomain = @{
+                DomainName = $correctDomainName;
+                DomainNetBIOSName = $correctDomainNetBIOSName;
+            }
+
+            ## Get-TargetResource returns the domain FQDN for .DomainName
+            $stubChildDomain = @{
+                DomainName = "$correctChildDomainName.$parentDomainName";
+                ParentDomainName = $parentDomainName;
+                DomainNetBIOSName = $correctDomainNetBIOSName;
+            }
+
+            It 'Returns "True" when "DomainName" matches' {
+                Mock Get-TargetResource { return $stubDomain; }
+
+                $result = Test-TargetResource @testDefaultParams -DomainName $correctDomainName;
+
+                $result | Should Be $true;
+            }
+
+            It 'Returns "False" when "DomainName" does not match' {
+                Mock Get-TargetResource { return $stubDomain; }
+
+                $result = Test-TargetResource @testDefaultParams -DomainName $incorrectDomainName;
+
+                $result | Should Be $false;
+            }
+
+            It 'Returns "True" when "DomainNetBIOSName" matches' {
+                Mock Get-TargetResource { return $stubDomain; }
+
+                $result = Test-TargetResource @testDefaultParams -DomainName $correctDomainName -DomainNetBIOSName $correctDomainNetBIOSName;
+
+                $result | Should Be $true;
+            }
+
+            It 'Returns "False" when "DomainNetBIOSName" does not match' {
+                Mock Get-TargetResource { return $stubDomain; }
+
+                $result = Test-TargetResource @testDefaultParams -DomainName $correctDomainName -DomainNetBIOSName 'INCORRECT';
+
+                $result | Should Be $false;
+            }
+
+            It 'Returns "True" when "ParentDomainName" matches' {
+                Mock Get-TargetResource { return $stubChildDomain; }
+
+                $result = Test-TargetResource @testDefaultParams -DomainName $correctChildDomainName -ParentDomainName $parentDomainName;
+
+                $result | Should Be $true;
+            }
+
+            It 'Returns "False" when "ParentDomainName" does not match' {
+                Mock Get-TargetResource { return $stubChildDomain; }
+
+                $result = Test-TargetResource @testDefaultParams -DomainName $correctChildDomainName -ParentDomainName 'incorrect.com';
+
+                $result | Should Be $false;
+            }
+
+        }
+        #endregion
+
+        #region Function Set-TargetResource
+        Describe "$($Global:DSCResourceName)\Set-TargetResource" {
+
+            function Install-ADDSForest {
+                param (
+                     $DomainName, $SafeModeAdministratorPassword, $CreateDnsDelegation, $DatabasePath,
+                     $DnsDelegationCredential, $InstallDns, $LogPath, $NoRebootOnCompletion, $SysvolPath,
+                     $DomainNetbiosName
+                 )
+            }
+            function Install-ADDSDomain {
+                param (
+                    $NewDomainName, $ParentDomainName, $SafeModeAdministratorPassword, $CreateDnsDelegation,
+                    $Credential, $DatabasePath, $DnsDelegationCredential, $DomainType, $InstallDns, $LogPath,
+                    $NewDomainNetbiosName, $NoRebootOnCompletion, $SysvolPath
+                )
+            }
+
+            $testDomainName = 'present.com';
+            $testParentDomainName = 'parent.com';
+            $testDomainNetBIOSNameName = 'PRESENT';
+            $testAdminCredential = New-Object System.Management.Automation.PSCredential 'Admin', (ConvertTo-SecureString 'DummyPassword' -AsPlainText -Force);
+            $testSafemodePassword = (ConvertTo-SecureString 'DummyPassword' -AsPlainText -Force);
+            $testSafemodeCredential = New-Object System.Management.Automation.PSCredential 'Safemode', $testSafemodePassword;
+            $testDelegationCredential = New-Object System.Management.Automation.PSCredential 'Delegation', (ConvertTo-SecureString 'DummyPassword' -AsPlainText -Force);
+
+            $newForestParams = @{
+                DomainName = $testDomainName;
+                DomainAdministratorCredential = $testAdminCredential;
+                SafemodeAdministratorPassword = $testSafemodeCredential;
+            }
+
+            $newDomainParams = @{
+                DomainName = $testDomainName;
+                ParentDomainName = $testParentDomainName;
+                DomainAdministratorCredential = $testAdminCredential;
+                SafemodeAdministratorPassword = $testSafemodeCredential;
+            }
+
+            $stubTargetResource = @{
+                DomainName = $testDomainName;
+                ParentDomainName = $testParentDomainName;
+                DomainNetBIOSName = $testDomainNetBIOSNameName;
+            }
+            Mock Get-TargetResource { return $stubTargetResource; }
+
+            It 'Calls "Install-ADDSForest" with "DomainName" when creating forest' {
+                Mock Install-ADDSForest -ParameterFilter { $DomainName -eq $testDomainName } { }
+
+                Set-TargetResource @newForestParams;
+
+                Assert-MockCalled Install-ADDSForest -ParameterFilter  { $DomainName -eq $testDomainName } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSForest" with "SafemodeAdministratorPassword" when creating forest' {
+                Mock Install-ADDSForest -ParameterFilter { $SafemodeAdministratorPassword -eq $testSafemodePassword } { }
+
+                Set-TargetResource @newForestParams;
+
+                Assert-MockCalled Install-ADDSForest -ParameterFilter { $SafemodeAdministratorPassword -eq $testSafemodePassword } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSForest" with "DnsDelegationCredential" when creating forest, if specified' {
+                Mock Install-ADDSForest -ParameterFilter { $DnsDelegationCredential -eq $testDelegationCredential } { }
+
+                Set-TargetResource @newForestParams -DnsDelegationCredential $testDelegationCredential;
+
+                Assert-MockCalled Install-ADDSForest -ParameterFilter  { $DnsDelegationCredential -eq $testDelegationCredential } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSForest" with "CreateDnsDelegation" when creating forest, if specified' {
+                Mock Install-ADDSForest -ParameterFilter { $CreateDnsDelegation -eq $true } { }
+
+                Set-TargetResource @newForestParams -DnsDelegationCredential $testDelegationCredential;
+
+                Assert-MockCalled Install-ADDSForest -ParameterFilter  { $CreateDnsDelegation -eq $true } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSForest" with "DatabasePath" when creating forest, if specified' {
+                $testPath = 'TestPath';
+                Mock Install-ADDSForest -ParameterFilter { $DatabasePath -eq $testPath } { }
+
+                Set-TargetResource @newForestParams -DatabasePath $testPath;
+
+                Assert-MockCalled Install-ADDSForest -ParameterFilter { $DatabasePath -eq $testPath } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSForest" with "LogPath" when creating forest, if specified' {
+                $testPath = 'TestPath';
+                Mock Install-ADDSForest -ParameterFilter { $LogPath -eq $testPath } { }
+
+                Set-TargetResource @newForestParams -LogPath $testPath;
+
+                Assert-MockCalled Install-ADDSForest -ParameterFilter { $LogPath -eq $testPath } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSForest" with "SysvolPath" when creating forest, if specified' {
+                $testPath = 'TestPath';
+                Mock Install-ADDSForest -ParameterFilter { $SysvolPath -eq $testPath } { }
+
+                Set-TargetResource @newForestParams -SysvolPath $testPath;
+
+                Assert-MockCalled Install-ADDSForest -ParameterFilter { $SysvolPath -eq $testPath } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSForest" with "DomainNetbiosName" when creating forest, if specified' {
+                Mock Install-ADDSForest -ParameterFilter { $DomainNetbiosName -eq $testDomainNetBIOSNameName } { }
+
+                Set-TargetResource @newForestParams -DomainNetBIOSName $testDomainNetBIOSNameName;
+
+                Assert-MockCalled Install-ADDSForest -ParameterFilter { $DomainNetbiosName -eq $testDomainNetBIOSNameName } -Scope It;
+            }
+
+            #### ADDSDomain
+
+            It 'Calls "Install-ADDSDomain" with "NewDomainName" when creating child domain' {
+                Mock Install-ADDSDomain -ParameterFilter { $NewDomainName -eq $testDomainName } { }
+
+                Set-TargetResource @newDomainParams;
+
+                Assert-MockCalled Install-ADDSDomain -ParameterFilter  { $NewDomainName -eq $testDomainName } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSDomain" with "ParentDomainName" when creating child domain' {
+                Mock Install-ADDSDomain -ParameterFilter { $ParentDomainName -eq $testParentDomainName } { }
+
+                Set-TargetResource @newDomainParams;
+
+                Assert-MockCalled Install-ADDSDomain -ParameterFilter  { $ParentDomainName -eq $testParentDomainName } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSDomain" with "DomainType" when creating child domain' {
+                Mock Install-ADDSDomain -ParameterFilter { $DomainType -eq 'ChildDomain' } { }
+
+                Set-TargetResource @newDomainParams;
+
+                Assert-MockCalled Install-ADDSDomain -ParameterFilter  { $DomainType -eq 'ChildDomain' } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSDomain" with "SafemodeAdministratorPassword" when creating child domain' {
+                Mock Install-ADDSDomain -ParameterFilter { $SafemodeAdministratorPassword -eq $testSafemodePassword } { }
+
+                Set-TargetResource @newDomainParams;
+
+                Assert-MockCalled Install-ADDSDomain -ParameterFilter { $SafemodeAdministratorPassword -eq $testSafemodePassword } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSDomain" with "Credential" when creating child domain' {
+                Mock Install-ADDSDomain -ParameterFilter { $Credential -eq $testParentDomainName } { }
+
+                Set-TargetResource @newDomainParams;
+
+                Assert-MockCalled Install-ADDSDomain -ParameterFilter  { $ParentDomainName -eq $testParentDomainName } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSDomain" with "ParentDomainName" when creating child domain' {
+                Mock Install-ADDSDomain -ParameterFilter { $ParentDomainName -eq $testParentDomainName } { }
+
+                Set-TargetResource @newDomainParams;
+
+                Assert-MockCalled Install-ADDSDomain -ParameterFilter  { $ParentDomainName -eq $testParentDomainName } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSDomain" with "DnsDelegationCredential" when creating child domain, if specified' {
+                Mock Install-ADDSDomain -ParameterFilter { $DnsDelegationCredential -eq $testDelegationCredential } { }
+
+                Set-TargetResource @newDomainParams -DnsDelegationCredential $testDelegationCredential;
+
+                Assert-MockCalled Install-ADDSDomain -ParameterFilter  { $DnsDelegationCredential -eq $testDelegationCredential } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSDomain" with "CreateDnsDelegation" when creating child domain, if specified' {
+                Mock Install-ADDSDomain -ParameterFilter { $CreateDnsDelegation -eq $true } { }
+
+                Set-TargetResource @newDomainParams -DnsDelegationCredential $testDelegationCredential;
+
+                Assert-MockCalled Install-ADDSDomain -ParameterFilter  { $CreateDnsDelegation -eq $true } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSDomain" with "DatabasePath" when creating child domain, if specified' {
+                $testPath = 'TestPath';
+                Mock Install-ADDSDomain -ParameterFilter { $DatabasePath -eq $testPath } { }
+
+                Set-TargetResource @newDomainParams -DatabasePath $testPath;
+
+                Assert-MockCalled Install-ADDSDomain -ParameterFilter { $DatabasePath -eq $testPath } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSDomain" with "LogPath" when creating child domain, if specified' {
+                $testPath = 'TestPath';
+                Mock Install-ADDSDomain -ParameterFilter { $LogPath -eq $testPath } { }
+
+                Set-TargetResource @newDomainParams -LogPath $testPath;
+
+                Assert-MockCalled Install-ADDSDomain -ParameterFilter { $LogPath -eq $testPath } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSDomain" with "SysvolPath" when creating child domain, if specified' {
+                $testPath = 'TestPath';
+                Mock Install-ADDSDomain -ParameterFilter { $SysvolPath -eq $testPath } { }
+
+                Set-TargetResource @newDomainParams -SysvolPath $testPath;
+
+                Assert-MockCalled Install-ADDSDomain -ParameterFilter { $SysvolPath -eq $testPath } -Scope It;
+            }
+
+            It 'Calls "Install-ADDSDomain" with "NewDomainNetbiosName" when creating child domain, if specified' {
+                Mock Install-ADDSDomain -ParameterFilter { $NewDomainNetbiosName -eq $testDomainNetBIOSNameName } { }
+
+                Set-TargetResource @newDomainParams -DomainNetBIOSName $testDomainNetBIOSNameName;
+
+                Assert-MockCalled Install-ADDSDomain -ParameterFilter { $NewDomainNetbiosName -eq $testDomainNetBIOSNameName } -Scope It;
+            }
+        }
+        #endregion
+
+    }
+    #endregion
+}
+finally
+{
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}

+ 245 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADDomainController.Tests.ps1

@@ -0,0 +1,245 @@
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
+param()
+
+$Script:DSCModuleName      = 'xActiveDirectory'
+$Script:DSCResourceName    = 'MSFT_xADDomainController'
+
+#region HEADER
+[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
+if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $Script:DSCModuleName `
+    -DSCResourceName $Script:DSCResourceName `
+    -TestType Unit
+#endregion
+
+# Begin Testing
+try
+{
+    #region Pester Test Initialization
+    $correctDomainName   = 'present.com'
+    $testAdminCredential = [System.Management.Automation.PSCredential]::Empty
+    $correctDatabasePath = 'C:\Windows\NTDS'
+    $correctLogPath      = 'C:\Windows\NTDS'
+    $correctSysvolPath   = 'C:\Windows\SYSVOL'
+    $correctSiteName     = 'PresentSite'
+    $incorrectSiteName   = 'IncorrectSite'
+
+    $testDefaultParams = @{
+        DomainAdministratorCredential = $testAdminCredential
+        SafemodeAdministratorPassword = $testAdminCredential
+    }
+
+    $commonMockParams = @{
+        ModuleName = $Script:DSCResourceName
+    }
+
+    $commonAssertParams = @{
+        ModuleName = $Script:DSCResourceName
+        Scope = 'It'
+        Exactly = $true
+    }
+
+    #Fake function because it is only available on Windows Server
+    function Install-ADDSDomainController {
+        param(
+            $DomainName, $SafeModeAdministratorPassword, $Credential, $NoRebootOnCompletion, $Force, $DatabasePath,
+            $LogPath, $SysvolPath, $SiteName
+        )
+
+        throw [exception] 'Not Implemented'
+    }
+    #endregion Pester Test Initialization
+
+    #region Function Get-TargetResource
+    Describe -Tag 'xADDomainController' "$($Script:DSCResourceName)\Get-TargetResource" {
+
+        Mock Get-ADDomain { return $true } @commonMockParams
+        Mock Get-ADDomainController {
+            return $stubDomainController = @{
+                Site = 'PresentSite'
+                Domain = 'present.com'
+            }
+        } @commonMockParams
+        Mock Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters' } {
+            return @{
+                'Database log files path' = 'C:\Windows\NTDS'
+                'DSA Working Directory'   = 'C:\Windows\NTDS'
+            }
+        } @commonMockParams
+        Mock Get-ItemProperty -ParameterFilter { $Path -eq 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' } {
+            return @{
+                'SysVol' = 'C:\Windows\SYSVOL\sysvol'
+            }
+        } @commonMockParams
+
+        It 'Returns current "DatabasePath"' {
+            $result = Get-TargetResource @testDefaultParams -DomainName $correctDomainName
+            $result.DatabasePath | Should Be $correctDatabasePath
+        }
+
+        It 'Returns current "LogPath"' {
+            $result = Get-TargetResource @testDefaultParams -DomainName $correctDomainName
+            $result.LogPath | Should Be $correctLogPath
+        }
+
+        It 'Returns current "SysvolPath"' {
+            $result = Get-TargetResource @testDefaultParams -DomainName $correctDomainName
+            $result.SysvolPath | Should Be $correctSysvolPath
+        }
+
+        It 'Returns current "SiteName"' {
+            $result = Get-TargetResource @testDefaultParams -DomainName $correctDomainName
+            $result.SiteName | Should Be $correctSiteName
+        }
+    }
+    #endregion
+
+    #region Function Test-TargetResource
+    Describe -Tag 'xADDomainController' "$($Script:DSCResourceName)\Test-TargetResource" {
+        InModuleScope $Script:DSCResourceName {
+            $correctSiteName = 'PresentSite'
+            $incorrectSiteName = 'IncorrectSite'
+            $correctDomainName = 'present.com'
+            $testAdminCredential = [System.Management.Automation.PSCredential]::Empty
+
+            $testDefaultParams = @{
+                DomainAdministratorCredential = $testAdminCredential
+                SafemodeAdministratorPassword = $testAdminCredential
+            }
+
+            It 'Returns "False" when "SiteName" does not match' {
+                $stubDomain = @{
+                    DNSRoot = $correctDomainName
+                }
+
+                $stubDomainController = @{
+                    Site = $incorrectSiteName
+                    Domain = $correctDomainName
+                }
+
+                Mock Get-ADDomain { return $true }
+                Mock Get-ADDomainController { return $stubDomainController }
+                Mock Test-ADReplicationSite { return $true }
+                Mock Get-ItemProperty { return @{} }
+
+                $result = Test-TargetResource @testDefaultParams -DomainName $correctDomainName -SiteName $correctSiteName
+
+                $result | Should Be $false
+            }
+
+            It 'Returns "True" when "SiteName" matches' {
+
+                $stubDomainController = @{
+                    Site = $correctSiteName
+                    Domain = $correctDomainName
+                }
+
+                Mock Get-ADDomain { return $true }
+                Mock Get-ADDomainController { return $stubDomainController }
+                Mock Test-ADReplicationSite { return $true }
+                Mock Get-ItemProperty { return @{} }
+
+                $result = Test-TargetResource @testDefaultParams -DomainName $correctDomainName -SiteName $correctSiteName
+
+                $result | Should Be $true
+            }
+
+            It 'Throws if "SiteName" is wrong' {
+
+                $stubDomainController = @{
+                    Site = $correctSiteName
+                    Domain = $correctDomainName
+                }
+
+                Mock Get-ADDomain { return $true }
+                Mock Get-ADDomainController { return $stubDomainController }
+                Mock Test-ADReplicationSite { return $false }
+                { Test-TargetResource @testDefaultParams -DomainName $correctDomainName -SiteName $incorrectSiteName } |
+                    Should Throw "Site '$($incorrectSiteName)' could not be found."
+            }
+        }
+    }
+    #endregion
+
+    #region Function Set-TargetResource
+    Describe -Tag 'xADDomainController' "$($Script:DSCResourceName)\Set-TargetResource" {
+        It 'Calls "Install-ADDSDomainController" with "Site", if specified' {
+            Mock Get-ADDomain {
+                return $true
+            } @commonMockParams
+
+            Mock Get-TargetResource {
+                return $stubTargetResource = @{
+                    Ensure = $false
+                }
+            } @commonMockParams
+            Mock Install-ADDSDomainController -MockWith {} -ParameterFilter { $SiteName -eq $correctSiteName } @commonMockParams
+
+            Set-TargetResource @testDefaultParams -DomainName $correctDomainName -SiteName $correctSiteName
+
+            Assert-MockCalled Install-ADDSDomainController -Times 1 -ParameterFilter { $SiteName -eq $correctSiteName } @commonAssertParams
+        }
+
+        It 'Calls "Move-ADDirectoryServer" when "SiteName" does not match' {
+            Mock Get-TargetResource {
+                return $stubTargetResource = @{
+                    Ensure = $true
+                    SiteName = 'IncorrectSite'
+                }
+            } @commonMockParams
+
+            Mock Move-ADDirectoryServer -MockWith {} -ParameterFilter { $Site.ToString() -eq $correctSiteName } @commonMockParams
+            Mock Move-ADDirectoryServer -MockWith {} @commonMockParams
+
+            Set-TargetResource @testDefaultParams -DomainName $correctDomainName -SiteName $correctSiteName
+
+            Assert-MockCalled Move-ADDirectoryServer -Times 1 -ParameterFilter { $Site.ToString() -eq $correctSiteName } @commonAssertParams
+        }
+
+        It 'Does not call "Move-ADDirectoryServer" when "SiteName" matches' {
+            Mock Get-TargetResource {
+                return $stubTargetResource = @{
+                    Ensure = $true
+                    SiteName = 'PresentSite'
+                }
+            } @commonMockParams
+
+            Mock Move-ADDirectoryServer {} @commonMockParams
+
+            Set-TargetResource @testDefaultParams -DomainName $correctDomainName -SiteName $correctSiteName
+
+            Assert-MockCalled Move-ADDirectoryServer -Times 0 @commonAssertParams
+        }
+
+        It 'Does not call "Move-ADDirectoryServer" when "SiteName" is not specified' {
+            Mock Get-TargetResource {
+                return $stubTargetResource = @{
+                    Ensure = $true
+                    SiteName = 'PresentSite'
+                }
+            } @commonMockParams
+
+            Mock Move-ADDirectoryServer {} @commonMockParams
+
+            Set-TargetResource @testDefaultParams -DomainName $correctDomainName
+
+            Assert-MockCalled Move-ADDirectoryServer -Times 0 @commonAssertParams
+        }
+    }
+    #endregion
+}
+finally
+{
+    #region FOOTER
+
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+
+    #endregion
+}

+ 289 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADDomainDefaultPasswordPolicy.Tests.ps1

@@ -0,0 +1,289 @@
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
+param()
+
+$Global:DSCModuleName      = 'xActiveDirectory' # Example xNetworking
+$Global:DSCResourceName    = 'MSFT_xADDomainDefaultPasswordPolicy' # Example MSFT_xFirewall
+
+#region HEADER
+[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path))
+Write-Host $moduleRoot -ForegroundColor Green;
+if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $Global:DSCModuleName `
+    -DSCResourceName $Global:DSCResourceName `
+    -TestType Unit
+#endregion
+
+
+# Begin Testing
+try
+{
+
+    #region Pester Tests
+
+    # The InModuleScope command allows you to perform white-box unit testing on the internal
+    # (non-exported) code of a Script Module.
+    InModuleScope $Global:DSCResourceName {
+
+        #region Pester Test Initialization
+
+        $testDomainName = 'contoso.com';
+        $testDefaultParams = @{
+            DomainName = $testDomainName;
+        }
+        $testDomainController = 'testserver.contoso.com';
+        $testPassword = (ConvertTo-SecureString 'DummyPassword' -AsPlainText -Force);
+        $testCredential = New-Object System.Management.Automation.PSCredential 'Safemode', $testPassword;
+
+        $fakePasswordPolicy = @{
+            ComplexityEnabled = $true;
+            LockoutDuration = New-TimeSpan -Minutes 30;
+            LockoutObservationWindow = New-TimeSpan -Minutes 30;
+            LockoutThreshold = 3;
+            MinPasswordAge = New-TimeSpan -Days 1;
+            MaxPasswordAge = New-TimeSpan -Days 42;
+            MinPasswordLength = 7;
+            PasswordHistoryCount = 12;
+            ReversibleEncryptionEnabled = $false;
+        }
+
+        #endregion
+
+        #region Function Get-TargetResource
+        Describe "$($Global:DSCResourceName)\Get-TargetResource" {
+
+            Mock Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } { }
+
+            It 'Calls "Assert-Module" to check "ActiveDirectory" module is installed' {
+                Mock Get-ADDefaultDomainPasswordPolicy { return $fakePasswordPolicy; }
+
+                $result = Get-TargetResource @testDefaultParams;
+
+                Assert-MockCalled Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } -Scope It;
+            }
+
+            It 'Returns "System.Collections.Hashtable" object type' {
+                Mock Get-ADDefaultDomainPasswordPolicy { return $fakePasswordPolicy; }
+
+                $result = Get-TargetResource @testDefaultParams;
+
+                $result -is [System.Collections.Hashtable] | Should Be $true;
+            }
+
+            It 'Calls "Get-ADDefaultDomainPasswordPolicy" without credentials by default' {
+                Mock Get-ADDefaultDomainPasswordPolicy -ParameterFilter { $Credential -eq $null } -MockWith { return $fakePasswordPolicy; }
+
+                $result = Get-TargetResource @testDefaultParams;
+
+                Assert-MockCalled Get-ADDefaultDomainPasswordPolicy -ParameterFilter { $Credential -eq $null } -Scope It;
+            }
+
+            It 'Calls "Get-ADDefaultDomainPasswordPolicy" with credentials when specified' {
+                Mock Get-ADDefaultDomainPasswordPolicy -ParameterFilter { $Credential -eq $testCredential } -MockWith { return $fakePasswordPolicy; }
+
+                $result = Get-TargetResource @testDefaultParams -Credential $testCredential;
+
+                Assert-MockCalled Get-ADDefaultDomainPasswordPolicy -ParameterFilter { $Credential -eq $testCredential } -Scope It;
+            }
+
+            It 'Calls "Get-ADDefaultDomainPasswordPolicy" without server by default' {
+                Mock Get-ADDefaultDomainPasswordPolicy -ParameterFilter { $Server -eq $null } -MockWith { return $fakePasswordPolicy; }
+
+                $result = Get-TargetResource @testDefaultParams;
+
+                Assert-MockCalled Get-ADDefaultDomainPasswordPolicy -ParameterFilter { $Server -eq $null } -Scope It;
+            }
+
+            It 'Calls "Get-ADDefaultDomainPasswordPolicy" with server when specified' {
+                Mock Get-ADDefaultDomainPasswordPolicy -ParameterFilter { $Server -eq $testDomainController } -MockWith { return $fakePasswordPolicy; }
+
+                $result = Get-TargetResource @testDefaultParams -DomainController $testDomainController;
+
+                Assert-MockCalled Get-ADDefaultDomainPasswordPolicy -ParameterFilter { $Server -eq $testDomainController } -Scope It;
+            }
+
+        }
+        #endregion
+
+        #region Function Test-TargetResource
+        Describe "$($Global:DSCResourceName)\Test-TargetResource" {
+
+            $testDomainName = 'contoso.com';
+            $testDefaultParams = @{
+                DomainName = $testDomainName;
+            }
+            $testDomainController = 'testserver.contoso.com';
+            $testPassword = (ConvertTo-SecureString 'DummyPassword' -AsPlainText -Force);
+            $testCredential = New-Object System.Management.Automation.PSCredential 'Safemode', $testPassword;
+
+            $stubPasswordPolicy = @{
+                ComplexityEnabled = $true;
+                LockoutDuration = (New-TimeSpan -Minutes 30).TotalMinutes;
+                LockoutObservationWindow = (New-TimeSpan -Minutes 30).TotalMinutes;
+                LockoutThreshold = 3;
+                MinPasswordAge = (New-TimeSpan -Days 1).TotalMinutes;
+                MaxPasswordAge = (New-TimeSpan -Days 42).TotalMinutes;
+                MinPasswordLength = 7;
+                PasswordHistoryCount = 12;
+                ReversibleEncryptionEnabled = $true;
+            }
+
+            It 'Returns "System.Boolean" object type' {
+                Mock Get-TargetResource { return $stubPasswordPolicy; }
+
+                $result = Test-TargetResource @testDefaultParams;
+
+                $result -is [System.Boolean] | Should Be $true;
+            }
+
+            It 'Calls "Get-TargetResource" with "Credential" parameter when specified' {
+                Mock Get-TargetResource -ParameterFilter { $Credential -eq $testCredential } { return $stubPasswordPolicy; }
+
+                $result = Test-TargetResource @testDefaultParams -Credential $testCredential;
+
+                Assert-MockCalled Get-TargetResource -ParameterFilter { $Credential -eq $testCredential } -Scope It;
+            }
+
+            It 'Calls "Get-TargetResource" with "DomainController" parameter when specified' {
+                Mock Get-TargetResource -ParameterFilter { $DomainController -eq $testDomainController } { return $stubPasswordPolicy; }
+
+                $result = Test-TargetResource @testDefaultParams -DomainController $testDomainController;
+
+                Assert-MockCalled Get-TargetResource -ParameterFilter { $DomainController -eq $testDomainController } -Scope It;
+            }
+
+            foreach ($propertyName in $stubPasswordPolicy.Keys)
+            {
+                It "Passes when '$propertyName' parameter matches resource property value" {
+                    Mock Get-TargetResource { return $stubPasswordPolicy; }
+                    $propertyDefaultParams = $testDefaultParams.Clone();
+                    $propertyDefaultParams[$propertyName] = $stubPasswordPolicy[$propertyName];
+
+                    $result = Test-TargetResource @propertyDefaultParams;
+
+                    $result | Should Be $true;
+                }
+
+                It "Fails when '$propertyName' parameter does not match resource property value" {
+                    Mock Get-TargetResource { return $stubPasswordPolicy; }
+                    $propertyDefaultParams = $testDefaultParams.Clone();
+
+                    switch ($stubPasswordPolicy[$propertyName].GetType())
+                    {
+                        'bool' {
+                            $propertyDefaultParams[$propertyName] = -not $stubPasswordPolicy[$propertyName];
+                        }
+                        'string' {
+                            $propertyDefaultParams[$propertyName] = 'not{0}' -f $stubPasswordPolicy[$propertyName];
+                        }
+                        default {
+                            $propertyDefaultParams[$propertyName] = $stubPasswordPolicy[$propertyName] + 1;
+                        }
+                    }
+
+                    $result = Test-TargetResource @propertyDefaultParams;
+
+                    $result | Should Be $false;
+                }
+            } #end foreach property
+
+        }
+        #endregion
+
+        #region Function Set-TargetResource
+        Describe "$($Global:DSCResourceName)\Set-TargetResource" {
+
+            $testDomainName = 'contoso.com';
+            $testDefaultParams = @{
+                DomainName = $testDomainName;
+            }
+            $testDomainController = 'testserver.contoso.com';
+            $testPassword = (ConvertTo-SecureString 'DummyPassword' -AsPlainText -Force);
+            $testCredential = New-Object System.Management.Automation.PSCredential 'Safemode', $testPassword;
+
+            $stubPasswordPolicy = @{
+                ComplexityEnabled = $true;
+                LockoutDuration = (New-TimeSpan -Minutes 30).TotalMinutes;
+                LockoutObservationWindow = (New-TimeSpan -Minutes 30).TotalMinutes;
+                LockoutThreshold = 3;
+                MinPasswordAge = (New-TimeSpan -Days 1).TotalMinutes;
+                MaxPasswordAge = (New-TimeSpan -Days 42).TotalMinutes;
+                MinPasswordLength = 7;
+                PasswordHistoryCount = 12;
+                ReversibleEncryptionEnabled = $true;
+            }
+
+            Mock Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } { }
+
+            It 'Calls "Assert-Module" to check "ActiveDirectory" module is installed' {
+                Mock Set-ADDefaultDomainPasswordPolicy { }
+
+                $result = Set-TargetResource @testDefaultParams;
+
+                Assert-MockCalled Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } -Scope It;
+            }
+
+            It 'Calls "Set-ADDefaultDomainPasswordPolicy" without "Credential" parameter by default' {
+                Mock Set-ADDefaultDomainPasswordPolicy -ParameterFilter { $Credential -eq $null } -MockWith { }
+
+                $result = Set-TargetResource @testDefaultParams;
+
+                Assert-MockCalled Set-ADDefaultDomainPasswordPolicy -ParameterFilter { $Credential -eq $null } -Scope It;
+            }
+
+            It 'Calls "Set-ADDefaultDomainPasswordPolicy" with "Credential" parameter when specified' {
+                Mock Set-ADDefaultDomainPasswordPolicy -ParameterFilter { $Credential -eq $testCredential } -MockWith { }
+
+                $result = Set-TargetResource @testDefaultParams -Credential $testCredential;
+
+                Assert-MockCalled Set-ADDefaultDomainPasswordPolicy -ParameterFilter { $Credential -eq $testCredential } -Scope It;
+            }
+
+            It 'Calls "Set-ADDefaultDomainPasswordPolicy" without "Server" parameter by default' {
+                Mock Set-ADDefaultDomainPasswordPolicy -ParameterFilter { $Server -eq $null } -MockWith { }
+
+                $result = Set-TargetResource @testDefaultParams;
+
+                Assert-MockCalled Set-ADDefaultDomainPasswordPolicy -ParameterFilter { $Server -eq $null } -Scope It;
+            }
+
+            It 'Calls "Set-ADDefaultDomainPasswordPolicy" with "Server" parameter when specified' {
+                Mock Set-ADDefaultDomainPasswordPolicy -ParameterFilter { $Server -eq $testDomainController } -MockWith { }
+
+                $result = Set-TargetResource @testDefaultParams -DomainController $testDomainController;
+
+                Assert-MockCalled Set-ADDefaultDomainPasswordPolicy -ParameterFilter { $Server -eq $testDomainController } -Scope It;
+            }
+
+            foreach ($propertyName in $stubPasswordPolicy.Keys)
+            {
+                It "Calls 'Set-ADDefaultDomainPasswordPolicy' with '$propertyName' parameter when specified" {
+                    $propertyDefaultParams = $testDefaultParams.Clone();
+                    $propertyDefaultParams[$propertyName] = $stubPasswordPolicy[$propertyName];
+                    Mock Set-ADDefaultDomainPasswordPolicy -ParameterFilter { $PSBoundParameters.ContainsKey($propertyName) } -MockWith { }
+
+                    $result = Set-TargetResource @propertyDefaultParams;
+
+                    Assert-MockCalled Set-ADDefaultDomainPasswordPolicy -ParameterFilter { $PSBoundParameters.ContainsKey($propertyName) } -Scope It;
+                }
+
+            } #end foreach property name
+
+        }
+        #endregion
+
+    }
+    #endregion
+}
+finally
+{
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}

+ 539 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADGroup.Tests.ps1

@@ -0,0 +1,539 @@
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
+param()
+
+$Global:DSCModuleName      = 'xActiveDirectory' # Example xNetworking
+$Global:DSCResourceName    = 'MSFT_xADGroup' # Example MSFT_xFirewall
+
+#region HEADER
+[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path))
+Write-Host $moduleRoot -ForegroundColor Green;
+if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $Global:DSCModuleName `
+    -DSCResourceName $Global:DSCResourceName `
+    -TestType Unit
+#endregion
+
+# Begin Testing
+try
+{
+
+    #region Pester Tests
+
+    # The InModuleScope command allows you to perform white-box unit testing on the internal
+    # (non-exported) code of a Script Module.
+    InModuleScope $Global:DSCResourceName {
+
+        #region Pester Test Initialization
+        $testPresentParams = @{
+            GroupName = 'TestGroup'
+            GroupScope = 'Global';
+            Category = 'Security';
+            Path = 'OU=Fake,DC=contoso,DC=com';
+            Description = 'Test AD group description';
+            DisplayName = 'Test display name';
+            Ensure = 'Present';
+            Notes = 'This is a test AD group';
+            ManagedBy = 'CN=User 1,CN=Users,DC=contoso,DC=com';
+        }
+
+        $testAbsentParams = $testPresentParams.Clone();
+        $testAbsentParams['Ensure'] = 'Absent';
+
+        $fakeADGroup = @{
+            Name = $testPresentParams.GroupName;
+            Identity = $testPresentParams.GroupName;
+            GroupScope = $testPresentParams.GroupScope;
+            GroupCategory = $testPresentParams.Category;
+            DistinguishedName = "CN=$($testPresentParams.GroupName),$($testPresentParams.Path)";
+            Description = $testPresentParams.Description;
+            DisplayName = $testPresentParams.DisplayName;
+            ManagedBy = $testPresentParams.ManagedBy;
+            Info = $testPresentParams.Notes;
+        }
+
+        $fakeADUser1 = [PSCustomObject] @{
+            DistinguishedName = 'CN=User 1,CN=Users,DC=contoso,DC=com';
+            ObjectGUID = 'a97cc867-0c9e-4928-8387-0dba0c883b8e';
+            SamAccountName = 'USER1';
+            SID = 'S-1-5-21-1131554080-2861379300-292325817-1106'
+        }
+        $fakeADUser2 = [PSCustomObject] @{
+            DistinguishedName = 'CN=User 2,CN=Users,DC=contoso,DC=com';
+            ObjectGUID = 'a97cc867-0c9e-4928-8387-0dba0c883b8f';
+            SamAccountName = 'USER2';
+            SID = 'S-1-5-21-1131554080-2861379300-292325817-1107'
+        }
+        $fakeADUser3 = [PSCustomObject] @{
+            DistinguishedName = 'CN=User 3,CN=Users,DC=contoso,DC=com';
+            ObjectGUID = 'a97cc867-0c9e-4928-8387-0dba0c883b90';
+            SamAccountName = 'USER3';
+            SID = 'S-1-5-21-1131554080-2861379300-292325817-1108'
+        }
+
+        $testDomainController = 'TESTDC';
+        $testCredentials = New-Object System.Management.Automation.PSCredential 'DummyUser', (ConvertTo-SecureString 'DummyPassword' -AsPlainText -Force);
+
+        #region Function Get-TargetResource
+        Describe "$($Global:DSCResourceName)\Get-TargetResource" {
+
+            Mock Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } { }
+
+            It 'Calls "Assert-Module" to check AD module is installed' {
+                Mock Get-ADGroup { return $fakeADGroup; }
+                Mock Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); }
+
+                $result = Get-TargetResource @testPresentParams; # -DomainName $correctDomainName;
+
+                Assert-MockCalled Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } -Scope It;
+            }
+
+            It "Returns 'Ensure' is 'Present' when group exists" {
+                Mock Get-ADGroup { return $fakeADGroup; }
+                Mock Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); }
+
+                (Get-TargetResource @testPresentParams).Ensure | Should Be 'Present';
+            }
+
+            It "Returns 'Ensure' is 'Absent' when group does not exist" {
+                Mock Get-ADGroup { throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException }
+
+                (Get-TargetResource @testPresentParams).Ensure | Should Be 'Absent';
+            }
+
+
+            It "Calls 'Get-ADGroup' with 'Server' parameter when 'DomainController' specified" {
+                Mock Get-ADGroup -ParameterFilter { $Server -eq $testDomainController } -MockWith { return $fakeADGroup; }
+                Mock Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); }
+
+                Get-TargetResource @testPresentParams -DomainController $testDomainController;
+
+                Assert-MockCalled Get-ADGroup -ParameterFilter { $Server -eq $testDomainController } -Scope It;
+            }
+
+            It "Calls 'Get-ADGroup' with 'Credential' parameter when specified" {
+                Mock Get-ADGroup -ParameterFilter { $Credential -eq $testCredentials } -MockWith { return $fakeADGroup; }
+                Mock Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); }
+
+                Get-TargetResource @testPresentParams -Credential $testCredentials;
+
+                Assert-MockCalled Get-ADGroup -ParameterFilter { $Credential -eq $testCredentials } -Scope It;
+            }
+
+            It "Calls 'Get-ADGroupMember' with 'Server' parameter when 'DomainController' specified" {
+                Mock Get-ADGroup  -MockWith { return $fakeADGroup; }
+                Mock Get-ADGroupMember -ParameterFilter { $Server -eq $testDomainController } -MockWith { return @($fakeADUser1, $fakeADUser2); }
+
+                Get-TargetResource @testPresentParams -DomainController $testDomainController;
+
+                Assert-MockCalled Get-ADGroupMember -ParameterFilter { $Server -eq $testDomainController } -Scope It;
+            }
+
+            It "Calls 'Get-ADGroupMember' with 'Credential' parameter when specified" {
+                Mock Get-ADGroup -MockWith { return $fakeADGroup; }
+                Mock Get-ADGroupMember -ParameterFilter { $Credential -eq $testCredentials } -MockWith { return @($fakeADUser1, $fakeADUser2); }
+
+                Get-TargetResource @testPresentParams -Credential $testCredentials;
+
+                Assert-MockCalled Get-ADGroupMember -ParameterFilter { $Credential -eq $testCredentials } -Scope It;
+            }
+
+        }
+        #end region
+
+        #region Function Test-TargetResource
+        Describe "$($Global:DSCResourceName)\Test-TargetResource" {
+
+            Mock Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } { }
+
+            foreach ($attribute in @('SamAccountName','DistinguishedName','ObjectGUID','SID')) {
+
+                It "Passes when group 'Members' match using '$attribute'" {
+                    Mock Get-ADGroup { return $fakeADGroup; }
+                    Mock Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); }
+
+                    $targetResource = Test-TargetResource @testPresentParams -Members $fakeADUser1.$attribute, $fakeADUser2.$attribute -MembershipAttribute $attribute;
+
+                    $targetResource | Should Be $true;
+                }
+
+                It "Fails when group membership counts do not match using '$attribute'" {
+                    Mock Get-ADGroup { return $fakeADGroup; }
+                    Mock Get-ADGroupMember { return @($fakeADUser1); }
+
+                    $targetResource = Test-TargetResource @testPresentParams -Members $fakeADUser2.$attribute, $fakeADUser3.$attribute -MembershipAttribute $attribute;
+
+                    $targetResource | Should Be $false;
+                }
+
+                It "Fails when group 'Members' do not match using '$attribute'" {
+                    Mock Get-ADGroup { return $fakeADGroup; }
+                    Mock Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); }
+
+                    $targetResource = Test-TargetResource @testPresentParams -Members $fakeADUser2.$attribute, $fakeADUser3.$attribute -MembershipAttribute $attribute;
+
+                    $targetResource | Should Be $false;
+                }
+
+                It "Passes when specified 'MembersToInclude' match using '$attribute'" {
+                    Mock Get-ADGroup { return $fakeADGroup; }
+                    Mock Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); }
+
+                    $targetResource = Test-TargetResource @testPresentParams -MembersToInclude $fakeADUser2.$attribute -MembershipAttribute $attribute;
+
+                    $targetResource | Should Be $true;
+                }
+
+                It "Fails when specified 'MembersToInclude' are missing using '$attribute'" {
+                    Mock Get-ADGroup { return $fakeADGroup; }
+                    Mock Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); }
+
+                    $targetResource = Test-TargetResource @testPresentParams -MembersToInclude $fakeADUser3.$attribute -MembershipAttribute $attribute;
+
+                    $targetResource | Should Be $false;
+                }
+
+                It "Passes when specified 'MembersToExclude' are missing using '$attribute'" {
+                    Mock Get-ADGroup { return $fakeADGroup; }
+                    Mock Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); }
+
+                    $targetResource = Test-TargetResource @testPresentParams -MembersToExclude $fakeADUser3.$attribute -MembershipAttribute $attribute;
+
+                    $targetResource | Should Be $true;
+                }
+
+                It "Fails when when specified 'MembersToExclude' match using '$attribute'" {
+                    Mock Get-ADGroup { return $fakeADGroup; }
+                    Mock Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); }
+
+                    $targetResource = Test-TargetResource @testPresentParams -MembersToExclude $fakeADUser2.$attribute -MembershipAttribute $attribute;
+
+                    $targetResource | Should Be $false;
+                }
+
+            } #end foreach attribute
+
+            It "Fails when group does not exist and 'Ensure' is 'Present'" {
+                Mock Get-TargetResource { return $testAbsentParams }
+
+                Test-TargetResource @testPresentParams | Should Be $false
+            }
+
+            It "Fails when group exists, 'Ensure' is 'Present' but 'Scope' is wrong" {
+                Mock Get-TargetResource {
+                    $duffADGroup = $testPresentParams.Clone();
+                    $duffADGroup['GroupScope'] = 'Universal';
+                    return $duffADGroup;
+                }
+
+                Test-TargetResource @testPresentParams | Should Be $false;
+            }
+
+            It "Fails when group exists, 'Ensure' is 'Present' but 'Category' is wrong" {
+                Mock Get-TargetResource {
+                    $duffADGroup = $testPresentParams.Clone();
+                    $duffADGroup['Category'] = 'Distribution';
+                    return $duffADGroup;
+                }
+
+                Test-TargetResource @testPresentParams | Should Be $false;
+            }
+
+            It "Fails when group exists, 'Ensure' is 'Present' but 'Path' is wrong" {
+                Mock Get-TargetResource {
+                    $duffADGroup = $testPresentParams.Clone();
+                    $duffADGroup['Path'] = 'OU=WrongPath,DC=contoso,DC=com';
+                    return $duffADGroup;
+                }
+
+                Test-TargetResource @testPresentParams | Should Be $false;
+            }
+
+            It "Fails when group exists, 'Ensure' is 'Present' but 'Description' is wrong" {
+                Mock Get-TargetResource {
+                    $duffADGroup = $testPresentParams.Clone();
+                    $duffADGroup['Description'] = 'Test AD group description is wrong';
+                    return $duffADGroup;
+                }
+
+                Test-TargetResource @testPresentParams | Should Be $false;
+            }
+
+            It "Fails when group exists, 'Ensure' is 'Present' but 'DisplayName' is wrong" {
+                Mock Get-TargetResource {
+                    $duffADGroup = $testPresentParams.Clone();
+                    $duffADGroup['DisplayName'] = 'Wrong display name';
+                    return $duffADGroup;
+                }
+
+                Test-TargetResource @testPresentParams | Should Be $false;
+            }
+
+            It "Fails when group exists, 'Ensure' is 'Present' but 'ManagedBy' is wrong" {
+                Mock Get-TargetResource {
+                    $duffADGroup = $testPresentParams.Clone();
+                    $duffADGroup['ManagedBy'] = $fakeADUser3.DistinguishedName;
+                    return $duffADGroup;
+                }
+
+                Test-TargetResource @testPresentParams | Should Be $false;
+            }
+
+            It "Fails when group exists, 'Ensure' is 'Present' but 'Notes' is wrong" {
+                Mock Get-TargetResource {
+                    $duffADGroup = $testPresentParams.Clone();
+                    $duffADGroup['Notes'] = 'These notes are clearly wrong';
+                    return $duffADGroup;
+                }
+
+                Test-TargetResource @testPresentParams | Should Be $false;
+            }
+
+            It "Fails when group exists and 'Ensure' is 'Absent'" {
+                Mock Get-TargetResource { return $testPresentParams }
+
+                Test-TargetResource @testAbsentParams | Should Be $false
+            }
+
+            It "Passes when group exists, target matches and 'Ensure' is 'Present'" {
+                Mock Get-TargetResource { return $testPresentParams }
+
+                Test-TargetResource @testPresentParams | Should Be $true
+            }
+
+            It "Passes when group does not exist and 'Ensure' is 'Absent'" {
+                Mock Get-TargetResource { return $testAbsentParams }
+
+                Test-TargetResource @testAbsentParams | Should Be $true
+            }
+
+        }
+        #end region
+
+        #region Function Set-TargetResource
+        Describe "$($Global:DSCResourceName)\Set-TargetResource" {
+
+            Mock Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } { }
+
+            It "Calls 'New-ADGroup' when 'Ensure' is 'Present' and the group does not exist" {
+                Mock Get-ADGroup { throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException }
+                Mock Set-ADGroup { }
+                Mock New-ADGroup { return [PSCustomObject] $fakeADGroup; }
+
+                Set-TargetResource @testPresentParams;
+
+                Assert-MockCalled New-ADGroup -Scope It;
+            }
+
+            $testProperties = @{
+                Description = 'Test AD Group description is wrong';
+                ManagedBy = $fakeADUser3.DistinguishedName;
+                DisplayName = 'Test DisplayName';
+            }
+
+            foreach ($property in $testProperties.Keys) {
+                It "Calls 'Set-ADGroup' when 'Ensure' is 'Present' and '$property' is specified" {
+                    Mock Set-ADGroup { }
+                    Mock Get-ADGroupMember { }
+                    Mock Get-ADGroup {
+                        $duffADGroup = $fakeADGroup.Clone();
+                        $duffADGroup[$property] = $testProperties.$property;
+                        return $duffADGroup;
+                    }
+
+                    Set-TargetResource @testPresentParams;
+
+                    Assert-MockCalled Set-ADGroup -Scope It -Exactly 1;
+                }
+            }
+
+            It "Calls 'Set-ADGroup' when 'Ensure' is 'Present' and 'Category' is specified" {
+                Mock Set-ADGroup -ParameterFilter { $GroupCategory -eq $testPresentParams.Category } { }
+                Mock Get-ADGroupMember { }
+                Mock Get-ADGroup {
+                    $duffADGroup = $fakeADGroup.Clone();
+                    $duffADGroup['GroupCategory'] = 'Distribution';
+                    return $duffADGroup;
+                }
+
+                Set-TargetResource @testPresentParams;
+
+                Assert-MockCalled Set-ADGroup -ParameterFilter { $GroupCategory -eq $testPresentParams.Category } -Scope It -Exactly 1;
+            }
+
+            It "Calls 'Set-ADGroup' when 'Ensure' is 'Present' and 'Notes' is specified" {
+                Mock Set-ADGroup -ParameterFilter { $Replace -ne $null } { }
+                Mock Get-ADGroupMember { }
+                Mock Get-ADGroup {
+                    $duffADGroup = $fakeADGroup.Clone();
+                    $duffADGroup['Info'] = 'My test note..';
+                    return $duffADGroup;
+                }
+
+                Set-TargetResource @testPresentParams;
+
+                Assert-MockCalled Set-ADGroup -ParameterFilter { $Replace -ne $null } -Scope It -Exactly 1;
+            }
+
+            It "Calls 'Set-ADGroup' twice when 'Ensure' is 'Present', the group exists but the 'Scope' has changed" {
+                Mock Set-ADGroup { }
+                Mock Get-ADGroupMember { }
+                Mock Get-ADGroup {
+                    $duffADGroup = $fakeADGroup.Clone();
+                    $duffADGroup['GroupScope'] = 'DomainLocal';
+                    return $duffADGroup;
+                }
+
+                Set-TargetResource @testPresentParams;
+
+                Assert-MockCalled Set-ADGroup -Scope It -Exactly 2;
+            }
+
+            It "Adds group members when 'Ensure' is 'Present', the group exists and 'Members' are specified" {
+                Mock Get-ADGroup { throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException }
+                Mock Set-ADGroup { }
+                Mock Add-ADGroupMember { }
+                Mock New-ADGroup { return [PSCustomObject] $fakeADGroup; }
+
+                Set-TargetResource @testPresentParams -Members @($fakeADUser1.SamAccountName, $fakeADUser2.SamAccountName);
+
+                Assert-MockCalled Add-ADGroupMember -Scope It;
+            }
+
+            It "Adds group members when 'Ensure' is 'Present', the group exists and 'MembersToInclude' are specified" {
+                Mock Get-ADGroup { throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException }
+                Mock Set-ADGroup { }
+                Mock Add-ADGroupMember { }
+                Mock New-ADGroup { return [PSCustomObject] $fakeADGroup; }
+
+                Set-TargetResource @testPresentParams -MembersToInclude @($fakeADUser1.SamAccountName, $fakeADUser2.SamAccountName);
+
+                Assert-MockCalled Add-ADGroupMember -Scope It;
+            }
+
+            It "Moves group when 'Ensure' is 'Present', the group exists but the 'Path' has changed" {
+                Mock Set-ADGroup { }
+                Mock Get-ADGroupMember { }
+                Mock Move-ADObject { }
+                Mock Get-ADGroup {
+                    $duffADGroup = $fakeADGroup.Clone();
+                    $duffADGroup['DistinguishedName'] = "CN=$($testPresentParams.GroupName),OU=WrongPath,DC=contoso,DC=com";
+                    return $duffADGroup;
+                }
+
+                Set-TargetResource @testPresentParams;
+
+                Assert-MockCalled Move-ADObject -Scope It;
+            }
+
+            It "Resets group membership when 'Ensure' is 'Present' and 'Members' is incorrect" {
+                Mock Get-ADGroup { return [PSCustomObject] $fakeADGroup; }
+                Mock Set-ADGroup { }
+                Mock Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); }
+                Mock Add-ADGroupMember { }
+                Mock Remove-ADGroupMember { }
+
+                Set-TargetResource @testPresentParams -Members $fakeADuser1.SamAccountName;
+
+                Assert-MockCalled Remove-ADGroupMember -Scope It -Exactly 1;
+                Assert-MockCalled Add-ADGroupMember -Scope It -Exactly 1;
+            }
+
+            It "Does not reset group membership when 'Ensure' is 'Present' and existing group is empty" {
+                Mock Get-ADGroup { return [PSCustomObject] $fakeADGroup; }
+                Mock Set-ADGroup { }
+                Mock Get-ADGroupMember { }
+                Mock Remove-ADGroupMember { }
+
+                Set-TargetResource @testPresentParams -MembersToExclude $fakeADuser1.SamAccountName;
+
+                Assert-MockCalled Remove-ADGroupMember -Scope It -Exactly 0;
+            }
+
+            It "Removes members when 'Ensure' is 'Present' and 'MembersToExclude' is incorrect" {
+                Mock Get-ADGroup { return [PSCustomObject] $fakeADGroup; }
+                Mock Set-ADGroup { }
+                Mock Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); }
+                Mock Remove-ADGroupMember { }
+
+                Set-TargetResource @testPresentParams -MembersToExclude $fakeADuser1.SamAccountName;
+
+                Assert-MockCalled Remove-ADGroupMember -Scope It -Exactly 1;
+            }
+
+            It "Adds members when 'Ensure' is 'Present' and 'MembersToInclude' is incorrect" {
+                Mock Get-ADGroup { return [PSCustomObject] $fakeADGroup; }
+                Mock Set-ADGroup { }
+                Mock Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); }
+                Mock Add-ADGroupMember { }
+
+                Set-TargetResource @testPresentParams -MembersToInclude $fakeADuser3.SamAccountName;
+
+                Assert-MockCalled Add-ADGroupMember -Scope It -Exactly 1;
+            }
+
+            It "Removes group when 'Ensure' is 'Absent' and group exists" {
+                Mock Get-ADGroup { return $fakeADGroup; }
+                Mock Remove-ADGroup { }
+
+                Set-TargetResource @testAbsentParams;
+
+                Assert-MockCalled Remove-ADGroup -Scope It;
+            }
+
+            It "Calls 'Set-ADGroup' with credentials when 'Ensure' is 'Present' and the group exists (#106)" {
+                Mock Get-ADGroup { return $fakeADGroup; }
+                Mock New-ADGroup { return [PSCustomObject] $fakeADGroup; }
+                Mock Get-ADGroupMember { }
+                Mock Set-ADGroup -ParameterFilter { $Credential -eq $testCredentials } -MockWith { }
+
+                Set-TargetResource @testPresentParams -Credential $testCredentials;
+
+                Assert-MockCalled Set-ADGroup -ParameterFilter { $Credential -eq $testCredentials } -Scope It;
+            }
+
+            It "Calls 'Set-ADGroup' with credentials when 'Ensure' is 'Present' and the group does not exist  (#106)" {
+                Mock Get-ADGroup { throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException }
+                Mock Set-ADGroup -ParameterFilter { $Credential -eq $testCredentials }  { }
+                Mock New-ADGroup { return [PSCustomObject] $fakeADGroup; }
+
+                Set-TargetResource @testPresentParams -Credential $testCredentials;
+
+                Assert-MockCalled Set-ADGroup -ParameterFilter { $Credential -eq $testCredentials } -Scope It;
+            }
+
+            It "Calls 'Move-ADObject' with credentials when specified (#106)" {
+                Mock Set-ADGroup { }
+                Mock Get-ADGroupMember { }
+                Mock Move-ADObject -ParameterFilter { $Credential -eq $testCredentials } { }
+                Mock Get-ADGroup {
+                    $duffADGroup = $fakeADGroup.Clone();
+                    $duffADGroup['DistinguishedName'] = "CN=$($testPresentParams.GroupName),OU=WrongPath,DC=contoso,DC=com";
+                    return $duffADGroup;
+                }
+
+                Set-TargetResource @testPresentParams -Credential $testCredentials;
+
+                Assert-MockCalled Move-ADObject -ParameterFilter { $Credential -eq $testCredentials } -Scope It;
+            }
+
+        }
+        #end region
+
+    }
+    #end region
+}
+finally
+{
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+
+    # TODO: Other Optional Cleanup Code Goes Here...
+}

+ 293 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADOrganizationalUnit.Tests.ps1

@@ -0,0 +1,293 @@
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
+param()
+
+$Global:DSCModuleName      = 'xActiveDirectory' # Example xNetworking
+$Global:DSCResourceName    = 'MSFT_xADOrganizationalUnit' # Example MSFT_xFirewall
+
+#region HEADER
+[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path))
+Write-Host $moduleRoot -ForegroundColor Green;
+if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $Global:DSCModuleName `
+    -DSCResourceName $Global:DSCResourceName `
+    -TestType Unit
+#endregion
+
+# Begin Testing
+try
+{
+
+    #region Pester Tests
+
+    # The InModuleScope command allows you to perform white-box unit testing on the internal
+    # (non-exported) code of a Script Module.
+    InModuleScope $Global:DSCResourceName {
+
+        function Get-ADOrganizationalUnit { param ($Name) }
+        function Set-ADOrganizationalUnit { param ($Identity, $Credential) }
+        function Remove-ADOrganizationalUnit { param ($Name, $Credential) }
+        function New-ADOrganizationalUnit { param ($Name, $Credential) }
+
+        $testCredential = New-Object System.Management.Automation.PSCredential 'DummyUser', (ConvertTo-SecureString 'DummyPassword' -AsPlainText -Force);
+
+        $testPresentParams = @{
+            Name = 'TestOU'
+            Path = 'OU=Fake,DC=contoso,DC=com';
+            Description = 'Test AD OU description';
+            Ensure = 'Present';
+        }
+
+        $testAbsentParams = $testPresentParams.Clone();
+        $testAbsentParams['Ensure'] = 'Absent';
+
+        $protectedFakeAdOu = @{
+            Name = $testPresentParams.Name;
+            ProtectedFromAccidentalDeletion = $true;
+            Description = $testPresentParams.Description;
+        }
+
+        #region Function Get-TargetResource
+        Describe "$($Global:DSCResourceName)\Get-TargetResource" {
+
+            It 'Returns a "System.Collections.Hashtable" object type' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { return [PSCustomObject] $protectedFakeAdOu }
+                $targetResource = Get-TargetResource -Name $testPresentParams.Name -Path $testPresentParams.Path
+
+                $targetResource -is [System.Collections.Hashtable] | Should Be $true
+            }
+
+            It 'Returns "Ensure" = "Present" when OU exists' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { return [PSCustomObject] $protectedFakeAdOu }
+                $targetResource = Get-TargetResource -Name $testPresentParams.Name -Path $testPresentParams.Path
+
+                $targetResource.Ensure | Should Be 'Present'
+            }
+
+            It 'Returns "Ensure" = "Absent" when OU does not exist' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { }
+                $targetResource = Get-TargetResource -Name $testPresentParams.Name -Path $testPresentParams.Path
+
+                $targetResource.Ensure | Should Be 'Absent'
+            }
+
+            It 'Returns "ProtectedFromAccidentalDeletion" = "$true" when OU is protected' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { return [PSCustomObject] $protectedFakeAdOu }
+                $targetResource = Get-TargetResource -Name $testPresentParams.Name -Path $testPresentParams.Path
+
+                $targetResource.ProtectedFromAccidentalDeletion | Should Be $true
+            }
+
+            It 'Returns "ProtectedFromAccidentalDeletion" = "$false" when OU is not protected' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith {
+                    $unprotectedFakeAdOu = $protectedFakeAdOu.Clone();
+                    $unprotectedFakeAdOu['ProtectedFromAccidentalDeletion'] = $false;
+                    return [PSCustomObject] $unprotectedFakeAdOu
+                }
+                $targetResource = Get-TargetResource -Name $testPresentParams.Name -Path $testPresentParams.Path
+
+                $targetResource.ProtectedFromAccidentalDeletion | Should Be $false
+            }
+
+            It 'Returns an empty description' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith {
+                    $noDescriptionFakeAdOu = $protectedFakeAdOu.Clone();
+                    $noDescriptionFakeAdOu['Description'] = '';
+                    return [PSCustomObject] $noDescriptionFakeAdOu
+                }
+
+                $targetResource = Get-TargetResource -Name $testPresentParams.Name -Path $testPresentParams.Path
+
+                $targetResource.Description | Should BeNullOrEmpty
+            }
+
+        }
+        #endregion
+
+        #region Function Test-TargetResource
+        Describe "$($Global:DSCResourceName)\Test-TargetResource" {
+
+            It 'Returns a "System.Boolean" object type' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit { return [PSCustomObject] $protectedFakeAdOu }
+                $targetResource = Test-TargetResource @testPresentParams
+
+                $targetResource -is [System.Boolean] | Should Be $true
+            }
+
+            It 'Fails when OU does not exist and "Ensure" = "Present"' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { }
+
+                Test-TargetResource @testPresentParams | Should Be $false
+            }
+
+            It 'Fails when OU does exist and "Ensure" = "Absent"' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { return [PSCustomObject] $protectedFakeAdOu }
+
+                Test-TargetResource @testAbsentParams | Should Be $false
+            }
+
+            It 'Fails when OU does exist but "Description" is incorrect' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit { return [PSCustomObject] $protectedFakeAdOu }
+                $testDescriptionParams = $testPresentParams.Clone()
+                $testDescriptionParams['Description'] = 'Wrong description'
+
+                Test-TargetResource @testDescriptionParams | Should Be $false
+            }
+
+            It 'Fails when OU does exist but "ProtectedFromAccidentalDeletion" is incorrect' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit { return [PSCustomObject] $protectedFakeAdOu }
+                $testProtectedFromAccidentalDeletionParams = $testPresentParams.Clone()
+                $testProtectedFromAccidentalDeletionParams['ProtectedFromAccidentalDeletion'] = $false
+
+                Test-TargetResource @testProtectedFromAccidentalDeletionParams | Should Be $false
+            }
+
+            It 'Passes when OU does exist, "Ensure" = "Present" and all properties are correct' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { return [PSCustomObject] $protectedFakeAdOu }
+
+                Test-TargetResource @testPresentParams | Should Be $true
+            }
+
+            It 'Passes when OU does not exist and "Ensure" = "Absent"' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { }
+
+                Test-TargetResource @testAbsentParams | Should Be $true
+            }
+
+            It 'Passes when no OU description is specified with existing OU description' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit { return [PSCustomObject] $protectedFakeAdOu }
+                $testEmptyDescriptionParams = $testPresentParams.Clone()
+                $testEmptyDescriptionParams['Description'] = ''
+
+                Test-TargetResource @testEmptyDescriptionParams | Should Be $true
+            }
+
+        }
+        #endregion
+
+        #region Function Set-TargetResource
+        Describe "$($Global:DSCResourceName)\Set-TargetResource" {
+
+            It 'Calls "New-ADOrganizationalUnit" when "Ensure" = "Present" and OU does not exist' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { }
+                Mock New-ADOrganizationalUnit -ParameterFilter { $Name -eq $testPresentParams.Name } -MockWith { }
+
+                Set-TargetResource @testPresentParams
+                Assert-MockCalled New-ADOrganizationalUnit -ParameterFilter { $Name -eq $testPresentParams.Name } -Scope It
+            }
+
+            It 'Calls "New-ADOrganizationalUnit" with credentials when specified' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { }
+                Mock New-ADOrganizationalUnit -ParameterFilter { $Credential -eq $testCredential } -MockWith { }
+
+                Set-TargetResource @testPresentParams -Credential $testCredential
+                Assert-MockCalled New-ADOrganizationalUnit -ParameterFilter { $Credential -eq $testCredential } -Scope It
+            }
+
+            It 'Calls "Set-ADOrganizationalUnit" when "Ensure" = "Present" and OU does exist' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { return [PSCustomObject] $protectedFakeAdOu }
+                Mock Set-ADOrganizationalUnit -MockWith { }
+
+                Set-TargetResource @testPresentParams
+                Assert-MockCalled Set-ADOrganizationalUnit -Scope It
+            }
+
+            It 'Calls "Set-ADOrganizationalUnit" with credentials when specified' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { return [PSCustomObject] $protectedFakeAdOu }
+                Mock Set-ADOrganizationalUnit -ParameterFilter { $Credential -eq $testCredential } -MockWith { }
+
+                Set-TargetResource @testPresentParams -Credential $testCredential
+                Assert-MockCalled Set-ADOrganizationalUnit -ParameterFilter { $Credential -eq $testCredential } -Scope It
+            }
+
+            It 'Calls "Remove-ADOrganizationalUnit" when "Ensure" = "Absent" and OU does exist but is unprotected' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith {
+                    $unprotectedFakeAdOu = $protectedFakeAdOu.Clone()
+                    $unprotectedFakeAdOu['ProtectedFromAccidentalDeletion'] = $false
+                    return [PSCustomObject] $unprotectedFakeAdOu
+                }
+                Mock Remove-ADOrganizationalUnit -MockWith { }
+
+                Set-TargetResource @testAbsentParams
+                Assert-MockCalled Remove-ADOrganizationalUnit -Scope It
+            }
+
+            It 'Calls "Remove-ADOrganizationalUnit" when "Ensure" = "Absent" and OU does exist and is protected' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { return [PSCustomObject] $protectedFakeAdOu }
+                Mock Remove-ADOrganizationalUnit -MockWith { }
+
+                Set-TargetResource @testAbsentParams
+                Assert-MockCalled Remove-ADOrganizationalUnit -Scope It
+            }
+
+            It 'Calls "Remove-ADOrganizationalUnit" with credentials when specified' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { return [PSCustomObject] $protectedFakeAdOu }
+                Mock Remove-ADOrganizationalUnit -ParameterFilter { $Credential -eq $testCredential } -MockWith { }
+
+                Set-TargetResource @testAbsentParams -Credential $testCredential
+                Assert-MockCalled Remove-ADOrganizationalUnit -ParameterFilter { $Credential -eq $testCredential } -Scope It
+            }
+
+            It 'Calls "Set-ADOrganizationalUnit" when "Ensure" = "Absent", OU does exist but is protected' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith { return [PSCustomObject] $protectedFakeAdOu }
+                Mock Remove-ADOrganizationalUnit -MockWith { }
+                Mock Set-ADOrganizationalUnit -MockWith { }
+
+                Set-TargetResource @testAbsentParams
+                Assert-MockCalled Set-ADOrganizationalUnit -Scope It
+            }
+
+            It 'Does not call "Set-ADOrganizationalUnit" when "Ensure" = "Absent", OU does exist but is unprotected' {
+                Mock Assert-Module -MockWith { }
+                Mock Get-ADOrganizationalUnit -MockWith {
+                    $unprotectedFakeAdOu = $protectedFakeAdOu.Clone()
+                    $unprotectedFakeAdOu['ProtectedFromAccidentalDeletion'] = $false
+                    return [PSCustomObject] $unprotectedFakeAdOu
+                }
+                Mock Remove-ADOrganizationalUnit -MockWith { }
+                Mock Set-ADOrganizationalUnit -MockWith { }
+
+                Set-TargetResource @testAbsentParams
+                Assert-MockCalled Set-ADOrganizationalUnit -Scope It -Exactly 0
+            }
+
+        }
+        #endregion
+
+    }
+    #endregion
+}
+finally
+{
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}

+ 455 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xADUser.Tests.ps1

@@ -0,0 +1,455 @@
+$Global:DSCModuleName      = 'xActiveDirectory'
+$Global:DSCResourceName    = 'MSFT_xADUser'
+
+#region HEADER
+[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path))
+if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $Global:DSCModuleName `
+    -DSCResourceName $Global:DSCResourceName `
+    -TestType Unit
+#endregion HEADER
+
+
+# Begin Testing
+try
+{
+
+    #region Pester Tests
+
+    InModuleScope $Global:DSCResourceName {
+
+        $testPresentParams = @{
+            DomainName = 'contoso.com';
+            UserName = 'TestUser';
+            Ensure = 'Present';
+        }
+
+        $testAbsentParams = $testPresentParams.Clone();
+        $testAbsentParams['Ensure'] = 'Absent';
+
+        $fakeADUser = @{
+            DistinguishedName = "CN=$($testPresentParams.UserName),CN=Users,DC=contoso,DC=com";
+            Enabled = $true;
+            GivenName = '';
+            Name = $testPresentParams.UserName;
+            SamAccountName = $testPresentParams.UserName;
+            Surname = '';
+            UserPrincipalName = '';
+        }
+
+        $testDomainController = 'TESTDC';
+        $testCredential = [System.Management.Automation.PSCredential]::Empty;
+
+        $testStringProperties = @(
+            'UserPrincipalName', 'DisplayName', 'Path',  'GivenName', 'Initials', 'Surname', 'Description', 'StreetAddress',
+            'POBox', 'City', 'State', 'PostalCode', 'Country', 'Department', 'Division', 'Company', 'Office', 'JobTitle',
+            'EmailAddress', 'EmployeeID', 'EmployeeNumber', 'HomeDirectory', 'HomeDrive', 'HomePage', 'ProfilePath',
+            'LogonScript', 'Notes', 'OfficePhone', 'MobilePhone', 'Fax', 'Pager', 'IPPhone', 'HomePhone','CommonName'
+        );
+        $testBooleanProperties = @('PasswordNeverExpires', 'CannotChangePassword','Enabled');
+
+        #region Function Get-TargetResource
+        Describe "$($Global:DSCResourceName)\Get-TargetResource" {
+
+            It "Returns a 'System.Collections.Hashtable' object type" {
+                Mock Get-ADUser { return [PSCustomObject] $fakeADUser; }
+
+                $adUser = Get-TargetResource @testPresentParams;
+
+                $adUser -is [System.Collections.Hashtable] | Should Be $true;
+            }
+
+            It "Returns 'Ensure' is 'Present' when user account exists" {
+                Mock Get-ADUser { return [PSCustomObject] $fakeADUser; }
+
+                $adUser = Get-TargetResource @testPresentParams;
+
+                $adUser.Ensure | Should Be 'Present';
+            }
+
+            It "Returns 'Ensure' is 'Absent' when user account does not exist" {
+                Mock Get-ADUser { throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException }
+
+                $adUser = Get-TargetResource @testPresentParams;
+
+                $adUser.Ensure | Should Be 'Absent';
+            }
+
+            It "Calls 'Get-ADUser' with 'Server' parameter when 'DomainController' specified" {
+                Mock Get-ADUser -ParameterFilter { $Server -eq $testDomainController } -MockWith { return [PSCustomObject] $fakeADUser; }
+
+                Get-TargetResource @testPresentParams -DomainController $testDomainController;
+
+                Assert-MockCalled Get-ADUser -ParameterFilter { $Server -eq $testDomainController } -Scope It;
+            }
+
+            It "Calls 'Get-ADUser' with 'Credential' parameter when 'DomainAdministratorCredential' specified" {
+                Mock Get-ADUser -ParameterFilter { $Credential -eq $testCredential } -MockWith { return [PSCustomObject] $fakeADUser; }
+
+                Get-TargetResource @testPresentParams -DomainAdministratorCredential $testCredential;
+
+                Assert-MockCalled Get-ADUser -ParameterFilter { $Credential -eq $testCredential } -Scope It;
+            }
+
+        }
+        #endregion
+
+        #region Function Test-TargetResource
+        Describe "$($Global:DSCResourceName)\Test-TargetResource" {
+
+            It "Passes when user account does not exist and 'Ensure' is 'Absent'" {
+                Mock Get-TargetResource { return $testAbsentParams }
+
+                Test-TargetResource @testAbsentParams | Should Be $true;
+            }
+
+            It "Passes when user account exists and 'Ensure' is 'Present'" {
+                Mock Get-TargetResource { return $testPresentParams }
+
+                Test-TargetResource @testPresentParams | Should Be $true;
+            }
+
+            It "Passes when user account password matches and 'Password' is specified" {
+                Mock Get-TargetResource { return $testPresentParams }
+                Mock Test-Password { return $true; }
+
+                Test-TargetResource @testPresentParams -Password $testCredential | Should Be $true;
+            }
+
+            It "Fails when user account does not exist and 'Ensure' is 'Present'" {
+                Mock Get-TargetResource { return $testAbsentParams }
+
+                Test-TargetResource @testPresentParams | Should Be $false;
+            }
+
+            It "Fails when user account exists, and 'Ensure' is 'Absent'" {
+                Mock Get-TargetResource { return $testPresentParams }
+
+                Test-TargetResource @testAbsentParams | Should Be $false;
+            }
+
+            It "Fails when user account password is incorrect and 'Password' is specified" {
+                Mock Get-TargetResource { return $testPresentParams }
+                Mock Test-Password { return $false; }
+
+                Test-TargetResource @testPresentParams -Password $testCredential | Should Be $false;
+            }
+
+            It "Calls 'Test-Password' with 'Default' PasswordAuthentication by default" {
+                Mock Get-TargetResource { return $testPresentParams }
+                Mock Test-Password -ParameterFilter { $PasswordAuthentication -eq 'Default' } { return $true; }
+
+                Test-TargetResource @testPresentParams -Password $testCredential;
+
+                Assert-MockCalled Test-Password -ParameterFilter { $PasswordAuthentication -eq 'Default' } -Scope It;
+            }
+
+            It "Calls 'Test-Password' with 'Negotiate' PasswordAuthentication when specified" {
+                Mock Get-TargetResource { return $testPresentParams }
+                Mock Test-Password -ParameterFilter { $PasswordAuthentication -eq 'Negotiate' } { return $false; }
+
+                Test-TargetResource @testPresentParams -Password $testCredential -PasswordAuthentication 'Negotiate';
+
+                Assert-MockCalled Test-Password -ParameterFilter { $PasswordAuthentication -eq 'Negotiate' } -Scope It;
+            }
+
+            foreach ($testParameter in $testStringProperties) {
+
+                It "Passes when user account '$testParameter' matches AD account property" {
+                    $testParameterValue = 'Test Parameter String Value';
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADUser = $testPresentParams.Clone();
+                    $invalidADUser = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADUser[$testParameter] = $testParameterValue;
+                        return $validADUser;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $true;
+                }
+
+                It "Fails when user account '$testParameter' does not match incorrect AD account property value" {
+                    $testParameterValue = 'Test Parameter String Value';
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADUser = $testPresentParams.Clone();
+                    $invalidADUser = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $invalidADUser[$testParameter] = $testParameterValue.Substring(0, ([System.Int32] $testParameterValue.Length/2));
+                        return $invalidADUser;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $false;
+                }
+
+                It "Fails when user account '$testParameter' does not match empty AD account property value" {
+                    $testParameterValue = 'Test Parameter String Value';
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADUser = $testPresentParams.Clone();
+                    $invalidADUser = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $invalidADUser[$testParameter] = '';
+                        return $invalidADUser;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $false;
+                }
+
+                It "Fails when user account '$testParameter' does not match null AD account property value" {
+                    $testParameterValue = 'Test Parameter String Value';
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADUser = $testPresentParams.Clone();
+                    $invalidADUser = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $invalidADUser[$testParameter] = $null;
+                        return $invalidADUser;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $false;
+                }
+
+                It "Passes when empty user account '$testParameter' matches empty AD account property" {
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADUser = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADUser[$testParameter] = '';
+                        return $validADUser;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $true;
+                }
+
+                It "Passes when empty user account '$testParameter' matches null AD account property" {
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADUser = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADUser[$testParameter] = $null;
+                        return $validADUser;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $true;
+                }
+
+            } #end foreach test string property
+
+            foreach ($testParameter in $testBooleanProperties) {
+
+                It "Passes when user account '$testParameter' matches AD account property" {
+                    $testParameterValue = $true;
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADUser = $testPresentParams.Clone();
+                    $invalidADUser = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $validADUser[$testParameter] = $testParameterValue;
+                        return $validADUser;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $true;
+                }
+
+                It "Fails when user account '$testParameter' does not match AD account property value" {
+                    $testParameterValue = $true;
+                    $testValidPresentParams = $testPresentParams.Clone();
+                    $testValidPresentParams[$testParameter] = $testParameterValue;
+                    $validADUser = $testPresentParams.Clone();
+                    $invalidADUser = $testPresentParams.Clone();
+                    Mock Get-TargetResource {
+                        $invalidADUser[$testParameter] = -not $testParameterValue;
+                        return $invalidADUser;
+                    }
+
+                    Test-TargetResource @testValidPresentParams | Should Be $false;
+                }
+
+            } #end foreach test boolean property
+
+        }
+        #endregion
+
+        #region Function Set-TargetResource
+        Describe "$($Global:DSCResourceName)\Set-TargetResource" {
+
+            It "Calls 'New-ADUser' when 'Ensure' is 'Present' and the account does not exist" {
+                $newUserName = 'NewUser'
+                $newAbsentParams = $testAbsentParams.Clone();
+                $newAbsentParams['UserName'] = $newUserName;
+                $newPresentParams = $testPresentParams.Clone();
+                $newPresentParams['UserName'] = $newUserName;
+                Mock New-ADUser -ParameterFilter { $Name -eq $newUserName } { }
+                Mock Set-ADUser { }
+                Mock Get-TargetResource -ParameterFilter { $Username -eq $newUserName } { return $newAbsentParams; }
+
+                Set-TargetResource @newPresentParams;
+
+                Assert-MockCalled New-ADUser -ParameterFilter { $Name -eq $newUserName } -Scope It;
+            }
+
+            It "Calls 'Move-ADObject' when 'Ensure' is 'Present', the account exists but Path is incorrect" {
+                $testTargetPath = 'CN=Users,DC=contoso,DC=com';
+                Mock Set-ADUser { }
+                Mock Get-ADUser {
+                    $duffADUser = $fakeADUser.Clone();
+                    $duffADUser['DistinguishedName'] = "CN=$($testPresentParams.UserName),OU=WrongPath,DC=contoso,DC=com";
+                    return $duffADUser;
+                }
+                Mock Move-ADObject -ParameterFilter { $TargetPath -eq $testTargetPath } -MockWith { }
+
+                Set-TargetResource @testPresentParams -Path $testTargetPath -Enabled $true;
+
+                Assert-MockCalled Move-ADObject -ParameterFilter { $TargetPath -eq $testTargetPath } -Scope It;
+            }
+
+            It "Calls 'Rename-ADObject' when 'Ensure' is 'Present', the account exists but 'CommonName' is incorrect" {
+                $testCommonName = 'Test Common Name';
+                Mock Set-ADUser { }
+                Mock Get-ADUser { return $fakeADUser; }
+                Mock Rename-ADObject -ParameterFilter { $NewName -eq $testCommonName } -MockWith { }
+
+                Set-TargetResource @testPresentParams -CommonName $testCommonName -Enabled $true;
+
+                Assert-MockCalled Rename-ADObject -ParameterFilter { $NewName -eq $testCommonName } -Scope It;
+            }
+
+            It "Calls 'Set-ADAccountPassword' when 'Password' parameter is specified" {
+                Mock Get-ADUser { return $fakeADUser; }
+                Mock Set-ADUser { }
+                Mock Set-ADAccountPassword -ParameterFilter { $NewPassword -eq $testCredential.Password } -MockWith { }
+
+                Set-TargetResource @testPresentParams -Password $testCredential;
+
+                Assert-MockCalled Set-ADAccountPassword -ParameterFilter { $NewPassword -eq $testCredential.Password } -Scope It;
+            }
+
+            It "Calls 'Set-ADUser' with 'Replace' when existing matching AD property is null" {
+                $testADPropertyName = 'Description';
+                Mock Get-ADUser {
+                    $duffADUser = $fakeADUser.Clone();
+                    $duffADUser[$testADPropertyName] = $null;
+                    return $duffADUser;
+                }
+                Mock Set-ADUser -ParameterFilter { $Replace.ContainsKey($testADPropertyName) } -MockWith { }
+
+                Set-TargetResource @testPresentParams -Description 'My custom description';
+
+                Assert-MockCalled Set-ADUser -ParameterFilter { $Replace.ContainsKey($testADPropertyName) } -Scope It -Exactly 1;
+            }
+
+            It "Calls 'Set-ADUser' with 'Replace' when existing matching AD property is empty" {
+                $testADPropertyName = 'Description';
+                Mock Get-ADUser {
+                    $duffADUser = $fakeADUser.Clone();
+                    $duffADUser[$testADPropertyName] = '';
+                    return $duffADUser;
+                }
+                Mock Set-ADUser -ParameterFilter { $Replace.ContainsKey($testADPropertyName) } -MockWith { }
+
+                Set-TargetResource @testPresentParams -Description 'My custom description';
+
+                Assert-MockCalled Set-ADUser -ParameterFilter { $Replace.ContainsKey($testADPropertyName) } -Scope It -Exactly 1;
+            }
+
+            It "Calls 'Set-ADUser' with 'Remove' when new matching AD property is empty" {
+                $testADPropertyName = 'Description';
+                Mock Get-ADUser {
+                    $duffADUser = $fakeADUser.Clone();
+                    $duffADUser[$testADPropertyName] = 'Incorrect parameter value';
+                    return $duffADUser;
+                }
+                Mock Set-ADUser -ParameterFilter { $Remove.ContainsKey($testADPropertyName) } -MockWith { }
+
+                Set-TargetResource @testPresentParams -Description '';
+
+                Assert-MockCalled Set-ADUser -ParameterFilter { $Remove.ContainsKey($testADPropertyName) } -Scope It -Exactly 1;
+            }
+
+            It "Calls 'Set-ADUser' with 'Replace' when existing mismatched AD property is null" {
+                $testADPropertyName = 'Title';
+                Mock Get-ADUser {
+                    $duffADUser = $fakeADUser.Clone();
+                    $duffADUser[$testADPropertyName] = $null;
+                    return $duffADUser;
+                }
+                Mock Set-ADUser -ParameterFilter { $Replace.ContainsKey($testADPropertyName) } -MockWith { }
+
+                Set-TargetResource @testPresentParams -JobTitle 'Gaffer';
+
+                Assert-MockCalled Set-ADUser -ParameterFilter { $Replace.ContainsKey($testADPropertyName) } -Scope It -Exactly 1;
+            }
+
+            It "Calls 'Set-ADUser' with 'Replace' when existing mismatched AD property is empty" {
+                $testADPropertyName = 'Title';
+                Mock Get-ADUser {
+                    $duffADUser = $fakeADUser.Clone();
+                    $duffADUser[$testADPropertyName] = '';
+                    return $duffADUser;
+                }
+                Mock Set-ADUser -ParameterFilter { $Replace.ContainsKey($testADPropertyName) } -MockWith { }
+
+                Set-TargetResource @testPresentParams -JobTitle 'Gaffer';
+
+                Assert-MockCalled Set-ADUser -ParameterFilter { $Replace.ContainsKey($testADPropertyName) } -Scope It -Exactly 1;
+            }
+
+            It "Calls 'Set-ADUser' with 'Remove' when new mismatched AD property is empty" {
+                $testADPropertyName = 'Title';
+                Mock Get-ADUser {
+                    $duffADUser = $fakeADUser.Clone();
+                    $duffADUser[$testADPropertyName] = 'Incorrect job title';
+                    return $duffADUser;
+                }
+                Mock Set-ADUser -ParameterFilter { $Remove.ContainsKey($testADPropertyName) } -MockWith { }
+
+                Set-TargetResource @testPresentParams -JobTitle '';
+
+                Assert-MockCalled Set-ADUser -ParameterFilter { $Remove.ContainsKey($testADPropertyName) } -Scope It -Exactly 1;
+            }
+
+            It "Calls 'Remove-ADUser' when 'Ensure' is 'Absent' and user account exists" {
+                Mock Get-ADUser { return [PSCustomObject] $fakeADUser; }
+                Mock Remove-ADUser -ParameterFilter { $Identity.ToString() -eq $testAbsentParams.UserName } -MockWith { }
+
+                Set-TargetResource @testAbsentParams;
+
+                Assert-MockCalled Remove-ADUser -ParameterFilter { $Identity.ToString() -eq $testAbsentParams.UserName } -Scope It;
+            }
+
+        }
+        #endregion
+
+        #region Function Assert-TargetResource
+        Describe "$($Global:DSCResourceName)\Assert-Parameters" {
+
+            It "Does not throw when 'PasswordNeverExpires' and 'CannotChangePassword' are specified" {
+                { Assert-Parameters -PasswordNeverExpires $true -CannotChangePassword $true } | Should Not Throw;
+            }
+
+            It "Throws when account is disabled and 'Password' is specified" {
+                { Assert-Parameters -Password $testCredential -Enabled $false } | Should Throw;
+            }
+
+        }
+        #endregion
+
+    }
+    #endregion
+}
+finally
+{
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}
+

+ 175 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/Tests/Unit/MSFT_xWaitForADDomain.Tests.ps1

@@ -0,0 +1,175 @@
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
+param()
+
+$Global:DSCModuleName      = 'xActiveDirectory'
+$Global:DSCResourceName    = 'MSFT_xWaitForADDomain'
+
+#region HEADER
+[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path))
+if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $Global:DSCModuleName `
+    -DSCResourceName $Global:DSCResourceName `
+    -TestType Unit
+#endregion
+
+# Begin Testing
+try
+{
+
+    #region Pester Tests
+
+    # The InModuleScope command allows you to perform white-box unit testing on the internal
+    # (non-exported) code of a Script Module.
+    InModuleScope $Global:DSCResourceName {
+
+        #region Pester Test Initialization
+        $password = 'Password' | ConvertTo-SecureString -AsPlainText -Force
+        $DomainUserCredential = New-Object pscredential('Username', $password)
+        $domainName = 'example.com'
+        $testParams = @{
+            DomainName = $domainName
+            DomainUserCredential = $DomainUserCredential
+            RetryIntervalSec = 10
+            RetryCount = 5
+        }
+
+        $rebootTestParams = @{
+            DomainName = $domainName
+            DomainUserCredential = $DomainUserCredential
+            RetryIntervalSec = 10
+            RetryCount = 5
+            RebootRetryCount = 3
+        }
+
+        $fakeDomainObject = @{Name = $domainName}
+        #endregion
+
+
+        #region Function Get-TargetResource
+        Describe "$($Global:DSCResourceName)\Get-TargetResource" {
+            It 'Returns a "System.Collections.Hashtable" object type' {
+                Mock -CommandName Get-Domain -MockWith {return $fakeDomainObject}
+                $targetResource = Get-TargetResource @testParams
+                $targetResource -is [System.Collections.Hashtable] | Should Be $true
+            }
+
+            It "Returns DomainName = $($testParams.DomainName) when domain is found" {
+                Mock -CommandName Get-Domain -MockWith {return $fakeDomainObject}
+                $targetResource = Get-TargetResource @testParams
+                $targetResource.DomainName | Should Be $testParams.DomainName
+            }
+
+            It "Returns an empty DomainName when domain is not found" {
+                Mock -CommandName Get-Domain -MockWith {}
+                $targetResource = Get-TargetResource @testParams
+                $targetResource.DomainName | Should Be $null
+            }
+        }
+        #endregion
+
+
+        #region Function Test-TargetResource
+        Describe "$($Global:DSCResourceName)\Test-TargetResource" {
+            It 'Returns a "System.Boolean" object type' {
+                Mock -CommandName Get-Domain -MockWith {return $fakeDomainObject}
+                $targetResource =  Test-TargetResource @testParams
+                $targetResource -is [System.Boolean] | Should Be $true
+            }
+
+            It 'Passes when domain found' {
+                Mock -CommandName Get-Domain -MockWith {return $fakeDomainObject}
+                Test-TargetResource @testParams | Should Be $true
+            }
+
+            It 'Fails when domain not found' {
+                Mock -CommandName Get-Domain -MockWith {}
+                Test-TargetResource @testParams | Should Be $false
+            }
+        }
+        #endregion
+
+
+        #region Function Set-TargetResource
+        Describe "$($Global:DSCResourceName)\Set-TargetResource" {
+            BeforeEach{
+                $global:DSCMachineStatus = $null
+            }
+
+            It "Doesn't throw exception and doesn't call Start-Sleep, Clear-DnsClientCache or set `$global:DSCMachineStatus when domain found" {
+                Mock -CommandName Get-Domain -MockWith {return $fakeDomainObject}
+                Mock -CommandName Start-Sleep -MockWith {}
+                Mock -CommandName Clear-DnsClientCache -MockWith {}
+                {Set-TargetResource @testParams} | Should Not Throw
+                 $global:DSCMachineStatus | should not be 1
+                Assert-MockCalled -CommandName Start-Sleep -Times 0 -Scope It
+                Assert-MockCalled -CommandName Clear-DnsClientCache -Times 0 -Scope It
+            }
+
+            It "Throws exception and does not set `$global:DSCMachineStatus when domain not found after $($testParams.RetryCount) retries when RebootRetryCount is not set" {
+                Mock -CommandName Get-Domain -MockWith {}
+                {Set-TargetResource @testParams} | Should Throw
+                $global:DSCMachineStatus | should not be 1
+            }
+
+            It "Throws exception when domain not found after $($rebootTestParams.RebootRetryCount) reboot retries when RebootRetryCount is exceeded" {
+                Mock -CommandName Get-Domain -MockWith {}
+                Mock -CommandName Get-Content -MockWith {return $rebootTestParams.RebootRetryCount}
+                {Set-TargetResource @rebootTestParams} | Should Throw
+            }
+
+            It "Calls Set-Content if reboot count is less than RebootRetryCount when domain not found" {
+                Mock -CommandName Get-Domain -MockWith {}
+                Mock -CommandName Get-Content -MockWith {return 0}
+                Mock -CommandName Set-Content -MockWith {}
+                {Set-TargetResource @rebootTestParams} | Should Not Throw
+                Assert-MockCalled -CommandName Set-Content -Times 1 -Exactly -Scope It
+            }
+
+            It "Sets `$global:DSCMachineStatus = 1 and does not throw an exception if the domain is not found and RebootRetryCount is not exceeded" {
+                Mock -CommandName Get-Domain -MockWith {}
+                Mock -CommandName Get-Content -MockWith {return 0}
+                {Set-TargetResource @rebootTestParams} | Should Not Throw
+                $global:DSCMachineStatus | should be 1
+            }
+
+            It "Calls Get-Domain exactly $($testParams.RetryCount) times when domain not found" {
+                Mock -CommandName Get-Domain -MockWith {}
+                Mock -CommandName Start-Sleep -MockWith {}
+                Mock -CommandName Clear-DnsClientCache -MockWith {}
+                {Set-TargetResource @testParams} | Should Throw
+                Assert-MockCalled -CommandName Get-Domain -Times $testParams.RetryCount -Exactly -Scope It
+            }
+
+            It "Calls Start-Sleep exactly $($testParams.RetryCount) times when domain not found" {
+                Mock -CommandName Get-Domain -MockWith {}
+                Mock -CommandName Start-Sleep -MockWith {}
+                Mock -CommandName Clear-DnsClientCache -MockWith {}
+                {Set-TargetResource @testParams} | Should Throw
+                Assert-MockCalled -CommandName Start-Sleep -Times $testParams.RetryCount -Exactly -Scope It
+            }
+
+            It "Calls Clear-DnsClientCache exactly $($testParams.RetryCount) times when domain not found" {
+                Mock -CommandName Get-Domain -MockWith {}
+                Mock -CommandName Start-Sleep -MockWith {}
+                Mock -CommandName Clear-DnsClientCache -MockWith {}
+                {Set-TargetResource @testParams} | Should Throw
+                Assert-MockCalled -CommandName Clear-DnsClientCache -Times $testParams.RetryCount -Exactly -Scope It
+            }
+        }
+        #endregion
+    }
+    #endregion
+}
+finally
+{
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}

+ 66 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/xActiveDirectory.psd1

@@ -0,0 +1,66 @@
+@{
+# Version number of this module.
+ModuleVersion = '2.16.0.0'
+
+# ID used to uniquely identify this module
+GUID = '9FECD4F6-8F02-4707-99B3-539E940E9FF5'
+
+# Author of this module
+Author = 'Microsoft Corporation'
+
+# Company or vendor of this module
+CompanyName = 'Microsoft Corporation'
+
+# Copyright statement for this module
+Copyright = '(c) 2014 Microsoft Corporation. All rights reserved.'
+
+# Description of the functionality provided by this module
+Description = 'The xActiveDirectory module is originally part of the Windows PowerShell Desired State Configuration (DSC) Resource Kit. This version has been modified for use in Azure. This module contains the xADDomain, xADDomainController, xADUser, and xWaitForDomain resources. These DSC Resources allow you to configure and manage Active Directory.
+
+All of the resources in the DSC Resource Kit are provided AS IS, and are not supported through any Microsoft standard support program or service.'
+
+# Minimum version of the Windows PowerShell engine required by this module
+PowerShellVersion = '4.0'
+
+# Minimum version of the common language runtime (CLR) required by this module
+CLRVersion = '4.0'
+
+# Functions to export from this module
+FunctionsToExport = '*'
+
+# Cmdlets to export from this module
+CmdletsToExport = '*'
+
+# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
+PrivateData = @{
+
+    PSData = @{
+
+        # Tags applied to this module. These help with module discovery in online galleries.
+        Tags = @('DesiredStateConfiguration', 'DSC', 'DSCResourceKit', 'DSCResource')
+
+        # A URL to the license for this module.
+        LicenseUri = 'https://github.com/PowerShell/xActiveDirectory/blob/master/LICENSE'
+
+        # A URL to the main website for this project.
+        ProjectUri = 'https://github.com/PowerShell/xActiveDirectory'
+
+        # A URL to an icon representing this module.
+        # IconUri = ''
+
+        # ReleaseNotes of this module
+        ReleaseNotes = '* xAdDomainController: Update to complete fix for SiteName being required field.
+* xADDomain: Added retry logic to prevent FaultException to crash in Get-TargetResource on subsequent reboots after a domain is created because the service is not yet running. This error is mostly occur when the resource is used with the DSCExtension on Azure. 
+
+'
+
+    } # End of PSData hashtable
+
+} # End of PrivateData hashtable
+}
+
+
+
+
+
+

+ 898 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xActiveDirectory/2.16.0.0/xActiveDirectory_TechNetDocumentation.html

@@ -0,0 +1,898 @@
+<body>
+        <div id="longDesc">
+            <p><span style="font-family:Calibri; font-size:medium">&nbsp;</span></p>
+            <h1 style="color:#2e74b5; font-family:Calibri Light; font-size:large">Introduction</h1>       
+            <div id="longDesc">
+                <span style="font-family:Calibri; font-size:medium">
+                    <p>
+                        The<strong> xActiveDirectory</strong> module is a part of the Windows PowerShell Desired State Configuration (DSC) Resource Kit, which is a collection of DSC Resources produced by the PowerShell Team. This module contains the
+                        <strong>xADDomain, xADDomainController, xADUser, xWaitForDomain, and xADDomainTrust</strong> resources. These DSC Resources allow you to configure and manage Active Directory.&nbsp; Note: these resources do not presently install the RSAT tools.
+                    </p>
+                    <p>
+                        <strong>All of the resources in the DSC Resource Kit are provided AS IS, and are not supported through any Microsoft standard support program or service. The &quot;x&quot; in xActiveDirectory stands for experimental</strong>, which means that these resources will
+                        be <strong>fix forward</strong> and monitored by the module owner(s).
+                    </p>
+                    <p>Please leave comments, feature requests, and bug reports in the Q &amp; A tab for this module.</p>
+                    <p>
+                        If you would like to modify <strong>xActiveDirectory</strong> module, feel free. When modifying, please update the module name, resource friendly name, and MOF class name (instructions below). As specified in the license, you may copy or modify this resource
+                        as long as they are used on the Windows Platform.
+                    </p>
+                    <p>
+                        For more information about Windows PowerShell Desired State Configuration, check out the blog posts on the
+                        <a href="http://blogs.msdn.com/b/powershell/"><span style="color:#0000ff">PowerShell Blog</span></a> (<a href="http://blogs.msdn.com/b/powershell/archive/2013/11/01/configuration-in-a-devops-world-windows-powershell-desired-state-configuration.aspx"><span style="color:#0000ff">this</span></a>
+                        is a good starting point). There are also great community resources, such as <a href="http://powershell.org/wp/tag/dsc/">
+                            <span style="color:#0000ff">PowerShell.org</span>
+                        </a>, or <a href="http://www.powershellmagazine.com/tag/dsc/">
+                            <span style="color:#0000ff">PowerShell Magazine</span>
+                        </a>. For more information on the DSC Resource Kit, check out
+                        <a href="http://go.microsoft.com/fwlink/?LinkID=389546"><span style="color:#0000ff">this blog post</span></a>.
+                    </p>
+                    <h1 style="color:#2e74b5; font-family:Calibri Light; font-size:large">Installation</h1>
+                    <p>To install <strong>xActiveDirectory</strong> module</p>
+                    <ul style="list-style-type:disc; direction:ltr">
+                        <li>Unzip the content under $env:ProgramFiles\WindowsPowerShell\Modules folder </li>
+                    </ul>
+                    <p>To confirm installation:</p>
+                    <ul style="list-style-type:disc; direction:ltr">
+                        <li>
+                            Run <strong>Get-DSCResource</strong> to see that <strong>xADDomain, xADDomainController, xADUser, xWaitForDomain, and xADDomainTrust</strong> are among the DSC Resources listed
+                        </li>
+                    </ul>
+                    <h1 style="color:#2e74b5; font-family:Calibri Light; font-size:large">Requirements</h1>
+                    <p>
+                        This module requires the latest version of PowerShell (v4.0, which ships in Windows 8.1 or Windows Server 2012R2). To easily use PowerShell 4.0 on older operating systems,
+                        <a href="http://www.microsoft.com/en-us/download/details.aspx?id=40855"><span style="color:#0000ff">install WMF 4.0</span></a>. Please read the installation instructions that are present on both the download page and the release notes for WMF 4.0.
+                    </p>
+                    <h1 style="color:#2e74b5; font-family:Calibri Light; font-size:large">Description</h1>
+                    <p>
+                        The <strong>xActiveDirectory </strong>module contains the <strong>xADDomain, xADDomainController, xADUser, xWaitForDomain, and ADDomainTrust</strong> DSC Resources. These DSC Resources allow you to configure new domain, child domains,high availability domain
+                        controllers&nbsp;and establish cross-domain trusts.&nbsp; <span style="font-family:Calibri; font-size:medium">
+                            The <span style="font-family:Calibri; font-size:medium">
+                                <strong>xADDomain</strong>
+                            </span>resource is responsible to create new Active directory forest configuration or new Active directory domain configuration.&nbsp;<span style="font-family:Calibri; font-size:medium">
+                                The
+                                <span style="font-family:Calibri; font-size:medium">
+                                    <strong>xADDomainController </strong>
+                                </span>resource is responsible to install a domain controller in Active directory.&nbsp;
+                                <span style="font-family:Calibri; font-size:medium">
+                                    The <span style="font-family:Calibri; font-size:medium">
+                                        <strong>xADUser</strong>
+                                    </span>resource is responsible to modify or remove Active directory User.&nbsp;<span style="font-family:Calibri; font-size:medium">
+                                        The
+                                        <span style="font-family:Calibri; font-size:medium"><strong>xWaitForDomain</strong></span> resource is responsible to wait for new domain to setup. It's worth noting that the RSAT tools will not be installed when these resources are used to configure AD. The
+                                        <span style="font-family:Calibri; font-size:medium"><strong>xADDomainTrust</strong></span> resource is used to establish a cross-domain trust.&nbsp;<span style="font-family:Calibri; font-size:medium">
+                                            <br>
+                                        </span>
+                                    </span>
+                                </span>
+                            </span>
+                        </span>
+                    </p>
+                    <h1 style="color:#2e74b5; font-family:Calibri Light; font-size:large">Details</h1>
+                    <p><strong>xADDomain</strong> resource has following properties:</p>
+                    <ul style="list-style-type:disc; direction:ltr">
+                        <li>
+                            <strong>DomainName</strong>: <span>
+                                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
+                            </span>Name of the domain.&nbsp; If no parent name is specified, this is the fully qualified domain name for first domain in the forest.
+                        </li>
+                        <li>
+                            <strong>ParentDomainName</strong>:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Name of the parent domain.
+                        </li>
+                        <li>
+                            <strong>DomainAdministratorCredential</strong>:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Credentials used to query for domain existence. Note: These are not used during domain creation. ( AD sets the localadmin credentials as new domain administrator credentials
+                            during setup )<span style="font-family:Calibri; font-size:medium">
+                                <br>
+                            </span>
+                        </li>
+                        <li>
+                            <strong>SafemodeAdministratorPassword</strong>: <span>&nbsp;&nbsp;&nbsp; </span>
+                            <span style="font-family:Calibri; font-size:medium">
+                                Password for the administrator account when the computer is started in Safe Mode.
+                            </span>
+                        </li>
+                        <li>
+                            <strong>
+                                DnsDelegationCredential: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
+                            </strong><span style="font-family:Calibri; font-size:medium">Credential used for creating DNS delegation</span>
+                        </li>
+                        <li>
+                            <strong>
+                                DatabasePath: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            </strong><span style="font-family:Calibri; font-size:medium">Destination path for the AD database</span>
+                        </li>
+                        <li>
+                            <strong>
+                                LogPath: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            </strong><span style="font-family:Calibri; font-size:medium">Destination path for the AD log files</span>
+                        </li>
+                        <li>
+                            <strong>
+                                SysvolPath: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            </strong><span style="font-family:Calibri; font-size:medium">Destination path for the sysvol store</span>
+                        </li>                                                                        
+                    </ul>
+                    <p><strong>xADDomainController</strong> resource has following properties:</p>
+                    <ul style="list-style-type:disc; direction:ltr">
+                        <li>
+                            <strong>DomainName</strong>: <span>
+                                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            </span>The fully qualified domain name for the domain where the domain controller will be present
+                        </li>
+                        <li>
+                            <strong>DomainAdministratorCredential</strong>: <span>
+                                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            </span>Specifies the credential for the account used to install the domain controller
+                        </li>
+                        <li>
+                            <strong>SafemodeAdministratorPassword</strong>:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Password for the administrator account when the computer is started in Safe Mode.
+                        </li>
+                        <li>
+                            <strong>
+                                DatabasePath: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            </strong><span style="font-family:Calibri; font-size:medium">Destination path for the AD database</span>
+                        </li>
+                        <li>
+                            <strong>
+                                LogPath: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            </strong><span style="font-family:Calibri; font-size:medium">Destination path for the AD log files</span>
+                        </li>
+                        <li>
+                            <strong>
+                                SysvolPath: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            </strong><span style="font-family:Calibri; font-size:medium">Destination path for the sysvol store</span>
+                        </li>                                                                        
+                    </ul>
+                    </ul>
+                    <p><strong>xADUser</strong> resource has following properties:</p>
+                    <ul style="list-style-type:disc; direction:ltr">
+                        <li>
+                            <strong>Ensure</strong>: <span>
+                                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            </span>Specifies whether the given user is present or absent
+                        </li>
+                        <li>
+                            <strong>DomainName</strong>: <span>
+                                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            </span>Name of the domain to which the user will be added
+                        </li>
+                        <li>
+                            <strong>UserName</strong>:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            Name of the user
+                        </li>
+                        <li>
+                            <strong>Password</strong>: <span>
+                                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                                Password value for the account
+                            </span>
+                        </li>
+                        <li>
+                            <strong>DomainAdministratorCredential: &nbsp; &nbsp; &nbsp; &nbsp; </strong>User account credentials used to perform the task
+                        </li>
+                    </ul>
+                    <p><strong>xWaitForADDomain</strong> resource has following properties:</p>
+                    <ul style="list-style-type:disc; direction:ltr">
+                        <li>
+                            <strong>DomainName</strong>: <span>
+                                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            </span>Name of the domain to wait for
+                        </li>
+                        <li>
+                            <strong>RetryIntervalSec</strong>: <span>
+                                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            </span>Interval to check for the domain's existance
+                        </li>
+                        <li>
+                            <strong>RetryCount</strong>:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            Maximum number of retries to check for the domain's existance
+                        </li>
+                    </ul>
+                    <p><strong>xADDomainTrust</strong> resource has following properties:</p>
+                    <ul style="list-style-type:disc; direction:ltr">
+                        <li>
+                            <strong>Ensure:</strong> <span>
+                                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            </span>Specifies whether the domain trust is present or absent
+                        </li>
+                        <li>
+                            <strong>TargetDomainAdministratorCredential: </strong><span style="font-family:Calibri; font-size:medium">Credentials to authenticate to the target domain</span>
+                        </li>
+                        <li>
+                            <strong>TargetDomainName:</strong> <span>
+                                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
+                            </span>Name of the AD domain that is being trusted
+                        </li>
+                        <li>
+                            <strong>TrustType:</strong> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Type of trust
+                        </li>
+                        <li>
+                            <strong>TrustDirection:</strong> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
+                            &nbsp; &nbsp; Direction of trust, the values for which may be Bidirectional,Inbound, or Outbound
+                        </li>
+                        <li>
+                            <strong>SourceDomainName:</strong> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Name of the AD domain that is requesting the trust
+                        </li>
+                    </ul>
+                    <h1 style="color:#2e74b5; font-family:Calibri Light; font-size:large">Renaming Requirements</h1>
+                    <p>When making changes to these resources, we suggest the following practice:</p>
+                    <ol style="list-style-type:decimal; direction:ltr">
+                        <li>
+                            Update the following names by replacing MSFT with your company/community name and replacing the
+                            <strong>&quot;x&quot;</strong> with <strong>&quot;c&quot;</strong> (short for &quot;Community&quot;) or another prefix of your choice:
+                            <ul>
+                                <li>
+                                    <strong>Module name (ex: x<span style="font-family:Calibri; font-size:medium"><strong>ADDomain</strong></span></strong> becomes
+                                    <strong>c<span style="font-family:Calibri; font-size:medium"><strong>ADDomain</strong></span></strong>)
+                                </li>
+                                <li>
+                                    <strong>Resource folder (ex: MSFT_</strong><span style="font-family:Calibri; font-size:medium"><strong>xADDomain</strong></span> becomes
+                                    <strong>Contoso_x</strong><span style="font-family:Calibri; font-size:medium"><strong>ADDomain</strong></span>)
+                                </li>
+                                <li>
+                                    <strong>Resource Name (ex: MSFT_<span style="font-family:Calibri; font-size:medium"><strong>xADDomain</strong></span></strong> becomes
+                                    <strong>Contoso_c</strong><span style="font-family:Calibri; font-size:medium"><strong>ADDomain</strong></span>)
+                                </li>
+                                <li>
+                                    <strong>
+                                        Resource Friendly Name (ex: <span style="font-family:Calibri; font-size:medium">
+                                            <strong>xADDomain</strong>
+                                        </span>
+                                    </strong>becomes <strong>c<span style="font-family:Calibri; font-size:medium"><strong>ADDomain</strong></span></strong>)
+                                </li>
+                                <li>
+                                    <strong>MOF class name (ex: MSFT_x<span style="font-family:Calibri; font-size:medium"><strong>ADDomain</strong></span></strong> becomes
+                                    <strong>Contoso_c<span style="font-family:Calibri; font-size:medium"><strong>ADDomain</strong></span></strong>)
+                                </li>
+                                <li>
+                                    <strong>Filename for the &lt;resource&gt;.schema.mof (ex: MSFT_x<span style="font-family:Calibri; font-size:medium"><strong>ADDomain</strong></span></strong>.schema.mof becomes
+                                    <strong>Contoso_c<span style="font-family:Calibri; font-size:medium"><strong>ADDomain</strong></span></strong>.schema.mof)
+                                </li>
+                            </ul>
+                        </li>
+                        <li>Update module and metadata information in the module manifest </li>
+                        <li>Update any configuration that use these resources </li>
+                    </ol>
+                    <p>
+                        <em>
+                            We reserve resource and module names without prefixes (&quot;x&quot; or &quot;c&quot;) for future use (e.g. &quot;MSFT_ADDomain&quot; or &quot;MSFT_ADUser&quot;). If the next version of Windows Server ships with a &quot;ADDomain&quot; resource, we don't want to break any configurations that use any
+                            community modifications. Please keep a prefix such as &quot;c&quot; on all community modifications.
+                        </em>
+                    </p>
+                    <h1 style="color:#2e74b5; font-family:Calibri Light; font-size:large">Versions</h1>
+                    <p>1.0.0.0</p>
+                    <ul style="list-style-type:disc; direction:ltr">
+                        <li>
+                            Initial release with the following resources
+                            <ul style="list-style-type:circle">
+                                <li>
+                                    <span style="font-family:Calibri; font-size:medium">xADDomain, xADDomainController, xADUser, and xWaitForDomain</span>
+                                </li>
+                            </ul>
+                        </li>
+                    </ul>
+                    <p>2.0.0.0</p>
+                    <ul style="list-style-type:disc; direction:ltr">
+                        <li>
+                            Updated release, which added the resource
+                            <ul style="list-style-type:circle">
+                                <li><span style="font-family:Calibri; font-size:medium">xADDomainTrust</span> </li>
+                            </ul>
+                        </li>
+                    </ul>
+                    <p>2.1.0.0</p>
+                    <ul style="list-style-type:disc; direction:ltr">
+                        <li>
+                            Minor update: Get-TargetResource to use domain name instead of name
+                        </li>
+                    </ul>
+                    <p>2.2</p>
+                    <ul style="list-style-type:disc; direction:ltr">
+                        <li>
+                            Modified xAdDomain and xAdDomainController to support Ensure as Present / Absent, rather than True/False. Note: this may cause issues for existing scripts. Also corrected return value to be a hashtable in both resources. 
+                        </li>
+                    </ul>
+                    <p>2.3</p>
+                    <ul style="list-style-type:disc; direction:ltr">
+                        <li>
+                            Added properties to xAdDomain and xAdDomainController:
+                            <ul style="list-style-type:circle">
+                                <li>
+                                    <span style="font-family:Calibri; font-size:medium">DatabasePath</span>
+                                </li>
+                                <li>
+                                    <span style="font-family:Calibri; font-size:medium">LogPath</span>
+                                </li>
+                                <li>
+                                    <span style="font-family:Calibri; font-size:medium">SysvolPath</span>
+                                </li>
+                            </ul>
+                        </li>
+                    </ul>
+                    <h1 style="margin-bottom:0pt; font-family:Calibri Light; color:#2e74b5; font-size:large">
+                        Example: Create a highly available Domain using multiple domain controllers
+                    </h1>
+                    <p>
+                        In the following example configuration, a highly available domain is created by adding a domain controller to an existing domain.&nbsp; This example uses the xWaitForDomain resource to ensure that the domain is present before the second domain controller
+                        is added.
+                    </p>
+                    <div class="scriptcode">
+                        <div class="pluginEditHolder" plugincommand="mceScriptCode">
+                            <div class="title"><span>PowerShell</span></div>
+                            <div class="pluginLinkHolder"><span class="pluginEditHolderLink">Edit</span>|<span class="pluginRemoveHolderLink">Remove</span></div>
+                            <span class="hidden">powershell</span>
+<pre class="hidden"># A configuration to Create High Availability Domain Controller 
+configuration AssertHADC
+{
+   param
+    (
+        [Parameter(Mandatory)]
+        [pscredential]$safemodeAdministratorCred,
+        [Parameter(Mandatory)]
+        [pscredential]$domainCred,
+	    [Parameter(Mandatory)]
+        [pscredential]$DNSDelegationCred,
+	    [Parameter(Mandatory)]
+        [pscredential]$NewADUserCred
+    )
+    Import-DscResource -ModuleName xActiveDirectory
+    Node $AllNodes.Where{$_.Role -eq &quot;Primary DC&quot;}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = &quot;Present&quot;
+            Name = &quot;AD-Domain-Services&quot;
+        }
+        xADDomain FirstDS
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domainCred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DnsDelegationCredential = $DNSDelegationCred
+            DependsOn = &quot;[WindowsFeature]ADDSInstall&quot;
+        }
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.DomainName
+            DomainUserCredential = $domainCred
+            RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = &quot;[xADDomain]FirstDS&quot;
+        }
+        xADUser FirstUser
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domainCred
+            UserName = &quot;dummy&quot;
+            Password = $NewADUserCred
+            Ensure = &quot;Present&quot;
+            DependsOn = &quot;[xWaitForADDomain]DscForestWait&quot;
+        }
+    }
+    Node $AllNodes.Where{$_.Role -eq &quot;Replica DC&quot;}.Nodename
+    {
+        WindowsFeature ADDSInstall
+        {
+            Ensure = &quot;Present&quot;
+            Name = &quot;AD-Domain-Services&quot;
+        }
+        xWaitForADDomain DscForestWait
+        {
+            DomainName = $Node.DomainName
+            DomainUserCredential = $domainCred
+	        RetryCount = $Node.RetryCount
+            RetryIntervalSec = $Node.RetryIntervalSec
+            DependsOn = &quot;[WindowsFeature]ADDSInstall&quot;
+        }
+        xADDomainController SecondDC
+        {
+            DomainName = $Node.DomainName
+            DomainAdministratorCredential = $domainCred
+            SafemodeAdministratorPassword = $safemodeAdministratorCred
+            DnsDelegationCredential = $DNSDelegationCred
+            DependsOn = &quot;[xWaitForADDomain]DscForestWait&quot;
+        }
+    }
+}
+# Configuration Data for AD 
+$ConfigData = @{
+    AllNodes = @(
+        @{
+            Nodename = &quot;dsc-testNode1&quot;
+            Role = &quot;Primary DC&quot;
+            DomainName = &quot;dsc-test.contoso.com&quot;
+            CertificateFile = &quot;C:\publicKeys\targetNode.cer&quot;  
+            Thumbprint = &quot;AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8&quot; 
+	        RetryCount = 20 
+	        RetryIntervalSec = 30 
+        },
+        @{
+            Nodename = &quot;dsc-testNode2&quot;
+            Role = &quot;Replica DC&quot;
+            DomainName = &quot;dsc-test.contoso.com&quot;
+            CertificateFile = &quot;C:\publicKeys\targetNode.cer&quot;  
+            Thumbprint = &quot;AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8&quot; 
+	        RetryCount = 20 
+	        RetryIntervalSec = 30 
+        }
+    )
+}
+AssertHADC -configurationData $ConfigData `
+-safemodeAdministratorCred (Get-Credential -Message &quot;New Domain Safe Mode Admin Credentials&quot;) `
+-domainCred (Get-Credential -Message &quot;New Domain Admin Credentials&quot;) `
+-DNSDelegationCred (Get-Credential -Message &quot;Credentials to Setup DNS Delegation&quot;) `
+-NewADUserCred (Get-Credential -Message &quot;New AD User Credentials&quot;)
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName &quot;dsc-testNode1&quot; -Path $PSScriptRoot\AssertHADC `
+-Credential (Get-Credential -Message &quot;Local Admin Credentials on Remote Machine&quot;)
+Start-DscConfiguration -Wait -Force -Verbose -ComputerName &quot;dsc-testNode2&quot; -Path $PSScriptRoot\AssertHADC `
+-Credential (Get-Credential -Message &quot;Local Admin Credentials on Remote Machine&quot;)
+</pre>
+                            <div class="preview">
+<pre class="powershell"><span class="powerShell__com">#&nbsp;A&nbsp;configuration&nbsp;to&nbsp;Create&nbsp;High&nbsp;Availability&nbsp;Domain&nbsp;Controller&nbsp;</span>&nbsp;
+&nbsp;
+configuration&nbsp;AssertHADC&nbsp;
+{&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;<span class="powerShell__keyword">param</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;(&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Parameter(Mandatory)]&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pscredential]<span class="powerShell__variable">$safemodeAdministratorCred</span>,&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Parameter(Mandatory)]&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pscredential]<span class="powerShell__variable">$domainCred</span>,&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Parameter(Mandatory)]&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pscredential]<span class="powerShell__variable">$DNSDelegationCred</span>,&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Parameter(Mandatory)]&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pscredential]<span class="powerShell__variable">$NewADUserCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;)&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;Import<span class="powerShell__operator">-</span>DscResource&nbsp;<span class="powerShell__operator">-</span>ModuleName&nbsp;xActiveDirectory&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;Node&nbsp;<span class="powerShell__variable">$AllNodes</span>.Where{<span class="powerShell__variable">$_</span>.Role&nbsp;<span class="powerShell__operator">-</span>eq&nbsp;<span class="powerShell__string">&quot;Primary&nbsp;DC&quot;</span>}.Nodename&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WindowsFeature&nbsp;ADDSInstall&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Ensure&nbsp;=&nbsp;<span class="powerShell__string">&quot;Present&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Name&nbsp;=&nbsp;<span class="powerShell__string">&quot;AD-Domain-Services&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xADDomain&nbsp;FirstDS&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.DomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainAdministratorCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domainCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SafemodeAdministratorPassword&nbsp;=&nbsp;<span class="powerShell__variable">$safemodeAdministratorCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DnsDelegationCredential&nbsp;=&nbsp;<span class="powerShell__variable">$DNSDelegationCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[WindowsFeature]ADDSInstall&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xWaitForADDomain&nbsp;DscForestWait&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.DomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainUserCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domainCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryCount&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.RetryCount&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryIntervalSec&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.RetryIntervalSec&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[xADDomain]FirstDS&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xADUser&nbsp;FirstUser&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.DomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainAdministratorCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domainCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UserName&nbsp;=&nbsp;<span class="powerShell__string">&quot;dummy&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Password&nbsp;=&nbsp;<span class="powerShell__variable">$NewADUserCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Ensure&nbsp;=&nbsp;<span class="powerShell__string">&quot;Present&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[xWaitForADDomain]DscForestWait&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;Node&nbsp;<span class="powerShell__variable">$AllNodes</span>.Where{<span class="powerShell__variable">$_</span>.Role&nbsp;<span class="powerShell__operator">-</span>eq&nbsp;<span class="powerShell__string">&quot;Replica&nbsp;DC&quot;</span>}.Nodename&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WindowsFeature&nbsp;ADDSInstall&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Ensure&nbsp;=&nbsp;<span class="powerShell__string">&quot;Present&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Name&nbsp;=&nbsp;<span class="powerShell__string">&quot;AD-Domain-Services&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xWaitForADDomain&nbsp;DscForestWait&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.DomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainUserCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domainCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryCount&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.RetryCount&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryIntervalSec&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.RetryIntervalSec&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[WindowsFeature]ADDSInstall&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xADDomainController&nbsp;SecondDC&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.DomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainAdministratorCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domainCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SafemodeAdministratorPassword&nbsp;=&nbsp;<span class="powerShell__variable">$safemodeAdministratorCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DnsDelegationCredential&nbsp;=&nbsp;<span class="powerShell__variable">$DNSDelegationCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[xWaitForADDomain]DscForestWait&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+}&nbsp;
+&nbsp;
+<span class="powerShell__com">#&nbsp;Configuration&nbsp;Data&nbsp;for&nbsp;AD&nbsp;</span>&nbsp;
+&nbsp;
+<span class="powerShell__variable">$ConfigData</span>&nbsp;=&nbsp;@{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;AllNodes&nbsp;=&nbsp;@(&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Nodename&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-testNode1&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Role&nbsp;=&nbsp;<span class="powerShell__string">&quot;Primary&nbsp;DC&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-test.contoso.com&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CertificateFile&nbsp;=&nbsp;<span class="powerShell__string">&quot;C:\publicKeys\targetNode.cer&quot;</span>&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thumbprint&nbsp;=&nbsp;<span class="powerShell__string">&quot;AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8&quot;</span>&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryCount&nbsp;=&nbsp;20&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryIntervalSec&nbsp;=&nbsp;30&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Nodename&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-testNode2&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Role&nbsp;=&nbsp;<span class="powerShell__string">&quot;Replica&nbsp;DC&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-test.contoso.com&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CertificateFile&nbsp;=&nbsp;<span class="powerShell__string">&quot;C:\publicKeys\targetNode.cer&quot;</span>&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thumbprint&nbsp;=&nbsp;<span class="powerShell__string">&quot;AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8&quot;</span>&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryCount&nbsp;=&nbsp;20&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryIntervalSec&nbsp;=&nbsp;30&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;)&nbsp;
+}&nbsp;
+&nbsp;
+AssertHADC&nbsp;<span class="powerShell__operator">-</span>configurationData&nbsp;<span class="powerShell__variable">$ConfigData</span>&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>safemodeAdministratorCred&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;New&nbsp;Domain&nbsp;Safe&nbsp;Mode&nbsp;Admin&nbsp;Credentials&quot;</span>)&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>domainCred&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;New&nbsp;Domain&nbsp;Admin&nbsp;Credentials&quot;</span>)&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>DNSDelegationCred&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;Credentials&nbsp;to&nbsp;Setup&nbsp;DNS&nbsp;Delegation&quot;</span>)&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>NewADUserCred&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;New&nbsp;AD&nbsp;User&nbsp;Credentials&quot;</span>)&nbsp;
+&nbsp;
+Start<span class="powerShell__operator">-</span>DscConfiguration&nbsp;<span class="powerShell__operator">-</span>Wait&nbsp;<span class="powerShell__operator">-</span>Force&nbsp;<span class="powerShell__operator">-</span>Verbose&nbsp;<span class="powerShell__operator">-</span>ComputerName&nbsp;<span class="powerShell__string">&quot;dsc-testNode1&quot;</span>&nbsp;<span class="powerShell__operator">-</span>Path&nbsp;<span class="powerShell__variable">$PSScriptRoot</span>\AssertHADC&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>Credential&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;Local&nbsp;Admin&nbsp;Credentials&nbsp;on&nbsp;Remote&nbsp;Machine&quot;</span>)&nbsp;
+&nbsp;
+Start<span class="powerShell__operator">-</span>DscConfiguration&nbsp;<span class="powerShell__operator">-</span>Wait&nbsp;<span class="powerShell__operator">-</span>Force&nbsp;<span class="powerShell__operator">-</span>Verbose&nbsp;<span class="powerShell__operator">-</span>ComputerName&nbsp;<span class="powerShell__string">&quot;dsc-testNode2&quot;</span>&nbsp;<span class="powerShell__operator">-</span>Path&nbsp;<span class="powerShell__variable">$PSScriptRoot</span>\AssertHADC&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>Credential&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;Local&nbsp;Admin&nbsp;Credentials&nbsp;on&nbsp;Remote&nbsp;Machine&quot;</span>)&nbsp;
+</pre>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="endscriptcode">
+                        &nbsp;<span style="font-family:Calibri; font-size:medium">
+                            <h1 style="margin-bottom:0pt; font-family:Calibri Light; color:#2e74b5; font-size:large">
+                                Example: Create a child domain under a parent domain
+                            </h1>
+                            <p>In this example, we create a domain, and then create a child domain on another node.</p>
+                            <p>&nbsp;</p>
+                            <div class="scriptcode">
+                                <div class="pluginEditHolder" plugincommand="mceScriptCode">
+                                    <div class="title"><span>PowerShell</span></div>
+                                    <div class="pluginLinkHolder"><span class="pluginEditHolderLink">Edit</span>|<span class="pluginRemoveHolderLink">Remove</span></div>
+                                    <span class="hidden">powershell</span>
+                                    <div class="preview">
+<pre class="powershell"><span class="powerShell__com">#&nbsp;Configuration&nbsp;to&nbsp;Setup&nbsp;Parent&nbsp;Child&nbsp;Domains&nbsp;</span>&nbsp;
+&nbsp;
+configuration&nbsp;AssertParentChildDomains&nbsp;
+{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;<span class="powerShell__keyword">param</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;(&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Parameter(Mandatory)]&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pscredential]<span class="powerShell__variable">$safemodeAdministratorCred</span>,&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Parameter(Mandatory)]&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pscredential]<span class="powerShell__variable">$domainCred</span>,&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Parameter(Mandatory)]&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pscredential]<span class="powerShell__variable">$DNSDelegationCred</span>,&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Parameter(Mandatory)]&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pscredential]<span class="powerShell__variable">$NewADUserCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;)&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;Import<span class="powerShell__operator">-</span>DscResource&nbsp;<span class="powerShell__operator">-</span>ModuleName&nbsp;xActiveDirectory&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;Node&nbsp;<span class="powerShell__variable">$AllNodes</span>.Where{<span class="powerShell__variable">$_</span>.Role&nbsp;<span class="powerShell__operator">-</span>eq&nbsp;<span class="powerShell__string">&quot;Parent&nbsp;DC&quot;</span>}.Nodename&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WindowsFeature&nbsp;ADDSInstall&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Ensure&nbsp;=&nbsp;<span class="powerShell__string">&quot;Present&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Name&nbsp;=&nbsp;<span class="powerShell__string">&quot;AD-Domain-Services&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xADDomain&nbsp;FirstDS&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.DomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainAdministratorCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domainCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SafemodeAdministratorPassword&nbsp;=&nbsp;<span class="powerShell__variable">$safemodeAdministratorCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DnsDelegationCredential&nbsp;=&nbsp;<span class="powerShell__variable">$DNSDelegationCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[WindowsFeature]ADDSInstall&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xWaitForADDomain&nbsp;DscForestWait&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.DomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainUserCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domainCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryCount&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.RetryCount&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryIntervalSec&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.RetryIntervalSec&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[xADDomain]FirstDS&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xADUser&nbsp;FirstUser&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.DomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainAdministratorCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domaincred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UserName&nbsp;=&nbsp;<span class="powerShell__string">&quot;dummy&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Password&nbsp;=&nbsp;<span class="powerShell__variable">$NewADUserCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Ensure&nbsp;=&nbsp;<span class="powerShell__string">&quot;Present&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[xWaitForADDomain]DscForestWait&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;Node&nbsp;<span class="powerShell__variable">$AllNodes</span>.Where{<span class="powerShell__variable">$_</span>.Role&nbsp;<span class="powerShell__operator">-</span>eq&nbsp;<span class="powerShell__string">&quot;Child&nbsp;DC&quot;</span>}.Nodename&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WindowsFeature&nbsp;ADDSInstall&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Ensure&nbsp;=&nbsp;<span class="powerShell__string">&quot;Present&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Name&nbsp;=&nbsp;<span class="powerShell__string">&quot;AD-Domain-Services&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xWaitForADDomain&nbsp;DscForestWait&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.ParentDomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainUserCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domainCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryCount&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.RetryCount&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryIntervalSec&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.RetryIntervalSec&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[WindowsFeature]ADDSInstall&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xADDomain&nbsp;ChildDS&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.DomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ParentDomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.ParentDomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainAdministratorCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domainCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SafemodeAdministratorPassword&nbsp;=&nbsp;<span class="powerShell__variable">$safemodeAdministratorCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[xWaitForADDomain]DscForestWait&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+}&nbsp;
+&nbsp;
+<span class="powerShell__variable">$ConfigData</span>&nbsp;=&nbsp;@{&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;AllNodes&nbsp;=&nbsp;@(&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Nodename&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-testNode1&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Role&nbsp;=&nbsp;<span class="powerShell__string">&quot;Parent&nbsp;DC&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-test.contoso.com&quot;</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CertificateFile&nbsp;=&nbsp;<span class="powerShell__string">&quot;C:\publicKeys\targetNode.cer&quot;</span>&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thumbprint&nbsp;=&nbsp;<span class="powerShell__string">&quot;AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8&quot;</span>&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryCount&nbsp;=&nbsp;50&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryIntervalSec&nbsp;=&nbsp;30&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Nodename&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-testNode2&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Role&nbsp;=&nbsp;<span class="powerShell__string">&quot;Child&nbsp;DC&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-child&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ParentDomainName&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-test.contoso.com&quot;</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CertificateFile&nbsp;=&nbsp;<span class="powerShell__string">&quot;C:\publicKeys\targetNode.cer&quot;</span>&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thumbprint&nbsp;=&nbsp;<span class="powerShell__string">&quot;AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8&quot;</span>&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryCount&nbsp;=&nbsp;50&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryIntervalSec&nbsp;=&nbsp;30&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;)&nbsp;
+}&nbsp;
+&nbsp;
+AssertParentChildDomains&nbsp;<span class="powerShell__operator">-</span>configurationData&nbsp;<span class="powerShell__variable">$ConfigData</span>&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>safemodeAdministratorCred&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;New&nbsp;Domain&nbsp;Safe&nbsp;Mode&nbsp;Admin&nbsp;Credentials&quot;</span>)&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>domainCred&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;New&nbsp;Domain&nbsp;Admin&nbsp;Credentials&quot;</span>)&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>DNSDelegationCred&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;Credentials&nbsp;to&nbsp;Setup&nbsp;DNS&nbsp;Delegation&quot;</span>)&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>NewADUserCred&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;New&nbsp;AD&nbsp;User&nbsp;Credentials&quot;</span>)&nbsp;
+&nbsp;
+&nbsp;
+Start<span class="powerShell__operator">-</span>DscConfiguration&nbsp;<span class="powerShell__operator">-</span>Wait&nbsp;<span class="powerShell__operator">-</span>Force&nbsp;<span class="powerShell__operator">-</span>Verbose&nbsp;<span class="powerShell__operator">-</span>ComputerName&nbsp;<span class="powerShell__string">&quot;dsc-testNode1&quot;</span>&nbsp;<span class="powerShell__operator">-</span>Path&nbsp;<span class="powerShell__variable">$PSScriptRoot</span>\AssertParentChildDomains&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>Credential&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;Local&nbsp;Admin&nbsp;Credentials&nbsp;on&nbsp;Remote&nbsp;Machine&quot;</span>)&nbsp;
+Start<span class="powerShell__operator">-</span>DscConfiguration&nbsp;<span class="powerShell__operator">-</span>Wait&nbsp;<span class="powerShell__operator">-</span>Force&nbsp;<span class="powerShell__operator">-</span>Verbose&nbsp;<span class="powerShell__operator">-</span>ComputerName&nbsp;<span class="powerShell__string">&quot;dsc-testNode2&quot;</span>&nbsp;<span class="powerShell__operator">-</span>Path&nbsp;<span class="powerShell__variable">$PSScriptRoot</span>\AssertParentChildDomains&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>Credential&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;Local&nbsp;Admin&nbsp;Credentials&nbsp;on&nbsp;Remote&nbsp;Machine&quot;</span>)&nbsp;
+&nbsp;
+</pre>
+                                    </div>
+                                </div>
+                            </div>
+                        </span>
+                    </div>
+                    <div class="endscriptcode">
+                        &nbsp;<span style="font-family:Calibri; font-size:medium">
+                            <h1 style="margin-bottom:0pt; font-family:Calibri Light; color:#2e74b5; font-size:large">
+                                Example: Create a cross-domain trust
+                            </h1>
+                            <p>In this example, we setup one-way trust between two domains</p>
+                            <p>&nbsp;</p>
+                            <div class="scriptcode">
+                                <div class="pluginEditHolder" plugincommand="mceScriptCode">
+                                    <div class="title"><span>PowerShell</span></div>
+                                    <div class="pluginLinkHolder"><span class="pluginEditHolderLink">Edit</span>|<span class="pluginRemoveHolderLink">Remove</span></div>
+                                    <span class="hidden">powershell</span>
+<pre class="hidden"> 
+configuration Sample_xADDomainTrust_OneWayTrust
+{
+    param
+    (
+        [Parameter(Mandatory)]
+        [String]$SourceDomain,
+        [Parameter(Mandatory)]
+        [String]$TargetDomain,
+        
+        [Parameter(Mandatory)]
+        [PSCredential]$TargetDomainAdminCred,
+        [Parameter(Mandatory)]
+        [String]$TrustDirection
+    )
+    Import-DscResource -module xActiveDirectory
+    Node $AllNodes.Where{$_.Role -eq 'DomainController'}.NodeName
+    {
+        xADDomainTrust trust
+        {
+            Ensure                              = 'Present'
+            SourceDomainName                    = $SourceDomain
+            TargetDomainName                    = $TargetDomain
+            TargetDomainAdministratorCredential = $TargetDomainAdminCred
+            TrustDirection                      = $TrustDirection
+            TrustType                           = 'External'
+        }
+    }
+}
+$config = @{
+    AllNodes = @(
+        @{
+            NodeName      = 'localhost'
+            Role          = 'DomainController'
+            # Certificate Thumbprint that is used to encrypt/decrypt the credential
+            CertificateID = 'B9192121495A307A492A19F6344E8752B51AC4A6'
+        }
+    )
+}
+Sample_xADDomainTrust_OneWayTrust -configurationdata $config `
+                                  -SourceDomain safeharbor.contoso.com `
+                                  -TargetDomain corporate.contoso.com `
+                                  -TargetDomainAdminCred (get-credential) `
+                                  -TrustDirection 'Inbound'
+</pre>
+                                    <div class="preview">
+<pre class="powershell"><span class="powerShell__com">#&nbsp;Configuration&nbsp;to&nbsp;Setup&nbsp;Parent&nbsp;Child&nbsp;Domains&nbsp;</span>&nbsp;
+&nbsp;
+configuration&nbsp;AssertParentChildDomains&nbsp;
+{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;<span class="powerShell__keyword">param</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;(&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Parameter(Mandatory)]&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pscredential]<span class="powerShell__variable">$safemodeAdministratorCred</span>,&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Parameter(Mandatory)]&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pscredential]<span class="powerShell__variable">$domainCred</span>,&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Parameter(Mandatory)]&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pscredential]<span class="powerShell__variable">$DNSDelegationCred</span>,&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[Parameter(Mandatory)]&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[pscredential]<span class="powerShell__variable">$NewADUserCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;)&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;Import<span class="powerShell__operator">-</span>DscResource&nbsp;<span class="powerShell__operator">-</span>ModuleName&nbsp;xActiveDirectory&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;Node&nbsp;<span class="powerShell__variable">$AllNodes</span>.Where{<span class="powerShell__variable">$_</span>.Role&nbsp;<span class="powerShell__operator">-</span>eq&nbsp;<span class="powerShell__string">&quot;Parent&nbsp;DC&quot;</span>}.Nodename&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WindowsFeature&nbsp;ADDSInstall&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Ensure&nbsp;=&nbsp;<span class="powerShell__string">&quot;Present&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Name&nbsp;=&nbsp;<span class="powerShell__string">&quot;AD-Domain-Services&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xADDomain&nbsp;FirstDS&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.DomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainAdministratorCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domainCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SafemodeAdministratorPassword&nbsp;=&nbsp;<span class="powerShell__variable">$safemodeAdministratorCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DnsDelegationCredential&nbsp;=&nbsp;<span class="powerShell__variable">$DNSDelegationCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[WindowsFeature]ADDSInstall&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xWaitForADDomain&nbsp;DscForestWait&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.DomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainUserCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domainCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryCount&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.RetryCount&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryIntervalSec&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.RetryIntervalSec&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[xADDomain]FirstDS&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xADUser&nbsp;FirstUser&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.DomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainAdministratorCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domaincred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;UserName&nbsp;=&nbsp;<span class="powerShell__string">&quot;dummy&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Password&nbsp;=&nbsp;<span class="powerShell__variable">$NewADUserCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Ensure&nbsp;=&nbsp;<span class="powerShell__string">&quot;Present&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[xWaitForADDomain]DscForestWait&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;Node&nbsp;<span class="powerShell__variable">$AllNodes</span>.Where{<span class="powerShell__variable">$_</span>.Role&nbsp;<span class="powerShell__operator">-</span>eq&nbsp;<span class="powerShell__string">&quot;Child&nbsp;DC&quot;</span>}.Nodename&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WindowsFeature&nbsp;ADDSInstall&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Ensure&nbsp;=&nbsp;<span class="powerShell__string">&quot;Present&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Name&nbsp;=&nbsp;<span class="powerShell__string">&quot;AD-Domain-Services&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xWaitForADDomain&nbsp;DscForestWait&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.ParentDomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainUserCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domainCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryCount&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.RetryCount&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryIntervalSec&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.RetryIntervalSec&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[WindowsFeature]ADDSInstall&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;xADDomain&nbsp;ChildDS&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.DomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ParentDomainName&nbsp;=&nbsp;<span class="powerShell__variable">$Node</span>.ParentDomainName&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainAdministratorCredential&nbsp;=&nbsp;<span class="powerShell__variable">$domainCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SafemodeAdministratorPassword&nbsp;=&nbsp;<span class="powerShell__variable">$safemodeAdministratorCred</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DependsOn&nbsp;=&nbsp;<span class="powerShell__string">&quot;[xWaitForADDomain]DscForestWait&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+}&nbsp;
+&nbsp;
+<span class="powerShell__variable">$ConfigData</span>&nbsp;=&nbsp;@{&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;AllNodes&nbsp;=&nbsp;@(&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Nodename&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-testNode1&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Role&nbsp;=&nbsp;<span class="powerShell__string">&quot;Parent&nbsp;DC&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-test.contoso.com&quot;</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CertificateFile&nbsp;=&nbsp;<span class="powerShell__string">&quot;C:\publicKeys\targetNode.cer&quot;</span>&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thumbprint&nbsp;=&nbsp;<span class="powerShell__string">&quot;AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8&quot;</span>&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryCount&nbsp;=&nbsp;50&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryIntervalSec&nbsp;=&nbsp;30&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},&nbsp;
+&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@{&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Nodename&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-testNode2&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Role&nbsp;=&nbsp;<span class="powerShell__string">&quot;Child&nbsp;DC&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;DomainName&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-child&quot;</span>&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ParentDomainName&nbsp;=&nbsp;<span class="powerShell__string">&quot;dsc-test.contoso.com&quot;</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CertificateFile&nbsp;=&nbsp;<span class="powerShell__string">&quot;C:\publicKeys\targetNode.cer&quot;</span>&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Thumbprint&nbsp;=&nbsp;<span class="powerShell__string">&quot;AC23EA3A9E291A75757A556D0B71CBBF8C4F6FD8&quot;</span>&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryCount&nbsp;=&nbsp;50&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RetryIntervalSec&nbsp;=&nbsp;30&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;)&nbsp;
+}&nbsp;
+&nbsp;
+AssertParentChildDomains&nbsp;<span class="powerShell__operator">-</span>configurationData&nbsp;<span class="powerShell__variable">$ConfigData</span>&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>safemodeAdministratorCred&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;New&nbsp;Domain&nbsp;Safe&nbsp;Mode&nbsp;Admin&nbsp;Credentials&quot;</span>)&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>domainCred&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;New&nbsp;Domain&nbsp;Admin&nbsp;Credentials&quot;</span>)&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>DNSDelegationCred&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;Credentials&nbsp;to&nbsp;Setup&nbsp;DNS&nbsp;Delegation&quot;</span>)&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>NewADUserCred&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;New&nbsp;AD&nbsp;User&nbsp;Credentials&quot;</span>)&nbsp;
+&nbsp;
+&nbsp;
+Start<span class="powerShell__operator">-</span>DscConfiguration&nbsp;<span class="powerShell__operator">-</span>Wait&nbsp;<span class="powerShell__operator">-</span>Force&nbsp;<span class="powerShell__operator">-</span>Verbose&nbsp;<span class="powerShell__operator">-</span>ComputerName&nbsp;<span class="powerShell__string">&quot;dsc-testNode1&quot;</span>&nbsp;<span class="powerShell__operator">-</span>Path&nbsp;<span class="powerShell__variable">$PSScriptRoot</span>\AssertParentChildDomains&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>Credential&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;Local&nbsp;Admin&nbsp;Credentials&nbsp;on&nbsp;Remote&nbsp;Machine&quot;</span>)&nbsp;
+Start<span class="powerShell__operator">-</span>DscConfiguration&nbsp;<span class="powerShell__operator">-</span>Wait&nbsp;<span class="powerShell__operator">-</span>Force&nbsp;<span class="powerShell__operator">-</span>Verbose&nbsp;<span class="powerShell__operator">-</span>ComputerName&nbsp;<span class="powerShell__string">&quot;dsc-testNode2&quot;</span>&nbsp;<span class="powerShell__operator">-</span>Path&nbsp;<span class="powerShell__variable">$PSScriptRoot</span>\AssertParentChildDomains&nbsp;`&nbsp;
+<span class="powerShell__operator">-</span>Credential&nbsp;(<span class="powerShell__cmdlets">Get-Credential</span>&nbsp;<span class="powerShell__operator">-</span>Message&nbsp;<span class="powerShell__string">&quot;Local&nbsp;Admin&nbsp;Credentials&nbsp;on&nbsp;Remote&nbsp;Machine&quot;</span>)&nbsp;
+&nbsp;
+</pre>
+                                    </div>
+                                </div>
+                            </div>
+                        </span>
+                    </div>
+                </span>
+            </div>
+        </div>
+
+</body>
+</html>

+ 163 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/CommonResourceHelper.psm1

@@ -0,0 +1,163 @@
+<#
+    .SYNOPSIS
+        Tests if the current machine is a Nano server.
+#>
+function Test-IsNanoServer
+{
+    if (Test-Command -Name Get-ComputerInfo)
+    {
+        $computerInfo = Get-ComputerInfo
+
+        if ("Server" -eq $computerInfo.OsProductType `
+            -and "NanoServer" -eq $computerInfo.OsServerLevel)
+        {
+            return $true
+        }
+    }
+
+    return $false
+}
+
+<#
+    .SYNOPSIS
+        Tests if the the specified command is found.
+#>
+function Test-Command
+{
+    param
+    (
+        [String] $Name
+    )
+
+    return ($null -ne (Get-Command -Name $Name -ErrorAction Continue 2> $null))
+}
+
+<#
+    .SYNOPSIS
+        Creates and throws an invalid argument exception
+
+    .PARAMETER Message
+        The message explaining why this error is being thrown
+
+    .PARAMETER ArgumentName
+        The name of the invalid argument that is causing this error to be thrown
+#>
+function New-InvalidArgumentException
+{
+    [CmdletBinding()]
+    param
+    (
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [String]
+        $Message,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [String]
+        $ArgumentName
+    )
+
+    $argumentException = New-Object -TypeName 'ArgumentException' -ArgumentList @( $Message,
+        $ArgumentName )
+    $newObjectParams = @{
+        TypeName = 'System.Management.Automation.ErrorRecord'
+        ArgumentList = @( $argumentException, $ArgumentName, 'InvalidArgument', $null )
+    }
+    $errorRecord = New-Object @newObjectParams
+
+    throw $errorRecord
+}
+
+<#
+    .SYNOPSIS
+        Creates and throws an invalid operation exception
+
+    .PARAMETER Message
+        The message explaining why this error is being thrown
+
+    .PARAMETER ErrorRecord
+        The error record containing the exception that is causing this terminating error
+#>
+function New-InvalidOperationException
+{
+    [CmdletBinding()]
+    param
+    (
+        [ValidateNotNullOrEmpty()]
+        [String]
+        $Message,
+
+        [ValidateNotNull()]
+        [System.Management.Automation.ErrorRecord]
+        $ErrorRecord
+    )
+
+    if ($null -eq $Message)
+    {
+        $invalidOperationException = New-Object -TypeName 'InvalidOperationException'
+    }
+    elseif ($null -eq $ErrorRecord)
+    {
+        $invalidOperationException =
+            New-Object -TypeName 'InvalidOperationException' -ArgumentList @( $Message )
+    }
+    else
+    {
+        $invalidOperationException =
+            New-Object -TypeName 'InvalidOperationException' -ArgumentList @( $Message,
+                $ErrorRecord.Exception )
+    }
+
+    $newObjectParams = @{
+        TypeName = 'System.Management.Automation.ErrorRecord'
+        ArgumentList = @( $invalidOperationException.ToString(), 'MachineStateIncorrect',
+            'InvalidOperation', $null )
+    }
+    $errorRecordToThrow = New-Object @newObjectParams
+    throw $errorRecordToThrow
+}
+
+<#
+    .SYNOPSIS
+        Retrieves the localized string data based on the machine's culture.
+        Falls back to en-US strings if the machine's culture is not supported.
+
+    .PARAMETER ResourceName
+        The name of the resource as it appears before '.strings.psd1' of the localized string file.
+
+        For example:
+            For WindowsOptionalFeature: MSFT_xWindowsOptionalFeature
+            For Service: MSFT_xServiceResource
+            For Registry: MSFT_xRegistryResource
+#>
+function Get-LocalizedData
+{
+    [CmdletBinding()]
+    param
+    (
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [String]
+        $ResourceName
+    )
+
+    $resourceDirectory = (Join-Path -Path $PSScriptRoot -ChildPath $ResourceName)
+    $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath $PSUICulture
+
+    if (-not (Test-Path -Path $localizedStringFileLocation))
+    {
+        # Fallback to en-US
+        $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath 'en-US'
+    }
+
+    Import-LocalizedData `
+        -BindingVariable 'localizedData' `
+        -FileName "$ResourceName.strings.psd1" `
+        -BaseDirectory $localizedStringFileLocation
+
+    return $localizedData
+}
+
+Export-ModuleMember -Function @( 'Test-IsNanoServer', 'New-InvalidArgumentException',
+    'New-InvalidOperationException', 'Get-LocalizedData' )

+ 628 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsCertificationAuthority/MSFT_xAdcsCertificationAuthority.psm1

@@ -0,0 +1,628 @@
+# Suppressed as per PSSA Rule Severity guidelines for unit/integration tests:
+# https://github.com/PowerShell/DscResources/blob/master/PSSARuleSeverities.md
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
+param ()
+
+Import-Module -Name (Join-Path -Path (Split-Path $PSScriptRoot -Parent) `
+    -ChildPath 'CommonResourceHelper.psm1')
+
+# Localized messages for Write-Verbose statements in this resource
+$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xAdcsCertificationAuthority'
+
+<#
+    .SYNOPSIS
+        Returns an object containing the current state information for the ADCS CA on the server.
+    .PARAMETER CAType
+        Specifies the type of certification authority to install.
+    .PARAMETER Credential
+        To install an enterprise certification authority, the computer must be joined to an Active
+        Directory Domain Services domain and a user account that is a member of the Enterprise Admin
+        group is required. To install a standalone certification authority, the computer can be in a
+        workgroup or AD DS domain. If the computer is in a workgroup, a user account that is a member
+        of Administrators is required. If the computer is in an AD DS domain, a user account that is
+        a member of Domain Admins is required.
+    .PARAMETER Ensure
+        Specifies whether the Certificate Authority should be installed or uninstalled.
+    .PARAMETER CACommonName
+        Specifies the certification authority common name.
+    .PARAMETER CADistinguishedNameSuffix
+        Specifies the certification authority distinguished name suffix.
+    .PARAMETER CertFile
+        Specifies the file name of certification authority PKCS 12 formatted certificate file.
+    .PARAMETER CertFilePassword
+        Specifies the password for certification authority certificate file.
+    .PARAMETER CertificateID
+        Specifies the thumbprint or serial number of certification authority certificate.
+    .PARAMETER CryptoProviderName
+        The name of the cryptographic service provider or key storage provider that is used to generate
+        or store the private key for the CA.
+    .PARAMETER DatabaseDirectory
+        Specifies the folder location of the certification authority database.
+    .PARAMETER HashAlgorithmName
+        Specifies the signature hash algorithm used by the certification authority.
+    .PARAMETER IgnoreUnicode
+        Specifies that Unicode characters are allowed in certification authority name string.
+    .PARAMETER KeyContainerName
+        Specifies the name of an existing private key container.
+    .PARAMETER KeyLength
+        Specifies the bit length for new certification authority key.
+    .PARAMETER LogDirectory
+        Specifies the folder location of the certification authority database log.
+    .PARAMETER OutputCertRequestFile
+        Specifies the folder location for certificate request file.
+    .PARAMETER OverwriteExistingCAinDS
+        Specifies that the computer object in the Active Directory Domain Service domain should be
+        overwritten with the same computer name.
+    .PARAMETER OverwriteExistingDatabase
+        Specifies that the existing certification authority database should be overwritten.
+    .PARAMETER OverwriteExistingKey
+        Overwrite existing key container with the same name.
+    .PARAMETER ParentCA
+        Specifies the configuration string of the parent certification authority that will certify this
+        CA.
+    .PARAMETER ValidityPeriod
+        Specifies the validity period of the certification authority certificate in hours, days, weeks,
+        months or years. If this is a subordinate CA, do not use this parameter, because the validity
+        period is determined by the parent CA.
+    .PARAMETER ValidityPeriodUnits
+        Validity period of the certification authority certificate. If this is a subordinate CA, do not
+        specify this parameter because the validity period is determined by the parent CA.
+    .OUTPUTS
+        Returns an object containing the ADCS CA state information.
+#>
+Function Get-TargetResource
+{
+    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+    [CmdletBinding(SupportsShouldProcess = $true)]
+    [OutputType([System.Collections.Hashtable])]
+    param (
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('EnterpriseRootCA','EnterpriseSubordinateCA','StandaloneRootCA','StandaloneSubordinateCA')]
+        [string]
+        $CAType,
+
+        [Parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.Credential()]
+        $Credential,
+
+        [Parameter()]
+        [ValidateSet('Present','Absent')]
+        [string]
+        $Ensure = 'Present',
+
+        [Parameter()]
+        [string]
+        $CACommonName,
+
+        [Parameter()]
+        [string]
+        $CADistinguishedNameSuffix,
+
+        [Parameter()]
+        [string]
+        $CertFile,
+
+        [Parameter()]
+        [pscredential]
+        $CertFilePassword,
+
+        [Parameter()]
+        [string]
+        $CertificateID,
+
+        [Parameter()]
+        [string]
+        $CryptoProviderName,
+
+        [Parameter()]
+        [string]
+        $DatabaseDirectory,
+
+        [Parameter()]
+        [string]
+        $HashAlgorithmName,
+
+        [Parameter()]
+        [boolean]
+        $IgnoreUnicode,
+
+        [Parameter()]
+        [string]
+        $KeyContainerName,
+
+        [Parameter()]
+        [uint32]
+        $KeyLength,
+
+        [Parameter()]
+        [string]
+        $LogDirectory,
+
+        [Parameter()]
+        [string]
+        $OutputCertRequestFile,
+
+        [Parameter()]
+        [boolean]
+        $OverwriteExistingCAinDS,
+
+        [Parameter()]
+        [boolean]
+        $OverwriteExistingDatabase,
+
+        [Parameter()]
+        [boolean]
+        $OverwriteExistingKey,
+
+        [Parameter()]
+        [string]
+        $ParentCA,
+
+        [Parameter()]
+        [ValidateSet('Hours','Days','Months','Years')]
+        [string]
+        $ValidityPeriod,
+
+        [Parameter()]
+        [uint32]
+        $ValidityPeriodUnits
+    )
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.GettingAdcsCAStatusMessage -f $CAType)
+        ) -join '' )
+
+    $ADCSParams = @{} + $PSBoundParameters
+    $null = $ADCSParams.Remove('Ensure')
+    $null = $ADCSParams.Remove('Debug')
+    $null = $ADCSParams.Remove('ErrorAction')
+
+    try
+    {
+        $null = Install-AdcsCertificationAuthority @ADCSParams -WhatIf
+        # CA is not installed
+        $Ensure = 'Absent'
+    }
+    catch [Microsoft.CertificateServices.Deployment.Common.CA.CertificationAuthoritySetupException]
+    {
+        # CA is already installed
+        $Ensure = 'Present'
+    }
+    catch
+    {
+        # Something else went wrong
+        Throw $_
+    }
+
+    return @{
+        Ensure     = $Ensure
+        CAType     = $CAType
+        Credential = $Credential
+    }
+} # Function Get-TargetResource
+
+<#
+    .SYNOPSIS
+        Installs or uinstalls the ADCS CA from the server.
+    .PARAMETER CAType
+        Specifies the type of certification authority to install.
+    .PARAMETER Credential
+        To install an enterprise certification authority, the computer must be joined to an Active
+        Directory Domain Services domain and a user account that is a member of the Enterprise Admin
+        group is required. To install a standalone certification authority, the computer can be in a
+        workgroup or AD DS domain. If the computer is in a workgroup, a user account that is a member
+        of Administrators is required. If the computer is in an AD DS domain, a user account that is
+        a member of Domain Admins is required.
+    .PARAMETER Ensure
+        Specifies whether the Certificate Authority should be installed or uninstalled.
+    .PARAMETER CACommonName
+        Specifies the certification authority common name.
+    .PARAMETER CADistinguishedNameSuffix
+        Specifies the certification authority distinguished name suffix.
+    .PARAMETER CertFile
+        Specifies the file name of certification authority PKCS 12 formatted certificate file.
+    .PARAMETER CertFilePassword
+        Specifies the password for certification authority certificate file.
+    .PARAMETER CertificateID
+        Specifies the thumbprint or serial number of certification authority certificate.
+    .PARAMETER CryptoProviderName
+        The name of the cryptographic service provider or key storage provider that is used to generate
+        or store the private key for the CA.
+    .PARAMETER DatabaseDirectory
+        Specifies the folder location of the certification authority database.
+    .PARAMETER HashAlgorithmName
+        Specifies the signature hash algorithm used by the certification authority.
+    .PARAMETER IgnoreUnicode
+        Specifies that Unicode characters are allowed in certification authority name string.
+    .PARAMETER KeyContainerName
+        Specifies the name of an existing private key container.
+    .PARAMETER KeyLength
+        Specifies the bit length for new certification authority key.
+    .PARAMETER LogDirectory
+        Specifies the folder location of the certification authority database log.
+    .PARAMETER OutputCertRequestFile
+        Specifies the folder location for certificate request file.
+    .PARAMETER OverwriteExistingCAinDS
+        Specifies that the computer object in the Active Directory Domain Service domain should be
+        overwritten with the same computer name.
+    .PARAMETER OverwriteExistingDatabase
+        Specifies that the existing certification authority database should be overwritten.
+    .PARAMETER OverwriteExistingKey
+        Overwrite existing key container with the same name.
+    .PARAMETER ParentCA
+        Specifies the configuration string of the parent certification authority that will certify this
+        CA.
+    .PARAMETER ValidityPeriod
+        Specifies the validity period of the certification authority certificate in hours, days, weeks,
+        months or years. If this is a subordinate CA, do not use this parameter, because the validity
+        period is determined by the parent CA.
+    .PARAMETER ValidityPeriodUnits
+        Validity period of the certification authority certificate. If this is a subordinate CA, do not
+        specify this parameter because the validity period is determined by the parent CA.
+#>
+Function Set-TargetResource
+{
+    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+    [CmdletBinding()]
+    param (
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('EnterpriseRootCA','EnterpriseSubordinateCA','StandaloneRootCA','StandaloneSubordinateCA')]
+        [string]
+        $CAType,
+
+        [Parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.Credential()]
+        $Credential,
+
+        [Parameter()]
+        [ValidateSet('Present','Absent')]
+        [string]
+        $Ensure = 'Present',
+
+        [Parameter()]
+        [string]
+        $CACommonName,
+
+        [Parameter()]
+        [string]
+        $CADistinguishedNameSuffix,
+
+        [Parameter()]
+        [string]
+        $CertFile,
+
+        [Parameter()]
+        [pscredential]
+        $CertFilePassword,
+
+        [Parameter()]
+        [string]
+        $CertificateID,
+
+        [Parameter()]
+        [string]
+        $CryptoProviderName,
+
+        [Parameter()]
+        [string]
+        $DatabaseDirectory,
+
+        [Parameter()]
+        [string]
+        $HashAlgorithmName,
+
+        [Parameter()]
+        [boolean]
+        $IgnoreUnicode,
+
+        [Parameter()]
+        [string]
+        $KeyContainerName,
+
+        [Parameter()]
+        [uint32]
+        $KeyLength,
+
+        [Parameter()]
+        [string]
+        $LogDirectory,
+
+        [Parameter()]
+        [string]
+        $OutputCertRequestFile,
+
+        [Parameter()]
+        [boolean]
+        $OverwriteExistingCAinDS,
+
+        [Parameter()]
+        [boolean]
+        $OverwriteExistingDatabase,
+
+        [Parameter()]
+        [boolean]
+        $OverwriteExistingKey,
+
+        [Parameter()]
+        [string]
+        $ParentCA,
+
+        [Parameter()]
+        [ValidateSet('Hours','Days','Months','Years')]
+        [string]
+        $ValidityPeriod,
+
+        [Parameter()]
+        [uint32]
+        $ValidityPeriodUnits
+    )
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.SettingAdcsCAStatusMessage -f $CAType)
+        ) -join '' )
+
+    $ADCSParams = @{} + $PSBoundParameters
+    $null = $ADCSParams.Remove('Ensure')
+    $null = $ADCSParams.Remove('Debug')
+    $null = $ADCSParams.Remove('ErrorAction')
+
+    switch ($Ensure)
+    {
+        'Present'
+        {
+            Write-Verbose -Message ( @(
+                    "$($MyInvocation.MyCommand): "
+                    $($LocalizedData.InstallingAdcsCAMessage -f $CAType)
+                ) -join '' )
+
+            (Install-AdcsCertificationAuthority @ADCSParams -Force).ErrorString
+        }
+        'Absent'
+        {
+            Write-Verbose -Message ( @(
+                    "$($MyInvocation.MyCommand): "
+                    $($LocalizedData.UninstallingAdcsCAMessage -f $CAType)
+                ) -join '' )
+
+            (Uninstall-AdcsCertificationAuthority -Force).ErrorString
+        }
+    } # switch
+} # Function Set-TargetResource
+
+<#
+    .SYNOPSIS
+        Tests is the ADCS CA is in the desired state.
+    .PARAMETER CAType
+        Specifies the type of certification authority to install.
+    .PARAMETER Credential
+        To install an enterprise certification authority, the computer must be joined to an Active
+        Directory Domain Services domain and a user account that is a member of the Enterprise Admin
+        group is required. To install a standalone certification authority, the computer can be in a
+        workgroup or AD DS domain. If the computer is in a workgroup, a user account that is a member
+        of Administrators is required. If the computer is in an AD DS domain, a user account that is
+        a member of Domain Admins is required.
+    .PARAMETER Ensure
+        Specifies whether the Certificate Authority should be installed or uninstalled.
+    .PARAMETER CACommonName
+        Specifies the certification authority common name.
+    .PARAMETER CADistinguishedNameSuffix
+        Specifies the certification authority distinguished name suffix.
+    .PARAMETER CertFile
+        Specifies the file name of certification authority PKCS 12 formatted certificate file.
+    .PARAMETER CertFilePassword
+        Specifies the password for certification authority certificate file.
+    .PARAMETER CertificateID
+        Specifies the thumbprint or serial number of certification authority certificate.
+    .PARAMETER CryptoProviderName
+        The name of the cryptographic service provider or key storage provider that is used to generate
+        or store the private key for the CA.
+    .PARAMETER DatabaseDirectory
+        Specifies the folder location of the certification authority database.
+    .PARAMETER HashAlgorithmName
+        Specifies the signature hash algorithm used by the certification authority.
+    .PARAMETER IgnoreUnicode
+        Specifies that Unicode characters are allowed in certification authority name string.
+    .PARAMETER KeyContainerName
+        Specifies the name of an existing private key container.
+    .PARAMETER KeyLength
+        Specifies the bit length for new certification authority key.
+    .PARAMETER LogDirectory
+        Specifies the folder location of the certification authority database log.
+    .PARAMETER OutputCertRequestFile
+        Specifies the folder location for certificate request file.
+    .PARAMETER OverwriteExistingCAinDS
+        Specifies that the computer object in the Active Directory Domain Service domain should be
+        overwritten with the same computer name.
+    .PARAMETER OverwriteExistingDatabase
+        Specifies that the existing certification authority database should be overwritten.
+    .PARAMETER OverwriteExistingKey
+        Overwrite existing key container with the same name.
+    .PARAMETER ParentCA
+        Specifies the configuration string of the parent certification authority that will certify this
+        CA.
+    .PARAMETER ValidityPeriod
+        Specifies the validity period of the certification authority certificate in hours, days, weeks,
+        months or years. If this is a subordinate CA, do not use this parameter, because the validity
+        period is determined by the parent CA.
+    .PARAMETER ValidityPeriodUnits
+        Validity period of the certification authority certificate. If this is a subordinate CA, do not
+        specify this parameter because the validity period is determined by the parent CA.
+    .OUTPUTS
+        Returns true if the ADCS CA is in the desired state.
+#>
+Function Test-TargetResource
+{
+    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param (
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('EnterpriseRootCA','EnterpriseSubordinateCA','StandaloneRootCA','StandaloneSubordinateCA')]
+        [string]
+        $CAType,
+
+        [Parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.Credential()]
+        $Credential,
+
+        [Parameter()]
+        [ValidateSet('Present','Absent')]
+        [string]
+        $Ensure = 'Present',
+
+        [Parameter()]
+        [string]
+        $CACommonName,
+
+        [Parameter()]
+        [string]
+        $CADistinguishedNameSuffix,
+
+        [Parameter()]
+        [string]
+        $CertFile,
+
+        [Parameter()]
+        [pscredential]
+        $CertFilePassword,
+
+        [Parameter()]
+        [string]
+        $CertificateID,
+
+        [Parameter()]
+        [string]
+        $CryptoProviderName,
+
+        [Parameter()]
+        [string]
+        $DatabaseDirectory,
+
+        [Parameter()]
+        [string]
+        $HashAlgorithmName,
+
+        [Parameter()]
+        [boolean]
+        $IgnoreUnicode,
+
+        [Parameter()]
+        [string]
+        $KeyContainerName,
+
+        [Parameter()]
+        [uint32]
+        $KeyLength,
+
+        [Parameter()]
+        [string]
+        $LogDirectory,
+
+        [Parameter()]
+        [string]
+        $OutputCertRequestFile,
+
+        [Parameter()]
+        [boolean]
+        $OverwriteExistingCAinDS,
+
+        [Parameter()]
+        [boolean]
+        $OverwriteExistingDatabase,
+
+        [Parameter()]
+        [boolean]
+        $OverwriteExistingKey,
+
+        [Parameter()]
+        [string]
+        $ParentCA,
+
+        [Parameter()]
+        [ValidateSet('Hours','Days','Months','Years')]
+        [string]
+        $ValidityPeriod,
+
+        [Parameter()]
+        [uint32]
+        $ValidityPeriodUnits
+    )
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.TestingAdcsCAStatusMessage -f $CAType)
+        ) -join '' )
+
+    $ADCSParams = @{} + $PSBoundParameters
+    $null = $ADCSParams.Remove('Ensure')
+    $null = $ADCSParams.Remove('Debug')
+    $null = $ADCSParams.Remove('ErrorAction')
+
+    try
+    {
+        $null = Install-AdcsCertificationAuthority @ADCSParams -WhatIf
+        # CA is not installed
+        switch ($Ensure)
+        {
+            'Present'
+            {
+                # CA is not installed but should be - change required
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.AdcsCANotInstalledButShouldBeMessage -f $CAType)
+                    ) -join '' )
+
+                return $false
+            }
+            'Absent'
+            {
+                # CA is not installed and should not be - change not required
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.AdcsCANotInstalledAndShouldNotBeMessage -f $CAType)
+                    ) -join '' )
+
+                return $true
+            }
+        } # switch
+    }
+    catch [Microsoft.CertificateServices.Deployment.Common.CA.CertificationAuthoritySetupException]
+    {
+        # CA is already installed
+        switch ($Ensure)
+        {
+            'Present'
+            {
+                # CA is installed and should be - change not required
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.AdcsCAInstalledAndShouldBeMessage -f $CAType)
+                    ) -join '' )
+
+                return $true
+            }
+            'Absent'
+            {
+                # CA is installed and should not be - change required
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.AdcsCAInstalledButShouldNotBeMessage -f $CAType)
+                    ) -join '' )
+
+                return $false
+            }
+        } # switch
+    }
+    catch
+    {
+        # Something else went wrong
+        Throw $_
+    } # try
+} # Function Test-TargetResource
+
+Export-ModuleMember -Function *-TargetResource

+ 27 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsCertificationAuthority/MSFT_xAdcsCertificationAuthority.schema.mof

@@ -0,0 +1,27 @@
+[ClassVersion("0.1.0.0"), FriendlyName("xAdcsCertificationAuthority")]
+class MSFT_xAdcsCertificationAuthority : OMI_BaseResource
+{
+    [key, Description("Specifies the type of certification authority to install. The possible values are EnterpriseRootCA, EnterpriseSubordinateCA, StandaloneRootCA, or StandaloneSubordinateCA."), ValueMap{"EnterpriseRootCA","EnterpriseSubordinateCA","StandaloneRootCA","StandaloneSubordinateCA"}, Values{"EnterpriseRootCA","EnterpriseSubordinateCA","StandaloneRootCA","StandaloneSubordinateCA"}] String CAType;
+    [Required, Description("To install an enterprise certification authority, the computer must be joined to an Active Directory Domain Services domain and a user account that is a member of the Enterprise Admin group is required. To install a standalone certification authority, the computer can be in a workgroup or AD DS domain. If the computer is in a workgroup, a user account that is a member of Administrators is required. If the computer is in an AD DS domain, a user account that is a member of Domain Admins is required."), EmbeddedInstance("MSFT_Credential")] String Credential;
+    [write, Description("Specifies whether the Certificate Authority should be installed or uninstalled."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
+    [write, Description("Specifies the certification authority common name.")] String CACommonName;
+    [write, Description("Specifies the certification authority distinguished name suffix.")] String CADistinguishedNameSuffix;
+    [write, Description("Specifies the file name of certification authority PKCS 12 formatted certificate file.")] String CertFile;
+    [write, Description("Specifies the password for certification authority certificate file."), EmbeddedInstance("MSFT_Credential")] String CertFilePassword;
+    [write, Description("Specifies the thumbprint or serial number of certification authority certificate.")] String CertificateID;
+    [write, Description("The name of the cryptographic service provider or key storage provider that is used to generate or store the private key for the CA.")] String CryptoProviderName;
+    [write, Description("Specifies the folder location of the certification authority database.")] String DatabaseDirectory;
+    [write, Description("Specifies the signature hash algorithm used by the certification authority.")] String HashAlgorithmName;
+    [write, Description("Specifies that Unicode characters are allowed in certification authority name string.")] Boolean IgnoreUnicode;
+    [write, Description("Specifies the name of an existing private key container.")] String KeyContainerName;
+    [write, Description("Specifies the bit length for new certification authority key.")] Uint32 KeyLength;
+    [write, Description("Specifies the folder location of the certification authority database log.")] String LogDirectory;
+    [write, Description("Specifies the folder location for certificate request file.")] String OutputCertRequestFile;
+    [write, Description("Specifies that the computer object in the Active Directory Domain Service domain should be overwritten with the same computer name.")] Boolean OverwriteExistingCAinDS;
+    [write, Description("Specifies that the existing certification authority database should be overwritten.")] Boolean OverwriteExistingDatabase;
+    [write, Description("Overwrite existing key container with the same name")] Boolean OverwriteExistingKey;
+    [write, Description("Specifies the configuration string of the parent certification authority that will certify this CA.")] String ParentCA;
+    [write, Description("Specifies the validity period of the certification authority certificate in hours, days, weeks, months or years. If this is a subordinate CA, do not use this parameter, because the validity period is determined by the parent CA."), ValueMap{"Hours","Days","Months","Years"}, Values{"Hours","Days","Months","Years"}] String ValidityPeriod;
+    [write, Description("Validity period of the certification authority certificate. If this is a subordinate CA, do not specify this parameter because the validity period is determined by the parent CA.")] Uint32 ValidityPeriodUnits;
+};
+

+ 13 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsCertificationAuthority/en-us/MSFT_xAdcsCertificationAuthority.strings.psd1

@@ -0,0 +1,13 @@
+# Localized resources for MSFT_xAdcsCertificationAuthority
+
+ConvertFrom-StringData @'
+    GettingAdcsCAStatusMessage = Getting ADCS {0} Status.
+    TestingAdcsCAStatusMessage = Testing ADCS {0} Status.
+    AdcsCAInstalledButShouldNotBeMessage = ADCS {0} is installed but should not be. Change required.
+    AdcsCAInstalledAndShouldBeMessage = ADCS {0} is installed and should be. Change not required.
+    AdcsCANotInstalledButShouldBeMessage = ADCS {0} is not installed but should be. Change required.
+    AdcsCANotInstalledAndShouldNotBeMessage = ADCS {0} is not installed and should not be. Change not required.
+    SettingAdcsCAStatusMessage = Setting ADCS {0} Status.
+    InstallingAdcsCAMessage = Installing ADCS {0}.
+    UninstallingAdcsCAMessage = Uninstalling ADCS {0}.
+'@

+ 262 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsOnlineResponder/MSFT_xAdcsOnlineResponder.psm1

@@ -0,0 +1,262 @@
+# Suppressed as per PSSA Rule Severity guidelines for unit/integration tests:
+# https://github.com/PowerShell/DscResources/blob/master/PSSARuleSeverities.md
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
+param ()
+
+Import-Module -Name (Join-Path -Path (Split-Path $PSScriptRoot -Parent) `
+    -ChildPath 'CommonResourceHelper.psm1')
+
+# Localized messages for Write-Verbose statements in this resource
+$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xAdcsOnlineResponder'
+
+<#
+    .SYNOPSIS
+        Returns an object containing the current state information for the ADCS Online Responder.
+    .PARAMETER IsSingleInstance
+        Specifies the resource is a single instance, the value must be 'Yes'.
+    .PARAMETER Credential
+        If the Online Responder service is configured to use Standalone certification authority,
+        then an account that is a member of the local Administrators on the CA is required. If
+        the Online Responder service is configured to use an Enterprise CA, then an account that
+        is a member of Domain Admins is required.
+    .PARAMETER Ensure
+        Specifies whether the Online Responder feature should be installed or uninstalled.
+    .OUTPUTS
+        Returns an object containing the ADCS Online Responder state information.
+#>
+Function Get-TargetResource
+{
+    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+    [CmdletBinding(SupportsShouldProcess = $true)]
+    [OutputType([System.Collections.Hashtable])]
+    param(
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('Yes')]
+        [String]
+        $IsSingleInstance,
+
+        [Parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.Credential()]
+        $Credential,
+
+        [Parameter()]
+        [ValidateSet('Present','Absent')]
+        [string]
+        $Ensure = 'Present'
+    )
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.GettingAdcsOnlineResponderStatusMessage)
+        ) -join '' )
+
+    $ADCSParams = @{} + $PSBoundParameters
+    $null = $ADCSParams.Remove('IsSingleInstance')
+    $null = $ADCSParams.Remove('Ensure')
+    $null = $ADCSParams.Remove('Debug')
+    $null = $ADCSParams.Remove('ErrorAction')
+
+    try
+    {
+        $null = Install-AdcsOnlineResponder @ADCSParams -WhatIf
+        # CA is not installed
+        $Ensure = 'Absent'
+    }
+    catch [Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException]
+    {
+        # CA is already installed
+        $Ensure = 'Present'
+    }
+    catch
+    {
+        # Something else went wrong
+        Throw $_
+    }
+
+    return @{
+        Ensure     = $Ensure
+        CAType     = $CAType
+        Credential = $Credential
+    }
+} # Function Get-TargetResource
+
+<#
+    .SYNOPSIS
+        Installs or uinstalls the ADCS Online Responder from the server.
+    .PARAMETER IsSingleInstance
+        Specifies the resource is a single instance, the value must be 'Yes'.
+    .PARAMETER Credential
+        If the Online Responder service is configured to use Standalone certification authority,
+        then an account that is a member of the local Administrators on the CA is required. If
+        the Online Responder service is configured to use an Enterprise CA, then an account that
+        is a member of Domain Admins is required.
+    .PARAMETER Ensure
+        Specifies whether the Online Responder feature should be installed or uninstalled.
+#>
+Function Set-TargetResource
+{
+    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+    [CmdletBinding()]
+    param(
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('Yes')]
+        [String]
+        $IsSingleInstance,
+
+        [Parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.Credential()]
+        $Credential,
+
+        [Parameter()]
+        [ValidateSet('Present','Absent')]
+        [string]
+        $Ensure = 'Present'
+    )
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.SettingAdcsOnlineResponderStatusMessage)
+        ) -join '' )
+
+    $ADCSParams = @{} + $PSBoundParameters
+    $null = $ADCSParams.Remove('IsSingleInstance')
+    $null = $ADCSParams.Remove('Ensure')
+    $null = $ADCSParams.Remove('Debug')
+    $null = $ADCSParams.Remove('ErrorAction')
+
+    switch ($Ensure)
+    {
+        'Present'
+        {
+            Write-Verbose -Message ( @(
+                    "$($MyInvocation.MyCommand): "
+                    $($LocalizedData.InstallingAdcsOnlineResponderMessage)
+                ) -join '' )
+
+            (Install-AdcsOnlineResponder @ADCSParams -Force).ErrorString
+        }
+        'Absent'
+        {
+            Write-Verbose -Message ( @(
+                    "$($MyInvocation.MyCommand): "
+                    $($LocalizedData.UninstallingAdcsOnlineResponderMessage)
+                ) -join '' )
+
+            (Uninstall-AdcsOnlineResponder -Force).ErrorString
+        }
+    } # switch
+} # Function Set-TargetResource
+
+<#
+    .SYNOPSIS
+        Tests is the ADCS Online Responder is in the desired state.
+    .PARAMETER IsSingleInstance
+        Specifies the resource is a single instance, the value must be 'Yes'.
+    .PARAMETER Credential
+        If the Online Responder service is configured to use Standalone certification authority,
+        then an account that is a member of the local Administrators on the CA is required. If
+        the Online Responder service is configured to use an Enterprise CA, then an account that
+        is a member of Domain Admins is required.
+    .PARAMETER Ensure
+        Specifies whether the Online Responder feature should be installed or uninstalled.
+    .OUTPUTS
+        Returns true if the ADCS Online Responder is in the desired state.
+#>
+Function Test-TargetResource
+{
+    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param(
+        [parameter(Mandatory = $true)]
+        [ValidateSet('Yes')]
+        [String]
+        $IsSingleInstance,
+
+        [Parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.Credential()]
+        $Credential,
+
+        [Parameter()]
+        [ValidateSet('Present','Absent')]
+        [string]
+        $Ensure = 'Present'
+    )
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.TestingAdcsOnlineResponderStatusMessage -f $CAType)
+        ) -join '' )
+
+    $ADCSParams = @{} + $PSBoundParameters
+    $null = $ADCSParams.Remove('IsSingleInstance')
+    $null = $ADCSParams.Remove('Ensure')
+    $null = $ADCSParams.Remove('Debug')
+    $null = $ADCSParams.Remove('ErrorAction')
+
+    try
+    {
+        $null = Install-AdcsOnlineResponder @ADCSParams -WhatIf
+        # Online Responder is not installed
+        switch ($Ensure)
+        {
+            'Present'
+            {
+                # Online Responder is not installed but should be - change required
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.AdcsOnlineResponderNotInstalledButShouldBeMessage)
+                    ) -join '' )
+
+                return $false
+            }
+            'Absent'
+            {
+                # Online Responder is not installed and should not be - change not required
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.AdcsOnlineResponderNotInstalledAndShouldNotBeMessage)
+                    ) -join '' )
+
+                return $true
+            }
+        } # switch
+    }
+    catch [Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException]
+    {
+        # Online Responder is already installed
+        switch ($Ensure)
+        {
+            'Present'
+            {
+                # Online Responder is installed and should be - change not required
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.AdcsOnlineResponderInstalledAndShouldBeMessage)
+                    ) -join '' )
+
+                return $true
+            }
+            'Absent'
+            {
+                # Online Responder is installed and should not be - change required
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.AdcsOnlineResponderInstalledButShouldNotBeMessage)
+                    ) -join '' )
+
+                return $false
+            }
+        } # switch
+    }
+    catch
+    {
+        # Something else went wrong
+        Throw $_
+    } # try
+} # Function Test-TargetResource
+
+Export-ModuleMember -Function *-TargetResource

+ 7 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsOnlineResponder/MSFT_xAdcsOnlineResponder.schema.mof

@@ -0,0 +1,7 @@
+[ClassVersion("0.1.0.0"), FriendlyName("xAdcsOnlineResponder")]
+class MSFT_xAdcsOnlineResponder : OMI_BaseResource
+{
+    [Key, Description("Specifies the resource is a single instance, the value must be 'Yes'"), ValueMap{"Yes"}, Values{"Yes"}] String IsSingleInstance;
+    [Required, Description("If the Online Responder service is configured to use Standalone certification authority, then an account that is a member of the local Administrators on the CA is required. If the Online Responder service is configured to use an Enterprise CA, then an account that is a member of Domain Admins is required."), EmbeddedInstance("MSFT_Credential")] String Credential;
+    [Write, Description("Specifies whether the Online Responder feature should be installed or uninstalled."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
+};

+ 13 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsOnlineResponder/en-us/MSFT_xAdcsOnlineResponder.strings.psd1

@@ -0,0 +1,13 @@
+# Localized resources for MSFT_xAdcsOnlineResponder
+
+ConvertFrom-StringData @'
+    GettingAdcsOnlineResponderStatusMessage = Getting ADCS Online Responder Status.
+    TestingAdcsOnlineResponderStatusMessage = Testing ADCS Online Responder Status.
+    AdcsOnlineResponderInstalledButShouldNotBeMessage = ADCS Online Responder is installed but should not be. Change required.
+    AdcsOnlineResponderInstalledAndShouldBeMessage = ADCS Online Responder is installed and should be. Change not required.
+    AdcsOnlineResponderNotInstalledButShouldBeMessage = ADCS Online Responder is not installed but should be. Change required.
+    AdcsOnlineResponderNotInstalledAndShouldNotBeMessage = ADCS Online Responder is not installed and should not be. Change not required.
+    SettingAdcsOnlineResponderStatusMessage = Setting ADCS Online Responder Status.
+    InstallingAdcsOnlineResponderMessage = Installing ADCS Online Responder.
+    UninstallingAdcsOnlineResponderMessage = Uninstalling ADCS Online Responder.
+'@

+ 281 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsWebEnrollment/MSFT_xAdcsWebEnrollment.psm1

@@ -0,0 +1,281 @@
+# Suppressed as per PSSA Rule Severity guidelines for unit/integration tests:
+# https://github.com/PowerShell/DscResources/blob/master/PSSARuleSeverities.md
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
+param ()
+
+Import-Module -Name (Join-Path -Path (Split-Path $PSScriptRoot -Parent) `
+    -ChildPath 'CommonResourceHelper.psm1')
+
+# Localized messages for Write-Verbose statements in this resource
+$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xAdcsWebEnrollment'
+
+<#
+    .SYNOPSIS
+        Returns an object containing the current state information for the ADCS Web Enrollment.
+    .PARAMETER IsSingleInstance
+        Specifies the resource is a single instance, the value must be 'Yes'.
+    .PARAMETER CAConfig
+        CAConfig parameter string. Do not specify this if there is a local CA installed.
+    .PARAMETER Credential
+        If the Web Enrollment service is configured to use Standalone certification authority, then
+        an account that is a member of the local Administrators on the CA is required. If the
+        Web Enrollment service is configured to use an Enterprise CA, then an account that is a
+        member of Domain Admins is required.
+    .PARAMETER Ensure
+        Specifies whether the Web Enrollment feature should be installed or uninstalled.
+    .OUTPUTS
+        Returns an object containing the ADCS Web Enrollment state information.
+#>
+Function Get-TargetResource
+{
+    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+    [CmdletBinding(SupportsShouldProcess = $true)]
+    [OutputType([System.Collections.Hashtable])]
+    param(
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('Yes')]
+        [String]
+        $IsSingleInstance,
+
+        [Parameter()]
+        [String]
+        $CAConfig,
+
+        [Parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.Credential()]
+        $Credential,
+
+        [Parameter()]
+        [ValidateSet('Present','Absent')]
+        [String]
+        $Ensure = 'Present'
+    )
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.GettingAdcsWebEnrollmentStatusMessage)
+        ) -join '' )
+
+    $ADCSParams = @{} + $PSBoundParameters
+    $null = $ADCSParams.Remove('IsSingleInstance')
+    $null = $ADCSParams.Remove('Ensure')
+    $null = $ADCSParams.Remove('Debug')
+    $null = $ADCSParams.Remove('ErrorAction')
+
+    try
+    {
+        $null = Install-AdcsWebEnrollment @ADCSParams -WhatIf
+        # CA is not installed
+        $Ensure = 'Absent'
+    }
+    catch [Microsoft.CertificateServices.Deployment.Common.WEP.WebEnrollmentSetupException]
+    {
+        # CA is already installed
+        $Ensure = 'Present'
+    }
+    catch
+    {
+        # Something else went wrong
+        Throw $_
+    }
+
+    return @{
+        Ensure     = $Ensure
+        CAType     = $CAType
+        Credential = $Credential
+    }
+} # Function Get-TargetResource
+
+<#
+    .SYNOPSIS
+        Installs or uinstalls the ADCS Web Enrollment from the server.
+    .PARAMETER IsSingleInstance
+        Specifies the resource is a single instance, the value must be 'Yes'.
+    .PARAMETER CAConfig
+        CAConfig parameter string. Do not specify this if there is a local CA installed.
+    .PARAMETER Credential
+        If the Web Enrollment service is configured to use Standalone certification authority, then
+        an account that is a member of the local Administrators on the CA is required. If the
+        Web Enrollment service is configured to use an Enterprise CA, then an account that is a
+        member of Domain Admins is required.
+    .PARAMETER Ensure
+        Specifies whether the Web Enrollment feature should be installed or uninstalled.
+#>
+Function Set-TargetResource
+{
+    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+    [CmdletBinding()]
+    param(
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('Yes')]
+        [String]
+        $IsSingleInstance,
+
+        [Parameter()]
+        [String]
+        $CAConfig,
+
+        [Parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.Credential()]
+        $Credential,
+
+        [Parameter()]
+        [ValidateSet('Present','Absent')]
+        [String]
+        $Ensure = 'Present'
+    )
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.SettingAdcsWebEnrollmentStatusMessage)
+        ) -join '' )
+
+    $ADCSParams = @{} + $PSBoundParameters
+    $null = $ADCSParams.Remove('IsSingleInstance')
+    $null = $ADCSParams.Remove('Ensure')
+    $null = $ADCSParams.Remove('Debug')
+    $null = $ADCSParams.Remove('ErrorAction')
+
+    switch ($Ensure)
+    {
+        'Present'
+        {
+            Write-Verbose -Message ( @(
+                    "$($MyInvocation.MyCommand): "
+                    $($LocalizedData.InstallingAdcsWebEnrollmentMessage)
+                ) -join '' )
+
+            (Install-AdcsWebEnrollment @ADCSParams -Force).ErrorString
+        }
+        'Absent'
+        {
+            Write-Verbose -Message ( @(
+                    "$($MyInvocation.MyCommand): "
+                    $($LocalizedData.UninstallingAdcsWebEnrollmentMessage)
+                ) -join '' )
+
+            (Uninstall-AdcsWebEnrollment -Force).ErrorString
+        }
+    } # switch
+} # Function Set-TargetResource
+
+<#
+    .SYNOPSIS
+        Tests is the ADCS Web Enrollment is in the desired state.
+    .PARAMETER IsSingleInstance
+        Specifies the resource is a single instance, the value must be 'Yes'.
+    .PARAMETER CAConfig
+        CAConfig parameter string. Do not specify this if there is a local CA installed.
+    .PARAMETER Credential
+        If the Web Enrollment service is configured to use Standalone certification authority, then
+        an account that is a member of the local Administrators on the CA is required. If the
+        Web Enrollment service is configured to use an Enterprise CA, then an account that is a
+        member of Domain Admins is required.
+    .PARAMETER Ensure
+        Specifies whether the Web Enrollment feature should be installed or uninstalled.
+    .OUTPUTS
+        Returns true if the ADCS Web Enrollment is in the desired state.
+#>
+Function Test-TargetResource
+{
+    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')]
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param(
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('Yes')]
+        [String]
+        $IsSingleInstance,
+
+        [Parameter()]
+        [String]
+        $CAConfig,
+
+        [Parameter(Mandatory = $true)]
+        [System.Management.Automation.PSCredential]
+        [System.Management.Automation.Credential()]
+        $Credential,
+
+        [Parameter()]
+        [ValidateSet('Present','Absent')]
+        [String]
+        $Ensure = 'Present'
+    )
+
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.TestingAdcsWebEnrollmentStatusMessage -f $CAType)
+        ) -join '' )
+
+    $ADCSParams = @{} + $PSBoundParameters
+    $null = $ADCSParams.Remove('IsSingleInstance')
+    $null = $ADCSParams.Remove('Ensure')
+    $null = $ADCSParams.Remove('Debug')
+    $null = $ADCSParams.Remove('ErrorAction')
+
+    try
+    {
+        $null = Install-AdcsWebEnrollment @ADCSParams -WhatIf
+        # Web Enrollment is not installed
+        switch ($Ensure)
+        {
+            'Present'
+            {
+                # Web Enrollment is not installed but should be - change required
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.AdcsWebEnrollmentNotInstalledButShouldBeMessage)
+                    ) -join '' )
+
+                return $false
+            }
+            'Absent'
+            {
+                # Web Enrollment is not installed and should not be - change not required
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.AdcsWebEnrollmentNotInstalledAndShouldNotBeMessage)
+                    ) -join '' )
+
+                return $true
+            }
+        } # switch
+    }
+    catch [Microsoft.CertificateServices.Deployment.Common.WEP.WebEnrollmentSetupException]
+    {
+        # Web Enrollment is already installed
+        switch ($Ensure)
+        {
+            'Present'
+            {
+                # Web Enrollment is installed and should be - change not required
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.AdcsWebEnrollmentInstalledAndShouldBeMessage)
+                    ) -join '' )
+
+                return $true
+            }
+            'Absent'
+            {
+                # Web Enrollment is installed and should not be - change required
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.AdcsWebEnrollmentInstalledButShouldNotBeMessage)
+                    ) -join '' )
+
+                return $false
+            }
+        } # switch
+    }
+    catch
+    {
+        # Something else went wrong
+        Throw $_
+    } # try
+} # Function Test-TargetResource
+
+Export-ModuleMember -Function *-TargetResource

+ 9 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsWebEnrollment/MSFT_xAdcsWebEnrollment.schema.mof

@@ -0,0 +1,9 @@
+[ClassVersion("0.1.0.0"), FriendlyName("xAdcsWebEnrollment")]
+class MSFT_xAdcsWebEnrollment : OMI_BaseResource
+{
+    [Key, Description("Specifies the resource is a single instance, the value must be 'Yes'"), ValueMap{"Yes"}, Values{"Yes"}] String IsSingleInstance;
+    [Write, Description("CAConfig parameter string. Do not specify this if there is a local CA installed.")] String CAConfig;
+    [Required, Description("If the Web Enrollment service is configured to use Standalone certification authority, then an account that is a member of the local Administrators on the CA is required. If the Web Enrollment service is configured to use an Enterprise CA, then an account that is a member of Domain Admins is required."), EmbeddedInstance("MSFT_Credential")] String Credential;
+    [Write, Description("Specifies whether the Web Enrollment feature should be installed or uninstalled."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
+};
+

+ 13 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/DSCResources/MSFT_xAdcsWebEnrollment/en-us/MSFT_xAdcsWebEnrollment.strings.psd1

@@ -0,0 +1,13 @@
+# Localized resources for MSFT_xAdcsWebEnrollment
+
+ConvertFrom-StringData @'
+    GettingAdcsWebEnrollmentStatusMessage = Getting ADCS Web Enrollment Status.
+    TestingAdcsWebEnrollmentStatusMessage = Testing ADCS Web Enrollment Status.
+    AdcsWebEnrollmentInstalledButShouldNotBeMessage = ADCS Web Enrollment is installed but should not be. Change required.
+    AdcsWebEnrollmentInstalledAndShouldBeMessage = ADCS Web Enrollment is installed and should be. Change not required.
+    AdcsWebEnrollmentNotInstalledButShouldBeMessage = ADCS Web Enrollment is not installed but should be. Change required.
+    AdcsWebEnrollmentNotInstalledAndShouldNotBeMessage = ADCS Web Enrollment is not installed and should not be. Change not required.
+    SettingAdcsWebEnrollmentStatusMessage = Setting ADCS Web Enrollment Status.
+    InstallingAdcsWebEnrollmentMessage = Installing ADCS Web Enrollment.
+    UninstallingAdcsWebEnrollmentMessage = Uninstalling ADCS Web Enrollment.
+'@

+ 258 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Examples/Config-SetupActiveDirectory.ps1

@@ -0,0 +1,258 @@
+#region Param
+
+param
+(
+    [String]$DomainName,
+
+    [String]$DomainNetbiosName,
+
+    [PSCredential]$Credential,
+    
+    [PSCredential]$DomainCredential,
+    
+    [PSCredential]$SafeModeAdministratorPassword,
+
+    [String]$EncryptionCertificateThumbprint
+)
+
+#endregion
+
+#region Decrypt
+
+function Decrypt
+{
+    param
+    (
+        [Parameter(Mandatory)]
+        [String]$Thumbprint,
+
+        [Parameter(Mandatory)]
+        [String]$Base64EncryptedValue
+    )
+
+    # Decode Base64 string
+    $encryptedBytes = [System.Convert]::FromBase64String($Base64EncryptedValue)
+
+    # Get certificate from store
+    $store = new-object System.Security.Cryptography.X509Certificates.X509Store([System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine)
+    $store.open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)
+    $certificate = $store.Certificates | %{if($_.thumbprint -eq $Thumbprint){$_}}
+   
+    # Decrypt
+    $decryptedBytes = $certificate.PrivateKey.Decrypt($encryptedBytes, $false)
+    $decryptedValue = [System.Text.Encoding]::UTF8.GetString($decryptedBytes)
+    
+    return $decryptedValue
+}
+
+if ($EncryptionCertificateThumbprint)
+{
+    Write-Verbose -Message "Decrypting parameters with certificate $EncryptionCertificateThumbprint..."
+
+    $Password = Decrypt -Thumbprint $EncryptionCertificateThumbprint -Base64EncryptedValue $Password
+    $SafeModeAdministratorPassword = Decrypt -Thumbprint $EncryptionCertificateThumbprint -Base64EncryptedValue $SafeModeAdministratorPassword
+
+    Write-Verbose -Message "Successfully decrypted parameters."
+}
+else
+{
+    Write-Verbose -Message "No encryption certificate specified. Assuming cleartext parameters."
+}
+
+#endregion
+
+#region Config Data
+
+if ($env:COMPUTERNAME -match 'DC-Server\d\d') {
+    $ConfigData = @{
+        AllNodes = @(
+             @{
+                Nodename = $env:COMPUTERNAME
+                ServerRole = 'Active Directory Domain Controller'
+                DomainName = $DomainName
+                DomainNetbiosName = $DomainNetbiosName
+                Disk = 1
+                Drive = 'F'
+                PSDscAllowPlainTextPassword = $true
+                Credential = $Credential
+                DomainCredential = $DomainCredential
+                SMCredential = $SafeModeAdministratorPassword
+            }
+        )
+    }
+}
+
+if ($EncryptionCertificateThumbprint)
+{
+    $certificate = dir Cert:\LocalMachine\My\$EncryptionCertificateThumbprint
+    $certificatePath = Join-Path -path $PSScriptRoot -childPath "EncryptionCertificate.cer"
+    Export-Certificate -Cert $certificate -FilePath $certificatePath | Out-Null
+    $configData = @{
+        AllNodes = @(
+            @{
+                Nodename = "*"
+                CertificateFile = $certificatePath
+                Thumbprint = $EncryptionCertificateThumbprint
+                PSDscAllowPlainTextPassword = $false
+            }
+        )
+    }
+}
+
+#endregion
+
+#region First DC Configuration Script
+
+if ((test-path C:\Windows\temp\FirstDC.txt) -eq $True)
+{
+    configuration FirstDomainController
+    {
+        Import-DscResource -ModuleName xComputerManagement, xActiveDirectory, xStorage, xAdcsDeployment
+
+        node $AllNodes.Where{$_.ServerRole -eq 'Active Directory Domain Controller'}.Nodename
+        {    
+            xWaitforDisk SMA
+            {
+                DiskNumber = $Node.Disk
+                RetryCount = 720
+            }
+
+            xDisk DataDisk
+            {
+                DiskNumber = $Node.Disk
+                DriveLetter = $Node.Drive
+                DependsOn = '[xWaitforDisk]SMA'
+            }
+        
+            WindowsFeature AD-Domain-Services
+            {
+                   Ensure = 'Present'
+                   Name = 'AD-Domain-Services'
+            }
+            
+            WindowsFeature ADCS-Cert-Authority
+            {
+                   Ensure = 'Present'
+                   Name = 'ADCS-Cert-Authority'
+            }
+
+            WindowsFeature ADCS-Web-Enrollment
+            {
+                Ensure = 'Present'
+                Name = 'ADCS-Web-Enrollment'
+            }
+
+            xADDomain PrimaryDC
+            {
+                DomainAdministratorCredential = $Node.Credential
+                DomainName = $Node.DomainName
+                SafemodeAdministratorPassword = $Node.SMCredential
+                DatabasePath = $Node.Drive + ":\NTDS"
+                LogPath = $Node.Drive + ":\NTDS"
+                SysvolPath = $Node.Drive + ":\SYSVOL"
+                DependsOn = "[xDisk]DataDisk", "[WindowsFeature]AD-Domain-Services"
+            }
+
+            xADCSCertificationAuthority ADCS
+            {
+                Ensure = 'Present'
+                Credential = $Node.Credential
+                CAType = 'EnterpriseRootCA'
+                DependsOn = '[WindowsFeature]ADCS-Cert-Authority'              
+            }
+
+            xADCSWebEnrollment CertSrv
+            {
+                IsSingleInstance = 'Yes'
+                Ensure = 'Absent'
+                Credential = $Node.Credential
+                DependsOn = '[xADCSCertificationAuthority]ADCS'
+            }
+            
+            LocalConfigurationManager
+            {
+                CertificateId = $node.Thumbprint
+                ConfigurationMode = 'ApplyandAutoCorrect'
+                RebootNodeIfNeeded = 'True'
+            }
+        }
+    }
+
+FirstDomainController -ConfigurationData $configData -OutputPath $PSScriptRoot
+
+}
+
+#endregion
+
+#region DC Configuration Script
+
+if ((test-path C:\Windows\temp\FirstDC.txt) -eq $False)
+{
+    configuration DomainController
+    {
+        Import-DscResource -ModuleName xComputerManagement, xActiveDirectory, xStorage
+
+        node $AllNodes.Where{$_.ServerRole -eq 'Active Directory Domain Controller'}.Nodename
+        {    
+             xWaitforDisk DataDisk
+            {
+                DiskNumber = $Node.Disk
+                RetryCount = 720
+            }
+
+            xDisk DataDisk
+            {
+                DiskNumber = $Node.Disk
+                DriveLetter = $Node.Drive
+                DependsOn = '[xWaitforDisk]DataDisk'
+            }
+        
+            WindowsFeature AD-Domain-Services
+            {
+                   Ensure = 'Present'
+                   Name = 'AD-Domain-Services'
+            }
+
+            xWaitForADDomain WaitforDomain
+            {
+                DomainName = $Node.DomainName
+                DomainUserCredential = $Node.DomainCredential
+                RetryCount = 720
+                RetryIntervalSec = 10
+                DependsOn = "[WindowsFeature]AD-Domain-Services"
+            }
+        
+            xADDomainController BackupDC
+            {
+                DomainAdministratorCredential = $Node.DomainCredential
+                DomainName = $Node.DomainName
+                SafemodeAdministratorPassword = $Node.SMCredential
+                DatabasePath = $Node.Drive + ":\NTDS"
+                LogPath = $Node.Drive + ":\NTDS"
+                SysvolPath = $Node.Drive + ":\SYSVOL"
+                DependsOn = '[xDisk]DataDisk', '[WindowsFeature]AD-Domain-Services', '[xWaitforADDomain]WaitforDomain'
+            }
+
+            LocalConfigurationManager
+            {
+                CertificateId = $node.Thumbprint
+                ConfigurationMode = 'ApplyandAutoCorrect'
+                RebootNodeIfNeeded = 'True'
+            }
+        }
+    }
+
+DomainController -ConfigurationData $configData -OutputPath $PSScriptRoot
+
+}
+
+#endregion
+
+#region Apply MOF
+
+Set-WSManQuickConfig -Force > $null
+
+Set-DscLocalConfigurationManager -ComputerName $env:COMPUTERNAME -Path $PSScriptRoot -Verbose
+Start-DscConfiguration -ComputerName $env:COMPUTERNAME -Path $PSScriptRoot -Force -Verbose -Wait
+
+#endregion

+ 21 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Microsoft Corporation.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

BIN
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/PSGetModuleInfo.xml


+ 204 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/README.md

@@ -0,0 +1,204 @@
+# xAdcsDeployment
+
+[![Build status](https://ci.appveyor.com/api/projects/status/2uua9s0qgmfmqqrh/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/xadcsdeployment/branch/master)
+
+The **xAdcsDeployment** DSC resources have been specifically tested as a method to populate a Certificate Services server role on Windows Server 2012 R2 after the Certificate Services role and the Web Enrollment feature have been enabled.
+Active Directory Certificate Services (AD CS) is used to create certification authorities and related role services that allow you to issue and manage certificates used in a variety of applications.
+
+This DSC resource can be used to address some of the most common scenarios including the need for a Stand-Alone Certificate Authority or an Active Directory Trusted Root Certificate Authority and the Certificate Services website for users to submit and complete certificate requests.
+In a specific example, when building out a web server workload such as an internal website that provides confidential information to be accessed from computers that are members of an Active Directory domain, AD CS can provide a source for the SSL certificats that will automatically be trusted.
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
+
+## How to Contribute
+
+If you would like to contribute to this repository, please read the DSC Resource Kit [contributing guidelines](https://github.com/PowerShell/DscResource.Kit/blob/master/CONTRIBUTING.md).
+
+## Resources
+
+- **xAdcsCertificationAuthority**: This resource can be used to install the ADCS Certificte Authority after the feature has been installed on the server.
+- **xAdcsWebEnrollment**: This resource can be used to install the ADCS Web Enrollment service after the feature has been installed on the server.
+- **xAdcsOnlineResponder**: This resource can be used to install an ADCS Online Responder after the feature has been installed on the server.
+
+### xAdcsCertificationAuthority
+
+This resource can be used to install the ADCS Certificte Authority after the feature has been installed on the server.
+Using this DSC Resource to confifgure an ADCS Certificate Authority assumes that the ```ADCS-Cert-Authority``` feature has already been installed.
+
+- **`[String]` CAType** (_Key_): Specifies the type of certification authority to install. { EnterpriseRootCA | EnterpriseSubordinateCA | StandaloneRootCA | StandaloneSubordinateCA }
+- **`[PSCredential]` Credential** (_Required_): To install an enterprise certification authority, the computer must be joined to an Active Directory Domain Services domain and a user account that is a member of the Enterprise Admin group is required. To install a standalone certification authority, the computer can be in a workgroup or AD DS domain. If the computer is in a workgroup, a user account that is a member of Administrators is required. If the computer is in an AD DS domain, a user account that is a member of Domain Admins is required.
+- **`[String]` Ensure** (_Write_): Specifies whether the Certificate Authority should be installed or uninstalled. { *Present* | Absent }
+- **`[String]` CACommonName** (_Write_): Specifies the certification authority common name.
+- **`[String]` CADistinguishedNameSuffix** (_Write_): Specifies the certification authority distinguished name suffix.
+- **`[String]` CertFile** (_Write_): Specifies the file name of certification authority PKCS 12 formatted certificate file.
+- **`[PSCredential]` CertFilePassword** (_Write_): Specifies the password for certification authority certificate file.
+- **`[String]` CertificateID** (_Write_): Specifies the thumbprint or serial number of certification authority certificate.
+- **`[String]` CryptoProviderName** (_Write_): The name of the cryptographic service provider or key storage provider that is used to generate or store the private key for the CA.
+- **`[String]` DatabaseDirectory** (_Write_): Specifies the folder location of the certification authority database.
+- **`[String]` HashAlgorithmName** (_Write_): Specifies the signature hash algorithm used by the certification authority.
+- **`[Boolean]` IgnoreUnicode** (_Write_): Specifies that Unicode characters are allowed in certification authority name string.
+- **`[String]` KeyContainerName** (_Write_): Specifies the name of an existing private key container.
+- **`[Uint32]` KeyLength** (_Write_): Specifies the bit length for new certification authority key.
+- **`[String]` LogDirectory** (_Write_): Specifies the folder location of the certification authority database log.
+- **`[String]` OutputCertRequestFile** (_Write_): Specifies the folder location for certificate request file.
+- **`[Boolean]` OverwriteExistingCAinDS** (_Write_): Specifies that the computer object in the Active Directory Domain Service domain should be overwritten with the same computer name.
+- **`[Boolean]` OverwriteExistingDatabase** (_Write_): Specifies that the existing certification authority database should be overwritten.
+- **`[Boolean]` OverwriteExistingKey** (_Write_): Overwrite existing key container with the same name.
+- **`[String]` ParentCA** (_Write_): Specifies the configuration string of the parent certification authority that will certify this CA.
+- **`[String]` ValidityPeriod** (_Write_): Specifies the validity period of the certification authority certificate in hours, days, weeks, months or years. If this is a subordinate CA, do not use this parameter, because the validity period is determined by the parent CA. { Hours | Days | Months | Years }
+- **`[Uint32]` ValidityPeriodUnits** (_Write_): Validity period of the certification authority certificate. If this is a subordinate CA, do not specify this parameter because the validity period is determined by the parent CA.
+
+### xAdcsWebEnrollment
+
+This resource can be used to install the ADCS Web Enrollment service after the feature has been installed on the server.
+Using this DSC Resource to confifgure an ADCS Certificate Authority assumes that the ```ADCS-Web-Enrollment``` feature has already been installed.
+For more information on Web Enrollment services, see [this article on TechNet](https://technet.microsoft.com/en-us/library/cc732517.aspx).
+
+- **`[String]` IsSingleInstance** (_Key_): Specifies the resource is a single instance, the value must be 'Yes' { Yes }
+- **`[String]` CAConfig** (_Write_): CAConfig parameter string. Do not specify this if there is a local CA installed.
+- **`[PSCredential]` Credential** (_Required_): If the Web Enrollment service is configured to use Standalone certification authority, then an account that is a member of the local Administrators on the CA is required. If the Web Enrollment service is configured to use an Enterprise CA, then an account that is a member of Domain Admins is required.
+- **`[String]` Ensure** (_Write_): Specifies whether the Web Enrollment feature should be installed or uninstalled. { *Present* | Absent }
+
+### xAdcsOnlineResponder
+
+This resource can be used to install an ADCS Online Responder after the feature has been installed on the server.
+Using this DSC Resource to confifgure an ADCS Certificate Authority assumes that the ```ADCS-Online-Responder``` feature has already been installed.
+For more information on ADCS Online Responders, see [this article on TechNet](https://technet.microsoft.com/en-us/library/cc725958.aspx).
+
+- **`[String]` IsSingleInstance** (_Key_): Specifies the resource is a single instance, the value must be 'Yes' { Yes }
+- **`[String]` CAConfig** (_Write_): CAConfig parameter string. Do not specify this if there is a local CA installed.
+- **`[PSCredential]` Credential** (_Required_): If the Online Responder service is configured to use Standalone certification authority, then an account that is a member of the local Administrators on the CA is required. If the Online Responder service is configured to use an Enterprise CA, then an account that is a member of Domain Admins is required.
+- **`[String]` Ensure** (_Write_): Specifies whether the Online Responder feature should be installed or uninstalled. { *Present* | Absent }
+
+## Versions
+
+### Unreleased
+
+### 1.1.0.0
+
+- Converted AppVeyor.yml to pull Pester from PSGallery instead of Chocolatey.
+- Changed AppVeyor.yml to use default image.
+- xAdcsCertificateAuthority:
+  - Change property format in Readme.md to be standard layout.
+  - Converted style to meet HQRM guidelines.
+  - Added verbose logging support.
+  - Added string localization.
+  - Fixed Get-TargetResource by removing IsCA and changing Ensure to return whether or not CA is installed.
+  - Added unit tests.
+  - Updated parameter format to meet HQRM guidelines.
+- xAdcsOnlineResponder:
+  - Change property format in Readme.md to be standard layout.
+  - Added unit test header to be latest version.
+  - Added function help.
+  - Updated parameter format to meet HQRM guidelines.
+  - Updated resource to meet HQRM guidelines.
+- xAdcsWebEnrollment:
+  - Change property format in Readme.md to be standard layout.
+  - Added unit test header to be latest version.
+  - Added function help.
+  - Updated parameter format to meet HQRM guidelines.
+  - Updated resource to meet HQRM guidelines.
+- Added CommonResourceHelper.psm1 (copied from xPSDesiredStateConfiguration).
+- Removed Technet Documentation HTML file from root folder.
+- Removed redundant code from AppVeyor.yml.
+- Fix markdown violations in Readme.md.
+- Updated readme.md to match DSCResource.Template\Readme.md.
+
+### 1.0.0.0
+
+- Moved Examples folder into root.
+- Removed legacy xCertificateServices folder.
+- Prevented Unit tests from Violating PSSA rules.
+- MSFT_xAdcsWebEnrollment: Created unit tests based on v1.0 Test Template.
+                           Update to meet Style Guidelines and ensure consistency.
+                           Updated to IsSingleInstance model. **Breaking change**
+- MSFT_xAdcsOnlineResponder: Update Unit tests to use v1.0 Test Template.
+                             Unit tests can be run without AD CS installed.
+                             Update to meet Style Guidelines and ensure consistency.
+- Usage of WinRm.exe replaced in Config-SetupActiveDirectory.ps1 example file with Set-WSManQuickConfig cmdlet.
+
+### 0.2.0.0
+
+- Added the following resources:
+  - MSFT_xADCSOnlineResponder resource to install the Online Responder service.
+- Correction to xAdcsCertificationAuthority property title in Readme.md.
+- Addition of .gitignore to ensure DSCResource.Tests folder is committed.
+- Updated AppVeyor.yml to use WMF 5 build environment.
+
+### 0.1.0.0
+
+- Initial release with the following resources
+  - xAdcsCertificationAuthority and xAdcsWebEnrollment.
+
+### Examples
+
+#### Example 1: Add a Certificate Authority and configure it for AD CS and Web Enrollment.
+
+This example will add the Windows Server Roles and Features to support a Certificate Authority and configure it to provide AD CS and Web Enrollment.
+
+```powershell
+Configuration CertificateAuthority
+{
+    Node ‘NodeName’
+    {
+        WindowsFeature ADCS-Cert-Authority
+        {
+               Ensure = 'Present'
+               Name = 'ADCS-Cert-Authority'
+        }
+        xADCSCertificationAuthority ADCS
+        {
+            Ensure = 'Present'
+            Credential = $Node.Credential
+            CAType = 'EnterpriseRootCA'
+            DependsOn = '[WindowsFeature]ADCS-Cert-Authority'
+        }
+        WindowsFeature ADCS-Web-Enrollment
+        {
+            Ensure = 'Present'
+            Name = 'ADCS-Web-Enrollment'
+            DependsOn = '[WindowsFeature]ADCS-Cert-Authority'
+        }
+        xADCSWebEnrollment CertSrv
+        {
+            Ensure = 'Present'
+            IsSingleInstance = 'Yes'
+            Credential = $Node.Credential
+            DependsOn = '[WindowsFeature]ADCS-Web-Enrollment','[xADCSCertificationAuthority]ADCS'
+        }
+    }
+}
+```
+
+#### Example 2: Remove the AD CS functionality from a server
+
+```powershell
+Configuration RetireCertificateAuthority
+{
+    Node ‘NodeName’
+    {
+        xADCSWebEnrollment CertSrv
+        {
+            Ensure = 'Absent'
+            Name = 'CertSrv'
+        }
+        {
+            Ensure = 'Absent'
+            Name = 'ADCS-Web-Enrollment'
+            DependsOn = '[xADCSWebEnrollment]CertSrv'
+        }
+        xADCSCertificationAuthority ADCS
+        {
+            Ensure = 'Absent'
+            DependsOn = '[WindowsFeature]ADCS-Web-Enrollment'
+        }
+            WindowsFeature ADCS-Cert-Authority
+        {
+            Ensure = 'Absent'
+            Name = 'ADCS-Cert-Authority'
+            DependsOn = ‘[xADCSCertificationAuthority]ADCS’
+        }
+    }
+}
+```

+ 34 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Integration/IntegrationTestsCommon.psm1

@@ -0,0 +1,34 @@
+<#
+    .SYNOPSIS
+        Tests if a specific Server Feature is installed on this OS.
+
+    .OUTPUTS
+        False if this is a non-Windows Server OS.
+        True if a specific Server feature is installed, otherwise False is returned.
+
+#>
+function Test-WindowsFeature
+{
+    [CmdletBinding()]
+    [OutputType([String])]
+    param
+    (
+        [String] $Name
+    )
+
+    # Ensure that the tests can be performed on this computer
+    $ProductType = (Get-CimInstance Win32_OperatingSystem).ProductType
+    if ($ProductType -ne 3)
+    {
+        # Unsupported OS type for testing
+        Write-Verbose -Message "Integration tests cannot be run on this operating system." -Verbose
+        Return $false
+    }
+    # Server OS
+    if (-not (Get-WindowsFeature @PSBoundParameters).Installed)
+    {
+        Write-Verbose -Message "Integration tests cannot be run because $Name is not installed." -Verbose
+        Return $false
+    }
+    Return $True
+} # end function Test-WindowsFeature

+ 141 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Integration/MSFT_xAdcsCertificationAuthority.Tests.ps1

@@ -0,0 +1,141 @@
+# Suppress this PSSA message because we need to allow credentials to be
+# set when running the tests.
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
+param ()
+
+# IMPORTANT: To run these tests requires a local Administrator account to be
+# available on the machine running the tests that can be used to install the
+# ADCS component being tested. Please change the following values to the
+# credentials that are set up for this purpose.
+# When these tests are run on AppVeyor, ADCS-Cert-Authority will be installed
+# and a new Administrator account will be created that uses credentials that
+# match the ones following.
+$script:adminUsername   = 'AdcsAdmin'
+$script:adminPassword   = 'NotPass12!'
+$script:DSCModuleName   = 'xAdcsDeployment'
+$script:DSCResourceName = 'MSFT_xAdcsCertificationAuthority'
+
+#region HEADER
+# Integration Test Template Version: 1.1.1
+[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
+if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $script:DSCModuleName `
+    -DSCResourceName $script:DSCResourceName `
+    -TestType Integration
+#endregion
+
+# Using try/finally to always cleanup even if something awful happens.
+try
+{
+    # Import the common integration test functions
+    Import-Module -Name ( Join-Path `
+        -Path $PSScriptRoot `
+        -ChildPath 'IntegrationTestsCommon.psm1' )
+
+    # Ensure that the tests can be performed on this computer
+    if (-not (Test-WindowsFeature -Name 'ADCS-Cert-Authority'))
+    {
+        Return
+    }
+
+    #region Integration Tests
+    $ConfigFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:DSCResourceName)_Install.config.ps1"
+    . $ConfigFile -Verbose -ErrorAction Stop
+
+    Describe "$($script:DSCResourceName)_Install_Integration" {
+        Context 'Install ADCS Certification Authority' {
+            #region DEFAULT TESTS
+            It 'Should compile without throwing' {
+                {
+                    $secureAdminPassword = ConvertTo-SecureString -String $script:adminPassword -AsPlainText -Force
+                    $adminCred = New-Object -TypeName System.Management.Automation.PSCredential `
+                        -ArgumentList ($script:adminUsername,$secureAdminPassword)
+                    $ConfigData = @{
+                        AllNodes = @(
+                            @{
+                                NodeName                    = 'localhost'
+                                AdminCred                   = $AdminCred
+                                PsDscAllowPlainTextPassword = $true
+                            }
+                        )
+                    }
+
+                    & "$($script:DSCResourceName)_Install_Config" `
+                        -OutputPath $TestDrive `
+                        -ConfigurationData $ConfigData
+                    Start-DscConfiguration -Path $TestDrive `
+                        -ComputerName localhost -Wait -Verbose -Force
+                } | Should not throw
+            }
+
+            It 'should be able to call Get-DscConfiguration without throwing' {
+                { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should Not throw
+            }
+            #endregion
+
+            It 'Should have set the resource and all the parameters should match' {
+                $current = Get-DscConfiguration | Where-Object {
+                    $_.ConfigurationName -eq "$($script:DSCResourceName)_Install_Config"
+                }
+                $current.Ensure           | Should Be 'Present'
+            }
+        }
+    }
+
+    $ConfigFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:DSCResourceName)_Uninstall.config.ps1"
+    . $ConfigFile -Verbose -ErrorAction Stop
+
+    Describe "$($script:DSCResourceName)_Uninstall_Integration" {
+        Context 'Uninstall ADCS Certification Authority' {
+            #region DEFAULT TESTS
+            It 'Should compile without throwing' {
+                {
+                    $secureAdminPassword = ConvertTo-SecureString -String $script:adminPassword -AsPlainText -Force
+                    $adminCred = New-Object -TypeName System.Management.Automation.PSCredential `
+                        -ArgumentList ($script:adminUsername,$secureAdminPassword)
+                    $ConfigData = @{
+                        AllNodes = @(
+                            @{
+                                NodeName                    = 'localhost'
+                                AdminCred                   = $AdminCred
+                                PsDscAllowPlainTextPassword = $true
+                            }
+                        )
+                    }
+
+                    & "$($script:DSCResourceName)_Uninstall_Config" `
+                        -OutputPath $TestDrive `
+                        -ConfigurationData $ConfigData
+                    Start-DscConfiguration -Path $TestDrive `
+                        -ComputerName localhost -Wait -Verbose -Force
+                } | Should not throw
+            }
+
+            It 'should be able to call Get-DscConfiguration without throwing' {
+                { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should Not throw
+            }
+            #endregion
+
+            It 'Should have set the resource and all the parameters should match' {
+                $current = Get-DscConfiguration | Where-Object {
+                    $_.ConfigurationName -eq "$($script:DSCResourceName)_Uninstall_Config"
+                }
+                $current.Ensure           | Should Be 'Absent'
+            }
+        }
+    }
+    #endregion
+}
+finally
+{
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}

+ 11 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Integration/MSFT_xAdcsCertificationAuthority_Install.config.ps1

@@ -0,0 +1,11 @@
+configuration MSFT_xADCSCertificationAuthority_Install_Config {
+    Import-DscResource -ModuleName xAdcsDeployment
+
+    node localhost {
+        xADCSCertificationAuthority Integration_Test {
+            CAType     = 'StandaloneRootCA'
+            Credential = $Node.AdminCred
+            Ensure     = 'Present'
+        }
+    }
+}

+ 11 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Integration/MSFT_xAdcsCertificationAuthority_Uninstall.config.ps1

@@ -0,0 +1,11 @@
+configuration MSFT_xADCSCertificationAuthority_Uninstall_Config {
+    Import-DscResource -ModuleName xAdcsDeployment
+
+    node localhost {
+        xADCSCertificationAuthority Integration_Test {
+            CAType     = 'StandaloneRootCA'
+            Credential = $Node.AdminCred
+            Ensure     = 'Absent'
+        }
+    }
+}

+ 383 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Unit/MSFT_xAdcsCertificationAuthority.Tests.ps1

@@ -0,0 +1,383 @@
+$script:DSCModuleName   = 'xAdcsDeployment'
+$script:DSCResourceName = 'MSFT_xAdcsCertificationAuthority'
+
+#region HEADER
+# Integration Test Template Version: 1.1.0
+[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
+if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $script:DSCModuleName `
+    -DSCResourceName $script:DSCResourceName `
+    -TestType Unit
+#endregion
+
+# Begin Testing
+try
+{
+    #region Pester Tests
+    InModuleScope $($script:DSCResourceName) {
+        if (-not ([System.Management.Automation.PSTypeName]'Microsoft.CertificateServices.Deployment.Common.CA.CertificationAuthoritySetupException').Type)
+        {
+            # Define the exception class:
+            # Microsoft.CertificateServices.Deployment.Common.CA.CertificationAuthoritySetupException
+            # so that unit tests can be run without ADCS being installed.
+
+            $ExceptionDefinition = @'
+namespace Microsoft.CertificateServices.Deployment.Common.CA {
+    public class CertificationAuthoritySetupException: System.Exception {
+    }
+}
+'@
+            Add-Type -TypeDefinition $ExceptionDefinition
+        }
+        $DSCResourceName = 'MSFT_xAdcsCertificationAuthority'
+
+        $DummyCredential = New-Object System.Management.Automation.PSCredential ("Administrator",(New-Object -Type SecureString))
+
+        $TestParametersPresent = @{
+            Ensure     = 'Present'
+            CAType     = 'StandaloneRootCA'
+            Credential = $DummyCredential
+            Verbose    = $true
+        }
+
+        $TestParametersAbsent = @{
+            Ensure     = 'Absent'
+            CAType     = 'StandaloneRootCA'
+            Credential = $DummyCredential
+            Verbose    = $true
+        }
+
+        Describe "$DSCResourceName\Get-TargetResource" {
+
+            function Install-AdcsCertificationAuthority {
+                [CmdletBinding()]
+                param (
+                    [Parameter(Mandatory = $True)]
+                    [ValidateSet('EnterpriseRootCA','EnterpriseSubordinateCA','StandaloneRootCA','StandaloneSubordinateCA')]
+                    [string] $CAType,
+
+                    [Parameter(Mandatory = $True)]
+                    [pscredential] $Credential,
+
+                    [string] $CACommonName,
+
+                    [string] $CADistinguishedNameSuffix,
+
+                    [string] $CertFile,
+
+                    [pscredential] $CertFilePassword,
+
+                    [string] $CertificateID,
+
+                    [string] $CryptoProviderName,
+
+                    [string] $DatabaseDirectory,
+
+                    [string] $HashAlgorithmName,
+
+                    [boolean] $IgnoreUnicode,
+
+                    [string] $KeyContainerName,
+
+                    [uint32] $KeyLength,
+
+                    [string] $LogDirectory,
+
+                    [string] $OutputCertRequestFile,
+
+                    [boolean] $OverwriteExistingCAinDS,
+
+                    [boolean] $OverwriteExistingDatabase,
+
+                    [boolean] $OverwriteExistingKey,
+
+                    [string] $ParentCA,
+
+                    [ValidateSet('Hours','Days','Months','Years')]
+                    [string] $ValidityPeriod,
+
+                    [uint32] $ValidityPeriodUnits,
+
+                    [Switch] $Force,
+
+                    [Switch] $WhatIf
+                )
+            }
+
+            #region Mocks
+            Mock `
+                -CommandName Install-AdcsCertificationAuthority `
+                -MockWith { Throw (New-Object -TypeName 'Microsoft.CertificateServices.Deployment.Common.CA.CertificationAuthoritySetupException') } `
+                -Verifiable
+            #endregion
+
+            Context 'CA is installed' {
+                $Result = Get-TargetResource @TestParametersPresent
+
+                It 'should return Ensure set to Present' {
+                    $Result.Ensure  | Should Be 'Present'
+                }
+
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsCertificationAuthority `
+                        -Exactly 1
+                }
+            }
+
+            #region Mocks
+            Mock `
+                -CommandName Install-AdcsCertificationAuthority
+            #endregion
+
+            Context 'CA is not installed' {
+                $Result = Get-TargetResource @TestParametersPresent
+
+                It 'should return Ensure set to Absent' {
+                    $Result.Ensure  | Should Be 'Absent'
+                }
+
+                It 'should call expected mocks' {
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsCertificationAuthority `
+                        -Exactly 1
+                }
+            }
+        }
+
+        Describe "$DSCResourceName\Set-TargetResource" {
+
+            function Install-AdcsCertificationAuthority {
+                [CmdletBinding()]
+                param (
+                    [Parameter(Mandatory = $True)]
+                    [ValidateSet('EnterpriseRootCA','EnterpriseSubordinateCA','StandaloneRootCA','StandaloneSubordinateCA')]
+                    [string] $CAType,
+
+                    [Parameter(Mandatory = $True)]
+                    [pscredential] $Credential,
+
+                    [string] $CACommonName,
+
+                    [string] $CADistinguishedNameSuffix,
+
+                    [string] $CertFile,
+
+                    [pscredential] $CertFilePassword,
+
+                    [string] $CertificateID,
+
+                    [string] $CryptoProviderName,
+
+                    [string] $DatabaseDirectory,
+
+                    [string] $HashAlgorithmName,
+
+                    [boolean] $IgnoreUnicode,
+
+                    [string] $KeyContainerName,
+
+                    [uint32] $KeyLength,
+
+                    [string] $LogDirectory,
+
+                    [string] $OutputCertRequestFile,
+
+                    [boolean] $OverwriteExistingCAinDS,
+
+                    [boolean] $OverwriteExistingDatabase,
+
+                    [boolean] $OverwriteExistingKey,
+
+                    [string] $ParentCA,
+
+                    [ValidateSet('Hours','Days','Months','Years')]
+                    [string] $ValidityPeriod,
+
+                    [uint32] $ValidityPeriodUnits,
+
+                    [Switch] $Force,
+
+                    [Switch] $WhatIf
+                )
+            }
+            function Uninstall-AdcsCertificationAuthority {
+                [CmdletBinding()]
+                param (
+                    [Switch] $Force
+                )
+            }
+
+            #region Mocks
+            Mock -CommandName Install-AdcsCertificationAuthority
+            Mock -CommandName Uninstall-AdcsCertificationAuthority
+            #endregion
+
+            Context 'CA is not installed but should be' {
+                Set-TargetResource @TestParametersPresent
+
+                It 'should call expected mocks' {
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsCertificationAuthority `
+                        -Exactly 1
+                    Assert-MockCalled `
+                        -CommandName Uninstall-AdcsCertificationAuthority `
+                        -Exactly 0
+                }
+            }
+
+            Context 'CA is installed but should not be' {
+                Set-TargetResource @TestParametersAbsent
+
+                It 'should call expected mocks' {
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsCertificationAuthority `
+                        -Exactly 0
+                    Assert-MockCalled `
+                        -CommandName Uninstall-AdcsCertificationAuthority `
+                        -Exactly 1
+                }
+            }
+        }
+
+        Describe "$DSCResourceName\Test-TargetResource" {
+
+            function Install-AdcsCertificationAuthority {
+                [CmdletBinding()]
+                param (
+                    [Parameter(Mandatory = $True)]
+                    [ValidateSet('EnterpriseRootCA','EnterpriseSubordinateCA','StandaloneRootCA','StandaloneSubordinateCA')]
+                    [string] $CAType,
+
+                    [Parameter(Mandatory = $True)]
+                    [pscredential] $Credential,
+
+                    [string] $CACommonName,
+
+                    [string] $CADistinguishedNameSuffix,
+
+                    [string] $CertFile,
+
+                    [pscredential] $CertFilePassword,
+
+                    [string] $CertificateID,
+
+                    [string] $CryptoProviderName,
+
+                    [string] $DatabaseDirectory,
+
+                    [string] $HashAlgorithmName,
+
+                    [boolean] $IgnoreUnicode,
+
+                    [string] $KeyContainerName,
+
+                    [uint32] $KeyLength,
+
+                    [string] $LogDirectory,
+
+                    [string] $OutputCertRequestFile,
+
+                    [boolean] $OverwriteExistingCAinDS,
+
+                    [boolean] $OverwriteExistingDatabase,
+
+                    [boolean] $OverwriteExistingKey,
+
+                    [string] $ParentCA,
+
+                    [ValidateSet('Hours','Days','Months','Years')]
+                    [string] $ValidityPeriod,
+
+                    [uint32] $ValidityPeriodUnits,
+
+                    [Switch] $Force,
+
+                    [Switch] $WhatIf
+                )
+            }
+
+            #region Mocks
+            Mock -CommandName Install-AdcsCertificationAuthority `
+                -MockWith { Throw (New-Object -TypeName 'Microsoft.CertificateServices.Deployment.Common.CA.CertificationAuthoritySetupException') } `
+                -Verifiable
+            #endregion
+
+            Context 'CA is installed and should be' {
+                $Result = Test-TargetResource @TestParametersPresent
+
+                It 'should return true' {
+                    $Result | Should be $True
+                }
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsCertificationAuthority `
+                        -Exactly 1
+                }
+            }
+
+            Context 'CA is installed but should not be' {
+                $Result = Test-TargetResource @TestParametersAbsent
+
+                It 'should return false' {
+                    $Result | Should be $False
+                }
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsCertificationAuthority `
+                        -Exactly 1
+                }
+            }
+
+            #region Mocks
+            Mock -CommandName Install-AdcsCertificationAuthority `
+                -Verifiable
+            #endregion
+
+            Context 'CA is not installed but should be' {
+                $Result = Test-TargetResource @TestParametersPresent
+
+                It 'should return false' {
+                    $Result | Should be $false
+                }
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsCertificationAuthority `
+                        -Exactly 1
+                }
+            }
+
+            Context 'CA is not installed and should not be' {
+                $Result = Test-TargetResource @TestParametersAbsent
+
+                It 'should return true' {
+                    $Result | Should be $True
+                }
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsCertificationAuthority `
+                        -Exactly 1
+                }
+            }
+        }
+    }
+    #endregion
+}
+finally
+{
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}

+ 252 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Unit/MSFT_xAdcsOnlineResponder.Tests.ps1

@@ -0,0 +1,252 @@
+$script:DSCModuleName   = 'xAdcsDeployment'
+$script:DSCResourceName = 'MSFT_xAdcsOnlineResponder'
+
+#region HEADER
+# Integration Test Template Version: 1.1.0
+[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
+if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $script:DSCModuleName `
+    -DSCResourceName $script:DSCResourceName `
+    -TestType Unit
+#endregion
+
+# Begin Testing
+try
+{
+    #region Pester Tests
+    InModuleScope $script:DSCResourceName {
+        if (-not ([System.Management.Automation.PSTypeName]'Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException').Type)
+        {
+            # Define the exception class:
+            # Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException
+            # so that unit tests can be run without ADCS being installed.
+
+            $ExceptionDefinition = @'
+namespace Microsoft.CertificateServices.Deployment.Common.OCSP {
+    public class OnlineResponderSetupException: System.Exception {
+    }
+}
+'@
+            Add-Type -TypeDefinition $ExceptionDefinition
+        }
+
+        $DSCResourceName = 'MSFT_xAdcsOnlineResponder'
+
+        $DummyCredential = New-Object System.Management.Automation.PSCredential ("Administrator",(New-Object -Type SecureString))
+
+        $TestParametersPresent = @{
+            IsSingleInstance = 'Yes'
+            Ensure           = 'Present'
+            Credential       = $DummyCredential
+            Verbose          = $true
+        }
+
+        $TestParametersAbsent = @{
+            IsSingleInstance = 'Yes'
+            Ensure           = 'Absent'
+            Credential       = $DummyCredential
+            Verbose          = $true
+        }
+
+        Describe "$DSCResourceName\Get-TargetResource" {
+
+            function Install-AdcsOnlineResponder {
+                [CmdletBinding()]
+                param(
+                    [PSCredential] $Credential,
+
+                    [Switch] $Force,
+
+                    [Switch] $WhatIf
+                )
+            }
+
+            #region Mocks
+            Mock `
+                -CommandName Install-AdcsOnlineResponder `
+                -MockWith { Throw (New-Object -TypeName 'Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException') } `
+                -Verifiable
+            #endregion
+
+            Context 'Online Responder is installed' {
+                $Result = Get-TargetResource @TestParametersPresent
+
+                It 'should return Ensure set to Present' {
+                    $Result.Ensure  | Should Be 'Present'
+                }
+
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsOnlineResponder `
+                        -Exactly 1
+                }
+            }
+
+            #region Mocks
+            Mock `
+                -CommandName Install-AdcsOnlineResponder
+            #endregion
+
+            Context 'Online Responder is not installed' {
+                $Result = Get-TargetResource @TestParametersPresent
+
+                It 'should return Ensure set to Absent' {
+                    $Result.Ensure  | Should Be 'Absent'
+                }
+
+                It 'should call expected mocks' {
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsOnlineResponder `
+                        -Exactly 1
+                }
+            }
+        }
+
+        Describe "$DSCResourceName\Set-TargetResource" {
+
+            function Install-AdcsOnlineResponder {
+                [CmdletBinding()]
+                param(
+                    [PSCredential] $Credential,
+
+                    [Switch] $Force,
+
+                    [Switch] $WhatIf
+                )
+            }
+            function Uninstall-AdcsOnlineResponder {
+                [CmdletBinding()]
+                param(
+                    [Switch] $Force
+                )
+            }
+
+            #region Mocks
+            Mock -CommandName Install-AdcsOnlineResponder
+            Mock -CommandName Uninstall-AdcsOnlineResponder
+            #endregion
+
+            Context 'Online Responder is not installed but should be' {
+                Set-TargetResource @TestParametersPresent
+
+                It 'should call expected mocks' {
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsOnlineResponder `
+                        -Exactly 1
+                    Assert-MockCalled `
+                        -CommandName Uninstall-AdcsOnlineResponder `
+                        -Exactly 0
+                }
+            }
+
+            Context 'Online Responder is installed but should not be' {
+                Set-TargetResource @TestParametersAbsent
+
+                It 'should call expected mocks' {
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsOnlineResponder `
+                        -Exactly 0
+                    Assert-MockCalled `
+                        -CommandName Uninstall-AdcsOnlineResponder `
+                        -Exactly 1
+                }
+            }
+        }
+
+        Describe "$DSCResourceName\Test-TargetResource" {
+
+            function Install-AdcsOnlineResponder {
+                [CmdletBinding()]
+                param(
+                    [PSCredential] $Credential,
+
+                    [Switch] $Force,
+
+                    [Switch] $WhatIf
+                )
+            }
+
+            #region Mocks
+            Mock -CommandName Install-AdcsOnlineResponder `
+                -MockWith { Throw (New-Object -TypeName 'Microsoft.CertificateServices.Deployment.Common.OCSP.OnlineResponderSetupException') } `
+                -Verifiable
+            #endregion
+
+            Context 'Online Responder is installed and should be' {
+                $Result = Test-TargetResource @TestParametersPresent
+
+                It 'should return true' {
+                    $Result | Should be $True
+                }
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsOnlineResponder `
+                        -Exactly 1
+                }
+            }
+
+            Context 'Online Responder is installed but should not be' {
+                $Result = Test-TargetResource @TestParametersAbsent
+
+                It 'should return false' {
+                    $Result | Should be $False
+                }
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsOnlineResponder `
+                        -Exactly 1
+                }
+            }
+
+            #region Mocks
+            Mock -CommandName Install-AdcsOnlineResponder `
+                -Verifiable
+            #endregion
+
+            Context 'Online Responder is not installed but should be' {
+                $Result = Test-TargetResource @TestParametersPresent
+
+                It 'should return false' {
+                    $Result | Should be $false
+                }
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsOnlineResponder `
+                        -Exactly 1
+                }
+            }
+
+            Context 'Online Responder is not installed and should not be' {
+                $Result = Test-TargetResource @TestParametersAbsent
+
+                It 'should return true' {
+                    $Result | Should be $True
+                }
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsOnlineResponder `
+                        -Exactly 1
+                }
+            }
+        }
+    }
+    #endregion
+}
+finally
+{
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}

+ 259 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/Tests/Unit/MSFT_xAdcsWebEnrollment.Tests.ps1

@@ -0,0 +1,259 @@
+$script:DSCModuleName   = 'xAdcsDeployment'
+$script:DSCResourceName = 'MSFT_xAdcsWebEnrollment'
+
+#region HEADER
+# Integration Test Template Version: 1.1.0
+[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
+if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $script:DSCModuleName `
+    -DSCResourceName $script:DSCResourceName `
+    -TestType Unit
+#endregion
+
+# Begin Testing
+try
+{
+    #region Pester Tests
+    InModuleScope $script:DSCResourceName {
+        if (-not ([System.Management.Automation.PSTypeName]'Microsoft.CertificateServices.Deployment.Common.WEP.WebEnrollmentSetupException').Type)
+        {
+            # Define the exception class:
+            # Microsoft.CertificateServices.Deployment.Common.WEP.WebEnrollmentSetupException
+            # so that unit tests can be run without ADCS being installed.
+
+            $ExceptionDefinition = @'
+namespace Microsoft.CertificateServices.Deployment.Common.WEP {
+    public class WebEnrollmentSetupException: System.Exception {
+    }
+}
+'@
+            Add-Type -TypeDefinition $ExceptionDefinition
+        }
+
+        $DSCResourceName = 'MSFT_xAdcsWebEnrollment'
+
+        $DummyCredential = New-Object System.Management.Automation.PSCredential ("Administrator",(New-Object -Type SecureString))
+
+        $TestParametersPresent = @{
+            IsSingleInstance = 'Yes'
+            Ensure           = 'Present'
+            CAConfig         = 'CAConfig'
+            Credential       = $DummyCredential
+            Verbose          = $true
+        }
+
+        $TestParametersAbsent = @{
+            IsSingleInstance = 'Yes'
+            Ensure           = 'Absent'
+            Credential       = $DummyCredential
+            Verbose          = $true
+        }
+
+        Describe "$DSCResourceName\Get-TargetResource" {
+
+            function Install-AdcsWebEnrollment {
+                [CmdletBinding()]
+                param(
+                    [String] $CAConfig,
+
+                    [PSCredential] $Credential,
+
+                    [Switch] $Force,
+
+                    [Switch] $WhatIf
+                )
+            }
+
+            #region Mocks
+            Mock `
+                -CommandName Install-AdcsWebEnrollment `
+                -MockWith { Throw (New-Object -TypeName 'Microsoft.CertificateServices.Deployment.Common.WEP.WebEnrollmentSetupException') } `
+                -Verifiable
+            #endregion
+
+            Context 'Web Enrollment is installed' {
+                $Result = Get-TargetResource @TestParametersPresent
+
+                It 'should return Ensure set to Present' {
+                    $Result.Ensure  | Should Be 'Present'
+                }
+
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsWebEnrollment `
+                        -Exactly 1
+                }
+            }
+
+            #region Mocks
+            Mock `
+                -CommandName Install-AdcsWebEnrollment
+            #endregion
+
+            Context 'Web Enrollment is not installed' {
+                $Result = Get-TargetResource @TestParametersPresent
+
+                It 'should return Ensure set to Absent' {
+                    $Result.Ensure  | Should Be 'Absent'
+                }
+
+                It 'should call expected mocks' {
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsWebEnrollment `
+                        -Exactly 1
+                }
+            }
+        }
+
+        Describe "$DSCResourceName\Set-TargetResource" {
+
+            function Install-AdcsWebEnrollment {
+                [CmdletBinding()]
+                param(
+                    [String] $CAConfig,
+
+                    [PSCredential] $Credential,
+
+                    [Switch] $Force,
+
+                    [Switch] $WhatIf
+                )
+            }
+            function Uninstall-AdcsWebEnrollment {
+                [CmdletBinding()]
+                param(
+                    [Switch] $Force
+                )
+            }
+
+            #region Mocks
+            Mock -CommandName Install-AdcsWebEnrollment
+            Mock -CommandName Uninstall-AdcsWebEnrollment
+            #endregion
+
+            Context 'Web Enrollment is not installed but should be' {
+                Set-TargetResource @TestParametersPresent
+
+                It 'should call expected mocks' {
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsWebEnrollment `
+                        -Exactly 1
+                    Assert-MockCalled `
+                        -CommandName Uninstall-AdcsWebEnrollment `
+                        -Exactly 0
+                }
+            }
+
+            Context 'Web Enrollment is installed but should not be' {
+                Set-TargetResource @TestParametersAbsent
+
+                It 'should call expected mocks' {
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsWebEnrollment `
+                        -Exactly 0
+                    Assert-MockCalled `
+                        -CommandName Uninstall-AdcsWebEnrollment `
+                        -Exactly 1
+                }
+            }
+        }
+
+        Describe "$DSCResourceName\Test-TargetResource" {
+
+            function Install-AdcsWebEnrollment {
+                [CmdletBinding()]
+                param(
+                    [String] $CAConfig,
+
+                    [PSCredential] $Credential,
+
+                    [Switch] $Force,
+
+                    [Switch] $WhatIf
+                )
+            }
+
+            #region Mocks
+            Mock -CommandName Install-AdcsWebEnrollment `
+                -MockWith { Throw (New-Object -TypeName 'Microsoft.CertificateServices.Deployment.Common.WEP.WebEnrollmentSetupException') } `
+                -Verifiable
+            #endregion
+
+            Context 'Web Enrollment is installed and should be' {
+                $Result = Test-TargetResource @TestParametersPresent
+
+                It 'should return true' {
+                    $Result | Should be $True
+                }
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsWebEnrollment `
+                        -Exactly 1
+                }
+            }
+
+            Context 'Web Enrollment is installed but should not be' {
+                $Result = Test-TargetResource @TestParametersAbsent
+
+                It 'should return false' {
+                    $Result | Should be $False
+                }
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsWebEnrollment `
+                        -Exactly 1
+                }
+            }
+
+            #region Mocks
+            Mock -CommandName Install-AdcsWebEnrollment `
+                -Verifiable
+            #endregion
+
+            Context 'Web Enrollment is not installed but should be' {
+                $Result = Test-TargetResource @TestParametersPresent
+
+                It 'should return false' {
+                    $Result | Should be $false
+                }
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsWebEnrollment `
+                        -Exactly 1
+                }
+            }
+
+            Context 'Web Enrollment is not installed and should not be' {
+                $Result = Test-TargetResource @TestParametersAbsent
+
+                It 'should return true' {
+                    $Result | Should be $True
+                }
+                It 'should call expected mocks' {
+                    Assert-VerifiableMocks
+                    Assert-MockCalled `
+                        -CommandName Install-AdcsWebEnrollment `
+                        -Exactly 1
+                }
+            }
+        }
+    }
+    #endregion
+}
+finally
+{
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}

+ 87 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xAdcsDeployment/1.1.0.0/xAdcsDeployment.psd1

@@ -0,0 +1,87 @@
+@{
+# Version number of this module.
+ModuleVersion = '1.1.0.0'
+
+# ID used to uniquely identify this module
+GUID = 'f8ddd7fc-c6d6-469e-8a80-c96efabe2fcc'
+
+# Author of this module
+Author = 'Microsoft Corporation'
+
+# Company or vendor of this module
+CompanyName = 'Microsoft Corporation'
+
+# Copyright statement for this module
+Copyright = '(c) 2013 Microsoft Corporation. All rights reserved.'
+
+# Description of the functionality provided by this module
+Description = 'The xCertificateServices module can be used to install or uninstall Certificate Services components in Windows Server.  All of the resources in the DSC Resource Kit are provided AS IS, and are not supported through any Microsoft standard support program or service.'
+
+# Minimum version of the Windows PowerShell engine required by this module
+PowerShellVersion = '4.0'
+
+# Minimum version of the common language runtime (CLR) required by this module
+CLRVersion = '4.0'
+
+# Functions to export from this module
+FunctionsToExport = '*'
+
+# Cmdlets to export from this module
+CmdletsToExport = '*'
+
+# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
+PrivateData = @{
+
+    PSData = @{
+
+        # Tags applied to this module. These help with module discovery in online galleries.
+        Tags = @('DesiredStateConfiguration', 'DSC', 'DSCResourceKit', 'DSCResource')
+
+        # A URL to the license for this module.
+        LicenseUri = 'https://github.com/PowerShell/xAdcsDeployment/blob/master/LICENSE'
+
+        # A URL to the main website for this project.
+        ProjectUri = 'https://github.com/PowerShell/xAdcsDeployment'
+
+        # A URL to an icon representing this module.
+        # IconUri = ''
+
+        # ReleaseNotes of this module
+        ReleaseNotes = '- Converted AppVeyor.yml to pull Pester from PSGallery instead of Chocolatey.
+- Changed AppVeyor.yml to use default image.
+- xAdcsCertificateAuthority:
+  - Change property format in Readme.md to be standard layout.
+  - Converted style to meet HQRM guidelines.
+  - Added verbose logging support.
+  - Added string localization.
+  - Fixed Get-TargetResource by removing IsCA and changing Ensure to return whether or not CA is installed.
+  - Added unit tests.
+  - Updated parameter format to meet HQRM guidelines.
+- xAdcsOnlineResponder:
+  - Change property format in Readme.md to be standard layout.
+  - Added unit test header to be latest version.
+  - Added function help.
+  - Updated parameter format to meet HQRM guidelines.
+  - Updated resource to meet HQRM guidelines.
+- xAdcsWebEnrollment:
+  - Change property format in Readme.md to be standard layout.
+  - Added unit test header to be latest version.
+  - Added function help.
+  - Updated parameter format to meet HQRM guidelines.
+  - Updated resource to meet HQRM guidelines.
+- Added CommonResourceHelper.psm1 (copied from xPSDesiredStateConfiguration).
+- Removed Technet Documentation HTML file from root folder.
+- Removed redundant code from AppVeyor.yml.
+- Fix markdown violations in Readme.md.
+- Updated readme.md to match DSCResource.Template\Readme.md.
+
+'
+
+    } # End of PSData hashtable
+
+} # End of PrivateData hashtable
+}
+
+
+
+

+ 241 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/CertificateCommon/CertificateCommon.psm1

@@ -0,0 +1,241 @@
+#region localizeddata
+if (Test-Path "${PSScriptRoot}\${PSUICulture}")
+{
+    Import-LocalizedData `
+        -BindingVariable LocalizedData `
+        -Filename CertificateCommon.strings.psd1 `
+        -BaseDirectory "${PSScriptRoot}\${PSUICulture}"
+}
+else
+{
+    #fallback to en-US
+    Import-LocalizedData `
+        -BindingVariable LocalizedData `
+        -Filename CertificateCommon.strings.psd1 `
+        -BaseDirectory "${PSScriptRoot}\en-US"
+}
+#endregion
+
+<#
+    .SYNOPSIS
+    Validates the existence of a file at a specific path.
+
+    .PARAMETER Path
+    The location of the file. Supports any path that Test-Path supports.
+
+    .PARAMETER Quiet
+    Returns $false if the file does not exist. By default this function throws an exception if the
+    file is missing.
+
+    .EXAMPLE
+    Test-CertificatePath -Path '\\server\share\Certificates\mycert.cer'
+
+    .EXAMPLE
+    Test-CertificatePath -Path 'C:\certs\my_missing.cer' -Quiet
+
+    .EXAMPLE
+    'D:\CertRepo\a_cert.cer' | Test-CertificatePath
+
+    .EXAMPLE
+    Get-ChildItem -Path D:\CertRepo\*.cer |
+        Test-CertificatePath
+#>
+function Test-CertificatePath
+{
+    [CmdletBinding()]
+    param
+    (
+        [Parameter(Mandatory,ValueFromPipeline)]
+        [String[]]
+        $Path,
+
+        [Parameter()]
+        [Switch]
+        $Quiet
+    )
+
+    Process
+    {
+        foreach ($pathNode in $Path)
+        {
+            if ($pathNode | Test-Path -PathType Leaf)
+            {
+                $true
+            }
+            elseif ($Quiet)
+            {
+                $false
+            }
+            else
+            {
+                New-InvalidArgumentError `
+                    -ErrorId 'CannotFindRootedPath' `
+                    -ErrorMessage ($LocalizedData.FileNotFoundError -f $pathNode)
+            }
+        }
+    }
+} # end function Test-CertificatePath
+
+<#
+    .SYNOPSIS
+    Validates whether a given certificate is valid based on the hash algoritms available on the
+    system.
+
+    .PARAMETER Thumbprint
+    One or more thumbprints to Test.
+
+    .PARAMETER Quiet
+    Returns $false if the thumbprint is not valid. By default this function throws an exception if
+    validation fails.
+
+    .EXAMPLE
+    Test-Thumbprint fd94e3a5a7991cb6ed3cd5dd01045edf7e2284de
+
+    .EXAMPLE
+    Test-Thumbprint `
+        -Thumbprint fd94e3a5a7991cb6ed3cd5dd01045edf7e2284de,0000e3a5a7991cb6ed3cd5dd01045edf7e220000 `
+        -Quiet
+
+    .EXAMPLE
+    Get-ChildItem -Path Cert:\LocalMachine -Recurse |
+        Where-Object -FilterScript { $_.Thumbprint } |
+        Select-Object -Expression Thumbprint |
+        Test-Thumbprint -Verbose
+#>
+function Test-Thumbprint
+{
+    [CmdletBinding()]
+    param
+    (
+        [Parameter(Mandatory,ValueFromPipeline)]
+        [ValidateNotNullOrEmpty()]
+        [String[]]
+        $Thumbprint,
+
+        [Parameter()]
+        [Switch]
+        $Quiet
+    )
+
+    Begin
+    {
+        # Get a list of Hash Providers
+        $hashProviders = [System.AppDomain]::CurrentDomain.GetAssemblies().GetTypes() |
+            Where-Object -FilterScript {
+                $_.BaseType.BaseType -eq [System.Security.Cryptography.HashAlgorithm] -and
+                ($_.Name -cmatch 'Managed$' -or $_.Name -cmatch 'Provider$')
+            }
+
+        # Get a list of all Valid Hash types and lengths into an array
+        $validHashes = @()
+        foreach ($hashProvider in $hashProviders)
+        {
+            $bitSize = ( New-Object -TypeName $hashProvider ).HashSize
+            $validHash = New-Object `
+                -TypeName PSObject `
+                -Property @{
+                    Hash      = $hashProvider.BaseType.Name
+                    BitSize   = $bitSize
+                    HexLength = $bitSize / 4
+                }
+            $validHashes += @( $validHash )
+        }
+    }
+
+    Process
+    {
+        foreach ($hash in $Thumbprint)
+        {
+            $isValid = $false
+
+            foreach ($algorithm in $validHashes)
+            {
+                if ($hash -cmatch "^[a-fA-F0-9]{$($algorithm.HexLength)}$")
+                {
+                    Write-Verbose -Message ($LocalizedData.InvalidHashError `
+                        -f $hash,$algorithm.Hash)
+                    $isValid = $true
+                }
+            }
+
+            if ($Quiet -or $isValid)
+            {
+                $isValid
+            }
+            else
+            {
+                New-InvalidArgumentError `
+                    -ErrorId 'CannotFindRootedPath' `
+                    -ErrorMessage ($LocalizedData.InvalidHashError -f $hash)
+            }
+        }
+    }
+} # end function Test-Thumbprint
+
+<#
+    .SYNOPSIS
+    Throws an InvalidOperation custom exception.
+
+    .PARAMETER ErrorId
+    The error Id of the exception.
+
+    .PARAMETER ErrorMessage
+    The error message text to set in the exception.
+#>
+function New-InvalidOperationError
+{
+    [CmdletBinding()]
+    param
+    (
+        [Parameter(Mandatory)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ErrorId,
+
+        [Parameter(Mandatory)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ErrorMessage
+    )
+
+    $exception = New-Object -TypeName System.InvalidOperationException `
+        -ArgumentList $ErrorMessage
+    $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation
+    $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord `
+        -ArgumentList $exception, $ErrorId, $errorCategory, $null
+    throw $errorRecord
+} # end function New-InvalidOperationError
+
+<#
+    .SYNOPSIS
+    Throws an InvalidArgument custom exception.
+
+    .PARAMETER ErrorId
+    The error Id of the exception.
+
+    .PARAMETER ErrorMessage
+    The error message text to set in the exception.
+#>
+function New-InvalidArgumentError
+{
+    [CmdletBinding()]
+    param
+    (
+        [Parameter(Mandatory)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ErrorId,
+
+        [Parameter(Mandatory)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ErrorMessage
+    )
+
+    $exception = New-Object -TypeName System.ArgumentException `
+        -ArgumentList $ErrorMessage
+    $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument
+    $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord `
+        -ArgumentList $exception, $ErrorId, $errorCategory, $null
+    throw $errorRecord
+} # end function New-InvalidArgumentError

+ 6 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/CertificateCommon/en-us/CertificateCommon.strings.psd1

@@ -0,0 +1,6 @@
+ConvertFrom-StringData @'
+    FileNotFoundError = File '{0}' not found.
+    InvalidHashError = '{0}' is not a valid hash.
+
+    ValidHashMessage = '{0}' is a valid {1} hash.
+'@

+ 661 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xCertReq/MSFT_xCertReq.psm1

@@ -0,0 +1,661 @@
+#Requires -Version 4.0
+
+#region localizeddata
+if (Test-Path "${PSScriptRoot}\${PSUICulture}")
+{
+    Import-LocalizedData `
+        -BindingVariable LocalizedData `
+        -Filename MSFT_xCertReq.strings.psd1 `
+        -BaseDirectory "${PSScriptRoot}\${PSUICulture}"
+}
+else
+{
+    #fallback to en-US
+    Import-LocalizedData `
+        -BindingVariable LocalizedData `
+        -Filename MSFT_xCertReq.strings.psd1 `
+        -BaseDirectory "${PSScriptRoot}\en-US"
+}
+#endregion
+
+# Import the common certificate functions
+Import-Module -Name ( Join-Path `
+    -Path (Split-Path -Path $PSScriptRoot -Parent) `
+    -ChildPath 'CertificateCommon\CertificateCommon.psm1' )
+
+<#
+    .SYNOPSIS
+    Returns the current state of the certificate that may need to be requested.
+
+    .PARAMETER Subject
+    Provide the text string to use as the subject of the certificate.
+
+    .PARAMETER CAServerFQDN
+    The FQDN of the Active Directory Certificate Authority on the local area network.
+
+    .PARAMETER CARootName
+    The name of the certificate authority, by default this will be in format domain-servername-ca.
+
+    .PARAMETER KeyLength
+    The bit length of the encryption key to be used.
+
+    .PARAMETER Exportable
+    The option to allow the certificate to be exportable, by default it will be true.
+
+    .PARAMETER ProviderName
+    The selection of provider for the type of encryption to be used.
+
+    .PARAMETER OID
+    The Object Identifier that is used to name the object.
+
+    .PARAMETER KeyUsage
+    The Keyusage is a restriction method that determines what a certificate can be used for.
+
+    .PARAMETER CertificateTemplate
+    The template used for the definiton of the certificate.
+
+    .PARAMETER SubjectAltName
+    The subject alternative name used to createthe certificate.
+
+    .PARAMETER Credential
+    The credentials that will be used to access the template in the Certificate Authority.
+
+    .PARAMETER AutoRenew
+    Determines if the resource will also renew a certificate within 7 days of expiration.
+#>
+function Get-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Collections.Hashtable])]
+    param
+    (
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Subject,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $CAServerFQDN,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $CARootName,
+
+        [Parameter()]
+        [ValidateSet("1024","2048","4096","8192")]
+        [System.String]
+        $KeyLength = '1024',
+
+        [Parameter()]
+        [System.Boolean]
+        $Exportable = $true,
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ProviderName = '"Microsoft RSA SChannel Cryptographic Provider"',
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $OID = '1.3.6.1.5.5.7.3.1',
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $KeyUsage = '0xa0',
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $CertificateTemplate = 'WebServer',
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $SubjectAltName,
+
+        [Parameter()]
+        [System.Management.Automation.PSCredential]
+        $Credential,
+
+        [Parameter()]
+        [System.Boolean]
+        $AutoRenew
+    )
+
+    # The certificate authority, accessible on the local area network
+    $ca = "'$CAServerFQDN\$CARootName'"
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.GettingCertReqStatusMessage -f $Subject,$CA)
+        ) -join '' )
+
+    $cert = Get-Childitem -Path Cert:\LocalMachine\My |
+        Where-Object -FilterScript {
+            $_.Subject -eq "CN=$Subject" -and `
+            $_.Issuer.split(',')[0] -eq "CN=$CARootName"
+        }
+
+    # If multiple certs have the same subject and were issued by the CA, return the newest
+    $cert = $cert |
+        Sort-Object -Property NotBefore -Descending |
+        Select-Object -First 1
+
+    if ($cert)
+    {
+        Write-Verbose -Message ( @(
+                "$($MyInvocation.MyCommand): "
+                $($LocalizedData.CertificateExistsMessage -f $Subject,$ca,$cert.Thumbprint)
+            ) -join '' )
+
+        $returnValue = @{
+            Subject              = $Cert.Subject.split(',')[0].replace('CN=','')
+            CAServerFQDN         = $null # This value can't be determined from the cert
+            CARootName           = $Cert.Issuer.split(',')[0].replace('CN=','')
+            KeyLength            = $Cert.Publickey.Key.KeySize
+            Exportable           = $null # This value can't be determined from the cert
+            ProviderName         = $null # This value can't be determined from the cert
+            OID                  = $null # This value can't be determined from the cert
+            KeyUsage             = $null # This value can't be determined from the cert
+            CertificateTemplate  = $null # This value can't be determined from the cert
+            SubjectAltName       = $null # This value can't be determined from the cert
+        }
+    }
+    else
+    {
+        $returnValue = @{}
+    }
+
+    $returnValue
+} # end function Get-TargetResource
+
+<#
+    .SYNOPSIS
+    Requests a new certificate based on the parameters provided.
+
+    .PARAMETER Subject
+    Provide the text string to use as the subject of the certificate.
+
+    .PARAMETER CAServerFQDN
+    The FQDN of the Active Directory Certificate Authority on the local area network.
+
+    .PARAMETER CARootName
+    The name of the certificate authority, by default this will be in format domain-servername-ca.
+
+    .PARAMETER KeyLength
+    The bit length of the encryption key to be used.
+
+    .PARAMETER Exportable
+    The option to allow the certificate to be exportable, by default it will be true.
+
+    .PARAMETER ProviderName
+    The selection of provider for the type of encryption to be used.
+
+    .PARAMETER OID
+    The Object Identifier that is used to name the object.
+
+    .PARAMETER KeyUsage
+    The Keyusage is a restriction method that determines what a certificate can be used for.
+
+    .PARAMETER CertificateTemplate
+    The template used for the definiton of the certificate.
+
+    .PARAMETER SubjectAltName
+    The subject alternative name used to createthe certificate.
+
+    .PARAMETER Credential
+    The credentials that will be used to access the template in the Certificate Authority.
+
+    .PARAMETER AutoRenew
+    Determines if the resource will also renew a certificate within 7 days of expiration.
+#>
+function Set-TargetResource
+{
+    [CmdletBinding()]
+    param
+    (
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Subject,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $CAServerFQDN,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $CARootName,
+
+        [Parameter()]
+        [ValidateSet("1024","2048","4096","8192")]
+        [System.String]
+        $KeyLength = '1024',
+
+        [Parameter()]
+        [System.Boolean]
+        $Exportable = $true,
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ProviderName = '"Microsoft RSA SChannel Cryptographic Provider"',
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $OID = '1.3.6.1.5.5.7.3.1',
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $KeyUsage = '0xa0',
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $CertificateTemplate = 'WebServer',
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $SubjectAltName,
+
+        [Parameter()]
+        [System.Management.Automation.PSCredential]
+        $Credential,
+
+        [Parameter()]
+        [System.Boolean]
+        $AutoRenew
+    )
+
+    # The certificate authority, accessible on the local area network
+    $ca = "'$CAServerFQDN\$CARootName'"
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.StartingCertReqMessage -f $Subject,$ca)
+        ) -join '' )
+
+    # If the Subject does not contain a full X500 path, construct just the CN
+    if (($Subject.split('=').Count) -eq 1)
+    {
+        $Subject = "CN=$Subject"
+    } # if
+
+    # If we should look for renewals, check for existing certs
+    if ($AutoRenew)
+    {
+        $certs = Get-Childitem -Path Cert:\LocalMachine\My |
+            Where-Object -FilterScript {
+                $_.Subject -eq $Subject -and `
+                $_.Issuer.split(',')[0] -eq "CN=$CARootName" -and `
+                $_.NotAfter -lt (Get-Date).AddDays(30)
+            }
+
+        # If multiple certs have the same subject and were issued by the CA and are 30 days from expiration, return the newest
+        $firstCert = $certs |
+            Sort-Object -Property NotBefore -Descending |
+            Select-Object -First 1
+        $thumbprint = $firstCert |
+            ForEach-Object -Process { $_.Thumbprint }
+    } # if
+
+    # Information that will be used in the INF file to generate the certificate request
+    # In future versions, select variables from the list below could be moved to parameters!
+    $Subject             = "`"$Subject`""
+    $keySpec             = '1'
+    $machineKeySet       = 'TRUE'
+    $smime               = 'FALSE'
+    $privateKeyArchive   = 'FALSE'
+    $userProtected       = 'FALSE'
+    $useExistingKeySet   = 'FALSE'
+    $providerType        = '12'
+    $requestType         = 'CMC'
+
+    # A unique identifier for temporary files that will be used when interacting with the command line utility
+    $guid = [system.guid]::NewGuid().guid
+    $workingPath = Join-Path -Path $ENV:Temp -ChildPath "xCertReq-$guid"
+    $infPath = [System.IO.Path]::ChangeExtension($workingPath,'.inf')
+    $reqPath = [System.IO.Path]::ChangeExtension($workingPath,'.req')
+    $cerPath = [System.IO.Path]::ChangeExtension($workingPath,'.cer')
+    $rspPath = [System.IO.Path]::ChangeExtension($workingPath,'.rsp')
+
+    # Create INF file
+    $requestDetails = @"
+[NewRequest]
+Subject = $Subject
+KeySpec = $keySpec
+KeyLength = $KeyLength
+Exportable = $($Exportable.ToString().ToUpper())
+MachineKeySet = $MachineKeySet
+SMIME = $smime
+PrivateKeyArchive = $privateKeyArchive
+UserProtected = $userProtected
+UseExistingKeySet = $useExistingKeySet
+ProviderName = $ProviderName
+ProviderType = $providerType
+RequestType = $requestType
+KeyUsage = $KeyUsage
+[RequestAttributes]
+CertificateTemplate = $CertificateTemplate
+[EnhancedKeyUsageExtension]
+OID = $OID
+"@
+    if ($PSBoundParameters.ContainsKey('SubjectAltName'))
+    {
+        # If a Subject Alt Name was specified, add it.
+        $requestDetails += @"
+
+[Extensions]
+2.5.29.17 = `"{text}$SubjectAltName`"
+"@
+    }
+    if ($thumbprint)
+    {
+        $requestDetails += @"
+
+RenewalCert = $Thumbprint
+"@
+    }
+    Set-Content -Path $infPath -Value $requestDetails
+
+    # Certreq.exe:
+    # Syntax: https://technet.microsoft.com/en-us/library/cc736326.aspx
+    # Reference: https://support2.microsoft.com/default.aspx?scid=kb;EN-US;321051
+
+    # NEW: Create a new request as directed by PolicyFileIn
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.CreateRequestCertificateMessage -f $infPath,$reqPath)
+        ) -join '' )
+
+    $createRequest = & certreq.exe @('-new','-q',$infPath,$reqPath)
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.CreateRequestResultCertificateMessage -f $createRequest)
+        ) -join '' )
+
+    # SUBMIT: Submit a request to a Certification Authority.
+    # DSC runs in the context of LocalSystem, which uses the Computer account in Active Directory
+    # to authenticate to network resources
+    # The Credential paramter with xPDT is used to impersonate a user making the request
+    if (Test-Path -Path $reqPath)
+    {
+        Write-Verbose -Message ( @(
+                "$($MyInvocation.MyCommand): "
+                $($LocalizedData.SubmittingRequestCertificateMessage -f $reqPath,$cerPath,$ca)
+            ) -join '' )
+
+        if ($Credential)
+        {
+            Import-Module -Name $PSScriptRoot\..\PDT\PDT.psm1 -Force
+
+            # Assemble the command and arguments to pass to the powershell process that
+            # will request the certificate
+            $certReqOutPath = [System.IO.Path]::ChangeExtension($workingPath,'.out')
+            $command = "$PSHOME\PowerShell.exe"
+            $arguments = "-Command ""& $ENV:SystemRoot\system32\certreq.exe" + `
+                " @('-submit','-q','-config',$ca,'$reqPath','$cerPath')" + `
+                " | Set-Content -Path '$certReqOutPath'"""
+
+            # This may output a win32-process object, but it often does not because of
+            # a timing issue in PDT (the process has often completed before the
+            # process can be read in).
+            $null = Start-Win32Process `
+                -Path $command `
+                -Arguments $arguments `
+                -Credential $Credential
+
+            Write-Verbose -Message ( @(
+                "$($MyInvocation.MyCommand): "
+                $($LocalizedData.SubmittingRequestProcessCertificateMessage)
+            ) -join '' )
+
+            $null = Wait-Win32ProcessEnd `
+                -Path $command `
+                -Arguments $arguments `
+                -Credential $Credential
+
+            if (Test-Path -Path $certReqOutPath)
+            {
+                $submitRequest = Get-Content -Path $certReqOutPath
+                Remove-Item -Path $certReqOutPath -Force
+            }
+            else
+            {
+                New-InvalidArgumentError `
+                    -ErrorId 'CertReqOutNotFoundError' `
+                    -ErrorMessage ($LocalizedData.CertReqOutNotFoundError -f $certReqOutPath)
+            } # if
+        }
+        else
+        {
+            $submitRequest = & certreq.exe @('-submit','-q','-config',$CA,$ReqPath,$CerPath)
+        } # if
+
+        Write-Verbose -Message ( @(
+                "$($MyInvocation.MyCommand): "
+                $($LocalizedData.SubmittingRequestResultCertificateMessage -f $submitRequest)
+            ) -join '' )
+    }
+    else
+    {
+        New-InvalidArgumentError `
+            -ErrorId 'CertificateReqNotFoundError' `
+            -ErrorMessage ($LocalizedData.CertificateReqNotFoundError -f $reqPath)
+    } # if
+
+    # ACCEPT: Accept the request
+    if (Test-Path -Path $cerPath)
+    {
+        Write-Verbose -Message ( @(
+                "$($MyInvocation.MyCommand): "
+                $($LocalizedData.AcceptingRequestCertificateMessage -f $cerPath,$ca)
+            ) -join '' )
+
+        $acceptRequest = & certreq.exe @('-accept','-machine','-q',$cerPath)
+
+        Write-Verbose -Message ( @(
+                "$($MyInvocation.MyCommand): "
+                $($LocalizedData.AcceptingRequestResultCertificateMessage -f $acceptRequest)
+            ) -join '' )
+    }
+    else
+    {
+        New-InvalidArgumentError `
+            -ErrorId 'CertificateCerNotFoundError' `
+            -ErrorMessage ($LocalizedData.CertificateCerNotFoundError -f $cerPath)
+    } # if
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.CleaningUpRequestFilesMessage -f "$($workingPath).*")
+        ) -join '' )
+    Remove-Item -Path "$($workingPath).*" -Force
+} # end function Set-TargetResource
+
+<#
+    .SYNOPSIS
+    Tests if a new certificate should be requested.
+
+    .PARAMETER Subject
+    Provide the text string to use as the subject of the certificate.
+
+    .PARAMETER CAServerFQDN
+    The FQDN of the Active Directory Certificate Authority on the local area network.
+
+    .PARAMETER CARootName
+    The name of the certificate authority, by default this will be in format domain-servername-ca.
+
+    .PARAMETER KeyLength
+    The bit length of the encryption key to be used.
+
+    .PARAMETER Exportable
+    The option to allow the certificate to be exportable, by default it will be true.
+
+    .PARAMETER ProviderName
+    The selection of provider for the type of encryption to be used.
+
+    .PARAMETER OID
+    The Object Identifier that is used to name the object.
+
+    .PARAMETER KeyUsage
+    The Keyusage is a restriction method that determines what a certificate can be used for.
+
+    .PARAMETER CertificateTemplate
+    The template used for the definiton of the certificate.
+
+    .PARAMETER SubjectAltName
+    The subject alternative name used to createthe certificate.
+
+    .PARAMETER Credential
+    The credentials that will be used to access the template in the Certificate Authority.
+
+    .PARAMETER AutoRenew
+    Determines if the resource will also renew a certificate within 7 days of expiration.
+#>
+function Test-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([System.Boolean])]
+    param
+    (
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Subject,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $CAServerFQDN,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $CARootName,
+
+        [Parameter()]
+        [ValidateSet("1024","2048","4096","8192")]
+        [System.String]
+        $KeyLength = '1024',
+
+        [Parameter()]
+        [System.Boolean]
+        $Exportable = $true,
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $ProviderName = '"Microsoft RSA SChannel Cryptographic Provider"',
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $OID = '1.3.6.1.5.5.7.3.1',
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $KeyUsage = '0xa0',
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $CertificateTemplate = 'WebServer',
+
+        [Parameter()]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $SubjectAltName,
+
+        [Parameter()]
+        [System.Management.Automation.PSCredential]
+        $Credential,
+
+        [Parameter()]
+        [System.Boolean]
+        $AutoRenew
+    )
+
+    # The certificate authority, accessible on the local area network
+    $ca = "'$CAServerFQDN\$CARootName'"
+
+    # If the Subject does not contain a full X500 path, construct just the CN
+    if (($Subject.split('=').count) -eq 1)
+    {
+        $Subject = "CN=$Subject"
+    }
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.TestingCertReqStatusMessage -f $Subject,$ca)
+        ) -join '' )
+
+    $cert = Get-Childitem -Path Cert:\LocalMachine\My |
+        Where-Object -FilterScript {
+            $_.Subject -eq $Subject -and `
+            $_.Issuer.split(',')[0] -eq "CN=$CARootName"
+        }
+
+    # If multiple certs have the same subject and were issued by the CA, return the newest
+    $cert = $cert |
+        Sort-Object -Property NotBefore -Descending |
+        Select-Object -First 1
+
+    if ($cert)
+    {
+        Write-Verbose -Message ( @(
+                "$($MyInvocation.MyCommand): "
+                $($LocalizedData.CertificateExistsMessage -f $Subject,$ca,$cert.Thumbprint)
+            ) -join '' )
+
+        if ($AutoRenew) {
+            if ($Cert.NotAfter -le (Get-Date).AddDays(-30))
+            {
+                # The certificate was found but it is expiring within 30 days or has expired
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.ExpiringCertificateMessage -f $Subject,$ca,$cert.Thumbprint)
+                    ) -join '' )
+                return $false
+            } # if
+        }
+        else
+        {
+            if ($cert.NotAfter -le (Get-Date))
+            {
+                # The certificate has expired
+                Write-Verbose -Message ( @(
+                        "$($MyInvocation.MyCommand): "
+                        $($LocalizedData.ExpiredCertificateMessage -f $Subject,$ca,$cert.Thumbprint)
+                    ) -join '' )
+                return $false
+            } # if
+        } # if
+
+        # The certificate was found and is OK - so no change required.
+        Write-Verbose -Message ( @(
+                "$($MyInvocation.MyCommand): "
+                $($LocalizedData.ValidCertificateExistsMessage -f $Subject,$ca,$cert.Thumbprint)
+            ) -join '' )
+        return $true
+    } # if
+
+    # A valid certificate was not found
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.NoValidCertificateMessage -f $Subject,$ca)
+        ) -join '' )
+    return $false
+} # end function Test-TargetResource

+ 16 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xCertReq/MSFT_xCertReq.schema.mof

@@ -0,0 +1,16 @@
+[ClassVersion("0.1.0.0"), FriendlyName("xCertReq")]
+class MSFT_xCertReq : OMI_BaseResource
+{
+    [Key, Description("Provide the text string to use as the subject of the certificate.")] String Subject;
+    [Required, Description("The FQDN of the Active Directory Certificate Authority on the local area network.")] String CAServerFQDN;
+    [Required, Description("The name of the certificate authority, by default this will be in format domain-servername-ca.")] String CARootName;
+    [Write, Description("The bit length of the encryption key to be used"), ValueMap{"1024","2048","4096","8192"}, Values{"1024","2048","4096","8192"}] String KeyLength;
+    [Write, Description("The option to allow the certificate to be exportable, by default it will be true.")] Boolean Exportable;
+    [Write, Description("The selection of provider for the type of encryption to be used.")] String ProviderName;
+    [Write, Description("The Object Identifier that is used to name the object.")] String OID;
+    [Write, Description("The Keyusage is a restriction method that determines what a certificate can be used for.")] String KeyUsage;
+    [Write, Description("The template used for the definiton of the certificate.")] String CertificateTemplate;
+    [Write, Description("The subject alternative name used to createthe certificate.")] String SubjectAltName;
+    [Write, Description("The credentials that will be used to access the template in the Certificate Authority."), EmbeddedInstance("MSFT_Credential")] String Credential;
+    [Write, Description("Determines if the resource will also renew a certificate within 7 days of expiration.")] Boolean AutoRenew;
+};

+ 22 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xCertReq/en-us/MSFT_xCertReq.strings.psd1

@@ -0,0 +1,22 @@
+ConvertFrom-StringData @'
+    GettingCertReqStatusMessage = Getting Certificate with Subject '{0}' issued by {1}.
+    CertificateExistsMessage = Certificate with Subject '{0}' issued by {1} found with thumbprint '{2}'.
+    StartingCertReqMessage = Starting Certificate request with Subject '{0}' issued by {1}.
+    CreateRequestCertificateMessage = Creating certificate request '{1}' from '{0}'.
+    CreateRequestResultCertificateMessage = Create certificate request result: {0}
+    SubmittingRequestCertificateMessage = Submitting certificate request '{0}' returning '{1}' issued by {2}.
+    SubmittingRequestProcessCertificateMessage = Submitting certificate request using separate process.
+    SubmittingRequestResultCertificateMessage = Submitting certificate request result: {0}
+    AcceptingRequestCertificateMessage = Accepting certificate '{1}' issued by {0}.
+    AcceptingRequestResultCertificateMessage = Accepting certificate result: {0}
+    CleaningUpRequestFilesMessage = Cleaning up certificate request files '{0}'.
+    TestingCertReqStatusMessage = Testing Certificate with Subject '{0}' issued by {1}.
+    ExpiringCertificateMessage = The certificate found with subject '{0}' issued by {1} with thumbprint '{2}' is about to expire.
+    NoValidCertificateMessage = No valid certificate found with subject '{0}' issued by {1}.
+    ExpiredCertificateMessage = The certificate found with subject '{0}' issued by {1} with thumbprint '{2}' has expired.
+    InvalidCertificateMessage = The certificate found with subject '{0}' issued by {1} with thumbprint '{2}' is inavlid.
+    ValidCertificateExistsMessage = Valid certificate '{2}' found with subject '{0}' issued by {1}.
+    CertificateReqNotFoundError = Certificate Request file '{0}' not found.
+    CertificateCerNotFoundError = Certificate file '{0}' not found.
+    CertReqOutNotFoundError = CertReq.exe output file '{0}' not found.
+'@

+ 278 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xCertificateImport/MSFT_xCertificateImport.psm1

@@ -0,0 +1,278 @@
+#Requires -Version 4.0
+
+#region localizeddata
+if (Test-Path "${PSScriptRoot}\${PSUICulture}")
+{
+    Import-LocalizedData `
+        -BindingVariable LocalizedData `
+        -Filename MSFT_xCertificateImport.strings.psd1 `
+        -BaseDirectory "${PSScriptRoot}\${PSUICulture}"
+}
+else
+{
+    #fallback to en-US
+    Import-LocalizedData `
+        -BindingVariable LocalizedData `
+        -Filename MSFT_xCertificateImport.strings.psd1 `
+        -BaseDirectory "${PSScriptRoot}\en-US"
+}
+#endregion
+
+# Import the common certificate functions
+Import-Module -Name ( Join-Path `
+    -Path (Split-Path -Path $PSScriptRoot -Parent) `
+    -ChildPath 'CertificateCommon\CertificateCommon.psm1' )
+
+<#
+    .SYNOPSIS
+    Returns the current state of the CER Certificte file that should be imported.
+
+    .PARAMETER Thumbprint
+    The thumbprint (unique identifier) of the certificate you're importing.
+
+    .PARAMETER Path
+    The path to the CER file you want to import.
+
+    .PARAMETER Location
+    The Windows Certificate Store Location to import the certificate to.
+
+    .PARAMETER Store
+    The Windows Certificate Store Name to import the certificate to.
+
+    .PARAMETER Ensure
+    Specifies whether the certificate should be present or absent.
+#>
+function Get-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([Hashtable])]
+    param
+    (
+        [Parameter(Mandatory = $true)]
+        [ValidateScript( { $_ | Test-Thumbprint } )]
+        [System.String]
+        $Thumbprint,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateScript( { $_ | Test-CertificatePath } )]
+        [System.String]
+        $Path,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('CurrentUser', 'LocalMachine')]
+        [System.String]
+        $Location,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Store,
+
+        [Parameter()]
+        [ValidateSet('Present', 'Absent')]
+        [System.String]
+        $Ensure = 'Present'
+    )
+
+    $certificateStore = 'Cert:' |
+        Join-Path -ChildPath $Location |
+        Join-Path -ChildPath $Store
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.GettingCertificateStatusMessage -f $Thumbprint,$certificateStore)
+        ) -join '' )
+
+    if ((Test-Path $certificateStore) -eq $false)
+    {
+        New-InvalidArgumentError `
+            -ErrorId 'CertificateStoreNotFound' `
+            -ErrorMessage ($LocalizedData.CertificateStoreNotFoundError -f $certificateStore)
+    }
+
+    $checkEnsure = [Bool] (
+        $certificateStore |
+        Get-ChildItem |
+        Where-Object -FilterScript { $_.Thumbprint -ieq $Thumbprint }
+    )
+    if ($checkEnsure)
+    {
+        $Ensure = 'Present'
+    }
+    else
+    {
+        $Ensure = 'Absent'
+    }
+
+    @{
+        Thumbprint = $Thumbprint
+        Path       = $Path
+        Location   = $Location
+        Store      = $Store
+        Ensure     = $Ensure
+    }
+} # end function Get-TargetResource
+
+<#
+    .SYNOPSIS
+    Tests if the CER Certificate file needs to be imported or removed.
+
+    .PARAMETER Thumbprint
+    The thumbprint (unique identifier) of the certificate you're importing.
+
+    .PARAMETER Path
+    The path to the CER file you want to import.
+
+    .PARAMETER Location
+    The Windows Certificate Store Location to import the certificate to.
+
+    .PARAMETER Store
+    The Windows Certificate Store Name to import the certificate to.
+
+    .PARAMETER Ensure
+    Specifies whether the certificate should be present or absent.
+#>
+function Test-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([Boolean])]
+    param
+    (
+        [Parameter(Mandatory = $true)]
+        [ValidateScript( { $_ | Test-Thumbprint } )]
+        [System.String]
+        $Thumbprint,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateScript( { $_ | Test-CertificatePath } )]
+        [System.String]
+        $Path,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('CurrentUser', 'LocalMachine')]
+        [System.String]
+        $Location,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Store,
+
+        [Parameter()]
+        [ValidateSet('Present', 'Absent')]
+        [System.String]
+        $Ensure = 'Present'
+    )
+
+    $result = @(Get-TargetResource @PSBoundParameters)
+
+    $certificateStore = 'Cert:' |
+        Join-Path -ChildPath $Location |
+        Join-Path -ChildPath $Store
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.TestingCertificateStatusMessage -f $Thumbprint,$CertificateStore)
+        ) -join '' )
+
+    if ($Ensure -ne $result.Ensure)
+    {
+        return $false
+    }
+    return $true
+} # end function Test-TargetResource
+
+<#
+    .SYNOPSIS
+    Imports or removes the specified CER Certifiicate file.
+
+    .PARAMETER Thumbprint
+    The thumbprint (unique identifier) of the certificate you're importing.
+
+    .PARAMETER Path
+    The path to the CER file you want to import.
+
+    .PARAMETER Location
+    The Windows Certificate Store Location to import the certificate to.
+
+    .PARAMETER Store
+    The Windows Certificate Store Name to import the certificate to.
+
+    .PARAMETER Ensure
+    Specifies whether the certificate should be present or absent.
+#>
+function Set-TargetResource
+{
+    [CmdletBinding(SupportsShouldProcess)]
+    param
+    (
+        [Parameter(Mandatory = $true)]
+        [ValidateScript( { $_ | Test-Thumbprint } )]
+        [System.String]
+        $Thumbprint,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateScript( { $_ | Test-CertificatePath } )]
+        [System.String]
+        $Path,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('CurrentUser', 'LocalMachine')]
+        [System.String]
+        $Location,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Store,
+
+        [Parameter()]
+        [ValidateSet('Present', 'Absent')]
+        [System.String]
+        $Ensure = 'Present'
+    )
+
+    $certificateStore = 'Cert:' |
+        Join-Path -ChildPath $Location |
+        Join-Path -ChildPath $Store
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.SettingCertificateStatusMessage -f $Thumbprint,$certificateStore)
+        ) -join '' )
+
+    if ($Ensure -ieq 'Present')
+    {
+        if ($PSCmdlet.ShouldProcess(($LocalizedData.ImportingCertificateShould `
+            -f $Path,$certificateStore)))
+        {
+            # Import the certificate into the Store
+            Write-Verbose -Message ( @(
+                    "$($MyInvocation.MyCommand): "
+                    $($LocalizedData.ImportingCertficateMessage -f $Path,$certificateStore)
+                ) -join '' )
+
+            $param = @{
+                CertStoreLocation = $certificateStore
+                FilePath          = $Path
+                Verbose           = $VerbosePreference
+            }
+
+            Import-Certificate @param
+        }
+    }
+    elseif ($Ensure -ieq 'Absent')
+    {
+        # Remove the certificate from the Store
+        Write-Verbose -Message ( @(
+                "$($MyInvocation.MyCommand): "
+                $($LocalizedData.RemovingCertficateMessage -f $Thumbprint,$certificateStore)
+            ) -join '' )
+
+        Get-ChildItem -Path $certificateStore |
+            Where-Object { $_.Thumbprint -ieq $Thumbprint } |
+            Remove-Item -Force
+    }
+}  # end function Test-TargetResource
+
+Export-ModuleMember -Function *-TargetResource

+ 9 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xCertificateImport/MSFT_xCertificateImport.schema.mof

@@ -0,0 +1,9 @@
+[ClassVersion("1.0"), FriendlyName("xCertificateImport")]
+class MSFT_xCertificateImport : OMI_BaseResource
+{
+    [Key,Description("The thumbprint (unique identifier) of the certificate you're importing.")] string Thumbprint;
+    [Required,Description("The path to the CER file you want to import.")] string Path;
+    [Key,Description("The Windows Certificate Store Location to import the certificate to."),ValueMap{"LocalMachine", "CurrentUser"},Values{"LocalMachine", "CurrentUser"}] string Location;
+    [Key,Description("The Windows Certificate Store Name to import the certificate to.")] string Store;
+    [Write,Description("Specifies whether the certificate should be present or absent."),ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] string Ensure;
+};

+ 11 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xCertificateImport/en-us/MSFT_xCertificateImport.strings.psd1

@@ -0,0 +1,11 @@
+ConvertFrom-StringData @'
+    GettingCertificateStatusMessage = Getting Certificate existence '{0}' in '{1}'.
+    TestingCertificateStatusMessage = Testing Certificate '{0}' existence in '{1}'.
+    SettingCertificateStatusMessage = Setting Certificate '{0}' existence in '{1}'.
+    ImportingCertficateMessage = Importing Certificate '{0}' into '{1}'.
+    RemovingCertficateMessage = Removing Certificate '{0}' from '{1}'.
+
+    CertificateStoreNotFoundError = Certificate Store '{0}' not found.
+
+    ImportingCertificateShould = Importing certificate '{0}' into '{1}'
+'@

+ 325 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xPfxImport/MSFT_xPfxImport.psm1

@@ -0,0 +1,325 @@
+#Requires -Version 4.0
+
+#region localizeddata
+if (Test-Path "${PSScriptRoot}\${PSUICulture}")
+{
+    Import-LocalizedData `
+        -BindingVariable LocalizedData `
+        -Filename MSFT_xPfxImport.strings.psd1 `
+        -BaseDirectory "${PSScriptRoot}\${PSUICulture}"
+}
+else
+{
+    #fallback to en-US
+    Import-LocalizedData `
+        -BindingVariable LocalizedData `
+        -Filename MSFT_xPfxImport.strings.psd1 `
+        -BaseDirectory "${PSScriptRoot}\en-US"
+}
+#endregion
+
+# Import the common certificate functions
+Import-Module -Name ( Join-Path `
+    -Path (Split-Path -Path $PSScriptRoot -Parent) `
+    -ChildPath 'CertificateCommon\CertificateCommon.psm1' )
+
+<#
+    .SYNOPSIS
+    Returns the current state of the PFX Certificte file that should be imported.
+
+    .PARAMETER Thumbprint
+    The thumbprint (unique identifier) of the PFX file you're importing.
+
+    .PARAMETER Path
+    The Windows Certificate Store Location to import the PFX file to.
+
+    .PARAMETER Location
+    The Windows Certificate Store Location to import the PFX file to.
+
+    .PARAMETER Store
+    The Windows Certificate Store Name to import the PFX file to.
+
+    .PARAMETER Exportable
+    Determines whether the private key is exportable from the machine after it has been imported.
+
+    .PARAMETER Credential
+    A [PSCredential] object that is used to decrypt the PFX file. Only the password is used, so any user name is valid.
+
+    .PARAMETER Ensure
+    Specifies whether the PFX file should be present or absent.
+#>
+function Get-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([Hashtable])]
+    param
+    (
+        [Parameter(Mandatory = $true)]
+        [ValidateScript( { $_ | Test-Thumbprint } )]
+        [System.String]
+        $Thumbprint,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateScript( { $_ | Test-CertificatePath } )]
+        [System.String]
+        $Path,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('CurrentUser', 'LocalMachine')]
+        [System.String]
+        $Location,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Store,
+
+        [Parameter()]
+        [Boolean]
+        $Exportable = $false,
+
+        [Parameter()]
+        [PSCredential]
+        $Credential,
+
+        [Parameter()]
+        [ValidateSet('Present', 'Absent')]
+        [System.String]
+        $Ensure = 'Present'
+    )
+
+    $certificateStore = 'Cert:' |
+        Join-Path -ChildPath $Location |
+        Join-Path -ChildPath $Store
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.GettingPfxStatusMessage -f $Thumbprint,$certificateStore)
+        ) -join '' )
+
+    if ((Test-Path $certificateStore) -eq $false)
+    {
+        New-InvalidArgumentError `
+            -ErrorId 'CertificateStoreNotFound' `
+            -ErrorMessage ($LocalizedData.CertificateStoreNotFoundError -f $certificateStore)
+    }
+
+    $checkEnsure = [Bool](
+        $certificateStore |
+        Get-ChildItem |
+        Where-Object -FilterScript {$_.Thumbprint -ieq $Thumbprint}
+    )
+    if ($checkEnsure)
+    {
+        $Ensure = 'Present'
+    }
+    else
+    {
+        $Ensure = 'Absent'
+    }
+
+    @{
+        Thumbprint = $Thumbprint
+        Path       = $Path
+        Location   = $Location
+        Store      = $Store
+        Exportable = $Exportable
+        Ensure     = $Ensure
+    }
+} # end function Get-TargetResource
+
+<#
+    .SYNOPSIS
+    Tests if the PFX Certificate file needs to be imported or removed.
+
+    .PARAMETER Thumbprint
+    The thumbprint (unique identifier) of the PFX file you're importing.
+
+    .PARAMETER Path
+    The Windows Certificate Store Location to import the PFX file to.
+
+    .PARAMETER Location
+    The Windows Certificate Store Location to import the PFX file to.
+
+    .PARAMETER Store
+    The Windows Certificate Store Name to import the PFX file to.
+
+    .PARAMETER Exportable
+    Determines whether the private key is exportable from the machine after it has been imported.
+
+    .PARAMETER Credential
+    A [PSCredential] object that is used to decrypt the PFX file. Only the password is used, so any user name is valid.
+
+    .PARAMETER Ensure
+    Specifies whether the PFX file should be present or absent.
+#>
+function Test-TargetResource
+{
+    [CmdletBinding()]
+    [OutputType([Boolean])]
+    param
+    (
+        [Parameter(Mandatory = $true)]
+        [ValidateScript( { $_ | Test-Thumbprint } )]
+        [System.String]
+        $Thumbprint,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateScript( { $_ | Test-CertificatePath } )]
+        [System.String]
+        $Path,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('CurrentUser', 'LocalMachine')]
+        [System.String]
+        $Location,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Store,
+
+        [Parameter()]
+        [Boolean]
+        $Exportable = $false,
+
+        [Parameter()]
+        [PSCredential]
+        $Credential,
+
+        [Parameter()]
+        [ValidateSet('Present', 'Absent')]
+        [System.String]
+        $Ensure = 'Present'
+    )
+
+    $result = @(Get-TargetResource @PSBoundParameters)
+
+    $certificateStore = 'Cert:' |
+        Join-Path -ChildPath $Location |
+        Join-Path -ChildPath $Store
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.TestingPfxStatusMessage -f $Thumbprint,$certificateStore)
+        ) -join '' )
+
+
+    if ($Ensure -ne $result.Ensure)
+    {
+        return $false
+    }
+    return $true
+} # end function Test-TargetResource
+
+<#
+    .SYNOPSIS
+    Imports or removes the specified PFX Certifiicate file.
+
+    .PARAMETER Thumbprint
+    The thumbprint (unique identifier) of the PFX file you're importing.
+
+    .PARAMETER Path
+    The Windows Certificate Store Location to import the PFX file to.
+
+    .PARAMETER Location
+    The Windows Certificate Store Location to import the PFX file to.
+
+    .PARAMETER Store
+    The Windows Certificate Store Name to import the PFX file to.
+
+    .PARAMETER Exportable
+    Determines whether the private key is exportable from the machine after it has been imported.
+
+    .PARAMETER Credential
+    A [PSCredential] object that is used to decrypt the PFX file. Only the password is used, so any user name is valid.
+
+    .PARAMETER Ensure
+    Specifies whether the PFX file should be present or absent.
+#>
+function Set-TargetResource
+{
+    [CmdletBinding(SupportsShouldProcess)]
+    param
+    (
+        [Parameter(Mandatory = $true)]
+        [ValidateScript( { $_ | Test-Thumbprint } )]
+        [System.String]
+        $Thumbprint,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateScript( { $_ | Test-CertificatePath } )]
+        [System.String]
+        $Path,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateSet('CurrentUser', 'LocalMachine')]
+        [System.String]
+        $Location,
+
+        [Parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [System.String]
+        $Store,
+
+        [Parameter()]
+        [Boolean]
+        $Exportable = $false,
+
+        [Parameter()]
+        [PSCredential]
+        $Credential,
+
+        [Parameter()]
+        [ValidateSet('Present', 'Absent')]
+        [System.String]
+        $Ensure = 'Present'
+    )
+
+    $certificateStore = 'Cert:' |
+        Join-Path -ChildPath $Location |
+        Join-Path -ChildPath $Store
+
+    Write-Verbose -Message ( @(
+            "$($MyInvocation.MyCommand): "
+            $($LocalizedData.SettingPfxStatusMessage -f $Thumbprint,$certificateStore)
+        ) -join '' )
+
+    if ($Ensure -ieq 'Present')
+    {
+        if ($PSCmdlet.ShouldProcess(($LocalizedData.ImportingPfxShould `
+            -f $Path,$certificateStore)))
+        {
+            # Import the certificate into the Store
+            Write-Verbose -Message ( @(
+                    "$($MyInvocation.MyCommand): "
+                    $($LocalizedData.ImportingPfxMessage -f $Path,$certificateStore)
+                ) -join '' )
+
+            $param = @{
+                Exportable        = $Exportable
+                CertStoreLocation = $certificateStore
+                FilePath          = $Path
+            }
+            if ($Credential)
+            {
+                $param['Password'] = $Credential.Password
+            }
+            Import-PfxCertificate @param
+        }
+    }
+    elseif ($Ensure -ieq 'Absent')
+    {
+        # Remove the certificate from the Store
+        Write-Verbose -Message ( @(
+                "$($MyInvocation.MyCommand): "
+                $($LocalizedData.RemovingPfxMessage -f $Thumbprint,$certificateStore)
+            ) -join '' )
+
+        Get-ChildItem -Path $certificateStore |
+            Where-Object { $_.Thumbprint -ieq $thumbprint } |
+            Remove-Item -Force
+    }
+} # end function Set-TargetResource
+
+Export-ModuleMember -Function *-TargetResource

+ 11 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xPfxImport/MSFT_xPfxImport.schema.mof

@@ -0,0 +1,11 @@
+[ClassVersion("1.0"), FriendlyName("xPfxImport")]
+class MSFT_xPfxImport : OMI_BaseResource
+{
+    [Key,Description("The thumbprint (unique identifier) of the PFX file you're importing.")] string Thumbprint;
+    [Required,Description("The path to the PFX file you want to import.")] string Path;
+    [Key,Description("The Windows Certificate Store Location to import the PFX file to."),ValueMap{"LocalMachine", "CurrentUser"},Values{"LocalMachine", "CurrentUser"}] string Location;
+    [Key,Description("The Windows Certificate Store Name to import the PFX file to.")] string Store;
+    [write,Description("Determines whether the private key is exportable from the machine after it has been imported")] boolean Exportable;
+    [write,Description("A [PSCredential] object that is used to decrypt the PFX file."),EmbeddedInstance("MSFT_Credential")] string Credential;
+    [Write,Description("Specifies whether the PFX file should be present or absent."),ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] string Ensure;
+};

+ 11 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/MSFT_xPfxImport/en-us/MSFT_xPfxImport.strings.psd1

@@ -0,0 +1,11 @@
+ConvertFrom-StringData @'
+    GettingPfxStatusMessage = Getting Pfx existence '{0}' in '{1}'.
+    TestingPfxStatusMessage = Testing Pfx '{0}' existence in '{1}'.
+    SettingPfxStatusMessage = Setting Pfx '{0}' existence in '{1}'.
+    ImportingPfxMessage = Importing Pfx '{0}' into '{1}'.
+    RemovingPfxMessage = Removing Pfx '{0}' from '{1}'.
+
+    CertificateStoreNotFoundError = Certificate Store '{0}' not found.
+
+    ImportingPfxShould = Importing Pfx '{0}' into '{1}'
+'@

+ 725 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/PDT/PDT.psm1

@@ -0,0 +1,725 @@
+#region localizeddata
+if (Test-Path "${PSScriptRoot}\${PSUICulture}")
+{
+    Import-LocalizedData `
+        -BindingVariable LocalizedData `
+        -Filename PDT.strings.psd1 `
+        -BaseDirectory "${PSScriptRoot}\${PSUICulture}"
+}
+else
+{
+    #fallback to en-US
+    Import-LocalizedData `
+        -BindingVariable LocalizedData `
+        -Filename PDT.strings.psd1 `
+        -BaseDirectory "${PSScriptRoot}\en-US"
+}
+#endregion
+
+# Import the common certificate functions
+Import-Module -Name ( Join-Path `
+    -Path (Split-Path -Path $PSScriptRoot -Parent) `
+    -ChildPath 'CertificateCommon\CertificateCommon.psm1' )
+
+<#
+    .SYNOPSIS
+    Extracts an array of arguments that were found in the Arguments list passed in.
+    It also optionally maps the arguments to a new name.
+
+    .PARAMETER FunctionBoundParameters
+    The parameters that were passed to the calling function.
+
+    .PARAMETER ArgumentNames
+    The array of arguments that should be extracted.
+
+    .PARAMETER NewArgumentNames
+    An array of argument names to rename each argument to.
+#>
+function Get-Arguments
+{
+    [CmdletBinding()]
+    param
+    (
+        [parameter(Mandatory = $true)]
+        $FunctionBoundParameters,
+
+        [parameter(Mandatory = $true)]
+        [string[]] $ArgumentNames,
+
+        [string[]] $NewArgumentNames
+    )
+
+    $returnValue=@{}
+
+    for ($i=0;$i -lt $ArgumentNames.Count;$i++)
+    {
+        $argumentName = $ArgumentNames[$i]
+
+        if ($NewArgumentNames -eq $null)
+        {
+            $newArgumentName = $argumentName
+        }
+        else
+        {
+            $newArgumentName = $NewArgumentNames[$i]
+        }
+
+        if ($FunctionBoundParameters.ContainsKey($argumentName))
+        {
+            $null = $returnValue.Add($NewArgumentName,$FunctionBoundParameters[$argumentName])
+        }
+    }
+
+    return $returnValue
+} # end function Get-Arguments
+
+<#
+    .SYNOPSIS
+    Initialize the Win32 PInvoke wrapper.
+#>
+function Initialize-PInvoke
+{
+    $script:ProgramSource = @"
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Security;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+using System.Security.Principal;
+using System.ComponentModel;
+using System.IO;
+
+namespace Source
+{
+    [SuppressUnmanagedCodeSecurity]
+    public static class NativeMethods
+    {
+        //The following structs and enums are used by the various Win32 API's that are used in the code below
+
+        [StructLayout(LayoutKind.Sequential)]
+        public struct STARTUPINFO
+        {
+            public Int32 cb;
+            public string lpReserved;
+            public string lpDesktop;
+            public string lpTitle;
+            public Int32 dwX;
+            public Int32 dwY;
+            public Int32 dwXSize;
+            public Int32 dwXCountChars;
+            public Int32 dwYCountChars;
+            public Int32 dwFillAttribute;
+            public Int32 dwFlags;
+            public Int16 wShowWindow;
+            public Int16 cbReserved2;
+            public IntPtr lpReserved2;
+            public IntPtr hStdInput;
+            public IntPtr hStdOutput;
+            public IntPtr hStdError;
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        public struct PROCESS_INFORMATION
+        {
+            public IntPtr hProcess;
+            public IntPtr hThread;
+            public Int32 dwProcessID;
+            public Int32 dwThreadID;
+        }
+
+        [Flags]
+        public enum LogonType
+        {
+            LOGON32_LOGON_INTERACTIVE = 2,
+            LOGON32_LOGON_NETWORK = 3,
+            LOGON32_LOGON_BATCH = 4,
+            LOGON32_LOGON_SERVICE = 5,
+            LOGON32_LOGON_UNLOCK = 7,
+            LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
+            LOGON32_LOGON_NEW_CREDENTIALS = 9
+        }
+
+        [Flags]
+        public enum LogonProvider
+        {
+            LOGON32_PROVIDER_DEFAULT = 0,
+            LOGON32_PROVIDER_WINNT35,
+            LOGON32_PROVIDER_WINNT40,
+            LOGON32_PROVIDER_WINNT50
+        }
+        [StructLayout(LayoutKind.Sequential)]
+        public struct SECURITY_ATTRIBUTES
+        {
+            public Int32 Length;
+            public IntPtr lpSecurityDescriptor;
+            public bool bInheritHandle;
+        }
+
+        public enum SECURITY_IMPERSONATION_LEVEL
+        {
+            SecurityAnonymous,
+            SecurityIdentification,
+            SecurityImpersonation,
+            SecurityDelegation
+        }
+
+        public enum TOKEN_TYPE
+        {
+            TokenPrimary = 1,
+            TokenImpersonation
+        }
+
+        [StructLayout(LayoutKind.Sequential, Pack = 1)]
+        internal struct TokPriv1Luid
+        {
+            public int Count;
+            public long Luid;
+            public int Attr;
+        }
+
+        public const int GENERIC_ALL_ACCESS = 0x10000000;
+        public const int CREATE_NO_WINDOW = 0x08000000;
+        internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
+        internal const int TOKEN_QUERY = 0x00000008;
+        internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
+        internal const string SE_INCRASE_QUOTA = "SeIncreaseQuotaPrivilege";
+
+        [DllImport("kernel32.dll",
+              EntryPoint = "CloseHandle", SetLastError = true,
+              CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
+        public static extern bool CloseHandle(IntPtr handle);
+
+        [DllImport("advapi32.dll",
+              EntryPoint = "CreateProcessAsUser", SetLastError = true,
+              CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
+        public static extern bool CreateProcessAsUser(
+            IntPtr hToken,
+            string lpApplicationName,
+            string lpCommandLine,
+            ref SECURITY_ATTRIBUTES lpProcessAttributes,
+            ref SECURITY_ATTRIBUTES lpThreadAttributes,
+            bool bInheritHandle,
+            Int32 dwCreationFlags,
+            IntPtr lpEnvrionment,
+            string lpCurrentDirectory,
+            ref STARTUPINFO lpStartupInfo,
+            ref PROCESS_INFORMATION lpProcessInformation
+            );
+
+        [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
+        public static extern bool DuplicateTokenEx(
+            IntPtr hExistingToken,
+            Int32 dwDesiredAccess,
+            ref SECURITY_ATTRIBUTES lpThreadAttributes,
+            Int32 ImpersonationLevel,
+            Int32 dwTokenType,
+            ref IntPtr phNewToken
+            );
+
+        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+        public static extern Boolean LogonUser(
+            String lpszUserName,
+            String lpszDomain,
+            String lpszPassword,
+            LogonType dwLogonType,
+            LogonProvider dwLogonProvider,
+            out IntPtr phToken
+            );
+
+        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
+        internal static extern bool AdjustTokenPrivileges(
+            IntPtr htok,
+            bool disall,
+            ref TokPriv1Luid newst,
+            int len,
+            IntPtr prev,
+            IntPtr relen
+            );
+
+        [DllImport("kernel32.dll", ExactSpelling = true)]
+        internal static extern IntPtr GetCurrentProcess();
+
+        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
+        internal static extern bool OpenProcessToken(
+            IntPtr h,
+            int acc,
+            ref IntPtr phtok
+            );
+
+        [DllImport("advapi32.dll", SetLastError = true)]
+        internal static extern bool LookupPrivilegeValue(
+            string host,
+            string name,
+            ref long pluid
+            );
+
+        public static void CreateProcessAsUser(string strCommand, string strDomain, string strName, string strPassword)
+        {
+            var hToken = IntPtr.Zero;
+            var hDupedToken = IntPtr.Zero;
+            TokPriv1Luid tp;
+            var pi = new PROCESS_INFORMATION();
+            var sa = new SECURITY_ATTRIBUTES();
+            sa.Length = Marshal.SizeOf(sa);
+            Boolean bResult = false;
+            try
+            {
+                bResult = LogonUser(
+                    strName,
+                    strDomain,
+                    strPassword,
+                    LogonType.LOGON32_LOGON_BATCH,
+                    LogonProvider.LOGON32_PROVIDER_DEFAULT,
+                    out hToken
+                    );
+                if (!bResult)
+                {
+                    throw new Win32Exception("The user could not be logged on. Ensure that the user has an existing profile on the machine and that correct credentials are provided. Logon error #" + Marshal.GetLastWin32Error().ToString());
+                }
+                IntPtr hproc = GetCurrentProcess();
+                IntPtr htok = IntPtr.Zero;
+                bResult = OpenProcessToken(
+                        hproc,
+                        TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
+                        ref htok
+                    );
+                if(!bResult)
+                {
+                    throw new Win32Exception("Open process token error #" + Marshal.GetLastWin32Error().ToString());
+                }
+                tp.Count = 1;
+                tp.Luid = 0;
+                tp.Attr = SE_PRIVILEGE_ENABLED;
+                bResult = LookupPrivilegeValue(
+                    null,
+                    SE_INCRASE_QUOTA,
+                    ref tp.Luid
+                    );
+                if(!bResult)
+                {
+                    throw new Win32Exception("Error in looking up privilege of the process. This should not happen if DSC is running as LocalSystem Lookup privilege error #" + Marshal.GetLastWin32Error().ToString());
+                }
+                bResult = AdjustTokenPrivileges(
+                    htok,
+                    false,
+                    ref tp,
+                    0,
+                    IntPtr.Zero,
+                    IntPtr.Zero
+                    );
+                if(!bResult)
+                {
+                    throw new Win32Exception("Token elevation error #" + Marshal.GetLastWin32Error().ToString());
+                }
+
+                bResult = DuplicateTokenEx(
+                    hToken,
+                    GENERIC_ALL_ACCESS,
+                    ref sa,
+                    (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
+                    (int)TOKEN_TYPE.TokenPrimary,
+                    ref hDupedToken
+                    );
+                if(!bResult)
+                {
+                    throw new Win32Exception("Duplicate Token error #" + Marshal.GetLastWin32Error().ToString());
+                }
+                var si = new STARTUPINFO();
+                si.cb = Marshal.SizeOf(si);
+                si.lpDesktop = "";
+                bResult = CreateProcessAsUser(
+                    hDupedToken,
+                    null,
+                    strCommand,
+                    ref sa,
+                    ref sa,
+                    false,
+                    0,
+                    IntPtr.Zero,
+                    null,
+                    ref si,
+                    ref pi
+                    );
+                if(!bResult)
+                {
+                    throw new Win32Exception("The process could not be created. Create process as user error #" + Marshal.GetLastWin32Error().ToString());
+                }
+            }
+            finally
+            {
+                if (pi.hThread != IntPtr.Zero)
+                {
+                    CloseHandle(pi.hThread);
+                }
+                if (pi.hProcess != IntPtr.Zero)
+                {
+                    CloseHandle(pi.hProcess);
+                }
+                 if (hDupedToken != IntPtr.Zero)
+                {
+                    CloseHandle(hDupedToken);
+                }
+            }
+        }
+    }
+}
+
+"@
+    Add-Type -TypeDefinition $ProgramSource -ReferencedAssemblies "System.ServiceProcess"
+} # end function Initialize-PInvoke
+
+<#
+    .SYNOPSIS
+    Gets a Win32 process that matches the path, arguments and is user.
+
+    .PARAMETER Path
+    The path to the executable running the process.
+
+    .PARAMETER Arguments
+    The arguments of the running process to find.
+
+    .PARAMETER Credential
+    The credentials of the account that the process is running under.
+#>
+function Get-Win32Process
+{
+    [CmdletBinding()]
+    param
+    (
+        [parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [String] $Path,
+
+        [String] $Arguments,
+
+        [PSCredential] $Credential
+    )
+
+    $fileName = [io.path]::GetFileNameWithoutExtension($Path)
+    $getProcesses = @(Get-Process -Name $fileName -ErrorAction SilentlyContinue)
+    $processes = foreach ($process in $GetProcesses)
+    {
+        if ($process.Path -ieq $Path)
+        {
+            try
+            {
+                [wmi]"Win32_Process.Handle='$($process.Id)'"
+            }
+            catch
+            {
+            }
+        }
+    }
+    if ($PSBoundParameters.ContainsKey('Credential'))
+    {
+        $processes = $processes |
+            Where-Object -FilterScript {
+                (Get-Win32ProcessOwner $_) -eq $Credential.UserName
+            }
+    }
+    if ($Arguments -eq $null)
+    {
+        $Arguments = ""
+    }
+    $processes = $processes |
+        Where-Object -FilterScript {
+            (Get-Win32ProcessArgumentsFromCommandLine $_.CommandLine) -eq $Arguments
+        }
+
+    return $processes
+} # end function Get-Win32Process
+
+<#
+    .SYNOPSIS
+    Returns the Owner of a Win32 Process.
+
+    .PARAMETER Process
+    The Win32 WMI process to get the owner for.
+#>
+function Get-Win32ProcessOwner
+{
+    param
+    (
+        [parameter(Mandatory = $true)]
+        [ValidateNotNull()]
+        $Process
+    )
+
+    try
+    {
+        $owner = $Process.GetOwner()
+    }
+    catch
+    {
+    }
+    if ($owner.Domain -ne $null)
+    {
+        return $owner.Domain + "\" + $owner.User
+    }
+    else
+    {
+        return $owner.User
+    }
+} # end function Get-Win32ProcessOwner
+
+<#
+    .SYNOPSIS
+    Extracts the arguments from a complete command line
+
+    .PARAMETER CommandLine
+    The complete command line to extract the arguments from.
+#>
+function Get-Win32ProcessArgumentsFromCommandLine
+{
+    param
+    (
+        [String] $CommandLine
+    )
+
+    if ($commandLine -eq $null)
+    {
+        return ""
+    }
+    $commandLine = $commandLine.Trim()
+    if ($commandLine.Length -eq 0)
+    {
+        return ""
+    }
+    if ($commandLine[0] -eq '"')
+    {
+        $charToLookfor = [char]'"'
+    }
+    else
+    {
+        $charToLookfor = [char]' '
+    }
+    $endOfCommand = $commandLine.IndexOf($charToLookfor ,1)
+    if ($endOfCommand -eq -1)
+    {
+        return ""
+    }
+    return $commandLine.Substring($endOfCommand+1).Trim()
+} # end funcion Get-Win32ProcessArgumentsFromCommandLine
+
+<#
+    .SYNOPSIS
+    Starts a Win32 Process using PInvoke.
+
+    .PARAMETER Path
+    The full path to the executable to start the process with.
+
+    .PARAMETER Arguments
+    The arguments to pass to the executable when starting the process.
+
+    .PARAMETER Credential
+    The user account to start the process under.
+#>
+function Start-Win32Process
+{
+    param
+    (
+        [parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [String] $Path,
+
+        [String] $Arguments,
+
+        [PSCredential] $Credential
+    )
+
+    $getArguments = Get-Arguments $PSBoundParameters ("Path","Arguments","Credential")
+    $processes = @(Get-Win32Process @getArguments)
+    if ($processes.Count -eq 0)
+    {
+        if($PSBoundParameters.ContainsKey("Credential"))
+        {
+            try
+            {
+                Initialize-PInvoke
+                [Source.NativeMethods]::CreateProcessAsUser(`
+                    ("$Path " + $Arguments),`
+                    $Credential.GetNetworkCredential().Domain,`
+                    $Credential.GetNetworkCredential().UserName,`
+                    $Credential.GetNetworkCredential().Password)
+            }
+            catch
+            {
+                try
+                {
+                    Initialize-PInvoke
+                    [Source.NativeMethods]::CreateProcessAsUser(`
+                        ("$Path " + $Arguments),`
+                        $Credential.GetNetworkCredential().Domain,`
+                        $Credential.GetNetworkCredential().UserName,`
+                        $Credential.GetNetworkCredential().Password)
+                }
+                catch
+                {
+                    $exception = New-Object System.ArgumentException $_
+                    $errorCategory = [System.Management.Automation.ErrorCategory]::OperationStopped
+                    $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord $exception, "Win32Exception", $errorCategory, $null
+                    $err = $errorRecord
+                }
+            }
+        }
+        else
+        {
+            $startArguments = Get-Arguments $PSBoundParameters `
+                    ("Path",     "Arguments",    "Credential") `
+                    ("FilePath", "ArgumentList", "Credential")
+            if([string]::IsNullOrEmpty($Arguments))
+            {
+                $null = $startArguments.Remove("ArgumentList")
+            }
+            $err = Start-Process @StartArguments
+        }
+        if($err -ne $null)
+        {
+            throw $err
+        }
+        Wait-Win32ProcessStart @GetArguments
+    }
+    else
+    {
+        return ($LocalizedData.ProcessAlreadyStarted -f $Path,$processes.ProcessId)
+    }
+    $processes = @(Get-Win32Process @getArguments)
+    return ($LocalizedData.ProcessStarted -f $Path,$processes.ProcessId)
+} # end function Start-Win32Process
+
+<#
+    .SYNOPSIS
+    Wait for a Win32 process to start.
+
+    .PARAMETER Path
+    The full path to the executable of the process to wait for.
+
+    .PARAMETER Arguments
+    The arguments passed to the executable of the process to wait for.
+
+    .PARAMETER Credential
+    The user account the process will be running under.
+
+    .PARAMETER Timeout
+    The milliseconds to wait for the process to start.
+#>
+function Wait-Win32ProcessStart
+{
+    [CmdletBinding()]
+    param
+    (
+        [parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [String] $Path,
+
+        [String] $Arguments,
+
+        [PSCredential] $Credential,
+
+        [Int] $Timeout = 5000
+    )
+
+    $start = [DateTime]::Now
+    $getArguments = Get-Arguments $PSBoundParameters ("Path","Arguments","Credential")
+    $started = (@(Get-Win32Process @GetArguments).Count -ge 1)
+    While (-not $started -and ([DateTime]::Now - $start).TotalMilliseconds -lt $Timeout)
+    {
+        Start-Sleep -Seconds 1
+        $started = @(Get-Win32Process @GetArguments).Count -ge 1
+    }
+    return $started
+} # end function Wait-Win32ProcessStart
+
+<#
+    .SYNOPSIS
+    Wait for a Win32 process to stop. This assumes the process was aleady confirmed to have been started by first
+    calling Wait-Win32ProcessStart.
+
+    .PARAMETER Path
+    The full path to the executable of the process to wait for.
+
+    .PARAMETER Arguments
+    The arguments passed to the executable of the process to wait for.
+
+    .PARAMETER Credential
+    The user account the process will be running under.
+
+    .PARAMETER Timeout
+    The milliseconds to wait for the process to stop.
+#>
+function Wait-Win32ProcessStop
+{
+    [CmdletBinding()]
+    param
+    (
+        [parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [String] $Path,
+
+        [String] $Arguments,
+
+        [PSCredential] $Credential,
+
+        [Int] $Timeout = 30000
+    )
+
+    $start = [DateTime]::Now
+    $getArguments = Get-Arguments $PSBoundParameters ("Path","Arguments","Credential")
+    $stopped = (@(Get-Win32Process @GetArguments).Count -eq 0)
+    While (-not $stopped -and ([DateTime]::Now - $start).TotalMilliseconds -lt $Timeout)
+    {
+        Start-Sleep -Seconds 1
+        $stopped = (@(Get-Win32Process @GetArguments).Count -eq 0)
+    }
+    return $stopped
+} # end function Wait-Win32ProcessStop
+
+<#
+    .SYNOPSIS
+    Wait for a Win32 process to complete.
+
+    .PARAMETER Path
+    The full path to the executable of the process to wait for.
+
+    .PARAMETER Arguments
+    The arguments passed to the executable of the process to wait for.
+
+    .PARAMETER Credential
+    The user account the process will be running under.
+
+    .PARAMETER Timeout
+    The amount of time to wait for the process to end.
+#>
+function Wait-Win32ProcessEnd
+{
+    [CmdletBinding()]
+    param
+    (
+        [parameter(Mandatory = $true)]
+        [ValidateNotNullOrEmpty()]
+        [String] $Path,
+
+        [String] $Arguments,
+
+        [PSCredential] $Credential
+    )
+
+    $getArguments = Get-Arguments $PSBoundParameters ("Path","Arguments","Credential")
+    # Wait for the process to start
+    if (-not (Wait-Win32ProcessStart @getArguments))
+    {
+        New-InvalidArgumentError `
+            -ErrorId 'ProcessFailedToStartError' `
+            -ErrorMessage ($LocalizedData.ProcessFailedToStartError -f $Path,$Arguments)
+    }
+    if (-not (Wait-Win32ProcessStop @getArguments))
+    {
+        # The process did not stop.
+        New-InvalidArgumentError `
+            -ErrorId 'ProcessFailedToStopError' `
+            -ErrorMessage ($LocalizedData.ProcessFailedToStopError -f $Path,$Arguments)
+    }
+} # end function Wait-Win32ProcessEnd
+
+Export-ModuleMember Start-Win32Process,Wait-Win32ProcessStart,Wait-Win32ProcessStop,Wait-Win32ProcessEnd

+ 10 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/DSCResources/PDT/en-us/PDT.strings.psd1

@@ -0,0 +1,10 @@
+ConvertFrom-StringData @'
+    FileNotFound = File not found in the environment path
+    AbsolutePathOrFileName = Absolute path or file name expected
+    InvalidArgument = Invalid argument: '{0}' with value: '{1}'
+    InvalidArgumentAndMessage = {0} {1}
+    ProcessFailedToStartError = The process '{0}' with arguments '{1}' failed to start within the specified timeout.
+    ProcessStarted = Process matching path '{0}' started in process ID {1}
+    ProcessAlreadyStarted = Process matching path '{0}' already started in process ID {1}
+    ProcessFailedToStopError = The process '{0}' with arguments '{1}' failed to stop within the specified timeout.
+'@

+ 54 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Examples/Sample_xCertReq_RequestAltSSLCert.ps1

@@ -0,0 +1,54 @@
+<#
+ Request and Accept a certificate from an Active Directory Root Certificate Authority. This certificate
+ is issued using an subject alternate name with multiple DNS addresses.
+
+ This example is allowing storage of credentials in plain text by setting PSDscAllowPlainTextPassword to $true.
+ Storing passwords in plain text is not a good practice and is presented only for simplicity and demonstration purposes.
+ To learn how to securely store credentials through the use of certificates,
+ please refer to the following TechNet topic: https://technet.microsoft.com/en-us/library/dn781430.aspx
+#>
+configuration Sample_xCertReq_RequestAltSSL
+{
+    param
+    (
+        [Parameter(Mandatory=$true)]
+        [ValidateNotNullorEmpty()]
+        [PSCredential] $Credential
+    )
+
+    Import-DscResource -ModuleName xCertificate
+    Node 'localhost'
+    {
+        xCertReq SSLCert
+        {
+            CARootName                = 'test-dc01-ca'
+            CAServerFQDN              = 'dc01.test.pha'
+            Subject                   = 'contoso.com'
+            KeyLength                 = '1024'
+            Exportable                = $true
+            ProviderName              = '"Microsoft RSA SChannel Cryptographic Provider"'
+            OID                       = '1.3.6.1.5.5.7.3.1'
+            KeyUsage                  = '0xa0'
+            CertificateTemplate       = 'WebServer'
+            SubjectAltName            = 'dns=fabrikam.com&dns=contoso.com'
+            AutoRenew                 = $true
+            Credential                = $Credential
+        }
+    }
+}
+$configData = @{
+    AllNodes = @(
+        @{
+            NodeName                    = 'localhost';
+            PSDscAllowPlainTextPassword = $true
+            }
+        )
+    }
+Sample_xCertReq_RequestSSL `
+    -ConfigurationData $configData `
+    -Credential (Get-Credential) `
+    -OutputPath 'c:\Sample_xCertReq_RequestAltSSL'
+Start-DscConfiguration -Wait -Force -Verbose -Path 'c:\Sample_xCertReq_RequestAltSSL'
+
+# Validate results
+Get-ChildItem Cert:\LocalMachine\My

+ 52 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Examples/Sample_xCertReq_RequestSSLCert.ps1

@@ -0,0 +1,52 @@
+<#
+ Request and Accept a certificate from an Active Directory Root Certificate Authority.
+
+ This example is allowing storage of credentials in plain text by setting PSDscAllowPlainTextPassword to $true.
+ Storing passwords in plain text is not a good practice and is presented only for simplicity and demonstration purposes.
+ To learn how to securely store credentials through the use of certificates,
+ please refer to the following TechNet topic: https://technet.microsoft.com/en-us/library/dn781430.aspx
+#>
+configuration Sample_xCertReq_RequestSSL
+{
+    param
+    (
+        [Parameter(Mandatory=$true)]
+        [ValidateNotNullorEmpty()]
+        [PSCredential] $Credential
+    )
+
+    Import-DscResource -ModuleName xCertificate
+    Node 'localhost'
+    {
+        xCertReq SSLCert
+        {
+            CARootName                = 'test-dc01-ca'
+            CAServerFQDN              = 'dc01.test.pha'
+            Subject                   = 'foodomain.test.net'
+            KeyLength                 = '1024'
+            Exportable                = $true
+            ProviderName              = '"Microsoft RSA SChannel Cryptographic Provider"'
+            OID                       = '1.3.6.1.5.5.7.3.1'
+            KeyUsage                  = '0xa0'
+            CertificateTemplate       = 'WebServer'
+            AutoRenew                 = $true
+            Credential                = $Credential
+        }
+    }
+}
+$configData = @{
+    AllNodes = @(
+        @{
+            NodeName                    = 'localhost';
+            PSDscAllowPlainTextPassword = $true
+            }
+        )
+    }
+Sample_xCertReq_RequestSSL `
+    -ConfigurationData $configData `
+    -Credential (Get-Credential) `
+    -OutputPath 'c:\Sample_xCertReq_RequestSSL'
+Start-DscConfiguration -Wait -Force -Verbose -Path 'c:\Sample_xCertReq_RequestSSL'
+
+# Validate results
+Get-ChildItem Cert:\LocalMachine\My

+ 22 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Examples/Sample_xCertificateImport_MinimalUsage.ps1

@@ -0,0 +1,22 @@
+# Import public key certificate into Trusted Root store.
+Configuration Sample_xCertificateImport_MinimalUsage
+{
+    Import-DscResource -ModuleName xCertificate
+
+    Node $AllNodes.NodeName
+    {
+        xCertificateImport MyTrustedRoot
+        {
+            Thumbprint = 'c81b94933420221a7ac004a90242d8b1d3e5070d'
+            Location   = 'LocalMachine'
+            Store      = 'Root'
+            Path       = '\\Server\Share\Certificates\MyTrustedRoot.cer'
+        }
+    }
+}
+Sample_xCertificateImport_MinimalUsage `
+    -OutputPath 'c:\Sample_xCertificateImport_MinimalUsage'
+Start-DscConfiguration -Wait -Force -Verbose -Path 'c:\Sample_xCertificateImport_MinimalUsage'
+
+# Validate results
+Get-ChildItem Cert:\LocalMachine\My

+ 51 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Examples/Sample_xPfxImport_IIS_WebSite.ps1

@@ -0,0 +1,51 @@
+# Import a PFX into the WebHosting store and bind it to an IIS Web Site.
+Configuration Sample_xPfxImport_IIS_WebSite
+{
+    param
+    (
+        [PSCredential]
+        $PfxPassword = (Get-Credential -Message 'Enter PFX extraction password.' -UserName 'Ignore')
+    )
+
+    Import-DscResource -ModuleName xCertificate
+    Import-DscResource -ModuleName xWebAdministration
+
+    Node $AllNodes.NodeName
+    {
+        WindowsFeature IIS
+        {
+            Ensure = 'Present'
+            Name   = 'Web-Server'
+        }
+
+        xPfxImport CompanyCert
+        {
+            Thumbprint = 'c81b94933420221a7ac004a90242d8b1d3e5070d'
+            Path       = '\\Server\Share\Certificates\CompanyCert.pfx'
+            Store      = 'WebHosting'
+            Credential = $PfxPassword
+            DependsOn  = '[WindowsFeature]IIS'
+        }
+
+        xWebsite CompanySite
+        {
+            Ensure          = 'Present'
+            Name            = 'CompanySite'
+            State           = 'Started'
+            PhysicalPath    = "B:\Web\CompanySite"
+            ApplicationPool = "CompanyPool"
+            BindingInfo     =
+                    MSFT_xWebBindingInformation {
+                        Protocol = 'HTTPS'
+                        Port = 443
+                        CertificateThumbprint = 'c81b94933420221a7ac004a90242d8b1d3e5070d'
+                        CertificateStoreName = 'WebHosting'
+                        HostName = "www.example.com"
+                    }
+            DependsOn       = '[WindowsFeature]Web-Server','[xPfxImport]CompanyCert'
+        }
+    }
+}
+Sample_xPfxImport_IIS_WebSite `
+    -OutputPath 'c:\Sample_xPfxImport_IIS_WebSite'
+Start-DscConfiguration -Wait -Force -Verbose -Path 'c:\Sample_xPfxImport_IIS_WebSite'

+ 41 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Examples/Sample_xPfxImport_MinimalUsage.ps1

@@ -0,0 +1,41 @@
+# Import a PFX into the My store.
+Configuration Sample_xPfxImport_MinimalUsage
+{
+    param
+    (
+        [Parameter(Mandatory)]
+        [ValidateNotNullOrEmpty()]
+        [String] $PfxThumbprint,
+
+        [Parameter(Mandatory)]
+        [ValidateNotNullOrEmpty()]
+        [String] $PfxPath,
+
+        [PSCredential]
+        $PfxPassword = (Get-Credential -Message 'Enter PFX extraction password.' -UserName 'Ignore')
+    )
+
+    Import-DscResource -ModuleName xCertificate
+
+    Node $AllNodes.NodeName
+    {
+        xPfxImport CompanyCert
+        {
+            Thumbprint = $PfxThumbprint
+            Path       = $PfxPath
+            Credential = $PfxPassword
+        }
+    }
+}
+
+$Thumbprint = 'c81b94933420221a7ac004a90242d8b1d3e5070d'
+
+Sample_xPfxImport_MinimalUsage `
+    -PfxThumbprint $Thumbprint `
+    -PfxPath '\\Server\Share\Certificates\CompanyCert.pfx' `
+    -OutputPath 'c:\Sample_xPfxImport_MinimalUsage'
+Start-DscConfiguration -Wait -Force -Verbose -Path 'c:\Sample_xPfxImport_MinimalUsage'
+
+# Validate results - a Certificate with a thumbprint matching $Thumbprint should be returned
+Get-ChildItem -Path Cert:\LocalMachine\My |
+    Where-Object -FilterScript  { $_.Thumbprint -eq $Thumbprint }

+ 22 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/LICENSE

@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Michael Greene
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

BIN
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/PSGetModuleInfo.xml


+ 367 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/ReadMe.md

@@ -0,0 +1,367 @@
+# xCertificate
+
+[![Build status](https://ci.appveyor.com/api/projects/status/0u9f8smiidg1j4kn/branch/master?svg=true)](https://ci.appveyor.com/project/PowerShell/xcertificate/branch/master)
+
+The **xCertificate** module is a part of the Windows PowerShell Desired State Configuration (DSC) Resource Kit, which is a collection of DSC Resources. This module includes DSC resources that simplify administration of certificates on a Windows Server, with simple declarative language.
+
+The **xCertificate** module contains the following resources:
+
+- **xCertReq**
+- **xPfxImport**
+- **xCertificateImport**
+
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
+For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
+
+## Contributing
+
+Please check out common DSC Resources [contributing guidelines](https://github.com/PowerShell/DscResource.Kit/blob/master/CONTRIBUTING.md).
+
+## Resources
+
+### xCertReq
+
+- **`[String]` Subject**: Provide the text string to use as the subject of the certificate. Key.
+- **`[String]` CAServerFQDN**: The FQDN of the Active Directory Certificate Authority on the local area network. Required.
+- **`[String]` CARootName**: The name of the certificate authority, by default this will be in format domain-servername-ca. Required.
+- **`[String]` KeyLength**: The bit length of the encryption key to be used. Optional. { *1024* | 2048 | 4096 | 8192 }.
+- **`[Boolean]` Exportable**: The option to allow the certificate to be exportable, by default it will be true. Optional. Defaults to `$true`.
+- **`[String]` ProviderName**: The selection of provider for the type of encryption to be used. Optional. Defaults to `"Microsoft RSA SChannel Cryptographic Provider"`.
+- **`[String]` OID**: The Object Identifier that is used to name the object. Optional. Defaults to `1.3.6.1.5.5.7.3.1`.
+- **`[String]` KeyUsage**: The Keyusage is a restriction method that determines what a certificate can be used for. Optional. Defaults to `0xa0`
+- **`[String]` CertificateTemplate** The template used for the definiton of the certificate. Optional. Defaults to `WebServer`
+- **`[String]` SubjectAltName** The subject alternative name used to createthe certificate. Optional.
+- **`[PSCredential]` Credential**: The credentials that will be used to access the template in the Certificate Authority. Optional.
+- **`[Boolean]` AutoRenew**: Determines if the resource will also renew a certificate within 7 days of expiration. Optional.
+
+### xPfxImport
+
+- **`[String]` Thumbprint** (_Key_): The thumbprint (unique identifier) of the PFX file you're importing.
+- **`[String]` Path** (_Required_): The path to the PFX file you want to import.
+- **`[String]` Location** (_Key_): The Windows Certificate Store Location to import the PFX file to. { LocalMachine | CurrentUser }
+- **`[String]` Store** (_Key_): The Windows Certificate Store Name to import the PFX file to.
+- **`[Boolean]` Exportable** (_Write_): Determines whether the private key is exportable from the machine after it has been imported. Defaults to `$false`.
+- **`[PSCredential]` Credential** (_Write_): A `[PSCredential]` object that is used to decrypt the PFX file. Only the password is used, so any user name is valid.
+- **`[String]` Ensure** (_Write_): Specifies whether the PFX file should be present or absent. { *Present* | Absent }.
+
+### xCertificateImport
+
+- **`[String]` Thumbprint** (_Key_): The thumbprint (unique identifier) of the certificate you're importing.
+- **`[String]` Path** (_Required_): The path to the CER file you want to import.
+- **`[String]` Location** (_Key_): The Windows Certificate Store Location to import the certificate to. { LocalMachine | CurrentUser }
+- **`[String]` Store** (_Key_): The Windows Certificate Store Name to import the certificate to.
+- **`[String]` Ensure** (_Write_): Specifies whether the certificate should be present or absent. { *Present* | Absent }.
+
+## Versions
+
+### Unreleased
+
+### 2.3.0.0
+
+- xCertReq:
+  - Added additional parameters KeyLength, Exportable, ProviderName, OID, KeyUsage, CertificateTemplate, SubjectAltName
+- Fixed most markdown errors in Readme.md.
+- Corrected Parameter decoration format to be consistent with guidelines.
+
+### 2.2.0.0
+
+- Converted appveyor.yml to install Pester from PSGallery instead of from Chocolatey.
+- Moved unit tests to correct folder structure.
+- Changed unit tests to use standard test templates.
+- Updated all resources to meet HQRM standards and style guidelines.
+- Added .gitignore file
+- Added .gitattributes file to force line endings to CRLF to allow unit tests to work.
+- xCertificateCommon:
+  - Moved common code into new module CertificateCommon.psm1
+  - Added standard exception code.
+  - Renamed common functions Validate-* to use acceptable verb Test-*.
+  - Added help to all functions.
+- xCertificateImport:
+  - Fixed bug with Test-TargetResource incorrectly detecting change required.
+  - Reworked unit tests for improved code coverage to meet HQRM standards.
+  - Created Integration tests for both importing and removing an imported certificate.
+  - Added descriptions to MOF file.
+  - Removed default parameter values for parameters that are required or keys.
+  - Added verbose messages.
+  - Split message and error strings into localization string files.
+  - Added help to all functions.
+- xPfxImport:
+  - Fixed bug with Test-TargetResource incorrectly detecting change required.
+  - Reworked unit tests for improved code coverage to meet HQRM standards.
+  - Created Integration tests for both importing and removing an imported certificate.
+  - Added descriptions to MOF file.
+  - Removed default parameter values for parameters that are required or keys.
+  - Added verbose messages.
+  - Split message and error strings into localization string files.
+  - Added help to all functions.
+- xCertReq:
+  - Cleaned up descriptions in MOF file.
+  - Fixed bugs generating certificate when credentials are specified.
+  - Allowed output of certificate request when credentials are specified.
+  - Split message and error strings into localization string files.
+  - Created unit tests and integration tests.
+  - Improved logging output to enable easier debugging.
+  - Added help to all functions.
+- xPDT:
+  - Renamed to match standard module name format (MSFT_x).
+  - Modified to meet 100 characters or less line length where possible.
+  - Split message and error strings into localization string files.
+  - Removed unused functions.
+  - Renamed functions to standard verb-noun form.
+  - Added help to all functions.
+  - Fixed bug in Wait-Win32ProcessEnd that prevented waiting for process to end.
+  - Added Wait-Win32ProcessStop to wait for a process to stop.
+  - Removed unused and broken scheduled task code.
+
+### 2.1.0.0
+
+- Fixed xCertReq to support CA Root Name with spaces
+
+### 2.0.0.0
+
+- Breaking Change - Updated xPfxImport Store parameter is now a key value making it mandatory
+- Updated xPfxImport with new Ensure support
+- Updated xPfxImport with support for the CurrentUser value
+- Updated xPfxImport with validationset for the Store parameter
+- Added new resource: xCertificateImport
+
+### 1.1.0.0
+
+- Added new resource: xPfxImport
+
+### 1.0.1.0
+
+- Minor documentation updates
+
+### 1.0.0.0
+
+- Initial public release of xCertificate module with following resources
+  - xCertReq
+
+## Examples
+
+### xCertReq
+
+#### Request an SSL Certificate
+
+Request and Accept a certificate from an Active Directory Root Certificate Authority.
+
+```powershell
+configuration xCertReq_RequestSSL
+{
+    param
+    (
+        [Parameter(Mandatory=$true)]
+        [ValidateNotNullorEmpty()]
+        [PsCredential] $Credential
+    )
+
+    Import-DscResource -ModuleName xCertificate
+    Node 'localhost'
+    {
+        xCertReq SSLCert
+        {
+            CARootName                = 'test-dc01-ca'
+            CAServerFQDN              = 'dc01.test.pha'
+            Subject                   = 'foodomain.test.net'
+            KeyLength                 = '1024'
+            Exportable                = $true
+            ProviderName              = '"Microsoft RSA SChannel Cryptographic Provider"'
+            OID                       = '1.3.6.1.5.5.7.3.1'
+            KeyUsage                  = '0xa0'
+            CertificateTemplate       = 'WebServer'
+            AutoRenew                 = $true
+            Credential                = $Credential
+        }
+    }
+}
+$configData = @{
+    AllNodes = @(
+        @{
+            NodeName                    = 'localhost';
+            PSDscAllowPlainTextPassword = $true
+            }
+        )
+    }
+xCertReq_RequestSSL `
+    -ConfigurationData $configData `
+    -Credential (Get-Credential) `
+    -OutputPath 'c:\xCertReq_RequestSSL'
+Start-DscConfiguration -Wait -Force -Verbose -Path 'c:\xCertReq_RequestSSL'
+
+# Validate results
+Get-ChildItem Cert:\LocalMachine\My
+```
+
+#### Request an SSL Certificate with alternative DNS names
+
+Request and Accept a certificate from an Active Directory Root Certificate Authority.
+This certificate is issued using an subject alternate name with multiple DNS addresses.
+
+```powershell
+configuration Sample_xCertReq_RequestAltSSL
+{
+    param
+    (
+        [Parameter(Mandatory=$true)]
+        [ValidateNotNullorEmpty()]
+        [PSCredential] $Credential
+    )
+
+    Import-DscResource -ModuleName xCertificate
+    Node 'localhost'
+    {
+        xCertReq SSLCert
+        {
+            CARootName                = 'test-dc01-ca'
+            CAServerFQDN              = 'dc01.test.pha'
+            Subject                   = 'contoso.com'
+            KeyLength                 = '1024'
+            Exportable                = $true
+            ProviderName              = '"Microsoft RSA SChannel Cryptographic Provider"'
+            OID                       = '1.3.6.1.5.5.7.3.1'
+            KeyUsage                  = '0xa0'
+            CertificateTemplate       = 'WebServer'
+            SubjectAltName            = 'dns=fabrikam.com&dns=contoso.com'
+            AutoRenew                 = $true
+            Credential                = $Credential
+        }
+    }
+}
+$configData = @{
+    AllNodes = @(
+        @{
+            NodeName                    = 'localhost';
+            PSDscAllowPlainTextPassword = $true
+            }
+        )
+    }
+Sample_xCertReq_RequestSSL `
+    -ConfigurationData $configData `
+    -Credential (Get-Credential) `
+    -OutputPath 'c:\Sample_xCertReq_RequestAltSSL'
+Start-DscConfiguration -Wait -Force -Verbose -Path 'c:\Sample_xCertReq_RequestAltSSL'
+
+# Validate results
+Get-ChildItem Cert:\LocalMachine\My
+```
+
+### xPfxImport
+
+#### Simple Usage
+
+Import a PFX into the My store.
+
+```powershell
+Configuration Sample_xPfxImport_MinimalUsage
+{
+    param
+    (
+        [PSCredential]
+        $PfxPassword = (Get-Credential -Message 'Enter PFX extraction password.' -UserName 'Ignore')
+    )
+
+    Import-DscResource -ModuleName xCertificate
+
+    Node $AllNodes.NodeName
+    {
+        xPfxImport CompanyCert
+        {
+            Thumbprint = 'c81b94933420221a7ac004a90242d8b1d3e5070d'
+            Path       = '\\Server\Share\Certificates\CompanyCert.pfx'
+            Credential = $PfxPassword
+        }
+    }
+}
+Sample_xPfxImport_MinimalUsage `
+    -OutputPath 'c:\Sample_xPfxImport_MinimalUsage'
+Start-DscConfiguration -Wait -Force -Verbose -Path 'c:\Sample_xPfxImport_MinimalUsage'
+
+# Validate results
+Get-ChildItem Cert:\LocalMachine\My
+```
+
+#### Used with xWebAdministration Resources
+
+Import a PFX into the WebHosting store and bind it to an IIS Web Site.
+
+```powershell
+Configuration Sample_xPfxImport_IIS_WebSite
+{
+    param
+    (
+        [PSCredential]
+        $PfxPassword = (Get-Credential -Message 'Enter PFX extraction password.' -UserName 'Ignore')
+    )
+
+    Import-DscResource -ModuleName xCertificate
+    Import-DscResource -ModuleName xWebAdministration
+
+    Node $AllNodes.NodeName
+    {
+        WindowsFeature IIS
+        {
+            Ensure = 'Present'
+            Name   = 'Web-Server'
+        }
+
+        xPfxImport CompanyCert
+        {
+            Thumbprint = 'c81b94933420221a7ac004a90242d8b1d3e5070d'
+            Path       = '\\Server\Share\Certificates\CompanyCert.pfx'
+            Store      = 'WebHosting'
+            Credential = $PfxPassword
+            DependsOn  = '[WindowsFeature]IIS'
+        }
+
+        xWebsite CompanySite
+        {
+            Ensure          = 'Present'
+            Name            = 'CompanySite'
+            State           = 'Started'
+            PhysicalPath    = "B:\Web\CompanySite"
+            ApplicationPool = "CompanyPool"
+            BindingInfo     =
+                    MSFT_xWebBindingInformation {
+                        Protocol = 'HTTPS'
+                        Port = 443
+                        CertificateThumbprint = 'c81b94933420221a7ac004a90242d8b1d3e5070d'
+                        CertificateStoreName = 'WebHosting'
+                        HostName = "www.example.com"
+                    }
+            DependsOn       = '[WindowsFeature]Web-Server','[xPfxImport]CompanyCert'
+        }
+    }
+}
+Sample_xPfxImport_IIS_WebSite `
+    -OutputPath 'c:\Sample_xPfxImport_IIS_WebSite'
+Start-DscConfiguration -Wait -Force -Verbose -Path 'c:\Sample_xPfxImport_IIS_WebSite'
+```
+
+### xCertificateImport
+
+Import public key certificate into Trusted Root store.
+
+```powershell
+Configuration Sample_xCertificateImport_MinimalUsage
+{
+    Import-DscResource -ModuleName xCertificate
+
+    Node $AllNodes.NodeName
+    {
+        xCertificateImport MyTrustedRoot
+        {
+            Thumbprint = 'c81b94933420221a7ac004a90242d8b1d3e5070d'
+            Location   = 'LocalMachine'
+            Store      = 'Root'
+            Path       = '\\Server\Share\Certificates\MyTrustedRoot.cer'
+        }
+    }
+}
+Sample_xCertificateImport_MinimalUsage `
+    -OutputPath 'c:\Sample_xCertificateImport_MinimalUsage'
+Start-DscConfiguration -Wait -Force -Verbose -Path 'c:\Sample_xCertificateImport_MinimalUsage'
+
+# Validate results
+Get-ChildItem Cert:\LocalMachine\My
+```

+ 109 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xCertReq.Integration.Tests.ps1

@@ -0,0 +1,109 @@
+<#
+ IMPORTANT INFORMATION:
+ Running these tests requires access to a AD CS Certificate Authority.
+ These integration tests are configured to use credentials to connect to the CA.
+ Therefore, automation of these tests shouldn't be performed using a production CA.
+#>
+
+$script:DSCModuleName   = 'xCertificate'
+$script:DSCResourceName = 'MSFT_xCertReq'
+
+<#
+ These tests can only be run if a CA is available and configured to be used on the
+ computer running these tests. This is usually required to be a domain joined computer.
+#>
+$CertUtilResult = & "$ENV:SystemRoot\system32\certutil.exe" @('-dump')
+$Result = ([regex]::matches($CertUtilResult,'Name:[ \t]+`([\sA-Za-z0-9._-]+)''','IgnoreCase'))
+if ([String]::IsNullOrEmpty($Result))
+{
+    Describe "$($script:DSCResourceName)_Integration" {
+        It 'should complete integration tests' {
+        } -Skip
+    }
+    return
+} # if
+
+#region HEADER
+# Integration Test Template Version: 1.1.0
+[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
+if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $script:DSCModuleName `
+    -DSCResourceName $script:DSCResourceName `
+    -TestType Integration
+#endregion
+
+# Using try/finally to always cleanup even if something awful happens.
+try
+{
+    #region Integration Tests
+    $ConfigFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:DSCResourceName).config.ps1"
+    . $ConfigFile
+
+    Describe "$($script:DSCResourceName)_Integration" {
+        #region DEFAULT TESTS
+        It 'Should compile without throwing' {
+            {
+                # This is to allow the testing of certreq with domain credentials
+                $ConfigData = @{
+                    AllNodes = @(
+                        @{
+                            NodeName = 'localhost'
+                            PsDscAllowDomainUser = $true
+                            PsDscAllowPlainTextPassword = $true
+                        }
+                    )
+                }
+
+                & "$($script:DSCResourceName)_Config" `
+                    -OutputPath $TestDrive `
+                    -ConfigurationData $ConfigData
+                Start-DscConfiguration -Path $TestDrive -ComputerName localhost -Wait -Verbose -Force
+            } | Should not throw
+        }
+
+        It 'should be able to call Get-DscConfiguration without throwing' {
+            { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should Not throw
+        }
+        #endregion
+
+        It 'Should have set the resource and all the parameters should match' {
+            # Get the Certificate details
+            $CertificateNew = Get-Childitem -Path Cert:\LocalMachine\My |
+                Where-Object -FilterScript {
+                    $_.Subject -eq "CN=$($TestCertReq.Subject)" -and `
+                    $_.Issuer.split(',')[0] -eq "CN=$($TestCertReq.CARootName)"
+                }
+            $CertificateNew.Subject                        | Should Be "CN=$($TestCertReq.Subject)"
+            $CertificateNew.Issuer.split(',')[0]           | Should Be "CN=$($TestCertReq.CARootName)"
+            $CertificateNew.Publickey.Key.KeySize          | Should Be $TestCertReq.KeyLength
+        }
+
+        AfterAll {
+            # Cleanup
+            $CertificateNew = Get-Childitem -Path Cert:\LocalMachine\My |
+                Where-Object -FilterScript {
+                    $_.Subject -eq "CN=$($TestCertReq.Subject)" -and `
+                    $_.Issuer.split(',')[0] -eq "CN=$($TestCertReq.CARootName)"
+                }
+
+            Remove-Item `
+                -Path $CertificateNew.PSPath `
+                -Force `
+                -ErrorAction SilentlyContinue
+        }
+   }
+    #endregion
+}
+finally
+{
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}

+ 47 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xCertReq.config.ps1

@@ -0,0 +1,47 @@
+# This will fail if the machine does not have a CA Configured.
+$CertUtilResult       = & "$ENV:SystemRoot\system32\certutil.exe" @('-dump')
+$CAServerFQDN         = ([regex]::matches($CertUtilResult,'Server:[ \t]+`([A-Za-z0-9._-]+)''','IgnoreCase')).Groups[1].Value
+$CARootName           = ([regex]::matches($CertUtilResult,'Name:[ \t]+`([\sA-Za-z0-9._-]+)''','IgnoreCase')).Groups[1].Value
+$KeyLength            = '1024'
+$Exportable           = $true
+$ProviderName         = '"Microsoft RSA SChannel Cryptographic Provider"'
+$OID                  = '1.3.6.1.5.5.7.3.1'
+$KeyUsage             = '0xa0'
+$CertificateTemplate  = 'WebServer'
+$SubjectAltName       = 'dns=contoso.com&dns=fabrikam.com'
+
+# If automated testing with a real CA can be performed then the credentials should be
+# obtained non-interactively way - do not do this in a production environment.
+$Credential = Get-Credential
+$TestCertReq = [PSObject]@{
+    Subject             = 'CertReq Test'
+    CAServerFQDN        = $CAServerFQDN
+    CARootName          = $CARootName
+    Credential          = $Credential
+    KeyLength           = $KeyLength
+    Exportable          = $($Exportable.ToString().ToUpper())
+    ProviderName        = $ProviderName
+    OID                 = $OID
+    KeyUsage            = $KeyUsage
+    CertificateTemplate = $CertificateTemplate
+    SubjectAltName      = $SubjectAltName
+}
+
+Configuration MSFT_xCertReq_Config {
+    Import-DscResource -ModuleName xCertificate
+    node localhost {
+        xCertReq Integration_Test {
+            Subject             = $TestCertReq.Subject
+            CAServerFQDN        = $TestCertReq.CAServerFQDN
+            CARootName          = $TestCertReq.CARootName
+            Credential          = $TestCertReq.Credential
+            KeyLength           = $TestCertReq.KeyLength
+            Exportable          = $TestCertReq.Exportable
+            ProviderName        = $TestCertReq.ProviderName
+            OID                 = $TestCertReq.OID
+            KeyUsage            = $TestCertReq.KeyUsage
+            CertificateTemplate = $TestCertReq.CertificateTemplate
+            SubjectAltName      = $TestCertReq.SubjectAltName
+        }
+    }
+}

+ 119 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xCertificateImport.Integration.Tests.ps1

@@ -0,0 +1,119 @@
+$script:DSCModuleName   = 'xCertificate'
+$script:DSCResourceName = 'MSFT_xCertificateImport'
+
+#region HEADER
+# Integration Test Template Version: 1.1.0
+[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
+if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $script:DSCModuleName `
+    -DSCResourceName $script:DSCResourceName `
+    -TestType Integration
+#endregion
+
+# Using try/finally to always cleanup even if something awful happens.
+try
+{
+    # Generate a self-signed certificate, export it and remove it from the store
+    # to use for testing.
+    # Don't use CurrentUser certificates for this test because they won't be found because
+    # DSC LCM runs under a different context (Local System).
+    $Certificate = New-SelfSignedCertificate `
+        -DnsName $ENV:ComputerName `
+        -CertStoreLocation Cert:\LocalMachine\My
+    $CertificatePath = Join-Path `
+        -Path $ENV:Temp `
+        -ChildPath "xCertificateImport-$($Certificate.Thumbprint).cer"
+    $null = Export-Certificate `
+        -Cert $Certificate `
+        -Type CERT `
+        -FilePath $CertificatePath
+    $null = Remove-Item `
+        -Path $Certificate.PSPath `
+        -Force
+
+    #region Integration Tests
+    $ConfigFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:DSCResourceName)_Add.config.ps1"
+    . $ConfigFile
+
+    Describe "$($script:DSCResourceName)_Add_Integration" {
+        #region DEFAULT TESTS
+        It 'Should compile without throwing' {
+            {
+                & "$($script:DSCResourceName)_Add_Config" `
+                    -OutputPath $TestDrive `
+                    -Path $CertificatePath `
+                    -Thumbprint $Certificate.Thumbprint
+                Start-DscConfiguration -Path $TestDrive -ComputerName localhost -Wait -Verbose -Force
+            } | Should not throw
+        }
+
+        It 'should be able to call Get-DscConfiguration without throwing' {
+            { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should Not throw
+        }
+        #endregion
+
+        It 'Should have set the resource and all the parameters should match' {
+            # Get the Certificate details
+            $CertificateNew = Get-Item `
+                -Path "Cert:\LocalMachine\My\$($Certificate.Thumbprint)"
+            $CertificateNew                             | Should BeOfType System.Security.Cryptography.X509Certificates.X509Certificate2
+            $CertificateNew.Thumbprint                  | Should Be $Certificate.Thumbprint
+            $CertificateNew.Subject                     | Should Be $Certificate.Subject
+        }
+    }
+    #endregion
+
+    #region Integration Tests
+    $ConfigFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:DSCResourceName)_Remove.config.ps1"
+    . $ConfigFile
+
+    Describe "$($script:DSCResourceName)_Remove_Integration" {
+        #region DEFAULT TESTS
+        It 'Should compile without throwing' {
+            {
+                & "$($script:DSCResourceName)_Remove_Config" `
+                    -OutputPath $TestDrive `
+                    -Path $CertificatePath `
+                    -Thumbprint $Certificate.Thumbprint
+                Start-DscConfiguration -Path $TestDrive -ComputerName localhost -Wait -Verbose -Force
+            } | Should not throw
+        }
+
+        It 'should be able to call Get-DscConfiguration without throwing' {
+            { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should Not throw
+        }
+        #endregion
+
+        It 'Should have set the resource and all the parameters should match' {
+            # Get the Certificate details
+            $CertificateNew = Get-Item `
+                -Path "Cert:\LocalMachine\My\$($Certificate.Thumbprint)" `
+                -ErrorAction SilentlyContinue
+            $CertificateNew                             | Should BeNullOrEmpty
+        }
+    }
+    #endregion
+}
+finally
+{
+    # Clean up
+    $null = Remove-Item `
+        -Path $CertificatePath `
+        -Force `
+        -ErrorAction SilentlyContinue
+    $null = Remove-Item `
+        -Path $Certificate.PSPath `
+        -Force `
+        -ErrorAction SilentlyContinue
+
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}

+ 17 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xCertificateImport_add.config.ps1

@@ -0,0 +1,17 @@
+Configuration MSFT_xCertificateImport_Add_Config {
+    param
+    (
+        $Thumbprint,
+        $Path
+    )
+    Import-DscResource -ModuleName xCertificate
+    node localhost {
+        xCertificateImport Integration_Test {
+            Thumbprint = $Thumbprint
+            Path       = $Path
+            Location   = 'LocalMachine'
+            Store      = 'My'
+            Ensure     = 'Present'
+        }
+    }
+}

+ 17 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xCertificateImport_remove.config.ps1

@@ -0,0 +1,17 @@
+Configuration MSFT_xCertificateImport_Remove_Config {
+    param
+    (
+        $Thumbprint,
+        $Path
+    )
+    Import-DscResource -ModuleName xCertificate
+    node localhost {
+        xCertificateImport Integration_Test {
+            Thumbprint = $Thumbprint
+            Path       = $Path
+            Location   = 'LocalMachine'
+            Store      = 'My'
+            Ensure     = 'Absent'
+        }
+    }
+}

+ 135 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xPfxImport.Integration.Tests.ps1

@@ -0,0 +1,135 @@
+[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')]
+param ()
+
+$script:DSCModuleName   = 'xCertificate'
+$script:DSCResourceName = 'MSFT_xPfxImport'
+
+#region HEADER
+# Integration Test Template Version: 1.1.0
+[String] $script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
+if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or `
+     (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) )
+{
+    & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $script:moduleRoot -ChildPath '\DSCResource.Tests\'))
+}
+
+Import-Module (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force
+$TestEnvironment = Initialize-TestEnvironment `
+    -DSCModuleName $script:DSCModuleName `
+    -DSCResourceName $script:DSCResourceName `
+    -TestType Integration
+#endregion
+
+# Using try/finally to always cleanup even if something awful happens.
+try
+{
+    # Generate a self-signed certificate, export it and remove it from the store
+    # to use for testing.
+    # Don't use CurrentUser certificates for this test because they won't be found because
+    # DSC LCM runs under a different context (Local System).
+    $Certificate = New-SelfSignedCertificate `
+        -DnsName $ENV:ComputerName `
+        -CertStoreLocation Cert:\LocalMachine\My
+    $CertificatePath = Join-Path `
+        -Path $ENV:Temp `
+        -ChildPath "xPfxImport-$($Certificate.Thumbprint).pfx"
+    $testUsername = 'DummyUsername'
+    $testPassword = 'DummyPassword'
+    $testCredential = New-Object System.Management.Automation.PSCredential $testUsername, (ConvertTo-SecureString $testPassword -AsPlainText -Force)
+    $null = Export-PfxCertificate `
+        -Cert $Certificate `
+        -FilePath $CertificatePath `
+        -Password $testCredential.Password
+    $null = Remove-Item `
+        -Path $Certificate.PSPath `
+        -Force
+
+    #region Integration Tests
+    $ConfigFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:DSCResourceName)_Add.config.ps1"
+    . $ConfigFile
+
+    Describe "$($script:DSCResourceName)_Add_Integration" {
+        #region DEFAULT TESTS
+        It 'Should compile without throwing' {
+            {
+                $configData = @{
+                    AllNodes = @(
+                        @{
+                            NodeName                    = 'localhost';
+                            PSDscAllowPlainTextPassword = $true
+                            }
+                        )
+                    }
+                & "$($script:DSCResourceName)_Add_Config" `
+                    -OutputPath $TestDrive `
+                    -ConfigurationData $configData `
+                    -Path $CertificatePath `
+                    -Thumbprint $Certificate.Thumbprint `
+                    -Credential $testCredential
+                Start-DscConfiguration -Path $TestDrive -ComputerName localhost -Wait -Verbose -Force
+            } | Should not throw
+        }
+
+        It 'should be able to call Get-DscConfiguration without throwing' {
+            { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should Not throw
+        }
+        #endregion
+
+        It 'Should have set the resource and all the parameters should match' {
+            # Get the Certificate details
+            $CertificateNew = Get-Item `
+                -Path "Cert:\LocalMachine\My\$($Certificate.Thumbprint)"
+            $CertificateNew                             | Should BeOfType System.Security.Cryptography.X509Certificates.X509Certificate2
+            $CertificateNew.Thumbprint                  | Should Be $Certificate.Thumbprint
+            $CertificateNew.Subject                     | Should Be $Certificate.Subject
+        }
+    }
+    #endregion
+
+    #region Integration Tests
+    $ConfigFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:DSCResourceName)_Remove.config.ps1"
+    . $ConfigFile
+
+    Describe "$($script:DSCResourceName)_Remove_Integration" {
+        #region DEFAULT TESTS
+        It 'Should compile without throwing' {
+            {
+                & "$($script:DSCResourceName)_Remove_Config" `
+                    -OutputPath $TestDrive `
+                    -Path $CertificatePath `
+                    -Thumbprint $Certificate.Thumbprint
+                Start-DscConfiguration -Path $TestDrive -ComputerName localhost -Wait -Verbose -Force
+            } | Should not throw
+        }
+
+        It 'should be able to call Get-DscConfiguration without throwing' {
+            { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should Not throw
+        }
+        #endregion
+
+        It 'Should have set the resource and all the parameters should match' {
+            # Get the Certificate details
+            $CertificateNew = Get-Item `
+                -Path "Cert:\LocalMachine\My\$($Certificate.Thumbprint)" `
+                -ErrorAction SilentlyContinue
+            $CertificateNew                             | Should BeNullOrEmpty
+        }
+    }
+    #endregion
+}
+finally
+{
+    # Clean up
+    $null = Remove-Item `
+        -Path $CertificatePath `
+        -Force `
+        -ErrorAction SilentlyContinue
+    $null = Remove-Item `
+        -Path $Certificate.PSPath `
+        -Force `
+        -ErrorAction SilentlyContinue
+
+    #region FOOTER
+    Restore-TestEnvironment -TestEnvironment $TestEnvironment
+    #endregion
+}

+ 19 - 0
active-directory-lab-hybrid-adfs/lab-hybrid-adfs/DSC/adDSC/xCertificate/2.3.0.0/Tests/Integration/MSFT_xPfxImport_add.config.ps1

@@ -0,0 +1,19 @@
+Configuration MSFT_xPfxImport_Add_Config {
+    param
+    (
+        $Thumbprint,
+        $Path,
+        $Credential
+    )
+    Import-DscResource -ModuleName xCertificate
+    node localhost {
+        xPfxImport Integration_Test {
+            Thumbprint   = $Thumbprint
+            Path         = $Path
+            Location     = 'LocalMachine'
+            Store        = 'My'
+            Ensure       = 'Present'
+            Credential   = $Credential
+        }
+    }
+}

Some files were not shown because too many files changed in this diff