Files
melissa-uploader/Test-Login.ps1
T
blance 1fa89da183 Confirm the typed password, and read the auth outcome from the log
Melissa's session log settles the earlier theory: WinSCP negotiated,
sent the password, and srtSSHServer answered "Password authentication
failed". The password was typed into this script rather than read from a
string literal, so PowerShell interpolation was never involved and the
credential is simply not being accepted.

Two changes follow from that. The password is now entered twice and
compared - nothing echoes while typing, so a slipped paste and a good
one look identical, and a test on the wrong string proves nothing.

And the failure report now reads the log rather than dumping matching
lines: it separates "the server rejected the password we sent" from "the
server never offered password auth" from "it failed before any password
was sent", because those three have completely different fixes.
2026-08-30 20:50:26 -05:00

309 lines
13 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
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
# Find WinSCPnet.dll wherever it happens to live - this script is meant to be
# dropped next to whatever uploader is in use, not just this one.
function Find-WinSCPDll {
$parent = Split-Path -Parent $scriptRoot
$candidates = @(
(Join-Path $scriptRoot "WinSCP\WinSCPnet.dll"),
(Join-Path $scriptRoot "WinSCPnet.dll"),
(Join-Path $parent "WinSCP\WinSCPnet.dll"),
(Join-Path $parent "WinSCPnet.dll"),
"C:\Program Files (x86)\WinSCP\WinSCPnet.dll",
"C:\Program Files\WinSCP\WinSCPnet.dll"
)
foreach ($c in $candidates) {
if (Test-Path $c) { return $c }
}
# Anywhere under the folder this script sits in.
$found = Get-ChildItem -Path $scriptRoot -Filter "WinSCPnet.dll" -Recurse -File -ErrorAction SilentlyContinue |
Select-Object -First 1
if ($found) { return $found.FullName }
return $null
}
$winscpDll = Find-WinSCPDll
if (-not $winscpDll) {
Write-Host ""
Write-Host "Could not find WinSCPnet.dll automatically." -ForegroundColor Yellow
Write-Host "It sits either in a WinSCP folder next to your upload script, or" -ForegroundColor Gray
Write-Host "in C:\Program Files (x86)\WinSCP\ if WinSCP is installed normally." -ForegroundColor Gray
Write-Host ""
$winscpDll = Read-Answer "Full path to WinSCPnet.dll" $null
if (-not (Test-Path $winscpDll)) {
Write-Host "Nothing at that path." -ForegroundColor Red
exit 1
}
}
Write-Host ""
Write-Host "Using $winscpDll" -ForegroundColor DarkGray
# Windows tags files copied from elsewhere as untrusted, which stops Add-Type
# loading them. Harmless everywhere else, so failure here is not worth a word.
try {
Get-ChildItem -Path (Split-Path -Parent $winscpDll) -Recurse -File -ErrorAction SilentlyContinue |
Unblock-File -ErrorAction SilentlyContinue
} catch { }
try {
Add-Type -Path $winscpDll
} catch {
Write-Host "Could not load WinSCPnet.dll - $($_.Exception.Message)" -ForegroundColor Red
Write-Host "If it says the operation is not supported, Windows has the file" -ForegroundColor Yellow
Write-Host "blocked: right-click it, Properties, tick Unblock, OK." -ForegroundColor Yellow
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)
# Nothing echoes while typing, so a slipped paste looks identical to a good
# one. Typing it twice is the only way to be sure of what actually went in.
Write-Host "Type it once more to check: " -NoNewline -ForegroundColor White
$secure2 = Read-Host -AsSecureString
$bstr2 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure2)
$password2 = [Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr2)
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr2)
if ($password -ne $password2) {
Write-Host ""
Write-Host " !! The two did not match." -ForegroundColor Red
Write-Host " First entry: $($password.Length) characters. Second: $($password2.Length)." -ForegroundColor Red
Write-Host " One of them is wrong, so this test would prove nothing." -ForegroundColor Red
Write-Host " Start over and paste carefully, or type it by hand." -ForegroundColor Yellow
Write-Host ""
exit 1
}
# -- 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)
$log = Get-Content $logFile -ErrorAction SilentlyContinue
$server = $log | Select-String -Pattern "Remote version:" | Select-Object -First 1
if ($server) { Write-Host " $(($server.Line -split 'Remote version:')[1].Trim())" -ForegroundColor Gray }
$methodLine = $log | Select-String -Pattern "Server offered these authentication methods:" | Select-Object -First 1
$methods = ""
if ($methodLine) {
$methods = (($methodLine.Line -split "authentication methods:")[1]).Trim()
Write-Host " Methods the server accepts: $methods" -ForegroundColor Gray
}
$sentPassword = ($log | Select-String -Pattern "Sent password").Count -gt 0
$pwRejected = ($log | Select-String -Pattern "Password authentication failed").Count -gt 0
Write-Host ""
if ($pwRejected -or ($sentPassword -and -not $connected)) {
Write-Host " The password reached the server and the server rejected it." -ForegroundColor Yellow
Write-Host ""
Write-Host " That rules out the script, the protocol, the port and the host" -ForegroundColor Yellow
Write-Host " key - all of those worked. Only three things are left:" -ForegroundColor Yellow
Write-Host ""
Write-Host " 1. The password is not the one this account expects." -ForegroundColor White
Write-Host " Check it is this practice's own password, not another's." -ForegroundColor Gray
Write-Host " 2. The account is locked out from repeated failed tries," -ForegroundColor White
Write-Host " or expired. Log in with the WinSCP window right now - if" -ForegroundColor Gray
Write-Host " that fails too, it is the account, not anything here." -ForegroundColor Gray
Write-Host " 3. The account is set to require a key rather than a" -ForegroundColor White
Write-Host " password, even though the server allows passwords" -ForegroundColor Gray
Write-Host " generally. Only the partner can tell you that." -ForegroundColor Gray
} elseif ($methods -and $methods -notmatch "password") {
Write-Host " The server never offered password authentication." -ForegroundColor Yellow
Write-Host " It accepts: $methods" -ForegroundColor Yellow
Write-Host " A password will never work here - the partner has to enable" -ForegroundColor Yellow
Write-Host " password auth or issue an SSH key for this account." -ForegroundColor Yellow
} else {
Write-Host " It failed before any password was sent - so this is about" -ForegroundColor Yellow
Write-Host " reaching the server, not about the credentials." -ForegroundColor Yellow
}
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 ""