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"
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
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}
A MOF file is created:
1CreateSelfSignedCert -ComputerName '192.168.29.108'
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
I'll use a PowerShell one-to-one remoting session to confirm the certificate was created:
1Get-ChildItem -Path Cert:\LocalMachine\My
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
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}
The MOF file has to be recreated since the configuration was changed:
1CreateSelfSignedCert -ComputerName '192.168.29.108'
As you can see, applying this new configuration does indeed remove the certificate:
1Start-DscConfiguration -Wait -Path .\CreateSelfSignedCert -Credential (Get-Credential) -Verbose
µ