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!

DSCResourceDesigner-Results

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

PSHORg_CADGroup mof file

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

Advertisement

2 thoughts on “Creating my very first DSC Resource

  1. mlintonford

    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.

    Like

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s