Jorge's Quest For Knowledge!

All About Identity And Security On-Premises And In The Cloud – It's Just Like An Addiction, The More You Have, The More You Want To Have!

Archive for the ‘Domain Join’ Category

(2017-03-16) Domain Join Through An RODC Instead Of An RWDC (Update 2)

Posted by Jorge on 2017-03-16


In the blog post (2009-01-01) Domain Join through an RODC instead of an RWDC I explained the so called read-only domain join against an RODC. In that blog post you will find a VBS script that helps you achieve that goal. Prior to the VBS script you see multiple ways of pre-creating the computer and having the password of the computer account replicate to the RODC.

In this blog post I provide an updated PowerShell script (don’t forget the execution policy on the server!) that performs the read-only domain join. You can get the PowerShell script through this link, or you can copy it from below.

### Abstract: This PoSH Script Joins A Stand Alone Server To An AD Domain Through A Targeted RODC ### Written by: Jorge de Almeida Pinto [MVP-EMS] ### BLOG: https://jorgequestforknowledge.wordpress.com/ ### ### 2013-04-12: Initial version of the script in PowerShell (v0.1) ### 2017-03-16: Added description for code 1219, added logging (same folder as script), check and clean any site related setting in registry, ### added check that script is executed with admin credentials, added check for connectivity to RODC (v0.2) ### ### WARNING: This script checks connectivity to the targeted RODC for a specific set of ports. The script is configured with default ports ### that are required, but it is also configured with a "Custom RPC Static Port For NetLogon" (40961) as that is what I have configured in ### my test/demo environment. If you are using a different port number, then make sure to change that first before running the script. ### If you use the dynamic range of RPC ports OR you do not have a firewall between your servers and RODCs, then remove that custom port number! ### <# .SYNOPSIS Joins a stand alone server to an AD domain through a targeted RODC. .DESCRIPTION Joins a stand alone server to an AD domain through a targeted RODC. .PARAMETER adDomain The FQDN of the AD domain, the server needs to be joined to. .PARAMETER rodcFQDN The FQDN of the RODC that will be targeted to join the server to the AD domain through a read-only join. .PARAMETER ipAddressLocalHost The IP address of the local server. .PARAMETER compAccountPWD The password of the computer account that was set during the pre-creation of that computer account. .EXAMPLE - Join the server SERVER1 to the AD domain COMPANY.COM through the RODC RODC1.COMPANY.COM .\Read-Only-Domain-Join.ps1 -adDomain COMPANY.COM -rodcFQDN RODC1.COMPANY.COM -ipAddressLocalHost 192.168.6.3 -compAccountPWD 'MyPa$$w0rd' .NOTES This script requires local administrator permissions. #> Param( [Parameter(Mandatory=$TRUE, ValueFromPipeline=$TRUE, ValueFromPipelineByPropertyName=$TRUE, HelpMessage='Please specify the FQDN of the AD domain to join to.')] [ValidateNotNullOrEmpty()] [string]$adDomain, [Parameter(Mandatory=$TRUE, ValueFromPipeline=$TRUE, ValueFromPipelineByPropertyName=$TRUE, HelpMessage='Please specify the FQDN of the RODC to target for the read-only domain join.')] [ValidateNotNullOrEmpty()] [string]$rodcFQDN, [Parameter(Mandatory=$TRUE, ValueFromPipeline=$TRUE, ValueFromPipelineByPropertyName=$TRUE, HelpMessage='Please specify the IP address of the local stand alone server.')] [ValidateNotNullOrEmpty()] [string]$ipAddressLocalHost, [Parameter(Mandatory=$TRUE, ValueFromPipeline=$TRUE, ValueFromPipelineByPropertyName=$TRUE, HelpMessage='Please specify the password that was set for the pre-created computer account.')] [ValidateNotNullOrEmpty()] [string]$compAccountPWD ) ### FUNCTION: Logging Data To The Log File Function Logging($dataToLog, $lineType) { $datetimeLogLine = "[" + $(Get-Date -format "yyyy-MM-dd HH:mm:ss") + "] : " Out-File -filepath "$logFileFullPath" -append -inputObject "$datetimeLogLine$dataToLog" #Write-Output($datetimeLogLine + $dataToLog) If ($lineType -eq $NULL) { Write-Host "$datetimeLogLine$dataToLog" } If ($lineType -eq "SUCCESS") { Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Green } If ($lineType -eq "ERROR") { Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Red } If ($lineType -eq "WARNING") { Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Red } If ($lineType -eq "HEADER") { Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Magenta } If ($lineType -eq "REMARK") { Write-Host "$datetimeLogLine$dataToLog" -ForeGroundColor Cyan } } ### FUNCTION: Test Credentials For Admin Privileges Function Test-Admin { $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent() (New-Object Security.Principal.WindowsPrincipal $currentUser).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) } ### FUNCTION: Test The Port Connection # Source: # Based Upon http://gallery.technet.microsoft.com/scriptcenter/97119ed6-6fb2-446d-98d8-32d823867131 Function PortConnectionCheck($fqdnServer,$port,$timeOut) { $tcpPortSocket = $null $portConnect = $null $tcpPortWait = $null $tcpPortSocket = New-Object System.Net.Sockets.TcpClient $portConnect = $tcpPortSocket.BeginConnect($fqdnServer,$port,$null,$null) $tcpPortWait = $portConnect.AsyncWaitHandle.WaitOne($timeOut,$false) If(!$tcpPortWait) { $tcpPortSocket.Close() Return "ERROR" } Else { #$error.Clear() $ErrorActionPreference = "SilentlyContinue" $tcpPortSocket.EndConnect($portConnect) | Out-Null If (!$?) { Return "ERROR" } Else { Return "SUCCESS" } $tcpPortSocket.Close() $ErrorActionPreference = "Continue" } } ### FUNCTION: Determine The Network ID To Which The IP Address And Subnet Mask Belong # Written By Nathan Linley | http://myitpath.blogspot.com # Source Of Original Script: http://poshcode.org/2888 Function Get-NetworkID ([string]$ipAddress, [string]$subnetMask) { $ipOctets = $ipAddress.split(".") $subnetOctets = $subnetMask.split(".") $result = "" For ($i = 0; $i -lt 4; $i++) { $result += $ipOctets[$i] -band $subnetOctets[$i] $result += "." } $result = $result.substring(0,$result.length -1) return $result } ### FUNCTION: Determine The Subnet Mask Based Upon The Specified Mask Bits Function Get-SubnetMask-ByLength ([int]$length) { If ($length -eq $null -or $length -gt 32 -or $length -lt 0) { Write-Error "Function 'Get-SubnetMask-ByLength'...: Invalid Subnet Mask Length Provided. Please Provide A Number BETWEEN 0 And 32" Return $null } switch ($length) { "32" {return "255.255.255.255"} "31" {return "255.255.255.254"} "30" {return "255.255.255.252"} "29" {return "255.255.255.248"} "28" {return "255.255.255.240"} "27" {return "255.255.255.224"} "26" {return "255.255.255.192"} "25" {return "255.255.255.128"} "24" {return "255.255.255.0"} "23" {return "255.255.254.0"} "22" {return "255.255.252.0"} "21" {return "255.255.248.0"} "20" {return "255.255.240.0"} "19" {return "255.255.224.0"} "18" {return "255.255.192.0"} "17" {return "255.255.128.0"} "16" {return "255.255.0.0"} "15" {return "255.254.0.0"} "14" {return "255.252.0.0"} "13" {return "255.248.0.0"} "12" {return "255.240.0.0"} "11" {return "255.224.0.0"} "10" {return "255.192.0.0"} "9" {return "255.128.0.0"} "8" {return "255.0.0.0"} "7" {return "254.0.0.0"} "6" {return "252.0.0.0"} "5" {return "248.0.0.0"} "4" {return "240.0.0.0"} "3" {return "224.0.0.0"} "2" {return "192.0.0.0"} "1" {return "128.0.0.0"} "0" {return "0.0.0.0"} } } ### Clear The Screen Clear-Host ### Configure The Appropriate Screen And Buffer Size To Make Sure Everything Fits Nicely $uiConfig = (Get-Host).UI.RawUI $uiConfig.WindowTitle = "+++ READ-ONLY DOMAIN JOIN THROUGH AN RODC +++" $uiConfig.ForegroundColor = "Yellow" $uiConfigBufferSize = $uiConfig.BufferSize $uiConfigBufferSize.Width = 140 $uiConfigBufferSize.Height = 9999 $uiConfigScreenSizeMax = $uiConfig.MaxPhysicalWindowSize $uiConfigScreenSizeMaxWidth = $uiConfigScreenSizeMax.Width $uiConfigScreenSizeMaxHeight = $uiConfigScreenSizeMax.Height $uiConfigScreenSize = $uiConfig.WindowSize If ($uiConfigScreenSizeMaxWidth -lt 140) { $uiConfigScreenSize.Width = $uiConfigScreenSizeMaxWidth } Else { $uiConfigScreenSize.Width = 140 } If ($uiConfigScreenSizeMaxHeight -lt 75) { $uiConfigScreenSize.Height = $uiConfigScreenSizeMaxHeight - 5 } Else { $uiConfigScreenSize.Height = 75 } $uiConfig.BufferSize = $uiConfigBufferSize $uiConfig.WindowSize = $uiConfigScreenSize ### Definition Of Some Constants $execDateTime = Get-Date $execDateTimeYEAR = $execDateTime.Year $execDateTimeMONTH = $execDateTime.Month $execDateTimeDAY = $execDateTime.Day $execDateTimeHOUR = $execDateTime.Hour $execDateTimeMINUTE = $execDateTime.Minute $execDateTimeSECOND = $execDateTime.Second $execDateTimeCustom = [STRING]$execDateTimeYEAR + "-" + $("{0:D2}" -f $execDateTimeMONTH) + "-" + $("{0:D2}" -f $execDateTimeDAY) + "_" + $("{0:D2}" -f $execDateTimeHOUR) + "." + $("{0:D2}" -f $execDateTimeMINUTE) + "." + $("{0:D2}" -f $execDateTimeSECOND) $localComputer = Get-WmiObject -Class Win32_ComputerSystem $localComputerName = $localComputer.Name $scriptFileFullPath = $MyInvocation.MyCommand.Definition $currentScriptFolderPath = Split-Path $scriptFileFullPath $logFileFullPath = Join-Path $currentScriptFolderPath $($execDateTimeCustom + "_Read-Only-Domain-Join_" + $localComputerName + ".log") $rodcNBT = $rodcFQDN.Substring(0,$rodcFQDN.IndexOf(".")) $userName = $adDomain + "\" + $localComputerName + "`$" $userPassword = $compAccountPWD $ports = 53,88,135,389,445,464,636,3268,3269,40961 # DNS, Kerberos, RPC Endpoint Mapper, LDAP, SMB, Kerberos Change/Set Password, LDAP-SSL, GC, GC-SSL, Custom RPC Static Port For NetLogon ### Definition Of Some Variables Set-Variable JOIN_DOMAIN -option Constant -value 1 # Joins a computer to a domain. If this value is not specified, the join is a computer to a workgroup Set-Variable ACCT_CREATE -option Constant -value 2 # Creates an account on a domain Set-Variable ACCT_DELETE -option Constant -value 4 # Deletes an account when a domain exists Set-Variable WIN9X_UPGRADE -option Constant -value 16 # The join operation is part of an upgrade from Windows 98 or Windows 95 to Windows 2000 or Windows NT Set-Variable DOMAIN_JOIN_IF_JOINED -option Constant -value 32 # Allows a join to a new domain, even if the computer is already joined to a domain Set-Variable JOIN_UNSECURE -option Constant -value 64 # Performs an unsecured join Set-Variable MACHINE_PASSWORD_PASSED -option Constant -value 128 # The machine, not the user, password passed. This option is only valid for unsecure joins Set-Variable DEFERRED_SPN_SET -option Constant -value 256 # Writing SPN and DnsHostName attributes on the computer object should be deferred until the rename that follows the join Set-Variable NETSETUP_JOIN_READONLY -option Constant -value 2048 # Use an RODC to perform the domain join against Set-Variable INSTALL_INVOCATION -option Constant -value 262144 # The APIs were invoked during install ### Domain Join Options To Use $domainJoinOption = $JOIN_DOMAIN + $MACHINE_PASSWORD_PASSED + $NETSETUP_JOIN_READONLY Logging "" Logging "**********************************************************" "HEADER" Logging "* *" "HEADER" Logging "* --> Read-Only Domain Join Through An RODC <-- *" "HEADER" Logging "* *" "HEADER" Logging "* Written By: Jorge de Almeida Pinto [MVP-EMS] *" "HEADER" Logging "* *" "HEADER" Logging " BLOG: 'Jorge's Quest For Knowledge' *" "HEADER" Logging " (https://jorgequestforknowledge.wordpress.com/) *" "HEADER" Logging "* *" "HEADER" Logging "**********************************************************" "HEADER" Logging "" ### Pre-Requisites Check Logging "" Logging "------------------------------------------------------------------------------------------------------------------" "HEADER" Logging "+++ PRE-REQUISITES CHECK +++" "HEADER" Logging "" Logging "ATTENTION: To Execute This Script, The Following Pre-Requisites Must Be met:" "WARNING" Logging " * Local Server Is Configured Correctly With IP Address, Subnet Mask And DNS Servers..." "WARNING" Logging " * Admin Account Must Be(Direct) Member Of Local 'Administrators' Group!..." "WARNING" Logging " * If UAC Is Used, Admin Account Must Be Running Within An Elevated Administrator Command Prompt!..." "WARNING" Logging " * Required Ports Must Be Opened Between This Server And Targeted RODC!..." "WARNING" Logging "" Logging "ATTENTION: This Script Will Fail Without The Pre-Requisites Mentioned Above!" "WARNING" Logging "" Logging "Press Any Key To Continue...(TWICE)" Logging "" $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") ### Checking For Admin Credentials And If Those Admin Credentials have Been Elevated Due To UAC If (!(Test-Admin)) { Logging "" Logging "WARNING:" "ERROR" Logging " * Your Admin Account IS NOT A (Direct) Member Of The Local 'Administrators' Group!..." "ERROR" Logging " * Your Admin Account IS NOT Running Within An Elevated Administrator Command Prompt!..." "ERROR" Logging "" Logging "Aborting Script..." "ERROR" Logging "" EXIT } Else { Logging "" Logging "SUCCESS:" "SUCCESS" Logging " * Your Admin Account IS A (Direct) Member Of The Local 'Administrators' Group!..." "SUCCESS" Logging " * Your Admin Account IS Running Within An Elevated Administrator Command Prompt!..." "SUCCESS" Logging "" Logging "Continuing Script..." "SUCCESS" Logging "" } ### Checking Connectivity (TCP Only!) Between This Server And The Target RODC $checkOK = $true $ports | %{ $port = $_ $connectionResult = $null $connectionResult = PortConnectionCheck $rodcFQDN $port 500 If ($connectionResult -eq "SUCCESS") { Logging "The RODC '$rodcFQDN' IS Accessible And Listening On Port '$port'..." "SUCCESS" } If ($connectionResult -eq "ERROR") { Logging "The RODC '$rodcFQDN' IS NOT Accessible And Listening On Port '$port'..." "ERROR" $checkOK = $false } } If (!$checkOK) { Logging "" Logging "WARNING:" "ERROR" Logging " * One Or More Of The Required Ports IS/ARE NOT Available..." "ERROR" Logging "" Logging "Aborting Script..." "ERROR" Logging "" EXIT } Else { Logging "" Logging "SUCCESS:" "SUCCESS" Logging " * All The Required Ports ARE Available..." "SUCCESS" Logging "" Logging "Continuing Script..." "SUCCESS" Logging "" } ### Checking Local Registry Settings For Site Definition $regNameExistSiteName = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" -Name SiteName -ErrorAction SilentlyContinue If ($regNameExistSiteName) { Remove-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" -Name SiteName -Force Logging "" Logging "Registry Value 'SiteName' In 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' Exists..." Logging "" Logging "Registry Value 'SiteName' Has Been Deleted..." Logging "" } $regNameExistDynamicSiteName = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" -Name DynamicSiteName -ErrorAction SilentlyContinue If ($regNameExistDynamicSiteName) { Remove-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" -Name DynamicSiteName -Force Logging "" Logging "Registry Value 'DynamicSiteName' In 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' Exists..." Logging "" Logging "Registry Value 'DynamicSiteName' Has Been Deleted..." Logging "" } ### Change Preferred Error Action $ErrorActionPreference = "SilentlyContinue" ### Connecting To AD On The RODC And Getting Some NCs $rootDSEldapPath = "LDAP://$rodcFQDN/rootDSE" $directoryEntryrootDSE = New-Object System.DirectoryServices.DirectoryEntry($rootDSEldapPath, $userName, $userPassword) $defaultNamingContext = $directoryEntryrootDSE.defaultNamingContext $configurationNamingContext = $directoryEntryrootDSE.configurationNamingContext ### Checking Pre-Created Computer Account Exists And The Correct Password Of The Computer Account Is Being Used $defaultNCldapPath = "LDAP://$rodcFQDN/$defaultNamingContext" $defaultNCdirectoryEntry = New-Object System.DirectoryServices.DirectoryEntry($defaultNCldapPath, $userName, $userPassword) $SearcherSRVCompAccount = $null $SearcherSRVCompAccount = New-Object DirectoryServices.DirectorySearcher($defaultNCdirectoryEntry) $SearcherSRVCompAccount.SearchScope = "Subtree" $SearcherSRVCompAccount.Filter = "(&(objectClass=computer)(sAMAccountName=$localComputerName`$))" $SearcherSRVCompAccountResult = $null $SearcherSRVCompAccountResult = $SearcherSRVCompAccount.FindOne() $dnSRVCompAccount = $null $dnSRVCompAccount = $SearcherSRVCompAccountResult.Properties.distinguishedname If ($dnSRVCompAccount) { Logging "" Logging "SUCCESS:" "SUCCESS" Logging " * A Computer Account For This Server DOES Exist...And" "SUCCESS" Logging " * A Correct Password Is Being Used..." "SUCCESS" Logging "" Logging "Continuing Script..." "SUCCESS" Logging "" } Else { Logging "" Logging "WARNING:" "ERROR" Logging " * A Computer Account For This Server DOES NOT Exist...Or" "ERROR" Logging " * An Incorrect Password Is Being Used..." "ERROR" Logging "" Logging "Aborting Script..." "ERROR" Logging "" EXIT } ### Change Preferred Error Action To Default $ErrorActionPreference = "Continue" $regNameExistSiteName = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" -Name SiteName -ErrorAction SilentlyContinue If ($regNameExistSiteName) { Remove-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" -Name SiteName -Force Logging "" Logging "Registry Value 'SiteName' In 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' Exists..." Logging "" Logging "Registry Value 'SiteName' Has Been Deleted..." Logging "" } $regNameExistDynamicSiteName = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" -Name DynamicSiteName -ErrorAction SilentlyContinue If ($regNameExistDynamicSiteName) { Remove-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" -Name DynamicSiteName -Force Logging "" Logging "Registry Value 'DynamicSiteName' In 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' Exists..." Logging "" Logging "Registry Value 'DynamicSiteName' Has Been Deleted..." Logging "" } ### Initiating Read-Only Domain Join Logging "" Logging "------------------------------------------------------------------------------------------------------------------" "HEADER" Logging "+++ INITIATING READ-ONLY DOMAIN JOIN +++" "HEADER" ### Determining The AD Site Of The Specified/Targeted RODC $rodcCompAccountldapPath = "LDAP://$rodcFQDN/CN=$rodcNBT,OU=Domain Controllers,$defaultNamingContext" $rodcCompAccountdirectoryEntry = $null $rodcCompAccountdirectoryEntry = New-Object System.DirectoryServices.DirectoryEntry($rodcCompAccountldapPath, $userName, $userPassword) $SearcherRodcCompAccount = $null $SearcherRodcCompAccount = New-Object DirectoryServices.DirectorySearcher($rodcCompAccountdirectoryEntry) $SearcherRodcCompAccount.SearchScope = "Base" $SearcherRodcCompAccount.Filter = "(&(objectClass=computer)(dNSHostName=$rodcFQDN))" $SearcherRodcCompAccount.PropertiesToLoad.Add("msDS-SiteName") | Out-Null $SearcherRodcCompAccountResult = $null $SearcherRodcCompAccountResult = $SearcherRodcCompAccount.FindOne() $rodcADSite = $null [string]$rodcADSite = $SearcherRodcCompAccountResult.Properties."msds-sitename" ### Matching The IP Address Of The Local Server Against An AD Site In The AD Forest $subnetsContainerldapPath = "LDAP://$rodcFQDN/CN=Subnets,CN=Sites,$configurationNamingContext" $subnetsContainerdirectoryEntry = $null $subnetsContainerdirectoryEntry = New-Object System.DirectoryServices.DirectoryEntry($subnetsContainerldapPath, $userName, $userPassword) $searcherSubnets = $null $searcherSubnets = New-Object DirectoryServices.DirectorySearcher($subnetsContainerdirectoryEntry) $searcherSubnets.SearchScope = "Subtree" $searcherSubnets.PropertiesToLoad.Add("name") | Out-Null $searcherSubnets.PropertiesToLoad.Add("siteObject") | Out-Null # We Can Take Network Masks In Both Length And Full Octet Format # We Need To Use Both. LDAP Searches # Use Length, And Network ID Generation Is By Full Octet Format. $startMaskLength = 32 For ($i = $startMaskLength; $i -ge 0; $i--) { # Loop Through Netmasks From /32 To /0 Looking For A Subnet Match In AD # Go Through All Masks From Longest To Shortest $subnetMask = &Get-SubnetMask-ByLength $i $networkID = &Get-NetworkID $ipAddressLocalHost $subnetMask # LDAP Search For The Network $searcherSubnets.filter = "(&(objectClass=subnet)(objectCategory=subnet)(cn=" + $networkID + "/" + $i + "))" $subnetObjectResult = $null $subnetObjectResult = $searcherSubnets.FindOne() #$subnetObjectsList = $searcherSubnets.FindAll() #$subnetsTable = @() #$subnetObjectsList.Properties | %{ # $subnetsTableObj = "" | Select "AD Subnet","AD Site" # $subnetsTableObj."AD Subnet" = ($_.name)[0] # $subnetsTableObj."AD Site" = $(($_.siteobject)[0]).Substring(3,$(($_.siteobject)[0]).IndexOf(",")-3) # $subnetsTable += $subnetsTableObj #} #$subnetsTable | FT -Autosize If ($subnetObjectResult -ne $null) { # If A Match Is Found, Return It Since It Is The Longest Length (Closest Match) $localComputerADSubnet = $null [string]$localComputerADSubnet = $($subnetObjectResult.Properties.name) $localComputerADSite = $null [string]$localComputerADSite = $($subnetObjectResult.Properties.siteobject).Substring(3,$($subnetObjectResult.Properties.siteobject).IndexOf(",")-3) #return $localComputerADSite Break } $subnetObjectResult = $null [string]$localComputerADSubnet = $null [string]$localComputerADSite = $null } If ($localComputerADSubnet -eq $null -Or $localComputerADSite -eq $null) { [string]$localComputerADSubnet = "NO_MATCH_FOUND" [string]$localComputerADSite = "NO_MATCH_FOUND" } ### Present The Information Logging "" Logging "Trying To Join The Local Computer '$localComputerName' To The AD Domain '$adDomain' Using The RODC '$rodcFQDN'..." Logging "" Logging "FQDN AD Domain............: $adDomain" Logging "FQDN RODC.................: $rodcFQDN" Logging "AD Site RODC..............: $rodcADSite" Logging "AD Site Local Computer....: $localComputerADSite" Logging "Matching AD Subnet........: $localComputerADSubnet" Logging "Local Computer Name.......: $localComputerName ($localComputerName`$)" Logging "Distinguished Name........: $dnSRVCompAccount" Logging "Computer Account Password.: $compAccountPWD" Logging "" ### AD Sites Must Match, Otherwise Something Is Wrong If ($rodcADSite.ToUpper() -ne $localComputerADSite.ToUpper() -Or $localComputerADSite -eq "NO_MATCH_FOUND") { Logging "" Logging "WARNING:" "ERROR" Logging " * The AD Site Of The Local Computer DOES NOT Match The AD Site Of The Specified RODC..." "ERROR" Logging " * Make Sure The IP Address Of The Local Server Is Configured Correctly So That It Will Match Against The Same AD Site As The Targeteed RODC..." "ERROR" Logging " * The Cause Of The Mismatch Can Be:" "ERROR" Logging " * The Specified IP Address IS NOT Correct..." "ERROR" Logging " * The Specified RODC IS NOT Correct..." "ERROR" Logging " * The AD Subnet For The Local Computer Is Linked To The Incorrect AD Site..." "ERROR" Logging "" Logging "Aborting Script..." "ERROR" Logging "" EXIT } ### Joining The Local Computer To The AD Domain Using The Specified Domain Join Options $returnErrorCode = $localComputer.JoinDomainOrWorkGroup($adDomain + "\" + $rodcFQDN, $compAccountPWD, $null, $null, $domainJoinOption) # List of 'system error codes' (http://msdn.microsoft.com/en-us/library/ms681381.aspx) and # List of 'network management error codes' (http://msdn.microsoft.com/en-us/library/aa370674(VS.85).aspx) $returnErrorDescription = switch ($($returnErrorCode.ReturnValue)) { 0 {"SUCCESS: The Operation Completed Successfully."} 5 {"FAILURE: Access Is Denied."} 53 {"FAILURE: The Network Path Was Not Found."} 64 {"FAILURE: The Specified Network Name Is No Longer Available."} 87 {"FAILURE: The Parameter Is Incorrect."} 1219 {"FAILURE: Logon Failure: Multiple Credentials In Use For Target Server."} 1326 {"FAILURE: Logon Failure: Unknown Username Or Bad Password."} 1355 {"FAILURE: The Specified Domain Either Does Not Exist Or Could Not Be Contacted."} 2691 {"FAILURE: The Machine Is Already Joined To The Domain."} default {"FAILURE: Unknown Error!"} } If ($($returnErrorCode.ReturnValue) -eq "0") { Logging "Domain Join Result Code...: $($returnErrorCode.ReturnValue)" "SUCCESS" Logging "Domain Join Result Text...: $returnErrorDescription" "SUCCESS" } Else { Logging "Domain Join Result Code...: $($returnErrorCode.ReturnValue)" "ERROR" Logging "Domain Join Result Text...: $returnErrorDescription" "ERROR" } If ($($returnErrorCode.ReturnValue) -eq "0") { Logging "" Logging "REMARK:" "REMARK" Logging " * The Computer Account Password Will Be Changed Shortly After The Domain Join!" "REMARK" Logging "" Logging "!!! THE COMPUTER WILL REBOOT AUTOMATICALLY IN 2 MINUTES !!!" "REMARK" Logging "" Logging "!!! TO STOP THE REBOOT USE THE COMMAND: SHUTDOWN /A !!!" "REMARK" SHUTDOWN /R /T 120 } Logging "" Logging "+++ FINISHED +++" "HEADER" Logging "------------------------------------------------------------------------------------------------------------------" "HEADER"

