Use Console as debug window in VBA - vba

So I have some macros run inside an Excel document and wanted to know if there is a way to frequently output text to a Console window (Basicly using it like the immediate window).
I know there are multiple ways of writing text to files, I just want to display some info on the running process without using the immediate window or other windows inside Excel itself.
Using this helps me to display a single line, but I dont want to open a new window for every line:
Call Shell("cmd.exe /K echo testInfo", vbNormalFocus)
I do NOT want to run a command (except echo maybe?) to execute a task, its just supposed to display text.
Thank you in advance for any advice.
EDIT:
As an addition to #JohnRC 's post I found a solution without external applications:
Call Shell("PowerShell.exe -noexit -command get-content " + strPath + " -wait")
Logging information to a textfile at the location after running the command above does the trick.

OK, as I got a couple of downvotes on my earlier answer, I thought I should attempt to provide an actual answer to the request, namely to provide a way of sending log messages to a command prompt window. Here goes...
This solution is implemented as a VBA class that will send messages as comment lines to a separately-running command prompt window that has the text "ExcelLog" in the title. This command prompt must be started separately. The easiest way to do this is to create a shortcut named "ExcelLog" to just run CMD, then when this shortcut is opened the command prompt window will have "ExcelLog" in the title.
Inside the spreadsheet add the code for the class cConsole (below), and then in your VBA code create a global instance of the class and use the method .W "message" to send a text message to the console as a comment line (in this case using the prefix :: to the line to identify it as a comment).
The cConsole class looks for any command prompt window with the requisite title, then sends the comment message to that window. If the window cannot be found, it simply skips the action so the Excel VBA code continues to execute without reporting an error. Also if you open the command prompt window after Excel VBA has started running, cConsole will automatically connect to the window and start/resume sending messages. This means you can close and reopen the command prompt ExcelLog window any time without interrupting the execution of the VBA code.
This seems to work on my setup OK. I think it is a bit more trouble than simply tailing a text file, but - hey, you pays your money and takes your choice.
Here is the code of the cConsole class.
Option Explicit
'// cConsole class
'// This class wraps an interface to a separately-started command prompt
'// window to which messages are sent as comments, so that the command prompt
'// window can be used as a real-time scrolling log from Excel.
'// Each instance of this class creates its own connection to the
'// command prompt window which must have a title containing the text
'// "ExcelLog". If such a window is not open then messages are not
'// logged. The command prompt window can be opened after messages
'// have started, and it will be connected when the next message is
'// sent.
'// The simplest way to set up the necessary command prompt window is to
'// create a shortcut on the desktop the name "ExcelLog" which runs CMD
'// Usage - - - - - - - - - - - -
'//
'// Dim oConsole As New cConsole
'// :
'// oConsole.W "Message to be written to the console"
'//
'// Windows functions to get window handles etc
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" _
(ByVal hWnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" _
(ByVal hWnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function SetForegroundWindow Lib "user32" _
(ByVal hWnd As Long) As Long
'// Handle of the excel log window
Private hLogWindow As Long
Private Sub Class_Initialize()
'// On instantiation, attempts to find the ExcelLog window
findExcelLogWindow
End Sub
Public Sub W(sMsg As String)
'// Public function used to send the given message
'// as a comment line to the linked window
SendToConsole ":: " & sMsg
End Sub
Private Sub SendToConsole(Command As String)
'// Connects to and sends a command line to the command prompt
'// window that is being used as the log
Dim res As Boolean
'// Check that a connection has been made and
'// attempt to connect if not
If hLogWindow = 0 Then
findExcelLogWindow
If hLogWindow = 0 Then Exit Sub
End If
On Error Resume Next
Do
'// Attempt to bring the logging window to the foreground
res = SetForegroundWindow(hLogWindow)
'// Check if successful, and send the command if so
If res Then
SendKeys Command & vbCrLf
Exit Do
Else
'// Not successful, so try reconnecting to the logging window
findExcelLogWindow
'// If we cannot connect, just exit without sending anything
If hLogWindow = 0 Then Exit Sub
End If
Loop
'// Check if there has been any error
If Err.Number <> 0 Then
hLogWindow = 0
MsgBox "Error: " & Err.Number & vbCrLf & Err.Description
End If
On Error GoTo 0
End Sub
Private Function findExcelLogWindow() As Long
'// This function looks for a command prompt window that has the text
'// ExcelLog in the title
Dim nLen As Long
Dim sData As String
Dim Class As String
Dim Title As String
'// Get handle to the first window
hLogWindow = 0
'// Check each window in turn
Do
hLogWindow = FindWindowEx(0&, hLogWindow, vbNullString, vbNullString)
'// Check that a window was found
If hLogWindow = 0 Then Exit Do
'// Get the class name of the window
sData = String$(100, Chr$(0))
nLen = GetClassName(hLogWindow, sData, 100)
Class = Left$(sData, nLen)
'// Get the title of the window
sData = String$(100, Chr$(0))
nLen = GetWindowText(hLogWindow, sData, 100)
Title = Left$(sData, nLen)
'// Check if the required window has been found
If Class = "ConsoleWindowClass" And InStr(Title, "ExcelLog") > 0 Then
'// Initialise the window to remove any prompt text
SendToConsole "PROMPT $S"
'// Write some initial messages
Me.W "*******************"
Me.W "[" & ThisWorkbook.Name & "] connected to console at " & Now
Me.W ""
'// Return the handle to the log window
findExcelLogWindow = hLogWindow
Exit Function
End If
Loop
'// The log window was not found, so return zero
findExcelLogWindow = 0
End Function
I tested this out by handling MouseMove events on an image control in a worksheet:
Option Explicit
Private oCons As New cConsole
Private Sub Image1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
oCons.W "MouseMove " & X & ", " & Y
End Sub
And here is the result

Instead of using a shell to act as a console to log messages, I have used a text file to hold the log, and monitored the output to the file with a tail utility (I have used WinTail from http://www.baremetalsoft.com/wintail/ but I am sure there are others). This is the code, which I put in a separate vba module named Log. Then call Log.W "Message" to log a message.
Option Explicit
'// You need a reference to "Microsoft Scripting Runtime" library in VBA
Private oLog As Scripting.TextStream
Private bErr As Boolean
Private Sub INIT()
'// Initialise the output log file
'// Check if log file is already open, or there has been an error
If bErr Then Exit Sub
If Not oLog Is Nothing Then Exit Sub
'// Open the log file for appending
Dim ofso As New Scripting.FileSystemObject
On Error Resume Next
Set oLog = ofso.OpenTextFile("excel.log", ForAppending, True)
'// Check that open was successful
If Err.Number <> 0 Then
MsgBox "Log file error: " & Err.Number & ": " & Err.Description
bErr = True
Exit Sub
End If
On Error GoTo 0
'// Write a starting block to the log
oLog.WriteLine "*"
W "********************************** START"
W "* Start of log " & Format(Date, "YYYY-MM-dd")
W ""
End Sub
Public Sub W(sMsg)
'// Writes a single line message to the log
'// Initialize if required
INIT
'// Check for log file error
If bErr Then Exit Sub
'// Create the log line and write to log file
Dim st As String
st = Format(Now, "hh:mm:ss ")
oLog.WriteLine st & sMsg
End Sub
Public Function ReportErr(Optional Loc As Variant = "") As Boolean
'// Reports information from the Err object, if an error has occured
'// Check if error has occurred, exit if not
If Err.Number = 0 Then ReportErr = False: Exit Function
'// Set return value
ReportErr = True
'// Initialize if required
INIT
'// Check for log file error
If bErr Then Exit Function
'// Write the error block to the log
W "*********** ERROR ******* " & IIf(Len(Loc) > 0, "[" & Loc & "]", "")
W "* Error #" & Err.Number
If Len(Err.Description) > 0 Then
W "* : " & Err.Description
W "*************************"
End If
End Function
Tailing the log file with WinTail means that output to the log appears immediately it is written, so you can monitor the log as the program is running.

Related

Return focus to ThisWorkbook.Activesheet after XMLHTTP60 file download

Situation:
I am unable to return focus to the Excel application after initiating a file download.
My usual tricks of AppActivate and Application.hwnd , when working between applications, don't seem to be working this time. I haven't had a problem doing this before so don't know if I am being particularly dense today, or, it is because I am involving a browser for the first time. I suspect it is the former.
Questions:
1) Can any one see where I am going wrong (why focus does not shift back to Excel)?
2) More importantly: Is there a way to download files in the background, using the default browser, keeping the focus on ThisWorkbook and thereby avoiding the issue altogether?
I am using a workaround of SendKeys "%{F4}" immediately after the download, at present, to close the browser and so am defaulting back to Excel.
Note: The default browser in my case is Google Chrome but clearly could be any browser.
What I have tried:
1) From #user1452705; focus didn't shift:
Public Declare Function SetForegroundWindow _
Lib "user32" (ByVal hwnd As Long) As Long
Public Sub Bring_to_front()
Dim setFocus As Long
ThisWorkbook.Worksheets("Sheet1").Activate
setfocus = SetForegroundWindow(Application.hwnd)
End Sub
2) Then I tried:
ThisWorkbook.Activate 'No shift in focus
Windows(ThisWorkbook.Name).Activate 'Nothing happened
Application.Windows(ThisWorkbook.Name & " - Excel").Activate 'Subscript out of range
3) AppActivate using Title as actually displayed in Window:
AppActivate "AmbSYS_testingv14.xlsm" & " - Excel" 'Nothing happened
4) More desperate attempts:
AppActivate Application.Caption 'Nothing happened
AppActivate ThisWorkbook.Name & " - Excel" 'Nothing happened
AppActivate ThisWorkbook.Name 'Nothing happened
AppActivate "Microsoft Excel" 'Invalid proc call
4) Finally, the current version of my code is using #ChipPearson's sub ActivateExcel , which also has no effect:
Module 1:
Public Sub DownloadFiles()
'Tools > ref> MS XML and HTML Object lib
Dim http As XMLHTTP60
Dim html As HTMLDocument
Set http = New XMLHTTP60
Set html = New HTMLDocument
With http
.Open "GET", "https://www.england.nhs.uk/statistics/statistical-work-areas/ambulance-quality-indicators/ambulance-quality-indicators-data-2017-18/", False
.send
html.body.innerHTML = .responseText
End With
'Test Download code
html.getElementsByTagName("p")(4).getElementsByTagName("a")(0).Click
' Application.Wait Now + TimeSerial(0, 0, 3) 'pause for downloads to finish before files
'Other code
ActivateExcel
End Sub
Module 2:
Option Explicit
Option Compare Text
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' modActivateExcel
' By Chip Pearson, www.cpearson.com, chip#cpearson.com
' http://www.cpearson.com/excel/ActivateExcelMain.aspx
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Window API Declarations
' These Declares MUST appear at the top of the
' code module, above and before any VBA procedures.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Declare PtrSafe Function BringWindowToTop Lib "user32" ( _
ByVal HWnd As Long) As Long
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Declare PtrSafe Function SetFocus Lib "user32" ( _
ByVal HWnd As Long) As Long
Public Sub ActivateExcel()
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' ActivateExcel
' This procedure activates the main Excel application window,
' ("XLMAIN") moving it to the top of the Z-Order and sets keyboard
' focus to Excel.
'
' !!!!!!!!!!!!!!!!!!!!!!!!!
' NOTE: This will not work properly if a VBA Editor is open.
' If a VBA Editor window is open, the system will set focus
' to that window, rather than the XLMAIN window.
' !!!!!!!!!!!!!!!!!!!!!!!!!
'
' This code should be able to activate the main window of any
' application whose main window class name is known. Just change
' the value of C_MAIN_WINDOW_CLASS to the window class of the
' main application window (e.g., "OpusApp" for Word).
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim Res As Long ' General purpose Result variable
Dim XLHWnd As Long ' Window handle of Excel
Const C_MAIN_WINDOW_CLASS = "XLMAIN"
'''''''''''''''''''''''''''''''''''''''''''
' Get the window handle of the main
' Excel application window ("XLMAIN"). If
' more than one instance of Excel is running,
' you have no control over which
' instance's HWnd will be retrieved.
' Related Note: You MUST use vbNullString
' not an empty string "" in the call to
' FindWindow. When calling API functions
' there is a difference between vbNullString
' and an empty string "".
''''''''''''''''''''''''''''''''''''''''''
XLHWnd = FindWindow(lpClassName:=C_MAIN_WINDOW_CLASS, _
lpWindowName:=vbNullString)
If XLHWnd > 0 Then
'''''''''''''''''''''''''''''''''''''''''
' If HWnd is > 0, FindWindow successfully
' found the Excel main application window.
' Move XLMAIN to the top of the
' Z-Order.
'''''''''''''''''''''''''''''''''''''''''
Res = BringWindowToTop(HWnd:=XLHWnd)
If Res = 0 Then
Debug.Print "Error With BringWindowToTop: " & _
CStr(Err.LastDllError)
Else
'''''''''''''''''''''''''''''''''
' No error.
' Set keyboard input focus XLMAIN
'''''''''''''''''''''''''''''''''
SetFocus HWnd:=XLHWnd
End If
Else
'''''''''''''''''''''''''''''''''
' HWnd was 0. FindWindow couldn't
' find Excel.
'''''''''''''''''''''''''''''''''
Debug.Print "Can't find Excel"
End If
End Sub
Additional references:
1) Toggle between Excel and IE
2) VBA API declarations. Bring window to front , regardless of application ; link also in main body
3) Return focus to excel after finishing downloading file with Internet explorer
4) Set focus back to the application window after showing userform
5) Close the application with sendkeys like ALt F4
Thanks to #OmegaStripes and #FlorentB for their input.
Using #OmegaStripes suggested method I:
Use XMLHTTP to get binary response content
Convert to UTF-8
Parse to extract the required URL
Use a new XMLHTTP to download binary
Use ADODB.Stream to write out file
Works a treat and no problems with shift in focus.
Notes: For step 3, I used the approach by #KarstenW to write the string , the converted responseText string, out to a txt file for examination to determine how to access the URL of interest.
Option Explicit
Public Const adSaveCreateOverWrite As Byte = 2
Public Const url As String = "https://www.england.nhs.uk/statistics/statistical-work-areas/ambulance-quality-indicators/ambulance-quality-indicators-data-2017-18/"
Public Const adTypeBinary As Byte = 1
Public Const adTypeText As Byte = 2
Public Const adModeReadWrite As Byte = 3
Public Sub DownLoadFiles()
Dim downLoadURL As String
Dim aBody As String
' Download via XHR
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", url, False
.send
' Get binary response content
aBody = BytesToString(.responseBody, "UTF-8")
End With
Dim respTextArr() As String
respTextArr = Split(Split(aBody, "New AmbSYS Indicators")(0))
downLoadURL = Split(respTextArr(UBound(respTextArr)), Chr$(34))(1)
Dim urlArr() As String
Dim fileName As String
Dim bBody As Variant
Dim sPath As String
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", downLoadURL, False
.send
urlArr = Split(downLoadURL, "/")
fileName = urlArr(UBound(urlArr))
bBody = .responseBody
sPath = ThisWorkbook.Path & "\" & fileName
End With
' Save binary content to the xls file
With CreateObject("ADODB.Stream")
.Type = 1
.Open
.Write bBody
.SaveToFile sPath, adSaveCreateOverWrite
.Close
End With
' Open saved workbook
With Workbooks.Open(sPath, , False)
End With
End Sub
Public Function BytesToString(ByVal bytes As Variant, ByVal charset As String) As String
With CreateObject("ADODB.Stream")
.Mode = adModeReadWrite
.Type = adTypeBinary
.Open
.Write bytes
.Position = 0
.Type = adTypeText
.charset = charset
BytesToString = .ReadText
End With
End Function
For Excel 2013 please see here a solution that worked for me
In summary, change this:
AppActivate "Microsoft Excel"
to
AppActivate "Excel
Note: a pause before the command can help (at least in my case):
Application.Wait (Now + TimeValue("0:00:1"))

