DSCResources/DSC_SqlAlias/DSC_SqlAlias.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 SQL Server alias.
 
    .PARAMETER Name
        The name of Alias (e.g. svr01\\inst01).
#>

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

    $returnValue = @{
        Name              = [System.String] $Name
        Protocol          = [System.String] ''
        ServerName        = [System.String] $null
        TcpPort           = [System.UInt16] 0
        UseDynamicTcpPort = [System.Boolean] $false
        PipeName          = [System.String] ''
        Ensure            = [System.String] 'Absent'
    }

    $protocolTcp = 'DBMSSOCN'
    $protocolNamedPipes = 'DBNMPNTW'

    Write-Verbose -Message (
        $script:localizedData.GetClientAlias -f $Name
    )

    $itemValue = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\MSSQLServer\Client\ConnectTo' `
        -Name $Name `
        -ErrorAction SilentlyContinue

    if (((Get-CimInstance -ClassName win32_OperatingSystem).OSArchitecture) -eq '64-bit')
    {
        Write-Verbose -Message (
            $script:localizedData.OSArchitecture64Bit -f $Name
        )

        $isWow6432Node = $true
        $itemValueWow6432Node = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\MSSQLServer\Client\ConnectTo' `
            -Name $Name `
            -ErrorAction SilentlyContinue
    }

    if ((-not $isWow6432Node -and $null -ne $itemValue ) -or `
        (($null -ne $itemValue -and $null -ne $itemValueWow6432Node) -and `
            ($isWow6432Node -and $itemValueWow6432Node."$Name" -eq $itemValue."$Name")))
    {
        $itemConfig = $itemValue."$Name" | ConvertFrom-Csv -Header 'Protocol', 'ServerName', 'TcpPort'
        if ($itemConfig)
        {
            if ($itemConfig.Protocol -eq $protocolTcp)
            {
                $returnValue.Ensure = 'Present'
                $returnValue.Protocol = 'TCP'
                $returnValue.ServerName = $itemConfig.ServerName
                if ($itemConfig.TcpPort)
                {
                    $returnValue.TcpPort = $itemConfig.TcpPort
                    $returnValue.UseDynamicTcpPort = $false
                }
                else
                {
                    $returnValue.UseDynamicTcpPort = $true
                    $returnValue.TcpPort = 0
                }
            }
            elseif ($itemConfig.Protocol -eq $protocolNamedPipes)
            {
                $returnValue.Ensure = 'Present'
                $returnValue.Protocol = 'NP'
                $returnValue.PipeName = $itemConfig.ServerName
            }
        }
        else
        {
            $returnValue.Ensure = 'Absent'
        }
    }
    else
    {
        $returnValue.Ensure = 'Absent'
    }

    $returnValue
}

<#
    .SYNOPSIS
        Sets the desired state of the SQL Server alias.
 
    .PARAMETER Name
        The name of Alias (e.g. svr01\\inst01).
 
    .PARAMETER Protocol
        Protocol to use when connecting. Valid values are 'TCP' or 'NP' (Named Pipes).
        Default value is 'TCP'.
 
    .PARAMETER ServerName
        The SQL Server you are aliasing (the NetBIOS name or FQDN).
 
    .PARAMETER TcpPort
        The TCP port the SQL Server instance is listening on. Only used when protocol
        is set to 'TCP'. Default value is port 1433.
 
    .PARAMETER UseDynamicTcpPort
        The UseDynamicTcpPort specify that the Net-Library will determine the port
        dynamically. The port specified in Port number will not be used. Default
        value is $false.
 
    .PARAMETER Ensure
        Determines whether the alias should be added or removed. Default value is
        'Present'.
#>

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

        [Parameter()]
        [ValidateSet('TCP', 'NP')]
        [System.String]
        $Protocol = 'TCP',

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ServerName,

        [Parameter()]
        [System.UInt16]
        $TcpPort = 1433,

        [Parameter()]
        [System.Boolean]
        $UseDynamicTcpPort = $false,

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

    if ($Protocol -eq 'NP')
    {
        $itemValue = "DBNMPNTW,\\$ServerName\PIPE\sql\query"
    }

    if ($Protocol -eq 'TCP')
    {
        $itemValue = "DBMSSOCN,$ServerName"
        if (-not $UseDynamicTcpPort)
        {
            $itemValue += ",$TcpPort"
        }
    }

    $registryPath = 'HKLM:\SOFTWARE\Microsoft\MSSQLServer\Client\ConnectTo'
    $registryPathWow6432Node = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\MSSQLServer\Client\ConnectTo'

    if ($Ensure -eq 'Present')
    {
        Write-Verbose -Message (
            $script:localizedData.AddClientAlias64Bit -f $Name
        )

        if (-not (Test-Path -Path $registryPath))
        {
            New-Item -Path $registryPath -Force | Out-Null
        }

        Set-ItemProperty -Path $registryPath -Name $Name -Value $itemValue | Out-Null

        # If this is a 64-bit OS then also update Wow6432Node
        if (((Get-CimInstance -ClassName win32_OperatingSystem).OSArchitecture) -eq '64-bit')
        {
            Write-Verbose -Message (
                $script:localizedData.AddClientAlias32Bit -f $Name
            )

            if (-not (Test-Path -Path $registryPathWow6432Node))
            {
                New-Item -Path $registryPathWow6432Node -Force | Out-Null
            }

            Set-ItemProperty -Path $registryPathWow6432Node -Name $Name -Value $itemValue | Out-Null
        }
    }

    if ($Ensure -eq 'Absent')
    {
        Write-Verbose -Message (
            $script:localizedData.RemoveClientAlias64Bit -f $Name
        )

        if (Test-Path -Path $registryPath)
        {
            Remove-ItemProperty -Path $registryPath -Name $Name
        }

        # If this is a 64-bit OS then also remove from Wow6432Node
        if (((Get-CimInstance -ClassName win32_OperatingSystem).OSArchitecture) -eq '64-bit' `
                -and (Test-Path -Path $registryPathWow6432Node))
        {
            Write-Verbose -Message (
                $script:localizedData.RemoveClientAlias32Bit -f $Name
            )

            Remove-ItemProperty -Path $registryPathWow6432Node -Name $Name
        }
    }
}

