Подготовка ключей Always Encrypted с помощью PowerShell

Область применения: SQL Server База данных SQL Azure Управляемый экземпляр SQL Azure

В этой статье описаны действия по подготовке ключей постоянного шифрования с помощью модуля SqlServer PowerShell. PowerShell можно использовать для подготовки ключей постоянного шифрования с разделением ролей и без разделения ролейс управлением пользователями, имеющими доступ к фактическим ключам шифрования в хранилище ключей и доступ к базе данных.

Примечание.

Microsoft рекомендует использовать PowerShell 7 или более поздней версии при выполнении скриптов PowerShell always Encrypted. PowerShell 7 обеспечивает улучшенную кроссплатформенную поддержку, более высокую производительность и последнюю совместимость с модулем SqlServer (версии 22+), который необходим для многих сценариев Always Encrypted.

Обзор процесса управления ключами Always Encrypted, включая некоторые общие рекомендации, см. в разделе Общие сведения об управлении ключами для Always Encrypted. Сведения о начале работы с модулем SqlServer PowerShell для постоянного шифрования см. в разделе Configure Always Encrypted using PowerShell(Настройка постоянного шифрования с помощью PowerShell).

Подготовка ключей без разделения ролей

Метод подготовки ключей, описанный в этом разделе, не поддерживает разделение ролей между администраторами безопасности и базами данных. Некоторые действия, описанные в этом разделе, объединяют операции с физическими ключами с операциями с метаданными ключей. Поэтому используйте этот метод подготовки ключей, если ваша организация использует модель DevOps или если база данных размещена в облаке, а основная цель — ограничить доступ к конфиденциальным данным администраторам облака (но не локальным базам данных). Не используйте этот метод, если среди потенциальных злоумышленников могут быть администраторы баз данных или если у администраторов баз данных не должно быть доступа к конфиденциальным данным.

Перед выполнением любых шагов, которые включают доступ к ключам в виде открытого текста или хранилищу ключей (что указано в столбце Accesses plaintext keys/key store в следующей таблице), убедитесь, что среда PowerShell работает на защищённом компьютере, отличном от компьютера, на котором размещена база данных. Дополнительные сведения см. в разделе Security Considerations for Key Management(Вопросы безопасности для управления ключами).

Задача Статья Доступ к ключам с открытым текстом или хранилищу ключей Доступ к базе данных
Шаг 1. Создание главного ключа столбца в хранилище ключей.

Примечание. Модуль SqlServer PowerShell не поддерживает этот шаг. Для выполнения этой задачи из командной строки используйте средства, поддерживаемые выбранным хранилищем ключей.
Создавайте и храните основные ключи столбцов для Always Encrypted Да Нет
Шаг 2. Запуск среды PowerShell и импорт модуля SqlServer. Настройка постоянного шифрования с помощью PowerShell Нет Нет
Шаг 3. Соединение с сервером и базой данных. Соединение с базой данных Нет Да
Шаг 4. Создание объекта SqlColumnMasterKeySettings , содержащего сведения о расположении главного ключа столбца. SqlColumnMasterKeySettings — это объект, который существует в памяти (PowerShell). Используйте командлет, специфичный для вашего хранилища ключей. New-SqlAzureKeyVaultColumnMasterKeySettings

Новые настройки мастер-ключа столбца SqlCertificateStore

Новая настройка ключа мастер-колонки SqlCng

New-SqlCspColumnMasterKeySettings
Нет Нет
Шаг 5. Создание метаданных о главном ключе столбца в базе данных.

Заметка: Мы не проверяем допустимость ключей или сертификатов, используемых для создания главного ключа столбца.
[New-SqlColumnMasterKey](/powershell/sqlserver/sqlserver/vlatest/new-sqlcolumnmasterkey

Примечание. При этом командлет выдает CREATE COLUMN MASTER KEY инструкцию для создания ключевых метаданных.
Нет Да
Шаг 6. Авторизуйтесь в Azure, если ваш главный ключ столбца хранится в хранилище ключей Azure. Connect-AzAccount Да Нет
Шаг 7. Получите токен доступа для Azure Key Vaults, если главный ключ столбца хранится в Azure Key Vault. Get-AzAccessToken Нет Нет
Шаг 8. Создайте новый ключ шифрования столбца, зашифруйте его с помощью главного ключа столбца и создайте метаданные ключа шифрования столбца в базе данных. New-SqlColumnEncryptionKey

Примечание. Используйте разновидность командлета, который внутренним образом создает и шифрует ключ шифрования столбца.

Примечание. В этой статье командлет выдает 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 Key Vault без разделения ролей (например)

Этот скрипт является полным примером подготовки и настройки хранилища ключей в Azure Key Vault, создания основного ключа столбца в хранилище, создания и шифрования ключа шифрования столбца, а также создания метаданных ключа в базе данных 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.

В этом примере используется хранилище ключей, использующее поставщика хранилища ключей программного обеспечения Майкрософт. Вы можете изменить пример, чтобы использовать другое хранилище, например аппаратный модуль безопасности. Чтобы это сделать, убедитесь, что на вашем компьютере установлен и корректно работает поставщик хранилища ключей (KSP), реализующий CNG для вашего устройства. Необходимо заменить 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 plaintext keys/key store в следующей таблице), убедитесь, что:

  • Среда PowerShell используется на защищенном компьютере (и не на компьютере с базой данных).
  • Администраторы баз данных в вашей организации не имеют доступа к компьютеру (что противоречило бы концепции разделения ролей).

