The PSPKI -module is really god for sending and approving certification requests via Powershell when Microsofts own tools leaves room for improvement.
This is really only for a testenviroment and offline-enviroments.
Folder structure needs to created to look below
Certini contains the template for the certificate like below for example
[Version]
Signature = "$Windows NT$"
[NewRequest]
Subject = "C=US,S=CA,L=OHIO,O=Fabrikam,OU=IT,CN=ajax.aspnetcdn.com"
Exportable = TRUE
KeyLength = 4096
KeySpec = 1
KeyUsage = 0xa0
MachineKeySet = True
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12
Silent = True
SMIME = False
RequestType = PKCS10
FriendlyName = "ajax.aspnetcdn.com"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$scriptPath = $(split-path -Parent $myinvocation.MyCommand.Definition) | |
Import-Module "$scriptPath\certAutoTools.psm1" -Force | |
import-module "$($scriptPath)\PSPKI-v3.4.1.0\PSPKI.psm1" | |
#settings | |
$logFile = "$($scriptPath)\lastRun.txt" | |
$destinationPath = "$($scriptPath)\generatedCertReq\" | |
$sourcePath = "$($scriptPath)\certIni" #where Ini-files are stored | |
$certPath = "$($scriptPath)\generatedCerts" #location for .cer output | |
$iisSites = "IISSite1","IISSite2" | |
$rootCertAuth = "myRootCertAuthority" | |
#settings end | |
Start-Transcript -Path $logFile | |
#check if certrenewal is needed | |
$certificate = get-certOnIIS -IisSites $iisSites |sort-object notafter |select -first 1 | |
$certExpires = get-certExpires -certificate $certificate -days 238 | |
if ($certExpires) { | |
write-output "Found expiring certificates, moving on with renewal" | |
#create certs on frontends | |
new-CertRequest -destinationPath $destinationPath -sourcePath $sourcePath | |
#renew certs on certsrv | |
submit-certRequest -sourcePath $destinationPath -certAuthName $rootCertAuth -outputPath $certPath | |
#import certs on frontends | |
$createdCerts = import-certs -sourcePath $certPath | |
#import certs to array | |
$newCerts = get-certsFromFile -certPath $certPath | |
#get current cert on sites | |
$iisMappings = get-iisBindingCerts -iisSites $iisSites | |
#set cert on iis-sites | |
set-sslBindings -iismappings $iisMappings -newCerts $newcerts | |
#clean up | |
Get-ChildItem -Path $certPath |copy-Item -Destination "$($certPath)\old\" -Force | |
Get-ChildItem -path $certpath |where-object {$_.psiscontainer -eq $false} |Remove-Item | |
Get-ChildItem -Path $destinationPath -Filter *.txt |Remove-Item | |
#remove previous certs | |
foreach ($mapping in $iismappings) { | |
Get-ChildItem -Path Cert:\LocalMachine\my |where-object {$_.Thumbprint -like $mapping.certhash} |remove-item | |
} | |
}#end check certexpires | |
else { | |
write-output "No certificate needed renewals" | |
} | |
Stop-Transcript |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function new-CertRequest { | |
param( | |
$destinationPath, | |
$sourcePath | |
) | |
$outputFolder = $destinationPath | |
$items = Get-ChildItem -Path $sourcepath -Filter *.ini | |
$date= get-date -Format "yyyyMMdd_HHmm" | |
foreach ($item in $items) { | |
write-output "Creating certificate request for $($item.name)" | |
certreq.exe -new $item.VersionInfo.FileName "$outputFolder$($item.name.trimend('.ini'))_$($env:COMPUTERNAME)_$date.txt" | |
$filesToCopy += "$outputFolder$($item.name.trimend('.ini'))_$($env:COMPUTERNAME)_$date.txt" | |
write-output "$outputFolder$($item.name.trimend('.ini'))_$($env:COMPUTERNAME)_$date.txt" | |
} | |
} | |
function submit-certRequest { | |
param( | |
$sourcePath, | |
$certAuthName, | |
$outputPath, | |
$latest = 4 | |
) | |
$certReqs = Get-ChildItem -Path $sourcePath -Filter "*.txt" |Sort-Object lastwritetime |select -First $latest | |
$ca = Get-CertificationAuthority -Name $certAuthName | |
$ids = @() | |
foreach ($certReq in $certReqs) { | |
$request = Submit-CertificateRequest -Path $certReq.FullName -CertificationAuthority $ca | |
$ids += $request.RequestID | |
} | |
Get-PendingRequest -CertificationAuthority $ca |select -first 1 | |
foreach ($id in $ids) { | |
Get-PendingRequest -CertificationAuthority $ca -RequestID $id |Approve-CertificateRequest | |
$mycert = Get-IssuedRequest -CertificationAuthority $ca -RequestID $id |Receive-Certificate -Path $outputPath -Force | |
$File = Get-ChildItem -Path "$($outputPath)\RequestID_$id.cer" | |
$fileName = "cert_$($mycert.Subject.split(".")[-2]).cer" | |
Rename-Item -Path $file.FullName -NewName $fileName | |
} | |
} | |
function import-certs { | |
param( | |
$sourcePath | |
) | |
$items = Get-ChildItem -Path $sourcePath -Filter *.cer | |
if ($items.count -ne $null -and $items.count -gt 0) { | |
foreach ($item in $items) { | |
write-output "importing $($item.versioninfo.filename)..." | |
certreq.exe -accept -machine $item.VersionInfo.FileName | |
} | |
} | |
else { write-output "No files found in $sourcepath"} | |
} | |
function get-certOnIIS { | |
param( | |
$IisSites | |
) | |
$certCollection = @() | |
foreach ($IisSite in $IisSites) { | |
$bindings = Get-WebBinding -Name $IisSite |where-object {$_.protocol -like "https"} | |
foreach ($binding in $bindings) { | |
write-output "Adding $($binding.certificatehash) on $IisSite" | |
$certCollection += (Get-ChildItem -Path cert:\ -Recurse |where-object {$_.Thumbprint -like $binding.certificateHash}) | |
} | |
} | |
return $certCollection | |
} | |
function get-certExpires { | |
param( | |
[System.Security.Cryptography.X509Certificates.X509Certificate]$certificate, | |
[int]$days | |
) | |
$timspan = New-TimeSpan -Start (get-date) -End $certificate.NotAfter | |
if ($timspan.Days -le $days) { | |
return $false | |
} | |
else { | |
return $true | |
} | |
} | |
function get-iisBindingCerts { | |
param( | |
$iisSites | |
) | |
$mappings = @() | |
foreach ($websiteName in $iisSites) { | |
$allbindings = Get-WebBinding -Name $websiteName -Protocol https | |
foreach ($allbinding in $allbindings) { | |
$currentCert = Get-ChildItem -Path Cert:\LocalMachine\my |where-object {$_.Thumbprint -eq $allbinding.certificateHash} | |
$mappings += new-object psobject -Property @{iis=$websiteName;bindingInformation=$allbinding.bindingInformation;certhash=$allbinding.certificateHash;friendlyName=$currentCert.FriendlyName;dnsName=$currentCert.DnsNameList.unicode;subject=$currentCert.Subject} | |
} | |
} | |
return $mappings | |
} | |
function get-certsFromFile { | |
param( | |
$certPath | |
) | |
$items = Get-ChildItem -Path $certpath -Filter *.cer | |
$collection = @() | |
foreach ($item in $items) { | |
if ($item -ne $null) { | |
$crt = new-object System.Security.Cryptography.X509Certificates.X509Certificate | |
$crt.Import($item.VersionInfo.FileName) | |
$collection += new-object psobject -Property @{thumbprint=$crt.GetCertHashString();subject=$crt.Subject} | |
} | |
} | |
return $collection | |
} | |
function set-sslBindings { | |
param( | |
$iisMappings, | |
$newCerts | |
) | |
write-output "has $($iisMappings.count) iismappings and $($newcerts.count) newcerts" | |
foreach ($iisMapping in $iisMappings) { | |
$httpsBinding = get-webBinding -name $iisMapping.iis -protocol https |where-object {$_.bindingInformation -like $iisMapping.bindingInformation} | |
if ($httpsBinding -ne $null) { | |
$bindCert = $newCerts |where-object {$_.subject -like $iisMapping.subject} |select -first 1 | |
if ($bindCert -ne $null) { | |
write-output "adding $($bindCert.thumbprint) to $($iismapping.bindingInformation)" | |
$httpsBinding.AddSslCertificate($bindCert.Thumbprint,'my') | |
write-output "binding $($httpsBinding.bindingInformation) to $($bindCert.subject)" | |
} | |
else { | |
write-output "Couldn't bind $($httpsBinding.bindingInformation) to $($bindCert.subject)" | |
} | |
} | |
else {write-output "webbinding empty $($iismapping.iis)" $($httpsBinding.getttype())} | |
} | |
} |
References:
https://www.sysadmins.lv/projects/pspki/default.aspx - PSPKI Module