VBA code inside Excel doesn't run when triggered via Scheduler - windows

So the setup on this WinServer 2012 R2 64bit is:
Windows Task Scheduler -> cscript .vbs file -> opening excel and run a sub in the main module
This runs fine in the background when I double click the .vbs file, but when I trigger the .vbs via the task scheduler, excel opens, but doesn't load the file or run the sub (not sure which). The task runs under an domain user that has administration rights on the machine. I use the same user when i try clicking on the .vbs
Code that is being run, in order:
Task scheduler launches:
C:\WINDOWS\system32\cscript.exe "D:\xyz\trigger.vbs"
.vbs does:
Option Explicit
Dim xlApp, xlBook, xlsheets, xlcopy
Set xlApp = CreateObject("Excel.Application")
xlapp.Interactive = False
xlapp.DisplayAlerts = False
xlapp.AskToUpdateLinks = False
xlapp.AlertBeforeOverwriting = False
Set xlBook = xlApp.Workbooks.Open("D:\xyz\excelfile.xlsm")
On Error Resume Next
Call xlBook.Application.Run("Main.Extrnal_Trigger")
xlbook.Saved = True
xlBook.Close
xlApp.Quit
Set xlBook = Nothing
Set xlcopy = Nothing
Set xlApp = Nothing
WScript.Quit(1)
Excel code:
Sub Extrnal_Trigger()
Application.EnableEvents = False
Application.AskToUpdateLinks = False
Application.DisplayAlerts = False
Application.AlertBeforeOverwriting = False
Call update_button
Call MainProgram
Call ReportSave
End Sub
How can I find out where the .vbs or the excel hangs and why? A very similar setup on another machine does run without troubles. It is virtually identical to the code quoted here.
I realize there are several bad practices (like not cleaning up xlapp settings), but I'd like to get the process running before cleaning up.
/edit:
Removing
On Error Resume Next
from the .vbs does not display an error.
/edit2:
I tried reverting as far back as possible.
Option Explicit
Dim fso, f, s, log
Set fso = CreateObject("Scripting.FileSystemObject")
Set log = fso.CreateTextFile("D:\xyz\TESTlog.txt")
log.WriteLine "before fso"
Set f = fso.GetFile("D:\xyz\excel.xlsm")
s = f.Path & " "
s = s & "Created: " & f.DateCreated & " "
s = s & "Last Accessed: " & f.DateLastAccessed & " "
s = s & "Last Modified: " & f.DateLastModified
log.WriteLine "after fso"
log.writeline "fso content"
log.writeline s
This works when being triggered by the task scheduler via cscript.exe.
I will try to modify to log what's happening around the call to the excel file.
/edit3:
Debugging showed that this
Set xlBook = xlApp.Workbooks.Open("D:\xyz\excel.xlsm")
never happens. I put out error numbers and got error 1004 for this call. Still not sure what's the issue, but at least I got an error number now.
/edit4:
error 1004 when trying to run this as a scheduled tasks persists. When I am running it by double clicking the .vbs, everything works.

The key was to create both these folders:
C:\Windows\System32\config\systemprofile\Desktop
and
C:\Windows\SysWOW64\config\systemprofile\Desktop
Excel apparently has troubles running in non-interactive mode when these folders are not present (not sure why). Creating them got rid ofthe 1004 error when opening the workbook via vbs.

Related

Closing open processes (except excluded) via VBScript