Дополнительные сведения см. в разделе Security Considerations for Key Management(Вопросы безопасности для управления ключами).

Задача Статья Доступ к ключам с открытым текстом или хранилищу ключей Доступ к базе данных
Шаг 1. Создание главного ключа столбца в хранилище ключей.

Примечание. Модуль SqlServer не поддерживает этот шаг. Для выполнения этой задачи из командной строки используйте средства, поддерживаемые выбранным хранилищем ключей.
Создавайте и храните основные ключи столбцов для Always Encrypted Да Нет
Шаг 2. Запуск сеанса PowerShell и импорт модуля SqlServer. Импорт модуля SqlServer Нет Нет
Шаг 3. Создание объекта SqlColumnMasterKeySettings , содержащего сведения о расположении главного ключа столбца. SqlColumnMasterKeySettings — это объект, который существует в памяти (PowerShell). Используйте командлет, специфичный для вашего хранилища ключей. New-SqlAzureKeyVaultColumnMasterKeySettings

Новые настройки мастер-ключа столбца SqlCertificateStore

Новая настройка ключа мастер-колонки SqlCng

New-SqlCspColumnMasterKeySettings
Нет Нет
Шаг 4. Авторизуйтесь в Azure, если ваш главный ключ столбца хранится в хранилище ключей Azure. Connect-AzAccount Да Нет
Шаг 5. Получите токен доступа для Azure Key Vaults, если главный ключ столбца хранится в Azure Key Vault. Get-AzAccessToken Нет Нет
Шаг 6. Создайте ключ шифрования столбца, зашифруйте его с помощью главного ключа столбца, чтобы получить зашифрованное значение ключа шифрования столбца. Новый зашифрованный ключ шифрования столбца SQL Да Нет
Шаг 7. Предоставьте администратору баз данных информацию о расположении главного ключа столбца (имя поставщика и путь к главному ключу столбца) и зашифрованное значение ключа шифрования столбца. См. примеры в конце статьи. Нет Нет

Администратор баз данных

Базы данных используют сведения, полученные администратором безопасности (шаг 7 выше), для создания метаданных ключа Always Encrypted в базе данных и управления ими.

Задача Статья Доступ к ключам с открытым текстом Доступ к базе данных
Шаг 1. Получите у администратора безопасности расположение основного ключа столбца и зашифрованное значение ключа шифрования столбца. См. примеры в конце статьи. Нет Нет
Шаг 2. Запуск среды PowerShell и импорт модуля SqlServer. Настройка постоянного шифрования с помощью PowerShell Нет Нет
Шаг 3. Соединение с сервером и базой данных. Соединение с базой данных Нет Да
Шаг 4. Создание объекта SqlColumnMasterKeySettings, содержащего сведения о расположении главного ключа столбца. SqlColumnMasterKeySettings — это объект, который существует в памяти. New-SqlColumnMasterKeySettings Нет Нет
Шаг 5. Создание метаданных о главном ключе столбца в базе данных.

Заметка: Мы не проверяем допустимость ключей или сертификатов, используемых для создания главного ключа столбца.
New-SqlColumnMasterKey
Примечание: на внутреннем уровне командлет выполняет оператор CREATE COLUMN MASTER KEY (Transact-SQL) для создания метаданных главного ключа столбца.
Нет Да
Шаг 6. Создание метаданных ключа шифрования столбца в базе данных. New-SqlColumnEncryptionKey
Примечание. Администраторы баз данных используют разновидность командлета, который создает только метаданные ключа шифрования столбца.
На внутреннем уровне командлет выполняет инструкцию 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'

Администратор баз данных

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