VBS doesn't complete loop when in sub, but does when not - loops

Long story short, I have a VBS script I'm writing with nested For loops.
If I put the code in a sub, it doesn't complete the loop. If the code is out in the main, it does. Example:
Do
For x = 0 to 9
For y = 0 to 9
For z = 0 to 9
want = CStr(x) & "." & CStr(y) & "." & CStr(z)
If want = "5.1.3"
Exit Do
End If
Next
Next
Next
Loop While 1 = 1
If this is out in main, it works. But this:
NestedLoop()
Sub NestedLoop()
Do
For x = 0 to 9
For y = 0 to 9
For z = 0 to 9
want = CStr(x) & "." & CStr(y) & "." & CStr(z)
If want = "5.1.3"
Exit Do
End If
Next
Next
Next
Loop While 1 = 1
End Sub
Will only loop to 2.0.5
Here's the full source code for each:
Nested Loop in Main
Nested Loop in Sub
Long story short, what it does is detects the OS arch (x86 or x64), goes out to VLC's download index (https://download.videolan.org/pub/videolan/vlc/last/win64/ or /win32/) and downloads to the working dir whatever version of the exe that resolves to HTTP status of 200. This also generates a log file at C:\Temp\vlc-installer.txt
So why would it not work in a sub? I know I could just have it in the main, but I'd prefer a sub.
FWIW, this is a "working interview" project. Me and another candidate for a position are tasked with coming up with an automated deployment solution.

Both of your scripts have global On Error Resume Next statements, so I think you need to know more about how it works.
Please pay attention to the bold sentences.
From On Error Statement
[...]
Within any particular procedure, an error is not necessarily fatal as
long as error-handling is enabled somewhere along the call stack. If
local error-handling is not enabled in a procedure and an error
occurs, control is passed back through the call stack until a
procedure with error-handling enabled is found and the error is
handled at that point. If no procedure in the call stack is found to
have error-handling enabled, an error message is displayed at that
point and execution stops or the host handles the error as
appropriate.
On Error Resume Next causes execution to continue with the statement
immediately following the statement that caused the run-time error, or
with the statement immediately following the most recent call out of
the procedure containing the On Error Resume Next statement. This
allows execution to continue despite a run-time error. You can then
build the error-handling routine inline within the procedure.
An On Error Resume Next statement becomes inactive when another
procedure is called, so you should execute an On Error Resume Next
statement in each called routine if you want inline error handling
within that routine. When a procedure is exited, the error-handling
capability reverts to whatever error-handling was in place before
entering the exited procedure.
[...]
In your case, when an error occurs within your sub procedure called FindVLC, it immediately exits (jumps to End Sub) then continues to work on the global scope by ignoring errors.
So, you should execute an On Error Resume Next statement in your sub routine if you want to ignore (or handle) errors within that as the docs say.
Sub FindVLC()
On Error Resume Next
'for loops etc.
End Sub

Related

How do I echo/print the amount of times I am looping a "do until" loop in command prompt in VBS?

Basically I am trying to print the "Do Until" number in this example to the command prompt. How do I do this? Here is my loop code:
'LOOP START
Dim x
x=0
Do Until x=5
(loop contents)
x=x+1
Loop
'LOOP END
With the trivial example provided, the answer is also trivial. That is, you know that the loop will execute 5 times, so you can just display 5, like so:
WScript.echo "5"
Do Until x=5
x = x + 1
Loop
Presumably, the actual code your working with is more complex, but there's still really only two possibilities: 1) You know how may times the loop is going to execute (as in the example), or 2) you don't know because the loop is going to run until some condition is met. For example, a loop that checks every half second for a certain file to exist, could run any number of times.
Please post an example that more closely matches your actual code and then we can see if that leads to a better answer.

JMeter For-Each Controller - Seems to Run Only First Several Iterations, Then Stops

