Powershell - SFTP Upload Using Posh-SSH



  • I had to convert an old ps script from old school FTP to SFTP that uploads files in a few different "local" directories and throws them in the root of our web server. After some digging, I came up with this script. Because the files are in different paths, I used a variable and separate command for each. It works just fine, but I was wondering if there is a more elegant or best practice way of doing that.

    # SFTP Upload of Inventory From CSV files to WPEngine SFTP. Requires installation of Posh-SSH 
    # Install-Module -Name Posh-SSH (https://github.com/darkoperator/Posh-SSH)
    
    # Set the credentials
    $Password = ConvertTo-SecureString 'passwordgoeshere' -AsPlainText -Force
    $Credential = New-Object System.Management.Automation.PSCredential ('usernamegoeshere', $Password)
    
    # Set local file path and SFTP path
    $FilePath1 = "D:\Data\SW\SWRUN\BA\BAUPC.CSV"
    $FilePath2 = "D:\Data\SW\SWRUN\MI\MIUPC.CSV"
    $FilePath3 = "D:\Data\SW\SWRUN\NM\NMUPC.CSV"
    $FilePath4 = "D:\Data\SW\SWRUN\SS\SSUPC.CSV"
    $SftpPath = '/'
    
    # Set the Hostname of the SFTP server
    $SftpServer = 'domain.sftp.wpengine.com'
    
    # Load the Posh-SSH module
    Import-Module Posh-SSH
    
    # Establish the SFTP connection
    $ThisSession = New-SFTPSession -ComputerName $SftpServer -Credential $Credential -AcceptKey -Port 2222
    
    # Upload the files to the SFTP path
    Set-SFTPFile -SessionId ($ThisSession).SessionId -Localfile $FilePath1 -RemotePath $SftpPath -Overwrite
    Set-SFTPFile -SessionId ($ThisSession).SessionId -Localfile $FilePath2 -RemotePath $SftpPath -Overwrite
    Set-SFTPFile -SessionId ($ThisSession).SessionId -Localfile $FilePath3 -RemotePath $SftpPath -Overwrite
    Set-SFTPFile -SessionId ($ThisSession).SessionId -Localfile $FilePath4 -RemotePath $SftpPath -Overwrite
    
    #Disconnect all SFTP Sessions
    Get-SFTPSession | % { Remove-SFTPSession -SessionId ($_.SessionId) }
    


  • Well, I am updating the script, as the requirements changed from multiple local dirs to single sftp dir, to multiple local dirs to multiple sftp servers' dirs.

    Tip for WP-Engine users-
    I had some issues using WP-Engine's path option in their sftp user settings. The feature essentially creates a shortcut to root when you connect. I was telling the script to go to a specific directory, '/inventory', based on root as being root. So, my script would error out because, when I connected, the script would try to go to /inventory/inventory. After changing the $SftpPath variable back to '/', it worked.

    However, I would still like feedback on if there is a better way to write the repetitive lines. Here is the latest version of the script for many to many.

    # SFTP Upload of Inventory From CSV files to WPEngine SFTP. Requires installation of Posh-SSH 
    # Install-Module -Name Posh-SSH (https://github.com/darkoperator/Posh-SSH)
    
    # Set the credentials
    $Password = ConvertTo-SecureString 'PasswordGoesHere' -AsPlainText -Force
    $Credential1 = New-Object System.Management.Automation.PSCredential ('username', $Password)
    $Credential2 = New-Object System.Management.Automation.PSCredential ('username', $Password)
    $Credential3 = New-Object System.Management.Automation.PSCredential ('username', $Password)
    $Credential4 = New-Object System.Management.Automation.PSCredential ('username', $Password)
    
    # Set local file path and SFTP path
    $FilePath1 = "D:\Data\SW\SWRUN\BA\BAUPC.CSV"
    $FilePath2 = "D:\Data\SW\SWRUN\MI\MIUPC.CSV"
    $FilePath3 = "D:\Data\SW\SWRUN\SS\SSUPC.CSV" 
    $FilePath4 = "D:\Data\SW\SWRUN\NM\NMUPC.CSV"
    $SftpPath = '/'
    
    # Set the Hostnames of the SFTP servers
    $SftpServer1 = '1.sftp.wpengine.com'
    $SftpServer2 = '2.sftp.wpengine.com'
    $SftpServer3 = '3.sftp.wpengine.com'
    $SftpServer4 = '4.sftp.wpengine.com'
    
    # Load the Posh-SSH module
    Import-Module Posh-SSH
    
    # Establish the SFTP connection
    $Session1 = New-SFTPSession -ComputerName $SftpServer1 -Credential $Credential1 -AcceptKey -Port 2222
    $Session2 = New-SFTPSession -ComputerName $SftpServer2 -Credential $Credential2 -AcceptKey -Port 2222
    $Session3 = New-SFTPSession -ComputerName $SftpServer3 -Credential $Credential3 -AcceptKey -Port 2222
    $Session4 = New-SFTPSession -ComputerName $SftpServer4 -Credential $Credential4 -AcceptKey -Port 2222
    
    # Upload the files to the SFTP path
    Set-SFTPFile -SessionId ($Session1).SessionId -Localfile $FilePath1 -RemotePath $SftpPath -Overwrite
    Set-SFTPFile -SessionId ($Session2).SessionId -Localfile $FilePath2 -RemotePath $SftpPath -Overwrite
    Set-SFTPFile -SessionId ($Session3).SessionId -Localfile $FilePath3 -RemotePath $SftpPath -Overwrite
    Set-SFTPFile -SessionId ($Session4).SessionId -Localfile $FilePath4 -RemotePath $SftpPath -Overwrite
    
    #Disconnect all SFTP Sessions
    Get-SFTPSession | % { Remove-SFTPSession -SessionId ($_.SessionId) }
    


  • @wrx7m said in Powershell - SFTP Upload Using Posh-SSH:

    However, I would still like feedback on if there is a better way to write the repetitive lines. Here is the latest version of the script for many to many.

    There is, but I mean... its' a small single use script so I don't know if its' worth it if you can't "just do it as you write it the first time". What you have is working... do you really care if the username and password is in the script? Only you can know... but practice wise, definitely not. Repetitive lines, no, functions yes, loops yes, built objects yes.

    I can dive in if you are wondering for the sake of learning, sure, glad to help.... ? (but not tonight, i'm beat and soon time for bed)



  • @Obsolesce said in Powershell - SFTP Upload Using Posh-SSH:

    @wrx7m said in Powershell - SFTP Upload Using Posh-SSH:

    However, I would still like feedback on if there is a better way to write the repetitive lines. Here is the latest version of the script for many to many.

    There is, but I mean... its' a small single use script so I don't know if its' worth it if you can't "just do it as you write it the first time". What you have is working... do you really care if the username and password is in the script? Only you can know... but practice wise, definitely not. Repetitive lines, no, functions yes, loops yes, built objects yes.

    I can dive in if you are wondering for the sake of learning, sure, glad to help.... ? (but not tonight, i'm beat and soon time for bed)

    Thanks. They don't offer cert-based auth from what I can see on their site. Also, I am asking because I am interested in learning how to do it properly.



  • Okay, as a very first step for learning, I'd create a simple function (first as a PoC) to consume what you already have... something like this:
    (I don't have an sftp server to test with, but seems like it will work)

    function Invoke-SFTPDance {
        [cmdletbinding(SupportsShouldProcess=$true)]
        param(
            [object]$Credentials,
            [string]$ServerName,
            [int]$Port,
            [string]$FilePath,
            [string]$RemotePath = "/"
        )
    
        $Session = New-SFTPSession -ComputerName $ServerName -Credential $Credentials -AcceptKey -Port $Port
    
        Set-SFTPFile -SessionId $Session.SessionId -Localfile $FilePath -RemotePath $RemotePath -Overwrite
    
        Remove-SFTPSession -SessionId $Session.SessionId
    
    }
    
    Invoke-SFTPDance -Credentials $Credential1 -ServerName $SftpServer1 -Port 2222 -FilePath $FilePath1
    Invoke-SFTPDance -Credentials $Credential2 -ServerName $SftpServer2 -Port 2222 -FilePath $FilePath2
    Invoke-SFTPDance -Credentials $Credential3 -ServerName $SftpServer3 -Port 2222 -FilePath $FilePath3
    Invoke-SFTPDance -Credentials $Credential4 -ServerName $SftpServer4 -Port 2222 -FilePath $FilePath4
    


  • Then I would start to build in more detail:

    function Invoke-SFTPDance {
        [cmdletbinding(SupportsShouldProcess=$true)]
        param(
            [Parameter(Mandatory,Position=0)]
            [object]$Credentials,
            [Parameter(Mandatory,Position=1)]
            [string]$ServerName,
            [Parameter(Mandatory,Position=2)]
            [int]$Port,
            [Parameter(Mandatory,Position=3)]
            [string]$FilePath,
            [Parameter(Position=3)]
            [string]$RemotePath = "/"
        )
    
        Begin {
    
            #### PRE-REQS ####
            $sshMod = Get-Module -Name Posh-SSH
    
            if (-not($sshMod)) {
                Install-Module -Name Posh-SSH
                Import-Module -Name Posh-SSH
            }
            ##################
    
        }
    
        Process {
    
            $Session = New-SFTPSession -ComputerName $ServerName -Credential $Credentials -AcceptKey -Port $Port
    
            Set-SFTPFile -SessionId $Session.SessionId -Localfile $FilePath -RemotePath $RemotePath -Overwrite
    
        }
    
        End {
    
            Remove-SFTPSession -SessionId $Session.SessionId
    
        }
    
    }
    
    Invoke-SFTPDance -Credentials $Credential1 -ServerName $SftpServer1 -Port 2222 -FilePath $FilePath1
    Invoke-SFTPDance -Credentials $Credential2 -ServerName $SftpServer2 -Port 2222 -FilePath $FilePath2
    Invoke-SFTPDance -Credentials $Credential3 -ServerName $SftpServer3 -Port 2222 -FilePath $FilePath3
    Invoke-SFTPDance -Credentials $Credential4 -ServerName $SftpServer4 -Port 2222 -FilePath $FilePath4
    


  • @Obsolesce Thanks, man! I will try my hand and making these adjustments. Appreciate the help and guidance.



  • And then, error handling...
    Note: I did this super fast and I really didn't check it over (running out of time). Also, ideally, you'd write to a log in addition to the Write-Warning. I'll leave the logging up to you. You can also create a nifty function for logging and automatic log cleaning if needed.

    function Invoke-SFTPDance {
        [cmdletbinding(SupportsShouldProcess=$true)]
        param(
            [Parameter(Mandatory,Position=0)]
            [object]$Credentials,
            [Parameter(Mandatory,Position=1)]
            [string]$ServerName,
            [Parameter(Mandatory,Position=2)]
            [int]$Port,
            [Parameter(Mandatory,Position=3)]
            [string]$FilePath,
            [Parameter(Position=3)]
            [string]$RemotePath = "/"
        )
    
        Begin {
    
            #### PRE-REQS ####
            $sshMod = Get-Module -Name Posh-SSH
    
            if (-not($sshMod)) {
                Install-Module -Name Posh-SSH
                Import-Module -Name Posh-SSH
            }
            ##################
    
        }
    
        Process {
    
            $Session = New-SFTPSession -ComputerName $ServerName -Credential $Credentials -AcceptKey -Port $Port -ErrorAction SilentlyContinue -ErrorVariable SessionERR
    
            if ($Session) {
    
                if (-not($SessionERR)) {
    
                    #  If session created without error
                    Set-SFTPFile -SessionId $Session.SessionId -Localfile $FilePath -RemotePath $RemotePath -Overwrite
    
                } else {
    
                    Write-Warning "A sessions was created, but also resulted in an error: $SessionERR"
                    Set-SFTPFile -SessionId $Session.SessionId -Localfile $FilePath -RemotePath $RemotePath -Overwrite
    
                }
    
            } else {
    
                if ($SessionERR) {
    
                    Write-Warning "There was a problem creating the session, with error - $SessionERR"
    
                } else {
    
                    Write-Warning "There was a problem creating the session, but no error..."
    
                }
            }
    
        }
    
        End {
    
            if ($Session) {
    
                Remove-SFTPSession -SessionId $Session.SessionId
                
            }
    
        }
    
    }
    
    Invoke-SFTPDance -Credentials $Credential1 -ServerName $SftpServer1 -Port 2222 -FilePath $FilePath1
    Invoke-SFTPDance -Credentials $Credential2 -ServerName $SftpServer2 -Port 2222 -FilePath $FilePath2
    Invoke-SFTPDance -Credentials $Credential3 -ServerName $SftpServer3 -Port 2222 -FilePath $FilePath3
    Invoke-SFTPDance -Credentials $Credential4 -ServerName $SftpServer4 -Port 2222 -FilePath $FilePath4
    

    Of course logging, which is always best done before you start using the script... it helps with testing.



  • There's also a lot more that can be done, especially with the credentials... storing them as an encrypted file and retrieving them for the function (function just for that, functions using other functions), keeping all the parameter data in an object to retrieve, or from CSV, etc....

    It depends on how far down the rabbit hole you want to go, and how much time you want to spend making it.

    Honestly, I'd do this kind of thing with a different project, but this can work too. There's a lot you can do.