I have a simple script that monitors processes' different performance statistics in Windows XP in a loop until it is terminated.
Despite my efforts, the script's memory footprint increases in size over time.
Any advice is greatly appreciated.
Set fso = CreateObject("Scripting.FileSystemObject")
logFileDirectory = "C:\POSrewrite\data\logs"
Dim output
Dim filePath
filePath = "\SCOPerformance-" & Day(Now()) & Month(Now()) & Year(Now()) & ".log"
IF fso.FolderExists(logFileDirectory) THEN
ELSE
Set objFolder = fso.CreateFolder(logFileDirectory)
END IF
logFilePath = logFileDirectory + filePath + ""
IF (fso.FileExists(logFilePath)) THEN
set logFile = fso.OpenTextFile(logFilePath, 8, True)
output = VBNewLine
output = output & (FormatDateTime(Now()) + " Open log file." & VBNewLine)
ELSE
set logFile = fso.CreateTextFile(logFilePath)
output = output & (FormatDateTime(Now()) + " Create log file." & VBNewLine)
END IF
output = output & (FormatDateTime(Now()) + " Begin Performance Log data." & VBNewLine)
output = output & ( "(Process) (Percent Processor Time) (Working Set(bytes)) (Page Faults Per Second) (PrivateBytes) (PageFileBytes)" & VBNewLine)
WHILE (True)
On Error Resume NEXT
IF Err = 0 THEN
strComputer = "."
Set objRefresher = CreateObject("WbemScripting.SWbemRefresher")
Set objServicesCimv2 = GetObject("winmgmts:\\" _
& strComputer & "\root\cimv2")
Set objRefreshableItem = _
objRefresher.AddEnum(objServicesCimv2 , _
"Win32_PerfFormattedData_PerfProc_Process")
objRefresher.Refresh
' Loop through the processes three times to locate
' and display all the process currently using
' more than 1 % of the process time. Refresh on each pass.
FOR i = 1 TO 3
objRefresher.Refresh
FOR Each Process in objRefreshableItem.ObjectSet
IF Process.PercentProcessorTime > 1 THEN
output = output & (FormatDateTime(Now()) & "," & i ) & _
("," & Process.Name & _
+"," & Process.PercentProcessorTime & "%") & _
("," & Process.WorkingSet) & ("," & Process.PageFaultsPerSec) & _
"," & Process.PrivateBytes & "," & Process.PageFileBytes & VBNewLine
END IF
NEXT
NEXT
ELSE
logFile.WriteLine(FormatDateTime(Now()) + Err.Description)
END IF
logFile.Write(output)
output = Empty
set objRefresher = Nothing
set objServicesCimv2 = Nothing
set objRefreshableItem = Nothing
set objFolder = Nothing
WScript.Sleep(10000)
Wend
I think the main problem with your script is that you initialize WMI objects inside the loop, that is, on every iteration of the loop, even though these objects are always the same:
strComputer = "."
Set objRefresher = CreateObject("WbemScripting.SWbemRefresher")
Set objServicesCimv2 = GetObject("winmgmts:\\" _
& strComputer & "\root\cimv2")
Set objRefreshableItem = _
objRefresher.AddEnum(objServicesCimv2 , _
"Win32_PerfFormattedData_PerfProc_Process")
You need to move this code out of the loop, e.g., at the beginning of the script.
Other tips and suggestions:
Use Option Explicit and explicitly declare all variables used in your script. Declared variables are slightly faster than undeclared ones.
Use FileSystemObject.BuildPath to combine multiple parts of the path. The useful thing about this method is that it inserts the necessary path separators for you.
logFileDirectory = "C:\POSrewrite\data\logs"
filePath = "SCOPerformance-" & Day(Now) & Month(Now) & Year(Now) & ".log"
logFilePath = fso.BuildPath(logFileDirectory, filePath)
The objFolder variable isn't used in your script, so there's no need to create it. Also, you can make the FolderExists check more readable by rewriting it as follows:
If Not fso.FolderExists(logFileDirectory) Then
fso.CreateFolder logFileDirectory
End If
Move repeated code into subroutines and functions for easier maintenance:
Function DateTime
DateTime = FormatDateTime(Now)
End Function
...
output = output & DateTime & " Open log file." & vbNewLine
Usually you don't need parentheses when concatenating strings:
output = output & DateTime & "," & i & _
"," & Process.Name & _
"," & Process.PercentProcessorTime & "%" & _
"," & Process.WorkingSet & "," & Process.PageFaultsPerSec & _
"," & Process.PrivateBytes & "," & Process.PageFileBytes & vbNewLine
In this article, Eric Lippert (Literally worked on designing and building VBScript at Microsoft) indicates that the order in which you dispose of things may be important. Maybe you are running into one of these bugs?
I'll let you read the rest...
When Are You Required To Set Objects To Nothing?
I would recommend against running the script in a permanent loop within the script unless you actually need such a tight loop. I would suggest a single iteration within in the script, called from Scheduled tasks.
I have run into the exact same issue, using it for a procmon-style attempt to capture a rogue process that appears to be respawning.
Narrowing it all down, it appears to be the objRefresher.Refresh and there simply appears to be no way around it
What I did to overcome this was use a for...next to run it 100 times, then immediately afterwards run the following, which would simply respawn the script and shutdown:
CreateObject("Wscript.Shell").Run """" & WScript.ScriptFullName & """", 0, False
So I would watch the memory crawl from 5Mb to 40Mb, then drop back down to 5Mb
Related
I'm doing some automation in Excel with VBA. We have several versions of a template and I'm working on functionality that allows users to import data from older versions into the newest version of the template.
Here's some of that code:
If .Range("E63").Interior.Color <> .Range("M5").Interior.Color Then
Call switch
.Range("E63:E142").Value = PTWorkbook.Sheets("Configuration").Range("E63:E142").Value
Call switch
...
The switch sub swaps two the columns in the new template to properly line them up so we can import from the old template more easily. The issue is that that sub doesn't run unless I step through the sub in debug mode. The values get imported into the template, but the columns don't switch like they're supposed to.
Sub switch()
Dim inputValue As Variant
Dim calculatedValue As Variant
If Range("F63").Interior.Color = Range("M5").Interior.Color Then
inputValue = "F"
calculatedValue = "E"
Else
inputValue = "E"
calculatedValue = "F"
End If
Range(calculatedValue & "63:" & calculatedValue & "142").Formula = Range(calculatedValue & "63:" & calculatedValue & "142").Value
Range(inputValue & "63").Formula = "=" & calculatedValue & "63"
If inputValue = "F" Then
Range(inputValue & "64:" & inputValue & "98").Formula = "=" & calculatedValue & "64-" & calculatedValue & "63"
Else
Range(inputValue & "64:" & inputValue & "98").Formula = "=" & calculatedValue & "64+" & inputValue & "63"
End If
Range(inputValue & "63:" & inputValue & "142").Interior.Color = Range("M13").Interior.Color
Range(calculatedValue & "63:" & calculatedValue & "142").Interior.Color = Range("M5").Interior.Color
End Sub
I did some research and a lot of people recommend putting DoEvents after the call to 'slow things down' and effectively wait for the sub to finish. But that hasn't worked.
I also have almost identical code elsewhere that does the exact same thing, but from a different version, and it works with the DoEvents call.
Does anyone know what might be going wrong here?
Edit:
Added switch sub.
I'm using Excel to upload some files onto an server with WinSCP.
This example works:
Sub FTP_upload()
Dim logfile, ftp_login, file_to_upload, upload_to_folder As String
logfile = "D:\temp\ftp.log"
ftp_login = "ftp://ftp_mydomain:mypassword#mydomain.com/"
file_to_upload = "D:\tmep\myfile.txt"
upload_to_folder = "/myfolder/"
'upload the file
Call Shell("C:\Program Files (x86)\WinSCP\WinSCP.com /log=" & logfile & " /command " & """open """ & ftp_login & " " & """put " & file_to_upload & " " & upload_to_folder & """ " & """exit""")
End Sub
I now want Excel to wait until the shell has closed.
Using the information from Wait for shell command to complete, I put it together this code:
Sub FTP_upload_with_wait()
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
Dim waitOnReturn As Boolean: waitOnReturn = True
Dim windowStyle As Integer: windowStyle = 1
Dim errorCode As Integer
Dim logfile, ftp_login, file_to_upload, upload_to_folder As String
logfile = "D:\temp\ftp.log"
ftp_login = "ftp://ftp_mydomain:mypassword#mydomain.com/"
file_to_upload = "D:\tmep\myfile.txt"
upload_to_folder = "/myfolder/"
execute_string = "C:\Program Files (x86)\WinSCP\WinSCP.com /log=" & logfile & " /command " & """open """ & ftp_login & " " & """put " & file_to_upload & " " & upload_to_folder & """ " & """exit"""
errorCode = wsh.Run(execute_string, windowStyle, waitOnReturn)
End Sub
Unfortunately, this doesn't work. Excel reports:
run-time error '-2147024894 (80070002)'
Automation error
The system cannot find the file specified
When I replace the string this way, it works:
execute_string = "notepad.exe"
It seems that wsh.Run doesn't like the quotation marks.
How can I make this work?
The path to WinSCP contains spaces, so you need to wrap it to double-quotes (which need to be doubled to escape them in VBA string):
execute_string = """C:\Program Files (x86)\WinSCP\WinSCP.com"" ..."
But that's only the first set of quotes that is wrong in your command.
The correct command would be like:
execute_string = """C:\Program Files (x86)\WinSCP\WinSCP.com"" " & _
"/log=" & logfile & " /command " & _
"""open " & ftp_login & """ " & _
"""put " & file_to_upload & " " & upload_to_folder & """ " & _
"""exit"""
Assuming that none of logfile, ftp_login, file_to_upload and upload_to_folder contains spaces, in which case would would need a way more double-quotes.
Read ore about WinSCP command-line syntax
The Call Shell must have some heuristics that adds the quotes around C:\Program Files (x86)\WinSCP\WinSCP.com. Though it's just a pure luck that the rest of the command-line works, the quotes are wrong there too. So even your first code is wrong. It runs the following command:
"C:\Program Files (x86)\WinSCP\WinSCP.com" /log=D:\temp\ftp.log /command "open "ftp://ftp_mydomain:mypassword#mydomain.com/ "put D:\tmep\myfile.txt /myfolder/" "exit"
(Note the misplaced quotes around open)
I tried using the code below to loop through the strDir variable to create 4 different folders in 4 different locations.
It does not create the folders. No errors appear.
Dim i as Integer
JobName = NewJob.Value
If New_Job.JobYes.Value Then
strDir1 = "C:\QTR\" & JobName & " QTR"
strDir2 = "C:\QT\" & JobName & " QT"
strDir3 = "C:\EMAILS\" & JobName & " EMAILS"
strDir4 = "C:\DOCUMENTS\" & JobName & " DOCS"
For i = 1 To 4
If Dir(strDir, vbDirectory) = "" Then
MkDir strDir & i
Else
MsgBox "Directory exists."
End If
Next i
Else
End If
I agree with the array approach but avoid creating blank entries in the array. It has a zero-based index (by default) and strDir(4) actually creates 5 entries; e.g. 0, 1, 2, 3, 4.
First off, either put Option Explicit at the top of the code sheet or go into the VBE's Tools ► Options ► Editor and put a check beside Require Variable Declaration. This will quickly identify the use of undeclared variables like the strDir in your code.
Dim d As Long, strDir As Variant, JobName As String
strDir = Array("C:\QTR\" & JobName & " QTR", _
"C:\QT\" & JobName & " QT", _
"C:\EMAILS\" & JobName & " EMAILS", _
"C:\DOCUMENTS\" & JobName & " DOCS")
For d = LBound(strDir) To UBound(strDir)
If Dir(strDir(d), vbDirectory) = "" Then
MkDir strDir(d)
Else
Debug.Print strDir(d) & " exists."
End If
Next d
The LBound and
UBound functions return the Upper and Lower Boundaries of the array.
Try this code:
Dim i as Integer
Dim strDir(4) as String
JobName = NewJob.Value
If New_Job.JobYes.Value Then
strDir(1) = "C:\QTR\" & JobName & " QTR"
strDir(2) = "C:\QT\" & JobName & " QT"
strDir(3) = "C:\EMAILS\" & JobName & " EMAILS"
strDir(4) = "C:\DOCUMENTS\" & JobName & " DOCS"
For i = 1 To 4
If Dir(strDir(i), vbDirectory) = "" Then
MkDir strDir(i)
Else
MsgBox "Directory exists."
End If
Next i
Else
End If
That will indeed give an error, since its not possible to concatenate the "strDir" & i together, to use that specific parameter. Easiest way to solve this correctly is to skip the loop and use:
If Dir(strDir, vbDirectory) = "" Then
MkDir strDir1
MkDir strDir2
MkDir strDir3
MkDir strDir4
Else
MsgBox "Directory exists."
End If
If you really need to create an enormous amount of directories, lets say > 10, then you might want to use dynamically requesting parameters by name, but if you don't need it, I would not recommend it.
I'm trying to attach a vbscript onto my windows scheduler so that as soon as I open a program (for example, Google Chrome) it runs an excel macro in the background. I've looked into task scheduler and can't find the ability to trigger events based on opening another program, as opposed to an administrative message or error. Sorry for the noob question!
This is from Windows SDK WMI section. http://msdn.microsoft.com/en-us/library/aa392396(v=vs.85).aspx
This monitors 6 (i=0 to 5) program starts and exits.
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" _
& strComputer & "\root\CIMV2")
Set objEvents = objWMIService.ExecNotificationQuery _
("SELECT * FROM Win32_ProcessTrace")
Wscript.Echo "Waiting for events ..."
i = 0
Do Until i=5
Set objReceivedEvent = objEvents.NextEvent
'report an event
Wscript.Echo "Win32_ProcessTrace event occurred" & VBNewLine _
& "Process Name = " _
& objReceivedEvent.ProcessName & VBNewLine _
& "Process ID = " _
& objReceivedEvent.Processid & VBNewLine _
& "Session ID = " & objReceivedEvent.SessionID
i = i+ 1
Loop
I am looking for a way to find the log folder path of a certain website in IIS by its ID name, like: W3SVC1 or any other method, from inside my application. Is this possible? I tried looking up and down google and found nothing. How is this achieved?
The following code works, but only gets all of the application names/ids (like W3SVC/1), i need the actual log file path, and since they can change I don't want to use the static default log file directory as the root on this:
Function ProcessWebSite(ServiceType, SiteNumber)
Set IISWebSite = getObject("IIS://localhost/" & ServiceType & "/" & SiteNumber)
Set IISWebSiteRoot = getObject("IIS://localhost/" & ServiceType & "/" & SiteNumber & "/root")
ProcessWebSite = IISWebSite.ServerComment
Set IISWebSiteRoot = nothing
Set IISWebSite = Nothing
end function
Function ShowSites(ServiceType, ClassName, Title)
Response.write "Web Sites Description=" & "<br>"
Response.write "===============================================================" & "<br>"
Set IISOBJ = getObject("IIS://localhost/" & ServiceType)
for each Web in IISOBJ
if (Web.Class = ClassName) then
Response.write Ucase(ServiceType) & "/" & Web.Name & _
Space(17-(len(Ucase(ServiceType))+1+len(Web.Name))) & " " & _
ProcessWebSite(ServiceType, Web.name) & "<br>"
end if
next
Set IISOBj=Nothing
End function
Call ShowSites("w3svc", "IIsWebServer", "Web")
Function Returnlog(WebSitePath)
Dim statut
Set IISOBJRoot = getObject(webSitePath)
Returnlog = IISOBJRoot.LogFileDirectory
set IISOBJRoot = Nothing
end Function
IIsObjectPath = "IIS://localhost/w3svc"
Set IIsObject = GetObject(IIsObjectPath)
for each obj in IISObject
if (Obj.Class = "IIsWebServer") then
BindingPath = IIsObjectPath & "/" & Obj.Name
Set IIsObjectIP = GetObject(BindingPath)
WScript.Echo IISObjectIP.ServerComment & ": " & Returnlog(IISObjectIP.ADSPath) & vbnewline
'WScript.Echo IISObjectIP.LogType & vbnewline
end if
next