VBScript to shutdown Windows when a process ends? - windows-7

I have a program that scans through data at the end of the night on some occasions. On those occasions, I would like to run a VBScript that will watch for that program to close, and when it does, will shut down Windows.
I created a .BAT file that runs the program and then shuts Windows down, but I don't always need to shutdown when I finish using the program.
So I would like to use the scanning program, and if, at the end of the night, I am ready to leave, but the program is still scanning, I would to open the VBScript that will watch for my scanning program to close.
Is this possible?
Windows 7 Ultimate
x64 UAC = ON

Well, I figured out how to do this via this post at Techimo.com.
Dim isRunning, wasRunningAtStart, strComputer, strShutdown, objWMIService
Dim objcolProcesses, objShell, strProcesses, strProcessName
'boolean condition for the loop
isRunning = True
wasRunningAtStart = True
'-----Specify the computer name on which to watch a process:
strComputer = "." '>>> "." for this computer
'-----Specify the process to watch. Must be enclosed in Single Quotes:
strProcessName = "'processname.exe'" '>>> Example: "'notepad.exe'"
Set objWMIService = GetObject("winmgmts:" & _
"{impersonationLevel=impersonate}!\\" & _
strComputer & "\root\cimv2")
strProcesses = "SELECT * FROM Win32_Process WHERE Name = "
strShutdown = "shutdown -s -t 0 -f -m \\" & strComputer
Set objShell = CreateObject("WScript.Shell")
'Check the process once, no need to run if the process
'isn't already running
'Query WMI for the running processes matching our process name
Set objColProcesses = objWMIService.ExecQuery ( _
strProcesses & strProcessName)
'If the process is running, the count will be greater than 0,
'so we switch our boolean here to exit the loop.
If objcolProcesses.Count = 0 Then
wasRunningAtStart = False
isRunning = False
End If
Set objColProcesses = Nothing
Do While isRunning
'Wait 2 seconds, prevents this script from using the CPU
WScript.Sleep 2000
'Query WMI for the running processes matching our process name
Set objColProcesses = objWMIService.ExecQuery ( _
strProcesses & strProcessName)
'If the process is running, the count will be greater than 0,
'so we switch our boolean here to exit the loop.
If objColProcesses.Count = 0 Then
isRunning = False
End If
Loop
If wasRunningAtStart Then
'MsgBox "Would shutdown here"
objShell.Run strShutdown
Else
MsgBox "The specified program is not already running."
End If
Set objColProcesses = Nothing
Set objShell = Nothing
Set objWMIService = Nothing

' Shutdown.vbs
' Example VBScript to Shutdown computers
' Author Josh Murray
' Version 4.1 - February 2007
' --------------------------------------Option Explicit
Dim objShell, strComputer, strInput
Dim strShutdown
Do
strComputer = (InputBox(" ComputerName to shutdown", "Computer Name"))
If strComputer <> "" Then
strInput = True
End if
Loop until strInput = True
strShutdown = "shutdown -s -t 0 -f -m \\" & strComputer
set objShell = CreateObject("WScript.Shell")
objShell.Run strShutdown
Wscript.Quit

Related

How can I get process if it's already running?

