Get errorlevel in bat from vbscript - windows

I’m trying to determine, whether the user clicked NO in the UAC-prompt and if so to not set up the nul-port.
I'm calling this script form a batch-file, which I'd like to exit, if the user clicked no.
The VBScript:
Option Explicit
Main
Sub Main
Dim oShell, objWMIService, servSpooler, objReg, objShellApp, result
Const PrinterPort = "NUL:"
Const HKLM = &h80000002
If Not WScript.Arguments.Named.Exists("elevate") Then
Set objShellApp = CreateObject("Shell.Application")
objShellApp.ShellExecute WScript.FullName, WScript.ScriptFullName & " /elevate", "", "runas", 0
WScript.Quit
End If
result = isElevated()
If result = True Then
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
set servSpooler = objWMIService.Get("Win32_Service.Name='spooler'")
Set objReg = GetObject("winmgmts:root\default:StdRegProv")
servSpooler.StopService
objReg.SetStringValue HKLM, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Ports", PrinterPort, ""
servSpooler.StartService
Else
WScript.Quit 1
End If
End Sub
Function isElevated
Dim shell, whoami, whoamiOutput, strWhoamiOutput
Set shell = CreateObject("WScript.Shell")
Set whoami = shell.Exec("whoami /groups")
Set whoamiOutput = whoami.StdOut
strWhoamiOutput = whoamiOutput.ReadAll
If InStr(1, strWhoamiOutput, "S-1-16-12288", vbTextCompare) Then
isElevated = True
Else
isElevated = False
End If
End Function
The batch:
cscript "set_port.vbs"
IF ERRORLEVEL 1 (
ECHO FAIL
PAUSE
EXIT
)
Now, I looked at this page:
http://www.robvanderwoude.com/errorlevel.php
and some others and I feel like I tried every possible combination. Probably, I just haven’t had the correct combination yet. Some tips and help would be highly appreciated!
The basic goal: Determine, whether the user clicked NO in the UAC-prompt and then end the VBScript and batch-file.
UPDATE:
Okay, thanks for all the answers so far. I'm pretty certain now it's the script. I use the errorlevel again in the batch-file and there it works just fine now.
As for the VBScript:
In Order to have an error code of let's say 1 when the user clicks NO in the UAC prompt (meaning the current file is not elevated), I need to put it like this:
If result = True Then
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
set servSpooler = objWMIService.Get("Win32_Service.Name='spooler'")
Set objReg = GetObject("winmgmts:root\default:StdRegProv")
servSpooler.StopService
objReg.SetStringValue HKLM, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Ports", PrinterPort, ""
servSpooler.StartService
WScript.Quit(0)
Else
WScript.Quit(1)
End If
But: in the first WScript.Quit after the ShellExecute, I also need to put WScript.Quit(1), right? Because otherwise I never get an error to be passed to errorlevel (or at least not greater than 0).
So:
objShellApp.ShellExecute WScript.FullName, WScript.ScriptFullName & " /elevate", "", "runas", 0
WScript.Quit(1)
The big problen, I guess, is that clicking NO on the UAC promtp doesnt eally cause an error, so I need to put WSCript.Quit(1) there.
OR i do it the other way round and say: WScript.Quit(1) when the user clicked YES and the script is elevated and put WScript.Quit(0) everyhwere else.
However, in the first case I always get errorlevel 1 and in the second case always errorlevel 0.
----------- UPDATE:
My VBScript file looks like this now:
Option Explicit
Main
Sub Main
Dim objShell, objWMIService, servSpooler, objReg, objShellApp, result, oShell
Dim whoami, strWhoamiOutput, whoamiOutput
Const PrinterPort = "NUL:"
Const HKLM = &h80000002
If Not WScript.Arguments.Named.Exists("elevate") Then
Set objShellApp = CreateObject("Shell.Application")
objShellApp.ShellExecute WScript.FullName, WScript.ScriptFullName & " /elevate", "", "runas", 0
WScript.Quit 10
WScript.Echo("Done")
Else
Set oShell = CreateObject("WScript.Shell")
Set whoami = oShell.Exec("whoami /groups")
Set whoamiOutput = whoami.StdOut
strWhoamiOutput = whoamiOutput.ReadAll
If InStr(1, strWhoamiOutput, "S-1-16-12288", vbTextCompare) Then
Wscript.Echo("ADMIN")
WScript.Echo("Port")
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
set servSpooler = objWMIService.Get("Win32_Service.Name='spooler'")
Set objReg = GetObject("winmgmts:root\default:StdRegProv")
servSpooler.StopService
objReg.SetStringValue HKLM, "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Ports", PrinterPort, ""
servSpooler.StartService
WScript.Quit 1
End if
WScript.Echo("Done 2")
End If
End Sub
And a test batch:
#echo off
cscript "test.vbs"
ECHO %errorlevel%
PAUSE
The errorlevel output is 10 and not 1, although the script is quit as intended and the message "Done" is never shown.

