Adding security to my workbook internally using VBA - vba

For testing purposes lets assume the code to unlock the workbook is 1.
I want an input box to show up on opening, if the user puts in an incorrect code/Or selects cancel on the input box then the workbook closes.
Private Sub Workbook_Open()
ActiveSheet.Range("A1").Activate
Code = Application.InputBox("Enter School Code", CancelCode)
If Code < 0 > 1 Or False Then
Code = Int(1)
MsgBox ("Unrecognised Code")
Application.Workbooks("CODING.xlsm").Close 'False
End If
End Sub
At the moment it appears but runs if any number is entered and also closes the input box and allows access if cancel is clicked.

Try this:
Private Sub Workbook_Open()
Dim Code As Variant
Code = Application.InputBox("Enter School Code", CancelCode)
If Not Code = 1 Then
MsgBox ("Unrecognised Code")
ThisWorkbook.Close SaveChanges:=False
End If
End Sub
Just tested it and it works for me.
I should add, however, that this is extremely low-level security, which could be bypassed by anyone with even rudimentary knowledge of VBA. You simply have to set Application.EnableEvents = False in another workbook (or in the VBA Immediate window) before opening up your workbook, and the Workbook_Open event will not run at all.

Related

Running vba code whenever a workbook is opened

I'm writing vba which manipulates data within a worksheet however I'm trying to make it run whenever a workbook is opened.
The problem I'm having is that due to the workbook (that the code needs to run on) is different/new every time, I need the auto_open code to be within a personal macro workbook.
Sub Auto_Open()
Dim bookname As String
Dim checkbook As String
Dim Workbook As Workbook
For Each Workbook In Application.Workbooks
bookname = Workbook.Name
checkbook = Left(bookname, 3)
If checkbook = "EDN" Then
Data_generator
Application.DisplayAlerts = False
ThisWorkbook.Save
Application.DisplayAlerts = True
Application.Quit
Else
End If
Next Workbook
End Sub
When this code runs it checks all open workbooks and sees if the first 3 letters of it are 'EDN', if it is then run the public sub called 'Data_generator', save it and quit. If it isn't check the next open workbook, etc.
When a file is opened from windows explorer, excel launches (with both the desired workbook and the personal macro workbook) however because excel opens the personal macro workbook first and runs the code before opening the desired workbook it doesn't find a workbook called 'EDN'.
If the above code is ran after both workbooks have opened then the code works as intended and cycles through each open workbook to see if there's one called 'EDN' (this was proved by putting a messagebox after the 'then' and running the code), if so run the sub.
I've proved this by putting a messagebox after the 'else', when this is done it displays the messagebox with the workbook I want, not open. After the message box is cleared, the workbook then opens.
Is there any way to make the desired workbook open first or any other work around for this?
You can create an Add-in that runs whenever a workbook is open
https://msdn.microsoft.com/en-us/library/office/gg597509(v=office.14).aspx
this tool may help to create the Add in http://www.andypope.info/vba/ribboneditor.htm
You should be able to use the Application.OnWindow event to trigger a macro when a file is opened or closed.
In ThisWorkbook
Private Sub Workbook_Open()
Call StartTracking
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Call StopTracking
End Sub
In a Module
Function StartTracking()
Application.OnWindow = "AutoRunOnWindowChange"
End Function
Function StopTracking()
Application.OnWindow = ""
End Function
Function AutoRunOnWindowChange()
If Left(ActiveWorkbook.Name, 3) = "EDN" Then
Call Data_generator
Application.DisplayAlerts = False
ThisWorkbook.Save
Application.DisplayAlerts = True
Application.Quit
End If
End Function

Prompting a macro on close of workbook, but kill the closing if user press "No"

