I'm using VBscript to open Microsoft Excel and convert xls documents to csv.
Here is a quick example that takes an argument and converts the first page
Dim oExcel
Dim oBook
Set oExcel = CreateObject("Excel.Application")
Set oBook = oExcel.Workbooks.Open(Wscript.Arguments.Item(0))
oBook.SaveAs "out.csv", 6
oBook.Close False
oExcel.Quit
If everything works, that's great. But when the script crashes before it can close excel, the process continues to stay around and lock the file until I manually kill the process.
How can I make sure that I perform any clean up routines even when the script fails?
As the question is/was? about closing Excel reliably when the VBScript used to automate it crashes:
If you write
Set oExcel = CreateObject("Excel.Application")
you create a 'simple' variable. VBScript may decrement a ref counter when the variable goes out of scope, but it certainly won't .Quit Excel for you.
If you want a feature like atexit calls or exception handling in VBScript, you'll have to write a class that 'does what I mean' in its Class_Terminate Sub. A simple example:
Option Explicit
Class cExcelWrapper
Private m_oExcel
Public Sub Class_Initialize()
Set m_oExcel = CreateObject("Excel.Application")
End Sub
Public Sub Class_Terminate()
m_oExcel.Quit
End Sub
Public Default Property Get Obj()
Set Obj = m_oExcel
End Property
End Class
Dim oExcel : Set oExcel = New cExcelWrapper
Dim oWBook : Set oWBook = oExcel.Obj.WorkBooks.Add()
oExcel.Obj.Visible = True
oExcel.Obj.DisplayAlerts = False
oWBook.Sheets(1).Cells(1,1) = "Div by Zero"
WScript.Echo "Check TaskManager & Enter!"
WScript.StdIn.ReadLine
WScript.Echo 1 / 0
(meant to be started with "cscript 20381749.vbs")
If you run this script with an open Taskmanager, you'll see Excel popup in the processes list (and on the screen, because of .Visible). If you then hit Enter, the script will abort with an "Division by Zero" error and the Excel process will vanish from the Processes list.
If you remove the .DisplayAlerts setting, Excel will ask you whether to save your work or not - proving thereby that the .Quit from the Class_Terminate() Sub really kicks Excel into byebye mode.
The class needs further work (basic settings, common actions (save?) before .Quit, perhaps a guard against misuse (Set oExcel = Nothing or other cargo cult crap), th .Obj addition isn't nice, and it won't help you if you kill your .vbs in a debugger, but for standard scenarios you won't see Excel zombies anymore.
Add "On Error Goto ErrorHandler" at the top of your script, and at the bottom of it, add "ErrorHandler:". Underneath the ErrorHandler label add code to manage the situation depending on the Err.Number
See Err object on MSDN.
You can also use "On Error Resume Next". Here is an example.
EDIT: My bad. "Goto" does not exist in VBS. The answer below is probably a much tidier approach.
Related
So I'm creating a library of commonly used macros for excel for my Excel 2011 for Mac. I'm running a couple of tests to make sure it's working properly. The biggest problem I'm running into is that when I run the code (despite having ScreenUpdating set to False), it visibly opens the library file while it runs the macro.
To make sure this is efficient going forward, I want to prevent that from happening. Here is currently what my code looks like:
Sub findRow()
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.EnableEvents = False
Dim bk As Workbook
Dim result As Long
Dim aWB As Workbook
Dim aWS As Worksheet
Set aWB = ActiveWorkbook
Set aWS = ActiveWorkbook.ActiveSheet
With Application
.ScreenUpdating = False
.DisplayAlerts = False
.EnableEvents = False
Set bk = .Workbooks.Open _
(ActiveWorkbook.Path & ":library.xlsm")
'Set bk = ActiveWorkbook.Path & ":library.xlsm"
End With
'Workbooks.Open (ActiveWorkbook.Path & ":library.xlsm")
'Workbooks.Open (bk)
result = Application.Run("'" & ActiveWorkbook.Name & "'!findFirstEmptyRow", aWS)
ActiveWorkbook.Close
Cells(result, 1) = result
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Application.EnableEvents = True
End Sub
There is quite a few redundancies in there as well as commented out lines that show various things I've tried. Originally I simply ran code that set Application.X settings to False, opened the Workbook, ran the macro, and closed.
Hopefully someone can come up with a reason for ScreenUpdating not taking effect when I open a new Workbook.
Setting ScreenUpdating to false only prevents spreadsheet updates from showing. It wont on its own hide the opening of a workbook. Although there are ways to hide the opening of a workbook, or access workbook data without creating an instance of Excel, for the task you're describing of creating a VBA library, the way to go about that is to create an Excel add-in. An add-in not only allows you to use a macro from any instance of Excel, but you can also edit your code and add new macros to a common repository from any instance also.
I am new to VBscript and am looking to write a simple script that changes a couple cells in a few thousand csv files in a given folder location. I have a good start, and it all seems to be working, except for the fact that when I run the script (from a .bat file that just calls the script) it only changes and moves 3-8 files at a time. The number of files it changes is random, its not like it always changes 5 files or something. I am not sure what is wrong in the code as to why it will not edit and move every single file and only does a couple at a time, here is what I have so far, thanks in advance for any help:
Set objFSO = Wscript.CreateObject("Scripting.FileSystemObject")
Set colFiles = ObjFSO.GetFolder("C:\Users\xxx\BadCSVs").Files
Set xl = CreateObject("Excel.Application")
For Each objFile in colFiles
If LCase(objFSO.GetExtensionName(objFile)) = "csv" Then
Set wb = xl.Workbooks.Open(objFile)
Set sht = xl.ActiveSheet
If(sht.Cells(1,11) <> "") Then
sht.Cells(1,8) = sht.Cells(1,8) & sht.Cells(1,9)
sht.Cells(1,9) = sht.Cells(1,10)
sht.Cells(1,10) = sht.Cells(1,11)
sht.Cells(1,11) = Null
wb.Save
wb.Close True
Else
'if file is opened up and has only 10 columns of data, then it makes no changes, then closes it.
wb.Close
End If
End If
Next
xl.Quit
Your EVIL global
On Error Resume Next
hides errors. Disable/Remove it and test again.
Your
wb.Close SaveChanges=True
passes the boolean result of comparing SaveChanges (undefined/empty) vs. the boolean literal True. Perhaps you copied VBA code
wb.Close SaveChanges:=True
(mark the colon) that is not legal in VBScript?
And
Set xl = CreateObject("Excel.Application")
should be paired with an
xl.Quit
If you invoke Excel in the loop, terminate it there. I would try to start/quit Excel out of the loop, but you should test that approach carefully.
we have an application that runs MS Word (hidden) to print documents.
If one of the printers has a problem, then Word hangs while waiting for the spooler to return the 'queued' message.
We have found that if we make Word visible (by using VBA in Excel with GetObject and oWordApp.visible=true for example) then the process continues printing the other documents with no problem.
We would like to make this more automatic by having a VBScript check for Word in running processes, if it finds it, make it visible, wait for a few seconds, hide it, and quit...
But I have a problem that the VBScript GetObject function instantiates Word if it's not already running.
how should I check that word is running using VBScript without creating an instance of it?
here is the code I have in my VBScript file:
dim oWord, WScriptShell
set oWord = getobject("", "Word.Application")
set WScriptShell = CreateObject("WScript.Shell")
if isobject(oWord) then 'and oWord.Documents.count>0
wscript.echo("Word is running")
oWord.visible=true
WScript.Sleep 1000
oWord.visible=false
else
wscript.echo("Word not running")
end if
so what should I use to check if word is running without creating an instance of it?
Just change GetObject("", "Word.Application") to GetObject(, "Word.Application") and it doesn't start an instance.
But this throws an error if Word is not running, so you can use it like this:
msgbox wordIsRunning
function wordIsRunning
dim wdApp
on error resume next
set wdApp = GetObject(, "Word.Application")
wordIsRunning = (err.Number = 0)
set wdApp = nothing
end function
Word hangs
check "DisplayAlerts" property. allowed only if property "Visible" set to "True".
tasklist /FI "IMAGENAME eq winword.exe"
will show you, does m$word running or not
I am using the below vb script to un-zip the files, so while un-zipping is going on, i am seeing a pop up messgae(Copying/extracting), is there any way to get rid of popup message?
FileToGetUnZipped = "InstallDir\UI_Files.zip"
DestPathForUnzippedFile = "InstallDir\system"
Set objFSO = CreateObject("Scripting.FileSystemObject")
If Not objFSO.FolderExists(DestPathForUnzippedFile) Then
objFSO.CreateFolder(DestPathForUnzippedFile)
End If
UnZipFile FileToGetUnZipped, DestPathForUnzippedFile
Sub UnZipFile(strArchive, DestPathForUnzippedFile)
Set objApp = CreateObject( "Shell.Application" )
Set objArchive = objApp.NameSpace(strArchive).Items()
Set objDest = objApp.NameSpace(DestPathForUnzippedFile)
objDest.CopyHere objArchive
End Sub
The CopyHere method takes a second argument which can be a combination of various options, including
(4)
Do not display a progress dialog box.
However, I have not had much success on getting many of these options to work reliably - I think it varies by Windows version as much as anything else.
As a side note, I think you may have issues with the CopyHere method being asynchronous - your script may complete before CopyHere does, which may kill the copying process.
I wrote a VBScript app to open Word and Excel documents and search and replace blocks of text and various sections, pulling the new text from a plain text file. I purposely avoided any error checking, primarily because I couldn't figure it out at the time (and the script ran reliably anyway). Now months later on my local machine, I am inexplicably getting error messages about Normal.dot being changed and a message box asking what I want to do about it (which requires three more dialogs to finally answer). Of course this kills my ability to run the script and simply walk away, as it causes the script to fail. Currently when this happens, I have to open the Task Manager, find Winword.exe (of which the GUI isn't running) and kill it then re-run my script.
What's a reasonable way of catching the error and successfully shutting down Word (or Excel). Based on this question I'm trying this:
Set objDoc = objWord.Documents.Open(curDir1 + "\docs\template_spec.dot")
If Err.Number <> 0 Then
WScript.Echo "Error in Word Open:" & Err.Description
objWord.Quit
Else
Set objSelection = objWord.Selection
'Do replacement activities'
ReplaceText(objSelection)
objDoc.SaveAs(curDir1 + "\docs\mynewdocument.doc")
objWord.Quit
End If
Set objShell = Nothing
Set objWord = Nothing
Set objExcel = Nothing
Of course, as fate would have it, I cannot replicate the problem, so it works like normal. Does this solution seem reasonable? And a side question: How the heck do I get Word to stop complaining about Normal.dot (or get the script to handle it)? It's as if Word leaves itself open in the background after I have closed the GUI in some cases.
have you considered wrapping everything into an 'On Error Resume Next' statement so that your script ignores all the errors and continues to run as much as possible before calling the objWord.quit regardless of success or fail.
if you want more information on the correct use of 'On Error Resume Next' then go over to the msdn article on it!
Hope this helps!
Paul
I'm afraid that
WScript.Echo "..."
if it ever fires, is going to stall your script. Other than that, everything looks right. I'll play with it when I get home.
Edit: Word does hang out in the background, quite frequently. For one thing, if you use Outlook, and use Word as your Outlook editor, Word won't go away until Outlook is gone.
I'd agree with the use of "on error resume next".
If you really need to forcefully terminate Word, you can use WMI and the Win32_Process class to find and kill the process. This should be a last resort if everything else fails.
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
Set colProcess = objWMIService.ExecQuery("Select * from Win32_Process Where Name = 'winword.exe'")
For Each objProcess in colProcess
objProcess.Terminate()
Next
This was a modified example from:
http://www.computerperformance.co.uk/vbscript/wmi_process_stop.htm
Also, make sure all your references to the Word automation object are closed and/or set to nothing before you terminate the process.
The most reliable way to terminate all ActiveX instances, clean up garbage, and release resources is to put the code for that purpose into Sub Class_Terminate() of a dummy class, created instance of the class allows to handle script quit event.
Option Explicit
Dim objBeforeQuitHandler, objWord
' create a dummy class instance
Set objBeforeQuitHandler = New clsBeforeQuitHandler
' create word app instance
Set objWord = CreateObject("Word.Application")
objWord.Visible = True
objWord.Documents.Add.ActiveWindow.Selection.TypeText "80040000 error was raised. About to terminate the script." & vbCrLf & "Word will be quitted without saving before script termination just you close popped up error message."
' your code here...
' raise an error
Err.Raise vbObjectError
Class clsBeforeQuitHandler
' dummy class for wrapping script quit event handler
Private Sub Class_Terminate()
Dim objDoc
On Error Resume Next ' to prevent errors in case of unexpected word app termination
If TypeName(objWord) <> "Object" Then ' word app has not been closed yet
objWord.DisplayAlerts = False
For Each objDoc In objWord.Documents
objDoc.Saved = True ' to prevent save as dialog popping up
objDoc.Close
Next
objWord.Quit
End If
End Sub
End Class