# encoding: ascii # api: powershell # title: WinSCPPowershell Module # description: WinSCP wrapper module I created out of necessity that I put together through my own exploration of the objects and the WinSCP documentation/examples. # version: 0.1 # type: script # author: BattleChicken # license: CC0 # function: Set-WinSCPDLLFolder # x-poshcode-id: 5981 # x-derived-from-id: 6056 # x-archived: 2016-05-17T14:33:51 # x-published: 2016-08-19T20:39:00 # # IT IS FLAWED IN HOW IT IS WRITTEN, but it functions. At some point I would like to revisit this and fix the issues with how I do valuefrompipeline (which is implemented ‘wrong’, but doesn’t manifest in problems because I limit the pipe to a single object) and other cleanup… # I haven’t finished all of the help for all of the functions either. This is incomplete, but the core functionality – connecting (at least via sftp), syncing directories, uploading files, and downloading files all function properly. Even if you don’t use the module, you can see how to accomplish the tasks by interacting with the COM objects to write your own method. # it’s not finished and I need to improve it but it functions. # $Script:WinSCPCSettingsOptionsObjectType = "" $Script:WinSCPDirHasBeenLoaded = $false $script:WinSCPDLLFolder = "" #path to folder containing the WinSCP DLL and EXE Function Set-WinSCPDLLFolder { <# .Synopsis Specify the folder for WinSCP.exe and WinSCPnet.dll .DESCRIPTION Validates that WinSCP.exe and WinSCPnet.dll exists, then sets the folder containing WinSCP.exe and WinSCPnet.dll .PARAMETER DLLFolder String that contains the path containing the WinSCPnet.dll and WinSCPnet.dll .EXAMPLE Set-WinSCPDLLFolder -DLLFolder C:\Admin Sets the DLL folder for the WinSCPPowershell module to C:\Admin .NOTES The Function will throw an error if the executables do not exist EXACTLY as WinSCPnet.dll and WinSCP.exe in the target directory. Once it gets set one time, no cmdlets require it to be provided. .COMPONENT WinSCPnet.dll .FUNCTIONALITY Sets the folder containing WinSCP.exe and WinSCPnet.dll #> param( [parameter(Mandatory=$true)] [string]$DLLFolder ) #Make sure the required components exist in $DLLFolder if (test-path $DLLFolder){ if ((Test-Path "${DLLFolder}\WinSCPnet.dll")){} else{ throw "no WinSCPnet.dll found in ${DLLFolder}." } if ((Test-Path "${DLLFolder}\WinSCP.exe")){} else{ throw "no WinSCP.exe found in ${DLLFolder}." } } else {throw "$DLLFolder does not appear to exist."} # set the global for later use $script:WinSCPDLLFolder = $DLLFolder # adding the type here so it's accessible in later cmdlets. add-type -Path "$dllfolder\WinSCPnet.dll" -ErrorAction stop if ($?){ $Script:WinSCPDirHasBeenLoaded = $true } # the next code line is to store the type of [WinSCP.SessionOptions] object so connect-WinscpServer can validate $WinSCPSessionOptionsObject AFTER this function # gets loaded inside the function - $WinSCPSessionOptionsObject has type [object] since [WinSCP.SessionOptions] doesn't exist until this function gets loaded and # connect-winscpserver loads it - Something of a Which came first, the chicken or the egg scenario. Edit: I changed the method but haven't retrofitted a lot of this. Anyway. $tempObj = New-Object WinSCP.SessionOptions $Script:WinSCPCSettingsOptionsObjectType = $tempObj.psobject.typenames[0] } #confirms a remote path exists. i may update this function do do more, validate additional things.. that's why it exists. Function Test-WinSCPRemotePathExists { <# .Synopsis Check if a remote file or folder exists on a SFTP site .DESCRIPTION Check if a remote file or folder exists on a SFTP site .PARAMETER WinSCPSessionObject WinSCP Session object. .PARAMETER RemotePath String containing a remote path .EXAMPLE Test-WinSCPRemotePathExists -WinSCPSessionObject $MySession -RemotePath /temp Checks if /temp exists on the SFTP site connected to $MySession .EXAMPLE $MySession | Test-WinSCPRemotePathExists -RemotePath /TextFile.txt Checks if /TextFile.txt exists on the SFTP site connected to $MySession .OUTPUTS Returns $True if $remotePath exists, $False if it does not. #> param( [parameter(ValueFromPipeLine=$true,Mandatory=$true)] [WinSCP.Session]$WinSCPSessionObject, [parameter(Mandatory=$true)] [string]$RemotePath ) if ($RemotePath.contains("\")){ $RemotePath = $RemotePath -replace "\\","/" # invert the slashes, since the behavior is really erratic if the type is wrong. } # remove Stars to confirm that if someone passes * to $remotepath that it won't throw an error. $WinSCPSessionObject.FileExists(($RemotePath.replace("*",""))) } Function Get-WinSCPItemInfo { <# .Synopsis Returns a PSObject that contains information about $RemotePath .DESCRIPTION Returns a PSObject that contains File information data about the file or folder $RemotePath .EXAMPLE Get-WinSCPItemInfo -WinSCPSessionObject $MySession -RemotePath /temp Returns a PSObject that contains information about /temp .EXAMPLE $MySession | Get-WinSCPItemInfo -RemotePath /TextFile.txt Returns a PSObject that contains information about /TextFile.txt .OUTPUTS Outputs an object containing data like the following (if it exists) /temp in this example is a directory: Name : /temp FileType : D Length : 0 LastWriteTime : 7/31/2014 1:22:10 PM FilePermissions : rwxrw-rw- IsDirectory : True Othewise it returns nothing. #> param( [parameter(ValueFromPipeLine=$true,Mandatory=$true)] [WinSCP.Session]$WinSCPSessionObject, [parameter(Mandatory=$true)] [string]$RemotePath ) try{ $WinSCPSessionObject.GetFileInfo($RemotePath) } catch { throw "$RemotePath does not exist" } } Function Get-PasswordFromEncryptedFile { <# .Synopsis Converts a password stored as a secure string to a file to plain text. .DESCRIPTION Converts a password stored as a secure string to a file to plain text. .EXAMPLE Get-PasswordFromEncryptedFile -PasswordFile "c:\admin\MyEncryptedPass.txt" Assuming the user who encryptedt he password is the same user executing the command, will decrypt the string in c:\admin\MyEncryptedPass.txt to plain-text. .OUTPUTS Outputs a string object .NOTES This function can be tricky. it decrypts a securestring, so it will only be reversible by the same user that created the original encrypted file. So, if my user is MyDomain\MyUsername, only MyDomain\MyUsername on the same machine can reverse the encryption. Keep in mind the decrypt will only work if you created the file on that same machine. .FUNCTIONALITY Decrypts a secure string saved to a file. #> param( [parameter(Mandatory=$true)] [string]$PasswordFile ) if (-not (Test-Path $PasswordFile)){ throw "Nonexistent Password file" } else { try{ $encryptedPass = get-content $PasswordFile | ConvertTo-SecureString $encryptedStr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($encryptedPass) [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($encryptedStr) [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($encryptedStr) # Cleanup to avoid memory leak } catch { throw "Error decrypting Secure string. Only files encrypted by $env:USERNAME on $env:COMPUTERNAME can be decrypted in this session." } } } Function New-PasswordFile { <# .Synopsis Saves a string (a password most likely) to the specified file. .DESCRIPTION Saves a string (a password most likely) to the specified file. .EXAMPLE New-PasswordFile -PasswordFile c:\admin\MyEncryptedPassword.txt Prompts the user for a string, which gets saved encrypted to c:\admin\MyEncryptedPassword.txt #> param( [parameter(Mandatory=$true)] [string]$PasswordFile ) read-host -AsSecureString "Enter a password" | ConvertFrom-SecureString -ErrorAction stop| out-file $PasswordFile -ErrorAction Stop } # # need to upgrade to account for all possible protocols. Need to figure out how to specify the port. Need default values based on protocol.. will use a switch probably # to set the port values. need to explore the sessionobject more. Built originally from the example code so it's all SFTP currently. # Function New-WinSCPBlankSessionOptions{ <# .Synopsis Create a WinSCP session options object. .DESCRIPTION Create a winSCP session options object, which will be blank in order to create a more flexible sessionoptions object. .PARAMETER DLLFolder The path to the directory containing WinSCP.exe and WinSCPnet.dll. .EXAMPLE $myOptions = New-WinSCPBlankSessionOptions Creates a blank WinSCP Session options object and stores it to $myoptions. You can then do $myOptions | Get-Member to show the available properties. .FUNCTIONALITY Create a blank WinSCP Session options object for connecting to SFTP. #> param( [parameter()] [string]$DLLFolder=$Script:WinSCPDLLFolder ) if (-not ($Script:WinSCPDirHasBeenLoaded)){ Set-WinSCPDLLFolder -DLLFolder $DLLFolder $DLLFolder = $Script:WinSCPDLLFolder } # Setup session options New-Object WinSCP.SessionOptions } Function New-WinSCPSessionOptions { <# .Synopsis Create a WinSCP session options object. .DESCRIPTION Create a winSCP session options object, which contains all of the configuration options to connect to a site via SFTP. .PARAMETER Hostname The IP or name of the site to connect to .PARAMETER Port Used to specify a non-standard to connect to the specified Hostname. If not specified, it will use the default protocol port. .PARAMETER Username Account to log into the SFTP/SCP/FTP site with .PARAMETER PasswordFile String path to an encrypted file containing a password to be read or created (depending on if -SetSecurePassword is selected) .PARAMETER SshHostKeyFingerprint String containing the ssh host key fingerprint. Expects format like "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" .PARAMETER SetSecurePassword Choose this if you want to create $PasswordFile in addition to reading $passwordFile in. It will use read-host, so not for automated usage. .PARAMETER PlainTextPassword String containing a plain-text password to log into the SFTP/SCP/FTP site. .PARAMETER DLLFolder The path to the directory containing WinSCP.exe and WinSCPnet.dll. .EXAMPLE New-WinSCPSessionOptions -Hostname sftp.example.com -Username MyUsername -PasswordFile c:\admin\MyUsernamePass.txt -SshHostKeyFingerprint "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" Creates a session options object pointing at sftp.example.com, which has a host key fingerprint of "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff". It will connect as MyUsername using the password encrypted in MyUsernamePass.txt .EXAMPLE New-WinSCPSessionOptions -Hostname sftp.example.com -Username MyUsername -PasswordFile c:\admin\MyUsernamePass.txt -SshHostKeyFingerprint "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" -SetSecurePassword Creates a session options object pointing at sftp.example.com, which has a host key fingerprint of "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff". It will connect as MyUsername and will prompt the user to enter a string, which it will save as MyUsernamePass.txt .EXAMPLE New-WinSCPSessionOptions -Hostname sftp.example.com -Username MyUsername -PlainTextPassword "Password123" -SshHostKeyFingerprint "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" Creates a session options object pointing at sftp.example.com, which has a host key fingerprint of "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff". It will connect as MyUsername using the password "Password123" .NOTES The function needs some work to increase the flexibiliy. Right now it only works for SFTP since it is all the author had access to for testing. to create a custom object, you can do New-Object WinSCP.SessionOptions. This will hopefully change at some point in the future. .FUNCTIONALITY Create a WinSCP Session options object for connecting to SFTP. #> param( [parameter(Mandatory=$true)] [string]$Hostname, [parameter()] [validateset("Ftp","Scp","Sftp")] # Webdav was added as an option, but not adding it yet since I haven't looked into the requirements yet. [string]$Protocol="Sftp", [parameter()] [string]$Port = $null, # based on protocol, the session options automatically use the right default port so it is not required. [parameter(Mandatory=$true)] [string]$Username, [parameter(Mandatory=$true,ParameterSetName="SecurePass")] [string]$PasswordFile, [parameter(Mandatory=$true)] [string]$SshHostKeyFingerprint, [parameter(ParameterSetName="SecurePass")] [switch]$SetSecurePassword, [parameter(Mandatory=$true,ParameterSetName="PlainTextPass")] [ValidateNotNullOrEmpty()] [string]$PlainTextPassword, [parameter()] [string]$DLLFolder=$Script:WinSCPDLLFolder ) if (-not ($Script:WinSCPDirHasBeenLoaded)){ Set-WinSCPDLLFolder -DLLFolder $DLLFolder $DLLFolder = $Script:WinSCPDLLFolder } # Setup session options $sessionOptions = New-Object WinSCP.SessionOptions if ($SetSecurePassword){ New-PasswordFile -PasswordFile $PasswordFile } $sessionOptions.Protocol = [WinSCP.Protocol]::$Protocol $sessionOptions.HostName = $HostName $sessionOptions.UserName = $Username if ($port){ $sessionOptions.PortNumber = $Port } if ($PasswordFile){ $sessionOptions.Password = Get-PasswordFromEncryptedFile -PasswordFile $PasswordFile } else { $sessionOptions.Password = $PlainTextPassword } $sessionOptions.SshHostKeyFingerprint = $SshHostKeyFingerprint $sessionOptions } # # Need to modify this to allow a passed in SessionOptions object. Add 2 appropriate param sets # Function Connect-WinSCPSFTPServer{ <# .Synopsis Create a WinSCP session options object. .DESCRIPTION Create a winSCP session options object, which contains all of the configuration options to connect to a site via SFTP. .PARAMETER Hostname The IP or name of the site to connect to .PARAMETER Port Used to specify a non-standard to connect to the specified Hostname. If not specified, it will use the default protocol port. .PARAMETER Username Account to log into the SFTP/SCP/FTP site with .PARAMETER PasswordFile String path to an encrypted file containing a password to be read or created (depending on if -SetSecurePassword is selected) .PARAMETER SshHostKeyFingerprint String containing the ssh host key fingerprint. Expects format like "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" .PARAMETER SetSecurePassword Choose this if you want to create $PasswordFile in addition to reading $passwordFile in. It will use read-host, so not for automated usage. .PARAMETER PlainTextPassword String containing a plain-text password to log into the SFTP/SCP/FTP site. .PARAMETER DLLFolder The path to the directory containing WinSCP.exe and WinSCPnet.dll. .EXAMPLE Connect-WinSCPSFTPServer -Hostname sftp.example.com -Username MyUsername -PasswordFile c:\admin\MyUsernamePass.txt -SshHostKeyFingerprint "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" Creates a session options object pointing at sftp.example.com, which has a host key fingerprint of "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff". It will connect as MyUsername using the password encrypted in MyUsernamePass.txt and connects to the server. .EXAMPLE Connect-WinSCPSFTPServer -Hostname sftp.example.com -Username MyUsername -PasswordFile c:\admin\MyUsernamePass.txt -SshHostKeyFingerprint "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" -SetSecurePassword Creates a session options object pointing at sftp.example.com, which has a host key fingerprint of "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff". It will connect as MyUsername and will prompt the user to enter a string, which it will save as MyUsernamePass.txt and connects to the server. .EXAMPLE Connect-WinSCPSFTPServer -Hostname sftp.example.com -Username MyUsername -PlainTextPassword "Password123" -SshHostKeyFingerprint "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" Creates a session options object pointing at sftp.example.com, which has a host key fingerprint of "ssh-rsa 2048 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff". It will connect as MyUsername using the password "Password123" and connects to the server. .EXAMPLE Connect-WinSCPSFTPServer -WinSCPSessionOptionsObject $customSessionOptions Creates a session object connected to the specified server using the WinSCPSessionOptionsObject created prior to the connection. .NOTES .FUNCTIONALITY Create a WinSCP Session options object for connecting to SFTP then connects to the specified host. #> param( [parameter(Mandatory=$true,ParameterSetName="MakeObjPlainTextPass")] [parameter(Mandatory=$true,ParameterSetName="MakeObjSecurePass")] [string]$Hostname, [parameter(ParameterSetName="MakeObjPlainTextPass")] [parameter(ParameterSetName="MakeObjSecurePass")] [validateset("Ftp","Scp","Sftp")] [string]$Protocol="Sftp", [parameter(ParameterSetName="MakeObjPlainTextPass")] [parameter(ParameterSetName="MakeObjSecurePass")] [int]$Port = $null, [parameter(Mandatory=$true,ParameterSetName="MakeObjPlainTextPass")] [parameter(Mandatory=$true,ParameterSetName="MakeObjSecurePass")] [string]$Username, [parameter(Mandatory=$true,ParameterSetName="MakeObjSecurePass")] [string]$PasswordFile, [parameter(Mandatory=$true,ParameterSetName="MakeObjPlainTextPass")] [parameter(Mandatory=$true,ParameterSetName="MakeObjSecurePass")] [string]$SshHostKeyFingerprint, [parameter(Mandatory=$true,ParameterSetName="MakeObjPlainTextPass")] [ValidateNotNullOrEmpty()] [string]$PlainTextPassword, [parameter(ParameterSetName="PassObj")] [Object]$WinSCPSessionOptionsObject, [parameter()] [string]$DLLFolder=$Script:WinSCPDLLFolder, [parameter(ParameterSetName="MakeObjSecurePass")] [switch]$SetSecurePassword ) if (-not ($Script:WinSCPDirHasBeenLoaded)){ Set-WinSCPDLLFolder -DLLFolder $DLLFolder $DLLFolder = $Script:WinSCPDLLFolder } if ($SetSecurePassword){ New-PasswordFile -PasswordFile $PasswordFile } $sessionOptions = $null if (-not $WinSCPSessionOptionsObject){ if ($PasswordFile){ $sessionOptions = New-WinSCPSessionOptions -DLLFolder $DLLFolder -Username $Username -Hostname $Hostname -Protocol $Protocol -Port $Port -PasswordFile $PasswordFile -SshHostKeyFingerprint $SshHostKeyFingerprint } if ($PlainTextPassword){ $sessionOptions = New-WinSCPSessionOptions -DLLFolder $DLLFolder -Username $Username -Hostname $Hostname -Protocol $Protocol -Port $Port -PlainTextPassword $PlainTextPassword -SshHostKeyFingerprint $SshHostKeyFingerprint } } else{ if ($WinSCPSessionOptionsObject.psobject.typenames[0] -eq $Script:WinSCPCSettingsOptionsObjectType){ $sessionOptions = $WinSCPSessionOptionsObject } else{ throw "invalid object type passed. Object of type $Script:WinSCPCSettingsOptionsObjectType expected" } } $session = New-Object WinSCP.Session $session.ExecutablePath = "$DLLFolder\WinSCP.exe" # I need to rework the output text. I don't like it currently - will do write-verbose Write-Verbose "attempting to connect to server $Hostname" # connect to FTP session try { $session.Open($sessionOptions) } catch { throw "Exception opening session. Double-check your credentials" } if ($session.opened -eq $true){ $session } else{ throw "Unable to connect to server. Check your connection details and try again" } }; New-Alias -Name Connect-WinSCPServer -Value Connect-WinSCPSFTPServer -Description "Connect to a MFT server" Function New-WinSCPTransferOptions { <# .Synopsis Creates a new WinSCP Transfer Options object .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet .EXAMPLE Another example of how to use this cmdlet .INPUTS Inputs to this cmdlet (if any) .OUTPUTS Output from this cmdlet (if any) .NOTES General notes .COMPONENT The component this cmdlet belongs to .ROLE The role this cmdlet belongs to .FUNCTIONALITY The functionality that best describes this cmdlet #> param( [parameter()] [ValidateSet("Ascii","Automatic","Binary")] [string]$Mode="Binary", [parameter()] [ValidateSet("Default","Off","On","Smart")] [string]$ResumeSupport="Default", [parameter()] [string]$FileMask="", [parameter()] [ValidateScript({$_ -ge 0})] [int]$SpeedLimitKB=0 ) # set optional params $TransferOptions = New-Object WinSCP.TransferOptions if($FileMask -ne "") { $TransferOptions.FileMask = $FileMask } $TransferOptions.SpeedLimit = $SpeedLimitKB $TransferOptions.TransferMode = [WinSCP.TransferMode]::$Mode $TransferOptions.ResumeSupport.State = [WinSCP.TransferResumeSupportState]::$ResumeSupport $TransferOptions } # This functions for a single file (might for multiple, haven't tested yet) and for a single folder. # I need to add some additional logic about the Local and remote locations to make sure there's a leading / at the end of the folder name. Function New-WinSCPTransfer { <# .Synopsis Specify the folder for WinSCP.exe and WinSCPnet.dll .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet .EXAMPLE Another example of how to use this cmdlet .INPUTS Inputs to this cmdlet (if any) .OUTPUTS Output from this cmdlet (if any) .NOTES General notes .COMPONENT The component this cmdlet belongs to .ROLE The role this cmdlet belongs to .FUNCTIONALITY The functionality that best describes this cmdlet #> param( [parameter(ValueFromPipeLine=$true,Mandatory=$true)] [WinSCP.Session]$WinSCPSessionObject, [parameter()] [validateset("Download","Upload")] [string]$TransferType, [parameter(Mandatory=$true)] [string]$LocalPath, [parameter(Mandatory=$true)] [string]$RemotePath, [parameter()] [ValidateSet("Ascii","Automatic","Binary")] [string]$Mode="Binary", [parameter()] [string]$FileMask="", [parameter()] [switch]$DeleteSourceFilesAfterTransfer, [parameter()] [ValidateSet("Default","Off","On","Smart")] [string]$ResumeSupport="Default", [parameter()] [ValidateScript({$_ -ge 0})] [int]$SpeedLimitKB=0 ) if (-not (Test-WinSCPRemotePathExists -WinSCPSessionObject $WinSCPSessionObject -RemotePath ($RemotePath.replace("*","")))){ throw("The RemotePath does not exist on FTP: ${RemotePath}") } if ($DeleteSourceFilesAfterTransfer){ $remove = $true } else { $remove = $false } # # Will increase the validation here at some point. It isn't doing as much error correction as I want it to do long term. # The com object expects the ending slashes. may need to rethink the bit for "Download". Upload absolutely requires the end slash on $remotepath # $RemotePath = $RemotePath -replace "\\","/" # Invert the slashes in case they're wrong. It behaves really erratically if the slashes are wrong. switch ($TransferType.tolower()){ "download" { #if (-not ($LocalPath.endswith("/"))){ # $LocalPath += "\" #} break } "upload" { if (-not ($RemotePath.EndsWith("/")) -or ($RemotePath.EndsWith("*"))){ $RemotePath += "/" } break } } $TransferOptions = New-WinSCPTransferOptions -Mode $Mode -ResumeSupport $ResumeSupport -FileMask $FileMask -SpeedLimitKB $SpeedLimitKB switch ($TransferType.tolower()){ upload { Write-Verbose "Beginning upload..." # execute File Upload $result = $WinSCPSessionObject.PutFiles($LocalPath, $RemotePath, $remove, $TransferOptions) } download{ Write-Verbose "Beginning download..." # execute File Download $result = $WinSCPSessionObject.GetFiles($RemotePath, $LocalPath, $remove, $TransferOptions) } } # need to validate this better. $result.Check() # Validate the transfers. May rework some of this, haven't decided yet. if(($result.Transfers | Measure).count -gt 0) { write-verbose "Files successfully transfered:" $i = 1 $result.Transfers | % { Write-Verbose "`t$i - $($_.Destination)" $i++ } } if(($result.Failures | Measure).count -gt 0) { Write-Verbose "Failed file transfers:" $i=1 $result.Failures | % { Write-Verbose "`t$i - $($_.FileName)" $i++ } } if(-not $result.IsSuccess) { throw("FTP transfer Failed") } } # # # Upload wrapper function # # Function New-WinSCPUpload { <# .Synopsis Specify the folder for WinSCP.exe and WinSCPnet.dll .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet .EXAMPLE Another example of how to use this cmdlet .INPUTS Inputs to this cmdlet (if any) .OUTPUTS Output from this cmdlet (if any) .NOTES General notes .COMPONENT The component this cmdlet belongs to .ROLE The role this cmdlet belongs to .FUNCTIONALITY The functionality that best describes this cmdlet #> param( [parameter(ValueFromPipeLine=$true,Mandatory=$true)] [WinSCP.Session]$WinSCPSessionObject, [parameter(Mandatory=$true)] [string]$LocalPath, [parameter(Mandatory=$true)] [string]$RemotePath, [parameter()] [ValidateSet("Ascii","Automatic","Binary")] [string]$Mode="Binary", [parameter()] [string]$FileMask="", [parameter()] [switch]$DeleteSourceFilesAfterTransfer, [parameter()] [ValidateSet("Default","Off","On","Smart")] [string]$ResumeSupport="Default", [parameter()] [ValidateScript({$_ -ge 0})] [int]$SpeedLimitKB=0 ) if ($DeleteSourceFilesAfterTransfer){ $WinSCPSessionObject | New-WinSCPTransfer -TransferType Upload -LocalPath $LocalPath -RemotePath $RemotePath -Mode $Mode -FileMask $FileMask -ResumeSupport $ResumeSupport -SpeedLimitKB $SpeedLimitKB -DeleteSourceFilesAfterTransfer } else{ $WinSCPSessionObject | New-WinSCPTransfer -TransferType Upload -LocalPath $LocalPath -RemotePath $RemotePath -Mode $Mode -FileMask $FileMask -ResumeSupport $ResumeSupport -SpeedLimitKB $SpeedLimitKB } }; New-Alias -Name Upload-Files -Value New-WinSCPUpload -Description "Upload a file using WinSCP" # # # Download wrapper function # # Function New-WinSCPDownload { <# .Synopsis Specify the folder for WinSCP.exe and WinSCPnet.dll .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet .EXAMPLE Another example of how to use this cmdlet .INPUTS Inputs to this cmdlet (if any) .OUTPUTS Output from this cmdlet (if any) .NOTES General notes .COMPONENT The component this cmdlet belongs to .ROLE The role this cmdlet belongs to .FUNCTIONALITY The functionality that best describes this cmdlet #> param( [parameter(ValueFromPipeLine=$true,Mandatory=$true)] [WinSCP.Session]$WinSCPSessionObject=$null, [parameter(Mandatory=$true)] [string]$LocalPath, [parameter(Mandatory=$true)] [string]$RemotePath, [parameter()] [ValidateSet("Ascii","Automatic","Binary")] [string]$Mode="Binary", [parameter()] [string]$FileMask="", [parameter()] [switch]$DeleteSourceFilesAfterTransfer, [parameter()] [ValidateSet("Default","Off","On","Smart")] [string]$ResumeSupport="Default", [parameter()] [ValidateScript({$_ -ge 0})] [int]$SpeedLimitKB=0 ) if ($DeleteSourceFilesAfterTransfer){ $WinSCPSessionObject | New-WinSCPTransfer -TransferType Download -LocalPath $LocalPath -RemotePath $RemotePath -Mode $Mode -FileMask $FileMask -ResumeSupport $ResumeSupport -SpeedLimitKB $SpeedLimitKB -DeleteSourceFilesAfterTransfer } else{ $WinSCPSessionObject | New-WinSCPTransfer -TransferType Download -LocalPath $LocalPath -RemotePath $RemotePath -Mode $Mode -FileMask $FileMask -ResumeSupport $ResumeSupport -SpeedLimitKB $SpeedLimitKB } }; New-Alias -Name Download-Files -Value New-WinSCPDownload -Description "Upload a file using WinSCP" Function Close-WinSCPSFTPServerConnection { <# .Synopsis Specify the folder for WinSCP.exe and WinSCPnet.dll .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet .EXAMPLE Another example of how to use this cmdlet .INPUTS Inputs to this cmdlet (if any) .OUTPUTS Output from this cmdlet (if any) .NOTES General notes .COMPONENT The component this cmdlet belongs to .ROLE The role this cmdlet belongs to .FUNCTIONALITY The functionality that best describes this cmdlet #> param( [parameter(ValueFromPipeLine=$true,Mandatory=$true)] [WinSCP.Session]$WinSCPSessionObject=$null ) $WinSCPSessionObject.dispose() } # # Returns an object array with file listing data # Function Get-WinSCPDirectoryList { <# .Synopsis Specify the folder for WinSCP.exe and WinSCPnet.dll .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet .EXAMPLE Another example of how to use this cmdlet .INPUTS Inputs to this cmdlet (if any) .OUTPUTS Output from this cmdlet (if any) .NOTES General notes .COMPONENT The component this cmdlet belongs to .ROLE The role this cmdlet belongs to .FUNCTIONALITY The functionality that best describes this cmdlet #> param( [parameter(ValueFromPipeLine=$true,Mandatory=$true)] [WinSCP.Session]$WinSCPSessionObject=$null, [parameter(Mandatory=$true)] [string]$RemotePath ) if (Test-WinSCPRemotePathExists -WinSCPSessionObject $WinSCPSessionObject -RemotePath $RemotePath){ $WinSCPSessionObject.ListDirectory($RemotePath) | select -ExpandProperty files } else { Write-Warning "$RemotePath Does not exist" return $null } } # # Creates remote directory # Function New-WinSCPRemoteDirectory { <# .Synopsis Specify the folder for WinSCP.exe and WinSCPnet.dll .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet .EXAMPLE Another example of how to use this cmdlet .INPUTS Inputs to this cmdlet (if any) .OUTPUTS Output from this cmdlet (if any) .NOTES General notes .COMPONENT The component this cmdlet belongs to .ROLE The role this cmdlet belongs to .FUNCTIONALITY The functionality that best describes this cmdlet #> param( [parameter(ValueFromPipeLine=$true,Mandatory=$true)] [WinSCP.Session]$WinSCPSessionObject, [parameter(Mandatory=$true)] [string]$RemotePath ) if (Test-WinSCPRemotePathExists -WinSCPSessionObject $WinSCPSessionObject -RemotePath $RemotePath){ Write-Verbose "$RemotePath already existed" } else{ try { $WinSCPSessionObject.CreateDirectory($RemotePath) if ($?){ Write-Verbose "$RemotePath has been created" } } catch { throw "Error creating $RemotePath" } } } Function Remove-WinSCPRemoteItem { <# .Synopsis Specify the folder for WinSCP.exe and WinSCPnet.dll .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet .EXAMPLE Another example of how to use this cmdlet .INPUTS Inputs to this cmdlet (if any) .OUTPUTS Output from this cmdlet (if any) .NOTES General notes .COMPONENT The component this cmdlet belongs to .ROLE The role this cmdlet belongs to .FUNCTIONALITY The functionality that best describes this cmdlet #> param( [parameter(ValueFromPipeLine=$true,Mandatory=$true)] [WinSCP.Session]$WinSCPSessionObject, [parameter(Mandatory=$true)] [string]$RemotePath, [parameter()] [switch]$force ) $delData = $null $dirContents = $null if (-not (Test-WinSCPRemotePathExists -WinSCPSessionObject $WinSCPSessionObject -RemotePath $RemotePath)){ Write-Verbose "$RemotePath does not exist. No action taken." } else{ $itemInfo = Get-WinSCPItemInfo -WinSCPSessionObject $WinSCPSessionObject -RemotePath $RemotePath if ($itemInfo.isDirectory){ $dirContents = @(Get-WinSCPDirectoryList -WinSCPSessionObject $WinSCPSessionObject -RemotePath $RemotePath) if (($dirContents.count -gt 0) -and (-not ($force))){ #Check if it recurses, correct the notice if it doesn't. Definitely don't want to recurse down. Write-Warning "${RemotePath} is a directory with additional files in it. at least $($dirContents.count) items. This check does NOT recurse - there may be a lot more files." $input = (read-host "Are you sure you want to delete everything inside ${RemotePath}?").tolower() if ($input.StartsWith("y")){ $delData = $WinSCPSessionObject.RemoveFiles($RemotePath) } else { Write-Verbose "$RemotePath has not been deleted. No action taken" } } else { $delData = $WinSCPSessionObject.RemoveFiles($RemotePath) } } else{ $delData = $WinSCPSessionObject.RemoveFiles($RemotePath) } } # # List what was deleted, confirm the deletes were succesful # if ($delData -ne $null){ # Validate the transfers. May rework some of this, haven't decided yet. if(($delData.Removals | Measure).count -gt 0) { write-verbose "Files/folders successfully deleted:" $i = 1 $delData.Removals | % { Write-Verbose "$i - $($_.FileName)" $i++ } } if(($delData.Failures | Measure).count -gt 0) { Write-Verbose "Failed file/folder deletions:" $i=1 $delData.Failures | % { Write-Verbose "$i - $($_.FileName)" $i++ } } if(-not $delData.IsSuccess) { throw("Deletion of ${RemotePath} failed.") } } } function Sync-WinSCPDirectory{ param( [parameter(ValueFromPipeLine=$true,Mandatory=$true)] [WinSCP.Session]$WinSCPSessionObject, [parameter(Mandatory=$true)] [string]$LocalPath, [parameter(Mandatory=$true)] [string]$RemotePath, [Parameter(Mandatory=$true)] [ValidateSet("Local","Remote","Both")] [WinSCP.SynchronizationMode]$SynchronizationMode, [Parameter()] [ValidateSet("None","Time","Size","Either")] [WinSCP.SynchronizationCriteria]$SynchronizationCriteria="Time", [Parameter()] [Switch]$Mirror, [parameter()] [ValidateSet("Ascii","Automatic","Binary")] [string]$TransferMode="Binary", [parameter()] [string]$FileMask="", [Parameter()] [Switch]$RemoveFilesAfterTransfer, [parameter()] [ValidateSet("Default","Off","On","Smart")] [string]$ResumeSupport="Default", [parameter()] [ValidateScript({$_ -ge 0})] [int]$SpeedLimitKB=0 ) $TransferOptions = New-WinSCPTransferOptions -Mode $Mode -ResumeSupport $ResumeSupport -FileMask $FileMask -SpeedLimitKB $SpeedLimitKB try{ $output = $WinSCPSessionObject.SynchronizeDirectories($SynchronizationMode, $LocalPath, $RemotePath, $RemoveFilesAfterTransfer.IsPresent, $Mirror.IsPresent, $SynchronizationCriteria, $TransferOptions) } catch { #I want to update this later Throw $_ } if (($output.uploads | Measure).count -gt 0){ write-verbose "Files/folders successfully uploaded:" $i = 1 $output.uploads | % {Write-Verbose "`t$i - $($_.FileName)"; $i++} } if (($output.downloads | Measure).count -gt 0){ write-verbose "Files/folders successfully downloaded:" $i = 1 $output.downloads | % {Write-Verbose "`t$i - $($_.FileName)"; $i++} } if (($output.Failures | Measure).count -gt 0){ write-verbose "Files/folders Failed:" $i = 1 $output.Failures | % {Write-Verbose "`t$i - $($_.FileName)"; $i++} } } # # Check if the WinSCP .exe and .dll exist in the module direcory and auto-load if so. # $modulePath = split-path $SCRIPT:MyInvocation.MyCommand.Path -parent if ((Test-Path "$modulePath\WinSCP.exe") -and (Test-Path "$modulePath\WinSCPnet.dll")){ Set-WinSCPDLLFolder -DLLFolder $modulePath } else{ throw "Unable to find the WinSCP executables WinSCPnet.dll and WinSCP.exe in $modulePath" } # End DLLFolder auto-load # export-modulemember -alias * -function *