Files
melissa-uploader/Test-Login.ps1
T
blance e384719ecb Add per-practice logins, a standalone login test, and pre-commit checks
The ChatGPT transcript showed each CAMBS practice has its own SFTP
account (YSMD is YAM62682 on io.myallylogin.com:14147), not one shared
entity login - so the wizard needed a way to set those without hand-
writing nine practice.json files. Option 5 does that, and the settings
screen now lists which login each practice will actually use.

test-login.bat tests one account on its own, with the details typed in
rather than read from a config, which isolates the account from whatever
a script is doing to the credentials. It reports password length,
stray whitespace, and $ / backtick characters - the last being the usual
reason a login works in the WinSCP window but fails from a script, since
"$pass" interpolates in PowerShell while '$pass' does not. On failure it
prints the authentication methods the server offered.

check.sh runs what has actually caught bugs here: non-ASCII characters,
"$name?" (? is legal in an unbraced variable name, so it renders empty -
this bit twice), parse errors, 5.1 syntax compatibility, and JSON validity.
2026-08-30 20:28:32 -05:00

225 lines
8.8 KiB
PowerShell

# Standalone SFTP/FTP login test. Nothing is read from a config file - you type
# the details in - so it isolates the account from whatever a script is doing
# with it. Called by test-login.bat.
$scriptRoot = Split-Path -Parent $PSCommandPath
$winscpDll = Join-Path $scriptRoot "WinSCP\WinSCPnet.dll"
function Read-Answer($question, $default) {
while ($true) {
if ($default) {
Write-Host "$question " -NoNewline -ForegroundColor White
Write-Host "[$default]" -NoNewline -ForegroundColor DarkGray
Write-Host ": " -NoNewline -ForegroundColor White
} else {
Write-Host "${question}: " -NoNewline -ForegroundColor White
}
$answer = Read-Host
if ([string]::IsNullOrWhiteSpace($answer)) {
if ($default) { return $default }
Write-Host " (required)" -ForegroundColor Yellow
continue
}
return $answer
}
}
Write-Host ""
Write-Host ("=" * 64) -ForegroundColor Cyan
Write-Host " Login test" -ForegroundColor Cyan
Write-Host ("=" * 64) -ForegroundColor Cyan
Write-Host ""
Write-Host "Type the details exactly as they work in WinSCP or FileZilla." -ForegroundColor Gray
if (-not (Test-Path $winscpDll)) {
Write-Host ""
Write-Host "WinSCPnet.dll not found at $winscpDll" -ForegroundColor Red
Write-Host "Run setup.bat first, or copy this script next to the WinSCP folder." -ForegroundColor Yellow
exit 1
}
Get-ChildItem -Path (Join-Path $scriptRoot "WinSCP") -Recurse -File |
Unblock-File -ErrorAction SilentlyContinue
try {
Add-Type -Path $winscpDll
} catch {
Write-Host "Could not load WinSCPnet.dll - $($_.Exception.Message)" -ForegroundColor Red
exit 1
}
Write-Host ""
$serverHost = Read-Answer "Server" $null
$serverHost = ($serverHost -replace '^\w+://', '').Split('/')[0].Trim()
$protoChoice = 0
Write-Host ""
Write-Host "Protocol:" -ForegroundColor White
Write-Host " 1. SFTP"
Write-Host " 2. FTPS"
Write-Host " 3. FTP"
while ($protoChoice -lt 1 -or $protoChoice -gt 3) {
Write-Host "Choose 1-3 " -NoNewline -ForegroundColor White
Write-Host "[1]" -NoNewline -ForegroundColor DarkGray
Write-Host ": " -NoNewline -ForegroundColor White
$a = Read-Host
if ([string]::IsNullOrWhiteSpace($a)) { $a = "1" }
[int]::TryParse($a.Trim(), [ref]$protoChoice) | Out-Null
}
$protocol = @("sftp", "ftps", "ftp")[$protoChoice - 1]
$defaultPort = if ($protocol -eq "sftp") { 22 } else { 21 }
$port = [int](Read-Answer "Port" $defaultPort)
$username = Read-Answer "Username" $null
Write-Host ""
Write-Host "Paste the password. It is not echoed and not saved anywhere." -ForegroundColor DarkGray
Write-Host "Password: " -NoNewline -ForegroundColor White
$secure = Read-Host -AsSecureString
$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)
$password = [Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
# -- What the credentials actually contain ------------------------------------
Write-Host ""
Write-Host ("-" * 64)
Write-Host "What was entered" -ForegroundColor Cyan
Write-Host ("-" * 64)
Write-Host " Server : [$serverHost] port $port over $($protocol.ToUpper())"
Write-Host " Username : [$username] ($($username.Length) characters)"
Write-Host " Password : $($password.Length) characters"
$warned = $false
if ($username -ne $username.Trim()) {
Write-Host " !! The username has a leading or trailing space." -ForegroundColor Yellow
$warned = $true
}
if ($password -ne $password.Trim()) {
Write-Host " !! The password has a leading or trailing space." -ForegroundColor Yellow
$warned = $true
}
if ($password -match '[\$`]') {
Write-Host " !! The password contains a `$ or a backtick." -ForegroundColor Yellow
Write-Host " In a PowerShell script these MUST be in single quotes:" -ForegroundColor Yellow
Write-Host " `$Password = 'the`$password' <- correct" -ForegroundColor Yellow
Write-Host " `$Password = `"the`$password`" <- WRONG, PowerShell eats `$password" -ForegroundColor Yellow
Write-Host " This is the most common reason a login works in the WinSCP" -ForegroundColor Yellow
Write-Host " window but fails from a script with the same credentials." -ForegroundColor Yellow
$warned = $true
}
if ($password -match '[^\x20-\x7E]') {
Write-Host " !! The password contains a non-standard character." -ForegroundColor Yellow
$warned = $true
}
if (-not $warned) {
Write-Host " Nothing unusual about either string." -ForegroundColor Green
}
# -- Try it -------------------------------------------------------------------
$logFile = Join-Path $scriptRoot "login-test.log"
if (Test-Path $logFile) { Remove-Item $logFile -Force }
$opts = New-Object WinSCP.SessionOptions
$opts.HostName = $serverHost
$opts.PortNumber = $port
$opts.UserName = $username
$opts.Password = $password
if ($protocol -eq "sftp") {
$opts.Protocol = [WinSCP.Protocol]::Sftp
$opts.GiveUpSecurityAndAcceptAnySshHostKey = $true
} else {
$opts.Protocol = [WinSCP.Protocol]::Ftp
if ($protocol -eq "ftps") {
$opts.FtpSecure = [WinSCP.FtpSecure]::Explicit
$opts.GiveUpSecurityAndAcceptAnyTlsHostCertificate = $true
} else {
$opts.FtpSecure = [WinSCP.FtpSecure]::None
}
}
Write-Host ""
Write-Host ("-" * 64)
Write-Host "Connecting" -ForegroundColor Cyan
Write-Host ("-" * 64)
$session = New-Object WinSCP.Session
$session.SessionLogPath = $logFile
$connected = $false
try {
$session.Open($opts)
$connected = $true
Write-Host " CONNECTED" -ForegroundColor Green
} catch {
Write-Host " FAILED" -ForegroundColor Red
Write-Host ""
Write-Host " $($_.Exception.Message)" -ForegroundColor Red
}
if ($connected) {
try {
Write-Host ""
Write-Host " Home directory: $($session.HomePath)" -ForegroundColor Gray
Write-Host " Folders visible at the top level:" -ForegroundColor Gray
$listing = $session.ListDirectory($session.HomePath)
$shown = 0
foreach ($item in $listing.Files) {
if ($item.Name -eq "." -or $item.Name -eq "..") { continue }
$kind = if ($item.IsDirectory) { "[dir] " } else { " " }
Write-Host " $kind$($item.Name)"
$shown++
if ($shown -ge 25) { Write-Host " ... (more)"; break }
}
if ($shown -eq 0) { Write-Host " (nothing - the account may be locked to an empty folder)" }
} catch {
Write-Host " Connected, but could not list the folder: $($_.Exception.Message)" -ForegroundColor Yellow
}
Write-Host ""
$checkPath = Read-Answer "Remote folder to check (or . to skip)" "."
if ($checkPath -ne ".") {
try {
if ($session.FileExists($checkPath)) {
Write-Host " $checkPath ... found" -ForegroundColor Green
} else {
Write-Host " $checkPath ... NOT THERE" -ForegroundColor Red
}
} catch {
Write-Host " Could not check: $($_.Exception.Message)" -ForegroundColor Yellow
}
}
}
$session.Dispose()
# -- The authentication conversation ------------------------------------------
if (-not $connected -and (Test-Path $logFile)) {
Write-Host ""
Write-Host ("-" * 64)
Write-Host "What the server actually said" -ForegroundColor Cyan
Write-Host ("-" * 64)
$interesting = Select-String -Path $logFile -Pattern "authentic|password|keyboard|publickey|banner|denied|method" -AllMatches
if ($interesting) {
foreach ($line in ($interesting | Select-Object -First 30)) {
Write-Host " $($line.Line.Trim())" -ForegroundColor Gray
}
Write-Host ""
Write-Host " Look for the line listing which methods the server offers." -ForegroundColor Yellow
Write-Host " If it offers only 'publickey', a password will never work and" -ForegroundColor Yellow
Write-Host " the partner has to either enable password auth or issue a key." -ForegroundColor Yellow
} else {
Write-Host " (nothing recognisable - see the full log)" -ForegroundColor Gray
}
Write-Host ""
Write-Host " Full log: $logFile" -ForegroundColor Cyan
Write-Host " It contains no password. Send it to Brad." -ForegroundColor Cyan
}
Write-Host ""
Write-Host ("=" * 64) -ForegroundColor Cyan
if ($connected) {
Write-Host " Login works. The account is fine - so if a script using these" -ForegroundColor Green
Write-Host " same details still fails, the script is mangling one of them." -ForegroundColor Green
} else {
Write-Host " Login failed. Compare against the WinSCP window: File > Session" -ForegroundColor Yellow
Write-Host " > Generate Session URL/Code > .NET assembly code. That shows the" -ForegroundColor Yellow
Write-Host " exact settings the working connection uses." -ForegroundColor Yellow
}
Write-Host ("=" * 64) -ForegroundColor Cyan
Write-Host ""