importing HTML and outputting with variables - html

I'm looking for a method to import a template HTML file in PowerShell and being able to populate it with variables, which in turn sends an e-mail in HTML format containing user data.
I already know how to send the HTML e-mail. And, currently have HTML in a 'here' string embedded in the code. I want to take it a step further, by being able to grab an HTML template based on country code. So, if the user is in the US, it'll get a English HTML data filled e-mail, if they're dutch, they'll get it in Dutch, etc.
function SendMessage {
Param(
[Parameter(Position=0,Mandatory=$true)]
[string]$Identity,
[Parameter(Position=1,Mandatory=$true)]
[string]$Body
)
$Subject = "Important information - Do not delete this email. Welcome to Voicemail"
$SmtpClient = New-object system.net.mail.smtpClient
$MailMessage = New-Object system.net.mail.mailmessage
$CredentialFile = ".\UMcloud-creds.txt"
$password = Get-Content $CredentialFile| ConvertTo-SecureString -Force
$UMCloudAdmin = ""
$SmtpClient.Credentials = New-Object System.Net.NetworkCredential($UMCloudAdmin, $Password)
$smtpclient.Host = "smtp-in.net"
$MailMessage.From = "Voicemail <P#domain.net>"
$MailMessage.To.clear()
$MailMessage.To.Add($Identity)
$MailMessage.Subject = $Subject
$Mailmessage.Body = $body
$MailMessage.IsBodyHtml = 1
$Logofilepath = ((Resolve-Path .\).Path) + "\logo.jpg"
$attachment = New-Object System.Net.Mail.Attachment -ArgumentList $LogoFilePath
$attachment.ContentDisposition.Inline = $True
$attachment.ContentDisposition.DispositionType = "Inline"
$attachment.ContentType.MediaType = "image/jpg"
$attachment.ContentId = "logo.jpg"
$MailMessage.Attachments.Add($attachment)
do {
$Continue = $false
try {
$smtpclient.Send($MailMessage)
Write-LogFile $OutputLogFile ("[SUCCESS] {0} {1}" -f $identity, $UMExtension)
Write-LogFile $customemaillog ("[SUCCESS] {0} {1}" -f $identity, $UMExtension) | out-null
$Continue = $true
} catch {
sleep -s 10
Write-LogFile $OutputLogFile "[ERROR] $Identity $_.Exception.Message"
Write-LogFile $CustomEmailLog "[ERROR] $Identity $UMExtension"
Write-Error $_.Exception.Message
}
} until($Continue -eq $true)
} # End send message
$WelcomeText = Get-Content -Path ".\$CountryID.txt"
$Body = #"
<html>
...
</html>
"#
The here string is part of the script, I'd like to be able to import it from TXT file as not to clutter the script.

Instead of using a text file, create a powershell file (ps1) for each html language format you want. Within those files, you can set a single variable as the html text (file EN_US.ps1):
$bodyENUS = #"Dear <b><font color=red>user</b></font> <br>
This is a test <b>HTML</b> email for your language preference<br>
Sincerely,<br> PdeRover<br>"
You can then pass the variable into the main ps file using two ways: Dot Sourcing or using a Global Variable.
Dot Sourcing: calling the variable by providing the file name.
In the main file:
..\EN_US.ps1
..\PT_PT.ps1
$smtp = "Exchange-Server"
$to = $Identity
$from = "Voicemail <P#domain.net>"
$subject = "This is a Test of HTML Email"
if (user is English speaking){
$bodyByLang = $bodyENUS}
elseif (user is Portuguese speaking) {
$bodyByLang = $bodyPTPT}
send-MailMessage -SmtpServer $smtp -To $to -From $from -Subject $subject -Body $bodyByLang -BodyAsHtml -Priority high
Global Variable: prefixing a variable with $Global:and calling the file during runtime. $Global: bodyENUS Then calling it using $bodyENUS
I asked my own SO question about the difference/best practice of using them. May be worth a read.
This should be enough to point you in the right direction.

Related

How do I dynamically insert a string into a text file in PowerShell?

