WScript.Shell.run on multi thread - vba

I would like to know if there is a way to run a shell window on another thread than excel ?
The idea is to not freeze excel when I launch a shell.
for instance :
Dim objShell
Set objShell = WScript.CreateObject ("WScript.shell")
objShell.run "cmd /K CD C:\ & Dir"
Set objShell = Nothing
Thanks for your help

I would agree and disagree with Praktik. I agree because you cannot create a real multi-threaded application in Excel. I disagree because you could use kind of trick to execute another script.
Let's assume you have a vbs script called "Script.vbs". Then you could code sth like this taskId = Shell("wscript Script.vbs parms"). The script would run independently from your VBA program. But may that's not what you want.

The answer would be NO, you cannot create multi-threaded application in Excel.
However you can use Application.ScreenUpdating = False to lock the current Excel Workbook.
Make sure you enable it back at the end of the code like so Application.ScreenUpdating = True
Hope that helps!

Related

How to get a variable from cmd and show it in vbscript - Vbscript

i'm a beginner of vbscript , i need you help , my question is how do i do to get a variable from cmd and show it in vbscript for example get a ping from www.google.com and show it in a msgbox in vbscript help me code :
dim cmd,x
set cmd = createobject("wscript.shell")
x= cmd.run("cmd /k ping www.google.com ",1,true)
Get that output and show it in a msgbox later , help me
Here an example of how to do that.
The response of the ping that is checked is in Dutch but that doesn't matter for your case.
Set objExec = CreateObject("WScript.Shell").exec("ping www.google.com")
With objExec
Do While .Status = 0
WScript.Sleep 10
Do While Not .StdOut.AtEndOfStream
WScript.Echo .StdOut.ReadLine
'Check the .StdErr to see if it is at the end of its
'stream. If not, call ReadLine on it
If Not .StdErr.AtEndOfStream Then
.StdErr.ReadLine
End If
Loop
Loop
End With
An advise though, don't begin scripting in vbscript, it's a dead end.
Choose some modern scripting language like Python or still better for beginners: Ruby.
Be sure to use cscript as engine in stead of wscript, execute the following to set that as default.
wscript //H:Cscript
Your vbscript is then one single line
puts `ping www.google.com`

Unused function in Excel addin causes crash only on second run and when run via VBScript

