Showing posts with label Active Directory. Show all posts
Showing posts with label Active Directory. Show all posts

Wednesday, March 20, 2024

New-ADComputer : A required attribute is missing

When trying to create a computer with this commandlet and you get this error, it may be a poorly worded exception. This error can come up if you don't have permissions to create computer objects on the OU you provided. If you follow the commandlet examples and provide what is required: samaccountname, name, and path; go check the OU permissions. If you had tried doing it with the older dsadd command, it will give an error that the modification was not permitted for security reasons. Once permissions are fixed, or a different set of properly delegated credentials are passed, it should work fine.

Another permissions related gotcha is when performing the task under run as different user, I have notice the same failure when the user performing the action has sufficient rights via group membership on the target OU.  When directly adding that user to the OU permissions, it allowed the creation of the computer object.  Possibly some run-as activity isn't passing a full access token on the connections and the group membership is ignored?

Sunday, August 20, 2023

Splitting AD integrated reverse DNS zones

If you have an environment with a reverse dns zone that was created with broad network range, you may decided at a later point in time that you want to split the zone. The reasons for this might be: ease of management in terms of loading the zone in the dns management console, easier to find records, requring differences in record age and scavenging control, etc. For a zone that is AD integrated, it will be in one of 3 partitions (domain partion, domaindns partition, or forestdns partition). You can adjust the code to the appropriate distinguishedname of the zone. Distinguishednames can be retrieved using get-dnsserverzone and reading the distinguishedname property on the returned object.

When it comes to splitting the zone, there's a few things to remember with AD integrated DNS. All records are objects under a zone object. Making changes in the dns management console doesn't mean objects in AD will automatically be deleted or migrated for you. You can end up in situations where hidden old records still exist in ldap, but don't show up in the dns management console. This code example below will help guide you in extracting the records you want and putting them in a new ldap dns zone object, along with preserving the data, timestamps, and permissions on the objects.

This example below is splitting of all 10.1.x.x records from a 10.x.x.x reverse zone. Before running your modified code, create your new reverse dns zone, then stop the dns server on the domain controller that you are making this change on.


get-adobject -searchbase "DC=10.in-addr.arpa,CN=MicrosoftDNS,DC=DomainDnsZones,DC=Contoso,DC=Com" -ldapfilter "(objectclass=dnsnode)" | 
    where {$_.name -match "\.1$"} | 
    move-adobject -targetpath "DC=1.10.in-addr.arpa,CN=MicrosoftDNS,DC=DomainDnsZones,DC=Contoso,DC=Com"
 

get-adobject -searchbase "DC=1.10.in-addr.arpa,CN=MicrosoftDNS,DC=DomainDnsZones,DC=Contoso,DC=Com" -ldapfilter "(objectclass=dnsnode)" | 
  where {$_.name -match "\.1$"} | %{

               $newname = $_.name.replace(".1","")
               $_ | rename-adobject -newname $newname
              
  }
Once this completes, start the dns server service to force rereading of ldap information. Dns zone reload is not sufficient. Other domain controllers should not require restart of dns service as they pick up the changes as it replicates. Test this in a lab environment first and run at your own risk.

Thursday, June 23, 2022

PetitPotam Defenses

Protection against coerced authentication on domain controllers:



Print Spooler:


Disable the service via group policy on all DC's

EFS RPC attack:



Create the two RPC filters by putting this in a text file (source):

rpc
filter
add rule layer=um actiontype=block
add condition field=if_uuid matchtype=equal data=c681d488-d850-11d0-8c52-00c04fd90f7e
add filter
add rule layer=um actiontype=block
add condition field=if_uuid matchtype=equal data=df1941c5-fe89-4e79-bf10-463657acf44d
add filter
quit


Save the file and use "netsh -f filename.txt" to apply it


DFS RPC attack:



Create one RPC filter by putting this in a text file (source)

rpc
filter
add rule layer=um actiontype=block
add condition field=if_uuid matchtype=equal data=4fc742e0-4a10-11cf-8273-00aa004ae673
add filter
quit


Save the file and use "netsh -f filename.txt" to apply it

This dfs filter has some impact on creation of new dfs namespaces. Otherwise it doesn't seem to cause other issues that I can tell.


Certificate Authority hardening:


Follow the MS guidance on hardening the CA against NTLM relay. Essentially you configure the web interfaces to allow kerberos only with extended protection. If possible, disable NTLM auth completely on the server. Beyond that, limiting access to client certificates and not allowing user supplied SAN's on them should be done.

Tuesday, September 28, 2021

Capturing unique simple bind or unsigned ldap queries from a domain controller

Using get-winevent in powershell with XML filter, you can grab the 2889 events from the directory services log. These contain the username, and source IP. With some custom defined attributes within select-object along with an array, you can filter this down to unique connections.

$query = @"

<QueryList>

  <Query Id="0" Path="Directory Service">

    <Select Path="Directory Service">*[System[(EventID=2889)]]</Select>

  </Query>

</QueryList>

"@


