Upload files by FTP from package

Octopus.Script exported 2020-08-08 by harrisonmeister belongs to ‘Winscp’ category.

Upload files to a remote server via File Transfer Protocol (SFTP or FTP) using WinSCP.

This step template uses the WinSCP .NET Assembly. In the absence of WinSCP installed on the machine, it will attempt to make use of the WinSCP PowerShell module, downloading a temporary copy if not already present.

Notes on usage

This version uses a referenced package parameter and is able to be run on a Worker.

Cleaning up deployments

If you aren’t deploying to an application host and don’t want the deployment files to persist on the tentacle irrespective of the life cycle retention policy ensure that you set the “Delete Previous Deployment” to true and they will be removed.

Parameters

When steps based on the template are included in a project’s deployment process, the parameters below can be set.

Path to WinScp

PathToWinScp =

The directory where you extracted the WinSCP .NET Assembly.

Host

FtpHost =

The address of your FTP server. Example: ftp.yourhost.com.

Username

FtpUsername = anonymous

If no username is specified, the well-known username anonymous will be used.

Password

FtpPassword = guest

If no password is specified, the well-known password guest will be used.

If the password field is bound, the binding expression will be visible to other authorized users.

Host Key Fingerprint(s)

FtpHostKeyFingerprint =

By supplying a host key fingerprint (or a semicolon separated list), you will force the deployment into SFTP mode, and automatically accept the host certificates.

Passkey

FtpPasskey =

Path to the PPK passkey file, leave blank if using a Password

Passkey Phrase

FtpPasskeyPhrase =

If your passkey is encrypted, please supply the pass phrase.

If the password field is bound, the binding expression will be visible to other authorized users.

Remote directory

FtpRemoteDirectory =

The directory on your FTP server in which you want files to be placed. Example: /site/wwwroot

Delete unrecognized files

FtpDeleteUnrecognizedFiles = false

Files can exist on the FTP server that do not exist in the NuGet package. Examples may be binaries from a previous release, or uploaded images in a CMS. Use this option to choose how to treat these files.

Delete Deployment Step?

DeleteDeploymentStep = false

Should this script delete the deployment - for example, if you are running this on the Octopus Server, you might want to clean up the output of the previous package deploy step when this has completed.

Package

FtpPackage =

Select the package to FTP

Script body

Steps based on this template will execute the following PowerShell script.

## --------------------------------------------------------------------------------------
## Input
## --------------------------------------------------------------------------------------
$PathToWinScp = $OctopusParameters['PathToWinScp']
$FtpHost = $OctopusParameters['FtpHost']
$FtpUsername = $OctopusParameters['FtpUsername']
$FtpPassword = $OctopusParameters['FtpPassword']
$FtpHostKeyFingerprint = $OctopusParameters['FtpHostKeyFingerprint']
$FtpPasskey = $OctopusParameters['FtpPasskey']
$FtpPasskeyPhrase = $OctopusParameters['FtpPasskeyPhrase']
$FtpRemoteDirectory = $OctopusParameters['FtpRemoteDirectory']
$FtpDeleteUnrecognizedFiles = $OctopusParameters['FtpDeleteUnrecognizedFiles']
$DeleteDeploymentStep = $OctopusParameters['DeleteDeploymentStep']

## --------------------------------------------------------------------------------------
## Helpers
## --------------------------------------------------------------------------------------

function Get-ModuleInstalled
{
    # Define parameters
    param(
        $PowerShellModuleName
    )

    # Check to see if the module is installed
    if ($null -ne (Get-Module -ListAvailable -Name $PowerShellModuleName))
    {
        # It is installed
        return $true
    }
    else
    {
        # Module not installed
        return $false
    }
}

function Install-PowerShellModule
{
    # Define parameters
    param(
        $PowerShellModuleName,
        $LocalModulesPath
    )

	# Check to see if the package provider has been installed
    if ((Get-NugetPackageProviderNotInstalled) -ne $false)
    {
    	# Display that we need the nuget package provider
        Write-Host "Nuget package provider not found, installing ..."
        
        # Install Nuget package provider
        Install-PackageProvider -Name Nuget -Force
    }

	# Save the module in the temporary location
    Save-Module -Name $PowerShellModuleName -Path $LocalModulesPath -Force
}

function Get-NugetPackageProviderNotInstalled
{
	# See if the nuget package provider has been installed
    return ($null -eq (Get-PackageProvider -ListAvailable -Name Nuget -ErrorAction SilentlyContinue))
}

