VBScript for terminating SAP generated Workbook Spreadsheet - vbscript

Can somebody help with this script I am using for closing/terminating SAP Workbook? This worked for a long time until the time that the company updated our MS Office to a new version (MS Office 365 ProPlus). The intention of this code is to force close a certain SAP generated spreadsheets that keeps on auto-pop up when extracting a SAP reports. This old code was shared by #ScriptMan before in this forum.
SAP_Workbook = "ZZFRAR080_EXTRACTED.XLSX"
on error resume next
do
err.clear
Set xclApp = GetObject(, "Excel.Application")
If Err.Number = 0 Then exit do
'msgbox "Wait for Excel session"
wscript.sleep 2000
loop
do
err.clear
Set xclwbk = xclApp.Workbooks.Item(SAP_Workbook)
If Err.Number = 0 Then exit do
wscript.sleep 2000
loop
on error goto 0
Set xclSheet = xclwbk.Worksheets(1)
xclApp.Visible = True
xclapp.DisplayAlerts = false
xclapp.ActiveWorkbook.Close
Set xclwbk = Nothing
Set xclsheet = Nothing
'xclapp.Quit
set xclapp = Nothing
Based on my initial checking, the issue is found in this part as it keeps on looping endlessly (detected it by putting a MsgBox after the loop). I have noticed that the new version of Excel 2016 makes the generated spreadsheets as new instance/session like it keeps on creating new Excel.exe in the process tab.
do
err.clear
Set xclwbk = xclApp.Workbooks.Item(SAP_Workbook)
If Err.Number = 0 Then exit do
wscript.sleep 2000
loop

You could try the following:
SAP_Workbook = "ZZFRAR080_EXTRACTED.XLSX"
Set Wshell = CreateObject("WScript.Shell")
Wshell.Run "c:\tmp\SAP_Workbook_Close.vbs" & " " & SAP_Workbook, 1, False
. . .
SAP_Workbook_Close.vbs:
myFileName = wscript.arguments(0)
on error resume next
do
err.clear
Set xclApp = GetObject(, "Excel.Application")
'xclApp.WindowState = -4140 'xlMinimized
If Err.Number = 0 Then exit do
'msgbox "Wait for Excel session"
wscript.sleep 2000
loop
do
err.clear
Set xclwbk = xclApp.Workbooks.Item(myFileName)
'xclApp.WindowState = -4140 'xlMinimized
If Err.Number = 0 Then exit do
'msgbox "Wait for SAP workbook"
wscript.sleep 2000
loop
on error goto 0
Set xclSheet = xclwbk.Worksheets(1)
xclapp.ActiveWorkbook.Close
xclApp.Visible = True
xclapp.DisplayAlerts = false
'xclApp.WindowState = -4143 'xlNormal
Set xclwbk = Nothing
Set xclsheet = Nothing
'xclapp.Quit
set xclapp = Nothing
Regards,
ScriptMan

Related

Continuously looping VBScript needs to be more robust to errors