Debugging technique:
Write a VBS script that just sets the errorlevel and quits - and get that working with your batch script.
Then you can massage your full vbs script.

Related

"dir" parameter in Shell.Execute command doesn't work when "verb" parameter contains "runas" option [duplicate]

I need to copy my file "manufacturer.bmp", wich is located in the same directory as the script (in my flash drive), to the system32 directory.
I succeed, in getting the variables sourcefile, destinationdirectory, and to elevate my script, but when I elevate it, my sourcefile variable is lost, because of the use of CurrentDirectory, which differs in this mode.
Set shell = WScript.CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")
CurrentDirectory = fso.GetAbsolutePathName(".")
sourcefile = fso.buildpath(CurrentDirectory, "manufacturer.bmp")
MsgBox(sourcefile)
'Checks if the script is running elevated (UAC)
Function isElevated
Set shell = CreateObject("WScript.Shell")
Set whoami = shell.Exec("whoami /groups")
Set whoamiOutput = whoami.StdOut
strWhoamiOutput = whoamiOutput.ReadAll
If InStr(1, strWhoamiOutput, "S-1-16-12288", vbTextCompare) Then
isElevated = True
Else
isElevated = False
End If
End Function
'Re-runs the process prompting for priv elevation on re-run
Sub uacPrompt
'Check if we need to run in C or W script
interpreter = "wscript.exe"
If InStr(1, WScript.FullName, "CScript", vbTextCompare) = 0 Then
interpreter = "wscript.exe"
Else
interpreter = "cscript.exe"
End If
'Start a new instance with an elevation prompt first
Set shellApp = CreateObject("Shell.Application")
shellApp.ShellExecute interpreter, Chr(34) & WScript.ScriptFullName & _
Chr(34) & " uac", "", "runas", 1
'End the non-elevated instance
WScript.Quit
End Sub
'Make sure we are running elevated, prompt if not
If Not isElevated Then uacPrompt
destinationdir = fso.buildpath(shell.ExpandEnvironmentStrings("%windir%"), _
"system32")
MsgBox(destinationdir)
fso.CopyFile sourcefile, destinationdir
Any idea of how to push my sourcefile var to the child elevated script?
The ShellExecute method allows you to specify the working directory as the 3rd argument, so you can pass the current directory to the elevated script and build the sourcefile path after elevation. Also, your code could be streamlined quite a bit.
Const HKLM = &h80000002
Const DELETE = &h10000
Set sh = CreateObject("WScript.Shell")
Set reg = GetObject("winmgmts://./root/default:StdRegProv")
reg.CheckAccess HKLM, "SYSTEM\CurrentControlSet", DELETE, isAdmin
If Not isAdmin Then
If WScript.Arguments.Count = 0 Then
CreateObject("Shell.Application").ShellExecute WScript.FullName, _
Chr(34) & WScript.ScriptFullName & Chr(34) & " uac", _
sh.CurrentDirectory, "runas", 1
WScript.Quit 0
Else
WScript.Echo "Privilege elevation failed!"
WScript.Quit 1
End If
End If
Set fso = CreateObject("Scripting.FileSystemObject")
src = fso.BuildPath(sh.CurrentDirectory, "manufacturer.bmp")
dst = fso.buildpath(sh.ExpandEnvironmentStrings("%windir%"), "system32")
fso.CopyFile src, dst & "\"
Edit: or at least that's how it would work if you weren't elevating the process. According to this blog post from Raymond Chen the start directory is ignored when elevating processes, so that malicious DLLs from the current directory aren't loaded into elevated processes by mistake. Meaning that you must pass the working directory "manually", like this:
Const HKLM = &h80000002
Const DELETE = &h10000
Set sh = CreateObject("WScript.Shell")
Set reg = GetObject("winmgmts://./root/default:StdRegProv")
reg.CheckAccess HKLM, "SYSTEM\CurrentControlSet", DELETE, isAdmin
If Not isAdmin Then
If WScript.Arguments.Count = 0 Then
CreateObject("Shell.Application").ShellExecute WScript.FullName, _
Chr(34) & WScript.ScriptFullName & Chr(34) & " " & _
Chr(34) & sh.CurrentDirectory & Chr(34), , "runas", 1
WScript.Quit 0
Else
WScript.Echo "Privilege elevation failed!"
WScript.Quit 1
End If
End If
sh.CurrentDirectory = WScript.Arguments(0)
Set fso = CreateObject("Scripting.FileSystemObject")
src = fso.BuildPath(sh.CurrentDirectory, "manufacturer.bmp")
dst = fso.buildpath(sh.ExpandEnvironmentStrings("%windir%"), "system32")
fso.CopyFile src, dst & "\"
Note that since your destination path is a folder, it must have a trailing backslash (as documented).

