Wicked Smart Data
LearnInsightsAboutContact
Sign InLet's Build
LearnInsightsAboutContact
Sign InLet's Build
Wicked Smart Data

Intelligence, automation, and expert execution — plus an elite library of free knowledge. We turn complexity into competitive advantage.

Start a conversation

Platform

  • Learning Paths
  • Insights
  • RSS Feed

Company

  • About
  • Contact
  • Work With Us

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Wicked Smart Data. All rights reserved.

Intelligence · Automation · Advantage

All Insights
Power Automate

Automating FTP and SFTP File Transfers in Power Automate Desktop: Connecting to Remote Servers, Uploading Batch Files, and Handling Transfer Errors in Unattended RPA Workflows

FTP and SFTP automation in Power Automate Desktop goes far beyond drag-and-drop upload actions — production-grade workflows require protocol awareness, secure credential management, per-file verification, and layered error handling. This lesson teaches you to build unattended transfer automation that handles real-world failures reliably, including the PowerShell/WinSCP path for SFTP that PAD's native actions don't cover.

🔥 Expert29 min readSep 22, 2026Updated Sep 22, 2026
Automating FTP and SFTP File Transfers in Power Automate Desktop: Connecting to Remote Servers, Uploading Batch Files, and Handling Transfer Errors in Unattended RPA Workflows
On this page
  • Introduction
  • Prerequisites
  • Understanding the Protocol Landscape Before You Write a Single Action
  • Setting Up the Flow Architecture Before Building Actions
  • Native FTP Actions in Power Automate Desktop
  • Opening an FTP Connection
  • Listing Remote Directory Contents
  • Uploading Files
  • Verifying Transfers: Don't Skip This Step
  • Closing the Connection
  • SFTP Transfers via PowerShell: The Production-Grade Approach
