Refreshing pivot tables on locked sheet with data from sheet locked by diff password - vba

I'm having a number of issues designing a VBA that will allow me to refresh pivot tables in one sheet with data from another sheet.
There are five sheets that we want our field teams to access, enter data into and edit (as a procurement tracker). These five sheets are to be protected by one password (say, Minneapolis). Data from these five sheets feed into pivot tables on the next six sheets and we would like these protected by a different password that only HQ knows (say, jambalaya). All of these sheets (i.e. those with pivot tables) tab names start with LOG. Following these eleven sheets are two sheets for guidelines. We're also working in a binary format but have also tried the macro-enabled format (although not necessarily with all the different iterations of code). We preferred binary because it makes for a smaller file size and this is important for, for ex., teams in South Sudan with slow and limited internet access but need to be able to send this back and forth to HQ.
Our problem is that in order to get the pivot tables to refresh, we have to unprotect, refresh and reprotect the sheets and we're meeting problems here. I've found a number of examples of solutions for this on the internet but we're having trouble with things like autofilters not working properly, some sheets being left unlocked, or fixing these but not being able to refresh the pivot tables.
Sub UnprotectRefreshAll()
Dim ws As Worksheet
On Error Resume Next
For Each ws In ActiveWorkbook.Worksheets
ws.Unprotect Password:="jambalaya"
Next ws
ActiveWorkbook.RefreshAll
For Each ws In ActiveWorkbook.Worksheets
ws.Protect Password:="jambalaya", _
AllowUsingPivotTables:=True
ActiveSheet.Protection.AllowFiltering = False
ActiveSheet.Protect AllowFiltering:=True
Next ws
Application.Calculate
End Sub
If I run this code in the VBA on the page with data that feeds into the pivot tables (called PROCUREMENT TRACKER), the autofilters work and other pages are properly locked, and this page is locked but without a password. If I run it from a button linked to that macro on the PROCUREMENT TRACKER page, the autofilters on the page don't work (and for some reason it lands on the second to last page, one of the guidance pages). There is also still the issue of wanting to have a different password for this, and if we start it with Minneapolis, it'll revert to jambalaya anyway. I was hoping that it would be possible to have it only refresh pages with the password jambalaya (and have tried a couple different codes for that), but it still changes the page it's run on even if that password is Minneapolis. I've also seen some options where you prompt the user to enter in the password but we DONT want to give the teams access to the password jambalaya, ONLY Minneapolis.
Sub LetsTryThis()
Dim I As Integer
On Error Resume Next
For Each Worksheet In ActiveWorkbook.Worksheets
If Current.Name Like "LOG*" Then
Worksheet.Unprotect Password:="jambalaya"
End If
Next
ActiveWorkbook.RefreshAll
For Each Worksheet In ActiveWorkbook.Worksheets
If Worksheet.Name Like "LOG*" Then
Worksheet.Protect Password:="jambalaya", _
AllowUsingPivotTables:=True
ActiveSheet.Protection.AllowFiltering = False
ActiveSheet.Protect AllowFiltering:=True
End If
Next
End Sub
Because the pages with pivot tables all start with LOG, I tried doing this code above as well, hoping it would only refresh the sheets with pivot tables. If I run this macro in the VBA from the PROCUREMENT TRACKER sheet, there is an issue of leaving the PROCUREMENT TRACKER sheet locked but without a password, and then if I do it from the button on the PROCUREMENT TRACKER sheet, it lands on the last LOG sheet (i.e. the ones with pivot tables), leaving that page protected without a password and it leaves the PROCUREMENT TRACKER sheet (again, the one with the button and the data used in the pivot tables) completely unprotected.
Ideal outcome:
(1) Have one password (such as Minneapolis) for sheets 1-4 where the field team can enter data into
(2) Have a second password (such as jambalaya) for sheets 5-12 with the pivot tables (and guides) that only HQ knows. These pivot tables pull data from sheet 3 (PROCUREMENT TRACKER).
(3) Find a way to refresh the pivot tables on tabs 5-10 without leaving anything unprotected/protected without password in the process and without altering features such as the use of autofilters.

