Exception Handling in Powershell 1.0 - powershell

I am using the following code to upload a file using PowerShell 1.0. How can I tell if the upload completed successfully or if there was an error? I need to delete the file if the upload was successful.
What I have tried:
1. the trap clause. Cant seem to get this one to work.
2. Checking the return value of $webclient.UploadFile -- this seems to always be an empty string, success or not
$File = "D:\Dev\somefilename.zip"
$ftp = "ftp://username:password#example.com/pub/incoming/somefilename.zip"
"ftp url: $ftp"
$webclient = New-Object System.Net.WebClient
$uri = New-Object System.Uri($ftp)
"Uploading $File..."
$webclient.UploadFile($uri, $File)

Drop the trap down into a new scope so that you trap on the exception thrown by Upload e.g.:
$succeeded = $true;
& {
trap { $script:succeeded = $false; continue }
$webclient.UploadFile($uri, $File)
}
if ($succeeded) { 'Yay!' } else { 'Doh!' }
You could also try to catch a specific exception like so:
trap [System.Net.WebException] { ... }

The UploadFile method is synchronous. If it completes without throwing an exception, you have had success. You should get a trappable WebException if it fails.
http://msdn.microsoft.com/en-us/library/36s52zhs.aspx
I'll leave out details about error trapping, as it appears you are familiar with it already.

Related

Uploading a File via FTP using PowerShell

