convert msg to html without using outlook in powershell - powershell

I want to convert msg files (in the local folder) to html in powershell. I have done this by using outlook but the problem is this script will work on server so I cant use outlook.
So far, I have searched these questions:
Save Email as MSG file without using Outlook (COM object, etc.) or 3rd party software
convert msg to html in background using vbscript
Read Outlook .msg File
I couldnt find many approaches with Powershell. However, with the help of the questions above I downloaded Redemption and used it like this:
$routlook = New-Object -COM Redemption.RDOSession
$routlook.Logon()
$msg = $routlook.GetMessageFromMsgFile("C:\temp\test.msg",$TRUE)
$path = "C:\temp\test.html"
$msg.HTMLBody | Set-Content $path
But $msg.HTMLBody just returns an empty html in the below and $msg.Body returns nothing:
<HTML>
<HEAD><META http-equiv=Content-Type content="text/html; charset=UTF-8">
</HEAD>
<BODY>
<!-- Converted from text/plain format -->
</BODY></HTML>
Probably I am using Redemption in a wrong way and dont know how to fix it. Or is there any other 3rd party solution that can be used in servers rather than using outlook?
Thanks for any help

The second parameter when you call GetMessageFromMsgFile is true, which means you are creating a brand new file. Pass false instead.
Also note that GetMessageFromMsgFile does not require an active session, so there is no need to call RDOSession.Logon. And there won't be a profile if you are running under a service user anyway.
To create an HTML file, you don't need to read the RDOMail.HTMLBody property. You can call RDOMail.SaveAs(..., olHTML).

You need to remove the $TRUE parameter:
$routlook = New-Object -COM Redemption.RDOSession
$routlook.Logon()
$msg = $routlook.GetMessageFromMsgFile("C:\temp\test.msg")
$path = "C:\temp\test.html"
$msg.HTMLBody | Set-Content $path
And you can open your msg file and after that, save as html

To make it clear, it worked fine with both answers and my complete code for either ways:
$routlook = New-Object -COM Redemption.RDOSession
$location = "C:\temp\Redemption\"
$msg = $routlook.GetMessageFromMsgFile("C:\temp\test.msg",$FALSE)
$name = "test.html"
$path = $location + $name
$msg.SaveAs($path,5)
Or
$routlook = New-Object -COM Redemption.RDOSession
$location = "C:\temp\Redemption\"
$msg = $routlook.GetMessageFromMsgFile("C:\temp\test.msg")
$name = $_.Name + ".html"
$path = $location + $name
$msg.HTMLBody | Set-Content $path

Related

In Powershell, using only memory (no disk storage available), how can I create a zip archive of a large text file and attach it to an email message?