transfer CurrentDirectory from un-elevated script to elevated one

I need to copy my file "manufacturer.bmp", wich is located in the same directory as the script (in my flash drive), to the system32 directory.
I succeed, in getting the variables sourcefile, destinationdirectory, and to elevate my script, but when I elevate it, my sourcefile variable is lost, because of the use of CurrentDirectory, which differs in this mode.
Set shell = WScript.CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")
CurrentDirectory = fso.GetAbsolutePathName(".")
sourcefile = fso.buildpath(CurrentDirectory, "manufacturer.bmp")
MsgBox(sourcefile)
'Checks if the script is running elevated (UAC)
Function isElevated
Set shell = CreateObject("WScript.Shell")
Set whoami = shell.Exec("whoami /groups")
Set whoamiOutput = whoami.StdOut
strWhoamiOutput = whoamiOutput.ReadAll
If InStr(1, strWhoamiOutput, "S-1-16-12288", vbTextCompare) Then
isElevated = True
Else
isElevated = False
End If
End Function
'Re-runs the process prompting for priv elevation on re-run
Sub uacPrompt
'Check if we need to run in C or W script
interpreter = "wscript.exe"
If InStr(1, WScript.FullName, "CScript", vbTextCompare) = 0 Then
interpreter = "wscript.exe"
Else
interpreter = "cscript.exe"
End If
'Start a new instance with an elevation prompt first
Set shellApp = CreateObject("Shell.Application")
shellApp.ShellExecute interpreter, Chr(34) & WScript.ScriptFullName & _
Chr(34) & " uac", "", "runas", 1
'End the non-elevated instance
WScript.Quit
End Sub
'Make sure we are running elevated, prompt if not
If Not isElevated Then uacPrompt
destinationdir = fso.buildpath(shell.ExpandEnvironmentStrings("%windir%"), _
"system32")
MsgBox(destinationdir)
fso.CopyFile sourcefile, destinationdir
Any idea of how to push my sourcefile var to the child elevated script?
The ShellExecute method allows you to specify the working directory as the 3rd argument, so you can pass the current directory to the elevated script and build the sourcefile path after elevation. Also, your code could be streamlined quite a bit.
Const HKLM = &h80000002
Const DELETE = &h10000
Set sh = CreateObject("WScript.Shell")
Set reg = GetObject("winmgmts://./root/default:StdRegProv")
reg.CheckAccess HKLM, "SYSTEM\CurrentControlSet", DELETE, isAdmin
If Not isAdmin Then
If WScript.Arguments.Count = 0 Then
CreateObject("Shell.Application").ShellExecute WScript.FullName, _
Chr(34) & WScript.ScriptFullName & Chr(34) & " uac", _
sh.CurrentDirectory, "runas", 1
WScript.Quit 0
Else
WScript.Echo "Privilege elevation failed!"
WScript.Quit 1
End If
End If
Set fso = CreateObject("Scripting.FileSystemObject")
src = fso.BuildPath(sh.CurrentDirectory, "manufacturer.bmp")
dst = fso.buildpath(sh.ExpandEnvironmentStrings("%windir%"), "system32")
fso.CopyFile src, dst & "\"
Edit: or at least that's how it would work if you weren't elevating the process. According to this blog post from Raymond Chen the start directory is ignored when elevating processes, so that malicious DLLs from the current directory aren't loaded into elevated processes by mistake. Meaning that you must pass the working directory "manually", like this:
Const HKLM = &h80000002
Const DELETE = &h10000
Set sh = CreateObject("WScript.Shell")
Set reg = GetObject("winmgmts://./root/default:StdRegProv")
reg.CheckAccess HKLM, "SYSTEM\CurrentControlSet", DELETE, isAdmin
If Not isAdmin Then
If WScript.Arguments.Count = 0 Then
CreateObject("Shell.Application").ShellExecute WScript.FullName, _
Chr(34) & WScript.ScriptFullName & Chr(34) & " " & _
Chr(34) & sh.CurrentDirectory & Chr(34), , "runas", 1
WScript.Quit 0
Else
WScript.Echo "Privilege elevation failed!"
WScript.Quit 1
End If
End If
sh.CurrentDirectory = WScript.Arguments(0)
Set fso = CreateObject("Scripting.FileSystemObject")
src = fso.BuildPath(sh.CurrentDirectory, "manufacturer.bmp")
dst = fso.buildpath(sh.ExpandEnvironmentStrings("%windir%"), "system32")
fso.CopyFile src, dst & "\"
Note that since your destination path is a folder, it must have a trailing backslash (as documented).

