How to attach files to email using Get-ChildItem in Powershell - powershell

I am trying to write a Powershell script that does the following:
Check all files in a directory for a valid string
Take every file that contains that valid string and then send them as an email attachment
I do not care if one email is sent with all of the valid files or if an email is sent for each valid file.
The following code is what I have but it errors out when trying to attach the files saying Send-MailMessage : Cannot find drive. A drive with the name '
$ValidFiles = Get-ChildItem -Path C:\Results -Recurse |
Select-String -Pattern "Test" |
Select-Object -Unique Path
foreach ($ValidFile in $ValidFiles)
{
$From = 'test <test#test.com>'
$To = 'me <me#test.com>'
$Subject = "Valid File Found"
$Username = "Test-Domain\test"
$Password = ConvertTo-SecureString "notrealpassword" -AsPlainText -Force
$Creds = New-Object System.Management.Automation.PSCredential($username, $password)
$Body = "Please review the attached files"
$Attachment = $ValidFile | Out-String
$SMTPServer = "test-com.mail.protection.outlook.com"
Send-MailMessage -From $From -To $To -Subject $Subject -Credential ($Creds) -Attachments $Attachment -UseSsl -Body $Body -DeliveryNotificationOption OnSuccess, OnFailure -SmtpServer $SMTPServer
}
Any help is greatly appreciated.

First, make sure that $ValidFiles contains only strings (file paths):
$ValidFiles = Get-ChildItem -Path C:\Results -Recurse |
Select-String -List -Pattern "Test" |
ForEach-Object Path
Adding -List to Select-String makes the search stop at the first match found in a given file, which obviates the need for Select-Object -Unique.
Select-Object outputs [pscustomobject] instances with the specified property/properties, even if only one property Path is specified; while you could use -ExpandProperty Path instead to get just the .Path property value, it is simpler to use ForEach-Object with the property name instead.
Then, use $ValidFile - which is now a path string - directly as the -Attachments argument (which is [string[]]-typed, but also accepts a scalar [string] instance (single string)).
Generally, do not use Out-String, unless you want a formatted for display representation of the input object(s).

Out-string stops $Attachment from getting populated.
$Attachment = $ValidFile | Out-String
Try
$Attachment = $ValidFile

Related

Get email Address from CSV file line and send email to each of them separately

I have a csv file, i want to send email separately to each user, currently from my script i am getting emails to all users addresses those are in my csv file "user" column. Here is my CSV file Data.
#TYPE System.Management.Automation.PSCustomObject
"time","remip","user","msg"
"11:12:15","124.29.228.164","abc#xyz.com.pk","SSL tunnel shutdown"
"11:12:43","124.29.228.164","efg#gmail.com","SSL tunnel established"
"11:13:25","124.29.228.164","abc#xyz.com.pk","SSL tunnel established"
"11:14:05","202.63.194.8","efg#gmail.com","SSL tunnel established"
This is my powershell code
$Subject = " Alerts $(get-date -Format "dddd MM-dd-yyyy HH:mm")"
$Server = "qs42.xyz.com"
$From = "logalerts#abc.com"
$To = $ID = Import-Csv myfile.txt | Select-Object -ExpandProperty user -Unique
$PWD = ConvertTo-SecureString "test123" -AsPlainText -force
$Cred = New-Object System.Management.Automation.PSCredential("logalerts#abc.com" , $pwd)
$path = "myfile.txt"
$file = (Import-Csv $path) | Select-Object -Unique -Property Time , remip , user , Msg
Send-MailMessage -From $from -to $To -Port 587 -SmtpServer $server -Subject $Subject -Credential $cred -UseSsl -Body ($file | Out-String)
Iam able to get emails of this data on both address abc#xyz.com, efg#gmail.com which i dont want, i know emails are receiving because of my variable setting but my requirement is to get emails of each user data on the same user email address.
>>time remip user msg
---- ----- ---- ---
11:12:15 124.29.228.164 abc#xyz.com SSL tunnel shutdown
11:12:59 124.29.228.164 efg#gmail.com SSL tunnel shutdown
11:13:25 124.29.228.164 abc#xyz.com SSL tunnel established
11:14:05 202.63.194.8 efg#gmail.com SSL tunnel established
I don't know how to do this any help please.
There were a couple of things wrong with your code:
$PWD is an automatic variable and means the current working directory. You must not use that name as self-defined variable
You use Import-Csv multiple times where once is enough
The way you try to get a value in the $To variable is wrong
you need Group-Object because some users may have more than one message
I'd recommend using Splatting the parameters to the Send-MailMessage cmdlet
Try:
$password = ConvertTo-SecureString "test123" -AsPlainText -force
$cred = New-Object System.Management.Automation.PSCredential("logalerts#abc.com" , $password)
# import the data from the csv, group on field 'user' (which is the emailaddress to send to)
Import-Csv -Path 'D:\Test\myfile.txt' | Group-Object user | ForEach-Object {
# build a Hashtable to neatly send the parameters to Send-MailMessage cmdlet
# this is called Splatting.
$mailParams = #{
From = 'logalerts#abc.com'
To = $_.Name # the Name of the group is from the user field in the csv
Port = 587
SmtpServer = 'qs42.xyz.com'
Subject = 'Alerts {0:dddd MM-dd-yyyy HH:mm}' -f (Get-Date)
Credential = $cred
UseSsl = $true
Body = $_.Group | Format-Table -AutoSize | Out-String
}
Send-MailMessage #mailParams
}
This is very similar to the answer given by #theo, but it doesn't use the Group-Object cmdlet. I've had problems with memory consumption when the data set is very large because it stores structures (PSObject or PSCustomObjects) within objects and the objects tend to be pretty large.
$PassWord = ConvertTo-SecureString "test123" -AsPlainText -force
$Cred = New-Object System.Management.Automation.PSCredential("logalerts#abc.com"
, $PassWord)
$path = "myfile.txt"
# Get all alerts
$Alerts = Import-Csv $path | Select-Object -Unique -Property Time , remip , user
, Msg
# get individual recipient addresses from the log file
$UniqueUsers = $Alerts |
Select-Object -ExpandProperty user -Unique
ForEach ($User in $UniqueUsers){
$UniqueAlerts = $Alerts |
Where-Object {$_.user -eq $User}
$params = #{
From = 'logalerts#abc.com'
To = $User
Subject = 'Alerts {0:dddd MM-dd-yyyy HH:mm}' -f (Get-Date)
Body = $UniqueAlerts | Out-String # This probably won't align
the
columns properly
# if a mono-spaced font isn't
used by the e-mail client.
# you might consider sending the
message in HTMP format
SmtpServer = 'qs42.xyz.com'
Port = 587
UseSsl = $true
Credential = $cred
}
Send-MailMessage #params
}
As noted in comments, the body of the message will probably be hard to read if it's in plain-text. A HTML message with the data within a table will produces a much easier to read message.

