# PowerShell script to upload files to SFTP server with remote folder cleanup # Works on clean Windows without additional utilities (uses WinSCP) # ========================================== # PARAMETERS (can override config values via command line) # ========================================== param( [Parameter(Mandatory=$false, HelpMessage="SFTP server IP address or hostname")] [string]$ServerAddress, [Parameter(Mandatory=$false, HelpMessage="Username for connection")] [string]$Username, [Parameter(Mandatory=$false, HelpMessage="Password for connection")] [string]$PasswordParam, [Parameter(Mandatory=$false, HelpMessage="Local file path or pattern (e.g., C:\files\* or dist/builds/x64/Rosetta-*.exe)")] [string]$LocalFilePath, [Parameter(Mandatory=$false, HelpMessage="Remote folder on server")] [string]$RemoteFolderPath, [Parameter(Mandatory=$false, HelpMessage="SSH port")] [int]$Port, [Parameter(Mandatory=$false, HelpMessage="Path to WinSCP executable (auto-detect if not provided)")] [string]$WinSCPPath ) # ========================================== # CONFIGURATION - Default fallback values # ========================================== # These values are used only if not provided via command-line parameters or environment variables $CONFIG_ServerAddress = "" $CONFIG_Username = "" $CONFIG_Password = "" $CONFIG_LocalFilePath = "" $CONFIG_RemoteFolderPath = "" $CONFIG_Port = 22 $CONFIG_WinSCPPath = "" # Priority: Command-line Parameters (highest) > Environment Variables > Config Values (lowest) # If parameter not provided via command line, check environment variable, then use config value if (-not $ServerAddress) { $ServerAddress = if ($env:SFTP_SERVER) { $env:SFTP_SERVER } else { $CONFIG_ServerAddress } } if (-not $Username) { $Username = if ($env:SFTP_USERNAME) { $env:SFTP_USERNAME } else { $CONFIG_Username } } if (-not $PasswordParam) { $PasswordParam = if ($env:SFTP_PASSWORD) { $env:SFTP_PASSWORD } else { $CONFIG_Password } } if (-not $LocalFilePath) { $LocalFilePath = if ($env:SFTP_LOCAL_PATH) { $env:SFTP_LOCAL_PATH } else { $CONFIG_LocalFilePath } } if (-not $RemoteFolderPath) { $RemoteFolderPath = if ($env:SFTP_REMOTE_PATH) { $env:SFTP_REMOTE_PATH } else { $CONFIG_RemoteFolderPath } } if (-not $Port -or $Port -eq 0) { $Port = if ($env:SFTP_PORT) { [int]$env:SFTP_PORT } else { $CONFIG_Port } } if (-not $WinSCPPath) { $WinSCPPath = if ($env:WINSCP_PATH) { $env:WINSCP_PATH } else { $CONFIG_WinSCPPath } } # Validate required parameters $requiredParams = @( @{Name = "ServerAddress"; Value = $ServerAddress}, @{Name = "Username"; Value = $Username}, @{Name = "Password"; Value = $PasswordParam}, @{Name = "LocalFilePath"; Value = $LocalFilePath}, @{Name = "RemoteFolderPath"; Value = $RemoteFolderPath} ) $missingParams = @() foreach ($param in $requiredParams) { if ([string]::IsNullOrWhiteSpace($param.Value)) { $missingParams += $param.Name } } if ($missingParams.Count -gt 0) { Write-Host "ERROR: Missing required parameters: $($missingParams -join ', ')" -ForegroundColor Red Write-Host "Please configure values in the script CONFIG section or pass them as parameters." -ForegroundColor Red exit 1 } # Logging function function Write-Log { param( [Parameter(Mandatory=$false)] [string]$Message = "(empty message)", [Parameter(Mandatory=$false)] [ValidateSet("Info", "Warning", "Error", "Success")] [string]$Level = "Info" ) # Handle null or empty messages if ([string]::IsNullOrWhiteSpace($Message)) { $Message = "(empty message)" } $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $color = switch ($Level) { "Error" { "Red" } "Warning" { "Yellow" } "Success" { "Green" } default { "White" } } Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color } # Function to find WinSCP installation function Find-WinSCP { $possiblePaths = @( "C:\Program Files\WinSCP\WinSCP.com", "C:\Program Files (x86)\WinSCP\WinSCP.com", "C:\Program Files\WinSCP\WinSCP.exe", "C:\Program Files (x86)\WinSCP\WinSCP.exe", "C:\Program Files\WinSCP\WinSCPPortable.exe", "C:\Program Files (x86)\WinSCP\WinSCPPortable.exe" ) foreach ($path in $possiblePaths) { if (Test-Path $path) { Write-Log "Found WinSCP at: $path" "Info" return $path } } Write-Log "WinSCP not found. Please install it from https://winscp.net/" "Error" return $null } # Main upload function using WinSCP function Upload-ToSFTP { param( [Parameter(Mandatory=$true)] [string]$Server, [Parameter(Mandatory=$true)] [string]$User, [Parameter(Mandatory=$true)] [string]$Pass, [Parameter(Mandatory=$true)] [string[]]$FileList, [Parameter(Mandatory=$true)] [string]$RemotePath, [Parameter(Mandatory=$true)] [int]$PortNum, [Parameter(Mandatory=$true)] [string]$WinSCPExe ) # Password is already a plain string, use it directly $plainPassword = $Pass # Escape special characters in password that could break URL or WinSCP syntax # Replace @ with %40, : with %3A, # with %23, $ with %24, & with %26 $escapedPassword = $plainPassword $escapedPassword = $escapedPassword -replace '@', '%40' $escapedPassword = $escapedPassword -replace ':', '%3A' $escapedPassword = $escapedPassword -replace '#', '%23' $escapedPassword = $escapedPassword -replace '\$', '%24' $escapedPassword = $escapedPassword -replace '`', '%60' $escapedPassword = $escapedPassword -replace '&', '%26' # Create temporary file paths BEFORE script content (needed for variable expansion) $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss_fff' $debugDir = Join-Path $env:TEMP "winscp_debug" if (-not (Test-Path $debugDir)) { New-Item -ItemType Directory -Path $debugDir -Force | Out-Null } $scriptPath = Join-Path $debugDir "script_$timestamp.txt" $logFile = Join-Path $debugDir "log_$timestamp.txt" $outputPath = Join-Path $debugDir "output_$timestamp.txt" $errorPath = Join-Path $debugDir "error_$timestamp.txt" # Create WinSCP script file WITH password (use @"..."@ to expand variables) $scriptContent = @" option batch abort option confirm off option echo off option reconnecttime 3 "@ # Add connection string with auto-accept of host key $scriptContent += "`r`nopen sftp://$User`:$escapedPassword@$Server`:$PortNum -hostkey=`"*`"`r`n" # Try to clear remote folder by removing all .exe files (ignore if none exist) $scriptContent += "call rm -f $RemotePath/*.exe`r`n" # Add files to WinSCP script if ($FileList.Count -eq 0) { Write-Log "No files found matching pattern" "Warning" $scriptContent += "exit`r`n" } else { foreach ($filePath in $FileList) { # For local Windows paths, keep backslashes as-is (don't convert to forward slashes) # WinSCP needs native Windows paths for local files $remoteFilename = Split-Path $filePath -Leaf $scriptContent += "put `"$filePath`" `"$RemotePath/$remoteFilename`"`r`n" } $scriptContent += "close`r`nexit`r`n" } # Save script to temporary file try { Set-Content -Path $scriptPath -Value $scriptContent -Encoding UTF8 Write-Log "Created WinSCP script at: $scriptPath" "Info" Write-Log "Script content:" "Info" Get-Content $scriptPath | ForEach-Object { Write-Log "$_" "Info" } Write-Log "Executing WinSCP: $WinSCPExe" "Info" try { # Determine if this is .com (command-line) or .exe (GUI) $isCom = $WinSCPExe -like "*.com" if ($isCom) { # WinSCP.com uses /log= for logging $process = Start-Process -FilePath $WinSCPExe ` -ArgumentList "/log=$logFile /script=$scriptPath" ` -NoNewWindow ` -PassThru ` -Wait ` -RedirectStandardOutput $outputPath ` -RedirectStandardError $errorPath } else { # WinSCP.exe (GUI) - needs option logfile in script $scriptContent += "`r`noption logfile=$logFile" Set-Content -Path $scriptPath -Value $scriptContent -Encoding UTF8 $process = Start-Process -FilePath $WinSCPExe ` -ArgumentList "/console /script=$scriptPath" ` -NoNewWindow ` -PassThru ` -Wait ` -RedirectStandardOutput $outputPath ` -RedirectStandardError $errorPath } } catch { Write-Log "Error starting process: $_" "Error" throw } Write-Log "WinSCP process finished with exit code: $($process.ExitCode)" "Info" # Read WinSCP logs $winscp_log = Get-Content $logFile -ErrorAction SilentlyContinue -Raw $output = Get-Content $outputPath -ErrorAction SilentlyContinue -Raw $error_output = Get-Content $errorPath -ErrorAction SilentlyContinue -Raw if ($winscp_log) { Write-Log "WinSCP Log:`r`n$winscp_log" "Info" } if ($output) { Write-Log "Output:`r`n$output" "Info" } else { Write-Log "No standard output from WinSCP" "Info" } if ($error_output) { Write-Log "Standard Error:`r`n$error_output" "Error" } if ($process.ExitCode -eq 0) { Write-Log "Upload completed successfully" "Success" return $true } else { Write-Log "Upload failed with exit code: $($process.ExitCode)" "Error" return $false } } catch { Write-Log "Error during upload: $_" "Error" return $false } finally { # Cleanup temporary files Start-Sleep -Milliseconds 500 if (Test-Path $scriptPath) { Remove-Item $scriptPath -Force -ErrorAction SilentlyContinue } if (Test-Path $logFile) { Remove-Item $logFile -Force -ErrorAction SilentlyContinue } if (Test-Path $outputPath) { Remove-Item $outputPath -Force -ErrorAction SilentlyContinue } if (Test-Path $errorPath) { Remove-Item $errorPath -Force -ErrorAction SilentlyContinue } } } # ================= # MAIN LOGIC # ================= Write-Log "========== STARTING FILE UPLOAD PROCESS ==========" "Info" Write-Log "Server: $ServerAddress`:$Port" "Info" Write-Log "Username: $Username" "Info" Write-Log "File pattern: $LocalFilePath" "Info" Write-Log "Remote folder: $RemoteFolderPath" "Info" Write-Log "=============================================" "Info" # Find WinSCP if path not provided if (-not $WinSCPPath) { $WinSCPPath = Find-WinSCP if (-not $WinSCPPath) { exit 1 } } # Verify WinSCP exists if (-not (Test-Path $WinSCPPath)) { Write-Log "Error: WinSCP not found at: $WinSCPPath" "Error" exit 1 } # Get files matching pattern $files = @(Get-Item -Path $LocalFilePath -ErrorAction SilentlyContinue | Where-Object {-not $_.PSIsContainer}) if ($files.Count -eq 0) { Write-Log "Error: No files found matching pattern: $LocalFilePath" "Error" Write-Log "Current directory: $(Get-Location)" "Error" Write-Log "Checking if path exists: $(Test-Path $LocalFilePath)" "Error" exit 1 } Write-Log "Found $($files.Count) file(s) to upload" "Info" $filePathList = @($files | ForEach-Object {$_.FullName}) # Perform upload $success = Upload-ToSFTP -Server $ServerAddress ` -User $Username ` -Pass $PasswordParam ` -FileList $filePathList ` -RemotePath $RemoteFolderPath ` -PortNum $Port ` -WinSCPExe $WinSCPPath Write-Log "========== PROCESS COMPLETED ==========" "Info" if ($success) { exit 0 } else { exit 1 }