How to detect Word.Application.SaveAs timeouts? - windows

I have a VBS script that converts a bunch of word (doc, dox, and docm) and powerpoint (pptx) files into PDFs, via Word and PowerPoint application objects and argument SaveAs.
That works fine, but for reasons unknown the processing fails for some Word files (guess: the files were created with another office version, or have some corruption). SaveAs runs indefinitely, with Word running using 100% of a CPU core, until I kill it manually.
After the SaveAs operation has ran for a programmed amount of time (say 5 minutes), I'd like to:
Abort the current SaveAs operation.
Raise an error.
I could then dispatch an error message via mail, then proceed to the next file (by specifying On Error Resume Next).
This code excerpt shows how I convert Word files to PDF in the script:
Dim oWord, Pres
Set oWord = CreateObject("Word.Application")
Set Pres = oWord.Documents.Open("/path/to/file.docm", , , msoFalse)
Pres.SaveAs "/path/to/file.pdf", 17, True 'runs forever on certain files
Pres.Close 0
How can I abort the current SaveAs operation after a specified delay has elapsed?

Related

Solving "unable to connect to the specified server" error in Diadem DataFileHeaderAccess

I am currently using Diadem to process a large amount of data.
There is a specific treatment that I must do on a large number of files. Therefore, I have a script loading each file one by one to do this every time.
The thing is, after several hours of computation, I get an error : Incorrect instruction or user command. In <DataFileHeaderAccess.VBC> (line:1328, column:5): Unable to conect to the specified server.
By this time, it will have successfully passed the portion of code where it happens several times, and if I launch it back on the file that has issues, it will not fail (not for this file at least).
Even more strange is that nothing is done remotely there, so I have no idea which server it might be talking about. And the file is ot opened elsewhere. Most of the time, it happens when I'm not even in the office.
And finally, I managed to find nothing anywhere regarding this issue, And I'm growing quite desperate to manage to solve it.
So ... Simple question ... "Help ?".
Well, let's develop it a little :
What might be the cause of this issue ?
How can I solve it ?
Here is the portion of code incriminated if it might help :
Function TryLoadGroup(sPath, sFileName, sGroupName, sNewGroupName)
Dim oDataFileHeader, oImportedGroup
Set oDataFileHeader = DataFileHeaderAccess(sPath & sFileName, "TDM", True)
Dim iLoop, bRet
For iLoop = 1 To oDataFileHeader.GroupCount
If oDataFileHeader.GroupNameGet(iLoop) = sGroupName Then
bret = True
End If
Next
oDataFileHeader.Close(False)
If bRet Then
Set oImportedGroup = DatafileLoadSel(sPath & sFileName,"TDM", sGroupName & "/*")
oImportedGroup.Item(1).Name = sNewGroupName
Set TryLoadGroup = oImportedGroup
Else
Set TryLoadGroup = Data.CreateElementList
End If
End Function
Set oDataFileHeader = DataFileHeaderAccess(sPath & sFileName, "TDM", True)
The error message just means that it is not capable to open the file.
There are some things I can think of
The file is corrupt (but this seems not to be true because you can open it)
The file is opened by DIAdem or a group of it is already loaded into DIAdem
DIAdem has run out of memory
Potentially you should put an error handler arround your inner loop
on error goto 0
' call a method
if 0 <> err.number then
LogFileWrite "Unable to insert file '" & filename & "': " & err.description
end if
on error goto 0
This will allow you to go on processing and see the error in the DIAdem Logfile later on.

Return to an already open application when a user tries to open a new instance in vb6