For example:
I have a code of VBS, it will help me check a program is already running
Function isProcessRunning(strComputer, strProcess)
Dim Process, strObject
strObject = "winmgmts://" & strComputer
For Each Process In GetObject(strObject).InstancesOf("Win32_Process")
If UCase(Process.Name) = UCase(strProcess) Then
isProcessRunning = True
Exit Function
Else
isProcessRunning = False
End If
Next
End Function
But, I want to get process is already running for re-using.
Function shell()
If isProcessRunning(".", "cmd.exe") == False Then
Dim objShell
Set objShell = WScript.CreateObject("WScript.Shell")
shell = objShell.Run(cmd)
Else
shell = ... 'how can I get cmd.exe already running here
End If
Next
End Function
Update my problem
Actually, I want to get instance of cmd.exe if it's already running. I mean, I don't want re-open cmd.exe again.
Example:
I have just opened cmd.exe.
I run this code below (its name is laucher.vbs).
Function isProcessRunning(strComputer, strProcess)
Dim Process, strObject
strObject = "winmgmts://" & strComputer
For Each Process In GetObject(strObject).InstancesOf("Win32_Process")
If UCase(Process.Name) = UCase(strProcess) Then
isProcessRunning = True
Exit Function
Else
isProcessRunning = False
End If
Next
End Function
Function getProcessRunning(strComputer, strProcess)
Dim Process, strObject
strObject = "winmgmts://" & strComputer
For Each Process In GetObject(strObject).InstancesOf("Win32_Process")
If UCase(Process.Name) = UCase(strProcess) Then
... 'I do not know how to code this here for return instance of Process is running ...
Exit Function
End If
Next
End Function
Function shell()
If isProcessRunning(".", "cmd.exe") == False Then
Dim objShell
Set objShell = WScript.CreateObject("WScript.Shell")
shellFn = objShell.Run("cmd")
Else
shellFn = getProcessRunning(".", "cmd.exe")
End If
End Function
Dim cmdExe
cmdExe = shell
cmdExe "shutdown -s -t 60"
Because cmd.exe is already running cmdExe "shutdown -s -t 60" will re-using cmd and execute command.
Give a try for this example :
Option Explicit
Dim ProcessPath1,ProcessPath2
ProcessPath1 = "%windir%\system32\cmd.exe"
ProcessPath2 = "%ProgramFiles%\Internet Explorer\iexplore.exe"
Call CheckProcess(ProcessPath1)
Call CheckProcess(ProcessPath2)
'**************************************************************************
Sub CheckProcess(ProcessPath)
Dim strComputer,objWMIService,colProcesses,WshShell,Tab,ProcessName
strComputer = "."
Tab = Split(ProcessPath,"\")
ProcessName = Tab(UBound(Tab))
ProcessName = Replace(ProcessName,Chr(34),"")
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colProcesses = objWMIService.ExecQuery _
("Select * from Win32_Process Where Name = '"& ProcessName & "'")
If colProcesses.Count = 0 Then
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run DblQuote(ProcessPath)
Else
MsgBox DblQuote(ProcessName) & " is aleardy running !", vbExclamation,_
DblQuote(ProcessName) & " is aleardy running !"
Exit Sub
End if
End Sub
'**************************************************************************
Function DblQuote(Str)
DblQuote = Chr(34) & Str & Chr(34)
End Function
'**************************************************************************

Use VBS to run PowerShell

So I have a .ps1 file which creates a form.
That Form takes 10-20secs depending on PCs performance and connection on first load.
Now I am currently using VBS to load a simple .gif file as a loading screen concurrently running the .ps1 file right after.
My issue at the moment is that, I want to close the loading screen when the form pops up. I tried to determine via processes but that failed because of the it loads powershell.exe but the form takes 10sec...
Is it this possible?
Of have you guys got a better idea to do this?
Dim i
Dim strComputer
Dim FindProc
Dim counter
counter = 0
strComputer = "."
FindProc = "powershell.exe"
'Load the gif file
Set objExplorer = CreateObject("InternetExplorer.Application")
With objExplorer
.Navigate "about:blank"
.Visible = 1
.Document.Title = "Show Image"
.Toolbar=False
.Statusbar=False
.Top=400
.Left=400
.Height=355
.Width=435
.Document.Body.InnerHTML = "<img src='\\10.10.67.173\Templates$\Scripts\Resources\loadingtest.gif'>"
End With
'Run the PS script
Set objShell = CreateObject("Wscript.shell")
objShell.Run "CMD /C START /B " & objShell.ExpandEnvironmentStrings("%SystemRoot%") & "\System32\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass -file \\10.10.67.173\Templates$\Scripts\FormSignature-V0.9.5.ps1", 0, False
'Determine when to close Loading screen
Do While counter < 3
wscript.Sleep 2000
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colProcessList = objWMIService.ExecQuery _
("Select Name from Win32_Process WHERE Name LIKE '" & FindProc & "%'")
If colProcessList.count>0 then
'Quit the process if it finds its running
WScript.Echo("found")
'objExplorer.quit
else
'Do nothing
WScript.Echo("not found")
End if
Set objWMIService = Nothing
Set colProcessList = Nothing
counter = counter + 1
Loop
Set objShell = Nothing
In your VB script:
Create a file in an folder the PS script can access.
Launch your loading image.
Launch your PS script.
In your wait loop, every second or so, check if the file still exists.
If it does, close the instance of IE and exit your script.
In your PS script:
After your form initialization code is finished or the first action after the form load, locate and delete the file.

Need to monitor a process

I am writing a vb script to monitor a process. The script monitors the status of a process and if the process is not running since 10 mins it should execute a command.Below is my script:
set objWMIService = GetObject ("winmgmts:")
foundProc = False
procName = "calc.exe"
Dim wshell
' Initialise the shell object to return the value to the monitor
Set wshell = CreateObject("WScript.Shell")
if err.number <> 0 then
WScript.Echo "Error: could not create WScript.Shell (error " & err.number & ", " & err.Description & ")"
WScript.quit(255)
end if
for each Process in objWMIService.InstancesOf ("Win32_Process")
If StrComp(Process.Name,procName,vbTextCompare) = 0 then
foundProc = True
procID = Process.ProcessId
End If
Next
#####code to check the proces status
If foundProc = True Then
WScript.Quit(0)
Else
WScript.sleep(1*60*1000)
If foundProc = True Then
WScript.Echo "Found Process (" & procID & ")"
Else
WScript.Echo "Process not running since 10 mins"
WScript.Quit(0)
End If
End If
Set WshShell = WScript.CreateObject("WScript.Shell")
Set objWMIService = GetObject("winmgmts:\\.\root\CIMV2")
Set objEvents = objWMIService.ExecNotificationQuery _
("SELECT * FROM Win32_ProcessTrace")
Do
Set objReceivedEvent = objEvents.NextEvent
If objReceivedEvent.ProcessName = "svchost.exe" then msgbox objReceivedEvent.ProcessName
' msgbox objReceivedEvent.ProcessName
Loop
You also have Win32_ProcessTraceStart and Win32_ProcessTraceStop. Above code is both.
It is also pointless doing error checking on WScript.Shell. It's a system component - it should be available. Also if it's not your script won't run as wscript isn't available to run your script.

Starting a process in VBS: path not found

I need to make a simple vbs script to run some process' automatically. I found the following script on microsoft's website. It works fine to run notepad.exe the way the original example shows, but I'm trying to modify it to run myprog.exe. The full path to this program is: C:\myprogdir\myprog.exe
Const SW_NORMAL = 1
strComputer = "."
strCommand = "myprog.exe"
strPath = "C:\myprogdir\"
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" _
& strComputer & "\root\cimv2")
' Configure the Notepad process to show a window
Set objStartup = objWMIService.Get("Win32_ProcessStartup")
Set objConfig = objStartup.SpawnInstance_
objConfig.ShowWindow = SW_NORMAL
' Create Notepad process
Set objProcess = objWMIService.Get("Win32_Process")
intReturn = objProcess.Create _
(strCommand, strPath, objConfig, intProcessID)
If intReturn <> 0 Then
Wscript.Echo "Process could not be created." & _
vbNewLine & "Command line: " & strCommand & _
vbNewLine & "Return value: " & intReturn
Else
Wscript.Echo "Process created." & _
vbNewLine & "Command line: " & strCommand & _
vbNewLine & "Process ID: " & intProcessID
End If
I keep getting Return value: 9, which indicates "Path Not Found". However the path is correct. Is there something I'm not getting?
You don't need all that to start a process, you just need the Shell object. Also, be sure to wrap the path of your executable in quotes (in case the path has spaces). Like this:
Option Explicit
Dim shl
Set shl = CreateObject("Wscript.Shell")
Call shl.Run("""C:\myprogdir\myprog.exe""")
Set shl = Nothing
WScript.Quit
Unless the path to your program is included in the system's %PATH% environment variable you need to specify the commandline with the full path to the executable. Specifying the path just as the working directory will not work.
strProgram = "myprog.exe"
strPath = "C:\myprogdir"
Set fso = CreateObject("Scripting.FileSystemObject")
strCommand = fso.BuildPath(strPath, strProgram)
...
intReturn = objProcess.Create(strCommand, strPath, objConfig, intProcessID)
Using the BuildPath method will save you the headaches caused by having to keep track of leading/trailing backslashes.
Note that you need to put double quotes around a path that contains spaces, e.g. like this:
strCommand = Chr(34) & fso.BuildPath(strPath, strProgram) & Chr(34)
As others have already pointed out, there are simpler ways to start a process on the local computer, like Run:
Set sh = CreateObject("WScript.Shell")
sh.Run strCommand, 1, True
or ShellExecute:
Set app = CreateObject("Shell.Application")
app.ShellExecute strCommand, , strPath, , 1
There are some notable differences between Run and ShellExecute, though. The former can be run either synchronously or asynchronously (which means the command either does or doesn't wait for the external program to terminate). The latter OTOH always runs asynchronously (i.e. the method returns immediately without waiting for the external program to terminate), but has the advantage that it can be used to launch programs with elevated privileges when UAC is enabled by specifying the verb "runas" as the 4th argument.
However, these methods only allow for launching processes on the local computer. If you want to be able to launch processes on remote computers you will have to use WMI:
strComputer = "otherhost"
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" _
& strComputer & "\root\cimv2")
See here for more information about WMI connections to remote hosts.