I am trying to execute a macro on close of a workbook.
The macro works perfectly but the problem is the closing function. I want a user to be prompted to say "Yes" or "No" when closing the workbook. If the user presses "Yes" the workbook should save as xlsm and be closed.
If the user presses "No" the macro should be executed so that the user is sent to sheet "Projektinformation" and the workbook should not be closed.
Here is my code, any thoughts?
Sub Auto_Close()
Dim OutPut As Integer
OutPut = MsgBox("Projektinformation ifylld?", vbYesNo, "Projektinformation.")
If OutPut = 6 Then
'Output = 6(Yes)
ThisWorkbook.SaveAs FileFormat:=xlOpenXMLWorkbookMacroEnabled
Else
'Output = 7(No)
Sheets("Projektinformation").Select
End If
End Sub
From your comment, I'm inferring that your code looks something like this on the Workbook_BeforeClose side:
Private Sub Workbook_BeforeClose(Cancel as Boolean)
Call Auto_Close()
End Sub
The problem is the code does exactly what you asked it to! It runs your Auto_Close subroutine (before the workbook closes) and then proceeds to close the workbook!
In order to achieve what you are trying to achieve, you have to change the Cancel parameter, passed into the Workbook_BeforeClose sub, to True. When Cancel = True the workbook will cancel the close event, otherwise it will continue as usual. I would either pass Cancel into your sub by reference and change the flag depending on what your user clicks or make Auto_Close() a function that returns a boolean, indicating whether or not to continue closing the workbook.
EXAMPLE
Private Sub Workbook_BeforeClose(Cancel as Boolean)
If SomeCondition = True Then
Cancel = True '<-- Workbook will stay open
Else
Cancel = False '<-- Workbook will close as usual
End If
End Sub
You are putting the code in the wrong place. It should be in the Workbook.BeforeClose Event event macro in the ThisWorkbook code sheet.
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Dim OutPut As Integer
OutPut = MsgBox("Projektinformation ifylld?", vbYesNo, "Projektinformation.")
If OutPut = 6 Then
'Output = 6(Yes)
ThisWorkbook.SaveAs FileFormat:=xlOpenXMLWorkbookMacroEnabled
Else
'Output = 7(No)
Cancel = True
Sheets("Projektinformation").Select
End If
End Sub
Note the Cancel = True. This tells Excel to halt the close operation and continue processing instructions.

Preventing Excel prompt from Word VBA

I'm working with VBA on Word and Excel. I have the Word running a userform which will automatically open an Excel file. User should fill some data in Excel file and then go back to the Word userform. When user finish filling all fields in Word userform, it will run some VBA code on Word that copy data from Excel to Word. After finished, the Excel file will be closed automatically. Therefore, I need to prevent user from closing the Excel app manually.
In order to do that, I use these code in Excel VBA in Sub Workbook_BeforeClose. If user close the Excel application window, it will show a message box that ask whether the user is still working with the Word userform. The code as follows:
Private Sub Workbook_BeforeClose(Cancel As Boolean)
answer = MsgBox("Are you still working with Word userform?", vbYesNo)
If answer = vbYes Then
Cancel = True
MsgBox "This workbook should not be closed. It will be automatically closed when you finish working with Ms. Word Template Userform."
Else
Application.ThisWorkbook.Saved = True
End If
End Sub
In the Word VBA, I have code to close the Excel file:
Sub closeExcelApp()
If Not excelApp Is Nothing Then
excelApp.DisplayAlerts = False
excelWb.Close savechanges:=False
Set excelWb = Nothing
excelApp.Quit
End If
Set excelApp = Nothing
End Sub
This Sub will be called when the Word VBA code done copying data from Excel to Word. However, calling this Sub will cause the Workbook_BeforeClose called. Meanwhile, I don't want the Workbook_BeforeClose called when I call this closeExcelApp sub from Word VBA.
Any suggestion?
You can just disable events:
Sub closeExcelApp()
If Not excelApp Is Nothing Then
excelApp.DisplayAlerts = False
excelApp.EnableEvents = False
excelWb.Close savechanges:=False
Set excelWb = Nothing
excelApp.Quit
End If
Set excelApp = Nothing
End Sub
As explained in comments, add this line on top of the modules : Public ClosingFromWord As Boolean and set this boolean to False when you start your code execution.
As you are working between apps, the easiest way will be to write the value of the boolean in a cell in Excel from Word and read/laod this value in the Workbook_BeforeClose to avoid going through the whole code of that routine.
And modify you code to look like this :
Sub closeExcelApp()
ClosingFromWord = True
excelApp.Workbooks(1).Sheets(1).Cells(1,1) = ClosingFromWord
If Not excelApp Is Nothing Then
excelApp.DisplayAlerts = False
excelWb.Close savechanges:=False
Set excelWb = Nothing
excelApp.Quit
End If
Set excelApp = Nothing
ClosingFromWord = False
excelApp.Workbooks(1).Sheets(1).Cells(1,1) = ClosingFromWord
End Sub
So while you execute closeExcelApp, the boolean will be set to True and the Workbook_BeforeClose won't be executed in his totality as it'll be exited with If ClosingFromWord Then Exit Sub :
Private Sub Workbook_BeforeClose(Cancel As Boolean)
ClosingFromWord = Workbooks(1).Sheets(1).Cells(1,1)
If ClosingFromWord Then Exit Sub
answer = MsgBox("Are you still working with Word userform?", vbYesNo)
If answer = vbYes Then
Cancel = True
MsgBox "This workbook should not be closed. It will be automatically closed when you finish working with Ms. Word Template Userform."
Else
Application.ThisWorkbook.Saved = True
End If
End Sub

