Public Access
Compare commits
4
Commits
1edd6520a3
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
698c102c1b | ||
|
|
c3ad14cb88 | ||
|
|
1965e07c7c | ||
|
|
b34360c917 |
+173
-15
@@ -83,29 +83,133 @@ try {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Read-Number($question, $min, $max, $default) {
|
||||||
|
while ($true) {
|
||||||
|
Write-Host "$question " -NoNewline -ForegroundColor White
|
||||||
|
Write-Host "[$default]" -NoNewline -ForegroundColor DarkGray
|
||||||
|
Write-Host ": " -NoNewline -ForegroundColor White
|
||||||
|
$a = Read-Host
|
||||||
|
if ([string]::IsNullOrWhiteSpace($a)) { $a = [string]$default }
|
||||||
|
$n = 0
|
||||||
|
if ([int]::TryParse(([string]$a).Trim(), [ref]$n) -and $n -ge $min -and $n -le $max) { return $n }
|
||||||
|
Write-Host " (a number between $min and $max)" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# FileZilla keeps site passwords Base64-encoded, so the value sitting in the
|
||||||
|
# exported XML is not the password - it has to be decoded first. Copying it
|
||||||
|
# out verbatim fails against the server every time, which is exactly what a
|
||||||
|
# working FileZilla login plus a failing script looks like.
|
||||||
|
function Import-FileZillaSite {
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
$serverHost = Read-Answer "Server" $null
|
Write-Host "In FileZilla: File > Export, tick 'Site Manager entries', save the .xml." -ForegroundColor Gray
|
||||||
|
$path = Read-Answer "Path to the exported .xml" $null
|
||||||
|
$path = $path.Trim('"').Trim()
|
||||||
|
if (-not (Test-Path $path)) {
|
||||||
|
Write-Host " Nothing at that path." -ForegroundColor Red
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$xml = [xml](Get-Content $path -Raw)
|
||||||
|
} catch {
|
||||||
|
Write-Host " That file is not readable XML - $($_.Exception.Message)" -ForegroundColor Red
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$servers = @($xml.SelectNodes("//Server"))
|
||||||
|
if ($servers.Count -eq 0) {
|
||||||
|
Write-Host " No sites found in that file." -ForegroundColor Red
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$node = $servers[0]
|
||||||
|
if ($servers.Count -gt 1) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Sites in that file:" -ForegroundColor White
|
||||||
|
for ($i = 0; $i -lt $servers.Count; $i++) {
|
||||||
|
$n = $servers[$i]
|
||||||
|
Write-Host (" {0}. {1} {2}@{3}:{4}" -f ($i + 1), $n.Name, $n.User, $n.Host, $n.Port)
|
||||||
|
}
|
||||||
|
$node = $servers[(Read-Number "Which one" 1 $servers.Count 1) - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
$pass = $null
|
||||||
|
$passNode = $node.SelectSingleNode("Pass")
|
||||||
|
if ($passNode) {
|
||||||
|
$encoding = $passNode.GetAttribute("encoding")
|
||||||
|
if ($encoding -eq "base64") {
|
||||||
|
try {
|
||||||
|
$pass = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($passNode.InnerText))
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " Decoded the password from Base64." -ForegroundColor Green
|
||||||
|
Write-Host " The XML holds $($passNode.InnerText.Length) characters; the real" -ForegroundColor Gray
|
||||||
|
Write-Host " password is $($pass.Length). Using the decoded one." -ForegroundColor Gray
|
||||||
|
} catch {
|
||||||
|
Write-Host " The password in the XML is not valid Base64." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
} elseif ($encoding -eq "crypt") {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " This export is protected by a FileZilla master password," -ForegroundColor Yellow
|
||||||
|
Write-Host " so the password cannot be read out of it. Type it instead." -ForegroundColor Yellow
|
||||||
|
} else {
|
||||||
|
$pass = $passNode.InnerText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# FileZilla's protocol numbering: 1 is SFTP, 0 is plain FTP, and the
|
||||||
|
# TLS variants use higher values. Only used as the menu default.
|
||||||
|
$proto = 1
|
||||||
|
switch ([string]$node.Protocol) {
|
||||||
|
"0" { $proto = 3 }
|
||||||
|
"1" { $proto = 1 }
|
||||||
|
"3" { $proto = 2 }
|
||||||
|
"4" { $proto = 2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
return @{
|
||||||
|
Host = [string]$node.Host
|
||||||
|
Port = [string]$node.Port
|
||||||
|
User = [string]$node.User
|
||||||
|
Password = $pass
|
||||||
|
Proto = $proto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$fromXml = $null
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Where are the details coming from?" -ForegroundColor White
|
||||||
|
Write-Host " 1. I will type them in"
|
||||||
|
Write-Host " 2. Read them from a FileZilla site export (.xml)"
|
||||||
|
if ((Read-Number "Choose 1-2" 1 2 1) -eq 2) {
|
||||||
|
$fromXml = Import-FileZillaSite
|
||||||
|
if (-not $fromXml) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Falling back to typing them in." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
$serverHost = Read-Answer "Server" $(if ($fromXml) { $fromXml.Host } else { $null })
|
||||||
$serverHost = ($serverHost -replace '^\w+://', '').Split('/')[0].Trim()
|
$serverHost = ($serverHost -replace '^\w+://', '').Split('/')[0].Trim()
|
||||||
|
|
||||||
$protoChoice = 0
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "Protocol:" -ForegroundColor White
|
Write-Host "Protocol:" -ForegroundColor White
|
||||||
Write-Host " 1. SFTP"
|
Write-Host " 1. SFTP"
|
||||||
Write-Host " 2. FTPS"
|
Write-Host " 2. FTPS"
|
||||||
Write-Host " 3. FTP"
|
Write-Host " 3. FTP"
|
||||||
while ($protoChoice -lt 1 -or $protoChoice -gt 3) {
|
$protoDefault = if ($fromXml) { $fromXml.Proto } else { 1 }
|
||||||
Write-Host "Choose 1-3 " -NoNewline -ForegroundColor White
|
$protoChoice = Read-Number "Choose 1-3" 1 3 $protoDefault
|
||||||
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]
|
$protocol = @("sftp", "ftps", "ftp")[$protoChoice - 1]
|
||||||
$defaultPort = if ($protocol -eq "sftp") { 22 } else { 21 }
|
|
||||||
|
|
||||||
$port = [int](Read-Answer "Port" $defaultPort)
|
$defaultPort = if ($fromXml -and $fromXml.Port) { $fromXml.Port }
|
||||||
$username = Read-Answer "Username" $null
|
elseif ($protocol -eq "sftp") { 22 } else { 21 }
|
||||||
|
$port = Read-Number "Port" 1 65535 $defaultPort
|
||||||
|
$username = Read-Answer "Username" $(if ($fromXml) { $fromXml.User } else { $null })
|
||||||
|
|
||||||
|
if ($fromXml -and $fromXml.Password) {
|
||||||
|
$password = $fromXml.Password
|
||||||
|
} else {
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host "Paste the password. It is not echoed and not saved anywhere." -ForegroundColor DarkGray
|
Write-Host "Paste the password. It is not echoed and not saved anywhere." -ForegroundColor DarkGray
|
||||||
Write-Host "Password: " -NoNewline -ForegroundColor White
|
Write-Host "Password: " -NoNewline -ForegroundColor White
|
||||||
@@ -131,6 +235,7 @@ if ($password -ne $password2) {
|
|||||||
Write-Host ""
|
Write-Host ""
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# -- What the credentials actually contain ------------------------------------
|
# -- What the credentials actually contain ------------------------------------
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
@@ -163,6 +268,19 @@ if ($password -match '[^\x20-\x7E]') {
|
|||||||
Write-Host " !! The password contains a non-standard character." -ForegroundColor Yellow
|
Write-Host " !! The password contains a non-standard character." -ForegroundColor Yellow
|
||||||
$warned = $true
|
$warned = $true
|
||||||
}
|
}
|
||||||
|
# A password copied straight out of a FileZilla export is still Base64.
|
||||||
|
if (-not $fromXml -and $password.Length -ge 8 -and $password -cmatch '^[A-Za-z0-9+/]+={0,2}$' -and ($password.Length % 4) -eq 0) {
|
||||||
|
$decoded = $null
|
||||||
|
try { $decoded = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($password)) } catch { }
|
||||||
|
if ($decoded -and $decoded -match '^[\x20-\x7E]+$') {
|
||||||
|
Write-Host " !! This looks like Base64, not a password." -ForegroundColor Yellow
|
||||||
|
Write-Host " FileZilla stores passwords Base64-encoded in its exported" -ForegroundColor Yellow
|
||||||
|
Write-Host " XML. Decoded, this is $($decoded.Length) characters - that is" -ForegroundColor Yellow
|
||||||
|
Write-Host " probably the real password. Re-run and choose option 2 to" -ForegroundColor Yellow
|
||||||
|
Write-Host " read the export directly, or paste the decoded value." -ForegroundColor Yellow
|
||||||
|
$warned = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
if (-not $warned) {
|
if (-not $warned) {
|
||||||
Write-Host " Nothing unusual about either string." -ForegroundColor Green
|
Write-Host " Nothing unusual about either string." -ForegroundColor Green
|
||||||
}
|
}
|
||||||
@@ -297,8 +415,48 @@ if (-not $connected -and (Test-Path $logFile)) {
|
|||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-Host ("=" * 64) -ForegroundColor Cyan
|
Write-Host ("=" * 64) -ForegroundColor Cyan
|
||||||
if ($connected) {
|
if ($connected) {
|
||||||
Write-Host " Login works. The account is fine - so if a script using these" -ForegroundColor Green
|
Write-Host " Login works. These details are correct." -ForegroundColor Green
|
||||||
Write-Host " same details still fails, the script is mangling one of them." -ForegroundColor Green
|
Write-Host ("=" * 64) -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Getting a working password into a script is its own trap, so hand back
|
||||||
|
# the literal to paste rather than leaving the quoting to chance.
|
||||||
|
$needsCare = $password -match '[\$`]'
|
||||||
|
if ($needsCare -or $password -match "'" -or $password -match '%') {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " Putting this password into a PowerShell script" -ForegroundColor Cyan
|
||||||
|
Write-Host (" " + ("-" * 60)) -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " Use exactly this line, single quotes included:" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " `$Password = $("'" + ($password -replace "'", "''") + "'")" -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
if ($needsCare) {
|
||||||
|
Write-Host " This password contains a `$ or a backtick. Inside DOUBLE" -ForegroundColor Yellow
|
||||||
|
Write-Host " quotes PowerShell treats that as a variable and silently" -ForegroundColor Yellow
|
||||||
|
Write-Host " sends something shorter - the login then fails even though" -ForegroundColor Yellow
|
||||||
|
Write-Host " the password is right. Single quotes send it untouched." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
if ($password -match "'") {
|
||||||
|
Write-Host " The password contains an apostrophe, so it is doubled in" -ForegroundColor Yellow
|
||||||
|
Write-Host " the line above. That is correct - it stays one character." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
if ($password -match '%') {
|
||||||
|
Write-Host " The password contains a %. That is fine in PowerShell, but" -ForegroundColor Yellow
|
||||||
|
Write-Host " in a .bat file it must be written as %% instead." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " Safer still: keep it out of the script. Put the password on" -ForegroundColor Gray
|
||||||
|
Write-Host " the first line of its own file and read it back - then no" -ForegroundColor Gray
|
||||||
|
Write-Host " quoting rules apply to it at all:" -ForegroundColor Gray
|
||||||
|
Write-Host ' $Password = Get-Content pw.txt -TotalCount 1' -ForegroundColor Gray
|
||||||
|
} else {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " Nothing in this password needs special quoting in a script." -ForegroundColor Green
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host ("=" * 64) -ForegroundColor Cyan
|
||||||
|
Write-Host " If a script using these same details still fails, the script is" -ForegroundColor Green
|
||||||
|
Write-Host " changing one of them on the way through." -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host " Login failed. Compare against the WinSCP window: File > Session" -ForegroundColor Yellow
|
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 " > Generate Session URL/Code > .NET assembly code. That shows the" -ForegroundColor Yellow
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
1fa89da 2026-08-30 login test reads the auth outcome
|
c3ad14c 2026-08-30 password quoting guidance
|
||||||
|
|||||||
Reference in New Issue
Block a user