$somelistofdomaincontrollers | %{

$serv = $_

$hashes = @();

get-winevent -filterxml $query | select @{n="dc";e={$_.machinename}},
@{n="source";e={($_.properties.value[0].split(":"))[0]}},
@{n="user";e={$_.properties.value[1]}},
@{n='connhash';e={$str = ($_.machinename + 
    $_.properties.value[0].split(":"))[0] +
    $_.properties.value[1]; $str.gethashcode()}} | %{

     if ($hashes.contains($_.connhash)) {} else {$hashes += $_.connhash; $_|
        select dc,source,user}

}

Thursday, August 5, 2021

Quick way to find all OU's in a domain that block gpo inheritence

Using bitwise and on the gpotions attribute of organizational Unit objects. This will run in seconds compared to attempting to use higher level functions like get-adorganizationalunit in combination with get-gpinheritance.

get-adobject -ldapfilter "(&(objectclass=organizationalunit)(gpoptions:1.2.840.113556.1.4.803:=1))"

Wednesday, May 8, 2019

AD groups - setting group owner and delegating permission with powershell

Given that permissions delegation is only a simple checkbox in the AD users and computers tool under the manager's name, it would be nice if set-adgroup had a similar functionality. There are several steps involved in delegating rights, and if the owner is being changed, typically the old owner's rights should be removed. With this script below, it should accomplish this at least on a single domain environment. If the owner and group are in multiple domains, it can get a bit more complicated and some adjustments would be needed. This script takes either get-adgroup pipeline input, or a single group name that would work as an "identity" value in get-adgroup.

[CmdletBinding()]

param(

               [parameter(helpmessage="samaccountname of group owner",mandatory=$true)]$owner,

                              #defaults to current user's domain if not specified

               [parameter(helpmessage="netbios domain name")]$domain=$env:userdomain,  

               [parameter(parametersetname='pipe', valuefrompipeline=$true)]

        [Microsoft.ActiveDirectory.Management.ADGroup]$group,

               [parameter(parametersetname='normal',valuefrompipeline=$false, helpmessage="AD group name")]$identity,

                              #for cross domain? needs testing and what if the group and owner are in different domains?

               [parameter(helpmessage="domain controller to do object lookups")]$server

)

 

begin{

               import-module activedirectory

               if ($server -eq $null) { $server = (get-addomaincontroller -Discover -Writable).hostname}

}

process {

               #allow for both a single group name to be passed as $identity, or take pipeline get-adgroup output

               if ($pscmdlet.parametersetname -eq 'normal') {

                              try {

                                             [array]$group = get-adgroup $identity -property managedby -ea stop -server $server

                              } catch {

                                             throw "Unable to find AD group"

                              }

               }

              

               #cast the group as an array for the foreach loop

               if (!($group -is [array])) { $group = [array]$group}

              

               #hardcode guid for the write members permission

               $guid =[guid]'bf9679c0-0de6-11d0-a285-00aa003049e2'

              

               #get the group owner's sid - this could be replaced with get-aduser

               $user = New-Object System.Security.Principal.NTAccount("$domain\$owner")

               $sid =$user.translate([System.Security.Principal.SecurityIdentifier])

               $ctrl =[System.Security.AccessControl.AccessControlType]::Allow

               $rights =[System.DirectoryServices.ActiveDirectoryRights]::WriteProperty

               $intype =[System.DirectoryServices.ActiveDirectorySecurityInheritance]::None

               try {

                              try {

                                             $aduserobj = get-aduser $owner -server $server

                              } catch {

                                             #if no user was found try looking for a group owner

                                             $aduserobj = get-aduser $owner -server $server

                              }

               } catch {

                              throw "Unable to find owner's account"

               }

 

 

               foreach ($groupobj in $group) {

              

                              $acl = Get-Acl ad:"$($groupobj.distinguishedname)"

                             

                             

                              #if the group was passed without the managedby property, look it up again

                              if (($groupobj |gm |where {$_.name -eq "managedby"}) -eq $null) {

                                             #if there was a previous manager listed that doesn't match the one being passed to this script

                                             # remove any existing rights for that user

                                             $groupobj = get-adgroup $groupobj.distinguishedname -property managedby -server $server

                              }

 

                              if ($groupobj.managedby -ne $null) {

                                             try {
                                                $previousownersid = (get-aduser $groupobj.managedby -property sid -server $server).sid } catch { $previousownersid = (get-adgroup $groupobj.managedby -property sid -server $server).sid

                                             if ($previousownersid.value -ne $sid.value) {

                #$prevownerID = new-object system.security.principal.securityidentifier($previousownersid)

                           $previousownerID = $previousownersid.Translate([System.security.principal.ntaccount]).Value

                                                            $acl.access | where {$_.identityreference -eq $previousownerID} | % {

                                                                           $acl.RemoveAccessRule($_)

                                                            }

                                             }

                              }

                                            

                             

                              #set the ManagedBy property to the new owner

                              set-adgroup $groupobj -managedby $aduserobj -server $server

                             

                              #add new owner's right to manage members

                              $rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($sid,$rights,$ctrl,$guid)

                              $acl.AddAccessRule($rule)

                              Set-Acl -acl $acl -path ad:"$($groupobj.distinguishedname)"

               }

}

Thursday, October 5, 2017

Master list of Domain Join errors

This article is a collection of error messages from the domain join process, windows event viewer and general observations.  All of these were tested on a windows 2012R2 server joining to a single domain controller 2012R2 over a simulated router.  The domain is testforest.local and domain controller IP 10.1.1.50.  Various ports were blocked for each test and the results are recorded below.



Main Error Message on client: "An Active Directory Domain Controller (AD DC) for the domain 'test.local' could not be contacted.  Ensure that the domain name is typed correctly"



Situation: No functional dns.  That means, the client has no dns IP's configured, they are not valid dns server IP's, they are not accessible to this client, etc.

Sub Error Message when Details are expanded:

Note: This information is intended for a network administrator.  If you are not your network's administrator, notify the administrator that you received this information, which has been recorded in the file C:\Windows\debug\dcdiag.txt.

The following error occurred when DNS was queried for the service location (SRV) resource record used to locate an Active Directory Domain Controller (AD DC) for domain "testforest.local":

The error was: "This operation returned because the timeout period expired."
(error code 0x000005B4 ERROR_TIMEOUT)

The query was for the SRV record for _ldap._tcp.dc._msdcs.testforest.local

The DNS servers used by this computer for name resolution are not responding. This computer is configured to use DNS servers with the following IP addresses:

10.1.1.50

Verify that this computer is connected to the network, that these are the correct DNS server IP addresses, and that at least one of the DNS servers is running.

Steps to perform: Ensure the client is pointing to a valid dns server that can resolve this active directory domain.  Use of nslookup as a troubleshooting tool, or nltest /dnsgetdc: will help test connectivity.



Situation:  a RODC is accessible, however a RW domain controller is not accessible.  Your machine may be at a branch office with a local RODC that is handling dns queries, however the link connecting back to a writable domain controller is down.  Additionally this error could come up if the client has a functioning dns server to query that does provide answers, but due to some connectivity problem, the machine can't connect to a domain controller.

Sub Error Message when Details are expanded:

DNS was successfully querie for the service location (SRV) resource record used to locate a domain controller for domain "testforest.local":

The query was for the SRV record _ldap._tcp.dc._msdcs.testforest.local

The following domain controllers were identified by the query:
forest1dc1.testforest.local

However no domain controllers could be contacted.



Situation: Functional dns server, however the server doesn't cover this zone.  This means, the DNS server is accessible and is providing answers, however it cannot resolve anything in this Active Directory zone.  It does not host the zone, it does not forward to another server than can answer, nor does it do any recursion to find the answer.


Sub Error Message when Details are expanded:
Note: This information is intended for a network administrator.  If you are not your network's administrator, notify the administrator that you received this information, which has been recorded in the file C:\Windows\debug\dcdiag.txt.

The following error occurred when DNS was queried for the service location (SRV) resource record used to locate an Active Directory Domain Controller (AD DC) for domain "testforest2.local":

The error was: "DNS server failure."
(error code 0x0000232A RCODE_SERVER_FAILURE)

The query was for the SRV record for _ldap._tcp.dc._msdcs.testforest2.local

Common causes of this error include the following:

- The DNS servers used by this computer contain incorrect root hints. This computer is configured to use DNS servers with the following IP addresses:

10.1.1.50

- One or more of the following zones contains incorrect delegation:

testforest2.local
local
. (the root zone)

 Steps to Perform: 1) Ensure that the name typed in for the domain name on the client is the correct name, 2) check DNS infrastructure to find a server that is capable of resolving the active directory domain's dns zone.



