Release a file lock that send-mailmessage leaves on error - email

I'm using the Send-MailMessage cmdlet to send a copy of a log file. However, I'd like to add a line to the log file with the status of the Send.
Code:
$To = "toaddress#email.com"
$Subject = "Test Email Attachment"
$From = "fromaddress#email.com"
$Body = "Please find log file attached"
$SMTPServer = "smtp.server.com"
$LogFile = "Testlog.log"
Add-Content -Path $LogFile -Value "Trying to email log..."
Try
{
send-mailmessage -to $To -subject $Subject -From $From -body $Body -smtpserver $SMTPServer -attachments $LogFile -ErrorAction Stop
Add-Content -Path $LogFile -Value "Successfully emailed log to: $To"
}
Catch [system.exception]
{
Add-Content -Path $LogFile -Value "Error emailing log to: $To"
}
Finally {}
Error:
PS E:\> . .\TestMail.ps1
Add-Content : The process cannot access the file 'E:\Testlog.log' because it is being used by another process.
At E:\TestMail.ps1:16 char:14
+ Add-Content <<<< -Path $LogFile -Value "Error emailing log to: $To"
+ CategoryInfo : WriteError: (E:\Testlog.log:String) [Add-Content], IOException
+ FullyQualifiedErrorId : GetContentWriterIOError,Microsoft.PowerShell.Commands.AddContentCommand
This works fine if the email succeeds, but if it fails (say the smtp server is unavailable), and erroraction is set to "Stop", then the file lock on the log file doesn't get released. If erroraction is not set to "Stop", then the log file gets released, but the Catch block doesn't get triggered.
I found a reference to the "dispose" method if you're rolling your own mail function, but I'm not sure how it could be used with the Send-MailMessage cmdlet: http://petermorrissey.blogspot.com.au/2013/01/sending-email-messages-with-powershell.html
Is there a command I should run to release the lock, or is this a bug, or am I going about this the wrong way?

Seems to be a bug: https://connect.microsoft.com/PowerShell/feedback/details/785417/out-file-cannot-access-logfile-references-in-attachments-of-failed-send-mailmessage-call
Email a copy of the file or create your own email function using PowerShell 1 methods (One example: how to send an email with attachement using powershell v1?)

Related

Script to check if a backup ran