I'm writing a program to open and run multiple VBA modules on a server to upload new data to the cloud. I need to run the modules every 5 minutes at least (preferably every 2 minutes) without stopping 24/7 for an extended period of time. So far, my code will work for 1000-4000 loops, but fails every 1-4 days. How can I make my program more robust?
I'm pretty new to VBScripts, but have pieced together some code that works pretty well from other examples I've found through my research. I've added some error handling (On Error Resume Next and an error check after each major operation) but there are still occasional errors that sneak through and stop the program.
The errors I've been seeing include "unknown VBA runtime error," permission errors (when opening the local log), and server errors. I've tried to mitigate or ignore these with pings and error handling, but some still manage to stop the code from looping.
I've also tried using the Windows Task Scheduler, but that can only go at most every 5 minutes and I couldn't get it to run reliably.
On Error Resume Next
Set WshShell = CreateObject("WScript.Shell")
Dim objFSO
Dim myLog
Dim myLocalLog
Dim pingResultV
Dim pingResultN
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objExcel = CreateObject("Excel.Application")
objExcel.DisplayAlerts = False
objExcel.Visible = True
Set myLog = objFSO.OpenTextFile("location of log on server", 8)
Set myLocalLog = objFSO.OpenTextFile("location of log on local hard drive", 8)
Dim i
i = 0
Do While i < 1000000
'Ping the server to check if the connection is open
PINGFlagV = Not CBool(WshShell.run("ping -n 1 server",0,True))
pingResultV = "Null"
'if the ping was successful, open each file in sequence and run the VBA modules
If PINGFlagV = True Then
timeStamp = Now()
Set Wb = objExcel.WorkBooks.Open("excel file on server",,True)
objExcel.Application.Run "VBA module to copy data to cloud"
objExcel.Application.Quit
If Err.Number <> 0 Then
myLocalLog.WriteLine(timeStamp & " Error in running VBA Script: " & Err.Description)
Err.Clear
End If
Set Wb2 = objExcel.WorkBooks.Open("second excel file on server",,True)
objExcel.Application.Run "VBA module to copy this data to cloud"
objExcel.Application.Quit
If Err.Number <> 0 Then
myLocalLog.WriteLine(timeStamp & " Error in running VBA Script 2: " & Err.Description)
Err.Clear
End If
'Write to the server log that it succeeded or failed
pingResultV = "Server Ping Success"
myLog.WriteLine(timeStamp & " i=" & i & " --- " & pingResultV)
Else
pingResultV = "Server Ping Failed"
timeStamp = Now()
End If
'Write to a local log whether the server was available or not
myLocalLog.WriteLine(timeStamp & " i=" & i & " --- " & pingResultV)
If Err.Number <> 0 Then
myLog.WriteLine(timeStamp & " Error in writing to local log: " & Err.Description)
Err.Clear
End If
'Time between iterations in milliseconds. 120000 ms = 2 minutes
WScript.Sleep(30000)
i = i + 1
Loop
I'm not asking for someone to overhaul my code, but I would appreciate any suggestions on improvements. I'm also open to methods other than VBScript if they're more robust.
Some additional background on the system that might be helpful: I have 2 computers that upload data to a server every few minutes, but these computers can't be connected to the internet for security reasons. I need to upload the data these computers generate to an internet database every few minutes so it can be accessed remotely. The code I wrote runs on a separate computer that's connected to the server and internet, and it periodically opens the excel files on the server in read-only mode and runs a VBA script that uploads new data lines to the cloud.
It's hard to tell whether your VBScript or your VBA module or both fail. From your error descriptions, I somewhat expect the latter to be the case. So I'd also suggest to check your VBA coda (again). As for this script, one thing I'd suggest to get rid off the permission / log file error would be to move the logging to its own method, e.g.
' *** VBScript (FSO) ***
Const ForReading = 1
Const ForWriting = 2
Const ForAppending = 8
' Log file
Const LOG_FILE = "C:\MyData\MyLog.txt"
Sub WriteLog (ByVal sLogFile, ByVal sText)
Dim oFso
Dim oLogFile
Set oFso = CreateObject("Scripting.FileSystemObject")
Set oLogFile = oFso.OpenTextFile(sLogFile, ForAppending, True)
' Prefix log message with current date/time
sText = Now & " " & sText
oLogFile.WriteLine sText
oLogFile.Close
' Free resources
Set oLogFile = Nothing
Set oFso = Nothing
End Sub
And call it like
If Err.Number <> 0 Then
WriteLog LOG_FILE, "Error in writing to local log: " & CStr(Err.Number) & ", " & Err.Description
Err.Clear
End If
I also suggest to release all resources at the end of the loop, e.g. the Excel object, for which you need to move the object creation into the loop:
Dim i
i = 0
Do While i < 1000000
' Start of loop
Set objExcel = CreateObject("Excel.Application")
objExcel.DisplayAlerts = False
objExcel.Visible = True
' Do your stuff ...
' End of Loop -> free resources
Set Wb = Nothing
Set Wb2 = Nothing
objExcel.WorkBooks.Close
Set objExcel = Nothing
' Time between iterations in milliseconds. 120000 ms = 2 minutes
WScript.Sleep(30000)
i = i + 1
Loop
And as I'm not an Excel expert, I wonder if objExcel.Visible = True does make sense here or if it would better be set to False?

