Before the PowerShell summit I had no idea of the existence of DSC. We had a hackathon evening where we got to work in groups creating (or rather make an attempt) a DSC Resource. I’ll admit I started out, but being in the room with Lee Homes, Don Jones and Richard Siddaway, who really wants to code then eh? It was awesome being able to interact with these guys!
Back at office I started gathering all information possible about DSC. The DSC Book @PowerShell.Org is a great place to start! If you’re anything like me, you’ll start wondering what makes DSC tick? How does it work under the hood? That’s where “Windows PowerShell Desired State Configuration Revealed” by Ravikanth Chaganti helps.
Ok I’ll go out on a limb here and make an assumption that you already know the basics so as not to bore you. 🙂 If you’re looking for a primer, have a look at Andrew Barnes (aka Scriptimus Prime) Desired State Configuration blog series. Great stuff!!!
Ok so the DSC Resource I’ve created is designed for creating an ADGroup. As luck would have it there’s already a xADUser DSC Resource, I’ve based cADGroup on the logic I found there, hey why reinvent the wheel? 😉
I like how the PowerShell team used a function ValidateProperties with an apply switch.
With -apply the configuration will be set, without only tested.
My first attempt was without using the DSCResourceDesigner. I knew the basics and started from there and I looked a lot into how things were done in xADUser. Turns out it’s not that difficult to apply
Import-Module xDSCResourceDesigner #Define DSC parameters $ResourceName = 'PSHOrg_cActiveDirectory' $FriendlyName = 'cADGroup' $DomainName = New-xDscResourceProperty -Name DomainName -Type String -Attribute Required $GroupName = New-xDscResourceProperty -Name GroupName -Type String -Attribute Key $GroupCategory = New-xDscResourceProperty -Name GroupCategory -Type String -Attribute Required $GroupScope = New-xDscResourceProperty -Name GroupScope -Type String -Attribute Required $ADSPath = New-xDscResourceProperty -Name ADSPath -Type String -Attribute Required $DomainAdministratorCredential = New-xDscResourceProperty -Name DomainAdminCredential -Type PSCredential -Attribute Required $Ensure = New-xDscResourceProperty -Name Ensure -Type String -Attribute Write -ValidateSet 'Present', 'Absent' # Create the DSC resource New-xDscResource ` -Name 'PSHOrg_cADGroup' ` -Property $DomainName,$GroupName,$GroupCategory,$GroupScope,$ADSPath,$DomainAdministratorCredential,$Ensure ` -Path "C:\Program Files\WindowsPowerShell\Modules\$ResourceName" ` -ClassVersion 1.0 ` -FriendlyName $FriendlyName ` -Force
So the DSCResource designer takes care of all the defaults. Things like creating the DSCResources folder, the default module and mof file. Definitely worth looking into if you want to get things right the first time!
Yeah I know, lesson learned.
Ok a quick recap:
- Get-TargetResource returns a hash table
- Test-TargetResource returns a boolean value
- Set-TargetResource, that’s where the magic happens! 🙂
Ok so what do we need to know to create a ADGroup using DSC? Staying close to xADUser I’ll use *-ADGroup cmdlets.
These are the parameters required:
- DomainName
- GroupName
- GroupScope
- ADSPath
- DomainCredentials
Loving the DSCResourceDesigner!
Now to update the module
function Get-TargetResource { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [parameter(Mandatory = $true)] [System.String] $DomainName, [parameter(Mandatory = $true)] [System.String] $GroupName, [parameter(Mandatory = $true)] [System.String] $GroupCategory, [parameter(Mandatory = $true)] [System.String] $GroupScope, [parameter(Mandatory = $true)] [System.String] $ADSPath, [parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $DomainAdminCredential ) try { Write-Verbose -Message "Checking if the group $GroupName in domain $DomainName is present ..." $group = Get-ADGroup -Identity $GroupName -Credential $DomainAdminCredential Write-Verbose -Message "Group $GroupName in domain $DomainName is present." $Ensure = 'Present' } #Group not found catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { Write-Verbose -Message "Group $GroupName account in domain $DomainName is NOT present" $Ensure = 'Absent' } catch { Write-Error -Message "Unhandled exception looking up $GroupName account in domain $DomainName." throw $_ } $returnValue = @{ DomainName = $DomainName GroupName = $GroupName GroupCategory = $GroupCategory GroupScope = $GroupScope ADSPath = $ADSPath Ensure = $Ensure } $returnValue } function Set-TargetResource { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [System.String] $DomainName, [parameter(Mandatory = $true)] [System.String] $GroupName, [parameter(Mandatory = $true)] [System.String] $GroupCategory, [parameter(Mandatory = $true)] [System.String] $GroupScope, [parameter(Mandatory = $true)] [System.String] $ADSPath, [parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $DomainAdminCredential, [ValidateSet('Present','Absent')] [System.String] $Ensure ) try { ValidateProperties @PSBoundParameters -Apply } catch { Write-Error -Message "Error setting ADGroup $GroupName in domain $DomainName. $_" throw $_ } } function Test-TargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [parameter(Mandatory = $true)] [System.String] $DomainName, [parameter(Mandatory = $true)] [System.String] $GroupName, [parameter(Mandatory = $true)] [System.String] $GroupCategory, [parameter(Mandatory = $true)] [System.String] $GroupScope, [parameter(Mandatory = $true)] [System.String] $ADSPath, [parameter(Mandatory = $true)] [System.Management.Automation.PSCredential] $DomainAdminCredential, [ValidateSet('Present','Absent')] [System.String] $Ensure ) try { $parameters = $PSBoundParameters.Remove('Debug'); ValidateProperties @PSBoundParameters } catch { Write-Error -Message "Error testing AD User $UserName in domain $DomainName. $_" throw $_ } } function ValidateProperties { param ( [Parameter(Mandatory)] [string]$DomainName, [Parameter(Mandatory)] [string]$GroupName, [Parameter(Mandatory)] [string]$GroupCategory, [Parameter(Mandatory)] [string]$GroupScope, [Parameter(Mandatory)] [string]$ADSPath, [Parameter(Mandatory)] [PSCredential]$DomainAdminCredential, [ValidateSet('Present','Absent')] [string]$Ensure, [Switch]$Apply ) # Check if group exists try { Write-Verbose -Message "Checking if the group $GroupName in domain $DomainName is present ..." $group = Get-ADGroup -Identity $GroupName -Credential $DomainAdminCredential if ($group) { Write-Verbose -Message "Group $GroupName in domain $DomainName is present." if( $Apply ) { if ($Ensure -ne 'Present'){ Remove-ADGroup -Identity $GroupName -Credential $DomainAdminCredential -Confirm:$false Write-Verbose -Message "Group $GroupName account in domain $DomainName has been deleted" } } Else{ return ($Ensure -eq 'Present') } } } #Group not found catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { Write-Verbose -Message "Group $GroupName account in domain $DomainName is NOT present" if($Apply) { if( $Ensure -ne 'Absent' ) { $params = @{ Name = $GroupName SamAccountName = $GroupName GroupCategory = $GroupCategory GroupScope = $GroupScope Path = $ADSPath Credential = $DomainAdminCredential } New-ADGRoup @params Write-Verbose -Message "Group $GroupName account in domain $DomainName has been created" } } else { return ( $Ensure -eq 'Absent' ) } } } Export-ModuleMember -Function *-TargetResource
At the heart of it is the ValidateProperties function. Did I mention that I like the way the PowerShell team solved this? 🙂 The logic is as follows:
See if the group exists. If apply switch has been provided and Ensure is ‘Absent’ then remove the group, else return (Ensure -eq Present). If the group doesn’t exist (using catch ADIdentityNotFoundException) see if apply switch has been provided. if Ensure isn’t equal to Absent create the group, else return (Ensure -eq Absent)
I’ll admit, I had to revise it quite a few times. The Test-TargetResource is the trickiest part in my opinion.
Here’s the code code create the mof files, Ofcourse this works in my environment:
$ConfigData = @{ AllNodes = @( @{ NodeName = '*' PSDscAllowPlainTextPassword = $True } @{ NodeName = 'dc-dsc-01' } ) } configuration CreateDomainGroup{ param( [string[]]$ComputerName, [string]$UserDomain, [pscredential]$Credential ) Import-DscResource -ModuleName 'PSHORG_cActiveDirectory' node $ComputerName{ cADGroup ComputerAdminGroup { Ensure = 'Present' GroupName = "DEL-$ComputerName-ADM" GroupScope = 'Global' GroupCategory = 'Security' ADSPath = 'OU=Delegation,OU=Resources,DC=pshirwin,DC=local' DomainName = $UserDomain DomainAdminCredential = $Credential } } } $Cred = Get-Credential CreateDomainGroup -Credential $Cred -ConfigurationData $ConfigData -OutputPath "$pwd\modules\CreateDomainGroup" -ComputerName 'DC-DSC-01' -UserDomain $env:USERDOMAIN Start-DscConfiguration -Path .\modules\CreateDomainGroup -Wait -Verbose -ComputerName DC-DSC-01 -Force
I know PSDSCAllowPlainTextPasswords are not done! Baby steps… 🙂
Now for something different
So that was my first attempt at creating a DSC Resource. I was quite proud of myself! But then I started thinking (Oh boy here we go…) Deleting and Creating Security Groups means that even though the object looks the same, the SID (Security Identifier) will be different. If for some reason after an extended period of time the group has been deleted (using a script or adac or dsa, take your pick) It will be recreated, only it will have a different SID. Any resource that’s using that group (Think NTFS security rights) will have a rogue ACE entry. So in this case, configuring the LocalConfigurationManager to ‘ApplyOnly’ might be a wise choice. And as I’m writing this… if you want more in-depth knowledge have a look at Ravikanth Chaganti book!
Infrastructure, make way for DSC
The future versions of DSC will be based on DSC Classes, that’s how quick DSC is evolving. DSC Classes does require WMF 5.0 at the moment. if you’re interested in PowerShell & Classes have a look at Doug Finke’s blog on the subject And if you really wanna get started with DSC Classes, Ben Gelens has just the thing for you, a DSC Class template!!! Gotta love the PowerShell community!!!
This just in: Microsoft Publishes DSC Resource Kit in GitHub.
I guess GitHub will be where we can get hands on DSC Resources in the future!
DSC is rapidly taking over the Infrastructure scene, so hop on while you still can! The best way to learn about DSC is by using it! I know this doesn’t cover all the ins and outs, but I hope it has conviced you that you should be looking into DSC…
Ttyl,
Urv
Hi, where does “PSHORG_cActiveDirectory” come from?
Can I just substitute it with “xActiveDirectory” or “MYORG_cActiveDirectory”, or will I need to build another dscresource to support this one?
Thanks…Great article.
LikeLike
Hi,
I wrote this particular one as part of the PowerShell Hackathon… There were rules to abide by…
Here’s the link for more Powershell.Org community DSC Resources https://github.com/PowerShellOrg/cActiveDirectory/tree/master/DSCResources. You can always improvise on a DSC Resource 🙂
Rg./Irwin
LikeLike