I'm trying to get the file size while I'm uploading the file. My end goal is to try and get the upload speed, but in my while loop i get a Exception calling "OpenRead" with "1" argument(s): "The remote server returned an error: (550) File unavailable (e.g., file not found, no access).
Please see code below
$uploadRemoteFile = "Remote URI"
$ftpuname = "UserName"
$ftppassword = 'Password'
function upload-ftp{
$File = "$env:TEMP\something.exe"
$webclient = New-Object System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($ftpuname,$ftppassword);
$uri = New-Object System.Uri($uploadRemoteFile)
$webclient.UploadFileAsync($Uri, $File)
$arrayftp = #()
Function Get-newfilesize{
$webclientRead = New-Object System.Net.WebClient;
$webclientRead.Credentials = New-Object System.Net.NetworkCredential($ftpuname,$ftppassword);
[void]$webclientRead.OpenRead($uri);
[Int64]$bytes_total= ($webclientRead.ResponseHeaders["Content-Length"])
$webclientsize = ($bytes_total.ToString());
$webclientsize
}
while ($webclient.IsBusy){
$oldftpfile = Get-newfilesize;
$oldftpdate = Get-Date;
Start-Sleep -Milliseconds 1
$newftpfile = Get-newfilesize;
$newftpdate = Get-Date;
$ftpsizediff = $newftpfile - $oldftpfile;
$ftptimediff = $newftpdate - $oldftpdate;
$totalftpdiff = $ftpsizediff / $ftptimediff.totalseconds;
$totalftpdiff | foreach {
if ($_ -gt 0){$arrayftp += $_ }
}
}
$testftpcap= New-Object psobject -Property #{"Upload Speed" =((($arrayftp | measure -Average).Average/1MB)* 10)}
$testftpcap | Export-csv -NoTypeInformation -Path $env:TEMP\ftpspeed.csv
}
Thanks
Not exactly a direct answer to your question but you could use Powershell FTP module - works great and has a progress bar too..
(http://gallery.technet.microsoft.com/scriptcenter/PowerShell-FTP-Client-db6fe0cb)
Related
maybe one of you experts can help a complete newbie (I don't know if what I want is even feasible).
Let's assume I have a CSV file with various data. (see csv_screenshot)csv_screenshot
I import this data via Powershell into a small GUI . How can I make it so that when I search for "Paris", I really only get the output for Paris in the GUI as a list view like this (see powershell_screenshot)
powershell_screenshot
Currently the output in the GUI looks like this (see current_result.png). How do I get it nicely formatted as a list in there. I really want to insert it like this (via Out Grid View it is no problem)
current_result.png
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Windows.Forms.Application]::EnableVisualStyles();
function search_csv {
$Input = $textbox_Search.text
$Input = "*$Input*"
$Input_Result = import-csv -path C:\Users\check.csv -Header "Location", "Client", "Mobile Device"
$output_TextBox.text = $Input_Result -like $Input
}
$search_csvtool = New-Object System.Windows.Forms.Form
$search_csvtool.Text = "CSV Search"
$search_csvtool.Size = New-Object System.Drawing.Size(674,500)
$search_csvtool.FormBorderStyle ="FixedDialog"
$search_csvtool.TopMost = $true
$search_csvtool.MaximizeBox = $false
$search_csvtool.MinimizeBox = $true
$search_csvtool.ControlBox = $true
$search_csvtool.StartPosition = "CenterScreen"
$search_csvtool.Font = "Courier New"
$label_Search = New-Object System.Windows.Forms.Label
$label_Search.Location = New-Object System.Drawing.Size(195,18)
$label_Search.Size = New-Object System.Drawing.Size(265,32)
$label_Search.TextAlign ="MiddleCenter"
$label_Search.Text = "Please enter "
$search_csvtool.Controls.Add($label_Search)
$textbox_Search = New-Object System.Windows.Forms.TextBox
$textbox_Search.Location = New-Object System.Drawing.Size(195,50)
$textbox_Search.Size = New-Object System.Drawing.Size(266,37)
$search_csvtool.Controls.Add($textbox_Search)
$button_Search = New-Object System.Windows.Forms.Button
$button_Search.Location = New-Object System.Drawing.Size(195,80)
$button_Search.Size = New-Object System.Drawing.Size(266,24)
$button_Search.TextAlign = "MiddleCenter"
$button_Search.Text = "Search"
$button_Search.Add_Click({search_csv})
$search_csvtool.Controls.Add($button_Search)
$output_TextBox = New-Object System.Windows.Forms.TextBox
$output_TextBox.Multiline = $true;
$output_TextBox.Location = New-Object System.Drawing.Size(16,130)
$output_TextBox.Size = New-Object System.Drawing.Size(627,314)
$output_TextBox.ScrollBars = "Vertical"
$output_TextBox.ReadOnly = $true;
$search_csvtool.Controls.Add($output_TextBox)
$search_csvtool.Add_Shown({$search_csvtool.Activate()})
[void] $search_csvtool.ShowDialog()
Ok, so here's what I meant in my comment:
Start the code with
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$csvData = Import-Csv -path 'C:\Users\check.csv' -Header "Location", "Client", "Mobile Device"
function search_csv {
$searchThis = $textbox_Search.Text.Trim()
# use $script: scoping here to reference the $csvData variable
$data = $script:csvData | Where-Object {$_.Location -like "*$searchThis*"}
if ($data) {
$output_TextBox.Text = ($data | Format-List | Out-String).Trim()
}
else {
$output_TextBox.Text = "Not found.."
}
}
Then create the rest of the form as you did.
Important: Destroy the form when done with a last new code line:
$search_csvtool.Dispose()
You should then have this result:
As per your comment to empty the textbox when nothing (or just whitespace) has been entered, you could change the function to:
function search_csv {
$searchThis = $textbox_Search.Text.Trim()
# use $script: scoping here to reference the $csvData variable
$data = $script:csvData | Where-Object {$_.Location -like "*$searchThis*"}
if ([string]::IsNullOrWhiteSpace($searchThis)) {
$output_TextBox.Clear()
}
elseif ($data) {
$output_TextBox.Text = ($data | Format-List | Out-String).Trim()
}
else {
$output_TextBox.Text = "Not found.."
}
}
However, as postanote already commented, it would be much better to simply disable the search button and only enable it when something other that whitespace has been entered.
To do that, you need to add an eventhandler to the textbox:
$textbox_Search = New-Object System.Windows.Forms.TextBox
$textbox_Search.Location = New-Object System.Drawing.Size(195,50)
$textbox_Search.Size = New-Object System.Drawing.Size(266,37)
$textbox_Search.Add_TextChanged({
# enable the button when there is at least one non-whitespace character present
$button_Search.Enabled = $this.Text -match '\S'
# or use
# $button_Search.Enabled = (-not [string]::IsNullOrWhiteSpace($this.Text))
})
$search_csvtool.Controls.Add($textbox_Search)
And initialize the button to be disabled at startup:
$button_Search = New-Object System.Windows.Forms.Button
$button_Search.Location = New-Object System.Drawing.Size(195,80)
$button_Search.Size = New-Object System.Drawing.Size(266,24)
$button_Search.TextAlign = "MiddleCenter"
$button_Search.Text = "Search"
# initialize to Disabled; will be enabled as soon as there is text entered in the $textbox_Search
$button_Search.Enabled = $false
$button_Search.Add_Click({search_csv})
$search_csvtool.Controls.Add($button_Search)
Inside an event handler, you can refer to the object itself using automatic variable $this
You should be able to go thru your string output line by line and change this.
That stated, you probably won't like how it looks. Normalized colons will probably look better.
This code should do the trick. I'll let you be the judge of if it looks "better"
$dataLines = ($data | Format-List | Out-String).Trim() -split '(?>\r\n|\n)'
$dataText = #(
foreach ($dataLine in $dataLines) {
$dataLine -replace '\s{1,}\:', ':'
}
) -join [Environment]::Newline
$output_TextBox.Text = $dataText
I am writing a function in PowerShell that will do the following when given some parameter:
Get Credetials to connect to remote SQL Server
Open SQL Connection with given parameters. If Opening connection takes longer than value in $sqlconnectionTimeout, it will report a log file with Timeout error
A query will be introduce in a specific DB from Remote Server. If executing command takes longer than value in $sqlCommandTimeout, it will report a log file with error.
The function will create a html file with connectivity result (True or False). If True, the script will check log reports within last 5 minutes. Connectivity will be True if no logs are found. If False, a log will be created and saved in a specific folder.
About the Timeout variables, No matter what I put in the variable, The Connection is opened and the command is executed. Even If I put 0 forcing it to output a timeout error. The connection is opened and the query executed.
My question is: What am I missing here so that Timeout takes effect when executing the function?
Thanks for your help
This is the Code:
# Editing Variables
$user = "userName"
$DatabaseName = "SampleDB"
$query = "INSERT INTO [SampleDB].[dbo].[HealthChecks]([DateTime],[Source],[User]) VALUES (CURRENT_TIMESTAMP, ##SERVERNAME, CURRENT_USER)"
#Set timeout when stablishing connection (Default = 15)
$sqlConnectionTimeout = 3
#Set timeout when executing sql command (Default = 30)
$sqlCommandTimeout = 3
# 1. R-etrieve the data
function Test-SqlConnection
{
param(
[Parameter(Mandatory)]
[string]$serverURL,
[Parameter(Mandatory)]
[string]$DatabaseName,
[Parameter(Mandatory)]
[string]$user,
[Parameter(Mandatory)]
[string]$query,
[Parameter(Mandatory=$False)]
[string]$sqlConnectionTimeout,
[Parameter(Mandatory=$False)]
[string]$sqlCommandTimeout
)
try
{
$logTime = (Get-Date -Format "hh-mm_dd-MM-yyyy")
$reportPath = "C:\Logs\DBLogs\"
$DBCheckPath = "C:\inetpub\wwwroot\UptimeRobot\"
# Obtain Credentials without revealing password in script
$userName = $user
$passwordFile = "C:\Utilities\keys\password.txt"
$keyFile = "C:\Utilities\keys\aes.key"
$key = Get-Content $keyFile
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $userName, (Get-Content $passwordFile | ConvertTo-SecureString -Key $key)
# Open Connection to DB
$userName = $Credential.UserName
$password = $Credential.GetNetworkCredential().Password
$connectionString = "Data Source={0};database={1};User ID={2};Password={3};Connection Timeout={4}" -f $serverURL,$DatabaseName,$userName,$password,$sqlConnectionTimeout
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection $ConnectionString
$sqlConnection.Open()
#3. P-repare the UpdateRequest
$sqlCommand = New-Object System.Data.SQLClient.SQLCommand
$sqlCommand.CommandTimeout = $sqlCommandTimeout
$sqlCommand.Connection = $sqlConnection
$sqlCommand.CommandText = $query
$res = $sqlCommand.ExecuteNonQuery()
# 4. S-end reports to log file in JSon format
$sqlLogs = Get-Childitem $reportPath -filter *.json | Where-Object {$_.LastWriteTime -gt [datetime]::Now.AddMinutes(-5)}
if($res -eq 1)
{
if($sqlLogs.count -gt 0)
{
$connectivityResult = #{
DBServer = $ServerFriendlyName
DataCenter = $dataCenter
Connectivity = $False
}
}else
{
$connectivityResult =#{
DBServer = $ServerFriendlyName
DataCenter = $dataCenter
Connectivity = $True
}
}
$connectivityResult | ConvertTo-Json -Compress | Out-File -Encoding utf8 -LiteralPath ($DBCheckPath + "DBCheck.html")
}
##TODO: Only report Connectivity Serviceable= $false when there are two logs with $false value within 5 minutes.
}catch
{
$errorMessage = $_.Exception
## Only return $false if the exception was thrown because it can't connect for some reason. Otherwise
## throw the general exception
if ($errorMessage -match 'The server was not found' -or 'timeout')
{
$connectivityResult = #{
DBServer = $ServerFriendlyName
DataCenter = $dataCenter
Connectivity = $False
ErrorMessage = $errorMessage.GetBaseException().message
}
$connectivityResult | ConvertTo-Json | Out-File -Encoding utf8 -LiteralPath ($reportPath + $logTime + ".json")
$connectivityResult | ConvertTo-Json | Out-File -Encoding utf8 -LiteralPath ($DBCheckPath + "DBCheck.html")
}
}
finally
{
$sqlConnection.Close()
}
}
I am trying to grab emails from Exchange using powershell in UI Path. When trying to return the items, I get the following error:
Throw : Unable to cast object of type 'Microsoft.Exchange.WebServices.Data.GetItemResponse' to type 'System.String'.
Even when I change the TypeArgument in UI Path. Currently I am using the Invoke power shell activity but I get the same issues using the RunPowershellScript activity. Below is a screenshot of my sequence in UI Path, my parameters, and my powershell script, Thank you!
Param(
[parameter()]
[string]$mailbox,
[parameter()]
[string]$password
)
try{
#https://forum.uipath.com/t/get-argument-from-an-process-with-exception-in-reframework/22537/4
function test-password(){
$Creds = new-object System.Net.networkCredential -ArgumentList $mailbox, $password
$mailFolder = "Inbox"
$itemView = 3
#[Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll")
$ExSer = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
$ExSer.Credentials = ($Creds)
$ExSer.Url = new-object Uri("https://outlook.office365.com/EWS/Exchange.asmx")
$ExSer.AutodiscoverUrl($mailbox, {$true})
$setMailFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($ExSer,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
$logs2 += "Successfully Connected to mailbox $mailbox"
$iv = new-object Microsoft.Exchange.WebServices.Data.ItemView($itemView)
$CustomFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And)
$ifIsRead = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::IsRead,$false)
$ifFrom = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::From,"filteremail#mycompany.com")
$CustomFilter.add($ifIsRead)
$CustomFilter.add($ifFrom)
$filteredEmails = $ExSer.FindItems($setMailFolder.Id,$CustomFilter, $iv)
$psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text;
#add-type "Microsoft.Exchange.WebServices.Data.GetItemResponse"
$ExSer.LoadPropertiesForItems($filteredEmails,$psPropertySet)
$item = $filteredEmails.Items[0]
return $mailbox, $item
}
test-password
}
catch{
Throw
}
I commented out the load dll as it seemed to work without it and was throwing a similar error when it hit it. The code seems to throw an error when it hits $Exser.LoadPropertyItems. I have also tried switching to Exchange 2007 etc. To clarify, when running purely powershell outside of UI Path, this code works just fine.
I figured it out....I was just being a dingus. It was trying to load an object, which was breaking it. If I saved it as a variable and then converted it within my script and returned what I needed as a string....guess what? It could understand the string. Below is my updated powershell.
Param(
[parameter()]
[string]$mailbox,
[parameter()]
[string]$password
)
try{
#https://forum.uipath.com/t/get-argument-from-an-process-with-exception-in-reframework/22537/4
function test-password(){
$Creds = new-object System.Net.networkCredential -ArgumentList $mailbox, $password
$mailFolder = "Inbox"
$itemView = 50
# Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
#[Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll")
$ExSer = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
$ExSer.Credentials = ($Creds)
$ExSer.Url = new-object Uri("https://outlook.office365.com/EWS/Exchange.asmx")
$ExSer.AutodiscoverUrl($mailbox, {$true})
$setMailFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($ExSer,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
$logs2 += "Successfully Connected to mailbox $mailbox"
$iv = new-object Microsoft.Exchange.WebServices.Data.ItemView($itemView)
$CustomFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And)
$ifIsRead = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::IsRead,$true)
$ifFrom = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::From,"emailtofilterby#mycompany.com")
$CustomFilter.add($ifIsRead)
$CustomFilter.add($ifFrom)
$filteredEmails = $ExSer.FindItems($setMailFolder.Id,$CustomFilter, $iv)
$psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text;
#add-type "Microsoft.Exchange.WebServices.Data.GetItemResponse"
$FilteredEmailitems = $ExSer.LoadPropertiesForItems($filteredEmails,$psPropertySet)
$EmailBody = $FilteredEmailitems.Item(0) | Select-Object -ExpandProperty Item | Select-Object -ExpandProperty Body | Select-Object -ExpandProperty Text
return $EmailBody
}
test-password
}
catch{
Throw
}
So in my powershell script when it starts up it polls a ftp server and downloads any files that aren't in the local folder. The problem is when it gets to a folders it downloads them as files. this is my code for checking for new files:
$LocFolder = 'C:\EMSDropBox\*'
Remove-Item $LocFolder
$ftprequest = [System.Net.FtpWebRequest]::Create("ftp://NZHQFTP1/tbarnes")
$ftprequest.Proxy = $null
$ftprequest.KeepAlive = $false
$ftprequest.TimeOut = 10000000
$ftprequest.UsePassive = $False
$ftprequest.Credentials = New-Object System.Net.NetworkCredential("tbarnes", "Static_flow2290")
$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectory
$FTPResponse = $ftprequest.GetResponse()
$ResponseStream = $FTPResponse.GetResponseStream()
$FTPReader = New-Object System.IO.Streamreader($ResponseStream)
$filename = $FTPReader.ReadLine()
while($filename -ne $null)
{
try
{
if((Test-Path ("C:\emsdropbox\"+$filename)) -ne $true)
{
downloadFtp($filename)
}
$filename = $FTPReader.ReadLine()
}
catch
{
Write-Host $_
}
}
$FTPReader.Close()
$FTPResponse.Close()
$ResponseStream.Close()
and this is the downloadFtp function:
# FTP Config
$FTPHost = "****"
$Username = "******"
$Password = "*********"
$FTPFile = $file
# FTP Log File Url
$FTPFileUrl = "ftp://" + $FTPHost + "/tbarnes/" + $FTPFile
# Create FTP Connection
$FTPRequest = [System.Net.FtpWebRequest]::Create("$FTPFileUrl")
$FTPRequest.Credentials = New-Object System.Net.NetworkCredential($Username, $Password)
$FTPRequest.Method = [System.Net.WebRequestMethods+Ftp]::DownloadFile
$FTPRequest.UsePassive = $false
$FTPRequest.UseBinary = $true
$FTPRequest.KeepAlive = $false
$targetfile = New-Object IO.FileStream (("C:\emsdropbox\"+$file),[IO.FileMode]::Create)
# Get FTP File
$FTPResponse = $FTPRequest.GetResponse()
$ResponseStream = $FTPResponse.GetResponseStream()
$FTPReader = New-Object -typename System.IO.StreamReader -ArgumentList $ResponseStream
[byte[]]$readbuffer = New-Object byte[] 1024
#loop through the download stream and send the data to the target file
do{
$readlength = $ResponseStream.Read($readbuffer,0,1024)
$targetfile.Write($readbuffer,0,$readlength)
}
while ($readlength -ne 0)
$FTPReader.Close()
Im not sure why it wont pull them down as folders so any help or pointers would be great!
The FTP methods don't support downloading of folders, or recursion, by themselves, so there's no other way I can think of doing this but what I've suggested below.
Change the method so you can differentiate between files and directories and handle them accordingly.
Change $ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectory to $ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectoryDetails
Which will list in this format:
-rwxrwxrwx 1 owner group 277632 Mar 4 17:15 xml_socket.log.rar
Directories will have a d in place of the - at the start of the line.
Here is an amended try block that will match only files so you can pass to the downloadFtp function:
try
{
if(!($filename -match '^d')){if((Test-Path ("C:\emsdropbox\"+$filename.Split(" ")[8])) -ne $true)
{
downloadFtp(($filename -split '\s+')[8])
}}
$filename = $FTPReader.ReadLine()
}
If you want to then get a list of directories, use the following try block against the same ftpresponse stream and for each, ListDirectoryDetails to get the list of files in each directory to process them:
try
{
if($filename -match '^d'){if((Test-Path ("C:\emsdropbox\"+$filename.Split(" ")[8])) -ne $true)
{
listdir(($filename -split '\s+')[8])
}}
$filename = $FTPReader.ReadLine()
}
You may also have to create the local directories too which you can do in powershell as follows:
New-Item c:\emsdropbox\newdir -type directory
I want to use PowerShell to transfer files with FTP to an anonymous FTP server. I would not use any extra packages. How?
I am not sure you can 100% bullet proof the script from not hanging or crashing, as there are things outside your control (what if the server loses power mid-upload?) - but this should provide a solid foundation for getting you started:
# create the FtpWebRequest and configure it
$ftp = [System.Net.FtpWebRequest]::Create("ftp://localhost/me.png")
$ftp = [System.Net.FtpWebRequest]$ftp
$ftp.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$ftp.Credentials = new-object System.Net.NetworkCredential("anonymous","anonymous#localhost")
$ftp.UseBinary = $true
$ftp.UsePassive = $true
# read in the file to upload as a byte array
$content = [System.IO.File]::ReadAllBytes("C:\me.png")
$ftp.ContentLength = $content.Length
# get the request stream, and write the bytes into it
$rs = $ftp.GetRequestStream()
$rs.Write($content, 0, $content.Length)
# be sure to clean up after ourselves
$rs.Close()
$rs.Dispose()
There are some other ways too. I have used the following script:
$File = "D:\Dev\somefilename.zip";
$ftp = "ftp://username:password#example.com/pub/incoming/somefilename.zip";
Write-Host -Object "ftp url: $ftp";
$webclient = New-Object -TypeName System.Net.WebClient;
$uri = New-Object -TypeName System.Uri -ArgumentList $ftp;
Write-Host -Object "Uploading $File...";
$webclient.UploadFile($uri, $File);
And you could run a script against the windows FTP command line utility using the following command
ftp -s:script.txt
(Check out this article)
The following question on SO also answers this: How to script FTP upload and download?
I'm not gonna claim that this is more elegant than the highest-voted solution...but this is cool (well, at least in my mind LOL) in its own way:
$server = "ftp.lolcats.com"
$filelist = "file1.txt file2.txt"
"open $server
user $user $password
binary
cd $dir
" +
($filelist.split(' ') | %{ "put ""$_""`n" }) | ftp -i -in
As you can see, it uses that dinky built-in windows FTP client. Much shorter and straightforward, too. Yes, I've actually used this and it works!
Easiest way
The most trivial way to upload a binary file to an FTP server using PowerShell is using WebClient.UploadFile:
$client = New-Object System.Net.WebClient
$client.Credentials =
New-Object System.Net.NetworkCredential("username", "password")
$client.UploadFile(
"ftp://ftp.example.com/remote/path/file.zip", "C:\local\path\file.zip")
Advanced options
If you need a greater control, that WebClient does not offer (like TLS/SSL encryption, etc), use FtpWebRequest. Easy way is to just copy a FileStream to FTP stream using Stream.CopyTo:
$request = [Net.WebRequest]::Create("ftp://ftp.example.com/remote/path/file.zip")
$request.Credentials =
New-Object System.Net.NetworkCredential("username", "password")
$request.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$fileStream = [System.IO.File]::OpenRead("C:\local\path\file.zip")
$ftpStream = $request.GetRequestStream()
$fileStream.CopyTo($ftpStream)
$ftpStream.Dispose()
$fileStream.Dispose()
Progress monitoring
If you need to monitor an upload progress, you have to copy the contents by chunks yourself:
$request = [Net.WebRequest]::Create("ftp://ftp.example.com/remote/path/file.zip")
$request.Credentials =
New-Object System.Net.NetworkCredential("username", "password")
$request.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$fileStream = [System.IO.File]::OpenRead("C:\local\path\file.zip")
$ftpStream = $request.GetRequestStream()
$buffer = New-Object Byte[] 10240
while (($read = $fileStream.Read($buffer, 0, $buffer.Length)) -gt 0)
{
$ftpStream.Write($buffer, 0, $read)
$pct = ($fileStream.Position / $fileStream.Length)
Write-Progress `
-Activity "Uploading" -Status ("{0:P0} complete:" -f $pct) `
-PercentComplete ($pct * 100)
}
$ftpStream.Dispose()
$fileStream.Dispose()
Uploading folder
If you want to upload all files from a folder, see
PowerShell Script to upload an entire folder to FTP
I recently wrote for powershell several functions for communicating with FTP, see https://github.com/AstralisSomnium/PowerShell-No-Library-Just-Functions/blob/master/FTPModule.ps1. The second function below, you can send a whole local folder to FTP. In the module are even functions for removing / adding / reading folders and files recursively.
#Add-FtpFile -ftpFilePath "ftp://myHost.com/folder/somewhere/uploaded.txt" -localFile "C:\temp\file.txt" -userName "User" -password "pw"
function Add-FtpFile($ftpFilePath, $localFile, $username, $password) {
$ftprequest = New-FtpRequest -sourceUri $ftpFilePath -method ([System.Net.WebRequestMethods+Ftp]::UploadFile) -username $username -password $password
Write-Host "$($ftpRequest.Method) for '$($ftpRequest.RequestUri)' complete'"
$content = $content = [System.IO.File]::ReadAllBytes($localFile)
$ftprequest.ContentLength = $content.Length
$requestStream = $ftprequest.GetRequestStream()
$requestStream.Write($content, 0, $content.Length)
$requestStream.Close()
$requestStream.Dispose()
}
#Add-FtpFolderWithFiles -sourceFolder "C:\temp\" -destinationFolder "ftp://myHost.com/folder/somewhere/" -userName "User" -password "pw"
function Add-FtpFolderWithFiles($sourceFolder, $destinationFolder, $userName, $password) {
Add-FtpDirectory $destinationFolder $userName $password
$files = Get-ChildItem $sourceFolder -File
foreach($file in $files) {
$uploadUrl ="$destinationFolder/$($file.Name)"
Add-FtpFile -ftpFilePath $uploadUrl -localFile $file.FullName -username $userName -password $password
}
}
#Add-FtpFolderWithFilesRecursive -sourceFolder "C:\temp\" -destinationFolder "ftp://myHost.com/folder/" -userName "User" -password "pw"
function Add-FtpFolderWithFilesRecursive($sourceFolder, $destinationFolder, $userName, $password) {
Add-FtpFolderWithFiles -sourceFolder $sourceFolder -destinationFolder $destinationFolder -userName $userName -password $password
$subDirectories = Get-ChildItem $sourceFolder -Directory
$fromUri = new-object System.Uri($sourceFolder)
foreach($subDirectory in $subDirectories) {
$toUri = new-object System.Uri($subDirectory.FullName)
$relativeUrl = $fromUri.MakeRelativeUri($toUri)
$relativePath = [System.Uri]::UnescapeDataString($relativeUrl.ToString())
$lastFolder = $relativePath.Substring($relativePath.LastIndexOf("/")+1)
Add-FtpFolderWithFilesRecursive -sourceFolder $subDirectory.FullName -destinationFolder "$destinationFolder/$lastFolder" -userName $userName -password $password
}
}
Here's my super cool version BECAUSE IT HAS A PROGRESS BAR :-)
Which is a completely useless feature, I know, but it still looks cool \m/ \m/
$webclient = New-Object System.Net.WebClient
Register-ObjectEvent -InputObject $webclient -EventName "UploadProgressChanged" -Action { Write-Progress -Activity "Upload progress..." -Status "Uploading" -PercentComplete $EventArgs.ProgressPercentage } > $null
$File = "filename.zip"
$ftp = "ftp://user:password#server/filename.zip"
$uri = New-Object System.Uri($ftp)
try{
$webclient.UploadFileAsync($uri, $File)
}
catch [Net.WebException]
{
Write-Host $_.Exception.ToString() -foregroundcolor red
}
while ($webclient.IsBusy) { continue }
PS. Helps a lot, when I'm wondering "did it stop working, or is it just my slow ASDL connection?"
You can simply handle file uploads through PowerShell, like this.
Complete project is available on Github here https://github.com/edouardkombo/PowerShellFtp
#Directory where to find pictures to upload
$Dir= 'c:\fff\medias\'
#Directory where to save uploaded pictures
$saveDir = 'c:\fff\save\'
#ftp server params
$ftp = 'ftp://10.0.1.11:21/'
$user = 'user'
$pass = 'pass'
#Connect to ftp webclient
$webclient = New-Object System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($user,$pass)
#Initialize var for infinite loop
$i=0
#Infinite loop
while($i -eq 0){
#Pause 1 seconde before continue
Start-Sleep -sec 1
#Search for pictures in directory
foreach($item in (dir $Dir "*.jpg"))
{
#Set default network status to 1
$onNetwork = "1"
#Get picture creation dateTime...
$pictureDateTime = (Get-ChildItem $item.fullName).CreationTime
#Convert dateTime to timeStamp
$pictureTimeStamp = (Get-Date $pictureDateTime).ToFileTime()
#Get actual timeStamp
$timeStamp = (Get-Date).ToFileTime()
#Get picture lifeTime
$pictureLifeTime = $timeStamp - $pictureTimeStamp
#We only treat pictures that are fully written on the disk
#So, we put a 2 second delay to ensure even big pictures have been fully wirtten in the disk
if($pictureLifeTime -gt "2") {
#If upload fails, we set network status at 0
try{
$uri = New-Object System.Uri($ftp+$item.Name)
$webclient.UploadFile($uri, $item.FullName)
} catch [Exception] {
$onNetwork = "0"
write-host $_.Exception.Message;
}
#If upload succeeded, we do further actions
if($onNetwork -eq "1"){
"Copying $item..."
Copy-Item -path $item.fullName -destination $saveDir$item
"Deleting $item..."
Remove-Item $item.fullName
}
}
}
}
You can use this function :
function SendByFTP {
param (
$userFTP = "anonymous",
$passFTP = "anonymous",
[Parameter(Mandatory=$True)]$serverFTP,
[Parameter(Mandatory=$True)]$localFile,
[Parameter(Mandatory=$True)]$remotePath
)
if(Test-Path $localFile){
$remoteFile = $localFile.Split("\")[-1]
$remotePath = Join-Path -Path $remotePath -ChildPath $remoteFile
$ftpAddr = "ftp://${userFTP}:${passFTP}#${serverFTP}/$remotePath"
$browser = New-Object System.Net.WebClient
$url = New-Object System.Uri($ftpAddr)
$browser.UploadFile($url, $localFile)
}
else{
Return "Unable to find $localFile"
}
}
This function send specified file by FTP.
You must call the function with these parameters :
userFTP = "anonymous" by default or your username
passFTP = "anonymous" by default or your password
serverFTP = IP address of the FTP server
localFile = File to send
remotePath = the path on the FTP server
For example :
SendByFTP -userFTP "USERNAME" -passFTP "PASSWORD" -serverFTP "MYSERVER" -localFile "toto.zip" -remotePath "path/on/the/FTP/"
Goyuix's solution works great, but as presented it gives me this error: "The requested FTP command is not supported when using HTTP proxy."
Adding this line after $ftp.UsePassive = $true fixed the problem for me:
$ftp.Proxy = $null;
Simple solution if you can install curl.
curl.exe -p --insecure "ftp://<ftp_server>" --user "user:password" -T "local_file_full_path"