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