vba to copy range of cells to new workbook - excel

I want to copy multiple sheets to new workbook starting from range (A3) to end of the table of each table, so the following code was used but it copy the entire sheet.
Private Sub Copytonewworkbook_Click()
Dim NewName As String
Dim nm As name
Dim ws As Worksheet
If MsgBox("Copy specific sheets to a new workbook" & vbCr & _
"New sheets will be pasted" , vbYesNo, "NewCopy") = vbNo Then
Exit Sub
With Application
.ScreenUpdating = False
On Error GoTo ErrCatcher
Sheets(Array("Payroll", " Bank Letter")).Copy
On Error Resume Next
For Each ws In ActiveWorkbook.Worksheets
ws.Cells(3,33)Paste:=xlCellTypeFormulas
Application.CutCopyMode = False
Cells(1, 1).Select
ws.Activate
Next ws
Cells(1, 1).Select
For Each nm In ActiveWorkbook.Names
nm.Delete
Next nm
NewName = InputBox("Please Specify the name of your new workbook", "New Copy")
ActiveWorkbook.SaveCopyAs ThisWorkbook.Path & "\" & NewName & ".xls"
ActiveWorkbook.Close SaveChanges:=False
.ScreenUpdating = True
End With
Exit Sub
ErrCatcher:
MsgBox "Specified sheets do not exist within this workbook"
End Sub

This is a possible way to do it (a little advanced, as it does not use the copy, but it gets the values):
Public Sub CopyMe()
Dim lLastRow As Long
Dim rngToCopy As Range
Dim shtTarget As Worksheet
With ActiveSheet
lLastRow = .Cells(.Rows.Count, 1).End(xlUp).row
Set rngToCopy = .Rows("3:" & lLastRow)
End With
Set shtTarget = ActiveWorkbook.Worksheets("Report")
shtTarget.Rows("1:" & rngToCopy.Rows.Count).value = rngToCopy.value
End Sub
You copy the rows from the third to the last value in the first column of the activesheet to a sheet named Report.
Addition:
On the fly, without trying you can do it like this:
Sheets(Array("Payroll", " Bank Letter")).Copy
On Error Resume Next
For Each ws In ActiveWorkbook.Worksheets
ws.Paste:=xlCellTypeFormulas
WS.ROWS("1:3").Clear

Related

Create a new worksheet from a template and link the new worksheet to a master log