How Can I pause speak command in vbscript? I have to play it from the same paused position

How Can I pause speak command in vbscript? I have to play it from the same paused position.
Code block:
Dim Speak, Path
Path = "string"
Path = "C:\Users\sony\Desktop\TheReunion.txt"
const ForReading = 1
Set objFileToRead = CreateObject("Scripting.FileSystemObject").OpenTextFile(Path,ForReading)
strFileText = objFileToRead.ReadAll()
Set Speak=CreateObject("sapi.spvoice")
Speak.Speak strFileText
objFileToRead.Close
Set objFileToRead = Nothing
You need to call the speak method asynchronously before using the pause and resume methods as mentioned by LotPings in the comments.
Code:
Dim Speak, Path
Path = "string"
Path = "C:\Users\sony\Desktop\TheReunion.txt"
const ForReading = 1
Set objFileToRead = CreateObject("Scripting.FileSystemObject").OpenTextFile(Path,ForReading)
strFileText = objFileToRead.ReadAll()
Set Speak=CreateObject("sapi.spvoice")
Speak.Speak strFileText,1 '1=Asynchronous. Click the link below for other possible values "SpeechVoiceSpeakFlags"
'Due to async call to speak method, we can proceed with the code execution while the voice is being played in the background. Now we can call pause and resume methods
wscript.sleep 5000 'voice played for 5000ms
Speak.pause 'paused
wscript.sleep 4000 'remains paused for 4000ms
Speak.resume 'resumes
objFileToRead.Close
Set objFileToRead = Nothing
SpeechVoiceSpeakFlags
Intrigue in this led me to take inspiration from Kira's answer and develop it somewhat (in a bad, novice kind of way), to achieve the pause/resume objective interactively, the code below works for me, and hopefully it's of some help to you...
option explicit
dim strpath, fso, strfile, strtxt, user, voice, flag
flag = 2
call init
sub init
do while len(strpath) = 0
strpath = inputbox ("Please enter the full path of txt file", "Txt to Speech")
if isempty(strpath) then
wscript.quit()
end if
loop
'strpath = "C:\Users\???\Desktop\???.txt"
set fso = createobject("scripting.filesystemobject")
on error resume next
set strfile = fso.opentextfile(strpath,1)
if err.number = 0 then
strtxt = strfile.readall()
strfile.close
call ctrl
else
wscript.echo "Error: " & err.number & vbcrlf & "Source: " & err.source & vbcrlf &_
"Description: " & err.description
err.clear
call init
end if
end sub
sub ctrl
user = msgbox("Press ""OK"" to Play / Pause", vbokcancel + vbexclamation, "Txt to Speech")
select case user
case vbok
if flag = 0 then
voice.pause
flag = 1
call ctrl
elseif flag = 1 then
voice.resume
flag = 0
call ctrl
else
call spk
end if
case vbcancel
wscript.quit
end select
end sub
sub spk
'wscript.echo strtxt
set voice = createobject("sapi.spvoice")
voice.speak strtxt,1
flag = 0
call ctrl
end sub

Test ADO connection and reconnect prior to execution if needed, in VBScript