Situation: Port 389 blocked (LDAP udp/tcp) 

Sub Error Message when Details are expanded:

Note: This information is intended for a network administrator.  If you are not your network's administrator, notify the administrator that you received this information, which has been recorded in the file C:\Windows\debug\dcdiag.txt.

DNS was successfully queried for the service location (SRV) resource record used to locate a domain controller for domain "testforest.local":

The query was for the SRV record for _ldap._tcp.dc._msdcs.testforest.local

The following domain controllers were identified by the query:
forest1dc1.testforest.local




## This ends the above section where the primary error message is domain controller could not be contacted.  In all three of these cases, there will be no prompt for credentials.


Error:  the RPC Server is unavailable

Situation: Block of port 135.  

What is seen:  User is prompted for credentials.  Domain join is slow but works eventually with a welcome to the domain error.  After the success, it may pop up "Changing the primary domain dns name of this computer to "" failed.  The name will remain "testforest.local".




Error:  Extremely slow domain join and everything else (boot up, logon, etc)


Situation: kerberos blocked (port 88 with DROP by firewall)

What is seen: Domain join still works but it is much slower, boot up is very slow, logons are very slow, GP update is very slow

Causes errors in system log
-lsasrv 6038  Microsoft Windows Server has detected NTLM authentication is presently being used between clients and this server....

-GroupPolicy 1055  Windows could not resolve the computer name

