Updating Links via VBA - excel

I am trying to update a file that is linked to three other files using VBA.
My code works, however, it requires all three files paths to be the same. How can I modify my code to allow all three links to be different?
Sub UpdateLinks()
Dim NewLink As Variant
Dim links As Variant
Dim i As Integer
links = ActiveWorkbook.LinkSources(xlExcelLinks)
NewLink = Application.GetOpenFilename
For i = 1 To UBound(links)
ActiveWorkbook.ChangeLink Name:=links(i), NewName:= NewLink, _
Type:=xlExcelLinks
Next i
End Sub

Related

VBA code that reads a txt file, places specified words into columns

I'm trying to write a VBA macro that will read through a text document and place specific words into columns. UPDATE: Here's a sample of the file, apparently it's XML, so at least I learned something new today. So i guess what I need is a program to shed the XML parts, and place just the text into columns.
<Alarm>
<ID>1002</ID>
<Type>Fault</Type>
<Message>Bad Brake</Message>
<Tagname>error.e2</Tagname>
</Alarm>
<Alarm>
<ID>1004</ID>
<Type>Fault</Type>
<Message>No Motion</Message>
<Tagname>error.e4</Tagname>
</Alarm>
<Alarm>
<ID>1005</ID>
<Type>Fault</Type>
<Message>Upper safety door open</Message>
<Tagname>error.e5</Tagname>
</Alarm>
Ultimately, I'm trying to put the 4 digit error codes in column A (i.e. 1002, 1004...), and the error message in column B (i.e. Bad Brake, No motion....). I'll paste what I have so far, I tried coding it for just one pair of data to start. I'm stuck trying to get the error message into column B. The error messages all start in the same position on each line, but I can't figure out how to stop copying the text, since each error message is a different length of characters. Any ideas?
(P.S. - I apologize if the code is terrible, I've been interning as an electrical engineer, so my programming has gotten rather rusty.)
Private Sub CommandButton1_Click()
Dim myFile As String, textLine As String, ID As Integer, error_msg As Integer
myFile = "C:\Users\scholtmn\Documents\Projects\Borg_Warner_txt_file\BW_fault_codes.txt"
Open myFile For Input As #1
Do Until EOF(1)
Line Input #1, textLine
Text = Text & textLine
Loop
Close #1
ID = InStr(Text, "<ID>")
error_msg = InStr(Text, "<Message>")
Range("A1").Value = Mid(Text, ID + 4, 4)
Range("B1").Value = Mid(Text, error_msg + 9, (InStr(Text, " <") - 31))
End Sub
Please, try the next code:
Sub ExtractErrorsDefinition()
'it needs a reference to 'Microsoft XML, v6.0'
Dim XMLFileName As String, oXMLFile As New MSXML2.DOMDocument60, sh As Worksheet
Dim N As MSXML2.IXMLDOMNode, i As Long, arr
Set sh = ActiveSheet 'use here the necessary sheet
XMLFileName = "the full text file path" '"C:\Utile\Teste Corel\XMLtext.txt"
oXMLFile.Load (XMLFileName)
ReDim arr(1 To oXMLFile.SelectNodes("AlarmDictionary/Alarm").length, 1 To 2): i = 1
For Each N In oXMLFile.SelectNodes("AlarmDictionary/Alarm")
arr(i, 1) = N.SelectSingleNode("ID").Text: arr(i, 1) = N.SelectSingleNode("Message").Text: i = i + 1
Next
sh.Range("A2").Resize(UBound(arr), 2).value = arr
End Sub
It may work using late binding, but it is better to have the intellisense suggestion, especially when not very well skilled in working with XML.
If looks complicated to add such a reference, I can add a piece of code to automatically add it.
Please, run the next code to automatically add the necessary reference. Save your workbook and run the first code after:
Sub addXMLRef()
'Add a reference to 'Microsoft Scripting Runtime':
'In case of error ('Programmatic access to Visual Basic Project not trusted'):
'Options->Trust Center->Trust Center Settings->Macro Settings->Developer Macro Settings->
' check "Trust access to the VBA project object model"
Application.VBE.ActiveVBProject.References.AddFromFile "C:\Windows\System32\msxml6.dll"
End Sub
It looks like the txt file you are using is actually an xml file. If you changed the format, this piece of code I slightly adjusted from here should work fine.
Sub From_XML_To_XL()
Dim xmlWb As Workbook, xSWb As Workbook, xStrPath$, xfdial As FileDialog, _
xFile$, lr%, first As Boolean, r As Range
first = True
Set xfdial = Application.FileDialog(msoFileDialogFilePicker)
xfdial.AllowMultiSelect = False
xfdial.Title = "Select an XML File"
If xfdial.Show = -1 Then xStrPath = xfdial.SelectedItems(1) & ""
If xStrPath = "" Then Exit Sub
Set xSWb = ThisWorkbook
lr = xSWb.ActiveSheet.Range("a" & Rows.Count).End(xlUp).Row ' last used row, column A
xFile = xStrPath
Set xmlWb = Workbooks.OpenXML(xFile)
If first Then
Set r = xmlWb.Sheets(1).UsedRange ' with header
Else
xmlWb.Sheets(1).Activate
Set r = ActiveSheet.UsedRange
Set r = Range(Cells(3, 1), Cells(r.Rows.Count, r.Columns.Count))
End If
r.Copy xSWb.ActiveSheet.Cells(lr + 1, 1)
lr = xSWb.ActiveSheet.Range("a" & Rows.Count).End(xlUp).Row
xmlWb.Close False
first = False
End Sub
I think you'll find this task a lot easier if you take advantage of the fact it is in XML format. You can find more information about working with XML in VBA here.
As Ben Mega already stated: you have an XML-File - why not use XML-functionality.
Add "Microsoft XML, v6.0" to your project references - then you can use this code
Public Sub insertTextFromXML()
Dim objXML As MSXML2.DOMDocument60
Set objXML = New MSXML2.DOMDocument60
If Not objXML.Load("T:\Stackoverflow\Test.xml") Then
Err.Raise objXML.parseError.ErrorCode, , objXML.parseError.reason
End If
Dim nAlarm As MSXML2.IXMLDOMNode
'loop through all alarms and output ID plus message
For Each nAlarm In objXML.SelectNodes("AlarmDictionary/Alarm")
With nAlarm
Debug.Print .SelectSingleNode("ID").Text, .SelectSingleNode("Message").Text
End With
Next
'Filter for ID 1004
Set nAlarm = objXML.SelectSingleNode("AlarmDictionary/Alarm[ID=1004]")
Debug.Print nAlarm.XML
End Sub
You can google for VBA XPath to find out how to access the various values.

Is there a way to export a list of all links within an excel document

As part of an automation project I am automating the updating of values within an excel file. There are multiple files to be updated and each of these has links to other files, usually links to around 20 files.
In order to streamline automation and optimise the process I need a list of all files each of the "documents to be updated" links to.
Is there a macro I can use other tool to export a list of links within an excel file. Even just printing it to somewhere within the excel file itself would be useful and allow me to map links across all files.
For clarity:
There are many formulas that reference external workbooks.
An example of one such formula:
VLOOKUP($D$7,'C:\DATA\[BalanceSheet-LevelsFlows-VO-M.xlsx]Position'!$A$1:$HA$9999,F4,FALSE)
There are more than 36000 formulas referencing external workbooks in one file. There are references to 8 external workbooks.
My aim is to get a list of these external workbooks.
Whilst it would be fast to do it manually in this instance I have over 30 of the files I need to list external references for and some of these reference over 30 external workbooks.
This is a one off process for mapping, it doesn't need to be elegant, just quicker than doing it manually.
Link Sources
LinkSources method
XlLink enumeration
The Code
Option Explicit
Sub testLinkSources()
Dim wb As Workbook: Set wb = ThisWorkbook
Dim Data As Variant: Data = getLinkSources(wb)
If Not IsEmpty(Data) Then
Dim ws As Worksheet: Set ws = wb.Worksheets.Add
ws.Range("A1").Resize(UBound(Data)).Value = Data
End If
Dim arr As Variant: arr = LinkSourcesToArray(wb)
If Not IsEmpty(arr) Then
Debug.Print Join(arr, vbLf)
End If
End Sub
Function getLinkSources( _
wb As Workbook) _
As Variant
If Not IsEmpty(wb.LinkSources) Then
Dim arr() As Variant: arr = wb.LinkSources
Dim rCount As Long: rCount = UBound(arr)
Dim Data As Variant: ReDim Data(1 To rCount, 1 To 1)
Dim r As Long
For r = 1 To rCount
Data(r, 1) = arr(r)
Next r
getLinkSources = Data
End If
End Function
Function LinkSourcesToArray( _
wb As Workbook) _
As Variant
If Not IsEmpty(wb.LinkSources) Then
LinkSourcesToArray = wb.LinkSources
End If
End Function

Accessing an image that's inside of an Excel Table via VBA

I am designing a VBA Form in Excel. The Workbook has a table called "images", and inside there I am dropping some images from my local hard drive.
These Workbook & UserForm are to be shared with my colleagues. They might not have these images in their harddrive, but they will have them inside of the Excel table.
I am looking for a way to load an image that's inside of a table inside of an "Image" VBA form control.
In Google all I find is how to load an image from my hard drive (i.e. using an absolute path like "C:/my_images/car.png"). What I can't find is how to load an image that's within a table, i.e. already bundled within the Workbook.
Any ideas?
If you are still interested in this question, I came up with a solution.
First you need to export the picture from the shape into a file. I found that only .jpg files can be used. My code generates a temporary filename (you need to be able to read/write that path but I think it is usually not a problem), and saves the picture by inserting it into a ChartObject, which can export its contents as a picture. I suppose this process may modify (e.g. compress) the original data but I saw no visible difference on the screen.
When this is done, it loads the picture from this file into the Image control on the UserForm.
Finally, it deletes the temporary file to clean up this side-effect.
Option Explicit
' Include: Tools > References > Microsoft Scripting Runtime
Private Sub cmdLoad_Click()
' Assumption: The UserForm on which you want to load the picture has a CommandButton, cmdLoad, and this function is its event handler
Dim imgImageOnForm As Image: Set imgImageOnForm = imgTarget ' TODO: Set which Control you want the Picture loaded into. You can find the Name in the VBA Form Editor's Properties Bar
Dim strSheetName As String: strSheetName = "TargetSheet" ' TODO: Specify the Name of the Worksheet where your Shape (picture) is
Dim strShapeName As String: strShapeName = "TargetPicture" ' TODO: Specify the Name of your Shape (picture) on the Worksheet
Dim strTemporaryFile As String: strTemporaryFile = GetTemporaryJpgFileName ' TODO: Give a path for the temporary file, the file extension is important, e.g. .jpg can be loaded into Form Controls, while .png cannot
LoadShapePictureToFormControl _
strSheetName, _
strShapeName, _
imgImageOnForm, _
strTemporaryFile
End Sub
Private Sub LoadShapePictureToFormControl(strSheetName As String, strShapeName As String, imgDst As MSForms.Image, strTemporaryFile As String)
' Note: This Sub overwrites the contents of the Clipboard
' Note: This Sub creates and deletes a temporary File, therefore it needs access rights to do so
Dim shpSrc As Shape: Set shpSrc = ThisWorkbook.Worksheets(strSheetName).Shapes(strShapeName)
Dim strTmp As String: strTmp = strTemporaryFile
ExportShapeToPictureFile shpSrc, strTmp
ImportPictureFileToImage strTmp, imgDst
FileSystem.Kill strTmp
End Sub
Private Sub ExportShapeToPictureFile(shpSrc As Shape, strDst As String)
shpSrc.CopyPicture xlScreen, xlBitmap
Dim chtTemp As ChartObject: Set chtTemp = shpSrc.Parent.ChartObjects.Add(0, 0, shpSrc.Width, shpSrc.Height)
With chtTemp
.Activate
.Parent.Shapes(.Name).Fill.Visible = msoFalse
.Parent.Shapes(.Name).Line.Visible = msoFalse
.Chart.Paste
.Chart.Export strDst
.Delete
End With
End Sub
Private Sub ImportPictureFileToImage(strSrc As String, imgDst As MSForms.Image)
Dim ipdLoaded As IPictureDisp: Set ipdLoaded = StdFunctions.LoadPicture(strSrc)
Set imgDst.Picture = ipdLoaded
End Sub
Private Function GetTemporaryJpgFileName() As String
Dim strTemporary As String: strTemporary = GetTemporaryFileName
Dim lngDot As Long: lngDot = InStrRev(strTemporary, ".")
If 0 < lngDot Then
strTemporary = Left(strTemporary, lngDot - 1)
End If
strTemporary = strTemporary & ".jpg"
GetTemporaryJpgFileName = strTemporary
End Function
Private Function GetTemporaryFileName() As String
Dim fsoTemporary As FileSystemObject: Set fsoTemporary = New FileSystemObject
Dim strResult As String: strResult = fsoTemporary.GetSpecialFolder(TemporaryFolder)
strResult = strResult & "\" & fsoTemporary.GetTempName
GetTemporaryFileName = strResult
End Function

For loop preserving previous iteration

I don't have much experience with VBA so it's been difficult to troubleshoot this. When running the code, it outputs Array(i<=i) instead of Array(i)
I've tested the for condition and found Array(0) properly returns the result. However Array(1) will print Array(1) with Array(0) and so on.
The goal of this code is to group worksheets based on their name and print them to pdfs based on grouping, i.e. all sheets starting with I1 to a single pdf.
Sub Test()
FolderPath = "C:\Example"
Dim aWS()
Dim n As Integer
Dim ws As Worksheet
Dim DocTypes()
DocTypes = Array("I1","I2","I3")
For i = LBound(DocTypes) To UBound(DocTypes)
For Each ws In Worksheets
If Left(ws.Name, 2) = DocTypes(i) Then
n = n + 1
ReDim Preserve aWS(1 To n) 'Add 1 to to array length
aWS(n) = ws.Name 'Add worksheet name meeting If condition
End If
Next ws
Sheets(aWS).Select
ActiveSheet.ExportAsFixedFormat Type:=xlTypePDF, Filename:=FolderPath &
DocTypes(i), _
openafterpublish:=True, ignoreprintareas:=False
Next i
End Sub
What I expect is:
i = 0 to 2
First Array(i) = "I1" so output all sheets beginning with "I1" as a pdf
Then move to i = 1
Here Array(i) = "I2" so output all sheets beginning with "I2" as a pdf
However when I step forward it doesn't seem to be following this logic and I don't understand why. I'm thinking it has to do with the selection, it would follow that if i=0 was selected, then i=1 was added to the selection this problem would make sense. I've tried re-selecting a single sheet right before Next i to force past this but it didn't work. This leads me to think I've made a logical mistake in my for loops.
You might not be aware but you can use a variant as a control variable in a for each to iterate an array of variants. Your use of redim to extend an array by 1 item suggests that you should be using a scripting dictionary as an intermediate step to your array. The .Items method of a scripting dictionary returns an array of items so it is easy to get your array that you use subsequently. Here is your code revised to use a scripting.dictionary and a variant control variable. In your specific case we are basically using the scripting.dictionary as a list by making the key and the item the same thing.
Option Explicit
Sub Test()
Const FolderPath As String = "C:\Example"
Dim aWS As Scripting.Dictionary
Dim ws As excel.Worksheet
Dim DocTypes() As Variant
Dim DocType As Variant
DocTypes = Array("I1", "I2", "I3")
For Each DocType In DocTypes
Set aWS = New Scripting.Dictionary
For Each ws In Worksheets
If DocType = left(ws.Name, 2) Then
aWS.Add Key:=ws.Name, Item:=ws.Name
End If
Next ws
Sheets(aWS.Items).Select
ActiveSheet.ExportAsFixedFormat _
Type:=xlTypePDF, _
FileName:=FolderPath & DocType, _
openafterpublish:=True, _
ignoreprintareas:=False
Next
End Sub
Use Selection.ExportAsFixedFormat etc instead of ActiveSheet. The ActiveSheet is always only one sheet while your selection comprises many.
Upon further study I find that you may have to include making a selection for each of the worksheets, like Ws.UsedRange.Select. Take a look at this thread.

VBA: List of folder paths, return list of excel file paths, then edit excels

I have a user-form that pastes folder-paths into a list. I then have the code below that is supposed to loop through that list and list all the sub-folders (then I'll probably have another code loop through the sub-folders to get the excel workbooks).
I know it's inelegant, because ultimately what I want is have my list of paths be looked in one a time, through each folder and subfolder to find and list the excel files. But there was a question like that and it was taken down. The question was then referred to a different q&a that I did not understand, that had to do with individual FILE NAMES, typed in a single cell not a range, nor as a path. I speak Russian, which some of his code was in, and still couldn't quite understand what his code meant and was referring to, and when I tried it, it kept telling met that "GetData" was undefined? so I've tried to ask a different but similar question in the hope that someone can explain to me what I need to do, as I've gone as far as I can and have tried to adapt both codes from the links in this post as well as many others. I have several modules with broken code that doesn't work, and the closest I've come is the code below. At this point I'd settle simply for a way to list the excel file names from a list of paths.
Option Explicit
Dim i As Long, j As Long
Dim searchfolders As Variant
Dim FileSystemObject
Sub ListOfFolders77()
Dim LookInTheFolder As String
'Dim ws As Worksheet: Set ws = Sheets("Output4")
Dim ws2 As Worksheet: Set ws2 = Sheets("Output3")
Dim rng As Range: Set rng = ws2.Range("A1:A" & Rows.Count).End(xlUp)
Dim mypath As Range
'Dim Region As Range: Set Region = ws.Range("A2")
'Dim district As Range: Set district = ws.Range("B2")
'Dim city As Range: Set city = ws.Range("C2")
'Dim atlas As Range: Set atlas = ws.Range("D2")
i = 1
For Each mypath In rng
LookInTheFolder = mypath.Value
Set FileSystemObject = CreateObject("Scripting.FileSystemObject")
For Each searchfolders In FileSystemObject.GetFolder(LookInTheFolder).subfolders
Sheets("Subfolders").Cells(i, 1) = searchfolders
i = i + 1
SearchWithin searchfolders
Next searchfolders
Next mypath
End Sub
Sub SearchWithin(searchfolders)
On Error GoTo exits
For Each searchfolders In FileSystemObject.GetFolder(searchfolders).subfolders
j = UBound(Split(searchfolders, "\"))
Cells(i, j) = searchfolders
i = i + 1
SearchWithin searchfolders
Next searchfolders
exits:
End Sub
Ideally I want to get all the excel files in the folders and subfolders, and copy paste the data on the first sheet into one long list, but I'm still on step 1. I posted a more detailed explanation here last week and have yet to receive any feedback or potential tips.
I apologize if this doesn't make sense or seems half-hazard. I am self taught in excel VBA and am struggling to understand if what I need is even possible. I attempted using Directory but I've little success putting directory in a for each loop.
I also tried using an array, which almost crashed by computer as it went to list ALL the folders and files in my entire computer.
If I understand correctly, your requirements are as follows:
Begin with a set of root paths
Iterate recursively through all the files in each root path
For each file in the resulting collection, if it's an Excel file, add to final list for further processing
Let's start with the first two points. I would suggest the following code (make sure to add a reference to Microsoft Scripting Runtime via Tools -> References... in the VBA editor menus):
Public Function GetFiles(ByVal roots As Variant) As Collection
Select Case TypeName(roots)
Case "String", "Folder"
roots = Array(roots)
End Select
Dim results As New Collection
Dim fso As New Scripting.FileSystemObject
Dim root As Variant
For Each root In roots
AddFilesFromFolder fso.GetFolder(root), results
Next
Set GetFiles = results
End Function
Private Sub AddFilesFromFolder(folder As Scripting.folder, results As Collection)
Dim file As Scripting.file
For Each file In folder.Files
results.Add file
Next
Dim subfolder As Scripting.folder
For Each subfolder In folder.SubFolders
AddFilesFromFolder subfolder, results
Next
End Sub
The GetFiles function can be called by passing in a single string (or Folder):
Debug.Print GetFiles("c:\users\win8\documents").Count
or anything that can be iterated over with For Each -- an array, collection, Dictionary, or even an Excel Range object:
Dim allFiles As Collection
Set allFiles = GetFiles(ws2.Range("A1:A" & Rows.Count).End(xlUp)) 'from question
GetFiles as it stands is flexible for many use cases, and doesn't use any Excel-specific objects. In order to limit the results to Excel files only, you can create a new collection, and only add the Excel files into the new collection:
'You could filter by the File object's Type property
Sub GetExcelFilesByType()
Dim allFiles As Collection
Set allFiles = GetFiles(ws2.Range("A1:A" & Rows.Count).End(xlUp)) 'from question
Dim excelFiles As New Collection
Dim file As Scripting.File
For Each file In allFiles
If file.Type = "Microsoft Excel Worksheet" Then excelFiles.Add file
Next
End Sub
' Or you could filter by extension, using the FileSystemObject.GetExtensionName method
Sub GetExcelFilesByExtensionName()
Dim allFiles As Collection
Set allFiles = GetFiles(ws2.Range("A1:A" & Rows.Count).End(xlUp)) 'from question
Dim excelFiles As New Collection
Dim fso As New Scripting.FileSystemObject
Dim file As Scripting.File
For Each file In allFiles
Select Case fso.GetExtensionName(file.path)
Case "xls", "xlsb", "xlsm"
excelFiles.Add file
End Select
Next
End Sub
Either will get you a Collection of File objects, of only Excel files, from the set of root folders.
Notes
This code is recursively adding all the files (not just Excel files) into one collection (in GetFiles) and then filtering out the non-Excel files into a new collection. This might be less performant than adding only Excel files into the original collection, but that would limit GetFiles to only this scenario.
If you want to paste the results into an Excel worksheet, you could iterate through excelFiles and paste each path into the sheet. Alternatively, you might convert excelFiles into an array, and use the Excel Range object's Value property to set all the values from the array, without using a For Each.
References
Microsoft Scripting Runtime
FileSystemObject object, GetExtensionName method
File object
Folder object
VBA
Collection object
Here's a quick way, slightly adapted from this answer.
Just add in your folder locations to the path() = ... list and it should work for you. It outputs, in the current excel sheet, the paths of all Excel files in folders you provide.
From there, you can do what you please. (Perhaps throw the file paths in to an array, so you have an array of files you want to open. From there you can do the copying of data).
'Force the explicit delcaration of variables
Option Explicit
Sub ListFiles()
'http://www.xl-central.com/list-the-files-in-a-folder-and-subfolders.html
'Set a reference to Microsoft Scripting Runtime by using
'Tools > References in the Visual Basic Editor (Alt+F11)
'Declare the variables
Dim objFSO As Scripting.FileSystemObject
Dim objTopFolder As Scripting.Folder
Dim strTopFolderName As String
Dim path() As Variant ' EDIT THE BELOW PATH LIST FOR WHATEVER YOU NEED!
path() = Array("C:\Users\USERNAME\Desktop\Stuff\New folder", "C:\Users\USERNAME\Desktop\Other Stuff\")
'Insert the headers for Columns
Range("A1").Value = "File Name"
Range("D1").Value = "File Path"
Dim i As Long
For i = LBound(path) To UBound(path)
strTopFolderName = path(i)
'Create an instance of the FileSystemObject
Set objFSO = CreateObject("Scripting.FileSystemObject")
'Get the top folder
Set objTopFolder = objFSO.GetFolder(strTopFolderName)
'Call the RecursiveFolder routine
Call RecursiveFolder(objTopFolder, True)
'Change the width of the columns to achieve the best fit
Columns.AutoFit
Next i
End Sub
Sub RecursiveFolder(objFolder As Scripting.Folder, _
IncludeSubFolders As Boolean)
'Declare the variables
Dim objFile As Scripting.File
Dim objSubFolder As Scripting.Folder
Dim NextRow As Long
'Find the next available row
NextRow = Cells(Rows.Count, "A").End(xlUp).Row + 1
'Loop through each file in the folder
For Each objFile In objFolder.Files
Debug.Print (objFile)
If objFile.Type = "Microsoft Excel Worksheet" Then
Cells(NextRow, "A").Value = objFile.Name
Cells(NextRow, "D").Value = objFile.path
NextRow = NextRow + 1
End If
Next objFile
'Loop through files in the subfolders
If IncludeSubFolders Then
For Each objSubFolder In objFolder.SubFolders
Call RecursiveFolder(objSubFolder, True)
Next objSubFolder
End If
End Sub

Resources