使用 PowerShell 预配 Always Encrypted 密钥

适用于:SQL ServerAzure SQL 数据库Azure SQL 托管实例

本文提供使用 SqlServer PowerShell 模块来预配 Always Encrypted 密钥的步骤。 你可在 使用或不使用角色分隔的情况下使用 PowerShell 预配 Always Encrypted 密钥,控制可访问密钥存储中实际加密密钥的人员和可访问该数据库的人员。

注意

Microsoft建议在运行 Always Encrypted PowerShell 脚本时使用 PowerShell 7 或更高版本。 PowerShell 7 提供了改进的跨平台支持、更好的性能和与 SqlServer 模块(v22+)的最新兼容性,这是许多 Always Encrypted 方案所必需的。

有关 Always Encrypted 密钥管理的概述(包括一些高级最佳做法建议),请参阅 Always Encrypted 密钥管理概述。 有关如何开始将 SqlServer PowerShell 模块用于 Always Encrypted 的信息,请参阅 使用 PowerShell 配置 Always Encrypted

不分离角色的密钥预配

本节中所述的关键预配方法不支持安全管理员和 DBA 之间的角色分离。 本节中的一些步骤将物理键操作与密钥元数据操作结合起来。 因此,如果组织使用 DevOps 模型,或者数据库托管在云中,并且主要目标是限制云管理员(而不是本地 DBA)访问敏感数据,请使用此方法预配密钥。 如果潜在的对手包括 DBA,或者 DBA 不应有权访问敏感数据,请不要使用此方法。

在运行涉及访问纯文本密钥或密钥存储的任何步骤(下表中的 Accesses 纯文本密钥/密钥存储 列中标识)之前,请确保 PowerShell 环境在与托管数据库的计算机不同的安全计算机上运行。 有关详细信息,请参阅 密钥管理的安全注意事项

任务 文章 访问纯文本密钥/密钥存储 访问数据库
步骤 1. 在密钥存储中创建列主密钥。

注意:SqlServer PowerShell 模块不支持这一步。 若要从命令行完成此任务,请使用特定于所选密钥存储的工具。
创建并存储 Always Encrypted 的列主密钥
步骤 2. 启动 PowerShell 环境并导入 SqlServer PowerShell 模块。 使用 PowerShell 配置“始终加密”功能
步骤 3. 连接到服务器和数据库。 连接到数据库
步骤 4. 创建 SqlColumnMasterKeySettings 对象,该对象中包含列主密钥的位置信息。 SqlColumnMasterKeySettings 是存在于内存中的对象(在 PowerShell 中)。 使用适用于你的密钥存储的 cmdlet。 New-SqlAzureKeyVaultColumnMasterKeySettings

New-SqlCertificateStoreColumnMasterKeySettings (新建-SQL证书存储列主密钥设置)

New-SqlCngColumnMasterKeySettings 新建-SqlCng列主密钥设置

New-SqlCspColumnMasterKeySettings
步骤 5。 在数据库中创建有关列主密钥的元数据。

