DSCResources/DSC_CMSecurityRoles/DSC_CMSecurityRoles.psm1

$script:dscResourceCommonPath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common'
$script:configMgrResourcehelper = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\ConfigMgrCBDsc.ResourceHelper'

Import-Module -Name $script:dscResourceCommonPath
Import-Module -Name $script:configMgrResourcehelper

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

<#
    .SYNOPSIS
        This will return a hashtable of results.
 
    .PARAMETER SiteCode
        Specifies the SiteCode for the Configuration Manager site.
 
    .PARAMETER SecurityRoleName
        Specifies the Security Role Name.
#>

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

        [Parameter(Mandatory = $true)]
        [String]
        $SecurityRoleName
    )

    Write-Verbose -Message $script:localizedData.RetrieveSettingValue
    Import-ConfigMgrPowerShellModule -SiteCode $SiteCode
    Set-Location -Path "$($SiteCode):\"

    $role = Get-CMSecurityRole -Name $SecurityRoleName

    if ($role)
    {
        foreach ($item in $role.Operations)
        {
            $ops = $ops + "$($item.ObjectTypeId)=$($item.GrantedOperations);"
        }

        if ($role.NumberOfAdmins -ge 1)
        {
            $users = Get-CMAdministrativeUser

            foreach ($user in $users)
            {
                if ($user.RoleNames -Contains $SecurityRoleName)
                {
                    [array]$adminUsers += $user.LogonName
                }
            }
        }

        $status = 'Present'
    }
    else
    {
        $status = 'Absent'
    }

    return @{
        SiteCode         = $SiteCode
        SecurityRoleName = $SecurityRoleName
        Description      = $role.RoleDescription
        Operation        = $ops
        UsersAssigned    = $adminUsers
        Ensure           = $status
    }
}

<#
    .SYNOPSIS
        This will set the desired state.
 
    .PARAMETER SiteCode
        Specifies the SiteCode for the Configuration Manager site.
 
    .PARAMETER SecurityRoleName
        Specifies the Security Role Name.
 
    .PARAMETER Description
        Specifies the description of the Security Role.
 
    .PARAMETER XmlPath
        Specifies the path the Security Role xml file to evalute and import.
 
    .PARAMETER OverWrite
        Specifies if the Security Roles does not match the xml this will overwrite the policy.
        If Overwrite and Append is set to false the settings in the Role are not evaluated.
 
    .PARAMETER Append
        Specifies additional settings in the xml will be appended to the current Security Role.
        If Overwrite and Append are set to true, Append will be used.
 
    .PARAMETER Ensure
        Specifies if the Security Role is present or absent.
