CPCertMigrator.psm1

# =============================================================================
# Модуль: CryptoProCertMigrator
# Файл: CPCertMigrator.psm1
# Назначение: PowerShell модуль для экспорта и импорта сертификатов/контейнеров
# CryptoPro CSP для областей LocalMachine и CurrentUser.
# Автор: zeroday
# =============================================================================

# Вспомогательная функция для проверки прав администратора
function Test-AdminRights {
    # Получаем текущего пользователя и проверяем его роль
    $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
    return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

# Вспомогательная функция для валидации PFX файла
function Test-PfxFile {
    param(
        [string]$FilePath,        # Путь к PFX файлу
        [SecureString]$Password   # Пароль для расшифровки (защищенная строка)
    )
    
    try {
        # Пытаемся открыть PFX файл с указанным паролем
        $null = Get-PfxCertificate -FilePath $FilePath -Password $Password
        return $true
    }
    catch {
        # Если не удалось открыть - файл поврежден или неверный пароль
        return $false
    }
}

# Вспомогательная функция для генерации умных имен файлов
function Get-SmartFileName {
    param(
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate
    )
    
    # Извлекаем CN из Subject и очищаем от недопустимых символов
    $subject = $Certificate.Subject -replace 'CN=([^,]+).*', '$1' -replace '[\\\/:\*\?\"<>|]', '_'
    # Берем первые 8 символов серийного номера для уникальности
    $serial = $Certificate.SerialNumber.Substring(0, [Math]::Min(8, $Certificate.SerialNumber.Length))
    
    # Если Subject пустой, используем только серийный номер
    if ([string]::IsNullOrEmpty($subject)) {
        return "Cert_$serial"
    }
    
    # Возвращаем комбинацию Subject и серийного номера
    return "${subject}_${serial}"
}

function Export-CryptoProCertificates {
    <#
    .SYNOPSIS
    Экспортирует сертификаты CryptoPro CSP в PFX файлы.
 
    .DESCRIPTION
    Функция экспортирует сертификаты из указанного хранилища (CurrentUser или LocalMachine)
    в защищенные паролем PFX файлы. Поддерживает фильтрацию по различным критериям и
    создает подробные логи операций.
 
    .PARAMETER Scope
    Область хранилища сертификатов: CurrentUser или LocalMachine.
    Для LocalMachine требуются права администратора.
 
    .PARAMETER ExportFolder
    Путь к папке для сохранения экспортированных PFX файлов.
    Папка будет создана автоматически, если не существует.
 
    .PARAMETER Password
    Пароль для защиты экспортируемых PFX файлов.
 
    .PARAMETER MinDaysRemaining
    Минимальное количество дней до истечения сертификата.
    По умолчанию 0 (экспортируются все сертификаты).
 
    .PARAMETER SubjectFilter
    Фильтр по полю Subject сертификата (поддерживает wildcards).
 
    .PARAMETER IssuerFilter
    Фильтр по полю Issuer сертификата (поддерживает wildcards).
 
    .PARAMETER WhatIf
    Режим предварительного просмотра без выполнения операций.
 
    .PARAMETER ShowProgress
    Показывать индикатор прогресса выполнения.
 
    .EXAMPLE
    Export-CryptoProCertificates -Scope CurrentUser -ExportFolder "C:\CertBackup" -Password "MySecurePassword"
     
    Экспортирует все сертификаты из пользовательского хранилища.
 
    .EXAMPLE
    Export-CryptoProCertificates -Scope CurrentUser -ExportFolder "C:\Backup" -Password "Pass123" -SubjectFilter "MyOrg" -MinDaysRemaining 30 -ShowProgress
     
    Экспортирует сертификаты организации MyOrg, действующие более 30 дней, с индикатором прогресса.
 
    .EXAMPLE
    Export-CryptoProCertificates -Scope LocalMachine -ExportFolder "C:\Backup" -Password "Pass123" -WhatIf
     
    Предварительный просмотр экспорта из машинного хранилища без выполнения операций.
 
    .NOTES
    Требует установленный CryptoPro CSP.
    Для работы с LocalMachine необходимы права администратора.
    Создает лог файл ExportPfxLog.csv в папке экспорта.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet("LocalMachine", "CurrentUser")]
        [string] $Scope,

        [Parameter(Mandatory = $true)]
        [string] $ExportFolder,

        [Parameter(Mandatory = $true)]
        [SecureString] $Password,

        [Parameter()]
        [int] $MinDaysRemaining = 0,

        [Parameter()]
        [string] $SubjectFilter = "",

        [Parameter()]
        [string] $IssuerFilter = "",

        [Parameter()]
        [switch] $ShowProgress
    )

    Write-Verbose "Начинаем операцию экспорта сертификатов"
    Write-Verbose "Область: $Scope, Папка экспорта: $ExportFolder, Мин. дней до истечения: $MinDaysRemaining"
    
    # Проверяем параметры
    if ($null -eq $Password -or $Password.Length -eq 0) {
        throw "Пароль не может быть пустым"
    }
    
    # Для SecureString проверяем длину через преобразование
    $passwordText = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password))
    if ($passwordText.Length -lt 4) {
        Write-Warning "Пароль очень короткий. Рекомендуется использовать более надежный пароль."
    }
    # Очищаем временную переменную с паролем
    $passwordText = $null

    # Проверяем права администратора для области LocalMachine
    if ($Scope -eq "LocalMachine" -and -not (Test-AdminRights)) {
        $errorMsg = "Для работы с областью LocalMachine требуются права администратора. Запустите PowerShell от имени администратора."
        Write-Error $errorMsg -Category PermissionDenied -ErrorAction Stop
    }

    # Проверяем путь к хранилищу сертификатов
    $storePath = "Cert:\$Scope\My"
    Write-Verbose "Путь к хранилищу сертификатов: $storePath"
    
    if (-not (Test-Path $storePath)) {
        $errorMsg = "Путь к хранилищу сертификатов '$storePath' не найден. Убедитесь, что CryptoPro CSP установлен."
        Write-Error $errorMsg -Category ObjectNotFound -ErrorAction Stop
    }

    # Создаем папку для экспорта при необходимости
    if (-not $WhatIfPreference) {
        if (-not (Test-Path $ExportFolder)) {
            try {
                Write-Verbose "Создаем папку для экспорта: $ExportFolder"
                New-Item -ItemType Directory -Path $ExportFolder -Force | Out-Null
                Write-Verbose "Папка для экспорта успешно создана"
            }
            catch {
                $errorMsg = "Не удалось создать папку для экспорта '$ExportFolder': $($_.Exception.Message)"
                Write-Error $errorMsg -Category WriteError -ErrorAction Stop
            }
        }
        else {
            Write-Verbose "Папка для экспорта уже существует: $ExportFolder"
        }
    }

    # Получаем сертификаты с применением фильтров
    Write-Verbose "Получаем сертификаты из хранилища..."
    try {
        $allCertificates = Get-ChildItem -Path $storePath -ErrorAction Stop
        Write-Verbose "Найдено $($allCertificates.Count) сертификатов в хранилище"
    }
    catch {
        $errorMsg = "Не удалось получить доступ к хранилищу сертификатов '$storePath': $($_.Exception.Message)"
        Write-Error $errorMsg -Category ReadError -ErrorAction Stop
    }

    # Применяем фильтр по дате
    $certificates = $allCertificates | Where-Object { $_.NotAfter -gt (Get-Date).AddDays($MinDaysRemaining) }
    Write-Verbose "После фильтра по дате (>$MinDaysRemaining дней): $($certificates.Count) сертификатов"

    # Применяем фильтр по Subject
    if ($SubjectFilter) {
        $beforeCount = $certificates.Count
        $certificates = $certificates | Where-Object { $_.Subject -like "*$SubjectFilter*" }
        Write-Verbose "После фильтра по Subject '$SubjectFilter': $($certificates.Count) сертификатов (отфильтровано: $($beforeCount - $certificates.Count))"
    }

    # Применяем фильтр по Issuer
    if ($IssuerFilter) {
        $beforeCount = $certificates.Count
        $certificates = $certificates | Where-Object { $_.Issuer -like "*$IssuerFilter*" }
        Write-Verbose "После фильтра по Issuer '$IssuerFilter': $($certificates.Count) сертификатов (отфильтровано: $($beforeCount - $certificates.Count))"
    }

    $totalCerts = $certificates.Count
    Write-Host "Найдено $totalCerts сертификатов для экспорта" -ForegroundColor Green
    
    if ($totalCerts -eq 0) {
        Write-Warning "Ни один сертификат не соответствует указанным критериям. Операция экспорта отменена."
        return
    }

    if ($WhatIfPreference) {
        Write-Host "Предварительный просмотр: будут экспортированы следующие сертификаты:"
        $certificates | ForEach-Object {
            $smartName = Get-SmartFileName -Certificate $_
            Write-Host " - Субъект: $($_.Subject)"
            Write-Host " Отпечаток: $($_.Thumbprint)"
            Write-Host " Файл: $smartName.pfx"
            Write-Host " Истекает: $($_.NotAfter)"
            Write-Host ""
        }
        return
    }

    # Подготавливаемся к экспорту
    Write-Verbose "Используем защищенный пароль..."
    $pwdSecure = $Password
    
    $logFile = Join-Path -Path $ExportFolder -ChildPath "ExportPfxLog.csv"
    Write-Verbose "Создаем файл журнала: $logFile"
    "Дата_Время,Область,Имя_Контейнера,Отпечаток,Субъект,Путь_Файла,Статус,Детали" | Out-File -FilePath $logFile -Encoding UTF8

    $counter = 0
    $successCount = 0
    $errorCount = 0
    Write-Verbose "Начинаем экспорт $totalCerts сертификатов..."
    $certificates | ForEach-Object {
        $cert = $_
        $counter++
        $thumb = $cert.Thumbprint
        $smartName = Get-SmartFileName -Certificate $cert
        $filePath = Join-Path -Path $ExportFolder -ChildPath ("{0}.pfx" -f $smartName)

        if ($ShowProgress) {
            $percentComplete = [math]::Round(($counter / $totalCerts) * 100)
            Write-Progress -Activity "Экспорт сертификатов" -Status "Обрабатываем $smartName" -PercentComplete $percentComplete
        }

        Write-Verbose "Экспортируем: Область=$Scope, Сертификат=$smartName, Отпечаток=$thumb, Файл=$filePath"

        # Проверяем, существует ли файл с таким именем
        if (Test-Path $filePath) {
            $filePath = Join-Path -Path $ExportFolder -ChildPath ("{0}_{1}.pfx" -f $smartName, $thumb.Substring(0, 8))
        }

        try {
            Write-Verbose "Экспортируем сертификат в: $filePath"
            $cert | Export-PfxCertificate -FilePath $filePath -Password $pwdSecure -Force -ErrorAction Stop
            
            # Проверяем успешность экспорта
            if (Test-Path $filePath) {
                $fileSize = (Get-Item $filePath).Length
                Write-Verbose "Экспорт успешен. Размер файла: $fileSize байт"
                $successCount++
            }
            else {
                throw "PFX файл не был создан"
            }
            
            $line = ("{0},{1},{2},{3},{4},{5},Успешно," -f (Get-Date -Format s), $Scope, $smartName, $thumb, $cert.Subject, $filePath)
            $line | Out-File -FilePath $logFile -Append -Encoding UTF8 -ErrorAction SilentlyContinue
        }
        catch {
            $errorCount++
            $detail = $_.Exception.Message.Replace(",", ";")
            $line = ("{0},{1},{2},{3},{4},{5},Ошибка,{6}" -f (Get-Date -Format s), $Scope, $smartName, $thumb, $cert.Subject, $filePath, $detail)
            $line | Out-File -FilePath $logFile -Append -Encoding UTF8 -ErrorAction SilentlyContinue
            
            Write-Warning "Не удалось экспортировать сертификат '$smartName'"
            Write-Verbose "Детали ошибки экспорта: $($_.Exception.Message)"
            
            # Дополнительный контекст ошибки
            if ($_.Exception.Message -like "*access*denied*" -or $_.Exception.Message -like "*доступ*запрещен*") {
                Write-Verbose "Возможная причина: Недостаточно прав или сертификат не экспортируемый"
            }
            elseif ($_.Exception.Message -like "*password*" -or $_.Exception.Message -like "*пароль*") {
                Write-Verbose "Возможная причина: Не соблюдены требования к сложности пароля"
            }
        }
    }

    if ($ShowProgress) {
        Write-Progress -Activity "Экспорт сертификатов" -Completed
    }

    # Итоговая сводка
    Write-Verbose "Операция экспорта завершена"
    Write-Verbose "Всего обработано: $counter сертификатов"
    Write-Verbose "Успешных экспортов: $successCount"
    Write-Verbose "Неудачных экспортов: $errorCount"
    Write-Verbose "Файл журнала: $logFile"
    
    if ($errorCount -eq 0) {
        Write-Host "✅ Экспорт успешно завершен! Экспортировано $successCount сертификатов." -ForegroundColor Green
    }
    else {
        Write-Host "⚠️ Экспорт завершен с проблемами. Успешно: $successCount, Ошибок: $errorCount" -ForegroundColor Yellow
    }
    
    Write-Host "📄 Файл журнала: $logFile" -ForegroundColor Gray
}

