Menyediakan kunci Always Encrypted menggunakan PowerShell

Berlaku untuk: SQL ServerAzure SQL DatabaseAzure SQL Managed Instance

Artikel ini menyediakan langkah-langkah untuk menyediakan kunci untuk Always Encrypted menggunakan modul SqlServer PowerShell. Anda dapat menggunakan PowerShell untuk menyediakan kunci Always Encrypted baik dengan maupun tanpa pemisahan peran, memberikan kontrol atas siapa yang memiliki akses ke kunci enkripsi yang sebenarnya di penyimpanan kunci dan siapa yang memiliki akses ke database.

Note

Microsoft merekomendasikan penggunaan PowerShell 7 atau yang lebih baru saat menjalankan skrip Always Encrypted PowerShell. PowerShell 7 memberikan dukungan lintas platform yang ditingkatkan, performa yang lebih baik, dan kompatibilitas terbaru dengan modul SqlServer (v22+), yang diperlukan untuk banyak skenario Always Encrypted.

Untuk gambaran umum manajemen kunci Always Encrypted, termasuk beberapa rekomendasi praktik terbaik tingkat tinggi, lihat Gambaran Umum manajemen kunci untuk Always Encrypted. Untuk informasi tentang cara mulai menggunakan modul SqlServer PowerShell untuk Always Encrypted, lihat Mengonfigurasi Always Encrypted menggunakan PowerShell.

Penyediaan Kunci tanpa pemisahan peran

Metode provisi utama yang dijelaskan di bagian ini tidak mendukung pemisahan peran antara administrator keamanan dan DBA. Beberapa langkah di bagian ini menggabungkan operasi pada kunci fisik dengan operasi pada metadata kunci. Oleh karena itu, gunakan metode penyediaan kunci ini jika organisasi Anda menggunakan model DevOps, atau jika database dihosting di cloud dan tujuan utamanya adalah membatasi administrator cloud (tetapi bukan DBA lokal) agar tidak mengakses data sensitif. Jangan gunakan metode ini jika pihak yang berpotensi menjadi ancaman mencakup administrator basis data (DBA), atau jika administrator basis data (DBA) tidak boleh memiliki akses ke data sensitif.

Sebelum menjalankan langkah apa pun yang melibatkan akses ke kunci teks biasa atau penyimpanan kunci (diidentifikasi di kolom Kunci teks biasa/penyimpanan kunci Accesses dalam tabel berikut), pastikan lingkungan PowerShell berjalan pada komputer aman yang berbeda dari komputer yang menghosting database Anda. Untuk informasi lebih lanjut, lihat Pertimbangan Keamanan untuk Manajemen Kunci.

Tugas Artikel Mengakses kunci teks biasa/penyimpanan kunci Mengakses database
Langkah 1. Buat kunci induk kolom di toko kunci.

Catatan: Modul SqlServer PowerShell tidak mendukung langkah ini. Untuk menyelesaikan tugas ini dari baris perintah, gunakan alat yang khusus untuk penyimpanan kunci yang Anda pilih.
Membuat dan menyimpan kunci master kolom untuk Always Encrypted Ya Tidak
Langkah 2. Mulai lingkungan PowerShell dan impor modul PowerShell SqlServer. Mengonfigurasi Always Encrypted menggunakan PowerShell Tidak Tidak
Langkah 3. Sambungkan ke server dan database Anda. Menyambungkan ke database Tidak Ya
Langkah 4. Buat objek SqlColumnMasterKeySettings yang berisi informasi tentang lokasi kunci master kolom Anda. SqlColumnMasterKeySettings adalah objek yang ada dalam memori (di PowerShell). Gunakan cmdlet yang khusus untuk penyimpanan kunci Anda. Pengaturan Kunci Utama Kolom pada Key Vault Sql Azure Baru

Pengaturan Kunci Induk Kolom Simpanan Sertifikat SQL Baru

New-SqlCngColumnMasterKeySettings

