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 ‘PowerShell’ Category

(2019-02-22) Getting Rid Of Self-Signed Certs In Intermediate CA Store

Posted by Jorge on 2019-02-22


In the case of ADFS servers you may end up with self-signed certificates in the Intermediate CA store. Those self-signed certificates are most likely root CA certificates and those should be move to the root CA store.

After running the “Export-AdfsDiagnosticsFile” CMDlet on the (primary) ADFS server and uploading it to ADFS Help – Diagnostics Analyzer you might see something like shown below

image

Figure 1: Self-Signed Certificates Reported In The Intermediate CA Store By The ADFS Help Tool

With PowerShell you can find self-signed certificates by checking certificates and see if the subject is the same as the issuer

Get-ChildItem <Certificate Store> | ?{$_.Issuer -eq $_.Subject}

image

Figure 2: Self-Signed Certificates In The Intermediate CA Store

First you need to identity if any self-signed certificate is not a root CA certificate. In that case you can most likely remove that certificate from the intermediate CA store.

To remove a certificate from a certificate store you can use:

Get-ChildItem <Certificate Store>\<Certficate Thumbprint> | Remove-Item

Anything left can be moved from the intermediate CA store to the root CA store. To do that you can use the following:

$sourceCertStore = "Cert:\LocalMachine\CA"
$sourceCertStoreObject = Get-Item $sourceCertStore
$targetCertStore = "Cert:\LocalMachine\Root"
$targetCertStoreObject = Get-Item $targetCertStore
Get-ChildItem $sourceCertStore | ?{$_.Issuer -eq $_.Subject} | %{
    $thumbprint = $null
    $thumbprint = $_.Thumbprint
    $cert = $null
    $cert = Get-Item $sourceCertStore\$thumbprint
    If (!(Test-Path $targetCertStore\$thumbprint)) {
        Write-Host ""
        Write-Host "Adding Certificate With Thumbprint ‘$thumbprint’ To ‘$targetCertStore’" -ForegroundColor green
        $targetCertStoreObject.Open("ReadWrite")
        $targetCertStoreObject.Add($cert)
        $targetCertStoreObject.Close()
    } Else {
        Write-Host ""
        Write-Host "Certificate With Thumbprint ‘$thumbprint’ Already Exists In ‘$targetCertStore’" -ForegroundColor Red
    }
    If (Test-Path $targetCertStore\$thumbprint) {
        Write-Host "Removing Certificate With Thumbprint ‘$thumbprint’ From ‘$sourceCertStore’" -ForegroundColor green
        $sourceCertStoreObject.Open("ReadWrite")
        $sourceCertStoreObject.Remove($cert)
        $sourceCertStoreObject.Close()
    }
}

image

Figure 3: Moving Certificates From The Intermediate CA Store To The Root CA Store

Have fun!

Cheers,
Jorge

————————————————————————————————————————————————————-
This posting is provided "AS IS" with no warranties and confers no rights!
Always evaluate/test everything yourself first before using/implementing this in production!
This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years!
DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
————————————————————————————————————————————————————-
########################### Jorge’s Quest For Knowledge ##########################
####################
http://JorgeQuestForKnowledge.wordpress.com/ ###################
————————————————————————————————————————————————————-

Posted in Active Directory Federation Services (ADFS), Certificates, PowerShell | Leave a Comment »

(2019-02-21) Code Snippets: Testing For Admin Credentials

Posted by Jorge on 2019-02-21


In your scripts you may need to test the current account used by the script for admin credentials, either local admin, domain admin or even enterprise admin. If you really want to be specific you may want to test if a user is a member of a specific group. In the latter case you use a group that was defined by you, hence the group name being targeted is specific and known. However, if you need to test being a member of the default admin groups in Windows or AD and you are in a global environment, you may need to cope with language differences. For example local “administrators” in dutch is “beheerders” and in portuguese it is “administradores”. Under the hood there is a common thing that can be used and that is the SID of that group which is universal and language independent. So, how to use that in PowerShell scripts?

First things first…

Defining the main function….

### FUNCTION: Test Credentials For Specific Admin Role
Function testAdminRole($adminRole) {
    # Determine Current User
    $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
   
    Write-Host ""
    Write-Host "The Current User Is: ‘$($currentUser.Name)’…" -ForeGroundColor Yellow
    Write-Host ""
   
    # Check The Current User Is In The Specified Admin Role
    (New-Object Security.Principal.WindowsPrincipal $currentUser).IsInRole($adminRole)
}

