Run VBScript Consecutively - vbscript

I have on Outlook rule that kicks off a batch statement which starts a VBScript that then kicks off other VBScripts based on the sender and subject heading. If two emails from the same sender come into the in box simultaneously, it will start the first instance correctly. However, the second will kick off and return an error stating "Permission denied". I would like to run each email consecutively.
I have already tried the sleep functions and other time bound delays, but the query times are not consistent due to the size of the data.
Here is the basic script I have been using.
Set olApp = CreateObject("Outlook.Application")
Set olMAPI = olApp.GetNameSpace("MAPI")
Set oFolder = olMAPI.Folders("FieldFinanceAutomatedReports#xxxxx.com").Folders("Inbox").Folders("Requested Report People")
Set allEmails = oFolder.Items
Set firstemail = allEmails.GetLast
unreadCount = 0
For Each email In oFolder.Items
If email.Unread = True Then
If email.Sender= "Sender_of_email#email.com" Then
Set objcreate = CreateObject("Scripting.FileSystemObject")
Set objread = objcreate.OpenTextFile("C:\path_to_script\script.vbs")
request = objread.ReadAll()
objread.Close
Set objread = Nothing
Execute request
Set request = Nothing
Set objcreate = Nothing
End If
WScript.Sleep 500
If email.Sender = "Another_Sender_of_email#email.com" Then
Set objcreate = CreateObject("Scripting.FileSystemObject")
Set objread = objcreate.OpenTextFile("C:\path_to_script\another_script.vbs")
another_request = objread.ReadAll()
objread.Close
Set objread = Nothing
Execute another_request
Set another_request = Nothing
Set objcreate = Nothing
End If
WScript.Sleep 500
unreadCount = unreadCount + 1
End If
Next
I would like for each of the instances to wait until it the first process is complete.

What you need is called a semaphore or mutex. Essentially that's a resource that can be held by only one process or thread at a time. In VBScript you could implement that by attempting to create a (temporary) file. First process to do that wins, subsequent attempts will fail because the file already exists.
Set sh = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")
filename = sh.ExpandEnvironmentStrings("%TEMP%\mutex.txt")
'this will throw an error if the file is already opened
Set f = fso.OpenTextFile(filename, 2, True)
Note that you need to open the file for writing (second parameter set to 2). Opening it for reading (second parameter set to 1) does not suffice.
Run the operation in a loop and you can wait for another process to finish and release the mutex.
Set sh = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")
filename = sh.ExpandEnvironmentStrings("%TEMP%\mutex.txt")
On Error Resume Next
Do
Err.Clear
Set f = fso.OpenTextFile(filename, 2, True)
If Err Then WScript.Sleep 100
Loop While Err
On Error Goto 0
Make sure you remove the file when your script leaves the critical section (or terminates), otherwise other scripts might have trouble acquiring the mutex later. A convenient way of doing this is to implement the mutex as a class. That way it will automatically be cleaned up, even if the script should terminate unexpectedly.
Class Mutex
Private f_
Private Sub Class_Initialize
Set sh = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")
filename = sh.ExpandEnvironmentStrings("%TEMP%\mutex.txt")
On Error Resume Next
Do
Err.Clear
Set f_ = fso.OpenTextFile(filename, 2, True)
If Err Then WScript.Sleep 100
Loop While Err
On Error Goto 0
End Sub
Private Sub Class_Terminate
f_.Close
End Sub
End Class
You use that Mutex class in your code like this:
Set m = New Mutex 'acquire mutex
'...
'critical section goes here
'...
Set m = Nothing 'release mutex

Related

Waiting while files are zipped in VBScript [duplicate]

