1 # MIT License
2 #
3 # Copyright (c) 2016 Raimund Andée
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in all
13 # copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 # SOFTWARE.
22
23 #region Internals
24 #region .net Types
25 $certStoreTypes = @'
26 ...
27 '@
28
29 $pkiInternalsTypes = @'
30 ...
31 /// <summary>
32 /// 2.28 msPKI-Certificate-Name-Flag Attribute
33 /// https://msdn.microsoft.com/en-us/library/cc226548.aspx
34 /// </summary>
35 ...
36 '@
37
38 $gpoType = @'
39 ...
40 '@
41 #endregion .net Types
42
43 $ApplicationPolicies = @{
44 # Remote Desktop
45 'Remote Desktop' = '1.3.6.1.4.1.311.54.1.2'
46 # Windows Update
47 'Windows Update' = '1.3.6.1.4.1.311.76.6.1'
48 # Windows Third Party Applicaiton Component
49 'Windows Third Party Application Component' = '1.3.6.1.4.1.311.10.3.25'
50 # Windows TCB Component
51 'Windows TCB Component' = '1.3.6.1.4.1.311.10.3.23'
52 # Windows Store
53 'Windows Store' = '1.3.6.1.4.1.311.76.3.1'
54 #...
55 }
56
57 $ExtendedKeyUsages = @{
58 OldAuthorityKeyIdentifier = '.29.1'
59 OldPrimaryKeyAttributes = '2.5.29.2'
60 #...
61 X509version3CertificateExtensionInhibitAny = '2.5.29.54'
62 }
63
64 #endregion Internals
65
66 #region Get-LabCertificate
Get-LabCertificate()67 function Get-LabCertificate
68 {
69 # .ExternalHelp AutomatedLab.Help.xml
70 [cmdletBinding(DefaultParameterSetName = 'Find')]
71 param (
72 [Parameter(Mandatory = $true, ParameterSetName = 'Find')]
73 [string]$SearchString,
74
75 [Parameter(Mandatory = $true, ParameterSetName = 'Find')]
76 [System.Security.Cryptography.X509Certificates.X509FindType]$FindType,
77
78 [System.Security.Cryptography.X509Certificates.CertStoreLocation]$Location,
79
80 [System.Security.Cryptography.X509Certificates.StoreName]$Store,
81
82 [string]$ServiceName,
83
84 [Parameter(Mandatory = $true, ParameterSetName = 'All')]
85 [switch]$All,
86
87 [Parameter(ParameterSetName = 'All')]
88 [switch]$IncludeServices,
89
90 [string]$Password = 'AL',
91
92 [Parameter(Mandatory)]
93 [string[]]$ComputerName
94 )
95
96 Write-LogFunctionEntry
97
98 $variables = Get-Variable -Name PSBoundParameters
99 $functions = Get-Command -Name Get-Certificate2, Sync-Parameter
100
101 $x = $PSBoundParameters
102
103 foreach ($computer in $ComputerName)
104 {
105 Invoke-LabCommand -ActivityName 'Adding Cert Store Types' -ComputerName $ComputerName -ScriptBlock {
106 Add-Type -TypeDefinition $args[0]
107 } -ArgumentList $certStoreTypes -NoDisplay
108
109 Invoke-LabCommand -ActivityName 'Exporting certificates' -ComputerName $ComputerName -ScriptBlock {
110 $variables['Password'] = $variables['Password'] | ConvertTo-SecureString -AsPlainText -Force
111 Sync-Parameter -Command (Get-Command -Name Get-Certificate2)
112 Get-Certificate2 @ALBoundParameters
113
114 } -Variable $variables -Function $functions -PassThru
115 }
116
117 Write-LogFunctionExit
118 }
119 #endregion Get-LabCertificate
120
121 #region Add-LabCertificate
Add-LabCertificate()122 function Add-LabCertificate
123 {
124 # .ExternalHelp AutomatedLab.Help.xml
125 [cmdletBinding(DefaultParameterSetName = 'ByteArray')]
126 param(
127 [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'File')]
128 [string]$Path,
129
130 [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ByteArray')]
131 [byte[]]$Cert,
132
133 [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
134 [System.Security.Cryptography.X509Certificates.StoreName]$Store,
135
136 [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
137 [System.Security.Cryptography.X509Certificates.CertStoreLocation]$Location,
138
139 [Parameter(ValueFromPipelineByPropertyName = $true)]
140 [string]$ServiceName,
141
142 [Parameter(ValueFromPipelineByPropertyName = $true)]
143 [ValidateSet('CER', 'PFX')]
144 [string]$CertificateType = 'CER',
145
146 [string]$Password = 'AL',
147
148 [Parameter(Mandatory, ValueFromPipelineByPropertyName = $true)]
149 [string[]]$ComputerName
150 )
151
152 begin
153 {
154 Write-LogFunctionEntry
155 }
156
157 process
158 {
159 $variables = Get-Variable -Name PSBoundParameters
160 $functions = Get-Command -Name Add-Certificate2, Sync-Parameter
161
162 Invoke-LabCommand -ActivityName 'Adding Cert Store Types' -ComputerName $ComputerName -ScriptBlock {
163 Add-Type -TypeDefinition $args[0]
164 } -ArgumentList $certStoreTypes -NoDisplay
165
166 Invoke-LabCommand -ActivityName 'Storing certificate bytes on target machine' -ComputerName $ComputerName -ScriptBlock {
167
168 $tempFile = [System.IO.Path]::GetTempFileName()
169 [System.IO.File]::WriteAllBytes($tempFile, $args[0])
170 Write-Verbose "Cert is written to '$tempFile'"
171
172 } -ArgumentList (,$Cert) -Variable $variables
173
174 Invoke-LabCommand -ActivityName 'Importing Cert file' -ComputerName $ComputerName -ScriptBlock {
175 $variables['Password'] = $variables['Password'] | ConvertTo-SecureString -AsPlainText -Force
176 Sync-Parameter -Command (Get-Command -Name Add-Certificate2)
177 $ALBoundParameters.Add('Path', $tempFile)
178 $ALBoundParameters.Remove('Cert')
179 Add-Certificate2 @ALBoundParameters | Out-Null
180 Remove-Item -Path $tempFile
181
182 } -Variable $variables -Function $functions -PassThru
183
184 }
185
186 end
187 {
188 Write-LogFunctionExit
189 }
190 }
191 #endregion Add-LabCertificate
192
193 # ...
194
195 #region Install-LabCAMachine
Install-LabCAMachine()196 function Install-LabCAMachine
197 {
198 # .ExternalHelp AutomatedLab.Help.xml
199 [CmdletBinding()]
200
201 param (
202 [Parameter(Mandatory)]
203 [AutomatedLab.Machine]$Machine,
204
205 [int]$PreDelaySeconds,
206
207 [switch]$PassThru
208 )
209
210 Write-LogFunctionEntry
211
212 Write-Verbose -Message '****************************************************'
213 Write-Verbose -Message "Starting installation of machine: $($machine.name)"
214 Write-Verbose -Message '****************************************************'
215
216 $role = $machine.Roles | Where-Object { $_.Name -eq ([AutomatedLab.Roles]::CaRoot) -or $_.Name -eq ([AutomatedLab.Roles]::CaSubordinate) }
217
218 $param = [ordered]@{ }
219
220 #region - Locate admin username and password for machine
221 if ($machine.IsDomainJoined)
222 {
223 $domain = $lab.Domains | Where-Object { $_.Name -eq $machine.DomainName }
224
225 $param.Add('UserName', ('{0}\{1}' -f $domain.Name, $domain.Administrator.UserName))
226 $param.Add('Password', $domain.Administrator.Password)
227
228 $rootDc = Get-LabMachine -Role RootDC | Where-Object DomainName -eq $machine.DomainName
229 if ($rootDc) #if there is a root domain controller in the same domain as the machine
230 {
231 $rootDomain = (Get-Lab).Domains | Where-Object Name -eq $rootDc.DomainName
232 $rootDomainNetBIOSName = ($rootDomain.Name -split '\.')[0]
233 }
234 else #else the machine is in a child domain and the parent domain need to be used for the query
235 {
236 $rootDomain = $lab.GetParentDomain($machine.DomainName)
237 $rootDomainNetBIOSName = ($rootDomain.Name -split '\.')[0]
238 $rootDc = Get-LabMachine -Role RootDC | Where-Object DomainName -eq $rootDomain
239 }
240
241 $param.Add('ForestAdminUserName', ('{0}\{1}' -f $rootDomainNetBIOSName, $rootDomain.Administrator.UserName))
242 $param.Add('ForestAdminPassword', $rootDomain.Administrator.Password)
243
244 Write-Debug -Message "Machine : $($machine.name)"
245 Write-Debug -Message "Machine Domain : $($machine.DomainName)"
246 Write-Debug -Message "Username for job : $($param.username)"
247 Write-Debug -Message "Password for job : $($param.Password)"
248 Write-Debug -Message "ForestAdmin Username : $($param.ForestAdminUserName)"
249 Write-Debug -Message "ForestAdmin Password : $($param.ForestAdminPassword)"
250 }
251 else
252 {
253 $param.Add('UserName', ('{0}\{1}' -f $machine.Name, $machine.InstallationUser.UserName))
254 $param.Add('Password', $machine.InstallationUser.Password)
255 }
256 $param.Add('ComputerName', $Machine.Name)
257 #endregion
258
259
260
261 #region - Determine DNS name for machine. This is used when installing Enterprise CAs
262 $caDNSName = $Machine.Name
263 if ($Machine.DomainName) { $caDNSName += ('.' + $Machine.DomainName) }
264
265 if ($Machine.DomainName)
266 {
267 $param.Add('DomainName', $Machine.DomainName)
268 }
269 else
270 {
271 $param.Add('DomainName', '')
272 }
273
274
275 if ($role.Name -eq 'CaSubordinate')
276 {
277 if (!($role.Properties.ContainsKey('ParentCA'))) { $param.Add('ParentCA', '<auto>') }
278 else { $param.Add('ParentCA', $role.Properties.ParentCA) }
279 if (!($role.Properties.ContainsKey('ParentCALogicalName'))) { $param.Add('ParentCALogicalName', '<auto>') }
280 else { $param.Add('ParentCALogicalName', $role.Properties.ParentCALogicalName) }
281 }
282
283 #...
284
285 if (!($role.Properties.ContainsKey('CPSURL'))) { $param.Add('CPSURL', 'http://' + $caDNSName + '/cps/cps.html') }
286 else { $param.Add('CPSURL', $role.Properties.CPSURL) }
287 if (!($role.Properties.ContainsKey('CPSText'))) { $param.Add('CPSText', 'Certification Practice Statement') }
288 else { $param.Add('CPSText', $($role.Properties.CPSText)) }
289
290 if (!($role.Properties.ContainsKey('InstallOCSP'))) { $param.Add('InstallOCSP', '<auto>') }
291 else { $param.Add('InstallOCSP', ($role.Properties.InstallOCSP -like '*Y*')) }
292 if (!($role.Properties.ContainsKey('OCSPHTTPURL01'))) { $param.Add('OCSPHTTPURL01', '<auto>') }
293 else { $param.Add('OCSPHTTPURL01', $role.Properties.OCSPHTTPURL01) }
294 if (!($role.Properties.ContainsKey('OCSPHTTPURL02'))) { $param.Add('OCSPHTTPURL02', '<auto>') }
295 else { $param.Add('OCSPHTTPURL02', $role.Properties.OCSPHTTPURL02) }
296
297 if (!($role.Properties.ContainsKey('DoNotLoadDefaultTemplates'))) { $param.Add('DoNotLoadDefaultTemplates', '<auto>') }
298 else { $param.Add('DoNotLoadDefaultTemplates', $role.Properties.DoNotLoadDefaultTemplates -like '*Y*') }
299
300
301 #region - Check if any unknown parameter name was passed
302 $knownParameters = @()
303 $knownParameters += 'ParentCA (only valid for Subordinate CA. Ignored for Root CAs)'
304 $knownParameters += 'ParentCALogicalName (only valid for Subordinate CAs. Ignored for Root CAs)'
305 $knownParameters += 'CACommonName'
306 $knownParameters += 'CAType'
307 #...
308 $knownParameters += 'DoNotLoadDefaultTemplates'
309 $knownParameters += 'PreDelaySeconds'
310 $unkownParFound = $false
311 foreach ($keySet in $role.Properties.GetEnumerator())
312 {
313 if ($keySet.Key -cnotin $knownParameters)
314 {
315 Write-Warning -Message "Parameter name '$($keySet.Key)' is unknown/ignored)"
316 $unkownParFound = $true
317 }
318 }
319 if ($unkownParFound)
320 {
321 Write-Warning -Message 'Valid parameter names are:'
322 Foreach ($name in ($knownParameters.GetEnumerator()))
323 {
324 Write-Warning -Message " $($name)"
325 }
326 Write-Warning -Message 'NOTE that all parameter names are CASE SENSITIVE!'
327 }
328 #endregion - Check if any unknown parameter names was passed
329
330 #endregion - Parameters
331
332
333 #region - Parameters debug
334 Write-Debug -Message '---------------------------------------------------------------------------------------'
335 Write-Debug -Message "Parameters for $($machine.name)"
336 Write-Debug -Message '---------------------------------------------------------------------------------------'
337 if ($machine.Roles.Properties.GetEnumerator().Count)
338 {
339 foreach ($r in $machine.Roles)
340 {
341 if (([AutomatedLab.Roles]$r.Name -band $roles) -ne 0) #if this is a CA role
342 {
343 foreach ($key in ($r.Properties.GetEnumerator() | Sort-Object -Property Key))
344 {
345 Write-Debug -Message " $($key.Key.PadRight(27)) $($key.Value)"
346 }
347 }
348 }
349 }
350 else
351 {
352 Write-Debug -message ' No parameters specified'
353 }
354 Write-Debug -Message '---------------------------------------------------------------------------------------'
355 #endregion - Parameters debug
356
357
358 #region ----- Input validation (raw values) -----
359 if ($role.Properties.ContainsKey('CACommonName') -and ($param.CACommonName.Length -gt 37))
360 {
361 Write-Error -Message "CACommonName cannot be longer than 37 characters. Specified value is: '$($param.CACommonName)'"; return
362 }
363
364 if ($role.Properties.ContainsKey('CACommonName') -and ($param.CACommonName.Length -lt 1))
365 {
366 Write-Error -Message "CACommonName cannot be blank. Specified value is: '$($param.CACommonName)'"; return
367 }
368
369 if ($role.Name -eq 'CaRoot')
370 {
371 if (-not ($param.CAType -in 'EnterpriseRootCA', 'StandAloneRootCA', '<auto>'))
372 {
373 Write-Error -Message "CAType needs to be 'EnterpriseRootCA' or 'StandAloneRootCA' when role is CaRoot. Specified value is: '$param.CAType'"; return
374 }
375 }
376
377 if ($role.Name -eq 'CaSubordinate')
378 {
379 if (-not ($param.CAType -in 'EnterpriseSubordinateCA', 'StandAloneSubordinateCA', '<auto>'))
380 {
381 Write-Error -Message "CAType needs to be 'EnterpriseSubordinateCA' or 'StandAloneSubordinateCA' when role is CaSubordinate. Specified value is: '$param.CAType'"; return
382 }
383 }
384
385
386 $availableCombinations = @()
387 $availableCombinations += @{CryptoProviderName='Microsoft Base SMart Card Crypto Provider'; HashAlgorithmName='sha1','md2','md4','md5'; KeyLength='1024','2048','4096'}
388 #...
389 $combination = $availableCombinations | Where-Object {$_.CryptoProviderName -eq $param.CryptoProviderName}
390
391 if (-not ($param.CryptoProviderName -in $combination.CryptoProviderName))
392 {
393 Write-Error -Message "CryptoProviderName '$($param.CryptoProviderName)' is unknown. `nList of valid options for CryptoProviderName:`n $($availableCombinations.CryptoProviderName -join "`n ")"; return
394 }
395 elseif (-not ($param.HashAlgorithmName -in $combination.HashAlgorithmName))
396 {
397 Write-Error -Message "HashAlgorithmName '$($param.HashAlgorithmName)' is not valid for CryptoProviderName '$($param.CryptoProviderName)'. The Crypto Provider selected supports the following Hash Algorithms:`n $($combination.HashAlgorithmName -join "`n ")"; return
398 }
399 elseif (-not ($param.KeyLength -in $combination.KeyLength))
400 {
401 Write-Error -Message "Keylength '$($param.KeyLength)' is not valid for CryptoProviderName '$($param.CryptoProviderName)'. The Crypto Provider selected supports the following keylengths:`n $($combination.KeyLength -join "`n ")"; return
402 }
403
404
405
406 if ($role.Properties.ContainsKey('DatabaseDirectory') -and -not ($param.DatabaseDirectory -match '^[C-Z]:\\'))
407 {
408 Write-Error -Message 'DatabaseDirectory needs to be located on a local drive (drive letter C-Z)'; return
409 }
410
411 #...
412
413 #if any validity parameter was defined, get these now and convert them all to hours (temporary variables)
414 if ($param.ValidityPeriodUnits -ne '<auto>')
415 {
416 switch ($param.ValidityPeriod)
417 {
418 'Years' { $validityPeriodUnitsHours = [int]$param.ValidityPeriodUnits * 365 * 24 }
419 'Months' { $validityPeriodUnitsHours = [int]$param.ValidityPeriodUnits * (365/12) * 24 }
420 'Weeks' { $validityPeriodUnitsHours = [int]$param.ValidityPeriodUnits * 7 * 24 }
421 'Days' { $validityPeriodUnitsHours = [int]$param.ValidityPeriodUnits * 24 }
422 'Hours' { $validityPeriodUnitsHours = [int]$param.ValidityPeriodUnits }
423 }
424 }
425 if ($param.CertsValidityPeriodUnits -ne '<auto>')
426 {
427 switch ($param.CertsValidityPeriod)
428 {
429 'Years' { $certsvalidityPeriodUnitsHours = [int]$param.CertsValidityPeriodUnits * 365 * 24 }
430 'Months' { $certsvalidityPeriodUnitsHours = [int]$param.CertsValidityPeriodUnits * (365/12) * 24 }
431 'Weeks' { $certsvalidityPeriodUnitsHours = [int]$param.CertsValidityPeriodUnits * 7 * 24 }
432 'Days' { $certsvalidityPeriodUnitsHours = [int]$param.CertsValidityPeriodUnits * 24 }
433 'Hours' { $certsvalidityPeriodUnitsHours = [int]$param.CertsValidityPeriodUnits }
434 }
435 }
436 if ($param.CRLPeriodUnits -ne '<auto>')
437 {
438 switch ($param.CRLPeriod)
439 {
440 'Years' { $cRLPeriodUnitsHours = [int]([int]$param.CRLPeriodUnits * 365 * 24) }
441 'Months' { $cRLPeriodUnitsHours = [int]([int]$param.CRLPeriodUnit * (365/12) * 24) }
442 'Weeks' { $cRLPeriodUnitsHours = [int]([int]$param.CRLPeriodUnits * 7 * 24) }
443 'Days' { $cRLPeriodUnitsHours = [int]([int]$param.CRLPeriodUnits * 24) }
444 'Hours' { $cRLPeriodUnitsHours = [int]([int]$param.CRLPeriodUnits) }
445 }
446 }
447 if ($param.CRLDeltaPeriodUnits -ne '<auto>')
448 {
449 switch ($param.CRLDeltaPeriod)
450 {
451 'Years' { $cRLDeltaPeriodUnitsHours = [int]([int]$param.CRLDeltaPeriodUnits * 365 * 24) }
452 'Months' { $cRLDeltaPeriodUnitsHours = [int]([int]$param.CRLDeltaPeriodUnits * (365/12) * 24) }
453 'Weeks' { $cRLDeltaPeriodUnitsHours = [int]([int]$param.CRLDeltaPeriodUnits * 7 * 24) }
454 'Days' { $cRLDeltaPeriodUnitsHours = [int]([int]$param.CRLDeltaPeriodUnits * 24) }
455 'Hours' { $cRLDeltaPeriodUnitsHours = [int]([int]$param.CRLDeltaPeriodUnits) }
456 }
457 }
458 if ($param.CRLOverlapUnits -ne '<auto>')
459 {
460 switch ($param.CRLOverlapPeriod)
461 {
462 'Years' { $CRLOverlapUnitsHours = [int]([int]$param.CRLOverlapUnits * 365 * 24) }
463 'Months' { $CRLOverlapUnitsHours = [int]([int]$param.CRLOverlapUnits * (365/12) * 24) }
464 'Weeks' { $CRLOverlapUnitsHours = [int]([int]$param.CRLOverlapUnits * 7 * 24) }
465 'Days' { $CRLOverlapUnitsHours = [int]([int]$param.CRLOverlapUnits * 24) }
466 'Hours' { $CRLOverlapUnitsHours = [int]([int]${param}.CRLOverlapUnits) }
467 }
468 }
469 #...
470 }
471 #endregion Install-LabCAMachine
472 #...
473 <#http://example.com.#>
474 Write-Debug `$`{`}
475 Write-Debug $false.$true.$param
476 $(Write-Debug $true)
477
478 :OuterLoop while ($true) {
479 while ($true) { break OuterLoop }
480 }
481