Currently I use the below portion of VBScript to close running processes, and it works great. But the management issue is that if there's an unknown process, the script isn't going to close it. So I'd like to change this to where all running processes are terminated, unless they're specifically excluded (as there are a few processes that I do not want terminated).
Dim WshShell, objShell
Set WshShell = CreateObject("WScript.Shell")
AppList = "iexplore.exe,notepad.exe,wordpad.exe"
'Closing all open applications that are specified in AppList
For Each app In Split(appList, ",")
Set objProcs=GetObject("winmgmts:\\.\root\cimv2").ExecQuery("select * from Win32_Process where Name= '" & app & "'")
For Each process In objProcs
On Error Resume Next
process.Terminate
On Error Goto 0
Next
Next
I've looked, but can't seem to find anything. And I'm ok using a PowerShell command (as all PC's will be Win10), as long as the PS command can be run completely inside of this VBS, without having to maintain a separate PS file.
So something like this would acceptable:
objShell.Run("powershell.exe -switch1 -switch2")
But not this:
objShell.Run("powershell.exe c:\scripts\test.ps1")
The idea of killing all processes except those in an exclude list sounds pretty risky to me. Can you really be sure your exclusion list is complete? New processes (that you may want/need) can be added at any time. This might be okay on a special purpose machine, but I wouldn't use this approach on my general purpose PC. Anyhow, here's the script. Use at your own risk.
Note: With KillEnabled set to False, the script just displays the Exe names that would be killed. Change that value to True to go live. Run using CScript (e.g. cscript killx.vbs).
KillEnabled = False
UserExesOnly = True
Exclude = "applicationframehost,backgroundtaskhost,chsime,cmd,conhost,cscript,ctfmon,dllhost,explorer,mshta,runtimebroker,settingsynchost,searchapp,shellexperiencehost,sihost,smartscreen,startmenuexperiencehost,svchost,systemsettings,textinputhost,useroobebroker,video.ui,wscript"
Exclude = LCase(Exclude)
Set oWMI = GetObject("winmgmts:\\.\root\cimv2")
Set oWSH = WScript.CreateObject("WScript.Shell")
If UserExesOnly Then X = " Where SessionID = 1"
Set oProcesses = oWMI.ExecQuery("SELECT * FROM Win32_Process" & X)
For Each oProcess In oProcesses
Kill = False
ExeName = LCase(oProcess.Name)
If InStr(ExeName,".exe") Then
Kill = True
For Each Exe In Split(Exclude,",")
If InStr(ExeName,Exe & ".exe") Then Kill = False
Next
If Kill Then
If KillEnabled Then
oProcess.Terminate
Else
WScript.Echo ExeName
End If
End If
End If
Next

Exporting the SAS Project Log file

I used to run 3 SAS EG Projects on a daily basis. Since a couple of days, we have a "SAS Scheduler" that is basically running those latter during the night (the first one at 00:00 AM, second one at 01:00 AM, third one at 03:00 AM). Each SAS Project has multiple SAS Programs.
All in all, that is great news, but this also mean I can't check the logs directly anymore.
To keep track of the night jobs, I am trying to find what could be the best way to export the log files for each project. I found out about the SAS Project Log recently, which basically summarize the logs from all the programs within a SAS Project.
I discovered CaseySmith's answer on the SAS Community forum, basically tweaking the .vbs script to save the SAS Project log file to a .txt using the following code:
Set objProjectLog = objProject.ProjectLog
objProjectLog.Clear()
objProjectLog.Enabled = True
'strProjectLog = objProjectLog.Text
objProjectLog.SaveAs "c:\temp\projectLog.txt"
But, 1) It is a .txt file not a log file and 2) I don't know where to add it in my current .vbs script:
Option Explicit
Dim app
Call dowork
'shut down the app
If not (app Is Nothing) Then
app.Quit
Set app = Nothing
End If
Sub dowork()
On Error Resume Next
'----
' Start up Enterprise Guide using the project name
'----
Dim prjName
Dim prjObject
prjName = "C:\Users\kermit\Desktop\Project.egp" 'Project Name
Set app = CreateObject("SASEGObjectModel.Application.8.1")
If Checkerror("CreateObject") = True Then
Exit Sub
End If
'-----
' open the project
'-----
Set prjObject = app.Open(prjName,"")
If Checkerror("app.Open") = True Then
Exit Sub
End If
'-----
' run the project
'-----
prjObject.run
If Checkerror("Project.run") = True Then
Exit Sub
End If
'-----
' Save the new project
'-----
prjObject.Save
If Checkerror("Project.Save") = True Then
Exit Sub
End If
'-----
' Close the project
'-----
prjObject.Close
If Checkerror("Project.Close") = True Then
Exit Sub
End If
End Sub
Function Checkerror(fnName)
Checkerror = False
Dim strmsg
Dim errNum
If Err.Number <> 0 Then
strmsg = "Error #" & Hex(Err.Number) & vbCrLf & "In Function " & fnName & vbCrLf & Err.Description
'MsgBox strmsg 'Uncomment this line if you want to be notified via MessageBox of Errors in the script.
Checkerror = True
End If
End Function
In the end, what I would like is that on the morning, I run a program that scan the 3 project log files for Notes, Warning and Errors and send to myself an email with the results. Hence, is there a way to export the SAS Project Log (not manually) in a folder?
So, first, what is this code doing?
Set objProjectLog = objProject.ProjectLog
objProjectLog.Clear()
This clears the project log. This needs to be done before your project is run - otherwise the log contains data from past runs. So put this before the prjOBject.Run().
objProjectLog.Enabled = True
'strProjectLog = objProjectLog.Text
objProjectLog.SaveAs "c:\temp\projectLog.txt"
This then exports the project log to a text file. You of course can call that text file whatever you want. You need this code to appear after your program runs, and somewhere before it closes. Right after PrjObject.Run() is probably fine.
You will need to update the names to match your vbs file's names - they use objproject and your vbs uses prjObject, but those are the same thing, just match the names.
Second - what else could you do? If VBS isn't your thing, you have a lot of other ways you could do this.
Export your EG project to a .sas file, then schedule this in base SAS with the normal output options. This may also be possible via the scheduling interface.
Use PROC PRINTTO to redirect your log inside your SAS code.
Copy your EG project to a location you can see. The EG project does contain the log of everything that was run - so there's no reason you couldn't just open the .egp and look at it, just make sure you're not doing that with the production file since you might forget to close out.
My preference is not to schedule EG projects, but to schedule .sas programs; use EG as the development environment and then export to .sas. This gives you more flexibility. But there are a lot of different ways to skin this cat.

