PowerShell Send-MailMessage format column in message body - powershell

Warning - I'm new to PowerShell. There are two outcomes I would like to achieve with this script. The first is to include the output in an email and format the columns in the message body so they align with the headers similar to Out-Host. Second is when out-csv, out-gridview or export-excel, how do I order the columns?
$VolArray = #();
$Volumes = Get-Ncvol | Where-Object {$_.VolumeMirrorAttributes.IsDataProtectionMirror -match 'False' -and $_.VolumeStateAttributes.IsVserverRoot -match 'False' -and -not $_.VolumeCloneAttributes.VolumeCloneParentAttributes}
ForEach ($Volume in $Volumes){
#get properties
$vol = Get-Ncvol $Volume
#create object with values
$volobj = New-Object -TypeName PSObject -Property #{
'Controller' = $vol.NcController
'Vserver' = $vol.Vserver
'Aggregate' = $vol.VolumeIdAttributes.ContainingAggregateName
'Name' = $vol.VolumeIdAttributes.Name
'Type' = $vol.VolumeIdAttributes.Type
'TotSizeGB'= $vol.VolumeSpaceAttributes.Size/1gb
'Used' = $vol.VolumeSpaceAttributes.SizeUsed/1gb
'%Used' = $vol.VolumeSpaceAttributes.PercentageSizeUsed
'AvailableGB' = $vol.VolumeSpaceAttributes.SizeAvailable/1gb
'SSResSizeGB' = $vol.VolumeSpaceAttributes.SnapshotReserveSize/1GB
'IsDPMirror' = $vol.VolumeMirrorAttributes.IsDataProtectionMirror
'IsReplicaVol' = $vol.VolumeMirrorAttributes.IsReplicaVolume
'IsDPSource' = $vol.VolumeMirrorAttributes.IsSnapmirrorSource
'DPInProgress' = $vol.VolumeMirrorAttributes.MirrorTransferInProgress
'SSPolicy' = $vol.VolumeSnapshotAttributes.SnapshotPolicy
'AutoSSEnabled' = $vol.VolumeSnapshotAttributes.AutoSnapshotsEnabled
'SSCount' = $vol.VolumeSnapshotAttributes.SnapshotCount
'%SSReserve' = $vol.VolumeSpaceAttributes.PercentageSnapshotReserve
'%SSResUsed' = $vol.VolumeSpaceAttributes.PercentageSnapshotReserveUsed
'SSSpaceUsed' = $vol.VolumeSpaceAttributes.SizeUsedBySnapshots/1GB;
}
#add to array outside opf for-loop
$VolArray += $volobj
}
#$VolArray | Export-Csv -Path c:\temp\file.csv
#$VolArray | Export-Excel -Path c:\temp\exceldump.xlsx
$VolArray | Out-String
#Send-MailMessage -To $mailto -Subject $subject -Body (-join $message) -Port $port -SmtpServer $smtp -from $emailfrom
Send-MailMessage -To $mailto -Subject $subject -Port $port -SmtpServer $smtp -from $emailfrom -Attachments c:\temp\file.csv
Message body:

Column ordering
In PowerShell, for performance reasons, there is no guarantee of order for properties of common hashtables. Thankfully you can use the [ordered] keyword to create ordered dictionaries (a hashtable is a form of dictionary) since version 3.
[PSCustomObject][ordered]#{
FirstColumn = 1
SecondColumn = 2
ThirdColumn = 3
}
This will ensure the order of the properties in subsequent operations like Export-Csv. Note that I also used the [PSCustomObject] accelerator, which is more efficient and than New-Object -TypeName PSObject.
Getting the data efficiently
In your code there are unnecessary calls to Get-Ncvol in the foreach loop. You already have the data you need form earlier:
$Volumes = Get-Ncvol |
Where-Object {
$_.VolumeMirrorAttributes.IsDataProtectionMirror -match 'False' -and
$_.VolumeStateAttributes.IsVserverRoot -match 'False' -and
-not $_.VolumeCloneAttributes.VolumeCloneParentAttributes
}
# Store results in a variable to use later
$reportData = foreach ($Volume in $Volumes) {
# Create object with values
[PSCustomObject][ordered]#{
'Controller' = $Volume.NcController
'Vserver' = $Volume.Vserver
'Aggregate' = $Volume.VolumeIdAttributes.ContainingAggregateName
'Name' = $Volume.VolumeIdAttributes.Name
'Type' = $Volume.VolumeIdAttributes.Type
'TotSizeGB' = $Volume.VolumeSpaceAttributes.Size / 1gb
'Used' = $Volume.VolumeSpaceAttributes.SizeUsed / 1gb
'%Used' = $Volume.VolumeSpaceAttributes.PercentageSizeUsed
'AvailableGB' = $Volume.VolumeSpaceAttributes.SizeAvailable / 1gb
'SSResSizeGB' = $Volume.VolumeSpaceAttributes.SnapshotReserveSize / 1GB
'IsDPMirror' = $Volume.VolumeMirrorAttributes.IsDataProtectionMirror
'IsReplicaVol' = $Volume.VolumeMirrorAttributes.IsReplicaVolume
'IsDPSource' = $Volume.VolumeMirrorAttributes.IsSnapmirrorSource
'DPInProgress' = $Volume.VolumeMirrorAttributes.MirrorTransferInProgress
'SSPolicy' = $Volume.VolumeSnapshotAttributes.SnapshotPolicy
'AutoSSEnabled' = $Volume.VolumeSnapshotAttributes.AutoSnapshotsEnabled
'SSCount' = $Volume.VolumeSnapshotAttributes.SnapshotCount
'%SSReserve' = $Volume.VolumeSpaceAttributes.PercentageSnapshotReserve
'%SSResUsed' = $Volume.VolumeSpaceAttributes.PercentageSnapshotReserveUsed
'SSSpaceUsed' = $Volume.VolumeSpaceAttributes.SizeUsedBySnapshots / 1GB;
}
}
Exporting & emailing
Since we already took care of the column ordering you just need to use $reportData | Export-Csv c:\temp\file.csv -NoTypeInformation or the Export-Excel equivalent.
Emailing is going to be a bit more difficult. Your best bet to convert the data to an HTML table and include it as the body of the email.
# The CSS is neccesary to make the table look nicer, adjust as needed
$css = #'
<style>
body {background-color: powderblue;}
h1 {color: red;}
p {color: blue;}
th, td {
padding: 15px;
text-align: left;
}
</style>
'#
$emailBody = $reportData | ConvertTo-Html -Head $css
# Use parameter splatting for redability
$emailParameters = #{
To = "jdoe#company.com"
Subject = "NetApp report for $(Get-Date -Format 'd')"
Body = $emailBody
BodyAsHtml = $true
SmtpServer = "smtp.company.com"
Credential = Get-Credential
}
Send-MailMessage #emailParameters

[Ordered] worked perfectly.
I modified the message body parameter:
Body = ($emailBody | Out-String)
Because of this error:
Send-MailMessage : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Body'. Specified method is not supported.
Would you have any recommendations for setting the number of decimal places? ...124.994548797607421875

Related

Powershell - Get yesterday's file name in real time