Template sheet labeled = "COQ 001"
Log labeled = "Change Order Log"
I have gotten this far: Code below creates a new sheet based off of the template labeled "COQ 001", renames the new worksheet based on user input, goes back to the change order log and sets it to active, inserts a row after the last entry. I think I got that right.
Sub CreateSheet()
Dim xName As String
Dim xSht As Object
On Error Resume Next
xName = InputBox("Please enter new COQ #. For Example: COQ 001", "NEW CHANGE ORDER QUOTE")
If xName = "" Then Exit Sub
Set xSht = Sheets(xName)
If Not xSht Is Nothing Then
MsgBox "Sheet cannot be created as there is already a worksheet with the same name in this
workbook"
Exit Sub
End If
Sheets("COQ 001").Copy after:=Sheets(Sheets.Count)
Set xNWS = Sheets(Sheets.Count)
xNWS.Name = xName
'Link2Log Macro
Worksheets("Change Order Log").Activate
'Locates Last Cell
Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).Select
'Inserts Row Below
Range("10:10").EntireRow.Insert
End Sub
To keep going I need it to link specific cells of each newly created worksheet to the change order log sheet since that is my master log. Instead of COQ 001 which shows up on the formula, I would like it to be each newly created sheet.
If anyone can help finish the thought that would be great!!Thanks,
New Formula:
Thanks Nick, this is what the formula looks like:
Sub CreateSheet()
Dim xName As String
Dim xSht As Object
On Error Resume Next
xName = InputBox("Please enter new COQ #. For Example: COQ 001", "NEW
CHANGE ORDER QUOTE")
If xName = "" Then Exit Sub
Set xSht = Sheets(xName)
If Not xSht Is Nothing Then
MsgBox "Sheet cannot be created as there is already a worksheet with
the same name in this workbook"
Exit Sub
End If
Sheets("COQ 001").Copy after:=Sheets(Sheets.Count)
Set xNWS = Sheets(Sheets.Count)
xNWS.Name = xName
'Link2Log Macro
Worksheets("Change Order Log").Activate
Range("10:10").EntireRow.Insert
Selection.Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
ActiveCell.FormulaR1C1 = "='xName'!R[-3]C[5]"
Range("C10").Select
ActiveCell.FormulaR1C1 = "='xName'!R[2]C:R[2]C[5]"
Range("D10").Select
ActiveCell.FormulaR1C1 = "='xName'!R[-2]C[3]"
Range("E10").Select
ActiveCell.FormulaR1C1 = "='xName'!R[-1]C[2]"
Range("G10").Select
ActiveCell.FormulaR1C1 = "='xName'!R[40]C"
Range("G11").Select
End Sub
But I get an error on the log:
LOG
You can try this:
Sub CreateSheet()
Dim xName As String, wb As Workbook
Dim ws As Worksheet
xName = InputBox("Please enter new COQ #. For Example: COQ 001", _
"NEWCHANGE ORDER QUOTE")
If xName = "" Then Exit Sub
Set wb = ThisWorkbook
If SheetExists(xName, wb) Then
MsgBox "Sheet '" & xName & "' cannot be created as there is already " & _
"a worksheet with the same name in this workbook"
Exit Sub
End If
wb.Worksheets("COQ 001").Copy after:=Sheets(Sheets.Count)
Set ws = wb.Sheets(Sheets.Count)
ws.Name = xName
With wb.Worksheets("Change Order Log")
.Activate
.Cells(Rows.Count, "B").End(xlUp).Offset(1).EntireRow.Insert 'add a row to the table
With .Cells(Rows.Count, "B").End(xlUp).Offset(1).EntireRow
.Columns("B").Formula = "='" & xName & "'!G7"
.Columns("C").Formula = "='" & xName & "'!C12" 'only need first cell of merged area
.Columns("D").Formula = "='" & xName & "'!G8"
.Columns("E").Formula = "='" & xName & "'!G9"
.Columns("G").Formula = "='" & xName & "'!G50" 'likewise
End With
End With
End Sub
Function SheetExists(SheetName As String, Optional wb As Excel.Workbook)
Dim s As Excel.Worksheet
If wb Is Nothing Then Set wb = ThisWorkbook
On Error Resume Next
Set s = wb.Sheets(SheetName)
On Error GoTo 0
SheetExists = Not s Is Nothing
End Function
Note you don't need to select/activate ranges to work with them - just reference them directly.

Creating a new worksheet for each row, but duplicates should be in same worksheet

I would like to create a new worksheet for every customer in my excel file. The customer number is given in column c, but it is only the first 7 letters that shows the customer number. Therefore I would like if the code named each new worksheet it creates, after the customer number, so that it can check if a customer already has a worksheet, and if it does, the next row in the first worksheet that contains the same customer number should be put into that new worksheet, below what has already been copied into there.
Function SheetExists(SheetName As String, Optional InWorkbook As Workbook) As Boolean
If InWorkbook Is Nothing Then Set InWorkbook = ActiveWorkbook
On Error Resume Next
SheetExists = Not InWorkbook.Sheets(SheetName) Is Nothing
On Error GoTo 0
End Function
Sub RowToSheet()
Dim xRow As Long
Dim I As Long
Dim ws As Worksheet
Application.ScreenUpdating = False
ActiveSheet.Name = "Sheet 1"
With ActiveSheet
xRow = .Range("A" & Rows.Count).End(xlUp).Row
For I = 2 To xRow
If Not SheetExists(Left(Cells(I, 3), 7)) Then Worksheets.Add(, Sheets(Sheets.Count)).Name = Left(Cells(I, 3), 7)
.Rows(I).Copy Sheets(Left(Cells(I, 3), 7)).Cells(Sheets(Left(Cells(I, 3), 7)).Cells(Rows.Count, 1).End(xlUp).Row + 1, 1)
Sheets(1).Rows(1).Copy Destination:=Sheets(Left(Cells(I, 3), 7)).Rows(1)
Next I
End With
Test if the sheet exists before adding a new one. Here's a simple function for checking if a sheet with that name exists:
Function SheetExists(SheetName As String, Optional InWorkbook As Workbook) As Boolean
If InWorkbook Is Nothing Then Set InWorkbook = ActiveWorkbook
On Error Resume Next
SheetExists = Not InWorkbook.Sheets(SheetName) Is Nothing
On Error GoTo 0
End Function
You would add it to your code like:
Sub RowToSheet()
Dim xRow As Long
Dim I As Long
Dim ws As Worksheet
Application.ScreenUpdating = False
ActiveSheet.Name = "Sheet 1"
With ActiveSheet
xRow = .Range("A" & Rows.Count).End(xlUp).Row
For I = 2 To xRow
If Not SheetExists("Row " & I) Then Worksheets.Add(, Sheets(Sheets.Count)).Name = "Row " & I
.Rows(I).Copy Sheets("Row " & I).Range("A2")
Sheets(1).Rows(1).Copy Destination:=Sheets(I).Rows(1)
Next I
End With
End Sub
This way the sheet is only created if it did not already exist. The .Copy will overwrite the values on Range("A2") so you will want to change that to dynamically search for the next empty row like:
.Rows(I).Copy Sheets("Row " & I).Cells(Sheets("Row " & I).Cells(Rows.Count, 1).End(xlUp).Row + 1, 1)
And your line Sheets(1).Rows(1).Copy Destination:=Sheets(I).Rows(1) is just guessing that the new sheet will be in the same position as the loop index. I suggest correcting that to be:
Sheets(1).Rows(1).Copy Destination:=Sheets("Row " & I).Rows(1)
Use a dictionary to hold the unique customer numbers. Loop through them applying a filter to column C and copy the filtered records to a new sheet/workbook.
Option Explicit
Sub RowToSheet()
Dim wb As Workbook
Dim ws As Worksheet, wsNew As Worksheet
Dim LastRow As Long, i As Long, n As Integer
Dim dict As Object, key, rng As Range
Set dict = CreateObject("Scripting.Dictionary")
Set wb = ThisWorkbook
Set ws = wb.Sheets("Sheet 1")
LastRow = ws.Cells(Rows.Count, "C").End(xlUp).Row
Set rng = ws.Range("A1:P" & LastRow)
' build list of unique values from col C
For i = 2 To LastRow
key = Trim(Left(ws.Cells(i, "C"), 7))
If Len(key) > 0 Then dict(key) = 1
Next
' delete any existing sheets
Application.DisplayAlerts = False
For Each wsNew In wb.Sheets
If wsNew.Name <> "Sheet 1" Then
wsNew.Delete
End If
Next
Application.DisplayAlerts = True
n = wb.Sheets.Count
' create new sheets/workbooks for each unique value
Application.ScreenUpdating = False
For Each key In dict.keys
Set wsNew = wb.Sheets.Add(after:=wb.Sheets(n))
wsNew.Name = Right(key, 5) ' number with C:
n = n + 1
' filter on col C and copy to new sheet
rng.AutoFilter 3, Criteria1:=CStr(key) & "*"
rng.Copy wsNew.Range("A1")
rng.AutoFilter 3
' copy to new workbook
wsNew.Copy
ActiveWorkbook.SaveAs wb.Path & "\" & wsNew.Name
ActiveWorkbook.Close False
Next
Application.ScreenUpdating = False
'ws.AutoFilterMode = False
MsgBox dict.Count & " workbooks created", vbInformation
End Sub
You need Ron de Bruin's new sheet for all unique values!
Sub Copy_To_Worksheets()
'Note: This macro use the function LastRow
Dim My_Range As Range
Dim FieldNum As Long
Dim CalcMode As Long
Dim ViewMode As Long
Dim ws2 As Worksheet
Dim Lrow As Long
Dim cell As Range
Dim CCount As Long
Dim WSNew As Worksheet
Dim ErrNum As Long
'Set filter range on ActiveSheet: A1 is the top left cell of your filter range
'and the header of the first column, D is the last column in the filter range.
'You can also add the sheet name to the code like this :
'Worksheets("Sheet1").Range("A1:D" & LastRow(Worksheets("Sheet1")))
'No need that the sheet is active then when you run the macro when you use this.
Set My_Range = Range("A1:D" & LastRow(ActiveSheet))
My_Range.Parent.Select
If ActiveWorkbook.ProtectStructure = True Or _
My_Range.Parent.ProtectContents = True Then
MsgBox "Sorry, not working when the workbook or worksheet is protected", _
vbOKOnly, "Copy to new worksheet"
Exit Sub
End If
'This example filters on the first column in the range(change the field if needed)
'In this case the range starts in A so Field:=1 is column A, 2 = column B, ......
FieldNum = 1
'Turn off AutoFilter
My_Range.Parent.AutoFilterMode = False
'Change ScreenUpdating, Calculation, EnableEvents, ....
With Application
CalcMode = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
.EnableEvents = False
End With
ViewMode = ActiveWindow.View
ActiveWindow.View = xlNormalView
ActiveSheet.DisplayPageBreaks = False
'Add a worksheet to copy the a unique list and add the CriteriaRange
Set ws2 = Worksheets.Add
With ws2
'first we copy the Unique data from the filter field to ws2
My_Range.Columns(FieldNum).AdvancedFilter _
Action:=xlFilterCopy, _
CopyToRange:=.Range("A1"), Unique:=True
'loop through the unique list in ws2 and filter/copy to a new sheet
Lrow = .Cells(Rows.Count, "A").End(xlUp).Row
For Each cell In .Range("A2:A" & Lrow)
'Filter the range
My_Range.AutoFilter Field:=FieldNum, Criteria1:="=" & _
Replace(Replace(Replace(cell.Value, "~", "~~"), "*", "~*"), "?", "~?")
'Check if there are no more then 8192 areas(limit of areas)
CCount = 0
On Error Resume Next
CCount = My_Range.Columns(1).SpecialCells(xlCellTypeVisible) _
.Areas(1).Cells.Count
On Error GoTo 0
If CCount = 0 Then
MsgBox "There are more than 8192 areas for the value : " & cell.Value _
& vbNewLine & "It is not possible to copy the visible data." _
& vbNewLine & "Tip: Sort your data before you use this macro.", _
vbOKOnly, "Split in worksheets"
Else
'Add a new worksheet
Set WSNew = Worksheets.Add(After:=Sheets(Sheets.Count))
On Error Resume Next
WSNew.Name = cell.Value
If Err.Number > 0 Then
ErrNum = ErrNum + 1
WSNew.Name = "Error_" & Format(ErrNum, "0000")
Err.Clear
End If
On Error GoTo 0
'Copy the visible data to the new worksheet
My_Range.SpecialCells(xlCellTypeVisible).Copy
With WSNew.Range("A1")
' Paste:=8 will copy the columnwidth in Excel 2000 and higher
' Remove this line if you use Excel 97
.PasteSpecial Paste:=8
.PasteSpecial xlPasteValues
.PasteSpecial xlPasteFormats
Application.CutCopyMode = False
.Select
End With
End If
'Show all data in the range
My_Range.AutoFilter Field:=FieldNum
Next cell
'Delete the ws2 sheet
On Error Resume Next
Application.DisplayAlerts = False
.Delete
Application.DisplayAlerts = True
On Error GoTo 0
End With
'Turn off AutoFilter
My_Range.Parent.AutoFilterMode = False
If ErrNum > 0 Then
MsgBox "Rename every WorkSheet name that start with ""Error_"" manually" _
& vbNewLine & "There are characters in the name that are not allowed" _
& vbNewLine & "in a sheet name or the worksheet already exist."
End If
'Restore ScreenUpdating, Calculation, EnableEvents, ....
My_Range.Parent.Select
ActiveWindow.View = ViewMode
With Application
.ScreenUpdating = True
.EnableEvents = True
.Calculation = CalcMode
End With
End Sub
Function LastRow(sh As Worksheet)
On Error Resume Next
LastRow = sh.Cells.Find(What:="*", _
After:=sh.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlValues, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
On Error GoTo 0
End Function
Before:
After:
Source:
https://www.rondebruin.nl/win/s3/win006_4.htm

Excel VBA - open a workbook and pasting data

I found this excellent code however I need to adapt it for my purposes.
Firstly I need to open a data workbook that is on our network. The problem I have is that it is likely at times to be open by another user and will offer the option of "read only". How can I get it to accept the read-only option so that I can commence extracting the data.
Secondly it copies using the "=" . How can I change it to copy just the values?
First macro:
Sub test()
'to open another workbook
Application.ScreenUpdating = False
Workbooks.Open Filename:=ThisWorkbook.Path & "\Schedule.xls"
ThisWorkbook.Activate
Application.ScreenUpdating = True
End Sub
2nd Macro:
Dim Sh As Worksheet
Dim Newsh As Worksheet
Dim myCell As Range
Dim ColNum As Integer
Dim RwNum As Long
Dim Basebook As Workbook
With Application
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
'Delete the sheet "Summary-Sheet" if it exist
Application.DisplayAlerts = False
On Error Resume Next
ThisWorkbook.Worksheets("Summary-Sheet").Delete
On Error GoTo 0
Application.DisplayAlerts = True
'Add a worksheet with the name "Summary-Sheet"
Set Basebook = ThisWorkbook
Set Newsh = Basebook.Worksheets.Add
Newsh.Name = "Summary-Sheet"
'The links to the first sheet will start in row 2
RwNum = 1
For Each Sh In Basebook.Worksheets
If Sh.Name <> Newsh.Name And Sh.Visible Then
ColNum = 1
RwNum = RwNum + 1
'Copy the sheet name in the A column
Newsh.Cells(RwNum, 1).Value = Sh.Name
For Each myCell In Sh.Range("A1,D5:E5,Z10") '<--Change the range
ColNum = ColNum + 1
Newsh.Cells(RwNum, ColNum).Formula = _
"='" & Sh.Name & "'!" & myCell.Address(False, False)
Next myCell
End If
Next Sh
Newsh.UsedRange.Columns.AutoFit
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
End Sub
You could always open the workbook as read-only if you are only extracting data.
Instead of using .formula use .value

Excluding specific cells from being copied when copy one Workbook to another Workbook

The code I am using takes sheets as array and copies them as XlValues, but there are few cells containing formulas which I want to keep and paste as xlFormats. How can i achieve that?
Sub CopyPasteSave()
Dim NewName As String
Dim nm As Name
Dim ws As Worksheet
Dim Path As String, rcell As Range
Set rcell = Sheets("EPF Daily Report").Range("I5")
Path = "D:\"
If MsgBox("Copy specific sheets to a new workbook" & vbCr & _
"New sheets will be pasted as values, named ranges removed" _
, vbYesNo, "NewCopy") = vbNo Then Exit Sub
With Application
.ScreenUpdating = False
' Copy specific sheets
' *SET THE SHEET NAMES TO COPY BELOW*
' Array("Sheet1", "Sheet2"))
' Sheet names go inside quotes, seperated by commas
On Error GoTo ErrCatcher
Sheets(Array("InletManifold", "Separator", "Crude Strippers & Reboilers ", "Water Strippers & Reboilers ", "Crude Storage&Export", "GSU,FLARE & GEN", "EPF Utility", "EPF Daily Report", "Choke Size")).Copy
On Error GoTo 0
' Paste sheets as values
' Remove External Links, Hperlinks and hard-code formulas
' Make sure A1 is selected on all sheets
For Each ws In ActiveWorkbook.Worksheets
ws.Cells.Copy
ws.[A1].PasteSpecial Paste:=xlValues
Application.DisplayAlerts = False
ws.Cells.Hyperlinks.Delete
Application.CutCopyMode = False
Cells(1, 1).Select
ws.Activate
Next ws
Cells(1, 1).Select
' Remove named ranges
For Each nm In ActiveWorkbook.Names
nm.Delete
Next nm
' Input box to name new file
'NewName = InputBox("Please Specify the name of your new workbook", "New Copy")
' Save it with the NewName and in the same directory as original
ActiveWorkbook.SaveAs Filename:=Path & " " & "EPF Daily Report" & " " & rcell.Value & ".xls"
ActiveWorkbook.Close SaveChanges:=True
.ScreenUpdating = False
End With
Exit Sub
ErrCatcher:
MsgBox "specified sheets do not exist within this work book"
End Sub
What I've done below, after the sheets are copied as values, is to copy cells that you specify from the original workbook, using PasteSpecial to keep their formulas intact. A couple of notes:
Added an array, CellsToCopy, that contains the addresses, e.g., B11 and B12 that
you want to copy with formulas. Mdoify this as needed.
Added wbSource and wbTarget workbook variables, to refer to in the PasteSpecial
Cleaned up your code, turning DisplayAlerts back on, and adding
to the error handling
Got rid of your Select statement and replaced with
Application.GoTo
Also, note that you don't have to do anything special to keep the formats, as the copy as values will not have changed them.
Sub CopyPasteSave()
Dim wbSource As Excel.Workbook
Dim wbTarget As Excel.Workbook
Dim nm As Name
Dim ws As Worksheet
Dim CellsToCopy() As String
Dim i As Long
Dim Path As String
Dim rcell As Range
If MsgBox("Copy specific sheets to a new workbook" & vbCr & _
"New sheets will be pasted as values, named ranges removed" _
, vbYesNo, "NewCopy") = vbNo Then
Exit Sub
End If
Set wbSource = ActiveWorkbook
Set rcell = Sheets("EPF Daily Report").Range("I5")
Path = "D:\"
'Enter cells to copy with formulas
CellsToCopy = Split(("B11,B12"), ",")
Application.ScreenUpdating = False
' Copy specific sheets
' *SET THE SHEET NAMES TO COPY BELOW*
' Sheet names go inside quotes, separated by commas
On Error GoTo ErrCatcher
wbSource.Sheets(Array("InletManifold", "Separator", "Crude Strippers & Reboilers ", "Water Strippers & Reboilers ", "Crude Storage&Export", "GSU,FLARE & GEN", "EPF Utility", "EPF Daily Report", "Choke Size")).Copy
On Error GoTo 0
' Paste sheets as values
' Remove External Links, Hyperlinks and hard-code formulas
' Make sure A1 is selected on all sheets
Set wbTarget = ActiveWorkbook
For Each ws In wbTarget.Worksheets
With ws
.Cells.Copy
.[A1].PasteSpecial Paste:=xlValues
For i = LBound(CellsToCopy) To UBound(CellsToCopy)
wbSource.Worksheets(ws.Name).Range(CellsToCopy(i)).Copy
ws.Range(CellsToCopy(i)).PasteSpecial xlPasteFormulas
Next i
Application.CutCopyMode = False
Application.DisplayAlerts = False
.Cells.Hyperlinks.Delete
Application.DisplayAlerts = True
Application.Goto .Range("A1")
End With
Next ws
With wbTarget
' Remove named ranges
For Each nm In .Names
nm.Delete
Next nm
' Input box to name new file
'NewName = InputBox("Please Specify the name of your new workbook", "New Copy")
' Save it with the NewName and in the same directory as original
.SaveAs Filename:=Path & " " & "EPF Daily Report" & " " & rcell.Value & ".xls"
.Close SaveChanges:=True
End With
Exit_Point:
Application.ScreenUpdating = False
Application.DisplayAlerts = True
Exit Sub
ErrCatcher:
MsgBox "specified sheets do not exist within this work book"
Resume Exit_Point
End Sub

Avoiding spaghetti code when copying from one wb to another

Is there an alternative to using ActiveWorkbook and ActiveSheet in VBA when working with multiple workbooks (copying from workbooks in a list to a master sheet) ? It is turning out to be more confusing than anything to know which workbook is open when working with multiple functions which need to use different workbooks. Is it a matter of code organization ?
For the moment I think I can manage by storing the activeworkbook's name at the beginning of every function and restoring it, but it seems like a lot of work and probably a lot of processing time for not much results.
Ideas ?
You might be interested in this page http://www.techrepublic.com/blog/10things/10-ways-to-reference-excel-workbooks-and-sheets-using-vba/967
Specifically look at 4: Explicitly reference a workbook
Or 10: Refer to a sheet’s code name property
Typically when you work through a list you will use a workbook variable to open, manipulate and then close each book
My code below is an example of work through and collate a directory of workbooks (similar to your list example). From Collating worksheets from one or more workbooks into a summary file
Public Sub ConsolidateSheets()
Dim Wb1 As Workbook
Dim Wb2 As Workbook
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim ws3 As Worksheet
Dim rng1 As Range
Dim rng2 As Range
Dim rng3 As Range
Dim rngArea As Range
Dim lrowSpace As Long
Dim lSht As Long
Dim lngCalc As Long
Dim lngRow As Long
Dim lngCol As Long
Dim X()
Dim bProcessFolder As Boolean
Dim bNewSheet As Boolean
Dim StrPrefix
Dim strFileName As String
Dim strFolderName As String
'variant declaration needed for the Shell object to use a default directory
Dim strDefaultFolder As Variant
bProcessFolder = (MsgBox("Process a single folder (Yes)," & vbNewLine & "or single file (No)", vbYesNo, "Application Scope: Folder or single File") = vbYes)
bNewSheet = (MsgBox("Extract all data to a single sheet (Yes)," & vbNewLine & "or a target file sheet for each source sheet (No)", vbYesNo, "Output Format: Single sheet or sheet by sheet collection") = vbYes)
If Not bProcessFolder Then
If Not bNewSheet Then
MsgBox "There isn't much point creating a exact replica of your source file :)"
Exit Sub
End If
End If
'set default directory here if needed
strDefaultFolder = "C:\temp"
'If the user is collating all the sheets to a single target sheet then the row spacing
'to distinguish between different sheets can be set here
lrowSpace = 1
If bProcessFolder Then
strFolderName = BrowseForFolder(strDefaultFolder)
'Look for xls, xlsx, xlsm files
strFileName = Dir(strFolderName & "\*.xls*")
Else
strFileName = Application _
.GetOpenFilename("Select file to process (*.xls*), *.xls*")
End If
Set Wb1 = Workbooks.Add(1)
Set ws1 = Wb1.Sheets(1)
If Not bNewSheet Then ws1.Range("A1:B1") = Array("workbook name", "worksheet count")
'Turn off screenupdating, events, alerts and set calculation to manual
With Application
.DisplayAlerts = False
.EnableEvents = False
.ScreenUpdating = False
lngCalc = .Calculation
.Calculation = xlCalculationManual
End With
'set path outside the loop
StrPrefix = strFolderName & IIf(bProcessFolder, "\", vbNullString)
Do While Len(strFileName) > 0
'Provide progress status to user
Application.StatusBar = Left("Processing " & strFolderName & "\" & strFileName, 255)
'Open each workbook in the folder of interest
Set Wb2 = Workbooks.Open(StrPrefix & strFileName)
If Not bNewSheet Then
'add summary details to first sheet
ws1.Cells(Rows.Count, "A").End(xlUp).Offset(1, 0) = Wb2.Name
ws1.Cells(Rows.Count, "A").End(xlUp).Offset(0, 1) = Wb2.Sheets.Count
End If
For Each ws2 In Wb2.Sheets
If bNewSheet Then
'All data to a single sheet
'Skip importing target sheet data if the source sheet is blank
Set rng2 = ws2.Cells.Find("*", ws2.[a1], xlValues, , xlByRows, xlPrevious)
If Not rng2 Is Nothing Then
Set rng1 = ws1.Cells.Find("*", ws1.[a1], xlValues, , xlByRows, xlPrevious)
'Find the first blank row on the target sheet
If Not rng1 Is Nothing Then
Set rng3 = ws2.Range(ws2.UsedRange.Cells(1), ws2.Cells(rng2.Row, "A"))
'Ensure that the row area in the target sheet won't be exceeded
If rng3.Rows.Count + rng1.Row < Rows.Count Then
'Copy the data from the used range of each source sheet to the first blank row
'of the target sheet, using the starting column address from the source sheet being copied
ws2.UsedRange.Copy ws1.Cells(rng1.Row + 1 + lrowSpace, ws2.UsedRange.Cells(1).Column)
Else
MsgBox "Summary sheet size exceeded. Process stopped on " & vbNewLine & _
"sheet: " & ws2.Name & vbNewLine & "of" & vbNewLine & "workbook: " & Wb2.Name
Wb2.Close False
Exit Do
End If
'colour the first of any spacer rows
If lrowSpace <> 0 Then ws1.Rows(rng1.Row + 1).Interior.Color = vbGreen
Else
'target sheet is empty so copy to first row
ws2.UsedRange.Copy ws1.Cells(1, ws2.UsedRange.Cells(1).Column)
End If
End If
Else
'new target sheet for each source sheet
ws2.Copy after:=Wb1.Sheets(Wb1.Sheets.Count)
'Remove any links in our target sheet
With Wb1.Sheets(Wb1.Sheets.Count).Cells
.Copy
.PasteSpecial xlPasteValues
End With
On Error Resume Next
Wb1.Sheets(Wb1.Sheets.Count).Name = ws2.Name
'sheet name already exists in target workbook
If Err.Number <> 0 Then
'Add a number to the sheet name till a unique name is derived
Do
lSht = lSht + 1
Set ws3 = Wb1.Sheets(ws2.Name & " " & lSht)
Loop While Not ws3 Is Nothing
lSht = 0
End If
On Error GoTo 0
End If
Next ws2
'Close the opened workbook
Wb2.Close False
'Check whether to force a DO loop exit if processing a single file
If bProcessFolder = False Then Exit Do
strFileName = Dir
Loop
'Remove any links if the user has used a target sheet
If bNewSheet Then
With ws1.UsedRange
.Copy
.Cells(1).PasteSpecial xlPasteValues
.Cells(1).Activate
End With
Else
'Format the summary sheet if the user has created separate target sheets
ws1.Activate
ws1.Range("A1:B1").Font.Bold = True
ws1.Columns.AutoFit
End If
With Application
.CutCopyMode = False
.DisplayAlerts = True
.EnableEvents = True
.ScreenUpdating = True
.Calculation = lngCalc
.StatusBar = vbNullString
End With
End Sub
Function BrowseForFolder(Optional OpenAt As Variant) As Variant
'From Ken Puls as used in his vbaexpress.com article
'http://www.vbaexpress.com/kb/getarticle.php?kb_id=284
Dim ShellApp As Object
'Create a file browser window at the default folder
Set ShellApp = CreateObject("Shell.Application"). _
BrowseForFolder(0, "Please choose a folder", 0, OpenAt)
'Set the folder to that selected. (On error in case cancelled)
On Error Resume Next
BrowseForFolder = ShellApp.self.Path
On Error GoTo 0
'Destroy the Shell Application
Set ShellApp = Nothing
'Check for invalid or non-entries and send to the Invalid error
'handler if found
'Valid selections can begin L: (where L is a letter) or
'\\ (as in \\servername\sharename. All others are invalid
Select Case Mid(BrowseForFolder, 2, 1)
Case Is = ":"
If Left(BrowseForFolder, 1) = ":" Then GoTo Invalid
Case Is = "\"
If Not Left(BrowseForFolder, 1) = "\" Then GoTo Invalid
Case Else
GoTo Invalid
End Select
Exit Function
Invalid:
'If it was determined that the selection was invalid, set to False
BrowseForFolder = False
End Function

Resources