New-SqlCspColumnMasterKeySettings
Tidak Tidak
Langkah 5. Buat metadata tentang kunci master kolom di database Anda.

Nota: Kami tidak memverifikasi validitas kunci atau sertifikat yang digunakan untuk menghasilkan kunci master kolom.
[New-SqlColumnMasterKey](/powershell/sqlserver/sqlserver/vlatest/new-sqlcolumnmasterkey)

Catatan: di bawah sampul, cmdlet mengeluarkan CREATE COLUMN MASTER KEY pernyataan untuk membuat metadata kunci.
Tidak Ya
Langkah 6. Autentikasi ke Azure, jika kunci master kolom Anda disimpan di Azure Key Vault. Connect-AzAccount Ya Tidak
Langkah 7. Dapatkan token akses untuk Azure Key Vault, jika kunci master kolom Anda disimpan di Azure Key Vault. Get-AzAccessToken Tidak Tidak
Langkah 8. Buat kunci enkripsi kolom baru, enkripsikan dengan kunci master kolom dan buat metadata kunci enkripsi kolom dalam database. New-SqlColumnEncryptionKey

Catatan: Gunakan variasi cmdlet yang secara internal menghasilkan dan mengenkripsi kunci enkripsi kolom.

Catatan: Di bawah sampul, cmdlet mengeluarkan CREATE COLUMN ENCRYPTION KEY pernyataan untuk membuat metadata kunci.
Ya Ya

Penyimpanan Sertifikat Windows tanpa pemisahan peran (contoh)

Skrip ini adalah contoh end-to-end untuk membuat kunci master kolom yang merupakan sertifikat di Penyimpanan Sertifikat Windows, menghasilkan dan mengenkripsi kunci enkripsi kolom, dan membuat metadata kunci dalam database SQL Server.