I'm noob in PowerShell and trying to use it to send email with attachment.
Today's date is 2021-02-18. I have a folder which have many files created by ERP system everyday, the files name are as follows:
2021-2-17WO_rate.xlsx
2021-2-16WO_rate.xlsx
2021-2-15WO_rate.xlsx
2021-2-14WO_rate.xlsx
2021-2-13WO_rate.xlsx
2021-2-12WO_rate.xlsx
...
I would like the script always choose the newest file as the attachment
(Get-Date).AddDays(-1).ToString('yyyy-MM-dd') can get the yesterday date, but when I intergrate it in to below script, it fails. I have no idea how to solve it after search the many websites.
Anyone can help me? much appreciate.
The code I use is as follows:
$UserName = "XXXX#XXXXX.com"
$Password = ConvertTo-SecureString XXXXXX -AsPlainText –Force
$cred = New-Object System.Management.Automation.PSCredential($UserName,$Password)
$a = (Get-Date).AddDays(-1).ToString('yyyy-MM-dd')
$mailParams = #{
SmtpServer = 'smtp.office365.com'
Port = '587' # or '25' if not using TLS
UseSSL = $true ## or not if using non-TLS
BodyasHtml = $true
Credential = $cred
From = $UserName
To = 'XXXXXX'
Subject = "WORK ORDER STATUS"
Body = 'XXXXXXX'
Attachments = '\\us-fs\us-Groups\Operationsus\Public\erpoutput\date\${a}WO_rate.xlsx'
DeliveryNotificationOption = 'OnFailure', 'OnSuccess'
}
Send-MailMessage #mailParams
The reason why the name is not populated is because you used single quotation mark '. If you want to have the value from the variable, you need to use double quotation string ":
<# Only relevant parts of the script, other lines removed #>
# Specify correct formatting string, based on the information from the question it should be either
$dataFormat = 'yyyy-M-d'
# or
$dataFormat = 'yyyy-M-dd'
$formattedDate = (Get-Date).AddDays(-1).ToString($dataFormat)
$mailParams = #{
<# other params #>
Attachments = "\\us-fs\us-Groups\Operationsus\Public\erpoutput\date\${formattedDate}WO_rate.xlsx"
}
Send-MailMessage #mailParams
NOTE: I also changed variable name so that it's obvious what it is used for.
Edit: corrected date format based on #Theo's comment
If the file is not modified since yesterday as well you can retrieve the path of the last file modified yesterday in the folder and use that as the attachment
$file = Get-ChildItem -Path 'C:\path\to\folder' -Filter '*.xlxs' -File | # get all files with xlxs extension in path
Where-Object { $_.LastWriteTime.Date -eq (Get-Date).AddDays(-1).Date } | # files that were last written to yesterday
Sort-Object LastWriteTime | # sort by lastwrite time in ascending order
Select-Object -Last 1 | # select only the last file on the list
ForEach-Object FullName # output the full file path
$UserName = "XXXX#XXXXX.com"
$Password = ConvertTo-SecureString XXXXXX -AsPlainText –Force
$cred = New-Object System.Management.Automation.PSCredential($UserName,$Password)
$a = (Get-Date).AddDays(-1).ToString('yyyy-MM-dd')
$mailParams = #{
SmtpServer = 'smtp.office365.com'
Port = '587' # or '25' if not using TLS
UseSSL = $true ## or not if using non-TLS
BodyasHtml = $true
Credential = $cred
From = $UserName
To = 'XXXXXX'
Subject = "WORK ORDER STATUS"
Body = 'XXXXXXX'
Attachments = $file # <--------------
DeliveryNotificationOption = 'OnFailure', 'OnSuccess'
}
Send-MailMessage #mailParams

I've created an Hyper-V replica Alert Script but struggling to get it alert me when an condition is met