-TerminalServices-RemoteConnectionManager  1067   The RD Session Host server cannot register 'TERMSRV' Service Principal Name to be use for server authentication.  The following error occured: The system cannot contact a domain controller to service the authentication request.

-DNS CLient Events 8019.  The system failed to register host (A or AAAA) resource recortapter with settings:...

In the application log
-Winlogon 6006 GPClient errors


Situation: Kerberos blocked with icmp reject (port unreachable), same slowness


Error:  none

Situation: port 137 is blocked

What is seen:  prompts for cred, no problem in domain join, works quickly, no issues.



Situation: port 445 blocked

What is seen: Domain join works quickly, Boot speed is fine, and logon speed is fine. Gpupdate seems to work over port 137/139 (further blocking these ports breaks group policy with eventID 1096 in system log).  TCP 139 is the primary backup to 445 though the other ports may be required to get the connection started


Situation: port 3268  (AD global catalog) blocked

What is seen: No problem, fast join, no obvious problems after join



Situation: All ICMP traffic is blocked

What is seen: Join is fast, boot is fine, logon is fine.  Nothing significant seen here.  Firewall didn't catch any pkt drop.



Situation: Clock time of machine doesn't match domain controller (large skew >5min)

What is seen:  No problem in domain join.  System reboot, logon are all fine.  Clock time sync's after domain join reboot.

Error: "An Active Directory Domain Controller (AD DC) for the domain 'test.local' could not be contacted.  Ensure that the domain name is typed correctly"


 Sub error message in Details:

Note: This information is intended for a network administrator.  If you are not your network's administrator, notify the administrator that you received this information, which has been recorded in the file C:\Windows\debug\dcdiag.txt.

The following error occurred when DNS was queried for the service location (SRV) resource record used to locate an Active Directory Domain Controller (AD DC) for domain "testforest.local":

The error was: "This operation returned because the timeout period expired."
(error code 0x000005B4 ERROR_TIMEOUT)

The query was for the SRV record for _ldap._tcp.dc._msdcs.testforest.local

The DNS servers used by this computer for name resolution are not responding. This computer is configured to use DNS servers with the following IP addresses:

10.1.1.50

Verify that this computer is connected to the network, that these are the correct DNS server IP addresses, and that at least one of the DNS servers is running.

Situation:  all dynamic ports above 1023 dropped in both directions.

Causes: dropped dns traffic on return.  If return traffic/dns is working.... domain join is fine, boot is slow, logon is slow

System log:

Group policy 1053.  The processing of Group Policy failed.  Windows could not resolve the user name.  This could be caused by ...

Group policy 1055.  The processing of Group policy failed.  Windows could not resolve the computer name.  This could be caused by ...

TerminalServices-RemoteConnection Manager 1067   The RD Session Host server cannot register 'TERMSRV' Service Principal Name to be used for server authentication. The following error occured: The RPC server is unavailable.
.

Service control manager 7022  The Network Location Awareness service hung on starting.

Windows Remote Management 10154

The WinRM service failed to create the following SPNs: WSMAN/Slave1.testforest.local; WSMAN/Slave1.

Additional Data
 The error received was 1722: %%1722.

User Action
 The SPNs can be created by an administrator using setspn.exe utility.

Application Log - winlogon 6006  GPClient taking a long time





Monday, July 31, 2017

Testing connectivity to your domain controller

In the distant past there was a useful client side tool for checking connectivity between clients and domain controllers (netdiag.exe). According to microsoft's command line reference guide, it is available in windows 8 and 2012, but in reality the command does not exist on any windows machine I have checked beyond 2003. Trying to run an older version won't work either due to some incompatibility. So, alternatives are required to do checks. One thing you would typically want to check between a client and a domain controller is port connectivity.  Below, I will show a simple script that tests most of the ports.  Some may not be open in your environment (like 636,3269 for ldaps).  Some ports are dynamic, so I haven't included trying to check these.

To begin with, you should know what domain controller your workstation has logged into.  This machine logon establishes the "secure channel" between your machine and the domain.  You can use an old tool that is still around called nltest. 

C:\Windows>nltest /sc_query:contoso.com
Flags: 30 HAS_IP  HAS_TIMESERV
Trusted DC Name \\DC1.contoso.com
Trusted DC Connection Status Status = 0 0x0 NERR_Success
The command completed successfully
This output shows the status of your secure channel, and the name of the domain controller you are querying.  You will need to provide the name of the domain you are connected to.  FQDN domain name or NETBIOS domain name should work fine.

This script will provide two functions, one port checker and one function to run to test your connection.  Run Test-DomainControllerPorts with your domain name (or leave it blank for auto detect).  The script returns the name of the DC that you are connected to, along with 2 arrays of ports that are open and another of ports that aren't responding.

