Public Access
Replace hand-edited JSON with a setup wizard
Melissa had to author entity.json by hand and know FTPS from SFTP to do it - which is what broke the last two CAMBS runs. setup-entity.bat now asks the questions and writes the file: - protocol is a numbered menu, stored as an explicit "protocol" key that wins over the sftp:// host prefix and the older tls flag - pasted schemes and paths are stripped off the host before saving - it creates the practice folders, then logs in and checks every practice's remote folder exists, so a wrong path or protocol surfaces in seconds instead of in the next morning's run Also drops insurance_file_type from the required path: with no setting the engine sends every file in the date folder minus Windows clutter, so "No .pdf files" can't happen on a folder full of TIFs. Connection failures now print what to change rather than just WinSCP's wording.
This commit is contained in:
+85
-30
@@ -83,8 +83,8 @@ $entityConfig = Get-Content $entityConfigFile -Raw | ConvertFrom-Json
|
||||
$configTemplate = [ordered]@{
|
||||
workflow = $null
|
||||
insurance_file_type = $null
|
||||
ftps = [ordered]@{ host = $null; port = $null; tls = $null; username = $null; password = $null; tiff_path = $null; pdf_path = $null }
|
||||
sftp = [ordered]@{ host = $null; port = $null; username = $null; password = $null; patient_path = $null }
|
||||
ftps = [ordered]@{ host = $null; protocol = $null; port = $null; tls = $null; username = $null; password = $null; tiff_path = $null; pdf_path = $null }
|
||||
sftp = [ordered]@{ host = $null; protocol = $null; port = $null; username = $null; password = $null; patient_path = $null }
|
||||
}
|
||||
$addedKeys = @()
|
||||
foreach ($topKey in $configTemplate.Keys) {
|
||||
@@ -110,10 +110,25 @@ if ($addedKeys.Count -gt 0) {
|
||||
}
|
||||
|
||||
$workflow = $entityConfig.workflow
|
||||
$insuranceExt = if ($entityConfig.insurance_file_type) { $entityConfig.insurance_file_type.TrimStart(".") } else { "pdf" }
|
||||
$export1 = Join-Path $EntityDir "Export1"
|
||||
$entityName = Split-Path -Leaf $EntityDir
|
||||
|
||||
# By default take whatever the imaging system left in the date folder, minus
|
||||
# the Windows clutter below - no need to declare pdf vs tif anywhere. Setting
|
||||
# insurance_file_type pins it to one extension for entities that need that.
|
||||
$insuranceExt = if ($entityConfig.insurance_file_type) { $entityConfig.insurance_file_type.TrimStart(".") } else { $null }
|
||||
$junkFiles = @("thumbs.db", "desktop.ini", ".ds_store")
|
||||
$junkExts = @(".tmp", ".lnk", ".ini", ".db")
|
||||
|
||||
function Get-UploadableFiles($folder) {
|
||||
if ($insuranceExt) {
|
||||
return @(Get-ChildItem -Path $folder -Filter "*.$insuranceExt" -File)
|
||||
}
|
||||
return @(Get-ChildItem -Path $folder -File | Where-Object {
|
||||
$junkFiles -notcontains $_.Name.ToLower() -and $junkExts -notcontains $_.Extension.ToLower()
|
||||
})
|
||||
}
|
||||
|
||||
if (-not (Test-Path $export1)) {
|
||||
Write-Host "ERROR: Export1 folder not found in $EntityDir" -ForegroundColor Red
|
||||
Write-Log "error" "Export1 not found in $EntityDir"
|
||||
@@ -121,9 +136,10 @@ if (-not (Test-Path $export1)) {
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Entity: $entityName | Workflow: $workflow | File type: .$insuranceExt | Date: $today | Log: $logLevel" -ForegroundColor Cyan
|
||||
$fileTypeLabel = if ($insuranceExt) { ".$insuranceExt only" } else { "any file" }
|
||||
Write-Host "Entity: $entityName | Workflow: $workflow | Sending: $fileTypeLabel | Date: $today | Log: $logLevel" -ForegroundColor Cyan
|
||||
Write-Host ("=" * 60)
|
||||
Write-Info "=== START entity=$entityName workflow=$workflow filetype=$insuranceExt ==="
|
||||
Write-Info "=== START entity=$entityName workflow=$workflow filetype=$fileTypeLabel ==="
|
||||
|
||||
# -- Helpers ------------------------------------------------------------------
|
||||
function Merge-Config($base, $override) {
|
||||
@@ -169,7 +185,7 @@ function Get-ArchiveDir($parent, $name) {
|
||||
}
|
||||
|
||||
# Host values may be pasted as URLs, e.g. "SFTP://server.com/" - pull out the
|
||||
# scheme (decides the protocol) and the bare hostname.
|
||||
# scheme and the bare hostname.
|
||||
function Get-HostInfo($rawHost) {
|
||||
$h = ([string]$rawHost).Trim()
|
||||
$scheme = $null
|
||||
@@ -181,24 +197,64 @@ function Get-HostInfo($rawHost) {
|
||||
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
|
||||
if ($hostInfo.Scheme -eq "sftp") {
|
||||
# "sftp", "ftps" or "ftp". An explicit protocol setting wins; otherwise fall
|
||||
# back to a scheme pasted into the host, then to the older tls true/false.
|
||||
function Get-Protocol($conn, $fallback) {
|
||||
if ($conn.protocol) {
|
||||
$p = ([string]$conn.protocol).ToLower().Trim()
|
||||
if ($p -in @("sftp", "ftps", "ftp")) { return $p }
|
||||
}
|
||||
$scheme = (Get-HostInfo $conn.host).Scheme
|
||||
if ($scheme -in @("sftp", "ftps", "ftp")) { return $scheme }
|
||||
if ($conn.tls -eq $true) { return "ftps" }
|
||||
return $fallback
|
||||
}
|
||||
|
||||
# Turns whatever WinSCP objected to into something worth acting on.
|
||||
function Get-ConnectionHint($message) {
|
||||
if ($message -imatch "cannot connect to an SFTP server using an FTP") {
|
||||
return "This server wants SFTP. Run setup-entity.bat, change this entity's server, and pick SFTP."
|
||||
}
|
||||
if ($message -imatch "cannot connect to an FTP server using an SSH|expected SSH") {
|
||||
return "This server wants FTP or FTPS. Run setup-entity.bat, change this entity's server, and pick FTPS."
|
||||
}
|
||||
if ($message -imatch "authentication|password|login incorrect|530") {
|
||||
return "The username or password was rejected. Run setup-entity.bat and retype the login."
|
||||
}
|
||||
if ($message -imatch "host does not exist|no such host|could not resolve") {
|
||||
return "That server address could not be found - check it for a typo in setup-entity.bat."
|
||||
}
|
||||
if ($message -imatch "timed out|timeout|refused") {
|
||||
return "No answer from the server. Check the address, or ask IT whether the firewall allows it."
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Set-SessionOptions($opts, $conn, $protocol) {
|
||||
$opts.HostName = (Get-HostInfo $conn.host).HostName
|
||||
$opts.UserName = $conn.username
|
||||
$opts.Password = $conn.password
|
||||
if ($protocol -eq "sftp") {
|
||||
$opts.Protocol = [WinSCP.Protocol]::Sftp
|
||||
$opts.PortNumber = if ($ftpConfig.port) { [int]$ftpConfig.port } else { 22 }
|
||||
$opts.PortNumber = if ($conn.port) { [int]$conn.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.PortNumber = if ($conn.port) { [int]$conn.port } else { 21 }
|
||||
if ($protocol -eq "ftps") {
|
||||
$opts.FtpSecure = [WinSCP.FtpSecure]::Explicit
|
||||
$opts.GiveUpSecurityAndAcceptAnyTlsHostCertificate = $true
|
||||
} else {
|
||||
$opts.FtpSecure = [WinSCP.FtpSecure]::None
|
||||
}
|
||||
}
|
||||
$opts.HostName = $hostInfo.HostName
|
||||
$opts.UserName = $ftpConfig.username
|
||||
$opts.Password = $ftpConfig.password
|
||||
}
|
||||
|
||||
# -- Upload the daily date-folder files ----------------------------------------
|
||||
function Invoke-FTPSUpload($ftpConfig, $files, $remotePath, $entity, $practice) {
|
||||
$protocol = Get-Protocol $ftpConfig "ftp"
|
||||
$opts = New-Object WinSCP.SessionOptions
|
||||
Set-SessionOptions $opts $ftpConfig $protocol
|
||||
|
||||
$session = New-Object WinSCP.Session
|
||||
if ($logLevel -eq "debug") {
|
||||
@@ -207,7 +263,7 @@ function Invoke-FTPSUpload($ftpConfig, $files, $remotePath, $entity, $practice)
|
||||
|
||||
$ok = 0; $fail = 0
|
||||
try {
|
||||
Write-Debug "Connect: $($opts.Protocol) $($hostInfo.HostName):$($opts.PortNumber) user=$($ftpConfig.username)"
|
||||
Write-Debug "Connect: $protocol $($opts.HostName):$($opts.PortNumber) user=$($ftpConfig.username)"
|
||||
$session.Open($opts)
|
||||
foreach ($file in $files) {
|
||||
$remote = $remotePath.TrimEnd("/") + "/" + $file.Name
|
||||
@@ -228,6 +284,8 @@ function Invoke-FTPSUpload($ftpConfig, $files, $remotePath, $entity, $practice)
|
||||
$err = $_.Exception.Message
|
||||
Write-Log "error" "FTPS connect FAILED entity=$entity practice=$practice host=$($ftpConfig.host) error=$err"
|
||||
Write-Host " Connection failed: $err" -ForegroundColor Red
|
||||
$hint = Get-ConnectionHint $err
|
||||
if ($hint) { Write-Host " -> $hint" -ForegroundColor Yellow }
|
||||
$fail += $files.Count
|
||||
} finally {
|
||||
$session.Dispose()
|
||||
@@ -237,14 +295,9 @@ function Invoke-FTPSUpload($ftpConfig, $files, $remotePath, $entity, $practice)
|
||||
|
||||
# -- Upload via SFTP -----------------------------------------------------------
|
||||
function Invoke-SFTPUpload($sftpConfig, $files, $remotePath, $entity, $practice) {
|
||||
$hostInfo = Get-HostInfo $sftpConfig.host
|
||||
$protocol = Get-Protocol $sftpConfig "sftp"
|
||||
$opts = New-Object WinSCP.SessionOptions
|
||||
$opts.Protocol = [WinSCP.Protocol]::Sftp
|
||||
$opts.HostName = $hostInfo.HostName
|
||||
$opts.PortNumber = if ($sftpConfig.port) { [int]$sftpConfig.port } else { 22 }
|
||||
$opts.UserName = $sftpConfig.username
|
||||
$opts.Password = $sftpConfig.password
|
||||
$opts.GiveUpSecurityAndAcceptAnySshHostKey = $true
|
||||
Set-SessionOptions $opts $sftpConfig $protocol
|
||||
|
||||
$session = New-Object WinSCP.Session
|
||||
if ($logLevel -eq "debug") {
|
||||
@@ -274,6 +327,8 @@ function Invoke-SFTPUpload($sftpConfig, $files, $remotePath, $entity, $practice)
|
||||
$err = $_.Exception.Message
|
||||
Write-Log "error" "SFTP connect FAILED entity=$entity practice=$practice host=$($sftpConfig.host) error=$err"
|
||||
Write-Host " Connection failed: $err" -ForegroundColor Red
|
||||
$hint = Get-ConnectionHint $err
|
||||
if ($hint) { Write-Host " -> $hint" -ForegroundColor Yellow }
|
||||
$fail += $files.Count
|
||||
} finally {
|
||||
$session.Dispose()
|
||||
@@ -314,7 +369,7 @@ foreach ($practice in $practices) {
|
||||
Write-Host " No $today\ folder - skipping insurance." -ForegroundColor Gray
|
||||
Write-Info "SKIP entity=$entityName practice=$pracName reason=no date folder"
|
||||
} else {
|
||||
$allInsuranceFiles = Get-ChildItem -Path $dateDir -Filter "*.$insuranceExt" -File
|
||||
$allInsuranceFiles = Get-UploadableFiles $dateDir
|
||||
$ptStmts = $allInsuranceFiles | Where-Object { $_.Name -imatch 'pt[\s_]*stmt' }
|
||||
$insFiles = $allInsuranceFiles | Where-Object { $_.Name -notmatch 'pt[\s_]*stmt' }
|
||||
|
||||
@@ -364,8 +419,8 @@ foreach ($practice in $practices) {
|
||||
Write-Host " $today\ NOT archived - $ignored file(s) were ignored (unconfigured path)." -ForegroundColor Yellow
|
||||
Write-Info "NOARCHIVE entity=$entityName practice=$pracName reason=$ignored ignored files"
|
||||
} elseif ($insFiles.Count + $ptStmts.Count -eq 0) {
|
||||
Write-Host " No .$insuranceExt files in $today\." -ForegroundColor Gray
|
||||
Write-Info "SKIP entity=$entityName practice=$pracName reason=no .$insuranceExt files in date folder"
|
||||
Write-Host " Nothing to send in $today\ ($fileTypeLabel)." -ForegroundColor Gray
|
||||
Write-Info "SKIP entity=$entityName practice=$pracName reason=no matching files in date folder"
|
||||
}
|
||||
|
||||
$totalOk += $insResult.Ok; $totalFail += $insResult.Fail
|
||||
|
||||
Reference in New Issue
Block a user