$status = Get-VMReplication | Select-Object Name, State, Health, Mode, FrequencySec, PrimaryServer, ReplicaServer, ReplicaPort | ConvertTo-Html
$Company = ""
$ReplicationHealth = (Get-VMReplication)
function ReplicationHealthFailedTelegram {
$token = (Get-Content -Path C:\temp\telegrambot\token.txt)
$chatid = (Get-Content -Path C:\temp\telegrambot\chatid.txt)
$Message = "Replication Failed"
$Company = ""
$status = Get-VMReplication | Select-Object Name, State, Health, Mode, FrequencySec, PrimaryServer, ReplicaServer, ReplicaPort | ConvertTo-Html
& 'C:\Program Files\PowerShell\7\pwsh.exe' -Command { $token = (Get-Content -Path C:\temp\telegrambot\token.txt);$chatid = (Get-Content -Path C:\temp\telegrambot\chatid.txt); $status = Get-VMReplication | Select-Object Name, State, Health, Mode, FrequencySec, PrimaryServer, ReplicaServer, ReplicaPort | ConvertTo-Html;$Message = "Replication Failed";$Company = "Howard Matthews Partnership - Harrogate";Send-TelegramTextMessage -BotToken $token -ChatID $chatid -Message $Company $Message}
}
function EmailAlert {
$User = "alert#domain.co.uk"
$File = (Get-Content C:\Temp\pw.txt | ConvertTo-SecureString)
$MyCredential = New-Object -TypeName System.Management.Automation.PSCredential `
-ArgumentList $User, $File
$To = "domain.co.uk"
$from = "domain.co.uk"
$EmailSubject = "Hyper-V Replication Error $Company"
$smtp = "auth.smtp.1and1.co.uk"
$DefaultMessage="
<p>Dear Help,</p>
<p>Replication has failed for $Company </p>
<p>$status</p>
<p>The Robot Checker .<br><br>
</p>"
$MailMessage = #{
To = $To
From = $from
# BCC = $Bcc
Subject = $EmailSubject
Body = $DefaultMessage
priority = "High"
Smtpserver = $smtp
Credential = $MyCredential
ErrorAction = "SilentlyContinue"
}
Send-MailMessage #MailMessage -bodyashtml
}
if ($ReplicationHealth.health) {
"Critical","Warning"
EmailAlert
ReplicationHealthFailedTelegram
} else {
$null
}
The Email sends but it regardless if the status of the replication is normal, warning or critical. How would I go by adding the logic of getting to send me an email when that health status is Critical or Warning and not send me an email when it's normal.
Many Thanks!
This bit doesn't do what you think it should
if ($ReplicationHealth.health) {
"Critical","Warning"
EmailAlert
ReplicationHealthFailedTelegram
} else {
$null
}
With your if you are basically asking; "is $ReplicationHealth.Health a thing?" rather than checking for the value "of the thing", and the answer will always be yes in this case assuming nothing went wrong running Get-VMReplication.
A different example to demonstrate what I mean could be something like the following
if($(Get-ChildItem -Path "$env:windir\System32\WindowsPowerShell\v1.0\powershell.exe").Extension) { "Yup, .Extension is a thing" } else { "Nah, it's not!" }
if($(Get-ChildItem -Path "$env:windir\System32\WindowsPowerShell\v1.0\powershell.exe").Extension -eq ".exe") { "Yup, .Extension is a thing and its value is `'.exe`'" } else { "Nah, it's not!" }
In order to fix your if, you could try something like
if($ReplicationHealth.Health -eq "Critical" -or $ReplicationHealth.Health -eq "Warning") {
EmailAlert
ReplicationHealthFailedTelegram
}

How to compare 2 text files using powershell

I want to be able compare the contents of 2 text files and see if the contents of those files are the same. If the contents of the 2 files are different, I need this powershell script to send me an email with a list of differences from the 2 files. Here is my code:
$fromaddress = "noreply#xy.com"
$toaddress = "me#xy.com "
$Subject = "Comparing 2 text files"
$login = "abc"
$password = "12345" | Convertto-SecureString -AsPlainText -Force
$smtpserver = "smtp.office.com"
$message = new-object System.Net.Mail.MailMessage
$message.From = $fromaddress
$message.To.Add($toaddress)
$message.IsBodyHtml = $True
$message.Subject = $Subject
$attach = new-object Net.Mail.Attachment($attachment)
$message.Attachments.Add($attach)
$message.body = $body
$message.Priority = [System.Net.Mail.MailPriority]::High
$smtp = new-object Net.Mail.SmtpClient($smtpserver, 587)
$smtp.EnableSsl = $true
$smtp.Credentials = New-Object System.Net.NetworkCredential($login, $password);
IF (Compare-Object -ReferenceObject (Get-Content $dir\file1.txt) -DifferenceObject (Get-Content $dir\file2.txt)){
Write-Output "The files are different "
$smtp.Send($message);
}
Else {
Write-Output "The files are not different"
}
I have looked at different online resources that suggested the use of Compare-Object cmdlet but it doesn't seem to work in my situation. The problem with my code is that, its returning everything from file1. Any one who knows why this is not working for me?
To compare two file and determine if they are identical use a file hash:
$hash1 = Get-FileHash $dir\file1.txt
$hash2 = Get-FileHash $dir\file2.txt
if($hash1 -eq $hash2){
'They are the same'
}else{
'They are NOT the same
}
This scans the 2 text files for differences and pipes it to a third File
$File1 = "C:\Scripts\Txt1.txt"
$File2 = "C:\Scripts\Txt2.txt"
$Location = "C:\Scripts\Txt3.txt"
compare-object (get-content $File1) (get-content $File2) | format-list | Out-File $Location
Output:
You're almost right with your initial script. You just need to add -IncludeEqual
See below. Text files 1 & 2 are just "hi there." Text file 3 is "hi there2"
$1 = gc 'H:\Skype Migrations\file1.txt'
$2 = gc 'H:\Skype Migrations\file2.txt'
$3 = gc 'H:\Skype Migrations\file3.txt'
IF ( ( Compare-Object -ReferenceObject $1 -DifferenceObject $3 -IncludeEqual ).sideindicator -ne "==" )
{ Write-Output "The files are different " }
Else
{ Write-Output "The files are not different" }
Edited to showcase how to use the IncludeEqual

Emailing a Hard Drive Disk Space Alert Using Powershell

I've been browsing the web trying to find a way if possible to email a low disk space alert from a Gmail account to a shared mail box using power shell but Im struggling with a query I've managed to piece together.
$EmailFrom = "FromEmail#Gmail.com"
$EmailTo = "ToEmail#Gmail.com"
$SMTPServer = "smtp.gmail.com"
$SMTPClient = New-Object Net.Mail.SmtpClient($SmtpServer, 587)
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential("Username", "Password");
$Computers = "Local Computer"
$Subject = "Disk Space Storage Report"
$Body = "This report was generated because the drive(s) listed below have less than $thresholdspace % free space. Drives above this threshold will not be listed."
[decimal]$thresholdspace = 50
$tableFragment = Get-WMIObject -ComputerName $computers Win32_LogicalDisk `
| select __SERVER, DriveType, VolumeName, Name, #{n='Size (Gb)' ;e={"{0:n2}" -f ($_.size/1gb)}},#{n='FreeSpace (Gb)';e={"{0:n2}" -f ($_.freespace/1gb)}}, #{n='PercentFree';e={"{0:n2}" -f ($_.freespace/$_.size*100)}} `
| Where-Object {$_.DriveType -eq 3} `
| ConvertTo-HTML -fragment
$regexsubject = $Body
$regex = [regex] '(?im)<td>'
if ($regex.IsMatch($regexsubject)) {$smtpclinet.send($fromemail, $EmailTo, $Subject, $Body)}
Script runs but nothing happens, any help would be fantastic!!!
My version would be longer because I'd have made a substitute for Send-MailMessage such that swapping between mine and Send-MailMessage is trivial.
This is one possible way of doing it. There are good uses for the Fragment parameter on ConvertTo-Html, but not much of a justification to do so here.
This is a script and expected to be a .ps1 file. Mandatory things I don't really want to hard-code beyond a default are set in the param block.
param(
[String[]]$ComputerName = $env:COMPUTERNAME,
[Decimal]$Theshold = 0.5,
[PSCredential]$Credential = (Get-Credential)
)
#
# Supporting functions
#
# This function acts in much the same way as Send-MailMessage.
function Send-SmtpMessage {
param(
[Parameter(Mandatory = $true, Position = 1)]
[String[]]$To,
[Parameter(Mandatory = $true, Position = 2)]
[String]$Subject,
[String]$Body,
[Switch]$BodyAsHtml,
[String]$SmtpServer = $PSEmailServer,
[Int32]$Port,
[Switch]$UseSSL,
[PSCredential]$Credential,
[Parameter(Mandatory = $true)]
[String]$From
)
if ([String]::IsNullOrEmtpy($_)) {
# I'd use $pscmdlet.ThrowTerminatingError for this normally
throw 'A value must be provided for SmtpServer'
}
# Create a mail message
$mailMessage = New-Object System.Net.Mail.MailMessage
# Email address formatting si validated by this, allowing failure to kill the command
try {
foreach ($recipient in $To) {
$mailMessage.To.Add($To)
}
$mailMessage.From = $From
} catch {
$pscmdlet.ThrowTerminatingError($_)
}
$mailMessage.Subject = $Subject
$mailMessage.Body = $Body
if ($BodyAsHtml) {
$mailMessage.IsBodyHtml = $true
}
try {
$smtpClient = New-Object System.Net.Mail.SmtpClient($SmtpServer, $Port)
if ($UseSSL) {
$smtpClient.EnableSsl = $true
}
if ($psboundparameters.ContainsKey('Credential')) {
$smtpClient.Credentials = $Credential.GetNetworkCredential()
}
$smtpClient.Send($mailMessage)
} catch {
# Return errors as non-terminating
Write-Error -ErrorRecord $_
}
}
#
# Main
#
# This is inserted before the table generated by the script
$PreContent = 'This report was generated because the drive(s) listed below have less than {0} free space. Drives above this threshold will not be listed.' -f ('{0:P2}' -f $Threshold)
# This is a result counter, it'll be incremented for each result which passes the threshold
$i = 0
# Generate the message body. There's not as much error control around WMI as I'd normally like.
$Body = Get-WmiObject Win32_LogicalDisk -Filter 'DriveType=3' -ComputerName $ComputerName | ForEach-Object {
# PSCustomObject requires PS 3 or greater.
# Using Math.Round below means we can still perform numeric comparisons
# Percent free remains as a decimal until the end. Programs like Excel expect percentages as a decimal (0 to 1).
[PSCustomObject]#{
ComputerName = $_.__SERVER
DriveType = $_.DriveType
VolumeName = $_.VolumeName
Name = $_.Name
'Size (GB)' = [Math]::Round(($_.Size / 1GB), 2)
'FreeSpace (GB)' = [Math]::Round(($_.FreeSpace / 1GB), 2)
PercentFree = [Math]::Round(($_.FreeSpace / $_.Size), 2)
}
} | Where-Object {
if ($_.PercentFree -lt $Threshold) {
$true
$i++
}
} | ForEach-Object {
# Make Percentage friendly. P2 adds % for us.
$_.PercentFree = '{0:P2}' -f $_.PercentFree
$_
} | ConvertTo-Html -PreContent $PreContent | Out-String
# If there's one or more warning to send.
if ($i -gt 0) {
$params = #{
To = "ToEmail#Gmail.com"
From = "FromEmail#Gmail.com"
Subject = "Disk Space Storage Report"
Body = $Body
SmtpServer = "smtp.gmail.com"
Port = 587
UseSsl = $true
Credential = $Credential
}
Send-SmtpMessage #params
}

Converting Hashtable in Powershell

I need some help outputting a a hash table to the body of an email message.
$computer = "adwte998"
$err = "This is the error"
$td = Get-Date
$table = #{
Workstation = $computer
Error = $err
Time = $td
}
send-mailmessage -to "email#email.com" -from "Automated Reboot<no-reply#email.com>" -subject "E-Mail HashTable Test" -body ($table | Out-String) -smtpserver smtpserver.email.com
The Message body looks like it should if i just returned the $table
I would ideally like to convert the table to a format that looks like a CSV with the proper columns and headers but I don't want to email it as an attachment.
Does anyone know how i can go about doing this? I would even considering converting it to HTML so it looks nice in the email.
Using V3:
$computer = "adwte998"
$err = "This is the error"
$td = Get-Date
$table = [ordered]#{
Workstation = $computer
Error = $err
Time = $td
}
[PSCustomObject]$table | ft -auto | out-string
Workstation Error Time
----------- ----- ----
adwte998 This is the error 10/18/2013 1:26:08 PM
for HTML:
[PSCustomObject]$table | convertto-html