|
1 | | -# Update notes: |
| 1 | +# Version 1.3.0 |
| 2 | + |
| 3 | +# Summary |
| 4 | +# Contains PowerShell functions to execute administration jobs for general Windows certificates, IIS and SQL Server. |
| 5 | +# There are additional supporting PowerShell functions to support job specific actions. |
| 6 | + |
| 7 | +# Update notes: |
2 | 8 | # 08/12/25 Updated functions to manage IIS bindings and certificates |
3 | 9 | # Updated script to read CSPs correctly using newer CNG Keys |
4 | 10 | # Fix an error with complex PFX passwords having irregular characters |
5 | 11 | # 08/29/25 Fixed the add cert to store function to return the correct thumbprint |
6 | 12 | # Made changes to the IIS Binding logic, breaking it into manageable pieces to aid in debugging issues |
| 13 | +# 09/16/25 Updated the Get CSP function to handle null values when reading hybrid certificates |
7 | 14 |
|
8 | 15 | # Set preferences globally at the script level |
9 | 16 | $DebugPreference = "Continue" |
@@ -374,160 +381,6 @@ function Remove-KFCertificateFromStore { |
374 | 381 | return $isSuccessful |
375 | 382 | } |
376 | 383 |
|
377 | | -function New-KFIISSiteBindingOLD { |
378 | | - [CmdletBinding()] |
379 | | - [OutputType([pscustomobject])] |
380 | | - param ( |
381 | | - [Parameter(Mandatory = $true)] |
382 | | - [string]$SiteName, |
383 | | - [string]$IPAddress = "*", |
384 | | - [int]$Port = 443, |
385 | | - [string]$Hostname = "", |
386 | | - [ValidateSet("http", "https")] |
387 | | - [string]$Protocol = "https", |
388 | | - [string]$Thumbprint, |
389 | | - [string]$StoreName = "My", |
390 | | - [int]$SslFlags = 0 |
391 | | - ) |
392 | | - |
393 | | - Write-Information "Entering PowerShell Script: New-KFIISSiteBinding" -InformationAction SilentlyContinue |
394 | | - Write-Verbose "Entered New-KFIISSiteBinding with values SiteName: '$SiteName', IPAddress: '$IPAddress', Port: $Port, HostName: '$Hostname', Protocol: '$Protocol', Thumbprint: '$Thumbprint', Store Path: '$StoreName', SslFlags: '$SslFlags'" |
395 | | - |
396 | | - $result = $null |
397 | | - |
398 | | - # Check for existing binding conflict |
399 | | - $conflicts = @(CheckExistingBindings -DesiredIP $IPAddress -DesiredPort $Port -DesiredHost $Hostname -TargetSiteName $SiteName) |
400 | | - |
401 | | - if ($conflicts.Count -gt 0) { |
402 | | - $conflictMessage = "Binding conflict detected with the following existing bindings:`n" + ($conflicts | ForEach-Object { " - Site: $($_.SiteName), IP: $($_.BindingIP), Port: $($_.BindingPort), Host: $($_.BindingHost)" }) -join "`n" |
403 | | - |
404 | | - Write-Warning $conflictMessage -InformationAction SilentlyContinue |
405 | | - |
406 | | - $result = New-ResultObject -Status Skipped -Code 100 -Step CheckBinding -Message $msg -ErrorMessage $conflictMessage |
407 | | - |
408 | | - return $result |
409 | | - } |
410 | | - Write-Verbose "No binding conflicts found for SiteName: '$SiteName', IPAddress: '$IPAddress', Port: $Port, HostName: '$Hostname'" |
411 | | - |
412 | | - $searchBindings = "${IPAddress}:${Port}:${Hostname}" |
413 | | - $hasIISDrive = Ensure-IISDrive |
414 | | - Write-Verbose "IIS Drive is available: $hasIISDrive" |
415 | | - |
416 | | - if ($hasIISDrive) { |
417 | | - |
418 | | - Write-Verbose "IIS Drive is available, using WebAdministration module." |
419 | | - |
420 | | - $null = Import-Module WebAdministration |
421 | | - $sitePath = "IIS:\Sites\$SiteName" |
422 | | - if (-not (Test-Path $sitePath)) { |
423 | | - $msg = "Site '$SiteName' not found in IIS drive." |
424 | | - Write-Error $msg -InformationAction SilentlyContinue |
425 | | - $result = New-ResultObject -Status Error -Code 201 -Step FindWebSite -ErrorMessage $msg -Details @{ SiteName = $SiteName; IPAddress = $IPAddress; Port = $Port; HostName = $Hostname } |
426 | | - } else { |
427 | | - $site = Get-Item $sitePath |
428 | | - $httpsBindings = $site.Bindings.Collection | Where-Object { |
429 | | - $_.bindingInformation -eq $searchBindings -and $_.protocol -eq "https" |
430 | | - } |
431 | | - |
432 | | - foreach ($binding in $httpsBindings) { |
433 | | - try { |
434 | | - $bindingInfo = $binding.GetAttributeValue("bindingInformation") |
435 | | - $protocol = $binding.protocol |
436 | | - |
437 | | - Write-Verbose "Calling Remove-WebBinding -Name $SiteName -BindingInformation $bindingInfo -Protocol $protocol -Confirm:$false" |
438 | | - Remove-WebBinding -Name $SiteName -BindingInformation $bindingInfo -Protocol $protocol -Confirm:$false |
439 | | - Write-Verbose "Completed removing the Web Binding" |
440 | | - |
441 | | - } catch { |
442 | | - $msg = "Error removing binding '$($binding.bindingInformation)': $_" |
443 | | - Write-Warning $msg -InformationAction SilentlyContinue |
444 | | - $result = New-ResultObject -Status Error -Code 201 -Step RemoveBinding -ErrorMessage $msg |
445 | | - return $result |
446 | | - } |
447 | | - } |
448 | | - |
449 | | - # Site2 then has Test1 cert assigned to it?? |
450 | | - try { |
451 | | - Write-Verbose "Calling New-WebBinding -Name $SiteName -Protocol $Protocol -IPAddress $IPAddress -Port $Port -HostHeader '$Hostname' -SslFlags $SslFlags" |
452 | | - New-WebBinding -Name $SiteName -Protocol $Protocol -IPAddress $IPAddress -Port $Port -HostHeader $Hostname -SslFlags $SslFlags |
453 | | - } catch { |
454 | | - $msg = "Error adding binding: $_" |
455 | | - Write-Warning $msg -InformationAction SilentlyContinue |
456 | | - $result = New-ResultObject -Status Error -Code 202 -Step AddBinding -ErrorMessage $msg |
457 | | - return $result |
458 | | - } |
459 | | - |
460 | | - Write-Verbose "Calling Get-WebBinding -Name $SiteName -Protocol $Protocol, Where BindingInformation equals '$searchBindings'" |
461 | | - $binding = Get-WebBinding -Name $SiteName -Protocol $Protocol | Where-Object { |
462 | | - $_.bindingInformation -eq $searchBindings |
463 | | - } |
464 | | - |
465 | | - try |
466 | | - { |
467 | | - if ($binding) { |
468 | | - Write-Verbose "Binding thumbprint $thumbprint to $binding.bindingInformation in store: $StoreName" |
469 | | - $null = $binding.AddSslCertificate($Thumbprint, $StoreName) |
470 | | - $result = New-ResultObject -Status Success -Code 0 -Step BindSSL |
471 | | - } else { |
472 | | - $result = New-ResultObject -Status Error -Code 202 -Step BindSSL -Message "No binding found for: $searchBindings" |
473 | | - } |
474 | | - } |
475 | | - catch |
476 | | - { |
477 | | - $result = New-ResultObject -Status Error -Code 202 -Step BindSSL -Message $_ |
478 | | - } |
479 | | - } |
480 | | - } else { |
481 | | - # SERVERMANAGER FALLBACK |
482 | | - Write-Verbose "IIS Drive is not available, using ServerManager fallback." |
483 | | - |
484 | | - Add-Type -Path "$env:windir\System32\inetsrv\Microsoft.Web.Administration.dll" |
485 | | - $iis = New-Object Microsoft.Web.Administration.ServerManager |
486 | | - $site = $iis.Sites[$SiteName] |
487 | | - |
488 | | - if ($null -eq $site) { |
489 | | - $msg = "Site '$SiteName' not found in ServerManager." |
490 | | - Write-Error $msg -InformationAction SilentlyContinue |
491 | | - $result = New-ResultObject -Status Error -Code 201 -Step FindWebSite -Message $msg -Details @{ SiteName = $SiteName; IPAddress = $IPAddress; Port = $Port; HostName = $Hostname } |
492 | | - } else { |
493 | | - $httpsBindings = $site.Bindings | Where-Object { |
494 | | - $_.bindingInformation -eq $searchBindings -and $_.protocol -eq "https" |
495 | | - } |
496 | | - |
497 | | - foreach ($binding in $httpsBindings) { |
498 | | - try { |
499 | | - $site.Bindings.Remove($binding) |
500 | | - } catch { |
501 | | - $msg = "Error removing binding: $_" |
502 | | - Write-Warning $msg -InformationAction SilentlyContinue |
503 | | - $result = New-ResultObject -Status Error -Code 201 -Step RemoveBinding -ErrorMessage $msg |
504 | | - return $result |
505 | | - } |
506 | | - } |
507 | | - |
508 | | - $cleanThumbprint = $Thumbprint -replace '[^a-fA-F0-9]', '' |
509 | | - $hashBytes = -split $cleanThumbprint -replace '..', '$& ' -split ' ' | Where-Object { $_ -ne '' } | ForEach-Object { [Convert]::ToByte($_, 16) } |
510 | | - |
511 | | - try { |
512 | | - $newBinding = $site.Bindings.Add($searchBindings, $Protocol) |
513 | | - if ($Protocol -eq "https") { |
514 | | - $newBinding.CertificateStoreName = $StoreName |
515 | | - $newBinding.CertificateHash = [byte[]]$hashBytes |
516 | | - $newBinding.SetAttributeValue("sslFlags", $SslFlags) |
517 | | - } |
518 | | - $iis.CommitChanges() |
519 | | - $result = New-ResultObject -Status Success -Code 0 -Step BindSSL -Message "Binding and certificate successfully applied via ServerManager." |
520 | | - } catch { |
521 | | - $msg = "Error adding binding: $_" |
522 | | - Write-Warning $msg -InformationAction SilentlyContinue |
523 | | - $result = New-ResultObject -Status Error -Code 202 -Step BindSSL -ErrorMessage $msg |
524 | | - } |
525 | | - } |
526 | | - } |
527 | | - |
528 | | - return $result |
529 | | -} |
530 | | - |
531 | 384 | # IIS Functions |
532 | 385 | function New-KFIISSiteBinding { |
533 | 386 | [CmdletBinding()] |
@@ -828,7 +681,7 @@ function Remove-KFIISSiteBinding { |
828 | 681 | } |
829 | 682 | } |
830 | 683 |
|
831 | | -# Called on a renewal to remove any certificats if not bound or used |
| 684 | +# Called on a renewal to remove any certificates if not bound or used |
832 | 685 | function Remove-KFIISCertificateIfUnused { |
833 | 686 | param ( |
834 | 687 | [Parameter(Mandatory = $true)] |
@@ -1381,6 +1234,52 @@ function Test-CryptoServiceProvider { |
1381 | 1234 |
|
1382 | 1235 | # Function that takes an x509 certificate object and returns the csp |
1383 | 1236 | function Get-CertificateCSP { |
| 1237 | + param( |
| 1238 | + [System.Security.Cryptography.X509Certificates.X509Certificate2]$cert |
| 1239 | + ) |
| 1240 | + |
| 1241 | + try { |
| 1242 | + # Check if certificate has a private key |
| 1243 | + if (-not $cert.HasPrivateKey) { |
| 1244 | + return "No private key" |
| 1245 | + } |
| 1246 | + |
| 1247 | + # Get the private key |
| 1248 | + $privateKey = $cert.PrivateKey |
| 1249 | + |
| 1250 | + if ($privateKey -and $privateKey.CspKeyContainerInfo) { |
| 1251 | + # For older .NET Framework |
| 1252 | + $cspKeyContainerInfo = $privateKey.CspKeyContainerInfo |
| 1253 | + |
| 1254 | + if ($cspKeyContainerInfo -and $cspKeyContainerInfo.ProviderName) { |
| 1255 | + return [string]$cspKeyContainerInfo.ProviderName |
| 1256 | + } |
| 1257 | + } |
| 1258 | + |
| 1259 | + # For newer .NET Core/5+ or CNG keys |
| 1260 | + try { |
| 1261 | + $key = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert) |
| 1262 | + if ($key -and $key.GetType().Name -eq "RSACng") { |
| 1263 | + $cngKey = $key.Key |
| 1264 | + if ($cngKey -and $cngKey.Provider -and $cngKey.Provider.Provider) { |
| 1265 | + return [string]$cngKey.Provider.Provider |
| 1266 | + } |
| 1267 | + } |
| 1268 | + } |
| 1269 | + catch { |
| 1270 | + Write-Verbose "CNG key detection failed: $($_.Exception.Message)" |
| 1271 | + } |
| 1272 | + |
| 1273 | + # Ensure we always return a string |
| 1274 | + return "Unknown provider" |
| 1275 | + |
| 1276 | + } |
| 1277 | + catch { |
| 1278 | + return "Error retrieving CSP: $($_.Exception.Message)" |
| 1279 | + } |
| 1280 | +} |
| 1281 | + |
| 1282 | +function Get-CertificateCSPV2 { |
1384 | 1283 | param ( |
1385 | 1284 | [Parameter(Mandatory = $true)] |
1386 | 1285 | [System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert |
|
0 commit comments