Creating a Desired State Configuration Resource for Self Signed Certificates

For those of you who follow my blog, you know that I've been working on using DSC (Desired State Configuration) to fully automate the build of my test environment that runs as Hyper-V VM's on my Windows 8.1 computer.

Last week in my blog article titled Automate the installation of DSC Resource Kit Wave 9 resources with PowerShell Desired State Configuration, I demonstrated how to do just that, automate the installation of the Microsoft created DSC resources that are part of the most recent DSC resource kit (wave 9). For more details on the current state of my test environment, see that previous blog article.

The first VM in my test environment is named Test01 and it will become the first domain controller in my test environment. That will require passwords to either be stored in clear text or a certificate to be created on Test01 that can be used to encrypt the passwords. I've previously written about that process in another blog article titled Use a certificate with PowerShell DSC to add a server to Active Directory without hard coding a password, but I needed a more automated solution.

At first, I tried using the Script resource which I used in last weeks blog article, but that ended up being a less than desirable solution so I figured I would write a DSC resource which would move the complexity from the DSC configuration to a DSC resource. That way a person who is less skilled with DSC could write the configuration if needed.

I'm not going to go into quite as much detail about all of the steps to create a DSC resource in this blog article since I've previously written a blog article that does that.

First, I create the skeleton of my new DSC resource with a PowerShell one-liner:

 1New-xDscResource Name cMrSelfSignedCert -Property (
 2    New-xDscResourceProperty Name Subject Type String Attribute Key), (
 3    New-xDscResourceProperty Name StoreLocation Type String Attribute Write ValidateSet 'CurrentUser', 'LocalMachine'), (
 4    New-xDscResourceProperty Name StoreName Type String Attribute Write ValidateSet 'TrustedPublisher',
 5                                                                                        'ClientAuthIssuer',
 6                                                                                        'Root',
 7                                                                                        'CA',
 8                                                                                        'AuthRoot',
 9                                                                                        'TrustedPeople',
10                                                                                        'My',
11                                                                                        'SmartCardRoot',
12                                                                                        'Trust',
13                                                                                        'Disallowed'), (
14    New-xDscResourceProperty Name Ensure Type String Attribute Write ValidateSet 'Absent', 'Present'
15) -Path "$env:ProgramFiles\WindowsPowershell\Modules\cMrSelfSignedCert"

dscresource-cert1a.jpg

The previous step creates the directory structure, a PowerShell script module (psm1 file) and a MOF file.

