I have a VBScript setup as a logon script in my GPO. I'm having an issue where every time it runs at logon, I get Permission Denied on the line:
set lf = fso.opentextfile(lfp, 2, true)
Points of interest:
Despite the error, the script completes successfully.
The script executes without error if I run it manually.
The lfp variable points to c:\folder\folder\file.log. The file is created (when necessary) and populated appropriately (overwritten, as expected when it does exist).
If the file is created, the fso is closed before trying to opentextfile.
The user logging in does have modify permission to the path and to the file being replaced when it exists via inherited Authenticated Users permission (from c:\folder1 - see below).
If I throw in a wscript.sleep 30000 just before that line, it just waits 30 seconds to throw permissions denied.
If user is a member of local administrators group on PC, I get no errors. Again, when user is not local admin, it errors, but completes successfully.
I see the same behavior under both Windows 7 and 10.
I'm at a loss here. Here's the pertinent section of code (please excuse any poor coding practice):
'notify robocopy log file location
function seelog(log)
lf.writeline "[" & now & "] " & "See log file for details: " & log
end function
'process robocopy exit codes and write log
function writeerrors(items)
docs = items(0)
retcode = items(1)
logfile = items(2)
if docs = "c" then
name = "some stuff"
else
name = "some other stuff"
end if
If retcode = 0 Then
lf.writeline "[" & now & "] " & name & " folder was already up to date."
elseif retcode = 1 then
lf.writeline "[" & now & "] " & name & " folder was updated."
seelog(logfile)
else
lf.writeline "[" & now & "] " & name & " folder update exited with robocopy error code: " & retcode
seelog(logfile)
End If
end function
'get logged in user
un = CreateObject("WScript.Network").UserName
'check for logfile, and if not exist, create
'folder1 always exists, no need to check or create
lfp = "c:\folder1\folder2\folder3\logfile1.log"
ld1 = "c:\folder1\folder2"
ld2 = "c:\folder1\folder2\folder3"
set fso = createobject("scripting.filesystemobject")
if not fso.fileexists(lfp) then
if not fso.folderexists(ld1) then
fso.createfolder(ld1)
end if
if not fso.folderexists(ld2) then
fso.createfolder(ld2)
end if
set cf = fso.createtextfile(lfp)
cf.close
end if
'open logfile (lfp variable)
'for writing (2)
'overwrite if already exists (true)
wscript.sleep 30000
'************permission denied happens on next line on lfp var*************
Set lf = fso.OpenTextFile(lfp, 2, True)
lf.writeline "[" & now & "] " & "Script started."
lf.writeline "[" & now & "] " & "Logged in user: " & un
lf.writeline "[" & now & "] " & "========================================================="
more code writing to log file and executing robocopy....
I suppose suppressing all errors is an option, but 1) I'd rather not, and 2) I'm not clear on how to accomplish that in VBScript.
ETA:
On Error Resume Next
Set lf = fso.OpenTextFile(lfp, 2, True)
On Error Goto 0
I tested this and it does break the script. lf is not set due to the error so the following lines error out with 'object required "lf"' code 800a01a8 as expected.
I don't like doing this, but my work around was to launch the .vbs from a .bat (personal preference - I like to keep everything from one job in one script so in the future I don't have to go chasing files around).
I placed a logon.bat file in my GPO as the logon script.
#echo off
echo Launching update script...
cscript c:\folder1\script.vbs
This seems to work around the (apparently false) permissions issue I was having. I'm still curious if anyone can tell me exactly why I was seeing the behavior I saw. Does the script engine write to a temp location maybe, when calling OpenTextFile (when launching directly from the GPO) that only admin users would have access to?
On Error Resume Next will ignore the error then On Error Goto 0 will turn normal error handling back on
Related
I have written an error handling sub in my vb script
errorNumber = DoAllWork
Sub ErrorHandling (Number, Description, i)
If Number <> 0 Then
WriteLogFileLine logfile, "Error No : " & Number & " - " & Description & " has occurred !"
Else
WriteLogFileLine logfile, "Success copying files as Err.Number : " & Err.Number & "Total " & i & " files were copied ! " & vbcrlf
End If
Err.Clear
End Sub
And I am calling it in my vb script like this
Function DoAllWork
On Error Resume Next
Err.Clear
Do Until CopyFiles.AtEndOfStream
line = CopyFiles.ReadLine
For Each line In CopyFiles
If objFSO.GetFolder(line).Files.Count <> 0 then
WriteLogFileLine logfile, "Copying files FromLocation " & Chr(34) & line & Chr(34) & " to ToLocation " & Chr(34) & ToLocation & Chr(34)
Else
WriteLogFileLine logfile, "No files present in the folder " & Chr(34) & line & Chr(34) & vbcrlf
End if
i=0
For Each File In objFSO.GetFolder(line).Files
objFSO.GetFile(File).Copy ToLocation & "\" & objFSO.GetFileName(File),True
i=i+1
Next
ErrorHandling Err.Number, Err.Description, i
Next
Loop
End Function
Now the log file which is getting created has this error messages logged in it even though the files has got copied successfully. Can someone please suggest what is wrong with this error handling technique ??
2015-12-15 15:03:47 - Copying files FromLocation "\\srv10219\archive\Article\20151116_073104" to ToLocation "C:\Users\TEMPPAHIR\LearnVB\ICCdata\Article"
2015-12-15 15:03:47 - Error No : 438 - Object doesn't support this property or method has occurred !
when I place this error handling directly after the File.copy statement, it gives me such log..
2015-12-15 16:31:55 - Error No : 438 - Object doesn't support this property or method has occurred !
2015-12-15 16:31:55 - Success copying files as Err.Number : 0
2015-12-15 16:31:55 - Total 2 files were copied !
that means for the first file which is being copied it throws an error and for the second one it gives success even though both the files has been copied successfully
I implemented this to do pretty much what you asked. What the script needed to do was take a folder and its contents that was dropped into a directory, and then copy the contents to a new folder formatted correctly where there was a poller that picked up the files and entered them in our system. I needed the script to quit on any error, especially for the last thing to do. That was to delete the folder from the source directory, but I had to make sure it got copied correctly. (You could clear the error and continue as well.)
So after each important line like a folder create or a file copy I did this check. This worked for me to do error handling. Passing the Err object itself is the best way to go.
Set FolderCreate = FSO.CreateFolder(PreStagingDirectory + "\" + FolderCreateName)
ReportErrors Err,"Error creating folder: " + PreStagingDirectory + "\" + FolderCreateName
FSO.CopyFile objFile.Path, FolderCreate.Path + "\", true
ReportErrors Err,"Error copying file: " + objFile.Path + " to location: " + FolderCreate.Path
Sub ReportErrors(ErrorObject, strExtraInformation)
'This will log any errors if they happen and the script will then quit
If ErrorObject.Number <> 0 Then
OutPutFile.WriteLine(DateTimeString + " Error Number: " & ErrorObject.Number)
OutPutFile.WriteLine(DateTimeString + " Error (Hex): " & Hex(ErrorObject.Number))
OutPutFile.WriteLine(DateTimeString + " Source: " & ErrorObject.Source)
OutPutFile.WriteLine(DateTimeString + " Description: " & ErrorObject.Description)
OutPutFile.WriteLine(DateTimeString + " Other Information: " + strExtraInformation)
OutPutFile.WriteLine(DateTimeString + " Script is quitting due to the Error Condition.")
wscript.quit
End If
End Sub
I have also done code like this. In this case I needed to catch if null was returned from a SQL query to the database. If null was returned and I try to cast the value, it caused an error.
To answer the question of should you check for errors in a sub and clear them, or check for errors all through your script really depends what you need to accomplish.
First I have to say error handling in vbscipt just sucks. Using 'On Error Resume Next', is absolutely needed but be careful where you place it. I found it is not best to place it in main, at the top, just within each function. Most important is to remember the scope, if you put it in a function, you catch the error there, and can handle it. If you only have it at as the first line of your script, you can only handle an error in main. It is needed in main, sometimes.
Also to debug any vbscript, comment out any call to that, or you will never know the problem.
ExecutionString = "select Sum(cast(Frame_Count as int)) from Sop_Instance_T"
Set objRecordSet = objConnection.Execute(ExecutionString)
TotalFrames = cdbl(objRecordSet(0))
'In case null is returned catch the exception
'that happens on the above line
If Err.Number <> 0 Then
TotalFrames = 0
Err.Clear ()
End If
I am writing a script to write event log information to a csv. My script was working before. But I changed some stuff, which shouldn't have any effect on writing to the csv. I have tried writing to unicode and to ASCII, based on some research I did on the internet about this issue. Neither makes a difference. The odd thing is that I use the same code later in my script to write other logs (I first write system logs, then I write application logs, etc.), and it works perfectly there. The code I am using is temporary, as I have not got around to writing a way to delete carriage returns from messages (which causes issues with importing the CSV to Excel). So it might fix itself once I do that. But it seems like it is a larger issue than that. Here is the script up until it moves on to other logs:
Set wshShell = WScript.CreateObject( "WScript.Shell" )
strComputerName = wshShell.ExpandEnvironmentStrings( "%COMPUTERNAME%" )
strComputer = "."
strType = "Error"
strPath = "T:\IT resources\Event Logs\ErrorLog" & strComputerName & ".csv"
'Script to convert UTC to human readable. From Script Repository.
Function WMIDateStringToDate(dtmInstallDate)
WMIDateStringToDate = CDate(Mid(dtmInstallDate, 5, 2) & "/" & _
Mid(dtmInstallDate, 7, 2) & "/" & Left(dtmInstallDate, 4) _
& " " & Mid (dtmInstallDate, 9, 2) & ":" & _
Mid(dtmInstallDate, 11, 2) & ":" & Mid(dtmInstallDate, _
13, 2))
End Function
'ForWriting is to write to file from start. ForAppending is to write to file from end of file.
constForWriting = 2
constForAppending = 8
constTristate = 0
boolUnicode = False
chrCarriageReturn = chr(13)
chrNewLine = chr(10)
Set objFSO = CreateObject("Scripting.FileSystemObject")
'This is so that cscript won't encounter a runtime error if the file already exists. Also so that it will write to the already existing file.
If objFSO.FileExists(strPath)=False Then
Set objErrLog = objFSO.CreateTextFile(strPath,constForWriting,boolUnicode)
objErrLog.Write "Type,"
objErrLog.Write "Time Generated,"
objErrLog.Write "Source Name,"
objErrLog.Write "Event Code,"
objErrLog.Write "Category,"
objErrLog.Write "Message"
objErrLog.Writeline
strTimeMin = "01/01/1970/0:00:00"
'19700101000000.000000-480
Else Set objErrLog = objFSO.OpenTextFile(strPath,constForAppending,constTristate)
'Only need this if it writes from the line the file ends on, as opposed to starting on a new line (which I expect it will).
objErrLog.WriteLine
End If
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
'Querying Event Logs
Set colLoggedEvents = objWMIService.ExecQuery _
("SELECT * FROM Win32_NTLogEvent WHERE Logfile = 'system' AND "_
& "Type = 'Error'")
'Type='Error' instead of "1" because it is a WQL query, I think. I believe that it is searching the entries in a database that reference the Win32_NTLogEvent objects. So I am searching the values in the database as opposed to the properties of the objects they reference. Or perhaps not. WHen I echo the type property of every object in colLoggedEvents, cscript outputs "Error". So maybe the I'm reading the SDK wrong? At least it seems to be working.
'This is a comparison function which tells where string 2 occurs in string 1. Starts at 1.
constStart = 1
constCompareType = 0
'This loop writes the information to a .csv.
For Each objEvent In colLoggedEvents
If objEvent.Timegenerated > strTimeMin Then
strTimeMin = objEvent.TimeGenerated
Else
End If
objErrLog.Write objEvent.Type & ","
objErrLog.Write WMIDateStringToDate(objEvent.TimeGenerated) & ","
objErrLog.Write objEvent.SourceName & ","
objErrLog.Write objEvent.EventCode & ","
constExist=InStr(constStart,objEvent.Message,chrCarriageReturn,constCompareType)+InStr(constStart,objEvent.Message,chrNewLine,constCompareType)
If constExist = 0 Then
objErrLog.Write objEvent.Category & ","
objErrLog.Write objEvent.Message
Else
objErrLog.Write objEvent.Category
End If
objErrLog.WriteLine
Next
Any help would be greatly appreciated.
Loose the misconception that code 'might fix itself'
Give the full error details (number, description, line identified) when asking a question
Assuming that you got a "5 - Invalid procedure call or argument" error on a line starting with "objErrLog.Write" see here for an explanation.
You claim you have tested a variant of your code using Unicode; you didn't, because:
The prototype of .CreateTextFile is
object.CreateTextFile(filename:string[, overwrite:bool[, unicode:bool]])
This clashes with your
objFSO.CreateTextFile(strPath,constForWriting,boolUnicode)
The prototype of .OpenTextFile is
object.OpenTextFile(filename:string[, iomode:enum[, create:bool[, format:enum]]])
This clashes with your
objFSO.OpenTextFile(strPath,constForAppending,constTristate)
So fix these blunders (yourself!), test with the file really opened for Unicode, and hope that assumption (3) holds.
Update wrt comments:
Please reflect upon "Give the full error details (number, description, line identified) when asking a question" in the context of:
I get an invalid procedure error after 68 members of colLoggedEvents
when I have the file in ASCII.
vs
I get the error when I call the OpenTextFile method
The first statement implies that the 68th member contains characters that can't be written in ASCII/ANSI mode. Really/Correctly using Unicode output format will fix the problem, provided the error log does not contain invalid data.
The second statement indicates that the parameters to the .Open/CreateTextfile methods are still not correct. Did you change both invocations to Unicode?
Update II:
The docs define
TristateTrue -1 Opens the file as Unicode.
Your code wrongly uses:
constTristate = 1
Set objErrLog = objFSO.OpenTextFile(strPath,constForAppending,boolCreate,constTristate)
Evidence:
>> Set ts = goFS.OpenTextFile("error5.txt", 8, False, 1)
>>
Error Number: 5
Error Description: Invalid procedure call or argument
>> Set ts = goFS.OpenTextFile("error5.txt", 8, False, -1)
>>
>> <-- no news are good news
Update wrt comment concerning TriStateTrue:
The VBScript doc say:
TristateUseDefault -2 Opens the file using the system default.
TristateTrue -1 Opens the file as Unicode.
TristateFalse 0 Opens the file as ASCII.
The doc #Adam refered to concerns VBA; but I wouldn't trust it without a check.
We have a logon script that sets up default shortcuts on users desktop deployed via Group Policy. This script was used in our previous Windows XP environment. The problem is, the person who set this up copied the shortcuts to %ALLUSERSPROFILE$\Desktop. Now that we're in Windows 7, I'm trying to move the shortcuts to %USERPROFILE%\Desktop and I'm getting permission denied when I try to delete the shortcuts via vbscript. I can delete the shortcuts manually, the UAC prompt comes up, but it works.
Three questions come out of this:
1) In what user context does the script run when run from GPO?
2) In what user context does the script run when run when I run it from the command line and have run the command prompt as administrator?
3) Is there a way to delete these via vbscript in my situation?
Thanks in advance for any help.
I tried using the following script deployed through GP as a startup script to no avail.
'Startup Script
' Force explicit variable declaration.
Option Explicit
On Error Resume Next
Const sPhoneLnk = "Phone_List.lnk"
Const sDesktop = "\Desktop\"
Dim g_oShell, g_oFSO, sAllUsrPrf, sPhoneLink
Set g_oShell = CreateObject("Wscript.Shell")
Set g_oFSO = CreateObject("Scripting.FileSystemObject")
sAllUsrPrf = g_oShell.ExpandEnvironmentStrings("%ALLUSERSPROFILE%")
sPhoneLink = sAllUsrPrf & sDesktop & sPhoneLnk
If g_oFSO.FileExists (sPhoneLink) Then
' wscript.echo sPhoneLnk & " Found."
g_oFSO.DeleteFile (sPhoneLink)
' wscript.echo sPhoneLnk & " Deleted."
Else
' wscript.echo sPhoneLnk & " Not found."
End if
I also tried running the above script from a command prompt as Administrator with UAC turned off and received Access denied.
1) In what user context does the script run when run from GPO?
The logon script activates with the security of the user logging on.
2) In what user context does the script run when run when I run it from the command line and have run the command prompt as administrator?
The script runs as administrator on the local machine.
3) Is there a way to delete these via vbscript in my situation?
Yes. But you should consider, how long do you need this script to be installed? Is it temporary or permanent. If it's temporary, you should write a simple computer startup script which remotes the shortcut links under the all users directory on boot. That way it is not tied to the User Accounts.
If you absolutely want to bypass security for all user accounts and perform actions on user logon no matter what. You can use a domain logon based vbscript:
' ======================================================================
'| name : DSMoveAs.vbs
'| author: Remco Simons [nl] 2007
'|
'| ( http://www.petri.co.il/forums/showthread.php?t=18003 )
' ======================================================================
'
' this script accepts Credentials from command-line
' Usage with GPO:
' Scripts / LogonScript / scriptName -> scriptname.vbs
' Scripts / LogonScript / ScriptParameters -> /u:"domain\user" /p:"password"
'(this user does not nessecarily have to be a member of the Domain Admins group, you can just delegate control over the OU's to it.
'
' this script can move computer objects in active directory
' you have to copy 'dsmove.exe' to a central share
Set objSysInfo = CreateObject("ADSystemInfo")
strComputerDN = objSysInfo.ComputerName
strComputerRDN = split(strComputerDN,",")(0)
strCurrentOU = Replace(strComputerDN, strComputerRDN & ",","")
strCurrentSite = UCase(objSysInfo.SiteName)
'tool
pathDSMOVE = "\\domain.local\sysvol\domain.local\scripts\Dsmove.exe"
'Alternate Credentials
Set Named = WScript.Arguments.Named 'Read script parameters
strUser = Empty
strSecret = Empty
If Named.Exists("u") Then
strUser = Named.Item("u")
If Named.Exists("p") Then _
strSecret = Named.Item("p")
End If
altCredentials = " -u """ & strUser & """ -p """ & strSecret & """"
'variables
strSiteName1 = UCase("New-York")
strSiteName2 = UCase("washington")
'conditional run
If (strCurrentSite = strSiteName1) Then
strNewOU = "CN=computers,DC=domain,dc=Local"
If Not UCase(strCurrentOU) = Ucase(strNewOU) Then
call MoveObject(pathDSMOVE, strComputerDN, strNewOU, altCredentials)
End If
ElseIf (strCurrentSite = strSiteName2) Then
strNewOU = "ou=workstations,DC=domain,dc=Local"
If Not UCase(strCurrentOU) = Ucase(strNewOU) Then
call MoveObject(pathDSMOVE, strComputerDN, strNewOU, altCredentials)
End If
End If
Sub MoveObject(pathDsmove, strComputerDN, targetOU, credentials)
With Wscript.CreateObject("WScript.Shell")
strCommand = pathDsmove & " """ & strComputerDN & """ " _
& "-newparent """ & targetOU & """ " _
& credentials
.Run "%comspec% /c #call " & strCommand,0,True
End With
End Sub
I'd recommend using Group Policy Preferences for modifying desktop shortcuts. Logon scripts are always running in the context of the user logging in. That user may or may not have sufficient privileges for deleting shortcuts from the All Users desktop.
I try several version of Wscript exec method.
If i use a cmd.exe /C MyRequest, the execution does not report an error if the MyRequest is failing but return the error if cmd.exe /c is not used.
I was thinking that cmd.exe is reporting the return code of the call to the MPyRequest but seems not. How to retreive the return code in this case.
Here is the simplified version of my test (comment direct version to have the non failure)
Environnement will be mainly windows 7 (normaly no other system, maybe XP)
' Missing.cmd does not exist to force the failure test
'version with cmd.exe (CmdDir content is a valid and working cmd.exe)
ExecCmd = CmdDir & " /c Missing.cmd 1"
' direct version
ExecCmd = "Missing.cmd 1"
Set objShell = CreateObject("WScript.Shell")
On Error Resume Next
Set oExec = objShell.exec( ExecCmd)
' -- Post treatment ---------------------------
If ( err.Number = 0) Then
If ( oExec.ExitCode = 0 ) Then
' No error
wscript.echo "Execution OK"
Else ' Exit with error
wscript.echo "Error :" & oExec.ExitCode
end if
Else ' error on exec itself
wscript.echo "Execution. Error on object at call: " & err.Number _
& " Source: " & Err.Source & " Desc: " & Err.Description
end if
Solution based on all your reply (thanks all) [ #Damien, #Ekkehard.Horner, #Hans Passant]
check error (via on error and err.Number) for vbs internal error like bad variable/method call. This should be at exec call sub level (so not at main process if exec call is in a function/subroutine)
exec.status to wait until it change to 1. Wait is a following process checking the status, not like a shell.sleep
during this wait, catch stdout/stderr with a AtEndOfStream if life info is needed (if not, read after the close of the process)
for a timeout process, use a cycle of shell.sleep with an exit (of cycle) if timeout or other event is trapped (I use a counter associate to a clock time/sleep time in this case) and exit the loop if trigger occur associate with a exec.Terminate to kill the process in this case (depending of your need ...)
You need to check the ExitCode property.
If it is actually returning an error code, perhaps you are processing exitcode to early. Maybe add in oexec.StdOut.ReadAll() to make it wait until completion.
Perhaps you could add in something like this
....
....
Set oExec = objShell.exec(ExecCmd)
oExecResult = oexec.StdOut.ReadAll()
SessionExitCode = oexec.ExitCode
' -- Post treatment ---------------------------
If ( err.Number = 0) Then
If ( SessionExitCode = 0 ) Then
' No error
wscript.echo "Execution OK"
Else ' Exit with error
wscript.echo "Error :" & SessionExitCode
end if
Else ' error on exec itself
....
....
I'd like to know how to get the line number of a line in vbscript programmaticly either at the point of the code like __LINE__ or more ideally a way to get the line number of where the current function was called like python's stack module so I can write a reusable debugging function(and the file the code is located in) and no I don't want to know how to turn on line numbers in my editor.
Also I'd like to now any similar useful information that can be extracted such as calling function, variable type as string, etc.
Unfortunatly that doesn't work the way like in Ruby and Python. The next best thing i worked out is putting a call to a errorhandling function everywhere where things could go wrong. The numbers in the parameter of this function are adapted each time i execute a macro in my editor (i use textpad, the \i is autonumbering in a Regular Expression). If your editor doesn't support this you could write a script that does this. So when an error occurs, it is logged with the number the errorhandling function was called and you can easily find it back in the source by looking for #number#.
This is usable for both asp and vbs but for vbs there is an easier way.
Some editors like textpad or sublimle text let you execute a vbs script, show the output in a tab and if an error is produced let you double click the line with the errormessage which opens the script at that line. This is also done by a regular expression. Let me know if you need the one for textpad.
on error resume next
'initialize constants DEBUGLEVEL and LOGFILE
'initialize strHostName
'some code
oConn.execute(sql)
if not LogError("#1#") then
'do the things if successfull, otherwise log error with number
end if
'again some code
if not LogError("#2#") then
'do the things if successfull, otherwise log error with number
end if
'the debug and log functions
function LogError(errornumber)
'LogError\(\"#[0-9]+#\"\) replace by LogError("#\i#")
if err.number <> 0 then
call debug("<name of script>/Logerror","","","Errornumber:" _
& errornumber & " " & err.number & " " & err.description & " " _
& err.source)
LogError = True
err.clear
errors = errors+1
else
LogError = False
end if
end function
function Debug (pagina, lijn, varnaam, varinhoud)
if DEBUGLEVEL > 0 then
const forReading = 1, forWriting = 2, forAppending = 8, CreateFile = True
dim fs,f, var, strHostName
set fs=CreateObject("Scripting.FileSystemObject")
strHostName = fs.GetFileName(WScript.FullName)
if fs.FileExists(LOGFILE) then
set f=fs.OpenTextFile(LOGFILE, forAppending)
else
set f=fs.OpenTextFile(LOGFILE, forWriting,true)
end if
var = now & " " & pagina & ":" & lijn & ":" & varnaam & ":" & varinhoud
f.WriteLine var
if LCase(strHostName) = "cscript.exe" then 'debugging
if DEBUGLEVEL > 1 then
wscript.echo var
end if
end if
f.Close
set f=Nothing
set fs=Nothing
end if
debug = true
end function
VBScript doesn't expose that information, so you can't access it programmatically from within the script (edge cases notwithstanding). You're going to need a debugger for extracting this kind of information. Or you could have another script interpret the first one and keep track of line numbers (like this). I wouldn't recommend the latter for any kind of production environment, though.
As long as it's happening outside of a function, the following works.
Automatic error-handling is turned off at the start of the script by On Error Resume Next, so that the script doesn't just exit before you can do anything. BUT, you can then turn error-handling back on using On Error GoTo 0 and Raise an exception yourself. That will output the line number in addition to any of your debugging messages.
For example:
On Error Resume Next
server = WScript.Arguments(0)
If Err.Number <> 0 Then
WScript.Echo("Need to pass in an argument!")
On Error GoTo 0
Err.Raise(1)
End if
If you run this without any arguments, you get the following output:
Need to pass in an argument!
C:\script.vbs(6, 5) Microsoft VBScript runtime error: Unknown runtime error
The "6" refers to the line number where the exception was raised.
This way you can print custom output, and also you'll know what line the error happened at.
Yes!
There is a way to get the exact error line number, but it's HUGLY, as we are talking about an ancient programming tool....
And yes, it is worth it, especially if your code is going to run in front of many users. That way you can get past isolating and reproducing the bug, right to solving it.
Take a close look at the last variable "Erl" in the line of code below. It is an undocumented global variable the VB script processor holds.
Dim sErrorMsg as String
sErrorMsg = Err.Description & "(" & Err.Number & ")" & vbNewLine & "Source: " & Err.Source & vbNewLine & "At line number: " & Erl
In order to get anything from that global "Erl" variable you need to (manually)** set its value at the beginning of each line of code as shown below. Beware, you set the line number, if you forget to set the number for a specific line, Erl will report the last set value. See the division by zero error line, it reports the line number set above because I did not set a line number value at the beginning of the line that caused the error.
I have not figured out the inbuilt call stack, though I know there is one. Please let me know if you figure that one out, for now I use a module level variable to build the stack.
More tips at the very end, below this code sample
Sub WhatEverSub ()
2 Const iColIdxPageNbr As Integer = 2
3 Const iColIdxDefinition As Integer = 3
5 Dim oDoc_Source As Document
6 Dim oDoc_Target As Document
10 Dim oTable As Table
11 Dim oRange As Range
12 Dim n As Long
13 Dim strAllFound As String
14 Dim Title As String
15 Dim Msg As String
On Error GoTo PrepErrorHandler
Dim xyz As Long
xyz = Rnd(3) / 0
16
17 Title = "Evil Finder - This program is about doing something important for the world"
18
19 'Show msg - stop if user does not click Yes
20 Msg = "This macro finds all evil things consisting of 2 or more " & _
"uppercase letters and extracts the hex representation to a table " & _
"in a new document." & vbNewLine & vbNewLine & _
"Do you want to continue?"
21 If MsgBox(Msg, vbYesNo + vbQuestion, Title) <> vbYes Then
22 Exit Sub
23 End If
(... whatever code ...)
820 Application.ScreenUpdating = True
830 If n = 1 Then
840 Msg = "No evil things were found. Need to find better detection tool"
850 oDoc_Target.Close savechanges:=wdDoNotSaveChanges
860 Else
870 Msg = "Finished extracting " & n - 1 & " evil thing(s) to a new document."
880 End If
PrepErrorResumeLine:
890 MsgBox Msg, vbOKOnly, Title
'Clean up
1000 Set oRange = Nothing
1010 Set oDoc_Source = Nothing
1020 Set oDoc_Target = Nothing
1030 Set oTable = Nothing
Exit Sub
PrepErrorHandler:
Msg = Err.Description & "(" & Err.Number & ")" & vbNewLine & "Source: " & Err.Source & vbNewLine & "At line number: " & Erl
Resume PrepErrorResumeLine
End Sub
**Some more tips:
1)
As for setting the error line number values manually, I wrote a utility (more than a decade ago) to automate the addition or removal or renumbering of all lines in a module by working directly on the VB project files (or standalone .vbs files), but the below will take care of the basic, with a few manual adjustsments remaining...
Set up VB code line #s using MS Excel
a) paste code in column C
b) set column A's first cell value to 10, and second to 20 and drag copy down to auto increment until you reach the last line/row of code in column B
c) in column B paste in the following formula and drag copy down =A1 & REPT(" ", 8 - LEN(A1))
d) copy columns B and C back into the VB code pane et voila!
Strip out the line numbers to do major edits using Word
Paste the code in,
Hit CTRL + H and make sure wildcards is checked (click the "more" button)
Fill in the following settings
FIND
[^13][0-9 ]{4}
REPLACE
^p
Done!
2)
number each line in increments of 10 at least so you can wedge in a few lines at the last minute without having to renumber each line below your change
3) On Error Resume Next is evil and will cost you a lot of debugging hours!
At least 90% of the time, one should use a specific handler, or nothing. If you do not already know how to recover from an error, do not use RESUME NEXT to silence it, instead, log all the details (using Erl) and learn from the run time logs and use GoTo 0 'Zero, not the letter O to let the rest of the errors bubble up the call stack.
On Error GoTo MyErrorHandlerSection
(... write your risky code here ...)
On Error GoTo 0
'the line immediately above disables all error handling in the current function, if any error happens, it will be passed to the calling function
Nothing prevents you from adding another handling section in the same function if you have another chunk of risky code using
On Error GoTo MySecondErrorHandlerSection