Dynamically Reference a Module Variable

Simple Version:
Module A has a Public variable X
I want to be able to get the value of X from Module B, without hardcoding the name 'Module A', i.e. (obviously this is not the right code):
MsgBox Modules("Module A").X
More Advanced Version:
I have an Add-In/XLSM (it can toggle itself) called TAAA.xlsm. I use Rob Bovey's error handling system, and want to improve/expand it.
A lot of my modules create new workbooks. If the user receives an error, I want to give them the option of sending me the error to examine myself. I'd like it to prompt the user, and if they say 'yes', the error handler would use Outlook to e-mail me:
Error Log
TAAA.xlsm
Any child workbooks related to the error
My plan was to have a Public Workbook Array for each module where it would store any workbooks created/used by the code that caused the error. That way when the error handler processes, it can access that public array in order to attach the workbooks.
I suppose a "simpler" solution would be to store this data on worksheet in TAAA, though it's not as elegant.
Any thoughts would be much appreciated!
EDIT
I solved my own problem in an Answer below. However, I'm still curious if there is a good answer to my original question or if that's impossible.
So in retrospect, the answer seems pretty obvious to me.
How does the central error handler know which module the error came from? By the private module name string being passed to the central error handler.
Likewise, I can just pass the workbook array as another parameter to the central error handler!
So instead of the central error handler looking like this:
Public Function bCentralErrorHandler( _
ByVal sModule As String, _
ByVal sProc As String, _
Optional ByVal sFile As String, _
Optional ByVal bEntryPoint As Boolean) As Boolean
Static sErrMsg As String
Dim iFile As Integer
Dim lErrNum As Long
Dim sFullSource As String
Dim sPath As String
Dim sLogText As String
' Grab the error info before it's cleared by
' On Error Resume Next below.
lErrNum = Err.Number
' If this is a user cancel, set the silent error flag
' message. This will cause the error to be ignored.
If lErrNum = glUSER_CANCEL Then sErrMsg = msSILENT_ERROR
' If this is the originating error, the static error
' message variable will be empty. In that case, store
' the originating error message in the static variable.
If Len(sErrMsg) = 0 Then sErrMsg = Err.Description
' We cannot allow errors in the central error handler.
On Error Resume Next
' Load the default filename if required.
If Len(sFile) = 0 Then sFile = ThisWorkbook.Name
' Get the application directory.
sPath = ThisWorkbook.Path
If Right$(sPath, 1) <> "\" Then sPath = sPath & "\"
' Construct the fully-qualified error source name.
sFullSource = "[" & sFile & "]" & sModule & "." & sProc
' Create the error text to be logged.
sLogText = " " & Application.UserName & sFullSource & ", Error " & _
CStr(lErrNum) & ": " & sErrMsg
' Open the log file, write out the error information and
' close the log file.
iFile = FreeFile()
Open sPath & msFILE_ERROR_LOG For Append As #iFile
Print #iFile, Format$(Now(), "mm/dd/yy hh:mm:ss"); sLogText
If bEntryPoint Then Print #iFile,
Close #iFile
' Do not display or debug silent errors.
If sErrMsg <> msSILENT_ERROR Then
' Show the error message when we reach the entry point
' procedure or immediately if we are in debug mode.
If bEntryPoint Or gbDEBUG_MODE Then
Application.ScreenUpdating = True
MsgBox sErrMsg, vbCritical, gsAPP_NAME
' Clear the static error message variable once
' we've reached the entry point so that we're ready
' to handle the next error.
sErrMsg = vbNullString
End If
' The return vale is the debug mode status.
bCentralErrorHandler = gbDEBUG_MODE
Else
' If this is a silent error, clear the static error
' message variable when we reach the entry point.
If bEntryPoint Then sErrMsg = vbNullString
bCentralErrorHandler = False
End If
End Function
I would change the definition to:
Public Function bCentralErrorHandler( _
ByVal sModule As String, _
ByVal sProc As String, _
Optional ByVal wbChildWorkbooks() As Workbook, _
Optional ByVal sFile As String, _
Optional ByVal bEntryPoint As Boolean) As Boolean
Fairly obvious in retrospect. Sorry for the wasted question.

