#Requires -RunAsAdministrator <# .SYNOPSIS Signs certificates for multiple PCs using the Certificate Authority .DESCRIPTION Reads a list of hostnames and creates individual signed certificates for each PC. This is the proper way to deploy WinRM HTTPS to 175 shopfloor PCs. .PARAMETER HostnameFile Path to text file containing PC hostnames (one per line) .PARAMETER Hostnames Array of hostnames to process .PARAMETER Domain The domain suffix (default: logon.ds.ge.com) .PARAMETER CAThumbprint Thumbprint of the CA certificate used to sign certificates .PARAMETER CAPfxPath Path to the CA PFX file .PARAMETER CAPassword Password for the CA PFX file .PARAMETER OutputPath Directory to save signed certificates (default: ./pc-certificates) .PARAMETER ValidityYears How many years certificates should be valid (default: 2) .PARAMETER CertificatePassword Password for all exported PC certificates (use same password for simplicity) .EXAMPLE # Sign certificates for all PCs in file $caPass = ConvertTo-SecureString "CAPassword" -AsPlainText -Force $certPass = ConvertTo-SecureString "PCCertPass" -AsPlainText -Force .\Sign-BulkPCCertificates.ps1 -HostnameFile shopfloor-hostnames.txt ` -CAPfxPath "CA.pfx" -CAPassword $caPass -CertificatePassword $certPass .EXAMPLE # Sign certificates using CA from local store $certPass = ConvertTo-SecureString "PCCertPass" -AsPlainText -Force .\Sign-BulkPCCertificates.ps1 -HostnameFile shopfloor-hostnames.txt ` -CAThumbprint "ABC123..." -CertificatePassword $certPass .NOTES Author: System Administrator Date: 2025-10-17 #> param( [Parameter(Mandatory=$false)] [string]$HostnameFile, [Parameter(Mandatory=$false)] [string[]]$Hostnames, [Parameter(Mandatory=$false)] [string]$Domain = "logon.ds.ge.com", [Parameter(Mandatory=$false)] [string]$CAThumbprint, [Parameter(Mandatory=$false)] [string]$CAPfxPath, [Parameter(Mandatory=$false)] [SecureString]$CAPassword, [Parameter(Mandatory=$false)] [string]$OutputPath = "./pc-certificates", [Parameter(Mandatory=$false)] [int]$ValidityYears = 2, [Parameter(Mandatory=$false)] [SecureString]$CertificatePassword ) Write-Host "" Write-Host "╔══════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan Write-Host "║ Bulk PC Certificate Signing with CA ║" -ForegroundColor Cyan Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan Write-Host "" # Get hostnames $hostnameList = @() if ($HostnameFile) { if (-not (Test-Path $HostnameFile)) { Write-Host "✗ Hostname file not found: $HostnameFile" -ForegroundColor Red exit 1 } Write-Host "Reading hostnames from file: $HostnameFile" -ForegroundColor Yellow $hostnameList = Get-Content $HostnameFile | Where-Object {$_ -match '\S'} | ForEach-Object {$_.Trim()} Write-Host "✓ Found $($hostnameList.Count) hostnames" -ForegroundColor Green Write-Host "" } elseif ($Hostnames) { $hostnameList = $Hostnames Write-Host "Processing $($hostnameList.Count) hostnames from parameter" -ForegroundColor Yellow Write-Host "" } else { Write-Host "✗ Must specify either -HostnameFile or -Hostnames" -ForegroundColor Red exit 1 } if ($hostnameList.Count -eq 0) { Write-Host "✗ No hostnames to process" -ForegroundColor Red exit 1 } # Get CA certificate $caCert = $null if ($CAPfxPath) { Write-Host "Loading CA certificate from file..." -ForegroundColor Yellow Write-Host " File: $CAPfxPath" -ForegroundColor Gray if (-not (Test-Path $CAPfxPath)) { Write-Host "✗ CA PFX file not found: $CAPfxPath" -ForegroundColor Red exit 1 } if (-not $CAPassword) { $CAPassword = Read-Host "Enter CA certificate password" -AsSecureString } try { $caCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CAPfxPath, $CAPassword, 'Exportable') Write-Host "✓ CA certificate loaded" -ForegroundColor Green } catch { Write-Host "✗ Failed to load CA certificate: $($_.Exception.Message)" -ForegroundColor Red exit 1 } } elseif ($CAThumbprint) { Write-Host "Loading CA certificate from local store..." -ForegroundColor Yellow Write-Host " Thumbprint: $CAThumbprint" -ForegroundColor Gray try { $caCert = Get-ChildItem Cert:\LocalMachine\My\$CAThumbprint -ErrorAction Stop Write-Host "✓ CA certificate found" -ForegroundColor Green } catch { Write-Host "✗ CA certificate not found in local store" -ForegroundColor Red exit 1 } } else { Write-Host "✗ Must specify either -CAThumbprint or -CAPfxPath" -ForegroundColor Red exit 1 } if (-not $caCert.HasPrivateKey) { Write-Host "✗ CA certificate does not have private key" -ForegroundColor Red exit 1 } Write-Host "" Write-Host "CA Certificate:" -ForegroundColor Cyan Write-Host " Subject: $($caCert.Subject)" -ForegroundColor White Write-Host " Thumbprint: $($caCert.Thumbprint)" -ForegroundColor White Write-Host " Valid Until: $($caCert.NotAfter)" -ForegroundColor White Write-Host "" # Get certificate password if (-not $CertificatePassword) { Write-Host "Enter password for PC certificates (same password for all):" -ForegroundColor Yellow $CertificatePassword = Read-Host "Certificate Password" -AsSecureString } # Create output directory if (-not (Test-Path $OutputPath)) { New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null } $timestamp = Get-Date -Format "yyyyMMdd-HHmmss" $batchPath = Join-Path $OutputPath "batch-$timestamp" New-Item -ItemType Directory -Path $batchPath -Force | Out-Null Write-Host "Output Directory: $batchPath" -ForegroundColor Cyan Write-Host "" # Process each hostname $results = @() $successCount = 0 $failCount = 0 Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Gray Write-Host "Processing Certificates..." -ForegroundColor Yellow Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Gray Write-Host "" $counter = 0 foreach ($hostname in $hostnameList) { $counter++ $hostname = $hostname.Trim() -replace "\.$Domain$", "" $fqdn = "$hostname.$Domain".ToLower() Write-Host "[$counter/$($hostnameList.Count)] Processing: $hostname" -ForegroundColor Cyan try { # Create certificate $notAfter = (Get-Date).AddYears($ValidityYears) $certParams = @{ Subject = "CN=$fqdn" DnsName = @($fqdn, $hostname) KeyExportPolicy = 'Exportable' KeyUsage = 'DigitalSignature', 'KeyEncipherment' KeyLength = 2048 KeyAlgorithm = 'RSA' HashAlgorithm = 'SHA256' CertStoreLocation = 'Cert:\LocalMachine\My' NotAfter = $notAfter TextExtension = @('2.5.29.37={text}1.3.6.1.5.5.7.3.1') Signer = $caCert } $pcCert = New-SelfSignedCertificate @certParams # Export PFX $pfxPath = Join-Path $batchPath "$hostname-$Domain-$timestamp.pfx" Export-PfxCertificate -Cert $pcCert -FilePath $pfxPath -Password $CertificatePassword | Out-Null # Export CER $cerPath = Join-Path $batchPath "$hostname-$Domain-$timestamp.cer" Export-Certificate -Cert $pcCert -FilePath $cerPath | Out-Null # Remove from store Remove-Item "Cert:\LocalMachine\My\$($pcCert.Thumbprint)" -Force -ErrorAction SilentlyContinue Write-Host " ✓ Certificate created: $pfxPath" -ForegroundColor Green $results += [PSCustomObject]@{ Hostname = $hostname FQDN = $fqdn Thumbprint = $pcCert.Thumbprint ValidUntil = $pcCert.NotAfter PFXFile = Split-Path $pfxPath -Leaf Status = "Success" Error = $null } $successCount++ } catch { Write-Host " ✗ Failed: $($_.Exception.Message)" -ForegroundColor Red $results += [PSCustomObject]@{ Hostname = $hostname FQDN = $fqdn Thumbprint = $null ValidUntil = $null PFXFile = $null Status = "Failed" Error = $_.Exception.Message } $failCount++ } Write-Host "" } # Create summary report Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Gray Write-Host "Creating Summary Report..." -ForegroundColor Yellow Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Gray Write-Host "" $summaryPath = Join-Path $batchPath "CERTIFICATE-SUMMARY.txt" $csvPath = Join-Path $batchPath "certificate-list.csv" $summaryContent = @" ================================================================================ BULK CERTIFICATE SIGNING SUMMARY ================================================================================ Date: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Batch: $timestamp Statistics: ----------- Total Hostnames: $($hostnameList.Count) Successful: $successCount Failed: $failCount Success Rate: $([math]::Round($successCount / $hostnameList.Count * 100, 2))% CA Certificate: --------------- Subject: $($caCert.Subject) Thumbprint: $($caCert.Thumbprint) Valid Until: $($caCert.NotAfter) Certificate Settings: --------------------- Domain: $Domain Validity: $ValidityYears years Key Size: 2048-bit RSA Hash: SHA256 Password: (Same for all certificates) Output Directory: ----------------- $batchPath ================================================================================ CERTIFICATE LIST ================================================================================ $($results | Where-Object {$_.Status -eq "Success"} | ForEach-Object { "Hostname: $($_.Hostname) FQDN: $($_.FQDN) Thumbprint: $($_.Thumbprint) Valid Until: $($_.ValidUntil) PFX File: $($_.PFXFile) " + ("-" * 80) } | Out-String) $(if ($failCount -gt 0) { "================================================================================ FAILED CERTIFICATES ================================================================================ $($results | Where-Object {$_.Status -eq "Failed"} | ForEach-Object { "Hostname: $($_.Hostname) Error: $($_.Error) " + ("-" * 80) } | Out-String) "}) ================================================================================ DEPLOYMENT INSTRUCTIONS ================================================================================ 1. INSTALL CA CERTIFICATE ON MANAGEMENT COMPUTERS - Install the CA public certificate (*.cer from CA creation) - Import to Trusted Root Certification Authorities - This makes all signed certificates automatically trusted Command: Import-Certificate -FilePath "CA.cer" `` -CertStoreLocation Cert:\LocalMachine\Root 2. DISTRIBUTE PC CERTIFICATES - Each PC needs its own PFX file - Copy the appropriate certificate to each PC - Password is the same for all certificates 3. DEPLOY TO EACH PC Option A - Manual deployment: a. Copy PFX file to PC b. Import certificate: `$pass = ConvertTo-SecureString "PASSWORD" -AsPlainText -Force Import-PfxCertificate -FilePath "HOSTNAME.pfx" `` -CertStoreLocation Cert:\LocalMachine\My `` -Password `$pass c. Configure WinRM HTTPS with the certificate Option B - Automated deployment (recommended): - Update deployment package with individual certificates - Use deployment script for each PC - See: Deploy-IndividualCertificates.ps1 4. VERIFY DEPLOYMENT From management computer: Test-WSMan -ComputerName HOSTNAME.$Domain -UseSSL -Port 5986 # Should work without -SessionOption if CA is trusted ================================================================================ FILES GENERATED ================================================================================ Summary Reports: - CERTIFICATE-SUMMARY.txt (this file) - certificate-list.csv (spreadsheet format) Certificate Files: $successCount PFX files (one per PC with private key) $successCount CER files (public certificates) ================================================================================ SECURITY NOTES ================================================================================ 1. All PC certificates use the same password for simplicity 2. Store the certificate password securely (password manager) 3. CA private key is NOT distributed to PCs (only signing cert) 4. Each PC only receives its own certificate 5. If a certificate is compromised, only one PC is affected 6. Certificates expire after $ValidityYears years - plan renewal ================================================================================ NEXT STEPS ================================================================================ [ ] 1. Install CA certificate on all management computers [ ] 2. Test: Import one certificate to one PC manually [ ] 3. Configure WinRM HTTPS on test PC [ ] 4. Verify remote connection works [ ] 5. Deploy to remaining PCs in batches [ ] 6. Track deployment progress [ ] 7. Verify all deployments successful [ ] 8. Document certificate expiration dates [ ] 9. Set calendar reminder for renewal ================================================================================ "@ $summaryContent | Out-File -FilePath $summaryPath -Encoding UTF8 Write-Host "✓ Summary created: $summaryPath" -ForegroundColor Green # Export CSV $results | Export-Csv -Path $csvPath -NoTypeInformation Write-Host "✓ CSV created: $csvPath" -ForegroundColor Green Write-Host "" # Final summary Write-Host "╔══════════════════════════════════════════════════════════════╗" -ForegroundColor Green Write-Host "║ BULK CERTIFICATE SIGNING COMPLETE ║" -ForegroundColor Green Write-Host "╚══════════════════════════════════════════════════════════════╝" -ForegroundColor Green Write-Host "" Write-Host "Summary:" -ForegroundColor Cyan Write-Host " Total Processed: $($hostnameList.Count)" -ForegroundColor White Write-Host " Successful: $successCount" -ForegroundColor Green Write-Host " Failed: $failCount" -ForegroundColor $(if($failCount -gt 0){'Red'}else{'Green'}) Write-Host "" Write-Host "Output Directory:" -ForegroundColor Cyan Write-Host " $batchPath" -ForegroundColor White Write-Host "" Write-Host "Files Created:" -ForegroundColor Cyan Write-Host " - $successCount PC certificates (PFX)" -ForegroundColor White Write-Host " - $successCount public certificates (CER)" -ForegroundColor White Write-Host " - Summary report (TXT)" -ForegroundColor White Write-Host " - Certificate list (CSV)" -ForegroundColor White Write-Host "" if ($successCount -gt 0) { Write-Host "Next Steps:" -ForegroundColor Yellow Write-Host " 1. Install CA certificate on management computers" -ForegroundColor White Write-Host " 2. Deploy individual certificates to each PC" -ForegroundColor White Write-Host " 3. Configure WinRM HTTPS on each PC" -ForegroundColor White Write-Host " 4. Verify connections from management computer" -ForegroundColor White Write-Host "" } if ($failCount -gt 0) { Write-Host "⚠ WARNING: $failCount certificate(s) failed" -ForegroundColor Yellow Write-Host " Review the summary report for details" -ForegroundColor Gray Write-Host "" }