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

  1. Setup
  2. Script
  3. Additional Information

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.

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-ItemProperty $path
    $acl = Get-ACL $path
    $attrs = $sftpArgs.attrs
    $attrs.creationTime = Get-UnixTime($file.CreationTimeUtc)
    $attrs.isDir = $file.PSIsContainer
    $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.