How to prevent free distribution of a commercial Excel spreadsheet

I'm not sure if this is more appropriate under a different SE site (Super User?).
I want to build and sell a complex macro driven driven spreadsheet to a certain vertical. I am mainly concerned about free/unauthorised distribution between customers within that vertical.
I can see that there is a few obscure products on the market that might be able to do what I want, but the few reviews that i've been able to find haven't been favourable.
One vendor however lists that free distribution can circumvented by either:
Using a key generator to create license codes
Using the online activation feature
Or by simply using an encrypted password
Is anyone aware of any guidelines/frameworks (any language) for me to build my own solution to achieve this, namely requiring licence codes or online activation?
If this is generally a difficult endeavour, is there a commercial product that anyone recommend?
I'm also thinking the complexities involved in achieving this might push me to building a small SaaS application instead. Am I better off just going that route?
I have created an Excel sheet that I could remotely remove access to if a monthly subscription payment failed. Here is how to accomplish this:
Create and HTML table and upload it to your website
Within your Excel doc go to the data tab and select get from web - import your table into a sheet called "Verify" - make sure your table has 3 columns. Serial Number is in the first column, description of user in 2nd, and your error message in the top of col 3. The error message stored here is what every user that isn't registered will see. The first serial number should appear in cell A2 of the sheet Verify.
Within your Visual Basic editor paste this code into a Module - This code will return an 8 digit serial number based on a PC's Hard Drive serial number:
Function HDSerialNumber() As String
Dim fsObj As Object
Dim drv As Object
Set fsObj = CreateObject("Scripting.FileSystemObject")
Set drv = fsObj.Drives("C")
HDSerialNumber = Left(Hex(drv.SerialNumber), 4) _
& "-" & Right(Hex(drv.SerialNumber), 4)
End Function
Also in another module I make sure the Internet is connected. If no Internet then the sheet closes. If you don't do this then if someone disconnects from the Internet your serials won't be loaded.
Option Explicit
#If VBA7 And Win64 Then
Private Declare PtrSafe Function InternetGetConnectedStateEx Lib "wininet.dll" (ByRef lpdwFlags As Long, ByVal lpszConnectionName As String, ByVal dwNameLen As Integer, ByVal dwReserved As Long) As Long
#Else
Private Declare Function InternetGetConnectedStateEx Lib "wininet.dll" (ByRef lpdwFlags As Long, ByVal lpszConnectionName As String, ByVal dwNameLen As Integer, ByVal dwReserved As Long) As Long
#End If
Function IsInternetConnected() As Boolean
Dim strConnType As String, lngReturnStatus As Long, MyScript As String
If Application.OperatingSystem Like "*Macintosh*" Then
MyScript = "repeat with i from 1 to 2" & vbNewLine
MyScript = MyScript & "try" & vbNewLine
MyScript = MyScript & "do shell script ""ping -o -t 2 www.apple.com""" & vbNewLine
MyScript = MyScript & "set mystatus to 1" & vbNewLine
MyScript = MyScript & "exit repeat" & vbNewLine
MyScript = MyScript & "on error" & vbNewLine
MyScript = MyScript & "If i = 2 Then set mystatus to 0" & vbNewLine
MyScript = MyScript & "end try" & vbNewLine
MyScript = MyScript & "end repeat" & vbNewLine
MyScript = MyScript & "return mystatus"
If MacScript(MyScript) Then IsInternetConnected = True
Else
lngReturnStatus = InternetGetConnectedStateEx(lngReturnStatus, strConnType, 254, 0)
If lngReturnStatus = 1 Then IsInternetConnected = True
End If
End Function
Then inside the Workbook_Open area paste this:
Private Sub Workbook_Open()
If IsInternetConnected Then
Dim objFSO As Object
Dim MyFolder As String
Dim sFileName As String
Dim iFileNum As Integer
Dim sBuf As String
Dim trialstartdate As String
Dim z As String
Dim fsoFSO
Set fsoFSO = CreateObject("Scripting.FileSystemObject")
'UNCOMMENT below to SHOW the serials sheet when the workbook is opened
ActiveWorkbook.Sheets("Verify").Visible = xlSheetVisible
'UNCOMMENT below to hide the serials sheet when the workbook is opened
'ActiveWorkbook.Sheets("Verify").Visible = xlSheetVeryHidden
Refresh_Serials
z = 2
'loop here for valid hard drive serial number
Do Until IsEmpty(Worksheets("Verify").Cells(z, 1).Value)
If Worksheets("Verify").Cells(z, 1).Value = HDSerialNumber Then
'verified and let pass
GoTo SerialVerified
End If
z = z + 1
Loop
Dim custommessage As String
custommessage = Worksheets("Verify").Cells(2, 3)
MsgBox custommessage + " Your serial number is: " + HDSerialNumber
Dim wsh1, MyKey1
Set wsh1 = CreateObject("Wscript.Shell")
MyKey1 = "%{TAB}"
wsh1.SendKeys MyKey1
MsgBox "The Commission Tracker will not open without a valid serial number. It will now close. uncomment this in workbook->open to close the workbook if the serial isn't found"
Application.DisplayAlerts = False
'uncomment this to close the workbook if the serial isn't found
'ActiveWorkbook.Close
Application.DisplayAlerts = True
SerialVerified:
' does the end user agree to not use this tool for mailicous purposes?
MsgAgree = MsgBox("Your PC's serial number is " & HDSerialNumber & ". By clicking 'Yes' you agree to use our software as described in our end user agreement. - the URL to your terms here", vbYesNo, "Final Agreement")
If MsgAgree = vbNo Then
'close program
MsgBox "This program will now close since you do not agree to our end user agreement"
Application.DisplayAlerts = False
ActiveWorkbook.Close
Application.DisplayAlerts = True
Else
'continue to open the program
End If
Else
MsgBox "No Network Connection Detected - You must have an internet connection to run the commission tracker."
Application.DisplayAlerts = False
ActiveWorkbook.Close
Application.DisplayAlerts = True
End If
End Sub
That should do it....
Create your own special unique license keys in a macro that will unlikely be generated in a key generator. Add your own prefix, for example. You could store if a user is using it in an online database. Downfall to this solution is that the users would have to be connected to the outside internet.
Then lock down that module with the keys by the following:
To protect your code, open the Excel Workbook and go to Tools>Macro>Visual Basic Editor (Alt+F11). Now, from within the VBE go to Tools>VBAProject Properties and then click the Protection page tab and then check "Lock project from viewing" and then enter your password and again to confirm it. After doing this you must save, close & reopen the Workbook for the protection to take effect.