I am trying to write a script that checks folders to see if a backup has run for that particular installation.I have a number of folders and in these folders are the backup .zip files, the names of the files are the same for all folders so for example folder A has the file "Backup.zip" folder B will also have "Backup.zip" but from a different installation. At the moment I got it working fine but it's not looking very good and for example if a new folder gets created and a backup will be placed here I need to copy paste a whole block of text in the script and change the names. I'm looking for a more automated check that if I add a folder it will check that aswell.
Also the point of the checks is , if the file didn't change (or is not present at all) in the last 24 hours I should be notified by e-mail, which I also have working right now.
This is the code I have right now
if (Test-Path "<Path to file>")
{
if ( ((get-date) - (ls <Path to file>).LastWriteTime).days -lt 1)
{
$success++
}
else
{
Send-MailMessage -From $MailFrom -To $MailTo -Subject "!!Backup failed!!" -Body "!!Backup failed!!" -Port $SMTPPort -Credential $EmailCredential -UseSsl
$failed++
}
}
else
{
Send-MailMessage -From $MailFrom -To $MailTo -Subject "!!Backup not found!!" -Body "!!Backup not found!!" -Port $SMTPPort -Credential $EmailCredential -UseSsl
$failed++
}
The variables $success and $failed is just used for a general email that gets sent after it has ran all the checks to see how many ran successfull and how many failed.
But this code above is what I have to copy everytime a new folder for backups is created, so I manually have to edit the Path to file aswell. Can somebody help me or push me in the right direction to make it an automated thing? I'm extremely new to Powershell and I don't really know how to get it going. I do know there is a thing called 'foreach' but I have no clue how to implement it here.
Thanks in advance!
EDIT: I managed to get it working partly, for some reason about 8 folders can't get checked, I get this error on those folders. 37 other folders get checked fine, what can be the issue here? All the folders have the same properties.
Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.IO.FileInfo".
At line:25 char:6
+ if ( ([System.Io.fileinfo] $i).LastWriteTime.Date -ge [datetime]::Tod ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : ConvertToFinalInvalidCastException
the code it will be like that
#if there is more than one parent path mentioned all of them in array like $parents = "path1","path2", ..... and loop on them with the same foreach
$Parentpath = <Parent path which contain the backups>
foreach ($folder in $parentpath){
#check it is a folder not a file
if (($Folder.GetType().name) -eq "DirectoryInfo"){
$path = Convert-Path $folder.PSPath
}
if (Test-Path "$path")
{
if ( ((get-date) - (ls if (Test-Path "$path") ).LastWriteTime).days -lt 1)
{
$success++
}
else
{
Send-MailMessage -From $MailFrom -To $MailTo -Subject "!!Backup failed!!" -Body "!!Backup failed!!" -Port $SMTPPort -Credential $EmailCredential -UseSsl
$failed++
}
}
else
{
Send-MailMessage -From $MailFrom -To $MailTo -Subject "!!Backup not found!!" -Body "!!Backup not found!!" -Port $SMTPPort -Credential $EmailCredential -UseSsl
$failed++
}
}
$Parentpath = "\\x.x.x.x\FTPbackups\3CX\"
foreach ($i in $parentpath)
{
if ( ([System.Io.fileinfo] $i).LastWriteTime.Date -ge [datetime]::Today )
{
Write-Host "-=File Found for '$i'"
$success++
}
else
{
Write-Host "-=File found but not modified in the last 24 hours"
Send-MailMessage -From $MailFrom -To $MailTo -Subject "!!Backup van niet gelukt!!" -Body "!!Backup van niet gelukt!!" -Port $SMTPPort -Credential $EmailCredential -UseSsl
$failed++
}
}
I don't quite understand how it works, do I need to make an array with every path of every folder?

Send-MailMessage Argument Null or empty

Can someone please help me figure this part out.
#Mail Configuration
$smtpUser = "email#domain.com"
$smtppass = ConvertTo-SecureString "PasswordAsPlainText" -AsPlainText -Force
$psCred = New-Object System.Management.Automation.PSCredential -ArgumentList ($smtpuser, $smtppass)
$ToAddress = "toaddress#domain.com"
$FromAddress = "send#domain.com"
$SMTPSERVER = "smtp.office365.com"
$SMTPPORT = "587"
$MailParam = #{
To = $ToAddress
From = $FromAddress
Subject = $subject
Body = $Mail
SMTPServer = $SMTPServer
Port = $SMTPPORT
Credential = $pscred
}
#Send Email
$GetChildItem = Get-ChildItem -Path C:\Temp
if ($GetChildItem -ne $Null)
{
Write-Host "Backup Success"
$subject = "$env:COMPUTERNAME SQL Backup Success"
$Mail = "SQL Backup Succeded on the server, please see the attached report for more details"
$attachment = $reportfile
Send-MailMessage #MailParam -BodyAsHtml -usessl
#Exit
}
if ($GetChildItem -eq $Null)
{
Write-Host "Backup Failed"
$subject = "$env:COMPUTERNAME SQL Backup Failed"
$Mail = "SQL Backup Failed on the server, please see the attached report for more details"
$attachment = $reportfile, $debuglog
Send-MailMessage #MailParam -usessl -BodyAsHtml
#Exit
}
Now I don't think anything is wrong with the code above, I am no expert coder but I can pretty much copy and paste :) and take stuff from there to get it working for me.
The issue with the above code is that when run it, it doesn't work for the 1st time but when you run it again it works fine. The error you get the first time is
Send-MailMessage : Cannot validate argument on parameter 'Body'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
At C:\Users\Aasim\Desktop\Untitled1.ps1:29 char:26
+ Send-MailMessage #MailParam -BodyAsHtml -usessl
+ ~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Send-MailMessage], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.SendMailMessage
I am not sure what the issue is and why it doesn't work only the first time but works every other subsequent time until you close and reopen the script.
Basically I am creating a SQL backup script that backs up databases to our Network Share and then emails me whether it was successful or not. so far the rest of the script works just fine.
In your If and Else blocks, instead of creating a variable $subject, or $Mail, update the hashtable, $MailParam. Also, you seem to be missing the Attachment variable in your Send-MailMessage
if ($GetChildItem -eq $Null)
{
Write-Host "Backup Failed"
$MailParam.Subject = "$env:COMPUTERNAME SQL Backup Failed"
$MailParam.Body = "SQL Backup Failed on the server, please see the attached report for more details"
$attachment = $reportfile, $debuglog
Send-MailMessage #MailParam -usessl -BodyAsHtml -Attachments $attachment
#Exit
}

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.

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 - write errors to .txt file and in one line

