We have some Report template documents in Word that are linked to template excel documents with various OLE linked objects from Excel to Word.
Since OLE uses absolute paths vs. relative, copy pasting the two documents to another location (or even moving them) breaks the OLE links.
So, what's the best way to facilitate moving the documents to another location AND moving the Word template and being able to link it to another Excel document in the new location?
I've searched through numerous sites and found some solutions for technical people:
http://www.msofficeforums.com/word/38722-word-fields-relative-paths-external-files.html
https://answers.microsoft.com/en-us/msoffice/forum/all/making-excel-links-in-word-portable-ie-relative/8f67c68e-6406-4ef2-9b97-4d96c43dcb2c,
BUT this needs to be easy enough for non-technical people to use.
I would like to be able to copy and paste BOTH documents (the Word template AND the linked Excel template) to a new location and have them work the same way they did in the original location.
I would also like to be able to copy just the Word template to a new location and link it to an Excel template in that new location.
I ended up writing a solution to a problem that I had a difficult time finding an answer for, so I wanted to share what ended up working for me.
The code looks in the working directory of the word document, finds the first excel document (I only have 1 excel file per folder in my job, so this setup works for me), and changes the source of all OLE objects in the word document to match the excel document, which makes it possible to create a word/excel template pair and move them to different locations.
*NOTE: I've used Windows sepecific objects/functions for I/O, i.e. MyFile, MyFSO, MyFolder... etc., but I don't think it would be terribly difficult to make the I/O platform agnostic.
**NOTE: I also haven't really added any error checking as it's a quick and dirty macro that's used internally to facilitate portability AND I've never used vba before, so garbage cleanup etc. was a just kind of thrown in there, if there's a way to refactor everything and clean it up, please let me know.
Sub UpdateWordLinks()
Dim newFilePath As Variant
Dim excelDocs As Variant
Dim range As Word.range
Dim shape As shape
Dim section As Word.section
excelDocs = GetFileNamesbyExt(ThisDocument.Path, ".xlsx")
'The new file path as a string (the text to replace with)'
newFilePath = ThisDocument.Path & Application.PathSeparator & excelDocs(1)
Call updateFields(ThisDocument.fields, newFilePath)
For Each section In ThisDocument.Sections
Call updateHeaderFooterLinks(section.headers, newFilePath)
Call updateHeaderFooterLinks(section.Footers, newFilePath)
Next
'Update the links
ThisDocument.fields.Update
Set newFilePath = Nothing
Set excelDocs(1) = Nothing
Set excelDocs = Nothing
Set range = Nothing
Set shape = Nothing
Set section = Nothing
End Sub
Function GetFileNamesbyExt(ByVal FolderPath As String, FileExt As String) As Variant
Dim Result As Variant
Dim i As Integer
Dim MyFile As Object
Dim MyFSO As Object
Dim MyFolder As Object
Dim MyFiles As Object
Set MyFSO = CreateObject("Scripting.FileSystemObject")
Set MyFolder = MyFSO.GetFolder(FolderPath)
Set MyFiles = MyFolder.Files
ReDim Result(1 To MyFiles.count)
i = 1
For Each MyFile In MyFiles
If InStr(1, MyFile.Name, FileExt) <> 0 Then
Result(i) = MyFile.Name
i = i + 1
End If
Next MyFile
ReDim Preserve Result(1 To i - 1)
GetFileNamesbyExt = Result
Set MyFile = Nothing
Set MyFSO = Nothing
Set MyFolder = Nothing
Set MyFiles = Nothing
End Function
Function updateHeaderFooterLinks(headersFooters As headersFooters, newFilePath As Variant)
Dim headerFooter As Word.headerFooter
For Each headerFooter In headersFooters
Call updateFields(headerFooter.range.fields, newFilePath)
Next
Set headerFooter = Nothing
End Function
Function updateFields(fields As fields, newFilePath As Variant)
Dim field As field
Dim oldFilePath As Variant
For Each field In fields
If field.Type = wdFieldLink Then
oldFilePath = field.LinkFormat.SourceFullName
field.Code.Text = Replace(field.Code.Text, _
Replace(oldFilePath, "\", "\\"), _
Replace(newFilePath, "\", "\\"))
End If
Next
Set field = Nothing
Set oldFilePath = Nothing
End Function
It works for me by allowing me to copy paste either both a word and excel file together to a new location and run the macro, or by allowing me to copy paste the word document only and run the macro to link it to an excel doc in the new location.
**I should also note that I only needed to look in the body and header/footer stories for the links we use, so this code is not as robust as it could be, but I don' think it would be too tough to add another loop or two to cover off any missing stories
Cheers!
If you are still looking for a way to create portable links between Excel and Word, our Excel-to-Word Document Automation Add-in may help. Unlike native linking through Office you can: rename files, copy/paste and reorganize content, share the linked files, etc. The same Excel document can update multiple destination Word and/or PowerPoint report templates. You only need to set up the links once and they can be updated numerous times. I hope this helps, you can find out more at https://analysisplace.com/Document-Automation
Related
I am new member, i will do my best efforts to be specifc and clear and respect the time of the members.I am a begginer in VBA and need your help.
I am using a free version of the program PDFtk that shows information about pdf files through a graphical interface (you can see the attached picture).
PDFtk
The program show for each pdf file 2 types of information:
The file name
The number of pages
However, i'm looking for a VBA macro that will retrive that information (file name, number of pages) and then write it to an open workbook in excel.
It's important that the VBA will search for pdf files at specific location in my computer c:\temp
and then write to excel somthing like this:
Excel filenames and number of pages
In another words, a need a VBA that can do the job without "really" opening the graphical interface, and yet use the PDFtk application to get the correct number of pages.
Thanks in advance for your help
Try this (source: link)
Sub Test()
Dim I As Long
Dim xRg As Range
Dim xStr As String
Dim xFd As FileDialog
Dim xFdItem As Variant
Dim xFileName As String
Dim xFileNum As Long
Dim RegExp As Object
Set xFd = Application.FileDialog(msoFileDialogFolderPicker)
If xFd.Show = -1 Then
xFdItem = xFd.SelectedItems(1) & Application.PathSeparator
xFileName = Dir(xFdItem & "*.pdf", vbDirectory)
Set xRg = Range("A1")
Range("A:B").ClearContents
Range("A1:B1").Font.Bold = True
xRg = "File Name"
xRg.Offset(0, 1) = "Pages"
I = 2
xStr = ""
Do While xFileName <> ""
Cells(I, 1) = xFileName
Set RegExp = CreateObject("VBscript.RegExp")
RegExp.Global = True
RegExp.Pattern = "/Type\s*/Page[^s]"
xFileNum = FreeFile
Open (xFdItem & xFileName) For Binary As #xFileNum
xStr = Space(LOF(xFileNum))
Get #xFileNum, , xStr
Close #xFileNum
Cells(I, 2) = RegExp.Execute(xStr).Count
I = I + 1
xFileName = Dir
Loop
Columns("A:B").AutoFit
End If
End Sub
Thank you for the link and thanks for your reply.
I have already tried that link couple of weeks ago.
I also tried this method- https://www.mrexcel.com/board/threads/vba-page-count.347911/ VBA code written by Haluk at year 2008, and although giving better results it still make mistakes.
After reading a lot about this subject i realized that a VBA that doesn't use a third party program make a lot of mistakes regarding counting pdf pages.
sometimes count more pages than the real, sometimes less than the real, sometime count zero, Counting pages Is very tricky and without a program it is not reliable.
I read an explanation why counting pdf pages without third party program is tricky
https://www.reddit.com/r/visualbasic/comments/29n2xa/getting_pdf_page_count/
Until now I found only one vba that always give the correct number of pdf pages - a vba that use adobe acrobat professional - which means using a third party program which is not free. This is not an option for me.
http://www.vbaexpress.com/forum/archive/index.php/t-40734.html
I prefer a free solution, and for that reason i need a vba that use a free third party program, for example PDFtk ,pdf sam basic.
Pdf property extension also count pages correctly- it is a lightweight COM extension that brings back PDF properties and columns to Windows Explorer – but I have no idea how to write a vba for it. https://coolsoft.altervista.org/en/pdfpropertyextension
At work we get a number X of e-mails per day which must be handled by 4 people. I am trying to come up with a macro that, when run, takes X and splits it randomly and equally into 4 subfolders so that every person knows what they have to do. I don’t have any coding background and my Excel knowledge is basic at best. How would I start and is it even doable?
Thanks!
Below is my pseudo-code for your requirement. Can you understand what I am suggesting? Can you suggest improvements? Try to convert my pseudo-code to VBA and update your question with your progress. I will respond with comments on your code if I think you are going wrong.
A issue to consider is where should this code be placed. It could be an Outlook macro or an Excel macro. An Outlook macro would be easier to write. But to run it, you would have to open Outlook and run the macro. With an Excel macro, you could create a specific workbook with an auto-run macro. You would simply click the workbook to start it and the emails would be distributed. A final message would tell how many emails had been distributed. The Excel macro would require greater understanding of linking different Microsoft Office products and some understanding of events.
Dim FldrInbox as Folder
Dim FldrsTeam(1 To 4) as Folder
Dim InxEmail as Long
Dim InxTeam as Long
Set FldrInbox = Reference to Shared Inbox
Set FldrsTeam(1) = Reference To Alice’s folder
Set FldrsTeam(2) = Reference To Bernard’s folder
Set FldrsTeam(3) = Reference To Christine’s folder
Set FldrsTeam(4) = Reference To David’s folder
InxTeam = Random number between 1 and 4
For InxEmail = FldrInbox.Items.Count To 1 Step -1
FldrInbox.Items(InxEmail).Move FldrsTeam(InxTeam)
If InxTeam = 4 Then
InxTeam = 1
Else
InxTeam = InxTeam + 1
End If
Next
Edit 1
The code below demonstrates the second technique for finding folders. It creates a file on your desktop containing an indented list of every store and folder to which you have access. Note: some of these folders are “secret” and are not listed in your folder pane because there is nothing you, as a human, can do with these folders. However, you could write a macro to access these folders if you wish.
I have lots of Outlook modules which I name for their contents. In general, I have one module per task. I also have some general modules. This code comes from my “ModDemonstrations”. You are just starting so you may not need several modules, but I have been writing Outlook VBA for 15 years and have lots of macros and organising them is essential.
This routine needs a reference to Microsoft Scripting Library. If you do not know what that means, I will add an explanation.
Run ListStoresAndAllFolders() and a file will be created on your desktop named “ListStoresAndAllFolders.txt”. You do not need this code today, but I believe you will find it helpful for the future. It uses recursion. Look up recursion. If you do not understand the explanations you find, I will try to write my own explanation.
Note that I write Dim FldrCrnt As Outlook.Folder. There are two types of Folder: Outlook and Scripting. A Scripting.Folder is a disc folder. If you progress to copying information from Outlook folders to disc folders, you must be careful to specify which type of folder you mean.
Sub ListStoresAndAllFolders()
' Displays the name of every accessible store
' Under each store, displays an indented list of all its folders
' Technique for locating desktop from answer by Kyle:
' http://stackoverflow.com/a/17551579/973283
' Needs reference to Microsoft Scripting Runtime if "TextStream"
' and "FileSystemObject" are to be recognised
Dim FileOut As TextStream
Dim FldrCrnt As Outlook.Folder
Dim Fso As FileSystemObject
Dim InxFldrChild As Long
Dim InxStoreCrnt As Long
Dim Path As String
Dim StoreCrnt As Outlook.Folder
Path = CreateObject("WScript.Shell").specialfolders("Desktop")
Set Fso = CreateObject("Scripting.FileSystemObject")
Set FileOut = Fso.CreateTextFile(Path & "\ListStoresAndAllFolders.txt", True)
With Application.Session
For InxStoreCrnt = 1 To .Folders.Count
Set StoreCrnt = .Folders(InxStoreCrnt)
With StoreCrnt
FileOut.WriteLine .Name
For InxFldrChild = .Folders.Count To 1 Step -1
Set FldrCrnt = .Folders(InxFldrChild)
Call ListAllFolders(FldrCrnt, 1, FileOut)
Next
End With
Next
End With
FileOut.Close
End Sub
Sub ListAllFolders(ByRef Fldr As Folder, ByVal Level As Long, ByRef FileOut As TextStream)
' This routine:
' 1. Output name of Fldr
' 2. Calls itself for each child of Fldr
' It is designed to be called by ListStoresAndAllFolders()
Dim InxFldrChild As Long
With Fldr
FileOut.WriteLine Space(Level * 2) & .Name
For InxFldrChild = .Folders.Count To 1 Step -1
Call ListAllFolders(.Folders(InxFldrChild), Level + 1, FileOut)
Next
End With
End Sub
I have some VBA code in excel 2010 that imports multiple .csv files in to one excel workbook however, sometimes there is a rogue file that contains nothing and has a file size of zero that throws up an error, I then have to manually go to the folder and delete this and run my macro again.
Therefore I'm after some help that will allow me to check the file size's of all .csv file contained in a folder and delete any that are zero before I import them. Is there a way I can do this? Or possibly another suggested method that would help?
I'm very new to VBA so please be patient if I don't fully understand straight away.
I have looked into FileLen(C:\Test\test.csv) = 0 Then Kill said file.
But this only deletes specifically the file mentioned. I would rather check all file sizes and if any are zero, kill them.
You can use the FileSystemObject to check files in a folder, and delete them if they are of the correct type and size = 0.
Be very careful running this as the deletions will be permanent (not recoverable).
Option Explicit
'Set reference to Microsoft Scripting Runtime
Sub terfuge()
Dim FSO As FileSystemObject, FI As File, FIs As Files, FO As Folder
Const strBasePath As String = "full_path_of_folder_to_search"
Dim bMsg As Integer
Set FSO = New FileSystemObject
Set FO = FSO.GetFolder(strBasePath)
Set FIs = FO.Files
For Each FI In FIs
If FI.Name Like "*.csv" Then
If FI.Size = 0 Then
bMsg = MsgBox(Prompt:="Are you sure you want to delete " & FI.Name & "?", Buttons:=vbYesNoCancel)
Select Case bMsg
Case vbYes
FI.Delete
Case vbCancel
Exit Sub
End Select
End If
End If
Next FI
End Sub
You can use the DIR command to search through all files in a folder then kill any with a file size of 0.
The code below searches through a specified folder and lists the *.xls filenames in a sheet called Main.
It should be easy to adapt this and combine with your own Kill command.
Sub Directory()
Dim strPath As String
Dim strFolderPath As String
Dim strFileName As String
Dim intRow As Integer
Dim intColumn As Integer
Dim intNumber As Integer
intRow = 1
intColumn = 1
intNumber = 0
strFolderPath = "h:\excel\*.xls" 'search through H drive excel folder
strFileName = Dir(strFolderPath) 'Get name of first file
Do
Sheets("Main").Cells(intRow, intColumn) = strFileName 'write filename
strFileName = Dir 'get next filename
intRow = intRow + 1
Loop Until strFileName = ""
End Sub
Thanks both for your speedy responses, I managed to eventually get my loop going jiggling Andy's sample.
I'd already started dabbling with his idea before Ron responded so continued with it.
I do however like the idea of the user being asked first whether or not they would like to delete the file, so I think I'll have a stab at Ron's suggestion too and maybe learn something along the way.
Thanks again.
I'm trying to create a macro that lists all the files in a given folder (and its sub folders) which match criteria for filename (in my example "job checklist") and type (in my example "*.xlsm"). since all the workbooks of this type and naming convention in my search folder are of the same type, i need to open and read values from each workbook and copy them into my host workbook. when the macro is run the run date/time should be noted in the host workbook, so that when the macro is run subsequently only new workbooks OR workbooks which have been modified since the most recent time stamp need to be opened and updated in the host workbook.
I have been trying to use some recursive code found in other posts, but threads, but i'm having a hard time to incorporate search criteria:
- file name
- file type
- modified date
[here] (VBA macro that search for file in multiple subfolders)
I have also tried to encorporate code from Pearson here to allow me to check file attributes of xls files but it doesnt seem to work (maybe due to 64 bit, though i found another version which was supposed to be compatible)
I've been trying to find a solution for several days, but am kinda stuck, any help would be appreciated.
working code i have so far which is listing all the files here of type .zip in my host workbook, i don't know how to check the modified date of a file. i assume that if i could i could add some code to open files (which meet type, name and modified (compared to a date/time value cell in the host workbook, and updates every time the macro is run) and then extract values from a known sheet/range into the host workbook.
```vba
Sub MainList()
Dim folder, xdir As Variant
Set folder = Application.FileDialog(msoFileDialogFolderPicker)
Call ListFilesInFolder("C:\Users\60066690\Desktop\Documents from BCP and loose MTC", True)
End Sub
Sub ListFilesInFolder(ByVal xFolderName As String, ByVal xIsSubfolders As Boolean)
Dim xFileSystemObject As Object
Dim xFolder As Object
Dim xSubFolder As Object
Dim xFile As Object
Dim rowIndex As Long
Set xFileSystemObject = CreateObject("Scripting.FileSystemObject")
Set xFolder = xFileSystemObject.GetFolder(xFolderName)
rowIndex = Application.ActiveSheet.Range("A65536").End(xlUp).Row + 1
For Each xFile In xFolder.Files
If Not InStr(1, xFile.Name, ".zip") = 0 Then
'could need to add in here If for name, but not sure how to add If for modified date, if i could i could compare the modified date to a date cell in this workbook
Application.ActiveSheet.Cells(rowIndex, 1).Formula = xFile.Name
'need to add some code in here to open the found workbook, then extract some values from known sheets/cells, store those values in variables, then close the found workbook and output the found variables to the colums beside the file name
rowIndex = rowIndex + 1
End If
Next xFile
If xIsSubfolders Then
For Each xSubFolder In xFolder.SubFolders
ListFilesInFolder xSubFolder.Path, True
Next xSubFolder
End If
Set xFile = Nothing
Set xFolder = Nothing
Set xFileSystemObject = Nothing
End Sub
```
I have two folders filled with similarly named excel files but in different templates. One of the templates (Newer) is blank, while the old template contains all the data.
The patterns between template A and template B are consistent - I know which cell in A goes to which cell in B, but I'm not sure how to create the macro in VBA to effectively process all of the files in one go.
I've so far created two File system objects, one per folder - but I'm not sure how to get it to pull up the identical file from the other folder to start the cloning process.
To avoid excel having issues opening files of the same name, the newer templates have a 3 character suffix at the end.
Any advice would be greatly appreciated!
Set picker = Application.FileDialog(msoFileDialogFolderPicker)
picker.Show
Set fldrs = picker.SelectedItems
fpath1 = fldrs(1)
Set picker = Application.FileDialog(msoFileDialogFolderPicker)
picker.Show
Set fldrs = picker.SelectedItems
fpath2 = fldrs(1)
Dim fso1 As Object
Dim vfolder1 As Object
Dim fso2 As Object
Dim vfolder2 As Object
Set fso1 = CreateObject("Scripting.FileSystemObject")
Set vfolder1 = fso1.GetFolder(fpath1)
Set fso2 = CreateObject("scripting.filesystemobject")
Set vfolder2 = fso2.GetFolder(fpath2)
For Each vfile In vfolder1.Files
Assuming that the file names always start the same ("the newer templates have a 3 character suffix at the end"), you can just strip the path and the extension from the file names with GetBaseName and compare them to see if the target file name starts with the source file name:
With New Scripting.FileSystemObject
Dim source As Folder, target As Folder
Set source = .GetFolder(fpath1)
Set target = .GetFolder(fpath2)
Dim item As File, fileName As String
For Each item In source.Files
'Get the filename without path or extension.
fileName = .GetBaseName(item)
Dim searched As File
For Each searched In target
'Does the file start with fileName?
If InStr(1, .GetBaseName(item), fileName) = 1 Then
'Files match, do your thing here.
End If
Next
Next
End With
Note that this is early bound. If you're insistant on not adding a reference to Microsoft Scripting Runtime (there's almost never a reason not to), just change the early bound variables to Object and replace the New Scripting.FileSystemObject with CreateObject.