Suppose a user minimize my visual basic application to the taskbar notification icon. Now I want when user open a new instance, the old one should restore.
Generally, the strategy used to create a single-instance application is to add some code to the application initialization that determines whether an instance is already running. If one is, it gets a handle to its main window, passes the focus to it, and silently dies. If one is not, it continues to run and completes the rest of the initialization sequence as usual.
You'll find lots of old VB 6 articles that accomplished this by iterating through all of the top-level windows, looking for one whose caption matches the one you expect. But this is a pretty fragile solution, it doesn't take very much to throw it off.
Same deal with the App.PrevInstance property. This is very simple to use, but also very simple in its implementation. It works based on the name of the executable and looks for a running process whose name is a match. However, this is easily defeated if a user creates and renames a copy of the executable. If this is acceptable for you, you could implement this very easily by querying the App.PrevInstance property. Otherwise, you'll need to use a more robust solution.
One such possibility is to create and register a named mutex when the first instance of your application is starting up. Then, when subsequent instances try to register that same mutex, they will fail, indicating that an instance is already running. You can find instructions on using mutexes in VB 6 in the answers to this question.
A couple of important caveats to using mutexes:
You need to make sure that you call the ReleaseMutex and CloseHandle functions when your application is closed in order to release ownership of and destroy the mutex that you created.
When you are running your program in the VB 6 IDE (e.g., to debug it) and it registers a mutex, the mutex belongs to the IDE and won't be released until you close the IDE and restart it. To prevent this, you can suppress the creation of the mutex when running inside of the IDE/debugger using conditional compilation. If you take this approach, make sure to test your program outside of the debugger to be sure that the mutex-related functionality is working as expected! You should never ship something to customers that you haven't thoroughly tested.
You can find all of the VB 6 declarations for these Windows API functions by using the API Viewer program that comes bundled with your VB 6 installation.
More information about handling multiple instances of a VB 6 application is available here on Karl Peterson's site. There's also a complete example implementation in this article on VB Accelerator—focus specifically at step 2, you don't need the rest of the code.
You can often do this fairly simply using DDE in a degenerate way:
Form1.frm
Option Explicit
'This is Form1. To use as DDE source at design time we set:
' Form1.LinkMode = 1 (Source, i.e. vbLinkSource).
' Form1.LinkTopic = "Form1" (default).
'
'Note we use (hidden) Label1 on this Form as a DDE destination.
Private PrevState As Integer
Private Sub Form_LinkExecute(CmdStr As String, Cancel As Integer)
'Got a "command" so restore Form1 and accept the command.
WindowState = PrevState
Caption = "I am awake!"
Cancel = False
End Sub
Private Sub Form_Load()
PrevState = WindowState
End Sub
Private Sub Form_Resize()
If WindowState <> vbMinimized Then PrevState = WindowState
End Sub
Module1.bas
Option Explicit
Private Sub Main()
Load Form1
'After Form1 is loaded (hidden), try DDE link to possible prior copy.
With Form1.Label1
.LinkTopic = App.EXEName & "|Form1"
On Error Resume Next
.LinkMode = vbLinkManual
If Err.Number = 0 Then
On Error GoTo 0
'Link succeeded. Wake up prior copy via pushback to
'the DDE source, then unload Form1 and terminate.
.LinkExecute "Wake up!"
Unload Form1
Else
On Error GoTo 0
'Link failed, so we're 1st. Show Form1.
Form1.Show vbModal
End If
End With
End Sub

how to kill orphaned winword.exe in matlab