[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 tanpa pemisahan fungsi (contoh)

Skrip ini adalah contoh end-to-end untuk menyediakan dan mengonfigurasi brankas kunci di Azure Key Vault, menghasilkan kunci master kolom di vault, menghasilkan dan mengenkripsi kunci enkripsi kolom, dan membuat metadata kunci dalam database Azure SQL.

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 tanpa pemisahan peran (contoh)

Skrip di bawah ini adalah contoh end-to-end untuk menghasilkan kunci master kolom di penyimpanan kunci yang mengimplementasikan Cryptography Next Generation API (CNG), menghasilkan dan mengenkripsi kunci enkripsi kolom, dan membuat metadata kunci dalam database SQL Server.

Contohnya menggunakan penyimpanan kunci yang menggunakan Penyedia Penyimpanan Kunci Perangkat Lunak Microsoft. Anda dapat memilih untuk mengubah contoh untuk menggunakan penyimpanan lain, seperti modul keamanan perangkat keras Anda. Untuk itu, Anda harus memastikan penyedia penyimpanan kunci (KSP) yang mengimplementasikan CNG untuk perangkat Anda terpasang dengan benar pada mesin Anda. Anda harus mengganti Microsoft Software Key Storage Provider dengan nama KSP perangkat Anda.

[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"

Provisi kunci dengan pemisahan peran

Bagian ini menyediakan langkah-langkah untuk mengonfigurasi enkripsi di mana administrator keamanan tidak memiliki akses ke database, dan administrator database tidak memiliki akses ke penyimpanan kunci atau kunci teks biasa.

Administrator Keamanan

Sebelum menjalankan langkah apa pun yang melibatkan akses ke kunci teks biasa atau penyimpanan kunci (diidentifikasi di kolom Kunci teks biasa/penyimpanan kunci Accesses dalam tabel berikut), pastikan bahwa:

  • Lingkungan PowerShell berjalan pada komputer aman yang berbeda dari komputer yang menghosting database Anda.
  • DBA di organisasi Anda tidak memiliki akses ke komputer (yang akan mengalahkan tujuan pemisahan peran).

Untuk informasi lebih lanjut, lihat Pertimbangan Keamanan untuk Manajemen Kunci.

Tugas Artikel Mengakses kunci teks biasa/penyimpanan kunci Mengakses database
Langkah 1. Buat kunci induk kolom di toko kunci.

Catatan: Modul SqlServer tidak mendukung langkah ini. Untuk menyelesaikan tugas ini dari baris perintah, Anda perlu menggunakan alat yang spesifik dengan jenis penyimpanan kunci Anda.
Membuat dan menyimpan kunci master kolom untuk Always Encrypted Ya Tidak
Langkah 2. Mulai sesi PowerShell dan impor modul SqlServer. Mengimpor modul SqlServer Tidak Tidak
Langkah 3. Buat objek SqlColumnMasterKeySettings yang berisi informasi tentang lokasi kunci master kolom Anda. SqlColumnMasterKeySettings adalah objek yang ada dalam memori (di PowerShell). Gunakan cmdlet yang khusus untuk penyimpanan kunci Anda. Pengaturan Kunci Utama Kolom pada Key Vault Sql Azure Baru

Pengaturan Kunci Induk Kolom Simpanan Sertifikat SQL Baru

New-SqlCngColumnMasterKeySettings

New-SqlCspColumnMasterKeySettings
Tidak Tidak
Langkah 4. Autentikasi ke Azure, jika kunci master kolom Anda disimpan di Azure Key Vault. Connect-AzAccount Ya Tidak
Langkah 5. Dapatkan token akses untuk Azure Key Vault, jika kunci master kolom Anda disimpan di Azure Key Vault. Get-AzAccessToken Tidak Tidak
Langkah 6. Buat kunci enkripsi kolom, enkripsi dengan kunci master kolom untuk menghasilkan nilai terenkripsi kunci enkripsi kolom. Nilai terenkripsi kunci enkripsi kolom SQL baru Ya Tidak
Langkah 7. Berikan lokasi kunci master kolom (nama penyedia dan jalur kunci master kolom) serta nilai terenkripsi dari kunci enkripsi kolom kepada DBA. Lihat contoh di akhir artikel. Tidak Tidak

DBA

DBA menggunakan informasi yang mereka terima dari Admin Keamanan (langkah 7 di atas) untuk membuat dan mengelola metadata kunci Always Encrypted dalam database.

Tugas Artikel Mengakses kunci teks biasa Mengakses database
Langkah 1. Dapatkan lokasi kunci master kolom dan nilai terenkripsi kunci enkripsi kolom dari Administrator Keamanan Anda. Lihat contoh di akhir artikel. Tidak Tidak
Langkah 2. Mulai lingkungan PowerShell dan impor modul SqlServer. Mengonfigurasi Always Encrypted menggunakan PowerShell Tidak Tidak
Langkah 3. Sambungkan ke server Anda dan ke sebuah database. Menyambungkan ke database Tidak Ya
Langkah 4. Buat objek SqlColumnMasterKeySettings yang berisi informasi tentang lokasi kunci master kolom Anda. SqlColumnMasterKeySettings adalah objek yang ada dalam memori. PengaturanKunciUtamaKolomSql-Baru Tidak Tidak
Langkah 5. Buat metadata tentang kunci master kolom di database Anda.

Nota: Kami tidak memverifikasi validitas kunci atau sertifikat yang digunakan untuk menghasilkan kunci master kolom.
New-SqlColumnMasterKey
Catatan: di balik layar, cmdlet menjalankan pernyataan CREATE COLUMN MASTER KEY (Transact-SQL) untuk membuat metadata untuk kunci master kolom.
Tidak Ya
Langkah 6. Buat metadata kunci enkripsi kolom dalam database. New-SqlColumnEncryptionKey
Catatan: DBA menggunakan variasi cmdlet yang hanya membuat metadata kunci enkripsi kolom.
Di balik layar, cmdlet mengeluarkan pernyataan CREATE COLUMN ENCRYPTION KEY (Transact-SQL) untuk membuat metadata kunci enkripsi kolom.
Tidak Ya

Penyimpanan Sertifikat Windows dengan pemisahan peran (contoh)

Administrator Keamanan

[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'