Find my own process ID in VBScript

I'm using the following code snippet to determine what process ID my vbscript is running as:
On Error Resume Next
Dim iMyPID : iMyPID = GetObject("winmgmts:root\cimv2").Get("Win32_Process.Handle='" & CreateObject("WScript.Shell").Exec("mshta.exe").ProcessID & "'").ParentProcessId
If Err.Number <> 0 Then Call Handle_Error(Err.Description)
On Error Goto 0
On my Windows 7 (32-bit) machine this works about 90% of the time and iMyPID contains the process ID of the currently running script. However 10% of the time Handle_Error gets called with the error message "SWbemServicesEX: Not found".
Recently someone else running Windows 7 (64-bit) reported that Handle_Error always gets called with the error message "Out of memory". This seems an insane error message just to find out your own process ID!
Can anyone recommend a better way of doing this?
mshta terminates itself immediately. Maybe it's too late to achieve parent process id by using WMI service.
So, I'd use something like this to eliminate concurrent script processes.
Generate random things.
Determine an application which could be installed on each system, never terminates by itself (e.g. command prompt with /k parameter).
Start the application in hidden mode with generated random argument (WshShell.Run).
Wait a few milliseconds
Query the running processes by using command line argument value.
Get the ParentProcessId property.
Function CurrProcessId
Dim oShell, sCmd, oWMI, oChldPrcs, oCols, lOut
lOut = 0
Set oShell = CreateObject("WScript.Shell")
Set oWMI = GetObject(_
"winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
sCmd = "/K " & Left(CreateObject("Scriptlet.TypeLib").Guid, 38)
oShell.Run "%comspec% " & sCmd, 0
WScript.Sleep 100 'For healthier skin, get some sleep
Set oChldPrcs = oWMI.ExecQuery(_
"Select * From Win32_Process Where CommandLine Like '%" & sCmd & "'",,32)
For Each oCols In oChldPrcs
lOut = oCols.ParentProcessId 'get parent
oCols.Terminate 'process terminated
Exit For
Next
CurrProcessId = lOut
End Function
Dim ProcessId
ProcessId = CurrProcessId 'will remain valid indefinitely
WScript.Echo ProcessId
Here's an even better code snippet:
' ***********************************************************************************************************
' lng_MyProcessID finds and returns my own process ID. This is excruciatingly difficult in VBScript. The
' method used here forks "cmd /c pause" with .Exec, and then uses the returned .Exec object's .ProcessID
' attribute to feed into WMI to get that process's Win32_Process descriptor object, and then uses THAT
' WMI Win32_Process descriptor object's .ParentProcessId attribute, which will be OUR Process ID, and finally
' we terminate the waiting cmd process. Execing cmd is what causes the brief cmd window to flash at start up,
' and I can' figure out out how to hide that window.
' returns: My own Process ID as a long int; zero if we can't get it.
' ************************************************************************************************************
Function lng_MyProcessID ()
lng_MyProcessID = 0 ' Initially assume failure
If objWMIService Is Nothing Then Exit Function ' Should only happen if in Guest or other super-limited account
Set objChildProcess = objWshShell.Exec ( """%ComSpec%"" /C pause" ) ' Fork a child process that just waits until its killed
Set colPIDs= objWMIService.ExecQuery ( "Select * From Win32_Process Where ProcessId=" & objChildProcess.ProcessID,, 0 )
For Each objPID In colPIDs ' There's exactly 1 item, but .ItemIndex(0) doesn't work in XP
lng_MyProcessID = objPID.ParentProcessId ' Return child's parent Process ID, which is MY process ID!
Next
Call objChildProcess.Terminate() ' Terminate our temp child
End Function ' lng_MyProcessID
I like Kul-Tigin's idea (+1), and Asok Smith's idea (based on .Exec) deserve respect (+1), and it w'd been even better if .Exec run hidden process. So, to feed my curiosity, I also toyed with this and this's what I did.
ts1 = Timer : res1 = CurrProcessId : te1 = Timer - ts1
ts2 = Timer : res2 = ThisProcessId : te2 = Timer - ts2
WScript.Echo "CurrProcessId", res1, FormatNumber(te1, 6), _
vbCrLf & "ThisProcessId", res2, FormatNumber(te2, 6), _
vbCrLf & "CurrProcessId / ThisProcessId = " & te1 / te2
'> CurrProcessId 6946 0,437500
'> ThisProcessId 6946 0,015625
'> CurrProcessId / ThisProcessId = 28
Function ThisProcessId
ThisProcessId = 0
Dim sTFile, oPrc
With CreateObject("Scripting.FileSystemObject")
sTFile = .BuildPath(.GetSpecialFolder(2), "sleep.vbs")
With .OpenTextFile(sTFile, 2, True)
.Write "WScript.Sleep 1000"
End With
End With
With CreateObject("WScript.Shell").Exec("WScript " & sTFile)
For Each oPrc In GetObject("winmgmts:\\.\root\cimv2").ExecQuery(_
"Select * From Win32_Process Where ProcessId=" & .ProcessID)
Exit For : Next
ThisProcessId = oPrc.ParentProcessId
End With
End Function
28 times faster(!), not bad :)
You may use Sleep from kernel32 instead of mshta.
MsgBox GetProcId()
Function GetProcId()
With GetObject("winmgmts:\\.\root\CIMV2:Win32_Process.Handle='" & CreateObject("WScript.Shell").Exec("rundll32 kernel32,Sleep").ProcessId & "'")
GetProcId = .ParentProcessId
.Terminate
End With
End Function
Code taken from here.
Also there is parent process name detection based on this approach.
Here is a better one, but in JScript (sorry, you translate it to VB ...)
var WshShell = WScript.CreateObject("WScript.Shell");
var objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2");
var childProcess =
WshShell.Exec
(
'"' + WshShell.Environment('PROCESS')('ComSpec') + '"'
+
" /C Echo \"Text lines\" && Set /p VarName="
);
childProcess.StdOut.ReadLine();
var current_pid =
objWMIService.ExecQuery
(
"Select * From Win32_Process Where ProcessId=" + childProcess.ProcessID
);
current_pid = (new Enumerator(current_pid)).item().ParentProcessId;
if (current_pid)
{
childProcess.StdIn.WriteLine("value"); // child process should now exit
WScript.Echo("Current PID: " + current_pid);
}
else
{
WScript.StdErr.WriteLine("Get current PID from WMI failed.");
WScript.Quit(7);
}
I just found this thread that partly solved my problem.
Thank you all.
"the code is unable to determine which process ID belongs to which script" : true, but as this is the first task that your script must achieve , you can keep the Pid that has the shortest lifetime.
Set com = CreateObject("Wscript.Shell")
Set objSWbemServices = GetObject ("WinMgmts:Root\Cimv2")
Set colProcess = objSWbemServices.ExecQuery ("Select * From Win32_Process")
dim toto, thisPid
thisPid=""
toto=200 ' just a high value like 200sec
For Each objProcess In colProcess
If InStr (objProcess.CommandLine, WScript.ScriptName) <> 0 Then
Ptime=((Cdbl(objProcess.UserModeTime)+Cdbl(objProcess.KernelModeTime))/10000000)
if toto > Ptime then
toto = Ptime
thisPid = objProcess.ProcessId
End If
End If
Next
If thisPid="" then
WScript.Echo "unable to get the PID"
Else
WScript.Echo "PID of this script : "&thisPid
End If
Except if you fired scripts quicker more than each one can retrieve their Pid, everything must be ok.
To retrieve the own process ID of a VB Script you can rely on the property CreationDate of the Process object.
At the moment a VB Script is started, the process that runs the script will have the latest CreationDate of all processes that runs the same script.
In fact, it will have the highest CreationDate of all running processes.
So, to get the PID, first thing to do is to search for the process with the highest CreationDate.
'Searching for processes
Dim strScriptName
Dim WMI, wql
Dim objProcess
'
'My process
Dim datHighest
Dim lngMyProcessId
'Which script to look for ?
strScriptName = "WScript.exe"
'strScriptName = "Notepad.exe"
'Iniitialise
datHighest = Cdbl(0)
Set WMI = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
wql = "SELECT * FROM Win32_Process WHERE Name = '" & strScriptName & "'"
'
For Each objProcess In WMI.ExecQuery(wql)
'The next If is not necessary, it only restricts the search to all processes on the current VB Script
'If Instr(objProcess.CommandLine, WScript.ScriptName) <> 0 Then
If objProcess.CreationDate > datHighest Then
'Take the process with the highest CreationDate so far
' e.g. 20160406121130.510941+120 i.e. 2016-04-06 12h11m:30s and fraction
datHighest = objProcess.CreationDate
lngMyProcessId = objProcess.ProcessId
End If
'End If
Next
'Show The result
WScript.Echo "My process Id = " & lngMyProcessId
Powershell can be used to retrieve the calling VBScript process ID. This approach utilizes the optional argument of the exit command which specifies the program's exit code. And, if the optional 3rd argument of the WShell.Run method is set to True, then it will return the exit code (which is the VBScript process ID) after powershell has closed.
Dim sCmd
Dim WShell
sCmd = _
"powershell -command exit " & _
"(gwmi Win32_Process -Filter " & _
"\""processid='$PID'\"").parentprocessid"
Set WShell = CreateObject("WScript.Shell")
MsgBox WShell.Run(sCmd, 0, True)
This is not my answer, I found this in some google groups discussion forum... See if it helps you.
Set objSWbemServices = GetObject ("WinMgmts:Root\Cimv2")
Set colProcess = objSWbemServices.ExecQuery ("Select * From Win32_Process")
For Each objProcess In colProcess
If InStr (objProcess.CommandLine, WScript.ScriptName) <> 0 Then
WScript.Echo objProcess.Name, objProcess.ProcessId, objProcess.CommandLine
End If
Next
Original Discussion Thread in Google Groups forum
Get the current processID
Set WshShell = CreateObject("WScript.Shell")
currentProgram=wscript.ScriptName
Const strComputer = "."
Dim objWMIService, colProcessList
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
query="SELECT * FROM Win32_Process WHERE Name = 'wscript.exe' "
Set colProcessList = objWMIService.ExecQuery(query)
For Each objProcess in colProcessList
If (InStr (objProcess.commandLine,wscript.ScriptName) <> 0 )Then
processDetails="Current ProcessId : "& objProcess.ProcessId & " \n, And Process Name:" & objProcess.name &"\n CommandLine is :"& objProcess.CommandLine
message = msgbox(processDetails,16,"Details")
End If
I used this to get a scripts own process id.
Function GetPid()
GetPid=GetObject("winmgmts:\\.\root\CIMV2").ExecQuery("Select * From Win32_Process Where CommandLine Like '%" &Wscript.ScriptName& "%'").ItemIndex(0).ProcessId
End Function
Wscript.Echo GetPid()

Resources