<#
    .SYNOPSIS
        Determines the desired state of the SQL Server alias.
 
    .PARAMETER Name
        The name of Alias (e.g. svr01\\inst01).
 
    .PARAMETER Protocol
        Protocol to use when connecting. Valid values are 'TCP' or 'NP' (Named Pipes).
        Default value is 'TCP'.
 
    .PARAMETER ServerName
        The SQL Server you are aliasing (the NetBIOS name or FQDN).
 
    .PARAMETER TcpPort
        The TCP port the SQL Server instance is listening on. Only used when protocol
        is set to 'TCP'. Default value is port 1433.
 
    .PARAMETER UseDynamicTcpPort
        The UseDynamicTcpPort specify that the Net-Library will determine the port
        dynamically. The port specified in Port number will not be used. Default
        value is $false.
 
    .PARAMETER Ensure
        Determines whether the alias should be added or removed. Default value is
        'Present'.
#>

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

        [Parameter()]
        [ValidateSet('TCP', 'NP')]
        [System.String]
        $Protocol = 'TCP',

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ServerName,

        [Parameter()]
        [System.UInt16]
        $TcpPort = 1433,

        [Parameter()]
        [System.Boolean]
        $UseDynamicTcpPort = $false,

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

    Write-Verbose -Message (
        $script:localizedData.TestingConfiguration -f $Name
    )

    $result = $false

    $parameters = @{
        Name = $PSBoundParameters.Name
    }

    $currentValues = Get-TargetResource @parameters

    if ($Ensure -eq $currentValues.Ensure)
    {
        if ($Ensure -eq 'Absent')
        {
            Write-Verbose -Message (
                $script:localizedData.ClientAliasMissing -f $Name
            )

            $result = $true
        }
        else
        {
            Write-Verbose -Message (
                $script:localizedData.ClientAliasPresent -f $Name
            )

            if ($Protocol -eq $currentValues.Protocol)
            {
                if ($Protocol -eq 'NP' -and
                    $currentValues.PipeName -eq "\\$ServerName\PIPE\sql\query")
                {
                    $result = $true
                }
                elseif ($Protocol -eq 'TCP' -and
                    $UseDynamicTcpPort -and
                    $currentValues.ServerName -eq $ServerName)
                {
                    $result = $true
                }
                elseif ($Protocol -eq 'TCP' -and
                    -not $UseDynamicTcpPort -and
                    $currentValues.ServerName -eq $ServerName -and
                    $currentValues.TcpPort -eq $TcpPort)
                {
                    $result = $true
                }
            }
        }
    }

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

    return $result
}