Running a VBS script elevated to get remote computer serial number

Ok, I have an error someplace in here, but not sure where. I am NOT a coder by any means, this is something I have put together from a couple of different sources. This code works, however it seems to run once as a normal user and once at elevated permissions... I just need it to run just once at elevated permissions.
Set WshShell = WScript.CreateObject("WScript.Shell")
If WScript.Arguments.length = 0 Then
Set ObjShell = CreateObject("Shell.Application")
ObjShell.ShellExecute "wscript.exe", """" & _
WScript.ScriptFullName & """" &_
" RunAsAdministrator", , "runas", 1
End if
On Error Resume Next
Dim System
if Wscript.Arguments.Count >0 then
sSystem=Wscript.Arguments(0)
end if
ComputerName = InputBox("Enter the name of the computer you wish to query")
winmgmt1 = "winmgmts:{impersonationLevel=impersonate}!//"& ComputerName &""
Set SNSet = GetObject( winmgmt1 ).InstancesOf ("Win32_BIOS")
for each SN in SNSet
MsgBox "The serial number for the specified computer is: " & SN.SerialNumber
next
This is the part that re-runs your script with elevated privileges by using the Shell.ShellExecute method with the "runas" verb:
If WScript.Arguments.length = 0 Then
Set ObjShell = CreateObject("Shell.Application")
ObjShell.ShellExecute "wscript.exe", """" & _
WScript.ScriptFullName & """" &_
" RunAsAdministrator", , "runas", 1
End if
Re-running the script with the additional parameter RunAsAdministrator makes sure that the re-run script skips the above part (since WScript.Arguments.Length is greater than 0 due to that parameter) and goes directly to the "worker" code.
However, the above code snippet doesn't exit after re-running the script, so both the elevated and the original invocation are executing the worker code.
Add a WScript.Quit statement to your code to make the original invocation exit right after re-running itself with elevated permissions and the issue will disappear:
If WScript.Arguments.Length = 0 Then
Set ObjShell = CreateObject("Shell.Application")
ObjShell.ShellExecute "wscript.exe", _
"""" & WScript.ScriptFullName & """ RunAsAdministrator", , "runas", 1
WScript.Quit 0
End If
that's all (for remote computer):
ComputerName = InputBox("Enter the name of the computer you wish to query")
winmgmt1 = "winmgmts:(impersonationLevel=impersonate}!//"& ComputerName &"\root\cimv2")
Set SNSet = winmgmt1.ExecQuery("Select * from Win32_BIOS")
for each SN in SNSet
MsgBox "The serial number for the specified computer is: " & SN.SerialNumber
next

