Extract http link from html element - powershell

Symantec recently changed their download page which moved to broadcom. Since then Invoke-WebRequest cannot grab the http url for the v5i64.exe file.
However the http url can be found when using Developer Tools in the browser looking at the Elements level, inside the body section of the page.
Does anyone have an idea on how this daily-changed url can be extracted with PowerShell?
$webreq = Invoke-WebRequest "https://www.broadcom.com/support/security-center/definitions/download/detail?gid=sep"
$webreq.Links | Select href

Use IE via ComObject
$ie = new-object -ComObject "InternetExplorer.Application"
$ie.visible=$True
while($ie.Busy) { Start-Sleep -Milliseconds 100 }
$IE.navigate2("https://www.broadcom.com/support/security-center/definitions/download/detail?gid=sep")
while ($IE.busy) {
start-sleep -milliseconds 1000 #wait 1 second interval to load page
}
Then find elementy by $ie.Document.IHTMLDocument3_getElementsByTagName("element name")

The following PowerShell script will prompt you to download the links containing the text v5i64.exe and HTTPS. This works on PowerShell 5.1 for Windows. It does not work for PowerShell 6 or 7 (PowerShell Core).
Tested on Windows 10.0.18363.657, Internet Explorer 11.657.18362, PowerShell 5.1.18362.628
$url = "https://www.broadcom.com/support/security-center/definitions/download/detail?gid=sep"
$outfile = "./v5i64.exe"
$ie = New-Object -ComObject "InternetExplorer.Application"
$ie.visible=$True
while($ie.Busy) {
Start-Sleep -Milliseconds 100
}
$ie.navigate2($url)
while($ie.ReadyState -ne 4 -or $ie.Busy) {
Start-Sleep -milliseconds 500
}
$ie.Document.getElementsByTagName("a") | % {
if ($_.ie8_href -like "*v5i64.exe") {
if ($_.ie8_href -like "https://*") {
$len = (Invoke-WebRequest $_.ie8_href -Method Head).Headers.'Content-Length'
Write-Host "File:" $_.ie8_href
Write-Host "Size:" $len
$confirm = Read-Host "Download file? [y/n]"
if ($confirm -eq "y") {
Write-Host "Downloading" $_.ie8_href
Invoke-WebRequest -Uri $_.ie8_href -OutFile $outfile
}
}
}
}
$ie.Stop()
$ie.Quit()

thanks for the proposed solutions. However here is my final code I am using:
$SEP_last_link = ("http://definitions.symantec.com/defs/"+($SEP_last | Select-String release -NotMatch | select -Last 1))
$Symantec_folder = "C:\Download for DVD\Symantec"
$Symantec_filepath = "$Symantec_folder\$SEP_last"
if (!(Test-Path "$Symantec_filepath" -PathType Leaf)) {
Write-Host "`rStart to download Symantec $SEP_last file: $(Get-Date)`r"
$start_time = (Get-Date)
$webclient = New-Object System.Net.WebClient
$WebClient.DownloadFile($SEP_last_link, $Symantec_filepath)
Write-Host "`r$SEP_last file has been downloaded successfully`r" -ForegroundColor Green
$end_time = $(get-date) - $start_time
$total_time = "{0:HH:mm:ss}" -f ([datetime]$end_time.Ticks)
Write-Host "`rTime to download Symantec $SEP_last file: $total_time`r"
} else {
Write-Host "`rSymantec $SEP_last file already exists!`r" -ForegroundColor Yellow
}
Get-ChildItem -Path "$Symantec_Folder\*-v5i64.exe" -Exclude "$SEP_last" -Verbose –Force | Remove-Item

Related

Problem downloading file using Powershell