Now to test for local admin credentials…

The local administrators group always has the SID “S-1-5-32-544”, no matter what it is called. Therefore based upon the (object)SID of the local administrators group we need to translate it into a a group name.

### Test For Local Admin Credentials
$localAdminSID = "S-1-5-32-544"
$localAdminRoleName = (New-Object System.Security.Principal.SecurityIdentifier($localAdminSID)).Translate([System.Security.Principal.NTAccount]).Value
$userIsLocalAdmin = $null
$userIsLocalAdmin = testAdminRole $localAdminRoleName
If (!$userIsLocalAdmin) {
    Write-Host ""
    Write-Host "Your User Account IS NOT Running With Local Administrator Equivalent Permissions!…" -ForeGroundColor Red
    Write-Host "The user account IS NOT a member of ‘$localAdminRoleName’!…" -ForeGroundColor Red
    Write-Host "Aborting Script…" -ForeGroundColor Red
    Write-Host ""
} Else {
    Write-Host ""
     Write-Host "Your User Account IS Running With Local Administrator Equivalent Permissions!…" -ForeGroundColor Green
    Write-Host "The user account IS a member of ‘$localAdminRoleName’!…" -ForeGroundColor Green
    Write-Host "Continuing Script…" -ForeGroundColor Green
    Write-Host ""
}

image

Figure 1: Testing For Local Admin – Failing

image

Figure 2: Testing For Local Admin – Succeeding

Now to test for domain admin credentials…

The domain administrators group is AD domain specific but always has the RID “512”, no matter what it is called. Therefore based upon the AD domain based (object)SID of the domain admins group we need to translate it into a a group name.

### Test For Domain Admin Credentials
$targetedDomainFQDN = "IAMTEC.NET"
$targetedDomain = Get-ADdomain $targetedDomainFQDN
$targetedDomainObjectSID = $targetedDomain.DomainSID.Value
$domainAdminRID = "512"
$domainAdminObjectSID = $targetedDomainObjectSID + "-" + $domainAdminRID
$domainAdminRoleName = (New-Object System.Security.Principal.SecurityIdentifier($domainAdminObjectSID)).Translate([System.Security.Principal.NTAccount]).Value
$userIsDomainAdmin = $null
$userIsDomainAdmin = testAdminRole $domainAdminRoleName
If (!$userIsDomainAdmin) {
    Write-Host ""
    Write-Host "Your User Account IS NOT Running With Domain Administrator Equivalent Permissions!…" -ForeGroundColor Red
    Write-Host "The user account IS NOT a member of ‘$domainAdminRoleName’!…" -ForeGroundColor Red
    Write-Host "Aborting Script…" -ForeGroundColor Red
    Write-Host ""
} Else {
    Write-Host ""
    Write-Host "Your User Account IS Running With Domain Administrator Equivalent Permissions!…" -ForeGroundColor Green
    Write-Host "The user account IS a member of ‘$domainAdminRoleName’!…" -ForeGroundColor Green
    Write-Host "Continuing Script…" -ForeGroundColor Green
    Write-Host ""
}

image

Figure 3: Testing For Domain Admin – Failing

image

Figure 4: Testing For Domain Admin – Succeeding

Now to test for enterprise admin credentials…

The enterprise administrators group is specific to the forest root AD domain of an AD forest but always has the RID “519”, no matter what it is called. Therefore based upon the forest root AD domain based (object)SID of the enterprise admins group we need to translate it into a a group name.

### Test For Enterprise Admin Credentials
$adForest = Get-ADForest
$targetedRootDomainFQDN = $adForest.RootDomain
$targetedRootDomain = Get-ADdomain $targetedRootDomainFQDN
$targetedRootDomainObjectSID = $targetedDomain.DomainSID.Value
$enterpriseAdminRID = "519"
$enterpriseAdminObjectSID = $targetedRootDomainObjectSID + "-" + $enterpriseAdminRID
$enterpriseAdminRoleName = (New-Object System.Security.Principal.SecurityIdentifier($enterpriseAdminObjectSID)).Translate([System.Security.Principal.NTAccount]).Value
$userIsEnterpriseAdmin = $null
$userIsEnterpriseAdmin = testAdminRole $enterpriseAdminRoleName
If (!$userIsEnterpriseAdmin) {
    Write-Host ""
    Write-Host "Your User Account IS NOT Running With Enterprise Administrator Equivalent Permissions!…" -ForeGroundColor Red
    Write-Host "The user account IS NOT a member of ‘$enterpriseAdminRoleName’!…" -ForeGroundColor Red
    Write-Host "Aborting Script…" -ForeGroundColor Red
    Write-Host ""
} Else {
    Write-Host ""
    Write-Host "Your User Account IS Running With Enterprise Administrator Equivalent Permissions!…" -ForeGroundColor Green
    Write-Host "The user account IS a member of ‘$enterpriseAdminRoleName’!…" -ForeGroundColor Green
    Write-Host "Continuing Script…" -ForeGroundColor Green
    Write-Host ""
}

