in MMC, certificates, select a certificate, right click on it:
but more importantly, I realized that I am using in powershell the option -runas which means it runs as local administrator.
it is a bit complicated. I have to run powershell using run as different user, and use my admin account - because that account has rights on the certificate server to request a certificate.
but on my local machine I have to run some commands as local administrator.... so the the certificate is imported locally using local administrator -when I call the certreq.exe in line 100,104 and 110 I use -runas
i.e. line 100: Start-Process "certreq" -ArgumentList "-new $inf $req" -Verb "RunAs"
And when I am exporting it is with my admin account.
So I created a separate script to update the certificate permissions to grant my admin account rights
Main script:
clear
$servers = get-content "C:\Certificates\servers.txt"
$outpath = "C:\Certificates"
$password = "Password"
$CAName = "certenroll.domain.com"
$TemplateName = "operationsManagerCert"
$E = "andre.prins@company.com"
$OU = "CES"
$O = "company"
$L = "city"
$S = "state"
$C = "country"
##############################################################################
function Remove-ReqTempfiles() {
param(
[String[]]$tempfiles
)
Write-Verbose "Cleanup temp files..."
Remove-Item -Path $tempfiles -Force -ErrorAction SilentlyContinue
}
Function TestReq
{
$Done=$False
Start-Sleep -Seconds 5
do
{
$proc = Get-Process -Name certreq -ErrorAction SilentlyContinue
if ($proc.count -ge 1)
{start-sleep -Seconds 1}
else
{$Done = $true}
} until ($Done)
}
############################### Create Certificates and load in \LocalMachine\My ########################################################
#get the CA details from AD
$rootDSE = [System.DirectoryServices.DirectoryEntry]'LDAP://RootDSE'
$searchBase = [System.DirectoryServices.DirectoryEntry]"LDAP://$($rootDSE.configurationNamingContext)"
$CAs = [System.DirectoryServices.DirectorySearcher]::new($searchBase,'objectClass=pKIEnrollmentService').FindAll()
if($CAs.Count -eq 4)
{$CAName = "$($CAs[2].Properties.dnshostname)\$($CAs[2].Properties.cn)"}
else
{$CAName = ""}
if (!$CAName -eq "")
{$CAName = " -config `"$CAName`""}
$server1 = @()
foreach ($CN in $servers)
{
if ($CN -eq "" -or $CN -eq $Null) {continue} #skip empty lines
if ($CN.IndexOf(".") -gt 1)
{
$FriendlyName = $CN.Substring(0,$CN.IndexOf("."))
$server1 += $CN.Substring(0,$CN.IndexOf("."))
}
else
{
$FriendlyName = $CN
$server1 += $CN
}
$file = @"
[NewRequest]
Subject = "E=$E,CN=$CN,C=$c, S=$s, L=$l, O=$o, OU=$OU"
MachineKeySet = TRUE
UseExistingKeySet = False
KeyLength = 2048
KeySpec=1
Exportable = TRUE
RequestType = PKCS10
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
FriendlyName = "$FriendlyName"
[RequestAttributes]
CertificateTemplate = "$TemplateName"
"@
try
{
$inf = [System.IO.Path]::GetTempFileName()
$req = [System.IO.Path]::GetTempFileName()
$cer = Join-Path -Path $env:TEMP -ChildPath "$CN.cer"
$rsp = Join-Path -Path $env:TEMP -ChildPath "$CN.rsp"
Remove-ReqTempfiles -tempfiles $inf, $req, $cer , $rsp
Set-Content -Path $inf -Value $file
Write-host "generate .req file with certreq.exe"
$error.Clear()
Start-Process "certreq" -ArgumentList "-new $inf $req" -Verb "RunAs"
TestReq
Write-host "certreq -submit $CAName `"$req`" `"$cer`""
Start-Process "certreq" -ArgumentList "-submit $CAName $req $cer" -Verb "RunAs"
TestReq
Write-host "request was successful. Result was saved to $cer"
write-host "retrieve and install the certificate"
Start-Process "certreq" -ArgumentList "-accept $cer -user" -Verb "RunAs"
TestReq
write-host "Done, cleaning up temp files"
Remove-ReqTempfiles -tempfiles $inf, $req, $cer, $rsp
}
catch
{
write-host "Error during request"
$error
}
}
#######################################################################################
# update security to allow $username to export the cert with private key - we have to use "RunAs" Administrator
# so the easiest is to create a seperate script.
# This script loads servers.txt and finds all the certificates in the store and updates security
Start-Process PowerShell.exe -Verb "RunAs" -ArgumentList "C:\Certificates\UpdateSecurity.ps1" -Wait
#######################################################################################
Write-Host "Start exporting the certificates"
$mypwd = ConvertTo-SecureString -String $password -Force -AsPlainText
$certs = Get-ChildItem -Path cert:\LocalMachine\my #currentuser LocalMachine
foreach ($cert in $certs)
{
if ($cert.Issuer -eq "CN=BHI WH Managed CA 1, DC=ent, DC=bhicorp, DC=com" -and $cert.FriendlyName -in $server1)
{
$name = $cert.DnsNameList.UniCode
$CertFileName = $cert.FriendlyName
$outname = $outpath + "\" + $CertFileName + ".pfx"
Export-PfxCertificate -Cert $cert -FilePath $outname -Password $mypwd -ChainOption EndEntityCertOnly -NoProperties -Verbose
write-host "Exported Certificate for " $cert.FriendlyName
}
}
#Remove the certificates from the store once exported.
Start-Process PowerShell.exe -Verb "RunAs" -ArgumentList "C:\Certificates\DeleteCerts.ps1" -Wait
write-host "Finished"
this is the script UpdateSecurity.ps1 - it also reads the same servers.txt, so it exactly knows which certificates to update....
$ErrorActionPreference="continue"
$userName = "domain\Myadminaccount"
$permission = "FullControl"
$certStoreLocation = "\localmachine\My"
$servers = get-content "C:\Certificates\servers.txt"
$server1 = @()
foreach ($S in $servers)
{
if ($S.IndexOf(".") -gt 1)
{$server1 += $S.Substring(0,$S.IndexOf("."))}
else
{$server1 += $S}
}
$mypwd = ConvertTo-SecureString -String "Baker123" -Force -AsPlainText
$certs = Get-ChildItem -Path cert:\LocalMachine\my #currentuser LocalMachine
foreach ($cert in $certs)
{
if ($cert.Issuer -eq "CN=CERT CA 1, DC=Domain, DC=com" -and $cert.FriendlyName -in $server1)
{
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","LocalMachine")
$store.Open("ReadWrite")
$rwCert = $store.Certificates | where {$_.Thumbprint -eq $cert.Thumbprint}
$rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$fileName = $rsaCert.key.UniqueName
$path = $env:ALLUSERSPROFILE + "\Microsoft\Crypto\RSA\MachineKeys\" + $fileName
$permissions = Get-Acl -Path $path
$rule = new-object security.accesscontrol.filesystemaccessrule $userName, $permission, allow
$permissions.AddAccessRule($rule)
Set-Acl -Path $path -AclObject $permissions
}
}
and to complete all the work, and delete the certificates at the end, DeleteCerts.ps1
$servers = get-content "C:\Certificates\servers.txt"
$certs = Get-ChildItem -Path cert:\LocalMachine\my
foreach ($CN in $servers)
{
if ($CN -eq "" -or $CN -eq $Null) {continue} #skip empty lines
if ($CN.IndexOf(".") -gt 1)
{
$server1 += $CN.Substring(0,$CN.IndexOf("."))
}
else
{
$server1 += $CN
}
}
foreach ($cert in $certs)
{
if ($cert.Issuer -eq "CN=Certs CA 1, DC=Domain, DC=com" -and $cert.FriendlyName -in $server1)
{
$path = "Cert:\LocalMachine\my\" + $cert.Thumbprint
Get-ChildItem $path | Remove-Item -Force
}
}
I run this now on a regular basis. all I do is populating servers.txt and run my script.
and to overcome the annoying UAC prompts when running the CertReq.exe as local administrator, I have 2 batchfiles to disable and enable the UAC:
disable:
REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 0 /f
Enable UAC:
REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 5 /f
and while writing this last bit, I realize, I can add that to the main script ;-)
so that's a last modification I will add myself, but you can figure that out yourself too :-)