I need to log the process in a text file and there are two problems:
1. How to write errors in a text file? Now when the e-mails are not sent, nothing is logged.
2. I need to write time, date and event in a single line
Now I have this in log.txt:
17. feb. 2012 10:47:34
Chyba: .gpg neexistuje
17. feb. 2012 10:57:28
Test.gpg existuje.
This is my code:
function write-log{
param(
[string]$mytext,
[string]$fgc
)
if(!$fgc){$fgc="Black"}
write-host $mytext -foregroundcolor $fgc
$myfile = "c:\gnupg\subor\log.txt"
Get-Date | Out-File $myfile -append
$mytext | Out-File $myfile -append
}
if(test-path "d:\vm\shared pre ws08R2+SQL05\SFRB\*.chk") {
echo "Subor .chk existuje a jeho nazov je "
get-childitem "d:\vm\shared pre ws08R2+SQL05\SFRB\*.chk" -Name
$a = get-childitem "d:\vm\shared pre ws08R2+SQL05\SFRB\*" -include *.chk -name | Foreach-Object {$a -replace ".chk", ""}
if(test-path d:\vm\"shared pre ws08R2+SQL05"\SFRB\$a.gpg) {
Write-Log $a".gpg existuje." Green
Write-Log "Presuvam do banky subor $a.gpg.." Green
Move-Item "d:\vm\shared pre ws08R2+SQL05\SFRB\$a.gpg" c:\gnupg\subor\$a.gpg
Write-Log "Presun ukonceny." Green
Write-Log "Presuvam do banky subor $a.chk.." Green
Move-Item "d:\vm\shared pre ws08R2+SQL05\SFRB\$a.chk" c:\gnupg\subor\$a.chk
Write-Log "Presun ukonceny. Subor je pripraveny na spracovanie." Green
Write-Log "Posielam notifikacne maily.." Green
$emailFrom = "sfrbControlMsg#primabanka.sk"
$emailTo = "msic#primabanka.sk"
$subject = "subject"
$body = "subor presunuty"
$smtpServer = "87.244.217.54"
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($emailFrom, $emailTo, $subject, $body)
# Write-Log "Maily odoslane." Green
}
else {
Write-Log " Chyba: $a.gpg neexistuje" Magenta
}
} else {
Write-log "V cielovom adresari neexistuje ziadny subor .chk."
}
Thank you.
Make use of the inbuilt Send-MailMessage cmdlet with parameter -ErrorAction Stop.
Use a try-catch around this (see about_try_catch_finally to pick up and handle any errors.
In the catch block $_ will be the error message (as would be shown on the console), and has an Exception property which is the (.NET) Exception (or subclass) instance (I use $_.Exception.GetType() below to specifically report this type as it is an important part of any diagnostics):
Eg. in a script I have:
try {
$trans = (MakeSummaryMessage) + "`r`n`r`n" + $trans
Send-MailMessage -From $emailFrom `
-To $emailTo `
-SmtpServer $emailServer `
-Subject $subject" `
-Body $trans `
-Encoding ([System.Text.Encoding]::UTF8) `
-ErrorAction Stop
$emailSent = $true
} catch {
Write-EventLog -LogName $eventLog -Source $eventSource -EntryType "Error" `
-EventId 100 -Message "Failed to send email: $($_.Exception.GetType()): $_"
}