function Import-CryptoProCertificates {
    <#
    .SYNOPSIS
    Импортирует сертификаты CryptoPro CSP из PFX файлов.
 
    .DESCRIPTION
    Функция импортирует сертификаты из PFX файлов в указанное хранилище (CurrentUser или LocalMachine).
    Поддерживает валидацию файлов, проверку дубликатов и создает подробные логи операций.
 
    .PARAMETER Scope
    Область хранилища сертификатов: CurrentUser или LocalMachine.
    Для LocalMachine требуются права администратора.
 
    .PARAMETER ImportFolder
    Путь к папке с PFX файлами для импорта.
 
    .PARAMETER Password
    Пароль для расшифровки PFX файлов.
 
    .PARAMETER WhatIf
    Режим предварительного просмотра без выполнения операций.
 
    .PARAMETER ShowProgress
    Показывать индикатор прогресса выполнения.
 
    .PARAMETER SkipExisting
    Пропускать сертификаты, которые уже существуют в хранилище.
 
    .EXAMPLE
    Import-CryptoProCertificates -Scope CurrentUser -ImportFolder "C:\CertBackup" -Password "MySecurePassword"
     
    Импортирует все PFX файлы из папки в пользовательское хранилище.
 
    .EXAMPLE
    Import-CryptoProCertificates -Scope LocalMachine -ImportFolder "C:\Backup" -Password "Pass123" -SkipExisting -ShowProgress
     
    Импортирует сертификаты в машинное хранилище, пропуская существующие, с индикатором прогресса.
 
    .EXAMPLE
    Import-CryptoProCertificates -Scope CurrentUser -ImportFolder "C:\Backup" -Password "Pass123" -WhatIf
     
    Предварительный просмотр импорта без выполнения операций.
 
    .NOTES
    Требует установленный CryptoPro CSP.
    Для работы с LocalMachine необходимы права администратора.
    Создает лог файл ImportPfxLog.csv в папке импорта.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet("LocalMachine", "CurrentUser")]
        [string] $Scope,

        [Parameter(Mandatory = $true)]
        [string] $ImportFolder,

        [Parameter(Mandatory = $true)]
        [SecureString] $Password,

        [Parameter()]
        [switch] $ShowProgress,

        [Parameter()]
        [switch] $SkipExisting
    )

    # Проверяем права администратора для области LocalMachine
    if ($Scope -eq "LocalMachine" -and -not (Test-AdminRights)) {
        throw "Для работы с областью LocalMachine требуются права администратора"
    }

    $targetStore = "Cert:\$Scope\My"
    
    # Получаем PFX файлы
    $pfxFiles = Get-ChildItem -Path $ImportFolder -Filter *.pfx
    $totalFiles = $pfxFiles.Count
    
    Write-Host "Найдено $totalFiles PFX файлов для импорта"

    if ($totalFiles -eq 0) {
        Write-Warning "PFX файлы не найдены в папке $ImportFolder"
        return
    }

    # Сначала проверяем файлы
    Write-Host "Проверяем PFX файлы..."
    $validFiles = @()
    $pfxFiles | ForEach-Object {
        if (Test-PfxFile -FilePath $_.FullName -Password $Password) {
            $validFiles += $_
        }
        else {
            Write-Warning "Неверный PFX файл или неправильный пароль: $($_.Name)"
        }
    }

    Write-Host "Корректных файлов: $($validFiles.Count) из $totalFiles"

    if ($WhatIfPreference) {
        Write-Host "Предварительный просмотр: будут импортированы следующие файлы:"
        $validFiles | ForEach-Object {
            Write-Host " - Файл: $($_.Name)"
            Write-Host " Размер: $([math]::Round($_.Length / 1KB, 2)) КБ"
            Write-Host ""
        }
        return
    }

    # Получаем существующие сертификаты для проверки дубликатов
    $existingCerts = @{}
    if ($SkipExisting) {
        Get-ChildItem -Path $targetStore | ForEach-Object {
            $existingCerts[$_.Thumbprint] = $true
        }
    }

    $pwdSecure = $Password
    $logFile = Join-Path -Path $ImportFolder -ChildPath "ImportPfxLog.csv"
    "Дата_Время,Область,Имя_Файла,Отпечаток,Субъект,Статус,Детали" | Out-File -FilePath $logFile -Encoding UTF8

    $counter = 0
    $imported = 0
    $skipped = 0

    $validFiles | ForEach-Object {
        $file = $_.FullName
        $fileName = $_.Name
        $counter++

        if ($ShowProgress) {
            $percentComplete = [math]::Round(($counter / $validFiles.Count) * 100)
            Write-Progress -Activity "Импорт сертификатов" -Status "Обрабатываем $fileName" -PercentComplete $percentComplete
        }

        Write-Verbose "Импортируем: Область=$Scope, Файл=$file"

        try {
            # Получаем информацию о сертификате для проверки дубликатов
            $tempCert = Get-PfxCertificate -FilePath $file -Password $pwdSecure
            
            if ($SkipExisting -and $existingCerts.ContainsKey($tempCert.Thumbprint)) {
                $line = ("{0},{1},{2},{3},{4},Пропущен,Сертификат уже существует" -f (Get-Date -Format s), $Scope, $fileName, $tempCert.Thumbprint, $tempCert.Subject)
                $line | Out-File -FilePath $logFile -Append -Encoding UTF8
                $skipped++
                Write-Verbose "Пропущен существующий сертификат: $($tempCert.Subject)"
                return
            }

            Import-PfxCertificate -FilePath $file -CertStoreLocation $targetStore -Password $pwdSecure -Exportable
            $line = ("{0},{1},{2},{3},{4},Успешно," -f (Get-Date -Format s), $Scope, $fileName, $tempCert.Thumbprint, $tempCert.Subject)
            $line | Out-File -FilePath $logFile -Append -Encoding UTF8
            $imported++
        }
        catch {
            $detail = $_.Exception.Message.Replace(",", ";")
            $line = ("{0},{1},{2},,Ошибка,{3}" -f (Get-Date -Format s), $Scope, $fileName, $detail)
            $line | Out-File -FilePath $logFile -Append -Encoding UTF8
            Write-Warning "Не удалось импортировать $fileName : $($_.Exception.Message)"
        }
    }

    if ($ShowProgress) {
        Write-Progress -Activity "Импорт сертификатов" -Completed
    }

    Write-Host "Импорт завершен. Импортировано: $imported, Пропущено: $skipped. Журнал: $logFile"
}