Installing WinSCP on the Automation Machine
  • Building the SFTP Upload Script
  • SSH Key Authentication
  • Implementing Multi-Layer Error Handling
  • Structuring Error Handling in PAD
  • Implementing Exponential Backoff for Retries
  • Building the Logging Infrastructure
  • Log File Structure
  • Integrating with SharePoint or Dataverse
  • File Management: Pre-Transfer Validation and Post-Transfer Archiving
  • Pre-Transfer Validation
  • Post-Transfer Archiving
  • Handling Duplicate File Names in Archive
  • Configuring for Unattended Execution
  • Scheduling and Triggers
  • Machine Configuration for Unattended Mode
  • Timeout and Concurrency
  • Hands-On Exercise
  • Common Mistakes & Troubleshooting
  • Mistake 1: Using Active FTP Mode Behind a Firewall
  • Mistake 2: SFTP Host Key Changes and Automation Breaks
  • Mistake 3: Password Expiry Causing Silent Failures
  • Mistake 4: Large File Transfers Timing Out Mid-Transfer
  • Mistake 5: Race Condition with Upstream File Generation
  • Mistake 6: PowerShell Script Failures Due to Execution Policy
  • Summary & Next Steps
  • Automating FTP and SFTP File Transfers in Power Automate Desktop: Connecting to Remote Servers, Uploading Batch Files, and Handling Transfer Errors in Unattended RPA Workflows

    Introduction

    Picture this: your organization generates nightly export files from three different legacy systems — purchase orders from an ERP, inventory snapshots from a warehouse management system, and reconciliation reports from a finance platform. Each of these needs to land on a trading partner's SFTP server by 6:00 AM, every business day, without anyone manually opening FileZilla and dragging files into a remote directory. When something goes wrong — a file is missing, the server is unreachable, or a transfer silently fails — you need the automation to detect it, retry intelligently, log the failure, and notify someone before the trading partner's batch job starts at 7:00 AM.

    This is exactly the kind of mission-critical, unattended file transfer workflow that Power Automate Desktop can own entirely. But doing it well requires more than just dropping an "FTP Upload" action into a flow. You need to understand the protocol differences between FTP and SFTP, manage credentials securely, handle the dozen ways a transfer can fail silently, build robust retry logic, and structure everything so it runs reliably as an unattended RPA job on a headless machine at 5:30 AM.

    By the end of this lesson, you'll have the knowledge and a working blueprint to build production-grade FTP/SFTP automation that handles real-world complexity. This isn't a happy-path tutorial — we'll cover the edge cases, the security implications, and the architectural decisions that separate a bot that works in testing from one that runs flawlessly in production for months.

    What you'll learn:

    • The architectural differences between FTP, FTPS, and SFTP and how they affect your Power Automate Desktop implementation choices
    • How to connect to remote FTP and SFTP servers using both native PAD actions and PowerShell-based approaches for advanced scenarios
    • How to build batch file upload logic that handles directory traversal, file filtering, and post-transfer archiving
    • How to implement multi-layer error handling that distinguishes network failures, authentication errors, and partial transfer failures
    • How to structure the entire solution for unattended execution with secure credential management, structured logging, and operational alerting

    Prerequisites

    Before diving in, you should be comfortable with the following:

    • Building and running desktop flows in Power Automate Desktop — if you're newer to the environment, start with Getting Started with Power Automate Desktop: Installing, Recording, and Running Your First Desktop Flow
    • Working with variables, lists, and loops in PAD — the Variables, Lists, and Data Tables in Power Automate Desktop: A Complete Practitioner's Guide article covers everything you need
    • Basic error handling concepts — the fundamentals are covered in Error Handling in Desktop Flows: On Block Error, Retry Policies, and Recovery Screenshots
    • A general understanding of unattended RPA execution — see Attended vs Unattended RPA: Choosing a Run Mode and Configuring Machines in Power Automate if you haven't set up unattended runs before

    You'll also need access to an FTP or SFTP server to test against. For development, free tools like FileZilla Server (for FTP/FTPS) or OpenSSH on Windows (for SFTP) work well locally.


    Understanding the Protocol Landscape Before You Write a Single Action

    Let's clear up something that trips up a lot of automation engineers: FTP, FTPS, and SFTP are three fundamentally different protocols, and treating them as interchangeable will cause you real pain.

    FTP (File Transfer Protocol) is the original, dating to 1971. It uses two separate TCP connections — a control channel on port 21 and a data channel on a dynamically negotiated port. It transmits credentials and data in plaintext. In 2024, no legitimate business partner should be asking you to transfer files over plain FTP, but legacy systems still exist, so we'll cover it.

    FTPS (FTP Secure) adds TLS encryption to FTP. It still uses the same dual-channel architecture, which creates firewall headaches (particularly with passive mode port ranges), but your credentials and data are encrypted. It runs on port 21 (explicit FTPS, where TLS is negotiated after connection) or port 990 (implicit FTPS, where TLS is mandatory from the start).

    SFTP (SSH File Transfer Protocol) is an entirely different beast — it has nothing to do with FTP despite the name similarity. SFTP runs over SSH on port 22, uses a single encrypted channel for everything, and supports key-based authentication. Most modern trading partners and cloud storage systems use SFTP. It's what you should prefer whenever you have a choice.

    Why does this matter for Power Automate Desktop? Because PAD's native FTP actions support FTP and FTPS only. SFTP support is notably absent from the built-in action library. This is a significant gap that forces you to choose between PowerShell scripting (using WinSCP or Posh-SSH modules) or third-party PAD connectors for true SFTP connectivity. We'll cover both paths.

    Key insight

    Don't assume "FTP actions" in PAD will handle your SFTP connection. Check your trading partner's requirements carefully. If they give you a host, port 22, and an SSH key or password, that's SFTP and you'll need the PowerShell path described later in this lesson.


    Setting Up the Flow Architecture Before Building Actions

    Before you start dragging actions into the canvas, design your flow architecture on paper. A well-structured FTP/SFTP automation for unattended use has several distinct layers:

    1. Initialization — Load configuration (server details, paths, file patterns), validate prerequisites (local files exist, credentials are available), set up logging variables.

    2. Pre-transfer validation — Confirm the source directory has the expected files, check file ages to catch stale data, optionally verify file sizes are non-zero.

    3. Connection — Establish the FTP/SFTP session. This step deserves its own error boundary since connection failures need different handling than mid-transfer failures.

    4. Transfer loop — Iterate through files, upload each one, verify the transfer (check remote file size matches local), and track results per file.

    5. Post-transfer actions — Archive uploaded files locally, clean up remote directories if needed, write final status to a log file or database.

    6. Notification and alerting — Send success/failure summaries via email, update a SharePoint list, or trigger a cloud flow with results.

    This layered approach maps naturally onto Subflows and Reusable Logic in Power Automate Desktop. Break each layer into a named subflow: Initialize, ValidateFiles, ConnectFTP, TransferFiles, PostTransfer, and SendAlert. Your main flow becomes a clean orchestration sequence that calls each subflow in order with shared variables flowing between them.

    The benefit of this architecture isn't just cleanliness — it's that you can implement targeted error handling at each layer. A connection failure triggers a different recovery path than a transfer failure for one file out of fifteen.


    Native FTP Actions in Power Automate Desktop

    Power Automate Desktop includes a built-in FTP action group. Let's walk through what's available and how to use it properly.

    Opening an FTP Connection

    The Open FTP connection action requires:

    • Host — the server hostname or IP address
    • Port — typically 21 for FTP/FTPS
    • Active/Passive mode — almost always use Passive in modern networks; active mode requires the server to initiate a connection back to your machine, which fails through most firewalls and NAT
    • Credentials — username and password
    • Protocol — FTP or FTPS (with explicit or implicit TLS option)
    • SSL certificate — for FTPS, decide whether to accept all certificates or validate against a trusted store

    The action produces an FTPConnection variable that you pass to every subsequent FTP action. This is analogous to a database connection object — keep it, reuse it, and close it explicitly when done.

    # Variable assignments in PAD notation:
    FTPHost = "ftp.tradingpartner.com"
    FTPPort = 21
    FTPUsername = %CredentialUsername%    # Loaded from secure variable
    FTPPassword = %CredentialPassword%    # Loaded from secure variable or Key Vault
    

    Warning

    Never hardcode FTP credentials as literal string values in your flow. At minimum, use PAD's Input variables marked as sensitive. For production unattended flows, retrieve credentials from Azure Key Vault at runtime — the pattern is explained in detail in Handling Credentials Securely in Desktop Flows: Sensitive Variables and Azure Key Vault. A hardcoded password in a desktop flow is visible to anyone with edit access to the flow.

    When building the Open FTP connection action, set the Timeout property. The default is often 30 seconds, but corporate network conditions — VPNs, proxy servers, network security appliances that inspect traffic — can introduce latency. For trading partner connections over the internet, 60 seconds is more realistic. Setting this too low causes spurious timeout errors that look like server availability problems.

    Listing Remote Directory Contents

    Before uploading, it's good practice to list the remote directory and confirm your target path exists. Use List FTP directory to retrieve the contents:

    FTP List directory action:
      FTP Connection: FTPConnection
      Directory: /incoming/purchase_orders/
      → Store result in: RemoteFileList
    

    The result is a list of FTP file objects with properties including Name, Size, LastModified, and IsFolder. You can inspect this list to verify the directory exists (if the action throws an error, the path likely doesn't exist) or to check for naming conflicts before uploading.

    Uploading Files

    The Upload file(s) to FTP action handles single files or can accept wildcard patterns. For batch uploads, you have two approaches:

    Option A — Upload all matching files at once using a wildcard:

    Upload file(s) to FTP action:
      FTP Connection: FTPConnection
      File(s) to upload: C:\Exports\PO_*.csv
      Remote path: /incoming/purchase_orders/
      If file exists: Overwrite
    

    Option B — Loop and upload individually (preferred for production):

    Get files in folder action:
      Folder: C:\Exports\PurchaseOrders\
      File filter: PO_*.csv
      → Store in: LocalFileList
    
    FOR EACH CurrentFile IN LocalFileList:
      Upload file(s) to FTP action:
        FTP Connection: FTPConnection
        File(s) to upload: %CurrentFile.FullName%
        Remote path: /incoming/purchase_orders/
        If file exists: Overwrite
      
      # Immediately verify the upload
      List FTP directory action:
        FTP Connection: FTPConnection
        Directory: /incoming/purchase_orders/
        → Store in: PostUploadList
      
      # Find the file we just uploaded in the remote listing
      ...verify file size matches...
      
      # Archive the local file
      Move file action:
        File to move: %CurrentFile.FullName%
        Destination: C:\Exports\Archive\
    

    Option B is significantly more robust because it lets you handle each file's success or failure independently. If one file fails to upload, the loop continues with the remaining files, and your failure tracking records exactly which file caused the problem. Option A treats the entire batch as an atomic unit — one failure potentially stops everything.

    Verifying Transfers: Don't Skip This Step

    One of the most dangerous assumptions in FTP automation is that a successful upload action means the file arrived correctly. FTP has no built-in integrity verification — the action completes when the data is sent, not when the server confirms it wrote correctly.

    After each upload, retrieve the remote file listing for that directory and compare the uploaded file's remote size to the local file's size:

    Get file info action:
      File: %CurrentFile.FullName%
      → Store in: LocalFileInfo
    
    # Find matching file in RemoteFileList
    FOR EACH RemoteFile IN PostUploadList:
      IF RemoteFile.Name = CurrentFile.Name THEN:
        IF RemoteFile.Size <> LocalFileInfo.Size THEN:
          SET TransferVerified = false
          SET FailureReason = "Size mismatch: Local=" + LocalFileInfo.Size + " Remote=" + RemoteFile.Size
        ELSE:
          SET TransferVerified = true
        END IF
      END IF
    END FOR
    

    Tip

    For text files (CSV, XML, JSON), a size mismatch might legitimately occur due to line-ending conversion during transfer (FTP's ASCII mode converts CRLF to LF). Either transfer in BINARY mode always, or account for the expected size difference in your verification logic. Most modern FTP servers handle this gracefully in binary mode.

    Closing the Connection

    Always close the FTP connection explicitly with the Close FTP connection action. Place this in a cleanup section that runs regardless of whether the transfer succeeded or failed — the same pattern as closing a database connection. Leaving connections open accumulates against server-side limits and can prevent future automation runs from connecting.


    SFTP Transfers via PowerShell: The Production-Grade Approach

    Since PAD lacks native SFTP actions, PowerShell is your primary tool. The two main options are:

    1. WinSCP .NET Assembly — WinSCP is a mature, widely-used SFTP client with excellent .NET integration. It supports SFTP, FTP, FTPS, SCP, and WebDAV, handles SSH key authentication, and provides comprehensive transfer verification.

    2. Posh-SSH PowerShell Module — A pure PowerShell module that wraps SSH/SFTP functionality, installable from the PowerShell Gallery.

    WinSCP is the better choice for production RPA for several reasons: it has superior error reporting, built-in transfer verification (checksum-based), a robust session object model, and excellent logging. Posh-SSH is simpler but more limited.

    Installing WinSCP on the Automation Machine

    On the unattended RPA machine, install WinSCP (the standard installer, not just the portable version) so the .NET assembly is available. Then confirm the assembly path:

    C:\Program Files (x86)\WinSCP\WinSCPnet.dll
    

    If you're deploying across multiple machines in a machine group, use a startup script or configuration management (SCCM, Intune) to ensure WinSCP is installed consistently. For more on machine group management, see Managing Machines and Machine Groups for Scalable Unattended Automation in Power Automate.

    Building the SFTP Upload Script

    In PAD, use the Run PowerShell script action to execute SFTP operations. Here's a complete, production-ready script for batch SFTP uploads with verification:

    # Parameters passed from PAD variables
    param(
        [string]$SFTPHost,
        [string]$SFTPUsername,
        [string]$SFTPPassword,
        [string]$SFTPPort = "22",
        [string]$RemotePath,
        [string]$LocalFolder,
        [string]$FilePattern,
        [string]$SSHHostKeyFingerprint,
        [string]$ArchiveFolder
    )
    
    # Output tracking
    $Results = @()
    $OverallSuccess = $true
    
    try {
        # Load WinSCP .NET assembly
        $WinSCPPath = "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
        Add-Type -Path $WinSCPPath
    
        # Configure session options
        $SessionOptions = New-Object WinSCP.SessionOptions -Property @{
            Protocol              = [WinSCP.Protocol]::Sftp
            HostName              = $SFTPHost
            UserName              = $SFTPUsername
            Password              = $SFTPPassword
            PortNumber            = [int]$SFTPPort
            SshHostKeyFingerprint = $SSHHostKeyFingerprint
        }
    
        $Session = New-Object WinSCP.Session
        
        # Enable transfer logging to a temp file
        $LogPath = "C:\RPA\Logs\WinSCP_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
        $Session.SessionLogPath = $LogPath
    
        # Open the SFTP session
        $Session.Open($SessionOptions)
    
        # Get files matching the pattern
        $FilesToUpload = Get-ChildItem -Path $LocalFolder -Filter $FilePattern
    
        if ($FilesToUpload.Count -eq 0) {
            Write-Output "NO_FILES_FOUND"
            exit 0
        }
    
        foreach ($File in $FilesToUpload) {
            $FileResult = [PSCustomObject]@{
                FileName    = $File.Name
                LocalSize   = $File.Length
                Status      = "Pending"
                ErrorDetail = ""
            }
    
            try {
                # Build transfer options with verification
                $TransferOptions = New-Object WinSCP.TransferOptions
                $TransferOptions.TransferMode = [WinSCP.TransferMode]::Binary
                $TransferOptions.ResumeSupport.State = [WinSCP.TransferResumeSupportState]::Off
    
                # Upload the file
                $TransferResult = $Session.PutFiles(
                    $File.FullName,
                    ($RemotePath.TrimEnd("/") + "/" + $File.Name),
                    $false,  # Don't remove source file
                    $TransferOptions
                )
    
                # Check for transfer errors
                $TransferResult.Check()
    
                # Verify remote file exists and size matches
                $RemoteFileInfo = $Session.GetFileInfo($RemotePath.TrimEnd("/") + "/" + $File.Name)
                
                if ($RemoteFileInfo.Length -ne $File.Length) {
                    throw "Size mismatch: local=$($File.Length), remote=$($RemoteFileInfo.Length)"
                }
    
                # Archive the local file
                if (![string]::IsNullOrEmpty($ArchiveFolder)) {
                    $ArchivePath = Join-Path $ArchiveFolder $File.Name
                    Move-Item -Path $File.FullName -Destination $ArchivePath -Force
                }
    
                $FileResult.Status = "Success"
            }
            catch {
                $FileResult.Status = "Failed"
                $FileResult.ErrorDetail = $_.Exception.Message
                $OverallSuccess = $false
            }
    
            $Results += $FileResult
        }
    
        $Session.Close()
    }
    catch {
        # Session-level failure (connection, authentication, etc.)
        Write-Output "SESSION_ERROR: $($_.Exception.Message)"
        exit 1
    }
    
    # Output structured results as JSON for PAD to parse
    $Output = [PSCustomObject]@{
        OverallSuccess = $OverallSuccess
        Results        = $Results
        TotalFiles     = $Results.Count
        SuccessCount   = ($Results | Where-Object { $_.Status -eq "Success" }).Count
        FailureCount   = ($Results | Where-Object { $_.Status -eq "Failed" }).Count
    }
    
    Write-Output ($Output | ConvertTo-Json -Depth 5)
    

    In PAD, call this with the Run PowerShell script action and capture the ScriptOutput variable. Then parse the JSON output:

    Run PowerShell script action:
      Script: %PowerShellScriptContent%
      Script parameters: 
        -SFTPHost %SFTPHost%
        -SFTPUsername %SFTPUsername%
        -SFTPPassword %SFTPPassword%
        -RemotePath "/incoming/purchase_orders/"
        -LocalFolder "C:\Exports\PurchaseOrders\"
        -FilePattern "PO_*.csv"
        -SSHHostKeyFingerprint %HostKeyFingerprint%
        -ArchiveFolder "C:\Exports\Archive\"
      → PowerShell output: ScriptOutput
      → PowerShell exit code: ScriptExitCode
    

    Warning

    Notice the SshHostKeyFingerprint parameter. Never use GiveUpSecurityAndAcceptAnySshHostKey = $true in production — this disables host verification and leaves you vulnerable to man-in-the-middle attacks. Obtain the legitimate fingerprint from your server administrator and store it as a flow variable. The fingerprint format WinSCP expects looks like: ssh-rsa 2048 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx.

    SSH Key Authentication

    Many SFTP servers require key-based authentication rather than passwords. WinSCP handles this with the SshPrivateKeyPath property on SessionOptions. The private key needs to be in PuTTY format (.ppk). If your trading partner gives you an OpenSSH format key, convert it using WinSCP's built-in key conversion tool.

    For unattended flows, the private key file needs to be present on the automation machine. Store it in a protected directory (not world-readable) and reference its path in your script. Pass the path as a parameter from PAD rather than hardcoding it, so you can change the location without modifying the script.

    # Add to SessionOptions when using key auth
    $SessionOptions = New-Object WinSCP.SessionOptions -Property @{
        Protocol              = [WinSCP.Protocol]::Sftp
        HostName              = $SFTPHost
        UserName              = $SFTPUsername
        SshPrivateKeyPath     = $SSHKeyPath
        SshPrivateKeyPassphrase = $KeyPassphrase   # if the key has a passphrase
        PortNumber            = [int]$SFTPPort
        SshHostKeyFingerprint = $SSHHostKeyFingerprint
    }
    

    Detailed guidance on securing sensitive values like key passphrases in PAD flows is covered in Handling Credentials Securely in Desktop Flows: Sensitive Variables and Azure Key Vault — the same patterns apply here.


    Implementing Multi-Layer Error Handling

    File transfer failures come in several distinct categories, and your error handling needs to treat them differently:

    Category 1: Connection failures — The server is unreachable, DNS resolution fails, the port is blocked, or the SSL/TLS handshake fails. These are typically transient (network hiccup) or permanent (wrong host). Retry 2-3 times with exponential backoff before declaring failure.

    Category 2: Authentication failures — Wrong credentials, expired password, IP whitelist violation, SSH key mismatch. Retrying immediately is futile and risks account lockout. Alert immediately, don't retry.

    Category 3: Partial transfer failures — The session connected, some files uploaded successfully, but one or more failed. This is the most complex case — you must not re-upload the successful files, but must retry or report the failures.

    Category 4: Post-transfer verification failures — The upload action "succeeded" but size verification shows a mismatch. Treat these as partial failures; delete the corrupt remote file if possible and retry.

    Category 5: No source files found — The local directory has no files matching the pattern. This might be normal (nothing to send today) or an upstream process failure. Your handling depends on business rules.

    Structuring Error Handling in PAD

    Use nested On block error handlers to implement this categorization. The outer block catches connection-level errors; the inner block (inside the transfer loop) catches per-file errors:

    ON BLOCK ERROR:
      # Handle connection failure
      IF ErrorMessage CONTAINS "Authentication" OR ErrorMessage CONTAINS "password":
        SET ErrorCategory = "AUTH_FAILURE"
        SET ShouldRetry = false
      ELSE IF ErrorMessage CONTAINS "timeout" OR ErrorMessage CONTAINS "connection refused":
        SET ErrorCategory = "CONNECTION_FAILURE"  
        SET ShouldRetry = true
        SET RetryDelaySeconds = 30
      ELSE:
        SET ErrorCategory = "UNKNOWN_FAILURE"
        SET ShouldRetry = true
        SET RetryDelaySeconds = 60
      END IF
      
      Call subflow: LogError (ErrorCategory, ErrorMessage, FlowContext)
      
      IF ShouldRetry AND RetryAttempt < MaxRetries:
        SET RetryAttempt = RetryAttempt + 1
        Wait action: RetryDelaySeconds seconds
        GOTO ConnectionBlock
      ELSE:
        Call subflow: SendFailureAlert
        EXIT FLOW
      END IF
    END ON BLOCK ERROR
    

    For per-file errors inside the transfer loop, don't let a single file failure kill the entire run. Instead, track failures in a data table and continue:

    DATA TABLE: TransferResults
    Columns: FileName, Status, ErrorMessage, Timestamp
    
    FOR EACH FileToUpload IN LocalFileList:
      ON BLOCK ERROR:
        Add row to TransferResults: [FileToUpload.Name, "FAILED", LastErrorMessage, Now()]
        SET FailureCount = FailureCount + 1
        CONTINUE  # Move to next file
      END ON BLOCK ERROR
      
      # Upload logic here...
      
      Add row to TransferResults: [FileToUpload.Name, "SUCCESS", "", Now()]
      SET SuccessCount = SuccessCount + 1
    END FOR
    

    After the loop, evaluate overall success and decide whether to alert:

    IF FailureCount > 0:
      IF SuccessCount = 0:
        # Total failure - all files failed
        Call subflow: SendCriticalAlert (TransferResults)
      ELSE:
        # Partial success - some files uploaded
        Call subflow: SendPartialFailureAlert (TransferResults)
      END IF
    ELSE:
      Call subflow: SendSuccessNotification (SuccessCount, TotalFiles)
    END IF
    

    Key insight

    The decision of whether a partial failure is "acceptable" is a business rule, not a technical one. In some organizations, 8 out of 9 files uploading successfully is fine — the missing file will be caught in reconciliation. In others, every file must arrive or the entire batch should be considered failed. Parameterize this threshold (e.g., MinSuccessRatePct = 100) so the business can change it without modifying the flow.

    Implementing Exponential Backoff for Retries

    Simple "wait 30 seconds and retry" logic works, but exponential backoff is more sophisticated and polite to the server:

    # In the retry block:
    SET WaitSeconds = BaseRetrySeconds * (2 ^ (RetryAttempt - 1))
    # Retry 1: 30 seconds, Retry 2: 60 seconds, Retry 3: 120 seconds
    
    # Add jitter to avoid thundering herd if multiple bots retry simultaneously
    SET JitterSeconds = [random number between 0 and 15]
    SET ActualWaitSeconds = WaitSeconds + JitterSeconds
    
    Wait action: ActualWaitSeconds seconds
    

    Power Automate Desktop's Generate random number action can provide the jitter. This matters when you have multiple bots in a machine group all hitting the same FTP server after a brief outage.


    Building the Logging Infrastructure

    An unattended flow that doesn't produce auditable logs is one you can't troubleshoot when it fails at 5:30 AM and you're asleep. Build structured logging from the start.

    Log File Structure

    Write a delimited log file that records every significant event:

    Timestamp|Level|FlowName|EventType|FileName|Status|Detail
    2024-03-15 05:31:02|INFO|SFTP_NightlyTransfer|FLOW_START|||Starting transfer run for 2024-03-15
    2024-03-15 05:31:04|INFO|SFTP_NightlyTransfer|FILES_FOUND||9|Found 9 files in C:\Exports\PurchaseOrders\
    2024-03-15 05:31:05|INFO|SFTP_NightlyTransfer|CONNECTION||SUCCESS|Connected to ftp.tradingpartner.com:22
    2024-03-15 05:31:07|INFO|SFTP_NightlyTransfer|UPLOAD|PO_20240315_001.csv|SUCCESS|Size: 48291 bytes
    2024-03-15 05:31:09|ERROR|SFTP_NightlyTransfer|UPLOAD|PO_20240315_004.csv|FAILED|Timeout after 60s
    2024-03-15 05:31:45|INFO|SFTP_NightlyTransfer|UPLOAD|PO_20240315_004.csv|SUCCESS|Retry 1 succeeded
    2024-03-15 05:32:18|INFO|SFTP_NightlyTransfer|FLOW_END|||Completed: 9/9 files, 1 retry
    

    Use PAD's Write text to file action in append mode to add each log entry. Build a LogEntry subflow that formats the timestamp and writes the line — call it from every point in your flow that needs logging.

    Integrating with SharePoint or Dataverse

    For enterprise deployments, writing logs to a local file isn't sufficient. You want a queryable audit trail. The pattern for this depends on your architecture:

    • Cloud flow trigger: If your desktop flow is triggered from a cloud flow, return the TransferResults data table as an output variable and let the cloud flow write it to SharePoint, Dataverse, or Azure SQL.

    • Direct HTTP: Use PAD's Invoke web service action to POST log entries to a Power Automate HTTP trigger or an Azure Function endpoint.

    • Database: Use an ODBC connection and PAD's SQL actions to INSERT audit records directly. This approach is fast and creates a proper audit trail for compliance purposes. See Automating Database Queries and Record Updates from Power Automate Desktop for the implementation pattern.


    File Management: Pre-Transfer Validation and Post-Transfer Archiving

    Pre-Transfer Validation

    Before opening a single FTP connection, validate that your source files are ready. This is especially important in chained automation where an upstream process generates the files:

    # Check 1: Do files exist?
    Get files in folder:
      Folder: C:\Exports\PurchaseOrders\
      File filter: PO_*.csv
      → LocalFileList
    
    IF LocalFileList.Count = 0:
      IF (today is a business day):
        # This is probably an upstream failure
        Call subflow: AlertMissingFiles
        EXIT FLOW with error
      ELSE:
        # Weekends/holidays - no files expected
        Call subflow: LogNoFilesExpected
        EXIT FLOW normally
      END IF
    END IF
    
    # Check 2: Are files non-zero size?
    FOR EACH File IN LocalFileList:
      IF File.Size = 0:
        Call subflow: AlertZeroByteFile (File.Name)
        Remove File from LocalFileList
      END IF
    END FOR
    
    # Check 3: Are files recent enough? (guard against stale files being re-sent)
    FOR EACH File IN LocalFileList:
      IF File.LastModified < (Now() - 25 hours):
        Call subflow: AlertStaleFile (File.Name, File.LastModified)
        # Depending on policy: either exclude or alert-and-continue
      END IF
    END FOR
    

    The business-day check requires knowing your organization's holiday calendar. A pragmatic approach: maintain a CSV file of business days (or non-business days) that you check at runtime. The Reading and Writing to CSV and Text Files in Power Automate Desktop article covers the mechanics of reading that file efficiently.

    Post-Transfer Archiving

    After confirmed successful upload, move local files to an archive directory with a date-stamped subfolder:

    # Create dated archive subdirectory
    SET ArchiveDir = "C:\Exports\Archive\" + Format(Today(), "yyyy-MM-dd")
    
    Create folder if not exists: ArchiveDir
    
    FOR EACH SuccessfulFile IN SuccessfulTransfers:
      Move file:
        Source: %SuccessfulFile.FullName%
        Destination: ArchiveDir + "\" + SuccessfulFile.Name
        If exists: Rename (append timestamp)
    END FOR
    

    Tip

    Don't delete source files — archive them. A hard delete means you have no recovery path if the receiving system reports the file was corrupted or never processed. Keep archives for at least 30 days (or whatever your data retention policy specifies) and implement a separate cleanup automation that purges archives older than the retention period.

    Handling Duplicate File Names in Archive

    If your automation runs twice on the same day (due to a retry or manual re-run), you'll hit naming conflicts in the archive. Append a timestamp suffix to disambiguate:

    SET ArchiveFileName = File.NameWithoutExtension + "_" + Format(Now(), "HHmmss") + File.Extension
    

    Configuring for Unattended Execution

    The entire flow only works as designed if it runs reliably unattended. Several configuration concerns deserve specific attention:

    Scheduling and Triggers

    For nightly runs, you have two options:

    1. Windows Task Scheduler triggers a cloud flow via webhook, which triggers the desktop flow via PAD
    2. Power Automate scheduled cloud flow triggers the desktop flow directly

    Option 2 is generally preferred because it keeps everything within the Power Automate ecosystem and gives you better monitoring through the Power Automate portal. The scheduling and triggering pattern is covered in depth in Triggering Desktop Flows from Cloud Flows: Passing Inputs and Returning Outputs.

    Machine Configuration for Unattended Mode

    The automation machine needs specific configuration for unattended FTP/SFTP:

    • Network access: Ensure the machine's firewall allows outbound TCP on port 21 (FTP), 21 with ephemeral ports for passive mode data channels, or 22 (SFTP)
    • Proxy settings: If your network routes traffic through a proxy, WinSCP needs explicit proxy configuration in SessionOptions; PAD's native FTP actions respect system proxy settings
    • PowerShell execution policy: Set to RemoteSigned or Bypass for the machine account that runs the unattended bot — the default Restricted policy will prevent your PowerShell scripts from running
    • WinSCP installation: Confirm it's installed under a path accessible to the machine account, not just your admin profile

    Warning

    Unattended flows run under the service account configured for the machine, not your personal Windows account. Always test your PowerShell scripts explicitly under that service account (use Run as different user or runas) before deploying. File permissions, module availability, and environment variables all differ between accounts.

    Timeout and Concurrency

    If you're running multiple FTP flows on different machines or handling multiple trading partners, be deliberate about concurrency. Most FTP servers enforce maximum concurrent session limits per user. If three bots try to authenticate simultaneously with the same credentials, two will fail.

    Handle this with Power Automate's queue-based dispatch or by staggering trigger times. For more on orchestrating flows across machine groups, see Building a Resilient Unattended RPA Orchestration Framework in Power Automate Desktop.


    Hands-On Exercise

    Build a complete FTP/SFTP transfer flow for the following scenario:

    Scenario: Your logistics team generates daily shipment confirmation files (CSV format, named SHIP_YYYYMMDD_NNN.csv) in C:\EDI\Outbound\ShipmentConfirm\. These must be uploaded to a trading partner's SFTP server at sftp.logistics-partner.com (port 22) in the /edi/inbound/ship_confirm/ directory by 7:00 AM each weekday.

    Build the following components:

    1. Initialize subflow: Load SFTP credentials from PAD sensitive input variables. Set constants: MaxRetries = 3, BaseRetrySeconds = 30, MinRequiredFiles = 1. Initialize a TransferResults data table with columns: FileName, FileSizeBytes, Status, RetryCount, ErrorDetail, Timestamp.

    2. ValidateFiles subflow: Scan the source directory for SHIP_*.csv files modified in the last 26 hours (to catch files from the previous evening's run). Alert and exit if zero files are found on a weekday. Remove zero-byte files from the transfer list with a warning log entry.

    3. TransferFiles subflow: Using the PowerShell/WinSCP approach, upload each file individually. After each upload, verify the remote file size matches the local size. On failure, retry up to 2 times with 30-second waits. Record each file's outcome in TransferResults.

    4. PostTransfer subflow: Move successfully uploaded files to C:\EDI\Archive\ShipmentConfirm\YYYY-MM-DD\. Write a summary log entry: total files, success count, failure count, total bytes transferred, elapsed time.

    5. SendAlert subflow: If any files failed after all retries, send an email (using PAD's Send email action or Outlook automation) with the TransferResults data table formatted as an HTML table in the email body. If all succeeded, write a success entry to the log file only (no email needed unless configured).

    Advanced challenge: Add a pre-upload step that reads the first line of each CSV to validate the header matches the expected schema (ShipmentID,OrderNumber,TrackingNumber,ShipDate,Carrier,Weight). Reject files with incorrect headers before attempting upload.


    Common Mistakes & Troubleshooting

    Mistake 1: Using Active FTP Mode Behind a Firewall

    Symptom: Connection succeeds (you see the 220 banner from the server) but listing directories or uploading files hangs or times out.

    Cause: Active FTP mode requires the server to make an inbound connection to your machine on an ephemeral port. Your firewall blocks it.

    Fix: Always use passive mode. In PAD's FTP action, look for the "Connection mode" property and set it to Passive. In WinSCP, set SessionOptions.AddRawSettings("FtpMode", "1").

    Mistake 2: SFTP Host Key Changes and Automation Breaks

    Symptom: SFTP connections start failing with "Host key doesn't match" errors after working for months.

    Cause: The trading partner replaced their server certificate, upgraded SSH, or migrated to new hardware. The SSH host key fingerprint changed.

    Fix: Build an alert specifically for this error code. When detected, notify an administrator to verify the new fingerprint with the trading partner (call them — don't just accept the new key automatically) and update the stored fingerprint variable. Never auto-accept changed host keys in production.

    Mistake 3: Password Expiry Causing Silent Failures

    Symptom: Authentication fails after 90 days with the same credentials that worked before.

    Cause: Many SFTP servers enforce password rotation policies. The password expired and wasn't rotated in your secure store.

    Fix: Implement a credential validation test that runs weekly (separate from the nightly transfer flow). If it fails, alert the team immediately so they can rotate credentials before the nightly run fails. Better yet, switch to SSH key authentication, which doesn't expire.

    Mistake 4: Large File Transfers Timing Out Mid-Transfer

    Symptom: Small files transfer fine; larger files (>50MB) fail with timeout errors partway through.

    Cause: PAD's default FTP action timeout applies to the operation overall, not per-packet. Long transfers on slow connections can exceed it.

    Fix: In WinSCP PowerShell scripts, set Session.Timeout = New-TimeSpan -Minutes 30 for large file transfers. For PAD native FTP actions, increase the timeout in the action properties. Also check whether the server has its own idle timeout that disconnects stale sessions.

    Mistake 5: Race Condition with Upstream File Generation

    Symptom: Occasionally, the transfer flow runs before the upstream process finishes writing files. Some files transfer with 0 bytes or partial content.

    Cause: The upstream process is still writing files when your transfer flow starts.

    Fix: Implement a file lock check or wait-for-stability pattern. Before including a file in the transfer list, record its size, wait 10 seconds, check the size again. If it changed, the file is still being written — wait and check again. Only transfer files that haven't changed size for two consecutive checks. Alternatively, coordinate with the upstream process to write a "ready" sentinel file (e.g., TRANSFER_READY.flag) that you wait for before starting.

    Mistake 6: PowerShell Script Failures Due to Execution Policy

    Symptom: PowerShell script action returns exit code 1 with an error about scripts being disabled.

    Cause: The machine account running the unattended bot has the default Restricted execution policy.

    Fix:

    # Option 1: Set machine-wide policy (requires admin)
    Set-ExecutionPolicy RemoteSigned -Scope LocalMachine
    
    # Option 2: Bypass for PAD's inline script only
    # In PAD's Run PowerShell script action, prepend:
    Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
    

    Option 2 is safer for shared machines as it only affects the current process.


    Summary & Next Steps

    Building production-grade FTP/SFTP automation in Power Automate Desktop is an exercise in layered engineering. The transfer itself is the easy part — the hard part is everything around it: distinguishing protocol differences, securing credentials, handling every failure mode distinctly, verifying transfers actually completed, and building logging that lets you diagnose problems at 5:30 AM without logging into the machine.

    The key architectural principles to carry forward:

    • Protocol awareness: Know whether you're dealing with FTP, FTPS, or SFTP before writing a single action. Native PAD FTP actions handle FTP/FTPS; SFTP requires PowerShell with WinSCP or Posh-SSH.
    • Defense in depth for errors: Connection failures, auth failures, and per-file transfer failures are different problems requiring different responses. Don't flatten them into a single "something went wrong" handler.
    • Transfer verification: Never assume a successful upload action means the file arrived correctly. Verify size at minimum; use checksum verification (WinSCP supports this) for critical files.
    • Subflow architecture: Split initialization, validation, transfer, post-processing, and alerting into named subflows. This makes the flow maintainable and each layer independently testable.
    • Unattended-first design: Think about service account permissions, execution policy, network configuration, and credential storage from the very beginning, not as an afterthought.

    Where to go next:

    If your file transfer workflow is part of a larger automation — where transferred files feed downstream processing — explore how to chain flows together and orchestrate multi-step pipelines in Building a Resilient Unattended RPA Orchestration Framework in Power Automate Desktop.

    If the files you're transferring come from or feed into Excel reports, the techniques in Automating Excel with Power Automate Desktop: Reading, Writing, and Running Macros will help you build end-to-end data pipelines entirely within Power Automate Desktop.

    And if you're looking to scale this across dozens of trading partners and machine groups, the monitoring and visibility infrastructure described in Monitoring and Troubleshooting Desktop Flow Runs at Scale will give you the operational visibility you need to run these workflows confidently in production.

    Work With Us

    From insight to implementation

    Reading is the start. When you're ready to build the data, automation, or AI systems behind it, our team turns strategy into shipped results.

    Let's Build

    Power Automate Desktop & RPA

    Previous

    Implementing Dynamic Selector Repair and Fallback Strategies in Power Automate Desktop: Detecting Broken UI Elements at Runtime and Switching to Alternative Identification Methods Without Flow Failure

    Next

    Automating Windows Credential Manager and Vault Operations in Power Automate Desktop: Storing, Retrieving, and Rotating Application Passwords for Secure Unattended Bot Authentication

    Related Insights

    Power AutomateExpert

    Automating Microsoft Access Database Operations with Power Automate Desktop: Opening Reports, Running Queries, and Exporting Results to Excel in Unattended RPA Workflows

    29 min
    Power AutomatePractitioner

    Automating Windows Notification Area and System Tray Interactions in Power Automate Desktop

    22 min
    Power AutomateExpert

    Automating PDF Form Filling and Digital Signature Workflows in Power Automate Desktop

    26 min

    On this page

    • Introduction
    • Prerequisites
    • Understanding the Protocol Landscape Before You Write a Single Action
    • Setting Up the Flow Architecture Before Building Actions
    • Native FTP Actions in Power Automate Desktop
    • Opening an FTP Connection
    • Listing Remote Directory Contents
    • Uploading Files
    • Verifying Transfers: Don't Skip This Step
    • Closing the Connection
    • SFTP Transfers via PowerShell: The Production-Grade Approach
    • Installing WinSCP on the Automation Machine
    • Building the SFTP Upload Script
    • SSH Key Authentication
    • Implementing Multi-Layer Error Handling
    • Structuring Error Handling in PAD
    • Implementing Exponential Backoff for Retries
    • Building the Logging Infrastructure
    • Log File Structure
    • Integrating with SharePoint or Dataverse
    • File Management: Pre-Transfer Validation and Post-Transfer Archiving
    • Pre-Transfer Validation
    • Post-Transfer Archiving
    • Handling Duplicate File Names in Archive
    • Configuring for Unattended Execution
    • Scheduling and Triggers
    • Machine Configuration for Unattended Mode
    • Timeout and Concurrency
    • Hands-On Exercise
    • Common Mistakes & Troubleshooting
    • Mistake 1: Using Active FTP Mode Behind a Firewall
    • Mistake 2: SFTP Host Key Changes and Automation Breaks
    • Mistake 3: Password Expiry Causing Silent Failures
    • Mistake 4: Large File Transfers Timing Out Mid-Transfer
    • Mistake 5: Race Condition with Upstream File Generation
    • Mistake 6: PowerShell Script Failures Due to Execution Policy
    • Summary & Next Steps