Have fun!

Cheers,

Jorge

———————————————————————————————

* This posting is provided "AS IS" with no warranties and confers no rights!

* Always evaluate/test yourself before using/implementing this!

* DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/

———————————————————————————————

############### Jorge’s Quest For Knowledge #############

######### http://JorgeQuestForKnowledge.wordpress.com/ ########

———————————————————————————————

Advertisement

Posted in Active Directory Domain Services (ADDS), Domain Join, PowerShell, Read-Only Domain Controller, Tooling/Scripting | 9 Comments »

(2014-04-04) Domain Join through an RODC instead of an RWDC (Update 1)

Posted by Jorge on 2014-04-04


UPDATE 2017-03-16: There is an updated version which you can find here

In the blog post (2009-01-01) Domain Join through an RODC instead of an RWDC I explained the so called read-only domain join against an RODC. In that blog post you will find a VBS script that helps you achieve that goal. Prior to the VBS script you see multiple ways of pre-creating the computer and having the password of the computer account replicate to the RODC.

In this blog post I provide a PowerShell script (don’t forget the execution policy on the server!) that performs the read-only domain join. You can get the PowerShell script through this link, or you can copy it from below.

Param( [string]$fqdnADdomain, # The FQDN Of the AD domain [string]$fqdnRODC, # The FQDN of the RODC to use [string]$computerAccountPWD # The password for the computer account ) Clear-Host Write-Host "*******************************************************************" -ForeGroundColor Yellow Write-Host "* --> Performing Read-Only Domain Join Against RODC <-- *" -ForeGroundColor Yellow Write-Host "* Written By: Jorge de Almeida Pinto [MVP-DS] *" -ForeGroundColor Yellow Write-Host "* https://jorgequestforknowledge.wordpress.com/ *" -ForeGroundColor Yellow Write-Host "*******************************************************************" -ForeGroundColor Yellow # Checking If All Parameters Are Available And Correct If (!($fqdnADdomain)) { Write-Host "" Write-Host "No FQDN Of An AD Domain Has Been Specified" -ForeGroundColor Red Write-Host "The FQDN Of An AD Domain Is Required!" -ForeGroundColor Red Write-Host "Aborting Script..." -ForeGroundColor Red Write-Host "" BREAK } If (!($fqdnRODC)) { Write-Host "" Write-Host "No FQDN Of An RODC Has Been Specified" -ForeGroundColor Red Write-Host "The FQDN Of An RODC Is Required!" -ForeGroundColor Red Write-Host "Aborting Script..." -ForeGroundColor Red Write-Host "" BREAK } If (!($computerAccountPWD)) { Write-Host "" Write-Host "No Computer Account Password Has Been Specified" -ForeGroundColor Red Write-Host "The Computer Account Password Specified During Pre-Creation Is Required!" -ForeGroundColor Red Write-Host "Aborting Script..." -ForeGroundColor Red Write-Host "" BREAK } # Defining Required Constants Set-Variable JOIN_DOMAIN -option Constant -value 1 # Joins a computer to a domain. If this value is not specified, the join is a computer to a workgroup Set-Variable MACHINE_PASSWORD_PASSED -option Constant -value 128 # The machine, not the user, password passed. This option is only valid for unsecure joins Set-Variable NETSETUP_JOIN_READONLY -option Constant -value 2048 # Use an RODC to perform the domain join against # Cumulative Value To Use $readOnlyDomainJoinOption = $JOIN_DOMAIN + $MACHINE_PASSWORD_PASSED + $NETSETUP_JOIN_READONLY # Getting Info From The Local Computer $localComputerSystem = Get-WMIObject Win32_ComputerSystem $computerName = $localComputerSystem.Name # Present The Gathered Information Write-Host "" Write-Host "" Write-Host "Trying To Perform A Read-Only Domain Join Using The Following Information..." -ForeGroundColor Yellow Write-Host "" Write-Host "FQDN AD Domain............: "$fqdnADdomain -ForeGroundColor Yellow Write-Host "FQDN RODC.................: "$fqdnRODC -ForeGroundColor Yellow Write-Host "Computer Name.............: "$computerName -ForeGroundColor Yellow Write-Host "Computer Account Password.: "$computerAccountPWD -ForeGroundColor Yellow Write-Host "" # Performing The Read-Only Domain Join $errorCode = $localComputerSystem.JoinDomainOrWorkGroup($fqdnADdomain+"\"+$fqdnRODC,$computerAccountPWD,$null,$null,$readOnlyDomainJoinOption) # Error Handling # List of 'system error codes' (http://msdn.microsoft.com/en-us/library/ms681381.aspx) and # List of 'network management error codes' (http://msdn.microsoft.com/en-us/library/aa370674(VS.85).aspx) $errorDescription = switch ($($errorCode.ReturnValue)) { 0 {"SUCCESS: The Operation Completed Successfully."} 5 {"FAILURE: Access Is Denied."} 53 {"FAILURE: The Network Path Was Not Found."} 64 {"FAILURE: The Specified Network Name Is No Longer Available."} 87 {"FAILURE: The Parameter Is Incorrect."} 1326 {"FAILURE: Logon failure: Unknown Username Or Bad Password."} 1355 {"FAILURE: The Specified Domain Either Does Not Exist Or Could Not Be Contacted."} 2691 {"FAILURE: The Machine Is Already Joined To The Domain."} default {"FAILURE: Unknown Error!"} } If ($($errorCode.ReturnValue) -eq "0") { Write-Host "Domain Join Result Code...: "$($errorCode.ReturnValue) -ForeGroundColor Green Write-Host "Domain Join Result Text...: "$errorDescription -ForeGroundColor Green } Else { Write-Host "Domain Join Result Code...: "$($errorCode.ReturnValue) -ForeGroundColor Red Write-Host "Domain Join Result Text...: "$errorDescription -ForeGroundColor Red } # Finishing Up Write-Host "" Write-Host "REMARK:" -ForeGroundColor Cyan Write-Host "The Computer Account Password Will Be Reset Shortly After The Domain Join!" -ForeGroundColor Cyan Write-Host "" Write-Host "###### FINISHED ######" Write-Host "-----------------------------------------------------" If ($($errorCode.ReturnValue) -eq "0") { Write-Host "" Write-Host "!!! THE COMPUTER WILL REBOOT AUTOMATICALLY IN 2 MINUTES !!!" -ForeGroundColor Cyan Write-Host "" Write-Host "!!! TO STOP THE REBOOT USE THE COMMAND: SHUTDOWN /A !!!" -ForeGroundColor Cyan SHUTDOWN /R /T 120 }

Have fun!

Cheers,

Jorge

———————————————————————————————

* This posting is provided "AS IS" with no warranties and confers no rights!

* Always evaluate/test yourself before using/implementing this!

* DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/

———————————————————————————————

############### Jorge’s Quest For Knowledge #############

######### http://JorgeQuestForKnowledge.wordpress.com/ ########

———————————————————————————————

Posted in Active Directory Domain Services (ADDS), Domain Join, PowerShell, Read-Only Domain Controller, Tooling/Scripting | 2 Comments »

(2009-01-01) Domain Join through an RODC instead of an RWDC

Posted by Jorge on 2009-01-01


Windows Server 2008 introduces one of the coolest features in AD, being IMHO the Read-Only Domain Controller (RODC). The main goal of the RODC is to improve the AD security and to mitigate risks. It is therefore also preferably deployed at the perimeter of the network. Based upon that, three different scenarios/deployments exist:

  • RODC in a Branch Office (= primary focus!)
  • RODC in the DMZ (under investigation by MS)
  • RODC on the internet (under investigation by MS)

With regards to the RODC, Microsoft created a planning and deployment guide. This guide can be found here. Additionally Microsoft also released a RODC Compatibility Pack which can be found here.

The RODC needs to be able to "talk" to a writable W2K8 DC (W2K8 RWDC) for replication, authentication forwarding, etc. In all three scenarios it is very feasible to place a firewall between RWDCs and RODCs, whereas only one or more RODCs can talk to one or more RWDCs. Clients and servers can make LDAP writes, which refer a client to a RWDC, or special writes which are forwarded by the RODC to an RWDC. For more info about this see the presentations HERE and HERE.

Joining clients to an AD domain is such an operation for which you normally require to contact an RWDC. However, in those scenarios where the Branch Office has been separated from the Datacenter by a firewall so that only the RODC can contact an RWDC, you cannot join clients in the normal way against an RODC. The reason? Well, only the RODC can contact an RWDC and the clients/servers cannot. Another way is to join the computer in the datacenter first and then ship it to the Branch Office. However, that may not be feasible. Another way is to join the computer against the RODC with a workaround. The steps for that are explained below.

On some RWDC perform the following steps:

(1) Pre-create a computer account and set a custom password on that same computer account:

  • From a command line: net computer \<NetBIOS Name Computer> /add & net user <NetBIOS Name Computer>$ <Password>
    (additional attributes must be set afterwards such as "dNSHostName", "servicePrincipalName"!) (Be aware that this way the "userAccountControl" attribute contains an incorrect value. I’m saying "incorrect" because the bit "Password Not Required is also set" and that’s something I do not like!)
    OR
  • From a command line: ADMOD -replacedn XXX-DOMAIN-XXX:_default -add -b "CN=<NetBIOS Name Computer>,OU=<SOME OU>,XXX-DOMAIN-XXX" "objectClass::computer" "sAMAccountName:: <NetBIOS Name Computer>$" "userAccountControl::4096" -kerbenc "unicodePwd::<Password>" "dNSHostName::<NetBIOS Name Computer>.%USERDNSDOMAIN%" "servicePrincipalName:++:HOST/<NetBIOS Name Computer>;HOST/<NetBIOS Name Computer>.%USERDNSDOMAIN%"

(2) Allow the password of the computer account to be cached on the RODC

  • Use the AD domain wide ALLOW group: "Allowed RODC Password Replication Group" (for ALL RODCs)
    • Using ADUC: By selecting the computer account of the RODC and selecting the tab "Password Replication Policy" and choosing AD domain wide ALLOW group to add the computer account to
      OR
    • From a command line: net localgroup "Allowed RODC Password Replication Group" <NetBIOS Name Computer>$ /add
      OR
    • From a command line: ADMOD -replacedn XXX-DOMAIN-XXX:_default -b "CN=Allowed RODC Password Replication Group,CN=Users,XXX-DOMAIN-XXX" "member::CN=<NetBIOS Name Computer>,OU=<SOME OU>,XXX-DOMAIN-XXX"

    OR

  • Use the ALLOW group which is specific to the single RODC only
    • Using ADUC: By selecting the computer account of the RODC and selecting the tab "Password Replication Policy" and choosing the RODC specific ALLOW group to add the computer account to
      OR
    • From a command line: net (local)group "<ALLOW group specific to RODC>" <NetBIOS Name Computer>$ /add
      OR
    • From a command line: ADMOD -replacedn XXX-DOMAIN-XXX:_default -b "CN=<ALLOW group specific to RODC>,OU=<SOME OU>,XXX-DOMAIN-XXX" "member::CN=<NetBIOS Name Computer>,OU=<SOME OU>,XXX-DOMAIN-XXX"

(3) Force the computer account to be cached at the RODC

  • Using ADUC: By selecting the computer account of the RODC and selecting the tab "Password Replication Policy" and clicking the Advanced button and clicking the Prepopulate Passwords button to pre-cache the password on the RODC
    OR
  • From a command line: REPADMIN /RODCPWDREPL <RODC> <RWDC> "CN=<NetBIOS Name Computer>,OU=<SOME OU>,DC=<DOMAIN>,DC=<TLD>"

On the client or server to be joined to the AD domain using an RODC implement the following script (DomainJoinAgainstRODC.vbs) and execute it:

Const JOIN_DOMAIN = 1 ' Joins a computer to a domain. If this value is not specified, the join is a computer to a workgroup Const ACCT_CREATE = 2 ' Creates an account on a domain Const ACCT_DELETE = 4 ' Deletes an account when a domain exists Const WIN9X_UPGRADE = 16 ' The join operation is part of an upgrade from Windows 98 or Windows 95 to Windows 2000 or Windows NT Const DOMAIN_JOIN_IF_JOINED = 32 ' Allows a join to a new domain, even if the computer is already joined to a domain Const JOIN_UNSECURE = 64 ' Performs an unsecured join Const MACHINE_PASSWORD_PASSED = 128 ' The machine, not the user, password passed. This option is only valid for unsecure joins Const DEFERRED_SPN_SET = 256 ' Writing SPN and DnsHostName attributes on the computer object should be deferred until the rename that follows the join Const NETSETUP_JOIN_READONLY = 2048 ' Use an RODC to perform the domain join against Const INSTALL_INVOCATION = 262144 ' The APIs were invoked during install strDomain = "ADCORP.LAB" ' The FQDN of the AD domain strRODC = "RFSRODC1.ADCORP.LAB" ' The FQDN of the RODC to use strPassword = "Pa$$w0rd" ' The custom password for the computer account Set objNetwork = CreateObject("WScript.Network") strComputer = objNetwork.ComputerName ' The NetBIOS name of the local computer Set objComputer = GetObject("winmgmts:{impersonationLevel=Impersonate}!\" & strComputer & "rootcimv2:Win32_ComputerSystem.Name='" & strComputer & "'") Wscript.echo("### STARTING ###") Wscript.echo("Trying to join the local computer to the AD domain using an RODC...") Wscript.echo("") ReturnValue = objComputer.JoinDomainOrWorkGroup(strDomain & "" & strRODC, strPassword, NULL, NULL, JOIN_DOMAIN+MACHINE_PASSWORD_PASSED+NETSETUP_JOIN_READONLY) ' List of 'system error codes' (http://msdn.microsoft.com/en-us/library/ms681381.aspx) and ' List of 'network management error codes' (http://msdn.microsoft.com/en-us/library/aa370674(VS.85).aspx) Select Case ReturnValue Case 0 strErrDescr = "The operation completed successfully" Case 5 strErrDescr = "Access is denied" Case 87 strErrDescr = "The parameter is incorrect" Case 1326 strErrDescr = "Logon failure: unknown username or bad password" Case 1355 strErrDescr = "The specified domain either does not exist or could not be contacted" Case 2691 strErrDescr = "The machine is already joined to the domain" End Select Wscript.echo("FQDN AD Domain : '" & strDomain & "'") Wscript.echo("FQDN RODC : '" & strRODC & "'") Wscript.echo("Local Computer Name : '" & strComputer & "." & strDomain & "'") Wscript.echo("Domain Join Result Code : '" & ReturnValue & "'") Wscript.echo("Domain Join Result Text : '" & strErrDescr & "'") Wscript.echo("") Wscript.echo("### FINISHED ###")

Execute the VBS script: CSCRIPT DomainJoinAgainstRODC.vbs

Depending in the current condition of the client you may receive an output similar to the picture below.

image

When:

Domain Join Result Code : ‘0’

Domain Join Result Text : ‘The operation completed successfully’

Then the client is successfully joined to the AD domain!

Let’s analyze a few parts of the script….

(1) The name of the RODC is specified. Why is this required? When a client/server joins an AD domain it queries DNS for DCs that have registered the domain-wide SRV resource records. RODCs by default DO NOT register domain-wide SRV resource records, only site-wide SRV resource records! Because of that, you must specify in the script WHICH DC the client/server must contact for the domain join

(2) You must specify the custom password that has been configured on the computer account in AD

(3) You need to specify the option ‘JOIN_DOMAIN’ to make sure it joins a DOMAIN and not a WORKGROUP

(4) You need to specify the option ‘MACHINE_PASSWORD_PASSED’ to make sure you pass the password that has been configured on the AD account and NOT credentials that are allowed to make the join. The authentication/authorization of the domain join occurs by just knowing the password that has been configured on the AD computer account!

(5) You need to specify the option ‘NETSETUP_JOIN_READONLY’ to make the join is made in READ-ONLY mode

The procedure above works for: Windows Vista (and later), Windows Server 2008 (and later), Windows XP Professional SP2 (and later), Windows Server 2003 SP1 (and later). For both Windows XP Professional and Windows Server 2003 the hotfix specified in MS-KBQ944043_Description of the Windows Server 2008 read-only domain controller compatibility pack for Windows Server 2003 clients and for Windows XP clients *MUST* be installed! You will get a "Domain Join Result Code" of 87 (The parameter is incorrect) when the hotfix is NOT installed! Make sure it is installed!

More information:

Windows 7 (Windows Server 2008 R2) will provide an even cooler way of joining clients/servers to an AD domain. More of that later, so stay tuned!

Cheers,

Jorge

———————————————————————————————

* This posting is provided "AS IS" with no warranties and confers no rights!

* Always evaluate/test yourself before using/implementing this!

* DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/

———————————————————————————————

############### Jorge’s Quest For Knowledge #############

#########

http://JorgeQuestForKnowledge.wordpress.com/ ########

———————————————————————————————

Posted in Active Directory Domain Services (ADDS), Domain Join, Read-Only Domain Controller, Tooling/Scripting, VB Script | 22 Comments »

 
%d bloggers like this: