DSCResources/DSC_SqlEndpoint/DSC_SqlEndpoint.psm1

$script:sqlServerDscHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\SqlServerDsc.Common'
$script:resourceHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common'

Import-Module -Name $script:sqlServerDscHelperModulePath
Import-Module -Name $script:resourceHelperModulePath

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

<#
    .SYNOPSIS
        Returns the current state of the endpoint.
 
    .PARAMETER EndpointName
        The name of the endpoint.
 
    .PARAMETER EndpointType
        Specifies the type of endpoint. Currently the only types that are supported
        are Database Mirror and Service Broker.
 
    .PARAMETER ServerName
        The host name of the SQL Server to be configured. Default value is the
        current computer name.
 
    .PARAMETER InstanceName
        The name of the SQL instance to be configured.
 
    .NOTES
        Get-TargetResource throws an error when the endpoint does not match the
        endpoint type. This is because the endpoint cannot be changed once
        the endpoint have been created and manual intervention is needed.
        Also Set-TargetResource and Test-TargetResource depends on that the
        Get-TargetResource does this check so we don't need to have the same
        check in those functions as well.
#>

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

        [Parameter(Mandatory = $true)]
        [ValidateSet('DatabaseMirroring', 'ServiceBroker')]
        [System.String]
        $EndpointType,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ServerName = (Get-ComputerName),

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

    Write-Verbose -Message (
        $script:localizedData.GetEndpoint -f $EndpointName, $InstanceName
    )

    $getTargetResourceReturnValues = @{
        ServerName                 = $ServerName
        InstanceName               = $InstanceName
        EndpointType               = $EndpointType
        Ensure                     = 'Absent'
        EndpointName               = ''
        Port                       = ''
        IpAddress                  = ''
        Owner                      = ''
        State                      = $null
        IsMessageForwardingEnabled = $null
        MessageForwardingSize      = $null
    }

    $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName -ErrorAction 'Stop'

    if ($sqlServerObject)
    {
        Write-Verbose -Message (
            $script:localizedData.ConnectedToInstance -f $ServerName, $InstanceName
        )

        $endpointObject = $sqlServerObject.Endpoints[$EndpointName]
        if ($endpointObject.Name -eq $EndpointName)
        {
            if ($endpointObject.EndpointType -ne $EndpointType)
            {
                $errorMessage = $script:localizedData.EndpointFoundButWrongType -f $EndpointName, $endpointObject.EndpointType, $EndpointType
                New-InvalidOperationException -Message $errorMessage
            }

            $getTargetResourceReturnValues.Ensure = 'Present'
            $getTargetResourceReturnValues.EndpointName = $endpointObject.Name
            $getTargetResourceReturnValues.Port = $endpointObject.Protocol.Tcp.ListenerPort
            $getTargetResourceReturnValues.IpAddress = $endpointObject.Protocol.Tcp.ListenerIPAddress
            $getTargetResourceReturnValues.Owner = $endpointObject.Owner
            $getTargetResourceReturnValues.State = $endpointObject.EndpointState
            if ($endpointObject.EndpointType -eq 'ServiceBroker')
            {
                $getTargetResourceReturnValues.IsMessageForwardingEnabled = $endpointObject.Payload.ServiceBroker.IsMessageForwardingEnabled
                $getTargetResourceReturnValues.MessageForwardingSize = $endpointObject.Payload.ServiceBroker.MessageForwardingSize
            }
        }
    }
    else
    {
        $errorMessage = $script:localizedData.NotConnectedToInstance -f $ServerName, $InstanceName
        New-InvalidOperationException -Message $errorMessage
    }

    return $getTargetResourceReturnValues
}

<#
    .SYNOPSIS
        Create, changes or drops an endpoint.
 
    .PARAMETER EndpointName
        The name of the endpoint.
 
    .PARAMETER EndpointType
        Specifies the type of endpoint. Currently the only types that are supported
        are Database Mirror and Service Broker.
 
    .PARAMETER Ensure
        If the endpoint should be present or absent. Default values is 'Present'.
 
    .PARAMETER Port
        The network port the endpoint is listening on. Default value is 5022, but
        default value is only used during endpoint creation, it is not enforced.
 
    .PARAMETER ServerName
        The host name of the SQL Server to be configured. Default value is the
        current computer name.
 
    .PARAMETER InstanceName
        The name of the SQL instance to be configured.
 
    .PARAMETER IpAddress
        The network IP address the endpoint is listening on. Default value is '0.0.0.0'
        which means listen on any valid IP address. The default value is only used
        during endpoint creation, it is not enforce.
 
    .PARAMETER Owner
        The owner of the endpoint. Default is the login used for the creation.
 
    .PARAMETER EnableMessageForwarding
        Determines whether messages received by this endpoint that are for services located elsewhere will be forwarded.
 
    .PARAMETER MessageForwardingSize
        Specifies the maximum amount of storage in megabytes to allocate for the endpoint to use when storing messages that are to be forwarded.
 
    .PARAMETER State
        Specifies the state of the endpoint. Valid states are Started, Stopped, or
        Disabled. When an endpoint is created and the state is not specified then
        the endpoint will be started after it is created. The state will not be
        enforced unless the parameter is specified.
 
 
#>

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

        [Parameter(Mandatory = $true)]
        [ValidateSet('DatabaseMirroring', 'ServiceBroker')]
        [System.String]
        $EndpointType,

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

        [Parameter()]
        [System.UInt16]
        $Port = 5022,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ServerName = (Get-ComputerName),

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

        [Parameter()]
        [System.String]
        $IpAddress = '0.0.0.0',

        [Parameter()]
        [System.String]
        $Owner,

        [Parameter()]
        [System.Boolean]
        $IsMessageForwardingEnabled,

        [Parameter()]
        [System.UInt32]
        $MessageForwardingSize,

        [Parameter()]
        [ValidateSet('Started', 'Stopped', 'Disabled')]
        [System.String]
        $State
    )

    $getTargetResourceParameters = @{
        EndpointName = $EndpointName
        EndpointType = $EndpointType
        ServerName   = $ServerName
        InstanceName = $InstanceName
    }

    $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters

    $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName -ErrorAction 'Stop'

    if ($sqlServerObject)
    {
        if ($Ensure -eq 'Present')
        {
            if ($getTargetResourceResult.Ensure -eq 'Absent')
            {
                Write-Verbose -Message (
                    $script:localizedData.CreateEndpoint -f $EndpointName, $InstanceName
                )

                if ($EndpointType -in @('DatabaseMirroring', 'ServiceBroker'))
                {
                    $endpointObject = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.Endpoint' -ArgumentList @($sqlServerObject, $EndpointName)

                    $endpointObject.ProtocolType = [Microsoft.SqlServer.Management.Smo.ProtocolType]::Tcp
                    $endpointObject.Protocol.Tcp.ListenerPort = $Port
                    $endpointObject.Protocol.Tcp.ListenerIPAddress = $IpAddress

                    if ($PSBoundParameters.ContainsKey('Owner'))
                    {
                        $endpointObject.Owner = $Owner
                    }

                    switch ($EndpointType)
                    {
                        'DatabaseMirroring'
                        {
                            $endpointObject.EndpointType = [Microsoft.SqlServer.Management.Smo.EndpointType]::DatabaseMirroring
                            $endpointObject.Payload.DatabaseMirroring.ServerMirroringRole = [Microsoft.SqlServer.Management.Smo.ServerMirroringRole]::All
                            $endpointObject.Payload.DatabaseMirroring.EndpointEncryption = [Microsoft.SqlServer.Management.Smo.EndpointEncryption]::Required
                            $endpointObject.Payload.DatabaseMirroring.EndpointEncryptionAlgorithm = [Microsoft.SqlServer.Management.Smo.EndpointEncryptionAlgorithm]::Aes
                            $endpointObject.Create()
                        }

                        'ServiceBroker'
                        {
                            $endpointObject.EndpointType = [Microsoft.SqlServer.Management.Smo.EndpointType]::ServiceBroker
                            $endpointObject.Payload.ServiceBroker.EndpointEncryption = [Microsoft.SqlServer.Management.Smo.EndpointEncryption]::Required
                            $endpointObject.Payload.ServiceBroker.EndpointEncryptionAlgorithm = [Microsoft.SqlServer.Management.Smo.EndpointEncryptionAlgorithm]::Aes
                            $endpointObject.Create()
                        }
                    }

                    <#
                        If endpoint state is not specified, then default to
                        starting the endpoint. If state is specified then
                        it will be handled later.
                    #>

                    if (-not ($PSBoundParameters.ContainsKey('State')))
                    {
                        $endpointObject.Start()
                    }
                }
            }
            else
            {
                Write-Verbose -Message (
                    $script:localizedData.SetEndpoint -f $EndpointName, $InstanceName
                )

                $endpointObject = $sqlServerObject.Endpoints[$EndpointName]

                if (-not $endpointObject)
                {
                    $errorMessage = $script:localizedData.EndpointNotFound -f $EndpointName

                    New-ObjectNotFoundException -Message $errorMessage
                }
            }

            <#
                The endpoint exist or was just created. Verifying supported
                properties so they are in desired state.
            #>


            # Properties regardless of endpoint type.
            if ($PSBoundParameters.ContainsKey('State'))
            {
                if ($endpointObject.EndpointState -ne $State)
                {
                    Write-Verbose -Message (
                        $script:localizedData.ChangingEndpointState -f $State
                    )

                    switch ($State)
                    {
                        'Started'
                        {
                            $endpointObject.Start()
                        }

                        'Stopped'
                        {
                            $endpointObject.Stop()
                        }

                        'Disabled'
                        {
                            $endpointObject.Disable()
                        }
                    }
                }
            }

            #These are for all endpoints
            if ($PSBoundParameters.ContainsKey('IpAddress'))
            {
                if ($endpointObject.Protocol.Tcp.ListenerIPAddress -ne $IpAddress)
                {
                    Write-Verbose -Message (
                        $script:localizedData.UpdatingEndpointIPAddress -f $IpAddress
                    )

                    $endpointObject.Protocol.Tcp.ListenerIPAddress = $IpAddress
                    $endpointObject.Alter()
                }
            }

            if ($PSBoundParameters.ContainsKey('Port'))
            {
                if ($endpointObject.Protocol.Tcp.ListenerPort -ne $Port)
                {
                    Write-Verbose -Message (
                        $script:localizedData.UpdatingEndpointPort -f $Port
                    )

                    $endpointObject.Protocol.Tcp.ListenerPort = $Port
                    $endpointObject.Alter()
                }
            }

            if ($PSBoundParameters.ContainsKey('Owner'))
            {
                if ($endpointObject.Owner -ne $Owner)
                {
                    Write-Verbose -Message (
                        $script:localizedData.UpdatingEndpointOwner -f $Owner
                    )

                    $endpointObject.Owner = $Owner
                    $endpointObject.Alter()
                }
            }

            # Individual endpoint type properties.
            switch ($EndpointType)
            {
                'DatabaseMirroring'
                {
                    break
                }

                'ServiceBroker'
                {
                    if ($PSBoundParameters.ContainsKey('IsMessageForwardingEnabled'))
                    {
                        if ($endpointObject.Payload.ServiceBroker.IsMessageForwardingEnabled -ne $IsMessageForwardingEnabled)
                        {
                            Write-Verbose -Message (
                                $script:localizedData.UpdatingEndpointIsMessageForwardingEnabled -f $IsMessageForwardingEnabled
                            )

                            $endpointObject.Payload.ServiceBroker.IsMessageForwardingEnabled = $IsMessageForwardingEnabled
                            $endpointObject.Alter()
                        }
                    }

                    if ($PSBoundParameters.ContainsKey('MessageForwardingSize'))
                    {
                        if ($endpointObject.Payload.ServiceBroker.MessageForwardingSize -ne $MessageForwardingSize)
                        {
                            Write-Verbose -Message (
                                $script:localizedData.UpdatingEndpointMessageForwardingSize -f $MessageForwardingSize
                            )

                            $endpointObject.Payload.ServiceBroker.MessageForwardingSize = $MessageForwardingSize
                            $endpointObject.Alter()
                        }
                    }
                }
            }
        }

        if ($Ensure -eq 'Absent' -and $getTargetResourceResult.Ensure -eq 'Present')
        {
            $endpointObject = $sqlServerObject.Endpoints[$EndpointName]

            if ($endpointObject)
            {
                Write-Verbose -Message (
                    $script:localizedData.DropEndpoint -f $EndpointName, $InstanceName
                )

                $endpointObject.Drop()
            }
            else
            {
                $errorMessage = $script:localizedData.EndpointNotFound -f $EndpointName

                New-ObjectNotFoundException -Message $errorMessage
            }
        }
    }
    else
    {
        $errorMessage = $script:localizedData.NotConnectedToInstance -f $ServerName, $InstanceName

        New-InvalidOperationException -Message $errorMessage
    }
}

<#
    .SYNOPSIS
        Tests if the principal (login) has the desired permissions.
 
    .PARAMETER EndpointName
        The name of the endpoint.
 
    .PARAMETER EndpointType
        Specifies the type of endpoint. Currently the only types that are supported
        are Database Mirror and Service Broker.
 
    .PARAMETER Ensure
        If the endpoint should be present or absent. Default values is 'Present'.
 
    .PARAMETER Port
        The network port the endpoint is listening on. Default value is 5022, but
        default value is only used during endpoint creation, it is not enforce.
 
    .PARAMETER ServerName
        The host name of the SQL Server to be configured. Default value is the
        current computer name.
 
    .PARAMETER InstanceName
        The name of the SQL instance to be configured.
 
    .PARAMETER IpAddress
        The network IP address the endpoint is listening on. Default value is '0.0.0.0'
        which means listen on any valid IP address. The default value is only used
        during endpoint creation, it is not enforce.
 
    .PARAMETER Owner
        The owner of the endpoint. Default is the login used for the creation.
 
    .PARAMETER EnableMessageForwarding
        Determines whether messages received by this endpoint that are for services located elsewhere will be forwarded.
 
    .PARAMETER MessageForwardingSize
        Specifies the maximum amount of storage in megabytes to allocate for the endpoint to use when storing messages that are to be forwarded.
 
    .PARAMETER State
        Specifies the state of the endpoint. Valid states are Started, Stopped, or
        Disabled. When an endpoint is created and the state is not specified then
        the endpoint will be started after it is created. The state will not be
        enforced unless the parameter is specified.
#>

function Test-TargetResource
{
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('SqlServerDsc.AnalyzerRules\Measure-CommandsNeededToLoadSMO', '', Justification='The command Connect-Sql is called when Get-TargetResource is called')]
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $EndpointName,

        [Parameter(Mandatory = $true)]
        [ValidateSet('DatabaseMirroring', 'ServiceBroker')]
        [System.String]
        $EndpointType,

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

        [Parameter()]
        [System.UInt16]
        $Port = 5022,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ServerName = (Get-ComputerName),

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

        [Parameter()]
        [System.String]
        $IpAddress = '0.0.0.0',

        [Parameter()]
        [System.String]
        $Owner,

        [Parameter()]
        [System.Boolean]
        $IsMessageForwardingEnabled,

        [Parameter()]
        [System.UInt32]
        $MessageForwardingSize,

        [Parameter()]
        [ValidateSet('Started', 'Stopped', 'Disabled')]
        [System.String]
        $State
    )

    Write-Verbose -Message (
        $script:localizedData.TestingConfiguration -f $EndpointName, $InstanceName
    )

    $getTargetResourceParameters = @{
        EndpointName = $EndpointName
        EndpointType = $EndpointType
        ServerName   = $ServerName
        InstanceName = $InstanceName
    }

    $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters

    if ($getTargetResourceResult.Ensure -eq $Ensure)
    {
        $result = $true

        if ($PSBoundParameters.ContainsKey('Owner'))
        {
            if ($getTargetResourceResult.Owner -ne $Owner)
            {
                $result = $false
            }
        }

        if ($PSBoundParameters.ContainsKey('State'))
        {
            if ($getTargetResourceResult.State -ne $State)
            {
                $result = $false
            }
        }

        if ($getTargetResourceResult.EndpointType -in @('DatabaseMirroring', 'ServiceBroker'))
        {
            if ($PSBoundParameters.ContainsKey('IsMessageForwardingEnabled'))
            {
                if ($getTargetResourceResult.IsMessageForwardingEnabled -ne $IsMessageForwardingEnabled)
                {
                    $result = $false
                }
            }

            if ($PSBoundParameters.ContainsKey('MessageForwardingSize'))
            {
                if ($getTargetResourceResult.MessageForwardingSize -ne $MessageForwardingSize)
                {
                    $result = $false
                }
            }
        }

        if ($getTargetResourceResult.Ensure -eq 'Present' `
                -and (
                $getTargetResourceResult.Port -ne $Port `
                    -or $getTargetResourceResult.IpAddress -ne $IpAddress
            )
        )
        {
            $result = $false
        }
    }
    else
    {
        $result = $false
    }

    if ($result)
    {
        Write-Verbose -Message (
            $script:localizedData.InDesiredState -f $EndpointName
        )
    }
    else
    {
        Write-Verbose -Message (
            $script:localizedData.NotInDesiredState -f $EndpointName
        )
    }

    return $result
}