image

Figure 5: Testing For Enterprise Admin – Failing

image

Figure 6: Testing For Enterprise Admin – Succeeding

Have fun!

Cheers,
Jorge

————————————————————————————————————————————————————-
This posting is provided "AS IS" with no warranties and confers no rights!
Always evaluate/test everything yourself first before using/implementing this in production!
This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years!
DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
————————————————————————————————————————————————————-
########################### Jorge’s Quest For Knowledge ##########################
####################
http://JorgeQuestForKnowledge.wordpress.com/ ###################
————————————————————————————————————————————————————-

Posted in PowerShell | Leave a Comment »

(2018-11-06) Meet Poshkatz – Mimikatz On PowerShell Steroids

Posted by Jorge on 2018-11-06


We all know Mimikatz right? If you do not, Mimikatz is a tool that really can do some geeky funky stuff with regards to Windows/AD security. It can do stuff like: extract plaintexts passwords, hash, PIN code and kerberos tickets from memory. mimikatz can also perform pass-the-hash, pass-the-ticket or build Golden tickets.

Now meet Poshkatz! Poshkatz is a PowerShell module/wrapper around Mimikatz with tab completion! The module was created by Adam Driscoll and Lee Berg

Enjoy!

Cheers,
Jorge

————————————————————————————————————————————————————-
This posting is provided "AS IS" with no warranties and confers no rights!
Always evaluate/test everything yourself first before using/implementing this in production!
This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years!
DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
————————————————————————————————————————————————————-
########################### Jorge’s Quest For Knowledge ##########################
####################
http://JorgeQuestForKnowledge.wordpress.com/ ###################
————————————————————————————————————————————————————-

Posted in PowerShell | Leave a Comment »

(2018-10-24) Setting/Fixing The Correct Permissions On Your ADFS Certificates And/Or On The Certificate Sharing Container

Posted by Jorge on 2018-10-24


Are the permissions on any of the private keys of your certificates in use by ADFS and/or the certificate sharing container screwed up? If YES, then you can use the PowerShell code below to fix those permissions for the account the ADFS Service is running under. Make sure to test this is a test environment first!

