0

I created an audit trail for any changes that occur on "Profile" sheet. Any changes that are made on the Profile sheet are recorded in another sheet - ChangeHistory.

However, I noticed that changes are only recorded when I change the contents of the cell manually. Changes that occur from external links from other sheets are not recorded.

Can you please help and suggest any amendments to this code? I am not an expert in VBA so would appreciated your valuable assistance.

This is my current code: Profile code

Thanks in advance

 Dim PreviousValue

Private Sub Worksheet_Change(ByVal Target As Range)
Dim AuditRecord As Range
' This is our change history ...
 Set AuditRecord = Worksheets("ChangeHistory").Range("A4:B65000")
 r = 0
 ' Now find the end of the Change History to start appending to ...
 Do
    r = r + 1
 Loop Until IsEmpty(AuditRecord.Cells(r, 1))
 ' For each cell modified ...
 For Each c In Target
   Value = c.Value
   Row = c.Row
   ' ... update Change History with value and time stamp of modification
   AuditRecord.Cells(r, 1) = Worksheets("Profile").Cells(Row, 4)
   AuditRecord.Cells(r, 2) = Value
   AuditRecord.Cells(r, 3).Value = PreviousValue
   AuditRecord.Cells(r, 5).NumberFormat = "dd mm yyyy    hh:mm:ss"
   AuditRecord.Cells(r, 5).Value = Now
   AuditRecord.Cells(r, 4).Value = Application.UserName

   r = r + 1

 Next

End Sub

Private Sub Worksheet_SelectionChange(ByVal Target As Range)
    PreviousValue = Target.Value
End Sub
Community
  • 1
  • 1
  • Not sure but this maybe because the changes by formula is taking place in more than 1 rows or columns concurrently if they are dependent on other cells hence the first or second line of your code exits the sub before doing anything. If you could copy and paste the code in your question instead of attaching the picture that would be great and if you can add the picture of the worksheet showing the formulas that would be enough to confirm whether that's the reason or not. – Stupid_Intern Feb 13 '16 at 18:54
  • I removed those 2 lines but still not working.... I am not sure what I am doing wrong. Ok let me paste. – Stefano Lazze' Feb 13 '16 at 19:01
  • When you say "external links to other sheets" do you mean within the same workbook or a different workbook? – ARich Feb 13 '16 at 19:35
  • You need to use the Calculate event for cells where values are from a formula or link – Tim Williams Feb 13 '16 at 19:37
  • The external links / formulas are located in the Sheet 2 in the same workbook. – Stefano Lazze' Feb 13 '16 at 20:10
  • Thanks Tim - can you please suggest a code for my macro? – Stefano Lazze' Feb 13 '16 at 20:13

1 Answers1

0

There may be a better way to do this, but this is what came to my mind:

In Profile Sheet Module

Option Explicit
Public dArr As Variant
Private Sub Worksheet_Calculate()
    Dim nArr As Variant
    Dim auditRecord As Range
    Dim i As Long
    Dim j As Long

    nArr = Me.UsedRange

    'Look for changes to the used range
    For i = 1 To UBound(dArr, 2)
        For j = 1 To UBound(dArr, 1)
            If nArr(j, i) <> dArr(j, i) Then
                'write to range
                If Not Write_Change(dArr(j, i), nArr(j, i), Me.Cells(j, i).Address) Then
                    MsgBox "The change was not recorded.", vbInformation
                End If
            End If
        Next j
    Next i

    Erase nArr, dArr
    dArr = Me.UsedRange
End Sub
Private Sub Worksheet_Change(ByVal target As Range)
    Dim Cell As Range
    Dim oldValue As Variant

    For Each Cell In target
        On Error Resume Next
        oldValue = vbNullString
        oldValue = dArr(Cell.Row, Cell.Column)
        On Error GoTo 0
        If oldValue <> Cell.Value Then
            If Not Write_Change(oldValue, Cell.Value, Cell.Address) Then
                MsgBox "The change was not recorded.", vbInformation
            End If
        End If
    Next Cell

    On Error Resume Next
    Erase dArr
    On Error GoTo 0

    dArr = Me.UsedRange
End Sub
Private Sub Worksheet_SelectionChange(ByVal target As Range)
    dArr = Me.UsedRange
