Support sftp:// and ftps:// URL schemes in host values; fix TLS-flag bug

CAMBS's partner turned out to receive insurance files over SFTP - Melissa
pasted "SFTP://sft.polluxsystems.com/" as ftps.host, which the engine fed to
WinSCP as a literal FTP hostname. Host values are now parsed: an sftp://
scheme switches the upload to the SFTP protocol (default port 22), ftps://
forces explicit TLS, a bare hostname behaves as before per the tls flag, and
the scheme/trailing slash are stripped from the hostname either way.

Also fixes a real bug this exposed: GiveUpSecurityAndAcceptAnyTlsHostCertificate
was set unconditionally, and WinSCP refuses that combination when FtpSecure
is None - exactly the "TlsHostCertificateFingerprint ... is set, but neither
FtpSecure nor Secure is enabled" error from her run. The flag is now only set
when TLS is actually on.
This commit is contained in:
2026-07-05 20:33:01 -05:00
parent 3105150341
commit 76dc830580
2 changed files with 30 additions and 8 deletions
+1 -1
View File
@@ -110,7 +110,7 @@ leave its login and every other path alone.
|---------|-----------|------------|
| `workflow` | Yes | `insurance` if no patient files, `insurance+patient` if both |
| `insurance_file_type` | No (defaults to `pdf`) | Set to `tif` for an entity whose imaging system exports the daily files as TIFs instead of PDFs (CAMBS, RMI, CONSENSIO, INLAND). Goes at the top level, next to `workflow` — NOT inside `ftps` |
| `ftps.host` | Yes | FTP server address |
| `ftps.host` | Yes | Server address. A plain name (`ftp.example.com`) uses FTP/FTPS per the `tls` setting. You can also paste a full URL — `sftp://server.com` sends these files over **SFTP** instead (some partners use SFTP for everything), and `ftps://server.com` forces FTPS |
| `ftps.port` | No (defaults to 21) | FTP port, only if not 21 |
| `ftps.tls` | Yes | `true` for FTPS (secure), `false` for plain FTP |
| `ftps.username` | Yes | FTP username (shared across all practices) |
+29 -7
View File
@@ -168,16 +168,37 @@ function Get-ArchiveDir($parent, $name) {
return $path
}
# Host values may be pasted as URLs, e.g. "SFTP://server.com/" - pull out the
# scheme (decides the protocol) and the bare hostname.
function Get-HostInfo($rawHost) {
$h = ([string]$rawHost).Trim()
$scheme = $null
if ($h -imatch '^(\w+)://') {
$scheme = $Matches[1].ToLower()
$h = $h -replace '^\w+://', ''
}
$h = $h.Split('/')[0]
return @{ Scheme = $scheme; HostName = $h }
}
# -- Upload via FTPS -----------------------------------------------------------
function Invoke-FTPSUpload($ftpConfig, $files, $remotePath, $entity, $practice) {
$hostInfo = Get-HostInfo $ftpConfig.host
$opts = New-Object WinSCP.SessionOptions
$opts.Protocol = [WinSCP.Protocol]::Ftp
$opts.FtpSecure = if ($ftpConfig.tls) { [WinSCP.FtpSecure]::Explicit } else { [WinSCP.FtpSecure]::None }
$opts.HostName = $ftpConfig.host
$opts.PortNumber = if ($ftpConfig.port) { [int]$ftpConfig.port } else { 21 }
if ($hostInfo.Scheme -eq "sftp") {
$opts.Protocol = [WinSCP.Protocol]::Sftp
$opts.PortNumber = if ($ftpConfig.port) { [int]$ftpConfig.port } else { 22 }
$opts.GiveUpSecurityAndAcceptAnySshHostKey = $true
} else {
$useTls = ($ftpConfig.tls -eq $true) -or ($hostInfo.Scheme -eq "ftps")
$opts.Protocol = [WinSCP.Protocol]::Ftp
$opts.FtpSecure = if ($useTls) { [WinSCP.FtpSecure]::Explicit } else { [WinSCP.FtpSecure]::None }
$opts.PortNumber = if ($ftpConfig.port) { [int]$ftpConfig.port } else { 21 }
if ($useTls) { $opts.GiveUpSecurityAndAcceptAnyTlsHostCertificate = $true }
}
$opts.HostName = $hostInfo.HostName
$opts.UserName = $ftpConfig.username
$opts.Password = $ftpConfig.password
$opts.GiveUpSecurityAndAcceptAnyTlsHostCertificate = $true
$session = New-Object WinSCP.Session
if ($logLevel -eq "debug") {
@@ -186,7 +207,7 @@ function Invoke-FTPSUpload($ftpConfig, $files, $remotePath, $entity, $practice)
$ok = 0; $fail = 0
try {
Write-Debug "FTPS connect: $($ftpConfig.host) user=$($ftpConfig.username) tls=$($ftpConfig.tls)"
Write-Debug "Connect: $($opts.Protocol) $($hostInfo.HostName):$($opts.PortNumber) user=$($ftpConfig.username)"
$session.Open($opts)
foreach ($file in $files) {
$remote = $remotePath.TrimEnd("/") + "/" + $file.Name
@@ -216,9 +237,10 @@ function Invoke-FTPSUpload($ftpConfig, $files, $remotePath, $entity, $practice)
# -- Upload via SFTP -----------------------------------------------------------
function Invoke-SFTPUpload($sftpConfig, $files, $remotePath, $entity, $practice) {
$hostInfo = Get-HostInfo $sftpConfig.host
$opts = New-Object WinSCP.SessionOptions
$opts.Protocol = [WinSCP.Protocol]::Sftp
$opts.HostName = $sftpConfig.host
$opts.HostName = $hostInfo.HostName
$opts.PortNumber = if ($sftpConfig.port) { [int]$sftpConfig.port } else { 22 }
$opts.UserName = $sftpConfig.username
$opts.Password = $sftpConfig.password