DSCResources/MSFT_SqlDatabaseRole/MSFT_SqlDatabaseRole.psm1

$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent
$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules'

$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.LocalizationHelper'
Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'DscResource.LocalizationHelper.psm1')

$script:resourceHelperModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'DscResource.Common'
Import-Module -Name (Join-Path -Path $script:resourceHelperModulePath -ChildPath 'DscResource.Common.psm1')

$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_SqlDatabaseRole'

<#
    .SYNOPSIS
    Returns the current state of the user memberships in the role(s).
 
    .PARAMETER Ensure
    Specifies the desired state of the membership of the role(s).
 
    .PARAMETER Name
    Specifies the name of the login that evaluated if it is member of the role(s).
 
    .PARAMETER ServerName
    Specifies the SQL server on which the instance exist.
 
    .PARAMETER InstanceName
    Specifies the SQL instance in which the database exist.
 
    .PARAMETER Database
    Specifies the database in which the login (user) and role(s) exist.
 
    .PARAMETER Role
    Specifies one or more roles to which the login (user) will be evaluated if it should be added or removed.
#>

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

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

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

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

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

    Write-Verbose -Message (
        $script:localizedData.GetDatabaseRole -f $Name, $Database, $InstanceName
    )

    $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName
    if ($sqlServerObject)
    {
        # Check database exists
        if ( -not ($sqlDatabaseObject = $sqlServerObject.Databases[$Database]) )
        {
            $errorMessage = $script:localizedData.DatabaseNotFound -f $Database
            New-ObjectNotFoundException -Message $errorMessage
        }

        # Check role exists
        foreach ($currentRole in $Role)
        {
            if ( -not ($sqlDatabaseObject.Roles[$currentRole]) )
            {
                $errorMessage = $script:localizedData.RoleNotFound -f $currentRole, $Database
                New-ObjectNotFoundException -Message $errorMessage
            }
        }

        # Check login exists
        if ( -not ($sqlServerObject.Logins[$Name]) )
        {
            $errorMessage = $script:localizedData.LoginNotFound -f $Name
            New-ObjectNotFoundException -Message $errorMessage
        }

        $ensure = 'Absent'
        $grantedRole = @()

        if ($sqlDatabaseUser = $sqlDatabaseObject.Users[$Name] )
        {
            foreach ($currentRole in $Role)
            {
                if ($sqlDatabaseUser.IsMember($currentRole))
                {
                    Write-Verbose -Message (
                        $script:localizedData.IsMember -f $Name, $currentRole, $Database
                    )

                    $grantedRole += $currentRole
                }
                else
                {
                    Write-Verbose -Message (
                        $script:localizedData.IsNotMember -f $Name, $currentRole, $Database
                    )
                }
            }

            if ( -not (Compare-Object -ReferenceObject $Role -DifferenceObject $grantedRole) )
            {
                $ensure = 'Present'
            }
        }
        else
        {
            Write-Verbose -Message (
                $script:localizedData.LoginIsNotUser -f $Name, $Database
            )
        }
    }

    $returnValue = @{
        Ensure       = $ensure
        Name         = $Name
        ServerName   = $ServerName
        InstanceName = $InstanceName
        Database     = $Database
        Role         = $grantedRole
    }

    $returnValue
}

<#
    .SYNOPSIS
    Adds the login (user) to each of the provided roles when Ensure is set to 'Present'.
    When Ensure is set to 'Absent' the login (user) will be removed from each of the provided roles.
    If the login does not exist as a user in the database, then the user will be created in the database using the login.
 
    .PARAMETER Ensure
    Specifies the desired state of the membership of the role(s).
 
    .PARAMETER Name
    Specifies the name of the login that evaluated if it is member of the role(s), if it is not it will be added.
    If the login does not exist as a user, a user will be created using the login.
 
    .PARAMETER ServerName
    Specifies the SQL server on which the instance exist.
 
    .PARAMETER InstanceName
    Specifies the SQL instance in which the database exist.
 
    .PARAMETER Database
    Specifies the database in which the login (user) and role(s) exist.
 
    .PARAMETER Role
    Specifies one or more roles to which the login (user) will be added or removed.
#>