There are three functions that the PSM1 file of your DSC resource must contain. Get-TargetResource which must return a hash table. Set-TargetResource which performs the action to bring whatever you're configuring into compliance, and Test-TargetResource which must return a Boolean as shown in the following example:

  1function Get-TargetResource {
  2    [CmdletBinding()]
  3    [OutputType([Hashtable])]
  4    param (
  5        [parameter(Mandatory)]
  6        [String]$Subject,
  7
  8        [parameter(Mandatory)]
  9        [ValidateSet('CurrentUser','LocalMachine')]
 10        [String]$StoreLocation,
 11
 12        [parameter(Mandatory)]
 13        [ValidateSet('TrustedPublisher','ClientAuthIssuer','Root','CA','AuthRoot','TrustedPeople','My','SmartCardRoot','Trust','Disallowed')]
 14        [String]$StoreName
 15    )
 16
 17    $certInfo = Get-ChildItem -Path "Cert:\$StoreLocation\$StoreName" |
 18                Where-Object Subject -eq $Subject
 19
 20    if ($certInfo) {
 21        Write-Verbose -Message "The certificate with subject: $Subject exists."
 22        $ensureResult = 'Present'
 23    }
 24    else {
 25        Write-Verbose -Message "The certificate with subject: $Subject does not exist."
 26        $ensureResult = 'Absent'
 27    }
 28
 29    $returnValue = @{
 30        Subject  = $certInfo.Subject
 31        Ensure = $ensureResult
 32    }
 33
 34    $returnValue
 35}
 36
 37function Set-TargetResource {
 38    [CmdletBinding()]
 39    param (
 40        [parameter(Mandatory)]
 41        [String]$Subject,
 42
 43        [parameter(Mandatory)]
 44        [ValidateSet('CurrentUser','LocalMachine')]
 45        [String]$StoreLocation,
 46
 47        [parameter(Mandatory)]
 48        [ValidateSet('TrustedPublisher','ClientAuthIssuer','Root','CA','AuthRoot','TrustedPeople','My','SmartCardRoot','Trust','Disallowed')]
 49        [String]$StoreName,
 50
 51        [parameter(Mandatory)]
 52        [ValidateSet('Absent','Present')]
 53        [String]$Ensure
 54    )
 55
 56    if ($Ensure -eq 'Present') {
 57
 58        if (-not(Get-Module -Name MrCertificate -ListAvailable)) {
 59
 60            $Uri = 'https://gallery.technet.microsoft.com/scriptcenter/Self-signed-certificate-5920a7c6/file/101251/1/New-SelfSignedCertificateEx.zip'
 61            $ModulePath = "$env:ProgramFiles\WindowsPowerShell\Modules\MrCertificate"
 62            $OutFile = "$env:ProgramFiles\WindowsPowerShell\Modules\MrCertificate\New-SelfSignedCertificateEx.zip"
 63
 64            Write-Verbose -Message 'Required module MrCertificate does not exist and will now be installed.'
 65            New-Item -Path $ModulePath -ItemType Directory
 66
 67            Write-Verbose -Message 'Downloading the New-SelfSignedCertificateEx.zip file from the TechNet script repository.'
 68            Invoke-WebRequest -Uri $Uri -OutFile $OutFile
 69            Unblock-File -Path $OutFile
 70
 71            Write-Verbose -Message 'Extracting the New-SelfSignedCertificateEx.zip file to the MrCertificate module folder.'
 72            Add-Type -AssemblyName System.IO.Compression.FileSystem
 73            [System.IO.Compression.ZipFile]::ExtractToDirectory($OutFile, $ModulePath)
 74
 75            Write-Verbose -Message 'Creating the mrcertificate.psm1 file and adding the necessary content to it.'
 76            New-Item -Path $ModulePath -Name mrcertificate.psm1 -ItemType File |
 77            Add-Content -Value '. "$env:ProgramFiles\WindowsPowerShell\Modules\MrCertificate\New-SelfSignedCertificateEx.ps1"'
 78
 79            Remove-Item -Path $OutFile -Force
 80        }
 81
 82        Import-Module -Name MrCertificate
 83
 84        Write-Verbose -Message "Creating certificate with subject: $Subject"
 85        New-SelfSignedCertificateEx -Subject $Subject -StoreLocation $StoreLocation -StoreName $StoreName
 86    }
 87    elseif ($Ensure -eq 'Absent') {
 88
 89        Write-Verbose -Message "Removing the certificate with subject $Subject."
 90        Get-ChildItem -Path "Cert:\$StoreLocation\$StoreName" |
 91        Where-Object Subject -eq $Subject |
 92        Remove-Item -Force
 93    }
 94}
 95
 96function Test-TargetResource {
 97    [CmdletBinding()]
 98    [OutputType([Boolean])]
 99    param (
100        [parameter(Mandatory)]
101        [String]$Subject,
102
103        [parameter(Mandatory)]
104        [ValidateSet('CurrentUser','LocalMachine')]
105        [String]$StoreLocation,
106
107        [parameter(Mandatory)]
108        [ValidateSet('TrustedPublisher','ClientAuthIssuer','Root','CA','AuthRoot','TrustedPeople','My','SmartCardRoot','Trust','Disallowed')]
109        [String]$StoreName,
110
111        [parameter(Mandatory)]
112        [ValidateSet('Absent','Present')]
113        [String]$Ensure
114    )
115
116    $certInfo = Get-ChildItem -Path "Cert:\$StoreLocation\$StoreName" |
117                Where-Object Subject -eq $Subject
118
119    switch ($Ensure) {
120        'Absent' {$DesiredSetting = $false}
121        'Present' {$DesiredSetting = $true}
122    }
123
124    if (($certInfo -and $DesiredSetting) -or (-not($certInfo) -and -not($DesiredSetting))) {
125         Write-Verbose -Message 'The certificate matches the desired state.'
126         [Boolean]$result = $true
127    }
128    else {
129        Write-Verbose -Message 'The certificate does not match the desired state.'
130        [Boolean]$result = $false
131    }
132
133    $result
134}
135
136Export-ModuleMember -Function *-TargetResource