# Helper for validating input parameters
function Validate-Parameter([string]$foo, [string[]]$validInput, $parameterName) {
    if (! $parameterName -contains "Password")
    {
        Write-Host "${parameterName}: $foo"
    }
    if (! $foo) {
        throw "No value was set for $parameterName, and it cannot be empty"
    }
}

# A collection of functions that can be used by script steps to determine where packages installed
# by previous steps are located on the filesystem.
function Find-InstallLocations {
    $result = @()
    $OctopusParameters.Keys | foreach {
        if ($_.EndsWith('].Output.Package.InstallationDirectoryPath')) {
            $result += $OctopusParameters[$_]
        }
    }
    return $result
}

function Find-InstallLocation($stepName) {
    $result = $OctopusParameters.Keys | where {
        $_.Equals("Octopus.Action[$stepName].Output.Package.InstallationDirectoryPath",  [System.StringComparison]::OrdinalIgnoreCase)
    } | select -first 1

    if ($result) {
        return $OctopusParameters[$result]
    }

    throw "No install location found for step: $stepName"
}

function Find-SingleInstallLocation {
    $all = @(Find-InstallLocations)
    if ($all.Length -eq 1) {
        return $all[0]
    }
    if ($all.Length -eq 0) {
        throw "No package steps found"
    }
    throw "Multiple package steps have run; please specify a single step"
}

# Session.FileTransferred event handler
function FileTransferred
{
    param($e)

    if ($e.Error -eq $Null)
    {
        Write-Host ("Upload of {0} succeeded" -f $e.FileName)
    }
    else
    {
        Write-Error ("Upload of {0} failed: {1}" -f $e.FileName, $e.Error)
    }

    if ($e.Chmod -ne $Null)
    {
        if ($e.Chmod.Error -eq $Null)
        {
            Write-Host "##octopus[stdout-verbose]"
            Write-Host ("Permisions of {0} set to {1}" -f $e.Chmod.FileName, $e.Chmod.FilePermissions)
            Write-Host "##octopus[stdout-default]"
        }
        else
        {
            Write-Error ("Setting permissions of {0} failed: {1}" -f $e.Chmod.FileName, $e.Chmod.Error)
        }

    }
    else
    {
        Write-Host "##octopus[stdout-verbose]"
        Write-Host ("Permissions of {0} kept with their defaults" -f $e.Destination)
        Write-Host "##octopus[stdout-default]"
    }

    if ($e.Touch -ne $Null)
    {
        if ($e.Touch.Error -eq $Null)
        {
            Write-Host "##octopus[stdout-verbose]"
            Write-Host ("Timestamp of {0} set to {1}" -f $e.Touch.FileName, $e.Touch.LastWriteTime)
            Write-Host "##octopus[stdout-default]"
        }
        else
        {
            Write-Error ("Setting timestamp of {0} failed: {1}" -f $e.Touch.FileName, $e.Touch.Error)
        }

    }
    else
    {
        # This should never happen during "local to remote" synchronization
        Write-Host "##octopus[stdout-verbose]"
        Write-Host ("Timestamp of {0} kept with its default (current time)" -f $e.Destination)
        Write-Host "##octopus[stdout-default]"
    }
}

## --------------------------------------------------------------------------------------
## Configuration
## --------------------------------------------------------------------------------------
# Define PowerShell Modules path
$LocalModules = (New-Item "$PSScriptRoot\Modules" -ItemType Directory -Force).FullName
$env:PSModulePath = "$LocalModules;$env:PSModulePath"
$PowerShellModuleName = "WinSCP"

# Set secure protocols
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls11 -bor [System.Net.SecurityProtocolType]::Tls12