The biggest issue you are facing here is the use of ActiveSheet. Stay away from that unless absolutely needed. It's causing behavior that you feel like you can't trace - as it often does.
Do the following
Manually protect Minneapolis sheets as needed. Then never do anything with them :)
Use this code for refresh.
Code
Option Explicit
Sub RefreshData()
ProtectPivotSheets False
ThisWorkbook.RefreshAll
ProtectPivotSheets True
End Sub
Sub ProtectPivotSheets(switch As Boolean)
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
If ws.Name Like "LOG*" Then
If switch Then
ws.Protect Password:="jambalaya", AllowUsingPivotTables:=True
Else
ws.Unprotect "jambalaya"
End If
End If
Next
End Sub

Related

Sheet inaccessible to macro: Error 1004: Application/Object Defined Error

This is happening in several of my macros, but this is the one in from of me:
Private Sub resettool()
'''resets step 2 input and user input on MPP tabs
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Call showsheets 'makes all of these sheets .Visible = True
'clear data from lookups and data corrals
Sheets("Media by Copy Lookup").Range("b1",Range("b1").End(xlToRight).End(xlDown)).ClearContents
Sheets("Total Media Lookup").Range("d1",Range("d1").End(xlToRight).End(xlDown)).ClearContents
Sheets("Total Media Lookup").Range("b2:c100").ClearContents
Sheets("Media by Copy Data").Range("a1",Range("a1").End(xlToRight).End(xlDown)).ClearContents
'etc etc
End Sub
It continues with similar data-clearing lines for a while. This started happening when I took someone else's code and cleaned it by removing the .Select usages as people on here have suggested. It seems that the macro isn't able to access the sheets I'm referencing, because a line runs successfully if I step into the code, manually select the referenced sheet, and then hit go (but then of course I get the same error when I try to edit another sheet).
Any ideas why the macro wouldn't be able to access these sheets unless I explicitly activate/select them? The sheets are all visible, so that shouldn't be the problem.
P.S. I've seen the guide on using .Rows.Count).End(xlUp) instead of End(xlDown) to find the bottom of my data and will implement that soon, but this issue is occurring no matter how I define the range; it's about the sheet.

VBA Lookup in Another Workbook

I am having an vba problem with Excel. So I have this workbook, "Book Tool - Updated Feb. 2017.xlsb", that I am currently updating and will distribute to about 10 team members to use to keep track of their work. What I am trying to do is lookup data from an outside document, "Team Data", put that in Column DE of the "Book Tool - Updated Feb. 2017.xlsb" file. So I wrote the below code, where when the team member pushes a button, it opens up the lookup file, looks for the data in Column A of the "SICcode" sheet of that external file, matches it with Column B of the "Book Sheet" of the "Book Tool" file, and returns the value in Column D of the lookup file. It runs for the length of the "Book Sheet", closes the external file, and you get a popup that the data add is done.
Now when I did this code myself, it works great. Automatically opened the external document, did the lookups, returned the correct value, closes the external document, the pop up. So I sent the file with the macro to my manager to play around with before giving it to the rest of my team, but the macro does not work. When the macro runs, the external document opens, it seems like it is running through the lookups, closes the external file, and the pop up appears, but there is no value in the DE column, nor is there the lookup formula there. My manager didn't change the name of the Tool document, he didn't mess with any code. He emailed it back to me and with that copy the formula isn't working, but I checked it with my master copy formula and even though it's the same, the macro will not populate the data.
We have to keep the external data in a separate file, because otherwise the tool with the lookup data is over 2MB and takes forever to run or crashes.
Is there something about emailing the tool back and forth that messes with the file, or is there some formatting issue I need to look into that causes it not to work? With my master copy on my computer, the code always works regardless if I work in a virtual desktop, have it in a different folder, whatever.
I am just okay with vba, I don't know all of the technicalities of this process, so maybe I am overlooking some flaw with how I've set it up or limitations Excel has. Any guidance or help would be appreciated.
Sub AddData()
On Error Resume Next
'Open External Data Source
Workbooks.Open Filename:= _
"W:\USB\Reporting\Book Tool\Attachments\Team Data.xls"
'View sheet where data will go into
Windows("Book Tool - Updated Feb. 2017.xlsb").Activate
'Gets last row of Tool sheet
Sheets("Book").Select
lastrow = Cells(Rows.Count, "B").End(xlUp).Row
'Lookup in External File
Sheets("Book").Select
Range("DE2:DE" & lastrow).FormulaR1C1 = "=VLOOKUP(RC[-108],'[Team Data.xls]SICcode'!C[-109]:C[-104],5,FALSE)"
'Close External Data File
Windows("Team Data.xls").Activate
ThisWorkbook.Saved = True
Application.DisplayAlerts = False
ActiveWindow.Close
MsgBox "Data Add Done"
End Sub
Be sure to properly qualify your statements, and also it would be wise to assign the appropriate workbook to a variable. See the modified code below:
Sub AddData()
On Error Resume Next ' I also suggest removing this since it wont warn you on an error.
Dim wb as Workbook
Dim wbExternal as Workbook
Dim ws as Worksheet
Dim wsExternal as Worksheet
'Open External Data Source
Set wbExternal = Workbooks.Open Filename:= _
"W:\USB\Reporting\Book Tool\Attachments\Team Data.xls"
' Depending on the location of your file, you may run into issues with workbook.Open
' If this does become an issue, I tend to use Workbook.FollowHyperlink()
'View sheet where data will go into
' Windows("Book Tool - Updated Feb. 2017.xlsb").Activate
' Set wb = ActiveWorkbook
' As noted by Shai Rado, do this instead:
Se wb = Workbooks("Book Tool - Updated Feb. 2017.xlsb")
' Or if the workbook running the code is book tool
' Set wb = ThisWorkbook
'Gets last row of Tool sheet
Set ws = wb.Sheets("Book")
lastrow = ws.Cells(ws.Rows.Count, "B").End(xlUp).Row
'Lookup in External File
Set wsExternal = wbExternal.Sheets("Book")
wsExternal.Range("DE2:DE" & lastrow).FormulaR1C1 = "=VLOOKUP(RC[-108],'[Team Data.xls]SICcode'!C[-109]:C[-104],5,FALSE)"
'Close External Data File
ThisWorkbook.Saved = True
Application.DisplayAlerts = False
Windows("Team Data.xls").Close
MsgBox "Data Add Done"
End Sub
I would also recommend browsing through SO for tips on avoiding .Select and .Activate as this can make your code unreliable and in some cases can slow down your code significantly.
Lastly, if performance is a concern you may want to look into loading your lookup values into arrays and finding the corresponding values this way. It will completely depend on what kind of data you are working with. I had a workbook using filldown vlookups that went from running in a matter of 5-10 minutes or more to consistently running in less than 20 seconds by replacing VLOOKUPS with for looping arrays.

Row level security in Excel - what approach should I use:

Morning folks,
Over the last couple of days I have been trying to find a way of implementing "pseudo" row level security in Excel using VBA macros.
What I require is to filter all of my Excel table based on the Windows user credentials. The problem is that I am not sure how to tackle this issue and this is the only idea I have so far:
1.Derive the NT account and store it in namedrange
2. Use autofilter based on NamedRange for each table in my spreadsheet
3. Delete / Hide the unnecessary rows ?
Example:
Perhaps, there are other ways which are more suitable for my scenario, however I havent been able to find them.
If you could point me into the right direction, I would appreciate it.
Thanks
This can be achieved by pasting the following into the ThisWorkBook Module in your specific WorkBook VBA project. Just remember to save the WorkBook as a .dotm
As mentioned by J.Chomel bypassing WorkBook passwords is not the hardest thing to do and you could find a macro to do that on the net.
Obviously you will need to password protect your VBA project as well to prevent users from accessing the WorkBook password from there.
Private Sub Workbook_Open()
Dim CurrentUserName As String
CurrentUserName = Environ("Username")
Dim WorkBookPassword As String
'Replace password with your desired password
WorkBookPassword = "password"
Dim DataWS1 As Worksheet
'Replace Sheet1 with your specific sheet name
Set DataWS1 = ThisWorkbook.Sheets("Sheet1")
With DataWS1
.Unprotect WorkBookPassword
If .AutoFilterMode Then .AutoFilterMode = False
.UsedRange.AutoFilter Field:=3, Criteria1:=CurrentUserName
.Protect Contents:=True, _
AllowFiltering:=False, _
UserInterfaceOnly:=True, _
Password:=WorkBookPassword
End With
End Sub
To implement row level security in Excel, you should avoid retrieving the data in Excel in the first place. Because if you use vba to filter, it will easily be bypassed by some users. So it could not be called security.

How to copy a row of data from one sheet to another based on cell data

I'm using excel for a project management tool. Right now I have a large table with a series of drop down menus for users to select who is responsible for the project, due dates, etc.
What I'd like to be able to do is copy over the full row (all project data) to each user individual page.
Say "Fred" creates a project and assigns it to "Tom" on the master page, I want Tom's sheet to autopopulate with the project details so instead of scrolling through the list to find him own name, he can click on his tab at the bottom of the master list and see all of his projects.
I've read through several questions somewhat similar and have yet to find anything that works.
For reference, the names are all in column F and there are currently 12 names that user can select from to assign a project; therefore, there are also 12 blank pages/sheets tabbed at the bottom next to the master page tab.
Thanks for any help you may be able to offer!
This macro might work for you - you need to put the right column number in for the filter...
It has to be put in the "Workbook" code (not a module - otherwise the event handling won't work).
It will run every time you change sheets - and will copy the most up to date information into the project leader's tab. If you have "other" sheets that you don't want this to act upon, you must catch the names of those sheets in the same line where code currently tests for "Master".
Changes needed to adapt to your specific situation - I'm sure you can handle it.
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Dim prjLeader As String
Dim wholeRange As Range
prjLeader = Sh.Name
If prjLeader = "Master" Then Exit Sub ' whatever the name of the master sheet is...
On Error GoTo outtahere
Application.EnableEvents = False
Application.ScreenUpdating = False
Sh.UsedRange.Clear ' remove "old" information
ActiveWorkbook.Sheets("Master").Activate
Set wholeRange = ActiveSheet.UsedRange
wholeRange.AutoFilter Field:=6, Criteria1:=prjLeader ' use the field where proj leader name is (F = 6)
wholeRange.Copy ' copy all filtered data
Sh.Activate ' and paste it in project leader's sheet
Range("A1").PasteSpecial
outtahere: ' handle errors - back to sheet, turn events back on
Sh.Activate
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub

How to capture worksheet being added through Copy/Paste in Excel VBA

I am trying to capture worksheets being copied in to a workbook from another workbook.
Workbook_NewSheet event does not trigger when the sheets are copied from another workbook.
It is triggered only if the user manually inserts them through (Insert->Worksheet menu option), or when you add a new sheet through VBA as ThisWorkbook.Worksheets.Add.
What I am trying to capture is basically a Paste operation which is resulting in a new sheet.
This might be from any of the below user actions:
User copies an existing sheet by dragging it holding Control Key (which adds a new sheet)
User copies sheet/s from another workbook
user moved sheets from another workbook
or any of the below VBA code:
SourceWorkbook.Sheets(“SourceSheet”).Copy Before:=TargetWorkbook.worksheets(“SheetNameIn Target”) 'copy across workbook'
SourceWorkbook.Sheets(“SourceSheet”).Move Before:=TargetWorkbook.worksheets(“SheetNameIn Target”) 'move across workbook'
ThisWorkbook. Sheets(“SheetName”).Copy 'copy within workbook'
If you know any way of capturing this action/macro results within VBA that would be greatly helpful.
Please note that I do not want to avoid such an user action (so i do not want to secure the workbook) but I want to handle the pasted sheet programatically to verify the data, and if the similar sheet already exists then update the existing sheet rather than having same data in two sheets.
When a sheet is copied, its name will always end with "(2)", or at least ")". You could check on that like this
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
If Sh.Name Like "*(2)" Then
Application.DisplayAlerts = False
Sh.Delete
Application.DisplayAlerts = True
End If
End Sub
The SheetActivate event will fire under all of those circumstances. Obviously it will fire under a lot of other circumstances too. This sounds like a royal pain, but you could maintain your own collection of worksheets and compare your collection to the ThisWorkbook.Sheets collection to see if something was added/deleted.
If you're trying to prevent it, you might consider protecting the workbook structure instead of doing it in code.
The way I have it implimented is
Private Sub Workbook_WindowActivate(ByVal Wn As Window)
ToggleMenuOptions False, 848, 889
End Sub
Private Sub Workbook_WindowDeactivate(ByVal Wn As Window)
ToggleMenuOptions True, 847, 848, 889
End Sub
Public Function ToggleMenuOptions(bToggle As Boolean, ParamArray ControlID() As Variant) As Boolean
'848 Move or Copy Sheet...
'889 Rename Sheet
'847 Delete Sheet
On Error GoTo lblError
Dim oControl As CommandBarControl, oControls As CommandBarControls, iControl As Integer
If IsMissing(ControlID) Then
ToggleMenuOptions = False
Exit Function
End If
For iControl = LBound(ControlID) To UBound(ControlID)
For Each oControl In Application.CommandBars.FindControls(ID:=ControlID(iControl))
oControl.Enabled = bToggle
Next
Next
ToggleMenuOptions = True
Exit Function
lblError:
If Err.Number Then
ToggleMenuOptions = False
Exit Function
End If
End Function
Private Sub Workbook_NewSheet(ByVal Sh As Object)
MsgBox "Please use Add New Project option in custom Toolbar to add new sheets!!", vbExclamation, "Not Supported"
Application.DisplayAlerts = False
Sh.Delete
Application.DisplayAlerts = True
End Sub
So my users wont be able to rename, add or delete sheets. This is working pretty well for now.
The only way I can think of doing this without maintaining a separate sheets collection is to maintain a static array of sheet names (or sheet codenames) and compare this to the actual sheets in the workbook each time the SheetActivate event fires to detect any additions. If you don't want to/can't keep the list in an array you could always use a hidden sheet to store the list. Whether this is any more or less of a pain than maintaining a separate collection is debatable :)
I am working on something similar but cannot block any of the user menu actions. I have sheets whose type are important - each sheet is either a Master or Slave - each Master sheet sums over the Slave sheets beneath it and I need to keep these formula clean.
Rather than maintain a list of sheets in an extra hidden sheet, I am defining 2 hidden names on each sheet recording the offset of the index of the Sheet to its linked Master sheet, and a reference to the linked Master sheet. So if my sheet is (say) +2 tabs from its Master sheet, then on Sheet activate/deactivate (not sure which of these is better to track at this stage) this offset will have changed if anything gets inserted, deleted or moved. This covers most or all of the events that would arise from moving or copying sheets.
If the sheet has been moved, I cycle through the workbook and calculate new Master/Slave index references for every sheet.
Will post code when I get this reasonably stable but it seems like a scheme that would work in a wide variety of circumstances.