Wednesday, October 20, 2021

Query the Signed in User When Running Script as System

I'm working on a desktop migration project where we run some PowerShell scripts to prepare the computer for migration. As part of this we need the locally signed in user.

Normally, you can obtain locally signed in user from an environment variable:

$env:USERNAME

However, we're running the script as SYSTEM. So, that returned value is incorrect. That value is the username associated with the PowerShell instance.

You can query the signed in user when you run a script as SYSTEM by using Get-WmiObject:

(Get-WmiObject -ClassName Win32_ComputerSystem).Username


Sunday, September 5, 2021

Set User Department Based on OU

When you create dynamic distribution groups on-premises, you have the option to create them based on organizational unit. In Exchange Online (EXO), you don't have this option because Azure AD doesn't have the OUs for your tenant.

There are many attributes available for creating dynamic distribution groups in EXO and one available through the web interface is department. So, to simulate OU-based groups, we can set the department attribute.

To do this, I created a script on a DC that runs once per hour and sets the Department attribute based on the OU. When you run a script as SYSTEM on a DC, it has the ability to modify Active Directory. The function below is the core of the script.

#Function requires the OU as a distinguished name
Function Set-UserDepartment {
    param(
        [parameter(Mandatory=$true)] $OU,
        [parameter(Mandatory=$true)] $Department
    )

    Write-Host ""
    Write-Host "Setting department attribute as $Department for users in $OU"
    
    #Find null values
    $nullusers = Get-ADUser -Filter {Department -notlike "*"} -Properties Department -SearchBase $OU
    
    #Find wrong value
    $wrongvalue = Get-ADUser -Filter {Department -ne $Department} -Properties Department -SearchBase $OU

    #Create one array of all users to fix
    $users = $nullusers + $wrongvalue

    Write-Host "null value: " $nullusers.count
    Write-Host "wrong value: " $wrongvalue.count

    #Set department
    Foreach ($u in $users) {
        Set-ADUser $u.DistinguishedName -Department $Department # -WhatIf
    }
} 

This function:

  • Expects the OU to be passed as a distinguished name
  • Finds users in the OU (and sub-OUs) with the department set to $null
  • Finds users in the OU (and sub-OUs) with the incorrect department
  • Sets the Department value as specified when you call the function for all users identified

Querying the users that don't have department set correctly and calling Set-ADUser for only those users is much faster than setting all users each time.

The count for null value or wrong value is incorrect when there is a single item because a single item is not an array. You can improve this by forcing them to be an array before populating them. Or, checking whether it's single item first, but for my purposes, this was sufficient.

Within the script, you can call the function as many times as required to set the attributes. You just pass the OU and the department value to the function like below.

#Call function to set department for Marketing
Set-UserDepartment -OU "OU=Marketing,DC=Contoso,dc=com" -Department Marketing


Tuesday, August 10, 2021

Script to Update DNS Record Permissions

 When you have secure dynamic update configured for DNS zones, the individual DNS records are protected by security permissions. The host records are typically secured by the associated computer account. The PTR records are typically secured by the DHCP server account or the associated computer account depending on whether it's a static or dynamic IP address.

If you have highly available DHCP servers, they should be configured with a user account for dynamic DNS updates. This user account is used by both DHCP servers to ensure that records created by one DHCP server can be updated by the other.

If you have highly available DHCP and don't use a shared account, then you'll see errors in the DHCP event log (Event ID 20322) indicating that the DNS record couldn't be updated. After you configure the shared account, the permissions on the DNS records will still be incorrect. The following script adds Full Control permissions on A and PTR records for the shared account. This allows the properly configured DHCP servers to update existing records.

You'll need to do some editing on this script for your environment:

  • $DynamicDnsUser needs to be set to your shared user account.
  • $Server needs to be set to the name of your DNS server.
  • $zones needs to contain the zones you want to modify. 

#DynamicDnsUser is the user configured on both DHCP servers for dynamic DNS
#This uses the SamAccountName of the user
$DynamicDnsUser = Get-ADUser ddnsuser

#Query the SID for this user and create a ACE allowing full control
$SID = New-Object System.Security.Principal.SecurityIdentifier $DynamicDnsUser.SID.Value
$ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $SID, "GenericAll", "Allow"

#When running DNS cmdlets from a workstation
#you need to specify the server you are acting on
$server = "DNSServer"

