Bereitstellen von Always Encrypted-Schlüsseln mithilfe von PowerShell

Gilt für:SQL ServerAzure SQL-DatenbankVerwaltete Azure SQL-Instanz

Dieser Artikel enthält die Schritte zum Bereitstellen von Schlüsseln für Always Encrypted mithilfe des SqlServer PowerShell-Moduls. Sie können PowerShell verwenden, um Always Encrypted-Schlüssel sowohl mit als auch ohne Rollentrennung bereitzustellen, wodurch Sie kontrollieren können, wer Zugriff auf die tatsächlichen Verschlüsselungsschlüssel im Schlüsselspeicher hat und wer auf die Datenbank zugreifen kann.

Hinweis

Microsoft empfiehlt die Verwendung von PowerShell 7 oder höher beim Ausführen von Always Encrypted PowerShell-Skripts. PowerShell 7 bietet eine verbesserte plattformübergreifende Unterstützung, bessere Leistung und die neueste Kompatibilität mit dem SqlServer-Modul (v22+), die für viele Always Encrypted-Szenarien erforderlich ist.

Eine Übersicht über die Always Encrypted-Schlüsselverwaltung, einschließlich einiger empfehlungen für bewährte Methoden auf hoher Ebene, finden Sie unter Übersicht über die Schlüsselverwaltung für Always Encrypted. Informationen dazu, wie Sie mit der Verwendung des SqlServer PowerShell-Moduls für Always Encrypted beginnen, finden Sie unter Configure Always Encrypted using PowerShell.

Schlüsselbereitstellung ohne Rollentrennung

Die in diesem Abschnitt beschriebene Schlüsselbereitstellungsmethode unterstützt keine Rollentrennung zwischen Sicherheitsadministratoren und DBAs. Einige der Schritte in diesem Abschnitt kombinieren Vorgänge für physische Schlüssel mit Vorgängen für Schlüsselmetadaten. Verwenden Sie daher diese Methode zum Bereitstellen der Schlüssel, wenn Ihre Organisation das DevOps-Modell verwendet oder wenn die Datenbank in der Cloud gehostet wird und das Hauptziel darin besteht, Cloudadministratoren (nicht jedoch On-Premises-DBAs) am Zugriff auf vertrauliche Daten zu hindern. Verwenden Sie diese Methode nicht, wenn potenzielle Angreifer DBAs enthalten oder wenn DBAs keinen Zugriff auf vertrauliche Daten haben sollten.

Stellen Sie vor dem Ausführen von Schritten, die den Zugriff auf Klartext-Schlüssel oder den Schlüsselspeicher beinhalten (in der Spalte Zugriffe auf Klartext-Schlüssel/Schlüsselspeicher in der folgenden Tabelle angegeben), sicher, dass die PowerShell-Umgebung auf einem sicheren Computer ausgeführt wird, der sich von dem Computer unterscheidet, auf dem Ihre Datenbank gehostet wird. Weitere Informationen finden Sie unter Sicherheitsüberlegungen für die Schlüsselverwaltung.

Aufgabe Artikel Greift auf Klartextschlüssel und -schlüsselspeicher zu Greift auf Datenbank zu
Schritt 1. Erstellen Sie einen Spaltenhauptschlüssel in einem Schlüsselspeicher.

Anmerkung: Das SqlServer PowerShell-Modul unterstützt diesen Schritt nicht. Verwenden Sie die Tools, die für Ihren gewählten Schlüsselspeicher spezifisch sind, um diese Aufgabe über eine Befehlszeile durchzuführen.
Erstellen und Speichern von Spaltenmasterschlüsseln für Always Encrypted Ja Nein
Schritt 2. Starten Sie eine PowerShell-Umgebung, und importieren Sie das SqlServer PowerShell-Modul. Konfigurieren von "Immer verschlüsselt" mithilfe von PowerShell Nein Nein
Schritt 3. Stellen Sie eine Verbindung mit Ihrem Server und Ihrer Datenbank her. Herstellen einer Verbindung mit einer Datenbank Nein Ja
Schritt 4. Erstellen Sie ein SqlColumnMasterKeySettings - Objekt, das Informationen zum Speicherort des Spaltenmasterschlüssels enthält. SqlColumnMasterKeySettings ist ein Objekt, das im Arbeitsspeicher (in PowerShell) vorhanden ist. Verwenden Sie das Cmdlet, das für Ihren Schlüsselspeicher spezifisch ist. New-SqlAzureKeyVaultColumnMasterKeySettings