function Get-CryptoProCertificates {
    <#
    .SYNOPSIS
    Получает список сертификатов CryptoPro CSP с детальной информацией.
 
    .DESCRIPTION
    Функция возвращает список сертификатов из указанного хранилища с подробной информацией
    включая Subject, Issuer, сроки действия и статус приватного ключа.
    Поддерживает фильтрацию по различным критериям.
 
    .PARAMETER Scope
    Область хранилища сертификатов: CurrentUser или LocalMachine.
    Для LocalMachine требуются права администратора.
 
    .PARAMETER MinDaysRemaining
    Минимальное количество дней до истечения сертификата.
    По умолчанию 0 (показываются все сертификаты).
 
    .PARAMETER SubjectFilter
    Фильтр по полю Subject сертификата (поддерживает wildcards).
 
    .PARAMETER IssuerFilter
    Фильтр по полю Issuer сертификата (поддерживает wildcards).
 
    .EXAMPLE
    Get-CryptoProCertificates -Scope CurrentUser
     
    Получает все сертификаты из пользовательского хранилища.
 
    .EXAMPLE
    Get-CryptoProCertificates -Scope CurrentUser -MinDaysRemaining 30 -SubjectFilter "MyOrg"
     
    Получает сертификаты организации MyOrg, действующие более 30 дней.
 
    .EXAMPLE
    Get-CryptoProCertificates -Scope LocalMachine -IssuerFilter "MyCA" | Format-Table
     
    Получает сертификаты от определенного УЦ из машинного хранилища в табличном виде.
 
    .NOTES
    Требует установленный CryptoPro CSP.
    Для работы с LocalMachine необходимы права администратора.
    Возвращает объекты с полями: Subject, Issuer, Thumbprint, NotBefore, NotAfter, DaysRemaining, HasPrivateKey, FriendlyName.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet("LocalMachine", "CurrentUser")]
        [string] $Scope,

        [Parameter()]
        [int] $MinDaysRemaining = 0,

        [Parameter()]
        [string] $SubjectFilter = "",

        [Parameter()]
        [string] $IssuerFilter = ""
    )

    # Проверяем права администратора для области LocalMachine
    if ($Scope -eq "LocalMachine" -and -not (Test-AdminRights)) {
        throw "Для работы с областью LocalMachine требуются права администратора"
    }

    $storePath = "Cert:\$Scope\My"
    
    # Получаем сертификаты с фильтрацией по дате
    $certificates = Get-ChildItem -Path $storePath |
    Where-Object { $_.NotAfter -gt (Get-Date).AddDays($MinDaysRemaining) }

    # Применяем фильтр по Subject если указан
    if ($SubjectFilter) {
        $certificates = $certificates | Where-Object { $_.Subject -like "*$SubjectFilter*" }
    }

    # Применяем фильтр по Issuer если указан
    if ($IssuerFilter) {
        $certificates = $certificates | Where-Object { $_.Issuer -like "*$IssuerFilter*" }
    }

    # Возвращаем сертификаты с русскими названиями полей
    $certificates | Select-Object @{
        Name       = 'Субъект'
        Expression = { $_.Subject }
    }, @{
        Name       = 'Издатель'
        Expression = { $_.Issuer }
    }, @{
        Name       = 'Отпечаток'
        Expression = { $_.Thumbprint }
    }, @{
        Name       = 'Действителен_с'
        Expression = { $_.NotBefore }
    }, @{
        Name       = 'Действителен_до'
        Expression = { $_.NotAfter }
    }, @{
        Name       = 'Дней_осталось'
        Expression = { [math]::Round(($_.NotAfter - (Get-Date)).TotalDays) }
    }, @{
        Name       = 'Есть_закрытый_ключ'
        Expression = { $_.HasPrivateKey }
    }, @{
        Name       = 'Понятное_имя'
        Expression = { $_.FriendlyName }
    }
}

