DSCResources/MSFT_xSQLPermission/MSFT_xSQLPermission.psm1

Import-Module -Name (Join-Path -Path (Split-Path (Split-Path $PSScriptRoot -Parent) -Parent) `
        -ChildPath 'xSQLHelper.psm1') `
    -Force
<#
    .SYNOPSIS
        Returns the current state of the permissions for the principal (login).

    .PARAMETER InstanceName
        The name of the SQL instance to be configured.

    .PARAMETER NodeName
        The host name of the SQL Server to be configured.

    .PARAMETER Principal
        The login to which permission will be set.

    .PARAMETER Permission
        The permission to set for the login. Valid values are AlterAnyAvailabilityGroup, ViewServerState or AlterAnyEndPoint.
#>

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

        [Parameter()]
        [System.String]
        $NodeName = $env:COMPUTERNAME,

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

        [Parameter()]
        [ValidateSet('ConnectSql', 'AlterAnyAvailabilityGroup', 'ViewServerState', 'AlterAnyEndPoint')]
        [System.String[]]
        $Permission
    )

    New-VerboseMessage -Message "Enumerating permissions for $Principal"

    try
    {
        $sqlServerObject = Connect-SQL -SQLServer $NodeName -SQLInstanceName $InstanceName

        # Gets a set of permissions granted based on the desired permissions in $Permission
        $desiredPermissionSet = Get-SQLServerPermissionSet -Permission $Permission
        $grantedPermissionSet = $sqlServerObject.EnumServerPermissions( $Principal, $desiredPermissionSet ) |
            Where-Object { $_.PermissionState -eq 'Grant' }

        if ($null -ne $grantedPermissionSet)
        {
            $concatenatedGrantedPermissionSet = Get-SQLServerPermissionSet -PermissionSet $grantedPermissionSet.PermissionType

            # Compare desired and granted permissions based on the permissions properties from $Permission.
            if (-not (Compare-Object -ReferenceObject $desiredPermissionSet -DifferenceObject $concatenatedGrantedPermissionSet -Property $Permission))
            {
                $ensure = 'Present'
            }
            else
            {
                $ensure = 'Absent'
            }

            # Return granted permissions as a string array.
            $grantedPermission = Get-SQLPermission -ServerPermissionSet $concatenatedGrantedPermissionSet
        }
        else
        {
            $ensure = 'Absent'
            $grantedPermission = ''
        }
    }
    catch
    {
        throw New-TerminatingError -ErrorType PermissionGetError -FormatArgs @($Principal) -ErrorCategory InvalidOperation -InnerException $_.Exception
    }

    return @{
        InstanceName = [System.String] $InstanceName
        NodeName     = [System.String] $NodeName
        Ensure       = [System.String] $ensure
        Principal    = [System.String] $Principal
        Permission   = [System.String[]] $grantedPermission
    }
}

<#
    .SYNOPSIS
        Grants or revokes the permission for the the principal (login).

    .PARAMETER InstanceName
        The name of the SQL instance to be configured.

    .PARAMETER NodeName
        The host name of the SQL Server to be configured.

    .PARAMETER Ensure
        If the permission should be present or absent. Default value is 'Present'.

    .PARAMETER Principal
        The login to which permission will be set.

    .PARAMETER Permission
        The permission to set for the login. Valid values are AlterAnyAvailabilityGroup, ViewServerState or AlterAnyEndPoint.
#>

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

        [Parameter()]
        [System.String]
        $NodeName = $env:COMPUTERNAME,

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

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

        [Parameter()]
        [ValidateSet('ConnectSql', 'AlterAnyAvailabilityGroup', 'ViewServerState', 'AlterAnyEndPoint')]
        [System.String[]]
        $Permission
    )

    $getTargetResourceParameters = @{
        InstanceName = [System.String] $InstanceName
        NodeName     = [System.String] $NodeName
        Principal    = [System.String] $Principal
        Permission   = [System.String[]] $Permission
    }

    $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters

    if ($getTargetResourceResult.Ensure -ne $Ensure)
    {
        try
        {
            $sqlServerObject = Connect-SQL -SQLServer $NodeName -SQLInstanceName $InstanceName

            $permissionSet = Get-SQLServerPermissionSet -Permission $Permission

            if ($Ensure -eq 'Present')
            {
                Write-Verbose -Message ('Grant permission for ''{0}''' -f $Principal)

                $sqlServerObject.Grant($permissionSet, $Principal)
            }
            else
            {
                Write-Verbose -Message ('Revoke permission for ''{0}''' -f $Principal)

                $sqlServerObject.Revoke($permissionSet, $Principal)
            }
        }
        catch
        {
            throw New-TerminatingError -ErrorType ChangingPermissionFailed -FormatArgs @($Principal) -ErrorCategory InvalidOperation -InnerException $_.Exception
        }
    }
    else
    {
        New-VerboseMessage -Message "State is already $Ensure"
    }
}

