Skip to content

Commit

Permalink
Merge pull request #2 from qubetzl/group-member-modification
Browse files Browse the repository at this point in the history
Carbon_Group: Allow modifying group members
  • Loading branch information
splatteredbits authored Dec 2, 2024
2 parents 44677e2 + ccfde80 commit 72e7110
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 45 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@

# Carbon.DSC Changelog

## 1.1.0

* Added support to Carbon_Group resource for adding new members without removing any members.
* Added support to Carbon_Group resource for removing group members.

## 1.0.1

Reducing directory depth of internal, private nested module dependencies.
Expand Down
2 changes: 1 addition & 1 deletion Carbon.DSC/Carbon.DSC.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
RootModule = 'Carbon.DSC.psm1'

# Version number of this module.
ModuleVersion = '1.0.1'
ModuleVersion = '1.1.0'

# ID used to uniquely identify this module
GUID = '8aa43ac0-cd66-4fc0-aa13-efa08d573946'
Expand Down
138 changes: 110 additions & 28 deletions Carbon.DSC/DscResources/Carbon_Group/Carbon_Group.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ function Get-TargetResource
if( $group )
{
$description = $group.Description
$members = $group.Members
$members = @($group.Members | Resolve-PrincipalName)
$ensure = 'Present'
$group.Dispose()
}

@{
Expand All @@ -57,11 +58,11 @@ function Set-TargetResource
DSC resource for configuring local Windows groups.
.DESCRIPTION
The `Carbon_Group` resource installs and uninstalls groups. It also adds members to existing groups.
The `Carbon_Group` resource installs and uninstalls groups. It can also modify members of 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 installed when `Ensure` is set to `Present`. Group's members are updated based on the values of both `Members` and `EnsureMembers` properties, where `Members` lists users and `EnsureMemebers` controls how the list is used. If `EnsureMembers` is set to `Exact`, the group is configured to have the exact members specified. If set to `Present`, the specified members are added to the group if they are not members already. If set to `Absent`, the specified members are removed from the group if they are members of it. Defaults to `Exact`. 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 group is removed when `Ensure` is set to `Absent`. When removing a group, all other properties are ignored.
The `Carbon_Group` resource was added in Carbon 2.1.0.
Expand All @@ -82,7 +83,7 @@ function Set-TargetResource
.EXAMPLE
>
Demonstrates how to install a group and add members to it.
Demonstrates how to install a group and set its members.
Carbon_Group 'CreateFirstOrder'
{
Expand All @@ -102,6 +103,30 @@ function Set-TargetResource
Ensure = 'Absent';
}
.EXAMPLE
>
Demonstrates how to add members to an existing group.
Carbon_Group 'AddVader'
{
Name = 'SithOrder';
Ensure = 'Present';
EnsureMembers = 'Present';
Members = @( 'SO\DarthVader' );
}
.EXAMPLE
>
Demonstrates how to remove members from an existing group.
Carbon_Group 'RemoveAnakin'
{
Name = 'JediOrder';
Ensure = 'Present';
EnsureMembers = 'Absent';
Members = @( 'JO\ASkywalker' );
}
#>
[CmdletBinding(SupportsShouldProcess)]
param
Expand All @@ -120,10 +145,12 @@ function Set-TargetResource
# 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,

# Controls how the list of users, given in the `Members` property, gets used. Should be either `Exact`, `Present` or `Absent`. If set to `Exact`, the group is configured to have the exact members specified. If set to `Present`, the specified members are added to the group if they are not members already. If set to `Absent`, the specified members are removed from the group if they are members of it.
[ValidateSet('Exact','Present','Absent')]
[String] $EnsureMembers = 'Exact',

[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.
# A list of users, which is used according to value of `EnsureMembers`. Only used when adding/updating a group (i.e. when `Ensure` is `Present`).
$Members = @()
)

Expand All @@ -135,22 +162,29 @@ function Set-TargetResource
return
}

$group = Install-CGroup -Name $Name -Description $Description -Member $Members -PassThru
$memberNames = @()
if ($Members)
{
$memberNames = $Members | Resolve-MemberName
}
$currentMemberNames = (Get-TargetResource -Name $Name).Members

$membershipChanges = Resolve-MembershipChange -EnsureMembers $EnsureMembers `
-MemberNames $memberNames `
-CurrentMemberNames $currentMemberNames
$membersToInstall = @($currentMemberNames;$membershipChanges.ToAdd)