#query a list of all zones to loop through and set permissions
#$zones = Get-DnsServerZone -ComputerName $server
#$zones = $zones | where ZoneType -eq Primary

$zones = Get-DnsServerZone "40.10.in-addr.arpa"

Foreach ($zone in $zones) {

    #Query all records in the zone
    #Record type ensures that the get the correct type of records
    #based on forward or reverse lookup zones
    If ($zone.IsReverseLookupZone -eq $true) {
        $recordType = "PTR"
    } Else {
        $recordType = "A"
    }

    $records = Get-DnsServerResourceRecord -ComputerName $server -ZoneName $zone.ZoneName -RRType $recordType

    #Loop through all records in the zone and add
    #the ACE for the dynamic DNS user
    #Need to set the location to AD: for the *-ACL cmdlets to work
    Foreach ($record in $records) {

        Push-Location -Path AD:
        $ACL = Get-Acl -Path $record.DistinguishedName
        $ACL.AddAccessRule($ACE)    
        $ACL | Set-Acl -Path $record.DistinguishedName    
        Pop-Location

    } #end foreach records

} #end foreach zones

Please be aware this only works to allow the DHCP server to update the DNS records. In most organizations, the host records are dynamically updated by the individual computers. If you want to add the correct computer account to a DNS record, then you need to approach this differently.

The following link has a script that finds a computer account that matches a host record and assigns permissions to that computer account. This script was used as a starting point for my script above.

Wednesday, July 21, 2021

Dynamic DNS Settings for Highly Available DHCP Servers

Windows DHCP servers can integrate with DNS to perform dynamic DNS on behalf of clients. This is useful when DHCP clients such as printers or mobile phones are not able to perform their own dynamic DNS updates. The DHCP server can also perform secure dynamic DNS updates when the client can't.

You can configure dynamic DNS settings at the IPv4 node (server level) or at the individual scope. If you don't configure dynamic DNS settings at the scope level, they are inherited from the server level. If you update dynamic DNS settings at the server level those new settings are used by all scopes that don't have dynamic DNS settings explicitly defined.

Unfortunately, there is no easy way to identify when dynamic DNS settings are configured at the scope level instead of the server level. If the settings are different then they are definitely configured at the scope level. But, if the settings are the same, they could be configured at either level.

When you have scopes configured for high availability with two Windows DHCP servers, then both servers can service the scope. If you have accidentally configure the dynamic DNS settings at the IPv4 node differently on the two servers, it can provide inconsistent settings for clients depending on which DHCP server provides the lease.

For example, DHCP1 and DHCP2 are configured with a failover relationship that is in load balancing mode. Scopes using this failover relationship service half of requests using DHCP1 and half of requests using DHCP1.

At the IPv4 node of DHCP1, it is configured to perform dynamic DNS updates on when requested by the clients.


At the IPv4 node of DHCP2, it is configured to perform dynamic updates for all clients.


If you create a new scope, named Client LAN and configure it to use the failover relationship, the scope appears on both servers. When you view the DNS tab in the properties of Client LAN, the settings match the server settings. So, the settings you see vary depending on which DHCP server that the DHCP admin console is connected to.

When a client leases an address from DHCP1, the dynamic DNS settings from the IPv4 node of DHCP1 are used. When a client leases an address from DHCP2, the dynamic DNS settings from the IPv4 node of DHCP2 are used.

To avoid this, you can do the following:

  • Ensure that the IPv4 settings are the same on both servers (you really should)
  • Manually configure the dynamic DNS settings in each scope

Secure Dynamic Update Credentails

Another consideration when using highly available DHCP with dynamic DNS updates is the credentials for secure updates in DNS. By default, when a DHCP server creates a DNS record that allows only secure dynamic updates, the record is secured with permissions based on the computer account of the DHCP server. When two DHCP servers are working together, this can result in DHCP1 creating a DNS record that DHCP2 can't update.

To ensure that both highly available DHCP servers can service all records created by either server, you need to configure a user account that is used by both servers to secure dynamic DNS records. This is configured on each server on the Advanced tab in the properties of IPv4.

 
After configuring the DNS dynamic update credentials on both servers, the DNS records are secured by that user account. Since both servers use the same user account, they can update DNS records created by the other DHCP server. This user account does not require any special permissions. It just needs to be a member of Domain Users. And of course, you should set the password to not expire.
 