New-SqlCertificateStoreColumnMasterKeySettings

New-SqlCngColumnMasterKeySettings

New-SqlCspColumnMasterKeySettings
Nein Nein
Schritt 5. Erstellen Sie die Metadaten über den Spaltenhauptschlüssel in Ihrer Datenbank.

Anmerkung: Wir überprüfen nicht die Gültigkeit der Schlüssel oder Zertifikate, die zum Generieren des Spaltenmasterschlüssels verwendet werden.
[New-SqlColumnMasterKey](/powershell/sqlserver/sqlserver/vlatest/new-sqlcolumnmasterkey

Hinweis: Unter den Covern gibt das Cmdlet die CREATE COLUMN MASTER KEY Anweisung aus, um wichtige Metadaten zu erstellen.
Nein Ja
Schritt 6. Authentifizieren Sie sich bei Azure, wenn Ihr Spaltenhauptschlüssel in Azure Key Vault gespeichert ist. Connect-AzAccount Ja Nein
Schritt 7. Rufen Sie ein Zugriffstoken für Azure Key Vaults ab, wenn Ihr Spaltenhauptschlüssel in Azure Key Vault gespeichert ist. Get-AzAccessToken Nein Nein
Schritt 8: Generieren Sie einen neuen Spaltenverschlüsselungsschlüssel, verschlüsseln Sie ihn mit dem Spaltenhauptschlüssel, und erstellen Sie Spaltenverschlüsselungsschlüssel-Metadaten in der Datenbank. New-SqlColumnEncryptionKey

Anmerkung: Verwenden Sie eine Variante des Cmdlets, das intern einen Spaltenverschlüsselungsschlüssel generiert und verschlüsselt.

Hinweis: Unter den Deckeln gibt das Cmdlet die CREATE COLUMN ENCRYPTION KEY Anweisung aus, um wichtige Metadaten zu erstellen.
Ja Ja

Windows-Zertifikatspeicher ohne Rollentrennung (Beispiel)

Dieses Skript ist ein End-to-End-Beispiel für das Generieren eines Spaltenhauptschlüssels, der ein Zertifikat im Windows-Zertifikatspeicher darstellt, einen Spaltenverschlüsselungsschlüssel generiert und verschlüsselt und Schlüsselmetadaten in einer SQL Server-Datenbank erstellt.

[CmdletBinding()]
param(
	[Parameter(Mandatory = $false)]
	[string]$DatabaseName = '<database name>',

	[Parameter(Mandatory = $false)]
	[string]$ServerName = "<server name>",

	[Parameter(Mandatory = $false)]
	[string]$CertificateSubject = "AlwaysEncryptedCert",

	[Parameter(Mandatory = $false)]
	[string]$CmkName = "CMK",

	[Parameter(Mandatory = $false)]
	[string]$CekName = "CEK"
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

Import-Module SqlServer -MinimumVersion 22.0.50 -ErrorAction Stop

Write-Host "[AE] Locating certificate '$CertificateSubject' in CurrentUser\\My"
$cert = Get-ChildItem -Path Cert:CurrentUser\My |
	Where-Object { $_.Subject -eq "CN=$CertificateSubject" } |
	Sort-Object NotAfter -Descending |
	Select-Object -First 1

if (-not $cert) {
	Write-Host "[AE] Certificate not found. Creating self-signed certificate."
	$cert = New-SelfSignedCertificate `
		-Subject $CertificateSubject `
		-CertStoreLocation Cert:CurrentUser\My `
		-KeyExportPolicy Exportable `
		-Type DocumentEncryptionCert `
		-KeyUsage DataEncipherment `
		-KeySpec KeyExchange
}

Write-Host "[AE] Connecting to SQL Server '$ServerName' / Database '$DatabaseName'"
$connStr = "Server=$ServerName;Database=$DatabaseName;Integrated Security=True;Encrypt=True;TrustServerCertificate=True;Connection Timeout=30"

try {
	$database = Get-SqlDatabase -ConnectionString $connStr -ErrorAction Stop
}
catch {
	Write-Error "Failed to connect to '$ServerName' database '$DatabaseName'. Verify instance name SQL2025, database existence, and local permissions."
	throw
}

Write-Host "[AE] Creating CMK settings from certificate thumbprint"
$cmkSettings = New-SqlCertificateStoreColumnMasterKeySettings -CertificateStoreLocation "CurrentUser" -Thumbprint $cert.Thumbprint

Write-Host "[AE] Ensuring CMK '$CmkName' exists"
$existingCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $CmkName }
if (-not $existingCmk) {
	New-SqlColumnMasterKey -Name $CmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings | Out-Null
}

Write-Host "[AE] Ensuring CEK '$CekName' exists"
$existingCek = Get-SqlColumnEncryptionKey -InputObject $database | Where-Object { $_.Name -eq $CekName }
if (-not $existingCek) {
	New-SqlColumnEncryptionKey -Name $CekName -InputObject $database -ColumnMasterKey $CmkName | Out-Null
}

Write-Host "Completed successfully"

Azure Key Vault ohne Rollentrennung (Beispiel)

Dieses Skript ist ein umfassendes Beispiel für das Bereitstellen und Konfigurieren eines Schlüsseltresors im Azure Key Vault, für das Generieren eines Spaltenhauptschlüssels im Tresor, für das Generieren und Verschlüsseln eines Spaltenhauptschlüssels und für das Erstellen von Schlüsselmetadaten in einer Azure SQL-Datenbank-Instanz.

param(
	[Parameter(Mandatory = $true)] [string]$SubscriptionId,
	[Parameter(Mandatory = $true)] [string]$ResourceGroupName,
	[Parameter(Mandatory = $true)] [string]$AzureLocation,
	[Parameter(Mandatory = $true)] [string]$KeyVaultName,
	[Parameter(Mandatory = $true)] [string]$KeyName,
	[Parameter(Mandatory = $true)] [string]$ServerName,
	[Parameter(Mandatory = $true)] [string]$DatabaseName,
	[string]$CmkName = "CMK",
	[string]$CekName = "CEK",
	[bool]$AssignRbacToCurrentPrincipal = $true
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

Import-Module Az.Accounts -ErrorAction Stop
Import-Module Az.Resources -ErrorAction Stop
Import-Module Az.KeyVault -ErrorAction Stop
Import-Module SqlServer -ErrorAction Stop

function Get-CurrentPrincipalObjectId {
	param([string]$AccountId)

	$userSignedIn = Get-AzADUser -SignedIn -ErrorAction SilentlyContinue
	if ($userSignedIn) { return $userSignedIn.Id }

	$user = Get-AzADUser -UserPrincipalName $AccountId -ErrorAction SilentlyContinue
	if ($user) { return $user.Id }

	$sp = Get-AzADServicePrincipal -DisplayName $AccountId -ErrorAction SilentlyContinue | Select-Object -First 1
	if ($sp) { return $sp.Id }

	throw "Could not resolve Microsoft Entra object id for account '$AccountId'."
}

try {
	Write-Host "[AE] Signing in and selecting subscription"
	Connect-AzAccount | Out-Null
	$ctx = Set-AzContext -SubscriptionId $SubscriptionId

	Write-Host "[AE] Ensuring resource group exists"
	$resourceGroup = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue
	if (-not $resourceGroup) {
		$resourceGroup = New-AzResourceGroup -Name $ResourceGroupName -Location $AzureLocation
	}

	Write-Host "[AE] Ensuring key vault exists (RBAC mode)"
	$vault = Get-AzKeyVault -VaultName $KeyVaultName -ResourceGroupName $ResourceGroupName -ErrorAction SilentlyContinue
	if (-not $vault) {
		$vault = New-AzKeyVault -VaultName $KeyVaultName -ResourceGroupName $ResourceGroupName -Location $AzureLocation -EnableRbacAuthorization
	}

	if (-not $vault.EnableRbacAuthorization) {
		throw "Key Vault '$KeyVaultName' is not using RBAC authorization. Enable RBAC authorization on the vault before running this script."
	}

	if ($AssignRbacToCurrentPrincipal) {
		Write-Host "[AE] Ensuring RBAC role assignment"
		$principalSignInName = $ctx.Account.Id
		$roleName = "Key Vault Crypto Officer"
		$existingRole = Get-AzRoleAssignment -SignInName $principalSignInName -Scope $vault.ResourceId -RoleDefinitionName $roleName -ErrorAction SilentlyContinue
		if (-not $existingRole) {
			New-AzRoleAssignment -SignInName $principalSignInName -Scope $vault.ResourceId -RoleDefinitionName $roleName | Out-Null
		}
	}

	Write-Host "[AE] Ensuring column master key material exists in Key Vault"
	$akvKey = Get-AzKeyVaultKey -VaultName $KeyVaultName -Name $KeyName -ErrorAction SilentlyContinue
	if (-not $akvKey) {
		$akvKey = Add-AzKeyVaultKey -VaultName $KeyVaultName -Name $KeyName -Destination "Software"
	}

	Write-Host "[AE] Connecting to Azure SQL and creating metadata"
	$keyVaultAccessToken = (Get-AzAccessToken -ResourceUrl "https://vault.azure.net").Token
	$connStr = "Server=tcp:$ServerName.database.windows.net,1433;Database=$DatabaseName;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;Authentication=Active Directory Interactive"
	$database = Get-SqlDatabase -ConnectionString $connStr -Encrypt Mandatory
	$cmkSettings = New-SqlAzureKeyVaultColumnMasterKeySettings -KeyUrl $akvKey.Key.Kid

	$existingCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $CmkName }
	if (-not $existingCmk) {
		New-SqlColumnMasterKey -Name $CmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings | Out-Null
	}

	$existingCek = Get-SqlColumnEncryptionKey -InputObject $database | Where-Object { $_.Name -eq $CekName }
	if (-not $existingCek) {
		New-SqlColumnEncryptionKey -Name $CekName -InputObject $database -ColumnMasterKey $CmkName -KeyVaultAccessToken $keyVaultAccessToken | Out-Null
	}

	Write-Host "Completed successfully"
}
catch {
	Write-Error "Script failed: $($_.Exception.Message)"
	throw
}

CNG/KSP ohne Rollentrennung (Beispiel)

Das Skript unten ist ein End-to-End-Beispiel für das Generieren eines Spaltenhauptschlüssels in einem Schlüsselspeicher, der die Cryptography Next Generation API (CNG) implementiert, für das Generieren und Verschlüsseln eines Spaltenhauptschlüssels und für das Erstellen von Schlüsselmetadaten in einer SQL Server-Datenbank.

Im Beispiel wird der Schlüsselspeicher verwendet, der den Microsoft Software Key Storage Provider verwendet. Sie können das Beispiel ändern, um einen anderen Speicher zu verwenden, z. B. Ihr Hardwaresicherheitsmodul. Dazu müssen Sie sicherstellen, dass der Schlüsselspeicheranbieter (KSP), der CNG für Ihr Gerät implementiert, ordnungsgemäß auf dem Computer installiert ist. Sie müssen Microsoft Software Key Storage Provider durch den KSP-Namen Ihres Geräts ersetzen.

[CmdletBinding()]
param(
	[Parameter(Mandatory = $false)]
	[string]$ServerName = "<server name>",

	[Parameter(Mandatory = $true)]
	[string]$DatabaseName = "<database name>",

	[Parameter(Mandatory = $false)]
	[string]$CngKeyName = "AlwaysEncryptedKey",

	[Parameter(Mandatory = $false)]
	[string]$CmkName = "CMK",

	[Parameter(Mandatory = $false)]
	[string]$CekName = "CEK"
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

# Local key store provider and key settings.
$cngProviderName = "Microsoft Software Key Storage Provider"
$cngAlgorithmName = "RSA"
$cngKeySize = 2048

Import-Module SqlServer -ErrorAction Stop

Write-Host "[AE] Creating local CNG key '$CngKeyName'"
$cngProvider = New-Object System.Security.Cryptography.CngProvider($cngProviderName)
$cngKeyParams = New-Object System.Security.Cryptography.CngKeyCreationParameters
$cngKeyParams.Provider = $cngProvider
$cngKeyParams.KeyCreationOptions = [System.Security.Cryptography.CngKeyCreationOptions]::OverwriteExistingKey
$keySizeProperty = New-Object System.Security.Cryptography.CngProperty(
	"Length",
	[System.BitConverter]::GetBytes($cngKeySize),
	[System.Security.Cryptography.CngPropertyOptions]::None
)
$cngKeyParams.Parameters.Add($keySizeProperty)
$cngAlgorithm = New-Object System.Security.Cryptography.CngAlgorithm($cngAlgorithmName)
[System.Security.Cryptography.CngKey]::Create($cngAlgorithm, $CngKeyName, $cngKeyParams) | Out-Null

Write-Host "[AE] Connecting to $ServerName / $DatabaseName"
$connStr = "Server=$ServerName;Database=$DatabaseName;Integrated Security=True;Encrypt=True;TrustServerCertificate=True"
$database = Get-SqlDatabase -ConnectionString $connStr

Write-Host "[AE] Preparing column master key settings"
$cmkSettings = New-SqlCngColumnMasterKeySettings -CngProviderName $cngProviderName -KeyName $CngKeyName

Write-Host "[AE] Ensuring CMK exists"
$existingCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $CmkName }
if (-not $existingCmk) {
	New-SqlColumnMasterKey -Name $CmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings | Out-Null
}

Write-Host "[AE] Ensuring CEK exists"
$existingCek = Get-SqlColumnEncryptionKey -InputObject $database | Where-Object { $_.Name -eq $CekName }
if (-not $existingCek) {
	New-SqlColumnEncryptionKey -Name $CekName -InputObject $database -ColumnMasterKey $CmkName | Out-Null
}

Write-Host "Completed successfully"

Schlüsselbereitstellung mit Rollentrennung

Dieser Abschnitt enthält die Schritte zum Konfigurieren einer Verschlüsselung, mit der Sicherheitsadministratoren nicht über Zugriff auf die Datenbank verfügen, und Datenbankadministratoren nicht über Zugriff auf den Schlüsselspeicher oder Nur-Text-Schlüssel verfügen.

Sicherheitsadministrator

Bevor Sie Schritte ausführen, die den Zugriff auf Nur-Text-Schlüssel oder den Schlüsselspeicher (in der Spalte "Nur-Text-Schlüssel/Schlüsselspeicher" in Accesses in der folgenden Tabelle angegeben) beinhalten, stellen Sie sicher, dass:

  • Die PowerShell-Umgebung wird auf einem sicheren Computer ausgeführt, der nicht der Computer ist, auf dem Ihre Datenbank gehostet wird.
  • Datenbankadministratoren in Ihrer Organisation haben keinen Zugriff auf die Maschine (was den Zweck der Rollentrennung zunichte machen würde).

Weitere Informationen finden Sie unter Sicherheitsüberlegungen für die Schlüsselverwaltung.

Aufgabe Artikel Greift auf Klartextschlüssel und -schlüsselspeicher zu Greift auf Datenbank zu
Schritt 1. Erstellen Sie einen Spaltenhauptschlüssel in einem Schlüsselspeicher.

Anmerkung: Das SqlServer-Modul unterstützt diesen Schritt nicht. Sie müssen die Tools Verwenden, die für den Typ Ihres Schlüsselspeichers spezifisch sind, um diese Aufgabe mithilfe einer Befehlszeile durchzuführen.
Erstellen und Speichern von Spaltenmasterschlüsseln für Always Encrypted Ja Nein
Schritt 2. Starten Sie eine PowerShell-Sitzung, und importieren Sie das SqlServer-Modul. Importieren des SqlServer-Moduls Nein Nein
Schritt 3. Erstellen Sie ein SqlColumnMasterKeySettings - Objekt, das Informationen zum Speicherort des Spaltenmasterschlüssels enthält. SqlColumnMasterKeySettings ist ein Objekt, das im Arbeitsspeicher (in PowerShell) vorhanden ist. Verwenden Sie das Cmdlet, das für Ihren Schlüsselspeicher spezifisch ist. New-SqlAzureKeyVaultColumnMasterKeySettings

New-SqlCertificateStoreColumnMasterKeySettings

New-SqlCngColumnMasterKeySettings

New-SqlCspColumnMasterKeySettings
Nein Nein
Schritt 4. Authentifizieren Sie sich bei Azure, wenn Ihr Spaltenhauptschlüssel in Azure Key Vault gespeichert ist. Connect-AzAccount Ja Nein
Schritt 5. Rufen Sie ein Zugriffstoken für Azure Key Vaults ab, wenn Ihr Spaltenhauptschlüssel in Azure Key Vault gespeichert ist. Get-AzAccessToken Nein Nein
Schritt 6. Generieren Sie einen Spaltenverschlüsselungsschlüssel, und verschlüsseln Sie ihn mit dem Spaltenhauptschlüssel, um einen verschlüsselten Wert des Spaltenverschlüsselungsschlüssels zu erstellen. New-SqlColumnEncryptionKeyEncryptedValue Ja Nein
Schritt 7. Stellen Sie den Speicherort des Spaltenhauptschlüssels (den Anbieternamen und einen Schlüsselpfad des Spaltenhauptschlüssels) und einen verschlüsselten Wert des Spaltenverschlüsselungsschlüssels für den DBA bereit. Sehen Sie sich die Beispiele am Ende des Artikels an. Nein Nein

DBA

Datenbankadministratoren (DBAs) verwenden die Informationen, die sie vom Sicherheitsadministrator (Schritt 7 oben) empfangen, um die Always Encrypted-Schlüsselmetadaten in der Datenbank zu erstellen und zu verwalten.

Aufgabe Artikel Greift auf Klartextschlüssel zu Greift auf Datenbank zu
Schritt 1. Rufen Sie den Speicherort des Spaltenhauptschlüssels und des verschlüsselten Werts des Spaltenverschlüsselungsschlüssels von Ihrem Sicherheitsadministrator ab. Sehen Sie sich die Beispiele am Ende des Artikels an. Nein Nein
Schritt 2. Starten Sie eine PowerShell-Umgebung, und importieren Sie das SqlServer-Modul. Konfigurieren von "Immer verschlüsselt" mithilfe von PowerShell Nein Nein
Schritt 3. Stellen Sie eine Verbindung mit Ihrem Server und Ihrer Datenbank her. Herstellen einer Verbindung mit einer Datenbank Nein Ja
Schritt 4. Erstellen Sie ein SqlColumnMasterKeySettings-Objekt, dass Informationen über den Speicherort Ihres Spaltenhauptschlüssels enthält. SqlColumnMasterKeySettings ist ein Objekt, das im Arbeitsspeicher vorhanden ist. New-SqlColumnMasterKeySettings Nein Nein
Schritt 5. Erstellen Sie die Metadaten über den Spaltenhauptschlüssel in Ihrer Datenbank.

Anmerkung: Wir überprüfen nicht die Gültigkeit der Schlüssel oder Zertifikate, die zum Generieren des Spaltenmasterschlüssels verwendet werden.
New-SqlColumnMasterKey
Hinweis: Intern führt das Cmdlet die CREATE COLUMN MASTER KEY (Transact-SQL)-Anweisung aus, um Metadaten für den Spaltenhauptschlüssel zu erstellen.
Nein Ja
Schritt 6. Erstellen Sie die Spaltenverschlüsselungsschlüssel-Metadaten in der Datenbank. New-SqlColumnEncryptionKey
Anmerkung: DBAs verwenden eine Variation des Cmdlets, das nur Spaltenverschlüsselungsschlüsselmetadaten erstellt.
Intern gibt das Cmdlet die Anweisung CREATE COLUMN ENCRYPTION KEY (Transact-SQL) aus, um Metadaten für Spaltenverschlüsselungsschlüssel zu erstellen.
Nein Ja

Windows-Zertifikatspeicher mit Rollentrennung (Beispiel)

Sicherheitsadministrator

[CmdletBinding()]
param(
	[Parameter(Mandatory = $false)]
	[string]$ServerName = '<server name>',

	[Parameter(Mandatory = $false)]
	[ValidateNotNullOrEmpty()]
	[string]$DatabaseName = '<database name>',

	[Parameter(Mandatory = $false)]
	[string]$CertificateSubject = 'AlwaysEncryptedCert',

	[Parameter(Mandatory = $false)]
	[string]$CmkName = 'CMK1',

	[Parameter(Mandatory = $false)]
	[string]$CekName = 'CEK1',

	[Parameter(Mandatory = $false)]
	[string]$ExportKeyDataPath = 'C:\temp\keydata.txt'
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

Import-Module SqlServer -MinimumVersion 22.0.50 -ErrorAction Stop

Write-Host "[AE] Finding certificate '$CertificateSubject' in CurrentUser\\My"
$cert = Get-ChildItem -Path 'Cert:CurrentUser\My' |
	Where-Object { $_.Subject -eq "CN=$CertificateSubject" } |
	Sort-Object NotAfter -Descending |
	Select-Object -First 1

if (-not $cert) {
	Write-Host '[AE] Certificate not found. Creating a new self-signed certificate.'
	$cert = New-SelfSignedCertificate `
		-Subject $CertificateSubject `
		-CertStoreLocation 'Cert:CurrentUser\My' `
		-KeyExportPolicy Exportable `
		-Type DocumentEncryptionCert `
		-KeyUsage DataEncipherment `
		-KeySpec KeyExchange
}

Write-Host "[AE] Connecting to SQL Server '$ServerName' / Database '$DatabaseName'"
$connStr = "Server=$ServerName;Database=$DatabaseName;Integrated Security=True;Encrypt=True;TrustServerCertificate=True;Connection Timeout=30"

try {
	$database = Get-SqlDatabase -ConnectionString $connStr -ErrorAction Stop
}
catch {
	Write-Error "Failed to connect to '$ServerName' / '$DatabaseName'. Verify instance name, database, and local permissions."
	throw
}

Write-Host '[AE] Building CMK settings from certificate'
$cmkSettings = New-SqlCertificateStoreColumnMasterKeySettings -CertificateStoreLocation 'CurrentUser' -Thumbprint $cert.Thumbprint

Write-Host "[AE] Ensuring CMK '$CmkName' exists"
$existingCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $CmkName }
if (-not $existingCmk) {
	New-SqlColumnMasterKey -Name $CmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings | Out-Null
}

Write-Host "[AE] Ensuring CEK '$CekName' exists"
$existingCek = Get-SqlColumnEncryptionKey -InputObject $database | Where-Object { $_.Name -eq $CekName }
if (-not $existingCek) {
	New-SqlColumnEncryptionKey -Name $CekName -InputObject $database -ColumnMasterKey $CmkName | Out-Null
}

if ($ExportKeyDataPath) {
	Write-Host "[AE] Exporting key metadata to '$ExportKeyDataPath'"
	$encryptedValue = New-SqlColumnEncryptionKeyEncryptedValue -TargetColumnMasterKeySettings $cmkSettings
	"KeyStoreProviderName,KeyPath,EncryptedValue" | Set-Content -Path $ExportKeyDataPath -Encoding UTF8
	"$($cmkSettings.KeyStoreProviderName),$($cmkSettings.KeyPath),$encryptedValue" | Add-Content -Path $ExportKeyDataPath -Encoding UTF8
}

Write-Host 'Completed successfully'

DBA

[CmdletBinding()]
param(
	[Parameter(Mandatory = $false)]
	[string]$ServerName = 'localhost\SQL2025',

	[Parameter(Mandatory = $false)]
	[ValidateNotNullOrEmpty()]
	[string]$DatabaseName = 'AdventureWorks2025',

	[Parameter(Mandatory = $false)]
	[ValidateNotNullOrEmpty()]
	[string]$KeyDataFile = 'C:\temp\keydata.txt',

	[Parameter(Mandatory = $false)]
	[ValidateNotNullOrEmpty()]
	[string]$CmkName = 'CMK1',

	[Parameter(Mandatory = $false)]
	[ValidateNotNullOrEmpty()]
	[string]$CekName = 'CEK1'
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

Import-Module SqlServer -MinimumVersion 22.0.50 -ErrorAction Stop

if (-not (Test-Path -Path $KeyDataFile -PathType Leaf)) {
	throw "Key data file not found: $KeyDataFile"
}

Write-Host "[AE] Loading key metadata from '$KeyDataFile'"
$keyData = Import-Csv -Path $KeyDataFile
if (-not $keyData) {
	throw "Key data file '$KeyDataFile' is empty."
}

$keyDataRow = $keyData | Select-Object -First 1
if (-not $keyDataRow.KeyStoreProviderName -or -not $keyDataRow.KeyPath -or -not $keyDataRow.EncryptedValue) {
	throw "Key data file must include non-empty columns: KeyStoreProviderName, KeyPath, EncryptedValue."
}

Write-Host "[AE] Connecting to SQL Server '$ServerName' / Database '$DatabaseName'"
$connStr = "Server=$ServerName;Database=$DatabaseName;Integrated Security=True;Encrypt=True;TrustServerCertificate=True;Connection Timeout=30"

try {
	$database = Get-SqlDatabase -ConnectionString $connStr -ErrorAction Stop
}
catch {
	Write-Error "Failed to connect to '$ServerName' / '$DatabaseName'. Verify instance name, database, and local permissions."
	throw
}

Write-Host "[AE] Building CMK settings for provider '$($keyDataRow.KeyStoreProviderName)'"
$cmkSettings = New-SqlColumnMasterKeySettings -KeyStoreProviderName $keyDataRow.KeyStoreProviderName -KeyPath $keyDataRow.KeyPath

Write-Host "[AE] Ensuring CMK '$CmkName' exists"
$existingCmk = Get-SqlColumnMasterKey -InputObject $database | Where-Object { $_.Name -eq $CmkName }
if (-not $existingCmk) {
	New-SqlColumnMasterKey -Name $CmkName -InputObject $database -ColumnMasterKeySettings $cmkSettings | Out-Null
}

Write-Host "[AE] Ensuring CEK '$CekName' exists"
$existingCek = Get-SqlColumnEncryptionKey -InputObject $database | Where-Object { $_.Name -eq $CekName }
if (-not $existingCek) {
	New-SqlColumnEncryptionKey -Name $CekName -InputObject $database -ColumnMasterKey $CmkName -EncryptedValue $keyDataRow.EncryptedValue | Out-Null
}

Write-Host 'Completed successfully'