The text is an html file. The line I am interested in looks something like:
<td>INC1234</td><td>INC1235</td><td>INC1236</td>
The INC numbers are different from file to file. I'd like to parse through the line by saying something like:
if like <td>INC, then concatenate '<td><a href="https://www.website.com/=' + INC# + '>"
To give an output like:
<td><a href="https://www.website.com/=INC1234>INC1234</a></td><td><a href="https://www.website.com/=INC1235>INC1235</a></td><td><a href="https://www.website.com/=INC1236>INC1236</a></td>"
EDIT1: Ok, if I do something like:
$parse = (-split (Get-Content -Raw C:\Temp\report.txt) -match '<td>INC')
$parse
It will find the characters, but it will return the entire line rather than looking for more that match the 'INC'. Presumably because they all reside on the same line with no spaces.
EDIT2: Maybe this will help. What I'm doing is using PowerShell to write SQL commands, send it to our SQL Server, return the data and use PSWriteHTML to build the report which works fantastic. But I am wanting the first column, which is the ticket number (eg. INC1234) to be a link to the ticket it reads.
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[string]$ReportName
)
## Build the query box
function Read-MultiLineInputBoxDialog([string]$Message, [string]$WindowTitle, [string]$DefaultText)
{
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Windows.Forms
## Create the Label.
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Size(10,10)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.AutoSize = $true
$label.Text = $Message
## Create the TextBox used to capture the user's text.
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Size(10,40)
$textBox.Size = New-Object System.Drawing.Size(575,200)
$textBox.AcceptsReturn = $true
$textBox.AcceptsTab = $false
$textBox.Multiline = $true
$textBox.ScrollBars = 'Both'
$textBox.Text = $DefaultText
## Create the OK button.
$okButton = New-Object System.Windows.Forms.Button
$okButton.Location = New-Object System.Drawing.Size(415,250)
$okButton.Size = New-Object System.Drawing.Size(75,25)
$okButton.Text = "OK"
$okButton.Add_Click({ $form.Tag = $textBox.Text; $form.Close() })
## Create the Cancel button.
$cancelButton = New-Object System.Windows.Forms.Button
$cancelButton.Location = New-Object System.Drawing.Size(510,250)
$cancelButton.Size = New-Object System.Drawing.Size(75,25)
$cancelButton.Text = "Cancel"
$cancelButton.Add_Click({ $form.Tag = $null; $form.Close() })
## Create the form.
$form = New-Object System.Windows.Forms.Form
$form.Text = $WindowTitle
$form.Size = New-Object System.Drawing.Size(610,320)
$form.FormBorderStyle = 'FixedSingle'
$form.StartPosition = "CenterScreen"
$form.AutoSizeMode = 'GrowAndShrink'
$form.Topmost = $True
$form.AcceptButton = $okButton
$form.CancelButton = $cancelButton
$form.ShowInTaskbar = $true
## Add all of the controls to the form.
$form.Controls.Add($label)
$form.Controls.Add($textBox)
$form.Controls.Add($okButton)
$form.Controls.Add($cancelButton)
## Initialize and show the form.
$form.Add_Shown({$form.Activate()})
$form.ShowDialog() > $null # Trash the text of the button that was clicked.
## Return the text that the user entered.
return $form.Tag
}
## Prompt the SQL Query Box
$Query = Read-MultiLineInputBoxDialog -Message "Enter SQL Query Here" -WindowTitle "SQL Query" -DefaultText "SELECT FROM"
if ($Query -eq $null) { Break }
else { Write-Host "You entered the following text: $Query" }
## Pass query to SQL Server
$Pass = Invoke-Sqlcmd -Query $Query -ServerInstance "MY-SERVER-INSTANCE" -Username "USERNAME" -Password "PASSWORD"
## Output the report and save to the network under the specified name
New-HTML {
New-HTMLTable -EnableColumnReorder -DisableInfo -DataTable $Pass -ExcludeProperty "RowError", "RowState", "Table", `
"ItemArray", "HasErrors" -HideFooter -PagingLength 25 -SearchBuilder
New-HTMLTableStyle -FontFamily Calibri -FontSize 15 -FontStyle normal -TextAlign center -TextColor "#0a0a0a"
New-HTMLTableStyle -FontFamily Calibri -BackgroundColor "#fffdb5" -FontSize 15px -TextColor "#0a0a0a" -TextAlign center -Type RowHover
} -ShowHTML -FilePath "\\Server\$ReportName.html" -Online
The report looks something like:
Ticket: Description:
----------------------------
INC1234 Broken Monitor
INC1235 No Sound
The HTML that PSWriteHTML builds throws all of the ticket numbers on one line so I would like to edit that HTML with the <a href=""> tag to dynamically create links for each ticket # mainly because I don't know how to do it in PS and can't seem to find a good answer through Google - which is why I came here.
Although not familiar with PsWriteHtml, I guess you could simply change the Ticket properties in the array you receive in $Pass:
## Pass query to SQL Server
$Pass = Invoke-Sqlcmd -Query $Query -ServerInstance "MY-SERVER-INSTANCE" -Username "USERNAME" -Password "PASSWORD"
# convert all Tickets into hyperlinks
foreach ($item in $Pass) {
$item.Ticket = '<a href="https://www.website.com/={0}>{0}</a>'-f $item.Ticket
}
Then the rest of your code
## Output the report and save to the network under the specified name
New-HTML {...}

converting Splatted Array to Formatted HTML email in power shell without creating a file

hi upon creation of a new user im wanting to take the $newuserParams, and put them in a nice formatted email. with each parameter on a new line.
ive got either the splatted params outputting all as one big line with | out-string, or i get this
IsReadOnly IsFixedSize IsSynchronized Keys Values SyncRoot Count
False False False System.Collections.Hashtable+KeyCollection System.Collections.Hashtable+ValueCollection System.Object 22
if i use ConvertTo-HTML
i basically want to do both but chaingin both pipes doesn't work haha, how do i make the array populate so that i can use the ConvertTo-HTML
i want to keep it part of the $mailbody.add, as i have other things adding to $mailbody.
$NewUserParams = #{
SamAccountName = $username
Name = "$firstname $surname"
DisplayName = "$firstname $surname"
UserPrincipalName = "$username#cyclone.com"
GivenName = $firstname
Surname = $surname
AccountPassword = $securePassword
Enabled = $false
Path = "OU=$NewUserOU,$domainpath" #change to switch based of Users Branch
City = $_.City
Country = $_.Country #NOTE: This Feild must be the 2 digit Country Code, NOT the String Name of athe Country.
company = $_.CompanyName
department = $_.OrgDepartmentName
Employeeid = $_.EmployeeId
mobile = $_.Mobile
Manager = $_.Manager
Office = $_.Branch
postalCode = $_.PostalCode
POBox = $_.PostOfficeBox
scriptPath = $_.scriptPath
StreetAddress = $_.StreetAddress
Title = $_.Title
}
try {
#Create the NEw user, and make them change password on first Logon
New-ADUser #NewUserParams -ErrorAction Stop
Set-ADUser -Identity $username -ChangePasswordAtLogon $true
# $CurrentAttributes = Get-ADUser -Identity $username -Properties *
[void]$mailBody.add("<h1>NEW User Created</h1> $($newUserParams| ConvertTo-Html)")
#Send-MailMessage #MailParams
Write-Host "email sent to Admin"
}
catch {
throw
Write-Warning "Could not create account $username. $($_.Exception.Message)"
}
}
if ($mailbody -ne #()) {
Write-Host "Change Added to mail"
"$(Get-Timestamp )USER Attribute Change $mailbody" | Out-File c:\log.txt -Append
Send-MailMessage #MailParams -BodyAsHtml -body "<b>$(Get-TimeStamp): USER UPDATE </b> <p>$mailbody</p>"
}
}
A quick shortcut; since $NewUserParams is already a hash, cast it to a PSCustom object.
$NewObject = [PSCustomObject]$NewUserParams
At that point you've got all sorts of options including ConvertTo-Html.
Personally I like to pipe to Format-List | OutString Then just slap a \<PRE>...\</PRE> around it. I like to use a mono-spaced font so the HTML will have the same table or list spacing just like you get in the PowerShell console. I frequently do this with Format-Table as well.
Reading through the ConvertTo-Html documentation it has the following:
Converts a .NET object into HTML that can be displayed in a Web browser.
The reason behind this behavior is Hashtables have key and value pairs opposed to PSCustomObject which is properties, so ConvertTo-Html treats them differently and you get the behavior you are experiencing.
#$ADObject | ForEach-Object {
$NewUserParams = #{
SamAccountName = $username
Name = "$firstname $surname"
DisplayName = "$firstname $surname"
UserPrincipalName = "$username#cyclone.com"
GivenName = $firstname
Surname = $surname
AccountPassword = $securePassword
Enabled = $false
Path = "OU=$NewUserOU,$domainpath" #change to switch based of Users Branch
City = $_.City
Country = $_.Country #NOTE: This Field must be the 2 digit Country Code, NOT the String Name of athe Country.
company = $_.CompanyName
department = $_.OrgDepartmentName
Employeeid = $_.EmployeeId
mobile = $_.Mobile
Manager = $_.Manager
Office = $_.Branch
postalCode = $_.PostalCode
POBox = $_.PostOfficeBox
scriptPath = $_.scriptPath
StreetAddress = $_.StreetAddress
Title = $_.Title
}
try {
#Create the Nww user, and make them change password on first Logon
New-ADUser #NewUserParams -ErrorAction Stop
[PSCustomObject]$NewUserParams = $NewUserParams
Set-ADUser -Identity $username -ChangePasswordAtLogon $true
# $CurrentAttributes = Get-ADUser -Identity $username -Properties *
[void]$mailBody.add("<h1>NEW User Created</h1> $($newUserParams| ConvertTo-Html)")
#Send-MailMessage #MailParams
Write-Host "email sent to Admin"
}
catch {
throw
Write-Warning "Could not create account $username. $($_.Exception.Message)"
}
}
if ($mailbody -ne #()) {
Write-Host "Change Added to mail"
"$(Get-Timestamp )USER Attribute Change $mailbody" | Out-File c:\log.txt -Append
Send-MailMessage #MailParams -BodyAsHtml -body "<b>$(Get-TimeStamp): USER UPDATE </b> <p>$mailbody</p>"
}
}
Successful output of generating HTML:

How do I get EWS conflictresolutionmode to work

I will need to go through many mailboxes and remove 'copy:' from the subject line of lots of meetings. At the moment the code works fine except that after the code performs the conflictresoltuionmode, alwaysoverwrite and I can see the successful result it does not save the update.
I have tried using the save function but its not working
#Variables and constants
$startdate = Get-Date
$enddate = $startdate.AddDays(365)
#$string = 'check'
#create a remote session to exchange
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://exchangeserver.com/PowerShell/ -Authentication Kerberos
Import-PSSession $Session -DisableNameChecking:$disablenamechecking
$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
[Void] [Reflection.Assembly]::LoadFile($dllPath)
$MailboxName = "firstname.lastname#company.com"
$ExchVer = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2
$exchService = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchVer)
$exchservice.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
$exchService.UseDefaultCredentials = $true
$exchService.AutodiscoverUrl($MailboxName)
$Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchservice,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar)
$CalendarView = New-Object Microsoft.Exchange.WebServices.Data.CalendarView($StartDate,$EndDate,1000)
$FindItems = $calendar.FindAppointments($CalendarView)
if($FindItems.Items.Count -gt 0)
{
foreach($Item in $finditems){
if ($item.Subject -like "check*") {
$item.subject.substring(6)
**$item.update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverWrite)
# the overwrite is successful but it's not saving**
#$item.Subject
}
#$ItemColl.Add($Item)
}
}
$item.Subject
Your code doesn't appear to actually make any change eg
$item.subject.substring(6)
Would simply print the first 6 character of the Subject if you wanted to change the subject you would need to have
$item.subject = "blah blah etc"
You don't have to call save as its only valid if the object is new all that is need is Update but it will only do anything if there is a property that has been changed which there isn't in the above example.

Embed images in email and send via Powershell

I am editing one of my old scripts to send an email to a user with images embedded into the text. I am trying to use the Send-MailMessage function to send the email as opposed to the older method of $smtp.send($msg). However, when trying to update the script, the images are no longer being embedded.
I know how to attach them to the email as actual attachments, but I am not sure what I am doing wrong to have them show as actual embedded images.
NOTE: for brevity, I removed some of the full email since it is large and as long as I can get an image or two working, it will all work.
# force powershell to run as an x86 process
Set-ExecutionPolicy -Scope CurrentUser Unrestricted
if ($env:Processor_Architecture -ne "x86") {
&"$env:windir\syswow64\windowspowershell\v1.0\powershell.exe" -file $myinvocation.Mycommand.path
exit
}
# initialize the script
if ($startupvariables) { try {Remove-Variable -Name startupvariables -Scope Global -ErrorAction SilentlyContinue } catch { } }
New-Variable -force -name startupVariables -value ( Get-Variable | ForEach-Object { $_.Name } )
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null
$Separator = ".", "#"
# advise what the script does
Add-Type -AssemblyName PresentationCore,PresentationFramework
$ButtonType = [System.Windows.MessageBoxButton]::OKCancel
$MessageIcon = [System.Windows.MessageBoxImage]::Warning
$MessageTitle = "Shared Drive Access Assistance"
$MessageBody = "This script asks the user to provide more information regarding a network drive that they would like access to.`n`nTo use it, enter the below information:`n`n`n`tTicket Number`n`n`tUser's Email Address`n`n`tRequestor's Email Address`n`n`nIf this is the script you want to use, click OK.`nIf not, click Cancel."
$Result = [System.Windows.MessageBox]::Show($MessageBody,$MessageTitle,$ButtonType,$MessageIcon)
if ($Result -eq "Cancel")
{
Exit-PSSession
}
else
{
# get the ticket number
$Ticket = [Microsoft.VisualBasic.Interaction]::InputBox("Enter the SCTask ticket number" , "Ticket Number")
# get the user id via the email address
$UserID = [Microsoft.VisualBasic.Interaction]::InputBox("Enter the user's email address" , "User Email Address")
$User = $UserID.split($Separator)
$Firstname = $User[0].substring(0,1).toupper()+$User[0].substring(1).tolower()
$Lastname = $User[1].substring(0,1).toupper()+$User[1].substring(1).tolower()
$User = $Firstname, $Lastname
# get local username
$Username = [System.Environment]::UserName
# create email
$subject = "Ticket $Ticket on Hold for User Response - Shared Drive Access for $User - Awaiting Additional Information"
$body = #"
<html>
<body style="font-family:calibri">
To $Requestor, $User,<br>
<br>
<br>
In order to proceed with your request for shared drive access, we require the server name and full path to the folder you need access to. If you do not already know this information, you will need to provide these instructions to someone that already has access to the folder that you need access to.<br>
<br>
1) Click the Start menu<br>
<br>
<img src="cid:image1.png"><br>
<img src="cid:image2.png"><br>
<img src="cid:image3.png"><br>
<br>
<br>
2) Navigate to "Computer"<br>
<br>
<img src="cid:image4.png"><br>
<br>
<br>
<br>
If you have any questions or need assistance with this process, please contact the Service Desk via one of the methods listed below.
<br>
<br>
Thank You,<br>
<br>
IT Service Desk<br>
</body>
</html>
"#
$att1 = new-object Net.Mail.Attachment ("T:\PS Scripts\Images\shareddrive1.png")
$att2 = new-object Net.Mail.Attachment ("T:\PS Scripts\Images\shareddrive2.png")
$att3 = new-object Net.Mail.Attachment ("T:\PS Scripts\Images\shareddrive3.png")
$att4 = new-object Net.Mail.Attachment ("T:\PS Scripts\Images\shareddrive4.png")
$att1.ContentId = "image1.png"
$att2.ContentId = "image2.png"
$att3.ContentId = "image3.png"
$att4.ContentId = "image4.png"
# $msg.Attachments.Add($att1)
# $msg.Attachments.Add($att2)
# $msg.Attachments.Add($att3)
# $msg.Attachments.Add($att4)
# create confirmation message
$ButtonType = [System.Windows.MessageBoxButton]::YesNo
$MessageIcon = [System.Windows.MessageBoxImage]::Warning
$MessageTitle = "Shared Drive Access Assistance"
$MessageBody = "The information you have entered is show below:`n`n`nTicket Number: $Ticket`n`nUser's Email Address: $UserID`n`nRequstor's Email Address: $RequestorID`n`n`nIf you would like to send the email, click Yes.`nOtherwise, click No."
$Result = [System.Windows.MessageBox]::Show($MessageBody,$MessageTitle,$ButtonType,$MessageIcon)
if ($Result -eq "No")
{
Exit-PSSession
}
else
# send email
{
Send-MailMessage -To "<$UserID>" -bcc "<$Username#dana.com>" -from "<itservicedesk#x.com>" -Subject $global:subject -SmtpServer "mailrelay.x.com" -BodyAsHtml -body $global:body
}
}
Function Clean-Memory {
Get-Variable |
Where-Object { $startupVariables -notcontains $_.Name } |
ForEach-Object {
try { Remove-Variable -Name "$($_.Name)" -Force -Scope "global" -ErrorAction SilentlyContinue -WarningAction SilentlyContinue}
catch { }
}
}
So the real question is how to embed a image into the HTML document from a attachment
CID aka Content ID will allow you to attach a image and then use that attached image in the document. Avoid using spaces in the Content ID name.
Send-MailMessage -To "Test#Test.com" -from "Test2#Test.com" -SmtpServer SMTP.TEST.NET -Subject "Hello" -BodyAsHtml -Body "<img src='cid:Test.png'>" -Port 25 -Attachments "C:\Users\Test\Test.png"
You are using
$att1 = new-object Net.Mail.Attachment ("T:\PS Scripts\Images\shareddrive1.png")
$att2 = new-object Net.Mail.Attachment ("T:\PS Scripts\Images\shareddrive2.png")
$att3 = new-object Net.Mail.Attachment ("T:\PS Scripts\Images\shareddrive3.png")
$att4 = new-object Net.Mail.Attachment ("T:\PS Scripts\Images\shareddrive4.png")
$att1.ContentId = "image1.png"
$att2.ContentId = "image2.png"
$att3.ContentId = "image3.png"
$att4.ContentId = "image4.png"
but when you send the mail you are not attaching those attachments
Send-MailMessage -To "<$UserID>" -bcc "<$Username#dana.com>" -from "<itservicedesk#x.com>" -Subject $global:subject -SmtpServer "mailrelay.x.com" -BodyAsHtml -body $global:body
You could stop using the Net.Mail.Attachment and instead do something like
$Body = #"
<html>
<body style="font-family:calibri">
<b>This is image 1</b>
<img src='cid:TEST1.png'>
<b>This is image 2</b>
<img src='cid:Test2.png'>
</body>
</html>
"#
Send-MailMessage -To "Test#Test.com" `
-from "Test2#Test.com" `
-SmtpServer Test.smtp.com `
-Subject "Hello" `
-BodyAsHtml -body $body `
-Attachments "C:\Test\TEST1.png", "C:\Test\TEST2.png"
You can embed the image into the HTML, therefore there are no extra files.
It is replacing
{img=file}
with data URL
{src="data:image/png;base64,[base64 encoded long string representing the image]}
There are many online converters that can convert your image file into the required code.
Just google for Image to Data URI converter.
Here is a simple example using MailKit. I had do download the packages from https://www.nuget.org/packages/MimeKit and https://www.nuget.org/packages/MailKit and unpack with 7Zip as PowerShell was struggling to install from the CLI. I got some ideas from here but wanted to add an example of an embedded image, https://adamtheautomator.com/powershell-email.
You can do all sorts with MimeKit and MailKit including modern Auth which is great. It's also supper fast and the Client and even a Connection can be used in a loop to send lots of messages. There is probably an upper bound on how long to use a connection without refreshing?
$mailServer = "domain-com.mail.protection.outlook.com"
$SMTP = New-Object MailKit.Net.Smtp.SmtpClient
Add-Type -Path ".\mailkit.3.3.0\netstandard2.0\MailKit.dll"
Add-Type -Path ".\mimekit.3.3.0\netstandard2.0\MimeKit.dll"
$SMTP.Connect($mailServer, 25, [MailKit.Security.SecureSocketOptions]::StartTls, $False)
$Message = New-Object MimeKit.MimeMessage
$message.From.Add("a.person#domain.com");
$message.To.Add("b.person#domain.com");
$message.Subject = "Some Subject";
$builder = [MimeKit.BodyBuilder]::new();
$image = $builder.LinkedResources.Add($path);
$image.ContentId = [MimeKit.Utils.MimeUtils]::GenerateMessageId();
$body = #"
<html>
<body style="font-family:calibri">
<b>This is image 1</b>
<img src='cid:$($image.ContentId)'>
</body>
</html>
"#
$builder.HtmlBody = $body
#Now we just need to set the message body and we're done
$message.Body = $builder.ToMessageBody();
$SMTP.Send($Message)
$SMTP.Disconnect($true)
$SMTP.Dispose()

Resolve DNS, export to Excel and HTML, then send mail

I was almost done with my script and did some late night editing and written over my old version so I cannot go back.
The script was running fine, still needed some tweaks but now it ha come to a complete halt.
The idea is to GC a list of IP's. Resolve the IP's and place them in an excel sheet. Then save the sheet to htm and xlsx. And finally mailing those to me.
Now it gets stuck on sorting the sheet, saving AND mailing...
Can someone give me some insight on what I did wrong here?
it gets stuck on sorting the sheet, saving AND mailing.
It no longer sorts B3:B$Count:
Exception calling "Sort" with "1" argument(s): "The sort reference is not valid. Make sure that it's within the data you want to sort, and the first Sort By box isn't the same or blank."
At C:\Folder\Scripts\Get-IP.ps1:137 char:5
+ [void] $objRange.Sort($objRange2)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
It no longer saves the xlsx file, but does save the HTM file. It is clearly not overwriting something. I even restarted to make sure.
Exception calling "SaveAs" with "1" argument(s): "Microsoft Excel cannot access the file 'C://Folder/BlockedIP/HTML/2014-07-08/0BCEF810'. workbook."
At C:\Folder\Scripts\Get-IP.ps1:160 char:5
+ $b.SaveAs("$FileXML")
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : ComMethodTargetInvocation
And finally, it will no longer send me e-mails:
New-Object : Exception calling ".ctor" with "2" argument(s): "The specified string is not in the form required for an e-mail address."
At C:\Folder\Scripts\Get-IP.ps1:217 char:13
+ $SMTP = New-Object System.Net.Mail.MailMessage($SMTP, 587)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException
+ FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
The script:
#Get current date
$Date = get-date -format yyyy-MM-dd
#Define all files/Paths.
$Path = "C:/Folder/BlockedIP"
md "$Path/HTML/$Date" -Force
$path2 = "$Path/HTML/$Date"
$PathWeb = "/HTML/$Date"
#Define File's used or created in this script.
$File = "$Path/IP-$Date.txt"
$FileHtml = "$Path2/IP-$Date.htm"
$FileXML = "$Path2/IP-$Date.xlsx"
$FileHTMLWeb = "$PathWeb/IP-$date.htm"
#Get content from given IP list.
$colComputers = #(get-content $File | Sort -unique)
$count = $colComputers.Count
write-output "$Count IP's detected."
#Define error actions.
#$erroractionpreference = "SilentlyContinue"
#Open Excel.
$a = New-Object -comobject Excel.Application
#Since we want this script to look like it's being used without excel I set it's visibility to false.
$a.visible = $True
#Disable excel confirmations.
$a.DisplayAlerts = $False
#Create sheets in Excel.
$b = $a.Workbooks.Add()
$c = $b.Worksheets.Item(1)
#Create a Title for the first worksheet and adjust the font
$row = 1
$Column = 1
target="_parent">Creator'
$c.Cells.Item($row,$column)= "Blocked IP's $Date"
$c.Cells.Item($row,$column).Font.Size = 18
$c.Cells.Item($row,$column).Font.Bold=$True
$c.Cells.Item($row,$column).Font.Name = "Cambria"
$c.Cells.Item($row,$column).Font.ThemeFont = 1
$c.Cells.Item($row,$column).Font.ThemeColor = 4
$c.Cells.Item($row,$column).Font.ColorIndex = 55
$c.Cells.Item($row,$column).Font.Color = 8210719
$range = $c.Range("a1","e1")
$range.Merge() | Out-Null
$range.VerticalAlignment = -4160
#Define subjects.
$c.Name = "Blocked IP's ($Date)"
$c.Cells.Item(2,1) = "Given IP"
$c.Cells.Item(2,2) = "Resolved DNS"
$c.Cells.Item(2,3) = "Returned IP"
$c.Cells.Item(2,5) = "Company name"
#Define cell formatting from subjects.
$c.Range("A2:E2").Interior.ColorIndex = 6
$c.Range("A2:E2").font.size = 13
$c.Range("A2:E2").Font.ColorIndex = 1
$c.Range("A2:E2").Font.Bold = $True
#Define the usedrange for autofitting.
$d = $c.UsedRange
#Make everything fit in it's cell
$D.EntireColumn.AutoFit() | Out-Null
#Define html code for Excel save to .htm.
$xlExcelHTML = 44
#Define rows to alter in excel.
$iRow = 3
$intRow = 3
#Time to run the script.
foreach ($strComputer in $colComputers)
{
#Place IP's from text in the excel sheet
$c.Cells.Item($intRow, 1) = $strComputer.ToUpper()
$d.EntireColumn.AutoFit() | Out-Null
#Create a status bar for the script
$i = 1
Write-Progress -Activity `
"Creating a usable 'Blocked IP' list ($i/$count)" `
-PercentComplete ($i/$colComputers.Count*100) `
-Status "Please stand by"
try {
$dnsresult = [System.Net.DNS]::GetHostEntry($strComputer)
}
catch {
$dnsresult = "$null"
}
#Clear screen on every checked IP to remove the 'True' statement.
#cls
#Do something with $dnsresults.
#Display information about host
#Give hostname Entry in Cell2
$c.Cells.Item($intRow,2) = $dnsresult.HostName
#IP listed in Cell 3
$c.Cells.Item($intRow,3) = $dnsresult.AddressList[0].IpAddressToString
#Make everything fit in it's cell.
$d.EntireColumn.AutoFit() | Out-Null
#Define row for the IP list.
$intRow = $intRow + 1
#Set background color for the IP list.
$d.Range("A$($iRow):E$($intRow)").interior.colorindex = 15
#Sort all IP's on resolved name.
$objWorksheet = $b.Worksheets.Item(1)
$objRange = $objWorksheet.UsedRange
$objRange2 = $objworksheet.Range("B3:B($Count)")
[void] $objRange.Sort($objRange2)
#Define borders here.
<# Insert script :D #>
#Define Filters here. (Picking out blank DNS and giving those a name)
<# Insert script :D #>
#Define Filters here. (Picking out specific DNS name and give them color code)
<# Insert script :D #>
#Make everything fit in it's cell.
$d.EntireColumn.AutoFit() | Out-Null
#Clear screen on every checked IP to remove the 'True' statement.
#cls
}
#Save the file as .xlsx on every placed IP to ensure the file is not lost due to any reason.
$b.SaveAs("$FileXML")
#Save final result as a .htm file
$b.SaveAs("$FileHTML",$xlExcelHTML)
#Close and quit Excel.
$b.Close()
get-process *Excel* | Stop-Process -force
#Move .txt file to the correct HTML folder.
move-item $file $path2 -Force
#Clear screen, again. (Let's keep things tidy.)
#cls
#Variables for public IP
# I am defining website url in a variable
$url = "http://checkip.dyndns.com"
# Creating a new .Net Object names a System.Net.Webclient
$webclient = New-Object System.Net.WebClient
# In this new webdownlader object we are telling $webclient to download the
# url $url
$IpPublic = $webclient.DownloadString($url)
# Just a simple text manuplation to get the ipadress form downloaded URL
# If you want to know what it contain try to see the variable $IpPublic
$IpPublic2 = $IpPublic.ToString()
$ipPublic3 = $IpPublic2.Split(" ")
$ipPublic4 = $ipPublic3[5]
$ipPublic5 = $ipPublic4.replace("</body>","")
$FinalIPAddress = $ipPublic5.replace("</html>","")
#Variables e-mail.
$From = "Blocked IP <###g##.com>"
$To = "IT Dept <#####.nl>"
$CC = "Someone <##r###.nl"
$SMTP = "smtp.gmail.com"
$Subject = "Blocked IPs for $date ($Count Total)"
#The href should point to the htm file in your iis/apache folder.
$WebLink = $FinalIPAddress+$FileHtmlWeb
$here = "<a href='http://$Weblink'><b>Here</b></a>"
#Define the body of your e-mail, in this case it displays a message and shows the server it is send from with it's local IP.
#A link to the .htm file, how many IP's were blocked and the date of the message.
$Body = "This is an automated message generated by server: $env:COMPUTERNAME, $IP</br></br>
Please see the attachment or click $here to get the $Count blocked IP's of $date. </br> </br></br>"
#Variables e-mail user.
$username = "#####.com"
$password = "##"
$secstr = New-Object -TypeName System.Security.SecureString
$password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$Cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr
$ip = (Get-WmiObject -class win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"').ipaddress[0]
#Clear screen, again. (Let's keep things tidy.)
#cls
#Send output as e-mail.
$SMTP = New-Object System.Net.Mail.MailMessage($SMTP, 587)
$SMTP.EnableSsl = $true
$SMTP.Credentials = New-Object System.Net.NetworkCredential("$username", "$password");
$SMTP.isbodyhtml= $true
$SMTP.Send($From, $To, $Subject, $FileXML, $Body)
send-mailmessage -BodyAsHtml -from $From -to $To -cc $CC -subject $Subject -Attachments $FileXML -body $Body -priority High -smtpServer $SMTP -credential ($cred) -usessl
#Create a function to relase Com object at end of script.
function Release-Ref ($ref) {
([System.Runtime.InteropServices.Marshal]::ReleaseComObject(
[System.__ComObject]$ref) -gt 0)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
#Release COM Object
[System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$a) | Out-Null
#Clear screen for the final time. (Let's keep things tidy.)
#cls
#Exit powershell
exit
Any help will be greatly appreciated!
Exception calling "Sort" with "1" argument(s): "The sort reference is not valid. Make sure that it's within the data you want to sort, and the first Sort By box isn't the same or blank."
Double-check that $objRange and $objRange2 reference the correct ranges:
$objRange.Address()
$objRange2.Address()
Not much else I can tell you here without seeing your actual data.
Exception calling "SaveAs" with "1" argument(s): "Microsoft Excel cannot access the file 'C://Folder/BlockedIP/HTML/2014-07-08/0BCEF810'. workbook."
If the path really were C://Folder/... you'd be getting a different exception. Please do not fabricate error messages.
New-Object : Exception calling ".ctor" with "2" argument(s): "The specified string is not in the form required for an e-mail address."
You're confusing MailMessage and SmtpClient class. Not to mention that you don't even need either of them, since you're using Send-MailMessage anyway. Just remove the following 5 lines:
$SMTP = New-Object System.Net.Mail.MailMessage($SMTP, 587)
$SMTP.EnableSsl = $true
$SMTP.Credentials = New-Object System.Net.NetworkCredential("$username", "$password");
$SMTP.isbodyhtml= $true
$SMTP.Send($From, $To, $Subject, $FileXML, $Body)
Solved the sorting problem by altering the code to:
$objRange = $c.Range("A$($iRow):E$($intRow)")
$objRange2 = $c.Range("B$($iRow):B$($intRow)")
[void] $objRange.Sort($objRange2)
Turns out it got stuck on the header in the xml file
Replaced mailing with:
$From = "Blocked IP <#####.##>"
$To = "IT Dept <#####.##>"
$CC = "Someone <#####.##"
$Subject = "Blocked IPs for $date ($Count Total)"
#The href should point to the htm file in your iis/apache folder.
$WebLink = $FinalIPAddress+$FileHtmlWeb
$here = "<a href='http://$Weblink'><b>Here</b></a>"
#Define the body of your e-mail, in this case it displays a message and shows the server it is send from with it's local IP.
#A link to the .htm file, how many IP's were blocked and the date of the message.
$body = "Dear <font color=black>$to</font>,<br><br>"
$SMTPServer = "smtp.gmail.com"
$SMTPPort = "587"
$Username = "###gmail.com"
$Password = "##"
$message = New-Object System.Net.Mail.MailMessage
$message.IsBodyHTML = $true
$message.ReplyTo = $From
$message.Sender = $From
$message.subject = $subject
$message.body = $body
$message.to.add($to)
$message.from = $username
$message.attachments.add($MailXML)
$smtp = New-Object System.Net.Mail.SmtpClient($SMTPServer, $SMTPPort);
$smtp.EnableSSL = $true
$smtp.Credentials = New-Object System.Net.NetworkCredential($Username, $Password);
$smtp.send($message)
The saving of the file turned out to be a misguided path.
Since the excel sheet was created from folder1 it wouldnt save instandly to folder 2 since its temp save file would remain in folder1.