Upgrading to outlook 2013 killed a a VBScript - image in table - image

I have cut down the script to be as simple as possible. The issue is inserting an image in a table for Outlook 2013. This script works with older versions.
1 table, 1 row, 2 columns and using the AddPicture in a cell kills the script!
objTable.Cell(1, 1).Range.Text = objSelection.InlineShapes.AddPicture(strLogo)
Full script below. Any work arounds would be appreciated.
'-------------
On Error Resume Next
Set objSysInfo = CreateObject("ADSystemInfo")
strUser = objSysInfo.UserName
Set objUser = GetObject("LDAP://" & strUser)
strName = objUser.FullName
strMail = objuser.mail
strLogo = "c:\1.jpg"
Set objWord = CreateObject("Word.Application")
objWord.Visible = False
Set objDoc = objWord.Documents.Add()
Set objSelection = objWord.Selection
Set objRange = objDoc.Range()
Set objEmailOptions = objWord.EmailOptions
Set objSignatureObject = objEmailOptions.EmailSignature
Set objSignatureEntries = objSignatureObject.EmailSignatureEntries
objDoc.Tables.Add objRange, 1, 2
Set objTable = objDoc.Tables(1)
objTable.Cell(1, 1).Range.Text = objSelection.InlineShapes.AddPicture(strLogo)
objTable.Cell(1, 2).select
objSelection.TypeParagraph()
objSelection.TypeText strName
objSelection.Font.Bold = false
objSelection.TypeParagraph()
objSelection.TypeText strMail
objSignatureEntries.Add "Signature", objRange
objSignatureObject.NewMessageSignature = "Signature"
objSignatureObject.ReplyMessageSignature = "Signature"
objDoc.Saved = True
objWord.Quit
'----------------

Your error is obvious:
objTable.Cell(1, 1).Range.Text = objSelection.InlineShapes.AddPicture(strLogo)
this cannot work because you try to assign to .Text something that is not a string. Moreover: This has never worked, you just never noticed.
.AddPicture() already does all you want, just select the right spot in the document before:
objTable.Cell(1, 1).Select
objSelection.InlineShapes.AddPicture(strLogo)
Apart from this your script violates a few basic rules.
Always use Option Explicit. No exceptions, no "but", no arguments with "quick" or "only".
Never use On Error Resume Next as a global setting.
Write functions/subs to wrap up steps that can fail. On Error Resume Next has function scope, you can switch it on in a function to guard a line that can throw an error and it will be reset when the function ends.
If you can't/don't want to create an extra function, use On Error Goto 0 to end the effect of On Error Resume Next as soon as possible, but not before you've checked the Err variable to handle the error yourself.
Write functions/subs to structure your code.
A matter of preference, but I like to use With blocks.
Another matter of preference, but Hungarian notation makes no sense. By convention I use PascalCase for objects and camelCase for primitive values (strings, numbers, dates), along with speaking variable names.
Here's an improved version:
Option Explicit
Dim User, logo
Set User = GetCurrentUser
logo = "C:\1.jpg"
If Not User Is Nothing Then
CreateEmailSignature User, logo
Else
WScript.Echo "Could not retrieve user from AD."
End If
'------------------------------------------------------------------------------
Function GetCurrentUser()
Set GetCurrentUser = Nothing
On Error Resume Next
Set GetCurrentUser = GetObject("LDAP://" & CreateObject("ADSystemInfo").UserName)
End Function
'------------------------------------------------------------------------------
Sub CreateEmailSignature(ADUser, logoPath)
Dim Doc, Table
With CreateObject("Word.Application")
Set Doc = .Documents.Add
Set Table = Doc.Tables.Add(Doc.Range, 1, 2)
Table.Cell(1, 1).Select
InsertPictureFromFile .Selection, logoPath
Table.Cell(1, 2).Select
.Selection.TypeParagraph
.Selection.TypeText ADUser.FullName
.Selection.Font.Bold = False
.Selection.TypeParagraph
.Selection.TypeText ADUser.Mail
With .EmailOptions.EmailSignature
.EmailSignatureEntries.Add "Signature", Doc.Range
.NewMessageSignature = "Signature"
.ReplyMessageSignature = "Signature"
End With
Doc.Close False
.Quit False
End With
End Sub
'------------------------------------------------------------------------------
Sub InsertPictureFromFile(Selection, picturePath)
On Error Resume Next
Selection.InlineShapes.AddPicture picturePath
End Sub
'------------------------------------------------------------------------------

I found out that it is a 64 bit Office issue.
I have reinstalled on multiple pc's using 32 bit Office 2013 and everything works as it should.

Related

add multiple multi string values to registry using array

Code is cleaned and changed from previous post since old logics had various errors that have been corrected and narrowed down to one error in one condition that I cant find an answer to. Currently getting error when my url is being read as only value and throwing Subscript Out of range error even though array is initialized. Other conditions when user has preset items or no key at all works perfectly. Thanks.
option explicit
'on error resume next
Dim ObjName,oADSysInfo,strComputer
Dim objReg,IE_Main,mstrValName,strFunctionIntranet,strNYHomepage,multiStringValues(),allURLs(),itemname,a,return
Set oADSysInfo = CreateObject("ADSystemInfo")
Set ObjName = GetObject("LDAP://" & oADSysInfo.UserName)
strComputer = "."
Set objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\default:StdRegProv")
Const HKCU = &H80000001
IE_Main = "Software\Microsoft\Internet Explorer\Main"
mstrValName = "Secondary Start Pages"
strNYHomepage = "http://www.google.com"
strFunctionIntranet = "www.mycompany.com"
SetHomePage
Sub SetHomepage
objReg.setStringValue HKCU,IE_Main,"Start Page",strNYHomepage
'Reading MultiStringValue of "Secondary Start Pages" for HKCU and continuing if it has something preset.
return = objReg.getMultiStringValue (HKCU,IE_Main,mstrValName,multiStringValues)
If return=0 Then
a=0
'Reading all items currently set to make sure users retain their existing URLs.
For Each itemname In multiStringValues
'Only continue if any of the existing URLs DO NOT MATCH what we are enforcing as the URL.
If itemname <> strFunctionIntranet Then
WScript.Echo itemname
WScript.Echo "itemname is NOT equal intranet"
a = a + 1
ReDim Preserve allURLs(a)
allURLs(a) = itemname
'a = a + 1
End If
Next
objReg.DeleteValue HKCU,IE_Main,mstrValName
'Enforce our URL to always be the first item.
allURLs(0)=strFunctionIntranet
'Set the new MultiStringValue registry key back.
objReg.setMultiStringValue HKCU,IE_Main,mstrValName,allURLs
WScript.echo "finished setting all secondary tabs... "
Else
strFunctionIntranet = Array(strFunctionIntranet)
objReg.setMultiStringValue HKCU,IE_Main,mstrValName,strFunctionIntranet
End If
End Sub
Wscript.Quit
Your array contains an empty element, because you create it one field too big.
Change this line:
ReDim Preserve allURLs(a+1)
into this:
ReDim Preserve allURLs(a)

Find and Replace?

Does anyone know of a way to do a more complex find and replace? For example, I have many documents with merge fields. I need to be able to change the merge fields in these documents based on a list of definitions\translations. So in this example lets say I have 100 equipment leases created in M$ word saved as .dot. Each one the following merge fields exists, and I want to change them all at once to a new value as shown below.
{MERGEFIELD state} -> {MERGEFIELD ownerstate}
{MERGEFIELD city} -> {MERGEFIELD ownercity}
{MERGEFIELD zip} -> {MERGEFIELD ownerzip}
It's not so important that I be able to edit more than 1 document at a time than it is that I be able to make multiple edits at once.
OK so I was able to create a solution to my own issue. To do this I created the following code to do a find and replace based on a definition list in excel.
Option Explicit
Private MyXL As Object
Sub Test()
Dim WB As Excel.Workbook, WS As Excel.Worksheet, MyDefTbl As Excel.Range, MyRow As Excel.Range
Dim MySearchRng As Excel.Range, ReplacementRng As Excel.Range
Dim myDoc As Document
Call MyInitializeOfficeApps
'Define the Workbook that contains the Definitions
Set WB = MyXL.Workbooks.Open("E:\MailMerges\Definitions\Equip.xlsx")
'Define the Woksheet that contains the Definition list
Set WS = WB.Worksheets("Sheet1")
'Define the Range name that defines the Definition list
Set MyDefTbl = WS.Range("MyDefs")
'Define the Document to be changed
Set myDoc = ActiveDocument
For Each MyRow In MyDefTbl.Rows
Set MySearchRng = WS.Cells(MyRow.Row, 1)
Set ReplacementRng = WS.Cells(MyRow.Row, 2)
'MsgBox MySearchRng & "====>" & ReplacementRng
myDoc.Select
With Selection.Find
.Text = " MERGEFIELD " & MySearchRng.Text
.Replacement.Text = " MERGEFIELD " & ReplacementRng.Text
.Forward = True
.Wrap = wdFindContinue
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
Next MyRow
Set MyDefTbl = Nothing
Set MyRow = Nothing
Set WS = Nothing
Set WB = Nothing
Set MyXL = Nothing
Set myDoc = Nothing
MsgBox "Complete"
End Sub
Sub MyInitializeOfficeApps()
On Error Resume Next
Set MyXL = GetObject(, "Excel.Application")
If MyXL Is Nothing Then
Set MyXL = CreateObject("Excel.Application")
End If
On Error GoTo 0
MyXL.Visible = True
End Sub

Excel and Word behaving difrerently in the same code

I have a problem - instances of Excel and Word behave differently in the same procedure. Have a look at the code. The idea there is to have a procedure that handles resaving files in excel and word in various format combinations.
The problem is that I notice that word and excel behave differently - the appWord and appExcel have different type names. At some point appWord is changed from Application to Object, which then makes it impossible to close it. I don't understand the differences in the behaviour, since the code applied to them is identical.
Option Explicit
Dim fso
Dim appWord
Dim appExcel
Set fso = CreateObject("Scripting.FileSystemObject")
startWord
ResaveFiles appWord.Documents, "docx", 12, 0
appWord.quit
startExcel
ResaveFiles appExcel.Workbooks, "xlsx", 51, 56
appExcel.quit
MsgBox "All done."
Sub ResaveFiles(appType, srcExtName, srcExtNum, tmpExtNum)
Dim objFile
Dim objOpenFile
Dim strDirectory
For Each objFile in fso.GetFolder(".").Files
If lcase(fso.GetExtensionName(objFile)) = srcExtName Then
If typeName(appType) = "Documents" Then StartWord
If typeName(appType) = "Workbooks" Then StartExcel
Set objOpenFile = appType.Open(objFile.path)
strDirectory = fso.BuildPath(objOpenFile.path, fso.GetBaseName(objOpenFile.name) & "._temp")
objOpenFile.SaveAs strDirectory, tmpExtNum
objOpenFile.Close
msgBox typename(appType) & objFile
msgBox typename(appWord) 'First typename test
msgBox Typename(appExcel)
If typeName(appType) = "Documents" Then appWord.Quit
If typeName(appType) = "Workbooks" Then appExcel.Quit
set objOpenFile = appType.Open(strDirectory)
objOpenFile.SaveAs objFile.path, srcExtNum
objOpenFile.Close
fso.DeleteFile(strDirectory)
msgBox typename(appWord) 'Second typename test
msgBox Typename(appExcel)
End If
Next
End Sub
'Start Word
Sub StartWord
Set appWord = CreateObject("Word.Application")
appWord.visible = false
appWord.DisplayAlerts = false
End Sub
'Start Excel
Sub StartExcel
Set appExcel = CreateObject("Excel.Application")
appExcel.visible = false
appExcel.DisplayAlerts = false
End Sub
I have tested it in the following way (with two typename tests) - when there are word files available, first appWord is Application and appExcel is empty, then it changes to Object and appExcel stays Empty (in this case we get an error when the subprocedure ends at AppWord.Quit). When there are no word files, and the script is processing Excels, first appWord is Object and appExcel is Application, then appWord is still Object and appExcel is still Application - in this case there are no errors when the subprocedure ends, on the appExcel.Quit.
Maybe i'm wrong, just my opinion:
If typeName(appType) = "Documents" Then appWord.Quit
If typeName(appType) = "Workbooks" Then appExcel.Quit
set objOpenFile = appType.Open(strDirectory)
appType is a reference to what appWord.Documents or appExcel.Workbooks are referencing before entering your ResaveFiles Sub, where you instantiate a new copy of 'Excel.Application' or 'Word.Application', and in each of the cases, you instruct the application TO QUIT. The question is not why in the case of word you got an error. From my point of view YOU SHOULD got an error. The question is why, if instructed to quit, excel keeps open and maintaining references to handle your code.
EDIT - And not tried. Just adapted from OP code. Adapt as needed
Option Explicit
ResaveFiles "Word.Application", "docx", 12, 0
ResaveFiles "Excel.Application", "xlsx", 51, 56
MsgBox "All done."
Sub ResaveFiles(progID, srcExtName, srcExtNum, tmpExtNum )
Dim app, doc
Dim fso, objFile, objOpenFile, strDirectory
Set fso = CreateObject("Scripting.FileSystemObject")
For Each objFile in fso.GetFolder( "." ).Files
If LCase(fso.GetExtensionName( objFile.Name )) = srcExtName Then
' Get references
Set app = GetNewAppInstance( progID )
Set doc = GetDocumentHandler( app )
' Save temp
Set objOpenFile = doc.Open( objFile.Path )
strDirectory = fso.BuildPath( objOpenFile.path, fso.GetBaseName(objOpenFile.name) & "._temp" )
objOpenFile.SaveAs strDirectory, tmpExtNum
objOpenFile.Close
' Release objects
Set objOpenFile = nothing
Set doc = nothing
app.Quit
Set app = nothing
' Get references again
Set app = GetNewAppInstance( progID )
Set doc = GetDocumentHandler( app )
' Resave file
Set objOpenFile = doc.Open( strDirectory )
objOpenFile.SaveAs objFile.path, srcExtNum
objOpenFile.Close
' Release objects
Set objOpenFile = nothing
Set doc = nothing
app.Quit
Set app = nothing
' Clean
fso.DeleteFile(strDirectory)
End If
Next
End Sub
Function GetNewAppInstance( ByVal progID )
Set GetNewAppInstance = CreateObject( progID )
With GetNewAppInstance
.Visible = False
.DisplayAlerts = False
End With
End Function
Function GetDocumentHandler( app )
Dim name
name = app.Name
If InStr(name,"Excel") > 0 Then
Set GetDocumentHandler = app.Workbooks
ElseIf InStr(name,"Word") > 0 Then
Set GetDocumentHandler = app.Documents
Else
Set GetDocumentHandler = app
End If
End Function