Running 7Z in a dos command from VBA Excel causes warning alert

I am currently using the following code to run a dos command as follows from VBA.
Set objShell = CreateObject("WScript.Shell")
dos_command="\\\10.xx.xx.xx\test\7z.exe a -r " etc etc etc
result = objShell.Run(dos_command, 0, True)
Set objShell =nothing
All runs well, the only problem is that I get an annoying Warning Windows Box advising a program is trying to run in my computer, press OK or Cancel
I must use "objshell" because I need VBA to wait until DOS command is completed.
is there a way to avoid the warning box from coming up from within VBA or adding some additional parameters to the DOS command ?
The 7z.exe file is running in a server (not local PC) so I assume that's the problem.
I cannot use or install 7z.exe in each machine.
Here are three options, presented in order from quickest/dirtiest to most robust:
Create a text file as part of command line and wait for its existence: modify your command line to something like this and run it using Shell (not your objShell):
dos_command = "\\\10.xx.xx.xx\test\7z.exe a -r " etc etc etc
dos_command = dos_command & " && echo > " & TempFileName
This will create a text file named TempFileName after your 7-zip code completes. You just need to make sure TempFileName does not exist before you run your shell command, then run the command and wait for the TempFileName file to exist.
Use OpenProcess and GetExitCodeProcess APIs: launch your command line using the OpenProcess API call which provides access to your new process (note that the Shell function returns the ProcessID of the launched process). Then use the ProcessID to sit in a loop and poll the process via GetExitCodeProcess. Relevant declarations:
Private Declare Function OpenProcess Lib "kernel32" _
(ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long) As Long
Private Declare Function GetExitCodeProcess Lib "kernel32" _
(ByVal hProcess As Long, _
lpExitCode As Long) As Long
Private Const STILL_ACTIVE = &H103
Private Const PROCESS_QUERY_INFORMATION = &H400
'---------------------------------------------------------------------------------------vv
' Procedure : ShellWait
' DateTime : 2/15/2008 10:59
' Author : Mike
' Purpose : Executes a shell command and waits for it to complete.
' Notes : Runs the shell as a batch file, allowing the user to pass a string with
' line breaks to execute a multi-line command.
'
' : Provides two means to break out of the loop.
' 1) Provide a timeout in seconds.
' The code breaks out once it reaches the timeout.
' 2) Provide a flag to tell the procedure to stop running.
' To use this option, you would need to pass the procedure a global flag
' that the user has the ability to change through the interface.
' Update (5/23/2008):
' - Uses a progressive sleep timer to allow fast processes to run quickly
' and long processes to get increasing clock cycles to work with.
' - Changed default window mode to hidden.
'---------------------------------------------------------------------------------------
'^^
Public Function ShellWait(DosCmd As String, _
Optional StartIn As String = "WINDOWS TEMP FOLDER", _
Optional WindowStyle As VbAppWinStyle = vbHide, _
Optional TimeOutSeconds As Long = -1, _
Optional ByRef StopWaiting As Boolean = False) 'vv
On Error GoTo Err_ShellWait
Dim hProcess As Long, RetVal As Long, StartTime As Long
Dim BatName As String, FileNum As Integer, SleepTime As Long
StartTime = Timer
BatName = TempFileName(StartIn, "bat")
FileNum = FreeFile()
Open BatName For Output As #FileNum
ChDrive Left(BatName, 1)
ChDir Left(BatName, InStrRev(BatName, "\"))
Print #FileNum, DosCmd
Close #FileNum
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, False, Shell(BatName, WindowStyle))
SleepTime = 10
Do
'Get the status of the process
GetExitCodeProcess hProcess, RetVal
DoEvents: Sleep SleepTime
If TimeOutSeconds <> -1 Then
If Timer - StartTime > TimeOutSeconds Then Exit Do
End If
If StopWaiting Then Exit Do
'Progressively increase the SleepTime by 10%
' This allows a quick process to finish quickly, while providing
' a long process with increasingly greater clock cycles to work with
SleepTime = SleepTime * 1.1
Loop While RetVal = STILL_ACTIVE
Kill BatName
Exit_ShellWait:
Exit Function
Err_ShellWait:
MsgBox Err.Description
Resume Exit_ShellWait
End Function
'---------------------------------------------------------------------------------------vv
' Procedure : TempFileName
' DateTime : 12/9/08
' Author : Mike
' Purpose : Returns an unused file name but does not create the file. Path can be
' passed with or without the trailing '\'.
' Requires : TempPath() function
'---------------------------------------------------------------------------------------
'^^
Function TempFileName(Optional ByVal Path As String = "WINDOWS TEMP FOLDER", _
Optional Ext As String = "txt", _
Optional Prefix As String = "temp") As String 'vv
Dim TempFName As String, i As Integer
If Path = "WINDOWS TEMP FOLDER" Then Path = TempPath
If Right(Path, 1) <> "\" Then Path = Path & "\"
If Not (Path Like "?:\*" Or Path Like "\\*") Then
Err.Raise 52 '"Bad file name or number."
ElseIf Dir(Path, vbDirectory) = "" Then
Err.Raise 76 '"Path not found."
End If
TempFName = Path & Prefix & "." & Ext
For i = 1 To 500
If Dir(TempFName) = "" Then
TempFileName = TempFName
GoTo Exit_TempFileName
End If
TempFName = Path & Prefix & "_" & Format(i, "000") & "." & Ext
Next i
TempFileName = ""
End Function
'---------------------------------------------------------------------------------------
' Procedure : TempPath
' Author : Mike
' Date : 8/12/2008
' Purpose : Returns something like:
' C:\DOCUME~1\BGRAND~1\LOCALS~1\Temp\
'---------------------------------------------------------------------------------------
'^^
Function TempPath() As String 'vv
Const TemporaryFolder = 2
Static TempFolderPath As String
Dim fs As Object
If Len(TempFolderPath) = 0 Then
Set fs = CreateObject("Scripting.FileSystemObject")
TempFolderPath = fs.GetSpecialFolder(TemporaryFolder) & "\"
End If
TempPath = TempFolderPath
End Function
Use CreateProcess and WaitForSingleObject APIs: refer to the "Super Shell" example at this help page for CreateProcess
Calling Microsoft® Windows® Script Host causes windows to display the message. Instead try this
Public Sub test()
Dim dos_command$, lRet&
dos_command = """\\xxx.xxx.xxx.xxx\xxx\xxx\7z.exe"" a test.zip ""\\xxx.xxx.xxx.xxx\xxx\xxx\*.log"" -r"
lRet = Shell(dos_command, vbMaximizedFocus)
MsgBox lRet
End Sub
UPDATE
You may do the following and use your code:
Open Start | Run and type gpedit.msc. Click OK
User Configuration >> Administrative Templates >> Windows Components >> Attachment Manager
Add 7z.exe to the Inclusion list for moderate risk file types setting.
Hpe this helps

FileSystemObject code has started to throw an error

Not sure why but the following code has begun to throw an unknown error. When the macro is run Excel stops responding.
Why is this error occuring?
What is an alternative route with the same functionality?
This code is located within an Excel 2010 xlsm file on a Windows 7 machine.
Sub CopyFolderToCasinoDirectory()
'reference Microsoft Scripting Runtime
On Error Resume Next
Dim fso As Scripting.FileSystemObject
Set fso = New Scripting.FileSystemObject
fso.CopyFolder _
"\\xxxfileserve\department$\DBA\Opers\All Operators\yyy", _
"\\xxxfileserve\department$\DBA\Cas\yyy", _
True
On Error GoTo 0
Set fso = Nothing
End Sub
ok - I've changed the pathways so that it is attempting to move less files - and it hesitates but does eventually run through. I suspect that the above is failing because there are too many files in the directory specified? Currently there are 753 files - maybe too much?
RonDeBruin has given me lots of ideas of how to test or alter the logic. One possibility might be to use DeleteFolder first on the destination folder, and then CopyFolder the target folder over?
Sorry for replying so late. I was not able to get hold of network directories and I wanted to test the code before posting it :)
Try this. Run the Sub Sample() Does it still hang? You will also see the Files getting transferred in a Windows Dialog Box.
Private Declare Function SHFileOperation _
Lib "shell32.dll" Alias "SHFileOperationA" _
(lpFileOp As SHFILEOPSTRUCT) As Long
Private Type SHFILEOPSTRUCT
hWnd As Long
wFunc As Long
pFrom As String
pTo As String
fFlags As Integer
fAborted As Boolean
hNameMaps As Long
sProgress As String
End Type
Private Const FO_COPY = &H2
Sub Sample()
Dim path1 As String, path2 As String
path1 = "\\xxxfileserve\department$\DBA\Opers\All Operators\yyy"
path2 = "\\xxxfileserve\department$\DBA\Opers\All Operators\yyy"
If CopyFolder(path1, path2) Then
MsgBox "Copied"
Else
MsgBox "Not copied"
End If
End Sub
Private Function CopyFolder(ByVal sFrom As String, _
ByVal sTo As String) As Boolean
Dim SHFileOp As SHFILEOPSTRUCT
On Error GoTo Whoa
CopyFolder = False
With SHFileOp
.wFunc = FO_COPY
.pFrom = sFrom
.pTo = sTo
End With
SHFileOperation SHFileOp
CopyFolder = True
Exit Function
Whoa:
MsgBox "Following error occurred while copying folder " & sFrom & vbCrLf & _
Err.Description, vbExclamation, "Error message"
End Function
There are some points regarding the fso.CopyFolder method:
If destination does not exist, the source folder and all its contents gets copied. This is the usual case.
If destination is an existing file, an error occurs.
If destination is a directory, an attempt is made to copy the folder and all its contents.
If a file contained in source already exists in destination, an error occurs if overwrite is False. Otherwise, it will attempt to copy the file over the existing file.
If destination is a read-only directory, an error occurs if an attempt is made to copy an existing read-only file into that directory and overwrite is False.
Make sure not any of these are becoming hindrance for your sub.
But test it another way like this
fso.CopyFolder _
"\\xxxfileserve\department$\DBA\Opers\All Operators\yyy\*", _
"\\xxxfileserve\department$\DBA\Cas\yyy", _
True
Hope this helps.