function tcpt ([string]$serv, [string]$p) {
 $result = $false
 try {
  $conn = new-object system.net.sockets.tcpclient($serv,$p)
  if ($conn.connected) { $result = $true } else { result = $false }
  $conn.close()
 } catch {
  $result =  $false
 }
 $conn = $null
 return $result
}
function test-DomainControllerPorts {
 param (
  $domainname = (gwmi win32_computersystem).domain
 )
 $secureChannelDC = (nltest /sc_query:$domainname |
  where {$_ -match "Trusted DC Name"}).split("\\") |
  where {$_ -match $domainname}
 $secureChanneldc = $securechanneldc.trim()
 $functionalports = @()
 $nonFunctionalPorts = @()
 $portsToCheck = ("53", "88", "135", "137", "139", "389", "445", "464", "3268", "636", "3269")
 foreach ($port in $portsToCheck) {
  $portstat = tcpt $secureChannelDC $port
  if ($portstat) {
   $functionalports += $port
  } else {
   $nonfunctionalPorts += $port
  }
 }
 $result = new-object PSObject
 add-member -inp $result NoteProperty DomainController $secureChannelDC
 add-member -inp $result NoteProperty OpenPorts $functionalports
 add-member -inp $result NoteProperty UnOpenPorts $nonfunctionalports
 out-default -inp $result
}


Update for later OS's (high than win 2008), some of the ports above are legacy and wouldn't be open on many domain controllers (such as 137, 139)

Sunday, July 2, 2017

AD: Simple way to remove all members of a group

No loops required, use the -clear parameter in set-adgroup.

Set-adgroup -identity "name of group" -clear member

The time required to execute will vary depending on number of people in the group.

Wednesday, March 29, 2017

Password change failed: Configuration information could not be read from the domain controller, either because the machine is unavailable, or access has been denied

This error was recently brought to my attention when a user was trying to change password after the expiration notice at logon. This was the first time I had seen it so I thought it was a bit odd. Based on the text of the error message alone, you would expect that the domain controller can't be contacted at all, there is some secure trust issue, or some weird issue on the domain controller. Typically when domain connectivity problems occur, you will get messages like domain controller unavailable or trust relation type problems. Searching around google comes up with some answers that don't seem to relevant, such as unjoin/rejoin the machine. One thing to look at is multi domain environments. Is the machine they are accessing on a different domain that the domain where the account exists? Is the trust one way or two way? Is the connectivity restricted between the two domains (dmz's)? In my particular case, the password change was being attempted on a trusting domain (one way) with limited access. Check this article to help determine possible connectivity requirements.

Tuesday, December 27, 2016

Fixing dns record permissions for dynamic dns

In case you have dns records that need to be changed from static to dynamic, or the machine/clusters that will be updating them have changed, you can modify the dns record permissions to allow updates.  Doing this through the gui is fine for a few records, but if you need something a bit more simple and automated, you can try this script.  It takes both the computer name (doesn't have to match the dns record, just needs to be a computer object), and the dns record (fqdn) that you are updating.  This script will work for AD dns only, and would be limited to the current domain, and likely any forest wide partitions.  If you check my article on managing other domains in powershell, you can probably get edits to other domains working on this with a few modifications and extra parameters.



param (
  $computername,
  $dnsrecord
)

$script:computernameSam = $computername + "$"

try {
  import-module activedirectory
} catch {
  write-error "This script requires the AD powershell module"
  exit
}
while ( (test-path -path Ad:) -ne $true  )
{
  start-sleep -seconds 2
}

#Standard ACL for a dynamic dns entry
  #ActiveDirectoryRights : CreateChild, DeleteChild, ListChildren, ReadProperty, DeleteTree, ExtendedRight, Delete,
  #                        GenericWrite, WriteDacl, WriteOwner
  #InheritanceType       : None
  #ObjectType            : 00000000-0000-0000-0000-000000000000
  #InheritedObjectType   : 00000000-0000-0000-0000-000000000000
  #ObjectFlags           : None
  #AccessControlType     : Allow
  #IdentityReference     : Domain\machine$
  #IsInherited           : False
  #InheritanceFlags      : None
  #PropagationFlags      : None
# 

function get-partition {
  param ( $record )
  #need to split off everything after first name to find longest zone match
  #using get-dnsserverzone ($name)
  
  $dnsrecordparts = $record.split(".")
  
  for ($i = 1; $i -lt $dnsrecordparts.length; $i++) {
    $zonenameTest = $dnsrecordparts[$i..($dnsrecordparts.length -1)] -join "."
    $zoneObj = get-dnsserverzone $zonenameTest -ea 0
    if ($zoneObj -ne $null) {
    write-output -inputobject $zoneobj
    $i = $dnsrecordparts.length + 1
    }
  }
  
}

function get-dnsobject {
  param ($record)
  $zoneObject = get-partition -record $record
  if ($zoneObject -ne $null) {
    $zonename = $zoneObject.zonename
    $record -match "(.*)(\.$zonename)"
    $dnsRecordDN = "dc=" + $matches[1] + "," + $zoneobject.distinguishedname
     try {
       get-adobject  $dnsRecordDN
    } catch { Throw "Unable to find dns record for this machine"}
  } else { throw "DNS zone not found"}
}

try {
  try {
    $guid = [guid]'00000000-0000-0000-0000-000000000000'
    $adcomputer = get-adcomputer $computername -property objectsid
    $sid = $adcomputer.objectsid
    $ctrl = [System.Security.AccessControl.AccessControlType]::Allow
    $rights = 983423
    $intype =[System.DirectoryServices.ActiveDirectorySecurityInheritance]::None
    $rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($sid,$rights,$ctrl,$guid)
  } catch { throw "Unable to get computer account SID" }

  try {
    #find record
    $dnsDN = get-dnsObject -record $dnsrecord
  } catch { throw $_ }
  
  try {
    $acl = get-acl ad:"$($dnsDN.distinguishedname)"
    $acl.setowner([system.security.principal.ntaccount]"$script:computernameSam")
    $acl.AddAccessRule($rule)
    Set-Acl -acl $acl -path ad:"$($dnsDN.distinguishedname)"
  } catch { throw $_ }

} catch { $_}

Thursday, July 28, 2016

MS - Certificate autoenrollment behind a firewall

For anyone who has autoenrollment for certificates on machines that are behind firewalls, here are the ports and servers you want to look at for setting up firewall rules:


Client to domain controller:
    Kerberos port 88 (UDP/TCP)
    Ldap (TCP 389)
    RPC (tcp 135)
    RPC on dynamic port (>1023 TCP)

Client to certificate server(s) with the template available 
    RPC (TCP 135)
    Dynamic RPC (TCP > 1023) for CA servers on windows 2003 and earlier
    Dynamic RPC (TCP > 49151) for CA servers on newer windows OS's


If you want to find the specific port that the certificate services server is listening on for RPC requests, you can find it in several different ways.  If you have access to logon to the server, you can use:

tasklist /svc |find /I "Certsvc"

In that output, look at the process ID number and use it in the command below.  If the port is 52775, use:

netstat -ano |find "LISTEN" | Find "52775"


If you do not have access to logon to the server to run these commands and you have access to the old windows resource kit files, you can use RPC dump to look for the uuid 91ae6020-9e3c-11cf-8d7c-00aa00c091be.  Use:

rpcdump /S nameofremoteserver /I /v /P ncacn_ip_tcp.

This will output more than what you need, so search through the output to find the guid in the UUID field.  The port will be what is listed as the Endpoint, or in the StringBinding.


ProtSeq:ncacn_ip_tcp
Endpoint:52775
NetOpt:
Annotation:
IsListening:YES
StringBinding:ncacn_ip_tcp:computername[52775]
UUID:91ae6020-9e3c-11cf-8d7c-00aa00c091be
ComTimeOutValue:RPC_C_BINDING_DEFAULT_TIMEOUT
VersMajor 0 VersMinor 0


Once you have obtained the port, you can use any port testing tool from the client to the CA server, like test-netconnection to see if that port, and port 135 is accessible to the client. 

Tuesday, June 21, 2016

Tips for AD group membership managment in powershell

Managing large groups can fail due to limits in Active Directory Web Services when too many members are in a group.

Fails: Get-adgroupmember "LargeGroup"
       error:  Get-ADGroupMember : The size limit for this request was exceeded

Works:  Add-adgroupmember and remove-adgroupmember

Work Around: get-adgroup "LargeGroup" -properties members | select -expand members

This will get the distinguishednames of all members as an array.

-----------------------------------------------

Piping groups or users into a group membership cmdlet to change the group memberships.

1) When you are piping groups into a cmdlet where the user(s) are static.  Pipe to Add-ADGroupMember.
    Ex:  get-adgroup -filter {name -like "HelpDesk*"}| add-adgroupmember -members $userdn

2) When you are piping users into a cmdlet where the group(s) are static.  Pipe to Add-ADPrincipalGroupMembership
    Ex: get-aduser bob | Add-ADPrincipalGroupMembership -memberof $groupdn

