Keep record of people who open Excel file - excel

The code below does exactly what I want.. It keeps a record of people that opens my excel file but the problem is that it keeps the file open to everyone.
I want this tab to work in the background, so it is not visible to anyone except for me, when I go to the backend or click a button somewhere that will make it visible.
Please not that just the case the tab Audit is visible, the code must always check if the tab is opened and hide immediately when someone opens the file
Thanks in advance
Private Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" ( _
ByVal lpBuffer As String, _
nSize As Long) As Long
Private Declare Function GetComputerName Lib "kernel32" Alias "GetComputerNameA" ( _
ByVal lpBuffer As String, _
nSize As Long) As Long
Private pAuditSheet As Worksheet
Private Const USERNAME_COL = 1
Private Const COMPUTERNAME_COL = 2
Private Const OPEN_TIME_COL = 3
Private Const CLOSE_TIME_COL = 4
Private Const OPEN_WB_NAME_COL = 5
Private Const CLOSE_WB_NAME_COL = 6
Private Const KEEP_ONLY_LAST_N_ENTRIES = 1
Private Sub Workbook_Open()
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Workbook_Open
' Runs when the workbook is opened.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Me.Worksheets("Audit").Protect UserInterfaceOnly:=True
Dim WS As Worksheet
Dim RowNum As Long
Dim N As Long
Dim S As String
Application.ScreenUpdating = False
On Error Resume Next
Err.Clear
Set WS = Me.Worksheets("Audit")
If Err.Number = 9 Then
Set WS = Me.Worksheets.Add(before:=1)
WS.Name = "Audit"
End If
On Error GoTo 0
With WS
If .Cells(1, USERNAME_COL).Value = vbNullString Then
.Cells(1, USERNAME_COL).Value = "User Name"
.Cells(1, COMPUTERNAME_COL).Value = "Computer Name"
.Cells(1, OPEN_TIME_COL).Value = "Open Time"
.Cells(1, CLOSE_TIME_COL).Value = "Close Time"
.Cells(1, OPEN_WB_NAME_COL).Value = "Open WB Name"
.Cells(1, CLOSE_WB_NAME_COL).Value = "Close WB Name"
End If
'.Visible = xlSheetVeryHidden
RowNum = .Cells(.Rows.Count, USERNAME_COL).End(xlUp)(2, 1).Row
N = 255
S = String(N, vbNullChar)
N = GetUserName(S, N)
.Cells(RowNum, USERNAME_COL).Value = TrimToNull(S)
N = 255
S = String(N, vbNullChar)
N = GetComputerName(S, N)
.Cells(RowNum, COMPUTERNAME_COL).Value = TrimToNull(S)
.Cells(RowNum, OPEN_TIME_COL).Value = Now
' Leave Close Time empty. It will be filled on close.
.Cells(RowNum, CLOSE_TIME_COL).Value = vbNullString
.Cells(RowNum, OPEN_WB_NAME_COL).Value = ThisWorkbook.FullName
' Leave Close Name empty. It will be filled on close.
.Cells(RowNum, CLOSE_WB_NAME_COL).Value = vbNullString
.UsedRange.Columns.AutoFit
End With
Application.ScreenUpdating = True
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Workbook_BeforeClose
' Runs when the workbook is closed.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim WS As Worksheet
Dim RowNum As Long
Dim EndRow As Long
Dim LastDel As Long
Dim FirstDel As Long
Application.ScreenUpdating = False
Set WS = Worksheets("Audit")
With WS
RowNum = .Cells(.Rows.Count, CLOSE_TIME_COL).End(xlUp).Row + 1
.Cells(RowNum, CLOSE_TIME_COL).Value = Now
.Cells(RowNum, CLOSE_WB_NAME_COL).Value = ThisWorkbook.FullName
.UsedRange.Columns.AutoFit
If KEEP_ONLY_LAST_N_ENTRIES > 0 Then
EndRow = .Cells(.Rows.Count, USERNAME_COL).End(xlUp).Row
If EndRow > 2 Then
FirstDel = 2
LastDel = EndRow - KEEP_ONLY_LAST_N_ENTRIES
If LastDel > 2 Then
.Cells(FirstDel, "A").Resize(LastDel - 1, 1).Select
End If
End If
End If
End With
Application.ScreenUpdating = True
End Sub
Private Function TrimToNull(S As String) As String
'''''''''''''''''''''''''''''''''''''''''''''''''''
' TrimToNull
' Returns the portion of string S that is to the
' left of the vbNullChar, Chr(0).
'''''''''''''''''''''''''''''''''''''''''''''''''''
Dim N As Long
N = InStr(1, S, vbNullChar)
If N = 0 Then
TrimToNull = S
Else
TrimToNull = Left(S, N - 1)
End If
End Function

you may find that if you uncomment the line
.Visible = xlSheetVeryHidden
it will most probably work in the way you want it to!

Related

Apply a macro to all opened Excel Workbooks