<#
    .SYNOPSIS
        Tests if the principal (login) has the desired permissions.

    .PARAMETER InstanceName
        The name of the SQL instance to be configured.

    .PARAMETER NodeName
        The host name of the SQL Server to be configured.

    .PARAMETER Ensure
        If the permission should be present or absent. Default value is 'Present'.

    .PARAMETER Principal
        The login to which permission will be set.

    .PARAMETER Permission
        The permission to set for the login. Valid values are AlterAnyAvailabilityGroup, ViewServerState or AlterAnyEndPoint.
#>

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

        [Parameter()]
        [System.String]
        $NodeName = $env:COMPUTERNAME,

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

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

        [Parameter()]
        [ValidateSet('ConnectSql', 'AlterAnyAvailabilityGroup', 'ViewServerState', 'AlterAnyEndPoint')]
        [System.String[]]
        $Permission
    )

    $getTargetResourceParameters = @{
        InstanceName = $InstanceName
        NodeName     = $NodeName
        Principal    = $Principal
        Permission   = $Permission
    }

    New-VerboseMessage -Message "Verifying permissions for $Principal"

    $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters

    return $getTargetResourceResult.Ensure -eq $Ensure
}

<#
    .SYNOPSIS
       Takes a Microsoft.SqlServer.Management.Smo.ServerPermissionSet object which will be
       enumerated and returned as a string array.

    .PARAMETER ServerPermissionSet
        A PermissionSet object which should be enumerated.
#>

function Get-SQLPermission
{
    [CmdletBinding()]
    [OutputType([System.String[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [Microsoft.SqlServer.Management.Smo.ServerPermissionSet]
        [ValidateNotNullOrEmpty()]
        $ServerPermissionSet
    )

    [System.String[]] $permissionArray = @()

    foreach ($property in $($ServerPermissionSet | Get-Member -Type Property))
    {
        if ($ServerPermissionSet.$($property.Name))
        {
            $permissionArray += $property.Name
        }
    }

    return $permissionArray
}

<#
    .SYNOPSIS
       Takes either an array of strings or an array of Microsoft.SqlServer.Management.Smo.ServerPermissionSet objects which
       will be enumerated and concatenated to a single Microsoft.SqlServer.Management.Smo.ServerPermissionSet object.

    .PARAMETER Permission
        An array of strings which should be concatenated to a single Microsoft.SqlServer.Management.Smo.ServerPermissionSet object.

    .PARAMETER ServerPermissionSet
        An array of Microsoft.SqlServer.Management.Smo.ServerPermissionSet objects which should be concatenated to a single
        Microsoft.SqlServer.Management.Smo.ServerPermissionSet object.
#>

function Get-SQLServerPermissionSet
{
    [CmdletBinding()]
    [OutputType([Object])]
    param
    (
        [Parameter(Mandatory = $true, ParameterSetName = 'Permission')]
        [System.String[]]
        [ValidateNotNullOrEmpty()]
        $Permission,

        [Parameter(Mandatory = $true, ParameterSetName = 'ServerPermissionSet')]
        [Microsoft.SqlServer.Management.Smo.ServerPermissionSet[]]
        [ValidateNotNullOrEmpty()]
        $PermissionSet
    )

    if ($Permission)
    {
        [Microsoft.SqlServer.Management.Smo.ServerPermissionSet] $concatenatedPermissionSet = New-Object -TypeName Microsoft.SqlServer.Management.Smo.ServerPermissionSet

        foreach ($currentPermission in $Permission)
        {
            $concatenatedPermissionSet.$($currentPermission) = $true
        }
    }
    else
    {
        $concatenatedPermissionSet = Merge-SQLPermissionSet -Object $PermissionSet
    }

    return $concatenatedPermissionSet
}

<#
    .SYNOPSIS
       Merges an array of any PermissionSet objects into a single PermissionSet.

       The though with this helper function si it can be used for any permission set object
       because all inheriths from Microsoft.SqlServer.Management.Smo.PermissionSetBase.

    .PARAMETER Object
        An array of strings which should be concatenated to a single PermissionSet object.
#>

function Merge-SQLPermissionSet
{
    param
    (
        [Parameter(Mandatory = $true)]
        [Object[]]
        [ValidateNotNullOrEmpty()]
        $Object
    )

    $baseObject = New-Object -TypeName ($Object[0].GetType())

    foreach ($currentObject in $Object)
    {
        foreach ($Property in $($currentObject | Get-Member -Type Property))
        {
            if ($currentObject.$($Property.Name))
            {
                $baseObject.$($Property.Name) = $currentObject.$($Property.Name)
            }
        }
    }

    return $baseObject
}

Export-ModuleMember -Function *-TargetResource