### Function To Permission Private Key Of Custom/Non-ADFS Managed Certificates
Function permissionPrivateKey($certificate,$certType,$svcAccount) {
    $ace = $svcAccount,"Read,Synchronize","Allow"
    $machineKeysLocation = $ENV:ALLUSERSPROFILE + "\Microsoft\Crypto\RSA\MachineKeys\"
    $CertKeyFile = $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
    $CertKeyFileFullPath = $MachineKeysLocation + $CertKeyFile
    $CertKeyFileACL = Get-Acl $CertKeyFileFullPath
    Write-Host "+++ RESULT +++" -ForeGroundColor Magenta
    $certSubject = $null
    $certSubject = $certificate.Subject
    Write-Host "Certificate Type……..: $certType" -ForegroundColor Yellow
    Write-Host "Certificate Subject…..: $certSubject" -ForegroundColor Yellow
    Write-Host ""
    Write-Host "Private Key Permissions (BEFORE)" -ForegroundColor Yellow
    $CertKeyFileACL.Access | FT @{n='[Account]’;e={$_.IdentityReference}},@{n='[Control]’;e={$_.AccessControlType}},@{n='[Permissions]’;e={$_.FileSystemRights}}
    $accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $ace
    $CertKeyFileACL.SetAccessRule($accessRule)
    $CertKeyFileACL | Set-Acl $CertKeyFileFullPath
    $CertKeyFileACL = $null
    $CertKeyFileACL = Get-Acl $CertKeyFileFullPath
     Write-Host ""
    Write-Host "Private Key Permissions (AFTER)" -ForegroundColor Yellow
    $CertKeyFileACL.Access | FT @{n='[Account]’;e={$_.IdentityReference}},@{n='[Control]’;e={$_.AccessControlType}},@{n='[Permissions]’;e={$_.FileSystemRights}}
}
   
### Determining Current ADFS Service Account
$adfsService = Get-WmiObject win32_service -filter "name=’ADFSSRV’"
$currentADFSServiceAccount = $adfsService.StartName

### Determining Database Type Being Used
$adfsSTS = Get-WmiObject -namespace root/ADFS -class SecurityTokenService
$configDBConnectionString = $adfsSTS.ConfigurationDatabaseConnectionString
If ($configDBConnectionString.contains("\\.\pipe\")) {
    Write-Host ""
    Write-Host "Windows Internal Database Is Being Used" -ForegroundColor Yellow
     $dbType = "WID"
    $roleADFSServer = (Get-AdfsSyncProperties).Role
    If ($roleADFSServer -eq "SecondaryComputer") {
         Write-Host ""
        Write-Host "Running On Secondary ADFS Server" -ForegroundColor Yellow
        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 {
                $ErrorActionPreference = "SilentlyContinue"
                $tcpPortSocket.EndConnect($portConnect) | Out-Null
                If (!$?) {
                    Return "ERROR"
                } Else {
                    Return "SUCCESS"
                }
                 $tcpPortSocket.Close()
                $ErrorActionPreference = "Continue"
            }
        }
        $primaryADFSServer = (Get-AdfsSyncProperties).PrimaryComputerName
        $adfsWIDIsSecondary = $true
    } Else {
        Write-Host "Running On Primary ADFS Server" -ForegroundColor Yellow
        $adfsWIDIsSecondary = $false
    }
} Else {
    Write-Host "SQL Server Is Being Used" -ForegroundColor Yellow
    $dbType = "SQL"
}

### Getting ADFS Properties And Certificates
If ($dbType -eq "WID") {
    If ($adfsWIDIsSecondary) {
        $ports = 5985,443,80 # WinRM For Remote PowerShell, ADFS HTTPS, ADFS HTTP
        $connectionCheckOK = $true
        $ports | %{
            $port = $null
            $port = $_
            $connectionResult = $null
            $connectionResult = portConnectionCheck $primaryADFSServer $port 500
            If ($connectionResult -eq "ERROR") {
                $connectionCheckOK = $false
            }
        }
        If ($connectionCheckOK) {
            $primaryADFSServerSession = New-PSSession -ComputerName $primaryADFSServer
            $fedSvcConfig = Invoke-Command -Session $primaryADFSServerSession -ScriptBlock {
                $fedSvcProperties = Get-AdfsProperties
                $fedSvcCertificates = Get-AdfsCertificate
                Return $fedSvcProperties,$fedSvcCertificates
            }
            Remove-PSSession $primaryADFSServerSession
            $fedSvcProps = $fedSvcConfig[0]
            $fedSvcCerts = $fedSvcConfig[1]
        } Else {
             Write-Host ""
            Write-Host "The Primary ADFS Server IS NOT Reachable" -ForegroundColor Red
            Write-Host "Aborting…" -ForegroundColor Red
            Write-Host ""
            EXIT
        }
    } Else {
        $fedSvcProps = Get-AdfsProperties
        $fedSvcCerts = Get-AdfsCertificate
     }
}
If ($dbType -eq "SQL") {
    $fedSvcProps = Get-AdfsProperties
    $fedSvcCerts = Get-AdfsCertificate
}

### Checking If Certificate Sharing Container Has Been Configured
If ($fedSvcProps.CertificateSharingContainer) {
    Write-Host ""
    Write-Host "Certificate Sharing Container IS Configured" -ForegroundColor Yellow
    $certSharingContainerConfigured = $true
    $certSharingContainerState = "Configured"
    $certSharingContainerDN = ($fedSvcProps.CertificateSharingContainer).ToString()
} Else {
     Write-Host ""
    Write-Host "Certificate Sharing Container IS NOT Configured" -ForegroundColor Yellow
    $certSharingContainerConfigured = $false
    $certSharingContainerState = "NOT Configured"
    $certSharingContainerDN = "N.A."
}

### Checking If Custom Certificates Are Being Used Or ADFS Managed Self-Signed Certificates For Token Signing/Encryption
If ($fedSvcProps.AutoCertificateRollover -eq $false) {
    Write-Host ""
    Write-Host "ADFS Managed Self-Signed Certs ARE NOT Being Used" -ForegroundColor Yellow
    $adfsManagedCerts = $false
    $certManagement = "NON-ADFS Managed"
} ElseIf ($fedSvcProps.AutoCertificateRollover -eq $true) {
    Write-Host ""
    Write-Host "ADFS Managed Self-Signed Certs ARE Being Used" -ForegroundColor Yellow
    $adfsManagedCerts = $true
    $certManagement = "ADFS Managed"
}

### Displaying All The Info Gathered
Invoke-Command -ScriptBlock {
    Write-Host ""
    Write-Host "Database Type………………………: $dbType" -ForegroundColor Yellow
    Write-Host ""
    Write-Host "Certificate Sharing Container State…..: $certSharingContainerState" -ForegroundColor Yellow
    Write-Host ""
    Write-Host "Certificate Sharing Container DN……..: $certSharingContainerDN" -ForegroundColor Yellow
    Write-Host ""
     Write-Host "Certificate Management………………: $certManagement" -ForegroundColor Yellow
    Write-Host ""
    Write-Host "Current Service Account……………..: $currentADFSServiceAccount" -ForegroundColor Yellow
    Write-Host ""
    Write-Host "Token-Signing Certificate(s)…" -ForegroundColor Yellow
    $fedSvcTokenSigningCert = $fedSvcCerts | ?{$_.CertificateType -eq "Token-Signing"}
    $fedSvcTokenSigningCert | FT Thumbprint,@{n=’Subject’;e={$_.Certificate.Subject}},@{n=’Not Before’;e={$_.Certificate.NotBefore}},@{n=’Not After’;e={$_.Certificate.NotAfter}}
    Write-Host "Token-Encryption Certificate(s)…" -ForegroundColor Yellow
    $fedSvcTokenEncryptCert = $fedSvcCerts | ?{$_.CertificateType -eq "Token-Decrypting"}
    $fedSvcTokenEncryptCert | FT Thumbprint,@{n=’Subject’;e={$_.Certificate.Subject}},@{n=’Not Before’;e={$_.Certificate.NotBefore}},@{n=’Not After’;e={$_.Certificate.NotAfter}}
    Write-Host "Service Communications / SSL Certificate…" -ForegroundColor Yellow
    $fedSvcSvcCommCert = $fedSvcCerts | ?{$_.CertificateType -eq "Service-Communications"}
    $fedSvcSvcCommCert | FT Thumbprint,@{n=’Subject’;e={$_.Certificate.Subject}},@{n=’Not Before’;e={$_.Certificate.NotBefore}},@{n=’Not After’;e={$_.Certificate.NotAfter}}
}

### Setting Permissions On Private Key Of Custom Token Certificates
If (!$adfsManagedCerts) {
    Write-Host ""
    Write-Host "Permissioning Custom Token Certificates" -ForegroundColor Yellow
    $certStoreLocation = "Cert:\LocalMachine\My"
    $fedSvcCerts | ?{$_.CertificateType -ne "Service-Communications"} | %{
        $certificateType = $null
        $certificateType = $_.CertificateType
        $certificateThumbprint = $null
        $certificateThumbprint = $_.Thumbprint
        $certificateInStore = $null
        $certificateInStore = Get-ChildItem $($certStoreLocation + "\" + $certificateThumbprint) -ErrorAction SilentlyContinue
        If ($certificateInStore -And $certificateInStore.HasPrivateKey) {
            permissionPrivateKey $certificateInStore $certificateType $currentADFSServiceAccount
        } Else {
             Write-Host ""
            Write-Host "The Certificate With Thumbprint ‘$certificateThumbprint’ ($certificateType) Does Not Exist" -ForegroundColor Red
            Write-Host "Or" -ForegroundColor Red
            Write-Host "The Certificate With Thumbprint ‘$certificateThumbprint’ ($certificateType) Does Not Have A Private Key" -ForegroundColor Red
            Write-Host ""
        }
    }
}

### Setting Permissions On Private Key Of SSL/Service-Communication Certificate
$fedSvcCerts | ?{$_.CertificateType -eq "Service-Communications"} | %{
    $certificateType = $null
    $certificateType = $_.CertificateType
    $certificateThumbprint = $null
    $certificateThumbprint = $_.Thumbprint
     $certificateInStore = $null
    $certificateInStore = Get-ChildItem $($certStoreLocation + "\" + $certificateThumbprint) -ErrorAction SilentlyContinue
    If ($certificateInStore -And $certificateInStore.HasPrivateKey) {
        permissionPrivateKey $certificateInStore $certificateType $currentADFSServiceAccount
    } Else {
        Write-Host ""
        Write-Host "The Certificate With Thumbprint ‘$certificateThumbprint’ ($certificateType) Does Not Exist" -ForegroundColor Red
        Write-Host "Or" -ForegroundColor Red
        Write-Host "The Certificate With Thumbprint ‘$certificateThumbprint’ ($certificateType) Does Not Have A Private Key" -ForegroundColor Red
        Write-Host ""
    }
}

### If Configured Setting Permissions On Certificate Sharing Container
### WARNING: ADDS PowerShell Module Required!!! – Will Be Installed!
If ($certSharingContainerConfigured) {
    Write-Host ""
    Write-Host "Certificate Sharing Container Is Configured Used" -ForegroundColor Yellow

    # Install RSAT PowerShell Tools And Load Module
    Install-WindowsFeature RSAT-AD-Tools -IncludeAllSubFeature
     Import-Module ActiveDirectory

    # Get The Certificate Sharing Container DN, Its ACL And Present The Results Of That
    $dnPathCertSharingContainer = ($fedSvcProps.CertificateSharingContainer).ToString()
    $dnPathCertSharingContainerPath = $(Join-Path "AD:\" $dnPathCertSharingContainer)
    Write-Host ""
    Write-Host "Certificate Sharing Container Permissions (BEFORE)" -ForegroundColor Yellow
    $aclCertSharingContainer = Get-Acl $dnPathCertSharingContainerPath
    $aclCertSharingContainer.Access | FT @{n='[Account]’;e={$_.IdentityReference}},@{n='[Control]’;e={$_.AccessControlType}},@{n='[Permissions]’;e={$_.ActiveDirectoryRights}}
   
    # Object Class And Inheritance Scope
    $schemaIDGUIDScopedObject = [guid]"00000000-0000-0000-0000-000000000000"
    $inheritanceScope = "All"

    # Security Principal To Assign Permissions To
    $securityPrincipalAccount = $currentADFSServiceAccount
    $securityPrincipalObject = New-Object System.Security.Principal.NTAccount($securityPrincipalAccount)

    # Define ACE
    $rightsCollection = [System.DirectoryServices.ActiveDirectoryRights]::"CreateChild","Self","WriteProperty","DeleteTree","GenericRead","WriteOwner"
    $aclType = [System.Security.AccessControl.AccessControlType]::"Allow"
    $aceDefinition = $securityPrincipalObject,$rightsCollection,$aclType,$schemaIDGUIDScopedObject,$inheritanceScope
    $accessRule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($aceDefinition)   

    # Set The New Access Rule
    $aclCertSharingContainer.AddAccessRule($accessRule)
    $aclCertSharingContainer | Set-Acl $dnPathCertSharingContainerPath

    # Get The Certificate Sharing Container ACL And Present The Results Of That
    Write-Host ""
    Write-Host "Certificate Sharing Container Permissions (AFTER)" -ForegroundColor Yellow
    $aclCertSharingContainer = Get-Acl $dnPathCertSharingContainerPath
    $aclCertSharingContainer.Access | FT @{n='[Account]’;e={$_.IdentityReference}},@{n='[Control]’;e={$_.AccessControlType}},@{n='[Permissions]’;e={$_.ActiveDirectoryRights}}
    $aclCertSharingContainer.Access | ?{$_.IdentityReference -eq $securityPrincipalAccount} | FT @{n='[Account]’;e={$_.IdentityReference}},@{n='[Control]’;e={$_.AccessControlType}},@{n='[Permissions]’;e={$_.ActiveDirectoryRights}}
    $aclCertSharingContainer.Access | ?{$_.IdentityReference -eq $securityPrincipalAccount}
}

Cheers,
Jorge

————————————————————————————————————————————————————-
This posting is provided "AS IS" with no warranties and confers no rights!
Always evaluate/test everything yourself first before using/implementing this in production!
This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years!
DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
————————————————————————————————————————————————————-
########################### Jorge’s Quest For Knowledge ##########################
####################
http://JorgeQuestForKnowledge.wordpress.com/ ###################
————————————————————————————————————————————————————-

Posted in Active Directory Federation Services (ADFS), Certificates, PowerShell | Leave a Comment »

(2017-06-19) Azure AD Connect Health For Sync To The Rescue

Posted by Jorge on 2017-06-19


I was playing around with some Azure AD (AAD) features and I required a new synched account in AAD. So I picked one of my existing accounts in the on-premises AD and added the user account to the groups that allowed it to sync to Azure AD, assign it a license for O365, assign a license for EMS and allow it to use SSPR. Instead of waiting for the next sync cycle I decided to force a delta sync within AAD Connect. So far so good. While AAD Connect was synching I decided to prepare some other things.

To my surprise I saw the following error in AAD Connect

image

Figure 1: Error In AAD Connect About “AttributeValueMustBeUnique”

Clicking on [Detail] I saw the following details regarding the error

image

Figure 2: Error Details In AAD Connect About “AttributeValueMustBeUnique”

ERROR = AttributeValueMustBeUnique
Unable to update this object because the following attributes associated with this object have values that may already be associated with another object in your local directory services: [ProxyAddresses SMTP:John.Doe@XXXXX;]. Correct or remove the duplicate values in your local directory. Please refer to
http://support.microsoft.com/kb/2647098 for more information on identifying objects with duplicate attribute values.
Tracking Id: xxxxxxxxxxxxxxxxxxxxxxxxxx
ExtraErrorDetails:
[{"Key":"ObjectId","Value":["xxxxxxxxxxxxxxxxxxxxxxxxx"]},{"Key":"ObjectIdInConflict","Value":["yyyyyyyyyyyyyyyyyyyyyy"]},{"Key":"AttributeConflictName","Value":["ProxyAddresses"]},{"Key":"AttributeConflictValues","Value":["SMTP:John.Doe@XXXXX"]}]

This is basically saying there is another object in AAD with the same value of "SMTP:John.Doe@XXXXX" in the proxyAddresses attribute

Now that was weird because the object with UPN ‘John.Doe@XXXXX’ was never synched to AAD as a user. To be sure I checked if there already was a user with the same UPN and/or proxyAddresses. As I expected there was none. I also knew for sure there was no conflicting object in the on-premises AD. Nevertheless I checked it with ADFIND (instead of PowerShell because hadn’t used for quite some time!). And again as expected there was none! So what the heck is going on!?

First I will tell you what was wrong and why, and after that I will guide you through troubleshooting

At this point in time I’m always using AAD Connect to sync stuff from AD to AAD. I also have a MIM Infrastructure using the AAD connector that also syncs stuff from AD to AAD and more specifically users, groups and contacts. However I had not used that for quite some time. What I had forgotten was that in the past I synched my mailbox enabled user accounts to AAD (Office 365) as contacts. So almost all on-premises users exist as contacts in AAD.

Definition of the issue:

The error in figure 2 is misleading as it is telling me to check my on-premises AD to resolve the conflict, but there is NO conflict in my on-premises AD!

Just to be sure I used:

  • ADFIND/PowerShell to query for any object with the same UPN value or the same proxyAddress value – Only 1 object was returned which was expected
  • IDFIX to check for conflicts – No conflicts found for this object in the on-premises AD – This was expected
  • Directory Synchronization Troubleshooter to check for conflicts – No conflicts found for this object in the on-premises AD – This was expected

Now it was time to check AAD. It had to be in AAD somewhere, but what was causing it!? I was looking at all troubleshooting capabilities.

I found the following blog posts that ONLY focus on user accounts in AAD

Looking at the error in figure 2:

  • “ObjectId” contains the objectID of the object I was trying to sync to AAD
  • “ObjectIdInConflict” contains the objectID of the object already in AAD with which the conflict was being caused

Therefore I needed to query AAD using the objectID in “ObjectIdInConflict”. I decided to use the CMDlets in the MSONLINE module

To look for users in AAD with that same objectID, I executed:

Get-MsolUser -ObjectId <objectid value in “ObjectIdInConflict”>

No result

To look for groups in AAD with that same objectID, I executed:

Get-MsolGroup -ObjectId <objectid value in “ObjectIdInConflict”>

No result

I suddenly remembered about my MIM infrastructure and what it was synching, so I decided to also execute:

Get-MsolContact -ObjectId <objectid value in “ObjectIdInConflict”>

Bingo! One object was returned! Yeah!

Now I do not have many objects. Howeverm if you do have many object, this might take quite some time as unfortunately the MSONLINE module does not have a CMDlet like ‘Get-MsolObject’. However, the AzureAD module does have such a CMDlet that allows you to search across object types, being:

Get-AzureADObjectByObjectId -ObjectIds <objectid value in “ObjectIdInConflict”>

Again, Bingo! One object was returned! Yeah!

I also received the following e-mail telling me about the conflict. To be honest, the mail is not really helpful from an informational perspective. It is however from a warning perspective. With the latter I mean it triggers me to go look in Azure AD Connect Health, which will tell me what is wrong and why.

image

Figure 3: Mail From AAD Telling Me About the Conflict Error

Another option for troubleshooting this is Azure AD Connect Health for Sync. Trust me when I say that Azure AD Connect Health tells what is wrong and why. It showed me the sync error including all the specifics around the error in one view. Really really helpful! Let’s have a look at this!

Go to the Azure AD Portal (https://portal.azure.com/) and then go to the Azure AD Connect Health blade. In my case I have Azure AD Connect Health for Sync, ADFS and AD. So should you!. Make it easy on yourself!

Although Azure AD Connect Sync tile so as healthy, to its left, does tell you there is a sync error. Click on that tile.

image

Figure 4: Azure AD Connect Health In The Azure AD Portal

A new window opens with all the sync errors by type. In this case it is about the “Duplicate Attribute” issue. I clicked on that tile

image

Figure 5: Azure AD Connect Health For Sync With Errors By Type

A new window opens with all the sync errors about “Duplicate Attributes”. In my case I had and saw an error about a duplicate value in the proxyAddresses attribute. I clicked on the listed object.

image

Figure 6: Azure AD Connect Health For Sync With A “Duplicate Attribute” Error For An Object

A new window opens with all the details about the “Duplicate Attribute” conflict and the objects involved. At the bottom you can see the conflicting attribute and the conflicting value. Now I know I have to get rid of the contact object to make it possible for the user account in AD to get a corresponding user account in AAD.

image

Figure 7: Azure AD Connect Health For Sync With “Duplicate Attribute” Error Details

BINGO!!! Smile

Now my suggestion for this is:

  • Make sure to get Azure AD Connect Health up and running
  • As soon as you see an error in AAD Connect (figure 1 and 2) or receive the e-mail (figure 3) about it, make sure to read it carefully. At this point in time I would say to go right away to the Azure AD Connect Health blade in the Azure AD Portal and check there what could be wrong. It is very likely it will tell you what is wrong and why!

Additional information:

Cheers,
Jorge

 

————————————————————————————————————————————————————-
This posting is provided "AS IS" with no warranties and confers no rights!
Always evaluate/test everything yourself first before using/implementing this in production!
This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years!
DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
————————————————————————————————————————————————————-
########################### Jorge’s Quest For Knowledge ##########################
####################
http://JorgeQuestForKnowledge.wordpress.com/ ###################
————————————————————————————————————————————————————-

Posted in Azure AD Connect, Azure AD Connect Health, PowerShell, Windows Azure Active Directory | 1 Comment »

(2017-06-08) WHOAMI, The PowerShell Way

Posted by Jorge on 2017-06-08


Many of you probably know the command:

WHOAMI /USER /GROUPS

image

Figure 1: Executing WHOAMI

Every wanted to do the same in PowerShell?

[Security.Principal.WindowsIdentity]::GetCurrent())

image

Figure 2: Executing WHOAMI The PowerShell Equivalent

Although it gives you the required information, it still does not list the groups in a nice way. So let’s try that!

Unfortunately not a on-liner, but rather 2 lines of code Smile

$tableLayout = @{Expression={((New-Object System.Security.Principal.SecurityIdentifier($_.Value)).Translate([System.Security.Principal.NTAccount])).Value};Label="Group Name";Width=40},@{Expression={$_.Value};Label="Group SID";Width=45},@{Expression={$_.Type};Label="Group Type";Width=75}

([Security.Principal.WindowsIdentity]::GetCurrent()).Claims | FT $tableLayout

image

Figure 3: List Of Group Names, Group SIDs And Group Types From The Access Token

Cheers,
Jorge

 

————————————————————————————————————————————————————-
This posting is provided "AS IS" with no warranties and confers no rights!
Always evaluate/test everything yourself first before using/implementing this in production!
This is today’s opinion/technology, it might be different tomorrow and will definitely be different in 10 years!
DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
————————————————————————————————————————————————————-
########################### Jorge’s Quest For Knowledge ##########################
####################
http://JorgeQuestForKnowledge.wordpress.com/ ###################
————————————————————————————————————————————————————-

Posted in PowerShell, Tooling/Scripting | 1 Comment »

(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/ ########

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

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

 
%d bloggers like this: