How To Search And Replace Across Multiple Files In Word? - excel

I have a folder with a few hundred-word documents. I want to be able to replace the words ORG NAME by clicking a macro, filling in an input box and letting it iterate. There are thousands of instances of ORG NAME across these documents and this process needs to happen a few dozen times a year.
We've got some challenges with trust centre policies in place that can't be changed so the macro needs to be done via excel.
The below was sort of working as word macro although it was crashing a lot, I moved it over to excel and now I'm getting the error: Named argument not found against macroname:
I can't find any similar questions that aren't solved by correcting spelling.
I'm also open to better solutions if they exist, this has been my first attempt so far.
Sub Button1_Click()
Dim xFileDialog As FileDialog, GetStr(1 To 300) As String '300 files is the maximum applying this code
Dim xFindStr As String
Dim xReplaceStr As String
Dim xDoc As Object
On Error Resume Next
Set xFileDialog = Application.FileDialog(msoFileDialogFilePicker)
With xFileDialog
.Filters.Clear
.Filters.Add "All WORD File ", "*.docx", 1
.AllowMultiSelect = True
i = 1
If .Show = -1 Then
For Each stiSelectedItem In .SelectedItems
GetStr(i) = stiSelectedItem
i = i + 1
Next
i = i - 1
End If
Application.ScreenUpdating = False
xFindStr = "ORG NAME"
xReplaceStr = InputBox("Enter the name of the organisation:", "Document Updater for Word", xReplaceStr)
For j = 1 To i Step 1
Set xDoc = Documents.Open(Filename:=GetStr(j), Visible:=True)
Windows(GetStr(j)).Activate
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting
With Selection.Find
.Text = xFindStr 'Find What
.Replacement.Text = xReplaceStr 'Replace With
.Forward = True
.Wrap = wdFindAsk
.Format = False
.MatchCase = False
.MatchWholeWord = False
.MatchByte = True
.MatchWildcards = False
.MatchSoundsLike = False
.MatchAllWordForms = False
End With
Selection.Find.Execute Replace:=wdReplaceAll
Application.Run macroname:="NEWMACROS"
ActiveDocument.Save
ActiveWindow.Close
Next
Application.ScreenUpdating = True
End With
MsgBox "Operation end, please view", vbInformation
End Sub

See this page by Ibby on the Word MVP website.
How to Find & ReplaceAll on a batch of documents in the same folder
The following code, if stored in a Global template, will perform a
Find & ReplaceAll in all of the documents in a specified folder. The
FindReplace dialog is displayed for the first document only. The user
sets the parameters in the dialog and presses Replace All and then
Close. The user is then asked whether to process all of the files in
the specified directory – if Yes, the rest of the files are processed
with the settings as entered in the original FindReplace dialog.
Option Explicit
Public Sub BatchReplaceAll()
Dim FirstLoop As Boolean
Dim myFile As String
Dim PathToUse As String
Dim myDoc As Document
Dim Response As Long
PathToUse = "C:\Test\"
'Error handler to handle error generated whenever
'the FindReplace dialog is closed
On Error Resume Next
'Close all open documents before beginning
Documents.Close SaveChanges:=wdPromptToSaveChanges
'Boolean expression to test whether first loop
'This is used so that the FindReplace dialog will
'only be displayed for the first document
FirstLoop = True
'Set the directory and type of file to batch process
myFile = Dir$(PathToUse & "*.doc")
While myFile <> ""
'Open document
Set myDoc = Documents.Open(PathToUse & myFile)
If FirstLoop Then
'Display dialog on first loop only
Dialogs(wdDialogEditReplace).Show
FirstLoop = False
Response = MsgBox("Do you want to process " & _
"the rest of the files in this folder", vbYesNo)
If Response = vbNo Then Exit Sub
Else
'On subsequent loops (files), a ReplaceAll is
'executed with the original settings and without
'displaying the dialog box again
With Dialogs(wdDialogEditReplace)
.ReplaceAll = 1
.Execute
End With
End If
'Close the modified document after saving changes
myDoc.Close SaveChanges:=wdSaveChanges
'Next file in folder
myFile = Dir$()
Wend
End Sub
That should get you started. The page has more on subfolders and password protected files.
If you need to do more than find and replace, see also utilities for batch processing documents by Greg Maxey and by Graham Mayor.

Related

How do you run a Word mail merge macro from Excel?

I have a macro in an Excel workbook that currently does the following:
Create a data.csv file with data in the first two rows (for a mail merge)
Pull a template of a selected Word document and make the data.csv file the source for the mail merge
If the user chooses, it finishes the merge for the document
If the user chooses, it opens the document when the macro is complete. If they don't choose to open, the word documents all close.
I've been running into a couple major issues:
The macro only seems to run smoothly if Word is entirely closed beforehand. My current workaround is a popup message if Word is open, telling the user to close word, but this is not ideal because it disrupts flow for some users who may have several instances of Word open.
The macro has been running slowly, especially for some of the document templates that have thousands of merge fields pre-entered in the template. It sometimes take longer than a minute, and sometimes completely freezes.
Would the macro would run more smoothly if I have the Excel VBA open the Word template and have most of the code for setting up and finishing the mail merge in Word VBA? I'm much less familiar with Word VBA - can anyone help me with bringing over my code to word (but still initiated by Excel)? Also, if you can figure out why the macro struggles when Word is already open, I'd greatly appreciate it.
I didn't include the entire code for proprietary reasons, but please let me know if there's something else you need to see.
Thank you!!
Sub Mail_Merge_Dynamic()
Dim mergeFile, tempFilePath As String
Dim WordDoc, WordApp As Object
Dim tempPath, mergePath, finalPath, curDir As String
Dim mergeFilePath, finalFilePath As String
Dim dataPath, FileNameCell, PrincCertCell, MMPrefix As String
Dim FileCount As Integer
Dim Close_Choice, ActiveWindow As String
Dim WarningMsg, WarningMsg2 As String
Dim NotFound, Overwrite1, Overwrite2 As Boolean
Dim oBook As Workbook
'Update csv file for Data Merge
narrative_merge
Call WarpSpeed_On
Sheets("Navigation").Select
Range("Merge_File_1").Select
Set WordApp = CreateObject("Word.Application")
'//////////////////////////////MAIL MERGE MACRO\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
'Set up Mail Merge Documents from Template Folder based on selections on Navigation tab
For i = 1 To FileCount
FileNameCell = "Merge_File_" & i
If Range(FileNameCell) = "" Then
Else
mergeFile = Range(FileNameCell)
tempFilePath = tempPath & mergeFile
mergeFilePath = mergePath & "MM_" & mergeFile
finalFilePath = finalPath & mergeFile
'Activate Mail Merge
If Range("MM_Activate") = 0 Then
Else
Set WordDoc = WordApp.Documents.Open(tempFilePath)
With WordDoc.MailMerge
.MainDocumentType = wdFormLetters
'Set up the mail merge data source
dataPath = curDir & "\data.csv"
.OpenDataSource Name:=dataPath
'Show values in the mail merge fields
.ViewMailMergeFieldCodes = wdToggle
End With
'WordDoc.ShowFieldCodes = False
'WordDoc.MailMerge.ViewMailMergeFieldCodes = False
WordDoc.SaveAs FileName:=mergeFilePath
End If
' Finish mail merge
If Range("MM_Finish") = 0 Then
Else
With WordDoc.MailMerge
.Destination = wdSendToNewDocument
.SuppressBlankLines = True
.Execute Pause:=False
End With
WordDoc.Application.ActiveDocument.SaveAs finalFilePath
End If
End If
Next i
Call CloseWordDocuments
'Make word visible if an Open command has been selected
If Range("MM_Open_Merge") = 1 Or Range("MM_Open_Doc") = 1 Then
curDir = ThisWorkbook.Path
Set WordApp = CreateObject("Word.Application")
For i = 1 To FileCount
FileNameCell = "Merge_File_" & i
If Range(FileNameCell) = "" Then
Else
mergeFile = Range(FileNameCell)
mergeFilePath = curDir & "\Merge-Active Forms\" & "MM_" & mergeFile
finalFilePath = curDir & "\Merge-Complete Forms\" & mergeFile
If Range("MM_Open_Merge") = 1 Then
Set WordDoc = WordApp.Documents.Open(mergeFilePath)
End If
If Range("MM_Open_Doc") = 1 Then
Set WordDoc = WordApp.Documents.Open(finalFilePath)
End If
End If
Next i
WordApp.Visible = True
'Windows(mergeFile).Activate
End If
GoTo Reset
Reset:
Call WarpSpeed_Off
End Sub
Sub WarpSpeed_On_Calcs_Off()
'Optimize Macro Speed
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
' Turn off display alerts
Application.DisplayAlerts = False
End Sub
Sub WarpSpeed_On()
'Optimize Macro Speed
Application.ScreenUpdating = False
Application.EnableEvents = False
' Turn off display alerts
Application.DisplayAlerts = False
End Sub
Sub WarpSpeed_Off()
'Reset Macro Optimization Settings
Application.EnableEvents = True
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
End Sub
Instead of:
Set WordApp = CreateObject("Word.Application")
this will open Word if it is not already open:
Set WordApp = GetObject(, "Word.Application")
Edit#1
In VBA you may do something like:
On Error GoTo CreateObj
' Is Word application already running ?
Set WordApp = GetObject(, "Word.Application")
GoTo gotApp
CreateObj:
' Not running, create first instance:
Set WordApp = CreateObject("Word.Application")
gotApp:
On Error GoTo 0 ' disable error handling
' continue
....
....

Ensure Excel file is not being used before opening using VBA

I am using an interface made with Excel to allow users to concurrently edit a shared data file. To prevent multiple users from editing at the same time I made the following function to do the following:
Open the file
If the file was opened as a read-only, close the file and re-open until the file is opened as read-write or the maximum number of allowed attempts is crossed.
Function OpenTillCanEditC(refpath As String, pw As String) As Workbook
Dim wbtoopen As Workbook
Dim maxOpen As Long
Dim i As Long
Dim buttonClicked As Long
maxOpen = 10
i = 0
On Error GoTo ErrHandler:
Application.DisplayAlerts = False
Set wbtoopen = Workbooks.Open(refpath, True, Password:=pw, ReadOnly:=False)
Application.DisplayAlerts = True
While wbtoopen.ReadOnly And i < maxOpen
If wbtoopen.ReadOnly Then
wbtoopen.Close (False)
Application.Wait (Now + TimeValue("00:00:01"))
Set wbtoopen = Nothing
i = i + 1
If i >= maxOpen Then
buttonClicked = MsgBox("It appears the masterlist is currently being used by someone else. Do you want to retry opening?", vbRetryCancel)
If buttonClicked = vbRetry Then
maxOpen = maxOpen + 10
Application.DisplayAlerts = False
Set wbtoopen = Workbooks.Open(refpath, True, Password:=pw, ReadOnly:=False)
Application.DisplayAlerts = True
End If
Else
On Error GoTo ErrHandler:
Application.DisplayAlerts = False
Set wbtoopen = Workbooks.Open(refpath, True, Password:=pw, ReadOnly:=False)
Application.DisplayAlerts = True
End If
End If
Wend
Set OpenTillCanEditC = wbtoopen
Exit Function
ErrHandler:
Application.DisplayAlerts = True
If Err.Number = 1004 Then
MsgBox "The password keyed in is wrong."
Else
MsgBox "The masterlist found in " & refpath & " cannot be opened. It may be used by someone else or corrupted. If corrupted please open the file manually using Excel."
End If
Set wbtoopen = Nothing
Set OpenTillCanEditC = wbtoopen
End Function
OpenTillCanEditC will be used in a sub for updating as shown below:
Sub UpdateFile()
'Try to open
Dim datawb As Workbook
Dim filepath As String
Dim pw As String
filepath = "C:\Folder Containing File\Data File.xlsx"
pw = "password"
Set datawb = OpenTillCanEditC(filepath, pw)
If datawb Is Nothing Then
MsgBox "File cannot be opened or is currently in use."
Exit Sub
End If
'Do functions needed in the workbook here
datawb.Save()
datawb.Close
End Sub
However I keep getting either of the following two errors:
When multiple people are attempting to access the file, the OpenTillCanEditC function will still ask for a password even though it is already being keyed in.
datawb.Save() will sometimes throw an error stating that the save failed due to multiple users accessing the file.
How do I fix both of these issues to allow multiple users to edit a shared password-protected file using VBA?

Find-replace code through vba gets ignored

I cannot seem to get the VBA code that finds and replaces words in the word document to work.
I can find the words in the word document manually, but the vba code has no effect.
Hello,
I am trying to create a custom form in word through an excel input sheet. My issue is that the code that finds and replaces the words in the word document gets ignored in vba even while the word document is open (I can manually find the words in the document). Opening the word file through VBA is not an issue.
Could someone please show me how to find and replace words in my word document?
As displayed in the code below, I alreay tried the "With.WordDoc.Content.find" method without success.
Below I added the code to find one of the words
Thank you!
Sub CreateWordDocuments()
Dim DocLoc, TagName, TagValue, TemplName, FileName As String
Dim WordDoc, Wordapp As Object
Dim RownumDocLoc As Integer
Dim inputsheet As Worksheet
Set inputsheet = ThisWorkbook.Sheets("Input")
Dim templatesheet As Worksheet
Set templatesheet = ThisWorkbook.Sheets("Templates")
inputsheet.Activate
templaterow = Application.Match("Template:", Columns("B:B"), 0)
If inputsheet.Cells(templaterow, 4) = "" Then
MsgBox "Please complete the template criteria", , "No template selected"
inputsheet.Cells(1, 1).Select
Exit Sub
End If
TemplName = inputsheet.Cells(templaterow, 4)
templatesheet.Activate
RownumDocLoc = Application.Match(TemplName, Columns("F:F"), 0)
DocLoc = templatesheet.Cells(RownumDocLoc, 7) & "\" & TemplName
'Open Word Template
On Error Resume Next
Set Wordapp = GetObject("Word.Application")
If Err.Number <> 0 Then
Err.Clear
Set Wordapp = CreateObject("Word.Application")
Wordapp.Visible = True
End If
'Open Template
Set WordDoc = Wordapp.documents.Open(FileName:=DocLoc, ReadOnly:=False)
With WordDoc.Content.Find
.Text = "samplestring"
.Replacement.Text = "adjustedstring"
.wrap = wdFindContinue
.Execute Replace:=wdReplaceall
End With
End Sub
I would expect the string "samplestring" to be adjusted to "adjustedstring" in the word file. However nothing happens when the code runs (no errors).

Running sub within sub (in a loop) causes textbox TabKeyBehavior to not work properly, ie = False

When dynamically creating textboxes/comboboxes on a userform, I create a combobox with information in another Excel workbook. (Not in the workbook I created the code/userform in. Nor can it be because it may change/get issued in the future & I don't want to have to move code into it).
After I create the combobox, I call another sub, Populate_Steel_List, to populate it. After everything has been generated, when tabbing through the textboxes/comboboxes actual Tab values are being placed in the textboxes/comboboxes, instead of moving on to the next textbox/combobox on the userform!
If I comment out calling for Populate_Steel_List, the userform's tabbing works fine. So I know calling it is causing the problem.
I tried adding a For-Next (For Each-Next as well) loop for each control, setting the .TabKeyBehavior = False. It didn't seem to do anything.
Here is my code:
Private Sub NumberMembers_Change()
' ....there is some other code in this sub, but has been tested,
' ie. commented out, till I found out
' where the offending code actually is
Dim TextBox8 As Object ' have also tried As Control; both run fine,
' but neither fix TabKeyBehavior on the userform
' Create Member Cross Section List Textboxes
Set TextBox8 = Controls.Add("Forms.ComboBox.1")
With TextBox8
.Name = "MemberSectionList" & i
.height = 17
.Width = 90
.Left = 494
.Top = 20 * i
.TabIndex = NextTabNumber + 3
End With
TextBox8Name(i) = TextBox8.Name
MembSectCurrBox = TextBox8.Name
Populate_Steel_List 'If I comment this out, the problem goes away,
' but that's not gonna work
End Sub
' ...and here's the code for Populate_Steel_List
Private Sub Populate_Steel_List()
Dim fd As Office.FileDialog
'Dim TextFile As Integer
' Check to see if log file has already been created
' Skip to using the path to find the excel document to have VBA read from it
If Len(FilePathToAISCShapesDatabase) > 0 Then GoTo Skip
If FilePathToAISCShapesDatabase = vbNullString Then
ChDir (Replace(ActiveWorkbook.FullName, ActiveWorkbook.Name, ""))
Set fd = Application.FileDialog(msoFileDialogFilePicker)
With fd
.AllowMultiSelect = False
' Set the title of the dialog box.
.Title = "Please select the latest AISC shapes database file."
' Clear out the current filters, and add our own.
.Filters.Clear
.InitialFileName = "AISC Shapes Database v14.1.xlsm"
.Filters.Add "Excel", "*.xlsm, *.xlsx"
.Filters.Add "Excel 97–Excel 2003", "*.xls"
If .Show = True Then
FilePathToAISCShapesDatabase = .SelectedItems(1)
'Now I have the filepath to the AISC Shapes Database
Else
Exit Sub
End If
End With
' Create textfile in this location:
' Application.ThisWorkbook.Path & "AISCListLoc.txt"
'Open the text file
Open DirFile For Output As #1
'Write some lines of text
Print #1, FilePathToAISCShapesDatabase
'Save & Close Text File
Close #1
End If
Skip:
' Read from file that you have path to
Application.ScreenUpdating = False
Application.Workbooks.Open (FilePathToAISCShapesDatabase)
Dim rng As Range
Set rng = Sheets("Database v14.1").Range("C2:C" & Range("C" & Rows.Count).End(xlUp).row)
Me.Controls(MembSectCurrBox).List = rng.value
ActiveWorkbook.Close False
Application.ScreenUpdating = True
Set rng = Nothing
AppActivate ThisWorkbook.Application
Dim i ' Not sure if I really need to have this here or not;
' I also added it in the UserForm_Initialize(),
' but adding it doesn't seem to help
For i = 1 To Me.Controls.Count
If TypeName(Me.Controls(i - 1)) = "Textbox" Then
Me.Controls(i - 1).TabKeyBehavior = False
End If
Next
End Sub
TextBox8Name(i) is a private variant I used to keep track of how many comboboxes get generated (for limitations of an FEA program down the road).
FilePathToAISCShapesDatabase is a global string (in the module) so the user doesn't always have to point to the AISC shapes spreadsheet after the first time.
I tried not to select anything while in the other workbook, AISC Shapes Database v14.1.xlsm, but maybe I messed up?

Check Folder Permissions Before Save VBA

I Have created a user form that will open an excel file open & hide the excel. When closing the user form will save & close the excel file. However, there are two types of users of the excel file.
Editors - Those who are entering data into the file
Viewers - Those who are viewing a file.
The folder which has the excel file only allow "Editors" to save. (Others have no permission to write). Therefore, I have to avoid save part if the user has no wright permission to the folder. Any ideas? My code for the close event of user form is here.
Private Sub UserForm_QueryClose (Cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
If ws.AutoFilterMode Then ws.AutoFilterMode = False
ws.Columns("F:H").Copy
ws.Activate
ws.Range("F1").Select
Application.DisplayAlerts = False
Selection.PasteSpecial Paste:=xlPasteValues
Application.DisplayAlerts = True
Application.CutCopyMode = False
Application.Visible = True
ActiveWorkbook.CheckCompatibility = False
ThisWorkbook.Close savechanges:=True
ActiveWorkbook.CheckCompatibility = True
End If
End Sub
Ws Denoted the declared name for the worksheet.
Edit
I have tried & found an alternative method to overcome the situation. However, this is not the solution & is a dirty method to get the result. Please see below code.
Private Sub UserForm_QueryClose (Cancel As Integer, CloseMode As Integer)
On Error Resume Next
If CloseMode = vbFormControlMenu Then
If ws.AutoFilterMode Then ws.AutoFilterMode = False
ws.Columns("F:H").Copy
ws.Activate
ws.Range("F1").Select
Application.DisplayAlerts = False
Selection.PasteSpecial Paste:=xlPasteValues
Application.DisplayAlerts = True
Application.CutCopyMode = False
Application.Visible = True
ActiveWorkbook.CheckCompatibility = False
ThisWorkbook.Save
ThisWorkbook.Close savechanges:=False
ActiveWorkbook.CheckCompatibility = True
End If
End Sub
On above code I have tracked error generated during the save process of viewers & jump to next line by using
on error resume next.
The answer above from Macro Man, while succinct and useful, will not work in an environment where folder access is managed by user groups instead of user names. As many corporate environments - including my own - use this method to manage folder access, I have posted below a solution that will assess a user's actual permissions to a folder. This will work whether the user has been granted individual or group access to a folder.
Private Function TestWriteAccess(ByVal StrPath As String) As Boolean
Dim StrName As String, iFile As Integer, iCount As Integer, BExists As Boolean
'Set the initial output to False
TestWriteAccess = False
'Ensure the file path has a trailing slash
If Right(StrPath, 1) <> "\" Then StrPath = StrPath & "\"
'Ensure the path exists and is a folder
On Error Resume Next
BExists = (GetAttr(StrPath) And vbDirectory) = vbDirectory
If Not BExists Then GoTo Exit_TestWriteAccess 'Folder does not exist
'Set error handling - return False if we encounter an error (folder does not exist or file cannot be created)
On Error GoTo Exit_TestWriteAccess
'Get the first available file name
Do
StrName = StrPath & "TestWriteAccess" & iCount & ".tmp"
iCount = iCount + 1
Loop Until Dir(StrName) = vbNullString
'Attempt to create a test file
iFile = FreeFile()
Open StrName For Output As #iFile
Write #iFile, "Testing folder access"
Close #iFile
TestWriteAccess = True
'Delete our test file
Kill StrName
Exit_TestWriteAccess:
End Function
In researching file access, I also stumbled upon Check Access Rights to File/Directory on NTFS Volume by Segey Merzlikin on FreeVBcode.com; this solution is overkill for my needs (and OP's) but will return the exact access rights that a user has to a particular file.
This checks the access list of the workbook's folder to see if the user's name appears in the list. If it does, then save the file.
If Instr(1, Environ("USERNAME"), CreateObject("WScript.Shell").Exec("CMD /C ICACLS """ & _
ThisWorkbook.Path & """").StdOut.ReadAll) > 0 Then ThisWorkbook.Save
It does this by opening a command prompt, running the ICACLS command through it and reading the output from that command. Then it uses the InStr() method to see if the username appears in that output.

Resources