I have this simple script that works on a laptop:
[Net.ServicePointManager]::SecurityProtocol
[enum]::GetNames([Net.SecurityProtocolType])
$url = "https://www.contextures.com/SampleData.zip"
wget -Uri $url -OutFile "C:\temp\temp.zip"
But when I'm trying to run it on a server I'm always getting this error:
wget : The request was aborted: Could not create SSL/TLS secure
channel.
Any ideas what might be causing this? Any help would be appreciated.
I already tried this and still getting the same error message:
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Set it to TLS 1.2:
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
You can give a try with this example while adding
$AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12';
cls
$Folder = "$Env:Temp\DownloadFolder\"
# We create a SubFolder Named "DownloadFolder" in the temporary file %Temp% if it doesn't exists yet !
If ((Test-Path -Path $Folder) -eq 0) { New-Item -Path $Folder -ItemType Directory | Out-Null }
$urls=#('https://www.contextures.com/SampleData.zip',
'https://cdn2.unrealengine.com/Fortnite%2FBoogieDown_GIF-1f2be97208316867da7d3cf5217c2486da3c2fe6.gif'
)
$start_time = Get-Date
$AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12';
[System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols;
$i = 0
foreach($url in $urls)
{
$i = $i + 1
$output = $Folder + $url.Split("/")[-1]
Try {
Write-Host "$i - Downloading" $output.Split("\")[-1] from $url
(New-Object System.Net.WebClient).DownloadFile($url,$output)
}
Catch
{
Write-Host "Error in : " $url -ForegroundColor Red -BackgroundColor Yellow
Write-Host "Message: [$($_.Exception.Message)"] -ForegroundColor Red -BackgroundColor Yellow
$i = $i - 1
}
}
Write-Output "Time taken: $((Get-Date).Subtract($start_time).Seconds) second(s) to download $i files"
ii $Folder

How do you get table data from a website after you login using powershell?

My company wants me to grab data from their internal website, organize it, and send it to a database. The data is displayed on tables that you navigate to within the site. I'm wanting to pull the fields into a file or memory for further processing.
So far, I can log into the site in powershell by getting the submit login button's ID, and passing my username/password. I'm able to pass use the navigate method to change the page to the appropriate page within the site. However, running an Invoke-WebRequest on the new page, as well as using the Net.WebClient on the new page is returning the information found on the original site's login screen(I know, because nothing from the table makes it into the returned values, regardless of the commands I use). The commented code is what I've tried previously.
Here is the code-minus the values of my id/password/site link
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$ie = New-Object -ComObject 'internetExplorer.Application'
$ie.Visible= $true # Make it visible
$username="myid"
$password="mypw"
$ie.Navigate("https://webpage.com/index.jsp")
While ($ie.Busy -eq $true) {Start-Sleep -Seconds 3;}
$usernamefield = $ie.document.getElementByID('login')
$usernamefield.value = "$username"
$passwordfield = $ie.document.getElementByID('password')
$passwordfield.value = "$password"
$Link = $ie.document.getElementByID('SubmitLogin')
$Link.click()
$url = "https://webpage.com/home.pa#%5BT1%2CM181%5D"
$ie.Navigate($url)
While ($ie.Busy -eq $true) {Start-Sleep -Seconds 3;}
$doc = $ie.document
$web = New-Object Net.WebClient
$web.DownloadString($url)
#$r = Invoke-WebRequest $url
#$r.Forms.fields | get-member
#$InnerText = $r.AllElements |
# Where-Object {$_.tagName -ne "TD" -and $_.innerText -ne $null} |
# Select -ExpandProperty innerText
#write-host $InnerText
#$r.AllElements|Where-Object {$_.InnerHtml -like "*=*"}
#$doc = $ie.Document
#$doc.getElementByID("ext-element-7") | % {
# if ($_.id -ne $null){
# write-host $_.id
# }
#}
$ie.Quit()
I obviously don't have your page and can't ensure that the body of the POST from signing in contains the fields login and password so that will require some trial & error from you. As a mini-example, if you open up your console dev tools network tab and filter by POST, you can observe how your login page signs you in. When I open reddit to sign in, it sends a POST to https://www.reddit.com/login with a body containing a username and password key/value (both plaintext). This action sets up my browser session to persist my login.
Here's a code example that uses the HtmlAgilityPack library to interact with the resulting page as if it were XML.
Enabling TLS1.2:
[System.Net.ServicePointManager]::SecurityProtocol =
[System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12
Setting up your web session:
$iwrParams = #{
'Uri' = 'https://webpage.com/index.jsp'
'Method' = 'POST'
'Body' = #{
'login' = $username
'password' = $password
}
'SessionVariable' = 'session'
# avoids cases where IE has not been opened
'UseBasicParsing' = $true
}
# don't care about response - only here to initialize the session
$null = Invoke-WebRequest #iwrParams
Getting the protect page content:
$iwrParams = #{
'Uri' = 'https://webpage.com/home.pa#%5BT1%2CM181%5D'
'WebSession' = $session
'UseBasicParsing' = $true
}
$output = (Invoke-WebRequest #iwrParams).Content
Downloading/adding HtmlAgility:
if (-not (Test-Path -Path "$PSScriptRoot\HtmlAgilityPack.dll" -PathType Leaf))
{
Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/HtmlAgilityPack -OutFile "$PSScriptRoot\html.zip"
Expand-Archive -Path "$PSScriptRoot\html.zip" -DestinationPath "$PSScriptRoot\html" -Force
Copy-Item -Path "$PSScriptRoot\html\lib\netstandard2.0\HtmlAgilityPack.dll" -Destination "$PSScriptRoot\"
Remove-Item -Path "$PSScriptRoot\html", "$PSScriptRoot\html.zip" -Recurse -Force
}
Add-Type -Path "$PSScriptRoot\HtmlAgilityPack.dll"
$html = [HtmlAgilityPack.HtmlDocument]::new()
Loading/parsing your page content:
$html.LoadHtml($output)
# do stuff with output.
$html.DocumentNode.SelectNodes('//*/text()').Text.Where{$PSItem -like '*=*'}
Footnote
I made the assumption in the code you were executing from a script where $PSScriptRoot will be populated. If it's being run interactively, you can use the $pwd automatic variable instead (carry-over from *nix, print working directory). This code requires PSv5+.
After some serious effort-I managed to get the pages to work correctly. Turns out I wasn't waiting for everything to load-but once I had that, I eventually found the correct tag/name to make everything work.
Assuming the code in the original post is correct up to "ie.Navigate($url)"
$ie.Navigate($url)
While ($ie.Busy -eq $true) {Start-Sleep -Seconds 3;}
$r = Invoke-WebRequest $url
$doc = $ie.document
$j = ($doc.getElementsByTagName("body") | Where {$_.className -eq 'thefullclassname found in the quotes of <body class="" of the area you wanted'}).innerText
write-host $j
This gave me the output of a very annoyingly done table that isn't a "table", and has the first row/col on it's own-so formatting the output to an easy to use version will be the new hassle. At least I got everything on the page that had the text I needed...so progress!

Download an Excel document from website with Powershell

recently I began to learn PowerShell to automate my job tasks.
So I want to access a web page and click on a button that download automatically an Excel file. This is the button that I want to click on:
<div class="NormalButton">
<a class="ActiveLink" title="Excel" alt="Excel" onclick="$find('ctl32').exportReport('EXCELOPENXML');" href="javascript:void(0)" style="padding:3px 8px 3px 8px;display:block;white-space:nowrap;text-decoration:none;">Excel</a>
</div>
This would be my PowerShell script:
$ie = New-Object -com "InternetExplorer.Application"
$ie.Navigate("http://test.test/")
$ie.Visible = $true
$link = $ie.Document.getElementsByTagName('a') | Where-Object {$_.onclick -eq "$find('ctl32').exportReport('EXCELOPENXML');"}
$link.click()
If I try to run it from the console I receive the error "You cannot call a method on a null-valued expression."
If its useful I'm using PowerShell 4.0 and the webpage has a delay until the report is loaded.
I have completed it by the following code:
[void][System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
[void][System.Reflection.Assembly]::LoadWithPartialName("'Microsoft.VisualBasic")
$url = "https://webpage.com"
$ie = New-Object -com internetexplorer.application
$ie.navigate($url)
$ie.StatusBar = $false
$ie.ToolBar = $false
$ie.visible = $true
#Get Excel
Start-Sleep -s 40
$btnExcel = $ie.Document.links | where-object { $_.outerText -eq 'Excel' -and $_.innerText -eq 'Excel' }
$btnExcel.click()
# Get Internet Explorer Focus
Start-Sleep -s 5
[Microsoft.VisualBasic.Interaction]::AppActivate("internet explorer")
[System.Windows.Forms.SendKeys]::SendWait("{F6}");
[System.Windows.Forms.SendKeys]::SendWait("{TAB}");
[System.Windows.Forms.SendKeys]::SendWait(" ");
Start-Sleep 1
[System.Windows.Forms.SendKeys]::SendWait("$file");
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}");
# Get Internet Explorer Focus
Start-Sleep -s 1
[Microsoft.VisualBasic.Interaction]::AppActivate("internet explorer")
[System.Windows.Forms.SendKeys]::SendWait("^{F4}");
Thank you all for you time :)
I Think the Your problem is because you are using Double Quotes which powershell threat it like variable and Try to expand it, so try to change it to Single quote, from:
$link = $ie.Document.getElementsByTagName('a') | Where-Object {$_.onclick -eq "$find('ctl32').exportReport('EXCELOPENXML');"}
to:
$link = $ie.Document.getElementsByTagName('a') | Where-Object {$_.onclick -eq '$find('ctl32').exportReport('EXCELOPENXML');'}
Also, you can change the -eq to -match and take just a portion of it like:
$_.onclick -match '$find('ctl32').exportReport'
For more information see: About Quoting Rules

