diff --git a/ESAE.sln b/ESAE.sln index cd7a149..5368cb9 100644 --- a/ESAE.sln +++ b/ESAE.sln @@ -3,7 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26228.4 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{F5034706-568F-408A-B7B3-4D38C6DB8A32}") = "ESAE", "ESAE\ESAE.pssproj", "{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7D0F4932-DD3D-4210-9848-15731BEA7AD8}" + ProjectSection(SolutionItems) = preProject + LICENSE = LICENSE + README.md = README.md + EndProjectSection +EndProject +Project("{F5034706-568F-408A-B7B3-4D38C6DB8A32}") = "ESAE", "ESAE\ESAE.pssproj", "{606AD942-B472-4C07-873A-4945F916A2C7}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,10 +17,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.Build.0 = Release|Any CPU + {606AD942-B472-4C07-873A-4945F916A2C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {606AD942-B472-4C07-873A-4945F916A2C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {606AD942-B472-4C07-873A-4945F916A2C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {606AD942-B472-4C07-873A-4945F916A2C7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ESAE/ESAE.psm1 b/ESAE/ESAE.psm1 deleted file mode 100644 index dbe9988..0000000 --- a/ESAE/ESAE.psm1 +++ /dev/null @@ -1,6 +0,0 @@ -<# - My Function -#> -function Get-Function { - -} \ No newline at end of file diff --git a/ESAE/ESAE.pssproj b/ESAE/ESAE.pssproj index 27fd09e..d79cf0c 100644 --- a/ESAE/ESAE.pssproj +++ b/ESAE/ESAE.pssproj @@ -1,12 +1,18 @@ - + + Debug 2.0 - 6CAFC0C6-A428-4d30-A9F9-700E829FEA51 + {606ad942-b472-4c07-873a-4945f916a2c7} Exe MyApplication MyApplication ESAE + SAK + SAK + SAK + SAK + true @@ -25,11 +31,39 @@ prompt 4 - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ESAE/Extras/AdditionalGPOBundle.zip b/ESAE/Extras/AdditionalGPOBundle.zip new file mode 100644 index 0000000..57011b0 Binary files /dev/null and b/ESAE/Extras/AdditionalGPOBundle.zip differ diff --git a/ESAE/Extras/ESAEAdditionalGPOBundle.zip b/ESAE/Extras/ESAEAdditionalGPOBundle.zip new file mode 100644 index 0000000..e9c4670 Binary files /dev/null and b/ESAE/Extras/ESAEAdditionalGPOBundle.zip differ diff --git a/ESAE/Extras/ESAEGroups.json b/ESAE/Extras/ESAEGroups.json new file mode 100644 index 0000000..feb9a75 --- /dev/null +++ b/ESAE/Extras/ESAEGroups.json @@ -0,0 +1,74 @@ +[ + { + "Name": "UG-ESAE-Domain-Admins", + "Scope": "Global", + "Category": "Security", + "MemberOf": [ + "Domain Admins" + ], + "Path": "OU=User Groups,OU=ESAE Privileged Resources" + }, + { + "Name": "UG-ESAE-GPO-Admins", + "Scope": "Global", + "Category": "Security", + "MemberOf": [ + "Group Policy Creator Owners" + ], + "Path": "OU=User Groups,OU=ESAE Privileged Resources" + }, + { + "Name" : "UG-ESAE-DNS-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "DnsAdmins" + ], + "Path" : "OU=User Groups,OU=ESAE Privileged Resources" + }, + { + "Name" : "UG-ESAE-Enterprise-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Enterprise Admins" + ], + "Path" : "OU=User Groups,OU=ESAE Privileged Resources" + }, + { + "Name" : "UG-ESAE-Schema-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Schema Admins" + ], + "Path" : "OU=User Groups,OU=ESAE Privileged Resources" + }, + { + "Name" : "UG-ESAE-Server-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Server Operators" + ], + "Path" : "OU=User Groups,OU=ESAE Privileged Resources" + }, + { + "Name": "UG-ESAE-Administrators", + "Scope": "Global", + "Category": "Security", + "MemberOf": [ + "Administrators" + ], + "Path": "OU=User Groups,OU=ESAE Privileged Resources" + }, + { + "Name" : "UG-ESAE-Account-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Account Operators" + ], + "Path" : "OU=User Groups,OU=ESAE Privileged Resources" + } +] \ No newline at end of file diff --git a/ESAE/Extras/ManagementGroups.json b/ESAE/Extras/ManagementGroups.json new file mode 100644 index 0000000..6c069ec --- /dev/null +++ b/ESAE/Extras/ManagementGroups.json @@ -0,0 +1,188 @@ +[ + { + "Name" : "UG-DS-Domain-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=DS User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name" : "UG-DS-Server-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=DS User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name" : "UG-DS-Administrators", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=DS User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name" : "UG-DS-Workstation-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=DS User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-DS-Account-Admins", + "Scope": "Global", + "Category": "Security", + "Path": "OU=DS User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-DS-GPOAdmin-Admins", + "Scope": "Global", + "Category": "Security", + "Path": "OU=DS User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-DS-GPOAdmin-Approvers", + "Scope": "Global", + "Category": "Security", + "Path": "OU=DS User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-DS-GPOAdmin-Creators", + "Scope": "Global", + "Category": "Security", + "Path": "OU=DS User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-DS-GPOAdmin-Reviewers", + "Scope": "Global", + "Category": "Security", + "Path": "OU=DS User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name" : "UG-Root-Enterprise-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=Root User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-Root-Schema-Admins", + "Scope": "Global", + "Category": "Security", + "Path": "OU=Root User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name" : "UG-Root-Domain-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=Root User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name" : "UG-Root-Server-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=Root User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name" : "UG-Root-Administrators", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=Root User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name" : "UG-Root-Workstation-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=Root User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-Root-Account-Admins", + "Scope": "Global", + "Category": "Security", + "Path": "OU=Root User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-Root-GPOAdmin-Admins", + "Scope": "Global", + "Category": "Security", + "Path": "OU=Root User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-Root-GPOAdmin-Approvers", + "Scope": "Global", + "Category": "Security", + "Path": "OU=Root User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-Root-GPOAdmin-Creators", + "Scope": "Global", + "Category": "Security", + "Path": "OU=Root User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-Root-GPOAdmin-Reviewers", + "Scope": "Global", + "Category": "Security", + "Path": "OU=Root User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name" : "UG-Dev-Enterprise-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=Dev User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-Dev-Schema-Admins", + "Scope": "Global", + "Category": "Security", + "Path": "OU=Dev User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name" : "UG-Dev-Domain-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=Dev User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name" : "UG-Dev-Server-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=Dev User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name" : "UG-Dev-Administrators", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=Dev User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name" : "UG-Dev-Workstation-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=Dev User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-Dev-Account-Admins", + "Scope": "Global", + "Category": "Security", + "Path": "OU=Dev User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-Dev-GPOAdmin-Admins", + "Scope": "Global", + "Category": "Security", + "Path": "OU=Dev User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-Dev-GPOAdmin-Approvers", + "Scope": "Global", + "Category": "Security", + "Path": "OU=Dev User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-Dev-GPOAdmin-Creators", + "Scope": "Global", + "Category": "Security", + "Path": "OU=Dev User Groups,OU=User Groups,OU=Privileged Resources" + }, + { + "Name": "UG-Dev-GPOAdmin-Reviewers", + "Scope": "Global", + "Category": "Security", + "Path": "OU=Dev User Groups,OU=User Groups,OU=Privileged Resources" + } +] \ No newline at end of file diff --git a/ESAE/Extras/OU.json b/ESAE/Extras/OU.json new file mode 100644 index 0000000..ac7aa1f --- /dev/null +++ b/ESAE/Extras/OU.json @@ -0,0 +1,105 @@ +[ + { + "Label": "ESAE Privileged Resources", + "Children": [ + { + "Label": "Admins", + "Children": [ ] + }, + { + "Label": "Workstations", + "Children": [ ] + }, + { + "Label": "User Groups", + "Children": [ ] + }, + { + "Label": "Resource Groups", + "Children": [ ] + }, + { + "Label": "Servers", + "Children": [ + { + "Label": "Windows Servers", + "Children": [ + { + "Label": "WSUS", + "Children": [ ] + }, + { + "Label": "Change Auditor", + "Children": [ ] + } + ] + }, + { + "Label": "Non Windows Servers", + "Children": [ ] + } + ] + } + ] + }, + { + "Label": "Privileged Resources", + "Children": [ + { + "Label": "Admins", + "Children": [ + { + "Label": "DS Admins", + "Children": [ ] + }, + { + "Label": "Root Admins", + "Children": [ ] + }, + { + "Label": "Dev Admins", + "Children": [ ] + } + ] + }, + { + "Label": "Workstations", + "Children": [ ] + }, + { + "Label": "User Groups", + "Children": [ + { + "Label": "DS User Groups", + "Children": [ ] + }, + { + "Label": "Root User Groups", + "Children": [ ] + }, + { + "Label": "Dev User Groups", + "Children": [ ] + } + ] + }, + { + "Label": "Resource Groups", + "Children": [ + { + "Label": "DS Resource Groups", + "Children": [ ] + }, + { + "Label": "Root Resource Groups", + "Children": [ ] + }, + { + "Label": "Dev Resource Groups", + "Children": [ ] + } + ] + } + ] + } +] \ No newline at end of file diff --git a/ESAE/src/AddOnAdmxBundle.zip b/ESAE/src/AddOnAdmxBundle.zip new file mode 100644 index 0000000..c4f7910 Binary files /dev/null and b/ESAE/src/AddOnAdmxBundle.zip differ diff --git a/ESAE/src/AdditionalSites.json b/ESAE/src/AdditionalSites.json new file mode 100644 index 0000000..278eb1c --- /dev/null +++ b/ESAE/src/AdditionalSites.json @@ -0,0 +1,8 @@ +[ + { + "Name" : "Site2", + "Subnets" : [ + "192.168.2.0/24" + ] + } +] \ No newline at end of file diff --git a/ESAE/src/BaseAdmxBundle.zip b/ESAE/src/BaseAdmxBundle.zip new file mode 100644 index 0000000..7d4f1e7 Binary files /dev/null and b/ESAE/src/BaseAdmxBundle.zip differ diff --git a/ESAE/src/BaseGPOBundle.zip b/ESAE/src/BaseGPOBundle.zip new file mode 100644 index 0000000..08682a8 Binary files /dev/null and b/ESAE/src/BaseGPOBundle.zip differ diff --git a/ESAE/ESAE.tests.ps1 b/ESAE/src/ESAE.ps1 similarity index 100% rename from ESAE/ESAE.tests.ps1 rename to ESAE/src/ESAE.ps1 diff --git a/ESAE/ESAE.psd1 b/ESAE/src/ESAE.psd1 similarity index 74% rename from ESAE/ESAE.psd1 rename to ESAE/src/ESAE.psd1 index 590c339..98aaf6e 100644 Binary files a/ESAE/ESAE.psd1 and b/ESAE/src/ESAE.psd1 differ diff --git a/ESAE/src/ESAE.psm1 b/ESAE/src/ESAE.psm1 new file mode 100644 index 0000000..3bd8bd6 --- /dev/null +++ b/ESAE/src/ESAE.psm1 @@ -0,0 +1,5577 @@ +$script:LogPath = "$env:SystemDrive\adforest_install.log" +$script:TranscriptPath = "$env:SystemDrive\transcript.log" + +[System.Environment]::SetEnvironmentVariable("LogPath", $script:LogPath, [System.EnvironmentVariableTarget]::Machine) + +$script:FinishSecureADForestTask = "FinishSecureADForest" +$script:InstallSecureADForestTask = "InstallSecureADForest" + +$script:RemoteForestPasswordFilePath = "$env:ALLUSERSPROFILE\Microsoft\RFPassword.txt" +$script:LocalForestPasswordFilePath = "$env:ALLUSERSPROFILE\Microsoft\LFPassword.txt" +$script:SafeModeAdminPasswordFilePath = "$env:ALLUSERSPROFILE\Microsoft\ADPassword.txt" + +$script:ConfigFilePath = "$PSScriptRoot\config.json" +$script:MSSFilePath = "$PSScriptRoot\sceregvl.inf" + +$script:BaseGPOBundle = "$PSScriptRoot\BaseGPOBundle.zip" +$script:ForestGPLinks = "$PSScriptRoot\Forest_GPLinks.json" +$script:OUStructure = "$PSScriptRoot\Forest_OU.json" +$script:AdditionalGPOs = "$PSScriptRoot\Forest_AdditionalGPOBundle.zip" +$script:AdditionalGPLinks = "$PSScriptRoot\Forest_AdditionalGPLinks.json" +$script:ForestLocalGroups = "$PSScriptRoot\Forest_LocalGroups.json" +$script:BaseAdmxBundle = "$PSScriptRoot\BaseAdmxBundle.zip" +$script:AddOnAdmxBundle = "$PSScriptRoot\AddOnAdmxBundle.zip" +$script:SysvolFiles = [System.IO.Path]::Combine($PSScriptRoot, "SysvolFiles.zip") + +$script:ESAEOUConfigFilePath = "$PSScriptRoot\ESAE_OU.json" +$script:ESAELocalGroups = "$PSScriptRoot\ESAE_LocalGroups.json" #Not really needed, the script will use the default groups and name them appropriately +$script:ESAERemoteGroups = "$PSScriptRoot\ESAE_RemoteManagementGroups.json" +$script:ESAEGPLinks = "$PSScriptRoot\ESAE_GPLinks.json" + +#region Base Commands + +Function New-SecureADForest { + <# + .SYNOPSIS + The cmdlet builds a new AD forest with a single domain on a single server, creates standard user groups and OUs, imports pre-configured GPOs with the DISA STIGs, and runs AD and DNS STIGs. + + .DESCRIPTION + The cmdlet renames the computer, if desired, sets the local administrator password, and then installs Active Directory with a new forest and domain. It sets up an AD site, and then sets up some AD + infrastructure from configuration files or uses a default setup to include an OU structure, security groups, and user accounts. DISA STIG GPOs are imported and linked to OUs based on a configuration + file. If this forest is designated as an Enhanced Security Administrative Environment (ESAE), it sets up conditional forwarders on the DC and the remote forest if specified, and then builds a one way + forest trust. The AD recycle bin is enabled, the Group Policy Central Store is setup, and the MSS GPO settings are imported to be available to use. + + This cmdlet takes the parameter input and creates a config file that is used by later steps that are enacted through scheduled tasks. All passwords are temporarily stored on the file system encrypted + with SYSTEM credentials and deleted. + + This cmdlet specifically creates the config file from the parameter and saves it, renames the computer, sets the administrator password, and saves the encrypted passwords that are used later. Then, + if a reboot is required due to renaming the computer, it creates a scheduled task to run Install-SecureADForest, otherwise the next cmdlet is run directly without a reboot. + + .PARAMETER ForestRootDomainName + The name of the forest and root domain. + + .PARAMETER ComputerName + The new name the domain controller should have. + + .PARAMETER AdminPassword + The new password for the local administrator account. + + .PARAMETER BaseNTDSPath + The root location to place the NTDS.dit, SYSVOL, and logs file folders. Useful if the Active Directory files contents are being placed on a separate drive from the OS. This defaults to c:\NTDS. + + .PARAMETER FunctionalLevel + The functional level of the forest and root domain. This defaults to 6. + + -- Windows Server 2003 : 2 + -- Windows Server 2008 : 3 + -- Windows Server 2008 R2 : 4 + -- Windows Server 2012 : 5 + -- Windows Server 2012 R2 : 6 + -- Windows Server 2016 : 7 + + .PARAMETER SiteName + The name of the AD site the new domain controller will be a part of. The defaults to Default-First-Site-Name. + + .PARAMETER SiteSubnets + The subnet blocks that will be added to the site. These should be in the format of X.X.X.X/CIDR. + + .PARAMETER SafeModePassword + The DSRM password. This will default to the AdminPassword if it is specified, or will prompt for input if it is not. + + .PARAMETER LogPath + The path progress logs are written to. This defaults to $env:SystemDrive\adforest_install.log. + + .PARAMETER OUConfigFilePath + The file containing the OU setup configuration. If no file is specified, a default OU structure is implemented. + + .PARAMETER ForestGroupsConfigFilePath + The file containing the security group configuration for the groups that are used to manage the forest. If no file is specified, a default set of administrative groups are deployed. The groups are added + to existing built in groups, like Domain Admins, so that well-known SID groups are not utilized for the membership of user accounts. + + If a custom OU structure is specified, this configuration must be specified so that the groups are created in OUs that actually exist. + + .PARAMETER IsESAEForest + Specify that this forest will be an ESAE forest. This option prompts additional available dynamic parameters needed to configure the ESAE forest and forest trust. + + .PARAMETER GPOBundleFilePath + The path to the default set of GPOs that include the DISA STIGs. This defaults to $PSScriptRoot\BaseGPOBundle.zip. The default bundle of GPOs utilize WMI filters that are also imported. + + .PARAMETER GPLinksFilePath + The path to the configuration file that maps the imported base GPOs specified in GPOBundleFilePath to OUs. This will default to "$PSScriptRoot\Forest_GPLinks.json" for a standalone forest install and + default to "$PSScriptRoot\ESAE_GPLinks.json" for an ESAE installation. + + .PARAMETER AdditonalGPOsFilePath + The path to a zip file containing any additional GPOs that should be imported. This is useful so that the base GPO bundle can contain just the STIG GPOs, and this can contain environment specific + GPOs. If no file is specified, no additional GPOs are imported. + + .PARAMETER AdditionalGPLinksFilePath + The path to the configuration file that maps the imported additional GPOs specified in AdditionalGPOsFilePath to OUs. + + .PARAMETER BaseAdmxBundleFilePath + The path to the zip file with the ADMX and ADML files that will be added to the central policy store. This defaults to "$PSScriptRoot\BaseAdmxBundle.zip". To use the local PolicyDefinitions folder, set this parameter as an empty string. + + .PARAMETER AddOnAdmxBundleFilePath + The path to the zip file with additional ADMX and ADML files. This is useful so that the base bundle can remain unchanged and the add-on can be environment specific. + + .PARAMETER SysvolFilesPath + The path to the zip containing files and folders that will be extracted to the SYSVOL into a folder named "files". This defaults to "$PSScriptRoot\Sysvolfiles.zip". + + .PARAMETER UsersFilePath + The path to the configuration file that specifies users to be created and their group membership. This defaults to none. + + .PARAMETER AdditionalSitesConfigFilePath + The path to the configuration file that specifies additional AD sites and subnets to be created. + + .PARAMETER RemoteForestCredential + The credentials to use to connect to the remote forest during an ESAE installation to create the conditional forwarder pointing to the ESAE forest and setup the forest trust. + + .PARAMETER RemoteForest + The DNS name of the remote forest during an ESAE installation. + + .PARAMETER LocalForest + The DNS name of the local forest being setup for ESAE. This is only required if the forest being setup is different than the local forest the cmdlet is running on. Typically this is not required. + + .PARAMETER LocalForestCredential + Specifies the credentials to use on the local forest to set up the forest trust for an ESAE forest. This is only needed if the specific credentials are needed in the local forest, typically not required. + + .PARAMETER ConfigFilePath + The path to the configuration file containing all of the required parameters to execute the cmdlet. This can be used to help programatically execute the cmdlet repeatedly, but this cmdlet will still + need to have the AdminPassword, SafeModePassword, or both specified directly. They can be stored as secure strings in the config file, but that is not really secure. + + .PARAMETER CreateRemoteConditionalForwarder + Specify that a conditional forwarder to the new ESAE forest should be created in the remote forest. Requires RemoteForestCredential is specified. + + .PARAMETER CreateLocalConditionalForwarder + Specify that a conditional forwarder to the remote forest should be created in the new ESAE forest. This needs to be specified unless an external DNS solution is utilized to resolve the remote forest. + + .PARAMETER RemoteForestMasterServers + The remote DNS servers that will be used to create the local conditional forwarder in the new ESAE forest. + + .PARAMETER ManagementGroupsConfigFilePath + The path to the configuration file that specifies groups to be created that will be used to manage remote forests and domains from the new ESAE forest. + + .INPUTS + System.String + This represents the configuration file path. + + .OUTPUTS + None + + .EXAMPLE + New-SecureADForest -ForestRootDomainName "admin.local" ` + -ComputerName "AdminDC" ` + -BaseNTDSPath "n:\NTDS" ` + -SiteName "Site1" ` + -SiteSubnets @("192.168.1.0/24") ` + -GPOBundleFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\BaseGPOBundle.zip" ` + -GPLinksFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\ESAE_GPLinks.json" ` + -OUConfigFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\ESAE_OU.json" ` + -ManagementGroupsConfigFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\ESAE_RemoteManagementGroups.json" ` + -ForestGroupsConfigFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\ESAE_LocalGroups.json" ` + -UsersFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\users.json" ` + -SysvolFilesPath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\ESAE_Sysvolfiles.zip" ` + -IsESAEForest ` + -RemoteForest "contoso.com" ` + -RemoteForestMasterServers @("192.168.2.1") ` + -CreateLocalConditionalForwarder ` + -RemoteForestCredential (Get-Credential) ` + -CreateRemoteConditionalForwarder ` + -AdditionalGPOsFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\ESAE_AdditionalGPOBundle.zip" ` + -AdditionalGPLinksFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\ESAE_AdditionalGPLinks.json" ` + -BaseAdmxBundleFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\BaseAdmxBundle.zip" ` + -AddOnAdmxBundleFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\AddOnAdmxBundle.zip" + + Builds a new ESAE forest including the defined OU structure, remote forest management groups, specified users, and DNS settings to administer the contoso.com forest from the new admin.local forest. + + The default security groups for managing the ESAE forest are used. The OU.json file uses an OU structure compatible with the specified security group creation locations. + + .EXAMPLE + New-SecureADForest -ForestRootDomainName "admin.local" ` + -ComputerName "AdminDC" ` + -BaseNTDSPath "n:\NTDS" ` + -SiteName "Site1" ` + -SiteSubnets @("192.168.1.0/24") ` + -ManagementGroupsConfigFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\ESAE_RemoteManagementGroups.json" ` + -UsersFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\users.json" ` + -IsESAEForest ` + -RemoteForest "contoso.com" ` + -RemoteForestMasterServers @("192.168.2.1") ` + -CreateLocalConditionalForwarder ` + -RemoteForestCredential (Get-Credential) ` + -CreateRemoteConditionalForwarder ` + -AdditionalGPOsFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\ESAE_AdditionalGPOBundle.zip" ` + -AdditionalGPLinksFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\ESAE_AdditionalGPLinks.json" ` + -AddOnAdmxBundleFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\AddOnAdmxBundle.zip" + + Builds a new ESAE forest including the defined OU structure, remote forest management groups, specified users, and DNS settings to administer the contoso.com forest from the new admin.local forest. + + The default security groups for managing the ESAE forest are used, as well as the default OU structure. The defaults for the base GPO Bundle, the base Admx Bundle, and the GP Links are used as well. + + .EXAMPLE + New-SecureADForest -ForestRootDomainName "admin.local" ` + -ComputerName "AdminDC" ` + -BaseNTDSPath "n:\NTDS" ` + -SiteName "Site1" ` + -SiteSubnets @("192.168.1.0/24") ` + -OUConfigFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\Forest_OU.json" ` + -ForestGroupsConfigFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\Forest_LocalGroups.json" ` + -UsersFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\users.json" ` + -AdditionalGPOsFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\Forest_AdditionalGPOBundle.zip" ` + -AdditionalGPLinksFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\Forest_AdditionalGPLinks.json" + + Builds a new standalone secure forest including the defined OU structure, and specified users. + + .EXAMPLE + New-SecureADForest -ForestRootDomainName "admin.local" + + This represents the minimum configuration to deploy a new forest called admin.local. All of the default OU and group settings are used. No users are created and no additional GPOs or ADMX files are imported. The default Sysvol files + are extracted. No AD Site information is updated. The base GPO bundle and associated links are used. + + .NOTES + The remote forest credentials should have Enterprise Admin permissions in the remote forest. + #> + [CmdletBinding(DefaultParameterSetName="File")] + Param( + [Parameter(Mandatory=$true, ParameterSetName="Parameters")] + [System.String]$ForestRootDomainName, + + [Parameter(ParameterSetName="Parameters")] + [System.String]$ComputerName, + + [Parameter()] + [SecureString]$AdminPassword, + + [Parameter(ParameterSetName="Parameters")] + [System.String]$BaseNTDSPath = "c:\NTDS", + + [Parameter(ParameterSetName="Parameters")] + [ValidateRange(2,7)] + [int]$FunctionalLevel = 6, + + [Parameter(ParameterSetName="Parameters")] + [System.String]$SiteName = "Default-First-Site-Name", + + [Parameter(ParameterSetName="Parameters")] + [System.String[]]$SiteSubnets = @(), + + [Parameter()] + [SecureString]$SafeModePassword, + + [Parameter(ParameterSetName="Parameters")] + [System.String]$LogPath = $script:LogPath, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$OUConfigFilePath = [System.String]::Empty, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$ForestGroupsConfigFilePath = [System.String]::Empty, + + [Parameter(ParameterSetName="Parameters")] + [switch]$IsESAEForest, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$GPOBundleFilePath = $script:BaseGPOBundle, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$GPLinksFilePath = [System.String]::Empty, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$AdditionalGPOsFilePath, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$AdditionalGPLinksFilePath, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$BaseAdmxBundleFilePath = $script:BaseAdmxBundle, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$AddOnAdmxBundleFilePath = [System.String]::Empty, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$UsersFilePath = [System.String]::Empty, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$AdditionalSitesConfigFilePath = [System.String]::Empty, + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$SysvolFilesPath = $script:SysvolFiles, + + [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName="File")] + [ValidateScript({Test-Path -Path $_})] + [System.String]$ConfigFilePath = $script:ConfigFilePath + ) + DynamicParam + { + [System.Management.Automation.RuntimeDefinedParameterDictionary]$ParamDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary + + $ValidateScript = New-Object -TypeName System.Management.Automation.ValidateScriptAttribute([System.Management.Automation.ScriptBlock]::Create("if (![System.String]::IsNullOrEmpty(`$_)) { Test-Path -Path `$_ } else { return `$true }")) + + if ($IsESAEForest) { + + [System.Management.Automation.ParameterAttribute]$Attributes = New-Object -TypeName System.Management.Automation.ParameterAttribute + $Attributes.ParameterSetName = "Parameters" + + $AttributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] + $AttributeCollection.Add($Attributes) + + #region RemoteForestCredential + + $Temp = $AttributeCollection.Remove($Attributes) + $Attributes.Mandatory = $true + $AttributeCollection.Add($Attributes) + + [System.Management.Automation.RuntimeDefinedParameter]$DynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("RemoteForestCredential", [PSCredential], $AttributeCollection) + $ParamDictionary.Add("RemoteForestCredential", $DynParam) + + $Temp = $AttributeCollection.Remove($Attributes) + $Attributes.Mandatory = $false + $AttributeCollection.Add($Attributes) + + #endregion + + #region RemoteForest + + [System.Management.Automation.RuntimeDefinedParameter]$DynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("RemoteForest", [System.String], $AttributeCollection) + $ParamDictionary.Add("RemoteForest", $DynParam) + + #endregion + + #region RemoteForest + + [System.Management.Automation.RuntimeDefinedParameter]$DynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("LocalForest", [System.String], $AttributeCollection) + $ParamDictionary.Add("LocalForest", $DynParam) + + #endregion + + #region LocalForestCredential + + [System.Management.Automation.RuntimeDefinedParameter]$DynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("LocalForestCredential", [PSCredential], $AttributeCollection) + $ParamDictionary.Add("LocalForestCredential", $DynParam) + + #endregion + + #region ESAE Stub Zones + + [System.Management.Automation.RuntimeDefinedParameter]$DynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("CreateRemoteConditionalForwarder", [switch], $AttributeCollection) + $ParamDictionary.Add("CreateRemoteConditionalForwarder", $DynParam) + + [System.Management.Automation.RuntimeDefinedParameter]$DynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("CreateLocalConditionalForwarder", [switch], $AttributeCollection) + $ParamDictionary.Add("CreateLocalConditionalForwarder", $DynParam) + + [System.Management.Automation.RuntimeDefinedParameter]$DynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("RemoteForestMasterServers", [System.String[]], $AttributeCollection) + $ParamDictionary.Add("RemoteForestMasterServers", $DynParam) + + #endregion + + #region Remote Forest Management Groups + + $AttributeCollection.Add($ValidateScript) + + [System.Management.Automation.RuntimeDefinedParameter]$DynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("ManagementGroupsConfigFilePath", [System.String], $AttributeCollection) + $ParamDictionary.Add("ManagementGroupsConfigFilePath", $DynParam) + + $Temp = $AttributeCollection.Remove($ValidateScript) + + #endregion + } + + return $ParamDictionary + } + + Begin { + Enable-TaskSchedulerHistory + } + + Process { + $PsBoundParameters.GetEnumerator() | ForEach-Object { + New-Variable -Name $_.Key -Value $_.Value -ErrorAction SilentlyContinue + } + + switch ($PSCmdlet.ParameterSetName) { + "Parameters" { + + if ([System.String]::IsNullOrEmpty($LogPath)) { + $LogPath = $script:LogPath + } + + $ParametersNotToSave = @("AdminPassword","SafeModePassword","ConfigFilePath","RemoteForestCredential","LocalForestCredential") + $CommandName = $PSCmdlet.MyInvocation.InvocationName + $ParameterList = @{} + + (Get-Command -Name $CommandName).Parameters.GetEnumerator() | Select-Object -ExpandProperty Key | Where-Object {$_ -notin $ParametersNotToSave} | ForEach-Object { + $Temp = Get-Variable -Name $_ -ErrorAction SilentlyContinue + + if ($Temp -ne $null -and $Temp.Name -ne $null) { + + if ($Temp.Value.GetType() -eq [System.Management.Automation.SwitchParameter]) { + $ParameterList.Add($Temp.Name, [System.Boolean]::Parse($Temp.Value.ToString())) + } + else { + $ParameterList.Add($Temp.Name,$Temp.Value) + } + } + } + + $PsBoundParameters.GetEnumerator() | Where-Object {$_.Key -notin $ParametersNotToSave } | ForEach-Object { + if ($_ -ne $null -and $_.Key -ne $null) { + if (!@($ParameterList.Keys).Contains($_.Key)) { + if ($_.Value.GetType() -eq [System.Management.Automation.SwitchParameter]) { + $ParameterList.Add($_.Key, [System.Boolean]::Parse($_.Value.ToString())) + } + else { + $ParameterList.Add($_.Key, $_.Value) + } + } + } + } + + if ($IsESAEForest) { + $RemoteForestCredential = $PSBoundParameters.RemoteForestCredential + $LocalForestCredential = $PSBoundParameters.LocalForestCredential + + $ParameterList.Add("RemoteForestUserName", $RemoteForestCredential.UserName) + $ParameterList.Add("RemoteForestPasswordFilePath", $script:RemoteForestPasswordFilePath) + + if ($LocalForestCredential -ne $null -and $LocalForestCredential -ne [PSCredential]::Empty) { + $ParameterList.Add("LocalForestUserName", $LocalForestCredential.UserName) + $ParameterList.Add("LocalForestPasswordFilePath", $script:LocalForestPasswordFilePath) + } + else { + $ParameterList.Add("LocalForestPasswordFilePath", "") + $ParameterList.Add("LocalForestUserName", "") + } + } + + $Config = New-ConfigurationFile -ParameterList $ParameterList + $Parameters = ConvertFrom-Json -InputObject $Config.Content + + break + } + "File" { + $Config = New-ConfigurationFile -SourceFilePath $ConfigFilePath + $Parameters = ConvertFrom-Json -InputObject $Config.Content + + if ([System.String]::IsNullOrEmpty($Parameters.ForestRootDomainName)) { + throw "Configuration file missing required parameter ForestRootDomainName." + } + + if ([System.String]::IsNullOrEmpty($Parameters.LogPath)) { + $Parameters.LogPath = $script:LogPath + } + + if ([System.String]::IsNullOrEmpty($Parameters.FunctionalLevel)) { + $Parameters.FunctionalLevel = 6 + } + + if ([System.String]::IsNullOrEmpty($Parameters.BaseAdmxBundleFilePath)) { + $Parameters.BaseAdmxBundleFilePath = $script:BaseAdmxBundle + } + + break + } + default { + throw "Could not determine the parameter set for New-SecureADForest" + } + } + + [System.Environment]::SetEnvironmentVariable("NewADForestLogPath", $Parameters.LogPath, [System.EnvironmentVariableTarget]::Machine) + + [bool]$Success = $true + + if ($SafeModePassword -eq $null -and $AdminPassword -eq $null) { + $VerifyPassword = $null + while(($SafeModePassword -eq $null -or $VerifyPassword -eq $null) -or ($SafeModePassword -ne $VerifyPassword)) { + $SafeModePassword = Read-Host -AsSecureString -Prompt "Enter the DSRM safe mode password" + $VerifyPassword = Read-Host -AsSecureString -Prompt "Verify the DSRM safe mode password" + } + } + + if ($SafeModePassword -eq $null -and $AdminPassword -ne $null) { + $SafeModePassword = $AdminPassword + } + + if (![System.String]::IsNullOrEmpty($Parameters.UsersFilePath)) { + if (!(Test-Path -Path $Parameters.UsersFilePath)) { + Write-Warning "The file specified for the user import, $($Parameters.UsersFilePath), could not be found." + Write-Log "The file specified for the user import, $($Parameters.UsersFilePath), could not be found." + throw [System.IO.FileNotFoundException]("The file specified for the user import, $($Parameters.UsersFilePath), could not be found." ) + } + } + + if (![System.String]::IsNullOrEmpty($Parameters.ForestGroupsConfigFilePath)) { + if (!(Test-Path -Path $Parameters.ForestGroupsConfigFilePath)) { + Write-Warning "The file specified for the forest groups, $($Parameters.ForestGroupsConfigFilePath), could not be found." + Write-Log "The file specified for the forest groups, $($Parameters.ForestGroupsConfigFilePath), could not be found." + throw [System.IO.FileNotFoundException]("The file specified for the forest groups, $($Parameters.ForestGroupsConfigFilePath), could not be found.") + } + } + + if (![System.String]::IsNullOrEmpty($Parameters.OUConfigFilePath)) { + if(!(Test-Path -Path $Parameters.OUConfigFilePath)) { + Write-Warning "The file specified for the OU configuration, $($Parameters.OUConfigFilePath), could not be found." + Write-Log "The file specified for the OU configuration, $($Parameters.OUConfigFilePath), could not be found." + throw [System.IO.FileNotFoundException]("The file specified for the OU configuration, $($Parameters.OUConfigFilePath), could not be found.") + } + } + + try + { + Write-Log "Adding ADDS windows feature." + Add-ADDSWindowsFeature -IncludeManagementTools + + try + { + if ($AdminPassword -ne $null) { + Write-Log "Setting local admin password and enabling the account if disabled." + Set-LocalAdminPassword -AdminPassword $AdminPassword -EnableAccount + } + else { + Write-Log "No new password specified, skipping." + } + + try + { + $RebootRequired = $false + + if (![System.String]::IsNullOrEmpty($Parameters.ComputerName)) { + Write-Log "Renaming computer to $($Parameters.ComputerName)." + Rename-LocalComputer -ComputerName $Parameters.ComputerName -ErrorAction SilentlyContinue + $RebootRequired = $true + } + else { + Write-Log "No computer name provided, not renaming." + } + + if ($Parameters.IsESAEForest) { + Write-Log "This is an ESAE Forest installation." + try + { + Write-Log "Saving encrypted remote forest password." + $Temp = New-SaveEncryptedPasswordTask -Password ($RemoteForestCredential.Password) -FilePath $script:RemoteForestPasswordFilePath + Write-Log "Remote forest password saved at $Temp." + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + + if ($LocalForestCredential -ne $null -and $LocalForestCredential -ne [PSCredential]::Empty) { + try + { + Write-Log "Saving encrypted local forest password." + $Temp = New-SaveEncryptedPasswordTask -Password ($LocalForestCredential.Password) -FilePath $script:LocalForestPasswordFilePath + Write-Log "Local forest password saved at $Temp." + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + } + } + + if ($RebootRequired) { + try + { + Write-Log "Saving encrypted admin password." + $Temp = New-SaveEncryptedPasswordTask -Password $SafeModePassword -FilePath $script:SafeModeAdminPasswordFilePath + Write-Log "Safe mode password saved at $Temp." + + try + { + Write-Log "Preparing Install Forest scheduled task." + New-InstallSecureADForestScheduledTask -ConfigFilePath ($Config.FilePath) -PasswordFilePath $script:SafeModeAdminPasswordFilePath + Write-Log "Completed building scheduled task." + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + } + else { + Write-Log "No reboot required, moving to installing the forest." + Install-SecureADForest -ConfigFilePath $Config.FilePath -SafeModePassword $SafeModePassword + } + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + + if ($Success -and $RebootRequired) { + Write-Log "Restarting computer." + Restart-Computer -Force + } + + if (!$Success) { + Write-Log "One of the steps in New-SecureADForest failed." + $CommandText = Get-CommandText -Command ($PSCmdlet.MyInvocation.MyCommand.Name) -Parameters $PSBoundParameters + Write-Log "Rerun this command: $CommandText" + } + } + + End { + } +} + +Function Install-SecureADForest { + <# + .SYNOPSIS + This cmdlet launches the installation of a new, secure AD forest. + + .DESCRIPTION + Active Directory is installed through this cmdlet at a 2012 R2 functional level. + + After the installation is complete, if a configuration file was used to initiate the command, a scheduled task is created that will call Set-ADForestSecurityConfiguration after a reboot. If the restart + parameter is not specified, the server must manually be rebooted to start the next steps. The New-SecureADForest cmdlet specifies that the server should be rebooted when calling this step. + + If the cmdlet is not launched from the configuration file input, the cmdlet will optionally reboot the machine based in the inputted parameter. + + .PARAMETER ConfigFilePath + The path to the configuration file with the parameters used to run this cmdlet. This defaults to "$PSScriptRoot\config.json". + + .PARAMETER SafeModePassword + The password to use with DSRM. + + .PARAMETER BaseNTDSPath + The root folder to put the appropriate Active Directory database, log, and sysvol folders and files. This is useful when those files are stored on a drive different than the OS. + + .PARAMETER ForestRootDomainName + The DNS name of the new forest. + + .PARAMETER Restart + Specify whether to automatically restart the server after Active Directory is installed. + + .PARAMETER FunctionalLevel + The functional level of the forest and root domain. This defaults to 6. + + -- Windows Server 2003 : 2 + -- Windows Server 2008 : 3 + -- Windows Server 2008 R2 : 4 + -- Windows Server 2012 : 5 + -- Windows Server 2012 R2 : 6 + + .INPUTS + None + + .OUTPUTS + None + + .EXAMPLE + Install-SecureADForest -ConfigFilePath "$PSScriptRoot\config.json" -Restart + + Installs Active Directory based on the config file path and automatically reboots the server after the installation. A scheduled task is created to run the follow on security hardening and configuration + steps after the reboot is complete. + + .EXAMPLE + Install-SecureADForest -BaseNTDSPath "n:\NTDS" -ForestRootDomainName "admin.local" -Restart -FunctionalLevel 6 -SafeModePassword (ConvertTo-SecureString -String "P@$$w0rd" -AsPlainText -Force) + + Installs Active Directory to n:\NTDS in a forest named admin.local with a Server 2012 R2 functional level. The server is automatically rebooted after installation. + + .NOTES + None + #> + [CmdletBinding(DefaultParameterSetName="File")] + Param ( + [Parameter(ParameterSetName="File")] + [ValidateScript({Test-Path -Path $_})] + [System.String]$ConfigFilePath = $script:ConfigFilePath, + + [Parameter(Mandatory=$true)] + [SecureString]$SafeModePassword, + + [Parameter(ParameterSetName="Parameters")] + [System.String]$BaseNTDSPath = "c:\NTDS", + + [Parameter(Mandatory=$true, ParameterSetName="Parameters")] + [System.String]$ForestRootDomainName, + + [Parameter(ParameterSetName="Parameters")] + [switch]$Restart = $false, + + [Parameter(ParameterSetName="Parameters")] + [ValidateRange(2,7)] + [int]$FunctionalLevel = 6 + ) + + Begin { + } + + Process { + switch ($PSCmdlet.ParameterSetName) { + "File" { + Write-Log "Getting configuration file information." + $Parameters = ConvertFrom-Json -InputObject (Get-Content -Path $ConfigFilePath -Raw) + + if (!([System.String]::IsNullOrEmpty($Parameters.ForestRootDomainName))) { + $ForestRootDomainName = $Parameters.ForestRootDomainName + } + else { + Write-Log "Forest root domain name is null or empty." + throw [System.ArgumentNullException]("Forest root domain name is null or empty") + } + + if (![System.String]::IsNullOrEmpty($Parameters.BaseNTDSPath)) { + $BaseNTDSPath = $Parameters.BaseNTDSPath + } + + if(![System.String]::IsNullOrEmpty($Parameters.FunctionalLevel)) { + $FunctionalLevel = $Parameters.FunctionalLevel + } + + break + } + "Parameters" { + + break + } + } + + [bool]$Success = $false + + Write-Log "Checking for Install AD Forest Scheduled Task." + + if ((Get-ScheduledTask -TaskName $script:InstallSecureADForestTask -ErrorAction SilentlyContinue) -ne $null) { + Write-Log "Removing Install AD Forest Scheduled Task." + Unregister-ScheduledTask -TaskName $script:InstallSecureADForestTask -Confirm:$false + } + + $Netbios = $ForestRootDomainName.Substring(0,[System.Math]::Min($ForestRootDomainName.IndexOf("."), 15)) + + <# + -- Windows Server 2003 : 2 or Win2003 + -- Windows Server 2008 : 3 or Win2008 + -- Windows Server 2008 R2 : 4 or Win2008R2 + -- Windows Server 2012 : 5 or Win2012 + -- Windows Server 2012 R2 : 6 or Win2012R2 + -- Windows Server 2016 : 7 or Win2016 + #> + + try + { + Write-Log "Installing AD Forest." + + Install-ADDSForest ` + -CreateDnsDelegation:$false ` + -DatabasePath "$BaseNTDSPath\Database" ` + -DomainMode 6 ` + -DomainName $ForestRootDomainName ` + -DomainNetbiosName $Netbios ` + -ForestMode 6 ` + -InstallDns:$true ` + -LogPath "$BaseNTDSPath\Logs" ` + -NoRebootOnCompletion:$true ` + -SysvolPath "$BaseNTDSPath\SYSVOL" ` + -Force:$true ` + -SafeModeAdministratorPassword $SafeModePassword + + + if ($PSCmdlet.ParameterSetName -eq "File") { + Write-Log "Preparing finish AD Forest installation scheduled task." + New-FinishSecureADForestInstallationScheduledTask -ConfigFilePath $ConfigFilePath + Write-Log "Completed building the scheduled task." + } + + $Success = $true + } + catch [Exception] + { + Write-Log $_ + } + + if ($Success) { + Write-Log "Installation completed successfully." + if (($PSCmdlet.ParameterSetName -eq "Parameters" -and $Restart) -or ($PSCmdlet.ParameterSetName -eq "File")) { + Write-Log "Restarting computer." + Restart-Computer -Force + } + else { + Write-Log "Restart automatically not selected. The server needs to be rebooted to finish the installation." + } + } + else { + Write-Log "One of the steps in Install-SecureADForest failed." + $CommandText = Get-CommandText -Command ($PSCmdlet.MyInvocation.MyCommand.Name) -Parameters $PSBoundParameters + Write-Log "Rerun this command: $CommandText" + } + } + + End { + } +} + +Function Set-ADForestSecurityConfiguration { + <# + .SYNOPSIS + This cmdlet completes the installation of Active Directory and sets default configurations and implements security hardening. + + .DESCRIPTION + This cmdlet renames the default site to the specified name, adds the specified subnets to the site, creates any additional specified sites, installs the KDS root key with an immediate effective time, + enables the AD recycle bin, adds the MSS GPO settings, creates the specified OU structure, and creates groups to manage the new forest. + + If this is an ESAE forest, additional groups are also created for managing remote forests, if specified. + + Then the central policy store is created and the ADMX bundle zip is imported to it, the Active Directory STIG items that cannot be applied through GPOs are run (requires the ActiveDirectoryStig module), + the DNS STIG items are applied (requires the DnsStig module), extracts and config files used for STIG implementation like Java or Mozilla or other config files like custom lockscreen backgrounds to the sysvol, + creates a standard set of WMI filters for GPOs (requires the ActiveDirectoryTools module), imports the base GPO bundle, links those GPOs to the OUs specified in the GP Links configuration file, imports + any additional GPOs specified and links those, then finally creates Fine Grained Password policies that comply with the DISA STIG requirements (one is set as the default and another is created with the + same settings called FGP_STIG and is applied to Domain Users. + + .PARAMETER ConfigFilePath + The path to the configuration file with the parameters used to run this cmdlet. This defaults to "$PSScriptRoot\config.json". + + .PARAMETER SiteName + The new name of the Default First Site. + + .PARAMETER SiteSubnets + The subnets to add to the default site. + + .PARAMETERS OUConfigFilePath + The path to the configuration file for the OU structure to be deployed in the new forest root domain. This defaults to a standard OU structure depending on whether the deployment is for an ESAE environment or not. + + .PARAMETER ForestGroupsConfigFilePath + The path to the configuration file for the security groups to be created that will be utilized to manage the new forest. The defaults to a standard set of groups. + + .PARAMETER GPOBundleFilePath + The path to the zip file containing the base GPOs. This defaults to "$PSScriptRoot\BaseGPOBundle.zip". + + .PARAMETER GPLinksFilePath + The path to the configuration file that maps the imported base GPOs specified in GPOBundleFilePath to OUs. This will default to "$PSScriptRoot\Forest_GPLinks.json" for a standalone forest install and + default to "$PSScriptRoot\ESAE_GPLinks.json" for an ESAE installation. + + .PARAMETER AdditonalGPOsFilePath + The path to a zip file containing any additional GPOs that should be imported. This is useful so that the base GPO bundle can contain just the STIG GPOs, and this can contain environment specific + GPOs. If no file is specified, no additional GPOs are imported. + + .PARAMETER AdditionalGPLinksFilePath + The path to the configuration file that maps the imported additional GPOs specified in AdditionalGPOsFilePath to OUs. + + .PARAMETER BaseAdmxBundleFilePath + The path to the zip file with the ADMX and ADML files that will be added to the central policy store. This defaults to "$PSScriptRoot\BaseAdmxBundle.zip". To use the local PolicyDefinitions folder, set this parameter as an empty string. + + .PARAMETER AddOnAdmxBundleFilePath + The path to the zip file with additional ADMX and ADML files. This is useful so that the base bundle can remain unchanged and the add-on can be environment specific. + + .PARAMETER IsESAEForest + Specify that this forest will be an ESAE forest. This option prompts additional available dynamic parameters needed to configure the ESAE forest and forest trust. + + .PARAMETER UsersFilePath + The path to the configuration file that specifies users to be created and their group membership. This defaults to none. + + .PARAMETER AdditionalSitesConfigFilePath + The path to the configuration file that specifies additional AD sites and subnets to be created. + + .PARAMETER ManagementGroupsConfigFilePath + The path to the configuration file that specifies groups to be created that will be used to manage remote forests and domains from the new ESAE forest. + + .PARAMETER SysvolFilesPath + The path to the zip containing files and folders that will be extracted to the SYSVOL into a folder named "files". This defaults to "$PSScriptRoot\Sysvolfiles.zip". + + .INPUTS + None + + .OUTPUTS + None + + .EXAMPLE + Set-ADForestSecurityConfiguration -SiteName "Site1" ` + -SiteSubnets @("192.168.1.0/24") ` + -OUConfigFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\ESAE_OU.json" ` + -UsersFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\users.json" ` + -IsESAEForest ` + -AdditionalGPOsFilePath "$env:SystemRoot\windowspowershell\v1.0\modules\ESAE\ESAE_AdditionalGPOBundle.zip" ` + -AdditionalGPLinksFilePath "$env:SystemRoot\windowspowershell\v1.0\ESAE\modules\ESAE_AdditionalGPLinks.json" ` + -ManagementGroupsConfigFilePath "$env:SystemRoot\windowspowershell\v1.0\ESAE\modules\ESAE_RemoteManagementGroups.json" ` + + Runs the security configuration with the specified parameters. This uses the default for the groups used to manage the ESAE forest. + + .NOTES + None + #> + [CmdletBinding(DefaultParameterSetName="File")] + Param ( + [Parameter(ParameterSetName="File")] + [ValidateScript({Test-Path -Path $_})] + [System.String]$ConfigFilePath = $script:ConfigFilePath, + + [Parameter(ParameterSetName="Parameters")] + [System.String]$SiteName = "Default-First-Site-Name", + + [Parameter(ParameterSetName="Parameters")] + [System.String[]]$SiteSubnets = @(), + + [Parameter(ParameterSetName="Parameters")] + [System.String]$OUConfigFilePath = [System.String]::Empty, + + [Parameter(ParameterSetName="Parameters")] + [System.String]$ForestGroupsConfigFilePath = [System.String]::Empty, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$GPOBundleFilePath = $script:BaseGPOBundle, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$GPLinksFilePath = [System.String]::Empty, + + [Parameter(ParameterSetName="Parameters")] + [System.String]$AdditionalGPLinksFilePath = [System.String]::Empty, + + [Parameter(ParameterSetName="Parameters")] + [System.String]$AdditionalGPOsFilePath = [System.String]::Empty, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$BaseAdmxBundleFilePath = $script:BaseAdmxBundle, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$AddOnAdmxBundleFilePath = [System.String]::Empty, + + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$SysvolFilesPath = $script:SysvolFiles, + + [Parameter(ParameterSetName="Parameters")] + [switch]$IsESAEForest, + + [Parameter(ParameterSetName="Parameters")] + [ValidateScript({ + if (![System.String]::IsNullOrEmpty($_)) { + Test-Path -Path $_ + } + else { + return $true + } + })] + [System.String]$AdditionalSitesConfigFilePath = [System.String]::Empty, + + [Parameter(ParameterSetName="Parameters")] + [System.String]$UsersFilePath = [System.String]::Empty + ) + DynamicParam + { + [System.Management.Automation.RuntimeDefinedParameterDictionary]$ParamDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary + + if ($IsESAEForest) { + + [System.Management.Automation.ParameterAttribute]$Attributes = New-Object -TypeName System.Management.Automation.ParameterAttribute + $Attributes.ParameterSetName = "Parameters" + + $AttributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] + $AttributeCollection.Add($Attributes) + + [System.Management.Automation.RuntimeDefinedParameter]$DynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter("ManagementGroupsConfigFilePath", [System.String], $AttributeCollection) + $ParamDictionary.Add("ManagementGroupsConfigFilePath", $DynParam) + } + + return $ParamDictionary + } + + Begin { + } + + Process + { + switch ($PSCmdlet.ParameterSetName) { + "File" { + Write-Log "Getting configuration file." + $Parameters = ConvertFrom-Json -InputObject (Get-Content -Path $ConfigFilePath -Raw) + Write-Log "Successfully retrieved configuration file." + + $IsESAEForest = $Parameters.IsESAEForest + $ForestRootDomainName = $Parameters.ForestRootDomainName + + if (![System.String]::IsNullOrEmpty($Parameters.SiteName)) { + $SiteName = $Parameters.SiteName + } + else { + $SiteName = [System.String]::Empty + } + + if (![System.String]::IsNullOrEmpty($Parameters.AdditionalSitesConfigFilePath)) { + $AdditionalSitesConfigFilePath = $Parameters.AdditionalSitesConfigFilePath + } + else { + $AdditionalSitesConfigFilePath = [System.String]::Empty + } + + if (![System.String]::IsNullOrEmpty($Parameters.GPOBundleFilePath) -and (Test-Path -Path $Parameters.GPOBundleFilePath)) { + $GPOBundleFilePath = $Parameters.GPOBundleFilePath + } + else { + $GPOBundleFilePath = $script:BaseGPOBundle + } + + if (![System.String]::IsNullOrEmpty($Parameters.GPLinksFilePath) -and (Test-Path -Path $Parameters.GPLinksFilePath)) { + $GPLinksFilePath = $Parameters.GPLinksFilePath + } + else { + if ($IsESAEForest) { + $GPLinksFilePath = $script:ESAEGPLinks + } + else { + $GPLinksFilePath = $script:ForestGPLinks + } + } + + if ($Parameters.SiteSubnets -ne $null -and $Parameters.SiteSubnets.Length -gt 0) { + $SiteSubnets = $Parameters.SiteSubnets + } + else { + $SiteSubnets = @() + } + + if (![System.String]::IsNullOrEmpty($Parameters.SysvolFilesPath) -and (Test-Path -Path $Parameters.SysvolFilesPath)) { + $SysvolFilesPath = $Parameters.SysvolFilesPath + } + else { + if (Test-Path -Path $script:SysvolFiles) { + $SyvolFilesPath = $script:SysvolFiles + } + else { + $SysvolFilesPath = [System.String]::Empty + } + } + + if (![System.String]::IsNullOrEmpty($Parameters.UsersFilePath) -and (Test-Path -Path $Parameters.UsersFilePath)) { + $UsersFilePath = $Parameters.UsersFilePath + + } + else { + $UsersFilePath = [System.String]::Empty + } + + if (![System.String]::IsNullOrEmpty($Parameters.AdditionalGPOsFilePath)) { + $AdditionalGPOsFilePath = $Parameters.AdditionalGPOsFilePath + } + else { + $AdditionalGPOsFilePath = [System.String]::Empty + } + + if (![System.String]::IsNullOrEmpty($Parameters.AdditionalGPLinksFilePath)) { + $AdditionalGPLinksFilePath = $Parameters.AdditionalGPLinksFilePath + } + else { + $AdditionalGPLinksFilePath = [System.String]::Empty + } + + if (![System.String]::IsNullOrEmpty($Parameters.OUConfigFilePath)) { + $OUConfigFilePath = $Parameters.OUConfigFilePath + } + else { + $OUConfigFilePath = [System.String]::Empty + } + + if (![System.String]::IsNullOrEmpty($Parameters.ForestGroupsConfigFilePath)) { + $ForestGroupsConfigFilePath = $Parameters.ForestGroupsConfigFilePath + } + else { + $ForestGroupsConfigFilePath = [System.String]::Empty + } + + if (![System.String]::IsNullOrEmpty($Parameters.ManagementGroupsConfigFilePath)) { + $ManagementGroupsConfigFilePath = $Parameters.ManagementGroupsConfigFilePath + } + else { + $ManagementGroupsConfigFilePath = [System.String]::Empty + } + + if (![System.String]::IsNullOrEmpty($Parameters.BaseAdmxBundleFilePath) -and (Test-Path -Path $Parameters.BaseAdmxBundleFilePath)) { + $BaseAdmxBundleFilePath = $Parameters.BaseAdmxBundleFilePath + } + else { + $BaseAdmxBundleFilePath = [System.String]::Empty + } + + if (![System.String]::IsNullOrEmpty($Parameters.AddOnAdmxBundleFilePath) -and (Test-Path -Path $Parameters.AddOnAdmxBundleFilePath)) { + $AddOnAdmxBundleFilePath = $Parameters.AddOnAdmxBundleFilePath + } + else { + $AddOnAdmxBundleFilePath = [System.String]::Empty + } + + break + } + "Parameters" { + $Counter = 0 + $ForestRootDomainName = "" + $TimeoutLimit = 10 + while ([System.String]::IsNullOrEmpty($ForestRootDomainName) -and $Counter -lt $TimeoutLimit) { + Start-Sleep -Seconds 10 + try { + $ForestRootDomainName = Get-ADForest -Current LocalComputer -Server $env:COMPUTERNAME | Select-Object -ExpandProperty Name + } + catch [Exception] { + Write-Log -Message "Could not call Get-ADForest with error: $($_.Exception.Message)." + $Counter++ + } + + if ($Counter -eq $TimeoutLimit) { + Write-Log -Message "Timeout waiting to successfully call Get-ADForest, using computer domain." + $ForestRootDomainName = Get-WmiObject -Class Win32_ComputerSystem | Select-Object -ExpandProperty Domain + } + } + break + } + } + + if ([System.String]::IsNullOrEmpty($GPOBundleFilePath)) { + $GPOBundleFilePath = $script:BaseGPOBundle + } + + if (![System.String]::IsNullOrEmpty($GPLinksFilePath)) { + if ($IsESAEForest) { + $GPLinksFilePath = $script:ESAEGPLinks + } + else { + $GPLinksFilePath = $script:ForestGPLinks + } + } + + if (![System.String]::IsNullOrEmpty($ForestGroupsConfigFilePath) -and !(Test-Path -Path $ForestGroupsConfigFilePath)) { + Write-Warning "The file specified for the forest groups, $ForestGroupsConfigFilePath, could not be found." + Write-Log "The file specified for the forest groups, $ForestGroupsConfigFilePath, could not be found." + throw [System.IO.FileNotFoundException]("The file specified for the forest groups, $ForestGroupsConfigFilePath, could not be found.") + } + + if (![System.String]::IsNullOrEmpty($UsersFilePath) -and !(Test-Path -Path $UsersFilePath)) { + Write-Warning "The file specified for the users import, $UsersFilePath, could not be found." + Write-Log "The file specified for the users import, $UsersFilePath, could not be found." + throw [System.IO.FileNotFoundException]("The file specified for the users import, $UsersFilePath, could not be found.") + } + + if (![System.String]::IsNullOrEmpty($AdditionalGPOsFilePath) -and !(Test-Path -Path $AdditionalGPOsFilePath)) { + Write-Warning "The file specified for the additional gpo import, $AdditionalGPOsFilePath, could not be found." + Write-Log "The file specified for the additional gpo import, $AdditionalGPOsFilePath, could not be found." + throw [System.IO.FileNotFoundException]("The file specified for the additional gpo import, $AdditionalGPOsFilePath, could not be found.") + } + + if (![System.String]::IsNullOrEmpty($AdditionalGPLinksFilePath) -and !(Test-Path -Path $AdditionalGPLinksFilePath)) { + Write-Warning "The file specified for the additional gpo links, $AdditionalGPLinksFilePath, could not be found." + Write-Log "The file specified for the additional gpo links, $AdditionalGPLinksFilePath, could not be found." + throw [System.IO.FileNotFoundException]("The file specified for the additional gpo links, $AdditionalGPLinksFilePath, could not be found.") + } + + if (![System.String]::IsNullOrEmpty($OUConfigFilePath) -and !(Test-Path -Path $OUConfigFilePath)) { + Write-Warning "The file specified, $OUConfigFilePath, for the OU configuration could not be found." + Write-Log "The file specified, $OUConfigFilePath, for the OU configuration could not be found." + throw [System.IO.FileNotFoundException]("The file specified, $OUConfigFilePath, for the OU configuration could not be found.") + } + + if ($IsESAEForest -and ![System.String]::IsNullOrEmpty($ManagementGroupsConfigFilePath) -and !(Test-Path -Path $ManagementGroupsConfigFilePath)) { + Write-Warning "The file specified for the managemement groups, $ManagementGroupsConfigFilePath, could not be found." + Write-Log "The file specified for the management groups, $ManagementGroupsConfigFilePath, could not be found." + throw [System.IO.FileNotFoundException]("The file specified for the management groups, $ManagementGroupsConfigFilePath, could not be found.") + } + + [bool]$Success = $true + + $Counter = 0 + $NTDS = Get-Service -Name NTDS + + while ($NTDS.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Running) { + Start-Sleep -Seconds 1 + $Counter++ + $NTDS = Get-Service -Name NTDS + + if ($Counter -gt 600) { + throw "Timeout waiting for Active Directory Domain Services to start." + } + } + + $Counter = 0 + $ADWS = Get-Service -Name ADWS + + while ($ADWS.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Running) { + Start-Sleep -Seconds 1 + $Counter++ + $ADWS = Get-Service -Name ADWS + + if ($Counter -gt 600) { + throw "Timeout waiting for Active Directory Web Services to start." + } + } + + $DomainSID = Get-ADDomain -Identity $ForestRootDomainName -Server $env:COMPUTERNAME | Select-Object -ExpandProperty DomainSID + $DomainUsersSID = (New-Object System.Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::AccountDomainUsersSid, $DomainSID)).Value.ToString() + + $SYSVOL = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" | Select-Object -ExpandProperty SysVol + + #region AD Sites + if (![System.String]::IsNullOrEmpty($SiteName)) { + try + { + Write-Log "Renaming site." + Rename-ADSite -NewName $SiteName + } + catch [Exception] + { + Write-Log $_ + $Success = $false + } + } + + if ($SiteSubnets -ne $null -and $SiteSubnets.Count -gt 0) { + foreach ($Subnet in $SiteSubnets) { + try + { + Write-Log "Adding subnet $Subnet." + Add-ADSiteSubnet -SiteSubnet $Subnet -SiteName $SiteName + } + catch [Exception] + { + Write-Log $_ + $Success = $false + } + + } + } + + if (![System.String]::IsNullOrEmpty($AdditionalSitesConfigFilePath)) { + Write-Log "Additional site information provided, converting from Json to object." + try { + $AdditionalSitesJson = Get-Content -Path $AdditionalSitesConfigFilePath -Raw + $AdditionalSites = ConvertFrom-Json -InputObject $AdditionalSitesJson + + foreach ($Site in $AdditionalSites) { + Write-Log "Adding site $($Site.Name)." + + try{ + $NewSite = Get-ADReplicationSite -Identity $Site.Name + Write-Log "Site $($NewSite.Name) already exists, continuing." + } + catch [Exception] { + try { + $NewSite = New-ADReplicationSite -Name $Site.Name -PassThru -ProtectedFromAccidentalDeletion $true + Write-Log "Successfully created new site." + } + catch [Exception] { + Write-Log $_ + } + } + + if ($NewSite -ne $null) { + foreach ($Subnet in $Site.Subnets) { + Write-Log "Adding $Subnet to site $($NewSite.Name)." + Add-ADSiteSubnet -SiteName $NewSite.Name -SiteSubnet $Subnet + } + } + else { + Write-Log "New site object was null, not adding subnets." + } + } + } + catch [Exception] { + Write-Log $_ + } + } + + #endregion + + try { + Write-Log "Installing KDS Root Key, effective immediately." + Add-KdsRootKey -EffectiveImmediately + Write-Log "Installed KDS Root Key." + } + catch [Exception] { + Write-Log $_ + } + + try + { + Write-Log "Enabling Recycle Bin." + Enable-ADRecycleBin -ForestRootDomainName $ForestRootDomainName + } + catch [Exception] + { + Write-Log $_ + } + + try + { + Write-Log "Adding sceregvl.inf to add MSS settings." + New-MSSGPOSettings -Wait + Write-Log "Completed adding sceregvl.inf file." + } + catch [Exception] + { + Write-Log $_ + } + + #region OU and Group Structure + + try + { + Write-Log "Creating Forest OU Structure." + if ([System.String]::IsNullOrEmpty($OUConfigFilePath)) { + if ($IsESAEForest) { + Write-Log "Config file was null or empty, but this is an ESAE forest installation, using the ESAE_OU.json file for the configuration." + Set-OUStructure -FilePath $script:ESAEOUConfigFilePath + } + else { + Set-OUStructure -OUStructureJson ([System.String]::Empty) + } + } + else { + Set-OUStructure -FilePath $OUConfigFilePath + } + + Write-Log "Completed creating OU structure." + + try + { + Write-Log "Creating local forest management groups." + + if ([System.String]::IsNullOrEmpty($ForestGroupsConfigFilePath)) { + if ($IsESAEForest) { + Write-Log -Message "Creating groups for an ESAE forest." + $GroupPrefix = "UG-ESAE" + $ResourceOUName = "ESAE Privileged Resources" + } + else { + Write-Log -Message "Creating groups for a standalone forest." + $GroupPrefix = "UG" + $ResourceOUName = "Privileged Resources" + } + + New-StandardForestGroups -GroupNamePrefix $GroupPrefix -ResourceOUName $ResourceOUName + } + else { + Write-Log -Message "Using configuration file to create groups: $ForestGroupsConfigFilePath." + New-StandardForestGroups -FilePath $ForestGroupsConfigFilePath + } + + Write-Log "Completed creating local forest management groups." + } + catch [Exception] + { + Write-Log $_ + $Success = $false + } + + if ($IsESAEForest) { + try + { + Write-Log "Creating remote forest management groups." + + if ([System.String]::IsNullOrEmpty($ManagementGroupsConfigFilePath)) { + if ([System.String]::IsNullOrEmpty($OUConfigFilePath)) { + New-ManagementGroups + } + else { + Write-Log "No management group config file path specified, but a custom OU structure was specified. Cannot create default groups with a custom OU structure." + } + } + else { + New-ManagementGroups -FilePath $ManagementGroupsConfigFilePath + } + + Write-Log "Completed creating remote forest management groups." + } + catch [Exception] + { + Write-Log $_ + $Success = $false + } + } + } + catch [Exception] + { + Write-Log $_ + $Success = $false + } + + #endregion + + try + { + Write-Log "Building central policy store." + New-CentralPolicyStore -FilePath $BaseAdmxBundleFilePath -SysVolPath $SYSVOL -ForestRootDomainName $ForestRootDomainName + + if (![System.String]::IsNullOrEmpty($AddOnAdmxBundleFilePath)) { + try + { + Write-Log "Importing Add-On ADMX files." + Add-AdmxBundleToCentralPolicyStore -FilePath $AddOnAdmxBundleFilePath -SysVolPath $SYSVOL -ForestRootDomainName $ForestRootDomainName + } + catch [Exception] + { + Write-Log $_ + $Success = $false + } + } + } + catch [Exception] + { + Write-Log $_ + $Success = $false + } + + try { + Write-Log "Setting Active Directory non GPO STIG items." + Start-Transcript -Path $script:TranscriptPath -Append + Import-Module ActiveDirectoryStig + Set-ActiveDirectoryStigItems + Stop-Transcript + Write-Log "Completed Active Directory STIG." + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + + try { + Write-Log "Setting DNS Server STIG." + Start-Transcript -Path $script:TranscriptPath -Append + Import-Module DnsStig + Set-DnsServerStig -Forest $ForestRootDomainName + Stop-Transcript + Write-Log "Completed DNS Server STIG." + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + + #region GPO Operations + + if (![System.String]::IsNullOrEmpty($SysvolFilesPath)) { + try { + Write-Log "Extracting config files for GPO use to the SYSVOL." + Add-SysvolFiles + Write-Log "Finished extracting config files." + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + } + else { + Write-Log -Message "No Sysvol files zip specified." + } + + try + { + Write-Log "Creating WMI Filters." + Start-Transcript -Path $script:TranscriptPath -Append + + Import-Module ActiveDirectoryTools + New-StandardGPOWmiFilters + + Stop-Transcript + Write-Log "Finished creating standard WMI filters." + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + + try + { + Write-Log "Importing baseline GPOs." + $Destination = [System.IO.Path]::Combine($PSScriptRoot,"GPOs") + Write-Log "Extracting GPO bundle from $GPOBundleFilePath." + + Start-Transcript -Path $script:TranscriptPath -Append + + Extract-ZipFile -Source $GPOBundleFilePath -Destination $Destination + + Write-Log "Creating Migration Table." + $MigrationTablePath = New-GPOMigrationTable -BackupRootDirectory $Destination + + Write-Log "Setting preference values." + Set-GPPMigrationValues -BackupRootDirectory $Destination + + Write-Log "Importing GPOs to domain from $Destination for domain $ForestRootDomainName using migration table at $MigrationTablePath." + Import-FullGPOBackups -Path $Destination -Domain $ForestRootDomainName -MigrationTable $MigrationTablePath + + Stop-Transcript + + Write-Log "Deleting temp GPO folder." + Remove-Item -Path $Destination -Force -Recurse -Confirm:$false + Remove-Item -Path $MigrationTablePath -Force -Recurse -Confirm:$false + Write-Log "Completed importing GPOs." + + try { + Write-Log "Linking GPOs." + + Start-Transcript -Path $script:TranscriptPath -Append + + New-GPOLinks -LinksFilePath $GPLinksFilePath + try { + Write-Log "Removing default domain controllers policy." + $TopLevelDomain = Get-ADDomain -Identity $ForestRootDomainName | Select-Object -ExpandProperty DistinguishedName + Remove-GPLink -Name "Default Domain Controllers Policy" -Target "OU=Domain Controllers,$TopLevelDomain" -ErrorAction Stop + Write-Log "Successfully removed default domain controllers policy." + } + catch [Exception] { + Write-Log $_ + } + + Stop-Transcript + + Write-Log "Completed linking GPOs." + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + } + catch [Exception] { + Write-Log -ErrorRecord $_ + $Success = $false + } + + if (![System.String]::IsNullOrEmpty($AdditionalGPOsFilePath)) { + try { + Write-Log "Importing additional GPOs from $AdditionalGPOsFilePath." + $Destination = [System.IO.Path]::Combine($PSScriptRoot,"AdditionalGPOs") + + Start-Transcript -Path $script:TranscriptPath -Append + + Extract-ZipFile -Source $AdditionalGPOsFilePath -Destination $Destination + + Write-Log "Creating Migration Table" + $MigrationTablePath = New-GPOMigrationTable -BackupRootDirectory $Destination + + Write-Log "Setting preference values." + Set-GPPMigrationValues -BackupRootDirectory $Destination + + Write-Log "Importing GPOs to domain from $Destination for domain $ForestRootDomainName using migration table at $MigrationTablePath." + Import-FullGPOBackups -Path $Destination -Domain $ForestRootDomainName -MigrationTable $MigrationTablePath + + Write-Log "Deleting temp GPO folder." + Remove-Item -Path $Destination -Force -Recurse -Confirm:$false + Remove-Item -Path $MigrationTablePath -Force -Recurse -Confirm:$false + + Write-Log -Message "Completed importing GPOs." + + if (![System.String]::IsNullOrEmpty($AdditionalGPLinksFilePath) -and (Test-Path -Path $AdditionalGPLinksFilePath)) { + try { + Write-Log -Message "Linking Additional GPOs." + New-GPOLinks -LinksFilePath $AdditionalGPLinksFilePath + Write-Log -Message "Completed linking additional GPOs." + } + catch [Exception] { + Write-Log -ErrorRecord $_ + $Success = $false + } + } + + Stop-Transcript + } + catch [Exception] { + Write-Log -ErrorRecord $_ + $Success = $false + } + } + + #endregion + + try { + Write-Log "Creating fine grained password policies." + Write-Log "Setting default domain password policy settings objects." + + Set-ADDefaultDomainPasswordPolicy -Identity $ForestRootDomainName ` + -ComplexityEnabled $true ` + -LockoutDuration ([System.Timespan]::MinValue) ` + -LockoutObservationWindow ([System.Timespan]::FromMinutes(60)) ` + -LockoutThreshold 3 ` + -MinPasswordLength 14 ` + -MaxPasswordAge ([System.Timespan]::FromDays(60)) ` + -MinPasswordAge ([System.Timespan]::FromDays(1)) ` + -ReversibleEncryptionEnabled $false ` + -PasswordHistoryCount 24 ` + -Server $env:COMPUTERNAME ` + + Write-Log "Creating STIG password settings object." + + $PSO = New-ADFineGrainedPasswordPolicy -Name "FGP_STIG" ` + -ComplexityEnabled $true ` + -DisplayName "FGP_STIG" ` + -LockoutDuration ([System.Timespan]::MinValue) ` + -LockoutObservationWindow ([System.Timespan]::FromMinutes(60)) ` + -LockoutThreshold 3 ` + -MinPasswordLength 14 ` + -MaxPasswordAge ([System.Timespan]::FromDays(60)) ` + -MinPasswordAge ([System.Timespan]::FromDays(1)) ` + -ProtectedFromAccidentalDeletion $true ` + -ReversibleEncryptionEnabled $false ` + -PasswordHistoryCount 24 ` + -Precedence ([System.Int32]::MaxValue) ` + -Server $env:COMPUTERNAME ` + -PassThru + + Add-ADFineGrainedPasswordPolicySubject -Identity $PSO -Subjects $DomainUsersSID -Server $env:COMPUTERNAME + + Write-Log "Created default and domain Password Settings Objects." + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + + if (![System.String]::IsNullOrEmpty($UsersFilePath)) { + try { + + Write-Log "Importing users." + Import-ADUsers -FilePath $UsersFilePath -EnableLogging + Write-Log "Completed user import." + } + catch [Exception] { + Write-Log $_ + $Success = $false + } + } + + Write-Log "Checking for Finish AD Forest Scheduled Task." + + if ((Get-ScheduledTask -TaskName $script:FinishSecureADForestTask -ErrorAction SilentlyContinue) -ne $null) { + Write-Log "Removing Finish AD Forest Scheduled Task." + Unregister-ScheduledTask -TaskName $script:FinishSecureADForestTask -Confirm:$false + } + + if ($Success) { + Write-Log "Completed forest install." + + if ($IsESAEForest) { + Write-Log "Starting ESAE Configuration function." + Set-ESAEForestConfiguration -ConfigFilePath $ConfigFilePath + } + } + else { + Write-Log "One of the steps in Set-ADForestSecurityConfiguration failed." + $CommandText = Get-CommandText -Command ($PSCmdlet.MyInvocation.MyCommand.Name) -Parameters $PSBoundParameters + Write-Log "Rerun this command: $CommandText" + } + } + + End { + } +} + +Function Set-ESAEForestConfiguration { + <# + .SYNOPSIS + This cmdlet conducts the steps to set up the forest trust and DNS settings to enable the ESAE management forest. + + .DESCRIPTION + The cmdlet builds the conditional forwarders in the two forests, if specified, and then sets up the one-way forest trust from the remote forest to the new ESAE forest. + + .PARAMETER ConfigFilePath + The path to the configuration file with the parameters used to run this cmdlet. This defaults to "$PSScriptRoot\config.json". + + .PARAMETER RemoteForestCredential + The credentials to use to connect to the remote forest during an ESAE installation to create the conditional forwarder pointing to the ESAE forest and setup the forest trust. + + .PARAMETER RemoteForest + The DNS name of the remote forest during an ESAE installation. + + .PARAMETER LocalForest + The DNS name of the local forest being setup for ESAE. This is only required if the forest being setup is different than the local forest the cmdlet is running on. Typically this is not required. + + .PARAMETER LocalForestCredential + Specifies the credentials to use on the local forest to set up the forest trust for an ESAE forest. This is only needed if the specific credentials are needed in the local forest, typically not required. + + .PARAMETER CreateRemoteConditionalForwarder + Specify that a conditional forwarder to the new ESAE forest should be created in the remote forest. Requires RemoteForestCredential is specified. + + .PARAMETER CreateLocalConditionalForwarder + Specify that a conditional forwarder to the remote forest should be created in the new ESAE forest. This needs to be specified unless an external DNS solution is utilized to resolve the remote forest. + + .PARAMETER RemoteForestMasterServers + The remote DNS servers that will be used to create the local conditional forwarder in the new ESAE forest. + + .INPUTS + None + + .OUTPUTS + None + + .EXAMPLE + Set-ESAEForestConfiguration -RemoteForest "contoso.com" ` + -RemoteForestMasterServers @("192.168.2.1") ` + -CreateLocalConditionalForwarder ` + -RemoteForestCredential (Get-Credential) ` + -CreateRemoteConditionalForwarder ` + + .NOTES + The remote forest credential should have Enterprise Admin rights to build the conditional forwarder and establish the forest trust. + #> + [CmdletBinding(DefaultParameterSetName="File")] + Param ( + [Parameter(ParameterSetName="File")] + [ValidateScript({Test-Path -Path $_})] + [System.String]$ConfigFilePath = $script:ConfigFilePath, + + [Parameter(ParameterSetName="Parameters",Mandatory=$true)] + [System.String]$RemoteForest, + + [Parameter(ParameterSetName="Parameters",Mandatory=$true)] + [ValidateNotNull()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $RemoteForestCredential, + + [Parameter(ParameterSetName="Parameters")] + [ValidateNotNull()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $LocalForestCredential = [System.Management.Automation.PSCredential]::Empty, + + [Parameter(ParameterSetName="Parameters")] + [switch]$CreateLocalConditionalForwarder, + + [Parameter(ParameterSetName="Parameters")] + [switch]$CreateRemoteConditionalForwarder, + + [Parameter(ParameterSetName="Parameters")] + [ValidateNotNull()] + [System.String[]]$RemoteForestMasterServers = @() + ) + + Begin { + } + + Process { + Write-Log "Starting ESAE Forest Configuration." + + switch ($PSCmdlet.ParameterSetName) { + "File" { + Write-Log "Getting config file from $ConfigFilePath." + $Parameters = ConvertFrom-Json -InputObject (Get-Content -Path $ConfigFilePath -Raw) + + Write-Log "Remote forest password file $($Parameters.RemoteForestPasswordFilePath)." + + $RemoteForest = $Parameters.RemoteForest + $RemoteForestCredentialPassword = Get-EncryptedPassword -FilePath $Parameters.RemoteForestPasswordFilePath + $RemoteForestCredential = New-Object -TypeName System.Management.Automation.PSCredential($Parameters.RemoteForestUserName, $RemoteForestCredentialPassword) + + try { + Write-Log "Removing remote forest password file." + Remove-Item -Path $Parameters.RemoteForestPasswordFilePath + Write-Log "Removed password file." + } + catch [Exception] { + Write-Log $_ + } + + try { + if (![System.String]::IsNullOrEmpty($Parameters.LocalForestPasswordFilePath) -and (Test-Path -Path $Parameters.LocalForestPasswordFilePath) -and ![System.String]::IsNullOrEmpty($Parameters.LocalForestUserName)) { + Write-Log "Local forest credentials specified." + $LocalForestCredentialPassword = Get-EncryptedPassword -FilePath $Parameters.LocalForestPasswordFilePath + $LocalForestCredential = New-Object -TypeName System.Management.Automation.PSCredential($Parameters.LocalForestUserName, $LocalForestCredentialPassword) + + Write-Log "Removing local forest password file." + Remove-Item -Path $Parameters.RemoteForestPasswordFilePath + Write-Log "Removed password file." + + } else { + $LocalForestCredential = [PSCredential]::Empty + } + } + catch [Exception] { + Write-Log $_ + } + + if ($Parameters.CreateLocalConditionalForwarder -ne $null) { + $CreateLocalConditionalForwarder = $Parameters.CreateLocalConditionalForwarder + } + else { + $CreateLocalConditionalForwarder = $false + } + + if ($CreateLocalConditionalForwarder) { + if ($Parameters.RemoteForestMasterServers -ne $null -and $Parameters.RemoteForestMasterServers.Count -gt 0) { + $RemoteForestMasterServers = $Parameters.RemoteForestMasterServers + } + else { + Write-Log "Create local stub zone was specified, but the remote forest master servers array was null or empty." + throw "Create local stub zone was specified, but the remote forest master servers array was null or empty." + } + } + + if ($Parameters.CreateRemoteConditionalForwarder -ne $null) { + $CreateRemoteConditionalForwarder = $Parameters.CreateRemoteConditionalForwarder + } + else { + $CreateRemoteConditionalForwarder = $false + } + + break + } + "Parameters" { + + break + } + } + + [bool]$Success = $false + $ForestRootDomainName = Get-ADForest -Current LocalComputer -Server $env:COMPUTERNAME | Select-Object -ExpandProperty Name + + try { + Write-Log "Creating forest trust with $RemoteForest." + + New-ADForestTrust -RemoteForest $RemoteForest ` + -RemoteForestCredential $RemoteForestCredential ` + -LocalForestCredential $LocalForestCredential ` + -CreateLocalConditionalForwarder:$CreateLocalConditionalForwarder ` + -CreateRemoteConditionalForwarder:$CreateRemoteConditionalForwarder ` + -RemoteForestMasterServers $RemoteForestMasterServers ` + -EnableSelectiveAuthentication $true ` + -SidFilteringEnabled $true ` + -TrustDirection Inbound ` + -EnableLogging ` + -TrustingDomainSupportsKerberosAESEncryption $true ` + -WaitForRpcSs + } + catch [Exception] { + Write-Log $_ + } + + Write-Log "Completed ESAE Forest Configuration." + } + + End { + } +} + +#endregion + +#region AD Forest Prerequisites + +Function Add-ADDSWindowsFeature { + <# + .SYNOPSIS + Installs the Active Directory feature. + + .DESCRIPTION + Installs the Active Directory feature and optionally installs the management tools. + + .PARAMETER IncludeManagementTools + Specify whether the management tools should be installed, this defaults to false. + + .INPUTS + None + + .OUTPUTS + Microsoft.Windows.ServerManager.Commands.FeatureOperationResult + + .EXAMPLE + Add-ADDSWindowsFeature -IncludeManagementTools + + Installs the Active Directory feature with the management tools. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter()] + [switch]$IncludeManagementTools = $false + ) + Begin {} + + Process { + $Feature = Add-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools:$IncludeManagementTools + Write-Output -InputObject $Feature + } + + End { + } +} + +Function Set-LocalAdminPassword { + <# + .SYNOPSIS + Sets the local administrator password. + + .DESCRIPTION + Sets the local administrator password and optionally enables the account if it is disabled. + + .PARAMETER AdminPassword + The new password for the local administrator account. + + .PARAMETER EnableAccount + Specify to enable the local administrator account if it is disabled. + + .INPUTS + System.Boolean + + .OUTPUTS + Microsoft.Windows.ServerManager.Commands.FeatureOperationResult + + .EXAMPLE + Set-LocalAdminPassword -EnableAccount + + The cmdlet will prompt the user to enter the new password. + + .NOTES + None + #> + [CmdletBinding()] + Param ( + [Parameter(Position=0 , ValueFromPipeline=$true)] + [SecureString]$AdminPassword, + + [Parameter()] + [switch]$EnableAccount + ) + Begin { + } + + Process { + $HostName = $env:COMPUTERNAME + $Computer = [ADSI]"WinNT://$HostName,Computer" + + while($AdminPassword -eq $null) { + $AdminPassword = Read-Host -AsSecureString -Prompt "Enter the new administrator password" + } + + foreach ($Child in $Computer.Children | Where {$_.Class -eq "User"} ) { + + $Sid = New-Object System.Security.Principal.SecurityIdentifier($Child.objectSid[0],0) + + if ($Sid.Value -match "S-1-5-21-.*-500") { + + $User = [ADSI]"WinNT://$HostName/$($Child.Name),User" + $Password = Convert-SecureStringToString -SecureString $AdminPassword + + $User.SetPassword($Password) + + if ($EnableAccount) { + #The 0x0002 flag specifies that the account is diabled + #The binary AND operator will test the value to see if the bit is set, if it is, the account is disabled. + #Doing a binary OR would add the value to the flags, since it would not be present, the OR would add it + if ($User.UserFlags.Value -band "0x0002") { + #The binary XOR will remove the flag, which enables the account, the XOR means that if both values have the bit set, the result does not + #If only 1 value has the bit set, then it will remain set, so we need to ensure that the bit is actually set with the -band above for the XOR to actually + #remove the disabled value + $User.UserFlags = $User.UserFlags -bxor "0x0002" + $User.SetInfo() + } + } + + break + } + } + + Write-Output -InputObject $true + } + + End { + } +} + +Function Rename-LocalComputer { + <# + .SYNOPSIS + Renames the computer. + + .DESCRIPTION + Renames the computer. + + .PARAMETER NewName + The new name for the computer, must be 15 characters or less. + + .INPUTS + System.String + + .OUTPUTS + System.String + + .EXAMPLE + Rename-LocalComputer -ComputerName "AdminDC" + + Renames the local computer to AdminDC. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] + [ValidateScript({$_.Length -le 15})] + [System.String]$ComputerName + ) + Begin {} + + Process { + $NewName = Rename-Computer -NewName $ComputerName -PassThru + Write-Output -InputObject $NewName + } + + End { + } +} + +#endregion + +#region Finish AD Forest Installation + +Function Rename-ADSite { + <# + .SYNOPSIS + Renames the specified Active Directory Site. + + .DESCRIPTION + Renames the specified Active Directory Site + + .PARAMETER Name + The current identity of the Active Directory site, this defaults to "Default-First-Site-Name". + + .PARAMETER NewName + The new name of the Active Directory site. + + .INPUTS + None + + .OUTPUTS + Microsoft.ActiveDirectory.Management.ADReplicationSite + + .EXAMPLE + Rename-ADSite -NewName "Headquarters" + + Renames the Default-First-Site-Name site to Headquarters. + + .NOTES + None + #> + [CmdletBinding()] + Param ( + [Parameter(Position=1)] + [System.String]$Name = "Default-First-Site-Name", + + [Parameter(Position=0, Mandatory=$true)] + [System.String]$NewName + ) + + Begin {} + + Process { + $Site = Get-ADReplicationSite -Server $env:COMPUTERNAME -Identity $Name | Rename-ADObject -NewName $NewName -Server $env:COMPUTERNAME -PassThru + Write-Output -InputObject $Site + } + + End { + } +} + +Function Add-ADSiteSubnet { + <# + .SYNOPSIS + Adds a single subnet to an Active Directory site. + + .DESCRIPTION + Adds a single subnet to an Active Directory site. If the subnet already exists as a replication subnet, it is assigned to the specified Site, which will remove it from any pre-existing association. If it does not exist, it is added and assigned to the site. + + .PARAMETER SiteSubnet + The subnet to add to the site, should be in the form of X.X.X.X/CIDR. + + .PARAMETER SiteName + The site the subnet will be added to. This defaults to the current site of the computer the cmdlet is being run on. + + .INPUTS + None + + .OUTPUTS + Microsoft.ActiveDirectory.Management.ADReplicationSubnet + + .EXAMPLE + Add-ADSiteSubnet -SiteSubnet "192.168.1.0/24" -SiteName "Headquarters" + + The 192.168.1.0/24 subnet is added to the Headquarters site. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true, Position=0)] + [System.String]$SiteSubnet, + + [Parameter(Position=1)] + [System.String]$SiteName = [System.String]::Empty + ) + + Begin { + } + + Process { + if ([System.String]::IsNullOrEmpty($SiteName)) + { + $SiteName = Get-ADReplicationSite -Server $env:COMPUTERNAME | Select-Object -First 1 -ExpandProperty Name + } + + try + { + $ExistingSubnet = Get-ADReplicationSubnet -Identity $SiteSubnet -ErrorAction SilentlyContinue + + if ($ExistingSubnet -eq $null) + { + $Subnet = New-ADReplicationSubnet -Name $SiteSubnet -Site $SiteName + } + else + { + $Subnet = Set-ADReplicationSubnet -Identity $ExistingSubnet -Site $SiteName -PassThru + } + } + catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] + { + $Subnet = New-ADReplicationSubnet -Name $SiteSubnet -Site $SiteName + } + + Write-Output -InputObject $Subnet + } + + End { + } +} + +Function Enable-ADRecycleBin { + <# + .SYNOPSIS + Enables the AD recycle bin. + + .DESCRIPTION + Enables the AD recycle bin in the specified forest. + + .PARAMETER ForestRootDomainName + The name of the forest root domain. This defaults to the forest of the computer the cmdlet is being run on. + + .INPUTS + System.String + + .OUTPUTS + None + + .EXAMPLE + Enable-ADRecycleBin -ForestRootDomainName "admin.local" + + Enables the AD recycle bin in the admin.local forest. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Position=0, ValueFromPipeline=$true)] + [System.String]$ForestRootDomainName = [System.String]::Empty + ) + + Begin { + } + + Process { + if ([System.String]::IsNullOrEmpty($ForestRootDomainName)) + { + $ForestRootDomainName = Get-ADForest -Current LocalComputer | Select-Object -ExpandProperty Name + } + + Enable-ADOptionalFeature -Identity "Recycle Bin Feature" -Scope ForestOrConfigurationSet -Target $ForestRootDomainName -Confirm:$false + + Write-Host "AD Recycle Bin successfully enabled" -ForegroundColor Green + } + + End { + } +} + +Function New-CentralPolicyStore { + <# + .SYNOPSIS + Creates the Central Policy Store for GPOs. + + .DESCRIPTION + Creates the Central Policy Store in the SYSVOL. Then, the cmdlet extracts the ADMX and English ADML files in the specified zip, or uses the local ADMX and English ADML files in the PolicyDefinitions folder, and + moves those to the Central Policy Store. The default included zip file contains the ADMX definitions from Windows 10 1511. + + .PARAMETER FilePath + The path to the zip containing the ADMX and ADML files. This defaults to "$PSScriptRoot\BaseAdmxBundle.zip". Set this option to an empty string to use the local PolicyDefinitions folder instead. + + .PARAMETER SysVolPath + The location of the SYSVOL. This parameter defaults to finding the SYSVOL from "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters\" SysVol property. This property only exists on domain controllers, so if the + cmdlet is being run remotely, the path should be explicitly specified. + + .PARAMETER ForestRootDomainName + The name of the forest root domain to be used with the SYSVOL path. This defaults to the current forest of the local computer. + + .INPUTS + System.String + + .OUTPUTS + None + + .EXAMPLE + New-CentralPolicyStore + + Creates the Central Policy Store using the default ADMX zip bundle for the current forest of the local domain controller the cmdlet is run on. + + .NOTES + None + #> + [CmdletBinding()] + Param ( + [Parameter(Position=0, ValueFromPipeline=$true)] + [System.String]$FilePath = "$PSScriptRoot\BaseAdmxBundle.zip", + + [Parameter(Position=1)] + [System.String]$SysVolPath = [System.String]::Empty, + + [Parameter(Position=2)] + [System.String]$ForestRootDomainName = [System.String]::Empty + ) + + Begin { + } + + Process { + if ([System.String]::IsNullOrEmpty($SysVolPath)) { + $SYSVOL = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" | Select-Object -ExpandProperty SysVol + } + else { + $SYSVOL = $SysVolPath + } + + if ([System.String]::IsNullOrEmpty($ForestRootDomainName)) { + $ForestRootDomainName = Get-ADForest -Current LocalComputer | Select-Object -ExpandProperty Name + } + + if ($FilePath -eq $null -or $FilePath -eq [System.String]::Empty -or !(Test-Path -Path $FilePath)) { + $SourceAdmx = "$env:SYSTEMROOT\PolicyDefinitions\*.admx" + $SourceAdml = "$env:SYSTEMROOT\PolicyDefinitions\en-us\*.adml" + + $CentralStoreAdmx = "$SYSVOL\$ForestRootDomainName\Policies\PolicyDefinitions" + $CentralStoreAdml = "$SYSVOL\$ForestRootDomainName\Policies\PolicyDefinitions\en-us" + + New-Item -ItemType Directory -Path $CentralStoreAdmx + New-Item -ItemType Directory -Path $CentralStoreAdml + + Copy-Item -Path $SourceAdmx -Destination $CentralStoreAdmx + Copy-Item -Path $SourceAdml -Destination $CentralStoreAdml + } + else { + Extract-ZipFile -Source $FilePath -Destination "$SYSVOL\$ForestRootDomainName\Policies\PolicyDefinitions" + } + + Write-Host "Central Policy Store successfullly created." -ForegroundColor Green + } + + End { + } +} + +Function Add-AdmxBundleToCentralPolicyStore { + <# + .SYNOPSIS + Adds the contents of zip file to the Central Policy Store + + .DESCRIPTION + Adds the contents of a zip file to the Central Policy Store. + + .PARAMETER FilePath + The path to the zip containing the ADMX and ADML files. + + .PARAMETER SysVolPath + The location of the SYSVOL. This parameter defaults to finding the SYSVOL from "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters\" SysVol property. This property only exists on domain controllers, so if the + cmdlet is being run remotely, the path should be explicitly specified. + + .PARAMETER ForestRootDomainName + The name of the forest root domain to be used with the SYSVOL path. This defaults to the current forest of the local computer. + + .INPUTS + System.String + + .OUTPUTS + None + + .EXAMPLE + Add-AdmxBundletoCentralPolicyStore -FilePath "$PSScriptRoot\AddOnAdmxBundle.zip" + + Adds the contents of the zip file to the central policy store. + + .NOTES + None + #> + [CmdletBinding()] + Param ( + [Parameter(Position=0, ValueFromPipeline=$true, Mandatory=$true)] + [ValidateScript({Test-Path -Path $_})] + [System.String]$FilePath, + + [Parameter(Position=1)] + [System.String]$SysVolPath = [System.String]::Empty, + + [Parameter(Position=2)] + [System.String]$ForestRootDomainName = [System.String]::Empty + ) + + Begin { + } + + Process { + if ([System.String]::IsNullOrEmpty($FilePath) -or !(Test-Path -Path $FilePath)) + { + throw [System.IO.FileNotFoundException]("The source zip file could not be found.") + } + + if ([System.String]::IsNullOrEmpty($SysVolPath)) + { + $SYSVOL = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" | Select-Object -ExpandProperty SysVol + } + else + { + $SYSVOL = $SysVolPath + } + + if ([System.String]::IsNullOrEmpty($ForestRootDomainName)) + { + $ForestRootDomainName = Get-ADForest -Current LocalComputer | Select-Object -ExpandProperty Name + } + + if (Test-Path -Path "$SYSVOL\$ForestRootDomainName\Policies\PolicyDefinitions") + { + Extract-ZipFile -Source $FilePath -Destination "$SYSVOL\$ForestRootDomainName\Policies\PolicyDefinitions" + Write-Host "Add-on ADMX files successfullly extracted." -ForegroundColor Green + } + else + { + Write-Warning -Message "The Policy Definitions folder in the Central Policy Store could not be found." + } + } + + End { + } +} + +Function New-MSSGPOSettings { + <# + .SYNOPSIS + Adds the new sceregvl.inf file to enable the MSS settings in Group Policy Management. + + .DESCRIPTION + Adds the new sceregvl.inf file to enable the MSS settings in Group Policy Management. The original file is renamed to .old. + + .PARAMETER FilePath + The path to the new sceregvl.inf file. This defaults to "$PSScriptRoot\sceregvl.inf" and shouldn't need to be changed. + + .PARAMETER Wait + The function executes a scheduled task that runs as SYSTEM. This parameter waits for the schedule task to complete before completing. + + .INPUTS + System.String + + .OUTPUTS + None + + .EXAMPLE + New-MSSGPOSettings + + Adds the new sceregvl.inf file and enables the management of MSS settings in GPMC. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Position=0, ValueFromPipeline=$true)] + [ValidateScript({Test-Path -Path $_})] + [System.String]$FilePath = $script:MSSFilePath, + + [Parameter(Position=1)] + [switch]$Wait + ) + + Begin{ + } + + Process{ + $TargetFilePath = "$env:SYSTEMROOT\inf\sceregvl.inf" + $NewName = "$env:SYSTEMROOT\inf\sceregvl.old" + + $System = New-Object Security.Principal.SecurityIdentifier([System.Security.Principal.WellKnownSidType]::LocalSystemSid, $null) + + $SystemAce = New-Object System.Security.AccessControl.FileSystemAccessRule($System, + [System.Security.AccessControl.FileSystemRights]::FullControl, + [System.Security.AccessControl.InheritanceFlags]::None, + [System.Security.AccessControl.PropagationFlags]::None, + [System.Security.AccessControl.AccessControlType]::Allow + ) + + <#$Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\takeown.exe" -ArgumentList @("/f $TargetFilePath") -NoNewWindow + + #$Acl = Get-Acl -Path $TargetFilePath + #$Acl.AddAccessRule($SystemAce) + #Set-Acl -Path $TargetFilePath -AclObject $Acl + + $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\icacls.exe" -ArgumentList @("$TargetFilePath", "/grant SYSTEM:f") + + Rename-Item -Path $TargetFilePath -NewName $NewName + Copy-Item -Path $FilePath -Destination $TargetFilePath -Force + + $Process = Start-Process -FilePath "$env:SYSTEMROOT\System32\regsvr32.exe" -ArgumentList @("scecli.dll") -NoNewWindow#> + + $TaskName = "takeown" + + $Command = @" + try { + Import-Module -Name ESAE + [System.Diagnostics.Process]`$Process = New-Object System.Diagnostics.Process + `$Process.StartInfo.Filename = "`$env:SYSTEMROOT\System32\takeown.exe" + `$Process.StartInfo.Arguments = "/f ```"$TargetFilePath```"" + `$Process.StartInfo.RedirectStandardOutput = `$true + `$Process.StartInfo.UseShellExecute = `$false + `$Process.StartInfo.CreateNoWindow = `$true + `$Process.StartInfo.RedirectStandardError = `$true + `$Process.Start() | Out-Null + `$Process.WaitForExit() + + if (`$Process.ExitCode -ne 0) { + Write-Log -Message "takeown.exe exited with code `$Process.ExitCode." + [System.String]`$Out = `$Process.StandardError.ReadToEnd() + Write-Log -Message "Error running takeown.exe `$Out" + } + else { + [System.String]`$Out = `$Process.StandardOutput.ReadToEnd() + Write-Log -Message "Successfully ran takeown - `$Out" + + [System.Diagnostics.Process]`$Process = New-Object System.Diagnostics.Process + `$Process.StartInfo.Filename = "`$env:SYSTEMROOT\System32\icacls.exe" + `$Process.StartInfo.Arguments = "```"$TargetFilePath```" /grant SYSTEM:f" + `$Process.StartInfo.RedirectStandardOutput = `$true + `$Process.StartInfo.UseShellExecute = `$false + `$Process.StartInfo.CreateNoWindow = `$true + `$Process.StartInfo.RedirectStandardError = `$true + `$Process.Start() | Out-Null + `$Process.WaitForExit() + + if (`$Process.ExitCode -ne 0) { + Write-Log -Message "icacls.exe exited with code `$Process.ExitCode." + [System.String]`$Out = `$Process.StandardError.ReadToEnd() + Write-Log -Message "Error running icacls.exe `$Out" + } + else { + [System.String]`$Out = `$Process.StandardOutput.ReadToEnd() + Write-Log -Message "Successfully ran icacls - `$Out" + + try { + Write-Log -Message "Renaming $TargetFilePath to $NewName." + Rename-Item -Path "$TargetFilePath" -NewName "$NewName" + Write-Log -Message "Copying new file from $FilePath to $TargetFilePath." + Copy-Item -Path "$FilePath" -Destination "$TargetFilePath" -Force + Start-Process -FilePath "`$env:SYSTEMROOT\System32\regsvr32.exe" -ArgumentList @("scecli.dll") -NoNewWindow + Write-Log -Message "Successfully registered new sceregvl.inf file." + + Write-Log -Message "Removing scheduled task, $TaskName." + Unregister-ScheduledTask -TaskName $TaskName -Confirm:`$false + Write-Log -Message "Successfully unregistered scheduled task." + } + catch [Exception] { + Write-Log `$_ + } + } + } + } + catch [Exception] { + Write-Log `$_ + } +"@ + +<# $Command = @" + Start-Process -FilePath "`$env:SYSTEMROOT\System32\takeown.exe" -ArgumentList @("/f ```"$TargetFilePath```"") -NoNewWindow + Start-Process -FilePath "`$env:SYSTEMROOT\System32\icacls.exe" -ArgumentList @("$TargetFilePath", "/grant SYSTEM:f") -NoNewWindow + Rename-Item -Path "$TargetFilePath" -NewName "$NewName" + Copy-Item -Path "$FilePath" -Destination "$TargetFilePath" -Force + Start-Process -FilePath "`$env:SYSTEMROOT\System32\regsvr32.exe" -ArgumentList @("scecli.dll") -NoNewWindow +"@#> + + $Bytes = [System.Text.Encoding]::Unicode.GetBytes($Command) + $EncodedCommand = [Convert]::ToBase64String($Bytes) + + $STParams = "-NonInteractive -WindowStyle Hidden -NoProfile -NoLogo -EncodedCommand $EncodedCommand" + $STSource = "$env:SYSTEMROOT\System32\WindowsPowerShell\v1.0\powershell.exe" + $STAction = New-ScheduledTaskAction -Execute $STSource -Argument $STParams + $STPrincipal = New-ScheduledTaskPrincipal -UserId SYSTEM -LogonType ServiceAccount -RunLevel Highest + $STSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -StartWhenAvailable -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -MultipleInstances IgnoreNew + + $ScheduledTask = Register-ScheduledTask -TaskName $TaskName -Action $STAction -Principal $STPrincipal -Settings $STSettings -ErrorAction Stop + + Start-ScheduledTask -TaskName $TaskName + + if ($Wait) { + $Task = $ScheduledTask + + $Counter = 0 + while ($Task -ne $null -and $Counter -lt 600) { + $Task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue + Start-Sleep -Seconds 1 + $Counter++ + + if ($Counter -eq 600) { + throw "Timeout waiting for scheduled task to replace sceregvl.inf to complete." + } + } + } + } + + End{ + } +} + +Function New-StandardForestGroups { + <# + .SYNOPSIS + Creates the standard set of groups in the forest to manage the environment. + + .DESCRIPTION + This cmdlet creates "pseudo" groups for the builtin administrative groups in Active Directory. This allows users to be added to these new groups and the builtin groups with well-known SIDs can be statically set + so that any changes to those can undone or trigger an alert through monitoring tools. + + .PARAMETER GroupsJson + A JSON string that defines the new groups and their membership. This defaults to empty and a standard set of groups is created based on the default OU configuration provided by the Set-OUStructure cmdlet. + + .PARAMETER FilePath + The path to the JSON file that defines the new groups and their membership. This defaults to empty and a standard set of groups is created based on the default OU configuration provided by the Set-OUStructure cmdlet. + + .PARAMETER GroupNamePrefix + The prefix of each group name if the default groups are used. This defaults to UG. + + .PARAMETER ResourceOUName + The name of the OU under the top level domain that the User Resources OU is nested in. This is used with the default set of groups and is ignored if the filepath or json options are used. This option defaults to "Privileged Resources". + + .INPUTS + System.String + + .OUTPUTS + Microsoft.ActiveDirectory.Management.ADGroup[] + + .EXAMPLE + New-StandardForestGroups -GroupNamePrefix "UG-ESAE" -ResourceOUName "ESAE Privileged Resources" + + Uses the default group definition and creates each group with the UG-ESAE prefix under the User Resources OU in the ESAE Privileged Resources OU at the top level of the domain. + + The results in groups like UG-ESAE-Domain-Admins. + + .EXAMPLE + New-StandardForestGroups + + Uses the default group definition and creates each group with the UG prefix under the User Resources OU in the Privileged Resources OU at the top level of the domain. + + The results in groups like UG-Domain-Admins. + + .NOTES + Refer to the Forest_LocalGroups.json file for appropriate syntax. The domain level should not be specified as part of the path for the group, it is derived programmatically. + #> + [CmdletBinding(DefaultParameterSetName="Default")] + Param( + [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName="Json",Mandatory=$true)] + [System.String]$GroupsJson = [System.String]::Empty, + + [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName="File",Mandatory=$true)] + [System.String]$FilePath, + + [Parameter(Position=1,ParameterSetName="Default")] + [System.String]$GroupNamePrefix = "UG", + + [Parameter(Position=2,ParameterSetName="Default")] + [System.String]$ResourceOUName = "Privileged Resources" + ) + + Begin { + } + + Process { + $TopLevelDomain = Get-ADDomain -Current LocalComputer | Select-Object -ExpandProperty DistinguishedName + + switch ($PSCmdlet.ParameterSetName) + { + "Default" { + $GroupsJson = @" +[ + { + "Name" : "$GroupNamePrefix-Domain-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Domain Admins" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name": "$GroupNamePrefix-GPO-Admins", + "Scope": "Global", + "Category": "Security", + "MemberOf": [ + "Group Policy Creator Owners" + ], + "Path": "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-DNS-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "DnsAdmins" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Enterprise-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Enterprise Admins" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Schema-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Schema Admins" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Server-AD-LocalAdmins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Server Operators" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Server-LocalAdmins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Server Operators" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Workstation-LocalAdmins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [], + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Workstation-AD-LocalAdmins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [], + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Builtin-Administrators", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Administrators" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Account-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Account Operators" + ], + "Path" :"OU=User Groups,OU=User Resources,OU=$ResourceOUName" + } +] +"@ + + break + } + "File" { + $GroupsJson = Get-Content -Path $FilePath -Raw + break + } + "Json" { + break + } + default { + throw "Could not determine parameter set for New-StandardForestGroups." + } + } + + $Groups = ConvertFrom-Json -InputObject $GroupsJson + $NewGroups = @() + + foreach ($Group in $Groups) { + try + { + #Test if group exists first + $Temp = "CN=$($Group.Name),$($Group.Path),$TopLevelDomain" + Write-Log -Message "Creating group $Temp." + + $NewGroup = Get-ADGroup -Filter {DistinguishedName -eq $Temp} + + if ($NewGroup -eq $null -or $NewGroup.Count -eq 0) { + $NewGroup = New-ADGroup -Name $Group.Name -DisplayName $Group.Name -Path "$($Group.Path),$TopLevelDomain" -SamAccountName $Group.Name -GroupCategory $Group.Category -GroupScope $Group.Scope -PassThru + if ($Group.MemberOf -ne $null -and $Group.MemberOf.Length -gt 0) { + foreach($Member in $Group.MemberOf) { + try { + Add-ADGroupMember -Identity $Member -Members $NewGroup + } + catch [Exception] { + Write-Warning ("Error adding $($NewGroup.Name) to $Member : " + $_.Exception.Message) + Write-Log ("Error adding $($NewGroup.Name) to $Member : " + $_.Exception.Message) + } + } + } + } + + $NewGroups += $NewGroup + } + catch [Exception] + { + Write-Warning ("Error creating group : " + $_.Exception.Message) + Write-Log ("Error creating group : " + $_.Exception.Message) + } + } + + Write-Log "Finished creating standard forest groups." + Write-Output -InputObject $NewGroups + } + + End { + } +} + +Function New-ManagementGroups { + <# + .SYNOPSIS + Creates the standard set of groups for an ESAE deployment to manage remote forests. + + .DESCRIPTION + This cmdlet creates "pseudo" groups for the builtin administrative groups in Active Directory for remote forests. These groups can then be added to domain local groups in the remote forest to enable remote + administration from the ESAE forest. + + .PARAMETER GroupsJson + A JSON string that defines the new groups. This defaults to empty and a standard set of groups is created based on the default OU configuration provided by the Set-OUStructure cmdlet. + + .PARAMETER FilePath + The path to the JSON file that defines the new groups. This defaults to empty and a standard set of groups is created based on the default OU configuration provided by the Set-OUStructure cmdlet. + + .PARAMETER GroupNamePrefix + The prefix of each group name if the default groups are used. This defaults to UG, but should be set to something like UG-DomainName to specify the domain the group is intended to manage. + + .PARAMETER ResourceOUName + The name of the OU under the top level domain that the User Resources OU is nested in. This is used with the default set of groups and is ignored if the filepath or json options are used. This option defaults to "Privileged Resources". + + .INPUTS + System.String + + .OUTPUTS + Microsoft.ActiveDirectory.Management.ADGroup[] + + .EXAMPLE + New-ManagementGroups -GroupNamePrefix "UG-Constoso" + + Uses the default group definition and creates each group with the UG-Contoso prefix under the User Resources OU in the Privileged Resources OU at the top level of the domain. + + The results in groups like UG-Contoso-Domain-Admins. + + .NOTES + Refer to the ESAE_RemoteManagementGroups.json file for appropriate syntax. The domain level should not be specified as part of the path for the group, it is derived programmatically. + #> + [CmdletBinding(DefaultParameterSetName="Json")] + Param( + [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName="Json")] + [System.String]$GroupsJson, + + [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName="File",Mandatory=$true)] + [System.String]$FilePath, + + [Parameter(Position=1, ParameterSetName="Default")] + [System.String]$GroupNamePrefix = "UG", + + [Parameter(Position=2, ParameterSetName="Default")] + [System.String]$ResourceOUName = "Privileged Resources" + ) + + Begin { + } + + Process { + $TopLevelDomain = Get-ADDomain -Current LocalComputer | Select-Object -ExpandProperty DistinguishedName + + switch ($PSCmdlet.ParameterSetName) + { + "Json" { + if ($GroupsJson -eq $null -or $GroupsJson -eq [System.String]::Empty) + { + $GroupsJson = @" +[ + { + "Name" : "$GroupNamePrefix-Domain-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Enterprise-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Schema-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Server-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Administrators", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Workstation-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + }, + { + "Name" : "$GroupNamePrefix-Account-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=$ResourceOUName" + } +] +"@ + } + + break + } + "File" { + $GroupsJson = Get-Content -Path $FilePath + break + } + default { + throw "Could not determine parameter set for New-ManagementGroups." + } + } + + $Groups = ConvertFrom-Json -InputObject $GroupsJson + $NewGroups = @() + + foreach ($Group in $Groups) { + try + { + if (!$Group.Path.EndsWith($TopLevelDomain)) { + $Path = ($Group.Path + "," + $TopLevelDomain) + } + else { + $Path = $Group.Path + } + + #TEST if group exits first + $Temp = "CN=$($Group.Name),$Path" + $NewGroup = Get-ADGroup -Filter {DistinguishedName -eq $Temp} + + if ($NewGroup -eq $null) { + $NewGroup = New-ADGroup -Name $Group.Name -DisplayName $Group.Name -Path $Path -SamAccountName $Group.Name -GroupCategory $Group.Category -GroupScope $Group.Scope -PassThru + } + + $NewGroups += $NewGroup + } + catch [Exception] + { + Write-Warning ("Error creating group $($Group.Name) " + $_.Exception.Message) + Write-Log ("Error creating group $($Group.Name) " + $_.Exception.Message) + } + } + + Write-Log "Finished creating Management groups." + Write-Output -InputObject $NewGroups + } + + End { + } +} + +#endregion + +#region Build AD Structure + +Function Set-OUStructure { + <# + .SYNOPSIS + Creates an OU structure in the Active Directory forest. + + .DESCRIPTION + Creates an OU structure in the Active Directory forest. This uses a default configuration if not JSON or file is specified. The structure is separated into two top level OUs, Privileged Resources that contain + all of the users, groups, workstations and servers that are used to run the forest, and an Operations OU that contains all of the users, groups, workstations, and servers that are hosted in the Forest. The Operations + OU is intended for non-privileged users, groups, etc that are tenants or consumers of services in AD, or for an ESAE deployment, only have administrative permissions in remote forests, but not the ESAE forest. + + The OU structure is created in the current domain of the computer that the cmdlet is being run on. + + .PARAMETER OUStructureJson + A JSON string that defines the OU structure to be created. This defaults to empty and uses the default OU structure. + + .PARAMETER FilePath + The path to the JSON file that defines the OU structure to be created. + + .INPUTS + System.String + + .OUTPUTS + Microsoft.ActiveDirectory.Management.ADOrganizationalUnit[] + + .EXAMPLE + Set-OUStructure + + Creates an OU structure in the current domain of the computer using the default setup. + + .NOTES + The user needs Domain Admin permissions to create OUs in the domain. + + Refer to Forest_OU.json for the correct syntax of the JSON structure. + #> + [CmdletBinding(DefaultParameterSetName="Json")] + Param ( + [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName="Json")] + [System.String]$OUStructureJson = [System.String]::Empty, + + [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName="File",Mandatory=$true)] + [ValidateScript({Test-Path -Path $_})] + [System.String]$FilePath + ) + + Begin { + } + + Process { + $TopLevelDomain = Get-ADDomain -Current LocalComputer | Select-Object -ExpandProperty DistinguishedName + + switch($PSCmdlet.ParameterSetName) { + "Json" { + if ([System.String]::IsNullOrEmpty($OUStructureJson)) { + $OUStructureJson = @" +[ + { + "Label" : "Privileged Resources", + "Children" : [ + { + "Label" : "User Resources", + "Children" : [ + { + "Label" : "Users", + "Children" : [] + }, + { + "Label" : "User Groups", + "Children" : [] + }, + { + "Label" : "Workstations", + "Children" : [] + }, + { + "Label" : "Resource Groups", + "Children" : [] + }, + { + "Label" : "Service Accounts", + "Children" : [] + } + ] + }, + { + "Label" : "Server Resources", + "Children" : [ + { + "Label" : "Windows Servers", + "Children" : [] + }, + { + "Label" : "Linux Servers", + "Children" : [] + }, + { + "Label" : "Non-Windows Computer Objects", + "Children" : [] + }, + { + "Label" : "Resource Groups", + "Children" : [] + } + ] + } + ] + }, + { + "Label" : "Operations", + "Children" : [ + { + "Label" : "User Resources", + "Children" : [ + { + "Label" : "Users", + "Children" : [] + }, + { + "Label" : "User Groups", + "Children" : [] + }, + { + "Label" : "Workstations", + "Children" : [] + }, + { + "Label" : "Resource Groups", + "Children" : [] + }, + { + "Label" : "Service Accounts", + "Children" : [] + } + ] + }, + { + "Label" : "Server Resources", + "Children" : [ + { + "Label" : "Windows Servers", + "Children" : [] + }, + { + "Label" : "Linux Servers", + "Children" : [] + }, + { + "Label" : "Non-Windows Computer Objects", + "Children" : [] + }, + { + "Label" : "Resource Groups", + "Children" : [] + } + ] + } + ] + } +] +"@ + } + + break + } + "File" { + $OUStructureJson = Get-Content -Path $FilePath -Raw + break + } + default { + throw "Could not determine parameter set for New-OUStructure" + } + } + + try + { + $OUStructure = ConvertFrom-Json -InputObject $OUStructureJson + } + catch [Exception] + { + throw $_.Exception + } + + $NewOUs = @() + + foreach ($OU in $OUStructure) + { + $NewOUs += New-RecursiveOUStructure -Name $OU.Label -Path $TopLevelDomain -Children $OU.Children + } + + Write-Output -InputObject $NewOUs + } + + End { + } +} + +Function New-RecursiveOUStructure { + <# + .SYNOPSIS + Creates an OU structure recursively. + + .DESCRIPTION + The cmdlet takes a name of a new OU, the path where it should be created, and any child OUs it should have. It creates the OU, and then calls itself for each defined child OU. All of the created OUs are returned + in an array. If the OU happens to already exist, the existing OU is added to the array and the children continue to be processed. + + .PARAMETER Name + The name of the new OU to create. + + .PARAMETER Path + The parent path of where the new OU should be created. This is a Distinguished Name. + + .PARAMETER Children + An array of OU objects to be created under this OU. Each Child object requires a Label property as its name and can have a Children property for additional nested OUs. + + .INPUTS + System.String + + .OUTPUTS + Microsoft.ActiveDirectory.Management.ADOrganizationalUnit[] + + .EXAMPLE + New-RecursiveOUStructure -Name "Privileged Resources" -Path "DC=admin,DC=local" -Children @(@{Label = "User Resources"; Children = @(@{Label = "Users"; Children = @()})}) + + Creates an OU structure in the current domain of the computer. The Privileged Resources OU is created under admin.local. Then the User Resources OU is created under Privileged Resources, and the Users OU is created + under the User Resources OU. + + .NOTES + The user needs Domain Admin permissions to create OUs in the domain. + #> + [CmdletBinding()] + Param( + [Parameter(Position=0,ValueFromPipeline=$true,Mandatory=$true)] + [System.String]$Name, + + [Parameter(Position=1,Mandatory=$true)] + [System.String]$Path, + + [Parameter(Position=2)] + [Object[]]$Children + ) + + Begin { + } + + Process { + $NewOUs = @() + + if (![ADSI]::Exists("LDAP://OU=$Name,$Path")) { + $NewOU = New-ADOrganizationalUnit -Name $Name -Path $Path -ProtectedFromAccidentalDeletion $true -PassThru + $NewOUs += $NewOU + } + else { + $NewOU = Get-ADOrganizationalUnit -Identity ("OU=$Name,$Path") -Properties DistinguishedName + } + + if ($Children -ne $null -and $Children.Length -gt 0) + { + foreach ($Child in $Children) { + $NewOUs += (New-RecursiveOUStructure -Name $Child.Label -Path $NewOU.DistinguishedName -Children $Child.Children) + } + } + + Write-Output -InputObject $NewOUs + } + + End { + } +} + +Function Import-ADUsers { + <# + .SYNOPSIS + Imports new Active Directory users from a JSON file. + + .DESCRIPTION + The cmdlet reads the contents of the JSON file, and creates users based on objects in the file. Any valid user class property can be defined. The path property defaults to the default Users container. If it is + defined, it should not include the top level domain name, this is appended programmatically from the current domain of the computer running the cmdlet. Additionally, group memberships can be specified using the + Membership property. + + .PARAMETER FilePath + The path to the JSON file containing the users definition. + + .PARAMETER Json + The JSON string defining the users to be created. + + .PARAMETER DefaultPassword + Specifies the default password to assign to all the created users that must be changed on first logon. If no password is specified, a strong random password is generated which will need to be manually reset by + an administrator before the user can logon. + + .PARAMETER EnableLogging + Specifies if the logging function should be used when running the function. + + .INPUTS + System.String + + .OUTPUTS + None + + .EXAMPLE + Import-ADUsers -FilePath "$PSScriptRoot\users.json" + + Creates the users contained in the specified json file with a randomly generated password for each user. + + .NOTES + Refer to the included Users.json file for syntax. + #> + [CmdletBinding()] + Param( + [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName="File",Mandatory=$true)] + [ValidateScript({Test-Path -Path $_})] + [System.String]$FilePath, + + [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName="Json",Mandatory=$true)] + [System.String]$Json, + + [Parameter(Position=1)] + [SecureString]$DefaultPassword = $null, + + [Parameter()] + [switch]$EnableLogging + ) + + Begin { + } + + Process { + if ($PSCmdlet.ParameterSetName -eq "File") { + $Json = Get-Content -Path $FilePath -Raw + } + + $Users = ConvertFrom-Json -InputObject $Json + + $Domain = Get-ADDomain -Current LoggedOnUser | Select-Object -Property * + $TopLevel = $Domain.DistinguishedName + $UsersContainer = $Domain.UsersContainer + $Suffix = $Domain.DnsRoot + + [System.String[]]$UserClassProperties = @() + + [System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema]$Schema = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema]::GetCurrentSchema() + [System.DirectoryServices.ActiveDirectory.ActiveDirectorySchemaClass]$UserClass = $Schema.FindClass("User") + $UserClassProperties += $UserClass.MandatoryProperties | Select-Object -ExpandProperty Name + $UserClassProperties += $UserClass.OptionalProperties | Select-Object -ExpandProperty Name + $UserClassProperties = $UserClassProperties.ToLower() + + $NotOtherAttrs = @("SamAccountName","GivenName","sn","DisplayName") + $NotOtherAttrs = $NotOtherAttrs.ToLower() + + foreach ($User in $Users) { + try { + if (![System.String]::IsNullOrEmpty($User.GivenName) -and ![System.String]::IsNullOrEmpty($User.sn)) { + + if ([System.String]::IsNullOrEmpty($User.SamAccountName)) { + $SamAccountName = "$($User.GivenName).$($User.sn)" + } + else { + $SamAccountName = $User.SamAccountName.Trim() + } + + Write-Host "Processing $SamAccountName." + if ($EnableLogging) { Write-Log "Processing $SamAccountName." } + + $SamAccountName = if ($SamAccountName.Length -gt 20) { $SamAccountName.Substring(0,20) } else {$SamAccountName} + + $Counter = 0 + $TempName = $SamAccountName + $NameSuffix = [System.String]::Empty + + while ((Get-ADUser -Filter {samAccountName -eq $TempName}) -ne $null) { + if ($Counter -gt 999) { + Write-Log "SamAccountName suffix extension exceeded 999, cannot create $($User.SamAccountName) automatically." + throw "SamAccountName suffix extension exceeded 999, cannot create $($User.SamAccountName) automatically." + } + + Write-Warning "$TempName already exists." + if ($EnableLogging) { Write-Log "$TempName already exists." } + + $NameSuffix = $Counter.ToString() + + while ($NameSuffix.Length -lt 3) { + $NameSuffix = "0" + $NameSuffix + } + + $TempName = if ($SamAccountName.Length -ge 16) { $SamAccountName.SubString(0,16) + ".$NameSuffix" } else { $SamAccountName + ".$NameSuffix" } + + $Counter++ + } + + Write-Host "Using $TempName for the user." + if ($EnableLogging) { Write-Log "Using $TempName for the user." } + + $SamAccountName = $TempName + + if (![System.String]::IsNullOrEmpty($User.DisplayName)) { + $DisplayName = ($User.DisplayName + " " + $NameSuffix) + } + else { + $DisplayName = "$($User.GivenName) $($User.sn) $NameSuffix" + } + + $OtherAttrs = @{} + + foreach ($Property in $User.psobject.Properties) { + if (!$NotOtherAttrs.Contains($Property.Name.ToLower()) -and $UserClassProperties.Contains($Property.Name.ToLower())) { + if ($Property.GetType() -eq [System.Array]) { + $Value = "@(" + ($Property.Value -join ",") + ")" + } + else { + $Value = $Property.Value.ToString() + } + + $OtherAttrs.Add($Property.Name, $Value) + } + } + + Write-Host "Creating user $SamAccountName" + if ($EnableLogging) { Write-Log "Creating user $SamAccountName" } + + if ($DefaultPassword -eq $null) { + $DefaultPassword = New-RandomPassword -EnforceComplexity -AsSecureString + } + + if ([System.String]::IsNullOrEmpty($User.Path)) { + $Path = $UsersContainer + } + else { + $Path = "$($User.Path),$TopLevel" + } + + try + { + $NewUser = New-ADUser -SamAccountName $SamAccountName ` + -DisplayName $DisplayName.Trim() ` + -GivenName $User.GivenName.Trim() ` + -Surname $User.sn.Trim() ` + -UserPrincipalName "$SamAccountName@$Suffix" ` + -Name $DisplayName.Trim() ` + -Path $Path ` + -AccountPassword $DefaultPassword ` + -PassThru + + if ($NewUser) { + Set-ADUser -Identity $NewUser -Replace $OtherAttrs + } + + foreach ($Group in $User.Membership) { + Write-Host "Checking $Group to add $SamAccountName." + if ($EnableLogging) { Write-Log "Checking $Group to add $SamAccountName." } + + if ((Get-ADGroup -Filter {name -eq $Group} -ErrorAction SilentlyContinue) -ne $null) { + try { + Write-Host "$Group exists, attempting to add $SamAccountName." + if ($EnableLogging) { Write-Log "$Group exists, attempting to add $SamAccountName." } + Add-ADGroupMember -Identity $Group -Members $SamAccountName + Write-Host "Successfully added $SamAccountName to $Group." + if ($EnableLogging) { Write-Log "Successfully added $SamAccountName to $Group." } + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + } + } + else { + Write-Warning "$Group does not exist." + if ($EnableLogging) { Write-Log "$Group does not exist." } + } + } + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + } + } + else { + Write-Warning "The user object must have at least the GivenName and sn attributes defined." + if ($EnableLogging) { Write-Log ("The user object must have at least the GivenName and sn attributes defined.`n" + (ConvertFrom-Json -InputObject $User)) } + } + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + } + } + } + + End { + } +} + +#endregion + +#region GPO Functions + +Function Add-SysvolFiles { + <# + .SYNOPSIS + Extracts the contents of a zip file and places them in the SYSVOL. + + .DESCRIPTION + The cmdlet takes a source zip file and extracts the contents to a destination. The default destination is the SYSVOL as located from Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" | Select-Object -ExpandProperty SysVol. + However, this property is only available on domain controllers, so if the command is run remotely, the SYSVOL destination needs to be explicitly set. + + .PARAMETER ZipFilePath + The path to the source zip file. This defaults to $PSScriptRoot\SysvolFiles.zip. + + .PARAMETER Destination + The location where the zip file should be extracted to. This defaults to the SYSVOL when run on a domain controller. + + .INPUTS + System.String + + .OUTPUTS + None + + .EXAMPLE + Add-SysvolFiles + + Extracts the contents of $PSScriptRoot\SysvolFiles.zip to the SYSVOL directory of the current forest root domain of the computer the cmdlet is being run on. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Position=0,ValueFromPipeline=$true)] + [ValidateScript({Test-Path -Path $_})] + [System.String]$ZipFilePath = $script:SysvolFiles, + + [Parameter(Position=1)] + [System.String]$Destination = [System.String]::Empty + ) + + Begin { + } + + Process { + $ForestRootDomainName = Get-ADForest -Current LocalComputer | Select-Object -ExpandProperty Name + + if ([System.String]::IsNullOrEmpty($Destination)) { + $SYSVOL = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" | Select-Object -ExpandProperty SysVol + $Destination = "$SYSVOL\$ForestRootDomainName\files" + } + + Write-Log "Extracting $ZipFilePath to $Destination" + Extract-ZipFile -Source $ZipFilePath -Destination $Destination + } + + End { + } +} + +Function Set-GPPMigrationValues { + <# + .SYNOPSIS + The cmdlet updates the values in Group Policy Preferences to replace any references of the source domain with the destination domain. + + .DESCRIPTION + The cmdlet examines each GPO backup in the backup directory specified and searches GPPs for references to the source domain. It replaces any found matches with the forest root domain name. + + .PARAMETER BackupRootDirectory + The top level folder containing GPO backups. + + .PARAMETER SourceDomain + The name of the domain where the GPO backups were originated from. This defaults to admin.local. + + .INPUTS + System.String + + .OUTPUTS + None + + .EXAMPLE + Set-GPPMigrationValues -BackupRootDirectory "c:\gpobackups" + + Replaces all references of admin.local in GPO backups located in c:\gpobackups for GPPs. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)] + [ValidateScript({Test-Path -Path $_})] + [System.String]$BackupRootDirectory, + + [Parameter(Position=1)] + [System.String]$SourceDomain = "admin.local" + ) + + Begin { + try { + Import-Module GroupPolicy + } + catch [Exception] { + throw "The GroupPolicy module must be installed to use this cmdlet." + } + } + + Process { + if ([System.String]::IsNullOrEmpty($SourceDomain)) { + $SourceDomain = "admin.local" + } + + $ForestRootDomainName = Get-ADForest -Current LocalComputer | Select-Object -ExpandProperty Name + $Netbios = $ForestRootDomainName.Substring(0, $ForestRootDomainName.IndexOf(".")) + + if ($SourceDomain.Contains(".")) { + $SourceNetbios = $SourceDomain.Substring(0, $SourceDomain.IndexOf(".")) + } + else { + throw "The source domain name was not properly formatted." + } + + $Directories = Get-ChildItem -Path $BackupRootDirectory -Recurse -Directory | Select-Object -ExpandProperty FullName + + foreach ($Directory in $Directories) + { + [Microsoft.GroupPolicy.BackupDirectory]$BackupDirectory = New-Object Microsoft.GroupPolicy.BackupDirectory($Directory, [Microsoft.GroupPolicy.BackupType]::Gpo) + [Microsoft.GroupPolicy.GPSearchCriteria]$SearchCriteria = New-Object Microsoft.GroupPolicy.GPSearchCriteria + $SearchCriteria.Add([Microsoft.GroupPolicy.SearchProperty]::MostRecentBackup, [Microsoft.GroupPolicy.SearchOperator]::Equals, $true) + [Microsoft.GroupPolicy.GpoBackupCollection]$Backups = $BackupDirectory.SearchGpoBackups($SearchCriteria) + + if ($Backups -ne $null -and $Backups.Count -gt 0) + { + foreach($Backup in $Backups) + { + $Items = @() + $MachineGPP = [System.IO.Path]::Combine($Backup.BackupDirectory, "DomainSysvol", "GPO", "Machine", "Preferences") + $UserGPP = [System.IO.Path]::Combine($Backup.BackupDirectory, "DomainSysvol", "GPO", "User", "Preferences") + $Netbios = $ForestRootDomainName.Substring(0, ([System.Math]::Min($ForestRootDomainName.IndexOf("."), 15))) + + if (Test-Path -Path $MachineGPP) { + $Items += Get-ChildItem -Path $MachineGPP -File + } + + if (Test-Path -Path $UserGPP) { + $Items += Get-ChildItem -Path $UserGPP -File + } + + foreach($Item in $Items) { + $StrContent = Get-Content -Path $Item.FullName -Raw + + $StrContent = $StrContent -replace $SourceDomain,$ForestRootDomainName #Replace any reference of the default domain name + $StrContent = $StrContent -replace "\b@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b","@$Netbios" #Replace any UPN type entries + $Strcontent = $StrContent -replace ([System.Text.RegularExpressions.Regex]::Escape("$SourceNetbios\")),("$Netbios\") + + Set-Content -Value $StrContent -Path $Item.FullName + } + } + } + } + } + + End { + } +} + +Function New-GPOMigrationTable { + <# + .SYNOPSIS + The cmdlet creates a GPO migration table to replace any references with the source domain of the GPO backups with the destination domain. + + .DESCRIPTION + The cmdlet creates a GPO migration table to replace any references with the source domain of the GPO backups with the destination domain. + + .PARAMETER BackupRootDirectory + The top level folder containing GPO backups. + + .PARAMETER SourceDomain + The name of the domain where the GPO backups were originated from. This defaults to admin.local. + + .PARAMETER DestinationDomain + The name of the new domain where the GPOs will be imported. This defaults to the forest root domain of the computer running the cmdlet. + + .INPUTS + System.String + + .OUTPUTS + System.String + The migration table path. + + .EXAMPLE + New-GPOMigrationTable -BackupRootDirectory "c:\gpobackups" + + Creates a migration table for all of the gpo backups in the c:\gpobackups folder. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)] + [ValidateScript({Test-Path -Path $_})] + [System.String]$BackupRootDirectory, + + [Parameter(Position=1)] + [System.String]$DestinationDomain = [System.String]::Empty, + + [Parameter(Position=2)] + [ValidateScript({$_.Contains(".")})] + [System.String]$SourceDomain = "admin.local" + ) + + Begin { + try { + Import-Module GroupPolicy + } + catch [Exception] { + throw "The GroupPolicy module must be installed to use this cmdlet." + } + } + + Process { + if ([System.String]::IsNullOrEmpty($DestinationDomain)) { + $ForestRootDomainName = Get-ADForest -Current LocalComputer | Select-Object -ExpandProperty Name + } + else { + $ForestRootDomainName = $DestinationDomain + } + + if ($ForestRootDomainName.Contains(".")) { + $Netbios = $ForestRootDomainName.Substring(0, $ForestRootDomainName.IndexOf(".")) + } + else { + throw "The destination domain name was not properly formatted." + } + + $GPM = New-Object -ComObject GPMgmt.GPM + $Constants = $GPM.GetConstants() + + if ([System.String]::IsNullOrEmpty($SourceDomain)) { + $SourceDomain = "admin.local" + } + + $MigrationTable = New-Object Microsoft.GroupPolicy.GPMigrationTable + $Directories = Get-ChildItem -Path $BackupRootDirectory -Recurse -Directory | Select-Object -ExpandProperty FullName + + foreach ($Directory in $Directories) + { + [Microsoft.GroupPolicy.BackupDirectory]$BackupDirectory = New-Object Microsoft.GroupPolicy.BackupDirectory($Directory, [Microsoft.GroupPolicy.BackupType]::Gpo) + [Microsoft.GroupPolicy.GPSearchCriteria]$SearchCriteria = New-Object Microsoft.GroupPolicy.GPSearchCriteria + $SearchCriteria.Add([Microsoft.GroupPolicy.SearchProperty]::MostRecentBackup, [Microsoft.GroupPolicy.SearchOperator]::Equals, $true) + [Microsoft.GroupPolicy.GpoBackupCollection]$Backups = $BackupDirectory.SearchGpoBackups($SearchCriteria) + + if ($Backups -ne $null -and $Backups.Count -gt 0) + { + foreach($Backup in $Backups) + { + $MigrationTable.Add($Backup, $false) + } + } + } + + foreach($Entry in $MigrationTable.GetEntries()) { + Write-Host $Entry.Source + switch ($Entry.EntryType) { + $Constants.EntryTypeUNCPath { + Write-Host "UNC Path." + if ($Entry.Source -like ".*$SoureDomain.*") { + $MigrationTable.UpdateDestination($Entry.Source, ($Entry.Source -replace $SourceDomain,$ForestRootDomainName)) | Out-Null + + $UpdatedEntry = $MigrationTable.GetEntry($Entry.Source) + Write-Host "Updated UNC Path Entry $($Entry.Source) to $($UpdatedEntry.Destination)" + } + break + } + {$_ -in $Constants.EntryTypeUser, $Constants.EntryTypeGlobalGroup, $Constants.EntryTypeUnknown} { + Write-Host "User, Global Group, or Unknown." + if ($Entry.Source -match "\b@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b") { + $MigrationTable.UpdateDestination($Entry.Source, ($Entry.Source -replace "\b@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b","@$ForestRootDomainName")) | Out-Null + + $UpdatedEntry = $MigrationTable.GetEntry($Entry.Source) + Write-Host "Updated User/Global Group/Unknown Entry $($Entry.Source) to $($UpdatedEntry.Destination)" + } + break + } + {$_ -in $Constants.EntryTypeUniversalGroup} { + Write-Host "Universal Group" + if ($Entry.Source -match "\b@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b") { + $MigrationTable.UpdateDestination($Entry.Source, ($Entry.Source -replace "\b@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b","@$ForestRootDomainName")) | Out-Null + + $UpdatedEntry = $MigrationTable.GetEntry($Entry.Source) + Write-Host "Updated Universal Group Entry $($Entry.Source) to $($UpdatedEntry.Destination)" + } + break + } + } + } + + $MigrationTablePath = [System.IO.Path]::Combine($PSScriptRoot,"MigrationTable.migtable") + $MigrationTable.Save($MigrationTablePath) + + Write-Output -InputObject $MigrationTablePath + } + + End { + } +} + +Function New-GPOLinks { + <# + .SYNOPSIS + The cmdlet creates GPO links for existing GPOs to specified OUs. + + .DESCRIPTION + The cmdlet processes an inputted json file that defines the GPOs and where each one should be linked. If the GPO or destination for the link does not exist it is ignored. + + .PARAMETER LinksFilePath + The path to the json file containing the link information. The OU property of each link object should not include the top level domain, this is generated programmatically. Leave the property blank to link the GPO + to the domain. The GPO property is the name of the GPO, these are matched with an implicit wildcard at the end of the name, so multiple GPOs can be linked with just one definition. + + You must define the GPO and Path. You can optionally define "Enforced" as "Yes" or "No" and optionally define "Order" as an integer value. + + .INPUTS + System.String + + .OUTPUTS + None + + .EXAMPLE + New-GPOLinks -LinksFilePath "$PSScriptRoot\Forest_GPLinks.json" + + Links all of the GPOs defined in the Forest_GPLinks.json file. + + .NOTES + Refer to the Forest_GPLinks.json or ESAE_GPLinks.json file for correct syntax. + #> + [CmdletBinding()] + Param( + [Parameter(Position=0,ValueFromPipeline=$true, Mandatory=$true)] + [ValidateScript({Test-Path -Path $_})] + [System.String]$LinksFilePath + ) + + Begin { + try { + Import-Module GroupPolicy + } + catch [Exception] { + Write-Log "The group policy module is required to run this command." + throw $_.Exception + } + } + + Process { + if (!(Test-Path -Path $LinksFilePath)) { + throw [System.IO.FileNotFoundException]("$LinksFilePath could not be found.") + } + + $Links = ConvertFrom-Json -InputObject (Get-Content -Path $LinksFilePath -Raw) + + $DnsName = Get-ADForest -Current LocalComputer | Select-Object -ExpandProperty Name + $TopLevelDomain = Get-ADDomain -Identity $DnsName | Select-Object -ExpandProperty DistinguishedName + + [Object[]]$AllGPOs = Get-GPO -All + + foreach ($Link in $Links) { + Write-Host "Processing GPOs that match $($Link.GPO)" + Write-Log "Processing GPOs that match $($Link.GPO)" + + $Subset = $AllGPOs | Where-Object {$_.DisplayName -like "$($Link.GPO)*"} + + if($Link.Enforced -eq $null) { + $Enforced = "No" + } + else { + $Enforced = $Link.Enforced + } + + if ([System.String]::IsNullOrEmpty($Link.OU)) { + $Target = $TopLevelDomain + } + else { + $Target = "$($Link.OU),$TopLevelDomain" + } + + if ($Subset.Length -gt 0) { + foreach($Item in $Subset) { + try { + Write-Host "Linking $($Item.DisplayName) to $Target." + Write-Log "Linking $($Item.DisplayName) to $Target." + + if ($Link.Order -ne $null) { + New-GPLink -Guid $Item.Id -Target $Target -LinkEnabled Yes -Enforced $Enforced -Order $Link.Order -Server $env:COMPUTERNAME -Domain $DnsName + } + else { + New-GPLink -Guid $Item.Id -Target $Target -LinkEnabled Yes -Enforced $Enforced -Server $env:COMPUTERNAME -Domain $DnsName + } + } + catch [Exception] { + Write-Warning $_.Exception.Message + Write-Log $_ + } + } + } + else { + Write-Warning "The GPO $($Link.GPO) does not exist in the domain and could not be linked to $($Link.OU)." + Write-Log "The GPO $($Link.GPO) does not exist in the domain and could not be linked to $($Link.OU)." + } + } + + Write-Host "Completed linking GPOs." + Write-Log -Message "Completed linking GPOs." + } + + End { + } +} + +#endregion + +#region Forest Trusts + +Function New-ADForestTrust { + <# + .SYNOPSIS + The cmdlet builds a forest trust between two forests. + + .DESCRIPTION + The cmdlet creates a forest trust between two forests and configures settings associated with the trust. The command will remove existing trust objects with the same name if they are discovered. Because the + Microsoft function for trusts only checks the NetBIOS name when it checks for existence, a trust for another forest could inadvertently be removed. For example, if the local forest is admin.local and the remote + forest has a trust to admin.com, building the trust would fail. + + .PARAMETER LocalForest + The name of the local forest where the cmdlet is being run. This defaults to the forest root domain of the computer running the cmdlet. + + .PARAMETER RemoteForest + The name of the remote forest where the trust will connect. + + .PARAMETER TrustDirection + The direction of the forest trust in relation to the server the cmdlet is being run on. This defaults to Inbound (the remote forest trusts the local forest). This can be Inbound, Outbound, or Bidirectional. + + .PARAMETER LocalForestCredential + The credential to use to setup the local side of the trust. The credential should have Enterprise Admin rights and defaults to the user running the cmdlet. + + .PARAMETER RemoteForestCredential + The credential to use to setup the remote side of the trust. The credential should have Enterprise Admin rights in the remote forest. + + .PARAMETER EnableSelectiveAuthentication + Specify whether selective authentication is enabled or not. This defaults to true. + + .PARAMETER SidFilteringEnabled + Specify whether SID filtering is enabled or not. This defaults to true. + + .PARAMETER CreateLocalConditionalForwarder + Specifies whether to create a DNS conditional forwarder in the local forest in order to resolve the remote forest DNS name. + + .PARAMETER RemoteForestMasterServers + The IP addresses of the remote forest DNS servers that the local conditional forwarder will point to. This is required if the CreateLocalConditionalForwarder is specified. + + .PARAMETER CreateRemoteConditionalForwarder + Specifies whether to create a DNS conditional forwarder in the remote forest in order to resolve the local forest DNS name. + + .PARAMETER TrustingDomainSupportsKerberosAESEncryption + Specifies whether the trusting domain supports Kerberos AES Encryption. This defaults to true. + + .PARAMETER EnableLogging + Specify if the module logging function should be used when running the cmdlet. + + .PARAMETER WaitForRpcSs + Specify whether to wait for the RPCSS service to be available on the local server before creating the DNS settings. The RPCSS service is sometime not available immediately after a reboot when the cmdlet is being + run as a scheduled task and the DNS configuration fails. + + .INPUTS + None + + .OUTPUTS + None + + .EXAMPLE + New-ADForestTrust -RemoteForest "contoso.com" -RemoteForestCredential (Get-Credential) -CreateLocalConditionalForwarder -CreateRemoteConditionalForwarder -RemoteForestMasterServers @("192.168.2.1") -WaitForRpcSs + + Creates conditional forwarders in both the remote and local forest and establishes and inbound forest trust. + + .NOTES + None + #> + [CmdletBinding(DefaultParameterSetName="None")] + Param( + [Parameter(Position=1)] + [System.String]$LocalForest = [System.String]::Empty, + + [Parameter(Position=0, Mandatory=$true)] + [System.String]$RemoteForest, + + [Parameter(Position=2)] + [System.DirectoryServices.ActiveDirectory.TrustDirection]$TrustDirection = [System.DirectoryServices.ActiveDirectory.TrustDirection]::Inbound, + + [Parameter(Position=3)] + [PSCredential]$LocalForestCredential = [System.Management.Automation.PSCredential]::Empty, + + [Parameter(Position=4,Mandatory=$true)] + [ValidateNotNull()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $RemoteForestCredential, + + [Parameter(Position=5)] + [bool]$EnableSelectiveAuthentication = $true, + + [Parameter(Position=6)] + [bool]$SidFilteringEnabled = $true, + + [Parameter(Position=7,ParameterSetName="StubZone")] + [switch]$CreateLocalConditionalForwarder = $false, + + [Parameter(Position=8,ParameterSetName="StubZone",Mandatory=$true)] + [System.String[]]$RemoteForestMasterServers, + + [Parameter(Position=9)] + [switch]$CreateRemoteConditionalForwarder = $false, + + [Parameter(Position=10)] + [bool]$TrustingDomainSupportsKerberosAESEncryption = $true, + + [Parameter(Position=11)] + [switch]$EnableLogging, + + [Parameter(Position=12)] + [switch]$WaitForRpcSs + ) + + Begin { + } + + Process { + if ([System.String]::IsNullOrEmpty($LocalForest)) { + $LocalForest = (Get-ADForest -Current LoggedOnUser).DnsRoot + } + + if ($CreateLocalConditionalForwarder -and $RemoteForestMasterServers.Count -lt 1) { + if ($EnableLogging) { Write-Log "The create local conditional forwarder parameter was specified, but no remote master servers were specified." } + throw "The create local conditional forwarder was specified, but no remote master servers were specified." + } + + if ($LocalForestCredential -ne $null -and $LocalForestCredential -ne [PSCredential]::Empty) { + try + { + [System.DirectoryServices.ActiveDirectory.DirectoryContext]$LocalForestContext = New-Object -TypeName System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Forest, $LocalForest, $LocalForestCredential.UserName, (Convert-SecureStringToString -SecureString $LocalForestCredential.Password)) + [System.DirectoryServices.ActiveDirectory.Forest]$LocalForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($LocalForestContext) + } + catch [Exception] { + Write-Warning $_.Exception + if ($EnableLogging) { Write-Log $_ } + throw $_.Exception + } + } + else { + try { + [System.DirectoryServices.ActiveDirectory.Forest]$LocalForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() + } + catch [Exception] { + Write-Warning $_.Exception + if ($EnableLogging) { Write-Log $_ } + throw $_.Exception + } + } + + $Continue = $true + + if ($CreateLocalConditionalForwarder) { + try { + if ((Test-RpcAvailability -ComputerName $LocalForest.Name -EnableLogging -Wait:$WaitForRpcSs)) { + + Write-Host "RPC available, testing for the stub zone existing already." + if ($EnableLogging) { Write-Log "RPC available, testing for the stub zone existing already." } + + if ((Get-DnsServerZone -Name $RemoteForest -ErrorAction SilentlyContinue) -eq $null) { + Write-Host "Zone does not exist, creating conditional forwarder for remote forest $RemoteForest." + if ($EnableLogging) { Write-Log "Zone does not exist, creating conditional forwarder for remote forest $RemoteForest." } + + try + { + $Counter = 0 + while ($true) { + try { + Add-DnsServerConditionalForwarderZone -Name $RemoteForest -MasterServers $RemoteForestMasterServers -ReplicationScope Forest -ComputerName $LocalForest.Name + Write-Host "Conditional forwarder $RemoteForest successfully created." + if ($EnableLogging) { Write-Log "Conditional forwarder $RemoteForest successfully created." } + break + } + catch [Exception] { + if ($Counter -gt 10) { + throw $_.Exception + } + else { + Write-Warning "Failed to create conditional forwarder on attempt $($Counter + 1)." + Write-Warning "$($_.Exception.Message)" + if ($EnableLogging) { Write-Log "Failed to create conditional forwarder on attempt $($Counter + 1)."} + if ($EnableLogging) { Write-Log $_ } + $Counter++ + Start-Sleep -Seconds 10 + } + } + } + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + } + } + else { + Write-Warning "Zone $RemoteForest already exists." + if ($EnableLogging) { Write-Log "Zone $RemoteForest already exists."} + } + } + else { + if ($EnableLogging) { Write-Log "RpcSs service not available on $($LocalForest.Name)."} + throw "RpcSs service not available on $($LocalForest.Name)." + } + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + $Continue = $false + } + } + + try { + Write-Host "Ensuring the remote forest name provided is actually the forest root." + if ($EnableLogging) { Write-Log "Ensuring the remote forest name provided is actually the forest root." } + $RemoteForest = (Get-ADDomain -Identity $RemoteForest -Credential $RemoteForestCredential).Forest + Write-Host "Forest root is $RemoteForest." + if ($EnableLogging) { Write-Log "Forest root is $RemoteForest." } + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log "Error getting forest root."} + if ($EnableLogging) { Write-Log $_ } + $Continue = $false + } + + if ($CreateRemoteConditionalForwarder -and $Continue) { + try + { + $Servers = Resolve-DnsName -Name "_ldap._tcp.dc._msdcs.$RemoteForest" -Type SRV | Where-Object {$_.Type -eq "SRV"} | Select-Object -ExpandProperty NameTarget + $RemoteServer = [System.String]::Empty + + foreach ($Server in $Servers) { + Write-Host "Testing connection to $Server." + if ($EnableLogging) { Write-Log "Testing connection to $Server." } + if (Test-Connection -ComputerName $Server -Quiet) { + Write-Host "Successfully connected to $Server." + if ($EnableLogging) { Write-Log "Successfully connected to $Server." } + $RemoteServer = $Server + break + } + else { + Write-Warning "Could not connect to $Server." + if ($EnableLogging) { Write-Log "Could not connect to $Server." } + } + } + + if (![System.String]::IsNullOrEmpty($RemoteServer)) { + + Write-Host "Creating conditional forwarder in remote forest on server $RemoteServer." + if ($EnableLogging) { Write-Log "Creating conditional forwarder in remote forest on server $RemoteServer."} + Write-Host "Getting list of master servers in $($LocalForest.Name) to create in remote forest conditional forwarder." + if ($EnableLogging) { Write-Log "Getting list of master servers in $($LocalForest.Name) to create in remote forest conditional forwarder." } + $LocalMasterServers = @() + + Resolve-DnsName -Name $LocalForest.Name -Type NS | Where-Object {![System.String]::IsNullOrEmpty($_.NameHost)} | Select-Object -ExpandProperty NameHost | ForEach-Object { + $LocalMasterServers += Resolve-DnsName -Name $_ -Type A | Select-Object -ExpandProperty IPAddress + } + + Write-Host ("Master Servers: " + ($LocalMasterServers -join " ")) + if ($EnableLogging) { Write-Log ("Master Servers: " + ($LocalMasterServers -join " ")) } + + #Create DNS StubZone for Trusting Forest so it can identify the trusted forest + Write-Host "Creating remote conditional forwarder pointing to the $($LocalForest.Name)." + if ($EnableLogging) { Write-Log "Creating remote conditional forwarder pointing to $($LocalForest.Name)." } + + Write-Host "Connecting to $RemoteServer." + if ($EnableLogging) { Write-Log "Connecting to $RemoteServer." } + + if (Test-Connection -ComputerName $RemoteServer -Quiet) { + + $Session = New-PSSession -ComputerName $RemoteServer -Credential $RemoteForestCredential -ErrorAction Stop + + $Result = Invoke-Command -Session $Session -ScriptBlock { + $Zone = Get-DnsServerZone -Name $args[0] -ErrorAction SilentlyContinue + if ($Zone -eq $null) { + $Zone = Add-DnsServerConditionalForwarderZone -Name $args[0] -MasterServers $args[1] -ReplicationScope Forest -PassThru + + Write-Output $Zone + } + else { + switch ($Zone.Type) { + "Forwarder" { + $Zone = Set-DnsServerConditionalForwarderZone -Name $args[0] -MasterServers $args[1] -PassThru + break + } + "Stub" { + $Zone = Set-DnsServerStubZone -Name $args[0] -MasterServers $args[1] -PassThru + break + } + default { + break + } + } + + Write-Output $Zone + + } + } -ErrorVariable ErrResult -ArgumentList @($LocalForest.Name, $LocalMasterServers) + + Remove-PSSession -Session $Session + + if ($ErrResult -ne $null -and $ErrResult.Count -gt 0) { + $Err = $ErrResult[0] + throw $Err.Exception + } + else { + Write-Host "Successfully created remote forest conditional forwarder." + Write-Host ($Result | Format-List) + if ($EnableLogging) { Write-Log "Successfully created remote forest conditional forwarder."} + if ($EnableLogging) { Write-Log ($Result | Format-List | Out-String) } + } + } + else { + Write-Warning ("Could not connect to $RemoteServer.") + if ($EnableLogging) { Write-Log "Could not connect to $RemoteServer." } + $Continue = $false + } + } + else { + Write-Warning "Could not connect to any servers resolved from DNS." + if ($EnableLogging) { "Could not connect to any servers resolved from DNS." } + $Continue = $false + } + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + $Continue = $false + } + } + + if ($Continue) { + try { + #Now that the local conditional forwarder exists and the DC can resolve the other domain name, build the remote forest context items + + try { + [System.DirectoryServices.ActiveDirectory.DirectoryContext]$RemoteForestContext = New-Object -TypeName System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Forest, $RemoteForest, $RemoteForestCredential.UserName, (Convert-SecureStringToString -SecureString $RemoteForestCredential.Password)) + [System.DirectoryServices.ActiveDirectory.Forest]$RemoteForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($RemoteForestContext) + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + throw $_.Exception + } + + $LocalNetbios = $LocalForest.Name.Substring(0, $LocalForest.Name.IndexOf(".")) + $RemoteNetbios = $RemoteForest.Name.Substring(0, $RemoteForest.Name.IndexOf(".")) + + #Assume trust exists on the far side and prove otherwise + $RemoteExists = $true + if ($EnableLogging) { Write-Log "Testing for remote trust existence."} + + try { + $RemoteTrust = Get-ADForestTrustRelationship -TargetForestName $LocalNetbios -CurrentForest $RemoteForest + if ($RemoteTrust -eq $null) { + $RemoteExists = $false + } + else { + $RemoteExists = $true + } + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + } + + Write-Host "Remote trust exists: $RemoteExists for Netbios $LocalNetbios." + if ($EnableLogging) { Write-Log "Remote trust exists: $RemoteExists for Netbios $LocalNetbios"} + + #Assume the local trust exists and prove otherwise + $LocalExists = $true + if ($EnableLogging) { Write-Log "Testing for local trust existence."} + + try { + $LocalTrust = Get-ADForestTrustRelationship -TargetForestName $RemoteNetbios -CurrentForest $LocalForest + + if ($LocalTrust -eq $null) { + $LocalExists = $false + } + else { + $LocalExists = $true + } + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + } + + Write-Host "Local trust exists: $LocalExists" + if ($EnableLogging) { Write-Log "Local trust exists: $LocalExists"} + + if ($RemoteExists -and $LocalExists) { + Write-Host "Deleting existing trust relationship." + if ($EnableLogging) { Write-Log "Deleting existing trust relationship."} + $Counter = 0 + + while ($true) { + try { + $LocalForest.DeleteTrustRelationship($RemoteForest) + Write-Host "Successfully deleted existing trust relationship." + if ($EnableLogging) { Write-Log "Successfully deleted existing trust relationship."} + break + } + catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + throw $_.Exception + } + catch [Exception] { + if ($Counter -gt 60) { + throw $_.Exception + } + else { + if ($EnableLogging) { Write-Log "Failed to delete the trust on attempt $($Counter + 1)."} + if ($EnableLogging) { Write-Log $_ } + Start-Sleep -Seconds 10 + $Counter++ + } + } + } + } + + if ($RemoteExists -and !$LocalExists) { + Write-Host "Deleting local side of remote forest trust in relation to the remote forest." + if ($EnableLogging) { Write-Log "Deleting local side of remote forest trust in relation to the remote forest."} + $Counter = 0 + + while ($true) { + try { + $RemoteForest.DeleteLocalSideOfTrustRelationship($LocalNetbios) + Write-Host "Successfully deleted local side of remote forest trust." + if ($EnableLogging) { Write-Log "Successfully deleted local side of remote forest trust."} + break + } + catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + throw $_.Exception + } + catch [Exception] { + if ($Counter -gt 60) { + throw $_.Exception + } + else { + if ($EnableLogging) { Write-Log "Failed to delete the trust on attempt $($Counter + 1)."} + if ($EnableLogging) { Write-Log $_ } + Start-Sleep -Seconds 10 + $Counter++ + } + } + } + } + + if (!$RemoteExists -and $LocalExists) { + Write-Host "Deleting local side of local forest trust." + if ($EnableLogging) { Write-Log "Deleting local side of local forest trust."} + $Counter = 0 + + while ($true) { + try { + $LocalForest.DeleteLocalSideOfTrustRelationship($RemoteNetbios) + Write-Host "Successfully deleted local side of local forest trust." + if ($EnableLogging) { Write-Log "Successfully deleted local side of local forest trust."} + break + } + catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + throw $_.Exception + } + catch [Exception] { + if ($Counter -gt 60) { + throw $_.Exception + } + else { + if ($EnableLogging) { Write-Log "Failed to delete the trust on attempt $($Counter + 1)."} + if ($EnableLogging) { Write-Log $_ } + Start-Sleep -Seconds 10 + $Counter++ + } + } + } + } + + Write-Host "Creating trust relationship between $($LocalForest.Name) and $($RemoteForest.Name) in the direction of $($TrustDirection.ToString())." + if ($EnableLogging) { Write-Log "Creating trust relationship between $($LocalForest.Name) and $($RemoteForest.Name) in the direction of $($TrustDirection.ToString())."} + + $LocalForest.CreateTrustRelationship($RemoteForest, $TrustDirection) + + Write-Host "Trust creation completed, verifying trust." + if ($EnableLogging) { Write-Log "Trust creation completed, verifying trust."} + + $LocalForest.VerifyTrustRelationship($RemoteForest, $TrustDirection) + + Write-Host "Trust creation verified." + if ($EnableLogging) { Write-Log "Trust creation verified."} + + try { + if ($EnableLogging) { Write-Log "Setting Forest Trust selective authentication."} + Set-ADForestTrustSelectiveAuthentication -TrustDirection $TrustDirection -LocalForest $LocalForest -RemoteForest $RemoteForest -SelectiveAuthenticationEnabled $true -EnableLogging + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + $Continue = $false + } + + Write-Host "Setting SID Filtering." + if ($EnableLogging) { Write-Log "Setting SID Filtering." } + + switch ($TrustDirection) { + #Inbound trust means that the remote forest trusts the local forest + ([System.DirectoryServices.ActiveDirectory.TrustDirection]::Inbound) { + Write-Host "Inbound trust, setting SID filtering on remote forest." + if ($EnableLogging) { Write-Log "Inbound trust, setting SID filtering on remote forest."} + + Set-ADForestTrustSIDFiltering -TrustingForest $RemoteForest -TrustedForest $LocalForest.Name -SidFilteringEnabled $SidFilteringEnabled -EnableLogging:$EnableLogging + + Write-Host "Completed enabling SID filtering." + if ($EnableLogging) { Write-Log "Completed enabling SID filtering."} + break + } + #Outbound trust means that the local forest trusts the remote forest + ([System.DirectoryServices.ActiveDirectory.TrustDirection]::Outbound) { + Write-Host "Outbound trust, setting SID filtering on local forest." + if ($EnableLogging) { Write-Log "Outboud trust, setting SID filtering on local forest."} + + Set-ADForestTrustSIDFiltering -TrustingForest $LocalForest -TrustedForest $RemoteForest.Name -SidFilteringEnabled $SidFilteringEnabled -EnableLogging:$EnableLogging + + Write-Host "Completed enabling SID filtering." + if ($EnableLogging) { Write-Log "Completed enabling SID filtering."} + break + } + ([System.DirectoryServices.ActiveDirectory.TrustDirection]::Bidirectional) { + Write-Host "Bidirectional trust, setting SID filtering on both forests." + if ($EnableLogging) { Write-Log "Bidirectional trust, setting SID filtering on both forests."} + + Set-ADForestTrustSIDFiltering -TrustingForest $RemoteForest -TrustedForest $LocalForest.Name -SidFilteringEnabled $SidFilteringEnabled -EnableLogging:$EnableLogging + Set-ADForestTrustSIDFiltering -TrustingForest $LocalForest -TrustedForest $RemoteForest.Name -SidFilteringEnabled $SidFilteringEnabled -EnableLogging:$EnableLogging + + Write-Host "Completed enabling SID filtering." + if ($EnableLogging) { Write-Log "Completed enabling SID filtering."} + break + } + default { + if ($EnableLogging) { Write-Log "Could not determine trust direction."} + throw "Could not determine trust direction." + } + } + + if ($EnableLogging) { Write-Log "Remote domain supports Kerberos AES encryption: $TrustingDomainSupportsKerberosAESEncryption"} + + if ($TrustingDomainSupportsKerberosAESEncryption) { + try + { + Write-Host "Setting Kerberos AES support." + if ($EnableLogging) { Write-Log "Setting Kerberos AES support."} + Set-ADForestTrustKerberosSupport -TrustDirection $TrustDirection -RemoteForest $RemoteForest.Name -LocalForest $LocalForest.Name -RemoteCredential $RemoteForestCredential -EnableLogging + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + $Continue = $false + } + } + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + $Continue = $false + } + } + + if (!$Continue) { + $CommandText = Get-CommandText -Command ($PSCmdlet.MyInvocation.MyCommand.Name) -Parameters $PSBoundParameters + Write-Warning "A step failed in creating the trust and its settings. Review the error and try again." + Write-Host "Rerun this command: $CommandText" + + if ($EnableLogging) { Write-Log "A step failed in creating the trust and its settings. Review the error and try again." } + if ($EnableLogging) { Write-Log "Rerun this command: $CommandText" } + } + } + + End { + } +} + +Function Set-ADForestTrustSelectiveAuthentication { + <# + .SYNOPSIS + The cmdlet sets up selective authentication for a forest trust. + + .DESCRIPTION + The cmdlet enables or disables selective authentication for a forest trust between the specified forests. + + .PARAMETER TrustDirection + The direction of the trust that should use selective authentication. + + .PARAMETER LocalForest + The forest object for the local forest. This defaults to the forest of the computer where the cmdlet is being run. + + .PARAMETER RemoteForest + The forest object for the remote forest. + + .PARAMETER LocalForestName + The name of the local forest where the cmdlet is being run. This defaults to the forest root domain of the computer running the cmdlet. + + .PARAMETER RemoteForestName + The name of the remote forest where the trust will connect. + + .PARAMETER LocalForestCredential + The credential to use to setup the local side of the trust. The credential should have Enterprise Admin rights and defaults to the user running the cmdlet. + + .PARAMETER RemoteForestCredential + The credential to use to setup the remote side of the trust. The credential should have Enterprise Admin rights in the remote forest. + + .PARAMETER SelectiveAuthenticationEnabled + Specify whether selective authentication should be enabled or not. This defaults to false. + + .PARAMETER EnableLogging + Specifies whether to use the module logging function when the cmdlet is run. + + .INPUTS + None + + .OUTPUTS + None + + .EXAMPLE + Set-ADForestTrustSelectiveAuthentication -RemoteForestName "contoso.com" -RemoteForestCredential (Get-Credential) -SelectiveAuthenticationEnabled $true -TrustDirection ([System.DirectoryServices.ActiveDirectory.TrustDirection]::Inbound) + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Position=0,Mandatory=$true)] + [System.DirectoryServices.ActiveDirectory.TrustDirection]$TrustDirection, + + [Parameter(Position=1,ParameterSetName="Objects")] + [System.DirectoryServices.ActiveDirectory.Forest]$LocalForest = $null, + + [Parameter(Position=2,ParameterSetName="Objects",Mandatory=$true)] + [System.DirectoryServices.ActiveDirectory.Forest]$RemoteForest, + + [Parameter(Position=1,ParameterSetName="Names")] + [System.String]$LocalForestName = [System.String]::Empty, + + [Parameter(Position=2,ParameterSetName="Names",Mandatory=$true)] + [System.String]$RemoteForestName, + + [Parameter(Position=3,ParameterSetName="Names")] + [PSCredential]$LocalForestCredential = [System.Management.Automation.PSCredential]::Empty, + + [Parameter(Position=4,Mandatory=$true,ParameterSetName="Names")] + [PSCredential]$RemoteForestCredential, + + [Parameter()] + [bool]$SelectiveAuthenticationEnabled = $false, + + [Parameter()] + [switch]$EnableLogging + ) + + Begin { + } + + Process { + switch ($PSCmdlet.ParameterSetName) { + "Objects" { + if ($LocalForest -eq $null) { + $LocalForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() + } + break + } + "Names" { + if ([System.String]::IsNullOrEmpty($LocalForestName)) { + $LocalForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() + } + else { + try { + if ($LocalForestCredential -ne $null -and $LocalForestCredential -ne [PSCredential]::Empty) { + [System.DirectoryServices.ActiveDirectory.DirectoryContext]$LocalForestContext = New-Object -TypeName System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Forest, $LocalForestName, $LocalForestCredential.UserName, (Convert-SecureStringToString -SecureString $LocalForestCredential.Password)) + } + else { + [System.DirectoryServices.ActiveDirectory.DirectoryContext]$LocalForestContext = New-Object -TypeName System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Forest, $LocalForestName) + } + [System.DirectoryServices.ActiveDirectory.Forest]$LocalForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($LocalForestContext) + } + catch [Exception] { + Write-Warning -Message $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + throw $_.Exception + } + } + + try { + [System.DirectoryServices.ActiveDirectory.DirectoryContext]$RemoteForestContext = New-Object -TypeName System.DirectoryServices.ActiveDirectory.DirectoryContext([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Forest, $RemoteForestName, $RemoteForestCredential.UserName, (Convert-SecureStringToString -SecureString $RemoteForestCredential.Password)) + [System.DirectoryServices.ActiveDirectory.Forest]$RemoteForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($RemoteForestContext) + } + catch [Exception] { + Write-Warning -Message $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + throw $_.Exception + } + break + } + default { + throw "Parameter set for Set-ADForestTrustSelectiveAuthentication could not be determined." + } + } + + Write-Host "Setting selective authentication." + if ($EnableLogging) { Write-Log "Setting selective authentication."} + + try { + switch ($TrustDirection) { + #Inbound trust means that the remote forest trusts the local forest + ([System.DirectoryServices.ActiveDirectory.TrustDirection]::Inbound) { + $RemoteForest.SetSelectiveAuthenticationStatus($LocalForest.Name, $SelectiveAuthenticationEnabled) + Write-Host "Completed setting selective authentication." + if ($EnableLogging) { Write-Log "Completed setting selective authentication."} + break + } + #Outbound trust means that the local forest trusts the remote forest + ([System.DirectoryServices.ActiveDirectory.TrustDirection]::Outbound) { + $LocalForest.SetSelectiveAuthenticationStatus($RemoteForest.Name, $SelectiveAuthenticationEnabled) + Write-Host "Completed setting selective authentication." + if ($EnableLogging) { Write-Log "Completed setting selective authentication."} + break + } + ([System.DirectoryServices.ActiveDirectory.TrustDirection]::Bidirectional) { + $LocalForest.SetSelectiveAuthenticationStatus($RemoteForest.Name, $SelectiveAuthenticationEnabled) + $RemoteForest.SetSelectiveAuthenticationStatus($LocalForest.Name, $SelectiveAuthenticationEnabled) + Write-Host "Completed setting selective authentication." + if ($EnableLogging) { Write-Log "Completed setting selective authentication."} + break + } + default { + if ($EnableLogging) { Write-Log "Could not determine trust direction."} + throw "Could not determine trust direction." + } + } + } + catch [Exception] { + if ($EnableLogging) { Write-Log $_ } + throw $_.Exception + } + } + + End { + } +} + +Function Set-ADForestTrustKerberosSupport { + <# + .SYNOPSIS + The cmdlet enables Kerberos AES 128 and 256 encryption in the forest. + + .DESCRIPTION + The cmdlet uses ksetup.exe to enables AES 128 and 256 encryption in the forest specified by the trust direction. An inbound trust enables encryption in the local forest, outbound in the remote forest, and bidirectional + in both forests. + + .PARAMETER TrustDirection + The direction of the forest trust in relation to the server the cmdlet is being run on. This can be Inbound, Outbound, or Bidirectional. + + .PARAMETER LocalForest + The name of the local forest where the cmdlet is being run. This defaults to the forest root domain of the computer running the cmdlet. + + .PARAMETER RemoteForest + The name of the remote forest. + + .PARAMETER RemoteCredential + The credential used to run Invoke-Command on the remote server that is selected via DNS resolution of the domain name. This credential needs access to the remote domain controller and to be able to run ksetup.exe in the remote forest. + + .PARAMETER PassThru + Returns the new or modified object. By default (i.e. if -PassThru is not specified), this cmdlet does not generate any output. + + .PARAMETER EnableLogging + Specifies whether to use the module's logging function when the cmdlet is run. + + .INPUTS + None + + .OUTPUTS + None or Microsoft.ActiveDirectory.Management.ADObject + + .EXAMPLE + Set-ADForestTrustKerberosSupport -RemoteForest "contoso.com" -TrustDirection ([System.DirectoryServices.ActiveDirectory.TrustDirection]::Inbound) -RemoteCredential (Get-Credential) + + Enables AES encryption in the contoso.com domain. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Position=0, Mandatory=$true)] + [System.DirectoryServices.ActiveDirectory.TrustDirection]$TrustDirection, + + [Parameter(Position=1, Mandatory=$true)] + [System.String]$RemoteForest, + + [Parameter(Position=2)] + [System.String]$LocalForest = [System.String]::Empty, + + [Parameter(Position=3,Mandatory=$true)] + [ValidateNotNull()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $RemoteCredential, + + [Parameter()] + [switch]$PassThru, + + [Parameter()] + [switch]$EnableLogging + ) + + Begin { + } + + Process { + if ([System.String]::IsNullOrEmpty($LocalForest)) { + $LocalForest = (Get-ADForest -Current LocalComputer).Name + } + + switch ($TrustDirection) { + #Inbound trust means that the remote forest trusts the local forest + ([System.DirectoryServices.ActiveDirectory.TrustDirection]::Inbound) { + $Process = Start-Process -FilePath "$env:SYSTEMROOT\system32\ksetup.exe" -ArgumentList @("/setenctypeattr",$RemoteForest,"AES128-CTS-HMAC-SHA1-96","AES256-CTS-HMAC-SHA1-96") -NoNewWindow + + Write-Host "Configured Kerberos AES encryption setting." + if ($EnableLogging) { Write-Log "Configured Kerberos AES encryption setting." } + + if ($PassThru) { + Write-Output (Get-ADObject -Filter {(objectClass -eq "trustedDomain") -and (name -eq $RemoteForest)} -Properties "msDS-SupportedEncryptionTypes") + } + + break + } + #Outbound trust means that the local forest trusts the remote forest + ([System.DirectoryServices.ActiveDirectory.TrustDirection]::Outbound) { + $Trust = Invoke-Command -ComputerName $RemoteForest.Name -ScriptBlock { + $Domain = $args[0] + $Proc = Start-Process -FilePath "$env:SYSTEMROOT\system32\ksetup.exe" -ArgumentList @("/setenctypeattr",$Domain,"AES128-CTS-HMAC-SHA1-96","AES256-CTS-HMAC-SHA1-96") -NoNewWindow + Get-ADObject -Filter {(objectClass -eq "trustedDomain") -and (name -eq $Domain)} -Properties "msDS-SupportedEncryptionTypes" + } -ArgumentList ($LocalForest) -Credential $RemoteCredential + + Write-Host "Configured Kerberos AES encryption setting." + if ($EnableLogging) { Write-Log "Configured Kerberos AES encryption setting." } + + if ($PassThru) { + Write-Output $Trust + } + + break + } + ([System.DirectoryServices.ActiveDirectory.TrustDirection]::Bidirectional) { + $Process = Start-Process -FilePath "$env:SYSTEMROOT\system32\ksetup.exe" -ArgumentList @("/setenctypeattr",$RemoteForest,"AES128-CTS-HMAC-SHA1-96","AES256-CTS-HMAC-SHA1-96") -NoNewWindow + $Trust = Invoke-Command -ComputerName $RemoteForest.Name -ScriptBlock { + $Domain = $args[0] + $Proc = Start-Process -FilePath "$env:SYSTEMROOT\system32\ksetup.exe" -ArgumentList @("/setenctypeattr",$Domain,"AES128-CTS-HMAC-SHA1-96","AES256-CTS-HMAC-SHA1-96") -NoNewWindow + Get-ADObject -Filter {(objectClass -eq "trustedDomain") -and (name -eq $Domain)} -Properties "msDS-SupportedEncryptionTypes" + } -ArgumentList ($LocalForest) + + Write-Host "Configured Kerberos AES encryption setting." + if ($EnableLogging) { Write-Log "Configured Kerberos AES encryption setting." } + + if ($PassThru) { + Write-Output (Get-ADObject -Filter {(objectClass -eq "trustedDomain") -and (name -eq $RemoteForest)} -Properties "msDS-SupportedEncryptionTypes") + Write-Output $Trust + } + + break + } + default { + Write-Warning "Could not determine trust direction." + if ($EnableLogging) { Write-Log "Could not determine trust direction."} + } + } + } + + End { + } +} + +Function Get-ADForestTrustRelationship { + <# + .SYNOPSIS + The cmdlet gets the trust relationship information about the local and remote forest. + + .DESCRIPTION + The cmdlet gets the trust relationship information about the local and remote forest. If no trust exists, the cmdlet returns null. + + .PARAMETER TargetForestName + The name of the remote forest to get the trust information about. + + .PARAMETER CurrentForest + The forest object to use to query about the trust information. This defaults to the current forest. + + .INPUTS + System.String + + .OUTPUTS + Null or System.DirectoryServices.ActiveDirectory.ForestTrustRelationshipInformation + + .EXAMPLE + Get-ADForestTrustRelationship -TargetForestName "contoso.com" + + Gets trust relationship information about contoso.com and the local forest. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Position=0,ValueFromPipeline=$true,Mandatory=$true)] + [System.String]$TargetForestName, + + [Parameter(Position=1)] + [System.DirectoryServices.ActiveDirectory.Forest]$CurrentForest = $null + ) + + Begin { + } + + Process { + if ($CurrentForest -eq $null) { + $CurrentForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() + } + + try { + Write-Output -InputObject $CurrentForest.GetTrustRelationship($TargetForestName) + } + catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] { + Write-Warning $_.Exception.Message + Write-Output -InputObject $null + } + } + + End {} +} + +Function Set-ADForestTrustSIDFiltering { + <# + .SYNOPSIS + The cmdlet sets SID filtering between two forests. + + .DESCRIPTION + The cmdlet enables or disables SID filtering between two forests with a forest trust. + + .PARAMETER TrustingForest + The forest object that trusts the other forest. The trust direction would be outbound or bidirectional for this forest. This defaults to the current forest. + + .PARAMETER TrustedForest + The forest that is trusted by the other forest. The trust direction would be inbound or bidirectional for this forest. + + .PARAMETER SidFilteringEnabled + Specify whether SID filtering should be enabled. This defaults to true. + + .PARAMETER EnableLogging + Specify whether the module's logging function should be used when running the cmdlet. + + .INPUTS + System.String + + .OUTPUTS + None + + .EXAMPLE + Set-ADForestTrustSIDFiltering -TrustedForest "contoso.com" + + The local forest, admin.local, that trusts contoso.com, has SID filtering enabled on the trust. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Position=1)] + [System.DirectoryServices.ActiveDirectory.Forest]$TrustingForest = $null, + + [Parameter(Position=0,ValueFromPipeline=$true,Mandatory=$true)] + [System.String]$TrustedForest, + + [Parameter(Position=2)] + [bool]$SidFilteringEnabled = $true, + + [Parameter()] + [switch]$EnableLogging + ) + + Begin { + } + + Process { + if ($TrustingForest -eq $null) { + $TrustingForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest() + } + + try { + Write-Host "Setting SID Filtering for $($TrustingForest.Name) to $SidFilteringEnabled." + if ($EnableLogging) { Write-Log "Setting SID Filtering for $($TrustingForest.Name) to $SidFilteringEnabled." } + + $TrustingForest.SetSidFilteringStatus($TrustedForest, $SidFilteringEnabled) + + Write-Host "Successfully set SID Filtering to $SidFilteringEnabled." + if ($EnableLogging) { Write-Log "Successfully set SID Filtering to $SidFilteringEnabled."} + + } + catch [Exception] { + Write-Warning $_.Exception.Message + if ($EnableLogging) { Write-Log $_ } + } + } + + End { + } +} + +#endregion + +#region Scheduled Task Functions + +Function New-SaveEncryptedPasswordTask { + <# + .SYNOPSIS + The cmdlet runs a scheduled task as a specified account to save a password encrypted with the principal's credentials. + + .DESCRIPTION + The cmdlet runs a scheduled task as a specified account to save a password encrypted with the principal's credentials. The scheduled task is deleted after the task is complete. + + .PARAMETER Password + The password to encrypt and save. + + .PARAMETER FilePath + The location to save the password file. This defaults to "$env:ALLUSERSPROFILE\Microsoft\ADPassword.txt". + + .PARAMETER Principal + The principal that the scheduled task will run as. This defaults to SYSTEM. + + .PARAMETER Timeout + The timeout for the scheduled task. This defaults to 10 minutes. + + .INPUTS + System.Security.SecureString + + .OUTPUTS + System.String + The file path of the encrypted password. + + .EXAMPLE + New-SaveEncryptedPasswordTask + + The cmdlet will prompt the user to enter the password, and then run a scheduled task as SYSTEM to encrypt the password. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Position=0,ValueFromPipeline=$true)] + [SecureString]$Password = $null, + + [Parameter(Position=1)] + [System.String]$FilePath = "$env:ALLUSERSPROFILE\Microsoft\ADPassword.txt", + + [Parameter(Position=2)] + [Microsoft.Management.Infrastructure.CimInstance]$Principal = $null, + + [Parameter(Position=3)] + [System.Timespan]$Timeout = [System.Timespan]::FromSeconds(600) + ) + + Begin { + } + + Process { + if ($FilePath -eq $null -or $FilePath -eq [System.String]::Empty) { + throw [System.ArgumentNullException]("The specified file path was null or empty.") + } + + while($Password -eq $null) { + $Password = Read-Host -AsSecureString -Prompt "Enter the password to encrypt" + } + + $TempPass = Convert-SecureStringToString -SecureString $Password + $Command = "try {`$EncryptedPass = New-EncryptedPassword -Password $TempPass; Set-Content -Path $FilePath -Value `$EncryptedPass -Force } catch [Exception] { Write-Log `$_ }" + $Bytes = [System.Text.Encoding]::Unicode.GetBytes($Command) + $EncodedCommand = [Convert]::ToBase64String($Bytes) + + $TempPass = $null + + Write-Host "Creating scheduled task to save password." + + $STParams = "-NonInteractive -WindowStyle Hidden -NoProfile -NoLogo -EncodedCommand $EncodedCommand" + + $STSource = "$env:SYSTEMROOT\System32\WindowsPowerShell\v1.0\powershell.exe" + $STAction = New-ScheduledTaskAction -Execute $STSource -Argument $STParams + + if ($Principal -eq $null) { + $STPrincipal = New-ScheduledTaskPrincipal -UserId SYSTEM -LogonType ServiceAccount -RunLevel Highest + } + else { + $STPrincipal = $Principal + } + + $STSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -StartWhenAvailable -DontStopIfGoingOnBatteries -DontStopOnIdleEnd + + if ((Get-ScheduledTask -TaskName "SavePassword" -ErrorAction SilentlyContinue) -ne $null) { + Unregister-ScheduledTask -TaskName "SavePassword" -Confirm:$false + } + + Write-Host "Registering scheduled task." + $ScheduledTask = Register-ScheduledTask -TaskName "SavePassword" -Action $STAction -Principal $STPrincipal -Settings $STSettings -ErrorAction Stop + + Write-Host "Executing scheduled task." + Start-ScheduledTask -TaskName "SavePassword" + + Write-Host "Waiting for password file to be created." + $Counter = 0 + while (!(Test-Path -Path $FilePath)) { + Start-Sleep -Seconds 1 + $Counter++ + + if ($Counter -gt $Timeout.TotalSeconds) { + Write-Warning "Password file failed to be created before timeout." + throw "Password file failed to be created before timeout." + } + } + + Write-Host "Password file successfully created." + + if ((Get-ScheduledTask -TaskName "SavePassword" -ErrorAction SilentlyContinue) -ne $null) { + Write-Host "Removing scheduled task." + Unregister-ScheduledTask -TaskName "SavePassword" -Confirm:$false + } + + Write-Output $FilePath + } + + End { + } +} + +Function New-InstallSecureADForestScheduledTask { + <# + .SYNOPSIS + The cmdlet creates a scheduled task to begin the Install-SecureADForest cmdlet after the next reboot. + + .DESCRIPTION + The cmdlet creates a scheduled task to begin the Install-SecureADForest after the next reboot that will run as the specified principal. In order to both read the encrypted password and run the scheduled task with + the appropriate permissions, the password should be encrypted with SYSTEM credentials and the scheduled task should be run as SYSTEM. This is the default configuration. + + .PARAMETER ConfigFilePath + The path to the config file that Install-SecureADForest will use to execute. + + .PARAMETER PasswordFilePath + The path to the password file containing the DSRM safe mode password. + + .PARAMETER Principal + The principal that the scheduled task will run as. This defaults to SYSTEM. + + .INPUTS + None + + .OUTPUTS + Microsoft.Management.Infrastructure.CimInstance#MSFT_ScheduledTask + + .EXAMPLE + New-InstallSecureADForestScheduledTask -ConfigFilePath "$PSScriptRoot\config.json" -PasswordFilePath "$env:ALLUSERSPROFILE\Microsoft\ADPassword.txt" + + Creates the scheduled task to run Install-SecureADForest as SYSTEM. + + .NOTES + None + #> + [CmdletBinding()] + Param ( + [Parameter(Position=0, Mandatory=$true)] + [ValidateScript({Test-Path -Path $_})] + [System.String]$ConfigFilePath, + + [Parameter(Position=1, Mandatory=$true)] + [ValidateScript({Test-Path -Path $_})] + [System.String]$PasswordFilePath, + + [Parameter(Position=2)] + [Microsoft.Management.Infrastructure.CimInstance]$Principal = $null + ) + + Begin { + } + + Process { + if ((Get-ScheduledTask -TaskName $script:InstallSecureADForestTask -ErrorAction SilentlyContinue) -ne $null) { + Unregister-ScheduledTask -TaskName $script:InstallSecureADForestTask -Confirm:$false + } + + $Command = "try {`$SafeModePassword = Get-EncryptedPassword -FilePath `"$PasswordFilePath`"; Remove-Item -Path `"$PasswordFilePath`" -Force -Confirm:`$false; Install-SecureADForest -ConfigFilePath `"$ConfigFilePath`" -SafeModePassword `$SafeModePassword} catch [Exception] {Write-Log `$_}" + $Bytes = [System.Text.Encoding]::Unicode.GetBytes($Command) + $EncodedCommand = [Convert]::ToBase64String($Bytes) + + $STParams = "-NonInteractive -WindowStyle Hidden -NoProfile -NoLogo -EncodedCommand $EncodedCommand" + $STSource = "$env:SYSTEMROOT\System32\WindowsPowerShell\v1.0\powershell.exe" + $STAction = New-ScheduledTaskAction -Execute $STSource -Argument $STParams + + if ($Principal -eq $null) { + $STPrincipal = New-ScheduledTaskPrincipal -UserId SYSTEM -LogonType ServiceAccount -RunLevel Highest + } + else { + $STPrincipal = $Principal + } + + $STSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -StartWhenAvailable -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -MultipleInstances IgnoreNew + $ScheduledTask = Register-ScheduledTask -TaskName $script:InstallSecureADForestTask -Action $STAction -Principal $STPrincipal -Trigger (New-ScheduledTaskTrigger -AtStartup -RandomDelay ([System.Timespan]::FromSeconds(30))) -Settings $STSettings -ErrorAction Stop + + Write-Output -InputObject $ScheduledTask + } + + End { + + } +} + +Function New-FinishSecureADForestInstallationScheduledTask { + <# + .SYNOPSIS + The cmdlet creates a scheduled task to begin the Set-ADForestSecurityConfiguration cmdlet after the next reboot. + + .DESCRIPTION + The cmdlet creates a scheduled task to begin the Set-ADForestSecurityConfiguration cmdlet after the next reboot that will run as the specified principal. + + .PARAMETER ConfigFilePath + The path to the config file that Set-ADForestSecurityConfiguration will use to execute. + + .PARAMETER Principal + The principal that the scheduled task will run as. This defaults to SYSTEM. + + .INPUTS + None + + .OUTPUTS + Microsoft.Management.Infrastructure.CimInstance#MSFT_ScheduledTask + + .EXAMPLE + New-FinishSecureADForestInstallationScheduledTask -ConfigFilePath "$PSScriptRoot\config.json" + + Creates the scheduled task to run Set-ADForestSecurityConfiguration as SYSTEM. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Position=0,ValueFromPipeline=$true,Mandatory=$true)] + [ValidateScript({Test-Path -Path $_})] + [System.String]$ConfigFilePath, + + [Parameter(Position=1)] + [Microsoft.Management.Infrastructure.CimInstance]$Principal = $null + ) + + Begin { + } + + Process { + if ((Get-ScheduledTask -TaskName $script:FinishSecureADForestTask -ErrorAction SilentlyContinue) -ne $null) { + Unregister-ScheduledTask -TaskName $script:FinishSecureADForestTask -Confirm:$false + } + + $Command = "try {Set-ADForestSecurityConfiguration -ConfigFilePath `"$ConfigFilePath`"} catch [Exception] {Write-Log `$_}" + $Bytes = [System.Text.Encoding]::Unicode.GetBytes($Command) + $EncodedCommand = [Convert]::ToBase64String($Bytes) + + $STParams = "-NonInteractive -WindowStyle Hidden -NoProfile -NoLogo -EncodedCommand $EncodedCommand" + $STSource = "$env:SYSTEMROOT\System32\WindowsPowerShell\v1.0\powershell.exe" + $STAction = New-ScheduledTaskAction -Execute $STSource -Argument $STParams + + if ($Principal -eq $null) { + $STPrincipal = New-ScheduledTaskPrincipal -UserId SYSTEM -LogonType ServiceAccount -RunLevel Highest + } + else { + $STPrincipal = $Principal + } + + $STSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -StartWhenAvailable -DontStopIfGoingOnBatteries -DontStopOnIdleEnd -MultipleInstances IgnoreNew + + $ScheduledTask = Register-ScheduledTask -TaskName $script:FinishSecureADForestTask -Action $STAction -Principal $STPrincipal -Trigger (New-ScheduledTaskTrigger -AtStartup -RandomDelay ([System.Timespan]::FromSeconds(30))) -Settings $STSettings -ErrorAction Stop + + Write-Output -InputObject $ScheduledTask + } + + End { + } +} + +#endregion + +#region Utility Functions + +Function Test-RpcAvailability { + <# + .SYNOPSIS + The cmdlet tests whether the RPCSS service is available on a computer. + + .DESCRIPTION + The cmdlet tests whether the RPCSS service is available on a computer by running a Get-Service against the provided computer name. + + .PARAMETER ComputerName + The computer to check the service availability on. + + .PARAMETER EnableLogging + Specify whether to use the module's logging function. + + .PARAMETER Wait + If the initial request returns null, specify whether to wait until the timeout to continue testing for the service's availability. + + .PARAMETER Timeout + If the Wait parameter is used, the timeout as a multiple of 10 seconds to keep trying before failing. This defaults to 180, which is 1800 seconds, or 30 minutes. + + .INPUTS + System.String + + .OUTPUTS + System.Boolean + + .EXAMPLE + Test-RpcAvailability -ComputerName "AdminDC" -Wait + + Tests for the availability of the RPCSS service and waits up to 30 minutes for it to become available. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Position=0,ValueFromPipeline=$true)] + [System.String]$ComputerName = [System.String]::Empty, + + [Parameter()] + [switch]$EnableLogging, + + [Parameter()] + [switch]$Wait, + + [Parameter(Position = 1)] + [System.Int32]$Timeout = 180 + ) + + Begin { + } + + Process { + if ([System.String]::IsNullOrEmpty($ComputerName)) { + $ComputerName = $env:COMPUTERNAME + } + + Write-Host "Testing for RPC availability on $ComputerName." + if ($EnableLogging) { Write-Log "Testing for RPC availability on $ComputerName." } + + $RpcSs = $RpcSs = Get-Service -ComputerName $ComputerName -Name RpcSs -ErrorAction SilentlyContinue + + if ($RpcSs -eq $null) { + if ($Wait) { + $Counter = 0 + + while ($RpcSs -eq $null) { + if ($Counter -gt $Timeout) { + if ($EnableLogging) { Write-Log "Timeout waiting for RpcSs service to be loaded. Could not create local stub zone." } + Write-Output $false + } + + Start-Sleep -Seconds 10 + $Counter++ + try { + $RpcSs = Get-Service -ComputerName $ComputerName -Name RpcSs -ErrorAction SilentlyContinue + + Write-Host ("Current RpcSs status: RpcSs = null : " + ($RpcSs -eq $null)) + if ($EnableLogging) { Write-Log ("Current RpcSs status: RpcSs = null : " + ($RpcSs -eq $null)) } + } + catch [Exception] { + if ($EnableLogging) { Write-Log ("Iteration $Counter " + $_.Exception.Message) } + } + } + } + else { + if ($EnableLogging) { Write-Log "RpcSs service is not available." } + Write-Output $false + } + } + + if ($EnableLogging) { Write-Log ("RpcSs service found: " + ($RpcSs -ne $null) + ".")} + if ($EnableLogging) { Write-Log "Connected to $($RpcSs.MachineName)." } + + $ComputerName = $RpcSs.MachineName + + $RpcEptMapper = Get-Service -ComputerName $ComputerName -Name RpcEptMapper + + if ($EnableLogging) { Write-Log "Current RpcSs status: $($RpcSs.Status)." } + if ($EnableLogging) { Write-Log "Current RpcEptMapper status: $($RpcEptMapper.Status)." } + + + if ($RpcSs.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Running -and $RpcEptMapper.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Running) { + if ($Wait) { + $Counter = 0 + + while ($RpcSs.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Running -and $RpcEptMapper.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Running) { + if ($Counter -gt 180) { + if ($EnableLogging) { Write-Log "Timeout waiting for RpcSs and RpcEptMapper to start. Could not create local stub zone." } + Write-Output $false + } + + Start-Sleep -Seconds 10 + $Counter++ + $RpcSs = Get-Service -ComputerName $ComputerName -Name RpcSs + $RpcEptMapper = Get-Service -ComputerName $ComputerName -Name RpcEptMapper + + Write-Host "Current RpcSs status: $($RpcSs.Status)." + Write-Host "Current RpcEptMapper status: $($RpcEptMapper.Status)." + if ($EnableLogging) { Write-Log "Current RpcSs status: $($RpcSs.Status)." } + if ($EnableLogging) { Write-Log "Current RpcEptMapper status: $($RpcEptMapper.Status)." } + } + } + else { + if ($EnableLogging) { Write-Log "The RpcSs and RpcEptMapper services are not running." } + Write-Output $false + } + } + + Write-Host "Rpc services are running, testing WMI connection via RPC." + if ($EnableLogging) { Write-Log "Rpc services are running, testing WMI connection via RPC." } + + $Comp = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $ComputerName -ErrorAction SilentlyContinue + + if ($Comp -ne $null) { + if ($EnableLogging) { Write-Log "Successfully connected to WMI via RPC."} + Write-Output $true + } + else { + if ($EnableLogging) { Write-Log "Could not connect to WMI via RPC, cannot create Stub Zone." } + Write-Output $false + } + } + + End { + } +} + +Function New-ConfigurationFile { + <# + .SYNOPSIS + The cmdlet writes creates a new configuration json file from the inputted parameters of a cmdlet. + + .DESCRIPTION + The cmdlet writes creates a new configuration json file from the inputted parameters of a cmdlet. + + .PARAMETER ParameterList + The list of parameters to create the configuration file with. + + .PARAMETER SourceFilePath + The path to the existing configuration file. + + .PARAMETER Destination + The path to where the resulting configuration file is written. + + .INPUTS + None + + .OUTPUTS + System.Collections.Hashtable + An object that contains the path to the configuration file and a JSON string of the content. + + .EXAMPLE + $ParameterList = @{} + $ParameterList.Add("Name", "John Smith") + $Info = New-ConfigurationFile -ParameterList $ParameterList -Destination "c:\config.json" + + Creates a new configuration file at c:\config.json and returns an object with the file path and configuration content. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true,ParameterSetName="Parameters",Position=0, ValueFromPipeline = $true)] + [System.Collections.Hashtable]$ParameterList, + + [Parameter(Mandatory=$true,ParameterSetName="File",Position=0, ValueFromPipeline = $true)] + [ValidateScript({Test-Path -Path $_})] + [System.String]$SourceFilePath, + + [Parameter(Position=1)] + [System.String]$Destination = [System.String]::Empty + ) + + Begin { + } + + Process { + $Values = @() + + if ([System.String]::IsNullOrEmpty($Destination)) { + $Destination = [System.IO.Path]::Combine($PSScriptRoot, "config.json") + } + + switch ($PSCmdlet.ParameterSetName) { + "Parameters" { + $Json = ConvertTo-Json -InputObject $ParameterList + + Set-Content -Value $Json -Path $Destination + break + } + "File" { + Copy-Item -Path $SourceFilePath -Destination $Destination + + $Counter = 0 + while (!(Test-Path -Path $Destination)) { + Start-Sleep -Seconds 1 + $Counter++ + + if ($Counter -gt 120) { + throw "Timeout waiting for config file to be copied." + } + } + $Json = Get-Content -Path $Destination -Raw + break + } + } + + Write-Output -InputObject @{"FilePath"=$Destination;"Content"=$Json} + } + + End { + } +} + +Function Get-CommandText { + <# + .SYNOPSIS + The cmdlet generates a string of the parameters and values that were used to run a cmdlet. + + .DESCRIPTION + The cmdlet generates a string of the parameters and values that were used to run a cmdlet. + + .PARAMETER Command + The command that was run. + + .PARAMETER Parameters + The parameters that were specified when running the command. + + .INPUTS + System.String + + .OUTPUTS + System.String + + .EXAMPLE + Get-CommandText -Command "Get-Service" + + Recreates the string "Get-Service" + + .EXAMPLE + Get-CommandText -Command "My-Function" -Parametets $PSBoundParameters + + When utilized inside the function "My-Function", it prints the command and parameters used to execute it. + + .NOTES + None + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)] + [System.String]$Command, + + [Parameter(Position=1)] + [System.Object]$Parameters = $null + ) + + Begin { + + } + + Process { + + if ($Parameters -ne $null) { + $Parameters.GetEnumerator() | ForEach-Object { + $Param = $_.Key + $Value = $_.Value + + if ($Value.GetType() -eq [System.String]) { + $Command += " -$Param `"$Value`"" + } + else { + if ($Value.GetType() -eq [System.Boolean]) { + $Command += " -$Param `$$($Value.ToString().ToLower())" + } + else { + $Command += " -$Param $Value" + } + } + } + } + + Write-Output -InputObject $Command + } + + End { + } +} + +#endregion + +$script:TrustAttributes = @( + [PSCustomObject]@{Key="0x00000001";Value="TRUST_ATTRIBUTE_NON_TRANSITIVE"}, + [PSCustomObject]@{Key="0x00000002";Value="TRUST_ATTRIBUTE_UPLEVEL_ONLY"}, + [PSCustomObject]@{Key="0x00000004";Value="TRUST_ATTRIBUTE_QUARANTINED_DOMAIN"}, + [PSCustomObject]@{Key="0x00000008";Value="TRUST_ATTRIBUTE_FOREST_TRANSITIVE"}, + [PSCustomObject]@{Key="0x00000010";Value="TRUST_ATTRIBUTE_CROSS_ORGANIZATION"} + [PSCustomObject]@{Key="0x00000020";Value="TRUST_ATTRIBUTE_WITHIN_FOREST"}, + [PSCustomObject]@{Key="0x00000040";Value="TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL"}, + [PSCustomObject]@{Key="0x00000080";Value="TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION"}, + [PSCustomObject]@{Key="0x00000200";Value="TRUST_ATTRIBUTE_CROSS_ORGANIZATION_NO_TGT_DELEGATION"}, + [PSCustomObject]@{Key="0x00000400";Value="TRUST_ATTRIBUTE_PIM_TRUST"} + ) \ No newline at end of file diff --git a/ESAE/src/ESAE_AdditionalGPLinks.json b/ESAE/src/ESAE_AdditionalGPLinks.json new file mode 100644 index 0000000..7582d18 --- /dev/null +++ b/ESAE/src/ESAE_AdditionalGPLinks.json @@ -0,0 +1,68 @@ +[ + { + "GPO": "VDI - View Agent Configuration", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "VDI - View Agent Configuration", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "GPO": "VDI - View UI Optimization", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "VDI - View UI Optimization", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "GPO": "VDI - USB Device Filtering", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "VDI - USB Device Filtering", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "GPO": "VDI - USB Storage Allow Override", + "Enforced": "No", + "OU" : "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "STIG EXEMPTION - VDI", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "STIG EXEMPTION - VDI", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "GPO": "VDI - UseProfilePathExtensionVersion", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "VDI - UseProfilePathExtensionVersion", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "GPO": "Enterprise - Background Settings", + "Enforced": "No", + "OU": "" + }, + { + "GPO": "STIG EXEMPTION - Disable FIPS Enforcement", + "Enforced": "No", + "Order": "1", + "OU": "OU=Windows Servers,OU=Server Resources,OU=ESAE Privileged Resources" + } +] \ No newline at end of file diff --git a/ESAE/src/ESAE_AdditionalGPOBundle.zip b/ESAE/src/ESAE_AdditionalGPOBundle.zip new file mode 100644 index 0000000..c2bb88e Binary files /dev/null and b/ESAE/src/ESAE_AdditionalGPOBundle.zip differ diff --git a/ESAE/src/ESAE_GPLinks.json b/ESAE/src/ESAE_GPLinks.json new file mode 100644 index 0000000..81382cd --- /dev/null +++ b/ESAE/src/ESAE_GPLinks.json @@ -0,0 +1,98 @@ +[ + { + "GPO": "STIG - Windows Server 2012 R2 Base", + "Enforced": "No", + "OU": "OU=Windows Servers,OU=Server Resources,OU=ESAE Privileged Resources" + }, + { + "GPO": "STIG - Windows Server 2012 R2 Base", + "Enforced": "No", + "OU": "OU=Domain Controllers" + }, + { + "GPO": "STIG - Windows Server 2012 R2 Member Server", + "Enforced": "No", + "OU": "OU=Windows Servers,OU=Server Resources,OU=ESAE Privileged Resources" + }, + { + "GPO" : "STIG - Windows Server 2012 R2 AD Management", + "Enforced" : "No", + "OU" : "OU=Windows Servers,OU=Server Resources,OU=ESAE Privileged Resources" + }, + { + "GPO" : "STIG - Windows Server 2012 R2 Domain Controller", + "Enforced" : "No", + "OU" : "OU=Domain Controllers" + }, + { + "GPO": "STIG - Windows 8.1 Base", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "GPO": "STIG - Windows 8.1 Base", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO" : "STIG - Windows 8.1 AD Management", + "Enforced" : "No", + "OU" : "OU=Workstations,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "GPO" : "STIG - Microsoft Office", + "Enforced" : "No", + "OU" : "" + }, + { + "GPO" : "STIG - JRE", + "Enforced" : "No", + "OU" : "" + }, + { + "GPO" : "STIG - Google Chrome", + "Enforced" : "No", + "OU" : "" + }, + { + "GPO" : "STIG - Mozilla Firefox", + "Enforced" : "No", + "OU" : "" + }, + { + "GPO" : "STIG - Internet Explorer", + "Enforced" : "No", + "OU" : "" + }, + { + "GPO" : "Admin -", + "Enforced" : "No", + "OU" : "" + }, + { + "GPO" : "LocalAdmins - Servers AD Management", + "Enforced" : "No", + "OU" : "OU=Windows Servers,OU=Server Resources,OU=ESAE Privileged Resources" + }, + { + "GPO": "LocalAdmins - Servers Non AD Management", + "Enforced": "No", + "OU": "OU=Windows Servers,OU=Server Resources,OU=ESAE Privileged Resources" + }, + { + "GPO" : "LocalAdmins - Workstations AD Management", + "Enforced" : "No", + "OU" : "OU=Workstations,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "GPO": "LocalAdmins - Workstations Non AD Management", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "GPO": "STIG EXEMPTION - Disable RPC Client Auth", + "Enforced": "No", + "Order": "1", + "OU": "OU=Domain Controllers" + } +] \ No newline at end of file diff --git a/ESAE/src/ESAE_LocalGroups.json b/ESAE/src/ESAE_LocalGroups.json new file mode 100644 index 0000000..76f7611 --- /dev/null +++ b/ESAE/src/ESAE_LocalGroups.json @@ -0,0 +1,74 @@ +[ + { + "Name": "UG-ESAE-Domain-Admins", + "Scope": "Global", + "Category": "Security", + "MemberOf": [ + "Domain Admins" + ], + "Path": "OU=User Groups,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "Name": "UG-ESAE-GPO-Admins", + "Scope": "Global", + "Category": "Security", + "MemberOf": [ + "Group Policy Creator Owners" + ], + "Path": "OU=User Groups,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "Name" : "UG-ESAE-DNS-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "DnsAdmins" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "Name" : "UG-ESAE-Enterprise-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Enterprise Admins" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "Name" : "UG-ESAE-Schema-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Schema Admins" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "Name" : "UG-ESAE-Server-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Server Operators" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "Name": "UG-ESAE-Administrators", + "Scope": "Global", + "Category": "Security", + "MemberOf": [ + "Administrators" + ], + "Path": "OU=User Groups,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "Name" : "UG-ESAE-Account-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Account Operators" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=ESAE Privileged Resources" + } +] \ No newline at end of file diff --git a/ESAE/src/ESAE_OU.json b/ESAE/src/ESAE_OU.json new file mode 100644 index 0000000..609ae06 --- /dev/null +++ b/ESAE/src/ESAE_OU.json @@ -0,0 +1,83 @@ +[ + { + "Label" : "ESAE Privileged Resources", + "Children" : [ + { + "Label" : "User Resources", + "Children" : [ + { + "Label" : "Users", + "Children" : [] + }, + { + "Label" : "User Groups", + "Children" : [] + }, + { + "Label" : "Workstations", + "Children" : [] + }, + { + "Label": "Resource Groups", + "Children": [ ] + }, + { + "Label" : "Service Accounts", + "Children" : [] + } + ] + }, + { + "Label" : "Server Resources", + "Children" : [ + { + "Label" : "Windows Servers", + "Children" : [] + }, + { + "Label" : "Linux Servers", + "Children" : [] + }, + { + "Label" : "Non-Windows Computer Objects", + "Children" : [] + }, + { + "Label" : "Resource Groups", + "Children" : [] + } + ] + } + ] + }, + { + "Label": "Privileged Resources", + "Children": [ + { + "Label": "User Resources", + "Children": [ + { + "Label": "Users", + "Children": [ ] + }, + { + "Label": "User Groups", + "Children": [ ] + }, + { + "Label": "Workstations", + "Children": [ ] + }, + { + "Label": "Resource Groups", + "Children": [ ] + }, + { + "Label" : "Service Accounts", + "Children" : [] + } + ] + } + ] + } +] \ No newline at end of file diff --git a/ESAE/src/ESAE_RemoteManagementGroups.json b/ESAE/src/ESAE_RemoteManagementGroups.json new file mode 100644 index 0000000..2eb54d8 --- /dev/null +++ b/ESAE/src/ESAE_RemoteManagementGroups.json @@ -0,0 +1,56 @@ +[ + { + "Name" : "UG-Domain-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name": "UG-Enterprise-Admins", + "Scope": "Global", + "Category": "Security", + "Path": "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name": "UG-GPO-Admins", + "Scope": "Global", + "Category": "Security", + "Path": "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name" : "UG-DNS-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name" : "UG-Schema-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name" : "UG-Server-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name" : "UG-Builtin-Administrators", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name" : "UG-Workstation-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name" : "UG-Account-Admins", + "Scope" : "Global", + "Category" : "Security", + "Path" : "OU=User Groups,OU=User Resources,OU=Privileged Resources" + } +] \ No newline at end of file diff --git a/ESAE/src/ESAE_SysvolFiles.zip b/ESAE/src/ESAE_SysvolFiles.zip new file mode 100644 index 0000000..c5165e2 Binary files /dev/null and b/ESAE/src/ESAE_SysvolFiles.zip differ diff --git a/ESAE/src/Forest_AdditionalGPLinks.json b/ESAE/src/Forest_AdditionalGPLinks.json new file mode 100644 index 0000000..57be087 --- /dev/null +++ b/ESAE/src/Forest_AdditionalGPLinks.json @@ -0,0 +1,74 @@ +[ + { + "GPO": "VDI - View Agent Configuration", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "VDI - View Agent Configuration", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Operations" + }, + { + "GPO": "VDI - View UI Optimization", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "VDI - View UI Optimization", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Operations" + }, + { + "GPO": "VDI - USB Device Filtering", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "VDI - USB Device Filtering", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Operations" + }, + { + "GPO": "VDI - USB Storage Allow Override", + "Enforced": "No", + "OU" : "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "STIG EXEMPTION - VDI", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "STIG EXEMPTION - VDI", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Operations" + }, + { + "GPO": "VDI - UseProfilePathExtensionVersion", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "VDI - UseProfilePathExtensionVersion", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Operations" + }, + { + "GPO": "Enterprise - Background Settings", + "Enforced": "No", + "OU": "" + }, + { + "GPO": "STIG EXEMPTION - Member Server Allow Domain Admin Logon", + "Enforced": "No", + "Order": "1", + "OU": "OU=Windows Servers,OU=Server Resources,OU=Privileged Resources" + }, + { + "GPO": "STIG EXEMPTION - Disable FIPS Enforcement", + "Enforced": "No", + "Order": "1", + "OU": "OU=Windows Servers,OU=Server Resources,OU=Privileged Resources" + } +] \ No newline at end of file diff --git a/ESAE/src/Forest_AdditionalGPOBundle.zip b/ESAE/src/Forest_AdditionalGPOBundle.zip new file mode 100644 index 0000000..9244e1f Binary files /dev/null and b/ESAE/src/Forest_AdditionalGPOBundle.zip differ diff --git a/ESAE/src/Forest_GPLinks.json b/ESAE/src/Forest_GPLinks.json new file mode 100644 index 0000000..e8f4a05 --- /dev/null +++ b/ESAE/src/Forest_GPLinks.json @@ -0,0 +1,118 @@ +[ + { + "GPO": "STIG - Windows Server 2012 R2 Base", + "Enforced": "No", + "OU": "OU=Windows Servers,OU=Server Resources,OU=Privileged Resources" + }, + { + "GPO": "STIG - Windows Server 2012 R2 Base", + "Enforced": "No", + "OU": "OU=Domain Controllers" + }, + { + "GPO" : "STIG - Windows Server 2012 R2 Base", + "Enforced" : "No", + "OU" : "OU=Windows Servers,OU=Server Resources,OU=Operations" + }, + { + "GPO": "STIG - Windows Server 2012 R2 Member Server", + "Enforced": "No", + "OU": "OU=Windows Servers,OU=Server Resources,OU=Privileged Resources" + }, + { + "GPO" : "STIG - Windows Server 2012 R2 Member Server", + "Enforced" : "No", + "OU" : "OU=Windows Servers,OU=Server Resources,OU=Operations" + }, + { + "GPO" : "STIG - Windows Server 2012 R2 AD Management", + "Enforced" : "No", + "OU" : "OU=Windows Servers,OU=Server Resources,OU=Privileged Resources" + }, + { + "GPO" : "STIG - Windows Server 2012 R2 Domain Controller", + "Enforced" : "No", + "OU" : "OU=Domain Controllers" + }, + { + "GPO": "STIG - Windows 8.1 Base", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO" : "STIG - Windows 8.1 Base", + "Enforced" : "No", + "OU": "OU=Workstations,OU=User Resources,OU=Operations" + }, + { + "GPO" : "STIG - Windows 8.1 AD Management", + "Enforced" : "No", + "OU" : "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO" : "STIG - Microsoft Office", + "Enforced" : "No", + "OU" : "" + }, + { + "GPO" : "STIG - JRE", + "Enforced" : "No", + "OU" : "" + }, + { + "GPO" : "STIG - Google Chrome", + "Enforced" : "No", + "OU" : "" + }, + { + "GPO" : "STIG - Mozilla Firefox", + "Enforced" : "No", + "OU" : "" + }, + { + "GPO" : "STIG - Internet Explorer", + "Enforced" : "No", + "OU" : "" + }, + { + "GPO" : "Admin -", + "Enforced" : "No", + "OU" : "" + }, + { + "GPO" : "LocalAdmins - Servers AD Management", + "Enforced" : "No", + "OU" : "OU=Windows Servers,OU=Server Resources,OU=Privileged Resources" + }, + { + "GPO": "LocalAdmins - Servers Non AD Management", + "Enforced": "No", + "OU": "OU=Windows Servers,OU=Server Resources,OU=Privileged Resources" + }, + { + "GPO" : "LocalAdmins - Servers Non AD Management", + "Enforced" : "No", + "OU" : "OU=Windows Servers,OU=Server Resources,OU=Operations" + }, + { + "GPO" : "LocalAdmins - Workstations AD Management", + "Enforced" : "No", + "OU" : "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "LocalAdmins - Workstations Non AD Management", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Privileged Resources" + }, + { + "GPO": "LocalAdmins - Workstations Non AD Management", + "Enforced": "No", + "OU": "OU=Workstations,OU=User Resources,OU=Operations" + }, + { + "GPO": "STIG EXEMPTION - Disable RPC Client Auth", + "Enforced": "No", + "Order": "1", + "OU": "OU=Domain Controllers" + } +] \ No newline at end of file diff --git a/ESAE/src/Forest_LocalGroups.json b/ESAE/src/Forest_LocalGroups.json new file mode 100644 index 0000000..a05fdd9 --- /dev/null +++ b/ESAE/src/Forest_LocalGroups.json @@ -0,0 +1,74 @@ +[ + { + "Name": "UG-Domain-Admins", + "Scope": "Global", + "Category": "Security", + "MemberOf": [ + "Domain Admins" + ], + "Path": "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name": "UG-GPO-Admins", + "Scope": "Global", + "Category": "Security", + "MemberOf": [ + "Group Policy Creator Owners" + ], + "Path": "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name" : "UG-DNS-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "DnsAdmins" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name" : "UG-Enterprise-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Enterprise Admins" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name" : "UG-Schema-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Schema Admins" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name" : "UG-Server-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Server Operators" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name": "UG-Builtin-Administrators", + "Scope": "Global", + "Category": "Security", + "MemberOf": [ + "Administrators" + ], + "Path": "OU=User Groups,OU=User Resources,OU=Privileged Resources" + }, + { + "Name" : "UG-Account-Admins", + "Scope" : "Global", + "Category" : "Security", + "MemberOf" : [ + "Account Operators" + ], + "Path" : "OU=User Groups,OU=User Resources,OU=Privileged Resources" + } +] \ No newline at end of file diff --git a/ESAE/src/Forest_OU.json b/ESAE/src/Forest_OU.json new file mode 100644 index 0000000..7507f82 --- /dev/null +++ b/ESAE/src/Forest_OU.json @@ -0,0 +1,104 @@ +[ + { + "Label" : "Privileged Resources", + "Children" : [ + { + "Label" : "User Resources", + "Children" : [ + { + "Label" : "Users", + "Children" : [] + }, + { + "Label" : "User Groups", + "Children" : [] + }, + { + "Label" : "Workstations", + "Children" : [] + }, + { + "Label": "Resource Groups", + "Children": [ ] + }, + { + "Label": "Service Accounts", + "Children": [ ] + } + ] + }, + { + "Label" : "Server Resources", + "Children" : [ + { + "Label" : "Windows Servers", + "Children" : [] + }, + { + "Label" : "Linux Servers", + "Children" : [] + }, + { + "Label" : "Non-Windows Computer Objects", + "Children" : [] + }, + { + "Label" : "Resource Groups", + "Children" : [] + } + ] + } + ] + }, + { + "Label" : "Operations", + "Children" : [ + { + "Label" : "User Resources", + "Children" : [ + { + "Label" : "Users", + "Children" : [] + }, + { + "Label" : "User Groups", + "Children" : [] + }, + { + "Label" : "Workstations", + "Children" : [] + }, + { + "Label": "Resource Groups", + "Children": [ ] + }, + { + "Label": "Service Accounts", + "Children": [ ] + } + ] + }, + { + "Label" : "Server Resources", + "Children" : [ + { + "Label" : "Windows Servers", + "Children" : [] + }, + { + "Label" : "Linux Servers", + "Children" : [] + }, + { + "Label" : "Non-Windows Computer Objects", + "Children" : [] + }, + { + "Label" : "Resource Groups", + "Children" : [] + } + ] + } + ] + } +] \ No newline at end of file diff --git a/ESAE/src/SysvolFiles.zip b/ESAE/src/SysvolFiles.zip new file mode 100644 index 0000000..3765428 Binary files /dev/null and b/ESAE/src/SysvolFiles.zip differ diff --git a/ESAE/src/Users.json b/ESAE/src/Users.json new file mode 100644 index 0000000..2d3cf48 --- /dev/null +++ b/ESAE/src/Users.json @@ -0,0 +1,27 @@ +[ + { + "GivenName" : "Michael", + "sn" : "Haken", + "DisplayName" : "Michael Haken ESAE Domain Admin", + "SamAccountName" : "hakenmt.esae.da", + "Membership" : [ + "UG-ESAE-Domain-Admins" + ], + "UserAccountControl" : 262656, + "msDS-SupportedEncryptionTypes" : 24, + "Path" : "OU=Users,OU=User Resources,OU=ESAE Privileged Resources" + }, + { + "GivenName" : "Michael", + "sn" : "Haken", + "DisplayName" : "Michael Haken ESAE Enterprise Admin", + "SamAccountName" : "hakenmt.esae.ea", + "Membership": [ + "UG-ESAE-Enterprise-Admins", + "UG-ESAE-Schema-Admins" + ], + "UserAccountControl" : 262656, + "msDS-SupportedEncryptionTypes" : 24, + "Path" : "OU=Users,OU=User Resources,OU=ESAE Privileged Resources" + } +] \ No newline at end of file diff --git a/ESAE/src/sceregvl.inf b/ESAE/src/sceregvl.inf new file mode 100644 index 0000000..914c7b3 --- /dev/null +++ b/ESAE/src/sceregvl.inf @@ -0,0 +1,336 @@ +; Copyright (c) Microsoft Corporation. All rights reserved. +; +; Security Configuration Template for Security Configuration Editor +; +; Template Name: SCERegVl.INF +; Template Version: 05.00.DR.0000 +; +; Revision History +; 0000 - Original + +[version] +signature="$CHICAGO$" +DriverVer=06/21/2006,6.2.9200.16384 + +[Register Registry Values] +; +; Syntax: RegPath,RegType,DisplayName,DisplayType,Options +; where +; RegPath: Includes the registry keypath and value +; RegType: 1 - REG_SZ, 2 - REG_EXPAND_SZ, 3 - REG_BINARY, 4 - REG_DWORD, 7 - REG_MULTI_SZ +; Display Name: Is a localizable string defined in the [strings] section +; Display type: 0 - boolean, 1 - Number, 2 - String, 3 - Choices, 4 - Multivalued, 5 - Bitmask +; Options: If Displaytype is 3 (Choices) or 5 (Bitmask), then specify the range of values and corresponding display strings +; in value|displaystring format separated by a comma. + + +MACHINE\System\CurrentControlSet\Control\Lsa\AuditBaseObjects,4,%AuditBaseObjects%,0 +MACHINE\System\CurrentControlSet\Control\Lsa\CrashOnAuditFail,4,%CrashOnAuditFail%,0 +MACHINE\System\CurrentControlSet\Control\Lsa\DisableDomainCreds,4,%DisableDomainCreds%,0 +MACHINE\System\CurrentControlSet\Control\Lsa\EveryoneIncludesAnonymous,4,%EveryoneIncludesAnonymous%,0 +MACHINE\System\CurrentControlSet\Control\Lsa\ForceGuest,4,%ForceGuest%,3,0|%Classic%,1|%GuestBased% +MACHINE\System\CurrentControlSet\Control\Lsa\FullPrivilegeAuditing,3,%FullPrivilegeAuditing%,0 +MACHINE\System\CurrentControlSet\Control\Lsa\LimitBlankPasswordUse,4,%LimitBlankPasswordUse%,0 +MACHINE\System\CurrentControlSet\Control\Lsa\LmCompatibilityLevel,4,%LmCompatibilityLevel%,3,0|%LMCLevel0%,1|%LMCLevel1%,2|%LMCLevel2%,3|%LMCLevel3%,4|%LMCLevel4%,5|%LMCLevel5% +MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\NTLMMinClientSec,4,%NTLMMinClientSec%,5,524288|%NTLMv2Session%,536870912|%NTLM128% +MACHINE\System\CurrentControlSet\Control\Lsa\MSV1_0\NTLMMinServerSec,4,%NTLMMinServerSec%,5,524288|%NTLMv2Session%,536870912|%NTLM128% +MACHINE\System\CurrentControlSet\Control\Lsa\NoLMHash,4,%NoLMHash%,0 +MACHINE\System\CurrentControlSet\Control\Lsa\RestrictAnonymous,4,%RestrictAnonymous%,0 +MACHINE\System\CurrentControlSet\Control\Lsa\RestrictAnonymousSAM,4,%RestrictAnonymousSAM%,0 +MACHINE\System\CurrentControlSet\Control\Lsa\SubmitControl,4,%SubmitControl%,0 +MACHINE\System\CurrentControlSet\Control\Lsa\SCENoApplyLegacyAuditPolicy,4,%SCENoApplyLegacyAuditPolicy%,0 + +MACHINE\System\CurrentControlSet\Control\Print\Providers\LanMan Print Services\Servers\AddPrinterDrivers,4,%AddPrintDrivers%,0 + +MACHINE\System\CurrentControlSet\Control\SecurePipeServers\Winreg\AllowedPaths\Machine,7,%AllowedPaths%,4 +MACHINE\System\CurrentControlSet\Control\SecurePipeServers\Winreg\AllowedExactPaths\Machine,7,%AllowedExactPaths%,4 + +MACHINE\System\CurrentControlSet\Control\Session Manager\Kernel\ObCaseInsensitive,4,%ObCaseInsensitive%,0 +MACHINE\System\CurrentControlSet\Control\Session Manager\Memory Management\ClearPageFileAtShutdown,4,%ClearPageFileAtShutdown%,0 +MACHINE\System\CurrentControlSet\Control\Session Manager\ProtectionMode,4,%ProtectionMode%,0 +MACHINE\System\CurrentControlSet\Control\Session Manager\SubSystems\optional,7,%OptionalSubSystems%,4 + +MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\EnableSecuritySignature,4,%EnableSMBSignServer%,0 +MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\RequireSecuritySignature,4,%RequireSMBSignServer%,0 +MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\EnableForcedLogOff,4,%EnableForcedLogoff%,0 +MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\AutoDisconnect,4,%AutoDisconnect%,1,%Unit-Minutes% +MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\RestrictNullSessAccess,4,%RestrictNullSessAccess%,0 +MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\NullSessionPipes,7,%NullPipes%,4 +MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters\NullSessionShares,7,%NullShares%,4 + +MACHINE\System\CurrentControlSet\Services\LanmanWorkstation\Parameters\EnableSecuritySignature,4,%EnableSMBSignRDR%,0 +MACHINE\System\CurrentControlSet\Services\LanmanWorkstation\Parameters\RequireSecuritySignature,4,%RequireSMBSignRDR%,0 +MACHINE\System\CurrentControlSet\Services\LanmanWorkstation\Parameters\EnablePlainTextPassword,4,%EnablePlainTextPassword%,0 + +MACHINE\System\CurrentControlSet\Services\LDAP\LDAPClientIntegrity,4,%LDAPClientIntegrity%,3,0|%LDAPClient0%,1|%LDAPClient1%,2|%LDAPClient2% + +MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\DisablePasswordChange,4,%DisablePWChange%,0 +MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\MaximumPasswordAge,4,%MaximumPWAge%,1,%Unit-Days% +MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\RefusePasswordChange,4,%RefusePWChange%,0 +MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\SignSecureChannel,4,%SignSecureChannel%,0 +MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\SealSecureChannel,4,%SealSecureChannel%,0 +MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\RequireSignOrSeal,4,%SignOrSeal%,0 +MACHINE\System\CurrentControlSet\Services\Netlogon\Parameters\RequireStrongKey,4,%StrongKey%,0 + +MACHINE\System\CurrentControlSet\Services\NTDS\Parameters\LDAPServerIntegrity,4,%LDAPServerIntegrity%,3,1|%LDAPServer1%,2|%LDAPServer2% + +MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DisableCAD,4,%DisableCAD%,0 +MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DontDisplayLastUserName,4,%DontDisplayLastUserName%,0 +MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\DontDisplayLockedUserId,4,%DontDisplayLockedUserId%,3,1|%LockedUserID0%,2|%LockedUserID1%,3|%LockedUserID2% +MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\LegalNoticeCaption,1,%LegalNoticeCaption%,2 +MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\LegalNoticeText,7,%LegalNoticeText%,4 +MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ScForceOption,4,%ScForceOption%,0 +MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\ShutdownWithoutLogon,4,%ShutdownWithoutLogon%,0 +MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\UndockWithoutLogon,4,%UndockWithoutLogon%,0 +MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System\NoConnectedUser,4,%BlockConnectedUser%,3,0|%PolicyDisabled%,1|%BlockAdd%,3|%BlockLogin% + +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Setup\RecoveryConsole\SecurityLevel,4,%RCAdmin%,0 +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Setup\RecoveryConsole\SetCommand,4,%RCSet%,0 + +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\CachedLogonsCount,1,%CachedLogonsCount%,1,%Unit-Logons% +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\ForceUnlockLogon,4,%ForceUnlockLogon%,0 +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\PasswordExpiryWarning,4,%PasswordExpiryWarning%,1,%Unit-Days% +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\ScRemoveOption,1,%ScRemove%,3,0|%ScRemove0%,1|%ScRemove1%,2|%ScRemove2%,3|%ScRemove3% + +MACHINE\Software\Policies\Microsoft\Cryptography\ForceKeyProtection,4,%ForceHighProtection%,3,0|%CryptAllowNoUI%,1|%CryptAllowNoPass%,2|%CryptUsePass% +MACHINE\Software\Policies\Microsoft\Windows\Safer\CodeIdentifiers\AuthenticodeEnabled,4,%AuthenticodeEnabled%,0 + +MACHINE\Software\Policies\Microsoft\Windows NT\DCOM\MachineLaunchRestriction,1,%DCOMLaunchRestriction%,2 +MACHINE\Software\Policies\Microsoft\Windows NT\DCOM\MachineAccessRestriction,1,%DCOMAccessRestriction%,2 + +; delete these values from the UI - Rdr in case NT4 w SCE +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\DisableCAD +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\DontDisplayLastUserName +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\LegalNoticeCaption +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\LegalNoticeText +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\ShutdownWithoutLogon +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\CmdConsSecurityLevel +MACHINE\System\CurrentControlSet\Control\Print\Providers\LanMan Print Services\AddPrintDrivers +MACHINE\System\CurrentControlSet\Services\MRxSMB\Parameters\EnableSecuritySignature +MACHINE\System\CurrentControlSet\Services\MRxSMB\Parameters\RequireSecuritySignature +MACHINE\System\CurrentControlSet\Services\MRxSMB\Parameters\EnablePlainTextPassword +MACHINE\System\CurrentControlSet\Services\Rdr\Parameters\EnableSecuritySignature +MACHINE\System\CurrentControlSet\Services\Rdr\Parameters\RequireSecuritySignature +MACHINE\System\CurrentControlSet\Services\Rdr\Parameters\EnablePlainTextPassword +MACHINE\Software\Microsoft\Windows\CurrentVersion\NetCache\EncryptEntireCache +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\EFS\AlgorithmID +MACHINE\Software\Microsoft\Non-Driver Signing\Policy +MACHINE\Software\Policies\Microsoft\Cryptography\ForceHighProtection + + +;========= Start of MSS Registry Values ========= +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\AutoAdminLogon,1,%DisableAutoLogon%,0 +MACHINE\SYSTEM\CurrentControlSet\Control\CrashControl\AutoReboot,4,%AutoReboot%,0 +MACHINE\System\CurrentControlSet\Services\LanmanServer\Parameters\AutoShareWks,4,%AdminShares%,0 +MACHINE\System\CurrentControlSet\Services\LanmanServer\Parameters\AutoShareServer,4,%AdminSharesServer%,0 +MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters\DisableIPSourceRouting,4,%DisableIPSourceRouting%,3,0|%DisableIPSourceRouting0%,1|%DisableIPSourceRouting1%,2|%DisableIPSourceRouting2% +MACHINE\SYSTEM\CurrentControlSet\Services\RasMan\Parameters\DisableSavePassword,4,%DisableSavePassword%,0 +MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters\EnableDeadGWDetect,4,%EnableDeadGWDetect%,0 +MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters\EnableICMPRedirect,4,%EnableICMPRedirect%,0 +MACHINE\System\CurrentControlSet\Services\Lanmanserver\Parameters\Hidden,4,%HideFromBrowseList%,0 +MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters\KeepAliveTime,4,%KeepAliveTime%,3,150000|%KeepAliveTime0%,300000|%KeepAliveTime1%,600000|%KeepAliveTime2%,1200000|%KeepAliveTime3%,2400000|%KeepAliveTime4%,3600000|%KeepAliveTime5%,7200000|%KeepAliveTime6% +MACHINE\System\CurrentControlSet\Services\IPSEC\NoDefaultExempt,4,%NoDefaultExempt%,3,0|%NoDefaultExempt0%,1|%NoDefaultExempt1%,2|%NoDefaultExempt2%,3|%NoDefaultExempt3% +MACHINE\System\CurrentControlSet\Services\Netbt\Parameters\NoNameReleaseOnDemand,4,%NoNameReleaseOnDemand%,0 +MACHINE\System\CurrentControlSet\Control\FileSystem\NtfsDisable8dot3NameCreation,4,%NtfsDisable8dot3NameCreation%,3,0|%NtfsDisable8dot3NameCreation0%,1|%NtfsDisable8dot3NameCreation1%,2|%NtfsDisable8dot3NameCreation2%,3|%NtfsDisable8dot3NameCreation3% +MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters\PerformRouterDiscovery,4,%PerformRouterDiscovery%,0 +MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\SafeDllSearchMode,4,%SafeDllSearchMode%,0 +MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\ScreenSaverGracePeriod,1,%ScreenSaverGracePeriod%,1 +MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters\SynAttackProtect,4,%SynAttackProtect%,3,0|%SynAttackProtect0%,1|%SynAttackProtect1% +MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters\TcpMaxConnectResponseRetransmissions,4,%TcpMaxConnectResponseRetransmissions%,3,0|%TcpMaxConnectResponseRetransmissions0%,1|%TcpMaxConnectResponseRetransmissions1%,2|%TcpMaxConnectResponseRetransmissions2%,3|%TcpMaxConnectResponseRetransmissions3% +MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters\TcpMaxDataRetransmissions,4,%TcpMaxDataRetransmissions%,1 +MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Security\WarningLevel,4,%WarningLevel%,3,50|%WarningLevel0%,60|%WarningLevel1%,70|%WarningLevel2%,80|%WarningLevel3%,90|%WarningLevel4% +MACHINE\System\CurrentControlSet\Services\Tcpip6\Parameters\DisableIPSourceRouting,4,%DisableIPSourceRoutingIPv6%,3,0|%DisableIPSourceRouting0%,1|%DisableIPSourceRouting1%,2|%DisableIPSourceRouting2% +MACHINE\System\CurrentControlSet\Services\Tcpip6\Parameters\TcpMaxDataRetransmissions ,4,%TcpMaxDataRetransmissionsIPv6%,1 +;========= End of MSS Registry Values ========= + + +[Strings] +;========= Start of MSS Strings Values ========= +DisableAutoLogon = "MSS: (AutoAdminLogon) Enable Automatic Logon (not recommended)" +AutoReboot = "MSS: (AutoReboot) Allow Windows to automatically restart after a system crash (recommended except for highly secure environments)" +AdminShares = "MSS: (AutoShareWks) Enable Administrative Shares (recommended except for highly secure environments)" +AdminSharesServer = "MSS: (AutoShareServer) Enable Administrative Shares (recommended except for highly secure environments)" +DisableIPSourceRouting = "MSS: (DisableIPSourceRouting) IP source routing protection level (protects against packet spoofing)" +DisableIPSourceRoutingIPv6 = "MSS: (DisableIPSourceRouting IPv6) IP source routing protection level (protects against packet spoofing)" +DisableIPSourceRouting0 = "No additional protection, source routed packets are allowed" +DisableIPSourceRouting1 = "Medium, source routed packets ignored when IP forwarding is enabled" +DisableIPSourceRouting2 = "Highest protection, source routing is completely disabled" +DisableSavePassword = "MSS: (DisableSavePassword) Prevent the dial-up passsword from being saved (recommended)" +EnableDeadGWDetect = "MSS: (EnableDeadGWDetect) Allow automatic detection of dead network gateways (could lead to DoS)" +EnableICMPRedirect = "MSS: (EnableICMPRedirect) Allow ICMP redirects to override OSPF generated routes" +HideFromBrowseList = "MSS: (Hidden) Hide Computer From the Browse List (not recommended except for highly secure environments)" +KeepAliveTime = "MSS: (KeepAliveTime) How often keep-alive packets are sent in milliseconds" +KeepAliveTime0 ="150000 or 2.5 minutes" +KeepAliveTime1 ="300000 or 5 minutes (recommended)" +KeepAliveTime2 ="600000 or 10 minutes" +KeepAliveTime3 ="1200000 or 20 minutes" +KeepAliveTime4 ="2400000 or 40 minutes" +KeepAliveTime5 ="3600000 or 1 hour" +KeepAliveTime6 ="7200000 or 2 hours (default value)" +NoDefaultExempt = "MSS: (NoDefaultExempt) Configure IPSec exemptions for various types of network traffic." +NoDefaultExempt0 = "Allow all exemptions (least secure)." +NoDefaultExempt1 = "Multicast, broadcast, & ISAKMP exempt (best for Windows XP)." +NoDefaultExempt2 = "RSVP, Kerberos, and ISAKMP are exempt." +NoDefaultExempt3 = "Only ISAKMP is exempt (recommended for Windows Server 2003)." +NoNameReleaseOnDemand = "MSS: (NoNameReleaseOnDemand) Allow the computer to ignore NetBIOS name release requests except from WINS servers" +NtfsDisable8dot3NameCreation = "MSS: (NtfsDisable8dot3NameCreation) Enable the computer to stop generating 8.3 style filenames" +NtfsDisable8dot3NameCreation0 = "Enable 8Dot3 Creation on all Volumes" +NtfsDisable8dot3NameCreation1 = "Disable 8Dot3 Creation on all Volumes" +NtfsDisable8dot3NameCreation2 = "Set 8dot3 name creation per volume using FSUTIL (Windows 7 or later) +NtfsDisable8dot3NameCreation3 = "Disable 8Dot3 name creation on all volumes except system volume (Windows 7 or later)" +PerformRouterDiscovery = "MSS: (PerformRouterDiscovery) Allow IRDP to detect and configure Default Gateway addresses (could lead to DoS)" +SafeDllSearchMode = "MSS: (SafeDllSearchMode) Enable Safe DLL search mode (recommended)" +ScreenSaverGracePeriod = "MSS: (ScreenSaverGracePeriod) The time in seconds before the screen saver grace period expires (0 recommended)" +SynAttackProtect = "MSS: (SynAttackProtect) Syn attack protection level (protects against DoS)" +SynAttackProtect0 = "No additional protection, use default settings" +SynAttackProtect1 = "Connections time out sooner if a SYN attack is detected" +TcpMaxConnectResponseRetransmissions = "MSS: (TcpMaxConnectResponseRetransmissions) SYN-ACK retransmissions when a connection request is not acknowledged" +TcpMaxConnectResponseRetransmissions0 = "No retransmission, half-open connections dropped after 3 seconds" +TcpMaxConnectResponseRetransmissions1 = "3 seconds, half-open connections dropped after 9 seconds" +TcpMaxConnectResponseRetransmissions2 = "3 & 6 seconds, half-open connections dropped after 21 seconds" +TcpMaxConnectResponseRetransmissions3 = "3, 6, & 9 seconds, half-open connections dropped after 45 seconds" +TcpMaxDataRetransmissions = "MSS: (TcpMaxDataRetransmissions) How many times unacknowledged data is retransmitted (3 recommended, 5 is default)" +TcpMaxDataRetransmissionsIPv6 = "MSS: (TcpMaxDataRetransmissions IPv6) How many times unacknowledged data is retransmitted (3 recommended, 5 is default)" +WarningLevel = "MSS: (WarningLevel) Percentage threshold for the security event log at which the system will generate a warning" +WarningLevel0 = "50%" +WarningLevel1 = "60%" +WarningLevel2 = "70%" +WarningLevel3 = "80%" +WarningLevel4 = "90%" +;========= End of MSS Strings Values ========= + +;================================ Accounts ============================================================================ +;Specified in UI code - Accounts: Administrator account status +;Specified in UI code - Accounts: Guest account status +;Specified in UI code - Accounts: Rename administrator account +;Specified in UI code - Accounts: Rename guest account +LimitBlankPasswordUse = "@wsecedit.dll,-59001" +BlockConnectedUser = "@wsecedit.dll,-59150" +PolicyDisabled = "@wsecedit.dll,-59151" +BlockAdd = "@wsecedit.dll,-59152" +BlockLogin = "@wsecedit.dll,-59153" + +;================================ Audit =============================================================================== + +AuditBaseObjects="@wsecedit.dll,-59002" +FullPrivilegeAuditing="@wsecedit.dll,-59003" +CrashOnAuditFail="@wsecedit.dll,-59004" +SCENoApplyLegacyAuditPolicy="@wsecedit.dll,-59104" + +;================================ Devices ============================================================================= +AddPrintDrivers="@wsecedit.dll,-59005" +UndockWithoutLogon="@wsecedit.dll,-59010" + +;================================ Domain controller ==================================================================== +SubmitControl="@wsecedit.dll,-59011" +RefusePWChange="@wsecedit.dll,-59012" +LDAPServerIntegrity = "@wsecedit.dll,-59013" +LDAPServer1 = "@wsecedit.dll,-59014" +LDAPServer2 = "@wsecedit.dll,-59015" + +;================================ Domain member ======================================================================== +DisablePWChange="@wsecedit.dll,-59016" +MaximumPWAge="@wsecedit.dll,-59017" +SignOrSeal="@wsecedit.dll,-59018" +SealSecureChannel="@wsecedit.dll,-59019" +SignSecureChannel="@wsecedit.dll,-59020" +StrongKey="@wsecedit.dll,-59021" + +;================================ Interactive logon ==================================================================== +DisableCAD = "@wsecedit.dll,-59022" +DontDisplayLastUserName = "@wsecedit.dll,-59023" +DontDisplayLockedUserId = "@wsecedit.dll,-59024" +LockedUserId0 = "@wsecedit.dll,-59025" +LockedUserId1 = "@wsecedit.dll,-59026" +LockedUserId2 = "@wsecedit.dll,-59027" +LegalNoticeText = "@wsecedit.dll,-59028" +LegalNoticeCaption = "@wsecedit.dll,-59029" +CachedLogonsCount = "@wsecedit.dll,-59030" +PasswordExpiryWarning = "@wsecedit.dll,-59031" +ForceUnlockLogon = "@wsecedit.dll,-59032" +ScForceOption = "@wsecedit.dll,-59033" +ScRemove = "@wsecedit.dll,-59034" +ScRemove0 = "@wsecedit.dll,-59035" +ScRemove1 = "@wsecedit.dll,-59036" +ScRemove2 = "@wsecedit.dll,-59037" +ScRemove3 = "@wsecedit.dll,-59038" + +;================================ Microsoft network client ============================================================= +RequireSMBSignRdr="@wsecedit.dll,-59039" +EnableSMBSignRdr="@wsecedit.dll,-59040" +EnablePlainTextPassword="@wsecedit.dll,-59041" + +;================================ Microsoft network server ============================================================= +AutoDisconnect="@wsecedit.dll,-59042" +RequireSMBSignServer="@wsecedit.dll,-59043" +EnableSMBSignServer="@wsecedit.dll,-59044" +EnableForcedLogoff="@wsecedit.dll,-59045" + +;================================ Network access ======================================================================= +;Specified in UI code - Network access: Allow anonymous SID/Name translation +DisableDomainCreds = "@wsecedit.dll,-59046" +RestrictAnonymousSAM = "@wsecedit.dll,-59047" +RestrictAnonymous = "@wsecedit.dll,-59048" +EveryoneIncludesAnonymous = "@wsecedit.dll,-59049" +RestrictNullSessAccess = "@wsecedit.dll,-59050" +NullPipes = "@wsecedit.dll,-59051" +NullShares = "@wsecedit.dll,-59052" +AllowedPaths = "@wsecedit.dll,-59053" +AllowedExactPaths = "@wsecedit.dll,-59054" +ForceGuest = "@wsecedit.dll,-59055" +Classic = "@wsecedit.dll,-59056" +GuestBased = "@wsecedit.dll,-59057" + +;================================ Network security ===================================================================== +;Specified in UI code - Network security: Enforce logon hour restrictions +NoLMHash = "@wsecedit.dll,-59058" +LmCompatibilityLevel = "@wsecedit.dll,-59059" +LMCLevel0 = "@wsecedit.dll,-59060" +LMCLevel1 = "@wsecedit.dll,-59061" +LMCLevel2 = "@wsecedit.dll,-59062" +LMCLevel3 = "@wsecedit.dll,-59063" +LMCLevel4 = "@wsecedit.dll,-59064" +LMCLevel5 = "@wsecedit.dll,-59065" +NTLMMinClientSec = "@wsecedit.dll,-59066" +NTLMMinServerSec = "@wsecedit.dll,-59067" +NTLMv2Session = "@wsecedit.dll,-59070" +NTLM128 = "@wsecedit.dll,-59071" +LDAPClientIntegrity = "@wsecedit.dll,-59072" +LDAPClient0 = "@wsecedit.dll,-59073" +LDAPClient1 = "@wsecedit.dll,-59074" +LDAPClient2 = "@wsecedit.dll,-59075" + +;================================ Recovery console ==================================================================== +RCAdmin="@wsecedit.dll,-59076" +RCSet="@wsecedit.dll,-59077" + +;================================ Shutdown ============================================================================ +ShutdownWithoutLogon="@wsecedit.dll,-59078" +ClearPageFileAtShutdown="@wsecedit.dll,-59079" + +ProtectionMode = "@wsecedit.dll,-59080" +ObCaseInsensitive = "@wsecedit.dll,-59084" + +;================================ System cryptography ================================================================= +FIPS="@wsecedit.dll,-59085" + +ForceHighProtection="@wsecedit.dll,-59086" + +CryptAllowNoUI="@wsecedit.dll,-59087" +CryptAllowNoPass="@wsecedit.dll,-59088" +CryptUsePass="@wsecedit.dll,-59089" + + +;================================ System Settings ===================================================================== +AuthenticodeEnabled = "@wsecedit.dll,-59090" +OptionalSubSystems = "@wsecedit.dll,-59091" + + +Unit-Logons="@wsecedit.dll,-59092" +Unit-Days="@wsecedit.dll,-59093" +Unit-Minutes="@wsecedit.dll,-59094" +Unit-Seconds="@wsecedit.dll,-59095" + +;================================ DCOM Machine Restrictions =========================================================== +DCOMLaunchRestriction="@wsecedit.dll,-59096" +DCOMAccessRestriction="@wsecedit.dll,-59097" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2ef290e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 BAMCIS + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..79e9b58 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Enhanced Security Administrative Environment (ESAE) \ No newline at end of file