DSCResources/cLocalGroup/cLocalGroup.psm1
<#
Author : Serge Nikalaichyk (https://www.linkedin.com/in/nikalaichyk) Version : 1.0.0 Date : 2015-10-05 #> function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $GroupName ) $Group = Get-LocalGroup -Name $GroupName -ErrorAction SilentlyContinue if ($Group) { Write-Verbose -Message "Local group '$GroupName' was found." $EnsureResult = 'Present' } else { Write-Verbose -Message "Local group '$GroupName' could not be found." $EnsureResult = 'Absent' } $ReturnValue = @{ GroupName = $GroupName Description = $Group.Description Members = $Group.Members Ensure = $EnsureResult } return $ReturnValue } function Set-TargetResource { [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $false)] [ValidateSet('Absent', 'Present')] [System.String] $Ensure = 'Present', [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $GroupName, [Parameter(Mandatory = $false)] [System.String] $Description, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [System.String[]] $Members, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [System.String[]] $MembersToExclude, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [System.String[]] $MembersToInclude ) $TargetResource = Get-TargetResource -GroupName $GroupName if (-not $PSCmdlet.ShouldProcess($GroupName)) { return } if ($Ensure -eq 'Absent') { if ($TargetResource.Ensure -eq 'Present') { Remove-LocalGroup -Name $GroupName -Confirm:$false -ErrorAction Stop } } elseif ($Ensure -eq 'Present') { if ($TargetResource.Ensure -eq 'Absent') { New-LocalGroup -Name $GroupName -ErrorAction Stop $TargetResource = Get-TargetResource -GroupName $GroupName -ErrorAction Stop } if ($PSBoundParameters.ContainsKey('Description')) { if ($TargetResource.Description -cne $Description) { Set-LocalGroup -Name $GroupName -Description $Description } } if ($PSBoundParameters.ContainsKey('Members')) { if ($PSBoundParameters.ContainsKey('MembersToExclude') -or $PSBoundParameters.ContainsKey('MembersToInclude')) { throw "Parameter 'Members' cannot be specified along with 'MembersToExclude' or 'MembersToInclude'." } $ReferenceMembers = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[System.String]' if ($Members.Count -ne 0) { $Members | Resolve-IdentityReference | Select-Object -ExpandProperty Name -Unique | ForEach-Object {$ReferenceMembers.Add($_)} } Compare-Object -ReferenceObject $ReferenceMembers -DifferenceObject $TargetResource.Members | ForEach-Object { if ($_.SideIndicator -eq '<=') { Add-LocalGroupMember -Name $GroupName -Members $_.InputObject } if ($_.SideIndicator -eq '=>') { Remove-LocalGroupMember -Name $GroupName -Members $_.InputObject } } } else { if ($PSBoundParameters.ContainsKey('MembersToExclude')) { $ReferenceMembersToExclude = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[System.String]' $MembersToExclude | Resolve-IdentityReference | Select-Object -ExpandProperty Name -Unique | ForEach-Object {$ReferenceMembersToExclude.Add($_)} } if ($PSBoundParameters.ContainsKey('MembersToInclude')) { $ReferenceMembersToInclude = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[System.String]' $MembersToInclude | Resolve-IdentityReference | Select-Object -ExpandProperty Name -Unique | ForEach-Object {$ReferenceMembersToInclude.Add($_)} } if ($ReferenceMembersToExclude -and $ReferenceMembersToInclude) { Compare-Object -DifferenceObject $ReferenceMembersToExclude -ReferenceObject $ReferenceMembersToInclude -ExcludeDifferent -IncludeEqual | ForEach-Object { "Member '{0}' is present in both 'MembersToExclude' and 'MembersToInclude' collections." -f $_.InputObject | Write-Verbose "'MembersToExclude' takes precedence over 'MembersToInclude'." | Write-Verbose [Void]$ReferenceMembersToInclude.Remove($_.InputObject) } } if ($ReferenceMembersToExclude.Count -ne 0) { $TargetResource.Members | Where-Object {$_ -in $ReferenceMembersToExclude} | Remove-LocalGroupMember -Name $GroupName } if ($ReferenceMembersToInclude.Count -ne 0) { $ReferenceMembersToInclude | Where-Object {$_ -notin $TargetResource.Members} | Add-LocalGroupMember -Name $GroupName } } } } function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $false)] [ValidateSet('Absent', 'Present')] [System.String] $Ensure = 'Present', [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $GroupName, [Parameter(Mandatory = $false)] [System.String] $Description, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [System.String[]] $Members, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [System.String[]] $MembersToExclude, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [System.String[]] $MembersToInclude ) $PSBoundParameters.GetEnumerator() | ForEach-Object -Begin { $Width = $PSBoundParameters.Keys.Length | Sort-Object -Descending | Select-Object -First 1 } -Process { "{0,-$($Width)} : '{1}'" -f $_.Key, ($_.Value -join ', ') | Write-Verbose } $TargetResource = Get-TargetResource -GroupName $GroupName if ($Ensure -eq 'Absent') { if ($TargetResource.Ensure -eq 'Absent') { $InDesiredState = $true } else { $InDesiredState = $false } } elseif ($Ensure -eq 'Present') { if ($TargetResource.Ensure -eq 'Absent') { $InDesiredState = $false } else { $InDesiredState = $true if ($PSBoundParameters.ContainsKey('Description')) { if ($TargetResource.Description -cne $Description) { $InDesiredState = $false } } if ($PSBoundParameters.ContainsKey('Members')) { if ($PSBoundParameters.ContainsKey('MembersToExclude') -or $PSBoundParameters.ContainsKey('MembersToInclude')) { throw "Parameter 'Members' cannot be specified along with 'MembersToExclude' or 'MembersToInclude'." } $ReferenceMembers = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[System.String]' if ($Members.Count -ne 0) { $Members | Resolve-IdentityReference | Select-Object -ExpandProperty Name -Unique | ForEach-Object {$ReferenceMembers.Add($_)} } if (Compare-Object -ReferenceObject $ReferenceMembers -DifferenceObject $TargetResource.Members) { $InDesiredState = $false } } else { if ($PSBoundParameters.ContainsKey('MembersToExclude')) { $ReferenceMembersToExclude = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[System.String]' $MembersToExclude | Resolve-IdentityReference | Select-Object -ExpandProperty Name -Unique | ForEach-Object {$ReferenceMembersToExclude.Add($_)} } if ($PSBoundParameters.ContainsKey('MembersToInclude')) { $ReferenceMembersToInclude = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[System.String]' $MembersToInclude | Resolve-IdentityReference | Select-Object -ExpandProperty Name -Unique | ForEach-Object {$ReferenceMembersToInclude.Add($_)} } if ($ReferenceMembersToExclude -and $ReferenceMembersToInclude) { Compare-Object -DifferenceObject $ReferenceMembersToExclude -ReferenceObject $ReferenceMembersToInclude -ExcludeDifferent -IncludeEqual | ForEach-Object { "Member '{0}' is present in both 'MembersToExclude' and 'MembersToInclude' collections." -f $_.InputObject | Write-Verbose "'MembersToExclude' takes precedence over 'MembersToInclude'." | Write-Verbose [Void]$ReferenceMembersToInclude.Remove($_.InputObject) } } if ($ReferenceMembersToExclude.Count -ne 0) { if ($TargetResource.Members | Where-Object {$_ -in $ReferenceMembersToExclude}) { $InDesiredState = $false } } if ($ReferenceMembersToInclude.Count -ne 0) { if ($ReferenceMembersToInclude | Where-Object {$_ -notin $TargetResource.Members}) { $InDesiredState = $false } } } } } if ($InDesiredState -eq $true) { Write-Verbose -Message "The target resource is already in the desired state. No action is required." } else { Write-Verbose -Message "The target resource is not in the desired state." } return $InDesiredState } Export-ModuleMember -Function Get-TargetResource, Set-TargetResource, Test-TargetResource #region Helper Functions function Get-LocalGroup { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name ) begin { $Computer = [ADSI]"WinNT://$Env:ComputerName" } process { try { $Group = $Computer.PSBase.Children.Find($Name, 'Group') } catch { "Local group '{0}' could not be found: '{1}'" -f $Name, $_.Exception.Message | Write-Error return } $OutputObject = [PSCustomObject]@{ Name = [System.String]$Group.Name Description = [System.String]$Group.Description Members = New-Object -TypeName 'System.Collections.ObjectModel.Collection`1[System.String]' } $Group.PSBase.Invoke('Members') | ForEach-Object { $objectSID = ([ADSI]$_).InvokeGet('objectSID') $SecurityIdentifier = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $objectSID, 0 $NTAccount = $SecurityIdentifier.Translate([System.Security.Principal.NTAccount]) $OutputObject.Members.Add($NTAccount.Value) } return $OutputObject } } function New-LocalGroup { [CmdletBinding(ConfirmImpact = 'Medium', SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name ) begin { $Computer = [ADSI]"WinNT://$Env:ComputerName" } process { if ($Name -match '(^[\s|\.]*?$)|([\\\/\"\[\]\:\|\<\>\+\=\;\,\?\*\@])') { "The name '$Name' cannot be used. Names may not consist entirely of periods and/or spaces, or contain these characters: \/`"[]:|<>+=;,?*@" | Write-Error return } if ($PSCmdlet.ShouldProcess($Name, 'Create Local Group')) { Write-Verbose -Message "Creating local group '$Name'." $Group = $Computer.Create('Group', $Name) $Group.SetInfo() } } } function Remove-LocalGroup { [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name ) begin { $Computer = [ADSI]"WinNT://$Env:ComputerName" } process { try { $Group = $Computer.PSBase.Children.Find($Name, 'Group') } catch { "Local group '{0}' could not be found: '{1}'" -f $Name, $_.Exception.Message | Write-Error return } if ($PSCmdlet.ShouldProcess($Name, 'Remove Local Group')) { Write-Verbose -Message "Removing local group '$Name'." $Computer.PSBase.Children.Remove($Group) } } } function Set-LocalGroup { [CmdletBinding(ConfirmImpact = 'Medium', SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name, [Parameter(Mandatory = $false)] [System.String] $Description = $null ) begin { $Computer = [ADSI]"WinNT://$Env:ComputerName" } process { try { $Group = $Computer.PSBase.Children.Find($Name, 'Group') } catch { "Local group '{0}' could not be found: '{1}'" -f $Name, $_.Exception.Message | Write-Error return } if ($PSCmdlet.ShouldProcess($Name, 'Set Local Group')) { if ($PSBoundParameters.ContainsKey('Description')) { Write-Verbose -Message "Setting description for local group '$Name'." $Group.Description = $Description $Group.SetInfo() } } } } function Add-LocalGroupMember { [CmdletBinding(ConfirmImpact = 'Medium', SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [System.String[]] $Members ) begin { $Computer = [ADSI]"WinNT://$Env:ComputerName" } process { try { $Group = $Computer.PSBase.Children.Find($Name, 'Group') } catch { "Local group '{0}' could not be found: '{1}'" -f $Name, $_.Exception.Message | Write-Error return } $Members | Select-Object -PipelineVariable Member | ForEach-Object { if ($Member -match '\\') { $ADsPath = $Member -ireplace '^(?<Domain>.*?)\\(?<UserName>.*?)$', 'WinNT://${Domain}/${UserName}' } else { try { # Resolve and normalize member's identity $Identity = Resolve-IdentityReference -Identity $Member -ErrorAction Stop $ADsPath = $Identity.Name -ireplace '^(?<Domain>.*?)\\(?<UserName>.*?)$', 'WinNT://${Domain}/${UserName}' } catch { Write-Error -Message $_.Exception.Message return } } try { if ($PSCmdlet.ShouldProcess($Name, 'Add Member')) { "Adding member '{0}' to local group '{1}'." -f $Member, $Name | Write-Verbose $Group.Add($ADsPath) } } catch { Write-Error -Message $_.Exception.Message return } } } } function Remove-LocalGroupMember { [CmdletBinding(ConfirmImpact = 'Medium', SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name, [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNullOrEmpty()] [System.String[]] $Members ) begin { $Computer = [ADSI]"WinNT://$Env:ComputerName" } process { try { $Group = $Computer.PSBase.Children.Find($Name, 'Group') } catch { "Local group '{0}' could not be found: '{1}'" -f $Name, $_.Exception.Message | Write-Error return } $Members | Select-Object -PipelineVariable Member | ForEach-Object { if ($Member -match '\\') { $ADsPath = $Member -ireplace '^(?<Domain>.*?)\\(?<UserName>.*?)$', 'WinNT://${Domain}/${UserName}' } else { try { # Resolve and normalize member's identity $Identity = Resolve-IdentityReference -Identity $Member -ErrorAction Stop $ADsPath = $Identity.Name -ireplace '^(?<Domain>.*?)\\(?<UserName>.*?)$', 'WinNT://${Domain}/${UserName}' } catch { Write-Error -Message $_.Exception.Message return } } try { if ($PSCmdlet.ShouldProcess($Name, 'Remove Member')) { "Removing member '{0}' from local group '{1}'." -f $Member, $Name | Write-Verbose $Group.Remove($ADsPath) } } catch { Write-Error -Message $_.Exception.Message return } } } } function Resolve-IdentityReference { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [System.String] $Identity ) process { try { Write-Verbose -Message "Resolving identity reference '$Identity'." if ($Identity -match '^S-\d-(\d+-){1,14}\d+$') { [System.Security.Principal.SecurityIdentifier]$Identity = $Identity } else { [System.Security.Principal.NTAccount]$Identity = $Identity } $SID = $Identity.Translate([System.Security.Principal.SecurityIdentifier]) $NTAccount = $SID.Translate([System.Security.Principal.NTAccount]) $OutputObject = [PSCustomObject]@{Name = $NTAccount.Value; SID = $SID.Value} return $OutputObject } catch { "Unable to resolve identity reference '{0}'. Error: '{1}'" -f $Identity, $_.Exception.Message | Write-Error return } } } #endregion |