Upgrading VB6 code from Outlook 2007 to Outlook 2010

We want to upgrade our VB6 code to use Outlook 2010, but we're getting the following error:
Active x cannot create object
This is our current code:
Public Sub SendEmail()
Set emailOutlookApp = CreateObject("Outlook.Application.12")
Set emailNameSpace = emailOutlookApp.GetNamespace("MAPI")
Set emailFolder = emailNameSpace.GetDefaultFolder(olFolderInbox)
Set emailItem = emailOutlookApp.CreateItem(olMailItem)
Set EmailRecipient = emailItem.Recipients
EmailRecipient.Add (EmailAddress)
EmailRecipient.Add (EmailAddress2)
emailItem.Importance = olImportanceHigh
emailItem.Subject = "My Subject"
emailItem.Body = "The Body"
'-----Send the Email-----'
emailItem.Save
emailItem.Send
'-----Clear out the memory space held by variables-----'
Set emailNameSpace = Nothing
Set emailFolder = Nothing
Set emailItem = Nothing
Set emailOutlookApp = Nothing
Exit Sub
I'm not sure if "Outlook.Application.12" is correct. But I can't find a definitive answer for this.
For Outlook 2010, this is definitly corect Outlook.Application.14.
But, I don't know what about office 2007.
I think it's Outlook.Application.12 and for lower versions it is simply "Outlook.Application".
Here's the code I switched to for 2010:
Private Sub EmailBlahbutton_Click()
Dim mOutlookApp As Object
Dim OutMail As Object
Dim Intro As String
On Error GoTo ErrorHandler
Set mOutlookApp = GetObject("", "Outlook.application")
Set OutMail = mOutlookApp.CreateItem(0)
With Application
.EnableEvents = False
.ScreenUpdating = False
End With
'These are the ranges being emailed.
ActiveSheet.Range(blahblahblah).Select
'Intro is the first line of the email
Intro = "BLAHBLAHBLHA"
'Set the To and Subject lines. Send the message.
With OutMail
.To = "blahblah#blah.com"
.Subject = "More BLAH here"
.HTMLBody = Intro & RangetoHTML(Selection)
.Send
End With
With Application
.EnableEvents = True
.ScreenUpdating = True
End With
ActiveSheet.Range("A1").Select
ActiveWindow.ScrollColumn = ActiveCell.Column
ActiveWindow.ScrollRow = ActiveCell.Row
Set OutMail = Nothing
Set mOutlookApp = Nothing
Exit Sub
ErrorHandler:
Set mOutlookApp = CreateObject("Outlook.application")
Resume Next
End Sub
Function RangetoHTML(rng As Range)
' Changed by Ron de Bruin 28-Oct-2006
' Working in Office 2000-2010
Dim fso As Object
Dim ts As Object
Dim TempFile As String
Dim TempWB As Workbook
TempFile = Environ$("temp") & "/" & Format(Now, "dd-mm-yy h-mm-ss") & ".htm"
'Copy the range and create a new workbook to past the data in
rng.Copy
Set TempWB = Workbooks.Add(1)
With TempWB.Sheets(1)
.Cells(1).PasteSpecial Paste:=8
.Cells(1).PasteSpecial xlPasteValues, , False, False
.Cells(1).PasteSpecial xlPasteFormats, , False, False
.Cells(1).Select
Application.CutCopyMode = False
On Error Resume Next
.DrawingObjects.Visible = True
.DrawingObjects.Delete
On Error GoTo 0
End With
'Publish the sheet to a htm file
With TempWB.PublishObjects.Add( _
SourceType:=xlSourceRange, _
Filename:=TempFile, _
Sheet:=TempWB.Sheets(1).Name, _
Source:=TempWB.Sheets(1).UsedRange.address, _
HtmlType:=xlHtmlStatic)
.Publish (True)
End With
'Read all data from the htm file into RangetoHTML
Set fso = CreateObject("Scripting.FileSystemObject")
Set ts = fso.GetFile(TempFile).OpenAsTextStream(1, -2)
RangetoHTML = ts.ReadAll
ts.Close
RangetoHTML = Replace(RangetoHTML, "align=center x:publishsource=", _
"align=left x:publishsource=")
'Close TempWB
TempWB.Close savechanges:=False
'Delete the htm file we used in this function
Kill TempFile
Set ts = Nothing
Set fso = Nothing
Set TempWB = Nothing
End Function
Why do you explicitly specify the version? Why not simply
Set emailOutlookApp = CreateObject("Outlook.Application")
Try "Outlook.Application.14". Not sure if this is related though: 2007 to 2010 upgrade issue
I realize it's not the exact issue, but it may lead you down the right path.