Here are UDVs, which I use as loop variables:
Here is the For-Each definition.
When I run this from starting = 1 to ending = 11, it only runs 1, 2, 3, 4.
Then it stops.
Here is loop #4.
This shows that variable status_1, status_2, ... , status_11 are all defined.
The question is: why is status_5 and onwards not running?
Either you made a typo/copy paste issue and one of the variables have an extra non-printable character like whitespace or line break
Or there is something else preventing the test from execution like CSV Data Set Config which has run out of the values or error has occurred and Thread Group is configured to stop in that case of whatever.
Check jmeter.log file for any suspicious entries, if nothing unusual is there - increase JMeter logging verbosity and check it again.
Under normal circumstances the ForEach Controller works as expected:

Retrieve ERRORLEVEL From Executed Script [duplicate]

I have a batch file that calls a VBScript (.vbs) program. After calling it, my batch script checks %errorlevel% to see if the .vbs program failed. I can signal failure with an exit code in the .vbs program with WScript.Quit(1).
However, I can only do that explicitly. If some unexpected run-time error happens, the .vbs quits with an error dialog box, however the exit code is zero so my batch file thinks it suceeded! How can I change that behavior?
And if you are thinking of saying, use on error goto, don't bother... that syntax is available in regular VB, but not in VBScript.
I thought of an out-of-the-box solution... Who says 0 has to mean success? VBScript sometimes returns a 0 return code for failures, so why not embrace that? Adopt 0 as (at least one possible) failure code and make up another number (e.g. 10) as the "success code".
At the end of the script, put WScript.Quit(10). That will only be hit if everything succeeded up to that point. Then instead of "if errorlevel 1" in the calling batch file, use "if %errorlevel% == 10"
EDIT : Having tentatively (see caveats) proposed this, I am rapidly beginning to think that it is a very bad idea, but I leave it here for posterity. The most compelling reason to not use this comes from Eric Lippert at Microsoft, who worked on the design & implementation of VBScript. He states, in answer to another question: VBScript does not make any guarantee that terminators always run. This can mean that this sometimes does not return a non-0 exit code in the case of an unhandled error.
I think I personally will use a 'wrapper batch file that subtracts 1 from the cscript exit code' solution in future.
I like the solution linked to by fmunkert, but I think it requires you to put your code in a particular Class_Initalize, which is clumsy at best. I've devised a related solution that does not require this; you simply "Commit" a successful result at the end of your code; if it's not called, any exception causes the ExitCodeHandler's Class_Terminate instance to set a non-zero exit code.
Option Explicit
Class ExitCodeHandler
private exit_code
Public Sub Commit()
exit_code = 0
End Sub
Private Sub Class_Initialize()
exit_code = -1 ' this exit code will be returned if Commit is never called
End Sub
Private Sub Class_Terminate()
if exit_code<>0 then WScript.Quit(exit_code)
End Sub
Public Sub Quit(exitCode)
Commit
WScript.Quit(exitCode) ' exit code will be respected since we have committed
End Sub
End Class
' create one of these at the start:
Dim ech: Set ech = New ExitCodeHandler
WSCript.StdOut.WriteLine "Hello"
s = "" ' undeclared variable causes runtime error - comment out to see success.
' WScript.Quit(-4) ' before a commit, -1 is returned due to the Class_Terminate
' Commit at the end
ech.Commit
' WScript.Quit(-5) ' after a commit, -5 is returned
Note that this idiom is used heavily in C++, where it is called RAII (Resource Acquisition Is Initialization)
You could of course embellish the class this to support other exit codes, error messages etc. You may want to put this in a common vbs file and use a mechanism for includes in vbscript to share it.
Caveats
I don't know the full details of downsides to calling WScript.Quit during stack unwinding due to an exeption in VBScript. I've disovered the following:
Use with caution. I have come up with this and poked around with it when I saw fmunkert's linked suggestion, not used it extensively.
If you explicitly call WScript.Quit(n), the ExitCodeHandler will replace n with its own exit code. The workaround is to either always call ExitCodeHandler.Commit before calling WScript.Quit, or call the supplied ExitCodeHandler.Quit instead which does it for you. However, relying on either of these methods may not always be practical/possible, and it is fairly non-idiomatic and may not be ovbious to maintainers.
If any other object with a Class_Terminate is terminated (i.e. after ExitCodeHandler's Class_Terminate calls WScript.Quit), you seem to get an error. You may get similar behaviour with any COM objects that are being destroyed. I don't know in what order VBScript destroys objects (or even if it's guaranteed), so I've asked about it in another question.
As you say, all that's available is On Error Resume Next, so your forced to use the pattern:
On Error Resume Next
ThingWithAChanceOfThrowingAnError ...
If (Err.number <> 0) then PrintErrorAndQuitWith1(Err.Description)
You could, if it's an option, use jscript instead which has better support for exception handling, including an easy way to return a non-zero exit code on any exception. See the solution to why does my JScript (windows script host) exit with 0 on an uncaught exception?
This is the #1 reason we're choosing jscript over vbscript (when we have to use one of the two!)
You might use the technique described in this article.
It requires you to wrap your script inside a VBScript class.

How do you know when PowerShell script finished?

I'm trying to wrap a piece of code into a WPF application so that a user can just hit the button and the code will run. However, because the script can take time to run, I would like to give a status to the end user so that they know it finished. Is there a way to do that with powershell and this style of code posted below?
1..255 | %{
$I = "192.168.2.$_"
Get-MacAddress($I);
function Get-MacAddress {
...
}
}
TL;DR: You can send an exit 1 to exit with a return code of 1 (or any number other than 0), which means it failed for some reason.
Normally (and under specific circumstances) PowerShell scripts exit with a code of 0. Most other applications return an exit code of 0 whenever they are 'successfully completed,' and without any sort of exception or error that the program is consciously aware occurred.
If you're trapping the exit code in the WPF, you could report whether it was successful (exit 0 inserted at some point in your code, or let it finish as expected), or if it failed (exit of any other number) - at which point you would want to consider reporting unique exit codes specific to the reasons that occurred wrong.
Consider also looking into try, catch, and throw as they're quite useful, as well.
EDIT: As a final note, take a good look at how %errorLevel% (where the exit code is stored) is handled under some unique situations. %errorLevel% is what you want to focus on, if you use exit codes.

Write to and replace a file multiple times in Fortran

I'm trying to run a code that takes a particularly long time. In order for it to complete, I've separated the time step loops as such so that the data can be dumped and then re-read for the next loop:
do 10 n1 = 1, 10
OPEN(unit=11,file='Temperature', status='replace')
if (n1.eq.1) then
(set initial conditions)
elseif (n1.gt.1) then
READ(11,*) (reads the T values from 11)
endif
do 20 n = 1, 10000
(all the calculations for new T values)
WRITE(11,*) (overwrites the T values in 11 - the file isn't empty to begin with)
20 continue
10 continue
My issue then is that this only works for 2 time n1 time steps - after it has replace file 11 once, it no longer replaces and just reiterates the values in there.
Is there something wrong with the open statement? Is there a way to be able to replace file 11 more than once in the same code?
Your program will execute the open statement 10 times, each time with status = 'replace'. On the first go round presumably the file does not exist so the open statement causes the creation of a new, empty, file. On the second go round the file does exist so the open statement causes the file to be deleted and a new, empty, file of the same name to be created. Any attempt to read from that file is likely to cause issues.
I would lift the initial file opening out of the loop and restructure the code along these lines:
open(unit=11,file='Temperature', status='replace')
(set initial conditions)
(write first data set into file)
do n1 = 2, 10
rewind(11)
read(11,*) (reads the T values from 11)
! do stuff
close(11) ! Not strictly necessary but aids comprehension of intent
! Now re-open the file and replace it
open(unit=11,file='Temperature', status='replace')
do n = 1, 10000
(all the calculations for new T values)
write(11,*) (overwrites the T values in 11 - the file isn't empty to begin with)
end do
end do
but there is any number of other ways to restructure the code; choose one that suits you.
In passing, passing data from one iteration to the next by writing/reading a file is likely to be very slow, I'd only use it for checkpointing to support restarting a failed execution.

Resources