Powershell Script to send email after validation

I have a Powershell script that logs the result to a text file (this is working fine), now I need to update it to send email instead of a Log file (see actual script below). I'm kind of a new to powershell, hopefully you can help me out.
# details of the script
# if a file "FILE.400" is found and check the modified date, it will send a log file (txt file)
# need to update the script, to send email instead of a log file
$svr=$env:ComputerName
$date=[datetime]::Today.ToString('MM-dd-yyyy')
$filename = "FILE.400"
$DestLogs1 = "E:\sample\pfupdatedfin-$date.txt"
$DestLogs2 = "E:\sample\pfoutdatedfin-$date.txt"
$SrcPath = "E:\sample\$filename"
If ((Get-ChildItem $SrcPath).LastWriteTime -gt $date)
{
$date | Out-File $DestLogs1
Write-Output "Found latest FILE.400 file" | Out-File $DestLogs1 -Append
(Get-ChildItem -Recurse -Path $SrcPath).LastWriteTime | Out-File $DestLogs1 -Append
}
Else
{
Write-Output "FILE.400 - is not updated, please verify " | Out-File $DestLogs2
(Get-ChildItem -Recurse -Path $SrcPath).LastWriteTime | Out-File $DestLogs2 -Append
}
Send-MailMessageis pretty easy to use. The online documentation for cmdlets is very easy to find. Just enter the name of the cmdlet in your favorite search engine. Or use the Get-Help cmdlet. Here is an example how to use it just fille the Variables reasonably :
Send-MailMessage -From $From -To $To -Subject $Subject -Body $Body -SmtpServer $SMTPServer -Port $SMTPPort -Credential $Credential # -UseSsl:$UseSsl
-useSsl is a switch so it is not necessary unless it's $true. If you want to provide a variable to a switch parameter, don't use a whitespace but a : in between.
Credentials in Powershell are stored in credential objects. You can't just use a plain text password. However don't think they are stored extremly save since the password can be easly extracted if you know how. Here is how you create the necessary credential object:
$SecPw = ConvertTo-SecureString -AsPlainText $Password -Force
$Credential = New-Object System.Management.Automation.PSCredential -ArgumentList $UserName,$SecPw
the script is working as expected, in my Home network
$svr=$env:ComputerName
$date=[datetime]::Today.ToString('MM-dd-yyyy')
$filename = "FILE.400"
$DestLogs1 = "E:\sample\pfupdatedfin-$date.txt"
$DestLogs2 = "E:\sample\pfoutdatedfin-$date.txt"
$SrcPath = "E:\sample\$filename"
# email settings
$MyEmail = "myemail#gmail.com"
$SMTP = "smtp.gmail.com"
$To = "samplereceiver#gmail.com"
$Subject1 = "JDA-Alert || Found FILE.400 file - Successful"
$Body1 = "JDA-Alert || Found FILE.400 file - Successful"
$Subject2 = "JDA-Alert || FILE.400 file - Outdated File"
$Body2 = "JDA-Alert || FILE.400 file - Outdated File"
$Creds = (Get-Credential -Credential "$MyEmail")
If ((Get-ChildItem $SrcPath).LastWriteTime -gt $date)
{
Start-Sleep 2
Send-MailMessage -To $To -From $MyEmail -Subject $Subject1 -Body $Body1 -SmtpServer $SMTP -Credential $Creds -UseSsl -Port 587 -DeliveryNotificationOption Never
}
Else
{
Start-Sleep 2
Send-MailMessage -To $To -From $MyEmail -Subject $Subject2 -Body $Body2 -SmtpServer $SMTP -Credential $Creds -UseSsl -Port 587 -DeliveryNotificationOption Never
}