注意: 我们不会验证用于生成列主密钥的密钥或证书的有效性。
[New-SqlColumnMasterKey](/powershell/sqlserver/sqlserver/vlatest/new-sqlcolumnmasterkey

注意: 在幕后,cmdlet 发出 CREATE COLUMN MASTER KEY 语句来创建密钥元数据。
步骤 6。 如果列主密钥存储在 Azure 密钥保管库中,请对 Azure 进行身份验证。 Connect-AzAccount
步骤 7. 如果列主密钥存储在 Azure 密钥保管库中,请获取 Azure 密钥保管库的访问令牌。 Get-AzAccessToken
步骤 8。 生成新的列加密密钥,使用列主密钥对其加密,并在数据库中创建列加密密钥元数据。 New-SqlColumnEncryptionKey

注意: 使用可在内部生成并加密列加密密钥的该 cmdlet 变体。

注意: 在幕后,cmdlet 发出 CREATE COLUMN ENCRYPTION KEY 用于创建密钥元数据的语句。

没有角色分离的 Windows 证书存储(示例)

此脚本是一个端到端示例,用于生成作为 Windows 证书存储中的证书的列主密钥、生成并加密列加密密钥,并在 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 密钥保管库 (示例)

此脚本是一个端到端示例,用于在 Azure 密钥保管库 中预配和配置密钥保管库、在该保管库中生成列主密钥、生成并加密列加密密钥,并在 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(示例)

下面的脚本是一个端到端示例,用于在实现下一代加密技术 API (CNG) 的密钥存储中生成列主密钥、生成并加密列加密密钥,并在 SQL Server 数据库中创建密钥元数据。

该示例利用的密钥存储使用 Microsoft 软件密钥存储提供程序。 你可选择将该示例修改为使用另一存储(例如你的硬件安全模块)。 为此,必须确保在计算机上正确安装为设备实现 CNG 的密钥存储提供程序 (KSP)。 你需要将 Microsoft Software Key Storage Provider 替换为你的设备的 KSP 名称。

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

使用角色分隔的密钥预配

此部分分步介绍了如何配置加密,其中安全管理员无权访问数据库,且数据库管理员无权访问密钥存储或纯文本密钥。

安全管理员

在运行涉及访问纯文本密钥或密钥存储的任何步骤(下表中的 Accesses 纯文本键/密钥存储 列中标识)之前,请确保:

  • PowerShell 环境在一台安全的计算机上运行,该计算机不同于承载您的数据库的计算机。
  • 你组织中的 DBA 没有对计算机的访问权限(这会违背角色分隔的目的)。

有关详细信息,请参阅 密钥管理的安全注意事项

任务 文章 访问纯文本密钥/密钥存储 访问数据库
步骤 1. 在密钥存储中创建列主密钥。

注意:SqlServer 模块不支持这一步。 若要从命令行完成此任务,你需要使用特定于密钥存储类型的工具。
创建并存储 Always Encrypted 的列主密钥
步骤 2. 启动 PowerShell 会话并导入 SqlServer 模块。 导入 SqlServer 模块
步骤 3. 创建 SqlColumnMasterKeySettings 对象,该对象中包含列主密钥的位置信息。 SqlColumnMasterKeySettings 是存在于内存中的对象(在 PowerShell 中)。 使用适用于你的密钥存储的 cmdlet。 New-SqlAzureKeyVaultColumnMasterKeySettings

New-SqlCertificateStoreColumnMasterKeySettings (新建-SQL证书存储列主密钥设置)

New-SqlCngColumnMasterKeySettings 新建-SqlCng列主密钥设置

New-SqlCspColumnMasterKeySettings
步骤 4. 如果列主密钥存储在 Azure 密钥保管库中,请对 Azure 进行身份验证。 Connect-AzAccount
步骤 5。 如果列主密钥存储在 Azure 密钥保管库中,请获取 Azure 密钥保管库的访问令牌。 Get-AzAccessToken
步骤 6。 生成列加密密钥,并使用列主密钥对其进行加密,以生成列加密密钥的加密值。 New-SqlColumnEncryptionKeyEncryptedValue
步骤 7. 向 DBA 提供列主密钥所在的位置(提供程序名称和列主密钥的密钥路径)以及列加密密钥的加密值。 请参阅本文末尾的示例。

数据库管理员 (DBA)

DBA 使用其从安全管理员(上面的步骤 7)接收的信息,在数据库中创建和管理 Always Encrypted 密钥元数据。

任务 文章 访问纯文本密钥 访问数据库
步骤 1. 从安全管理员处获取列主密钥的位置和列加密密钥的加密值。 请参阅本文末尾的示例。
步骤 2. 启动 PowerShell 环境并导入 SqlServer 模块。 使用 PowerShell 配置“始终加密”功能
步骤 3. 连接到服务器和数据库。 连接到数据库
步骤 4. 创建 SqlColumnMasterKeySettings 对象,该对象中包含列主密钥的位置信息。 SqlColumnMasterKeySettings 是存在于内存中的对象。 New-SqlColumnMasterKeySettings
步骤 5。 在数据库中创建有关列主密钥的元数据。

注意: 我们不会验证用于生成列主密钥的密钥或证书的有效性。
New-SqlColumnMasterKey
注意: 在幕后,cmdlet 发出 CREATE COLUMN MASTER KEY 创建列主密钥元数据的 (Transact-SQL) 语句。
步骤 6。 在数据库中创建列加密密钥元数据。 New-SqlColumnEncryptionKey
注意: DBA 使用仅创建列加密密钥元数据的 cmdlet 的变体。
在内部,cmdlet 会发出 CREATE COLUMN ENCRYPTION KEY(Transact-SQL) 语句来创建列加密密钥元数据。

具有角色分离的 Windows 证书存储(示例)

安全管理员

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