DSCResources/MSFT_xDnsRecordSrv/MSFT_xDnsRecordSrv.psm1

$script:dscResourceCommonPath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common'
$script:dnsServerDscCommonPath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DnsServerDsc.Common'

Import-Module -Name $script:dscResourceCommonPath
Import-Module -Name $script:dnsServerDscCommonPath

$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US'

<#
    .SYNOPSIS
        This will return the current state of the resource.
 
    .PARAMETER Zone
        Specifies the name of a DNS zone.
 
    .PARAMETER SymbolicName
        Service name for the SRV record. eg: xmpp, ldap, etc.
 
    .PARAMETER Protocol
        Service transmission protocol ('TCP' or 'UDP')
 
    .PARAMETER Port
        The TCP or UDP port on which the service is found
 
    .PARAMETER Target
        Specifies the Target Hostname or IP Address.
 
    .PARAMETER Priority
        Specifies the Priority value of the SRV record.
 
    .PARAMETER Weight
        Specifies the weight of the SRV record.
 
    .PARAMETER TTL
        Specifies the TTL value of the SRV record. Value must be in valid TimeSpan format.
 
    .PARAMETER DnsServer
        Name of the DnsServer to create the record on.
 
    .PARAMETER Ensure
        Whether the host record should be present or removed.
#>

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Zone,

        [Parameter(Mandatory = $true)]
        [System.String]
        $SymbolicName,

        [Parameter(Mandatory = $true)]
        [ValidateSet('TCP', 'UDP')]
        [System.String]
        $Protocol,

        [Parameter(Mandatory = $true)]
        [ValidateRange(1, 65535)]
        [System.UInt16]
        $Port,

        [Parameter(Mandatory = $true)]
        [System.String]
        $Target,

        [Parameter(Mandatory = $true)]
        [System.UInt16]
        $Priority,

        [Parameter(Mandatory = $true)]
        [System.UInt16]
        $Weight,

        [Parameter()]
        [System.String]
        $DnsServer = 'localhost'
    )

    $recordHostName = "_$($SymbolicName)._$($Protocol)".ToLower()

    Write-Verbose -Message ($script:localizedData.GettingDnsRecordMessage -f $recordHostName, $target, 'SRV', $Zone, $DnsServer)

    $dnsParameters = @{
        Name         = $recordHostName
        ZoneName     = $Zone
        ComputerName = $DnsServer
        RRType       = 'SRV'
    }

    $record = Get-DnsServerResourceRecord @dnsParameters -ErrorAction SilentlyContinue | Where-Object {
        $_.HostName -eq $recordHostName -and
        $_.RecordData.Port -eq $Port -and
        $_.RecordData.DomainName -eq "$($Target)."
    }

    if ($null -eq $record)
    {
        return @{
            Zone         = $Zone
            SymbolicName = $SymbolicName
            Protocol     = $Protocol.ToLower()
            Port         = $Port
            Target       = $Target
            Priority     = $Priority
            Weight       = $Weight
            TTL          = $null
            DnsServer    = $DnsServer
            Ensure       = 'Absent'
        }
    }

    return @{
        Zone         = $Zone
        SymbolicName = $SymbolicName
        Protocol     = $Protocol.ToLower()
        Port         = $Port
        Target       = ($record.RecordData.DomainName).TrimEnd('.')
        Priority     = $record.RecordData.Priority
        Weight       = $record.RecordData.Weight
        TTL          = $record.TimeToLive.ToString()
        DnsServer    = $DnsServer
        Ensure       = 'Present'
    }
} #end function Get-TargetResource

<#
    .SYNOPSIS
        This will set the resource to the desired state.
 
    .PARAMETER Zone
        Specifies the name of a DNS zone.
 
    .PARAMETER SymbolicName
        Service name for the SRV record. eg: xmpp, ldap, etc.
 
    .PARAMETER Protocol
        Service transmission protocol ('TCP' or 'UDP')
 
    .PARAMETER Port
        The TCP or UDP port on which the service is found
 
    .PARAMETER Target
        Specifies the Target Hostname or IP Address.
 
    .PARAMETER Priority
        Specifies the Priority value of the SRV record.
 
    .PARAMETER Weight
        Specifies the weight of the SRV record.
 
    .PARAMETER TTL
        Specifies the TTL value of the SRV record. Value must be in valid TimeSpan format.
 
    .PARAMETER DnsServer
        Name of the DnsServer to create the record on.
 
    .PARAMETER Ensure
        Whether the host record should be present or removed.
#>

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Zone,

        [Parameter(Mandatory = $true)]
        [System.String]
        $SymbolicName,

        [Parameter(Mandatory = $true)]
        [ValidateSet('TCP', 'UDP')]
        [System.String]
        $Protocol,

        [Parameter(Mandatory = $true)]
        [ValidateRange(1, 65535)]
        [System.UInt16]
        $Port,

        [Parameter(Mandatory = $true)]
        [System.String]
        $Target,

        [Parameter(Mandatory = $true)]
        [System.UInt16]
        $Priority,

        [Parameter(Mandatory = $true)]
        [System.UInt16]
        $Weight,

        [Parameter()]
        [ValidateScript( { $ts = New-TimeSpan; [System.Timespan]::TryParse($_, [ref] $ts) })]
        [System.String]
        $TTL,

        [Parameter()]
        [System.String]
        $DnsServer = 'localhost',

        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure = 'Present'
    )

    $dnsParameters = @{
        ZoneName     = $Zone
        ComputerName = $DnsServer
    }

    $recordHostName = "_$($SymbolicName)._$($Protocol)".ToLower()

    $existingSrvRecord = Get-DnsServerResourceRecord @dnsParameters -RRType 'SRV' -ErrorAction SilentlyContinue | Where-Object {
        $_.HostName -eq $recordHostName -and
        $_.RecordData.Port -eq $Port -and
        $_.RecordData.DomainName -eq "$($Target)."
    }

    if ($Ensure -eq 'Present')
    {
        <#
            If the entry exists, update it instead of adding a new one
            Note that although mandatory, Priority and Weight are not key values
            and will always be overwritten with the values specified.
        #>

        if ($null -ne $existingSrvRecord)
        {
            # Clone the CIM instance record.
            $newSrvRecord = [Microsoft.Management.Infrastructure.CimInstance]::new($existingSrvRecord)

            # Priority, and Weight will always have values
            $newSrvRecord.RecordData.Priority = $Priority
            $newSrvRecord.RecordData.Weight = $Weight

            # TTL may not always have a value provided
            if ($PSBoundParameters.ContainsKey('TTL'))
            {
                <#
                    The value must be explicitly cast to a timespan,
                    otherwise it gets parsed as a date.
                #>

                $newSrvRecord.TimeToLive = [timespan] $TTL
            }

            $dnsParameters.Add('OldInputObject', $existingSrvRecord)
            $dnsParameters.Add('NewInputObject', $newSrvRecord)

            Write-Verbose -Message ($script:localizedData.UpdatingDnsRecordMessage -f 'SRV', $recordHostName, $Target, $Zone, $DnsServer)

            Set-DnsServerResourceRecord @dnsParameters
        }
        else
        {
            $dnsParameters.Add('Name', $recordHostName)
            $dnsParameters.Add('Srv', $true)
            $dnsParameters.Add('DomainName', $Target)
            $dnsParameters.Add('Port', $Port)
            $dnsParameters.Add('Priority', $Priority)
            $dnsParameters.Add('Weight', $Weight)

            if ($PSBoundParameters.ContainsKey('TTL'))
            {
                $dnsParameters.Add('TimeToLive', $TTL)
            }

            Write-Verbose -Message ($script:localizedData.CreatingDnsRecordMessage -f 'SRV', $recordHostName, $Target, $Zone, $DnsServer)

            Add-DnsServerResourceRecord @dnsParameters
        }
    }
    elseif ($Ensure -eq 'Absent')
    {
        if ($null -ne $existingSrvRecord)
        {
            Write-Verbose -Message ($script:localizedData.RemovingDnsRecordMessage -f 'SRV', $recordHostName, $Target, $Zone, $DnsServer)

            $existingSrvRecord | Remove-DnsServerResourceRecord @dnsParameters
        }
    }
} #end function Set-TargetResource

