[CmdletBinding()] param( # Set to $null to disable issuer filtering [string]$IssuerMustContain = "EA-Int-CA-01" ) $ErrorActionPreference = 'Stop' function Get-SubjectCN { param([Parameter(Mandatory)][System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert) try { $cn = $Cert.GetNameInfo([System.Security.Cryptography.X509Certificates.X509NameType]::SimpleName, $false) if (-not [string]::IsNullOrWhiteSpace($cn)) { return $cn } } catch { } if ($Cert.Subject -match 'CN=([^,]+)') { return $Matches[1].Trim() } return $null } function Decode-BmpStringFromAsn1Octets { param([Parameter(Mandatory)][byte[]]$Data) if (-not $Data -or $Data.Length -lt 4) { return $null } $zeroCount = ($Data | Where-Object { $_ -eq 0x00 }).Count if ($zeroCount -lt 2) { return $null } try { $s = [System.Text.Encoding]::BigEndianUnicode.GetString($Data) $s = ($s -replace "`0","").Trim() if ($s -match '^[A-Za-z0-9._-]+$') { return $s } } catch { } try { $s = [System.Text.Encoding]::Unicode.GetString($Data) $s = ($s -replace "`0","").Trim() if ($s -match '^[A-Za-z0-9._-]+$') { return $s } } catch { } return $null } function Get-CertTemplateName { param([Parameter(Mandatory)][System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert) $oidTemplateName = '1.3.6.1.4.1.311.20.2' $oidTemplateInfo = '1.3.6.1.4.1.311.21.7' # Prefer Template Name extension try { $ext = $Cert.Extensions | Where-Object { $_.Oid.Value -eq $oidTemplateName } | Select-Object -First 1 if ($ext) { $name = Decode-BmpStringFromAsn1Octets -Data $ext.RawData if ($name) { return $name } # Best-effort fallback try { $raw = $ext.Format($true) if ($raw) { $lines = $raw -split "(\r?\n)+" $solo = ($lines | ForEach-Object { $_.Trim() } | Where-Object { $_ -match '^[A-Za-z0-9._-]+$' } | Select-Object -First 1) if ($solo) { return $solo } } } catch { } } } catch { } # Best-effort Template Info parse try { $ext2 = $Cert.Extensions | Where-Object { $_.Oid.Value -eq $oidTemplateInfo } | Select-Object -First 1 if ($ext2) { try { $raw2 = $ext2.Format($true) if ($raw2) { $lines2 = $raw2 -split "(\r?\n)+" foreach ($ln in $lines2) { $t = $ln.Trim() if ($t -match 'Template\s*=\s*([A-Za-z0-9._-]+)') { return $Matches[1] } if ($t -match 'Certificate Template Name\s*:\s*([A-Za-z0-9._-]+)') { return $Matches[1] } if ($t -match '^[A-Za-z0-9._-]+$') { return $t } } } } catch { } } } catch { } return $null } function Invoke-EACertCleanup { [CmdletBinding()] param([string]$IssuerMustContain = "EA-Int-CA-01") # KEEP pattern example: EA2002L-LAB or EA2002L-LAB.eanet.local $KeepCNRegex = '^EA\d{4}L-LAB(\..+)?$' $storePath = "Cert:\LocalMachine\My" $certs = Get-ChildItem -Path $storePath -ErrorAction Stop $matches = foreach ($c in $certs) { if ($IssuerMustContain -and ($c.Issuer -notlike "*$IssuerMustContain*")) { continue } $template = Get-CertTemplateName -Cert $c $cn = Get-SubjectCN -Cert $c $remove = $false $reason = $null if ($template -eq 'EA-Workstation-Auth') { $remove = $true $reason = "Template=EA-Workstation-Auth" } elseif ($template -eq 'EA-PKCS') { # keep existing rule if ($cn -like 'laptop*') { $remove = $true $reason = "Template=EA-PKCS and CN starts with laptop" } # NEW rule: # Remove if CN starts with EA, but KEEP if it matches EA####L-LAB(.domain optional) elseif ($cn -like 'EA*' -and ($cn -notmatch $KeepCNRegex)) { $remove = $true $reason = "Template=EA-PKCS and CN starts with EA (not matching keep pattern EA####L-LAB)" } } if ($remove) { [pscustomobject]@{ Thumbprint = (($c.Thumbprint -replace '\s','').ToUpperInvariant()) SubjectCN = $cn Issuer = $c.Issuer NotAfter = $c.NotAfter Template = $template Reason = $reason } } } if (-not $matches) { return @() } $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My", "LocalMachine") try { $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) foreach ($m in $matches) { $certToRemove = $store.Certificates | Where-Object { $_.Thumbprint -eq $m.Thumbprint } | Select-Object -First 1 if ($certToRemove) { $store.Remove($certToRemove) } } } finally { $store.Close() } return $matches } # If run directly, execute cleanup. If dot-sourced, only define functions. if ($MyInvocation.InvocationName -ne '.') { Invoke-EACertCleanup -IssuerMustContain $IssuerMustContain }