I will tell you, the code you see in the PSM1 file shown in the previous example isn't your ordinary DSC resource example because it does something brilliant or insane depending on how you look at it and whether or not your a half full or half empty kind of person.

As noted in one of my blog articles that was previously referenced, fellow PowerShell MVP Vadims Podans has a PowerShell self-signed certificate generator function that I wanted to use to simplify the process of creating the actual certificate. His function is contained in a signed PS1 file which I didn't want to modify so my DSC resource simply downloads his function as a zip file from the TechNet script repository, extracts it, and creates a module named MrCertificate that does nothing more than dot source his PS1 script file.

I create a module manifest (psd1 file) for my new DSC resource:

1New-ModuleManifest -Path "$env:ProgramFiles\WindowsPowershell\Modules\cMrSelfSignedCert\cMrSelfSignedCert.psd1" -Author 'Mike F Robbins' -CompanyName 'mikefrobbins.com' -RootModule 'cMrSelfSignedCert' -Description 'Module to Create Self Signed Certificates' -PowerShellVersion 4.0 -FunctionsToExport '*.TargetResource' -Verbose

dscresource-cert2a.jpg

Using a simple DSC configuration:

 1Configuration CreateSelfSignedCert {
 2
 3    param (
 4        [Parameter(Mandatory)]
 5        [string[]]$ComputerName
 6    )
 7
 8    Import-DscResource -ModuleName cMrSelfSignedCert
 9
10    node $ComputerName {
11
12        cMrSelfSignedCert CreateCert {
13            Subject = 'CN=192.168.29.108'
14            StoreLocation = 'LocalMachine'
15            StoreName = 'My'
16            Ensure = 'Present'
17        }
18    }
19}

dscresource-cert3a.jpg

A MOF file is created:

1CreateSelfSignedCert -ComputerName '192.168.29.108'

dscresource-cert4a.jpg

When the configuration is applied, a self signed certificate is created on my Test01 VM which can be used to encrypt the password that is contained in the MOF file of future configurations that I plan to create:

1Start-DscConfiguration -Wait -Path .\CreateSelfSignedCert -Credential (Get-Credential) -Verbose

dscresource-cert5a.jpg

I'll use a PowerShell one-to-one remoting session to confirm the certificate was created:

1Get-ChildItem -Path Cert:\LocalMachine\My

dscresource-cert6a.jpg

Reapplying the configuration shows that the certificate already exists and nothing needs to be done so the set portion of the configuration is skipped:

1Start-DscConfiguration -Wait -Path .\CreateSelfSignedCert -Credential (Get-Credential) -Verbose

dscresource-cert7a.jpg

I'll test removing the certificate by changing Ensure in the configuration to Absent:

 1Configuration CreateSelfSignedCert {
 2
 3    param (
 4        [Parameter(Mandatory)]
 5        [string[]]$ComputerName
 6    )
 7
 8    Import-DscResource -ModuleName cMrSelfSignedCert
 9
10    node $ComputerName {
11
12        cMrSelfSignedCert CreateCert {
13            Subject = 'CN=192.168.29.108'
14            StoreLocation = 'LocalMachine'
15            StoreName = 'My'
16            Ensure = 'Absent'
17        }
18    }
19}

dscresource-cert8a.jpg

The MOF file has to be recreated since the configuration was changed:

1CreateSelfSignedCert -ComputerName '192.168.29.108'

dscresource-cert9a.jpg

As you can see, applying this new configuration does indeed remove the certificate:

1Start-DscConfiguration -Wait -Path .\CreateSelfSignedCert -Credential (Get-Credential) -Verbose

dscresource-cert10a.jpg

µ