DSCResources/DSC_CMBoundaryGroups/DSC_CMBoundaryGroups.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 BoundaryGroup
        Specifies the boundary group name.
#>

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

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

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

    $groupId = Get-CMBoundaryGroup -Name $BoundaryGroup

    if ($groupId)
    {
        $groupMembers = Get-CMBoundary -BoundaryGroupId $groupId.GroupId

        if ($groupMembers)
        {
            $cimBoundaries = ConvertTo-CimBoundaries -InputObject $groupMembers
        }

        $serverPath = (Get-CMBoundaryGroupSiteSystem -ID $groupId.GroupId).ServerNalPath

        if ($serverPath)
        {
            foreach ($item in $serverPath)
            {
                [array]$mappings += $item.TrimEnd('\').Split('\')[-1]
            }
        }

        $scopeObject = Get-CMObjectSecurityScope -InputObject $groupId

        foreach ($item in $scopeObject)
        {
            [array]$scopes += $item.CategoryName
        }

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

    return @{
        SiteCode       = $SiteCode
        BoundaryGroup  = $BoundaryGroup
        Boundaries     = $cimBoundaries
        SiteSystems    = $mappings
        SecurityScopes = $scopes
        Ensure         = $status
    }
}

<#
    .SYNOPSIS
        This will set the desired state.
 
    .PARAMETER SiteCode
        Specifies the SiteCode for the Configuration Manager site.
 
    .PARAMETER BoundaryGroup
        Specifies the Boundary Group name.
 
    .Parameter Boundaries
        Specifies an array of Boundaries to add or remove.
 
    .Parameter BoundaryAction
        Specifies the Boundaries are to match, add, or remove Boundaries from the Boundary Group.
 
    .Parameter SiteSystems
        Specifies an array of SiteSystems to match on the Boundary Group.
 
    .Parameter SiteSystemsToInclude
        Specifies an array of SiteSystems to add to the Boundary Group.
 
    .Parameter SiteSystemsToExclude
        Specifies an array of SiteSystems to remove from the Boundary Group.
 
    .PARAMETER SecurityScopes
        Specifies an array of Security Scopes to match to the Boundary Group.
 
    .PARAMETER SecurityScopesToInclude
        Specifies an array of Security Scopes to add to the Boundary Group.
 
    .PARAMETER SecurityScopesToExclude
        Specifies an array of Security Scopes to remove from the Boundary Group.
 
    .Parameter Ensure
        Specifies if the Boundary Group is to be absent or present.
#>

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

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

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $Boundaries,

        [Parameter()]
        [ValidateSet('Match','Add','Remove')]
        [String]
        $BoundaryAction = 'Add',

        [Parameter()]
        [String[]]
        $SiteSystems,

        [Parameter()]
        [String[]]
        $SiteSystemsToInclude,

        [Parameter()]
        [String[]]
        $SiteSystemsToExclude,

        [Parameter()]
        [String[]]
        $SecurityScopes,

        [Parameter()]
        [String[]]
        $SecurityScopesToInclude,

        [Parameter()]
        [String[]]
        $SecurityScopesToExclude,

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

    Import-ConfigMgrPowerShellModule -SiteCode $SiteCode
    Set-Location -Path "$($SiteCode):\"

    try
    {
        $state = Get-TargetResource -SiteCode $SiteCode -BoundaryGroup $BoundaryGroup

        if ($Ensure -eq 'Present')
        {
            if (-not $PSBoundParameters.ContainsKey('SiteSystems') -and
                $PSBoundParameters.ContainsKey('SiteSystemsToInclude') -and
                $PSBoundParameters.ContainsKey('SiteSystemsToExclude'))
            {
                foreach ($item in $SiteSystemsToInclude)
                {
                    if ($SiteSystemsToExclude -contains $item)
                    {
                        throw ($script:localizedData.SiteInEx -f $item)
                    }
                }
            }

            if (-not $PSBoundParameters.ContainsKey('SecurityScopes') -and
                $PSBoundParameters.ContainsKey('SecurityScopesToInclude') -and
                $PSBoundParameters.ContainsKey('SecurityScopesToExclude'))
            {
                foreach ($item in $SecurityScopesToInclude)
                {
                    if ($SecurityScopesToExclude -contains $item)
                    {
                        throw ($script:localizedData.ScopeInEx -f $item)
                    }
                }
            }

            if (-not $PSBoundParameters.ContainsKey('SecurityScopes') -and
                -not $PSBoundParameters.ContainsKey('SecurityScopesToInclude') -and
                $PSBoundParameters.ContainsKey('SecurityScopesToExclude'))
            {
                if ($state.SecurityScopes.Count -eq $SecurityScopesToExclude.Count)
                {
                    $excludeAll = Compare-Object -ReferenceObject $state.SecurityScopes -DifferenceObject $SecurityScopesToExclude
                    if ([string]::IsNullOrEmpty($excludeAll))
                    {
                        throw ($script:localizedData.ScopeExcludeAll)
                    }
                }
            }

            if ($state.Ensure -eq 'Absent')
            {
                Write-Verbose -Message ($script:localizedData.CreateBoundaryGroup -f $BoundaryGroup)
                New-CMBoundaryGroup -Name $BoundaryGroup
            }

            if ($Boundaries)
            {
                $convert = Convert-BoundariesIPSubnets -InputObject $Boundaries
                $boundaryToGroup = @()
                $boundaryToRemove = @()

                if ([string]::IsNullOrEmpty($state.Boundaries))
                {
                    if ($BoundaryAction -ne 'Remove')
                    {
                        foreach ($entry in $convert)
                        {
                            $boundaryToGroup += @{
                                Value = $entry.Value
                                Type  = $entry.Type
                            }
                        }
                    }
                }
                else
                {
                    $comparesParam = @{
                        ReferenceObject  = $state.Boundaries
                        DifferenceObject = $convert
                        Property         = 'Value','Type'
                    }

                    $compares = Compare-Object @comparesParam -IncludeEqual

                    if ($BoundaryAction -ne 'Remove')
                    {
                        foreach ($addCompare in $compares)
                        {
                            if ($addCompare.SideIndicator -eq '=>')
                            {
                                $boundaryToGroup += @{
                                    Value = $addCompare.Value
                                    Type  = $addCompare.Type
                                }
                            }
                        }
                    }

                    if ($BoundaryAction -ne 'Add')
                    {
                        foreach ($removeCompare in $compares)
                        {
                            if ($BoundaryAction -eq 'Match')
                            {
                                if ($removeCompare.SideIndicator -eq '<=')
                                {
                                    $boundaryToRemove += @{
                                        Value = $removeCompare.Value
                                        Type  = $removeCompare.Type
                                    }
                                }
                            }
                            else
                            {
                                if ($removeCompare.SideIndicator -eq '==')
                                {
                                    $boundaryToRemove += @{
                                        Value = $removeCompare.Value
                                        Type  = $removeCompare.Type
                                    }
                                }
                            }
                        }

                        if ($boundaryToRemove)
                        {
                            foreach ($item in $boundaryToRemove)
                            {
                                $removeBoundary = Get-BoundaryInfo -Value $item.Value -Type $item.Type

                                if ($removeBoundary)
                                {
                                    Write-Verbose -Message ($script:localizedData.ExcludingBoundary `
                                        -f $BoundaryGroup, $item.Type, $item.Value)

                                    Remove-CMBoundaryFromGroup -BoundaryId $removeBoundary -BoundaryGroupName $BoundaryGroup
                                }
                            }
                        }
                    }
                }

                if ($boundaryToGroup)
                {
                    foreach ($item in $boundaryToGroup)
                    {
                        $addBoundary = Get-BoundaryInfo -Value $item.Value -Type $item.Type

                        if ($addBoundary)
                        {
                            Write-Verbose -Message ($script:localizedData.AddingBoundary -f `
                                $BoundaryGroup, $item.Type, $item.Value)
                            Add-CMBoundaryToGroup -BoundaryId $addBoundary -BoundaryGroupName $BoundaryGroup
                        }
                        else
                        {
                            $errorMsg += ($script:localizedData.BoundaryAbsent -f $item.Type, $item.Value)
                        }
                    }
                }
            }

            if ($SiteSystems -or $SiteSystemsToInclude -or $SiteSystemsToExclude)
            {
                $systemsArray = @{
                    Match        = $SiteSystems
                    Include      = $SiteSystemsToInclude
                    Exclude      = $SiteSystemsToExclude
                    CurrentState = $state.SiteSystems
                }

                $systemsCompare = Compare-MultipleCompares @systemsArray

                if ($systemsCompare.Missing)
                {
                    foreach ($add in $systemsCompare.Missing)
                    {
                        if (Get-CMSiteSystemServer -SiteSystemServerName $add -SiteCode $siteCode)
                        {
                            [array]$addSystems += $add
                        }
                        else
                        {
                            $errorMsg += ($script:localizedData.SiteSystemMissing -f $add)
                        }
                    }

                    if ($addSystems)
                    {
                        Write-Verbose -Message ($script:localizedData.SystemMissing -f ($addSystems | Out-String))
                        Set-CMBoundaryGroup -Name $BoundaryGroup -AddSiteSystemServerName $addSystems
                    }
                }

                if ($systemsCompare.Remove)
                {
                    Write-Verbose -Message ($script:localizedData.SystemRemove -f ($systemsCompare.Remove | Out-String))
                    Set-CMBoundaryGroup -Name $BoundaryGroup  -RemoveSiteSystemServerName $systemsCompare.Remove
                }
            }

            if ($SecurityScopes -or $SecurityScopesToInclude -or $SecurityScopesToExclude)
            {
                $bgObject = Get-CMBoundaryGroup -Name $BoundaryGroup

                $scopesArray = @{
                    Match        = $SecurityScopes
                    Include      = $SecurityScopesToInclude
                    Exclude      = $SecurityScopesToExclude
                    CurrentState = $state.SecurityScopes
                }

                $scopesCompare = Compare-MultipleCompares @scopesArray

                if ($scopesCompare.Missing)
                {
                    foreach ($add in $scopesCompare.Missing)
                    {
                        if (Get-CMSecurityScope -Name $add)
                        {
                            Write-Verbose -Message ($script:localizedData.AddScope -f $add, $BoundaryGroup)
                            Add-CMObjectSecurityScope -Name $add -InputObject $bgObject
                        }
                        else
                        {
                            $errorMsg += ($script:localizedData.SecurityScopeMissing -f $add)
                        }
                    }
                }

                if ($scopesCompare.Remove)
                {
                    foreach ($remove in $scopesCompare.Remove)
                    {
                        Write-Verbose -Message ($script:localizedData.RemoveScope -f $remove, $BoundaryGroup)
                        Remove-CMObjectSecurityScope -Name $remove -InputObject $bgObject
                    }
                }
            }
        }
        elseif ($state.Ensure -eq 'Present')
        {
            Write-Verbose -Message ($script:localizedData.BoundaryGroupDelete -f $BoundaryGroup)
            Remove-CMBoundaryGroup -Name $BoundaryGroup
        }

        if ($errorMsg)
        {
            throw ($errorMsg | Out-String)
        }
    }
    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 BoundaryGroup
        Specifies the Boundary Group name.
 
    .Parameter Boundaries
        Specifies an array of Boundaries to add or remove.
 
    .Parameter BoundaryAction
        Specifies the Boundaries are to match, add, or remove Boundaries from the Boundary Group.
 
    .Parameter SiteSystems
        Specifies an array of SiteSystems to match on the Boundary Group.
 
    .Parameter SiteSystemsToInclude
        Specifies an array of SiteSystems to add to the Boundary Group.
 
    .Parameter SiteSystemsToExclude
        Specifies an array of SiteSystems to remove from the Boundary Group.
 
    .PARAMETER SecurityScopes
        Specifies an array of Security Scopes to match to the Boundary Group.
 
    .PARAMETER SecurityScopesToInclude
        Specifies an array of Security Scopes to add to the Boundary Group.
 
    .PARAMETER SecurityScopesToExclude
        Specifies an array of Security Scopes to remove from the Boundary Group.
 
    .Parameter Ensure
        Specifies if the Boundary Group is to be absent or present.
#>

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

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

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $Boundaries,

        [Parameter()]
        [ValidateSet('Match','Add','Remove')]
        [String]
        $BoundaryAction = 'Add',

        [Parameter()]
        [String[]]
        $SiteSystems,

        [Parameter()]
        [String[]]
        $SiteSystemsToInclude,

        [Parameter()]
        [String[]]
        $SiteSystemsToExclude,

        [Parameter()]
        [String[]]
        $SecurityScopes,

        [Parameter()]
        [String[]]
        $SecurityScopesToInclude,

        [Parameter()]
        [String[]]
        $SecurityScopesToExclude,

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

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

    if ($Ensure -eq 'Present')
    {
        if ($PSBoundParameters.ContainsKey('SiteSystems'))
        {
            if ($PSBoundParameters.ContainsKey('SiteSystemsToInclude') -or
                $PSBoundParameters.ContainsKey('SiteSystemsToExclude'))
            {
                Write-Warning -Message $script:localizedData.ParamIgnore
            }
        }
        elseif (-not $PSBoundParameters.ContainsKey('SiteSystems') -and
            $PSBoundParameters.ContainsKey('SiteSystemsToInclude') -and
            $PSBoundParameters.ContainsKey('SiteSystemsToExclude'))
        {
            foreach ($item in $SiteSystemsToInclude)
            {
                if ($SiteSystemsToExclude -contains $item)
                {
                    Write-Warning -Message ($script:localizedData.SiteInEx -f $item)
                    $result = $false
                }
            }
        }

        if ($PSBoundParameters.ContainsKey('SecurityScopes'))
        {
            if ($PSBoundParameters.ContainsKey('SecurityScopesToInclude') -or
                $PSBoundParameters.ContainsKey('SecurityScopesToExclude'))
            {
                Write-Warning -Message $script:localizedData.ParamIgnoreScopes
            }
        }
        elseif (-not $PSBoundParameters.ContainsKey('SecurityScopes') -and
            $PSBoundParameters.ContainsKey('SecurityScopesToInclude') -and
            $PSBoundParameters.ContainsKey('SecurityScopesToExclude'))
        {
            foreach ($item in $SecurityScopesToInclude)
            {
                if ($SecurityScopesToExclude -contains $item)
                {
                    Write-Warning -Message ($script:localizedData.ScopeInEx -f $item)
                    $result = $false
                }
            }
        }

        if ($state.Ensure -eq 'Absent')
        {
            Write-Verbose -Message ($script:localizedData.BoundaryGroupMissing -f $BoundaryGroup)
            $result = $false
        }
        else
        {
            if ($Boundaries)
            {
                $convert = Convert-BoundariesIPSubnets -InputObject $Boundaries

                if ([string]::IsNullOrEmpty($state.Boundaries))
                {
                    if ($BoundaryAction -ne 'Remove')
                    {
                        foreach ($toAdd in $convert)
                        {
                            Write-Verbose -Message ($script:localizedData.MissingBoundary `
                                -f $BoundaryGroup, $toAdd.Type, $toAdd.Value)
                            $result = $false
                        }
                    }
                }
                else
                {
                    $comparesParam = @{
                        ReferenceObject  = $state.Boundaries
                        DifferenceObject = $convert
                        Property         = 'Value','Type'
                    }

                    $compares = Compare-Object @comparesParam -IncludeEqual

                    if ($BoundaryAction -ne 'Remove')
                    {
                        foreach ($addCompare in $compares)
                        {
                            if ($addCompare.SideIndicator -eq '=>')
                            {
                                Write-Verbose -Message ($script:localizedData.MissingBoundary `
                                    -f $BoundaryGroup, $addCompare.Type, $addCompare.Value)
                                $result = $false
                            }
                        }
                    }

                    if ($BoundaryAction -ne 'Add')
                    {
                        foreach ($removeCompare in $compares)
                        {
                            if ($BoundaryAction -eq 'Match')
                            {
                                if ($removeCompare.SideIndicator -eq '<=')
                                {
                                    Write-Verbose -Message ($script:localizedData.ExtraBoundary `
                                        -f $BoundaryGroup, $removeCompare.Type, $removeCompare.Value)
                                    $result = $false
                                }
                            }
                            else
                            {
                                if ($removeCompare.SideIndicator -eq '==')
                                {
                                    Write-Verbose -Message ($script:localizedData.ExtraBoundary `
                                        -f $BoundaryGroup, $removeCompare.Type, $removeCompare.Value)
                                    $result = $false
                                }
                            }
                        }
                    }
                }
            }

            if ($SiteSystems -or $SiteSystemsToInclude -or $SiteSystemsToExclude)
            {
                $systemsArray = @{
                    Match        = $SiteSystems
                    Include      = $SiteSystemsToInclude
                    Exclude      = $SiteSystemsToExclude
                    CurrentState = $state.SiteSystems
                }

                $systemsCompare = Compare-MultipleCompares @systemsArray

                if ($systemsCompare.Missing)
                {
                    Write-Verbose -Message ($script:localizedData.SystemMissing -f ($systemsCompare.Missing | Out-String))
                    $result = $false
                }

                if ($systemsCompare.Remove)
                {
                    Write-Verbose -Message ($script:localizedData.SystemRemove -f ($systemsCompare.Remove | Out-String))
                    $result = $false
                }
            }

            if ($SecurityScopes -or $SecurityScopesToInclude -or $SecurityScopesToExclude)
            {
                $scopeArray = @{
                    Match        = $SecurityScopes
                    Include      = $SecurityScopesToInclude
                    Exclude      = $SecurityScopesToExclude
                    CurrentState = $state.SecurityScopes
                }

                $scopeCompare = Compare-MultipleCompares @scopeArray

                if ($scopeCompare.Missing)
                {
                    Write-Verbose -Message ($script:localizedData.ScopeMissing -f ($scopeCompare.Missing | Out-String))
                    $result = $false
                }

                if ($scopeCompare.Remove)
                {
                    Write-Verbose -Message ($script:localizedData.ScopeRemove -f ($scopeCompare.Remove | Out-String))
                    $result = $false
                }
            }
        }
    }
    elseif ($state.Ensure -eq 'Present')
    {
        Write-Verbose -Message ($script:localizedData.BoundaryGroupRemove -f $BoundaryGroup)
        $result = $false
    }

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

Export-ModuleMember -Function *-TargetResource