I am trying to append data to a CSV file on an FTP server using PowerShell.
Currently my script works fine, however it is overwriting the data.
Here is the main bit of my code:
$webclient = New-Object System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($user,$pass)
$UserInfo = [pscustomobject]#{
UserName = $env:UserName
ComputerName = $env:computername
}
$contents =
(($UserInfo | ConvertTo-Csv -NoTypeInformation) -join [Environment]::NewLine) +
[Environment]::NewLine
$webclient.UploadString(($ftp + "/Installed.csv"), $contents)
Ideally, I'd like to do this without any native plugins or libraries. The only other thing I can think of is using something like get-content to get the current data from the file, and then the new data would be
getcontent myfile.csv + the new data from $UserInfo
Basically I am installing a VPN through group policy and would like to log who successfully installed the client. We don't have any software distribution platforms unfortunately so we're trying to use GP and FTP to perform this task.
Any suggestions are appreciated. Thank you in advance.
It's not possible with WebClient. But it can be done with FtpWebRequest and WebRequestMethods.Ftp.AppendFile.
$request = [System.Net.WebRequest]::Create("ftp://example.com/remote/path/file.txt")
$request.Credentials = New-Object System.Net.NetworkCredential("username", "password")
$request.Method = [System.Net.WebRequestMethods+Ftp]::AppendFile
$ftpStream = $request.GetRequestStream()
$writer = New-Object System.IO.StreamWriter($ftpStream)
$writer.Write($contents)
$writer.Close()
$ftpStream.Dispose()
Related
For reducing traffic on our VPN routes, I need to download the windows updates from an external server while reporting to our internal server.
So I'm doing the following:
Create an UpdateSession and Search for Updates, storing them in $SearchResult.
Then I download the Updates from an external Server and then I want to pass them into the Windows Update Api via IUpdate2.CopyToCache(IStringCollection)
Everything is just fine, except passing the StringCollection into the Method CopyToCache and it just ends up with an 'Specified cast is not valid.'-Error.
This is my code:
Thank's for help! eldo-ob
$UpdateSession = New-Object -ComObject Microsoft.Update.Session
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
$UpdateCollection = New-Object -Com Microsoft.Update.UpdateColl
$SearchResult = $UpdateSearcher.Search("IsInstalled=0 and Type='Software'")
$AvailibleUpdates = [int16]$SearchResult.Updates.Count
$AvailibleUpdates
$WebClient = New-Object System.Net.WebClient
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
#($SearchResult.Updates.Item(0).BundledUpdates) | Foreach {
$_.DownloadContents | Foreach {
$FileName = $_.DownloadUrl.Split("/")[-1]
$downloadFrom = $_.DownloadUrl.Replace("http://contoso-intern.com","https://contoso-extern.com")
$WebClient.DownloadFile($downloadFrom,("C:\temp\WSUS\{0}" -f $FileName))
Write-Host "File Downloaded" -ForegroundColor Green
}
$StringCollection = New-Object System.Collections.Specialized.StringCollection
$StringCollection.Add(("C:\temp\WSUS\{0}" -f $FileName))
$_.CopyToCache($StringCollection)
}
Error Msg:
Specified cast is not valid.
At line:24 char:19
+ $_.CopyToCache($StringCollection)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], InvalidCastException
+ FullyQualifiedErrorId : System.InvalidCastException
Update / Solution:
# create UpdateSession
$UpdateSession = New-Object -ComObject Microsoft.Update.Session
# create UpdateSearcher
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
# search for updates & count updates
$SearchResult = $UpdateSearcher.Search("IsInstalled=0 and Type='Software'")
$AvailibleUpdates = [int16]$SearchResult.Updates.Count
# create an WebClient instance for downloading Updates from alternative source
$WebClient = New-Object System.Net.WebClient
# fix some tls issues (not for everyone neccessary)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# iterate the updates in searchresult
$SearchResult.Updates | ForEach-Object {
# iterate bundledupdates
$_.BundledUpdates | ForEach-Object {
# create COM stringcollection
$StringCollection = New-Object -ComObject "Microsoft.Update.StringColl.1"
# iterate downloadcontents
$_.DownloadContents | ForEach-Object {
# get the filename from url
$FileName = $_.DownloadUrl.Split("/")[-1]
# create external downloadlink
$downloadFrom = $_.DownloadUrl.Replace("http://contoso-intern.com","https://contoso-extern.com/wsusreplica")
# download update with webclient
$WebClient.DownloadFile($downloadFrom,("C:\temp\WSUS\{0}" -f $FileName))
# adding downloaded filepath to stringcollection
$StringCollection.Add(("C:\temp\WSUS\{0}" -f $FileName))
}
# copy downloaded file to cache (load into wuapi)
$_.CopyToCache($StringCollection)
}
}
# create installer
$Installer = $UpdateSession.CreateUpdateInstaller()
# set the updates
$Installer.Updates = $SearchResult.Updates
# and install
$Installer.Install()
The updates where successfuly installed without using the UpdateDownloader from a self choosen location. So now I'm able to report and search updates through the vpn-tunnel and download the updates from an external source, where we can route the traffic beside the vpn tunnel.
You are using a .NET object. After searching the registry for the interface, then looking up the TypeLib for the interface, it pointed to the wuapi.dll. I then searched for COM objects that use wuapi.dll as their InprocServer32. I found "Microsoft.Update.StringColl.1". It has an Add() method, so it should work the same in the code as your other method (I think). So, replace where you initialize the $StringCollection with this:
$StringCollection = new-object -ComObject "Microsoft.Update.StringColl.1"
The problem:
A client requires that we upload extracted data from our system to their box.com platform, rather than our normal SFTP utility. I have box.com credentials, and am aware they require FTPS not SFTP, and require passive mode. I've cribbed a fragment from ThomasMaurer's Powershell FTP Upload and Download script. Powershell version on my server is 4.0
Code fragment is:
#config
$Username = "username#host.com"
$Password = "redactedpassword"
$LocalFile = "C:\path\to\my\file.csv"
$RemoteFile = "ftp://ftp.box.com:990/file.csv"
#Create FTPWebRequest
$FTPRequest = [System.Net.FtpWebRequest]::Create($RemoteFile)
$FTPRequest = [System.Net.FtpWebRequest]$FTPRequest
$FTPRequest.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$FTPRequest.Credentials = New-Object System.Net.NetworkCredential($Username, $Password)
$FTPRequest.UseBinary = $true
$FTPRequest.UsePassive = $true
#read file for upload
$FileContent = gc -en byte $LocalFile
$FTPRequest.ContentLength = $FileContent.Length
#get stream request by bytes
$run = $FTPRequest.GetRequestStream()
$run.Write($FileContent,0,$FileContent.Length)
#cleanup
$run.Close()
$run.Dispose()
The error(s):
Exception calling "GetRequestStream" with "0" argument(s): "System error." At C:\path\to\my\powershellscript.ps1:28 char:1
+ $Run = $FTPRequest.GetRequestStream()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: () [], MethodInvocationException
+ FullyQualifiedErrorId: WebException
I also get downstream errors on calling the $FileContent.Length property and $run.close and $run.dispose().
Has anyone successfully automated to box (specifically) or to a passive implicit-ssl using only PowerShell 4.0 commands, and do you have a solid pattern I could reuse? Many thanks
I'm uploading files with a derived version of System.Net.WebClient, which supports FTP over TLS. This can easily be achieved by embedding C# code in PowerShell:
$typeDefinition = #"
using System;
using System.Net;
public class FtpClient : WebClient
{
protected override WebRequest GetWebRequest(Uri address)
{
FtpWebRequest ftpWebRequest = base.GetWebRequest(address) as FtpWebRequest;
ftpWebRequest.EnableSsl = true;
return ftpWebRequest;
}
}
"#
Add-Type -TypeDefinition $typeDefinition
$ftpClient = New-Object FtpClient
$ftpClient.UploadFile("ftp://your-ftp-server/yourfile.name", "STOR", "C:\YourLocalFile.name")
The answer by #h0r41i0 solves the problem by using WebClient. But as the WebClient internally uses (Ftp)WebRequest, it cannot be the solution on its own.
I'll assume that the "System error" occurs because either OP is trying to connect to a secure port (990) with an insecure connection.
Or because the file is too large and the OP code tries to read it whole to memory:
$FileContent = gc -en byte $LocalFile
In either case, there's no reason to give up on FtpWebRequest. Just use a secure connection (FtpWebRequest.EnableSsl). And an efficient way to feed the data from the file to the FTP stream, for example 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
$request.EnableSsl = $True
$fileStream = [System.IO.File]::OpenRead("C:\local\path\file.zip")
$ftpStream = $request.GetRequestStream()
$fileStream.CopyTo($ftpStream)
$ftpStream.Dispose()
$fileStream.Dispose()
For other options, see Upload files with FTP using PowerShell.
Though note that .NET framework does not support implicit TLS (what is typical use of 990). Only explicit TLS. But support for the explicit TLS is more common ayway. See Does .NET FtpWebRequest Support both Implicit (FTPS) and explicit (FTPES)?
Probably too late to be useful to original questioner, but I found this other answer did the trick for me: Cyril Gupta's answer to Upload files with ftp using powershell
Here is my revised edition, including URL encoding (since the box.com usernames are email addresses which include the "at sign"):
## https://stackoverflow.com/a/2485696/537243
## User comment complains can't turn off passive mode,
## but that is exactly what we want here!
[Reflection.Assembly]::LoadWithPartialName("System.Web") | Out-Null
# config
$Username = "foo#bar.com"
$Password = "s3cr3tpAssw0rd"
$Servername = "ftp.box.com"
# This is what we need URI it to look like:
# ftp://foo%40bar.com:s3cr3tpAssw0rd#ftp.box.com/
$baseURI = "ftp://$([System.Web.HttpUtility]::UrlEncode($Username)):$([System.Web.HttpUtility]::UrlEncode($Password))#$($Servername)"
$LocalFile = "C:\tmp\to_upload\data.csv"
$RemoteFile = "date.csv"
$ftpURI = "$($baseURI)/$($RemoteFile)"
Write-output "ftp uri: $($ftpURI)";
$webclient = New-Object -TypeName System.Net.WebClient;
$ftpURI = New-Object -TypeName System.Uri -ArgumentList $ftpURI; #"convert" it
$webclient.UploadFile($ftpURI, $LocalFile);
Write-output "Uploaded $($LocalFile) ... "; # of course since we didn't use try/catch or other error dectection this is a bit presuming.
Also should note this example uses plain FTP, not FTPS or SFTP.
I am trying to create a PowerShell script that will send an email if a service goes into a stopped state. I would like to be able to read the email configuration from another file.
Email configuration file:
.\emailconfig.conf
$emailSmtpServer = "smtp.company.com"
$emailSmtpServerPort = "587"
$emailSmtpUser = "usera"
$emailSmtpPass = "passwordb"
$emailFrom = "userA#company.com"
$emailTo = "userB#company.com"
$emailcc= "userC#company.com"
And this is what I have so far in the PowerShell script:
.\emailservicecheck.ps1
$A = Get-Service "Service B"
if ($A.Status -eq "Stopped") {
Get-Content emailconfig.conf | Out-String
$emailMessage = New-Object System.Net.Mail.MailMessage($emailFrom, $emailTo)
$emailMessage.Cc.Add($emailcc)
$emailMessage.Subject = "subject"
#$emailMessage.IsBodyHtml = $true # true or false depends
$emailMessage.Body = Get-Service "Service B" | Out-String
$SMTPClient = New-Object System.Net.Mail.SmtpClient($emailSmtpServer, $emailSmtpServerPort)
$SMTPClient.EnableSsl = $False
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential($emailSmtpUser, $emailSmtpPass);
$SMTPClient.Send($emailMessage)
}
The script works if I enter the text from the email config file into the script but I cannot seem to be able to read in the data from the file on the fly and get the script to work. It errors out and says that my variables are empty.
What you are searching for, (I think) are .psd1 files. I personally prefer them (along with JSON) over the other configuration formats. The link I'm referring to also describes other well-known formats and how to use them in PowerShell.
In short, module manifests work as follows:
configuration.psd1
#{
SmtpServer = "";
MailFrom = "";
Auth = #{
User = "";
Pass = "";
};
}
Script.ps1
$mailConfig = Import-LocalizedData -BaseDirectory C:\ -FileName configuration.psd1
$emailMessage = New-Object System.Net.Mail.MailMessage( $$mailConfig.mailFrom , $mailConfig.mailTo )
As Mark already pointed out, Get-Content emailconfig.conf | Out-String will just output the content of the file, it won't define the variables in your code. For that you'd need to dot-source the file, which requires a file with the extension ".ps1".
If you want to stick with a simple config file format I'd recommend changing the file to something like this:
emailSmtpServer = smtp.company.com
emailSmtpServerPort = 587
emailSmtpUser = usera
emailSmtpPass = passwordb
emailFrom = userA#company.com
emailTo = userB#company.com
emailcc = userC#company.com
And importing it into a hashtable via ConvertFrom-StringData:
$cfg = Get-Content emailconfig.conf | Out-String | ConvertFrom-StringData
The data in the hashtable can be accessed via dot-notation ($cfg.emailFrom) as well as via the index operator ($cfg['emailFrom']), so your code would have to look somewhat like this:
$msg = New-Object Net.Mail.MailMessage($cfg.emailFrom, $cfg.emailTo)
$msg.Cc.Add($cfg.emailcc)
$msg.Subject = 'subject'
$msg.Body = Get-Service 'Service B' | Out-String
$smtp = New-Object Net.Mail.SmtpClient($cfg.emailSmtpServer, $cfg.emailSmtpServerPort)
$smtp.EnableSsl = $false
$smtp.Credentials = New-Object Net.NetworkCredential($cfg.emailSmtpUser, $cfg.emailSmtpPass)
$smtp.Send($msg)
It looks like what you're trying to do is include some script from another file. This can be done by dot sourcing, however the file needs to be saved as a .ps1 file, you can't use .conf.
You'd do it as follows (in place of your existing Get-Content) line:
. .\emailconfig.ps1
Assuming the file is kept in the current working directory of the script.
Your script wasn't working because
get-content emailconfig.conf | Out-String
Was returning the contents of that file to the output pipeline, rather than including it in the script and executing it.
I'm not sure i understood correctly what you want.
If you want to use variables from external file, you need to dot source your external script, for example, create a file named variables.ps1 and put in the same folder
In the beginning of the main script use
. .\variables.ps1
If you are after expanding variables that are in external file to ues as an email template please do as following:
$HTMLBody = get-content "yourfilepath" | Foreach-Object {$ExecutionContext.InvokeCommand.ExpandString($_)}
This will expand all variables and put it in the $HTMLBody variable
Then use:
$emailMessage.Body = (ConvertTo-Html -body $HTMLBody)
I want this script to choose the latest file from a folder and then send it via ftp to the server.
I think it is choosing the file late because there is a new file on the FTP after running it. However it crashes constantly showing
uploading .....
uploading .....
uploading .....
$Dir="C:/log1"
$ftp = "ftpftpftp"
$user = "useruseruser"
$pass = "passpasspass"
$latest = Get-ChildItem -Path $Dir | Sort-Object LastAccessTime -Descending | Select-Object -First 1
$webclient = New-Object System.Net.WebClient
$webclient.Credentials = New-Object System.Net.NetworkCredential($user,$pass)
for($latest){
"Uploading $latest..."
$uri = New-Object System.Uri($ftp+$latest.Name)
$webclient.UploadFile($uri, $latest.FullName)
}
I think you are using the wrong code block by accident. Currently you have created an infinite loop as you have no condition to how the for will exit.
A simple example of such a loop would be
for(){"Hello? Is it me you are looking for?"}
It should be structured like this
for (initialization; condition; repeat){code block}
an example would be
for($index =1; $index -lt 6;$index++){$index}
There is no need for that code block at all as long as $Dir is not empty. What you can do for a little error prevention is if($latest){} which will only work if $latest contains a file (in this code structure).
if($latest){
"Uploading $latest..."
$uri = New-Object System.Uri($ftp+$latest.Name)
$webclient.UploadFile($uri, $latest.FullName)
}
Your sample output does not have a file name in it so I suspect your $dir contains no files?
I am trying to download a file from reporting services. The first time I run this code it works fine, but the next time it wont overwrite the first ones downloaded file. I have tried adding Remove-Item $file before I create the WebClient object, but when I do this I get the error The process cannot access the file 'D:\Work\RawMaterialCodes.xls' because it is being used by another process., the process being Powershell itself. I have tried calling $webClient.Dispose() thinking this might release the file, but no luck.
Does anyone have any ideas how I can overwrite the downloaded file and/or remove it before the next download attempt?
$reportServer = "http://localhost/ReportServer_TRITON"
$reportName = "RawMaterialCodes"
$file = "D:\Work\RawMaterialCodes.xls"
$startDate = "2014-01-22"
$endDate = "2014-01-24"
$category = "Cat1"
$destination = ""
$reportUrl = $reportServer + "?/" + $reportName + "&StartDate=" + $startDate + "&EndDate=" + $endDate + "&Category=" + $category + "&Destination=" + $destination + "&rs:Format=Excel"
$webClient = new-object System.Net.WebClient
$webClient.Credentials = New-Object System.Net.NetworkCredential($userName, $password, $domain)
$webClient.DownloadFile($reportUrl, $file)
$mailMessage = new-object System.Net.Mail.MailMessage
$mailMessage.From = $emailFrom
$mailMessage.To.Add($emailTo)
$mailMessage.Subject = $emailSubject
$mailMessage.Body = $emailBody
$attachment = new-object System.Net.Mail.Attachment($file, 'text/plain')
$mailMessage.Attachments.Add($attachment)
$smtpClient = New-Object System.Net.Mail.SmtpClient($smtpServer, 25)
$smtpClient.EnableSsl = $enableSsl
if ($smtpAuthUsername -ne "")
{
$smtpClient.Credentials = New-Object System.Net.NetworkCredential($smtpAuthUsername, $smtpAuthPassword)
}
$smtpClient.Send($mailMessage)
$mailMessage is keeping an open handle to your file. Add
$mailMessage.Dispose()
at the end of the script and you should be able to overwrite.
I don't see anything wrong with the code as you have it written. Disposing the WebClient is unnecessary here with respect to freeing up the file as it doesn't keep a handle to it. The most likely cause of this error is another piece of your code or another program which is opening the file and not properly disposing of it.
I would first assume it was my code at fault and carefully audit any other place I manipulated this file and see if I accidentally left a handle to it open