DSCResources/MSFT_xAzureSqlDatabase/MSFT_xAzureSqlDatabase.psm1

data localizedData
{
    # culture="en-US"
    ConvertFrom-StringData @'
DatabaseServerCouldNotBeCreatedError=Could not create database in the following location: "{0}". Verify you did not exceed database server limit.
DatabaseServerNotExistsError=Database server "{0}" does not exist. You have to specify name of existing server.
'@

}

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

        [parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $ServerCredential,
        
        [parameter(Mandatory = $true)]
        [System.String]
        $ServerName,

        [parameter(Mandatory = $true)]
        [System.String]
        $RuleName,

        [parameter(Mandatory = $true)]
        [System.String]
        $RuleStartIPAddress,

        [parameter(Mandatory = $true)]
        [System.String]
        $RuleEndIPAddress
    )

    # Verify that database server with given name exists
    Write-Verbose "Verifying that server '$ServerName' exists"
    
    try {
        $server = Get-AzureSqlDatabaseServer -ServerName $ServerName -ErrorAction Stop
        Write-Verbose "Server '$ServerName' exists"
    } catch [System.Exception] {
        # Throw exception if specified ServerName does not exist
        $errorMessage = $($LocalizedData.DatabaseServerNotExistsError) -f ${ServerName} 
        $errorMessage += "`nException message: `n" + $_
        Throw-InvalidOperationException -errorId "DatabaseServerNotExistsFailure" -errorMessage $errorMessage
    }

    $nameState = $Name
    $maxSizeGBState = $null
    $collationState = $null
    $editionState = $null
    $serverCredentialState = $null
    $serverNameState = $ServerName
    $ruleNameState = $RuleName
    $ruleStartIPAddressState = $null
    $ruleEndIPAddressState = $null
    $ensureState = $null

    # Get firewall rule state
    $currentFirewallRule = Get-AzureSqlDatabaseServerFirewallRule -ServerName $ServerName -RuleName $RuleName  -ErrorAction SilentlyContinue
    Write-Verbose "Getting status of firewall rule '$RuleName'"
    if ($currentFirewallRule) {
        $ruleStartIPAddressState = $currentFirewallRule.StartIPAddress
        $ruleEndIPAddressState = $currentFirewallRule.EndIPAddress
    }

    # Get database state
    Write-Verbose "Creating database server context for $ServerName"
    $databaseServerContext = New-AzureSqlDatabaseServerContext -ServerName $ServerName -Credential $ServerCredential

    Write-Verbose "Checking whether Azure SQL database '$Name' exists"
    $currentDatabase = Get-AzureSqlDatabase -ConnectionContext $databaseServerContext -DatabaseName $Name -ErrorAction SilentlyContinue

    Write-Verbose "Getting status of database '$Name'"
    if ($currentDatabase) {
        $maxSizeGBState = $currentDatabase.MaxSizeGB
        $collationState = $currentDatabase.CollationName
        $editionState = $currentDatabase.Edition
        $ensureState = "Present"
        
    } else {
        $ensureState = "Absent"
    }

    $returnValue = @{
        Name = $nameState
        MaxSizeGB = $maxSizeGBState
        Collation = $collationState
        Edition = $editionState
        ServerCredential = $serverCredentialState
        ServerName = $serverNameState
        RuleName = $ruleNameState
        RuleStartIPAddress = $ruleStartIPAddressState
        RuleEndIPAddress = $ruleEndIPAddressState
        Ensure = $ensureState
    }

    $returnValue
}


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

        [System.UInt32]
        $MaxSizeGB = 1,

        [System.String]
        $Collation = "SQL_Latin1_General_CP1_CI_AS",

        [System.String]
        $Edition = "Web",

        [parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $ServerCredential,

        [parameter(Mandatory = $true)]
        [System.String]
        $ServerName,

        [parameter(Mandatory = $true)]
        [System.String]
        $RuleName,

        [parameter(Mandatory = $true)]
        [System.String]
        $RuleStartIPAddress,

        [parameter(Mandatory = $true)]
        [System.String]
        $RuleEndIPAddress,

        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure = "Present"
    )
    # Verify that database server with given name exists
    Write-Verbose "Verifying that server '$ServerName' exists"
    
    try {
        $server = Get-AzureSqlDatabaseServer -ServerName $ServerName -ErrorAction Stop
        Write-Verbose "Server '$ServerName' exists"
    } catch [System.Exception] {
        # Throw exception if specified ServerName does not exist
        $errorMessage = $($LocalizedData.DatabaseServerNotExistsError) -f ${ServerName} 
        $errorMessage += "`nException message: `n" + $_
        Throw-InvalidOperationException -errorId "DatabaseServerNotExistsFailure" -errorMessage $errorMessage
    }

    # Check whether firewall rule exists
    Write-Verbose "Checking whether SQL database server firewall rule '$RuleName' exists"
    $firewallRuleExists = $false
 
    $currentFirewallRule = Get-AzureSqlDatabaseServerFirewallRule -ServerName $ServerName -RuleName $RuleName  -ErrorAction SilentlyContinue
    if ($currentFirewallRule) {
        $firewallRuleExists = $true
        Write-Verbose "SQL database server firewall rule '$RuleName' exists"
    } else {
        Write-Verbose "SQL database server firewall rule '$RuleName' does not exist"
    }

    # If we want to ensure database is present
    if ($Ensure -eq "Present")
    {
        # Create firewall rule if not exists
        if (-not $firewallRuleExists) {
            Write-Verbose "Creating firewall rule '$RuleName'"
            New-AzureSqlDatabaseServerFirewallRule -ServerName $ServerName -RuleName $RuleName -StartIpAddress $RuleStartIPAddress -EndIpAddress $RuleEndIPAddress -ErrorAction Stop | Out-Null
        } else {
            Write-Verbose "Updating firewall rule '$RuleName'"
            Set-AzureSqlDatabaseServerFirewallRule -ServerName $ServerName -RuleName $RuleName -StartIpAddress $RuleStartIPAddress -EndIpAddress $RuleEndIPAddress -ErrorAction Stop | Out-Null
        }
        
        # Create database server context
        Write-Verbose "Creating database server context for $ServerName"
        $databaseServerContext = New-AzureSqlDatabaseServerContext -ServerName $ServerName -Credential $ServerCredential

        Write-Verbose "Checking whether Azure SQL database '$Name' exists"
        $database = Get-AzureSqlDatabase -ConnectionContext $databaseServerContext -DatabaseName $Name -ErrorAction SilentlyContinue

        if ($database) {
            Write-Verbose "Azure SQL database '$Name' exists."
            
            # Get passed values
            $splat = @{}
            $value = $null

            $paramName = "MaxSizeGB"
            if ($PSBoundParameters.TryGetValue($paramName, [ref]$value)) {
                $splat.Add($paramName, $value)
            }
            
            $paramName = "Edition"
            if ($PSBoundParameters.TryGetValue($paramName, [ref]$value)) {
                $splat.Add($paramName, $value)
            }

            if ($splat.Count -ne 0) {
                # Update database with passed values
                Write-Verbose "Updating database '$Name' with properties specified by user."
                $database = Set-AzureSqlDatabase -ConnectionContext $databaseServerContext -DatabaseName $Name @splat -ErrorAction Stop
            } else {
                Write-Verbose "No need for updating database '$Name' cause no additional properties were passed by the user."
            }

            # If user specified collation which is different than current one, delete and recreate database (collation cannot be changed)
            $paramName = "Collation"
            if ($PSBoundParameters.TryGetValue($paramName, [ref]$value)) {
                if ($value -ne $database.CollationName) {
                    Write-Verbose "Collation was specified with value '$value' which is different than current collation: '$($database.CollationName)'. Will remove and recreate database."
                    Remove-AzureSqlDatabase -ConnectionContext $databaseServerContext -DatabaseName $Name -Force
                    Write-Verbose "Azure SQL database '$Name' has been removed. Recreating it..."
                    $database = New-AzureSqlDatabase -ConnectionContext $databaseServerContext -DatabaseName $Name -MaxSizeGB $MaxSizeGB -Collation $Collation -Edition $Edition -ErrorAction Stop
                    Write-Verbose "Azure SQL database '$Name' has been created"
                }
            }

        } else {
            Write-Verbose "Azure SQL database '$Name' does not exist. Creating it..."
            if ($Collation) {
                $database = New-AzureSqlDatabase -ConnectionContext $databaseServerContext -DatabaseName $Name -MaxSizeGB $MaxSizeGB -Collation $Collation -Edition $Edition -ErrorAction Stop
            } else {
                $database = New-AzureSqlDatabase -ConnectionContext $databaseServerContext -DatabaseName $Name -MaxSizeGB $MaxSizeGB -Edition $Edition -ErrorAction Stop
            }
            Write-Verbose "Azure SQL database '$Name' has been created"
        }
    }
    # If we want to ensure database is absent
    elseif ($Ensure -eq "Absent")
    {
        # Create database server context
        Write-Verbose "Creating database server context for $ServerName"
        $databaseServerContext = New-AzureSqlDatabaseServerContext -ServerName $ServerName -Credential $ServerCredential

        # Remove firewall rule
        if ($firewallRuleExists) {
            Write-Verbose "Firewall rule '$RuleName' exists. Removing it..."
            Remove-AzureSqlDatabaseServerFirewallRule -ServerName $ServerName -RuleName $RuleName
            Write-Verbose "Firewall rule '$RuleName' has been removed."
        } else {
            Write-Verbose "Firewall rule '$RuleName' does not exist. No need to remove."
        }

        # Remove database
        Write-Verbose "Checking whether Azure SQL database '$Name' exists."
        $database = Get-AzureSqlDatabase -ConnectionContext $databaseServerContext -DatabaseName $Name -ErrorAction SilentlyContinue

        if ($database) {
            Write-Verbose "Azure SQL database '$Name' exists. Removing it..."
            Remove-AzureSqlDatabase -ConnectionContext $databaseServerContext -DatabaseName $Name -Force
            Write-Verbose "Azure SQL database '$Name' has been removed."
        } else {
            Write-Verbose "Azure SQL database '$Name' does not exist. No need to remove."
        }
    }
}


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

        [System.UInt32]
        $MaxSizeGB = 1,

        [System.String]
        $Collation = "SQL_Latin1_General_CP1_CI_AS",

        [System.String]
        $Edition = "Web",

        [parameter(Mandatory = $true)]
        [System.Management.Automation.PSCredential]
        $ServerCredential,

        [parameter(Mandatory = $true)]
        [System.String]
        $ServerName,

        [parameter(Mandatory = $true)]
        [System.String]
        $RuleName,

        [parameter(Mandatory = $true)]
        [System.String]
        $RuleStartIPAddress,

        [parameter(Mandatory = $true)]
        [System.String]
        $RuleEndIPAddress,

        [ValidateSet("Present","Absent")]
        [System.String]
        $Ensure = "Present"
    )
    
    $testResult = $true;

    if ($Ensure -eq "Present")
    {
        # Verify that database server with given name exists
        Write-Verbose "Verifying that server '$ServerName' exists"
    
        try {
            $server = Get-AzureSqlDatabaseServer -ServerName $ServerName -ErrorAction Stop
            Write-Verbose "Server '$ServerName' exists"
        } catch [System.Exception] {
            # Throw exception if specified ServerName does not exist
            $errorMessage = $($LocalizedData.DatabaseServerNotExistsError) -f ${ServerName} 
            $errorMessage += "`nException message: `n" + $_
            Throw-InvalidOperationException -errorId "DatabaseServerNotExistsFailure" -errorMessage $errorMessage
        }

        # Check whether firewall rule is in desired state
        Write-Verbose "Checking whether SQL database server firewall rule '$RuleName' exists"

        $currentFirewallRule = Get-AzureSqlDatabaseServerFirewallRule -ServerName $ServerName -RuleName $RuleName  -ErrorAction SilentlyContinue
        if ($currentFirewallRule) {
            # Check whether current firewall rule properties match the specified ones
            if($currentFirewallRule.StartIpAddress.Equals($RuleStartIPAddress) -and $currentFirewallRule.EndIpAddress.Equals($RuleEndIPAddress)) {
                Write-Verbose "SQL database server firewall rule '$RuleName' exists and properties match."
            } else {
                $testResult = $false
                Write-Verbose "SQL database server firewall rule '$RuleName' exists but properties don't match. Resource is not in desired state."
            }
        } else {
            Write-Verbose "SQL database server firewall rule '$RuleName' does not exist. Resource is not in desired state."
            $testResult = $false
        }

        # Check whether database is in desired state
        Write-Verbose "Creating database server context for $ServerName"
        $databaseServerContext = New-AzureSqlDatabaseServerContext -ServerName $ServerName -Credential $ServerCredential

        Write-Verbose "Checking whether Azure SQL database '$Name' exists"
        $currentDatabase = Get-AzureSqlDatabase -ConnectionContext $databaseServerContext -DatabaseName $Name -ErrorAction SilentlyContinue

        if ($currentDatabase) {
        
            # Check whether current database properties match the specified ones
            if ($currentDatabase.Edition.Equals($Edition) -and ($currentDatabase.MaxSizeGB -eq $MaxSizeGB) -and $currentDatabase.CollationName.Equals($Collation)) {
                Write-Verbose "Azure SQL database '$Name' exists and properties match."
            } else {
                $testResult = $false
                Write-Verbose "Azure SQL database '$Name' exists but properties don't match. Resource is not in desired state."
            }
        } else {
            Write-Verbose "Azure SQL database '$Name' does not exist. Resource is not in desired state."
            $testResult = $false
        }
    } 
    elseif ($Ensure -eq "Absent") 
    {
        # Check whether database is in desired state
        Write-Verbose "Creating database server context for $ServerName"
        $databaseServerContext = New-AzureSqlDatabaseServerContext -ServerName $ServerName -Credential $ServerCredential

        Write-Verbose "Checking whether Azure SQL database '$Name' exists"
        $currentDatabase = Get-AzureSqlDatabase -ConnectionContext $databaseServerContext -DatabaseName $Name -ErrorAction SilentlyContinue

        if ($currentDatabase) {
            Write-Verbose "Azure SQL database '$Name' exists. Resource is not in desired state."
            $testResult = $false
        }
    }

    return $testResult
}

# Throws terminating error of category InvalidOperation with specified errorId and errorMessage
function Throw-InvalidOperationException
{
    param(
        [parameter(Mandatory = $true)]
        [System.String] 
        $errorId,
        [parameter(Mandatory = $true)]
        [System.String]
        $errorMessage
    )
    
    $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation
    $exception = New-Object System.InvalidOperationException $errorMessage 
    $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null
    throw $errorRecord
}

Export-ModuleMember -Function *-TargetResource