DscResources/Carbon_Group/Carbon_Group.psm1

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

& (Join-Path -Path $PSScriptRoot -ChildPath '..\Initialize-CarbonDscResource.ps1' -Resolve)
. (Join-Path -Path $PSScriptRoot -ChildPath '..\..\Functions\Use-CallerPreference.ps1' -Resolve)

function Get-TargetResource
{
    [CmdletBinding()]
    [OutputType([hashtable])]
    param
    (
        [parameter(Mandatory = $true)]
        [System.String]
        $Name
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState

    $group = Get-Group -Name $Name -ErrorAction Ignore

    $ensure = 'Absent'
    $description = $null
    $members = @()
    if( $group )
    {
        $description = $group.Description
        $members = $group.Members
        $ensure = 'Present'
    }

    @{
        Name = $Name
        Ensure = $ensure
        Description = $description
        Members = $members
    }
}

function Set-TargetResource
{
    <#
    .SYNOPSIS
    DSC resource for configuring local Windows groups.
 
    .DESCRIPTION
    The `Carbon_Group` resource installs and uninstalls groups. It also adds members to existing groups.
     
    The group is installed when `Ensure` is set to `Present`. Members of the group are updated to match the `Members` property (i.e. members not listed in the `Members` property are removed from the group). If `Members` has no value, all members are removed. Because DSC resources run under the LCM which runs as `System`, local system accounts must have access to the directories where both new and existing member accounts can be found.
 
    The group is removed when `Ensure` is set to `Absent`. When removing a group, the `Members` property is ignored.
 
    The `Carbon_Group` resource was added in Carbon 2.1.0.
 
    .LINK
    Add-GroupMember
 
    .LINK
    Install-Group
 
    .LINK
    Remove-GroupMember
 
    .LINK
    Test-Group
 
    .LINK
    Uninstall-Group
 
    .EXAMPLE
    >
    Demonstrates how to install a group and add members to it.
 
        Carbon_Group 'CreateFirstOrder'
        {
            Name = 'FirstOrder';
            Description = 'On to victory!';
            Ensure = 'Present';
            Members = @( 'FO\SupremeLeaderSnope', 'FO\KRen' );
        }
 
    .EXAMPLE
    >
    Demonstrates how to uninstall a group.
 
        Carbon_Group 'RemoveRepublic
        {
            Name = 'Republic';
            Ensure = 'Absent';
        }
 
    #>

    [CmdletBinding(SupportsShouldProcess=$true)]
    param
    (
        [parameter(Mandatory=$true)]
        [string]
        # The name of the group.
        $Name,

        [string]
        # A description of the group. Only used when adding/updating a group (i.e. when `Ensure` is `Present`).
        $Description,

        [ValidateSet("Present","Absent")]
        [string]
        # Should be either `Present` or `Absent`. If set to `Present`, a group is configured and membership configured. If set to `Absent`, the group is removed.
        $Ensure,

        [string[]]
        # The group's members. Only used when adding/updating a group (i.e. when `Ensure` is `Present`).
        #
        # Members not in this list are removed from the group.
        $Members = @()
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState

    if( $Ensure -eq 'Absent' )
    {
        Uninstall-Group -Name $Name
        return
    }

    $group = Install-Group -Name $Name -Description $Description -Member $Members -PassThru
    if( -not $group )
    {
        return
    }

    try
    {
        $memberNames = @()
        if( $Members )
        {
             $memberNames = $Members | Resolve-MemberName
        }
        $membersToRemove = $group.Members | Where-Object {
                                                            $memberName = Resolve-PrincipalName -Principal $_
                                                            return $memberNames -notcontains $memberName
                                                         }
        if( $membersToRemove )
        {
            foreach( $memberToRemove in $membersToRemove )
            {
                Write-Verbose -Message ('[{0}] Members {1} ->' -f $Name,(Resolve-PrincipalName -Principal $memberToRemove))
                $group.Members.Remove( $memberToRemove )
            }

            if( $PSCmdlet.ShouldProcess( ('local group {0}' -f $Name), 'remove members' ) )
            {
                $group.Save()
            }
        }
    }
    finally
    {
        $group.Dispose()
    }
}

function Test-TargetResource
{
    [CmdletBinding()]
    [OutputType([bool])]
    param
    (
        [parameter(Mandatory = $true)]
        [string]
        $Name,

        [string]
        $Description = $null,

        [ValidateSet("Present","Absent")]
        [string]
        $Ensure = "Present",

        [string[]]
        $Members = @()
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState

    $resource = Get-TargetResource -Name $Name

    # Do we need to delete the group?
    if( $Ensure -eq 'Absent' -and $resource.Ensure -eq 'Present' )
    {
        Write-Verbose -Message ('[{0}] Group is present but should be absent.' -f $Name)
        return $false
    }

    # Is it already gone?
    if( $Ensure -eq 'Absent' -and $resource.Ensure -eq 'Absent' )
    {
        return $true
    }

    # Do we need to create the group?
    if( $Ensure -eq 'Present' -and $resource.Ensure -eq 'Absent' )
    {
        Write-Verbose -Message ('[{0}] Group is absent but should be present.' -f $Name)
        return $false
    }

    # Is the group out-of-date?
    $upToDate = $true
    if( $Description -ne $resource.Description )
    {
        Write-Verbose -Message ('[{0}] [Description] ''{1}'' != ''{2}''' -f $Name,$Description,$resource.Description)
        $upToDate = $false
    }

    $memberNames = @()
    if( $Members )
    {
         $memberNames = $Members | Resolve-MemberName
    }
    $currentMemberNames = $resource['Members'] | Resolve-PrincipalName

    # Is the current group missing the desired members?
    foreach( $memberName in $memberNames )
    {
        if( $currentMemberNames -notcontains $memberName )
        {
            Write-Verbose -Message ('[{0}] [Members] {1} is absent but should be present' -f $Name,$memberName)
            $upToDate = $false
        }
    }

    # Does the current group contains extra members?
    foreach( $memberName in $currentMemberNames )
    {
        if( $memberNames -notcontains $memberName )
        {
            Write-Verbose -Message ('[{0}] [Members] {1} is present but should be absent' -f $Name,$memberName)
            $upToDate = $false
        }
    }

    return $upToDate
}

function Resolve-MemberName
{
    param(
        [Parameter(Mandatory=$true,VAlueFromPipeline=$true)]
        [string]
        $Name
    )

    process
    {
        Resolve-IdentityName -Name $Name
    }
}

function Resolve-PrincipalName
{
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        $Principal
    )

    process
    {
        Resolve-Identity -SID $Principal.Sid.Value | Select-Object -ExpandProperty 'FullName'
    }
}

Export-ModuleMember -Function *-TargetResource