Prevent user from deleting a particular sheet

Protecting workbook structure will prevent a user from deleting sheets. But how could I (using VBA) prevent a user from deleting a particular sheet I designate? I've seen examples where an active sheet is prevented from deletion by
Set mymenubar = CommandBars.ActiveMenuBar
mymenubar.Controls("Edit").Controls("Delete sheet").Visible = False
in its Worksheet_Activate event but that of course only works if the sheet is activated.
Is there a way to prevent a sheet from being deleted whether active or no?
For clarity: I'm fine with the user deleting some sheets, just not a couple of particular sheets.
So protecting workbook structure won't work.
As far as I can tell, it isn't possible to natively tag a single sheet as non-deletable; and there isn't an event that can be used to detect when a sheet is about to be deleted so the workbook can be protected preventively.
However, here is one potential workaround:
Protect workbook structure: this will, as you indicate, prevent all sheets from being deleted.
Create a "Controls" sheet. On this sheet, maintain a list of all sheet names (except those you don't want to be deletable).
If users want to delete a sheet, they will have to select its name on the Controls sheet (e.g. in a data validation drop-down menu) and press a "Delete" button. This button will call a macro that temporarily unprotects the workbook, deletes the selected sheet, and then reprotects the workbook.
Of course, the users will have to get used to this way of deleting sheets (as opposed to just right-click > Delete on the sheet's tab). Still, this isn't crazy complicated.
As for how to achieve #2 i.e. maintaining that list of sheet names, I suppose you could make use of a UDF like this one (must be called as an array formula):
Function DeletableSheetNames() As String()
Application.Volatile
Dim i As Long
Dim sn() As String
With ThisWorkbook
ReDim sn(1 To .Sheets.Count)
For i = 1 To .Sheets.Count
With .Sheets(i)
If .Name = "DataEntry1" Or .Name = "DataEntry2" Then
'Don't include it in the list.
Else
sn(i) = .Name
End If
End With
Next i
End With
DeletableSheetNames = sn
End Function
You cannot stop users to delete a particular sheet but you could use the Workbook_BeforeSave() event to prevent the workbook from being saved if a particular sheet is missing. The documentation on this event precisely shows how to allow saving a workbook only when certain conditions are met. See http://msdn.microsoft.com/en-us/library/office/ff840057(v=office.14).aspx
I can prevent a sheet from being deleted via the Worksheet_BeforeDelete Event as follows:
Private Sub Worksheet_BeforeDelete()
Call ThisWorkbook.Protect("password")
Call MsgBox("This sheet cannot be deleted.", vbExclamation)
End Sub
This protects all sheets from being deleted, however if you add some event code on the ThisWorkbook module like the following :
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Call ThisWorkbook.Unprotect("password")
End Sub
I will then be able to delete any other sheet as soon as it is selected.
Bear in mind, you will lose copy and paste functionality between pages due to the page unlocking when it is selected.
"there isn't an event that can be used to detect when a sheet is about to be deleted"
Since Office 2013, it is possible with the SheetBeforeDelete event.
I found this solution, similar to Dan's, on ExtendOffice.com. Put this code on the Worksheet's module:
Private Sub Worksheet_Activate()
ThisWorkbook.Protect "yourpassword"
End Sub
Private Sub Worksheet_Deactivate()
ThisWorkbook.Unprotect "yourpassword"
End Sub
When you activate the sheet in question, the whole workbook is protected, and the "Delete" option is grayed out. When you switch to any other sheet, the workbook is free again. It's subtle because you only notice the change when you go to the "safe" sheet.
Answer is by adding the following code to each of the protected sheets:
Private Sub Worksheet_Deactivate()
ThisWorkbook.Protect , True
Application.OnTime Now, "UnprotectBook"
End Sub
And the following to a Module:
Sub UnprotectBook()
ThisWorkbook.Unprotect
End Sub
Check https://www.top-password.com/blog/prevent-excel-sheet-from-being-deleted/ for credits accordingly.
The following disables the menu when you right click on tab. This stops the delete option being available.
Sub tab_rclick_off()
Application.CommandBars("Ply").Enabled = False
End Sub
The following turns the menu back on.
Sub tab_rclick_on()
Application.CommandBars("Ply").Enabled = True
End Sub
This option is simple, concise, prevents any issues with data entry/editing with protected sheets and can be called from anywhere in code, ie in conjunction with log on permissions can be given to some and not others etc. foremost yourself.
Maybe you could try to protect the structure of the workbook in SheetBeforeDelete.
See my example:
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
ThisWorkbook.Protect Structure:=False
End Sub
Private Sub Workbook_SheetBeforeDelete(ByVal Sh As Object)
If Sh.Name = "Example" Then
ThisWorkbook.Protect Structure:=True
End If
End Sub
Here is another answer from mine base on the idea of #Jean-François Corbett
You can use 'Protect WB Structure' and Event 'Workbook_SheetBeforeDelete' to achieve this.
The result is that an dialog will pop up said "Workbook is protected and cannot be changed."
Private Sub zPreventWShDel(WSh As Worksheet, Protection As Boolean)
Dim zPassword As String: zPassword = ""
Dim zWB As Workbook
Set zWB = WSh.Parent
If Protection Then
zWB.Protect zPassword, Protection
Else
zWB.Protect zPassword, Protection
End If
'Stop
End Sub
Private Sub Workbook_SheetBeforeDelete(ByVal Sh As Object)
Call zPreventWShDel(Sh, True)
End Sub
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
Call zPreventWShDel(Sh, False)
End Sub
Do not Call the code on Sheet Deactivation like below. Because it will deactivate it. Sequence of running is Event_SheetBeforeDelete -> Event_SheetDeactivate.
Private Sub Workbook_SheetDeactivate(ByVal Sh As Object)
Call zPreventWShDel(Sh, False)
End Sub
I created a different approach to this.
On the sheet(s) you want protected, add this code:
Private Sub Worksheet_BeforeDelete()
ThisWorkbook.Worksheets("Unprotect Workbook").Visible = True
ThisWorkbook.Worksheets("Unprotect Workbook").Activate
ThisWorkbook.Protect
End Sub
Create the ("Unprotect Workbook") sheet and make the visibility: xlSheetVeryHidden
on the "Unprotect Workbook" sheet add a button or shape that you can assign a macro to.
on the "Unprotect Workbook" sheet, add this code:
Sub unprotectThisWorkbook()
ThisWorkbook.unprotect
ActiveSheet.Visible = xlSheetVeryHidden
End Sub
Assign the sub you added, "Sub unprotectThisWorkbook()", to the button on the "Unprotect Worksheet" sheet
When you delete the sheet you protected, the workbook is protected and it takes you to the unprotect worksheet as a notice to the user and as a way to unprotect the workbook. Once the button is clicked, the workbook is unprotected and the "unprotect sheet" is hidden again.
This will work for any sheet you want to protect.

How to get Excel to require user to allow macro else cannot open workbook?

I have a macro in my Excel file that is meant to determine whether a user can open it or not. However, it seems like, depending on the settings of the user's machine, Excel by default disables the macro. This ended up opening and revealing the contents in the xls file.
How can I make sure that the user has to accept running the macro or Excel will close the workbook?
UPDATED the code as it was not doing exactly what it was meant to...
This is pretty much the only way you can do this WITH a macro (thanks to #OlleSjögren for the insight :p)
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Dim Current As Worksheet
Worksheets.Add(Before:=Worksheets(1)).Name = "Protection"
Worksheets("Protection").Cells(1, 1) = "Please activate macros to view this workbook."
For Each Current In Worksheets
If Current.Name <> "Protection" Then
Current.Visible = xlSheetVeryHidden
End If
Next Current
End Sub
Private Sub Workbook_Open()
Dim Current As Worksheet
For Each Current In Worksheets
Current.Visible = xlSheetVisible
Next Current
Application.DisplayAlerts = False
Worksheets("Protection").Delete
Application.DisplayAlerts = True
End Sub
The property xlSheetVeryHidden means that it cannot be made visible through the UI (VBA code only can change it).
I think you can't do it. But we can make a workaround.
First of all you need to hide all your worksheet and prevent user to unhide it. Link. Following the link you will see that you can make a sheet veryHidden. As the link shows, you need to add a password to vba code too.
Then you can to create a macro that adds an InputBox with password
Sub CheckPassword()
Dim password As String
password = InputBox("Enter the password") 'You can use a Form with Textbox [PasswordChar] to put ****
If password = "myPass" Then
Sheets(3).Visible = xlSheetVisible
Sheets(3).Select
End If
End Sub
Finally you can attach a sub in the close event.
Private Sub Workbook_BeforeClose(Cancel As Boolean)
Sheets(3).Visible = xlSheetVeryHidden
End Sub