PowerShell Server: SFTP Scripting
Introduction
By default, the SFTP Server will act as a standard SFTP server and provide file management functionality for the specified root directory. In some cases, it may be desirable to implement advanced functionality. PowerShell Server provides an advanced SFTP scripting technique where a PowerShell script can be used to customize the SFTP functions.
This tutorial will guide you through setting up this functionality within PowerShell Server as well as the PowerShell functions that are required within the script.
Chapter Listing
Setup
To use a PowerShell Script to control SFTP functionality, simply point the SFTP Root Directory under the SFTP tab to the location of a PowerShell script. The dialog/modal available via the Browse button only allows selecting folders so you'll need to type in the path.
Script
The script used in this tutorial can be downloaded here.
Below is the path variable and some additional functions that are used by the example functions listed below.
$sftpRoot = "C:\temp" function Get-UnixTime($time) { return [long]($time - [DateTime]'1970/01/01 12:00:00 AM').TotalSeconds } function Resolve-SFTPPath($vpath) { return [IO.Path]::Combine($sftpRoot, $vpath.Substring(1)) }
Required Functions
Below is a list of functions that must be implemented in the PowerShell script to control the corresponding SFTP functionality. Note that these are just examples of default functionality, and can be modified to suit your implementation’s specific needs.
Confirm-DirList: Called when listing the contents of a directory.
function Confirm-DirList($sftpArgs) { # $sftpArgs.connectionId: connection id # $sftpArgs.user: username # $sftpArgs.path: directory virtual path # out: # $sftpArgs.statusCode: operation result # $sftpArgs.fileList: string[] with just filenames $path = Resolve-SFTPPath $sftpArgs.path if ( -not (test-path $path) ) { $sftpArgs.statusCode = $SSH_FXS_NO_SUCH_PATH return } $sftpArgs.fileList = Get-ChildItem $path | %{ $_.Name } $sftpArgs.statusCode = $SSH_FXS_OK }
Confirm-DirCreate: Called when creating a directory.
function Confirm-DirCreate($sftpArgs) { # $sftpArgs.connectionId: connection id # $sftpArgs.user: username # $sftpArgs.path: directory virtual path # $sftpArgs.attrs: directory attributes # out: # $sftpArgs.statusCode: operation result $sftpArgs.statusCode = $SSH_FXS_OK $path = Resolve-SFTPPath $sftpArgs.path New-Item -Path $path -ItemType Directory }
Confirm-DirRemove: Called when removing a directory.
function Confirm-DirRemove($sftpArgs) { # $sftpArgs.connectionId: connection id # $sftpArgs.user: username # $sftpArgs.path: directory virtual path # out: # $sftpArgs.statusCode: operation result $sftpArgs.statusCode = $SSH_FXS_OK $path = Resolve-SFTPPath $sftpArgs.path Remove-Item -Path $path -force }
Confirm-FileOpen: Called when opening a file.
function Confirm-FileOpen($sftpArgs) { # $sftpArgs.connectionId: connection id # $sftpArgs.user: username # $sftpArgs.path: file virtual path # $sftpArgs.desiredAccess: desired file access # $sftpArgs.flags: file open flags # $sftpArgs.attrs: file attributes # out: # $sftpArgs.statusCode: operation result # $sftpArgs.physicalPath: physical path to file the server will handle $physicalPath = Resolve-SFTPPath $sftpArgs.path $flags = $sftpArgs.flags if ( -not ($flags -band $SSH_V3_FXF_CREAT) ) { # opening existing file if ( -not (test-path $physicalPath) ) { $sftpArgs.statusCode = $SSH_FXS_NO_SUCH_FILE; return } } else { # creating a new file if ( -not (test-path $physicalPath) ) { New-Item -Path $physicalPath -ItemType File } } $sftpArgs.physicalPath = $physicalPath $sftpArgs.statusCode = $SSH_FXS_OK }
Confirm-FileClose: Called when closing a file.
function Confirm-FileClose($sftpArgs) { # $sftpArgs.connectionId: connection id # $sftpArgs.user: username # $sftpArgs.path: file or directory virtual path # $sftpArgs.statusCode: operation result # $sftpArgs.physicalPath: physical path of the opened file # you could for example grab the contents here and delete it $sftpArgs.statusCode = $SSH_FXS_OK }
Confirm-FileRemove: Called when removing a file.
function Confirm-FileRemove($sftpArgs) { # $sftpArgs.connectionId: connection id # $sftpArgs.user: username # $sftpArgs.path: file virtual path # out: # $sftpArgs.statusCode: operation result $sftpArgs.statusCode = $SSH_FXS_OK $path = Resolve-SFTPPath $sftpArgs.path if ( -not (test-path $path) ) { $sftpArgs.statusCode = $SSH_FXS_NO_SUCH_PATH return } Remove-Item $path }
Confirm-FileRename: Called when renaming a file.
function Confirm-FileRename($sftpArgs) { # $sftpArgs.connectionId: connection id # $sftpArgs.user: username # $sftpArgs.path: original file virtual path # $sftpArgs.newPath: new file virtual path # out: # $sftpArgs.statusCode: operation result $sftpArgs.statusCode = $SSH_FXS_OK $path = Resolve-SFTPPath $sftpArgs.path if ( -not (test-path $path) ) { $sftpArgs.statusCode = $SSH_FXS_NO_SUCH_PATH return } $newPath = Resolve-SFTPPath $sftpArgs.newPath Write-Debug -Message "Moving $path to $newPath" Move-Item $path $newPath }
Confirm-GetAttributes: Called when retrieving a file’s attributes.
function Confirm-GetAttributes($sftpArgs) { # $sftpArgs.connectionId: connection id # $sftpArgs.user: username # $sftpArgs.path: directory virtual path # $sftpArgs.flags: flags for this operation # $sftpArgs.attrs: file attributes to return, as a hashtable # out: # $sftpArgs.statusCode: operation result $sftpArgs.statusCode = $SSH_FXS_OK $path = Resolve-SFTPPath $sftpArgs.path if ( -not (test-path $path) ) { $sftpArgs.statusCode = $SSH_FXS_NO_SUCH_PATH return } $file = Get-Item $path $acl = Get-ACL $path $attrs = $sftpArgs.attrs $attrs.creationTime = Get-UnixTime($file.CreationTimeUtc) $attrs.isDir = $file.PSIsContainer if ($file.PSIsContainer -eq "true") { $attrs.fileType = 2 } else { $attrs.fileType = 1 } $attrs.modifiedTime = Get-UnixTime($file.LastWriteTimeUtc) $attrs.accessTime = Get-UnixTime($file.LastAccessTimeUtc) $attrs.size = $file.Length $attrs.ownerId = $acl.Owner $attrs.groupId = $acl.Group }
Confirm-SetAttributes: Called when setting a file’s attributes.
function Confirm-SetAttributes($sftpArgs) { # $sftpArgs.connectionId: connection id # $sftpArgs.user: username # $sftpArgs.path: file virtual path # $sftpArgs.attrs: file attributes # out: # $sftpArgs.statusCode: operation result $sftpArgs.statusCode = $SSH_FXS_OK }
Error Codes
The following SFTP Error Codes may be useful if you need to return an error from one of the above functions.
$SSH_FXS_OK = 0 $SSH_FXS_EOF = 1 $SSH_FXS_NO_SUCH_FILE = 2 $SSH_FXS_PERMISSION_DENIED = 3 $SSH_FXS_FAILURE = 4 $SSH_FXS_BAD_MESSAGE = 5 $SSH_FXS_NO_CONNECTION = 6 $SSH_FXS_CONNECTION_LOST = 7 $SSH_FXS_OP_UNSUPPORTED = 8 $SSH_FXS_INVALID_HANDLE = 9 $SSH_FXS_NO_SUCH_PATH = 10 $SSH_FXS_FILE_ALREADY_EXISTS = 11 $SSH_FXS_WRITE_PROTECT = 12 $SSH_FXS_NO_MEDIA = 13 $SSH_FXS_NO_SPACE_ON_FILESYSTEM = 14 $SSH_FXS_QUOTA_EXCEEDED = 15 $SSH_FXS_UNKNOWN_PRINCIPAL = 16 $SSH_FXS_LOCK_CONFLICT = 17 $SSH_FXS_DIR_NOT_EMPTY = 18 $SSH_FXS_NOT_A_DIRECTORY = 19 $SSH_FXS_INVALID_FILENAME = 20 $SSH_FXS_LINK_LOOP = 21 $SSH_FXS_CANNOT_DELETE = 22 $SSH_FXS_INVALID_PARAMETER = 23 $SSH_FXS_FILE_IS_A_DIRECTORY = 24 $SSH_FXS_BYTE_RANGE_LOCK_CONFLICT = 25 $SSH_FXS_BYTE_RANGE_LOCK_REFUSED = 26 $SSH_FXS_DELETE_PENDING = 27 $SSH_FXS_FILE_CORRUPT = 28 $SSH_FXS_OWNER_INVALID = 29 $SSH_FXS_GROUP_INVALID = 30 $SSH_FXS_NO_MATCHING_BYTE_RANGE_LOCK = 31 # File open flags $SSH_V3_FXF_READ = 0x00000001 $SSH_V3_FXF_WRITE = 0x00000002 $SSH_V3_FXF_APPEND = 0x00000004 $SSH_V3_FXF_CREAT = 0x00000008 $SSH_V3_FXF_TRUNC = 0x00000010 $SSH_V3_FXF_EXCL = 0x00000020 $SSH_V4_FXF_TEXT = 0x00000040
Additional Information
The sample script used in this tutorial can be downloaded here.
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@nsoftware.com.
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@nsoftware.com.