In Powershell, I'm trying to create a zip file from a large text string, attach it to an email message, and send it along. The trick is that I need to do it without using local storage or disk resources, creating the constructs in memory.
I have two pieces where I'm stuck.
The steps I'm taking below don't write any content to the zip variable or its entry.
How do I attach the zip, once it's complete, to the email?
Because I can't get past the first issue of creating the zip, I haven't been able to attempt to attach the zip to the email.
$strTextToZip = '<html><head><title>Log report</title></head><body><p>Paragraph</p><ul><li>first</li><li>second</li></ul></body></html>'
Add-Type -Assembly 'System.IO.Compression'
Add-Type -Assembly 'System.IO.Compression.FileSystem'
# step 1 - create the memorystream for the zip archive
$memoryStream = New-Object System.IO.Memorystream
$zipArchive = New-Object System.IO.Compression.ZipArchive($memoryStream, [System.IO.Compression.ZipArchiveMode]::Create, $true)
# step 2 - create the zip archive's entry for the log file and open it for writing
$htmlFile = $zipArchive.CreateEntry("log.html", 0)
$entryStream = $htmlFile.Open()
# step 3 - write the HTML file to the zip archive entry
$fileStream = [System.IO.StreamWriter]::new( $entryStream )
$fileStream.Write( $strTextToZip )
$fileStream.close()
# step 4 - create mail message
$msg = New-Object Net.Mail.MailMessage($smtp['from'], $smtp['to'], $smtp['subject'], $smtp['body'])
$msg.IsBodyHTML = $true
# step 5 - add attachment
$attachment = New-Object Net.Mail.Attachment $memoryStream, "application/zip"
$msg.Attachments.Add($attachment)
# step 6 - send email
$smtpClient = New-Object Net.Mail.SmtpClient($smtp['server'])
$smtpClient.Send($msg)
The streaming in step 3 for the entry doesn't populate the variable. Also, once populated successfully, I'm not sure how to close the streams and keep the content available for the email attachment. Lastly, I think the Add for the Net.Mail.Attachment in step 5 should successfully copy the zip to the message, but any pointers here would be welcome as well.
For the sake of at least giving a proper guidance on the first item, how to create a Zip in Memory without a file. You're pretty close, this is how it's done, older version of Compress-Archive uses a similar logic with a MemoryStream and it's worth noting because of this it has it's 2Gb limitation, see this answer for more details.
using namespace System.Text
using namespace System.IO
using namespace System.IO.Compression
using namespace System.Net.Mail
using namespace System.Net.Mime
Add-Type -Assembly System.IO.Compression, System.IO.Compression.FileSystem
$memStream = [MemoryStream]::new()
$zipArchive = [ZipArchive]::new($memStream, [ZipArchiveMode]::Create, $true)
$entry = $zipArchive.CreateEntry('log.html', [CompressionLevel]::Optimal)
$wrappedStream = $entry.Open()
$writer = [StreamWriter] $wrappedStream
$writer.AutoFlush = $true
$writer.Write(#'
<html>
<head>
<title>Log report</title>
</head>
<body>
<p>Paragraph</p>
<ul>
<li>first</li>
<li>second</li>
</ul>
</body>
</html>
'#)
$writer, $wrappedStream, $zipArchive | ForEach-Object Dispose
Up until this point, the first question is answered, now $memStream holds your Zip file in memory, if we were to test if this is true (obviously we would need a file, since I don't have an smtp server available for further testing):
$file = (New-Item 'test.zip' -ItemType File -Force).OpenWrite()
$memStream.Flush()
$memStream.WriteTo($file)
# We dispose here for demonstration purposes
# in the actual code you should Dispose as last step (I think, untested)
$file, $memStream | ForEach-Object Dispose
Resulting Zip file would become:
Now trying to answer the 2nd question, this is based on a hunch and I think your logic is sound already, Attachment class has a .ctor that supports a stream Attachment(Stream, ContentType) so I feel this should work properly though I can't personally test it:
$msg = [MailMessage]::new($smtp['from'], $smtp['to'], $smtp['subject'], $smtp['body'])
$msg.IsBodyHTML = $true
# EDIT:
# Based on OP's comments, this is the correct order to follow
# first Flush()
$memStream.Flush()
# then
$memStream.Position = 0
# now we can attach the memstream
# Use `Attachment(Stream, String, String)` ctor instead
# so you can give it a name
$msg.Attachments.Add([Attachment]::new(
$memStream,
'nameyourziphere.zip',
[ContentType]::new('application/zip')
))
$memStream.Close() # I think you can close here then send
$smtpClient = [SmtpClient]::new($smtp['server'])
$smtpClient.EnableSsl = $true
$smtpClient.Send($msg)
$msg, $smtpClient | ForEach-Object Dispose

How add autocorrect entries with an hyperlink using powershell script

In MS Outlook, is there a way to automatically replace some words like Google, MSN, Facebook, etc (I have an exhausting list in a CSV file), by the hyperlink that redirects to correct website.
So basically when I type google it transforms it to a hyperlink.
My CSV file:
Word, URL
Facebook, https://facebook.com
MSN, https://msn.com
Google, https://google.com
What I have so far is a script that add to the object autocorrect entries a word and replaces it by another word not using a CSV but a word document. But I'm not able to replace it by an hyperlink. It causes an error saying that autocorrect entries accept only string format and not object (hyperlink).
Reference: Add formatted text to Word autocorrect via PowerShell
When I create manually via outlook an hyperlink and I add this hyperlink to autocorrect and I run the following PowerShell script I can't find this autocorrect entry:
(New-Object -ComObject word.application).AutoCorrect.Entries | where{$_.Value -like "*http*"}
I want to adapt this code coming from Use PowerShell to Add Bulk AutoCorrect Entries to Word
If someone has an idea on how to add a hyperlink to the autocorrect entries, I would be grateful.
Thanks!
I finally managed how to add autocorrect entries for both word and outlook.
I need to create a .docx file with 'X row' and '2 Columns', the first column contain the word that i want an autocorrect like 'google' and the second column the 'google' link.
$objWord = New-Object -Com Word.Application
$filename = 'C:\Users\id097109\Downloads\test3.docx'
$objDocument = $objWord.Documents.Open($filename)
$LETable = $objDocument.Tables.Item(1)
$LETableCols = $LETable.Columns.Count
$LETableRows = $LETable.Rows.Count
$entries = $objWord.AutoCorrect.entries
for($r=1; $r -le $LETableRows; $r++) {
$replace = $LETable.Cell($r,1).Range.Text
$replace = $replace.Substring(0,$replace.Length-2)
$withRange = $LETable.Cell($r,2).Range
$withRange.End = $withRange.End -1
# $with = $withRange.Text
Try {
$entries.AddRichText($replace, $withRange) | out-null
}
Catch [system.exception] {
Write-Host $_.Exception.ToString()
}
}
$objDocument.Close()
$objWord.Quit()
[gc]::collect()
[gc]::WaitForPendingFinalizers()
$rc = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($objWord)
This code allow to modify the file Normal.dotm that contains all the autocorrect to an object link (C:\Users{your user id}\AppData\Roaming\Microsoft\Templates)
But then to apply those change to Outlook you have delete the 'NormalEmail.dotm' and the copy/paste 'Normal.dotm' and the rename it to 'NormalEmail.dotm'
This is the script to avoid to do it manually :
$FileName='C:\Users\{your id}\AppData\Roaming\Microsoft\Templates\Normal.dotm'
$SaveTo='C:\Users\{your id}\AppData\Roaming\Microsoft\Templates\NormalEmail.dotm'
Remove-Item –path $SaveTo
$Word = New-Object –ComObject Word.Application
$Document=$Word.Documents.Open($Filename)
$Document.SaveAs($SaveTo)
$Document.Close

Powershell script to increment version in a text file

I have a text file that contains some text and end of the file
"Version"="1.3.0"
I would like to increment the build number "Version"="1.4.0".
in the same file, without affecting the other contents.
help needed in powershell please.
I assume you can parse the file to the point you get to similar content you give in your question. Additionally, my example is lacking in error handling.
Example
$fileContents = '"Version"="1.4.0"'
$parts=$fileContents.Split('=')
$versionString = $parts[1].Replace('"','')
$version = [Version]$versionString
$newVersionString = (New-Object -TypeName 'System.Version' -ArgumentList #($version.Major, ($version.Minor+1), $version.Build)).ToString()
$fileContents.Replace($versionString,$newVersionString)
Result
"Version"="1.5.0"
Thanks a lot #Matt && #TravisEz13 for your tips. with your help here is the another working version.
$Verfile = "C:\Work\Example\versionfile.reg"
$fileContents = (Get-Content $Verfile | Select -Last 1)
$parts=$fileContents.Split('=')
$versionString = $parts[1].Replace('"','')
$version = [Version]$versionString
$newVersionString = (New-Object -TypeName 'System.Version' -ArgumentList #($version.Major, ($version.Minor+1), $version.Build)).ToString()
(gc $Verfile) -replace "$versionString", "$newVersionString" | sc $Verfile

How to pin to start menu using PowerShell

I can pin some programs to taskbar on Win7 using PowerShell.
$shell = new-object -com "Shell.Application"
$folder = $shell.Namespace('C:\Windows')
$item = $folder.Parsename('notepad.exe')
$verb = $item.Verbs() | ? {$_.Name -eq 'Pin to Tas&kbar'}
if ($verb) {$verb.DoIt()}
How do I modify the above code to pin a program to the Start menu?
Another way
$sa = new-object -c shell.application
$pn = $sa.namespace($env:windir).parsename('notepad.exe')
$pn.invokeverb('startpin')
Or unpin
$pn.invokeverb('startunpin')
Use the code below
$shell = new-object -com "Shell.Application"
$folder = $shell.Namespace('C:\Windows')
$item = $folder.Parsename('notepad.exe')
$verb = $item.Verbs() | ? {$_.Name -eq 'Pin to Start Men&u'}
if ($verb) {$verb.DoIt()}
Note: the change is in the fourth line.
The main problem with most of the solution is that they enumerate the verbs on a file, search for the string to perform the action (“Pin to Startmenu” etc.) and then execute it. This does not work if you need to support 30+ languages in your company, except you use external function to search for the localized command (see answer from shtako-verflow).
The answer from Steven Penny is the first that is language neutral and does not need any external code. It uses the verbs stored in the registry HKEY_CLASSES_ROOT\CLSID\{90AA3A4E-1CBA-4233-B8BB-535773D48449} and HKEY_CLASSES_ROOT\CLSID\{a2a9545d-a0c2-42b4-9708-a0b2badd77c8}
Based on this, here’s the code we are now using:
function PinToTaskbar {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "taskbarpin"
}
function UnpinFromTaskbar {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "taskbarunpin"
}
function PinToStartmenu {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "startpin"
}
function UnpinFromStartmenu {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "startunpin"
}
function ExecuteVerb {
param(
[Parameter(Mandatory=$true)][string]$File,
[Parameter(Mandatory=$true)][string]$Verb
)
$path = [System.Environment]::ExpandEnvironmentVariables($File)
$basePath = split-path $path -parent #retrieve only the path File=C:\Windows\notepad.exe -> C:\Windows
$targetFile = split-path $path -leaf #retrieve only the file File=C:\Windows\notepad.exe -> notepad.exe
$shell = new-object -com "Shell.Application"
$folder = $shell.Namespace($basePath)
if ($folder)
{
$item = $folder.Parsename($targetFile)
if ($item)
{
$item.invokeverb($Verb)
# "This method does not return a value." (http://msdn.microsoft.com/en-us/library/windows/desktop/bb787816%28v=vs.85%29.aspx)
# Therefore we have no chance to know if this was successful...
write-host "Method [$Verb] executed for [$path]"
}
else
{
write-host "Target file [$targetFile] not found, aborting"
}
}
else
{
write-host "Folder [$basePath] not found, aborting"
}
}
#PinToTaskbar "%WINDIR%\notepad.exe"
#UnpinFromTaskbar "%WINDIR%\notepad.exe"
PinToStartmenu "%WINDIR%\notepad.exe"
#UnpinFromStartmenu "%WINDIR%\notepad.exe"
See the script (international) here : http://gallery.technet.microsoft.com/scriptcenter/b66434f1-4b3f-4a94-8dc3-e406eb30b750
If you want to add an action like Pin to Modern UI interface (Windows 8), at $verbs, add 51201
Steven Penny's second answer above worked well for me. Here are a couple more tidbits.
It's doing COM through PowerShell, so you can do the same thing with pretty much any COM client. For example, here's an AutoHotkey version.
Shell := ComObjCreate("Shell.Application")
Target := Shell.Namespace(EnvGet("WinDir")).ParseName("Notepad.exe")
Target.InvokeVerb("startpin")
VBScript or InnoSetup would look almost the same except for the function used to create the object.
I also found that I have one program that pinned OK, but didn't have the right icon and/or description because of limitations in the compiler. I just made a little 1-line WinForms app that starts the target with Process.Start, and then added the appropriate icon, and the name I wanted in the Start Menu in the Title property in AppInfo.cs.

Expressions are only allowed as the first element of a pipeline

I'm new at writing in powershell but this is what I'm trying to accomplish.
I want to compare the dates of the two excel files to determine if one is newer than the other.
I want to convert a file from csv to xls on a computer that doesn't have excel. Only if the statement above is true, the initial xls file was copied already.
I want to copy the newly converted xls file to another location
If the file is already open it will fail to copy so I want to send out an email alert on success or failure of this operation.
Here is the script that I'm having issues with. The error is "Expressions are only allowed as the first element of a pipeline." I know it's to do with the email operation but I'm at a loss as to how to write this out manually with all those variables included. There are probably more errors but I'm not seeing them now. Thanks for any help, I appreciate it!
$CSV = "C:filename.csv"
$LocalXLS = "C:\filename.xls"
$RemoteXLS = "D:\filename.xls"
$LocalDate = (Get-Item $LocalXLS).LASTWRITETIME
$RemoteDate = (Get-Item $RemoteXLS).LASTWRITETIME
$convert = "D:\CSV Converter\csvcnv.exe"
if ($LocalDate -eq $RemoteDate) {break}
else {
& $convert $CSV $LocalXLS
$FromAddress = "email#address.com"
$ToAddress = "email#address.com"
$MessageSubject = "vague subject"
$SendingServer = "mail.mail.com"
$SMTPMessage = New-Object System.Net.Mail.MailMessage $FromAddress, $ToAddress, $MessageSubject, $MessageBody
$SMTPClient = New-Object System.Net.Mail.SMTPClient $SendingServer
$SendEmailSuccess = $MessageBody = "The copy completed successfully!" | New-Object System.Net.Mail.SMTPClient mail.mail.com $SMTPMessage
$RenamedXLS = {$_.BaseName+(Get-Date -f yyyy-MM-dd)+$_.Extension}
Rename-Item -path $RemoteXLS -newname $RenamedXLS -force -erroraction silentlycontinue
If (!$error)
{ $SendEmailSuccess | copy-item $LocalXLS -destination $RemoteXLS -force }
Else
{$MessageBody = "The copy failed, please make sure the file is closed." | $SMTPClient.Send($SMTPMessage)}
}
You get this error when you are trying to execute an independent block of code from within a pipeline chain.
Just as a different example, imagine this code using jQuery:
$("div").not(".main").console.log(this)
Each dot (.) will chain the array into the next function. In the above function this breaks with console because it's not meant to have any values piped in. If we want to break from our chaining to execute some code (perhaps on objects in the chain - we can do so with each like this:
$("div").not(".main").each(function() {console.log(this)})
The solution is powershell is identical. If you want to run a script against each item in your chain individually, you can use ForEach-Object or it's alias (%).
Imagine you have the following function in Powershell:
$settings | ?{$_.Key -eq 'Environment' } | $_.Value = "Prod"
The last line cannot be executed because it is a script, but we can fix that with ForEach like this:
$settings | ?{$_.Key -eq 'Environment' } | %{ $_.Value = "Prod" }
This error basically happens when you use an expression on the receiving side of the pipeline when it cannot receive the objects from the pipeline.
You would get the error if you do something like this:
$a="test" | $a
or even this:
"test" | $a
I don't know why are trying to pipe everywhere. I would recommend you to learn basics about Powershell pipelining. You are approaching it wrong. Also, I think you can refer to the link below to see how to send mail, should be straight forward without the complications that you have added with the pipes : http://www.searchmarked.com/windows/how-to-send-an-email-using-a-windows-powershell-script.php