NOTE: Add-ADPrincipalGroupMembership will generate successful security audit events (Directory Service Change) for the addition of the group member, even if they were already a member of the group

-----------------------------------------------

When using Add-ADGroupMember with an array of members, if any of them are part of the group already, the whole operation will fail.  Its best to try adding one at a time.

Thursday, June 16, 2016

Piping get-aduser output through several custom powershell functions

For people who write scripts to process large amounts of AD objects, you may find that use of pipelines will be more memory efficient that variables and foreach. In one case, I was working through a problem with a script in powershell v2, where lots of strange failures were occurring, such as failing to assign values to a variable. A statement like: $a = 1, may just randomly not work. As the script was doing some heavy processing of large numbers of objects, I assumed memory consumption was the problem. I wanted to convert the script to use of pipeline. Since the script had several functions that handled different aspects of what was needed, I thought it would be good to try multiple pipelines. In the end, the change to pipelines fixed all the failures in the script.

What I wanted:

Take an OU, run get-aduser on the OU -> Pipe to an analysis function to check password expiration for different types of accounts and password policies, then decide if an email notice needed to be sent -> Pipe (if needed) to an email function -> Pipe the results of all of the above to logging function.

At each stage, different bits of calculated data or additional properties needed to be added to the original get-aduser object. This was possible by using custom PSObjects after the initial analysis function. The basics of the code is below:

function process-OU { 
 param(  
  [parameter(mandatory=$true)][string]$searchbase, 
  [string]$type="standard"
 )
 Get-ADUser -Filter {(enabled -eq $True) -and (mail -like "*") } `
      -SearchBase $SearchBase `
      -Properties mail, PasswordLastSet, sn, PasswordNeverExpires | 
           analyze-user -type $type |email-user |log-result
}

function Analyze-User{
 [CmdletBinding()]
 param (
  [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
    [Microsoft.ActiveDirectory.Management.ADAccount]$user,
  [string]$Type
 )
 begin {}
 process {
  #do some analysis and decide if you want to 
                #continue with write-output $user
  #
  #Add any additional pieces of information to the user object with
  #   add-member -input $user -force NoteProperty Expired $False
  if ($proceedtoEmail) { write-output $user } 
 }
}

function Email-User {
 [CmdletBinding()]
      Param(  
        [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
        [PSobject]$emailuser  
      )
    #Notice the parameter type is a generic 
    #[psobject] as it is no long conforming 
    #to the [Microsoft.ActiveDirectory.Management.ADAccount] type
 Begin{}
 Process {
  #handle email creation and sending.  
                #Check if it was sent without error, 
                #add email status as another property
 }
}

function log-result {
 [CmdletBinding()]
         Param(   
            [Parameter(Mandatory=$True,ValueFromPipeline=$True)]
              [PSObject]$user   
        )
 begin {}
 process {
  #do some logging here
 }
}


process-OU -searchbase "ou=myusers,dc=contoso,dc=com" -type "regular"

Tuesday, April 12, 2016

Active Directory ACL's explained

In a previous post on decoding AD ACL's, I provided some code which took BSonPosh's get-adacl output and decoded the SID's and GUID's to help provide more readable output.  The example of this is below, however you may find some of the other fields to be a bit confusing.  So I created a few different types of test permissions on an OU to show how they are reflected in the Powershell output of these two commands.


Permission set in GUI: "Apply to: All Descendant objects, create/delete Conference Site objects"

ActiveDirectoryRights :  CreateChild, DeleteChild
InheritanceType       :     Descendents
ObjectType            :      msExchConferenceContainer
InheritedObjectType   :   00000000-0000-0000-0000-000000000000
ObjectFlags           :       ObjectAceTypePresent
AccessControlType     : Allow
IdentityReference     :    TEST.LOCAL\Nathan
IsInherited           :        False
InheritanceFlags      :    ContainerInherit
PropagationFlags      :   InheritOnly

Permission set in GUI:  "Apply to: This object and all descendant objects, create/delete Contact objects"

ActiveDirectoryRights : CreateChild, DeleteChild
InheritanceType       :    All
ObjectType            :     contact
InheritedObjectType   : 00000000-0000-0000-0000-000000000000
ObjectFlags           :      ObjectAceTypePresent
AccessControlType     : Allow
IdentityReference     :     TEST.LOCAL\Nathan
IsInherited           :        False
InheritanceFlags      :    ContainerInherit
PropagationFlags      :   None

Permission set in GUI:  "Apply to: This object only, create/delete Computer Objects"

ActiveDirectoryRights : CreateChild, DeleteChild
InheritanceType       :    None
ObjectType            :     computer
InheritedObjectType   : 00000000-0000-0000-0000-000000000000
ObjectFlags           :     ObjectAceTypePresent
AccessControlType     : Allow
IdentityReference     :    TEST.LOCAL\Nathan
IsInherited           :        False
InheritanceFlags      :    None
PropagationFlags      :  None

Permission set in GUI:   "Apply to: Descendent Computer objects, Modify Owner"

ActiveDirectoryRights : WriteOwner
InheritanceType       :    Descendents
ObjectType            :     00000000-0000-0000-0000-000000000000
InheritedObjectType   : computer
ObjectFlags           :      InheritedObjectAceTypePresent
AccessControlType     : Allow
IdentityReference     :    BHI-MASTER\adminlinlnat
IsInherited           :        False
InheritanceFlags      :    ContainerInherit
PropagationFlags      :  InheritOnly

InheritedObjectType:  Notice this will be all zero's when the permission is for creating a child object in a container.  When it is permissions being set on a specific type of child objects, then it will be set that that object type, and the ObjectType value will be all zero's.  When setting a permission on a specific property of a specific type of child object, you will get both fields filled in with the ObjectType being the specified property, and InheritedObjectType being the AD object's type.

PropagationFlags: InheritOnly exists when applying to something other than the current OU.  (https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.propagationflags(v=vs.110).aspx)

InheritenceFlags: ContainerInherit when applying to anything below the current level, ObjectInherit when applying to child objects (https://msdn.microsoft.com/en-us/library/system.security.accesscontrol.inheritanceflags(v=vs.110).aspx)

InheritanceType: All (everything from this level down), Descendents (children and descendants, not the current object), None (current level only)   (https://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectorysecurityinheritance(v=vs.110).aspx)

Wednesday, December 16, 2015

Tis the season for vacation clearing (and password expiration)

As we approach the end of the year, along with its holidays, its common for many employees to take leave for long periods of time.  So as a gift to helpdesks everywhere, often they would request to know who will have their password expire during the peak holiday times (to prepare for the support calls).  To build a list like this is quite easy with powershell.  This assumes you don't have fine grain password policies.  In this example, we look at expiring passwords between Dec 21 and Jan 4 given the working days and anticipated return dates around the Christmas and New Years holidays:

import-module activedirectory

#grab the domain wide password policy and extract a # of days integer
$passwordage = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge |select -exp days

#define your start and end filter dates and subtract the max Password age value.
#We need to calculate using passwordlastset timestamps

$startdate = ([datetime]"12-21-2015").adddays(-$passwordage)
$enddate = ([datetime]"1-4-2016").adddays(-$passwordage)

#Filter as much as possible on the LDAP side with the date ranges.
#The Select statement includes a calculated
#expression to convert the passwordlastset value to an actual expiration date.
#Convert to CSV and output to file.
#Zip it and mail it out.

get-aduser -filter {(enabled -eq $true) -and (passwordlastset -ge $startdate) -and (passwordlastset -le $enddate)} -Properties passwordlastset, mail | select samaccountname, name, mail, @{name="ExpirationDate"; exp={$_.passwordlastset.adddays($passwordage)}} | convertto-csv -notypeinfo | out-file .\expiringholidays.csv

Monday, August 3, 2015

Encouraging domain controller to advertise time

param(
[parameter(mandatory=$true)]$computer
)

$sb = {
set-itemproperty -path `
     HKLM:\system\currentcontrolset\services\w32time\timeproviders\ntpserver `
    -name "Enabled" -value "1"
restart-service w32time
w32tm /resync
}
invoke-command -script $sb -computer $computer




2023 - Update to this article. It is possible for a server to have a value of 1 on the Enabled dword
for ntserver and the server still reports that it is not advertising. There is a group policy setting
that can prevent the machine from acting as an NTP server which will block the service from working and won't
present anything obvious in the registry.

Checking NTP time offsets with powershell - parsing dos utility output

Recently I ran into a problem with domain controllers being out of sync with each other. Most of the DC's were sync'd with each other, but they had falling out of sync with the root PDC. Since SCOM's time monitoring only tests between the local system clock and the PDC of that domain, it will not detect all time problems in a multi-domain environment. In the past, this may have been fine as the windows time service had servers sync'ing to the PDC. Now there is some difference in how the time service finds a sync partner. It can be configured to use the PDC, or the new default which is based on sites (CrossSiteSyncFlags registry setting, time service reference). So, due to this problem I went through an old solution I had come up with using w32tm /monitor to look at all domain controllers. The problem with this is the output is really messy, and apparently may not be the same from one OS version to another. I had it working on 2003, and 2012R2, but when running on 2008R2 the old parsing rules I had fell apart. So using the same w32tm utility, I started over with the stripchart command to do a test of a single machine with the function below. Using this function, you can test any ntp server that is accessible (domain controller or otherwise). This way you can wrap the function into a monitoring tool that looks at all domain controllers in all domains, as well as the higher level NTP source that the domain sync's to.

The number of samples in the stripchart command will greatly impact the run time if you are running it against a large number of computers. I put it at 3 due to false non-responsive alerts when it was set to 1. If your network is repsonsive, 1 may be sufficient.


function get-timeoffset {
 param([parameter(mandatory=$true)]$computer)
 write-verbose "working on server $computer"
 $resultval = new-object PSobject
 add-member -input $resultval NoteProperty Computer $computer
 $a = w32tm /stripchart /computer:$computer /dataonly /samples:3 /ipprotocol:4
 if (-not ($a -is [array])) {
  add-member -input $resultval NoteProperty Status "Offline"
  add-member -input $resultval NoteProperty Offset $null
 } else {
  $foundtime=$false
  #go through the 5 samples to find a response with timeoffset.
  for ($i = 3; $i -lt 8; $i++) {
   if (-not $foundtime) {
    if ($a[$i] -match ", ([-+]\d+\.\d+)s") {
     $offset = [float]$matches[1] 
     add-member -input $resultval NoteProperty Status "Online"
     add-member -input $resultval NoteProperty Offset $offset
     $foundtime=$true
    } 
   }
  }
  #if no time samples were found, check for error
  if (-not $foundtime) {
   if ($a[3] -match "error") {
    #0x800705B4 is not advertising/responding
    add-member -input $resultval NoteProperty Status "NTP not responding"
   } else {
    add-member -input $resultval NoteProperty Status $a[3]
   }
   add-member -input $resultval NoteProperty Offset $null
  }
 }
 $resultval
 
}

Thursday, July 23, 2015

Finding domain controllers replicating from a particular server

This will look at all ntds server connections in each site to find which servers (listed in the distinguishedname attribute) are replicating from a particular machine (in the fromServer attribute).

get-adobject -searchbase `
("CN=Sites," + (get-adforest).partitionscontainer.substring(14)) `
-ldapfilter "(objectclass=nTDSConnection)" `
-Properties distinguishedname,fromServer |select distinguishedname,fromServer |
where {$_.fromserver -match "servername"}