CPCertMigrator.psm1

# =============================================================================
# Module: CryptoProCertMigrator
# File: CPCertMigrator.psm1
# Purpose: PowerShell module to export and import certificates/containers
# for CryptoPro CSP for both LocalMachine and CurrentUser scopes.
# Version: 1.1.0
# Author: zeroday
# =============================================================================

# Helper function to check administrator privileges
function Test-AdminRights {
    $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
    return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

# Helper function to validate PFX file
function Test-PfxFile {
    param(
        [string]$FilePath,
        [string]$Password
    )
    
    try {
        $pwdSecure = ConvertTo-SecureString -String $Password -AsPlainText -Force
        $null = Get-PfxCertificate -FilePath $FilePath -Password $pwdSecure
        return $true
    }
    catch {
        return $false
    }
}

# Helper function to generate smart file names
function Get-SmartFileName {
    param(
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate
    )
    
    $subject = $Certificate.Subject -replace 'CN=([^,]+).*', '$1' -replace '[\\\/:\*\?\"<>|]', '_'
    $serial = $Certificate.SerialNumber.Substring(0, [Math]::Min(8, $Certificate.SerialNumber.Length))
    
    if ([string]::IsNullOrEmpty($subject)) {
        return "Cert_$serial"
    }
    
    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)]
        [string] $Password,

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

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

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

        [Parameter()]
        [switch] $WhatIf,

        [Parameter()]
        [switch] $ShowProgress
    )

    Write-Verbose "Starting certificate export operation"
    Write-Verbose "Scope: $Scope, ExportFolder: $ExportFolder, MinDaysRemaining: $MinDaysRemaining"
    
    # Validate parameters
    if ([string]::IsNullOrWhiteSpace($Password)) {
        throw "Password cannot be empty or whitespace"
    }
    
    if ($Password.Length -lt 4) {
        Write-Warning "Password is very short. Consider using a stronger password."
    }

    # Check admin rights for LocalMachine scope
    if ($Scope -eq "LocalMachine" -and -not (Test-AdminRights)) {
        $errorMsg = "Administrator privileges required for LocalMachine scope operations. Please run PowerShell as Administrator."
        Write-Error $errorMsg -Category PermissionDenied -ErrorAction Stop
    }

    # Validate certificate store path
    $storePath = "Cert:\$Scope\My"
    Write-Verbose "Certificate store path: $storePath"
    
    if (-not (Test-Path $storePath)) {
        $errorMsg = "Certificate store path '$storePath' not found. Please ensure CryptoPro CSP is installed."
        Write-Error $errorMsg -Category ObjectNotFound -ErrorAction Stop
    }

    # Create export folder if needed
    if (-not $WhatIf) {
        if (-not (Test-Path $ExportFolder)) {
            try {
                Write-Verbose "Creating export folder: $ExportFolder"
                New-Item -ItemType Directory -Path $ExportFolder -Force | Out-Null
                Write-Verbose "Export folder created successfully"
            }
            catch {
                $errorMsg = "Failed to create export folder '$ExportFolder': $($_.Exception.Message)"
                Write-Error $errorMsg -Category WriteError -ErrorAction Stop
            }
        }
        else {
            Write-Verbose "Export folder already exists: $ExportFolder"
        }
    }

    # Get certificates with filters
    Write-Verbose "Retrieving certificates from store..."
    try {
        $allCertificates = Get-ChildItem -Path $storePath -ErrorAction Stop
        Write-Verbose "Found $($allCertificates.Count) total certificates in store"
    }
    catch {
        $errorMsg = "Failed to access certificate store '$storePath': $($_.Exception.Message)"
        Write-Error $errorMsg -Category ReadError -ErrorAction Stop
    }

    # Apply date filter
    $certificates = $allCertificates | Where-Object { $_.NotAfter -gt (Get-Date).AddDays($MinDaysRemaining) }
    Write-Verbose "After date filter (>$MinDaysRemaining days): $($certificates.Count) certificates"

    # Apply subject filter
    if ($SubjectFilter) {
        $beforeCount = $certificates.Count
        $certificates = $certificates | Where-Object { $_.Subject -like "*$SubjectFilter*" }
        Write-Verbose "After Subject filter '$SubjectFilter': $($certificates.Count) certificates (filtered out: $($beforeCount - $certificates.Count))"
    }

    # Apply issuer filter
    if ($IssuerFilter) {
        $beforeCount = $certificates.Count
        $certificates = $certificates | Where-Object { $_.Issuer -like "*$IssuerFilter*" }
        Write-Verbose "After Issuer filter '$IssuerFilter': $($certificates.Count) certificates (filtered out: $($beforeCount - $certificates.Count))"
    }

    $totalCerts = $certificates.Count
    Write-Host "Found $totalCerts certificates to export" -ForegroundColor Green
    
    if ($totalCerts -eq 0) {
        Write-Warning "No certificates match the specified criteria. Export operation cancelled."
        return
    }

    if ($WhatIf) {
        Write-Host "WhatIf: Would export the following certificates:"
        $certificates | ForEach-Object {
            $smartName = Get-SmartFileName -Certificate $_
            Write-Host " - Subject: $($_.Subject)"
            Write-Host " Thumbprint: $($_.Thumbprint)"
            Write-Host " File: $smartName.pfx"
            Write-Host " Expires: $($_.NotAfter)"
            Write-Host ""
        }
        return
    }

    # Prepare for export
    Write-Verbose "Converting password to secure string..."
    $pwdSecure = ConvertTo-SecureString -String $Password -AsPlainText -Force
    
    $logFile = Join-Path -Path $ExportFolder -ChildPath "ExportPfxLog.csv"
    Write-Verbose "Creating log file: $logFile"
    "DateTime,Scope,ContainerName,Thumbprint,Subject,FilePath,Status,Detail" | Out-File -FilePath $logFile -Encoding UTF8

    $counter = 0
    $successCount = 0
    $errorCount = 0
    Write-Verbose "Starting export of $totalCerts certificates..."
    $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 "Exporting Certificates" -Status "Processing $smartName" -PercentComplete $percentComplete
        }

        Write-Verbose "Exporting: Scope=$Scope, Cert=$smartName, Thumbprint=$thumb, File=$filePath"

        # Check if file already exists
        if (Test-Path $filePath) {
            $filePath = Join-Path -Path $ExportFolder -ChildPath ("{0}_{1}.pfx" -f $smartName, $thumb.Substring(0, 8))
        }

        try {
            Write-Verbose "Exporting certificate to: $filePath"
            $cert | Export-PfxCertificate -FilePath $filePath -Password $pwdSecure -Force -ErrorAction Stop
            
            # Verify export success
            if (Test-Path $filePath) {
                $fileSize = (Get-Item $filePath).Length
                Write-Verbose "Export successful. File size: $fileSize bytes"
                $successCount++
            } else {
                throw "PFX file was not created"
            }
            
            $line = ("{0},{1},{2},{3},{4},{5},Success," -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},Failed,{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 "Failed to export certificate '$smartName'"
            Write-Verbose "Export error details: $($_.Exception.Message)"
            
            # Additional error context
            if ($_.Exception.Message -like "*access*denied*") {
                Write-Verbose "Possible cause: Insufficient permissions or certificate is not exportable"
            }
            elseif ($_.Exception.Message -like "*password*") {
                Write-Verbose "Possible cause: Password complexity requirements not met"
            }
        }
    }

    if ($ShowProgress) {
        Write-Progress -Activity "Exporting Certificates" -Completed
    }

    # Final summary
    Write-Verbose "Export operation completed"
    Write-Verbose "Total processed: $counter certificates"
    Write-Verbose "Successful exports: $successCount"
    Write-Verbose "Failed exports: $errorCount"
    Write-Verbose "Log file: $logFile"
    
    if ($errorCount -eq 0) {
        Write-Host "✅ Export completed successfully! Exported $successCount certificates." -ForegroundColor Green
    } else {
        Write-Host "⚠️ Export completed with issues. Success: $successCount, Failed: $errorCount" -ForegroundColor Yellow
    }
    
    Write-Host "📄 Log file: $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)]
        [string] $Password,

        [Parameter()]
        [switch] $WhatIf,

        [Parameter()]
        [switch] $ShowProgress,

        [Parameter()]
        [switch] $SkipExisting
    )

    # Check admin rights for LocalMachine scope
    if ($Scope -eq "LocalMachine" -and -not (Test-AdminRights)) {
        throw "Administrator privileges required for LocalMachine scope operations"
    }

    $targetStore = "Cert:\$Scope\My"
    
    # Get PFX files
    $pfxFiles = Get-ChildItem -Path $ImportFolder -Filter *.pfx
    $totalFiles = $pfxFiles.Count
    
    Write-Host "Found $totalFiles PFX files to import"

    if ($totalFiles -eq 0) {
        Write-Warning "No PFX files found in $ImportFolder"
        return
    }

    # Validate files first
    Write-Host "Validating PFX files..."
    $validFiles = @()
    $pfxFiles | ForEach-Object {
        if (Test-PfxFile -FilePath $_.FullName -Password $Password) {
            $validFiles += $_
        }
        else {
            Write-Warning "Invalid PFX file or wrong password: $($_.Name)"
        }
    }

    Write-Host "Valid files: $($validFiles.Count) of $totalFiles"

    if ($WhatIf) {
        Write-Host "WhatIf: Would import the following files:"
        $validFiles | ForEach-Object {
            Write-Host " - File: $($_.Name)"
            Write-Host " Size: $([math]::Round($_.Length / 1KB, 2)) KB"
            Write-Host ""
        }
        return
    }

    # Get existing certificates for duplicate check
    $existingCerts = @{}
    if ($SkipExisting) {
        Get-ChildItem -Path $targetStore | ForEach-Object {
            $existingCerts[$_.Thumbprint] = $true
        }
    }

    $pwdSecure = ConvertTo-SecureString -String $Password -AsPlainText -Force
    $logFile = Join-Path -Path $ImportFolder -ChildPath "ImportPfxLog.csv"
    "DateTime,Scope,FileName,Thumbprint,Subject,Status,Detail" | 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 "Importing Certificates" -Status "Processing $fileName" -PercentComplete $percentComplete
        }

        Write-Verbose "Importing: Scope=$Scope, File=$file"

        try {
            # Get certificate info for duplicate check
            $tempCert = Get-PfxCertificate -FilePath $file -Password $pwdSecure
            
            if ($SkipExisting -and $existingCerts.ContainsKey($tempCert.Thumbprint)) {
                $line = ("{0},{1},{2},{3},{4},Skipped,Certificate already exists" -f (Get-Date -Format s), $Scope, $fileName, $tempCert.Thumbprint, $tempCert.Subject)
                $line | Out-File -FilePath $logFile -Append -Encoding UTF8
                $skipped++
                Write-Verbose "Skipped existing certificate: $($tempCert.Subject)"
                return
            }

            Import-PfxCertificate -FilePath $file -CertStoreLocation $targetStore -Password $pwdSecure -Exportable
            $line = ("{0},{1},{2},{3},{4},Success," -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},,Failed,{3}" -f (Get-Date -Format s), $Scope, $fileName, $detail)
            $line | Out-File -FilePath $logFile -Append -Encoding UTF8
            Write-Warning "Failed to import $fileName : $($_.Exception.Message)"
        }
    }

    if ($ShowProgress) {
        Write-Progress -Activity "Importing Certificates" -Completed
    }

    Write-Host "Import completed. Imported: $imported, Skipped: $skipped. Log: $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 = ""
    )

    # Check admin rights for LocalMachine scope
    if ($Scope -eq "LocalMachine" -and -not (Test-AdminRights)) {
        throw "Administrator privileges required for LocalMachine scope operations"
    }

    $storePath = "Cert:\$Scope\My"
    
    $certificates = Get-ChildItem -Path $storePath |
    Where-Object { $_.NotAfter -gt (Get-Date).AddDays($MinDaysRemaining) }

    if ($SubjectFilter) {
        $certificates = $certificates | Where-Object { $_.Subject -like "*$SubjectFilter*" }
    }

    if ($IssuerFilter) {
        $certificates = $certificates | Where-Object { $_.Issuer -like "*$IssuerFilter*" }
    }

    $certificates | Select-Object @{
        Name       = 'Subject'
        Expression = { $_.Subject }
    }, @{
        Name       = 'Issuer'
        Expression = { $_.Issuer }
    }, @{
        Name       = 'Thumbprint'
        Expression = { $_.Thumbprint }
    }, @{
        Name       = 'NotBefore'
        Expression = { $_.NotBefore }
    }, @{
        Name       = 'NotAfter'
        Expression = { $_.NotAfter }
    }, @{
        Name       = 'DaysRemaining'
        Expression = { [math]::Round(($_.NotAfter - (Get-Date)).TotalDays) }
    }, @{
        Name       = 'HasPrivateKey'
        Expression = { $_.HasPrivateKey }
    }, @{
        Name       = 'FriendlyName'
        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
                    $passwordText = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
                    
                    try {
                        Export-CryptoProCertificates -Scope $scope -ExportFolder $folder -Password $passwordText -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
                    $passwordText = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
                    
                    try {
                        Import-CryptoProCertificates -Scope $scope -ImportFolder $folder -Password $passwordText -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')"
                    $password = "TempMigration$(Get-Random -Minimum 100000 -Maximum 999999)"
                    
                    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