End Sub
Public Function Write_Change(oldValue, newValue, cellAddress As String) As Boolean
    Dim auditRecord As Range
    On Error GoTo errHandler
    Set auditRecord = Sheets("ChangeHistory").Range("A:A").Find("*", searchdirection:=xlPrevious).Offset(1, 0)
    With auditRecord
        .Value = cellAddress 'Address of change
        .Offset(0, 1).Value = newValue 'new value
        .Offset(0, 2).Value = oldValue 'previous value
        .Offset(0, 3).NumberFormat = "dd mm yyyy hh:mm:ss"
        .Offset(0, 3).Value = Now 'time of change
        .Offset(0, 4).Value = Application.UserName 'user who made change
        .Offset(0, 5).Value = Me.Range(Split(cellAddress, "$")(1) & 1).Value 'header column value
        .Offset(0, 6).Value = Me.Range("D" & Split(cellAddress, "$")(2)).Value 'header row value
    End With
    Write_Change = True
    Exit Function
errHandler:
    Write_Change = False
    Debug.Print "Error number: " & Err.Number
    Debug.Print "Error descr: " & Err.Description
End Function

In ThisWorkbook Module

Private Sub Workbook_Open()
    dArr = Sheets("Profile").UsedRange
End Sub

Explanation

The key to this solution is the public array dArr. The array will hold a static list of values from your sheet in memory and will update anytime you make a different selection on your worksheet using the SelectionChange event.

We use the Calculate event to handle times where formulas update cells' contents. To do this, we store the new values on the sheet in an array nArr and then loop through the array and compare the values against the static values in dArr.

Pasted values or manually added values will be captured using the Change event.

For this to work, dArr has to be filled anytime the workbook is opened by a user. To do this, you'll have to add this to the Workbook_Open event as shown above.

Other Notes

As noted here by Tim, there are times when global variables can lose their values by way of unhandled errors, so make sure to include good error handling in this project if you choose to use this solution.

This only writes value changes. Formatting changes will not be captured using this method.

Will not work if there's only one value on the Profile sheet. Could be modified to work like that if needed though.

I did some minor testing of the above code in 64-bit , but you may want to do more extensive testing to ensure that all changes are captured via the above code.

Community
  • 1
  • 1
ARich
  • 3,230
  • 5
  • 30
  • 56
  • Thanks Rich for this. I tried this morning but the macro just dont work. When I change a cell, nothing is happening. What am I doing wrong? I copied your code exactly the same..... Moreover, when a formula is changed, the following error is coming up: Compile error - End If without Block If – Stefano Lazze' Feb 14 '16 at 13:19
  • @StefanoLazze' I've made a few changes to the code which should make it more robust. Let me know if it still doesn't work for you. – ARich Feb 14 '16 at 21:07
  • Oh great Rich - you did a wonderful job !! Now it's working fine. Just one last question regarding the cell address - if instead of the cell address (example: $G$19) it picks up the heading in that row like in what in my original code (= Worksheets("Profile").Cells(Row, 4)), what would you suggest? That will be perfect as it will help identify the subject of the change without looking what the cell in question is. Thanks a million – Stefano Lazze' Feb 15 '16 at 06:56
  • @StefanoLazze' If your header row is always the same, you could hardcode the row and add a column to the `Write_Change` function. For example, if your header is in row 3, you could get the value via `.Offset(0, 5).Value = Me.range(split(cellAddress,"$")(1) & 3).value`. – ARich Feb 15 '16 at 14:27
  • I apologise Rich but I meant to say header column not row. What would the code be in that case? Promise that is my last query and I am already grateful for your precious input. – Stefano Lazze' Feb 15 '16 at 20:47
  • You would use this instead: `.Offset(0, 5).Value = Me.Range("D" & Split(cellAddress, "$")(2)).Value `. – ARich Feb 15 '16 at 22:57
  • It worked fine until I used a linked cell and 2 issues came up. The first issue is that the link is literally showing in the cell. Example the word "=Sheet1!A1 is showing in the cell of the Profile sheet instead of showing the value of that link. However, the value of the cell is recorded in the Changehistory. The second issue that although the change of cell value is recorded in the ChangeHistory, any subsequent changes ( links only) are being overwritten in the same line instead of recording the change on the subsequent line. P.S - Only the new value is recorded in the same line – Stefano Lazze' Feb 16 '16 at 07:27
  • Problem solved - the format of the cell was Text instead of General. All works fine now. – Stefano Lazze' Feb 16 '16 at 17:06