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.
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"
}
}
Page updated on Saturday, August 8, 2020