DSCResources/DSC_UpdateServicesComputerTargetGroup/DSC_UpdateServicesComputerTargetGroup.psm1

# DSC resource to manage WSUS Computer Target Groups.

# Load Common Module
$script:resourceHelperModulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common'
Import-Module -Name $script:resourceHelperModulePath
$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US'


<#
    .SYNOPSIS
        Retrieves the current state of the WSUS Computer Target Group.
 
    .DESCRIPTION
        This function retrieves the current state of a WSUS Computer Target Group
        by querying the WSUS server and validating the group's path.
 
    .PARAMETER Name
        The Name of the WSUS Computer Target Group.
 
    .PARAMETER Path
        The Path to the WSUS Computer Target Group in the format 'Parent/Child'.
#>

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

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

    try
    {
        $WsusServer = Get-WsusServer
    }
    catch
    {
        New-InvalidOperationException -Message $script:localizedData.WSUSConfigurationFailed -ErrorRecord $_
    }

    $Ensure = 'Absent'
    $Id = $null

    if ($null -ne $WsusServer)
    {
        Write-Verbose -Message ($script:localizedData.GetWsusServerSucceeded -f $WsusServer.Name)
        $ComputerTargetGroup = $WsusServer.GetComputerTargetGroups().Where({ $_.Name -eq $Name }) | Select-Object -First 1

        if ($null -ne $ComputerTargetGroup)
        {
            $ComputerTargetGroupPath = Get-ComputerTargetGroupPath -ComputerTargetGroup $ComputerTargetGroup
            if ($Path -eq $ComputerTargetGroupPath)
            {
                $Ensure = 'Present'
                $Id = $ComputerTargetGroup.Id.Guid
                Write-Verbose -Message ($script:localizedData.FoundComputerTargetGroup -f $Name, $Path, $Id)
            }
            else
            {
                # ComputerTargetGroup Names must be unique within the overall hierarchy
                New-InvalidOperationException -Message ($script:localizedData.DuplicateComputerTargetGroup -f $ComputerTargetGroup.Name, $ComputerTargetGroupPath)
            }
        }
    }
    else
    {
        Write-Verbose -Message $script:localizedData.GetWsusServerFailed
    }

    if ($null -eq $Id)
    {
        Write-Verbose -Message ($script:localizedData.NotFoundComputerTargetGroup -f $Name, $Path)
    }

    $returnValue = @{
        Ensure = $Ensure
        Name   = $Name
        Path   = $Path
        Id     = $Id
    }

    return $returnValue
}

<#
    .SYNOPSIS
        Sets the state of the WSUS Computer Target Group.
 
    .DESCRIPTION
        This function creates or removes a WSUS Computer Target Group based on
        the Ensure parameter. It validates the parent path and performs the
        appropriate action on the WSUS server.
 
    .PARAMETER Ensure
        Determines if the Computer Target Group should be created or removed.
        Accepts 'Present' (default) or 'Absent'.
 
    .PARAMETER Name
        Name of the Computer Target Group.
 
    .PARAMETER Path
        The Path to the Computer Target Group in the format 'Parent/Child'.