# Check to see if $PathToWinScp is empty
if ([string]::IsNullOrEmpty($PathToWinScp))
{
    # Check to see if WinSCP module is installed
    if ((Get-ModuleInstalled -PowerShellModuleName $PowerShellModuleName) -ne $true)
    {
      # Tell user what we're doing
      Write-Output "PowerShell module $PowerShellModuleName is not installed, downloading temporary copy ..."

      # Install temporary copy
      Install-PowerShellModule -PowerShellModuleName $PowerShellModuleName -LocalModulesPath $LocalModules
      
      # Import module
      Write-Output "Importing $PowerShellModuleName ..."
      Import-Module $PowerShellModuleName
    }
	#Get-ChildItem -Path ([System.IO.Path]::GetDirectoryName((Get-Module $PowerShellModuleName).Path))
	# Get the path to where the dll resides
    #$PathToWinScp = [System.IO.Path]::GetDirectoryName((Get-Module $PowerShellModuleName).Path)
}
else
{
	Validate-Parameter $PathToWinScp -parameterName "Path to WinSCP .NET Assembly"
    # Load WinSCP .NET assembly
    $fullPathToWinScp = "$PathToWinScp\WinSCPnet.dll"
    if(-not (Test-Path $fullPathToWinScp))
    {
        throw "$PathToWinScp does not contain the WinSCP .NET Assembly"
    }
    Add-Type -Path $fullPathToWinScp    
}

#Validate-Parameter $PathToWinScp -parameterName "Path to WinSCP .NET Assembly"
Validate-Parameter $FtpHost -parameterName "Host"
Validate-Parameter $FtpUsername -parameterName "Username"
Validate-Parameter $FtpPassword -parameterName "Password"
Validate-Parameter $FtpRemoteDirectory -parameterName "Remote directory"
Validate-Parameter $FtpDeleteUnrecognizedFiles -parameterName "Delete unrecognized files"

## --------------------------------------------------------------------------------------
## Main script
## --------------------------------------------------------------------------------------

# Load WinSCP .NET assembly
<#
$fullPathToWinScp = "$PathToWinScp\WinSCPnet.dll"
if(-not (Test-Path $fullPathToWinScp))
{
    throw "$PathToWinScp does not contain the WinSCP .NET Assembly"
}
Add-Type -Path $fullPathToWinScp
#>
$stepPath = ""

$stepPath = $OctopusParameters["Octopus.Action.Package[FtpPackage].ExtractedPath"]

Write-Host "Package was installed to: $stepPath"

try
{
    $sessionOptions = New-Object WinSCP.SessionOptions

    # WinSCP defaults to SFTP, but it's good to ensure that's the case
    if (![string]::IsNullOrEmpty($FtpHostKeyFingerprint)) {
      $sessionOptions.Protocol = [WinScp.Protocol]::Sftp
      $sessionOptions.SshHostKeyFingerprint = $FtpHostKeyFingerprint
    }
    else {
      $sessionOptions.Protocol = [WinSCP.Protocol]::Ftp
    }
    $sessionOptions.HostName = $FtpHost
    $sessionOptions.UserName = $FtpUsername

    

    # If there is a path to the private key, use that instead of a password
    if (![string]::IsNullOrEmpty($FtpPasskey)) {
      Write-Host "Attempting to use passkey instead of password"

      # Check key exists
      if (!(Test-Path $FtpPasskey)) {
        throw "Unable to locate passkey at: $FtpPasskey"
      }

      $sessionOptions.SshPrivateKeyPath = $FtpPasskey

      # If the key requires a passphrase to access
      if ($FtpPasskeyPhrase -ne "") {
        $sessionOptions.PrivateKeyPassphrase = $FtpPasskeyPhrase
      }
    }
    else {
      $sessionOptions.Password = $FtpPassword
    }

    $session = New-Object WinSCP.Session
    
    if ([string]::IsNullOrEmpty($PathToWinScp))
    {
    	# Using PowerShell module, need to set the executable location
        $session.ExecutablePath = "$([System.IO.Path]::GetDirectoryName((Get-Module $PowerShellModuleName).Path))\bin\winscp.exe"
    }
    
    try
    {
    
        # Will continuously report progress of synchronization
        $session.add_FileTransferred( { FileTransferred($_) } )

        # Connect
        $session.Open($sessionOptions)

        Write-Host "Beginning synchronization between $stepPath and $FtpRemoteDirectory on $FtpHost"

        if (-not $session.FileExists($FtpRemoteDirectory))
        {
            Write-Host "Remote directory not found, creating $FtpRemoteDirectory"
            $session.CreateDirectory($FtpRemoteDirectory);
        }

        # Synchronize files
        $synchronizationResult = $session.SynchronizeDirectories(
            [WinSCP.SynchronizationMode]::Remote, $stepPath, $FtpRemoteDirectory, $FtpDeleteUnrecognizedFiles)

        # Throw on any error
        $synchronizationResult.Check()
    }
    finally
    {
        # Disconnect, clean up
        $session.Dispose()

        if ($DeleteDeploymentStep) {
          Remove-Item -Path $stepPath -Recurse
        }
    }

    exit 0
}
catch [Exception]
{
    throw $_.Exception.Message
}