File checking on Windows Startup is failing [duplicate]

This question already has answers here:
Getting current directory in VBScript
(9 answers)
Cannot get the current directory in .vbs file [duplicate]
(1 answer)
Closed 2 years ago.
I have this script that allows me to automatically backup my MySQL Database every 5 minutes using a batch file.
Dim WshShell
Dim FSO
Dim stopBackup
stopBackup = false
' Register on Windows Startup if not registered when this file is opened.
RegisterOnWindowsStartUp()
' Keep backing up the database every 5 minutes, loop will do.
Do While True
Set FSO = CreateObject("Scripting.FileSystemObject")
If fso.FileExists("auto_backup.bat") Then ' Check if bat file for backing up the database exist.
MsgBox "Backup Message Test."
' Run the batch file which handle the auto backup of database, keep it invisible.
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run chr(34) & "auto_backup.bat" & Chr(34), 0
Set WshShell = Nothing
WScript.Sleep 300000 ' Delay loop every 5 minutes.
Else ' Stop the loop and do not proceed anymore when the bat file is not exist.
WScript.Echo "Failed to auto backup the database, this won't continue anymore."
stopBackup = true
RemoveFromRegistry() ' Unregister this file on Windows Startup since the bat file is no longer exist.
End If
If stopBackup Then ' Break the loop when stopBackup become true
Exit Do ' Break the loop here.
End If
Loop
' Remove this script from registry on Windows Startup
Function RemoveFromRegistry()
Set objShell = Wscript.CreateObject("Wscript.Shell")
objShell.RegDelete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run\autobackup_key"
End Function
' Register this script on Windows Startup only if not registered.
Function RegisterOnWindowsStartUp()
If IsRegistryExist = False Then
Set WshShell = CreateObject("WScript.Shell")
keyNameLocation = "HKCU\Software\Microsoft\Windows\CurrentVersion\Run\autobackup_key"
valueFileLocation = WScript.ScriptFullName
keyType = "REG_SZ"
WshShell.RegWrite keyNameLocation, valueFileLocation, keyType
Set WshShell = Nothing
End If
End Function
' Check if Registry Key Exist on Windows Startup.
Function IsRegistryExist()
Dim sKey, bFound
skey = "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\autobackup_key"
with CreateObject("WScript.Shell")
on error resume next ' turn off error trapping
sValue = .regread(sKey) ' read attempt
bFound = (err.number = 0) ' test for success
on error goto 0 ' restore error trapping
end with
If bFound Then
IsRegistryExist = True
Else
IsRegistryExist = False
End If
End Function
The filename of the batch file that allows me to back up the database is auto_backup.bat and it's working fine and no problem, it's on the same directory where the script above is located.
The problem is every time the Windows Startup, it fails to check for the existence of auto_backup.bat, but when I open the script and run it, its working fine and no issue.
There might be some issue with my logic, can anybody help me fix it?
it's on the same directory where the script above is located
This is not where a relative path looks. It starts from the working directory.
The working directory of a shortcut is not necessarily the directory containing the shortcut. You should edit the shortcut properties and set the working directory you want, instead of using the default (which is often C:\Windows or C:\Users\%USERNAME%)
Or you can put an absolute path in the script, or have the script change working directory to its own directory, or have the script combine its directory and the filename to dynamically create an absolute path.
The problem is because when you register your script on Windows Startup through the registry, the script will be executed from the directory of Windows Startup when windows started.
In order to get the original working directory where you can look for the auto_backup.bat, you can combine the FileSystemObject and WScript.ScriptFullName functions to get the parent directory of the current script.
CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) & "\auto_backup.bat"
Then the IF-ELSE condition inside your loop would be
...
Do While True
Set FSO = CreateObject("Scripting.FileSystemObject")
' (1) Add this code.
autobackup_bat_file = CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) & "\auto_backup.bat"
If fso.FileExists(autobackup_bat_file) Then ' (2) Change this line.
MsgBox "Backup Message Test."
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run chr(34) & autobackup_bat_file & Chr(34), 0 ' (3) Change this line.
' Continue some of your code here...
...