I am writing a Powershell script that watches a directory, and when a file (or multiple) is uploaded into the directory, it takes those files, copies them to another folder, sends them to an FTP server, and then deletes the file from the original directory.
I am having problems connecting to the FTP server. I am not sure if the problem is the way I am configuring the Web Client, or if the problem is that the ftp URI has spaces in it and I am not escaping them properly.
Here is the code:
$source = "c:/testFtp"
$ftpdestination = "ftp://username:password#ftp.ftpsite.com/folder with space/folder with space"
$webclient = New-Object -TypeName System.Net.WebClient
$files = Get-ChildItem $source
foreach ($file in $files)
{
Write-Host "Uploading $file"
try {
$ftp = "$ftpdestination/$file"
$uri = New-Object -TypeName System.Uri -ArgumentList $ftp
$webclient.UploadFile($uri, $source/$file)
} catch {
Add-content "$logs" -value "There was an error uploading to ftp"
}
}
$webclient.Dispose()
I have tried escaping the folder spaces multiple ways, so I am beginning to think that is not the problem and that I am not configuring the web client properly.
It is also not catching errors very often, so I don't believe it throws an error when the webclient has failure on the upload. Any help is appreciated!
ANSWER:
It turns out the WebClient was connecting properly, but SSL was blocking the files from being sent. I found this out by using C# to compile and run the script, and it gave me better error handling, as I am new to Powershell scripts and cannot seem to get good error handling.
After researching, I could not find a way to enable SSL with WebClient, so I switched over to FtpWebRequest. Here is the successful code (This try catch block does not seem to log errors as I would like, but the tool will successfully send files to the ftp server now:
try {
$ftp = [System.Net.FtpWebRequest]::Create("$ftpsite/$filename")
$ftp = [System.Net.FtpWebRequest]$ftp
$ftp.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$ftp.Credentials = new-object System.Net.NetworkCredential("$username","$password")
$ftp.UseBinary = $true
$ftp.UsePassive = $true
$ftp.EnableSSL = $true #<-----------------This was the line that made this work
$ftp.KeepAlive = $false
# read in the file to upload as a byte array
$content = [System.IO.File]::ReadAllBytes("$source/$file")
$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()
Write-Host "Successfully uploaded: $source/$file"
Copy-Item "$source/$file" -Destination "$copydestination"
Remove-Item "$source/$file"
$logline = "$(Get-Date), Added File: $file to $copydestination"
Add-content "$logs" -value $logline
} catch {
$res = $ftp.GetResponse()
Add-content "$logs" -value "There was an error: $res.StatusCode"
Write-Error -Message "There was an error." -ErrorAction Stop
}

Need help for ftp files status

i have been looking for a way to know the status of my ftp files, there are few log files are being uploading on my ftp server after every 15mints, but few times it fails to upload i just want an alert when ever a file fails to upload.
following code has been tired
function update {
$ftprequest = [System.Net.FtpWebRequest]::Create("ftp://ftpsite.com/Script_Apps/install_firefox.exe")
$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::GetDateTimestamp
$response = $ftprequest.GetResponse().StatusDescription
$tokens = $response.Split(" ")
$code = $tokens[0]
$localfile = (Get-Item "$dir\Apps\install_firefox.exe").LastWriteTimeUtc
if ($tokens -gt $localfile) {
write-host "Updating Firefox Installer..."
$File = "$dir\Apps\install_firefox.exe"
$ftp = "ftp://ftpsite.com/Script_Apps/install_firefox.exe"
$webclient = New-Object System.Net.WebClient
$uri = New-Object System.Uri($ftp)
$webclient.DownloadFile($uri, $File)
"Updated Firefox" >> $global:logfile
mainmenu
}
else {
Write-Host "Local Copy is Newer."
sleep 3
mainmenu
}
}
I'm going to assume this line deals with downloading/uploading files for your FTP site.
$ftp = "ftp://ftpsite.com/Script_Apps/install_firefox.exe"
$webclient = New-Object System.Net.WebClient
$uri = New-Object System.Uri($ftp)
$webclient.DownloadFile($uri, $File)
If so, then we can lookout for errors and do something when one occurs with try{}catch{} blocks.
You can put any code inside a try block, and when it hits a terminating error it will capture the error as $Error[0] which we can reference in a catch block.
try{
$ftp = "ftp://ftpsite.com/Script_Apps/install_firefox.exe"
$webclient = New-Object System.Net.WebClient
$uri = New-Object System.Uri($ftp)
$webclient.DownloadFile($uri, $File)
}
catch{
#Write out an error to the screen
"failed to download file $($ftp). `n--Ran into error $($Error[0].Exception.Message)"
#Todo - send a notification
}
This will give a result like this, when it fails to download.
Failed to download file ftp://ftpsite.com/Script_Apps/install_firefox.exe.
---Ran into error Exception calling "DownloadFile" with "2" argument(s):
"Unable to connect to the remote server"
Then, you can determine the best way to send a notification to suit your needs. There's way's to send e-mail from PowerShell, or Tweets, or even using a Push message to the Pushbullet app on your phone!

Trapping Powershell Error in a function

I'm having a bit of trouble preventing a certain error message from bubbling up from a function to my main routine's 'Catch'. I would like to have my function react to a particular error, then do something, and continue processing as usual without alerting my main routine that there was an error. Currently, if the file this script is trying to read is in use (being written to), it will write a System.IO.IOException error to my log. But sometimes I expect this error to occur and it isn't an issue and I don't want to fill my log with these type of errors. I would expect from the code below that the checkFileLock function would catch the error, return 0 to the findErrorInFile function, and no error would be caught to my error log.
Function findErrorsInFile{
param(
[string]$dir,
[string]$file,
[String]$errorCode
)
If((Get-Item $($dir + "`\" + $file)) -is [System.IO.DirectoryInfo]){ #we dont want to look at directories, only files
}Else{
If($(checkFileLock -filePath $($dir + "`\" + $file))){
$reader = New-Object System.IO.StreamReader($($dir + "`\" + $file))
$content = $reader.ReadToEnd()
$results = $content | select-string -Pattern $errorCode #if there is no regex match (no matching error code found), then the string $results will be == $null
If($results){
Return 1 #we found the error in the file
}Else{
Return 0 #no error found in the file
}
}Else{
Return 0 #The file was being written to, we will skip it and assume no error. This is rare.
}
}
}
Function checkFileLock{
param(
[String]$filePath
)
try{
$openFile = New-Object System.IO.FileInfo $filePath
$testStream = $openFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) #try to open a filestream
If($testStream){ #If the filestream opens, then it isn't locked
$testStream.Close() #close the filestream
}
return $false #File is not locked
}
catch{
return $true #File is locked
}
}
#### START MAIN PROCESS ####
Try{
if($(findErrorsInFile -dir 'somepath' -file 'somefilename' -errorcode 'abc')){
write-host "found something"
}else{
write-host "didn't find anything"
}
}
Catch{
$_.Exception.ToString() >> mylogfile.txt
}
try/catch blocks only catch terminating errors. Is your code generating a terminating or non-terminating error?
ArcSet has highlighted essentially what is required: force a non-terminating error to be a terminating error. I suspect it needs to be added to this line, if allowed:
$testStream = $openFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) -ErrorAction Stop
Edit - solution as -ErrorAction not an accepted parameter
I tried the above and it's not allowed. An alternative is to set the $ErrorActionPreference to Stop. This will affect all errors, so recommend reverting. Someone with more experience using System.IO.FileInfo objects may have a more elegant solution.
try{
$currentErrorSetting = $ErrorActionPreference
$ErrorActionPreference = "Stop"
$openFile = New-Object System.IO.FileInfo $filePath
$testStream = $openFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None) #try to open a filestream
If($testStream){ #If the filestream opens, then it isn't locked
$testStream.Close() #close the filestream
}
$ErrorActionPreference = $currentErrorSetting
return $false #File is not locked
}
catch{
$ErrorActionPreference = $currentErrorSetting
return $true #File is locked
}
Use to force a catch
-ErrorAction Stop
use to suppress a error
[Command with error] | out-null

How do you get the response from a 404 page requested from powershell

I have to call an API exposed by TeamCity that will tell me whether a user exists. The API url is this: http://myteamcityserver.com:8080/httpAuth/app/rest/users/monkey
When called from the browser (or fiddler), I get the following back:
Error has occurred during request processing (Not Found).
Error: jetbrains.buildServer.server.rest.errors.NotFoundException: No user can be found by username 'monkey'.
Could not find the entity requested. Check the reference is correct and the user has permissions to access the entity.
I have to call the API using powershell. When I do it I get an exception and I don't see the text above. This is the powershell I use:
try{
$client = New-Object System.Net.WebClient
$client.Credentials = New-Object System.Net.NetworkCredential $TeamCityAgentUserName, $TeamCityAgentPassword
$teamCityUser = $client.DownloadString($url)
return $teamCityUser
}
catch
{
$exceptionDetails = $_.Exception
Write-Host "$exceptionDetails" -foregroundcolor "red"
}
The exception:
System.Management.Automation.MethodInvocationException: Exception calling "DownloadString" with "1" argument(s): "The remote server returned an error: (404) Not Found." ---> System.Net.WebException: The remote server returned an error: (404) Not Found.
at System.Net.WebClient.DownloadDataInternal(Uri address, WebRequest& request)
at System.Net.WebClient.DownloadString(Uri address)
at CallSite.Target(Closure , CallSite , Object , Object )
--- End of inner exception stack trace ---
at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exception)
at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
I need to be able to check that the page is returned contains the text described above. This way I know whether I should create a new user automatically or not.
I could just check for 404, but my fear is that if the API is changed and the call really returns a 404, then I would be none the wiser.
Change your catch clause to catch the more specific WebException, then you can use the Response property on it to get the status code:
{
#...
}
catch [System.Net.WebException]
{
$statusCode = [int]$_.Exception.Response.StatusCode
$html = $_.Exception.Response.StatusDescription
}
BrokenGlass gave the answer, but this might help:
try
{
$URI='http://8bit-museum.de/notfound.htm'
$HTTP_Request = [System.Net.WebRequest]::Create($URI)
"check: $URI"
$HTTP_Response = $HTTP_Request.GetResponse()
# We then get the HTTP code as an integer.
$HTTP_Status = [int]$HTTP_Response.StatusCode
}
catch [System.Net.WebException]
{
$statusCode = [int]$_.Exception.Response.StatusCode
$statusCode
$html = $_.Exception.Response.StatusDescription
$html
}
$HTTP_Response.Close()
Response:
check: http://8bit-museum.de/notfound.htm
404
Not Found
another approach:
$URI='http://8bit-museum.de/notfound.htm'
try {
$HttpWebResponse = $null;
$HttpWebRequest = [System.Net.HttpWebRequest]::Create("$URI");
$HttpWebResponse = $HttpWebRequest.GetResponse();
if ($HttpWebResponse) {
Write-Host -Object $HttpWebResponse.StatusCode.value__;
Write-Host -Object $HttpWebResponse.GetResponseHeader("X-Detailed-Error");
}
}
catch {
$ErrorMessage = $Error[0].Exception.ErrorRecord.Exception.Message;
$Matched = ($ErrorMessage -match '[0-9]{3}')
if ($Matched) {
Write-Host -Object ('HTTP status code was {0} ({1})' -f $HttpStatusCode, $matches.0);
}
else {
Write-Host -Object $ErrorMessage;
}
$HttpWebResponse = $Error[0].Exception.InnerException.Response;
$HttpWebResponse.GetResponseHeader("X-Detailed-Error");
}
if i understand the question then $ErrorMessage = $Error[0].Exception.ErrorRecord.Exception.Message contains the errormessage you are looking for.
(source: Error Handling in System.Net.HttpWebRequest::GetResponse() )
Another simple example, hope this helps:
BEGIN
{
# set an object to store results
$queries = New-Object System.Collections.ArrayList
Function Test-Website($Site)
{
try
{
# check the Site param passed in
$request = Invoke-WebRequest -Uri $Site
}
catch [System.Net.WebException] # web exception
{
# if a 404
if([int]$_.Exception.Response.StatusCode -eq 404)
{
$request = [PSCustomObject]#{Site=$site;ReturnCode=[int]$_.Exception.Response.StatusCode}
}
else
{
# set a variable to set a value available to automate with later
$request = [PSCustomObject]#{Site=$site;ReturnCode='another_thing'}
}
}
catch
{
# available to automate with later
$request = [PSCustomObject]#{Site=$site;ReturnCode='request_failure'}
}
# if successful as an invocation and has
# a StatusCode property
if($request.StatusCode)
{
$siteURI = $Site
$response = $request.StatusCode
}
else
{
$response = $request.ReturnCode
}
# return the data
return [PSCustomObject]#{Site=$Site;Response=$response}
}
}
PROCESS
{
# test all the things
$nullTest = Test-Website -Site 'http://www.Idontexist.meh'
$nonNullTest = Test-Website -Site 'https://www.stackoverflow.com'
$404Test = Test-Website -Site 'https://www.stackoverflow.com/thispagedoesnotexist'
# add all the things to results
$queries.Add($nullTest) | Out-Null
$queries.Add($nonNullTest) | Out-Null
$queries.Add($404Test) | Out-Null
# show the info
$queries | Format-Table
}
END{}
Output:
Site Response
---- --------
http://www.Idontexist.meh another_thing
https://www.stackoverflow.com 200
https://www.stackoverflow.com/thispagedoesnotexist 404
You could try using the Internet Explorer COM object instead. It allows you to check the browser return codes and navigate the HTML object model.
Note: I've found that you need to run this from an elevated PowerShell prompt in order to maintain the COM object definition.
$url = "http://myteamcityserver.com:8080/httpAuth/app/rest/users/monkey"
$ie = New-Object -ComObject InternetExplorer.Application
Add this to See the browser
$ie.visibility = $true
Navigate to the site
$ie.navigate($url)
This will pause the script until the page fully loads
do { start-sleep -Milliseconds 250 } until ($ie.ReadyState -eq 4)
Then verify your URL to make sure it's not an error page
if ($ie.document.url -ne $url) {
Write-Host "Site Failed to Load" -ForegroundColor "RED"
} else {
[Retrieve and Return Data]
}
You can navigate HTML Object model via $ie.document. Using Get-Member and HTML methods such as GetElementsByTagName() or GetElementById().
If credentials are an issue, build this into a function then use Invoke-Command with the -Credentials parameter to define your logon information.

Test app link with powershell

I am using powershell to do some monitoring and I want to check if an application's jnlp
exists on a website and is available for downloading.
I have the link to the .jnlp and so far I'm downloading the file with .navigate().
$ie = new-object -com "InternetExplorer.Application"
Try {
$ie.navigate("http://bla.com/testApp.jnlp")
} Catch {
#$_
$ErrorMessage = $_.Exception.Message
}
I tried to catch an exception by giving invalid filename but it doesn't work.
Also I thought of downloading the app and try to delete the file afterwards so as to
check that it actually exists but it would be too slow since I have many jnlps to check.
Is there another more simple and elegant way to do so? I want to avoid the downloading of
each file I want to test.
How about using WebClient class from .Net? Getting data is simple enough. Like so,
$webclient = new-object System.Net.WebClient
try {
# Download data as string and store the result into $data
$data = $webclient.DownloadString("http://www.google.com/")
} catch [Net.WebException] {
# A 404 or some other error occured, process the exception here
$ex = $_
$ex.Exception
}
If you're using PowerShell 3.0 or higher, you can use Invoke-WebRequest to see if a page exists by issuing an HTTP HEAD request and checking the status code.
$Result = Invoke-WebRequest -uri `http://bla.com/testApp.jnlp` -method head
if ($Result.StatusCode -ne 200){
# Something other than "OK" was returned.
}
This is doable with System.Net.WebClient as well but it's a bit more effort.