bin/Public/Install-sqmCertificate.ps1
|
<#
.SYNOPSIS Installiert ein Zertifikat (selbstsigniert oder CA-signiert) in SQL Server und bindet es automatisch an den konfigurierten Verwendungszweck. .DESCRIPTION Unterstuetzt drei Eingabeformate: PFX (.pfx) - Zertifikat + Private Key in einer Datei (CA-signiert oder exportiert) CER+PVK - Zertifikat (.cer) + verschluesselter Private Key (.pvk) getrennt CER only - Nur Zertifikat ohne Private Key (z.B. Public Key fuer AlwaysOn-Replikate) Ablauf: 1. Zertifikatsdatei lesen und Inhalt validieren (Ablaufdatum, Subject, Format) 2. Pruefen ob bereits ein Zertifikat mit gleichem Thumbprint in SQL Server existiert 3. Zertifikat per CREATE CERTIFICATE in SQL Server importieren 4. Je nach -Purpose automatisch einbinden: AlwaysOn ? ALTER ENDPOINT ... AUTHENTICATION = CERTIFICATE <neu> Hinweis fuer Replikat-Verteilung ausgeben TDE ? ALTER DATABASE ... SET ENCRYPTION KEY ... CERTIFICATE <neu> SSL ? Zertifikat in Windows-Maschinen-Store importieren + SQL Server Netzwerkprotokoll-Zertifikat setzen (Registry) ServiceBroker ? ALTER ENDPOINT ... AUTHENTICATION = CERTIFICATE <neu> 5. Installationsprotokoll als TXT schreiben .PARAMETER SqlInstance SQL Server-Instanz (Standard: aktueller Computername). .PARAMETER SqlCredential PSCredential fuer die SQL Server-Verbindung. .PARAMETER CertFile Pfad zur Zertifikatsdatei (.pfx, .cer, .crt, .p12). Bei PFX wird der Private Key automatisch mitimportiert. .PARAMETER PrivateKeyFile Pfad zur separaten Private Key-Datei (.pvk). Nur bei CER+PVK-Format erforderlich. .PARAMETER CertPassword Passwort fuer PFX-Datei oder .pvk-Datei (als SecureString). .PARAMETER CertificateName Name unter dem das Zertifikat in SQL Server angelegt wird. Standard: Dateiname ohne Extension. .PARAMETER Database Zieldatenbank in SQL Server. Standard: master. .PARAMETER Purpose Verwendungszweck bestimmt die automatische Einbindung nach dem Import. Gueltige Werte: AlwaysOn, TDE, SSL, ServiceBroker, UserDefined. Standard: UserDefined (kein automatisches Binden). .PARAMETER EndpointName Name des Endpoints fuer AlwaysOn/ServiceBroker-Bindung. Wenn nicht angegeben, wird der erste passende Endpoint automatisch ermittelt. .PARAMETER TdeDatabaseName Name der Datenbank fuer TDE-Bindung. Wenn nicht angegeben, wird die aktuelle TDE-verschluesselte Datenbank auf der Instanz ermittelt (nur wenn eindeutig). .PARAMETER ReplaceCertificateName Name eines bestehenden Zertifikats das nach erfolgreicher Installation ersetzt (Endpoint/TDE umgestellt) wird. Das alte Zertifikat wird NICHT geloescht. .PARAMETER ImportToWindowsStore Zertifikat zusaetzlich in den Windows-Maschinen-Zertifikatspeicher importieren. Wird fuer SSL/TLS-Verbindungen benoetigt. Standard: $false, bei Purpose=SSL automatisch $true. .PARAMETER SetSqlServerSslCert SQL Server Netzwerkkonfiguration auf dieses Zertifikat (Thumbprint) setzen. Setzt Neustart des SQL Server-Dienstes voraus. Standard: $false. .PARAMETER OutputPath Ausgabeverzeichnis fuer das Installationsprotokoll. Standard: aus Modulkonfiguration. .PARAMETER EnableException Ausnahmen sofort ausloesen. .EXAMPLE # PFX von CA importieren und an AlwaysOn-Endpoint binden Install-sqmCertificate -SqlInstance "SQL01" -CertFile "C:\Certs\sql01.pfx" ` -CertPassword (Read-Host -AsSecureString) -Purpose AlwaysOn .EXAMPLE # Public-Key-Zertifikat auf AlwaysOn-Replikat installieren (kein Private Key) Install-sqmCertificate -SqlInstance "SQL02" -CertFile "C:\Certs\SQL01_AG_CERT.cer" ` -CertificateName "SQL01_AG_CERT" -Purpose AlwaysOn .EXAMPLE # CER + PVK installieren und TDE binden Install-sqmCertificate -SqlInstance "SQL01" ` -CertFile "C:\Certs\tde_new.cer" ` -PrivateKeyFile "C:\Certs\tde_new.pvk" ` -CertPassword (Read-Host -AsSecureString "PVK-Passwort") ` -Purpose TDE -TdeDatabaseName "ProdDB" .EXAMPLE # SSL-Zertifikat installieren (Windows Store + SQL Server Netzwerk) Install-sqmCertificate -SqlInstance "SQL01" -CertFile "C:\Certs\ssl.pfx" ` -CertPassword (Read-Host -AsSecureString) -Purpose SSL -SetSqlServerSslCert .NOTES Erfordert: dbatools, Invoke-sqmLogging, Get-sqmDefaultOutputPath, Copy-sqmToCentralPath Benoetigt: sysadmin auf der SQL Server-Instanz SSL-Bindung (-SetSqlServerSslCert): Erfordert Neustart des SQL Server-Dienstes. AlwaysOn: Public-Key (.cer) muss auf ALLEN Replikat-Instanzen installiert werden. PFX-Import: Der Private Key wird in SQL Server unter dem Service-Account-Kontext gespeichert. Zertifikatsdateien werden nach dem Import NICHT geloescht. #> function Install-sqmCertificate { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $false, Position = 0)] [string]$SqlInstance, [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential]$SqlCredential, [Parameter(Mandatory = $true)] [ValidateScript({ Test-Path $_ -PathType Leaf })] [string]$CertFile, [Parameter(Mandatory = $false)] [ValidateScript({ -not $_ -or (Test-Path $_ -PathType Leaf) })] [string]$PrivateKeyFile, [Parameter(Mandatory = $false)] [System.Security.SecureString]$CertPassword, [Parameter(Mandatory = $false)] [string]$CertificateName, [Parameter(Mandatory = $false)] [string]$Database = 'master', [Parameter(Mandatory = $false)] [ValidateSet('AlwaysOn', 'TDE', 'SSL', 'ServiceBroker', 'UserDefined')] [string]$Purpose = 'UserDefined', [Parameter(Mandatory = $false)] [string]$EndpointName, [Parameter(Mandatory = $false)] [string]$TdeDatabaseName, [Parameter(Mandatory = $false)] [string]$ReplaceCertificateName, [Parameter(Mandatory = $false)] [switch]$ImportToWindowsStore, [Parameter(Mandatory = $false)] [switch]$SetSqlServerSslCert, [Parameter(Mandatory = $false)] [string]$OutputPath, [Parameter(Mandatory = $false)] [switch]$EnableException ) begin { $functionName = $MyInvocation.MyCommand.Name if (-not $PSBoundParameters.ContainsKey('SqlInstance') -or [string]::IsNullOrWhiteSpace($SqlInstance)) { $SqlInstance = $env:COMPUTERNAME } if (-not $script:dbatoolsAvailable) { $msg = "dbatools-Modul nicht gefunden. Bitte installieren: Install-Module dbatools" Invoke-sqmLogging -Message $msg -FunctionName $functionName -Level "ERROR" throw $msg } if (-not $OutputPath) { $OutputPath = Get-sqmDefaultOutputPath } # SSL setzt Windows-Store-Import voraus if ($Purpose -eq 'SSL') { $ImportToWindowsStore = $true } Invoke-sqmLogging -Message ("Starte " + $functionName + ": '$CertFile' auf $SqlInstance (Purpose=$Purpose)") -FunctionName $functionName -Level "INFO" } process { try { $datestamp = Get-Date -Format 'yyyyMMdd_HHmsqm' $certExt = [System.IO.Path]::GetExtension($CertFile).ToLower() $isPfx = $certExt -in @('.pfx', '.p12') $hasPvk = [bool]$PrivateKeyFile $hasPrivKey = $isPfx -or $hasPvk # Zertifikatsnamen aus Dateinamen ableiten wenn nicht angegeben if (-not $CertificateName) { $CertificateName = [System.IO.Path]::GetFileNameWithoutExtension($CertFile) -replace '[^a-zA-Z0-9_]', '_' } $connParams = @{ SqlInstance = $SqlInstance SqlCredential = $SqlCredential Database = $Database } # ------------------------------------------------------------------- # 1. Zertifikatsdatei lesen und vorab validieren # ------------------------------------------------------------------- $certInfo = Get-sqmCertFileInfo -CertFile $CertFile -CertPassword $CertPassword -IsPfx $isPfx Invoke-sqmLogging -Message "Zertifikat gelesen: Subject='$($certInfo.Subject)', Ablauf=$($certInfo.NotAfter.ToString('yyyy-MM-dd'))" -FunctionName $functionName -Level "INFO" # Ablaufdatum warnen (nicht blockieren - Installation eines abgelaufenen Zerts kann gewollt sein) if ($certInfo.NotAfter -lt (Get-Date)) { Write-Warning "ACHTUNG: Das Zertifikat '$CertificateName' ist bereits abgelaufen ($($certInfo.NotAfter.ToString('yyyy-MM-dd')))!" Invoke-sqmLogging -Message "Zertifikat ist abgelaufen: $($certInfo.NotAfter)" -FunctionName $functionName -Level "WARNING" } elseif ($certInfo.NotAfter -lt (Get-Date).AddDays(30)) { Write-Warning "HINWEIS: Zertifikat laeuft in weniger als 30 Tagen ab ($($certInfo.NotAfter.ToString('yyyy-MM-dd')))." } # ------------------------------------------------------------------- # 2. Pruefen ob Zertifikat bereits in SQL Server existiert # ------------------------------------------------------------------- $existsQuery = "SELECT name, thumbprint FROM sys.certificates WHERE name = '$($CertificateName -replace "'", "''")'" $existingInSql = Invoke-DbaQuery @connParams -Query $existsQuery -ErrorAction SilentlyContinue if ($existingInSql) { $existingThumb = [System.BitConverter]::ToString($existingInSql.thumbprint).Replace('-', '') if ($existingThumb -eq $certInfo.Thumbprint) { Write-Warning "Zertifikat '$CertificateName' mit identischem Thumbprint ist bereits in SQL Server vorhanden. ueberspringe Import." Invoke-sqmLogging -Message "Zertifikat bereits vorhanden (gleicher Thumbprint). Fahre mit Bindung fort." -FunctionName $functionName -Level "WARNING" } else { throw "Ein Zertifikat mit dem Namen '$CertificateName' existiert bereits in SQL Server mit anderem Thumbprint. Bitte anderen -CertificateName angeben oder bestehendes Zertifikat entfernen." } } # ------------------------------------------------------------------- # 3. ShouldProcess # ------------------------------------------------------------------- $action = "Zertifikat '$CertificateName' in SQL Server '$SqlInstance' installieren (Purpose: $Purpose)" if (-not $PSCmdlet.ShouldProcess($SqlInstance, $action)) { Invoke-sqmLogging -Message "Abgebrochen durch ShouldProcess." -FunctionName $functionName -Level "INFO" return $null } # ------------------------------------------------------------------- # 4. Zertifikat in SQL Server importieren # ------------------------------------------------------------------- $installResult = [PSCustomObject]@{ SqlInstance = $SqlInstance CertificateName = $CertificateName Subject = $certInfo.Subject Thumbprint = $certInfo.Thumbprint NotAfter = $certInfo.NotAfter Purpose = $Purpose SqlImported = $false WindowsStoreImport = $false SslCertSet = $false EndpointBound = $false TdeBound = $false ProtocolFile = $null Success = $false ErrorMessage = $null Warnings = [System.Collections.Generic.List[string]]::new() } if (-not $existingInSql) { $importSql = Build-sqmCertImportSql ` -CertificateName $CertificateName ` -CertFile $CertFile ` -PrivateKeyFile $PrivateKeyFile ` -CertPassword $CertPassword ` -IsPfx $isPfx ` -HasPrivKey $hasPrivKey Invoke-DbaQuery @connParams -Query $importSql -ErrorAction Stop $installResult.SqlImported = $true Invoke-sqmLogging -Message "Zertifikat '$CertificateName' erfolgreich in SQL Server importiert." -FunctionName $functionName -Level "INFO" } else { $installResult.SqlImported = $true # war bereits vorhanden } # ------------------------------------------------------------------- # 5. Windows-Maschinen-Store importieren (fuer SSL) # ------------------------------------------------------------------- if ($ImportToWindowsStore) { try { if ($isPfx) { $pfxBytes = [System.IO.File]::ReadAllBytes($CertFile) $x509 = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new( $pfxBytes, $CertPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet ) } else { $x509 = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($CertFile) } $store = [System.Security.Cryptography.X509Certificates.X509Store]::new( [System.Security.Cryptography.X509Certificates.StoreName]::My, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine ) $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) $store.Add($x509) $store.Close() $installResult.WindowsStoreImport = $true Invoke-sqmLogging -Message "Zertifikat in Windows Maschinen-Store (LocalMachine\My) importiert." -FunctionName $functionName -Level "INFO" } catch { $warnMsg = "Windows-Store-Import fehlgeschlagen: $($_.Exception.Message)" Write-Warning $warnMsg $installResult.Warnings.Add($warnMsg) Invoke-sqmLogging -Message $warnMsg -FunctionName $functionName -Level "WARNING" } } # ------------------------------------------------------------------- # 6. SQL Server Netzwerkkonfiguration (SSL-Zertifikat setzen) # ------------------------------------------------------------------- if ($SetSqlServerSslCert -and $installResult.WindowsStoreImport) { try { $thumbprintForReg = $certInfo.Thumbprint # ohne Trennzeichen # Instanzname fuer Registry-Pfad ermitteln $instancePart = if ($SqlInstance -match '\\(.+)$') { $Matches[1] } else { 'MSSQLSERVER' } $regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL$($server.VersionMajor).${instancePart}\MSSQLServer\SuperSocketNetLib" if (Test-Path $regPath) { Set-ItemProperty -Path $regPath -Name 'Certificate' -Value $thumbprintForReg.ToLower() -ErrorAction Stop $installResult.SslCertSet = $true Invoke-sqmLogging -Message "SSL-Zertifikat in SQL Server Netzwerkkonfiguration gesetzt (Thumbprint: $thumbprintForReg)." -FunctionName $functionName -Level "INFO" $installResult.Warnings.Add("SQL Server-Dienst muss neu gestartet werden, damit das SSL-Zertifikat aktiv wird.") Write-Warning "SSL-Zertifikat gesetzt - SQL Server-Dienst muss neu gestartet werden!" } else { $warnMsg = "Registry-Pfad fuer SQL Server Netzwerkkonfiguration nicht gefunden: $regPath" Write-Warning $warnMsg $installResult.Warnings.Add($warnMsg) } } catch { $warnMsg = "SSL-Zertifikat in Registry konnte nicht gesetzt werden: $($_.Exception.Message)" Write-Warning $warnMsg $installResult.Warnings.Add($warnMsg) Invoke-sqmLogging -Message $warnMsg -FunctionName $functionName -Level "WARNING" } } # ------------------------------------------------------------------- # 7. Endpoint-Bindung (AlwaysOn / ServiceBroker) # ------------------------------------------------------------------- if ($Purpose -in @('AlwaysOn', 'ServiceBroker')) { $epToUse = $EndpointName # Automatisch ermitteln wenn nicht angegeben if (-not $epToUse) { $epQuery = @" SELECT TOP 1 e.name FROM sys.endpoints e INNER JOIN sys.database_mirroring_endpoints dme ON e.endpoint_id = dme.endpoint_id ORDER BY e.endpoint_id "@ $autoEp = Invoke-DbaQuery @connParams -Database 'master' -Query $epQuery -ErrorAction SilentlyContinue $epToUse = $autoEp.name } if ($epToUse) { try { $alterEpSql = "ALTER ENDPOINT [$epToUse] FOR DATABASE_MIRRORING (AUTHENTICATION = CERTIFICATE [$CertificateName]);" Invoke-DbaQuery @connParams -Database 'master' -Query $alterEpSql -ErrorAction Stop $installResult.EndpointBound = $true Invoke-sqmLogging -Message "Endpoint '$epToUse' auf '$CertificateName' umgestellt." -FunctionName $functionName -Level "INFO" } catch { $warnMsg = "Endpoint-Bindung fehlgeschlagen: $($_.Exception.Message)" Write-Warning $warnMsg $installResult.Warnings.Add($warnMsg) } } else { $warnMsg = "Kein passender Endpoint gefunden. Bitte -EndpointName angeben." Write-Warning $warnMsg $installResult.Warnings.Add($warnMsg) } } # ------------------------------------------------------------------- # 8. TDE-Bindung # ------------------------------------------------------------------- if ($Purpose -eq 'TDE') { $tdeDbName = $TdeDatabaseName # Automatisch ermitteln wenn nicht angegeben und eindeutig if (-not $tdeDbName) { $tdeAutoQuery = @" SELECT d.name FROM sys.dm_database_encryption_keys dek INNER JOIN sys.databases d ON dek.database_id = d.database_id WHERE dek.encryption_state = 3 "@ $tdeDbs = Invoke-DbaQuery @connParams -Database 'master' -Query $tdeAutoQuery -ErrorAction SilentlyContinue if (@($tdeDbs).Count -eq 1) { $tdeDbName = $tdeDbs.name } elseif (@($tdeDbs).Count -gt 1) { $warnMsg = "Mehrere TDE-verschluesselte Datenbanken gefunden. Bitte -TdeDatabaseName angeben: $($tdeDbs.name -join ', ')" Write-Warning $warnMsg $installResult.Warnings.Add($warnMsg) } } if ($tdeDbName) { if (-not $hasPrivKey) { $warnMsg = "TDE-Zertifikat ohne Private Key kann nicht fuer TDE-Verschluesselung verwendet werden." Write-Warning $warnMsg $installResult.Warnings.Add($warnMsg) } else { try { $alterTdeSql = @" USE [$tdeDbName]; ALTER DATABASE ENCRYPTION KEY ENCRYPTION BY SERVER CERTIFICATE [$CertificateName]; "@ Invoke-DbaQuery @connParams -Database 'master' -Query $alterTdeSql -ErrorAction Stop $installResult.TdeBound = $true Invoke-sqmLogging -Message "TDE fuer '$tdeDbName' auf '$CertificateName' umgestellt." -FunctionName $functionName -Level "INFO" } catch { $warnMsg = "TDE-Bindung fehlgeschlagen: $($_.Exception.Message)" Write-Warning $warnMsg $installResult.Warnings.Add($warnMsg) } } } } # ------------------------------------------------------------------- # 9. Altes Zertifikat abloesen (Endpoint/TDE umstellen) # ------------------------------------------------------------------- if ($ReplaceCertificateName) { Invoke-sqmLogging -Message "Altes Zertifikat '$ReplaceCertificateName' abgeloest. Loeschen nach Verifikation: DROP CERTIFICATE [$ReplaceCertificateName];" -FunctionName $functionName -Level "INFO" $installResult.Warnings.Add("Altes Zertifikat '$ReplaceCertificateName' ist noch vorhanden. Nach Verifikation loeschen: DROP CERTIFICATE [$ReplaceCertificateName];") } # ------------------------------------------------------------------- # 10. Installationsprotokoll schreiben # ------------------------------------------------------------------- if (-not (Test-Path $OutputPath)) { New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null } $safeInstance = $SqlInstance -replace '\\', '_' $protFile = Join-Path $OutputPath "CertInstall_${safeInstance}_${CertificateName}_${datestamp}.txt" $lines = [System.Collections.Generic.List[string]]::new() $lines.Add("=" * 70) $lines.Add(" ZERTIFIKAT-INSTALLATIONSPROTOKOLL") $lines.Add(" Datum : $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')") $lines.Add(" Instanz : $SqlInstance") $lines.Add(" Datenbank : $Database") $lines.Add("=" * 70) $lines.Add("") $lines.Add("ZERTIFIKAT") $lines.Add("-" * 40) $lines.Add(" Name : $CertificateName") $lines.Add(" Subject : $($certInfo.Subject)") $lines.Add(" Aussteller : $($certInfo.Issuer)") $lines.Add(" Gueltig von : $($certInfo.NotBefore.ToString('yyyy-MM-dd'))") $lines.Add(" Gueltig bis : $($certInfo.NotAfter.ToString('yyyy-MM-dd'))") $lines.Add(" Thumbprint : $($certInfo.Thumbprint)") $lines.Add(" Private Key : $(if ($hasPrivKey) { 'Ja' } else { 'Nein (nur Public Key)' })") $lines.Add(" Quelldatei : $CertFile") if ($PrivateKeyFile) { $lines.Add(" PVK-Datei : $PrivateKeyFile") } $lines.Add(" Verwendung : $Purpose") $lines.Add("") $lines.Add("INSTALLATIONSERGEBNIS") $lines.Add("-" * 40) $lines.Add(" SQL Import : $(if ($installResult.SqlImported) { 'OK' } else { 'FEHLER' })") $lines.Add(" Windows Store : $(if ($installResult.WindowsStoreImport) { 'OK' } elseif ($ImportToWindowsStore) { 'FEHLER' } else { 'Nicht durchgefuehrt' })") $lines.Add(" SSL-Zertifikat : $(if ($installResult.SslCertSet) { 'OK - Neustart erforderlich!' } elseif ($SetSqlServerSslCert) { 'FEHLER' } else { 'Nicht gesetzt' })") $lines.Add(" Endpoint-Bindung : $(if ($installResult.EndpointBound) { 'OK' } elseif ($Purpose -in 'AlwaysOn', 'ServiceBroker') { 'FEHLGESCHLAGEN - manuell binden' } else { 'Nicht zutreffend' })") $lines.Add(" TDE-Bindung : $(if ($installResult.TdeBound) { 'OK' } elseif ($Purpose -eq 'TDE') { 'FEHLGESCHLAGEN - manuell binden' } else { 'Nicht zutreffend' })") $lines.Add("") if ($installResult.Warnings.Count -gt 0) { $lines.Add("WARNUNGEN") $lines.Add("-" * 40) foreach ($w in $installResult.Warnings) { $lines.Add(" ! $w") } $lines.Add("") } # Purpose-spezifische Nacharbeiten if ($Purpose -eq 'AlwaysOn') { $lines.Add("WEITERE SCHRITTE - ALWAYSON") $lines.Add("-" * 40) $lines.Add(" Das installierte Zertifikat (Public Key) muss auf alle Replikat-Instanzen") $lines.Add(" uebertragen werden. Vorgehen fuer jedes Replikat:") $lines.Add("") $lines.Add(" 1. Public Key exportieren (auf PRIMARY ausfuehren):") $lines.Add(" BACKUP CERTIFICATE [$CertificateName] TO FILE = N'\\<Share>\$CertificateName.cer';") $lines.Add("") $lines.Add(" 2. Auf jedem Replikat installieren:") $lines.Add(" Install-sqmCertificate -SqlInstance '<Replikat>' \") $lines.Add(" -CertFile '\\<Share>\$CertificateName.cer' \") $lines.Add(" -CertificateName '$CertificateName' \") $lines.Add(" -Purpose AlwaysOn") $lines.Add("") $lines.Add(" 3. AG-Replikation und Sync-Status pruefen:") $lines.Add(" Get-sqmAgHealthReport -SqlInstance '<Primary>'") } if ($Purpose -eq 'TDE') { $lines.Add("WEITERE SCHRITTE - TDE") $lines.Add("-" * 40) $lines.Add(" PFLICHT: Neues TDE-Zertifikat sofort sichern (inkl. Private Key)!") $lines.Add(" BACKUP CERTIFICATE [$CertificateName] TO FILE = N'<Pfad>\$CertificateName.cer'") $lines.Add(" WITH PRIVATE KEY (") $lines.Add(" FILE = N'<Pfad>\$CertificateName.pvk',") $lines.Add(" ENCRYPTION BY PASSWORD = N'<sicheres Passwort>'") $lines.Add(" );") $lines.Add(" Backup sicher und getrennt vom Server aufbewahren!") } if ($Purpose -eq 'SSL' -and $installResult.SslCertSet) { $lines.Add("WEITERE SCHRITTE - SSL") $lines.Add("-" * 40) $lines.Add(" SQL Server-Dienst neu starten:") $lines.Add(" Restart-Service MSSQLSERVER (oder benannte Instanz)") $lines.Add(" Nach Neustart: Verschluesselte Verbindungen testen.") } $lines | Out-File -FilePath $protFile -Encoding UTF8 -Force $installResult.ProtocolFile = $protFile $installResult.Success = $installResult.SqlImported Invoke-sqmLogging -Message "Installationsprotokoll: $protFile" -FunctionName $functionName -Level "INFO" Copy-sqmToCentralPath -Path @($protFile) Write-Host "Zertifikat '$CertificateName' installiert auf $SqlInstance." -ForegroundColor Green Write-Host "Protokoll: $protFile" -ForegroundColor Cyan if ($installResult.Warnings.Count -gt 0) { Write-Host "$($installResult.Warnings.Count) Warnung(en) - siehe Protokoll." -ForegroundColor Yellow } return $installResult } catch { $errMsg = "Fehler bei Zertifikats-Installation: $($_.Exception.Message)" Invoke-sqmLogging -Message $errMsg -FunctionName $functionName -Level "ERROR" if ($EnableException) { throw } Write-Error $errMsg return [PSCustomObject]@{ Success = $false; ErrorMessage = $errMsg } } } end { Invoke-sqmLogging -Message "$functionName abgeschlossen." -FunctionName $functionName -Level "INFO" } } # --------------------------------------------------------------------------- # Private Hilfsfunktionen # --------------------------------------------------------------------------- function Get-sqmCertFileInfo { param ( [string]$CertFile, [System.Security.SecureString]$CertPassword, [bool]$IsPfx ) try { if ($IsPfx) { $pfxBytes = [System.IO.File]::ReadAllBytes($CertFile) $x509 = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new( $pfxBytes, $CertPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::EphemeralKeySet ) } else { $x509 = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($CertFile) } return [PSCustomObject]@{ Subject = $x509.Subject Issuer = $x509.Issuer NotBefore = $x509.NotBefore NotAfter = $x509.NotAfter Thumbprint = $x509.Thumbprint } } catch { throw "Zertifikatsdatei kann nicht gelesen werden: $($_.Exception.Message). Passwort korrekt?" } } function Build-sqmCertImportSql { param ( [string]$CertificateName, [string]$CertFile, [string]$PrivateKeyFile, [System.Security.SecureString]$CertPassword, [bool]$IsPfx, [bool]$HasPrivKey ) # Passwort entschluesseln (nur temporaer im Speicher) $plainPwd = $null if ($CertPassword) { $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($CertPassword) $plainPwd = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr) [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } try { if ($IsPfx) { # PFX: SQL Server 2022+ unterstuetzt CERTIFICATE FROM FILE fuer PFX direkt # Fuer aeltere Versionen: DECRYPTION BY PASSWORD $pwdClause = if ($plainPwd) { "DECRYPTION BY PASSWORD = N'$plainPwd'" } else { '' } return @" CREATE CERTIFICATE [$CertificateName] FROM FILE = N'$CertFile' WITH PRIVATE KEY ( FILE = N'$CertFile', $pwdClause ); "@ } elseif ($PrivateKeyFile) { $pwdClause = if ($plainPwd) { "DECRYPTION BY PASSWORD = N'$plainPwd'" } else { '' } return @" CREATE CERTIFICATE [$CertificateName] FROM FILE = N'$CertFile' WITH PRIVATE KEY ( FILE = N'$PrivateKeyFile', $pwdClause ); "@ } else { # Nur Public Key (CER ohne Private Key) return @" CREATE CERTIFICATE [$CertificateName] FROM FILE = N'$CertFile'; "@ } } finally { $plainPwd = $null # Passwort aus Speicher entfernen } } |