<#
    .SYNOPSIS
        This will return whether the resource is in desired state.
 
    .PARAMETER Zone
        Specifies the name of a DNS zone.
 
    .PARAMETER SymbolicName
        Service name for the SRV record. eg: xmpp, ldap, etc.
 
    .PARAMETER Protocol
        Service transmission protocol ('TCP' or 'UDP')
 
    .PARAMETER Port
        The TCP or UDP port on which the service is found
 
    .PARAMETER Target
        Specifies the Target Hostname or IP Address.
 
    .PARAMETER Priority
        Specifies the Priority value of the SRV record.
 
    .PARAMETER Weight
        Specifies the weight of the SRV record.
 
    .PARAMETER TTL
        Specifies the TTL value of the SRV record. Value must be in valid TimeSpan format.
 
    .PARAMETER DnsServer
        Name of the DnsServer to create the record on.
 
    .PARAMETER Ensure
        Whether the host record should be present or removed.
#>

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Zone,

        [Parameter(Mandatory = $true)]
        [System.String]
        $SymbolicName,

        [Parameter(Mandatory = $true)]
        [ValidateSet('TCP', 'UDP')]
        [System.String]
        $Protocol,

        [Parameter(Mandatory = $true)]
        [ValidateRange(1, 65535)]
        [System.UInt16]
        $Port,

        [Parameter(Mandatory = $true)]
        [System.String]
        $Target,

        [Parameter(Mandatory = $true)]
        [System.UInt16]
        $Priority,

        [Parameter(Mandatory = $true)]
        [System.UInt16]
        $Weight,

        [Parameter()]
        [ValidateScript( { $ts = New-TimeSpan; [System.Timespan]::TryParse($_, [ref] $ts) })]
        [System.String]
        $TTL,

        [Parameter()]
        [System.String]
        $DnsServer = 'localhost',

        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure = 'Present'
    )

    # Set up a variable to determine the result of the test
    $hasPassedTest = $true

    # Get-TargetResource does not take the full set of arguments
    $getTargetResourceParams = @{
        Zone         = $Zone
        SymbolicName = $SymbolicName
        Protocol     = $Protocol
        Port         = $Port
        Target       = $Target
        Priority     = $Priority
        Weight       = $Weight
        DnsServer    = $DnsServer
    }

    $result = Get-TargetResource @getTargetResourceParams

    $resultHostName = "_$($result.SymbolicName)._$($result.Protocol)"

    if ($Ensure -ne $result.Ensure)
    {
        Write-Verbose -Message ($script:localizedData.NotDesiredPropertyMessage -f 'Ensure', $Ensure, $result.Ensure)
        $hasPassedTest = $false
    }

    if ($Ensure -eq 'Present')
    {
        if ($result.SymbolicName -ne $SymbolicName)
        {
            Write-Verbose -Message ($script:localizedData.NotDesiredPropertyMessage -f 'SymbolicName', $SymbolicName, $result.SymbolicName)
            $hasPassedTest = $false
        }

        if ($result.Protocol -ne $Protocol)
        {
            Write-Verbose -Message ($script:localizedData.NotDesiredPropertyMessage -f 'Protocol', $Protocol.ToLower(), $result.Protocol)
            $hasPassedTest = $false
        }

        if ($result.Port -ne $Port)
        {
            Write-Verbose -Message ($script:localizedData.NotDesiredPropertyMessage -f 'Port', $Port, $result.Port)
            $hasPassedTest = $false
        }

        if ($result.Target -ne $Target)
        {
            Write-Verbose -Message ($script:localizedData.NotDesiredPropertyMessage -f 'Target', $Target, $result.Target)
            $hasPassedTest = $false
        }

        if ($result.Priority -ne $Priority)
        {
            Write-Verbose -Message ($script:localizedData.NotDesiredPropertyMessage -f 'Priority', $Priority, $result.Priority)
            $hasPassedTest = $false
        }

        if ($result.Weight -ne $Weight)
        {
            Write-Verbose -Message ($script:localizedData.NotDesiredPropertyMessage -f 'Weight', $Weight, $result.Weight)
            $hasPassedTest = $false
        }

        if ($PSBoundParameters.ContainsKey('TTL') -and $result.TTL -ne $TTL)
        {
            Write-Verbose -Message ($script:localizedData.NotDesiredPropertyMessage -f 'TTL', $TTL, $result.TTL)
            $hasPassedTest = $false
        }
    }

    if ($hasPassedTest)
    {
        Write-Verbose -Message ($script:localizedData.InDesiredStateMessage -f $resultHostName)
    }
    else
    {
        Write-Verbose -Message ($script:localizedData.NotInDesiredStateMessage -f $resultHostName)
    }

    return $hasPassedTest
} #end function Test-TargetResource

Export-ModuleMember -Function *-TargetResource