VBS Script for modifying multi-value Active Directory display specifier

Following the howto Extending the Active Directory Schema To Track Custom Info I'm able to setup a single-value schema attribute that is easily changeable via a context menu in ADUC. Multi-value schema attributes get considerably more complicated. Say (for the sake of argument) my value is "Projects" and each user may be a list as many projects as necessary.
Following is a sad little script that will set Project to a single value:
Dim oproject
Dim oUser1
Dim temp1
Set oproject = Wscript.Arguments
Set oUser1 = GetObject(oproject(0))
temp1 = InputBox("Project: " & oUser1.project & vbCRLF & vbCRLF & "Project")
if temp1 <> "" then oUser1.Put "project",temp1
oUser1.SetInfo
Set oUser1 = Nothing
Set oproject = Nothing
Set temp1 = Nothing
WScript.Quit
How can I modify this to allow, assign, and modify multiple values?
I gave up on an elegant UI and just went with the semicolon delimited list. Here's the code if anyone cares:
Dim objProject
Dim objUser
Dim temp1, title, message, default
Dim projects
title = "Projects"
Set objProject = Wscript.Arguments
Set objUser = GetObject(objProject(0))
'Find our current projects
projects = objUser.projects
If Not isArray(projects) Then
projects = Array(projects)
End If
'Setup our message box
message = "Semicolon-delimited list of Projects"
default = arrayToStr(projects)
temp1 = InputBox(message, title, default)
'catch cancels
if IsEmpty(temp1) Then
WScript.Quit
End If
' update our data
projects = strToArray(temp1)
objUser.Put "projects",projects
objUser.SetInfo
'Clean up and quit
Set projects = Nothing
Set objUser = Nothing
Set objProject = Nothing
Set temp1 = Nothing
Set title = Nothing
Set message = Nothing
Set default = Nothing
WScript.Quit
'Functions
Function strToArray(s)
Dim a
Dim token
' discard blank entries
For Each token in split(s, ";")
token = trim(token)
If token <> "" Then
If isEmpty(a) Then
a = token
Else
a = a & ";" & token
End If
End If
Next
' return array
strToArray = split(a, ";")
End Function
Function arrayToStr(a)
Dim s
Dim token
For Each token in a
If isEmpty(s) Then
s = token
Else
s = s & ";" & token
End If
Next
' return string
arrayToStr = s
End Function

Resources