#>

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

        [Parameter(Mandatory = $true)]
        [String]
        $SecurityRoleName,

        [Parameter()]
        [String]
        $Description,

        [Parameter()]
        [ValidateScript({((Test-Path -Path $_) -and ($_.EndsWith('.xml')))})]
        [String]
        $XmlPath,

        [Parameter()]
        [Boolean]
        $OverWrite = $false,

        [Parameter()]
        [Boolean]
        $Append = $false,

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

    Import-ConfigMgrPowerShellModule -SiteCode $SiteCode
    Set-Location -Path "$($SiteCode):\"
    $state = Get-TargetResource -SiteCode $SiteCode -SecurityRoleName $SecurityRoleName

    try
    {
        if ($Ensure -eq 'Present')
        {
            if ($PSBoundParameters.ContainsKey('XmlPath'))
            {
                try
                {
                    [xml]$xmlFile = Get-Content -Path $XmlPath
                }
                catch
                {
                    throw $script:localizedData.InvalidXmlThow
                }

                if (-not [string]::IsNullOrEmpty($xmlFile))
                {
                    $xmlContent = $xmlFile.SelectNodes("/SMS_Roles/SMS_Role/Operations/Operation")
                    $xmlName = $xmlFile.SelectNodes("/SMS_Roles/SMS_Role").RoleName

                    if ([string]::IsNullOrEmpty($xmlContent) -or [string]::IsNullOrEmpty($xmlName))
                    {
                        throw $script:localizedData.InvalidXmlThow
                    }

                    if ($xmlName -ne $SecurityRoleName)
                    {
                        throw $script:localizedData.XmlNameMismatch
                    }
                }
                else
                {
                    throw $script:localizedData.InvalidXmlThow
                }
            }

            if ($state.Ensure -eq 'Absent')
            {
                if ([string]::IsNullOrEmpty($XMLPath))
                {
                    throw $script:localizedData.AbsentRoleXmlMissing
                }

                $applyXml = $true
            }
            else
            {
                if ($PSBoundParameters.ContainsKey('XmlPath') -and $OverWrite -eq $false -and $Append -eq $false)
                {
                    Write-Warning -Message $script:localizedData.XmlFileNoOverwrite
                }
                elseif ($PSBoundParameters.ContainsKey('XmlPath') -and ($OverWrite -eq $true -or $Append -eq $true))
                {
                    if ($state.Operation)
                    {
                        $convert = $state.Operation.TrimEnd(';').Split(';')
                        $object = @{}
                        foreach ($item in $convert)
                        {
                            $sItem = $item.Split('=')

                            $object += @{
                                $sItem[0] = $sItem[1]
                            }
                        }

                        foreach ($item in $xmlContent)
                        {
                            if ($object.ContainsKey($item.ObjectTypeID))
                            {
                                if ($object[$item.ObjectTypeID] -ne $item.GrantedOperations)
                                {
                                    Write-Verbose -Message ($script:localizedData.SettingsMismatchSet -f $item.ObjectTypeID,$item.GrantedOperations)
                                    $applyXml = $true
                                }
                            }
                            else
                            {
                                Write-Verbose -Message ($script:localizedData.SettingsMismatchSet -f $item.ObjectTypeID,$item.GrantedOperations)
                                $applyXml = $true
                            }
                        }

                        foreach ($item in $object.GetEnumerator())
                        {
                            if ($xmlContent.ObjectTypeId -notcontains $item.Name)
                            {
                                if ($Append -eq $false)
                                {
                                    $action = 'over written'
                                    Write-Warning -Message ($script:localizedData.AdditionalSettings -f $SecurityRoleName,$action,$item.Name,$item.Value)
                                    $applyXml = $true
                                }
                                else
                                {
                                    $action = 'appended'
                                    Write-Warning -Message ($script:localizedData.AdditionalSettings -f $SecurityRoleName,$action,$item.Name,$item.Value)
                                    $primaryValue = $xmlFile.SMS_Roles.SMS_Role.Operations
                                    $orgValue = $xmlFile.SMS_Roles.SMS_Role.Operations.Operation
                                    $element = $orgValue[0].Clone()
                                    $element.ObjectTypeID = $item.Name
                                    $element.GrantedOperations = $item.Value
                                    $primaryValue.AppendChild($element)
                                    $saveXML = $true
                                }
                            }
                        }

                        if ($saveXML)
                        {
                            $renameFile = Get-ChildItem -Path $XmlPath
                            $dateStamp = Get-Date -UFormat "%Y-%m-%d-%H-%M-%S"
                            $newName = "$($renameFile.BaseName).$dateStamp.old"
                            Rename-Item -Path $renameFile.FullName -NewName $newName
                            $xmlFile.Save($XmlPath)
                            $applyXml = $true
                        }
                    }
                    else
                    {
                        Write-Warning -Message $script:localizedData.MissingGetConfig
                        $applyXml = $true
                    }
                }
            }

            if ($applyXml)
            {
                Import-CMSecurityRole -XmlFileName $XmlPath -OverWrite $true
            }

            if ($PSBoundParameters.ContainsKey('Description'))
            {
                $xmlDescription = (Get-CMSecurityRole -Name $SecurityRoleName).RoleDescription

                if ($Description -ne $xmlDescription)
                {
                    Write-Verbose -Message ($script:localizedData.DescriptionSet -f $Description)
                    Set-CMSecurityRole -Name $SecurityRoleName -Description $Description
                }
            }
        }
        elseif ($state.Ensure -eq 'Present')
        {
            if ($state.UsersAssigned)
            {
                throw ($script:localizedData.RoleDeleteAdmin -f ($state.UsersAssigned | Out-String))
            }

            Write-Verbose -Message ($script:localizedData.DeleteRole -f $SecurityRoleName)
            Remove-CMSecurityRole -Name $SecurityRoleName -Force
        }
    }
    catch
    {
        throw $_
    }
    finally
    {
        Set-Location -Path "$env:temp"
    }
}

<#
    .SYNOPSIS
        This will test the desired state.
 
    .PARAMETER SiteCode
        Specifies the SiteCode for the Configuration Manager site.
 
    .PARAMETER SecurityRoleName
        Specifies the Security Role Name.
 
    .PARAMETER Description
        Specifies the description of the Security Role.
 
    .PARAMETER XmlPath
        Specifies the path the Security Role xml file to evalute and import.
 
    .PARAMETER OverWrite
        Specifies if the Security Roles does not match the xml this will overwrite the policy.
        If Overwrite and Append is set to false the settings in the Security Role are not evaluated.
 
    .PARAMETER Append
        Specifies additional settings in the xml will be appended to the current Security Role.
        If Overwrite and Append are set to true, Append will be used.
 
    .PARAMETER Ensure
        Specifies if the Security Role is present or absent.