Sending foreach output via email

I have a script that is checking mirror status of databases. Output in Powershell is fine, but when I try to send it via mail, I'm getting "Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData" instead of data itself. I've tried to change it to Out-String but then I'm getting all results in one line. How this could be done to have formated output the same way as it is formated directly in PowerShell?
# rozszerzenie do obslugi
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | Out-Null;
$mail_from = "xxx";
$mail_to = "xxx";
$mail_subject = "Status mirrorowanych baz";
$mail_encoding = "UTF8";
$mail_smtp = "xxx";
# lista serwerow
$list = #("SERVER01V",
"SERVER02V"
);
$output = foreach($server in $list)
{
$srv = New-Object "Microsoft.SqlServer.Management.Smo.Server" $server;
# pokaz tylko mirrorowane
$databases = $srv.Databases | Where-Object {$_.IsMirroringEnabled -eq $true};
Write-Output "<br>==================================<br>";
Write-Output $server;
Write-Output "<br>==================================<br>";
$databases | Select-Object -Property Name, MirroringStatus | Format-Table -AutoSize | Out-String;
Write-Output "<br>";
}
$mail_body = $output;
Send-MailMessage -To $mail_to -From $mail_from -Subject $mail_subject -SmtpServer $mail_smtp -Encoding $mail_encoding -Body $mail_body -BodyAsHtml
You're currently sending a HTML mail. As such line breaks won't matter. If you want line breaks in your mail you either need to use text format or replace line breaks with a <br /> or something similar. It's probably going to be wise to have manually add <br /> in your look to have a consistent pattern and replace.
Try gather all data into an array, then use ConvertTo-HTML cmdlet, and the 'BodyAsHTML' switch in Send-MailMessage
$DatabaseArray=#()
ForEach ($server in $list) {
$srv = New-Object "Microsoft.SqlServer.Management.Smo.Server" $server
$DatabaseArray += $srv.Databases | Where-Object {$_.IsMirroringEnabled -eq $true} | Select-Object Name,MirroringStatus
}
$HTMLBody = $DatabaseArray | ConvertTo-HTML
Send-MailMessage -subject x -body $HTMLBody -BodyAsHTML

Powershell search for longfile path names and send the owner of a file a mail to do something about it