#>

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

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

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

    try
    {
        $WsusServer = Get-WsusServer
    }
    catch
    {
        New-InvalidOperationException -Message $script:localizedData.WSUSConfigurationFailed -ErrorRecord $_
    }

    # break down path to identify the parent computer target group based on name and its own unique path
    $ParentComputerTargetGroupName = (($Path -split '/')[-1])
    $ParentComputerTargetGroupPath = ($Path -replace "[/]$ParentComputerTargetGroupName", '')

    if ($null -ne $WsusServer)
    {
        $ParentComputerTargetGroups = $WsusServer.GetComputerTargetGroups().Where({
                $_.Name -eq $ParentComputerTargetGroupName
            }) | Select-Object -First 1

        if ($null -ne $ParentComputerTargetGroups)
        {
            foreach ($ParentComputerTargetGroup in $ParentComputerTargetGroups)
            {
                $ComputerTargetGroupPath = Get-ComputerTargetGroupPath -ComputerTargetGroup $ParentComputerTargetGroup
                if ($ParentComputerTargetGroupPath -eq $ComputerTargetGroupPath)
                {
                    # parent Computer Target Group Exists
                    Write-Verbose -Message ($script:localizedData.FoundParentComputerTargetGroup -f $ParentComputerTargetGroupName, `
                            $ParentComputerTargetGroupPath, $ParentComputerTargetGroup.Id.Guid)

                    # create the new Computer Target Group if Ensure -eq 'Present'
                    if ($Ensure -eq 'Present')
                    {
                        try
                        {
                            $null = $WsusServer.CreateComputerTargetGroup($Name, $ParentComputerTargetGroup)
                            Write-Verbose -Message ($script:localizedData.CreateComputerTargetGroupSuccess -f $Name, $Path)
                            return
                        }
                        catch
                        {
                            New-InvalidOperationException -Message (
                                $script:localizedData.CreateComputerTargetGroupFailed -f $Name, $Path
                            ) -ErrorRecord $_
                        }
                    }
                    else
                    {
                        # $Ensure -eq 'Absent' - must call the Delete() method on the group itself for removal
                        $ChildComputerTargetGroup = $ParentComputerTargetGroup.GetChildTargetGroups().Where({
                                $_.Name -eq $Name
                            }) | Select-Object -First 1

                        if ($null -eq $ChildComputerTargetGroup)
                        {
                            # Already absent
                            Write-Verbose -Message ($script:localizedData.NotFoundComputerTargetGroup -f $Name, $Path)
                            return
                        }

                        try
                        {
                            $childId = $ChildComputerTargetGroup.Id.Guid
                            $null = $ChildComputerTargetGroup.Delete()
                            Write-Verbose -Message ($script:localizedData.DeleteComputerTargetGroupSuccess -f $Name, $childId, $Path)
                            return
                        }
                        catch
                        {
                            $childId = if ($ChildComputerTargetGroup)
                            {
                                $ChildComputerTargetGroup.Id.Guid 
                            }
                            else
                            {
                                'N/A' 
                            }
                            New-InvalidOperationException -Message (
                                $script:localizedData.DeleteComputerTargetGroupFailed -f $Name, $childId, $Path
                            ) -ErrorRecord $_
                        }
                    }
                }
            }
        }

        New-InvalidOperationException -Message ($script:localizedData.NotFoundParentComputerTargetGroup -f $ParentComputerTargetGroupName, `
                $ParentComputerTargetGroupPath, $Name)
    }
    else
    {
        Write-Verbose -Message $script:localizedData.GetWsusServerFailed
    }
}

<#
    .SYNOPSIS
        Tests the current state of the WSUS Computer Target Group.
 
    .DESCRIPTION
        This function determines whether the WSUS Computer Target Group is in
        the desired state by comparing the current state to the requested Ensure value.
 
    .PARAMETER Ensure
        Determines if the Computer Target Group should be created or removed.
        Accepts 'Present' (default) or 'Absent'.
 
    .PARAMETER Name
        Name of the Computer Target Group
 
    .PARAMETER Path
        The Path to the Computer Target Group in the format 'Parent/Child'.
#>

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

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

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

    $result = Get-TargetResource -Name $Name -Path $Path

    if ($Ensure -eq $result.Ensure)
    {
        Write-Verbose -Message ($script:localizedData.ResourceInDesiredState -f $Name, $Path, $result.Ensure)
        return $true
    }
    else
    {
        Write-Verbose -Message ($script:localizedData.ResourceNotInDesiredState -f $Name, $Path, $result.Ensure)
        return $false
    }
}


<#
    .SYNOPSIS
        Gets the Computer Target Group Path within WSUS by recursing up through each Parent Computer Target Group
 
    .DESCRIPTION
        This function recursively traverses the parent hierarchy of a WSUS Computer
        Target Group to construct its full path in the format 'Parent/Child/GrandChild'.
 
    .PARAMETER ComputerTargetGroup
        The Computer Target Group object for which to retrieve the path.
 
    .OUTPUTS
        System.String
 
        Returns the full hierarchical path of the Computer Target Group.
#>

function Get-ComputerTargetGroupPath
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.Object]
        $ComputerTargetGroup
    )

    if ($ComputerTargetGroup.Name -eq 'All Computers')
    {
        return 'All Computers'
    }

    $computerTargetGroupPath = ''
    $computerTargetGroupParents = @()
    $moreParentContainers = $true
    $x = 0

    do
    {
        try
        {
            $ComputerTargetGroup = $ComputerTargetGroup.GetParentTargetGroup()
            $computerTargetGroupParents += $ComputerTargetGroup.Name
        }
        catch
        {
            # 'All Computers' container throws an exception when GetParentTargetGroup() method called
            $moreParentContainers = $false
        }

        $x++
    } while ($moreParentContainers -and ($x -lt 20))

    for ($i = ($computerTargetGroupParents.Count - 1); $i -ge 0; $i--)
    {
        if (-not [string]::IsNullOrEmpty($computerTargetGroupPath))
        {
            $computerTargetGroupPath += ('/' + $computerTargetGroupParents[$i])
        }
        else
        {
            $computerTargetGroupPath += $computerTargetGroupParents[$i]
        }
    }

    return $computerTargetGroupPath
}

Export-ModuleMember -Function *-TargetResource