Our environment is migrating from Windows 2000/2003/XP to Windows 2008/Vista. When you make the jump from any of the older server or desktop OSs to the newer server and desktop OSs (Vista & WS2008), some stuff breaks. The broken functionality that is causing me a lot of pain is email. The CDO messaging component that I've relied upon for 5 years is missing from Vista.
This works in 2000/2003/XP but not Vista:
Dim objEmail
Set objEmail = CreateObject("CDO.Message")
objEmail.From = strFrom
objEmail.To = strRecipients
objEmail.Subject = strSubject
objEmail.Textbody = strBody
objEmail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
objEmail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "<mail server>"
objEmail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
objEmail.Configuration.Fields.Update
objEmail.Send
Set objEmail = Nothing
This works in Vista because PowerShell is installed in Vista:
$msg = New-object System.Net.Mail.MailMessage 'MessageSender@live.com', 'MessageRecipient@Go.org', 'Message Subject', 'Message Body';
$client = New-object System.Net.Mail.SmtpClient 'Mail_Server.Live.com';
$client.UseDefaultCredentials = $true;
$client.send($msg);
But can what do you do if you want to keep using the same VB Scripts (because they work) and replace the dead components? You could try something like this:
Dim objShell
Dim exitcode
Dim strFrom
Dim strRecipients
Dim strSubject
Dim strBody
Dim strMailServer
Set objShell = WScript.CreateObject("WScript.shell")
strFrom = "MessageSender@Live.com"
strRecipients = "MessageRecipient@Go.org"
strSubject = "Subject"
strMailServer = "Mail_Server.Live.com"
strBody = "Blah Blah Back Schleep, Have you any Woolski?"
strBody = Replace(strBody,"'","`")
exitcode = objShell.Run("Powershell.exe -NoProfile -command ""& {$msg = New-object System.Net.Mail.MailMessage " & _
"'" & strFrom & "','" & strRecipients & "','" & strSubject & "','" & strBody & "';" & _
"$client = New-object System.Net.Mail.SmtpClient '" & strMailServer & "';" & _
"$client.UseDefaultCredentials = $true;$client.send($msg);}""")
But the problem with this (above) code is that once your body reaches a certian character limit, the objShell.Run() call fails. So what else to do?
What about using a file to hold the contents of the body?
Dim objShell
Dim exitcode
Dim strFrom
Dim strRecipients
Dim strSubject
Dim strBody
Dim strMailServer
Dim objFSO
Dim objEmail_Body_File
Dim strEmail_Body_File_Full_Path
Set objShell = WScript.CreateObject("WScript.shell")
Set objFSO = Wscript.CreateObject("Scripting.FileSystemObject")
strEmail_Body_File_Full_Path = objShell.ExpandEnvironmentStrings(objShell.Environment("User")("TEMP")) & "\" & objFSO.GetTempName
Set objEmail_Body_File = objFSO.CreateTextFile(strEmail_Body_File_Full_Path, True)
strFrom = "MessageSender@Live.com"
strRecipients = "MessageRecipient@Go.org"
strSubject = "Subject"
strMailServer = "Mail_Server.Live.com"
strBody = "Blah Blah Back Schleep, Have you any Woolski?"
strBody = Replace(strBody,"'","`")
objEmail_Body_File.Write strBody
objEmail_Body_File.Close
WScript.Sleep 2000 'The pause is necessary to allow the file system to catch up before handing it over to PoSH
Set objEmail_Body_File = Nothing
exitcode = objShell.Run("Powershell.exe -NoProfile -command ""& {$msg = New-object System.Net.Mail.MailMessage " & _
"'" & strFrom & "','" & strRecipients & "','" & strSubject & "'," & _
"$([String]::Join('" & vbcrlf & "',$(get-content " & strEmail_Body_File_Full_Path & ")));" & _
"$client = New-object System.Net.Mail.SmtpClient '" & strMailServer & "';" & _
"$client.UseDefaultCredentials = $true;$client.send($msg);}""")
This works* but relies upon somewhere to write on the file system. I'd really like to move away from using files to transfer the data between scripting languages. You'll notice an extra line in the PoSH command. When 'get-content' reads a file, it doesn't return a string. It returns an object that contains an array of strings. These strings were created on Carridge Return / Line Feed markers. It's the equivalent of 'arrLines = Split(strEmailBody,VBCRLF)' in VBS. If you pass the PoSH object $msg this array of strings, it fails. So you have to Join it back together using the PoSH command '[String]::Join()'.
*This doesn't always work. While it does work on my own Vista box, it fails on a co-workers Vista box with a "System cannot find the file specified" error. I've not yet had the chance to sit down and figure out why the code breaks on his workstation.
What other options do we have? What about an objShell.Exec() attempt? To keep it simple, lets not even worry about sending an email. Lets instead center on getting commands into and out of an objShell.Exec shell object.
Dim objExec
Dim objStdIn
Dim objStdOut
Set objExec = objShell.Exec("%comspec% /c Powershell.exe")
Set objStdIn = objExec.StdIn
Set objStdOut = objExec.StdOut
WScript.Sleep 1000
Wscript.Echo objStdOut.ReadAll 'Hangs here after dumping PoSH script environment information to the screen
objStdIn.Write("Exit")
objExec.Terminate
WScript.Quit
You can see from my comment in the code above she no-workie. I have to kill the PowerShell.exe executable in Task Manager before the script will continue. When it does continue, it obviously errors out since the shell instance running inside objExec is no longer "alive". If I were to take out the command 'Wscript.echo objStdOut.ReadAll' entierly, it would go on to 'objStdIn.Write("Exit")' and hang again. I had hoped that with this code I would be able to open a shell, run PoSH inside of it then close it down gracefully. Unfortunately it hangs like toilet paper on trees in late October.
So this is where I stand: I can use objShell.Run() but don't like having to pass the body of an email from one language to the next through the file system. Does anyone have any ideas?