Send-MailMessage fails on long unc file name with $ character - powershell

Testing this for another script Im working on, but having trouble with a new group of files. See below. $Attach2 causes the error while $Attachment1 works fine
$Attachment1 = "c:\temp\I went to the beach-and now too.txt"
$Attach2 = '\\qa-west\e$\orders\15557__45747457-Re_[EXTERNAL]SomeBoxShipmentTestName-JoeSmithers-FileWest-232264_42211_3674745752.msg'
$smtpServer = "mail.somewhare.com"
try{
Send-MailMessage -From 'nobody#somwhare.com' -To 'testdev#somewhare.com' -Subject 'test sub' -Body 'this is body' -SmtpServer $smtpServer -Attachments $Attachment1
}
catch {
# log the error
$ErrorMessage = $PSItem.Exception.Message
#-- test
Write-Host $ErrorMessage
}
Send-MailMessage fails with exception:
Exception:System.IO.FileNotFoundException: Unable to find the
specified file.\r\n at
System.Management.Automation.MshCommandRuntime.ThrowTerminatingError(ErrorRecorderrorRecord)
ErrorDetails:Cannot perform operation because the wildcard path did not resolve to a file FileName $null

I don't think the problem is the $- I think it's the square brackets. Square brackets are wildcards in PowerShell, and Send-MailMessage doesn't support wildcards in -Attachments
Add a backtick (`) in front of the two square brackets in your filename. There's some more details about this issue available here

Related

Catch error from Send-MailMessage cmdlet when called from batch file

I have a batch file that uses PowerShell's Send-MailMessage cmdlet to send emails out with status updates:
powershell -command " & {Send-MailMessage -SMTPServer smtp.somewhere.com -From 'Sender <sender#email.com>' -To 'Recipient <recipient#email.com>' -Subject 'Subject' -Body 'Body' -Attachments 'D:\Logs\Log.log';}"
if %ERRORLEVEL% neq 0 (
echo ERROR - Something went wrong sending the email
goto Error
)
If the Send-MailMessage command fails (e.g. if attachment file not found, or other error), the %ERRORLEVEL% is not raised above 0.
Other answers suggest adding explicit exit codes into a PowerShell script to return a code, but I can't find anything about how to capture an error if using a built in PS cmdlet like Send-MailMessage. Can it be done, or do I need to wrap Send-MailMessage in another PS script?
Wrap the whole command in a try catch block and throw an explicit error. This will return a non-zero exit code.
powershell -command "Try {Send-MailMessage ... -ErrorAction Stop;} Catch { Exit 1 }"
It's a little harder to read in a one line but:
powershell -command "Try {Send-MailMessage -SMTPServer smtp.somewhere.com -From 'Sender <sender#email.com>' -To 'Recipient <recipient#email.com>' -Subject 'Subject' -Body 'Body' -Attachments 'D:\Logs\Log.log' -ErrorAction Stop;} Catch { Exit 1 }"
if %ERRORLEVEL% neq 0 (
echo ERROR - Something went wrong sending the email
goto Error
)

Identifying Failed File on Expand-Archive

I am using PowerShell 5. I am using Expand-Archive to unzip files containing several thousand files. All works well most of the time but occasionally we get a bad filename in a zip. In my test-case the pdf file within the zip file has a colon ':' in the name, which causes the failure.
This is a scheduled task that should run silently, only reporting back if there is an error. On error, it should complete, but send an error warning. Optimally I would like to know both the archive name, and on which file within the archive it failed so I can add it to the report. Does anyone know how to do this?
I tried the -WarningVariable switch described here: PowerShell Pscx Expand-Archive method failure detection but had no content in the variable.
My current code sends a report, but the report contains only the name of the archive. I see no way to obtain the name of the file within the archive that causes the failure:
$file_list | ForEach-Object {
try{
Expand-Archive -Path $_.FullName -DestinationPath $input_temp -force
}
catch {
Send-MailMessage -from "email#address" -to "email#address" -subject $subject -body "File ($need_to_know_on_which_file_the_extract_failed) failed to extract from $($_.FullName)" -SmtpServer smtp.server
}
}
I also tried:
$shell = new-object -com shell.application
$zip = $shell.NameSpace($zipFile)
$files = $zip.items().count
if ($files -gt 0) {
foreach($item in $zip.items()){
if ($item.name -like "*.pdf") {
Write-Verbose "File: $($item.name)"
try {
$shell.Namespace($destination).copyhere($item,0x14)
}
catch {
Send-MailMessage -from "email#address" -to "email#address" -subject $subject -body "File ($need_to_know_on_which_file_the_extract_failed) failed to extract from $($_.FullName)" -SmtpServer smtp.server
}
}
}
}
This lets me get the name of the problem file within the archive if I run it manually, since it outputs just before trying the copyhere, but the try/catch doesn't work because on error it pops up an application screen which stops the script and no report is sent.

function to send email multiple times

I am working on a PowerShell script which sends emails multiple times with different subject and body each time.
I am trying to move Send-MailMessage into a function or something that I could use to reduce the code lines.
$Sender = 'jones#example.com'
$text = "<html><body>"
$text += "<p>Welcome</p>"
### A cmdlet that would give recipient email address
$Recipient = (Get-Details -user $user).email
$smtp = "server.example.com"
$subject = "welcome email"
Send-MailMessage -BodyAsHtml $text -from $Sender -SmtpServer $smtp -Priority high -to $Recipient -Subject $subject
Write-Output "executing commands to capture results"
Write-Output ""
### Few Commands executed in this step
Write-Output "Analyzing results"
### Few commands executed in this step
$newtext = "<html><body>"
$newtext += "Congrats, you are selected"
$newsubject = "results email"
Send-MailMessage -BodyAsHtml $newtext -from $Sender -SmtpServer $smtp -Priority high -to $Recipient -Subject $subject
You could create a function like this:
Function Send-Email($text,$subject,$recipient)
{
Send-MailMessage -BodyAsHtml $text -From "jones#example.com"
-SmtpServer "server.example.com" -Priority High -To $recipient -Subject $subject
}
You can call it like:
Send-Email -text "Hello" -subject "Test" -recipient "test#example.com"
You can add or remove arguments depending on what will change though. Assuming the smtp server won't change for example, this isn't needed as a parameter.
I am trying to move Send-MailMessage into a function or something that I could use to reduce the code lines.
Writing a function for one line can be useful if the options are many and never change. However you do have one that changes. There is another PowerShell feature that would work here just as well. Splatting!
$emailParameters = #{
From = $Sender
SmtpServer = $smtp
Priority = "high"
To = $Recipient
Subject = $subject
}
Send-MailMessage -BodyAsHtml $text #emailParameters
# ... other code and stuff
Send-MailMessage -BodyAsHtml $newtext #emailParameters
Now you still only have to make changes in one place and the code is arguably more terse.
Another point is that when you are making multi-line strings all at once, as supposed to building over the course of the script you can always use here strings. You only have two lines but if you code evolves over time it is a good tactic to start early instead of many $object += "string" lines
$text = #"
<html><body>
<p>Welcome</p>
"#
Note that indentation is preserved in the resulting here-string. The "# has to appear on its own line with no leading whitespace. Using double quotes means you can still expand variables as well in there.

IF statement + send-mailmessage confused

Quick question;
I want to make a log of bad stuff happening to the servers, but that's not the question.
The log should be deleted upon send, and if there is no log at the time the script is being run it should send a different message, without an attachment.
Here's the script so far:
<#
Set-ExecutionPolicy unrestricted -Force
iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))
cinst BetterCredentials
#>
Import-Module BetterCredentials
$Mailcred = Get-Credential -user user#gmail.com -pass 12345password
Try{
$File = C:/path/errorfile.log
}
Catch [System.Management.Automation.PSArgumentException]
{
"invalid object"
}
Catch [system.exception]
{
"caught a system exception"
}
Finally
{
If ($File -eq ){
then (send-mailmessage -ErrorAction SilentlyContinue -from "Server <Server#company.com>" -to "IT Dept. <Itdept#company.com>" -subject "Log of the day." -body "Good Morning,
Fortuneatly, nothing bad to report today!" -priority High -dno onSuccess, onFailure -smtpServer smtp.company.com -credential ($Mailcred) -usessl)
else (send-mailmessage -ErrorAction SilentlyContinue -from "Servers <Server#company.com>" -to "IT Dept. <ITDept#company.com>" -subject "Log of the day." -body "Good Morning,
Here is your daily report." -Attachments $File -priority High -dno onSuccess, onFailure -smtpServer smtp.company.com -credential ($Mailcred) -usessl)
}
}
What AM I doing wrong here?
Some parts of your code do not make any sense. Let's review those.
$File = C:/path/errorfile.log
...
If ($File -eq ){
The first part is about assignment. In order to find out wether errorfile.log exists, use the Test-Path cmdlet like so,
$File = test-path 'C:/path/errorfile.log'
Always use quotes around file name. If you got spaces within path, unquoted path doesn't work.
The second part is the actual test.
If ($File){
... # file was on disk, send alert and delete it
} else {
... # no file found, send ok
}

Powershell Function runs for each item in array (I don't want this to happen)

I have created a script to provision Lync users, important details (such as assigned LineURI) for new provisions need to be emailed. Also any errors need to be sent (fluffed up with some friendly error messages of course :)).
So I created a few CSVs with all relevant data..
Then I created a function:
Function Send-Email ($attachArray) {
# Get a list of to addresses
$toAddresses = "foo#corp.local","bar#corp.local"
# Process replacments
Replace-EmailMasks
# Send conditionaly
Switch ($attachArray) {
$null {
Send-MailMessage -SmtpServer "internalrelay.corp.local" `
-From "test#andylab.local" -To $toAddresses `
-Subject "There should really be something more informative here" `
-BodyAsHTML $SCRIPT:htmlBody
}
Default {
Send-MailMessage -SmtpServer "internalrelay.corp.local" `
-From "test#andylab.local" -To $toAddresses `
-Subject "There should really be something more informative here" `
-BodyAsHTML $SCRIPT:htmlBody
-Attachments $attachArray
}
}
}
Here's how I invoke it:
# Logic, then send
If (($npSuccess -gt 0) -AND ($errorsExist -gt 0)) {
# Attaching both
# Heres the summary paragraph
$SCRIPT:customSummary = '<p>Success and errors :|</p>'
# Now I'm sending it.
Send-Email "$($tempPlace.fullname)\NewProviSsion_Output.csv","$($tempPlace.fullname)\Errors_Output.csv"
} ElseIf ($npSuccess -gt 0) {..} # output-generating Success
ElseIf ($errorsExist -gt 0) {..} # Failed somewhere
Else {..} # no output-generating Success, no overall fails
Now this works; Email looks nice, goes to who it should, files attached etc..
Problem is:
For however many files I specify in $attachArray, that's how many emails get sent. The emails are all exactly the same, going to all the same people n many times.
It's as if i'm doing this:
ForEach ($item in $attachArray) {
Send-Email "$($tempPlace.fullname)\NewProviSsion_Output.csv","$($tempPlace.fullname)\Errors_Output.csv"
}
Except i'm not..
To clarify my objective, I want the email to be sent to all in $toAddresses only once.
Can anyone enlighten me as to what's going on here?
Maybe I've just had a bad Monday morning..
The switch statement fires for each element of the array. This behavior is documented (check Get-Help about_Switch):
If the test value is a collection, such as an array, each item in the collection is evaluated in the order in which it appears.
Use a regular conditional instead (since you have only 2 cases anyway):
if ($attachArray -eq $null) {
Send-MailMessage -SmtpServer "internalrelay.corp.local" `
-From "test#andylab.local" -To $toAddresses `
-Subject "There should really be something more informative here" `
-BodyAsHTML $SCRIPT:htmlBody
} else {
Send-MailMessage -SmtpServer "internalrelay.corp.local" `
-From "test#andylab.local" -To $toAddresses `
-Subject "There should really be something more informative here" `
-BodyAsHTML $SCRIPT:htmlBody
-Attachments $attachArray
}