Sorry for the long title.
I have several .xlsm files which share a lot of code, so I moved the repeated parts to an addin .xlam file. I have been using a .vbs script to open all the files one after another and run a macro in each.
Problem
The problem I'm facing is that on the second run of the .vbs script, excel crashes and gives what seems to be a very generic error, said here to be an "Automation Error":
Script: C:\Users\~\Desktop\test\test.vbs
Line: 5
Char: 1
Error: The server threw an exception.
Code: 80010105
Source: (null)
To my surprise, I was able to reproduce this crash even after removing 99% of the content of my files.
test.vbs:
Dim xlApp
Dim xlBook
Set xlApp = CreateObject("Excel.Application")
Set xlBook = xlApp.Workbooks.Open("C:\Users\~\Desktop\test\test.xlsm")
xlApp.Run "Auto.Run" '<~~ error on this line
xlBook.Save
xlBook.Close (True)
xlApp.Quit
Set xlBook = Nothing
Set xlApp = Nothing
test.xlsm:
test.xlam has a module Module1, test.xlsm has a Module Auto and a Reference to test.xlam
test.xlsm, Auto:
Sub Run()
MsgBox "hello"
Test.Load
MsgBox "goodbye"
End Sub
test.xlam, Module1
Sub Load()
MsgBox "Load"
End Sub
Function Other()
End Function
With the function Other() commented out, the code works fine (saying hello, load and goodbye). It also works fine if the macro is run from within excel. Only when Other() is present, and Run() is run through the .vbs file is there an error (right after hello).
Workaround
If I open test.xlsm, save it, and close it again in between each run of test.vbs, there are no problems. I believe this has something to do with the addin, rather than the spreadsheet, because in my original script, which opened multiple excel files, only one file needs to be opened and saved.
I also noticed that the excel file is a little bigger in its "problem" state, and that once I open and save it, it returns to its slightly smaller original size. (EDIT: This is at least partly caused by new cache streams __SRP_4 and __SRP_5 inside the vbaProject.bin file, which I extracted using this answer (oh, and this). After manually deleting all SRP entries, I was able to run the .vbs script again without problems, although just like the open-save-close strategy, it's only temporary, and will then crash on the third run rather than the second.)
Question
Are addins not appropriate for shared code? May they not contain functions? Is there any way to work around this crash besides what I'm doing right now?
Any thoughts are appreciated.
It sounds to me like the first instance isn't being unloaded/released before the second instance is being called. Perhaps using the Application.Wait Method to wait a few seconds before each subsequent run in performed might help?
'Open file1
'Run macro from file1
'Close file1
Application.Wait(Now + TimeValue("0:00:10")) 'wait 10 seconds
'Open file1
'Run macro from file1
...
...
So on
To install your add-in to excel via vbscript you can use the following code
'Launch Excel
set objExcel = createobject("Excel.Application")
strAddIn = "ESP Assistant.xlam"
'~~> Path where the XLAM resides
SourcePath = "Your source path\" & strAddIn
'Add the AddIn
On Error Resume Next
With objExcel
'Add Workbook
.Workbooks.Add
'Show Excel
objExcel.Visible = True
.AddIns.Add(SourcePath, False).Installed = True
End With
If this fails you might have to clear your registry values first, then rerun the above script
'File to use just in case Add-In installation fails
'Refreshes Excel Registry Entries to allow for clean install of Add-In
Dim objFSO, objShell
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objShell = WScript.CreateObject ("WScript.shell")
objShell.Run "cmd /c ""C:\Program Files (x86)\Microsoft Office\Office14\excel.exe"" /unregserver && timeout /t 3 && tskill excel && ""C:\Program Files (x86)\Microsoft Office\Office14\excel.exe"" /regserver",1,True
Set objFSO = Nothing
Set objShell = Nothing
x=msgbox("Excel registry refreshed." ,0, "Registry Update")
wscript.quit
Unfortunately, I still don't know why this is happening, but I found an automated solution that I'm going to stick with.
As I mentioned in my question, the test.xlsm file was a little bigger in its "problem" state, due at least partially to some kind of cache, of which I could only find one offical mention here:
2.2.6 SRP Streams
Streams that specify an implementation-specific and version-dependent performance cache. MUST be
ignored on read. MUST NOT be present on write.
The name of each of these streams is specified by the following ABNF grammar:
SRPStreamName = "__SRP_" 1*25DIGIT
My solution was to remove the cache, which I did manually at first with this tool. When that seemed to work, I wrote a Java program to do it automatically (gist here). It's glue between java.util.zip and Apache POIFS.
I also added a line to call the Java at the end of the .vbs script:
CreateObject("WScript.Shell").Run "java -jar clear-excel-cache.jar C:\Users\~\Desktop\test\test.xlsm", 1, false
In my actual .vbs file, which calls multiple excel files in a loop, this line is just inside the loop. There is a little cmd window that opens after each file is run but it no longer crashes on the second run, so I'm calling that a success.
Your issue could be the same issue which I am trying to resolve - Random 64-bit Excel 2013 VBA crashes (VBE7.dll errors). You can check the Application Event logs for a VBE7.dll crash to confirm this.
In my case various XLSM files become intermittently corrupted through manual use.
My fix as an alternative to yours is the following VBS (anything to trigger a VBA "recompile").
Resave "myfile.xlsm"
Sub Resave(filename)
Set objExcel = CreateObject("Excel.Application")
currentDirectory = left(WScript.ScriptFullName,(Len(WScript.ScriptFullName))-(len(WScript.ScriptName)))
objExcel.Application.AutomationSecurity = 3 ' Disable to avoid crash
objExcel.Application.enableevents = False
objExcel.Application.Workbooks.open(currentDirectory + "\" + filename)
objExcel.Application.Visible = True
objExcel.Application.DisplayAlerts = False
Set objSheet = objExcel.ActiveWorkbook.Sheets.Add
objSheet.Delete
objExcel.Application.DisplayAlerts = True
objExcel.Application.enableevents = True
objExcel.ActiveWorkbook.Save
objExcel.ActiveWorkbook.Close
objExcel.Application.Quit
Set objExcel = Nothing
End Sub
FYI - Microsoft released a patch which fixes the issue in Excel 2013 on 3rd May 2016.
https://support.microsoft.com/en-us/kb/3085486

How to prevent Excel Add-In file from changing cell references to R1C1/columns to letters

I created an Excel Add-In file (.xlam) to be able to distribute my macro the my department. However, I'm faced with an issue that I can't seem to find when I search the web for answers. When I add and install the Add-In file to Excel (via vbscript, if that matters) it sets Excel to R1C1 mode, so the columns are numbered instead of lettered. Any idea what might be causing this? Could it something in the vbscript or Add-In files that trigger this change? Has anyone ever had this happen to them before when deploying an Add-In for Excel? How do I prevent it?
Try to look if you changing Application.ReferenceStyle = xlR1C1
If not, i would try to insert
Dim previousRefStyle
previousRefStyle = Application.ReferenceStyle
Application.ReferenceStyle = xlA1
and on the begining
Application.ReferenceStyle = previousRefStyle
So user will have restored original settings
Okay, it appears I found the actual solution from this link, where Post #10 on that forum thread points to Tip 2 in this link.
The steps are as follows:
Exit Excel (After adding the Add-In)
Click Start > Run > Enter Excel.exe /UnregServer
Wait for Excel to finish opening again
Exit Excel (again),
Click Start > Run > Enter Excel.exe /RegServer
What this does is cleans (un-registers & re-registers) the registry. I hope this will save even one person the hours of googling and forum surfing that it took me to finally stumble upon the actual solution, instead of just a workaround.
Update to include VBScript Implementation Example:
To accomplish the above steps using VBScript (below script adapted from here), you can use code similar to this (changing your path to your Excel.exe of course)
Dim objFSO, objShell
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objShell = WScript.CreateObject ("WScript.shell")
objShell.Run "cmd /c ""C:\Program Files (x86)\Microsoft Office\Office14\excel.exe"" /unregserver && timeout /t 3 && tskill excel && ""C:\Program Files (x86)\Microsoft Office\Office14\excel.exe"" /regserver",1,True
Set objFSO = Nothing
Set objShell = Nothing
x=msgbox("Excel registry refreshed." ,0, "Registry Update")
wscript.quit
Disclaimer:
As #Rory points out below (see link in his comment), according to Microsoft's documentation those switches don't work from versions dated 2010 on. Though there are many instances of people citing that they have used this method with 2010 or later versions with success (see links in my comments) I figured I would just make whoever is reading this aware that it is a now-unsupported method by Microsoft. However, if it works for you and your situation (as many unsupported features of Microsoft often do) feel free to still use it.

Toggle Windows Explorer "Work Online" mode via Excel VBA

I have written several VBA macros that access intranet network locations. They work well when users are located on-site. However, when they are off-site and accessing the network via VPN, these network locations are not available unless they manually navigate to them via Windows Explorer and select the "work online" option at the top of the explorer window.
I can already verify whether they are connected via VPN programmatically.
What I need is to be able to perform the equivalent of activating "work online" mode via Excel VBA.
Any suggestions from the hive mind?
Didn't have any success via Google or existing SO posts.
Simplest approach would be to trap the error when the folder cannot be accessed, display a message box to inform the user of the required option, and use Shell command to open the Windows Explorer:
Dim Foldername As String
Foldername = "\\UNCPATH\TO\NETWORK_DRIVE\"
Shell "C:\WINDOWS\explorer.exe """ & Foldername & "", vbNormalFocus
Alternatively, you may be able to get this to work, although I could not, it was too long to post as a comment so I will include the procedure here:
Sub fnOfflineStatusVB()
Dim objShell As Object 'IShellDispatch5
Dim objFolder As Object 'Folder3
Set objShell = CreateObject("Shell.Application")
Set objFolder = objShell.Namespace("\\UNCPATH\TO\NETWORK_DRIVE\")
If (Not objFolder Is Nothing) Then
Dim nReturn As Integer
nReturn = objFolder.OfflineStatus()
End If
Set objFolder = Nothing
Set objShell = Nothing
End Sub
Found it here
Found PowerShell commands that can be executed through CMD successfully. Roadblock that remains is integrating these into a batch file.
These CMD commands work when entered manually:
powershell.exe -noexit "$oWMI=[wmiclass]""\\localhost\root\cimv2:win32_offlinefilescache"""
$oWMI.TransitionOnline("<path here>")
Where "Path here" in angle brackets is an UNC path on my network. However, executing them in a batch file has been unsuccessful thus far.
Here is the code:
#ECHO ON
::Move to non UNC Path directory
cd /D "C:\"
powershell -NoExit -Command "& {'$oWMI=[wmiclass]''\\localhost\root\cimv2:win32_offlinefilescache';$oWMI.TransitionOnline('<Path here>')}"
:: Pause CMD window to give user confirmation that execution has occurred
PAUSE
If anyone has any suggestions on the issue, your advice would be greatly appreciated.
Running PowerShell scripts directly are not permitted due to Group Policy set by IT, which is the reason for this round-about method.

Calling an External VBA from VBScript

I am using a program called mathtype to pull some equation objects out of a word document. I've written code in VBA that works perfectly using their API, but I have to translate it to a VBScript file. I have looked all over google, but have not found any solution on how (If it is even possible) to call a VBA library from VBScript.
VBScript can't see the MathTypeSDK Objects/Functions.
If not possible, how would I encase the macro I need to run in a globally available word file and call it from the VBScript?
Edit: Got it! Unfortunately the approaches below, while helpful, did not work for my situation. I found something closer: Embedding the macro in a global file and calling it through the Word Objects Run command.
objWord.Run "Normal.NewMacros.RunMain"
Here is an approach which might work for you. I tested this simple example.
Class "clsTest" in file "Tester.docm":
Public Sub Hello()
MsgBox "Hello"
End Sub
Class "Instancing" is marked "PublicNotCreatable".
Module in "Tester.docm":
Public Function GetClass() As clsTest
Set GetClass = New clsTest
End Function
In your vbscript:
Dim fPath, fName
fPath = "C:\Documents and Settings\twilliams\Desktop\"
fName = "Tester.docm"
Dim wdApp, o
Set wdApp = CreateObject("word.application")
wdApp.visible=true
wdapp.documents.open fPath & fName
Set o = wdApp.Run("GetClass")
o.Hello
Set o=nothing
Again - I only tested this simple example: you'll have to adapt it to your situation and try it out.
Word-VBA was not made to create reusable libraries, I suppose (for usage in external programs).
One way to reuse existing Word-VBA code is, however, run Word via WScript.Shell.Run using the /m<macroname> command line switch (see http://support.microsoft.com/kb/210565/en-us for details). This, has the restriction that evertime you need to call a specific macro, a Word process is started again, running that macro, and ends afterwards. Means, if you need just one call to your Word.VBA for a specfific task, this may be ok, but if you need a lot of interprocess communication between your VBScript and your VBA macro, you should look for a different solution.