Public Access
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.
225 lines
8.8 KiB
PowerShell
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 ""
|