In This Chapter
External data is exactly what it sounds like: data that isn’t located in the Excel workbook in which you’re operating. Some examples of external data sources are text files, Access tables, SQL Server tables, and even other Excel workbooks.
There are numerous ways to get data into Excel. In fact, between the functionality found in the UI and the VBA/code techniques, there are too many techniques to focus on in one chapter. Instead, then, in this chapter we’ll focus on a handful of techniques that can be implemented in most situations and don’t come with a lot of pitfalls and gotchas.
The first of those techniques is to use an external data connection.
Excel has made it easy to manually connect to external data sources such as Microsoft Access, SQL Server, or any other ODBC connection you regularly use. For example, you can connect to an Access database by following these steps:
In the Get External Data group, select the From Access icon.
The Select Data Source dialog box opens, as shown in Figure 11.1. If the database from which you want to import data is local, browse to the file’s location and select it. If your target Access database resides on a network drive at another location, you need the proper authorization to select it.
Navigate to your sample database and click Open.
In some environments, a series of Data Link Properties dialog boxes opens asking for credentials (username and password). Most Access databases don’t require logon credentials, but if your database does require a username and password, type them in the Data Link Properties dialog box.
Click OK. The Select Table dialog box shown in Figure 11.2 opens. This dialog box lists all the available tables and queries in the selected database.
Select your target table or query and click OK.
The Import Data dialog box shown in Figure 11.3 opens. Here you define where and how to import the table. You have the option of importing the data into a Table, a PivotTable Report, a PivotChart, or a Power View Report. You also have the option of creating only the connection, making the connection available for later use.
Note that if you choose PivotChart or PivotTable Report, the data is saved to a pivot cache without writing the actual data to the worksheet. Thus your pivot table can function as normal without you having to import potentially hundreds of thousands of data rows twice (once for the pivot cache and once for the spreadsheet).
Your reward for all your work is a table that contains the imported data from your Access database, as shown in Figure 11.4.
The incredibly powerful thing about importing data this way is that it’s refreshable. That’s right. If you import data from Access using this technique, Excel creates a table that you can update by right-clicking it and selecting Refresh from the pop-up menu, as shown in Figure 11.5. When you update your imported data, Excel reconnects to your Access database and imports the data again. As long as a connection to your database is available, you can refresh with a mere click of the mouse.
Again, a major advantage to using the Get External Data group is that you can establish a refreshable data connection between Excel and Access. In most cases, you can set up the connection one time and then just update the data connection when needed. You can even record an Excel macro to update the data on some trigger or event, which is ideal for automating the transfer of data from Access.
Once you have a connection, you can use the connection properties to write your own SQL statements. This gives you more control over the data you pull into your Excel model and allows you to perform advanced actions like running SQL Server stored procedures.
Go to the Data tab on the Ribbon and select Connections. This will activate the Workbook Connections dialog box shown in Figure 11.6. Choose the connection you want to edit and then click on the Properties button.
The Connection Properties dialog box will open. Here, you can click on the Definition tab (see Figure 11.7). Change the Command Type property to SQL, and then enter your SQL statement.
You may have noticed that the last few examples have hard-coded the criteria in the SQL statements. For example, in Figure 11.7, Tulsa is specified directly into the SQL statement WHERE clause. This obviously would cause the data being returned to always be data for Tulsa.
But what if you wanted to select a market and have the SQL statement dynamically change to respond to your selection? Well, you can use a bit of VBA to change the SQL statement on the fly. Follow these steps:
Enter the following code in the newly created module:
Sub RefreshQuery() ActiveWorkbook.Connections( _ "Facility Services").OLEDBConnection.CommandText = _ "SELECT * FROM [Sales_By_Employee] WHERE [Market] = '" & _ Range("C2").Value &"'" ActiveWorkbook.Connections("Facility Services").Refresh End Sub
This code creates a new macro called RefreshQuery. This macro uses the Workbook .Connections collection to change the attributes of the specified connection. In this case, you want to change the CommandText property of the FacilityServices connection.
The command text is essentially the SQL Statement you want the connection to use when connecting to the data source. In this example, the Command Text selects from the [Sales_By_Employee] table and sets the criteria for the [Market] field to the value in cell C2. The code then refreshes the Facility Services connection.
If all went smoothly, you will have a nifty mechanism that allows for the dynamic extraction of data from your external database based on the criteria you specified (See Figure 11.10).
You can also use the Workbook.Connections collection to iterate through all the connection objects in a workbook and examine or modify their properties. For instance, the following macro populates a worksheet with a list of all connection objects in the current workbook, along with their associated connection strings and command texts:
Sub ListConnections() Dim i As Long Dim Cn As WorkbookConnection Worksheets.Add With ActiveSheet.Range("A1:C1") .Value = Array("Cn Name","Connection String","Command Text") .EntireColumn.AutoFit End With For Each Cn In ThisWorkbook.Connections i = i + 1 Select Case Cn.Type Case Is = xlConnectionTypeODBC With ActiveSheet .Range("A1").Offset(i, 0).Value = Cn.Name .Range("A1").Offset(i, 1).Value = Cn.ODBCConnection.Connection .Range("A1").Offset(i, 2).Value = Cn.ODBCConnection.CommandText End With Case Is = xlConnectionTypeOLEDB With ActiveSheet .Range("A1").Offset(i, 0).Value = Cn.Name .Range("A1").Offset(i, 1).Value = Cn.OLEDBConnection.Connection .Range("A1").Offset(i, 2).Value = Cn.OLEDBConnection.CommandText End With End Select Next Cn End Sub
Another technique for working with external data is to use VBA with ADO (ActiveX Data Objects). Using the combination of ADO with VBA will allow you to work with external data sets in memory. This comes in handy when you need to perform complex, multi-layered procedures and checks on external data sets, but you don’t want to create workbook connections or return those external data sets to the workbook.
When trying to grasp the basics of ADO, it helps to think of ADO as a tool that will help you accomplish two tasks: connect to a data source and specify the dataset with which to work. In the following section, you will explore the fundamental syntax you will need to know in order to do just that.
The first thing you must do is connect to a data source. In order to do this, you must give VBA a few pieces of information. This information is passed to VBA in the form of a connection string. Here is an example connection string that points to an Access database.
"Provider=Microsoft.ACE.OLEDB.12.0;" & _ "Data Source= C:MyDatabase.accdb;" & _ "User ID=Administrator;" & _ "Password=AdminPassword"
Don’t be intimidated by all the syntax here. A connection string is fundamentally nothing more than a text string that holds a series of variables (also called arguments), which VBA uses to identify and open a connection to a data source. Although connection strings can get pretty fancy with a myriad of arguments and options, there are a handful of arguments that are commonly used when connecting to either Access or Excel.
For novices of ADO, it helps to focus on these commonly used arguments when working with connection strings: Provider, Data Source, Extended Properties, User ID, and Password:
Take a moment now to examine a few examples of how these arguments are used in different connection strings.
Connecting to an Access database:
"Provider=Microsoft.ACE.OLEDB.12.0;" & _ "Data Source= C:MyDatabase.accdb"
Connecting to an Access database with Password and User ID:
"Provider=Microsoft.ACE.OLEDB.12.0;" & _ "Data Source= C:MyDatabase.accdb;" & _ "User ID=Administrator;" & _ "Password=AdminPassword"
Connecting to an Excel workbook:
"Provider=Microsoft.ACE.OLEDB.12.0;" & _ "Data Source=C:MyExcelWorkbook.xlsx;" & _ "Extended Properties=Excel 12.0"
In addition to building a connection to your data source, you will need to define the data set with which you need to work. In ADO, this dataset is referred to as the Recordset. A Recordset object is essentially a container for the records and fields returned from the data source. The most common way to define a Recordset is to open an existing table or query using the following arguments:
Recordset.Open Source, ConnectString, CursorType, LockType
The Source argument specifies the data that is to be extracted. This is typically a table, a query, or a SQL statement that retrieves records. The ConnectString argument specifies the connection string used to connect to your chosen data source. The CursorType argument defines how a Recordset allows you to move through the data to be extracted. The CursorTypes that are commonly used are:
The LockType argument lets you specify whether the data returned by the Recordset can be changed. This argument is typically set to adLockReadOnly (the default setting) to indicate that there is no need to edit the data returned. Alternatively, you can set this argument to adLockOptimistic which allows for the free editing of the data returned.
With these basic ADO fundamentals under your belt, you’re ready to create your own ADO procedure. But before you do anything with ADO, you need to first set a reference to the ADO object library. Just as each Microsoft Office application has its own set of objects, properties, and methods, so does ADO. Since Excel does not inherently know the ADO object model, you will need to point Excel to the ADO reference library.
Start by opening a new Excel workbook and opening the Visual Basic Editor.
Once you are in the Visual Basic Editor, go up to the application menu and select Tools ➜ References. This will open the References dialog box illustrated here in Figure 11.11. Scroll down until you locate the latest version of the Microsoft ActiveX Data Objects Library. Place a checkmark beside this entry and click OK.
After you click the OK button, you can open the References dialog box again to ensure that your reference is set. You will know that your selection took effect when the Microsoft ActiveX Data Objects Library is displayed at the top of the Reference dialog box with a check next to it.
Now that you understand a few of the basics of ADO, take a look at how they come together in VBA. The following example code uses ADO to connect to an Access database and retrieve the Products table.
Sub GetAccessData() Dim MyConnect As String Dim MyRecordset As ADODB.Recordset MyConnect ="Provider=Microsoft.ACE.OLEDB.12.0;" & _ "Data Source= C:MyDirMyDatabaseName.accdb" Set MyRecordset = New ADODB.Recordset MyRecordset.Open"Products", _ MyConnect, adOpenStatic, adLockReadOnly Sheets("MySheetName").Range("A2").CopyFromRecordset _ MyRecordset With ActiveSheet.Range("A1:C1") .Value = Array("Product","Description","Segment") .EntireColumn.AutoFit End With End Sub
Now take a moment to understand what this macro is doing.
You first declare two variables: a string variable to hold the connection string and a Recordset object to hold the results of the data pull. In this example, the variable called MyConnect will hold the connection string identifying the data source. Meanwhile, the variable called MyRecordset will hold the data that is returned by the procedure.
Next, you define the connection string for the ADO procedure. In this scenario, you are connecting to the MyDatabaseName.accdb file found in the C:MyDir directory. Once you have defined the data source, you can open the Recordset and use MyConnect to return static read-only data.
Now you can use Excel’s CopyFromRecordset method to get the data out of the RecordSet and into the spreadsheet. This method requires two pieces of information: the location of the data output and the Recordset object that holds the data. In this example, you are copying the data in the MyRecordset object onto the sheet called MySheetName (starting at cell A2).
Interestingly enough, the CopyFromRecordset method does not return column headers or field names. This forces one final action where you add column headers by simply defining them in an array and writing them to the active sheet.
With ADO and VBA, you can build all the necessary components one time in a nicely packaged macro and then simply forget about it. As long as the defined variables in your code (i.e. the data source path, the Recordset, the output path) do not change, then your ADO-based procedures will require virtually zero maintenance.
There are countless ways you can use the fundamentals you have learned in this chapter. Of course, it would be impossible to go through every possibility here. However, there are some common scenarios where VBA can greatly enhance integration between Excel and Access.
You can use an Excel workbook as a data source for your ADO procedures. To do so, you simply build an SQL statement that references the data within the Excel workbook. The idea is to pinpoint the dataset in Excel to query by passing either a sheet name, a range of cells, or a named range to the SQL statement.
To query all of the data on a specific worksheet, you pass the name of that worksheet followed by the dollar sign ($) as the table name in your SQL statement. Be sure to encapsulate the worksheet name with square brackets. For example:
SELECT * FROM [MySheet$]
If the worksheet name contains spaces or characters that are not alphanumeric, you need to wrap the worksheet name in single quotes. For instance:
Select * from ['January Forecast vs. Budget$']
To query a range of cells within a given worksheet, you first identify the sheet as described above and then add the target range. For example:
SELECT * FROM [MySheet$A1:G17]
To query a named range, simply use the name of the range as the table name in your SQL statement. For example:
SELECT * FROM MyNamedRange
In the following example, the entire used range in the SampleData worksheet is queried to return only those records that belong to the North Region.
Sub GetData_From_Excel_Sheet() Dim MyConnect As String Dim MyRecordset As ADODB.Recordset Dim MySQL As String MyConnect ="Provider=Microsoft.ACE.OLEDB.12.0;" & _ "Data Source=" & ThisWorkbook.FullName &";" & _ "Extended Properties=Excel 12.0" MySQL =" SELECT * FROM [SampleData$]" & _ " WHERE Region ='NORTH'" Set MyRecordset = New ADODB.Recordset MyRecordset.Open MySQL, MyConnect, adOpenStatic, adLockReadOnly ThisWorkbook.Sheets.Add ActiveSheet.Range("A2").CopyFromRecordset MyRecordset With ActiveSheet.Range("A1:F1") .Value = Array("Region","Market","Branch_Number", _ "Invoice_Number","Sales_Amount","Contracted Hours") .EntireColumn.AutoFit End With End Sub
There are often times when you don’t necessarily want to overwrite the data in your Excel worksheet when you bring in fresh data. Instead, you may want to simply add or append data to the existing table. In a typical scenario, you hard-code the location or range where you want a given recordset to be copied. In these situations, this location must dynamically change to reflect the first empty cell in your worksheet. The following example code demonstrates this technique.
Sub Append_Results() Dim MyConnect As String Dim MyRecordset As ADODB.Recordset Dim MyRange As String MyConnect ="Provider=Microsoft.ACE.OLEDB.12.0;" & _ "Data Source= C:MyDirMyDatabase.accdb" Set MyRecordset = New ADODB.Recordset MyRecordset.Open"Products", MyConnect, adOpenStatic Sheets("AppendData").Select MyRange ="A" & _ ActiveSheet.Cells.SpecialCells(xlCellTypeLastCell).Row + 1 ActiveSheet.Range(MyRange).CopyFromRecordset MyRecordset End Sub
Because you want to append data to an existing table, you need to dynamically determine the first available empty cell that can be used as the output location for the data pull. The first step in accomplishing this goal is to find the first empty row. This is relatively easy to do thanks to Excel’s SpecialCells method.
Using the SpecialCells method, you can find the last used cell in the worksheet and then extract the row number of that cell. This gives you the last used row. To get the row number of the first empty row, you simply add 1; the next row down from the last used row will inherently be empty.
The idea is to concatenate the SpecialCells routine with a column letter (in this case “A”) to create a string that represents a range. For example, if the first empty row turns out to be 10, then the following code returns “A10.”
"A" & ActiveSheet.Cells.SpecialCells(xlCellTypeLastCell).Row + 1
Trapping this answer in the MyRange string variable allows you to pass the answer to the CopyFromRecordset.
VBA contains a number of statements that allow low-level manipulation of files. These input/output (I/O) statements give you much more control over files than Excel’s normal text file import and export options.
You can access a file in any of three ways:
Because random and binary access files are rarely used with VBA, this chapter focuses on sequential access files. In sequential access, your code starts reading from the beginning of the file and reads each line sequentially. For output, your code writes data to the end of the file.
The VBA Open statement (not to be confused with the Open method of the Workbooks object) opens a file for reading or writing. Before you can read from or write to a file, you must open it.
The Open statement is versatile and has a complex syntax:
Open pathname For mode [Access access] [lock] _ As [#]filenumber [Len=reclength]
The basic procedure for reading a text file with VBA consists of the following steps:
The basic procedure for writing a text file is as follows:
Most VBA programmers simply designate a file number in their Open statement. For example:
Open"myfile.txt" For Input As #1
Then you can refer to the file in subsequent statements as #1.
If a second file is opened while the first is still open, you’d designate the second file as #2:
Open"another.txt" For Input As #2
Another approach is to use the VBA FreeFile function to get a file handle. Then you can refer to the file by using a variable. Here’s an example:
FileHandle = FreeFile Open"myfile.txt" For Input As FileHandle
For sequential file access, you rarely need to know the current location in the file. If for some reason you need to know this information, you can use the Seek function.
VBA provides several statements to read and write data to a file.
Three statements are used for reading data from a sequential access file:
Two statements are used for writing data to a sequential access file:
This section contains a number of examples that demonstrate various techniques that manipulate text files.
The code in the following example reads a text file and then places each line of data in a single cell (beginning with the active cell):
Sub ImportData() Open"c:data extfile.txt" For Input As #1 r = 0 Do Until EOF(1) Line Input #1, data ActiveCell.Offset(r, 0) = data r = r + 1 Loop Close #1 End Sub
In most cases, this procedure won’t be very useful because each line of data is simply dumped into a single cell. It is easier to just open the text file directly by using File ➜ Open.
The example in this section writes the data in a selected worksheet range to a CSV text file. Although Excel can export data to a CSV file, it exports the entire worksheet. This macro works with a specified range of cells.
Sub ExportRange() Dim Filename As String Dim NumRows As Long, NumCols As Integer Dim r As Long, c As Integer Dim Data Dim ExpRng As Range Set ExpRng = Selection NumCols = ExpRng.Columns.Count NumRows = ExpRng.Rows.Count Filename = Application.DefaultFilePath &" extfile.csv" Open Filename For Output As #1 For r = 1 To NumRows For c = 1 To NumCols Data = ExpRng.Cells(r, c).Value If IsNumeric(Data) Then Data = Val(Data) If IsEmpty(ExpRng.Cells(r, c)) Then Data ="" If c <> NumCols Then Write #1, Data; Else Write #1, Data End If Next c Next r Close #1 End Sub
Note that the procedure uses two Write # statements. The first statement ends with a semicolon, so a return/linefeed sequence isn’t written. For the last cell in a row, however, the second Write # statement doesn’t use a semicolon, which causes the next output to appear on a new line.
You used a variable named Data to store the contents of each cell. If the cell is numeric, the variable is converted to a value. This step ensures that numeric data won’t be stored with quotation marks. If a cell is empty, its Value property returns 0. Therefore, the code also checks for a blank cell (by using the IsEmpty function) and substitutes an empty string instead of a 0.
The example in this section reads the CSV file created in the preceding example and then stores the values beginning at the active cell in the active worksheet. The code reads each character and essentially parses the line of data, ignoring quote characters and looking for commas to delineate the columns.
Sub ImportRange() Dim ImpRng As Range Dim Filename As String Dim r As Long, c As Integer Dim txt As String, Char As String * 1 Dim Data Dim i As Integer Set ImpRng = ActiveCell On Error Resume Next Filename = Application.DefaultFilePath &" extfile.csv" Open Filename For Input As #1 If Err <> 0 Then MsgBox"Not found:" & Filename, vbCritical,"ERROR" Exit Sub End If r = 0 c = 0 txt ="" Application.ScreenUpdating = False Do Until EOF(1) Line Input #1, Data For i = 1 To Len(Data) Char = Mid(Data, i, 1) If Char ="," Then 'comma ActiveCell.Offset(r, c) = txt c = c + 1 txt ="" ElseIf i = Len(Data) Then 'end of line If Char <> Chr(34) Then txt = txt & Char ActiveCell.Offset(r, c) = txt txt ="" ElseIf Char <> Chr(34) Then txt = txt & Char End If Next i c = 0 r = r + 1 Loop Close #1 Application.ScreenUpdating = True End Sub
The example in this section writes data to a text file every time Excel is opened and closed. For this example to work reliably, the procedure must be located in a workbook that’s opened every time you start Excel. Storing the macro in your Personal Macro Workbook is an excellent choice.
The following procedure, stored in the code module for the ThisWorkbook object, is executed when the file is opened:
Private Sub Workbook_Open() Open Application.DefaultFilePath &"excelusage.txt" For Append As #1 Print #1,"Started" & Now Close #1 End Sub
The procedure appends a new line to a file named excelusage.txt. The new line contains the current date and time and might look something like this:
Started 11/16/2013 9:27:43 PM
The following procedure is executed before the workbook is closed. It appends a new line that contains the word Stopped along with the current date and time.
Private Sub Workbook_BeforeClose(Cancel As Boolean) Open Application.DefaultFilePath &"excelusage.txt" _ For Append As #1 Print #1,"Stopped" & Now Close #1 End Sub
The example in this section demonstrates how to work with two text files at once. The FilterFile procedure that follows reads a text file (infile.txt) and copies only the rows that contain a specific text string ("January") to a second text file (output.txt):
Sub FilterFile() Open ThisWorkbook.Path &"infile.txt" For Input As #1 Open Application.DefaultFilePath &"output.txt" For Output As #2 TextToFind ="January" Do Until EOF(1) Line Input #1, data If InStr(1, data, TextToFind) Then Print #2, data End If Loop Close 'Close all files End Sub
Many applications that you develop for Excel require working with external files. For example, you might need to get a listing of files in a directory, delete files, or rename files. Excel can import and export several types of text files. In many cases, however, Excel’s built-in text file handling isn’t sufficient. For example, you might want to paste a list of filenames into a range or export a range of cells to a simple HyperText Markup Language (HTML) file.
In this chapter, you explore how to use Visual Basic for Applications (VBA) to perform common (and not so common) file operations and work directly with text files.
Excel provides two ways to perform common file operations:
In the sections that follow, you explore these two methods and some examples.
The VBA statements that you can use to work with files are summarized in Table 11.1. Most of these statements are straightforward, and all are described in the Help system.
Table 11.1 VBA File-Related Statements
Command | What It Does |
ChDir | Changes the current directory |
ChDrive | Changes the current drive |
Dir | Returns a filename or directory that matches a specified pattern or file attribute |
FileCopy | Copies a file |
FileDateTime | Returns the date and time when a file was last modified |
FileLen | Returns the size of a file, in bytes |
GetAttr | Returns a value that represents an attribute of a file |
Kill | Deletes a file |
MkDir | Creates a new directory |
Name | Renames a file or directory |
RmDir | Removes an empty directory |
SetAttr | Changes an attribute for a file |
The remainder of this section consists of examples that demonstrate some of the file manipulation commands.
The following function returns True if a particular file exists and False if it doesn’t exist. If the Dir function returns an empty string, the file couldn’t be found, so the function returns False.
Function FileExists(fname) As Boolean FileExists = Dir(fname) <>"" End Function
The argument for the FileExists function consists of a full path and filename. The function can be used in a worksheet or called from a VBA procedure. Here’s an example:
MyFile ="c:udgeting2013 budget notes.docx" Msgbox FileExists(MyFile)
The following function returns True if a specified path exists and False otherwise:
Function PathExists(pname) As Boolean ' Returns TRUE if the path exists On Error Resume Next PathExists = (GetAttr(pname) And vbDirectory) = vbDirectory End Function
The pname argument is a string that contains a directory (without a filename). The trailing backslash in the pathname is optional. Here’s an example of calling the function:
MyFolder ="c:usersjohndesktopdownloads" MsgBox PathExists(MyFolder)
The following procedure displays (in the active worksheet) a list of files in a particular directory, along with the file size and date:
Sub ListFiles() Dim Directory As String Dim r As Long Dim f As String Dim FileSize As Double Directory ="f:excelfilesudgeting" r = 1 ' Insert headers Cells(r, 1) ="FileName" Cells(r, 2) ="Size" Cells(r, 3) ="Date/Time" Range("A1:C1").Font.Bold = True ' Get first file f = Dir(Directory, vbReadOnly + vbHidden + vbSystem) Do While f <>"" r = r + 1 Cells(r, 1) = f 'Adjust for filesize > 2 gigabytes FileSize = FileLen(Directory & f) If FileSize < 0 Then FileSize = FileSize + 4294967296# Cells(r, 2) = FileSize Cells(r, 3) = FileDateTime(Directory & f) ' Get next file f = Dir() Loop End Sub
Note that the procedure uses the Dir function twice. The first time (used with an argument), it retrieves the first matching filename found. Subsequent calls (without an argument) retrieve additional matching filenames. When no more files are found, the Dir function returns an empty string.
The Dir function also accepts wildcard file specifications in its first argument. To get a list of Excel files, for example, you could use a statement such as this:
f = Dir(Directory &"*.xl??", vbReadOnly + vbHidden + vbSystem)
This statement retrieves the name of the first *.xl?? file in the specified directory. The wildcard specification returns a four-character extension that begins with XL. For example, the extension could be .xlsx, .xltx, or .xlam. The second argument for the Dir function lets you specify the attributes of the files (in terms of built-in constants). In this example, the Dir function retrieves filenames that have no attributes, read-only files, hidden files, and system files.
To also retrieve Excel files in an earlier format (for example, .xls and .xla files), use the following wildcard specification:
*.xl*
Table 11.2 lists the built-in constants for the Dir function.
Table 11.2 File Attribute Constants for the Dir Function
Constant | Value | Description |
vbNormal | 0 | Files with no attributes. This is the default setting and is always in effect. |
vbReadOnly | 1 | Read-only files. |
vbHidden | 2 | Hidden files. |
vbSystem | 4 | System files. |
vbVolume | 8 | Volume label. If any other attribute is specified, this attribute is ignored. |
vbDirectory | 16 | Directories. This attribute doesn’t work. Calling the Dir function with the vbDirectory attribute doesn’t continually return subdirectories. |
The example in this section creates a list of files in a specified directory, including its subdirectories. This procedure is unusual because it calls itself — a concept known as recursion.
Public Sub RecursiveDir(ByVal CurrDir As String, Optional ByVal Level As Long) Dim Dirs() As String Dim NumDirs As Long Dim FileName As String Dim PathAndName As String Dim i As Long Dim Filesize As Double ' Make sure path ends in backslash If Right(CurrDir, 1) <>"" Then CurrDir = CurrDir &"" ' Put column headings on active sheet Cells(1, 1) ="Path" Cells(1, 2) ="Filename" Cells(1, 3) ="Size" Cells(1, 4) ="Date/Time" Range("A1:D1").Font.Bold = True ' Get files FileName = Dir(CurrDir &"*.*", vbDirectory) Do While Len(FileName) <> 0 If Left(FileName, 1) <>"." Then 'Current dir PathAndName = CurrDir & FileName If (GetAttr(PathAndName) And vbDirectory) = vbDirectory Then 'store found directories ReDim Preserve Dirs(0 To NumDirs) As String Dirs(NumDirs) = PathAndName NumDirs = NumDirs + 1 Else 'Write the path and file to the sheet Cells(WorksheetFunction.CountA(Range("A:A")) + 1, 1) = _ CurrDir Cells(WorksheetFunction.CountA(Range("B:B")) + 1, 2) = _ FileName 'adjust for filesize > 2 gigabytes Filesize = FileLen(PathAndName) If Filesize < 0 Then Filesize = Filesize + 4294967296# Cells(WorksheetFunction.CountA(Range("C:C")) + 1, 3) = Filesize Cells(WorksheetFunction.CountA(Range("D:D")) + 1, 4) = _ FileDateTime(PathAndName) End If End If FileName = Dir() Loop ' Process the found directories, recursively For i = 0 To NumDirs - 1 RecursiveDir Dirs(i), Level + 2 Next i End Sub
The procedure takes one argument, CurrDir, which is the directory being examined. Information for each file is displayed in the active worksheet. As the procedure loops through the files, it stores the subdirectory names in an array named Dirs. When no more files are found, the procedure calls itself using an entry in the Dirs array for its argument. When all directories in the Dirs array have been processed, the procedure ends.
Because the RecursiveDir procedure uses an argument, it must be executed from another procedure by using a statement like this:
Call RecursiveDir("c:directory")
The FileSystemObject object is a member of Windows Scripting Host and provides access to a computer’s file system. This object is often used in script-oriented web pages (for example, VBScript and JavaScript) and can be used with Excel 2000 and later versions.
The name FileSystemObject is a bit misleading because it includes a number of objects, each designed for a specific purpose:
The first step in using the FileSystemObject object is to create an instance of the object. You can perform this task in two ways: early binding or late binding.
The late binding method uses two statements, like this:
Dim FileSys As Object Set FileSys = CreateObject("Scripting.FileSystemObject")
Note that the FileSys object variable is declared as a generic Object rather than as an actual object type. The object type is resolved at runtime.
The early binding method of creating the object requires that you set up a reference to Windows Script Host Object Model. You do this by using Tools ➜ References in VBE. After you’ve established the reference, create the object by using statements like these:
Dim FileSys As FileSystemObject Set FileSys = CreateObject("Scripting.FileSystemObject")
Using the early binding method enables you to take advantage of the VBE Auto List Members feature to help you identify properties and methods as you type. In addition, you can use Object Browser (by pressing F2) to learn more about the object model.
The examples that follow demonstrate various tasks using the FileSystemObject object.
The Function procedure that follows accepts one argument (the path and filename) and returns True if the file exists:
Function FileExists3(fname) As Boolean Dim FileSys As Object 'FileSystemObject Set FileSys = CreateObject("Scripting.FileSystemObject") FileExists3 = FileSys.FileExists(fname) End Function
The function creates a new FileSystemObject object named FileSys and then accesses the FileExists property for that object.
The Function procedure that follows accepts one argument (the path) and returns True if the path exists:
Function PathExists2(path) As Boolean Dim FileSys As Object 'FileSystemObject Set FileSys = CreateObject("Scripting.FileSystemObject") PathExists2 = FileSys.FolderExists(path) End Function
The example in this section uses FileSystemObject to retrieve and display information about all disk drives. The procedure loops through the Drives collection and writes various property values to a worksheet.
Sub ShowDriveInfo() Dim FileSys As FileSystemObject Dim Drv As Drive Dim Row As Long Set FileSys = CreateObject("Scripting.FileSystemObject") Cells.ClearContents Row = 1 ' Column headers Range("A1:F1") = Array("Drive","Ready","Type","Vol. Name", _ "Size","Available") On Error Resume Next ' Loop through the drives For Each Drv In FileSys.Drives Row = Row + 1 Cells(Row, 1) = Drv.DriveLetter Cells(Row, 2) = Drv.IsReady Select Case Drv.DriveType Case 0: Cells(Row, 3) ="Unknown" Case 1: Cells(Row, 3) ="Removable" Case 2: Cells(Row, 3) ="Fixed" Case 3: Cells(Row, 3) ="Network" Case 4: Cells(Row, 3) ="CD-ROM" Case 5: Cells(Row, 3) ="RAM Disk" End Select Cells(Row, 4) = Drv.VolumeName Cells(Row, 5) = Drv.TotalSize Cells(Row, 6) = Drv.AvailableSpace Next Drv 'Make a table ActiveSheet.ListObjects.Add xlSrcRange, _ Range("A1").CurrentRegion, , xlYes End Sub
Perhaps the most commonly used type of file compression is the Zip format. Even Excel 2007 (and later) files are stored in the Zip format (although they don’t use the .zip extension). A Zip file can contain any number of files, and even complete directory structures. The content of the files determines the degree of compression. For example, JPG image files and MP3 audio files are already compressed, so zipping these file types has little effect on the file size. Text files, on the other hand, usually shrink quite a bit when compressed.
The example in this section demonstrates how to create a Zip file from a group of user-selected files. The ZipFiles procedure displays a dialog box so that the user can select the files. It then creates a Zip file named compressed.zip in Excel’s default directory.
Sub ZipFiles() Dim ShellApp As Object Dim FileNameZip As Variant Dim FileNames As Variant Dim i As Long, FileCount As Long ' Get the file names FileNames = Application.GetOpenFilename _ (FileFilter:="All Files (*.*),*.*", _ FilterIndex:=1, _ Title:="Select the files to ZIP", _ MultiSelect:=True) ' Exit if dialog box canceled If Not IsArray(FileNames) Then Exit Sub FileCount = UBound(FileNames) FileNameZip = Application.DefaultFilePath &"compressed.zip" 'Create empty Zip File with zip header Open FileNameZip For Output As #1 Print #1, Chr$(80) & Chr$(75) & Chr$(5) & Chr$(6) & String(18, 0) Close #1 Set ShellApp = CreateObject("Shell.Application") 'Copy the files to the compressed folder For i = LBound(FileNames) To UBound(FileNames) ShellApp.Namespace(FileNameZip).CopyHere FileNames(i) 'Keep script waiting until Compressing is done On Error Resume Next Do Until ShellApp.Namespace(FileNameZip).items.Count = i Application.Wait (Now + TimeValue("0:00:01")) Loop Next i If MsgBox(FileCount &" files were zipped to:" & _ vbNewLine & FileNameZip & vbNewLine & vbNewLine & _ "View the zip file?", vbQuestion + vbYesNo) = vbYes Then _ Shell"Explorer.exe /e," & FileNameZip, vbNormalFocus End Sub
The ZipFiles procedure creates a file named compressed.zip and writes a string of characters, which identify it as a Zip file. Next, a Shell.Application object is created, and the code uses its CopyHere method to copy the files to the Zip archive. The next section of the code is a Do Until loop, which checks the number of files in the Zip archive every second. This step is necessary because copying the files could take some time, and if the procedure ends before the files are copied, the Zip file will be incomplete (and probably corrupt).
When the number of files in the Zip archive matches the number that should be there, the loop ends and the user is presented with a message box asking if he wants to see the files. Clicking the Yes button opens a Windows Explorer window that shows the zipped files.
The example in this section performs the opposite function of the preceding example. It asks the user for a ZIP filename and then unzips the files and puts them in a directory named Unzipped, located in Excel’s default file directory.
Sub UnzipAFile() Dim ShellApp As Object Dim TargetFile Dim ZipFolder ' Target file & temp dir TargetFile = Application.GetOpenFilename _ (FileFilter:="Zip Files (*.zip), *.zip") If TargetFile = False Then Exit Sub ZipFolder = Application.DefaultFilePath &"Unzipped" ' Create a temp folder On Error Resume Next RmDir ZipFolder MkDir ZipFolder On Error GoTo 0 ' Copy the zipped files to the newly created folder Set ShellApp = CreateObject("Shell.Application") ShellApp.Namespace(ZipFolder).CopyHere _ ShellApp.Namespace(TargetFile).items If MsgBox("The files was unzipped to:" & _ vbNewLine & ZipFolder & vbNewLine & vbNewLine & _ "View the folder?", vbQuestion + vbYesNo) = vbYes Then _ Shell"Explorer.exe /e," & ZipFolder, vbNormalFocus End Sub
The UnzipAFile procedure uses the GetOpenFilename method to get the Zip file. It then creates the new folder and uses the Shell.Application object to copy the contents of the Zip file to the new folder. Finally, the user can choose to display the new directory.
3.128.31.194