I am using VBscript to scan folders, create zip files and add files to them (compress), but as I run my script on folders with a lot of files, I get the following error: "Compressed (zip) Cannot create output file"
my zip handling code is as follows:
Dim objFSO
Set objFSO= CreateObject("Scripting.FileSystemObject"
Function PreformZip(objFile,target,zip_name, number_of_file)
Set shell = CreateObject("WScript.Shell")
zip_target = target + "\" + zip_name +".zip"
If Not objFSO.FileExists(zip_target) Then
MakePathIfNotExist(target)
NewZip(zip_target)
Else
If number_of_file=0 Then
objFSO.DeleteFile(zip_target)
NewZip(zip_target)
End if
End If
Set zipApp = CreateObject("Shell.Application")
aSourceName = Split(objFile, "\")
sSourceName = (aSourceName(Ubound(aSourceName)))
zip_file_count = zipApp.NameSpace(zip_target).items.Count
zipApp.NameSpace(zip_target).Copyhere objFile, 16
On Error Resume Next
sLoop = 0
Do Until zip_file_count < zipApp.NameSpace(zip_target).Items.Count
Wscript.Sleep(100)
sLoop = sLoop + 1
Loop
On Error GoTo 0
End Function
Sub NewZip(zip)
Set new_zip = objFSO.CreateTextFile(zip)
new_zip.Write Chr(80) & Chr(75) & Chr(5) & Chr(6) & String(18, 0)
new_zip.Close
Set new_zip = Nothing
WScript.Sleep(5000)
End Sub
Function MakePathIfNotExist(DirPath)
Dim FSO, aDirectories, sCreateDirectory, iDirectory
Set FSO = CreateObject("Scripting.FileSystemObject")
If FSO.FolderExists(DirPath) Then
Exit Function
End If
aDirectories = Split(DirPath, "\")
sCreateDirectory = aDirectories(0)
For iDirectory = 1 To UBound(aDirectories)
sCreateDirectory = sCreateDirectory & "\" & aDirectories(iDirectory)
If Not FSO.FolderExists(sCreateDirectory) Then
FSO.CreateFolder(sCreateDirectory)
End If
Next
End Function
Function Recursion(DirectoryPath)
Dim FSO : Set FSO = CreateObject("Scripting.FileSystemObject")
If FSO.FolderExists(DirectoryPath) Then Exit Function
Call Recursion(FSO.GetParentFolderName(DirectoryPath))
FSO.CreateFolder(DirectoryPath)
End Function
I first thought I'm not waiting long enough after creating the zip, but I even tried it with 10 seconds wait after each zip and I still get the same error.
How can I solve it?
If there is no solution, is there an alternative way to make a zip? The script is not only for my own use so I don't want ro relay on a software which needs to be installed?
Although Folder.CopyHere method does not return a value and no notification is given to the calling program to indicate that the copy has completed, you could wait with next code snippet and I hope you can see proper (re)placement in your script:
On Error GoTo 0
zipApp.NameSpace(zip_target).Copyhere objFile _
, 4 +8 +16 +256 +512 +1024
Wscript.Sleep( 100)
On Error GoTo 0
Notice: no waiting Do..Loop, this Wscript.Sleep( 100) is sufficient to zip small files or start progress dialog box in case of huge files - and your script will wait for it...
Notice: no 'On Error Resume Next. Avoid invoking On Error Resume Next if you do not handle errors...
Flags used as follows.
Const FOF_SILENT = &h0004 'ineffective?
Const FOF_RENAMEONCOLLISION = &h0008 'ineffective?
Const FOF_NOCONFIRMATION = &h0010 '
Const FOF_SIMPLEPROGRESS = &h0100 'ineffective?
Const FOF_NOCONFIRMMKDIR = &h0200 '
Const FOF_NOERRORUI = &h0400 '
Unfortunately, in some cases, such as compressed (.zip) files, some option flags may be ignored by design (sic!) by MSDN!
If FOF_SILENT flag ineffective, then user could Cancel zipping process...
If FOF_RENAMEONCOLLISION flag ineffective, then newer file of the same name is not zipped, existing zip file keeps previous version without caution against; only existing folder brings on an extra error message...
Those could be fixed up as well, but it's subject of another question...
Well, after a great amount of research I found out that there is no possible way to fix this problem when using shell to perform zip.
I solved this issue by using za7.exe (7-zip) in the following way:
Dim zipParams
zipParams = "a -tzip"
Dim objShell: Set objShell = CreateObject("WScript.Shell")
command = zip_exe_location + " " + zipParams + " " + zip_target + " " + SourceFile
objShell.Run Command, 0 ,true
the "a" in the zip parameters means "add to file" and -tzip sets the type of the file as zip.

Retrieving an argument of a VBScript

How do I pass and return arguments from a VBScript WITHOUT using cscript.exe?
For example, I want to call script2 from script1 that returns a value to script1 without any involvement of cscript.exe.
I have searched various answers but they somehow involve the usage of cscript.exe.
This script gets installed voices and sets the one provided in the file voice.txt.
Set WshShell = CreateObject("WScript.Shell")
WShShell.CurrentDirectory = "..\Confirmatory Texts"
Set FSO = CreateObject("Scripting.FileSystemObject")
If FSO.FileExists("voice.txt") Then
Set temp = FSO.OpenTextFile("voice.txt")
confirm_voice = temp.ReadLine()
temp.Close
Set Sapi = CreateObject("SAPI.SpVoice")
For Each Voice In Sapi.GetVoices
i = i + 1
Next
For loopvar = 0 To i-1
If loopvar = CInt(confirm_voice) Then
Set Sapi.Voice = Sapi.GetVoices.Item(loopvar)
End If
Next
Else
WScript.Echo "An Error Occured"
End If
If I call this script from another script, how can I make this script to return some value to the script that invoked it?
VBScript doesn't really provide call or import mechanisms for other VBScript files. The closest thing is to read the contents of the other file and run them via ExecuteGlobal.
Demonstration:
Put the following two files in the same directory and run script1.vbs. It will read the contents of script2.vbs and make the function Square available in the global scope by running the code via ExecuteGlobal. Once the function is available in the global scope you it can be used in the rest of the script.
script1.vbs:
Set fso = CreateObject("Scripting.FileSystemObject")
dir = fso.GetParentFolderName(WScript.ScriptFullName)
script = fso.BuildPath(dir, "script2.vbs")
ExecuteGlobal fso.OpenTextFile(script).ReadAll '"import" code into global scope
WScript.Echo Square(3)
script2.vbs:
Function Square(i)
Square = i*i
End Function

measuring print job time programmatically

I need to measure print job time, which means time between 'send print command' and 'print job disappear from the print queue'
so I am trying to do these things by script
search all pdf files
print a file
get the print time (as above)
go to next file and do all above for all files
this is my work so far(i omit some parts)
For Each file In objFolder.Items
' check for the extension
if objFSO.GetExtensionName(file.name) = "pdf" then
' invoke to print
file.InvokeVerbEx( "Print" )
' select print jobs
Set Printers = objWMIService.ExecQuery _
("Select * from Win32_PrintJob")
For Each objPrinter in Printers
DateTime.Value = objPrinter.TimeSubmitted
TimeinQueue = DateDiff("n", actualTime, Now)
Wscript.Echo TimeinQueue
Next
end if
next
mainly i need to ask how can i get the time when print job disappear from the print queue.
And I need to keep next job till one print job ends.
any ideas ?
There's no simple way to obtain that information from inside your script, because when the job is removed from the print queue it's gone. You could set up an event monitor for the print spooler like this:
Set wmi = GetObject("winmgmts://./root/cimv2")
Set wbemDateTime = CreateObject("WbemScripting.SWbemDateTime")
qry = "SELECT * FROM __InstanceOperationEvent WITHIN 1 " & _
"WHERE TargetInstance ISA 'Win32_PrintJob'"
Set mon = wmi.ExecNotificationQuery(qry)
Do
Set evt = mon.NextEvent
If evt.Path_.Class = "__InstanceDeletionEvent" Then
wbemDateTime.Value = evt.TargetInstance.TimeSubmitted
WScript.Echo evt.TargetInstance.Document & ": " & _
DateDiff("n", wbemDateTime.GetVarDate, Now)
End If
Loop
However, you'd have to run that from a different script, because VBScript doesn't support multi-threading (i.e. running things in parallel), so the event handler loop would block the rest of your script operations.
If you want a rough value from within your script, you could try something like this, but don't expect queue times to be very accurate:
'singleton SWbemDateTime instance for time conversions
Set wbemDateTime = CreateObject("WbemScripting.SWbemDateTime")
'global list to keep track of printing documents
'MUST NOT BE MODIFIED FROM ANYWHERE EXCEPT CheckPrintQueue!
Set printingDocs = CreateObject("Scripting.Dictionary")
Function CheckPrintQueue
Set printJobs = objWMIService.ExecQuery("SELECT * FROM Win32_PrintJob")
Set currentDocs = CreateObject("Scripting.Dictionary")
'get currently printing jobs from queue
For Each job In printJobs
currentDocs.Add job.Document, job.TimeSubmitted
Next
'compare global list to current list, print the queue time for documents
'that are no longer queued, and remove them from the global list
For Each doc In printingDocs.Keys
If Not currentDocs.Exists(doc) Then
wbemDateTime.Value = printingDocs(doc)
WScript.Echo doc & ": " & DateDiff("n", wbemDateTime.GetVarDate, Now)
printingDocs.Remove doc
End If
Next
'add new documents to global list
For Each doc In currentDocs.Keys
If Not printingDocs.Exists(doc) Then printingDocs.Add doc, currentDocs(doc)
Next
CheckPrintQueue = printJobs.Count
End Function
For Each file In objFolder.Items
If objFSO.GetExtensionName(file.name) = "pdf" Then
file.InvokeVerbEx "Print"
CheckPrintQueue
End If
Next
'wait until all jobs finished printing
Do While CheckPrintQueue > 0
WScript.Sleep 100
Loop
I was looking for exact thing and I simply summurized ansgar's script to get what I want.
its take every pdf files and print 5 times each(so i can get a avarage which better) while getting the time.finally print them in to a csv file. and untill one going to desapear from the print queue others wait waits. Not 100% acuurate but enough for the perfomance comparision
may be it may usefull (please correct my mistakes if any)
Dim objFSO, outFile
const forAppending = 8
const useDefault = -2
'Output File name
fileName = ".\output.csv"
Set objFSO = CreateObject("Scripting.FileSystemObject")
set outFile = objFSO.OpenTextFile(fileName, forAppending, true, useDefault)
'write heading to output file
outFile.WriteLine("Filename,1,2,3,4,5")
'get current Folder
strFolder = Replace(wscript.scriptfullname,wscript.scriptname,".\")
strFolder = objFSO.getFolder(strFolder)
'Open the Shell Folders object
Set objShell = CreateObject( "Shell.Application" )
'Create an object for the specified file's parent folder
Set objFolder = objShell.Namespace( strFolder )
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
For Each file In objFolder.Items
If objFSO.GetExtensionName(file.name) = "pdf" Then
'write in to out file
outFile.write(file.name)
outFile.writeLine ""
'outer loop for 5 times per each file
index = 0
do while index < 5
'if already doing a printing do not start a new one
if CheckPrintQueue() = 0 then
file.InvokeVerbEx "Print"
startTime = timer
waitTostart
'if first time on outer loop it should be empty queue
else
if index = 0 then
wscript.echo "queue doesnt empty"
finish
end if
end if
'wait until all jobs finished printing
waitToEnd
'count time
ellapsTime = timer - startTime
'write in to out file
outFile.write(",")
outFile.Write(ellapsTime)
index = index + 1
loop
End If
Next
outFile.Close
'----------------function CheckPrintQueue-----------------------
Function CheckPrintQueue()
Set printJobs = objWMIService.ExecQuery("SELECT * FROM Win32_PrintJob")
Set currentDocs = CreateObject("Scripting.Dictionary")
CheckPrintQueue = printJobs.Count
End Function
'----------------end of function-----------------
sub waitTostart
Do
WScript.Sleep 100
'check print queue
printJobCount = CheckPrintQueue()
Loop until printJobCount > 0
end sub
sub waitToEnd
Do
WScript.Sleep 100
'check print queue
printJobCount = CheckPrintQueue()
Loop until printJobCount = 0
end sub
sub finish
outFile.Close
objFSO = nothing
objFolder = nothing
objShell = nothing
objWMIService = nothing
wscript.quit 1
end sub

How to use filesystem as semaphore

I have several script trying to access the clipboard. Only, one script at a time can access the clipboard at a time. My solution did not work. Here is the solution I implemented
check if clipboardLock.txt exists.
-if it does not exist then create it
--do processes
-if it does exist then wait 3 seconds to 10 seconds and check if it exists
This did not work well because two scripts tried to create the file and errored out. Is there a technique to guarantee only one script can access the clipboard? Also, I do not have access to a database.
Instead of having the scripts create a file, have them open an existing file in exclusive mode (that is, no one else can open it). If the file opens processing can proceed, otherwise the script must wait.
In order to open the file exclusively, you can use OpenTextFile to open it for writing:
Const ForReading = 1, ForWriting = 2, ForAppending = 8
Set MyFile = fso.OpenTextFile(FileName, ForWriting)
Once the processing is complete, close the file so that other scripts can attempt to open the file.
Using your method, vbscript does not block on ForWriting and wait until the file is closed. Launch the following script twice ... first leaving the msgbox "File Open..." open ... then launch again. You'll get "Permission Denied" and the second script will break. On Error Resume Next will defeat the objective of waiting for the file to become available before proceeding.
Const ForReading = 1, ForWriting = 2, ForAppending = 8
Set filesys = CreateObject("Scripting.FileSystemObject")
Set filetxt = filesys.OpenTextFile("c:\somefile.txt", ForWriting, True)
wscript.echo "File Open..."
filetxt.Close
wscript.echo "Done..."
So I see there are 4 upvotes ... how did this possibly work?
Here's a working routine - just sit in while loop until the file becomes available:
lockFile
sub lockFile ()
Dim fso, LockFile, LockFileName, done
Const ForWriting = 2
LockFileName = "C:\somefile.lck"
Set filesys = CreateObject("Scripting.FileSystemObject")
done = false
on error resume next 'need to evaluate error
while (not(done))
err.clear
Set filetxt = filesys.OpenTextFile(LockFileName, ForWriting, True)
if (err.number = 0) then
done = true
else
done = false
end if
wscript.echo "Error [0=file open, 70=file in use] : " & err.number
wscript.sleep(1000) 'wait one second instead of chewing up CPU
wend
wscript.echo "File Open..."
filetxt.Close
wscript.echo "Done..."
on error goto 0 'reset error level
end sub

Opening Word from VBScript hangs, can't figure out why

I'm not really a programmer by trade, so forgive me if I'm not aware of any standard debugging tools.
I have what I thought was a very simple VBScript (just a txt file saved with a .vbs extension):
Const wdDoNotSaveChanges = 0
Const wdRevisionsViewFinal = 0
Const wdFormatPDF = 17
Dim arguments
Set arguments = WScript.Arguments
Function DOC2PDF(sDocFile)
Dim fso ' As FileSystemObject
Dim wdo ' As Word.Application
Dim wdoc ' As Word.Document
Dim wdocs ' As Word.Documents
Set fso = CreateObject("Scripting.FileSystemObject")
sDocFile = fso.GetAbsolutePathName(sDocFile)
sPdfFile = fso.GetParentFolderName(sDocFile) + "\" + fso.GetBaseName(sDocFile) + ".pdf"
Set wdo = CreateObject("Word.Application")
Set wdocs = wdo.Documents
Set wdoc = wdocs.Open(sDocFile)
if fso.FileExists(sPdfFile) Then
fso.DeleteFile sPdfFile, True
End If
Set wview = wdoc.ActiveWindow.View
wview.ShowRevisionsAndComments = False
wview.RevisionsView = wdRevisionsViewFinal
wdoc.SaveAs sPdfFile, wdFormatPDF
wdo.Quit wdDoNotSaveChanges
Set fso = Nothing
Set wdo = Nothing
End Function
however, the following line is causing huge grief:
Set wdoc = wdocs.Open(sDocFile)
Sometimes the Word ActiveX object just freezes at this step. I've verified this by some super-simple debugging by putting a WriteLine after each line and seeing where it stops.
Word just sits there consuming 100% CPU, and the script never gets past that step.
How can I go about debugging to find out what the hell is going on with the Word ActiveX object and why it's just hanging and never returning?
Word might be waiting for a prompt from you. I would make Word visible and see if you can visually see what the problem is:
Set wdo = CreateObject("Word.Application")
'if memory serves, this should make Word visible
wdo.Visible = true
Set wdocs = wdo.Documents

Resources