function Start-CryptoProCertMigrator {
    <#
    .SYNOPSIS
    Запускает интерактивное консольное меню для работы с сертификатами CryptoPro CSP.
 
    .DESCRIPTION
    Функция предоставляет удобное интерактивное меню для выполнения основных операций
    с сертификатами: просмотр, экспорт, импорт и быстрая миграция между хранилищами.
    Подходит для пользователей, которые предпочитают пошаговый интерфейс.
 
    .EXAMPLE
    Start-CryptoProCertMigrator
     
    Запускает интерактивное меню с опциями:
    1. Просмотр сертификатов
    2. Экспорт сертификатов
    3. Импорт сертификатов
    4. Быстрая миграция CurrentUser -> LocalMachine
 
    .NOTES
    Требует установленный CryptoPro CSP.
    Для операций с LocalMachine необходимы права администратора.
    Меню работает в цикле до выбора пункта "Выход".
    #>

    [CmdletBinding()]
    param()

    do {
        Clear-Host
        Write-Host "=== CryptoPro Certificate Migrator ===" -ForegroundColor Cyan
        Write-Host ""
        Write-Host "1. Просмотр сертификатов" -ForegroundColor Green
        Write-Host "2. Экспорт сертификатов" -ForegroundColor Yellow
        Write-Host "3. Импорт сертификатов" -ForegroundColor Yellow
        Write-Host "4. Быстрая миграция (CurrentUser -> LocalMachine)" -ForegroundColor Magenta
        Write-Host "0. Выход" -ForegroundColor Red
        Write-Host ""
        
        $choice = Read-Host "Выберите действие (0-4)"
        
        switch ($choice) {
            "1" {
                # Просмотр сертификатов
                Write-Host ""
                Write-Host "Выберите область:" -ForegroundColor Cyan
                Write-Host "1. CurrentUser"
                Write-Host "2. LocalMachine"
                $scopeChoice = Read-Host "Область (1-2)"
                
                $scope = switch ($scopeChoice) {
                    "1" { "CurrentUser" }
                    "2" { "LocalMachine" }
                    default { $null }
                }
                
                if ($scope) {
                    try {
                        $certs = Get-CryptoProCertificates -Scope $scope
                        $certs | Format-Table -AutoSize
                        Write-Host "Всего найдено: $($certs.Count) сертификатов" -ForegroundColor Green
                        Read-Host "Нажмите Enter для продолжения"
                    }
                    catch {
                        Write-Host "Ошибка: $($_.Exception.Message)" -ForegroundColor Red
                        Read-Host "Нажмите Enter для продолжения"
                    }
                }
            }
            "2" {
                # Экспорт сертификатов
                Write-Host ""
                Write-Host "=== Экспорт сертификатов ===" -ForegroundColor Yellow
                
                Write-Host "Выберите область:" -ForegroundColor Cyan
                Write-Host "1. CurrentUser"
                Write-Host "2. LocalMachine"
                $scopeChoice = Read-Host "Область (1-2)"
                
                $scope = switch ($scopeChoice) {
                    "1" { "CurrentUser" }
                    "2" { "LocalMachine" }
                    default { $null }
                }
                
                if ($scope) {
                    $folder = Read-Host "Папка для экспорта (по умолчанию: $env:USERPROFILE\Desktop\CertExport)"
                    if ([string]::IsNullOrWhiteSpace($folder)) {
                        $folder = "$env:USERPROFILE\Desktop\CertExport"
                    }
                    
                    $password = Read-Host "Пароль для PFX файлов" -AsSecureString
                    
                    try {
                        Export-CryptoProCertificates -Scope $scope -ExportFolder $folder -Password $password -ShowProgress
                        Write-Host "Экспорт завершен!" -ForegroundColor Green
                        Read-Host "Нажмите Enter для продолжения"
                    }
                    catch {
                        Write-Host "Ошибка экспорта: $($_.Exception.Message)" -ForegroundColor Red
                        Read-Host "Нажмите Enter для продолжения"
                    }
                }
            }
            "3" {
                # Импорт сертификатов
                Write-Host ""
                Write-Host "=== Импорт сертификатов ===" -ForegroundColor Yellow
                
                Write-Host "Выберите область:" -ForegroundColor Cyan
                Write-Host "1. CurrentUser"
                Write-Host "2. LocalMachine"
                $scopeChoice = Read-Host "Область (1-2)"
                
                $scope = switch ($scopeChoice) {
                    "1" { "CurrentUser" }
                    "2" { "LocalMachine" }
                    default { $null }
                }
                
                if ($scope) {
                    $folder = Read-Host "Папка с PFX файлами (по умолчанию: $env:USERPROFILE\Desktop\CertExport)"
                    if ([string]::IsNullOrWhiteSpace($folder)) {
                        $folder = "$env:USERPROFILE\Desktop\CertExport"
                    }
                    
                    $password = Read-Host "Пароль для PFX файлов" -AsSecureString
                    
                    try {
                        Import-CryptoProCertificates -Scope $scope -ImportFolder $folder -Password $password -ShowProgress -SkipExisting
                        Write-Host "Импорт завершен!" -ForegroundColor Green
                        Read-Host "Нажмите Enter для продолжения"
                    }
                    catch {
                        Write-Host "Ошибка импорта: $($_.Exception.Message)" -ForegroundColor Red
                        Read-Host "Нажмите Enter для продолжения"
                    }
                }
            }
            "4" {
                # Быстрая миграция
                Write-Host ""
                Write-Host "=== Быстрая миграция ===" -ForegroundColor Magenta
                Write-Host "CurrentUser -> LocalMachine" -ForegroundColor Yellow
                Write-Host ""
                
                if (-not (Test-AdminRights)) {
                    Write-Host "ОШИБКА: Требуются права администратора!" -ForegroundColor Red
                    Read-Host "Нажмите Enter для продолжения"
                    continue
                }
                
                $confirm = Read-Host "Продолжить миграцию? (y/N)"
                if ($confirm -eq "y" -or $confirm -eq "Y") {
                    $tempFolder = "$env:TEMP\CertMigration_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
                    $passwordText = "TempMigration$(Get-Random -Minimum 100000 -Maximum 999999)"
                    $password = ConvertTo-SecureString -String $passwordText -AsPlainText -Force
                    
                    try {
                        Write-Host "Экспортируем из CurrentUser..." -ForegroundColor Yellow
                        Export-CryptoProCertificates -Scope CurrentUser -ExportFolder $tempFolder -Password $password -ShowProgress
                        
                        Write-Host "Импортируем в LocalMachine..." -ForegroundColor Yellow
                        Import-CryptoProCertificates -Scope LocalMachine -ImportFolder $tempFolder -Password $password -ShowProgress -SkipExisting
                        
                        Write-Host "Удаляем временные файлы..." -ForegroundColor Yellow
                        Remove-Item -Path $tempFolder -Recurse -Force
                        
                        Write-Host "Миграция успешно завершена!" -ForegroundColor Green
                        Read-Host "Нажмите Enter для продолжения"
                    }
                    catch {
                        Write-Host "Ошибка миграции: $($_.Exception.Message)" -ForegroundColor Red
                        if (Test-Path $tempFolder) {
                            Remove-Item -Path $tempFolder -Recurse -Force -ErrorAction SilentlyContinue
                        }
                        Read-Host "Нажмите Enter для продолжения"
                    }
                }
            }
            "0" {
                Write-Host "До свидания!" -ForegroundColor Green
                break
            }
            default {
                Write-Host "Неверный выбор. Попробуйте снова." -ForegroundColor Red
                Start-Sleep 1
            }
        }
    } while ($choice -ne "0")
}

Export-ModuleMember -Function Export-CryptoProCertificates, Import-CryptoProCertificates, Get-CryptoProCertificates, Start-CryptoProCertMigrator