how to add a log to my vbscript

i have this script that reads a list of computers and check to see if the computers have the right software version install. the script echo to me the computers with the wrong version, but i want to make a log instead
Dim strComputer, objFSO, ObjShell, strDisplayName, objList, strObject
Dim objReg, arrSubKeys, strProduct, strVersion, strReqVersion
Const For_Writing = 2
Const ForReading = 1
const ForAppending = 3
Const HKLM = &H80000002
Const strKeyPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
strReqVersion = "8.2.1 MP2"
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objShell = CreateObject("WScript.Shell")
Set objList = objFSO.OpenTextFile("c:\test\test.txt",ForReading)
Do While Not objList.AtEndOfStream
strComputer = objList.ReadLine
If HostOnline(strComputer) = True Then
Inventory(strComputer)
End If
Loop
Function Inventory(strComputer)
Set objTextFile = objFSO.OpenTextFile("c:\test\inventory.txt",2,true)
'creating a dictionary object
Set objDictionary = CreateObject("Scripting.Dictionary")
Set objReg = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & _
strComputer & "\root\default:StdRegProv")
' Enumerate the subkeys of the Uninstall key
objReg.EnumKey HKLM, strKeyPath, arrSubKeys
For Each strProduct In arrSubKeys
' Get the product's display name
objReg.GetStringValue HKLM, strKeyPath & "\" & strProduct, "DisplayName", strDisplayName
' Process only products whose name contain 'symantec'
If InStr(1, strDisplayName, "Symantec", vbTextCompare) > 0 Then
' Get the product's display version
objReg.GetStringValue HKLM, strKeyPath & "\" & strProduct, "DisplayVersion", strVersion
If strReqVersion <> strVersion Then
WScript.Echo strObject
objDictionary.Add strComputer, strVersion
For Each strObject In objDictionary
WScript.Echo strObject
objTextFile.WriteLine(strObject)
Next
objTextFile.Close
End If
End If
Next
End Function
Function HostOnline(strComputername)
'---------- Test to see if host or url alive through ping -----------------
' Returns True if Host responds to ping
'
' strComputername is a hostname or IP
Const OpenAsASCII = 0
Const FailIfNotExist = 0
Const ForReading = 1
Dim objShell, objFSO, sTempFile, fFile
Set objShell = CreateObject("WScript.Shell")
Set objFSO = CreateObject("Scripting.FileSystemObject")
sTempFile = objFSO.GetSpecialFolder(2).ShortPath & "\" & objFSO.GetTempName
objShell.Run "cmd /c ping -n 2 -l 8 " & strComputername & ">" & sTempFile, 0 , True
Set fFile = objFSO.OpenTextFile(sTempFile, ForReading, FailIfNotExist, OpenAsASCII)
Select Case InStr(fFile.ReadAll, "TTL=")
Case 0
HostOnline = False
Case Else
HostOnline = True
End Select
ffile.close
objFSO.DeleteFile(sTempFile)
Set objFSO = Nothing
Set objShell = Nothing
End Function
can some one help me please thanks
There are several ways to do this. The simplest way, without any modification to your script, would be to call the script with cscript.exe (in a command prompt) and redirect the output to a file:
cscript your.vbs > output.log
However, if you want a log to be created even when users double-click your script you'll have to change your script so that it writes to a file instead of echoing the output. Open the log file at the beginning of the script:
Set myLog = objFSO.OpenTextFile("C:\my.log", For_Writing, True)
replace WScript.Echo ... with myLog.WriteLine ..., and close the file before you exit from the script:
myLog.Close
A somewhat more sophisticated approach would be to create a set of logging functions, which will allow you create log lines depending on certain conditions, e.g. LogInfo() for informational log messages and LogError() for errors.
Shameless plug: Some time ago I got fed up with writing the same boilerplate logging functions over and over again, so I wrote a logger class that encapsulates the usual logging facilities (interactive console, files, eventlog) and provides logging methods for 4 log levels (Error, Warning, Information, Debug). The class can be used for logging to a file like this:
Set myLog = New CLogger
myLog.LogToConsole = False
myLog.LogFile = "C:\my.log"
myLog.LogInfo "info message"
...
myLog.LogError "an error occurred"
The log file is automatically closed when the object is released.
Why not use the system's event log? I described how in this answer
It means most of the work is done for you and you don't need to worry about where to put your log file

