Unable to use function while monitoring folder - vbscript

I'm trying to monitor a specific folder, for a file creation using VBS.
This is the monitor creation function of the folder, as I've seen in many examples:
Function CreateMonitor(path)
Set wmi = GetObject("winmgmts://./root/cimv2")
Set fso = CreateObject("Scripting.FileSystemObject")
path = Split(fso.GetAbsolutePathName(path), ":")
drv = path(0) & ":"
dir = Replace(path(1), "\", "\\")
If Right(dir, 2) <> "\\" Then dir = dir & "\\"
query = "SELECT * FROM __InstanceOperationEvent" & _
" WITHIN " & Interval & _
" WHERE Targetinstance ISA 'CIM_DataFile'" & _
" AND TargetInstance.Drive='" & drv & "'" & _
" AND TargetInstance.Path='" & dir & "'"
Set CreateMonitor = wmi.ExecNotificationQuery(query)
End Function
Then I save it to the following var:
Set monitor = CreateMonitor(FolderPath)
Eventually I use the following loop which will run endlessly (monitor) and will create an instance when a file in the folder was created :
Do
Set evt = monitor.NextEvent()
Select Case evt.Path_.Class
Case "__InstanceCreationEvent"
Call SendNotification (evt.TargetInstance)
End Select
Loop
After a successful monitoring process creation I'm willing to move further with the file that has been created and send it to "SendNotification" function.
The problem is that the calling of the function doesn't occurs and I'm finding myself stuck in the loop without entering this function. It waits until a file is being created and only then proceed further.
What am I doing wrong? Whats is the proper way for a function call in this case?

Return value If the NextEvent method is successful, it returns an SWbemObject object that contains the requested event. If the call
times out, the returned object is NULL and an error is raised.
Error codes Upon the completion of the NextEvent method, the Err object may contain the error code in the following list.
wbemErrTimedOut - 0x80043001 Requested event did not arrive in the amount of time specified in iTimeoutMs
https://msdn.microsoft.com/en-us/library/aa393711(v=vs.85).aspx
Said that, I would try If evt Is Nothing Then Exit Loop as follows
Do
Set evt = monitor.NextEvent()
If evt Is Nothing Then Exit Do
Select Case evt.Path_.Class
Case "__InstanceCreationEvent"
call SendNotification (evt.TargetInstance)
End Select
Loop
Or
On Error Resume Next
Do
Set evt = monitor.NextEvent()
If Err <> 0 Then
If Err.Number = wbemErrTimedout Then ' = &H80043001
Exit Do
Select Case evt.Path_.Class
Case "__InstanceCreationEvent"
call SendNotification (evt.TargetInstance)
End Select
Loop

Related

VBScript watchdog [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I am trying to create a script that will look at a specific .txt file on the local computer, get it's DateLastModified attribute, and compare it to the last/previous value when it was last checked (in a loop, every 1-2 seconds).
The loop (running every second) would increment a counter, and if the counter reaches a limit (say 10 seconds), a section of code would perform a task of "kill/terminate" a specific process/.exe that is likely hung up, and restart it.
I've found some pretty good samples of .vbs online that use subscription events, I don't think this is the route I want/need to go, as the script actually needs to be continuously running and not asynchronously only when the specific file is modified.
Edit: I am looking for a VBScript that provides a "watchdog" function, by monitoring a .txt file for modifications. The script should run a loop every second that checks for modifications, and if no modifications, increments a counter. Once the counter reaches a limit (10 seconds?) it would terminate a fixed process (hardcoded as a parameter in the VBScript), and then relaunch the process (path to the process as a parameter in the VBScript).
I haven't found a good example to share thus far. I've been playing with other examples that utilize the objWMIService.ExecNotificationQuery(Query) which seems cool in that little overhead is used (asynchronous in nature) -- it does not appear to fit my needs as described above.
If I must share what I've found and been toying with... OK... here it is:
intInterval = "1"
strDrive = "C:"
strFolder = "\\Project\\"
strComputer = "."
intTmrVal = 0
Set objWMIService = GetObject( "winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2" )
strQuery = "Select * From __InstanceOperationEvent" _
& " Within " & intInterval _
& " Where Targetinstance Isa 'CIM_DataFile'" _
& " And TargetInstance.Name='C:\\Project\\test.txt'"
Set colEvents = objWMIService.ExecNotificationQuery(strQuery)
Do
Set objEvent = colEvents.NextEvent()
Set objTargetInst = objEvent.TargetInstance
Select Case objEvent.Path_.Class
Case "__InstanceCreationEvent"
WScript.Echo "Created: " & objTargetInst.Name
Case "__InstanceDeletionEvent"
WScript.Echo "Deleted: " & objTargetInst.Name
Case "__InstanceModificationEvent"
Set objPrevInst = objEvent.PreviousInstance
For Each objProperty In objTargetInst.Properties_
If objProperty.Value <> objPrevInst.Properties_(objProperty.Name) Then
WScript.Echo "Changed: " & objTargetInst.Name
WScript.Echo "Property: " & objProperty.Name
WScript.Echo "Previous value: " & objPrevInst.Properties_(objProperty.Name)
WScript.Echo "New value: " & objProperty.Value
End If
Next
End Select
'Count how many times it has been modified // just playing with a counter
If objEvent.TargetInstance.LastModified <> objEvent.PreviousInstance.LastModified Then
intTmrVal = intTmrVal+1
WScript.Echo "Changed: " & intTmrVal & " times"
WScript.Echo
End If
Loop
An easy way is to directly set the timeout in the WMI request for the next event
Option Explicit
Const MONITOR_FILE = "c:\temp\test.txt"
Const MONITOR_LIMIT = 10
Dim wmi
Set wmi = GetObject( "winmgmts:" & "{impersonationLevel=impersonate}!\\.\root\cimv2" )
Dim query
query = "SELECT * FROM __InstanceOperationEvent WITHIN 1 " _
& " WHERE TargetInstance ISA 'CIM_DataFile' " _
& " AND TargetInstance.Name='" & Replace(MONITOR_FILE, "\", "\\") & "'"
Dim colEvents
Set colEvents = wmi.ExecNotificationQuery( query )
Dim currentEvent
Do
' Flag value
Set currentEvent = Nothing
' Try to get the next event with a timeout limit
' If a timeout happens we need to catch raised error
On Error Resume Next
Set currentEvent = colEvents.NextEvent( MONITOR_LIMIT * 1000 )
On Error GoTo 0
' If there is not an event there was a timeout
If currentEvent Is Nothing Then
Exit Do
End If
Loop
WScript.Echo "File has not been changed for " & MONITOR_LIMIT & " seconds."
Event watchers react to events when they occur. You, however, want to react to events that are not occurring (namely a file not getting modified). You cannot use an event watcher for that for obvious reasons.
What you need is code that checks the file's last modified date
path = "C:\Project\test.txt"
Set fso = CreateObject("Scripting.FileSystemObject")
ts = fso.GetFile(path).DateLastModified
and then kills a given process if that timestamp is more than x seconds/minutes
pname = "foo.exe"
wmi = GetObject("winmgmts://./root/cimv2")
qry = "SELECT * FROM Win32_Process WHERE Name='" & pname & "'"
If DateDiff("s", ts, Now) > 10 Then
For Each p In wmi.ExecQuery(qry)
p.Terminate
Next
End If
However, the Windows Task Scheduler doesn't support your desired granularity of running the task every second (or every 2 seconds). And even if it did, spawning a new process every second wouldn't be very good for system performance in the first place. You can set a daily schedule and then instruct Task Scheduler to re-run the task every 5 minutes for the duration of a day. The rest you need to account for in your script.
Define a 5 minute timeout and re-run your check in a loop until that timeout expires:
interval = 2 'seconds
timeout = 5 'minutes
endtime = DateAdd("n", timeout, Now)
sleeptime = interval * 1000 'milliseconds
Do
'check and process handling go here
WScript.Sleep sleeptime
Loop Until Now >= endtime
Since fso and wmi can be re-used don't waste resources by re-defining them over and over again inside the loop. Define them just once at the beginning of the script.

VBS - How to wait for a Windows Settings App to finish?

I'm building a custom setup for a set of Windows10 tablets by VBS scripts.
This setup is a sequence of calls that open some Windows Settings apps like the following:
start ms-settings:dateandtime
start ms-settings:camera
....
I'd like that, of course, each command waits the end of previous one.
If I use the
shell.run("ms-settings:dateandtime")
command with the wait set, I receive the error 'Unable to wait for process'.
If i run the command:
shell.exec("start ms-settings:dateandtime /wait")
I recevice the error: the system cannot find the file specified.
The same if i use the .Run command.
Not sure this will work as is in a tablet (I can only test on desktop), but you can use it as a starting point
Option Explicit
Call ShowSettingsAndWait ( "ms-settings:dateandtime" )
Function ShowSettingsAndWait( setting )
' Default return value
ShowSettingsAndWait = False
' Resolve executable file and start required setting panel
Dim executable
With WScript.CreateObject("WScript.Shell")
executable = Replace( _
.ExpandEnvironmentStrings("%systemroot%\ImmersiveControlPanel\SystemSettings.exe") _
, "\", "\\" _
)
Call .Run( setting )
End With
' Wait for the process to start
Call WScript.Sleep( 500 )
' Instantiate WMI
Dim wmi, query
Set wmi = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
' Search for SystemSettings executable
Dim process, processID
query = "SELECT * FROM Win32_Process WHERE ExecutablePath='" & executable & "'"
processID = - 1
For Each process In wmi.ExecQuery( query )
processID = process.processID
Next
' If not found, leave
If processID < 0 Then
Exit Function
End If
' Request process termination events
Dim events
query = "Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA 'Win32_Process'"
Set events = wmi.ExecNotificationQuery( query )
' Wait for the process to end
Dim lastEvent
Do While True
WScript.Echo "."
Set lastEvent = events.NextEvent
If lastEvent.TargetInstance.ProcessID = processID Then
Exit Do
End If
Loop
' Done, everything was
ShowSettingsAndWait = True
End Function

VBScript not running depending on the web server

I've a very strange problem with a VBScript: it works fine depending on the server where the web is located.
I have the same web application on two servers with IIS 7.5. In each server, the code is exactly the same.
The VBScript executes some Excel lines and it updates some information at the web application. The problem comes when updating this information. In one of the servers, there's no problem, but in the other, I get the error below:
As the script runs as it would do, I guess there's no syntax error
The code throwing the error is:
With objIE
If (testing) Then
.Navigate URLtesting
Else
.Navigate URL
WScript.Sleep 2000
End If
WaitWebLoad()
.document.getElementById(user).Value = userScript
.document.getElementById(password).Value = passwordScript
.document.getElementById(logInButton).Click()
WaitWebLoad()
.document.getElementByID(loretoLink).Click()
WaitWebLoad()
.document.getElementByID(updatePLLink).Click()
WaitWebLoad()
Do
lastRow = activeSheet.Cells(rowIndex, columnPL_ID).Value
dateAssociated = activeSheet.Cells(rowIndex, columnArrivalDate).Value
concesion = activeSheet.Cells(rowIndex, columnConcesion).Value
If lastRow <> "" Then
.document.getElementByID(searchPL_TB).Value = lastRow
.document.getElementByID(searchPL_Button).Click()
WaitWebLoad()
If (Not (.document.getElementByID(errorLookingPL)) Is Nothing Or Not (.document.getElementByID(noSearchResult) Is Nothing)) Then
'PL not found
recordsNotFound = recordsNotFound + 1
WriteOpenFileText(logFilePath), (Now & vbTab & "***PL not found: " & lastRow & " - [" & dateAssociated & "]" & " - " & concesion & vbCrLf)
Else ...
The line number 295 is:
If (Not (.document.getElementByID(errorLookingPL)) is Nothing Or Not (.document.getElementByID(noSearchResult) is Nothing)) Then
The WaitWebLoad function code is:
Function WaitWebLoad()
Dim timer
timer = 0
With objIE
Do While .Busy
If timer < timerWebLoad Then
WScript.Sleep 100
timer = 100
Else
'Error loading the web
objExcel.Workbooks.close
objExcel.quit
objIE.quit
DeleteFile(pathTemp)
endScriptStamp = Now
WScript.Echo("Error." & vbCrLf & "Total runtime is: " & DateDiff("s", startScriptStamp, endScriptStamp) & " seconds.")
WScript.quit
End If
Loop
End With
End Function
I guess the problem is that the script, when running the server where the error takes place, is losing the objIE object, but I don't know why it's becoming lost only in one of the servers.
The getElementById method returns the first object with the same ID attribute as the specified value, or null if the id cannot be found. Reference: .NET Framework and Document Object Model (DOM).
On the other side, Is operator compares two object reference variables and Null is not an object; it indicates that a variable contains no valid data.
Moreover, if used in VBScript, getElementById method could raise 800A01A8 (=decimal -2146827864) Microsoft VBScript runtime error: Object required if used improperly: for instance, as you can't write Set objAny = Null!
Here's a workaround:
On Error Resume Next ' enable error handling
Set oerrorLookingPL = .document.getElementByID(errorLookingPL) ' try
If Err.Number <> 0 Then Set oerrorLookingPL = Nothing ' an error occurred?
Err.Clear
If Vartype(oerrorLookingPL) = 1 Then Set oerrorLookingPL = Nothing ' Null => Nothing
Set onoSearchResult = .document.getElementByID(noSearchResult)
If Err.Number <> 0 Then Set onoSearchResult = Nothing
Err.Clear
If Vartype(onoSearchResult) = 1 Then Set onoSearchResult = Nothing
On Error Goto 0 ' disable error handling
If (Not (oerrorLookingPL Is Nothing) Or Not (onoSearchResult Is Nothing)) Then
I can't recommend global use of On Error Resume Next as If condition Then … statement always evaluates given condition to True if a runtime error occurs while evaluating it, see next example:
On Error Resume Next
' show Variant subtype information about Null and Nothing
Wscript.Echo VarType(Null) & " Null " & TypeName(Null)
Wscript.Echo VarType(Nothing) & " Nothing " & TypeName(Nothing)
' all conditions as well as their negations are evaluated to `True`
if (Null = Nothing) then Wscript.Echo " Null = Nothing"
if NOT (Null = Nothing) then Wscript.Echo "not (Null = Nothing)"
if (Null is Nothing) then Wscript.Echo " Null is Nothing"
if NOT (Null is Nothing) then Wscript.Echo "not (Null is Nothing)"
' show runtime error
On Error GoTo 0
if (Null is Nothing) then Wscript.Echo " Null is Nothing"
Both Null = Nothing and Null is Nothing conditions are evaluated to True as well as their negations!
==> cscript D:\VB_scripts\SO\37563820.vbs
1 Null Null
9 Nothing Nothing
Null = Nothing
not (Null = Nothing)
Null is Nothing
NOT (Null is Nothing)
==> D:\VB_scripts\SO\37563820.vbs(12, 1) Microsoft VBScript runtime error: Object required

Access VBA to Close a Chrome window opened via Shell

I am attempting to close a shell Chrome window via a VBA function. My function runs a URL query that returns a .csv file. The thing is I would like to close the window so that it is not always showing (This process runs every 3 minutes). I haven't been able to find a solution that I can get to work as of yet. I tried adding SendKeys "%{F4}" after as one site suggested. This merely minimizes the window, not close it. I also attempted to try adding DoCmd.Close Shell, "Untitled" after, yet this also did not work. I have spent several hours attempting to do, what I imagine is a simple task, and felt another set of eyes could point me in the right direction. Below is my code that opens Chrome. Any assistance is greatly appreciated.
Public Function RunYahooAPI()
Dim chromePath As String
chromePath = """C:\Program Files\Google\Chrome\Application\chrome.exe"""
Shell (chromePath & " -url http://download.finance.yahoo.com/d/quotes.csv?s=CVX%2CXOM%2CHP%2CSLB%2CPBA%2CATR%2CECL%2CNVZMY%2CMON&f=nsl1op&e=.csv")
End Function
this VBA code will launch (as in your question) chrome, save the Process handle in the variable pHandle, loop all processes with this Handle and then stop the process (after checking user and domain of the process owner) .
Sub LaunchandStopProcess()
'
' As in your Question
'
Dim chromePath As String
Dim pHandle As Variant
chromePath = "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
'
' Note: Shell pass the Process Handle to the PID variable
'
PHandle = Shell(chromePath & " -url http://download.finance.yahoo.com/d/quotes.csv?s=CVX%2CXOM%2CHP%2CSLB%2CPBA%2CATR%2CECL%2CNVZMY%2CMON&f=nsl1op&e=.csv")
Dim objWMIcimv2 As Object
Dim objProcess As Object
Dim objList As Object
Dim ProcToTerminate As String
Dim intError As Integer
Set objWMIcimv2 = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\.\root\cimv2")
Set objList = objWMIcimv2.ExecQuery("select * from win32_process where Handle='" & CStr(pHandle) & "'")
'
' ObjList contains the list of all process matching the Handle (normally your chrome App, if running)
'
If objList.Count = 0 Then
' No matching Process
' Set all objects to nothing
Set objWMIcimv2 = Nothing
Set objList = Nothing
Set objProcess = Nothing
Exit Sub
Else
'
' Parse all matching Processes
'
For Each objProcess In objList
' additionally check with actual user
colProperties = objProcess.getowner(strNameofUser, strUserdomain)
If strUserdomain + "\" + strNameofUser = Environ$("userdomain") + "\" + Environ$("username") Then
intError = objProcess.Terminate
If intError <> 0 Then
'
' Trap Error or do nothing if code run unattended
'
Else
' Confirm that process is killed or nothing if code run unattended
End If
End If
Next
Set objWMIcimv2 = Nothing
Set objList = Nothing
Set objProcess = Nothing
End If
End Sub

VBScript Error: "Entry not found in index..." when opening documents Lotus Notes

Here is a working script for extract attachments from Lotus Notes letters:
Dim s
s = 1
Do
s = s + 1
Dim Session
Dim Maildb
Dim view
Dim vc
Dim doc
Dim Item
Dim coll
Dim x
Dim Sender
Dim sentTo
Dim viewTimer
Dim ws
Dim Source
Set Session = CreateObject("Lotus.NotesSession")
Call Session.Initialize("password")
Set Maildb = Session.GetDatabase("DOMAIN/Servers/Server-Name/RU", "GroupMail\mailbox.nsf")
Set view = Maildb.GetView("($inbox)")
If Not Maildb.IsOpen = True Then
Call Maildb.Open
End If
With view
x = 0
ReDim LmailID(x)
ReDim HasAttach(x)
Set doc = .GetFirstDocument
If doc Is Nothing Then
else
Call doc.PutInFolder("Processed")
Call doc.Removefromfolder("($inbox)")
Do
fileNames = Session.Evaluate("#AttachmentNames", doc)
For Each Filename In fileNames
Sender = doc.GETITEMVALUE("From")(0)
strSearchForSpecificName = "SpecificName"
If InStr(1, Sender, strSearchForSpecificName) > 0 then
sentTo = "SpecificName#mail.ru"
else
sentTo = Sender
End If
If Filename <> "" Then
Call doc.Save( False, False, True )
Set NotesEmbeddedObject = doc.GetAttachment(FileName)
NotesEmbeddedObject.ExtractFile ("d:\#files\" + Right("0" & Month(Now), 2) & "-" & Right("0" & Day(Now), 2) & "-" & Year(Now) & "-" & Hour(Time) & Minute(time) & Second(time) & "_" & Filename)
Set reply = doc.CreateReplyMessage( False )
Call reply.replaceItemValue("SendTo", sentTo)
Call reply.replaceItemValue("CopyTo", "copy#mail.ru")
Call reply.replaceItemValue("Subject", "Re: " & "файл " + Filename + " передан в обработку " + Right("0" & Month(Now), 2) & "-" & Right("0" & Day(Now), 2) & "-" & Year(Now) & Hour(Time) & ":" & Minute(time) & ":" & Second(time))
doc.SaveMessageOnSend = True
Call reply.Send( False )
End If
Next
x = x + 1
ReDim Preserve LmailID(x)
Set doc = .GetNextDocument(doc)
Loop Until doc Is Nothing
End If
End With
Wscript.Sleep (30 * 1000)
Set Session = Nothing
Set Maildb = Nothing
Set view = Nothing
Set vc = Nothing
Set doc = Nothing
Set Item = Nothing
Set coll = Nothing
Set x = Nothing
s = s - 1
Loop While s > 0
The problem is that sometimes I'm getting an error: Error: "Entry not found in index..." and program stopped at Set doc = .GetNextDocument(doc) line.
Is there there solution for resolve this error?
Your code is removing the first doc from the $Inbox, therefore it cannot be used as the anchor for getting the next document in $Inbox. The solution would normally be to make sure that you get the next document before you remove the current document. I.e., change
Call doc.PutInFolder("Processed")
Call doc.Removefromfolder("($inbox)")
to
Call doc.PutInFolder("Processed")
set nextDoc = .getNextDocument(doc)
Call doc.Removefromfolder("($inbox)")
and change
Set doc = .GetNextDocument(doc)
to
set doc = nextDoc
However, your code with the putInFolder and RemoveFromFolder calls is not actually inside the loop, therefore only the first doc that you process will be moved to the Processed folder and nextDoc will not be properly set after the first iteration of the loop. If you really only want to move the first document to the Processed folder, then the above solution still isn't right because you'll only be setting nextDoc once, outside the loop and you will have an infinite loop since you'll always be setting doc to the same nextDoc value. You'll need another instance of nextDoc = getNextDocument(doc) inside the loop. If you really want all documents to be moved to the Processed folder, then you just need to move the entire block of code dealing with the folders and assigning nextDoc into the inside of the loop.
The problem is simple: The document is removed from Inbox and therefor is not in the index anymore. This happens if one of two conditions are true:
The NotesView- Property "AutoUpdate" is set to true
Your timer- triggered "refreshView"- sub (that is not in your example code) does a view.Refresh.
Despite of the fact, that in your code there is a lot of "nonsense" (sorry to say), it is easy to fix this code:
Just use the fact, that the view- object can be updated as advantage and change to "getfirstdocument" all the time:
Change this line:
Set doc = .GetNextDocument(doc)
to these lines:
Call .Refresh()
Set doc = .GetFirstDocument
What does this do: The Refresh removes the currently processed document from the view. The "next" document will be the first in view. And that one you get until there is no more "first" document...
And: Richard is right. You need to move the two rows Call doc.PutInFolder("Processed")
Call doc.Removefromfolder("($inbox)") below the Do in order to move ALL documents to the folder and not only the first one.

Resources