Provided under the Apache License version 2.0.

Report an issue

To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.

{
  "Id": "8b316926-0821-459b-9869-3ca98fd9087e",
  "Name": "Upload files by FTP from package",
  "Description": "Upload files to a remote server via File Transfer Protocol (SFTP or FTP) using WinSCP.\n\nThis step template uses the [WinSCP .NET Assembly](http://winscp.net/eng/docs/library#downloading_and_installing_the_assembly).  In the absence of WinSCP installed on the machine, it will attempt to make use of the WinSCP PowerShell module, downloading a temporary copy if not already present. \n\n# Notes on usage\n\nThis version uses a referenced package parameter and is able to be run on a Worker.\n\n## Cleaning up deployments\n\nIf you aren't deploying to an application host and don't want the deployment files to persist on the tentacle irrespective of the life cycle retention policy ensure that you set the \"Delete Previous Deployment\" to `true` and they will be removed.",
  "Version": 4,
  "ExportedAt": "2020-08-08T00:22:01.921Z",
  "ActionType": "Octopus.Script",
  "Author": "harrisonmeister",
  "Packages": [
    {
      "PackageId": null,
      "FeedId": null,
      "AcquisitionLocation": "Server",
      "Properties": {
        "Extract": "True",
        "SelectionMode": "deferred",
        "PackageParameterName": "FtpPackage"
      },
      "Id": "76cedf6d-e8b0-4fc2-af04-a745ae6659ea",
      "Name": "FtpPackage"
    }
  ],
  "Parameters": [
    {
      "Id": "a884e52a-0e90-4b98-ba36-916dbbb7302c",
      "Name": "PathToWinScp",
      "Label": "Path to WinScp",
      "HelpText": "The directory where you extracted the WinSCP .NET Assembly.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "09f9bb2d-2c7e-402c-8824-e91c6c7dd3e4",
      "Name": "FtpHost",
      "Label": "Host",
      "HelpText": "The address of your FTP server. Example: `ftp.yourhost.com`.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "81768a2a-9fbb-47d0-b584-91cb1c93fc28",
      "Name": "FtpUsername",
      "Label": "Username",
      "HelpText": "If no username is specified, the well-known username `anonymous` will be used.",
      "DefaultValue": "anonymous",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "f36ea459-db54-4ff8-9f9f-f6917f56f330",
      "Name": "FtpPassword",
      "Label": "Password",
      "HelpText": "If no password is specified, the well-known password `guest` will be used.\n\nIf the password field is bound, the binding expression will be visible to other authorized users.",
      "DefaultValue": "guest",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "c970e25e-e1cf-4143-81e7-ab158f80b7bf",
      "Name": "FtpHostKeyFingerprint",
      "Label": "Host Key Fingerprint(s)",
      "HelpText": "By supplying a host key fingerprint (or a semicolon separated list), you will force the deployment into SFTP mode, and automatically accept the host certificates.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "4c4ace47-c5a0-46e3-9898-39fb722a524d",
      "Name": "FtpPasskey",
      "Label": "Passkey",
      "HelpText": "Path to the PPK passkey file, leave blank if using a Password",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "0ecfc143-7470-45a2-a099-f0c6f37f58e5",
      "Name": "FtpPasskeyPhrase",
      "Label": "Passkey Phrase",
      "HelpText": "If your passkey is encrypted, please supply the pass phrase.\n\nIf the password field is bound, the binding expression will be visible to other authorized users.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "b2fa7e63-c46b-4539-8d3b-c1d7c41c71ea",
      "Name": "FtpRemoteDirectory",
      "Label": "Remote directory",
      "HelpText": "The directory on your FTP server in which you want files to be placed. Example: `/site/wwwroot`",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "f02cbc65-dffb-40cc-a0b5-d87e19346b90",
      "Name": "FtpDeleteUnrecognizedFiles",
      "Label": "Delete unrecognized files",
      "HelpText": "Files can exist on the FTP server that do not exist in the NuGet package. Examples may be binaries from a previous release, or uploaded images in a CMS. Use this option to choose how to treat these files.",
      "DefaultValue": "false",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "e7ca0ba2-02cc-4988-acef-0f0bb12094ad",
      "Name": "DeleteDeploymentStep",
      "Label": "Delete Deployment Step?",
      "HelpText": "Should this script delete the deployment - for example, if you are running this on the Octopus Server, you might want to clean up the output of the previous package deploy step when this has completed.",
      "DefaultValue": "false",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "b7a1af1a-0670-4817-a577-132fe1db01e5",
      "Name": "FtpPackage",
      "Label": "Package",
      "HelpText": "Select the package to FTP",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Package"
      }
    }
  ],
  "Properties": {
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptBody": "## --------------------------------------------------------------------------------------\n## Input\n## --------------------------------------------------------------------------------------\n$PathToWinScp = $OctopusParameters['PathToWinScp']\n$FtpHost = $OctopusParameters['FtpHost']\n$FtpUsername = $OctopusParameters['FtpUsername']\n$FtpPassword = $OctopusParameters['FtpPassword']\n$FtpHostKeyFingerprint = $OctopusParameters['FtpHostKeyFingerprint']\n$FtpPasskey = $OctopusParameters['FtpPasskey']\n$FtpPasskeyPhrase = $OctopusParameters['FtpPasskeyPhrase']\n$FtpRemoteDirectory = $OctopusParameters['FtpRemoteDirectory']\n$FtpDeleteUnrecognizedFiles = $OctopusParameters['FtpDeleteUnrecognizedFiles']\n$DeleteDeploymentStep = $OctopusParameters['DeleteDeploymentStep']\n\n## --------------------------------------------------------------------------------------\n## Helpers\n## --------------------------------------------------------------------------------------\n\nfunction Get-ModuleInstalled\n{\n    # Define parameters\n    param(\n        $PowerShellModuleName\n    )\n\n    # Check to see if the module is installed\n    if ($null -ne (Get-Module -ListAvailable -Name $PowerShellModuleName))\n    {\n        # It is installed\n        return $true\n    }\n    else\n    {\n        # Module not installed\n        return $false\n    }\n}\n\nfunction Install-PowerShellModule\n{\n    # Define parameters\n    param(\n        $PowerShellModuleName,\n        $LocalModulesPath\n    )\n\n\t# Check to see if the package provider has been installed\n    if ((Get-NugetPackageProviderNotInstalled) -ne $false)\n    {\n    \t# Display that we need the nuget package provider\n        Write-Host \"Nuget package provider not found, installing ...\"\n        \n        # Install Nuget package provider\n        Install-PackageProvider -Name Nuget -Force\n    }\n\n\t# Save the module in the temporary location\n    Save-Module -Name $PowerShellModuleName -Path $LocalModulesPath -Force\n}\n\nfunction Get-NugetPackageProviderNotInstalled\n{\n\t# See if the nuget package provider has been installed\n    return ($null -eq (Get-PackageProvider -ListAvailable -Name Nuget -ErrorAction SilentlyContinue))\n}\n\n# Helper for validating input parameters\nfunction Validate-Parameter([string]$foo, [string[]]$validInput, $parameterName) {\n    if (! $parameterName -contains \"Password\")\n    {\n        Write-Host \"${parameterName}: $foo\"\n    }\n    if (! $foo) {\n        throw \"No value was set for $parameterName, and it cannot be empty\"\n    }\n}\n\n# A collection of functions that can be used by script steps to determine where packages installed\n# by previous steps are located on the filesystem.\nfunction Find-InstallLocations {\n    $result = @()\n    $OctopusParameters.Keys | foreach {\n        if ($_.EndsWith('].Output.Package.InstallationDirectoryPath')) {\n            $result += $OctopusParameters[$_]\n        }\n    }\n    return $result\n}\n\nfunction Find-InstallLocation($stepName) {\n    $result = $OctopusParameters.Keys | where {\n        $_.Equals(\"Octopus.Action[$stepName].Output.Package.InstallationDirectoryPath\",  [System.StringComparison]::OrdinalIgnoreCase)\n    } | select -first 1\n\n    if ($result) {\n        return $OctopusParameters[$result]\n    }\n\n    throw \"No install location found for step: $stepName\"\n}\n\nfunction Find-SingleInstallLocation {\n    $all = @(Find-InstallLocations)\n    if ($all.Length -eq 1) {\n        return $all[0]\n    }\n    if ($all.Length -eq 0) {\n        throw \"No package steps found\"\n    }\n    throw \"Multiple package steps have run; please specify a single step\"\n}\n\n# Session.FileTransferred event handler\nfunction FileTransferred\n{\n    param($e)\n\n    if ($e.Error -eq $Null)\n    {\n        Write-Host (\"Upload of {0} succeeded\" -f $e.FileName)\n    }\n    else\n    {\n        Write-Error (\"Upload of {0} failed: {1}\" -f $e.FileName, $e.Error)\n    }\n\n    if ($e.Chmod -ne $Null)\n    {\n        if ($e.Chmod.Error -eq $Null)\n        {\n            Write-Host \"##octopus[stdout-verbose]\"\n            Write-Host (\"Permisions of {0} set to {1}\" -f $e.Chmod.FileName, $e.Chmod.FilePermissions)\n            Write-Host \"##octopus[stdout-default]\"\n        }\n        else\n        {\n            Write-Error (\"Setting permissions of {0} failed: {1}\" -f $e.Chmod.FileName, $e.Chmod.Error)\n        }\n\n    }\n    else\n    {\n        Write-Host \"##octopus[stdout-verbose]\"\n        Write-Host (\"Permissions of {0} kept with their defaults\" -f $e.Destination)\n        Write-Host \"##octopus[stdout-default]\"\n    }\n\n    if ($e.Touch -ne $Null)\n    {\n        if ($e.Touch.Error -eq $Null)\n        {\n            Write-Host \"##octopus[stdout-verbose]\"\n            Write-Host (\"Timestamp of {0} set to {1}\" -f $e.Touch.FileName, $e.Touch.LastWriteTime)\n            Write-Host \"##octopus[stdout-default]\"\n        }\n        else\n        {\n            Write-Error (\"Setting timestamp of {0} failed: {1}\" -f $e.Touch.FileName, $e.Touch.Error)\n        }\n\n    }\n    else\n    {\n        # This should never happen during \"local to remote\" synchronization\n        Write-Host \"##octopus[stdout-verbose]\"\n        Write-Host (\"Timestamp of {0} kept with its default (current time)\" -f $e.Destination)\n        Write-Host \"##octopus[stdout-default]\"\n    }\n}\n\n## --------------------------------------------------------------------------------------\n## Configuration\n## --------------------------------------------------------------------------------------\n# Define PowerShell Modules path\n$LocalModules = (New-Item \"$PSScriptRoot\\Modules\" -ItemType Directory -Force).FullName\n$env:PSModulePath = \"$LocalModules;$env:PSModulePath\"\n$PowerShellModuleName = \"WinSCP\"\n\n# Set secure protocols\n[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls11 -bor [System.Net.SecurityProtocolType]::Tls12\n\n# Check to see if $PathToWinScp is empty\nif ([string]::IsNullOrEmpty($PathToWinScp))\n{\n    # Check to see if WinSCP module is installed\n    if ((Get-ModuleInstalled -PowerShellModuleName $PowerShellModuleName) -ne $true)\n    {\n      # Tell user what we're doing\n      Write-Output \"PowerShell module $PowerShellModuleName is not installed, downloading temporary copy ...\"\n\n      # Install temporary copy\n      Install-PowerShellModule -PowerShellModuleName $PowerShellModuleName -LocalModulesPath $LocalModules\n      \n      # Import module\n      Write-Output \"Importing $PowerShellModuleName ...\"\n      Import-Module $PowerShellModuleName\n    }\n\t#Get-ChildItem -Path ([System.IO.Path]::GetDirectoryName((Get-Module $PowerShellModuleName).Path))\n\t# Get the path to where the dll resides\n    #$PathToWinScp = [System.IO.Path]::GetDirectoryName((Get-Module $PowerShellModuleName).Path)\n}\nelse\n{\n\tValidate-Parameter $PathToWinScp -parameterName \"Path to WinSCP .NET Assembly\"\n    # Load WinSCP .NET assembly\n    $fullPathToWinScp = \"$PathToWinScp\\WinSCPnet.dll\"\n    if(-not (Test-Path $fullPathToWinScp))\n    {\n        throw \"$PathToWinScp does not contain the WinSCP .NET Assembly\"\n    }\n    Add-Type -Path $fullPathToWinScp    \n}\n\n#Validate-Parameter $PathToWinScp -parameterName \"Path to WinSCP .NET Assembly\"\nValidate-Parameter $FtpHost -parameterName \"Host\"\nValidate-Parameter $FtpUsername -parameterName \"Username\"\nValidate-Parameter $FtpPassword -parameterName \"Password\"\nValidate-Parameter $FtpRemoteDirectory -parameterName \"Remote directory\"\nValidate-Parameter $FtpDeleteUnrecognizedFiles -parameterName \"Delete unrecognized files\"\n\n## --------------------------------------------------------------------------------------\n## Main script\n## --------------------------------------------------------------------------------------\n\n# Load WinSCP .NET assembly\n<#\n$fullPathToWinScp = \"$PathToWinScp\\WinSCPnet.dll\"\nif(-not (Test-Path $fullPathToWinScp))\n{\n    throw \"$PathToWinScp does not contain the WinSCP .NET Assembly\"\n}\nAdd-Type -Path $fullPathToWinScp\n#>\n$stepPath = \"\"\n\n$stepPath = $OctopusParameters[\"Octopus.Action.Package[FtpPackage].ExtractedPath\"]\n\nWrite-Host \"Package was installed to: $stepPath\"\n\ntry\n{\n    $sessionOptions = New-Object WinSCP.SessionOptions\n\n    # WinSCP defaults to SFTP, but it's good to ensure that's the case\n    if (![string]::IsNullOrEmpty($FtpHostKeyFingerprint)) {\n      $sessionOptions.Protocol = [WinScp.Protocol]::Sftp\n      $sessionOptions.SshHostKeyFingerprint = $FtpHostKeyFingerprint\n    }\n    else {\n      $sessionOptions.Protocol = [WinSCP.Protocol]::Ftp\n    }\n    $sessionOptions.HostName = $FtpHost\n    $sessionOptions.UserName = $FtpUsername\n\n    \n\n    # If there is a path to the private key, use that instead of a password\n    if (![string]::IsNullOrEmpty($FtpPasskey)) {\n      Write-Host \"Attempting to use passkey instead of password\"\n\n      # Check key exists\n      if (!(Test-Path $FtpPasskey)) {\n        throw \"Unable to locate passkey at: $FtpPasskey\"\n      }\n\n      $sessionOptions.SshPrivateKeyPath = $FtpPasskey\n\n      # If the key requires a passphrase to access\n      if ($FtpPasskeyPhrase -ne \"\") {\n        $sessionOptions.PrivateKeyPassphrase = $FtpPasskeyPhrase\n      }\n    }\n    else {\n      $sessionOptions.Password = $FtpPassword\n    }\n\n    $session = New-Object WinSCP.Session\n    \n    if ([string]::IsNullOrEmpty($PathToWinScp))\n    {\n    \t# Using PowerShell module, need to set the executable location\n        $session.ExecutablePath = \"$([System.IO.Path]::GetDirectoryName((Get-Module $PowerShellModuleName).Path))\\bin\\winscp.exe\"\n    }\n    \n    try\n    {\n    \n        # Will continuously report progress of synchronization\n        $session.add_FileTransferred( { FileTransferred($_) } )\n\n        # Connect\n        $session.Open($sessionOptions)\n\n        Write-Host \"Beginning synchronization between $stepPath and $FtpRemoteDirectory on $FtpHost\"\n\n        if (-not $session.FileExists($FtpRemoteDirectory))\n        {\n            Write-Host \"Remote directory not found, creating $FtpRemoteDirectory\"\n            $session.CreateDirectory($FtpRemoteDirectory);\n        }\n\n        # Synchronize files\n        $synchronizationResult = $session.SynchronizeDirectories(\n            [WinSCP.SynchronizationMode]::Remote, $stepPath, $FtpRemoteDirectory, $FtpDeleteUnrecognizedFiles)\n\n        # Throw on any error\n        $synchronizationResult.Check()\n    }\n    finally\n    {\n        # Disconnect, clean up\n        $session.Dispose()\n\n        if ($DeleteDeploymentStep) {\n          Remove-Item -Path $stepPath -Recurse\n        }\n    }\n\n    exit 0\n}\ncatch [Exception]\n{\n    throw $_.Exception.Message\n}\n\n",
    "Octopus.Action.Script.ScriptSource": "Inline"
  },
  "Category": "Winscp",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/ftp-uploadfiles-package.json",
  "Website": "/step-templates/8b316926-0821-459b-9869-3ca98fd9087e",
  "Logo": "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",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Saturday, August 8, 2020