.VBS called by .BAT to create .zip - run silently (without interface)?

I have a .bat file which I use to back up files, which calls a .vbs and passes it two parameters, as follows:
...
ZipCMD.vbs "C:\Source" "C:\Destination\Data.zip"
...
ZipCMD.vbs contains the following code (Credit to garbb):
Set objArgs = WScript.Arguments
Set FS = CreateObject("Scripting.FileSystemObject")
InputFolder = FS.GetAbsolutePathName(objArgs(0))
ZipFile = FS.GetAbsolutePathName(objArgs(1))
CreateObject("Scripting.FileSystemObject").CreateTextFile(ZipFile, True).Write "PK" & Chr(5) & Chr(6) & String(18, vbNullChar)
Set objShell = CreateObject("Shell.Application")
Set source = objShell.NameSpace(InputFolder).Items
numfolderitems = objShell.NameSpace(InputFolder).Items.count
objShell.NameSpace(ZipFile).CopyHere(source)
' wait until number of items in zip file is the same as in the folder we are zipping up
' also sometimes gets errors when getting folder object for zip file, probably because it is in use? so ignore these
On Error Resume Next
Do while True
numitemsinzip = objShell.NameSpace(ZipFile).Items.count
If Err.Number = 0 and numitemsinzip = numfolderitems Then
Exit Do
ElseIf Err.Number <> 0 then
Err.Clear
End If
wScript.Sleep 10
Loop
On Error Goto 0
When the zipping is occurring, the usual windows 'Compressing files' interface appears, and shows the progress bar ticking along for a few minutes, before closing and disappearing.
Question: Can vbs run a compression silently (i.e. without interface)? -- I've read this article, which shows a flag, however this doesn't appear to work with copying to .zip, for some reason.
Follow-up question: If it's not possible for the .vbs which I'm using to achieve this, then is there an alternative way, which still utilises calling another file/process(?) (.vbs / .js, or other?) and feeding it the two paths from cmd?
Edit: I'm trying to achieve this without the use of third-party software (e.g. 7zip), and simply using native windows code.
Suppose I am almost 3 months late on this, but if you have powershell version 5 or later, you can simply create a powershell script:
Compress-Archive "C:\Source" "C:\Destination\Data.zip"
or from a batch file:
powershell Compress-Archive "C:\Source" "C:\Destination\Data.zip"
Also see this option

VBscript Unable to Run Shell Command

I have set up the following Sub to run shell commands quickly and easily.
This script works for the login scripts at my company without fail.
I am currently developing a script to add a large batch of users to our domain.
When I used this Sub in my new script I receive an error saying that the file cannot be found.
I have tried using the fix in this stackoverflow post, but I recieve the same error even with this code.
VBScript WScript.Shell Run() - The system cannot find the file specified
The part I find puzzling is that this sub works just fine when run from the netlogon folder of our domain controller.
What am I doing wrong?
Thanks,
Sub runcommand(strCommand)
Dim objWshShell, intRC
set objWshShell = WScript.CreateObject("WScript.Shell")
intRC = objWshShell.Run(strCommand, 0, TRUE)
call reportError(intRC,strCommand)
set objWshShell = nothing
end Sub
function reportError(intRC, command)
if intRC <> 0 then
WScript.Echo "Error Code: " & intRC
WScript.Echo "Command: " & command
end if
end function
The previous values for strCommand had no spaces and were very straightforward. Your new script is passing more complex variables to your Sub so you need additional conditional handling, as Alex K. pointed out in his Collusion (i.e., "Comment/Solution") above. Alex K.'s sample above is perfect, so, being a Point Pimp tonight, will post it as the solution:
objWshShell.Run("cmd /k echo Hello World", 1, TRUE)

Resources