param( [string]$Server = $env:ONEC_SQL_SERVER, [string]$Database = $env:ONEC_SQL_DATABASE, [string]$User = $env:ONEC_SQL_USER, [string]$Password = $env:ONEC_SQL_PASSWORD, [ValidateSet("Config", "ConfigSave", "ConfigCAS", "ConfigCASSave", "Params")] [string]$Table = "Params", [string]$OutputPath = $env:ONEC_SQL_EXPORT_PATH, [string[]]$FileName, [int]$Limit = 0, [int]$CommandTimeoutSeconds = 0 ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" if (-not $Server) { throw "Server is required. Use -Server or ONEC_SQL_SERVER." } if (-not $Database) { throw "Database is required. Use -Database or ONEC_SQL_DATABASE." } if (-not $User) { throw "User is required. Use -User or ONEC_SQL_USER." } if (-not $Password) { throw "Password is required. Use -Password or ONEC_SQL_PASSWORD." } if (-not $OutputPath) { throw "OutputPath is required. Use -OutputPath or ONEC_SQL_EXPORT_PATH." } $resolvedOutput = [System.IO.Path]::GetFullPath($OutputPath) New-Item -ItemType Directory -Force -Path $resolvedOutput | Out-Null $connectionString = "Server=$Server;Database=$Database;User ID=$User;Password=$Password;Encrypt=False;TrustServerCertificate=True;Application Name=Codex 1C SQL File Export;" $connection = [System.Data.SqlClient.SqlConnection]::new($connectionString) $command = $connection.CreateCommand() $command.CommandTimeout = $CommandTimeoutSeconds $where = "" if ($FileName -and $FileName.Count -gt 0) { $placeholders = @() for ($i = 0; $i -lt $FileName.Count; $i++) { $name = "@file$i" $placeholders += $name $null = $command.Parameters.Add($name, [System.Data.SqlDbType]::NVarChar, 512) $command.Parameters[$name].Value = $FileName[$i] } $where = "WHERE FileName IN ($($placeholders -join ', '))" } $top = "" if ($Limit -gt 0 -and -not $where) { $top = "TOP ($Limit)" } $command.CommandText = "SELECT $top FileName, PartNo, BinaryData FROM dbo.[$Table] $where ORDER BY FileName, PartNo" $fileCount = 0 $chunkCount = 0 $byteCount = [int64]0 $currentName = $null $currentStream = $null $manifest = @() function Close-CurrentStream { if ($script:currentStream -ne $null) { $script:currentStream.Dispose() $script:currentStream = $null $script:fileCount++ } } try { $connection.Open() $reader = $command.ExecuteReader([System.Data.CommandBehavior]::SequentialAccess) try { while ($reader.Read()) { $file = [string]$reader.GetValue(0) if ([System.IO.Path]::GetFileName($file) -ne $file) { throw "Unsafe FileName value from SQL: $file" } if ($currentName -ne $file) { Close-CurrentStream $currentName = $file $target = Join-Path $resolvedOutput $file $currentStream = [System.IO.File]::Open($target, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write, [System.IO.FileShare]::None) $manifest += [pscustomobject]@{ file_name = $file; path = $target } } $buffer = [byte[]]$reader.GetValue(2) $currentStream.Write($buffer, 0, $buffer.Length) $chunkCount++ $byteCount += $buffer.Length } } finally { if ($reader -ne $null) { $reader.Dispose() } Close-CurrentStream } } finally { if ($connection -ne $null) { $connection.Dispose() } } $result = [pscustomobject]@{ table = $Table output_path = $resolvedOutput files = $fileCount chunks = $chunkCount bytes = $byteCount exported = $manifest } $result | ConvertTo-Json -Depth 5