I have the following VBScript (vbs):
Option Explicit
Dim cn, cmDB, rs
Set cn = CreateObject("ADODB.Connection")
cn.ConnectionString = "DSN=PostgreSQLDNSHere"
cn.Open
cn.CommandTimeout = 28800
Set cmDB = CreateObject("ADODB.Command")
cmDB.CommandTimeout = 28800
Set rs = CreateObject("ADODB.Recordset")
rs.CursorType = 2
MsgBox "disconnected network here then clicked ok to proceed"
MsgBox cn.State
MsgBox cmDB.State
MsgBox rs.State
Set rs = cn.Execute("select * from test;")
WScript.Quit
At the first message box I would like to simulate losing a connection to our database. Possible causes could be that the database is down or the LAN is down, etc. In other words, I want to test if the connection is in good order so a valid execute statement will succeed. The msgboxes above never change after I disconnect from the network.
The only way I can currently do it is to Execute after a On Error Resume Next, then look at the Err.Number. Is there a way to test the connection prior to the execute so I can reconnect then execute like this:
Option Explicit
Dim cn, cmDB, rs
Set cn = CreateObject("ADODB.Connection")
cn.ConnectionString = "DSN=PostgreSQLDNSHere"
cn.Open
cn.CommandTimeout = 28800
Set cmDB = CreateObject("ADODB.Command")
cmDB.CommandTimeout = 28800
Set rs = CreateObject("ADODB.recordset")
rs.CursorType = 2
MsgBox "disconnected network here then clicked ok to proceed"
If cn.State = ?? Then
'reconnect here
End If
Set rs = cn.Execute("select * from test;")
WScript.Quit
EDIT1:
I also tried setting the recordset after disconnect, but that didn't change the message box result in the first code snippet.
The State property indicates just the state of the connection on the client side. AFAIK you need to execute a query in order to detect whether or not the server is still available.
Set cmd = CreateObject("ADODB.Command")
cmd.ActiveConnection = cn
cmd.CommandText = "SELECT 1;"
On Error Resume Next
Set rs = cmd.Execute
If Err Then
If Err.Number = &h80004005 Then
'server side disconnected -> re-open
cn.Close
cn.Open
Else
WScript.Echo "Unexpected error 0x" & Hex(Err.Number) & ": " & Err.Description
WScript.Quit 1
End If
End If
Note that you may need to re-assign the re-opened connection to the object using it.
Note also that the above does just the most basic reconnect by closing and re-opening the connection. In real-world scenarios you may want to be able to retry at least a couple times if the reconnect fails as well (e.g. because the network or server hasn't come back up yet).
Using Ansgar's suggestion I am posting code that will "try at least a couple times". The function will return the connection object if it successfully reconnects or the connection is already good, else nothing after trying a user input number of times and waiting a user input number of seconds between tries:
Option Explicit
dim cn, cmDB, rs
set cn = CreateObject("ADODB.Connection")
cn.ConnectionString= "DSN=PostgreSQLDsn"
cn.open
cn.CommandTimeout = 28800
Set cmDB = CreateObject("ADODB.Command")
cmDB.CommandTimeout = 28800
set rs = CreateObject("ADODB.recordset")
rs.CursorType = 2
msgbox "disconnected internet here then clicked ok to proceed"
set cn = TestReOpenConnection(cn,"DSN=PostgreSQLDsn",28800,2,100)
if cn is nothing then
msgbox "not good"
WScript.Quit
end if
set rs = cn.execute("select * from test;")
msgbox "all good: " & rs.fields("x")
WScript.Quit
function TestReOpenConnection(cn,sDsn,iConnTimeOut,iWaitSecs,iTimesToTry)
dim iWaitMilSecs
iWaitMilSecs = iWaitSecs * 1000
dim bConnected
bConnected = false
dim iTries
iTries = 0
dim rsTest
set rsTest = CreateObject("ADODB.recordset")
do while bConnected = false
On Error Resume Next
Set rsTest = cn.execute("select 1;")
If Err Then
if iTries <> 0 then
WScript.Sleep iWaitMilSecs 'if we tried once already, then wait
end if
cn.Close
set cn = CreateObject("ADODB.Connection")
cn.ConnectionString= sDsn
On Error Resume Next
cn.open
cn.CommandTimeout = iConnTimeOut
else
bConnected = true
set TestReOpenConnection = cn
End If
iTries = iTries + 1
if iTries > iTimesToTry then
set TestReOpenConnection = nothing
exit do
end if
loop
end function
This answer isn't necessary to the central question I asked, but I thought it would be useful to people viewing this in the future. Probably could use some cleaning up.

To run two or more .VBS script parallely

I have two .vbs file say a.vbs and b.vbs.Now both are written for the same Excel,but would work on 2 different sheets.So can we run those in parallel?
EDIT
a.vbs will update sheet2 and b.vbs will update sheet3.But for both source sheet is sheet1.
Please advice how to set such environment
CODE A
Option Explicit
Dim objExcel1
Dim strPathExcel1
Dim objSheet1,objSheet2
Dim IntRow1,IntRow2
Dim ColStart
Set objExcel1 = CreateObject("Excel.Application")'Object for Condition Dump
strPathExcel1 = "D:\AravoVB\Copy of Original Scripts\CopyofGEWingtoWing_latest_dump_21112012.xls"
objExcel1.Workbooks.open strPathExcel1
Set objSheet1 = objExcel1.ActiveWorkbook.Worksheets(1)
Set objSheet2 = objExcel1.ActiveWorkbook.Worksheets("Bad Data")
objExcel1.ScreenUpdating = False
objExcel1.Calculation = -4135 'xlCalculationManual
IntRow2=2
IntRow1=4
Do Until IntRow1 > objSheet1.UsedRange.Rows.Count
ColStart = objExcel1.Application.WorksheetFunction.Match("Parent Business Process ID", objSheet1.Rows(3), 0) + 1
Do Until ColStart > objSheet1.UsedRange.Columns.Count And objSheet1.Cells(IntRow1,ColStart) = ""
If objSheet1.Cells(IntRow1,ColStart + 1) > objSheet1.Cells(IntRow1,ColStart + 5) And objSheet1.Cells(IntRow1,ColStart + 5) <> "" Then
objSheet1.Range(objSheet1.Cells(IntRow1,1),objSheet1.Cells(IntRow1,objSheet1.UsedRange.Columns.Count)).Copy
objSheet2.Range(objSheet2.Cells(IntRow2,1),objSheet2.Cells(IntRow2,objSheet1.UsedRange.Columns.Count)).PasteSpecial
IntRow2=IntRow2+1
Exit Do
End If
ColStart=ColStart+4
Loop
IntRow1=IntRow1+1
Loop
objExcel1.ScreenUpdating = True
objExcel1.Calculation = -4105 'xlCalculationAutomatic
CODE B
Option Explicit
Dim objExcel1
Dim strPathExcel1
Dim objSheet1,objSheet2
Dim IntRow1,IntRow2
Dim Flag
Dim IntColTemp,IntRowTemp
Dim Strcmp1,Strcmp2
Flag=0
IntColTemp=1
IntRowTemp=3
Set objExcel1 = CreateObject("Excel.Application")'Object for Condition Dump
If Err.Number <> 0 Then
On Error GoTo 0
Wscript.Echo "Excel application not found."
Wscript.Quit
End If
strPathExcel1 = "D:\VA\CopyofGEWingtoWing_latest_dump_21112012.xls"
objExcel1.Workbooks.open strPathExcel1
Set objSheet1 = objExcel1.ActiveWorkbook.Worksheets(1)
Set objSheet2 = objExcel1.ActiveWorkbook.Worksheets(2)
IntRow1=4
IntRow2=1
Do While objSheet1.Cells(IntRow1, 1).Value <> ""
objSheet2.Cells(IntRow2, 1).Value = objSheet1.Cells(IntRow1, 1).Value
IntColTemp=1
Flag=0
'This will travarse to the Parent Business Process ID column horizantally in the excel.
Do While Flag=0
If objSheet1.Cells(IntRowTemp,IntColTemp).Value="Parent Business Process ID" Then
Flag=1
End If
IntColTemp=IntColTemp+1
Loop
IntColTemp=IntColTemp-1
'MsgBox(IntColTemp)
Strcmp1=trim(objSheet1.Cells(IntRow1, 1).Value)
Strcmp2=trim(objSheet1.Cells(IntRow1,IntColTemp).Value)
If Strcmp1=Strcmp2 Then
objSheet2.Cells(IntRow2, 2).Value="Parent"
Else
objSheet2.Cells(IntRow2, 2).Value="child"
End If
IntRow1=IntRow1+1
IntRow2=IntRow2+1
Loop
Working on two different sheets should be possible by putting something like this in both of your scripts:
strPathExcel1 = "D:\CopyofGEWingtoWing_latest_dump_21112012.xls"
On Error Resume Next
Set objExcel1 = GetObject(, "Excel.Application") ' attach to running instance
If Err.Number = 429 Then ' if that fails
Err.Clear
Set objExcel1 = CreateObject("Excel.Application") ' create new instance
If Err Then ' if that still fails
WScript.Echo Err.Description & " (0x" & Hex(Err.Number) & ")"
WScript.Quit 1 ' report error and terminate
End If
objExcel1.Workbooks.Open strPathExcel1
End If
On Error Goto 0
However, I doubt that this approach would gain you enough performance to justify the additional complexity.
In CODE A replace the lines
Set objExcel1 = CreateObject("Excel.Application")'Object for Condition Dump
strPathExcel1 = "D:\AravoVB\Copy of Original Scripts\CopyofGEWingtoWing_latest_dump_21112012.xls"
objExcel1.Workbooks.open strPathExcel1
with the above code block.
In CODE B replace the lines
Set objExcel1 = CreateObject("Excel.Application")'Object for Condition Dump
If Err.Number <> 0 Then
On Error GoTo 0
Wscript.Echo "Excel application not found."
Wscript.Quit
End If
strPathExcel1 = "D:\VA\CopyofGEWingtoWing_latest_dump_21112012.xls"
objExcel1.Workbooks.open strPathExcel1
with the above code block.

vbscript, validate a user is in active directory by schema attribute

I'm trying to write a vb script that prompts a user for a schema attribute which I'll call bID and checks that the person with that bID is in active directory. I really have no idea how to get started, there are plenty of examples on how to query active directory users but I havent found a good one regarding checking against specific attributes. Any help/suggestions are greatly appreciated!
UPDATE:
ok heres my code so far, doesnt error out and returns 0, but I dont get a wscript.echo of the distinguished name for some reason. I included a few debugging wscript.echo's and it seems to never get into the while loop. Any ideas?
Option Explicit
GetUsers "CN=users,DC=example,DC=example,DC=example,DC=com","123456"
Function GetUsers(domainNc, ID)
Dim cnxn
Set cnxn = WScript.CreateObject("ADODB.Connection")
cnxn.Provider = "ADsDSOObject"
cnxn.Open "Active Directory Provider"
Dim cmd
Set cmd = WScript.CreateObject("ADODB.Command")
cmd.ActiveConnection = cnxn
cmd.CommandText = "<LDAP://" & domainNc & ">;(&(objectCategory=user)(objectClass=user) (employeeNumber=" & ID & "));distinguishedName;subtree"
WScript.Echo cmd.CommandText
cmd.Properties("Page Size") = 100
cmd.Properties("Timeout") = 30
cmd.Properties("Cache Results") = False
WScript.Echo "setting cmd.properties"
Dim rs
Set rs = cmd.Execute
WScript.Echo "rs object set"
While Not rs.eof
On Error Resume Next
WScript.Echo "while loop start"
Wscript.Echo rs.fields("distinguishedName".Value)
rs.MoveNext
If (Err.Number <> 0) Then
WScript.Echo vbCrLf& "Error # "& CStr(Err.Number)& " "& Err.Description
Else
On Error GoTo 0
End If
Wend
WScript.Echo "while loop end"
rs.close
WScript.Echo "rs object closed"
cnxn.Close
Set rs = Nothing
Set cmd = Nothing
Set cnxn = Nothing
End Function
Here's some vbscript that will find all users with bID=FooVal and write their DN out
Function GetUsers(domainNc, bIdVal)
Dim cnxn
Set cnxn = WScript.CreateObject("ADODB.Connection")
cnxn.Provider = "ADsDSOObject"
cnxn.Open "Active Directory Provider"
Dim cmd
Set cmd = WScript.CreateObject("ADODB.Command")
cmd.ActiveConnection = cnxn
cmd.CommandText = "<LDAP://" & domainNc & ">;(&(objectCass=user)(objectCategory=person)(bid=" & bidVal & "));distinguishedName;subtree"
cmd.Properties("Page Size") = 100
cmd.Properties("Timeout") = 30
cmd.Properties("Cache Results") = False
Dim rs
Set rs = cmd.Execute
While Not rs.eof
Wscript.Echo rs.fields("distinguishedName").Value
rs.MoveNext
Wend
rs.close
cnxn.Close
Set rs = Nothing
Set cmd = Nothing
Set cnxn = Nothing
End Function

Resources