I am trying to create a macro that can be used to summarise data provided by users on a weekly basis. I have written several Subroutines that combined do what I want, but I'm now looking to be able to run the VBA code once on all workbooks in a folder and save me from opening each one and then running the macro.
To give context the idea is to sum daily activity and place this on a newly created worksheet in the workbook which I call "Weekly Totals", the idea being that I'll copy the data from "Weekly Totals" to a single workbook at a later point.
Sub DoEverything()
Dim ws As Worksheet
For Each ws In Worksheets
ws.Activate
SumRowsValues
SumColumnsValues
Next ws
AddTotalSheet
CopyFromWorksheets
ListSheetNames
GetFileName
RemoveTextBeforeUnderscore
StringToDate
End Sub
I have created a Personal.xlsb so that I can access the Subroutine above and I have another macro that opens every workbook within a designated folder, but what can I add to this Subroutine that would make it apply to any number of workbooks that I open or that are in this designated folder?
Edit:
I shall include the code so the question is not wasting people's time unnecessarily.
Sub SumRowsValues()
Dim i As Long
For i = 4 To 44
If Application.WorksheetFunction.Sum(Range(Cells(i, 3), Cells(i, 10))) <> 0 Then
Cells(i, 11) = 15
End If
Next i
End Sub
Sub SumColumnsValues()
Dim i As Long
For i = 3 To 11
Cells(45, i) = Application.WorksheetFunction.Sum(Range(Cells(4, i), Cells(44, i)))
Next i
End Sub
Sub AddTotalSheet()
Sheets.Add(Before:=Sheets("Mon")).Name = "Weekly Totals"
End Sub
Sub CopyFromWorksheets()
Worksheets("Weekly Totals").Range("A1").Value = "Date"
Worksheets("Weekly Totals").Range("B1").Value = "Person"
Worksheets("Weekly Totals").Range("C1").Value = "Day"
Worksheets("Mon").Range("C3:K3").Copy Worksheets("Weekly Totals").Range("D1")
Worksheets("Mon").Range("C45:K45").Copy Worksheets("Weekly Totals").Range("D2")
Worksheets("Tue").Range("C45:K45").Copy Worksheets("Weekly Totals").Range("D3")
Worksheets("Wed").Range("C45:K45").Copy Worksheets("Weekly Totals").Range("D4")
Worksheets("Thu").Range("C45:K45").Copy Worksheets("Weekly Totals").Range("D5")
Worksheets("Fri").Range("C45:K45").Copy Worksheets("Weekly Totals").Range("D6")
End Sub
Sub ListSheetNames()
Dim ws As Worksheet
Sheets("Weekly Totals").Activate
ActiveSheet.Cells(2, 3).Select
For Each ws In Worksheets
If ws.Name = "Weekly Totals" Then
Else
ActiveCell = ws.Name
ActiveCell.Offset(1, 0).Select
End If
Next
End Sub
Sub GetFileName()
Dim strFileFullName, DateText, NameText, strDuplicateFileName As String
strFileFullName = ActiveWorkbook.Name
strDuplicateFileName = strFileFullName
DateText = Split(strFileFullName, "_")
NameText = Split(strDuplicateFileName, ".")
Worksheets("Weekly Totals").Range("A2").Value = DateText
Worksheets("Weekly Totals").Range("B2").Value = NameText
End Sub
Sub RemoveTextBeforeUnderscore()
Dim i As Long '
Dim rng As Range
Dim cell As Range
Set rng = Worksheets("Weekly Totals").Range("B2")
For i = 1 To 5 '
For Each cell In rng
cell(i, 1).Value = Right(cell.Value, Len(cell.Value) + 1 - InStr(cell.Value, "_") - 1)
Next cell
Next i
End Sub
Sub StringToDate()
Dim InitialValue As Long
Dim DateAsString As String
Dim FinalDate As Date
InitialValue = Worksheets("Weekly Totals").Range("A2").Value
DateAsString = CStr(InitialValue)
FinalDate = DateSerial(CInt(Left(DateAsString, 4)), CInt(Mid(DateAsString, 5, 2)), CInt(Right(DateAsString, 2)))
Range("A2").Value = FinalDate
Range("A3").Value = FinalDate + 1
Range("A4").Value = FinalDate + 2
Range("A5").Value = FinalDate + 3
Range("A6").Value = FinalDate + 4
Columns("A").AutoFit
End Sub
Not I am sure the most efficient or elegant, but it does work to this point. The code for opening all workbooks in a folder is:
Sub OpenAllFilesDirectory()
Dim Folder As String, FileName As String
Folder = "pathway..."
FileName = Dir(Folder & "\*.xlsx")
Do
Workbooks.Open Folder & "\" & FileName
FileName = Dir
Loop Until FileName = ""
End Sub
All the files will having the naming convention of "YYYYMMDD_Name.xlsx", e.g. 20211128_JSmith
The table on worksheet looks like this:
etc.
The output looks like this:
etc.
This is partially tested since we have no data to test for the SumRowsValues, SumColumnsValues and CopyFromWorksheets but it should work as I did not change much from it other than changing the range reference away from ActiveWorkbook and Activesheet.
I have tried to change as little as possible from the original code as this answer is only focused on how to connect OpenAllFilesDirectory to DoEverything. There are many things that can be streamlined and improve on.
Option Explicit
Const TOTAL_WSNAME As String = "Weekly Totals"
Sub OpenAllFilesDirectory()
Dim Folder As String, FileName As String
Folder = "pathway..."
FileName = Dir(Folder & "\*.xlsx")
Do
Dim currentWB As Workbook
Set currentWB = Workbooks.Open(Folder & "\" & FileName)
DoEverything currentWB
FileName = Dir
Loop Until FileName = ""
End Sub
Sub DoEverything(argWB As Workbook)
Dim ws As Worksheet
For Each ws In argWB.Worksheets
SumRowsValues ws
SumColumnsValues ws
Next ws
Dim totalWS As Worksheet
Set totalWS = AddTotalSheet(argWB)
CopyFromWorksheets argWB
ListSheetNames argWB
GetFileName totalWS
RemoveTextBeforeUnderscore totalWS
StringToDate totalWS
End Sub
Sub SumRowsValues(argWS As Worksheet)
Dim i As Long
For i = 4 To 44
If Application.WorksheetFunction.Sum(argWS.Range(argWS.Cells(i, 3), argWS.Cells(i, 10))) <> 0 Then
argWS.Cells(i, 11) = 15
End If
Next i
End Sub
Sub SumColumnsValues(argWS As Worksheet)
Dim i As Long
For i = 3 To 11
argWS.Cells(45, i) = Application.WorksheetFunction.Sum(argWS.Range(argWS.Cells(4, i), argWS.Cells(44, i)))
Next i
End Sub
Function AddTotalSheet(argWB As Workbook) As Worksheet
Dim totalWS As Worksheet
Set totalWS = argWB.Sheets.Add(Before:=argWB.Sheets("Mon"))
totalWS.Name = TOTAL_WSNAME
Set AddTotalSheet = totalWS
End Function
Sub CopyFromWorksheets(argWB As Workbook)
Dim totalWS As Worksheet
Set totalWS = argWB.Worksheets(TOTAL_WSNAME)
totalWS.Range("A1").Value = "Date"
totalWS.Range("B1").Value = "Person"
totalWS.Range("C1").Value = "Day"
argWB.Worksheets("Mon").Range("C3:K3").Copy totalWS.Range("D1")
argWB.Worksheets("Mon").Range("C45:K45").Copy totalWS.Range("D2")
argWB.Worksheets("Tue").Range("C45:K45").Copy totalWS.Range("D3")
argWB.Worksheets("Wed").Range("C45:K45").Copy totalWS.Range("D4")
argWB.Worksheets("Thu").Range("C45:K45").Copy totalWS.Range("D5")
argWB.Worksheets("Fri").Range("C45:K45").Copy totalWS.Range("D6")
End Sub
Sub ListSheetNames(argWB As Workbook)
Dim insertCell As Range
Set insertCell = argWB.Worksheets(TOTAL_WSNAME).Range("C2")
Dim ws As Worksheet
For Each ws In argWB.Worksheets
If ws.Name <> TOTAL_WSNAME Then
insertCell.Value = ws.Name
Set insertCell = insertCell.Offset(1)
End If
Next
End Sub
Sub GetFileName(argWS As Worksheet)
Dim strFileFullName As String
Dim DateText As String
Dim NameText As String
strFileFullName = argWS.Parent.Name
DateText = Split(strFileFullName, "_")(0)
NameText = Split(strFileFullName, ".")(0)
argWS.Range("A2").Value = DateText
argWS.Range("B2").Value = NameText
End Sub
Sub RemoveTextBeforeUnderscore(argWS As Worksheet)
Dim i As Long
Dim rng As Range
Dim cell As Range
Set rng = argWS.Range("B2")
For i = 1 To 5 '
For Each cell In rng
cell(i, 1).Value = Right(cell.Value, Len(cell.Value) + 1 - InStr(cell.Value, "_") - 1)
Next cell
Next i
End Sub
Sub StringToDate(argWS As Worksheet)
Dim InitialValue As Long
Dim DateAsString As String
Dim FinalDate As Date
InitialValue = argWS.Range("A2").Value
DateAsString = CStr(InitialValue)
FinalDate = DateSerial(CInt(Left(DateAsString, 4)), CInt(Mid(DateAsString, 5, 2)), CInt(Right(DateAsString, 2)))
argWS.Range("A2").Value = FinalDate
argWS.Range("A3").Value = FinalDate + 1
argWS.Range("A4").Value = FinalDate + 2
argWS.Range("A5").Value = FinalDate + 3
argWS.Range("A6").Value = FinalDate + 4
argWS.Columns("A").AutoFit
End Sub

Why is my match row function not being entered?

I have a userform that is pulling in data from a worksheet into the userform fields. I have a function that matches the row of that employee if the employee number in userform is found in column F.
It used to work but now it doesn't even enter the function to determine if that employee exists in the data.
Private Sub CommandButton2_Click()
On Error Resume Next
Dim wb As Workbook: Set wb = Workbooks.Open("J:\HRIS Team\Analytics\Headcount Tracking File.xlsx")
Dim ws As Worksheet: Set ws = wb.Worksheets("Sheet1")
Dim lString As String, lArray() As String
lString = cmbEmployee.Value
lArray = Split(lString, " - ")
Dim recRow As Range
If optEmployeeName.Value = True And optEmployeeID.Value <> True Then
Set recRow = MatchRow(ws.Range("A1").CurrentRegion, _
lArray(1))
Else
Set recRow = MatchRow(ws.Range("A1").CurrentRegion, _
lArray(0))
End If
If recRow Is Nothing Then MsgBox "Employee not found"
With recRow.EntireRow
Me.cmbFunction.Value = .Cells(1).Value
Me.cmbHRBP.Value = .Cells(3).Value
Me.cmbRequestType.Value = .Cells(4).Value
Me.cmbMovementType.Value = .Cells(7).Value
Me.txtEffectiveDate.Value = .Cells(8).Value
Function MatchRow(tablerange As Range, lArray) As Range
Dim rw As Range
Dim lString_2 As String, lArray_2() As String
lString_2 = cmbEmployee.Value
lArray_2 = Split(lString_2, " - ")
For Each rw In tablerange.Rows
If optEmployeeName.Value = True Then
If CStr(rw.Cells(6).Value) = Trim(lArray_2(1)) Then
Set MatchRow = rw
Exit Function
End If
ElseIf optEmployeeID.Value = True Then
If CStr(rw.Cells(6).Value) = Trim(lArray_2(0)) Then
Set MatchRow = rw
Exit Function
End If
End If
Next rw
End Function
I hover over to make sure it's getting the employee ID correctly from the lArray, and its there. I can't figure out the reasoning behind why it wouldn't even attempt to enter the matchrow function. Any ideas?

VBA / Macro upgraded from 32 bit to 64 bit

I have been upgraded from 32bit to 64 bit and my macro to merge and arrange files in a folder does not work any more, not a VBA user so am pretty much stuck and would appreciate help getting the marco to work ?
Option Explicit
'32-bit API declarations
Declare Function SHGetPathFromIDList Lib "shell32.dll" _
Alias "SHGetPathFromIDListA" (ByVal pidl As Long, ByVal _
pszpath As String) As Long
Declare Function SHBrowseForFolder Lib "shell32.dll" _
Alias "SHBrowseForFolderA" (lpBrowseInfo As BrowseInfo) _
As Long
Public Type BrowseInfo
hOwner As Long
pIDLRoot As Long
pszDisplayName As String
lpszTitle As String
ulFlags As Long
lpfn As Long
lParam As Long
iImage As Long
End Type
Function GetDirectory(Optional msg) As String
On Error Resume Next
Dim bInfo As BrowseInfo
Dim path As String
Dim r As Long, x As Long, pos As Integer
'Root folder = Desktop
bInfo.pIDLRoot = 0&
'Title in the dialog
If IsMissing(msg) Then
bInfo.lpszTitle = "Please select the folder of the excel files to copy."
Else
bInfo.lpszTitle = msg
End If
'Type of directory to return
bInfo.ulFlags = &H1
'Display the dialog
x = SHBrowseForFolder(bInfo)
'Parse the result
path = Space$(512)
r = SHGetPathFromIDList(ByVal x, ByVal path)
If r Then
pos = InStr(path, Chr$(0))
GetDirectory = Left(path, pos - 1)
Else
GetDirectory = ""
End If
End Function
Sub CombineFiles()
Dim path As String
Dim FileName As String
Dim LastCell As Range
Dim Wkb As Workbook
Dim ws As Worksheet
Dim ThisWB As String
Application.DisplayAlerts = False
ThisWB = ThisWorkbook.Name
Application.EnableEvents = False
Application.ScreenUpdating = False
path = GetDirectory
FileName = Dir(path & "\*.xlsx", vbNormal)
Do Until FileName = ""
If FileName <> ThisWB Then
Set Wkb = Workbooks.Open(FileName:=path & "\" & FileName)
For Each ws In Wkb.Worksheets
If ws.Visible = xlSheetHidden Then
Else
ws.Copy After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count)
End If
Next ws
Wkb.Close False
End If
FileName = Dir()
Loop
Application.EnableEvents = True
Application.ScreenUpdating = True
Set Wkb = Nothing
Set LastCell = Nothing
Call Sort_Tabs
Call Hide_create_tab
Call Select_all_sheets
Application.Dialogs(xlDialogSaveAs).Show "Enter MCR file name"
Application.DisplayAlerts = True
End Sub
Sub Sort_Tabs()
'declare our variables
Dim i, j As Integer
Dim iNumSheets As Integer
'find the number of sheets to work with
iNumSheets = ActiveWorkbook.Sheets.Count
'turn off screen updating to prevent screen flicker
Application.ScreenUpdating = False
'work through our number of sheets
For i = 1 To iNumSheets - 1
For j = i + 1 To iNumSheets
'check the name of the sheet regardless of case
If UCase(Sheets(i).Name) > UCase(Sheets(j).Name) Then
'set where to move the sheet to
Sheets(j).Move before:=Sheets(i)
End If
'do next sheet
Next j
Next i
'let the screen update
Application.ScreenUpdating = True
End Sub
Sub Hide_create_tab()
Sheets("Create MCR").Select
ActiveSheet.Visible = False
End Sub
Sub Select_all_sheets()
Dim ws As Worksheet
For Each ws In Sheets
If ws.Visible Then ws.Select (False)
Next
End Sub
Have a look at this page:
https://jkp-ads.com/Articles/apideclarations.asp
NB: Please make sure all code in your message is marked as such so we can easily read the code.

VBA match function mismatch despite having the search value in source

Option Explicit
Sub ExtractDivFromAastocks()
Dim StockCode As String, Anchor As String
Dim ws As Worksheet
StockCode = "02800"
Anchor = "Announce Date"
Set ws = ExtractRawDivFromAastocks(StockCode)
Call CleanAastocksDiv(StockCode, ws)
End Sub
Private Function ExtractRawDivFromAastocks(StockCode As String)
Dim WsFound As Boolean
Dim i As Integer
WsFound = False
For i = 1 To Sheets.Count():
If Worksheets(i).Name = StockCode Then
WsFound = True
End If
If WsFound = True Then
Exit For
End If
Next i
If WsFound = True Then
Application.DisplayAlerts = False
Worksheets(StockCode).Delete
Application.DisplayAlerts = True
End If
Dim ws As Worksheet
Dim qt As QueryTable
Dim Website As String, Aastock As String
Aastock = "http://www.aastocks.com/en/stocks/analysis/dividend.aspx?symbol="
Website = Aastock & StockCode
Set ws = Sheets.Add(After:=Worksheets(Worksheets.Count()))
ws.Name = StockCode
Set qt = ws.QueryTables.Add( _
Connection:="URL;" & Website, _
Destination:=ws.Range("A1"))
With qt
.RefreshOnFileOpen = True
.Refresh
End With
Set ExtractRawDivFromAastocks = ws
End Function
Private Sub CleanAastocksDiv(StockCode As String, ws As Worksheet)
Dim StartRow As Integer
StartRow = Application.Match("Announce Date", ws.Range("A:A"), 0)
ws.Range("A1:" & _
ws.Cells(StartRow - 1, ws.Columns.Count()).Address).EntireRow.Delete
End Sub
The worksheet indeed has the string value in it, and I have no idea why the match fails. I have tried using the Match function on the sheet itself, it works. Could this be some kind of reference issues? The cell in the sheet doesn't seem to have weird whitespaces. It would be really great if anyone can help me with this:
Public Sub TestMe()
Dim ws As Worksheet: Set ws = Worksheets(1)
Dim StartRow As Variant
StartRow = Application.Match("Announce Date", ws.Range("A:A"), 0)
If IsError(StartRow) Then Exit Sub
If StartRow < 2 Then Exit Sub
ws.Range("A1:A" & StartRow - 1).EntireRow.Delete
End Sub
Declare StartRow as a Variant, because if Announce Date does not exist, it would return an error;
It can be checked with the IsError(StartRow) and exit if it is not the case;
If StartRow < 2 Exit Sub is needed to avoid a possible error if StartRow is 1;

How to clean a workbook and reset the last used cell on all sheets

What is the most reliable and efficient way to trim all sheets of an Excel file of empty formatting?
I consider the Used Range to be all cells with visible data and objects, excluding Comments.
Reliability aspects:
Preserve all visible data (with its formatting) and formulas on all sheets
Preserve objects on all sheets: charts, pivot tables, and list objects (data tables)
Graphics to remain exactly the same position, size and all other properties after the cleanup
Remove all blank cells with old formatting or empty strings that generate a "false" Used Range
These can be cells that were previously used but their data was removed
Invalid formulas, or invisible characters like an untrimmed strings or carriage returns
The solution should also remove all invalid Names (containing the string "#REF!")
Cleanup conditional formatting rules on all sheets removing duplicate rules for the same columns
Clean excess formatting on workbooks and sheets unprotected or protected without a password
A solution with more coverage than the one provided by Microsoft on this page
How to reset the last cell in Excel
The code in the Excel Add-in available in this answer
I'm providing my own attempt to cover the requirements, as a reference
Paste code in a new VBA module and run the first procedure (trimXL)
.
Option Explicit
Private pb01 As Boolean, pb02 As Boolean 'protected attribs of WB & WS
Private ps01 As Boolean, ps02 As Boolean, ps03 As Boolean, ps04 As Boolean
Private ps05 As Boolean, ps06 As Boolean, ps07 As Boolean, ps08 As Boolean
Private ps09 As Boolean, ps10 As Boolean, ps11 As Boolean, ps12 As Boolean
Private ps13 As Boolean, ps14 As Boolean, ps15 As Boolean, ps16 As Boolean
Private isWBProtected As Boolean
Private shapeInfo As Object
Public Function trimXL() As Boolean
Dim wb As Workbook, ws As Worksheet, sCnt As Long, shapesOnWS As Long
Dim lastCel As Range, urAll As Range, thisActWS As Worksheet, isGo As Boolean
Dim lrAll As Long, lcAll As Long, lrDat As Long, lcDat As Long, msg As String
Dim emptyRows As Range, emptyCols As Range, sz1 As Single, sz2 As Single
enableXL False
Set wb = ThisWorkbook
If wbIsReady(wb) Then
Set thisActWS = wb.ActiveSheet
removeInvalidNames
sz1 = FileLen(wb.FullName) / 1024
For Each ws In wb.Worksheets
isGo = IIf(isWBProtected, canUnprotectWs(ws), True)
If isGo Then
Set urAll = ws.UsedRange
lrAll = urAll.Rows.Count + urAll.Row - 1
lcAll = urAll.Columns.Count + urAll.Column - 1
If 0 Then unhideRows ws, urAll
removeXLErrors ws.UsedRange
trimWhiteSpaces ws
Set shapeInfo = newDictionary
shapesOnWS = persistShapesInfo(ws)
trimListObjects ws
Set lastCel = GetMaxCell(urAll)
lrDat = lastCel.Row
lcDat = lastCel.Column
Set emptyRows = ws.Range(ws.Cells(lrDat + 1, 1), ws.Cells(lrAll + 1, 1))
Set emptyCols = ws.Range(ws.Cells(1, lcDat + 1), ws.Cells(1, lcAll + 1))
'setStandardSize ws, emptyRows, emptyCols
If (lrDat = 1 And lcDat = 1) Or (lrAll <> lrDat Or lcAll <> lcDat) Then
If lrDat = 1 And lcDat = 1 And Len(lastCel.Value2) = 0 Then
urAll.EntireRow.Delete
If lrAll <> lrDat Or lcAll <> lcDat Then sCnt = sCnt + 1
Else
If lrAll <> lrDat Or lcAll <> lcDat Then
If lrAll <> lrDat Then emptyRows.EntireRow.Delete
If lcAll <> lcDat Then emptyCols.EntireColumn.Delete
sCnt = sCnt + 1
End If
End If
End If
If shapesOnWS > 0 Then resetShapesInfo ws
'resetConditionalFormatting
If isWBProtected Then protectWs ws
End If
Next
activateFirstCell ws
thisActWS.Activate
If isWBProtected Then protectWB wb
sz2 = FileLen(wb.FullName) / 1024
'wb.Save
Set thisActWS = Nothing
Set shapeInfo = Nothing
End If
enableXL
msg = msg & " File '" & wb.Name & "' cleaned" & vbNewLine & vbNewLine
msg = msg & " Size" & vbTab & "Before: " & vbTab & sz1 & " Kb" & vbNewLine
msg = msg & vbTab & " After: " & vbTab & sz2 & " Kb" & vbNewLine & vbNewLine
msg = msg & vbTab & "Trimmed Sheets" & vbTab & sCnt & vbTab & vbNewLine & vbNewLine
MsgBox msg, vbInformation, " Trim Completed: """ & wb.Name & """"
End Function
'Sheet Functions -----------------------------------------------------------------------
Private Sub activateFirstCell(ByRef ws As Worksheet)
If ws Is Nothing Then Set ws = ThisWorkbook.ActiveSheet
Application.Goto ws.Cells(1), True
'ws.Activate: ws.Cells(1).Activate
End Sub
Private Sub setStandardSize(ByRef ws As Worksheet, ByRef eRows As Range, eCols As Range)
eRows.EntireColumn.ColumnWidth = ws.StandardWidth
eCols.EntireColumn.ColumnWidth = ws.StandardWidth
eRows.EntireRow.RowHeight = ws.StandardHeight
eCols.EntireRow.RowHeight = ws.StandardHeight
End Sub
Public Sub unhideRows(ByRef ws As Worksheet, ByRef rng As Range)
If ws Is Nothing Then Set ws = ThisWorkbook.ActiveSheet
If rng Is Nothing Then Set rng = ws.UsedRange
If Not ws.AutoFilter Is Nothing Then
With ws.AutoFilter
If .FilterMode Then If .Filters.Count = 1 Then rng.AutoFilter
End With
End If
rng.Rows.EntireRow.Hidden = False
rng.Columns.EntireColumn.Hidden = False
End Sub
Public Function GetMaxCell(Optional ByRef rng As Range = Nothing) As Range
'It returns the last cell of range with data, or A1 if Worksheet is empty
Const NONEMPTY As String = "*"
Dim lRow As Range, lCol As Range
If rng Is Nothing Then Set rng = Application.ThisWorkbook.ActiveSheet.UsedRange
If WorksheetFunction.CountA(rng) = 0 Then
Set GetMaxCell = rng.Parent.Cells(1, 1)
Else
With rng
Set lRow = .Cells.Find(What:=NONEMPTY, LookIn:=xlFormulas, _
after:=.Cells(1, 1), _
SearchDirection:=xlPrevious, _
SearchOrder:=xlByRows)
Set lCol = .Cells.Find(What:=NONEMPTY, LookIn:=xlFormulas, _
after:=.Cells(1, 1), _
SearchDirection:=xlPrevious, _
SearchOrder:=xlByColumns)
Set GetMaxCell = .Parent.Cells(lRow.Row, lCol.Column)
End With
End If
End Function
Public Sub trimWhiteSpaces(ByRef ws As Worksheet) 'Blanks ----------------------------
Dim i As Byte
With ws.UsedRange
For i = 1 To 10
.Replace What:=Space(i), Replacement:=vbNullString, LookAt:=xlWhole
Next
.Replace What:=vbTab, Replacement:=vbNullString, LookAt:=xlWhole
.Replace What:=vbCrLf, Replacement:=vbNullString, LookAt:=xlWhole
.Replace What:=vbCr, Replacement:=vbNullString, LookAt:=xlWhole
.Replace What:=vbLf, Replacement:=vbNullString, LookAt:=xlWhole
.Replace What:=vbNewLine, Replacement:=vbNullString, LookAt:=xlWhole
.Replace What:=vbNullChar, Replacement:=vbNullString, LookAt:=xlWhole
.Replace What:=vbBack, Replacement:=vbNullString, LookAt:=xlWhole
.Replace What:=vbFormFeed, Replacement:=vbNullString, LookAt:=xlWhole
.Replace What:=vbVerticalTab, Replacement:=vbNullString, LookAt:=xlWhole
.Replace What:=vbObjectError, Replacement:=vbNullString, LookAt:=xlWhole
End With
End Sub
Public Sub trimListObjects(ByRef ws As Worksheet) 'tables
Dim tbl As ListObject, lastCel As Range, lrDat As Long, lcDat As Long
For Each tbl In ws.ListObjects
With tbl
lcDat = .ListColumns.Count
If .Range.Count <> (.ListRows.Count * lcDat) Then
Set lastCel = GetMaxCell(.Range)
lrDat = lastCel.Row - .Range.Row + 1
If lrDat = 1 Then .Delete Else .Resize .Range.Resize(lrDat + 1, lcDat)
End If
End With
Next
End Sub
Public Sub removeXLErrors(ByRef ur As Range) 'All errors ----------------------------
Dim i As Byte, xlError() As String
On Error Resume Next
ur.SpecialCells(xlCellTypeFormulas, xlErrors).Clear
If 0 Then
ReDim xlError(6)
xlError(0) = "#DIV/0!" 'Excel.XlCVError.xlErrDiv0 = 2007 => #DIV/0!
xlError(1) = "#N/A" 'Excel.XlCVError.xlErrNA = 2042 => #N/A
xlError(2) = "#NAME?" 'Excel.XlCVError.xlErrName = 2029 => #NAME?
xlError(3) = "#NULL" 'Excel.XlCVError.xlErrNull = 2000 => #NULL
xlError(4) = "#NUM!" 'Excel.XlCVError.xlErrNum = 2036 => #NUM!
xlError(5) = "#REF" 'Excel.XlCVError.xlErrRef = 2023 => #REF
xlError(6) = "#VALUE!" 'Excel.XlCVError.xlErrValue = 2015 => #VALUE!
'VBA.Conversion.CVErr 1 / 0
'Public Const EXCEL_ERROR As String = "#N/A"
For i = 0 To 6
ur.Replace What:=xlError(i), Replacement:=vbNullString, LookAt:=xlWhole
Next
End If
End Sub
Public Sub resetConditionalFormatting(Optional ByRef rng As Range = Nothing)
Const F_ROW As Long = 2
Dim ws As Worksheet, ur As Range, maxCol As Long, maxRow As Long, thisCol As Long
Dim colRng As Range, fcCol As Range, fcCount As Long, fcAdr As String
If rng Is Nothing Then Set rng = Application.ThisWorkbook.ActiveSheet.UsedRange
Set ws = ThisWorkbook.ActiveSheet
Set ur = ws.UsedRange
maxRow = ur.Rows.Count
maxCol = ur.Columns.Count
For Each colRng In ws.Columns
If colRng.Column > maxCol Then Exit For
thisCol = thisCol + 1
Set fcCol = ws.Range(ws.Cells(F_ROW, thisCol), ws.Cells(maxRow, thisCol))
With colRng.FormatConditions
If .Count > 0 Then
fcCount = 1
fcAdr = .Item(fcCount).AppliesTo.Address
While fcCount <= .Count
If .Item(fcCount).AppliesTo.Address = fcAdr Then
.Item(fcCount).ModifyAppliesToRange fcCol
fcCount = fcCount + 1
Else
On Error Resume Next
.Item(fcCount).Delete
End If
Wend
End If
End With
Next
End Sub
'Workbook Functions --------------------------------------------------------------------
Public Sub removeInvalidNames()
Dim itm As Name
With ThisWorkbook
If .Names.Count > 0 Then
On Error Resume Next
Err.Clear
For Each itm In .Names
If InStr(itm.RefersTo, "#REF!") > 0 Then itm.Delete
Next
End If
'xlResetSettings
.Saved = True
End With
End Sub
'Shape Functions -----------------------------------------------------------------------
Public Function newDictionary(Optional ByRef dictObj As Object, _
Optional ByVal caseSensitive As Boolean = False) As Object
If Not dictObj Is Nothing Then Set dictObj = Nothing
'Set dictionaryObject = New Dictionary
Set dictObj = CreateObject("Scripting.Dictionary")
dictObj.CompareMode = IIf(caseSensitive, vbBinaryCompare, vbTextCompare)
Set newDictionary = dictObj
End Function
Private Function persistShapesInfo(ByRef ws As Worksheet) As Long
Dim sh As Shape, totalShapes As Long
For Each sh In ws.Shapes
If Not sh.Type = msoComment Then
With sh
shapeInfo(.Name) = .Top & "|" & .Left & "|" & .Height & "|" & .Width
shapeInfo(.Name) = shapeInfo(.Name) & "|" & .Placement
.Placement = xlFreeFloating
End With
totalShapes = totalShapes + 1
End If
Next
persistShapesInfo = totalShapes
End Function
Private Sub resetShapesInfo(ByRef ws As Worksheet)
Dim sh As Variant, shInfo As Variant
For Each sh In shapeInfo
shInfo = Split(shapeInfo(sh), "|")
With ws.Shapes(sh)
.Top = shInfo(0)
.Left = shInfo(1)
.Height = shInfo(2)
.Width = shInfo(3)
.Placement = shInfo(4)
End With
Next
End Sub
'Excel Functions -----------------------------------------------------------------------
Public Sub enableXL(Optional ByVal opt As Boolean = True)
With Application
.Calculation = IIf(opt, xlCalculationAutomatic, xlCalculationManual)
.DisplayAlerts = opt
.DisplayStatusBar = opt
.EnableAnimations = opt
.EnableEvents = opt
.ScreenUpdating = opt
End With
enableWS , opt
End Sub
Public Sub enableWS(Optional ByVal ws As Worksheet, Optional ByVal opt As Boolean =True)
If ws Is Nothing Then
For Each ws In Application.ActiveWorkbook.Sheets
setWS ws, opt
Next
Else
setWS ws, opt
End If
End Sub
Private Sub setWS(ByVal ws As Worksheet, Optional ByVal opt As Boolean = True)
With ws
.DisplayPageBreaks = False
.EnableCalculation = opt
.EnableFormatConditionsCalculation = opt
.EnablePivotTable = opt
End With
End Sub
Public Sub xlResetSettings() 'default Excel settings
With Application
.Calculation = xlCalculationAutomatic
.DisplayAlerts = True
.DisplayStatusBar = True
.EnableAnimations = False
.EnableEvents = True
.ScreenUpdating = True
Dim sh As Worksheet
For Each sh In Application.ActiveWorkbook.Sheets
With sh
.DisplayPageBreaks = False
.EnableCalculation = True
.EnableFormatConditionsCalculation = True
.EnablePivotTable = True
End With
Next
End With
End Sub
'Protection Functions ------------------------------------------------------------------
Private Function wbIsReady(ByRef wb As Workbook) As Boolean
isWBProtected = wbIsProtected(wb)
wbIsReady = canUnprotectWb(wb)
End Function
Private Function wbIsProtected(ByRef wb As Workbook) As Boolean
Dim hasPassword As Boolean, ws As Worksheet
If Not wb.ReadOnly Then
pb01 = wb.ProtectStructure
pb02 = wb.ProtectWindows
hasPassword = pb01 Or pb02
For Each ws In wb.Worksheets
hasPassword = hasPassword Or wsIsProtected(ws)
If hasPassword Then Exit For
Next
End If
wbIsProtected = hasPassword
End Function
Private Function wsIsProtected(ByRef ws As Worksheet) As Boolean
With ws
ps01 = .ProtectContents
ps02 = .ProtectDrawingObjects
With .Protection
ps03 = .AllowDeletingColumns
ps04 = .AllowDeletingRows
ps05 = .AllowEditRanges.Count > 0
ps06 = .AllowFiltering
ps07 = .AllowFormattingCells
ps08 = .AllowFormattingColumns:
ps09 = .AllowFormattingRows
ps10 = .AllowInsertingColumns
ps11 = .AllowInsertingHyperlinks
ps12 = .AllowInsertingRows
ps13 = .AllowSorting
ps14 = .AllowUsingPivotTables
End With
ps15 = .ProtectionMode
ps16 = .ProtectScenarios
End With
wsIsProtected = ps01 Or ps02 Or ps03 Or ps04 Or ps05 Or ps06 Or ps07 Or ps08 Or _
ps09 Or ps10 Or ps11 Or ps12 Or ps13 Or ps14 Or ps15 Or ps16
End Function
Private Sub protectWB(ByRef wb As Workbook)
If Not wb.ReadOnly Then wb.Protect vbNullString, pb01, pb02
End Sub
Private Sub protectWs(ByRef ws As Worksheet)
ws.Protect vbNullString, ps02, ps01, ps16, True, ps07, ps08, _
ps09, ps10, ps12, ps11, ps03, ps04, ps13, ps06, ps14
End Sub
Private Function canUnprotectWb(ByRef wb As Workbook) As Boolean
Dim hasPassword As Boolean
hasPassword = True
On Error Resume Next
wb.Unprotect vbNullString
If Err.Number = 1004 Then
Err.Clear
hasPassword = True
End If
canUnprotectWb = hasPassword
End Function
Private Function canUnprotectWs(ByRef ws As Worksheet) As Boolean
Dim hasPassword As Boolean
hasPassword = True
On Error Resume Next
ws.Unprotect vbNullString
If Err.Number = 1004 Then
Err.Clear
hasPassword = False
End If
canUnprotectWs = hasPassword
End Function
More details about cleaning up conditional formatting rules in this SO answer

Resources