Powershell document.getElementbyID - doesn't pull right information

I got a little powershell problem troubling me for quite a while now.
Im trying to get information from a RSS site. I download the XML and go through it. I just want certain stuff from it. That for I use .document.getElementByID().outerText
The problem is that somehow it pulls the first information correctly but after that everything fails he just picks random text or just keeps the one text from the beginning without refreshing the variable. Also Powershell ISE says "You cannot call a method on a null-valued expression." randomly.
Here is my code:
<#
AUTHOR: KOCH,MICHAEL [GRE-IT]
DESCRIPTION: RSS READER
DATE: 28.06.17
DATE LAST WRITTEN: 19.07.17
LAST CHANGE:
#>
$debug = 1 #DEBUG
$receiver="A#MailAdress.com"
$sender="A#MailAdress.com"
$smtp="A.SMTP.SERVER"
$encoding = [System.Text.Encoding]::UTF8
$path_config = "C:\RSS\Zoll\config.txt"
$output = "C:\RSS\Zoll\meldung.html"
$output_edit_path = "C:\RSS\Zoll\meldung_edit.html"
$nmbr=0
$count=0
Invoke-WebRequest -Uri 'http://www.zoll.de/SiteGlobals/Functions/RSSFeed/DE/RSSNewsfeed/RSSZollImFokus.xml' -OutFile C:\RSS\Zoll\meldungen.xml -ErrorAction Stop
[xml]$content = Get-Content C:\RSS\Zoll\meldungen.xml
$feed = $content.rss.channel
$tag = #()
if($lines=Get-Content $path_config | Measure-Object -Line) #gets the number of lines
{
while($count -ne $lines.Lines)
{
if($entrys=(Get-Content $path_config)[$nmbr]) #gets the entrys from config.txt and goes through line by line
{
$entrys >> $tag[$nmbr]
if ($debug -eq 1)
{
Write-Output "$tag[$nmbr]"
Write-Output "$entrys"
Write-Output "$count"
}
}
$count++
$nmbr++ #jumps into the next line
}
}
$ie = New-Object -ComObject "InternetExplorer.Application"
Foreach($msg in $feed.Item)
{
$link = ($msg.link)
$subject = ($msg.title)
$ie.navigate("$link")
#$return = Invoke-WebRequest -Uri $link -OutFile "C:\RSS\Zoll\link.html"
$return = $ie.document
$innertext = $return.documentElement.document.getElementById("main").outerText
$body = $innertext#.Replace('Ä', 'Ä')
<#
$body = $innertext.Replace('ä', 'ä')
$body = $innertext.Replace('Ö', 'Ö')
$body = $innertext.Replace('ö', 'ö')
$body = $innertext.Replace('Ü', 'Ü')
$body = $innertext.Replace('ü', 'ü')
$body = $innertext.Replace('ß', 'ß')
#>
if ($debug -eq 1)
{
Write-Output "Subject $subject"
Write-Output "Tag $tag"
Write-Output "Link $link"
Write-Output $body
#exit
}
if($link -match "Zigaretten") #searchs in the <link> for the string "Zigaretten"
{
if($subject -match $tag) #searches for the specified tag in config.txt !!! only one argument per line !!!
{
if($mail = Send-MailMessage -From "$sender" -To "$receiver" -Subject "Zoll Meldung: $subject" -Body "$body" -SmtpServer "$smtp" -BodyAsHtml -encoding $encoding)
{
if($debug -eq 1)
{
Write-Output "$tag"
Write-Output "Send. Tag = $tag"
}
Write-Output "Send."
}
}
}
else
{
Write-Host "Empty."
}
}
$ie.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($ie)
Remove-Variable ie
Added a wait if busy loop to make sure IE loads the full html document. Thats the solution of the problem ! :)
<#
AUTHOR: KOCH,MICHAEL [GRE-IT]
DESCRIPTION: RSS READER
DATE: 28.06.17
DATE LAST WRITTEN: 20.07.17
LAST CHANGE: ADDED WAIT IF BUSY !
#>
$debug = 0 #DEBUG
$receiver="A#MailAdress.de"
$sender="A#MailAdress.de"
$smtp="A.SMTP.SERVER"
$encoding = [System.Text.Encoding]::UTF8
$path_config = "C:\RSS\Zoll\config.txt"
$output = "C:\RSS\Zoll\meldung.html"
$output_edit_path = "C:\RSS\Zoll\meldung_edit.html"
$nmbr=0
$count=0
Invoke-WebRequest -Uri 'http://www.zoll.de/SiteGlobals/Functions/RSSFeed/DE/RSSNewsfeed/RSSZollImFokus.xml' -OutFile C:\RSS\Zoll\meldungen.xml -ErrorAction Stop
[xml]$content = Get-Content C:\RSS\Zoll\meldungen.xml
$feed = $content.rss.channel
$tag = #()
if($lines=Get-Content $path_config | Measure-Object -Line) #gets the number of lines
{
while($count -ne $lines.Lines)
{
if($entrys=(Get-Content $path_config)[$nmbr]) #gets the entrys from config.txt and goes through line by line
{
$entrys >> $tag[$nmbr]
if ($debug -eq 1)
{
Write-Output "$tag[$nmbr]"
Write-Output "$entrys"
Write-Output "$count"
}
}
$count++
$nmbr++ #jumps into the next line
}
}
$ie = New-Object -ComObject InternetExplorer.Application #creates new ComObject IE
Foreach($msg in $feed.Item)
{
$link = ($msg.link)
$subject = ($msg.title)
if ($debug -eq 1)
{
$ie.visible = $true
}
$ie.navigate("$link") #navigate with Internetexplorer to the website
while ($ie.busy -and $ie.ReadyState -ne 4){ sleep -Milliseconds 200 } # if getting the website from IE.navigate is still .busy wait 200 milliseconds
$return = $ie.document
$innertext = $return.documentelement.document.IHTMLDocument3_getElementById("main").outerText #gets the outer text from the div with the element ID "main"
while ($innertext.busy -and $innertext.ReadyState -ne 4){ sleep -Milliseconds 200 } # if getting Text is .busy wait 200 milliseconds
$body = $innertext
if ($debug -eq 1)
{
Write-Output "Subject $subject"
Write-Output "Tag $tag"
Write-Output "Link $link"
Write-Output "INNERTEXT $innertext"
Write-Output "BODY $body"
#exit
}
if($link -match "Zigaretten") #searchs in the <link> for the string "Zigaretten"
{
if($subject -match $tag) #searches for the specified tag in config.txt !!! only one argument per line !!!
{
if($mail = Send-MailMessage -From "$sender" -To "$receiver" -Subject "Zoll Meldung: $subject" -Body "$body" -SmtpServer "$smtp" -BodyAsHtml -encoding $encoding)
{
Write-Output "Send."
}
}
}
else
{
Write-Host "Empty."
}
}
$ie.Quit() #----|
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($ie) # ---> Quits the Internet Explorer Session otherwise there are to many IE.exe open and no more ID's left
Remove-Variable ie

Upload files with FTP using PowerShell

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"