function Set-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Ensure = 'Present',

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

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

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

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

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

    $sqlServerObject = Connect-SQL -ServerName $ServerName -InstanceName $InstanceName
    if ($sqlServerObject)
    {
        $sqlDatabaseObject = $sqlServerObject.Databases[$Database]

        switch ($Ensure)
        {
            'Present'
            {
                # Adding database user if it does not exist.
                if ( -not ($sqlDatabaseObject.Users[$Name]) )
                {
                    try
                    {
                        Write-Verbose -Message (
                            '{0} {1}' -f
                                ($script:localizedData.LoginIsNotUser -f $Name, $Database),
                                $script:localizedData.AddingLoginAsUser
                        )

                        $sqlDatabaseUser = New-Object -TypeName 'Microsoft.SqlServer.Management.Smo.User' `
                            -ArgumentList $sqlDatabaseObject, $Name
                        $sqlDatabaseUser.Login = $Name
                        $sqlDatabaseUser.Create()
                    }
                    catch
                    {
                        $errorMessage = $script:localizedData.FailedToAddUser -f $Name, $Database
                        New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
                    }
                }

                # Adding database user to the role.
                foreach ($currentRole in $Role)
                {
                    try
                    {
                        Write-Verbose -Message (
                            $script:localizedData.AddUserToRole -f $Name, $currentRole, $Database
                        )

                        $sqlDatabaseRole = $sqlDatabaseObject.Roles[$currentRole]
                        $sqlDatabaseRole.AddMember($Name)
                    }
                    catch
                    {
                        $errorMessage = $script:localizedData.FailedToAddUserToRole -f $Name, $currentRole, $Database
                        New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
                    }
                }
            }

            'Absent'
            {
                try
                {
                    foreach ($currentRole in $Role)
                    {
                        Write-Verbose -Message (
                            $script:localizedData.DropUserFromRole -f $Name, $currentRole, $Database
                        )

                        $sqlDatabaseRole = $sqlDatabaseObject.Roles[$currentRole]
                        $sqlDatabaseRole.DropMember($Name)
                    }
                }
                catch
                {
                    $errorMessage = $script:localizedData.FailedToDropUserFromRole -f $Name, $currentRole, $Database
                    New-InvalidOperationException -Message $errorMessage -ErrorRecord $_
                }
            }
        }
    }
}

<#
    .SYNOPSIS
    Tests if the login (user) has the desired state in each of the provided roles.
 
    .PARAMETER Ensure
    Specifies the desired state of the membership of the role(s).
 
    .PARAMETER Name
    Specifies the name of the login that evaluated if it is member of the role(s).
 
    .PARAMETER ServerName
    Specifies the SQL server on which the instance exist.
 
    .PARAMETER InstanceName
    Specifies the SQL instance in which the database exist.
 
    .PARAMETER Database
    Specifies the database in which the login (user) and role(s) exist.
 
    .PARAMETER Role
    Specifies one or more roles to which the login (user) will be tested if it should added or removed.
#>

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $Ensure = 'Present',

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

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

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

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

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

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

    $getTargetResourceParameters = @{
        InstanceName = $PSBoundParameters.InstanceName
        ServerName   = $PSBoundParameters.ServerName
        Role         = $PSBoundParameters.Role
        Database     = $PSBoundParameters.Database
        Name         = $PSBoundParameters.Name
    }

    $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters

    $isDatabaseRoleInDesiredState = $true

    switch ($Ensure)
    {
        'Absent'
        {
            if ($getTargetResourceResult.Ensure -ne 'Absent')
            {
                Write-Verbose -Message (
                    $script:localizedData.NotInDesiredStateAbsent -f $Name, $Database
                )

                $isDatabaseRoleInDesiredState = $false
            }
        }

        'Present'
        {
            if ($getTargetResourceResult.Ensure -ne 'Present')
            {
                Write-Verbose -Message (
                    $script:localizedData.NotInDesiredStatePresent -f $Name, $Database
                )

                $isDatabaseRoleInDesiredState = $false
            }
        }
    }

    if ($isDatabaseRoleInDesiredState)
    {
        Write-Verbose -Message (
            $script:localizedData.InDesiredState -f $Name, $Database
        )
    }

    return $isDatabaseRoleInDesiredState
}

Export-ModuleMember -Function *-TargetResource