Files
melissa-uploader/Test-Login.ps1
T

309 lines
13 KiB
PowerShell
Raw Normal View History

# 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 ""