If the DNS zones are configured to allow insecure dynamic updates then security is ignored during  dynamic DNS updates and the credentials are not important.



Tuesday, June 1, 2021

DNS Forwarding Timeouts

When you configure forwarders on Windows DNS servers, it's not obvious what the timeout values are. You might intuitively think that more forwarders is better. In reality, with the default values, you're just kidding yourself.

DNS forwarders have a default timeout of 3 seconds. If the first forwarder does not respond within 3 seconds then the second forwarder is contacted, and so forth.

However, there is an overall recursion timeout of 8 seconds. After 8 seconds no more forwarders will be contacted. So, best case, the process looks like this:

  • 0s - Contact forwarder 1
  • 3s - Contact forwarder 2
  • 6s - Contact forwarder 3
  • 8s - recursion timeout (process ends)

As you can see, only the first three forwarders listed are ever used. Putting more than 3 forwarders on a DNS server is misleading because forwarders 4 and up will never be contacted.

Conditional forwarders have a similar process but with different timeout values. Conditional forwarders have a default timeout of 5 seconds along with the recursion timeout of 8 seconds. This means that only two conditional forwarders are ever contacted.

The conditional forwarder process looks like this:

  • 0s - Contact conditional forwarder 1
  • 5s - Contact conditional forwarder 2
  • 8s - recursion timeout (process ends)

Again, I suggest don't ever list more than two conditional forwarders or it is misleading.

If you want to allow additional forwarders or conditional forwarders to be queried, you can modify the default values in the registry of the DNS servers. However, be sure to do this on all DNS servers so that it is consistent. And, document it as part of your domain controller build process so that it is configured on new domain controllers too.

Registry keys to modify the default timeout values:

  • Recursion timeout (per DNS server)
    • HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\**RecursionTimeout
  • Forwarding timeout (per DNS server)
    • HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\**ForwardingTimeout
  • Forwarder timeout (per zone/conditional forwarder)
    •  HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\ <zone_name>\ForwarderTimeout

For more detailed information about this process, see:

Wednesday, March 24, 2021

Script to compare file presence in directory structures

I'm still running into a few organizations that need to convert their sysvol replication from FRS to DFS-R. The process for doing that is quite straight forward, but if FRS replication is broken, then there is the risk that some data could be lost during this process.

As part of due diligence before converting, I like to compare the contents of sysvol among the domain controllers. The PDC emulator is the default source for new sysvol data. So, I compare that to other domain controllers. To help me with this I've created a PowerShell script that compares the files to identify any that are not the same on two servers.

I use this for sysvol, but you could use it to compare any two data structures. This script does not compare time stamps, file size, or file contents. It only looks for presence.

 

#Domain controller path to sysvol
$srcpath="\\Server1\sysvol"
$targetpath="\\Server2\sysvol"

#Get list of files
$srcfiles = get-childitem -Path $srcpath -File -Recurse
$targetfiles = get-childitem -Path $targetpath -File -Recurse

#Add property looks only at relative file name path
#Required for proper comparison without server name
Foreach ($file in $srcfiles) {

    $cleanpath = ($file.FullName).replace($srcpath,"")
    $file | Add-Member -NotePropertyName ShortPath -NotePropertyValue $cleanpath -Force

}

Foreach ($file in $targetfiles) {

    $cleanpath = ($file.FullName).replace($targetpath,"")
    $file | Add-Member -NotePropertyName ShortPath -NotePropertyValue $cleanpath -Force

}

$dif = Compare-Object $srcfiles $targetfiles -property ShortPath #-PassThru

Write-Host "Source ( <= ) is: " $srcpath
Write-Host "Target ( => ) is: " $targetpath
Write-Host ""
$dif

Tuesday, March 23, 2021

0x80070780: The file cannot be accessed by the system

Today got this error when trying to import a virtual machine on my newly rebuilt Hyper-V VM host running Windows Server 2019:

0x80070780: The file cannot be accessed by the system

I tried playing with permissions but it kept erroring out saying that it couldn't access the vhdx files. Finally, I realized that when I rebuilt the VM host, I forgot to install the data deduplication feature and thus the system could see the file names, but not access the deduplicated data.

After I enabled the data deduplication role service and rebooted, the system recognized the drives had deduplicated data and could access the files without issue.