Show Message dialog while executing

I use this snippet in vbscript:
Set WSH = CreateObject("WScript.Shell")
cmd = "some command"
flag = WSH.Run(cmd, 0, true)
As it can be noticed, in .Run() call, "WaitOnReturn" is set to "true" as I want to know when external program finishes and additionally it status
Problem is that external program needs some time to finish and I want to pop "Please wait..." MsgBox but I can't this way as I set "WaitOnReturn" on "true" which I need as I need result from that program for additional processing
Is there a way I can show somehow this MsgBox while external program is executed?
Sorry, it slipped to me that i can call MsgBox just before executing, Run()
:embarrassed:
Edit:
for no user interaction here is one workaround (taken from http://www.robvanderwoude.com/vbstech_ui_progress.php)
Function ProgressMsg( strMessage, strWindowTitle )
' Written by Denis St-Pierre
Set wshShell = WScript.CreateObject( "WScript.Shell" )
strTEMP = wshShell.ExpandEnvironmentStrings( "%TEMP%" )
If strMessage = "" Then
On Error Resume Next
objProgressMsg.Terminate( )
On Error Goto 0
Exit Function
End If
Set objFSO = CreateObject("Scripting.FileSystemObject")
strTempVBS = strTEMP + "\" & "Message.vbs"
Set objTempMessage = objFSO.CreateTextFile( strTempVBS, True )
objTempMessage.WriteLine( "MsgBox""" & strMessage & """, 4096, """ & strWindowTitle & """" )
objTempMessage.Close
On Error Resume Next
objProgressMsg.Terminate( )
On Error Goto 0
Set objProgressMsg = WshShell.Exec( "%windir%\system32\wscript.exe " & strTempVBS )
Set wshShell = Nothing
Set objFSO = Nothing
End Function
Then call it with:
ProgressMsg "Installing, Please wait.", "Some title"
end terminate it with:
ProgressMsg "", "Some title"
I was given an answer on another blog, basically, all I had to do was dim the variable "ProgressMsg" globally.
Thanks

Resources