#>

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

        [Parameter(Mandatory = $true)]
        [String]
        $SecurityRoleName,

        [Parameter()]
        [String]
        $Description,

        [Parameter()]
        [ValidateScript({((Test-Path -Path $_) -and ($_.EndsWith('.xml')))})]
        [String]
        $XmlPath,

        [Parameter()]
        [Boolean]
        $OverWrite = $false,

        [Parameter()]
        [Boolean]
        $Append = $false,

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

    Import-ConfigMgrPowerShellModule -SiteCode $SiteCode
    Set-Location -Path "$($SiteCode):\"
    $state = Get-TargetResource -SiteCode $SiteCode -SecurityRoleName $SecurityRoleName
    $result = $true

    if ($Ensure -eq 'Present')
    {
        if ($state.Ensure -eq 'Absent')
        {
            if ([string]::IsNullOrEmpty($XmlPath))
            {
                Write-Warning -Message $script:localizedData.AbsentRoleXmlMissing
            }

            $result = $false
        }
        else
        {
            if ($PSBoundParameters.ContainsKey('XmlPath') -and $OverWrite -eq $false -and $Append -eq $false)
            {
                Write-Warning -Message $script:localizedData.XmlFileNoOverwrite
            }

            if ($PSBoundParameters.ContainsKey('XmlPath') -and ($OverWrite -eq $true -or $Append -eq $true))
            {
                if ($OverWrite -eq $true -and $Append -eq $true)
                {
                    Write-Warning -Message $script:localizedData.OverwriteAppend
                }

                try
                {
                    [xml]$xmlFile = Get-Content -Path $XmlPath
                }
                catch
                {
                    $invalid = $true
                }

                if (-not [string]::IsNullOrEmpty($xmlFile) -and $invalid -ne $true)
                {
                    $xmlContent = $xmlFile.SelectNodes("/SMS_Roles/SMS_Role/Operations/Operation")
                    $xmlName = $xmlFile.SelectNodes("/SMS_Roles/SMS_Role").RoleName

                    if ([string]::IsNullOrEmpty($xmlContent) -or [string]::IsNullOrEmpty($xmlName))
                    {
                        Write-Warning -Message ($script:localizedData.InvalidXml -f $XmlPath)
                        $result = $false
                    }
                    else
                    {
                        if ($xmlName -ne $SecurityRoleName)
                        {
                            Write-Warning -Message $script:localizedData.XmlNameMismatch
                        }

                        if ($state.Operation)
                        {
                            $convert = $state.Operation.TrimEnd(';').Split(';')
                            $object = @{}
                            foreach ($item in $convert)
                            {
                                $sItem = $item.Split('=')

                                $object += @{
                                    $sItem[0] = $sItem[1]
                                }
                            }

                            foreach ($item in $xmlContent)
                            {
                                if ($object.ContainsKey($item.ObjectTypeID))
                                {
                                    if ($object[$item.ObjectTypeID] -ne $item.GrantedOperations)
                                    {
                                        Write-Verbose -Message ($script:localizedData.SettingsMismatch -f $item.ObjectTypeId,$item.GrantedOperations,$object[$item.ObjectTypeID])
                                        $result = $false
                                    }
                                }
                                else
                                {
                                    Write-Verbose -Message ($script:localizedData.MissingSettings -f $item.ObjectTypeId,$item.GrantedOperations)
                                    $result = $false
                                }
                            }

                            foreach ($item in $object.GetEnumerator())
                            {
                                if ($xmlContent.ObjectTypeId -notcontains $item.Name)
                                {
                                    if ($Append -eq $true)
                                    {
                                        $action = 'appended'
                                    }
                                    else
                                    {
                                        $action = 'over written'
                                        $result = $false
                                    }

                                    Write-Warning -Message ($script:localizedData.AdditionalSettings -f $SecurityRoleName,$action,$item.Name,$item.Value)
                                }
                            }
                        }
                        else
                        {
                            Write-Verbose -Message $script:localizedData.MissingGetConfig
                            $result = $false
                        }
                    }
                }
                else
                {
                    Write-Warning -Message ($script:localizedData.InvalidXml -f $XmlPath)
                    $result = $false
                }
            }

            if ($PSBoundParameters.ContainsKey('Description'))
            {
                if ($Description -ne $state.Description)
                {
                    Write-Verbose -Message ($script:localizedData.DescriptionMismatch -f $Description, $state.Description)
                    $result = $false
                }
            }
        }
    }
    elseif ($state.Ensure -eq 'Present')
    {
        if ($state.UsersAssigned)
        {
            Write-Warning -Message ($script:localizedData.RoleDeleteAdmin -f ($state.UsersAssigned | Out-String))
        }

        $result = $false
    }

    Write-Verbose -Message ($script:localizedData.TestState -f $result)
    Set-Location -Path "$env:temp"
    return $result
}