I am trying to set up a PowerShell script which reads out a UNC path on Windows and searches for longfiles/paths. After it has found the file it needs to send a mail to the owner of the file.
I have found already a script and tweaked it a bit but it doesn't seems to work yet. The following script can find the long file path now but the mail is not working properly.
$limit = 90
$testpath = "C:\test"
$resultpath = "c:\test"
$admins = "moh#test.com"
$from = "moh#test.com"
$smtpserver = "smtp.office365.com"
Get-ChildItem -Path $testpath -Recurse | ?{$_.fullname.length -gt $limit} |
Select-Object fullname,
#{n="owner";e={
$_.GetAccessControl().GetOwner('System.Security.Principal.NTAccount')}},
#{n="namelength"; e={$_.fullname.length}} |
%{
Out-File -FilePath "$resultpath\Longfiles of $($_.owner -replace "\\","-").txt" -Append -InputObject "$($_.namelength) - $($_.fullname)"
}
Get-ChildItem $resultpath -Filter "longfiles of *" | % {
if($_.name -match "Longfiles\sof\s(.+)\.txt"){
$user = $matches[1] -replace "-","\"
$ntacc = New-Object System.Security.Principal.NTAccount($user)
$sid = $ntacc.Translate([System.Security.Principal.SecurityIdentifier])
$aduser = [ADSI]"LDAP://<SID=$sid>"
$email = $aduser.Properties.mail
if($email) {Send-MailMessage -Attachments $_.fullname -Body "Please change the filenames of the files listed in the attached file to shorter!"
-From $from -SmtpServer $smtpserver -Subject "System notice" -To
$email -cc $admins
}
else {
Send-MailMessage -Attachments $_.fullname -Body "email coudn't be sent to owner" `
-From $from -SmtpServer $smtpserver -Subject "System notice" -To $admins
}
}
else {Write-Host "Some error with file $_"}
}
EDIT: This is what i see, after running the script... it is asking me to fill in the fields, while the fields are already filled in in the script such as (From: moh#test.com to moh#test.com)
[]
You issue was because of line breaks in the middle of a command. In some lines, you had a backtick character which escapes the end of the line. But as you found these are really easy to break and that's why it's best practice to use splatting on commands with many parameters.
I also changed your Select-Object calculated properties into a more readable [pscustomobject] since they are hard to format in a readable way, but this does require PS3+.
$limit = 90
$testpath = "C:\test"
$resultpath = "c:\test"
$admins = "moh#test.com"
$from = "moh#test.com"
$smtpserver = "smtp.office365.com"
Get-ChildItem -Path $testpath -Recurse |
Where-Object {$_.fullname.length -gt $limit} |
ForEach-Object {
[PSCustomObject]#{
'fullname' = $_.fullname
'owner' = $_.GetAccessControl().GetOwner('System.Security.Principal.NTAccount')
'namelength' = $_.fullname.length
}
} |
ForEach-Object {
Out-File -FilePath "$resultpath\Longfiles of $($_.owner -replace "\\","-").txt" -Append -InputObject "$($_.namelength) - $($_.fullname)"
}
Get-ChildItem $resultpath -Filter "longfiles of *" | ForEach-Object {
if ($_.name -match "Longfiles\sof\s(.+)\.txt") {
$user = $matches[1] -replace "-", "\"
$ntacc = New-Object System.Security.Principal.NTAccount($user)
$sid = $ntacc.Translate([System.Security.Principal.SecurityIdentifier])
$aduser = [ADSI]"LDAP://<SID=$sid>"
$email = $aduser.Properties.mail
if ($email) {
$mailparams = #{
'Attachments' = $_.fullname
'Body' = "Please change the filenames of the files listed in the attached file to shorter!"
'From' = $from
'SmtpServer' = $smtpserver
'Subject' = "System notice"
'To' = $email
'cc' = $admins
}
Send-MailMessage #mailparams
} else {
$mailparams = #{
'Attachments' = $_.fullname
'Body' = "email coudn't be sent to owner"
'From' = $from
'SmtpServer' = $smtpserver
'Subject' = "System notice"
'To' = $admins
}
Send-MailMessage #mailparams
}
} else {
Write-Host "Some error with file $_"
}
}
Remove the line breaks or escape them with a backtick: `. Your script must actually look like this:
if ($email) {
Send-MailMessage -Attachments $_.fullname -Body "Please change the filenames of the files listed in the attached file to shorter!"
-From $from -SmtpServer $smtpserver -Subject "System notice" -To
$email -cc $admins
}
else {
Send-MailMessage -Attachments $_.fullname -Body "email coudn't be sent to owner" `
-From $from -SmtpServer $smtpserver -Subject "System notice" -To $admins
}
And Powershell doesn't know that the line beginning with -From is part of Send-MailMessage.

Adding a config file to loop through for a PS script

My powershell script is setup as follows:
$body = Get-ChildItem E:\log -File -Recurse | Where Name -Match '(\d{8})\.' |
Foreach {Add-Member -Inp $_ NoteProperty ReturnDate ($matches[1]) -PassThru} |
Group DirectoryName |
Foreach {$_.Group | Sort ReturnDate -Desc | Select -First 1 | Out-String }
$emailSmtpServer = "server"
$emailFrom = "email"
$emailTo = "email"
$emailSubject = "Testing e-mail"
$emailBody = $body
Send-MailMessage -To $emailTo -From $emailFrom -Subject $emailSubject -Body ($body|Out-String) -SmtpServer $emailSmtpServer
In the log folder I have a bunch of subfolders, e.g. folder1, folder2, folder3. These are likely to change so I'd like to setup a config file to be able to maintain them instead of going through the entire E:\log folder each time I run the script.
I want to add something such as
$configfile = Get-Content -path E:\config.txt
This outputs Process1, Process2, Process3, etc and I'm uncertain how to put that data into my script the way it's currently structured. Any advice would be appreciated. I was trying to add
$body = Get-Childitem E:\log\$_
to my initial line, but that was not working
Try this:
get-childitem ( get-content e:/config.txt )