$group = Install-CGroup -Name $Name -Description $Description -Member $membersToInstall -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
return $membershipChanges.ToRemove -contains $memberName
}
if( $membersToRemove )
{
Expand Down Expand Up @@ -189,6 +223,9 @@ function Test-TargetResource
[String]
$Ensure = "Present",

[ValidateSet('Exact','Present','Absent')]
[String] $EnsureMembers = 'Exact',

[string[]]
$Members = @()
)
Expand Down Expand Up @@ -226,35 +263,80 @@ function Test-TargetResource
}

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

# Is the current group missing the desired members?
foreach( $memberName in $memberNames )
$membershipChanges = Resolve-MembershipChange -EnsureMembers $EnsureMembers `
-MemberNames $memberNames `
-CurrentMemberNames $resource.Members

if ($membershipChanges.ToAdd)
{
if( $currentMemberNames -notcontains $memberName )
$upToDate = $false
foreach ($memberName in $membershipChanges.ToAdd)
{
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 ($membershipChanges.ToRemove)
{
if( $memberNames -notcontains $memberName )
$upToDate = $false
foreach ($memberName in $membershipChanges.ToRemove)
{
Write-Verbose -Message ('[{0}] [Members] {1} is present but should be absent' -f $Name,$memberName)
$upToDate = $false
}
}

return $upToDate
}

function Resolve-MembershipChange
{
[CmdletBinding()]
[OutputType([hashtable])]
param (
[Parameter(Mandatory)]
[ValidateSet('Exact','Present','Absent')]
[String] $EnsureMembers,

[Parameter(Mandatory)]
[AllowEmptyCollection()]
[String[]] $MemberNames,

[Parameter(Mandatory)]
[AllowEmptyCollection()]
[String[]] $CurrentMemberNames
)

$alreadyMembers = @($MemberNames | Where-Object { $_ -in $CurrentMemberNames })
$extraMembers = @($CurrentMemberNames | Where-Object { $_ -notin $MemberNames })
$newMembers = @($MemberNames | Where-Object { $_ -notin $CurrentMemberNames })

$membersToAdd = $null
$membersToRemove = $null
if ($EnsureMembers -eq 'Present')
{
$membersToAdd = $newMembers
$membersToRemove = @()
}
elseif ($EnsureMembers -eq 'Absent')
{
$membersToAdd = @()
$membersToRemove = $alreadyMembers
}
else
{
$membersToAdd = $newMembers
$membersToRemove = $extraMembers
}

return @{
ToAdd = $membersToAdd
ToRemove = $membersToRemove
}
}

function Resolve-MemberName
{
param(
Expand Down
3 changes: 2 additions & 1 deletion Carbon.DSC/DscResources/Carbon_Group/Carbon_Group.schema.mof
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Carbon_Group : OMI_BaseResource
[Key, Description("Name of the group to configure.")] String Name;
[Write, Description("Description of the group.")] String Description;
[Write, Description("Determines whether the group is created or deleted"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure;
[Write, Description("Members of the group. After configuration, only these members will be in the group.")] String Members[];
[Write, Description("Controls how the list of users, given in the `Members` property, get used"), ValueMap{"Exact", "Present","Absent"}, Values{"Exact", "Present","Absent"}] String EnsureMembers;
[Write, Description("A list of users, which is used according to value of `EnsureMembers`")] String Members[];
};

108 changes: 93 additions & 15 deletions Tests/Carbon_Group.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,9 @@ Describe 'Carbon_Group' {
Assert-DscResourcePresent $resource

$resource.Members.Count | Should -Be $admins.Members.Count
$resourceMembers = $resource.Members | ForEach-Object { Resolve-CIdentity -Name $_ }

foreach( $admin in $admins.Members )
{
$found = $false
foreach( $potentialAdmin in $resource.Members )
{
if( $potentialAdmin.Sid -eq $admin.Sid )
{
$found = $true
break
}
}
$found | Should -BeTrue
}
$resourceMembers.Sid | Should -Be $admins.Members.Sid
}

It 'get target resource does not exist' {
Expand Down Expand Up @@ -126,6 +115,62 @@ Describe 'Carbon_Group' {
$result = Test-TargetResource -Name $script:groupName -Description $script:description -Ensure Absent
$result | Should -Not -BeNullOrEmpty
$result | Should -BeFalse

# EnsureMembers='Present'
$commonSetupArgs = @{
Name = $script:groupName;
Ensure = 'Present';
Description = $script:description;
EnsureMembers = 'Present';
}

$result = Test-TargetResource -Members @() @commonSetupArgs
$result | Should -Not -BeNullOrEmpty
$result | Should -BeTrue -Because 'the specified members list is empty'

$result = Test-TargetResource -Members $script:username1,$script:username2 @commonSetupArgs
$result | Should -Not -BeNullOrEmpty
$result | Should -BeTrue -Because 'the specified members list matches the current group members list'

$result = Test-TargetResource -Members $script:username2 @commonSetupArgs
$result | Should -Not -BeNullOrEmpty
$result | Should -BeTrue -Because 'all specified members exist in the current group members list'

$result = Test-TargetResource -Members $script:username3 @commonSetupArgs
$result | Should -Not -BeNullOrEmpty
$result | Should -BeFalse -Because 'all specified members do not exist in the current group members list'

$result = Test-TargetResource -Members $script:username2,$script:username3 @commonSetupArgs
$result | Should -Not -BeNullOrEmpty
$result | Should -BeFalse -Because 'only some specified members exist in the current group members list'

# EnsureMembers='Absent'
$commonSetupArgs = @{
Name = $script:groupName;
Ensure = 'Present';
Description = $script:description;
EnsureMembers = 'Absent';
}

$result = Test-TargetResource -Members @() @commonSetupArgs
$result | Should -Not -BeNullOrEmpty
$result | Should -BeTrue -Because 'the specified members array is empty'

$result = Test-TargetResource -Members $script:username3 @commonSetupArgs
$result | Should -Not -BeNullOrEmpty
$result | Should -BeTrue -Because 'all specified members do not exist in the current group members list'

$result = Test-TargetResource -Members $script:username1,$script:username2 @commonSetupArgs
$result | Should -Not -BeNullOrEmpty
$result | Should -BeFalse -Because 'the specified members list matches the current group members list'

$result = Test-TargetResource -Members $script:username2 @commonSetupArgs
$result | Should -Not -BeNullOrEmpty
$result | Should -BeFalse -Because 'all specified members exist in the current group members list'

$result = Test-TargetResource -Members $script:username2,$script:username3 @commonSetupArgs
$result | Should -Not -BeNullOrEmpty
$result | Should -BeFalse -Because 'only some specified members exist in the current group members list'
}

It 'set target resource' {
Expand All @@ -143,13 +188,46 @@ Describe 'Carbon_Group' {
$group.Members.Count | Should -Be 0

# Change members
Set-TargetResource -Name $script:groupName -Members $script:username1 -Ensure 'Present'
## EnsureMembers='Exact'
foreach ($i in 0..1)
{
Set-TargetResource -Name $script:groupName -Members $script:username1 -Ensure 'Present'
}

$group = Get-CGroup -Name $script:groupName
$group | Should -Not -BeNullOrEmpty
$group.Name | Should -Be $script:groupName
$group.Description | Should -BeNullOrEmpty
$group.Members.Count | Should -Be 1
$group.Members[0].Sid | Should -Be $script:user1.Sid
$group.Members.Sid | Should -Be $script:user1.Sid

## EnsureMembers='Present'
foreach ($i in 0..1)
{
Set-TargetResource -Name $script:groupName -EnsureMembers 'Present' -Members $script:username2 -Ensure 'Present'
}

$group = Get-CGroup -Name $script:groupName
$group | Should -Not -BeNullOrEmpty
$group.Name | Should -Be $script:groupName
$group.Description | Should -BeNullOrEmpty
$group.Members.Count | Should -Be 2
($group.Members.Sid -contains $script:user1.Sid) | Should -BeTrue -Because "$script:username1 is an existing member"
($group.Members.Sid -contains $script:user2.Sid) | Should -BeTrue -Because "$script:username2 should have been added to the group"

## EnsureMembers='Absent'
foreach ($i in 0..1)
{
Set-TargetResource -Name $script:groupName -EnsureMembers 'Absent' -Members $script:username2 -Ensure 'Present'
}

$group = Get-CGroup -Name $script:groupName
$group | Should -Not -BeNullOrEmpty
$group.Name | Should -Be $script:groupName
$group.Description | Should -BeNullOrEmpty
$group.Members.Count | Should -Be 1
($group.Members.Sid -notcontains $script:user2.Sid) | Should -BeTrue -Because "$script:username2 should have been removed to the group"
($group.Members.Sid -contains $script:user1.Sid) | Should -BeTrue -Because "$script:username1 is an existing member"

# Change description
Set-TargetResource -Name $script:groupName -Members $script:username1 -Description 'group description' -Ensure 'Present'
Expand Down

0 comments on commit 72e7110

Please sign in to comment.