Running matlab R2010B on Windows 7 Enterprise
In matlab scripts, I save a bunch of results to a word file and then at the end, close and quit word. The code I use is:
WordFname = ['BatInfoDoc' sprintf('%0.3f',now) '.doc']; % serialnumbered filenames
WordFile = fullfile(pwd,WordFname);
WordApp = actxserver('Word.Application');
WordDoc = WordApp.Documents.Add;
WordDoc.SaveAs2(WordFile);
....
WordApp.Selection.TypeText([title2 title3 title4 title5 title6]);
WordApp.Selection.TypeParagraph;
then finally at the end of the script
WordDoc.Close;
WordApp.Quit;
The problem I have is that through my development process, I often crash the matlab script and wind up leaving orphaned WINWORD.EXE processes, each one of which keeps a lock on the file it had been writing.
Up until now I have been using TaskManager to kill these processes one at a time by hand. Having been developing all morning, I find myself with around 20 files I can't delete because they are locked by about 11 orphaned WINWORD.EXE processes!
My question(s):
1) Is there an elegant way to handle the file writing and saving and closing and so on so I don't lock up files and processes when my script crashes out before I get to the part where I close the file and quit word?
2) Is there an elegant way to determine the bad processes from within matlab script and go through and delete them from within a matlab script? That is, can I code my matlab so it cleans up after itself?
ADDED A FEW MINUTES LATER:
By the way, I would prefer NOT to enclose all my code in a big try-catch and then close the windows after I've caught my error. The problem with this is I do like to go to debug mode on error, and caught errors don't bring me to debug mode.
Straight after you create Wordapp, use c = onCleanup(#()Wordapp.Quit). When your function exits, either naturally or with a crash, c will be deleted and its function will execute, quitting Word. If this is part of a script rather than a function, you can manually delete c to quit.
Also - while developing/debugging, I would set Wordapp.Visible to true so you can manually close word if necessary. Set back to false for production.
Use a handle class to delete them automatically.
classdef SafeWord < handle
properties(Access=public)
WordApp;
end
methods(Access=public)
function this = SafeWord(WordApp)
this.WordApp= WordApp;
end
function delete(this)
this.WordDoc.Close;
this.WordApp.Quit;
end
end
end
And the use case:
sw = SafeWord(Word.Application());
WordApplication = sw.WordApp;
% Do something here
% When sw ends its lifecycle, it calls delete.
Here is a related question.

how to get the dsf code below to work?

I wish to send input to multiple emulated mice using dsf(device simulation framework) incuded in the current windows ddk. My code is below the problem is device manager recogizes the device got added but my program (mouse mischief - also microsoft) doesnt create the addtional pointer like its supposed to:
WriteLine "Create first input report to send to the consumer control"
Dim strMessage
strMessage = "Press Enter key to stop looping"
WriteLine strMessage
Do While NOT WScript.StdIn.AtEndOfLine
Dim InputReport1(4)
InputReport1(0) = CByte(0)
InputReport1(1) = CByte(100)
InputReport1(2) = CByte(100)
InputReport1(3) = CByte(0)
InputReport1(4) = CByte(0)
'PromptAndWaitForEnterKey "Queue input reports for processing"
GenericHIDDev.QueueInputReport(InputReport1), 10
'PromptAndWaitForEnterKey "Wait for the device to finish enumerating. Press enter to start processing input reports."
GenericHIDDev.StartProcessing
'WriteLine "You may send additional input reports at this time..."
'PromptAndWaitForEnterKey "Press enter at any time to stop processing input reports and start cleanup."
GenericHIDDev.StopProcessing
Input = WScript.StdIn.Read(1)
Loop
Note: this is the only section i modified of the TestGenericHid sample included with the dsf in the windows ddk(device driver kit). After install of windows ddk go to c:\Program Files\dsf\GenericHid or something similar to access the vbscript file.
Need mousmischief and windows ddk to fully understand whats going on and to correctly answer this. Dont worry all samples are in vbscript and can be redone in vb.net but i dont wish to waste time converting until i get the vbscript to work.
I have since I posted this got it working with multiple mouse and within 2-3 months time will have a nice beta upload of my whole kinectmultipoint project here:
http://kinectmultipoint.codeplex.com
The code above is in a zip file at the address above in the preceding sentence.

Is there any way to tell if the Windows Directory is writeable without actually writing to it to test?

I have some old vb6 code that checks to see if the Windows directory is writeable by WRITING to it then reading a value back.
But... we have a virus scanner that's viewing that as suspicious behavior so I want to check it without touching it.
Any Windows API calls for that? (Ideally for Win 98 and above)
Something to remember here is that the file system is volatile. About the only way I can see this code being used is to first do a check if a folder is writable, and then try to write something you wanted to write. The problem here is that with a volatile file system things might change in between when you make your check and when you try to write. As a consequence, you still have to be able to handle an exception if your write fails. That means the initial check is pretty much wasted. Better to put your effort into writing a better exception handler.
Additionally, for windows 2000 and later the Windows directly should only ever be writable if the user is running as an administrator. For a very long time running as an administrator was common practice, but people are starting to get the hint that this isn't a good idea. Long term, it's not a good idea for your program to do anything that requires running that way.
In fact, starting with Windows Vista, the user doesn't run anything as administrator by default, even when logged in to the administrator account. Instead, they have to manually choose to run the program as administrator or wait a security check to fail the system can prompt them to elevate.
If you have the VB6 code, you should take the time to fix it so that it does NOT need to write to the Windows directory at all because regardless of whether or not you are an administrator - unless you work at Microsoft you should consider that directory off limits.
However, you should consider that on Windows 98, the user will always have the ability to write to the Windows directory. On Windows XP, local administrators will. On Windows Vista and Seven, even administrators will not unless your application has been elevated.
So you can check for whether or not the user is in the built-in role BUILTIN\Administrators using CheckTokenMembership. This will be false for non-admins or non-elevated processes. It does not guarantee you can write to the Windows directory but it will be right most of the time. You can then add error handling logic for when the call actually fails.
But again, you should take the opportunity to fix the code and not use the Windows directory.
For Windows 2000 and above you could use GetNamedSecurityInfo() and AccessCheck(), but I would imagine those are a pain to call from VB.
Here is a function that will do it. I adapted this from some other code kind of quickly so if you use it you need to add error handling, (for instance a directory that doesn't exist just returns False. I have no idea if your anti-virus software is going to like this or not.
Function FolderIsReadOnly(ByVal FolderSpec As String) As Boolean
Dim rst As Long
Dim udtW32FindD As WIN32_FIND_DATA
Dim lngFHandle As Long
Dim strFolder As String 'set to FolderSpec parameter so I can change it
If Len(FolderSpec) = 0 Then
FolderIsReadOnly = False
Exit Function
End If
strFolder = FolderSpec
If Right$(strFolder, 1) <> "\" Then
strFolder = strFolder & "\"
End If
strFolder = strFolder & "*" 'add the wildcard allows finding share roots
lngFHandle = FindFirstFile(strFolder, udtW32FindD)
If lngFHandle <> INVALID_HANDLE_VALUE Then
Call FindClose(lngFHandle)
FolderIsReadOnly = (udtW32FindD.dwFileAttributes And FILE_ATTRIBUTE_READONLY) = FILE_ATTRIBUTE_READONLY
End If
End Function
Function IsPathAccessible(ByVal sPath As String) As Boolean
On Error GoTo ErrHandler
FileSystem.SetAttr sPath, vbNormal
IsPathAccessible = True
Exit Function
ErrHandler:
IsPathAccessible = False
End Function

Resources