Prevent >1 person from running VBA macro at a time (shared wkb) - vba

What I'm aiming at is getting a list of all currently running scripts in order to check if other users aren't running the same VBA macro at the same time (and if yes, then stop the code etc., similar to what the OP of the below question wanted). This would be for a shared workbook (I learned it's not designed for this type of work, but I need to try it).
https://stackoverflow.com/a/36116091/5947935
I've been trying to make the code in the above answer work in VBA, but it seems it's a vbs thing and I would like to avoid that.
I'm not an expert to say the least, so I'm having trouble understanding how to get this to work in Excel VBA. I don't even know if it's possible at all.
I've found this as well: VBA Getting program names and task ID of running processes and it works fine but it only lists the running processes.
I've no idea however how to merge the two... or even if the WMI is the correct way to go.
I'd appreciate any sort of help.

I used to create a "locking file" which was just an empty text file with the name of the workbook followed by the username and an extension of .LCK
First thing my code did on auto open was look for a locking file then report back to the user which user had it open then cancel the open.
If it didn't find a locking file then it created one and proceeded as normal. If it found one but it was the same username (ie that user had it crash on them) it proceeded with the open.
The last thing the code did was delete the file.

No codes here and theory not tested yet, but the idea of preventing a different user executing a Macro on a shared workbook requires some thinking.
I would create a hidden worksheet, and use one of the cells to store the Environ("USERNAME") when the macro is first started - to indicate who has it running, then clear it when complete, first-in-first-out.
Lets say named range MUser (macro user) is range A1 in that hidden worksheet
When the macro runs, it will first check if MUser is empty, if so then change it's value to Environ("USERNAME") and Save the file before next step (here I am not certain the value is updated on others session).
If MUser is not empty, either abort or retry in a few second.
When macro completes, MUser will be ClearContents, and save the File to free up the workbook for macro.
Idea is here but please test. Post your own code for us to troubleshoot. You may also use Workbook events to "lock" the macro execution this way. Or even use this hidden sheet to make a log record for debug. Also some fail-safe needs to be implemented (such as a time stamp at macro start and override the lock after some minutes).

Related

Prevent saving a copy of a shared workbook if user was disconnected

Before we start: Please don't question why I'm Using them or complain about shared workbooks because they break and crash and be come corrupted etc unfortunately it is a necessity for the situation.
I have resolved almost all the issues with them and posted most of the code to accomplish this on Stack Exchange. I'm up to over 10 months of them running smoothly with no issues and about 100 users using them daily making thousands of changes.
I am now down to one final problem of preventing a user saving a copy of the workbook or overwriting a workbook if they were disconnected.
What I would actaully like is if the user has been disconnected then any attempts to save are canceled.
I attempted to modify following code to cancel the save if a user was disconnected but it fails every time even if the workbook is shared:
How to check whether the current user has been removed from the shared workbook?
I have tried creating a workbook with only the code mentioned above to determine if it was quirk from another part of the code in my main workbooks but it still fails.
Does anybody else have an idea how to determine in vba if a user was disconnected to cancel a save?
Is there an error code that I could catch that is associated with the pop up that says you are no longer connected to this work book that then asks you to save the workbook as a copy?
Would a disconnected users excel see the workbook as read only?
You can not check during the BeforeSave event as Excel takes exclusive access of the file during this event apparently that is why any attempts to read the user list fail during this or testing for anything else.
Excel Should not allow you to save over a file if you were disconnected and you can cancel the Save As UI in the BeforeSave event to prevent a copy being made.

Executing a macro in another workbook

I'm doing an internship as a student developer and I have to program something that will automatically analyse spreadsheets every month.
I will have an Excel file that the users will open, and after typing the name of the files he wants to analyse and compare, generate graphs and other statistics.
Completely new to VBA (I only learned C#, PHP and Python at school), I figured out how to open a new Excel file with workbooks.open, but running a macro after opening a file won't work.
I guess it may be because my macros aren't recorded on those other files, but I need my program to work even after I'm gone, and I can't tell my users to copy the macros into new files every month.
I'm also looking into passing variables between workbooks, since for statistics purposes, I will have to retrieve, for instance, the value of cell G22 in every file opened and copy them in my primary file in order to make a graph out of those values.
Can someone point me in the right direction?
If you make a function or sub public you should be able to call it from another workbook, provided both are open.
public sub testMessage(input as string)
msgbox input
end sub

Update VBAs in multiple workbooks

END GAME: A user saved Workbook opens and mirrors code from a target file.
I am trying to create a simple VBA application that has an Excel front-end and an Access back-end. There will be multiple users who would have the option to save the front-end Excel piece anywhere they desire.
I would like to know the most efficient way to be able to update macros in all user instances when I need to push updates.
Essentially, I would like to mirror code from a "global" file on Workbook_open. In the past I did actually set code to open a separate workbook and run code (dim x as workbook, open, app.runmacro and etc.), But I think that is not really the most efficient way to do it.
Four possible solutions pop to mind for this (other than your option of having an intermediary workbook), there are likely others:
Treat the Workbook as purely an interface, and move the code to the
Access database and have it accept the Workbook as a parameter if
needed. The advantage would be the code could be maintained in one
place (Access), but it would have two main disadvantages. Each user
would need to have Access installed in order for it to instantiate
the application to call methods on, and it would lock in your
"interface" - that is, changes to how it calls Access macros would
still require Workbook updates.
Create a canonical Workbook and have the user Workbook version check
against the canonical Workbook when opened. If the version is
different, open the new one, move all of the data to it, delete the
old one, and save the new copy to the same filename as the old one.
The main disadvantage of this method would be ensuring that old code doesn't run might be difficult, as you would need
to take measures to prevent situations where the user could abort
the update process and still have a working copy of the old code.
Automate the VBE (see this answer for implementation details -
there are numerous resources on how to do this). Depending on how
you wanted to do this, you could either store the current modules as
files and import them, or store the code in the database itself and
query for it. The main disadvantages of this method are that the
VBE can be fickle about changing code that is actually running. I'm
not sure that I'd trust it to change it's own implementation. You
would also need to allow access to the VBE in each user's security
settings, which may pose a security threat.
Store the location of the Workbooks themselves in the database, then
push out updated copies with external code. The Workbook would
report it's filepath when opened, and if it wasn't already recorded
in the database, check to see if it was the most current version,
and then write a record for itself. This has the disadvantage of
only being able to inform the user that they don't have the current
version if they (for example) move the Workbook in Explorer and
don't open it until after your push.
Note that these are all "pull" type as opposed to "push" type solutions with the exception of the last one. Regardless of the method you use for version checking, any push solution is going to share the disadvantage of number 4 - there is no reliable way to make sure that a push catches all the invalidated versions.

Excel. Use vba to record when someone removes the shared feature

We have a problem at work. Im not sure if this is due to an individual or the system admin applying patches during working hours but... we use an Excel sheet at work to track engineering work. It is a shared workbook so that multiple people can work at any time and is working well. Recently people have been losing their work because something is removing the shared status from the file during the day. It is only discovered when people go to save their work or open the workbook again after lunch. Is it possible to write a macro that records when the shared feature is applied or removed. I assume it is a property of the workbook collection/object but cant see how to do it from the docs
Take a look at the Workbook.MultiUserEditing property.
This code snippet (from Microsoft help) would save the workbook in shared mode if the workbook is not currently in shared mode.
If Not ActiveWorkbook.MultiUserEditing Then
ActiveWorkbook.SaveAs fileName:=ActiveWorkbook.FullName, _
accessMode:=xlShared
End If
You may consider placing this code (or similar) in the Workbook_BeforeClose() event or other relevant event.

How to allow users to quit out of long-running VBA tasks?

I have a routine that examines thousands of records looking for discrepancies. This can take upwards of 5 minutes to complete and although I provide a progress bar and elapsed time count, I'm not sure I want to encourage folk pressing ctrl-break to quit the report should it be taking longer than expected.
A button in the progress bar won't work as the form is non-modal, so is there any neat way of allowing users to quit in this situation?
You need DoEvents and a variable whose scope is greater than the scope of what you're running. That is, if it's just a procedure, you need a module level variable. If it's more than one module, you need a global variable. See here
Stopwatch at DDoE
Normally, the VB engine will tie up the processor until it's done. With DoEvents, however, VB allows the processor to work on whatever is next in the queue, then return to VB.
I don't think there is a way to do it like you would want it to work. VBA is a scripting language so when you start your procedure, it's gonna run until it's done. If you had another button somewhere that even WOULD let you click it while the original procedure was running, I'm not sure how you would reference that procedure and stop it.
You could do something like ask the user if they want to contine, but that would make it run even longer.
Also you could have your procedure check for a condition outside of Excel and keep running as long as it's true. Something easy might be check if a certain text file is in a folder. If you wanted the procedure to stop, open the folder and move the file. On your loop's next iteration, it wouldn't see the file and stop running. Cludgy, inefficient, and not elegant, but it would work. You could also have it check a cell, checkbox, radiobutton, basically any control in another Excel sheet running in another instance of Excel. Again cludgy.
CTRL+Break works. Accept it and move on. One neat trick about that though, is that if you password protect your code and they hit CTRL+Break, the debug option is unavailable and they will only get Continue or End.
If this is code that is run frequently, have you considered scripting something that runs it during times when a human is not using the computer? I used to run telnet screen scraping macros that would take hours to go through our widgets, but I always had them run either on a separate computer or when I wasn't there (nights/weekends).