I work for a company which has a mechanism in place to control and block messages with certains extensions, like .doc, .xls, xlsx, xlsm et cetera.
We receive a lot of requests to approve those messages because they are relevant to our customers, but before we do that, we check the attached files for code in VBA, but when I open them I get the message "PROTECTED VIEW Files from the internet can contain viruses" and if I proceed to open the VBA editor (ALT + F11), the code isn't there.
There is nothing. No Sheet, no ThisWorkBook, nothing. Everything is greyed out. So how do I view the code, without running potential malicious macro code AND stay in protected mode.
When I enable all macro's without notification or hit "Enable editing", it works, but that is not something I want.
You can't see the code in Protected View (unless there is a trick which someone will share.)
But for the part So how do I view the code, without running potential malicious macro code .... here is the solution:
Using Application.AutomationSecurity you can build/create a basic macro, which will allow you to open all the recieved files in macro disabled mode.
You can then manually read the code. (Hope you are not trying to build a code scanner!)
Here is example to start with from MSDN
Sub Security()
Dim strFile As String
Dim secAutomation As MsoAutomationSecurity
secAutomation = Application.AutomationSecurity
Application.AutomationSecurity = msoAutomationSecurityForceDisable
'/ Just an example workbook.
strFile = "C:\Users\username\Desktop\fail-test.xlsm"
Workbooks.Open strFile
'/ once the workbook is launced, yoou applicationsetting is restored.
Application.AutomationSecurity = secAutomation
End Sub
Related
I have an Excel macro that performs a few functions on a document (Creates as form, and a few emails) all from MS-Word documents. If the macro is executed from the main spreadsheet (where the macro is), everything works normally. I want to place this macro on the ribbon allowing a user to launch it without having (or knowing where the main excel document is located or having it open). I created a sub to check to see if the spreadsheet was open and modified the ribbon to include an icon for the macro.
Which works. However, when launched from the ribbon while the main Excel spreadsheet is not open, it opens the workbook and runs the macro in entirety (Without executing the open workbook line of the macro). I assume the spreadsheet is being open because the macro that is being called resides with it (makes sense). Since the macro is dependent on the data contains in the spreadsheet, I need to allow the users to modify it and then re-running the macros from the ribbon again.
Does anyone have a recommended approach or best practices? Thank you in advance.
Sub MainForm()
Dim WorkingFolder As String
Dim File01 As String 'Main Excel Data File, where all data is
Dim File02 As String 'Preliminary Email to send to user
Dim File03 As String 'Final Email to Send to user when production is complete
Dim wb As Workbook
WorkingFolder = "C:\Temp\"
File01 = "01-MainData.xlsm"
File02 = "02-PreProductionEmail.docx"
File03 = "03-FinalProductionEmail.docx"
If wbIsOpen(File01) = True Then
MsgBox "Workbook Is Open"
Run ("'C:\Users\Guest\Nextcloud\Documents\Excel Forms\02-TEST-Production Request-Data.xlsm'!CreateProductionForm")
Else
MsgBox "The Main Datafile is not open, verify the last row before re-runing", vbOKOnly, "Not Open"
Set wk = Workbooks.Open(WorkingFolder & DataFile)
End If
End Sub
The Guide mentioned by Ricardo was not quite what I needed but it was helpful and did put me on the right track. Thank you again Ricardo. To get this working, I needed to do the following.
1 - Make the procedure above its own separate *.xlam file (saved as an add-in).
2 - Add the add-in to start automatically via the developers tab
3 - Add the Macro to the Ribbon.
I have modified the sample above to included the open statement along with the statement that executes a macros from another workbook.
Appreciate the guidance.
Summary: We need to go to a website, download an Excel file, and copy&paste the data into an Excel template file all automatically.
Current Problem: My current VBA code opens IE, does the data scrape and downloads the file automatically without any problems. However, ONLY in Break Mode that it opens the downloaded workbook and copy&paste the data into the template.
What Didn't Work: I tried multiple ideas like Sleep, Application.Wait, and DoEvents. Unfortunately, it keeps getting stuck on the same line. The code does not recognize a Workbook has been open and can't find the data to copy&paste. Only after I enter into the Break Mode that the file will eventually open.
Observation: Security Settings on Excel removed so I do not receive a warning message when opening a file.
This snippet is part of a loop which will make the same procedure you will find below for 5 different files. After it downloads each file, it copies&paste the data into my template.
Any idea & solution is totally welcomed!
Please find my code below:
sub test()
'-----------------------------------
'Code to do the Data Scrape in here. It works fine.
'-----------------------------------
Dim o As IUIAutomation
Dim e As IUIAutomationElement
Dim h As Long
Dim wkbook As Workbook
Dim Cwkbook As String, ThisWkbook As String
'Code to Automate IE Download
Set o = New CUIAutomation
h = ieApp.hWnd
h = FindWindowEx(h, 0, "Frame Notification Bar", vbNullString)
If h = 0 Then Exit Sub
Set e = o.ElementFromHandle(ByVal h)
Dim iCnd As IUIAutomationCondition
Set iCnd = o.CreatePropertyCondition(UIA_NamePropertyId, "Open")
Dim Button As IUIAutomationElement
Set Button = e.FindFirst(TreeScope_Subtree, iCnd)
Dim InvokePattern As IUIAutomationInvokePattern
Set InvokePattern = Button.GetCurrentPattern(UIA_InvokePatternId)
InvokePattern.Invoke
'Download Completed and File Should be opened.
DoEvents
Application.Wait Now + #12:00:10 AM#
'Even after using DoEvents & Application.Wait, the code won't open the workbook until it goes into Break Mode.
'Code to find Downloaded Workbook which will come named either as X or Y.
ThisWkbook = ThisWorkbook.Name
For Each wkbook In Application.Workbooks
If Left(wkbook.Name, 9) = "XXXXXXXXX" Or Left(wkbook.Name, 9) = "YYYYYYYYY" Then
Cwkbook = wkbook.Name
'-----------------------------------
'Code to Copy&Paste data in here. It works fine.
'-----------------------------------
End If
Next
End Sub
You will need the UIAutomationClient reference to make the code work. If you get an error when including it (like I did), add "Microsoft Visual Basic for Applications Extensibility" reference and run the code below:
Sub MyUIAutomation()
'----------------------------------------------------
'Add UIAutomation as VBA Reference
'----------------------------------------------------
Dim VBProj As VBIDE.VBProject
Set VBProj = ThisWorkbook.VBProject
VBProj.References.AddFromFile Environ("systemroot") & "/system32\uiautomationcore.dll"
Set VBProj = Nothing
End Sub
UPDATE 8.28.17: I tried a work around to copy/paste the data only when a Workbook is opened. The idea would be to finish my main "data extract" code. After the code is finished the Workbooks should open as it was behaving before, and the second code would trigger, copying/pasting the data.->
Run VBA macro whenever any workbook is opened
Outcome: It is still not working. It would only open the last workbook of the whole loop and copy&paste the data of it. The other 4 previous files wouldn't open.
UPDATE 8.31.17 Another work around failed. I tried to create an User Form, where the user would have to click on 5 different buttons. The idea is to check if my problem was the looping. Each button would extract the file and after that my Copy&Paste Macro would trigger.
Outcome: It did not work as well. I figure that while I have my "User Form" open excel recognizes that a macro is running. If I run it under Break Mode it will still open the file and copy the data.
I believe it would be important to other people know that I found a solution to my own problem.
The main problem was that my original code was structured this way:
'Open IE -> Search for a specific criteria -> Download File -> Open File -> Copy&Paste File.
It would repeat the above procedure 5 times for each different file I need.
The code would run perfectly under break mode. However, when you actually ran it, it wouldn't bring over the data from the downloaded file.
FIX: It is a work around. I separated my code into pieces. The first piece is going to search, download, and open the file I want. The second piece will only trigger when a file with a specific name is opened. It will then perform all the remaining steps I need.
I used the Workbook_Open event to do such thing:
Private Sub Workbook_Open()
The third and last piece, is a form created using shapes like circles, squares, and text box. It actually looks way nicer than an userform. Each circle performs like a button and will trigger the download of a specific file when clicked.
Now the code is structured like:
'1. Form using Shapes, each circle activates a specific script
'2. 5 Scripts -> Open IE, Search for a specific criteria, Download File, Open File
'3. 1 Script. Only Triggers when Excel Workbooks are opened. It will then only copy the data over if it meets certain criteria (such as filename).
Context
I have developed a .xlam add-in that contains user-data inside. In other words, the user can decide to show the add-in file through a ThisWorkbook.IsAddIn = False to edit the content, which is functional to the add-in itself.
However, the user should not be able to perform some operations when he's/she's working on the add-in's spreadsheets rather than on the normal workbook where the Add-In is running.
Need to check for file extension
From here, it comes my need of checking for the file extension and validate it when some specific "forbidden" procedures might get called. I have made the following tests:
If ThisWorkbook.IsAddIn = True, then ThisWorkbook.FileFormat = 55;
If ThisWorkbook.IsAddIn = False, then ThisWorkbook.FileFormat = 52;
The source of confusion
This is not what I was expecting. By simply executing a FullName request when the Add-In is set visible:
ThisWorkbook.IsAddIn = False
MsgBox ThisWorkbook.FullName
I can read that my file is still named C:\myFile.xlam, even if in that moment is visible to the user. So, I would expect ThisWorkbook.FileFormat to raise a 55 even if visible at run-time. But it doesn't do that, apparently.
The question
I need to make sure to distinguish between modifications on the Add-In (.xlam) and modifications on a possible .xlsm file that the user created, from which is using my Add-In.
Why is the FileFormat of my add-in being equal to the one of an xlsm, if the file is clearly xlam to which is associated a 55 instead of a 52? Where am I being wrong?
EDIT - Example of the action to forbid
On the ribbon there's a button created and added from the add-in, which is connected to a macro that cannot be run into the Add-In. So the check I had in mind was something like this:
If ActiveWorkbook.FileFormat = 55 Then
Exit Sub
End If
However, as said above, this check will not be performed because the Add-In has FileFormat = 52 in the moment in which is set to .IsAddIn = False; hence, even if the ActiveWorkbook is the add-in where I do not want to run the macro, the check will fail and the macro will run anyway.
The .IsAddIn workbook property simply indicates whether the file is being run as an Add-in. It does not change the file format. From the documentation:
When you set this property to True, the workbook has the following characteristics:
You won’t be prompted to save the workbook if changes are made while the workbook is open.
The workbook window won’t be visible.
Any macros in the workbook won’t be visible in the Macro dialog box (displayed by pointing to Macro on the Tools menu and clicking Macros).
Macros in the workbook can still be run from the Macro dialog box even though they’re not visible. In addition, macro names don’t need to be qualified with the workbook name.
Holding down the SHIFT key when you open the workbook has no effect.
I sense that this is the real problem you're trying to tackle:
However, the user should not be able to perform some operations when he's/she's working on the add-in's spreadsheets rather than on the normal workbook where the Add-In is running.
Perhaps it will be best if you can specify what actions you're trying to restrict? There may be a better way to solve this.
For the moment I have found four possible solutions, that I'm going to post here just in case someone would have my same issue:
Comparing the full names - credit to Tim Williams
The "special code" cannot run if the full names are different:
If ActiveWorkbook.FullName = ThisWorkbook.FullName Then
Exit Sub
End If
'"special code"
Comparing the isAddIn property - credit to David Zemens
The "special code" cannot run if this workbook is not currently an add-in:
If ThisWorkbook.IsAddIn = False Then
Exit Sub
End If
'"special code"
Comparing the two objects
The "special code" cannot run if the active workbook is the add-in workbook:
If ActiveWorkbook Is ThisWorkbook Then
Exit Sub
End If
'"special code"
Checking for "xlam" extension
The "special" code will not be run if the extension of the file is xlam:
If Right(ActiveWorkbook.FullName,4) = "xlam" Then
Exit Sub
End If
The four solutions above work fine for the purpose, but the question is still opened : why the FileFormat property changes over the same file depending on ThisWorkbook.IsAddIn being False rather than True?
I've spent the last two days working on this problem. Most of the content I've found on this topic doesn't address the issue I'm having, so I'm hopeful that someone here can help me.
I've been working on some code that does the following from a "master scorecard" workbook:
Takes each "student" sheet in the workbook and copies the sheet into a new workbook,
Does a few minor manipulations of the new workbook,
Imports a module of code into the new workbook,
Adds a Workbook_Open event and a Workbook_BeforeClose event to the new workbook (to make certain sheets xlVeryHidden depending on level of access),
Runs a subprocedure from the newly imported module,
Saves and closes the workbook.
Each scorecard uses code to ensure that only the person whose name is on the scorecard can access it. I've used Environ("username") in the workbook events to ensure security, but as you well know, if one and understands how to run macros, he/she could merely open the VBEditor and unhide the xlVeryHidden sheets in the workbook very easily.
So, my thought was to password protect the new workbook's VBAProject programmatically (see above: step number five). I found a few sources online of how to use SendKeys to achieve this goal (see below), but SendKeys is unreliable (at best) and isn't cooperating with my code. The code works like a charm if I run it by itself, but if I call it from another project using Run Macro:="filename!macroname" it doesn't set the protection. After the code has run and all the workbooks have been created, the VBAProject properties window(s) from the earlier code are all open and try to execute at the same time which crashes Excel.
Sub LockVBAProject()
Const VBAProjectPassword As String = "123"
Dim VBP As VBProject, openWin As VBIDE.Window
Dim wbActive As Workbook
Dim i As Integer
Set wbActive = ActiveWorkbook
Set VBP = wbActive.VBProject
Application.ScreenUpdating = False
' close any code windows to ensure we hit the right project
For Each openWin In VBP.VBE.Windows
If InStr(openWin.Caption, "(") > 0 Then openWin.Close
Next openWin
wbActive.Activate
With Application
'//execute the controls to lock the project\\
.VBE.CommandBars("Menu Bar").Controls("Tools") _
.Controls("VBAProject Properties...").Execute
'//activate 'protection'\\
.SendKeys "^{TAB}"
'//CAUTION: this either checks OR UNchecks the\\
'//"Lock Project for Viewing" checkbox, if it's already\\
'//been locked for viewing, then this will UNlock it\\
.SendKeys "{ }"
'//enter password\\
.SendKeys "{TAB}" & VBAProjectPassword
'//confirm password\\
.SendKeys "{TAB}" & VBAProjectPassword
'//scroll down to OK key\\
.SendKeys "{TAB}"
'//click OK key\\
.SendKeys "{ENTER}"
'the project is now locked - this takes effect
'the very next time the book's opened...
End With
ThisWorkbook.SaveAs Filename:=Sheets(Sheets.Count).Name, FileFormat:=xlOpenXMLWorkbookMacroEnabled
Debug.Print "It Worked " & Now()
End Sub
I'm not sure why this is happening; like I said, the code works fine when run on its own. I found this post where this link to a non-SendKeys approach was outlined, but it was written several years ago and I'm not sure how I'd need to modify it for my purposes since I've never coded in VB6...
Are there any thoughts as to why the SendKeys method is bunching up after the code has already run instead of executing when it's supposed to during the code? Should I abandon SendKeys in favor of this other method? I'm at a loss, so any help will be much appreciated!
EDIT: I think the reason the code isn't working is because the correct project isn't activated at the time the SendKeys code is executed. I had hoped that activating the proper workbook would solve the issue, but it doesn't appear to have helped.
Ok, so after another couple of hours of searching the web for alternative methods to achieve my goal, I stumbled across this post.
I created a template workbook (with the event code already in ThisWorkbook), password protected the project, and modified my code to use the template workbook for each new sheet. Now when the sheets are created, the project is already locked for viewing and requires a password. While I realize the security under this approach isn't very secure, it will help "keep honest people honest" as they say.
For those who stumble across this post and still wish to programmatically lock/unlock their VBA Project, see these resources:
This SO post
This blog
Both are great resources that walk through a way to do it in VBA.
To add a nuance to the otherwise fine piece of code originally posted here: If you change the Project Name for the workbook within the VBE, you'll need to change one line of code to:
.VBE.CommandBars("Menu Bar").Controls("Tools") _
.Controls(VBP.Name & " Properties...").Execute
(sigh)
I have created an Excel Spreadsheet which helps with data analysis from an Oracle database.
The user enters then clicks the "Refresh Query" button which generates a query for Oracle to execute. The query takes a minute or so to complete. Although the VBA code does not hang on ".Refresh", all Excel windows remain frozen until the query completes.
Sub refreshQuery_click()
Dim queryStr as String
' Validate parameters and generate query
' ** Code not included **
'
' Refresh Query
With ActiveWorkbook.Connections("Connection").OLEDBConnection
.CommandText = queryStr
.Refresh
End With
End Sub
Is there a way for the user to manually cancel the query (calling .CancelRefresh) while the Excel user-interface is frozen?
EDIT I don't know if the following is worth noting or regular behavior. While the query is executing, all open Excel windows (including the VBA Editor) become "Not Responding" in Task Manager. Neither pressing Esc nor Ctrl+Break will cancel the script. Also, calling DoEvents (either before or after .Refresh) does not change this behavior.
Here's a method that I know will work. However, there are some complications.
Here's how it's done:
Put the spreadsheet with the data in a separate workbook. This worksheet should execute the refresh query when it's opened and then close once the data is updated.
Create a batch file to call the "Data" Excel file.
Within a different workbook, create a procedure (macro) for the user to call. This procedure will call the batch file, which subsequently calls the Excel file. Since you are calling a batch file and not Excel directly, the Excel procedure will continue because the command shell is released so quickly and opens the other Excel file in a different thread. This allows you to continue working within the main Excel file.
Here are some complications:
I included a method to alert the user that the data has been udpated. There are timing issues where it's possible to try to check if the data has been update when the workbook is not accessible, which forces the user to try to update values. I included a method called my time which pauses the execution of the code so it only checks every so many seconds.
The updated worksheet will pop up in a new window, so the user will need to click on their original worksheet and keep working. You could learn to hide this if you're comfortable with Windows scripting (I haven't learned that yet).
Here are some files and code. Be sure to read the comments in the code for why some things are there.
FILE: C:\DataUpdate.xls
We'll make a workbook called "DataUpdate.xls" and put it in our C:\ folder. In cell A1 of Sheet1, we'll add our QueryTable which grabs external data.
Option Explicit
Sub UpdateTable()
Dim ws As Worksheet
Dim qt As QueryTable
Set ws = Worksheets("Sheet1")
Set qt = ws.Range("A1").QueryTable
qt.Refresh BackgroundQuery:=False
End Sub
Sub OnWorkbookOpen()
Dim wb As Workbook
Set wb = ActiveWorkbook
'I put this If statement in so I can change the file's
'name and then edit the file without code
'running. You may find a better way to do this.
If ActiveWorkbook.Name = "DataUpdate.xls" Then
UpdateTable
'I update a cell in a different sheet once the update is completed.
'I'll check this cell from the "user workbook" to see when the data's been updated.
Sheets("Sheet2").Range("A1").Value = "Update Table Completed " & Now()
wb.Save
Application.Quit
End If
End Sub
In the ThisWorkbook object in Excel, there's a procedure called Workbook_Open(). It should look like the following so it executes the update code when it is opened.
Private Sub Workbook_Open()
OnWorkbookOpen
End Sub
NOTE: I found a bug when this file closed if 1) you accessed the file from the command line or shell and 2) you have the Office Live Add-in installed. If you have the Office Live Add-in installed, it will throw an exception on exit.
FILE: C:\RunExcel.bat
Next, we're going to create a batch file that will open the Excel file we just made. The reason that call the Excel file from within the batch file and not directly from the other Excel file using Shell is because Shell will not continue until the other application closes (at least when using Excel.exe "c:\File.xls"). The batch file, however, runs its code and then immediately closes, thus allowing the original code that called it to continue. This is what will let your uses continue working in Excel.
All this file needs is:
cd "C:\Program Files\Microsoft Office\Office10\"
Excel.exe "C:\DataUpdate.xls"
If you're handy with Windows Scripting, you do fancy things like open the window in a hidden mode or pass a parameter of the file name or Excel location. I kept it simple with a batch file.
FILE: C:\UserWorkbook.xls
This is the file that the user will open to "do their work in." They'll call the code to update the other workbook from within this workbook and they'll still be able to work in this workbook while this one is updating.
You need a cell in this workbook where you'll check the "Update Table Completed" cell from the DataUpdate workbook. I chose cell G1 in Sheet1 for my example.
Add the following code to a VBA module in this workbook:
Option Explicit
Sub UpdateOtherWorkbook()
Dim strFilePath As String
Dim intOpenMode As Integer
Dim strCallPath As String
Dim strCellValue As String
Dim strCellFormula As String
Dim ws As Worksheet
Dim rng As Range
Set ws = Worksheets("Sheet1")
Set rng = ws.Range("G1")
strCellFormula = "='C:\[DataUpdate.xls]Sheet2'!A1"
'This makes sure the formula has the most recent "Updated" value
'from the data file.
rng.Formula = strCellFormula
strFilePath = "C:\RunExcel.bat"
intOpenMode = vbHide
'This will call the batch file that calls the Excel file.
'Since the batch file executes it's code and then closes,
'the Excel file will be able to keep running.
Shell strFilePath, intOpenMode
'This method, defined below, will alert the user with a
'message box once the update is complete. We know that
'the update is complete because the "Updated" value will
'have changed in the Data workbook.
AlertWhenChanged
End Sub
'
Sub AlertWhenChanged()
Dim strCellValue As String
Dim strUpdatedCellValue As String
Dim strCellFormula As String
Dim ws As Worksheet
Dim rng As Range
Set ws = Worksheets("Sheet1")
Set rng = ws.Range("G1")
strCellFormula = "='C:\[DataUpdate.xls]Sheet2'!A1"
strCellValue = rng.Value
strUpdatedCellValue = strCellValue
'This will check every 4 seconds to see if the Update value of the
'Data workbook has been changed. MyWait is included to make sure
'we don't try to access the Data file while it is inaccessible.
'During this entire process, the user is still able to work.
Do While strCellValue = strUpdatedCellValue
MyWait 2
rng.Formula = strCellFormula
MyWait 2
strUpdatedCellValue = rng.Value
DoEvents
Loop
MsgBox "Data Has Been Updated!"
End Sub
'
Sub MyWait(lngSeconds As Long)
Dim dtmNewTime As Date
dtmNewTime = DateAdd("s", lngSeconds, Now)
Do While Now < dtmNewTime
DoEvents
Loop
End Sub
As you can see, I constantly updated the formula in the "Listening Cell" to see when the other cell was updated. Once the data workbook has been updated, I'm not sure how you'd force an update in code without rewriting all the cells. Closing the workbook and reopening it should refresh the values, but I'm not sure of the best way to do it in code.
This whole process works because you're using a batch file to call Excel into a different thread from the original file. This allows you to work in the original file and still be alerted when the other file has been updated.
Good luck!
EDIT: Rather than include a more complete answer in this same answer, I've created a separate answer dedicated entirely to that solution. Check it out below (or above if it gets voted up)
Your users can break the VBA function by pressing Ctrl+Break on the keyboard. However, I've found that this can cause your functions to randomly break until each time any function is run. It goes away when the computer is restarted.
If you open this file in a new instance of Excel (meaning, go to Start > Programs and open Excel from there), I think that the only workbook that will be frozen will be the one executing the code. Other intances of Excel shouldn't be affected.
Lastly, you might research the DoEvents functions, which yields execution back to the Operating System so that it can process other events. I'm not sure if it would work in your case, but you could look into it. That way you can do other things while the process is being completed (It's kind of dangerous because the user can then change the state of your application while the process is working).
I believe I know a way that actually will work, but it's complicated and I don't have the code in front of me. It involves creating a separate instance of the Excel application in code and attaching a handler to the execution of that instance. You include the DoEvents part of the code in a loop that releases once the application closes. The other instantiated Excel application has the sole purpose of opening a file to execute a script and then close itself. I've done something like this before so I know that it works. I'll see if I can find the code tomorrow and add it.
Well, you could consider the old-fashion way -- split the query into smaller batches and use Do Events in between batches.
You could try XLLoop. This lets you write excel functions (UDfs) on an external server. It includes server implementations in many languages (eg. Java, Ruby, Python, PHP).
You could then connect to your oracle database (and potentially add a caching layer) and serve up the data to your spreadsheet that way.
The XLL also has a feature to popup a "busy" GUI that lets the user cancel the function call (which disconnects from the server).
BTW, I work on the project so let me know if you have any questions.