VBS Script - Do Until (Success = True or Fail = True) - scripting

I am trying to uninstall a ClickOnce application programmatically using a VBS script. It works pretty well. But if the uninstall fails I want it to send a response "Application has already been removed".
The below is what I have so far, and it works for the most part. Sometimes the delay is not long enough or another window steals focus and the "OK" for sendkeys does not make it to the window.
--- Full Source Code ---
On Error Resume Next
Set objShell = WScript.CreateObject("WScript.Shell")
objShell.Run "taskkill /f /im TEST.App.UI.exe*"
objShell.Run "rundll32.exe dfshim.dll,ShArpMaintain test.app.ui.application, Culture=neutral, PublicKeyToken=f77d770cef, processorArchitecture=msil"
Do Until Success = True
Success = objShell.AppActivate("Test App")
Wscript.Sleep 500
Loop
objShell.SendKeys "OK"
'Commented out on purpose
'install new
'objShell.Run ""

When tackling problems of logical control flow, it's best to start with a
story/description:
I want to start an uninstall process and wait for it to terminate with
either success or failure (indicated by opening a window with
appropriate title)
Never use comparison against True/False in conditions; that's just error-prone
fat.
Don't rely on VBscript's intelligence to convert non-booleans; use
variables/expressions of appropriate (sub) type in conditionals.
Remember that VBScript doesn't shortcut the evaluation of conditionals:
If This() Or That() Then
will call both functions, even if This returns True (and therefore the
whole clause must be True). So don't be tempted to do:
... Until (objShell.AppActivate("Uninstall - Success") Or _
objShell.AppActivate("Uninstall - Failure"))
When designing a loop, think about when the leave-checking should be done; if
you want to read from a possibly empty file, it makes sense to check early:
Do Until tsIn.AtEndOfStream
sLine = tsIn.ReadLine()
...
Loop
but if you want to get valid input from the user, you have to ask before
you can check:
Do
sInp = ...
Loop While isBad(sInp)
.AppActivate and .SendKeys are every programmer's foes; postpone fighting
against them, until you have the control flow problem solved.
In code:
' Wait Wait Fail Success
' , Array(False, False, True , True ) _ shouldn't happen, but will terminate too
Dim aHappenings : aHappenings = Array( _
Array(False, False, False, True ) _
, Array(False, False, True , False) _
)
Dim aTest
For Each aTest In aHappenings
WScript.Echo Join(aTest)
Dim bOk : bOk = False
Dim bFail : bFail = False
Dim i : i = 0
Do
bOk = aTest(i + 0)
bFail = aTest(i + 1)
WScript.Echo Join(Array(" ", i, bOk, bFail))
i = i + 2
Loop Until bOk Or bFail
WScript.Echo "Done."
WScript.Echo
Next
Output:
False False False True
0 False False
2 False True
Done
False False True False
0 False False
2 True False
Done.
You may exploit the fact, that either success or failure (not both) will happen:
Dim i : i = 0
Dim bDone
Do
bDone = aTest(i + 0)
If Not bDone Then
bDone = aTest(i + 1)
End If
WScript.Echo Join(Array(" ", i, bDone))
i = i + 2
Loop Until bDone

Related

How to detect if all checkboxes are disabled?

I have an access table that stores information about the steps completed on a product. When entering information, I have a form pop up asking which product they want to enter data for. Each record in the table gets its own checkbox (dynamically created). If information has already been recorded for that product, for that step then the checkbox is disabled. When all checkboxes are disabled I have a message box pop up saying all products for that step have been completed.
The issue: Say for whatever reason (operator choice, production reason, etc), work is done on product 4 for an order but not complete on 1, 2, 3. The code that I have says that all products have info entered just because that is the last record checked.
Dim Args As Variant
Dim i As Integer
Dim ctl As Control
Dim bCheck As Boolean
bCheck = False
If Not IsNull(Me.OpenArgs) Then
Args = Split(Me.OpenArgs, ";")
Me.txtForm = Args(0)
Me.lblChoices.Caption = Args(1)
End If
For Each ctl In Forms(Me.Name).Controls
If ctl.ControlType = acCheckBox Then
ctl.Value = False
If ctl.Enabled = True Then
bCheck = False
Else
bCheck = True
End If
End If
Next
If bCheck = True Then
fncMsgBox "Labor has been entered for all bundles on this step."
DoCmd.Close acForm, Me.Name
End If
This IF statement is the problem and its obvious to me why it doesn't work. I'm curious as to how I can get around this?
If ctl.Enabled = True Then
bCheck = False
Else
bCheck = True
End If
Exit the loop as soon as you find an enabled checkbox:
Dim Args As Variant
Dim i As Integer
Dim ctl As Control
Dim bCheck As Boolean
bCheck = False
If Not IsNull(Me.OpenArgs) Then
Args = Split(Me.OpenArgs, ";")
Me.txtForm = Args(0)
Me.lblChoices.Caption = Args(1)
End If
For Each ctl In Forms(Me.Name).Controls
If ctl.ControlType = acCheckBox Then
ctl.Value = False
If ctl.Enabled Then
bCheck = True
Exit For 'stop checking
End If
End If
Next
If bCheck Then
fncMsgBox "Labor has been entered for all bundles on this step."
DoCmd.Close acForm, Me.Name
End If
Note: you don't need = True in your If when the value you're checking already represents a boolean.

How to send focus to a text box of a VBA form during its initialization/activate event?

I have a VBA form in Corel. Behaving exactly like a similar one in Excel...
Initially, when the form initialize event used to contain only some lines of code, the simple ending line me.txtCsv.Setfocus used to send the focus on it. I mean, it appeared being in edit mode with the cursor blinking inside.
After a period of time, after the application became complex, I am not able to send the focus to the text box in discussion.
I know that Activate event goes last and I also have in it the line me.txtCsv.Setfocus. But without expected result. Inside the Initialization event code I inserted that line Debug.Print Me.ActiveControl.Name & " - 1", changing 1 in 2, 3 up to 6 in many places, including the last line and all the time the name of the text box in discussion (txtCsv) appears in Immediate Window.
So, the control in discussion is the activate one, but the cursor is not inside it when the form is loaded.
TabStop is set to True. I set the TabIndex to 0.
The control is enabled and not blocked. I have created a new simple form with three text boxes and it works well.
I mean the text box which I want to send the focus, has the focus when the form is loaded, keeping a similar code in its Initialize or Activate events.
I compared all properties of the two forms and all text box controls and they are the same...
When I send the focus from another control on the form, the text box in discussion receives it.
It does not receive the focus (anymore) only when the form is shown, the focus being sent by Initialize or Activate evens.
Events code:
Private Sub UserForm_Activate()
Me.txtCsv.SetFocus
End Sub
Private Sub UserForm_Initialize()
Dim P As Printer, i As Long, NrImp As Long, prDefault As String, strJustEngr As String
Dim Printers() As String, n As Long, s As String, boolFound As Boolean
Dim strEng As String, MEngr As Variant, m As Variant, el As Variant, defSize As String
Dim strDropbox As String
boolOpt = True: boolFound = False
Me.cbPrinters.Clear
If Me.chkNewStyle.Value = True Then boolNewStyle = True
prDefault = Application.Printers.Default.Name
strEng = GetSetting(ECA_K, ECA_set, ECA_Engr, "No settings...")
If strEng <> "No settings..." Then
boolSelectedEngravers = True ' only adding engraver is possible...
MEngr = Split(strEng, "|")
'Incarcare in combo:
Me.cbPrinters.Clear
For Each el In MEngr
m = Split(el, ":")
Me.cbPrinters.AddItem m(0)
If m(0) = prDefault Then
boolFound = True
defSize = m(1)
End If
Next
Me.cbPrinters.Value = Me.cbPrinters.List(0)
With Me.btChoosePrinters
.Caption = "Add an Engraver"
.ControlTipText = "Add another Engraver(must be installed)"
End With
Me.btEliminatePrinters.Enabled = True
Me.lblPrinters.Caption = "Engravers: "
Me.cbPrinters.ControlTipText = "Select Engraver to be used!"
Else
Printers = GetPrinterFullNames()
For n = LBound(Printers) To UBound(Printers)
Me.cbPrinters.AddItem Printers(n)
If Printers(n) = prDefault Then boolFound = True
Next n
boolSelectedEngravers = False
End If
Debug.Print Me.ActiveControl.Name & " - 1"
If boolFound Then
Me.cbPrinters.Value = prDefault
Else
Me.lblStatus.Caption = "The default printer (""" & prDefault & """) is not a laser Engraver..."
End If
If GetSetting(ECA_K, ECA_set, "LowRAM", "No settings...") <> "No settings..." Then
boolLowRAM = CBool(GetSetting(ECA_K, ECA_set, "LowRAM", "No settings..."))
End If
If boolLowRAM = True Then
Me.chkLowRAM.Value = True
Else
Me.chkLowRAM.Value = False
End If
Debug.Print Me.ActiveControl.Name & " - 2"
'Direct engrave setting:
Dim strDirectEngrave As String
strDirectEngrave = GetSetting(ECA_K, ECA_set, ECA_Direct_Engrave, "Nothing")
If strDirectEngrave <> "Nothing" Then
Me.chkDirectEngrave.Value = CBool(strDirectEngrave)
If CBool(strDirectEngrave) = True Then
boolDirectEngrave = True
Else
boolDirectEngrave = False
End If
End If
'_______________________________________
strJustEngr = GetSetting(ECA_K, ECA_set, ECA_Just_Engrave, "Nothing")
If strJustEngr <> "Nothing" Then
'Application.EventsEnabled = False
boolChangeEngr = True
Me.chkJustEngrave.Value = CBool(strJustEngr)
boolChangeEngr = False
'Application.EventsEnabled = True
If CBool(strJustEngr) = True Then
Me.chkDirectEngrave.Enabled = True
boolJustEngrave = True
Me.frLocFoldPath.Enabled = True
Else
Me.frLocFoldPath.Enabled = False
Me.chkDirectEngrave.Enabled = False
End If
End If
Debug.Print Me.ActiveControl.Name & " - 3"
If boolSelectedEngravers Then
Application.EventsEnabled = False
Me.btGo.ForeColor = RGB(45, 105, 7)
Me.txtCsv.BackColor = RGB(153, 255, 51)
Me.btGo.Enabled = False
Me.txtCsv.SetFocus
Application.EventsEnabled = True
End If
strDropbox = GetSetting(ECA_K, ECA_set, ECA_Dropbox, "No value")
If strDropbox <> "No value" Then
If CBool(strDropbox) = True Then
Me.chkDropbox.Value = True
End If
End If
AllRefresh
Me.chkCloseDoc.Value = True
Me.txtCsv.SetFocus
Debug.Print Me.ActiveControl.Name & " - 4"
End Sub
Private Sub AllRefresh()
Application.Optimization = False
Application.EventsEnabled = True
If Documents.Count > 0 Then
ActiveWindow.Refresh
ActiveDocument.PreserveSelection = True
End If
Application.Refresh
End Sub
Is there something else, crossing your mind, to be tested?
In the meantime I did some more tests, respectively:
I created a new project (.GMS file) and I imported the form in discussion.I started commenting all the Initialize event code, except the last two code lines.
It didn't set the focus! Commenting everything, letting only the Activate event code, it worked.
I started to un-comment lines in Initialize event code and I found a line not allowing the focus to be sent to that text box.
Setting the value of the combo: Me.cbPrinters.Value = Me.cbPrinters.List(0), moving it in the Activate event code, before the part pointing to txtCSV, worked well.
Now, I tried to do the same in the original form and it does not work...
The above question has been solved by Disabling followed by Enabling of the text box in discussion, but only doing that in Form Activate event. It did not work in Initialize event...
Private Sub UserForm_Activate()
Me.txtCsv.Disable: Me.txtCsv.Enable
Me.txtCsv.SetFocus
End Sub

VBA Excel 2016 - Import text file - Unhandleable error when filelen < 2

I have a big problem that I need to solve!
I'm creating a workbook for people to open it on their machines so I must be sure that the code behaviors will be logical...
I'm stucked with a weird case!
I am trying to import a .csv file through a querytable. If that file contains less than 3 characters I get an "out of memory" (7) error that I can't handle with on error statement!!
Here I create the queryTable :
Private Function CreateProductsQueryTable(ByRef ws As Worksheet) As QueryTable
Set CreateProductsQueryTable = ws.QueryTables.Add("TEXT;" & GetPath(ws.Parent) & PRODUCTS_FILE_NAME, ws.range("A2"))
With CreateProductsQueryTable
.name = CONNECTION_NAME
.RefreshStyle = xlOverwriteCells
.TextFilePlatform = 1252
.TextFileStartRow = 1
.TextFileTabDelimiter = False
.TextFileSemicolonDelimiter = True
.AdjustColumnWidth = False
.TextFileColumnDataTypes = Array(2, 2, 2, 1, 1, 1, 1, 1)
.TextFileDecimalSeparator = ","
End With
End Function
No problems with that as you may check!
An here I try to refresh it :
Private Function Refresh(ByRef qt As QueryTable) As Boolean
With qt
On Error GoTo emptyFile
.Refresh True
On Error GoTo 0
If .ResultRange.Columns.count <> GetName(HEADERS_NAME, .Parent).RefersToRange.Columns.count Then
.ResultRange = ""
.Delete
Else
Refresh = True
End If
Exit Function
emptyFile:
.ResultRange = ""
.Delete
End With
End Function
Despite the "on error goto emptyFile" statement I still get an "out of memory" error prompt highlighting the ".refresh" method, when my file contains 0, 1 or 2 characters..!! I just have to manually put a third character to get things work properly...
I really need to know if this behavior is intended and if it would be the same on any machine! If so I would just control the filelen! But what if this is a weird bug that happens for a random filesize like if a user get that unhandled error while trying to refresh with a 4 characters long file..?? That would be a mess ^^'

Workbook.CheckIn always produces an error the first time it's called?

I have some workbooks stored in a document library on Sharepoint 2007. I want to check out a workbook, modify it, and check it back in.
Using the following code:
Option Explicit
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Sub test()
Dim bk As Workbook
Dim path As String
path = "http://sharepoint/sites/test/TEST_Relink.xlsm"
If Workbooks.CanCheckOut(path) Then
Application.DisplayAlerts = False
Workbooks.CheckOut path
DoEvents
Set bk = Workbooks.Open(path, False)
bk.Sheets("test").Range("h1").Value = "modified " & Now
DoEvents
Sleep 10000
bk.checkIn True
Application.DisplayAlerts = True
End If
End Sub
The bk.checkIn call always produces the following run-time error:
Method 'CheckIn' of object '_Workbook' failed
After I go into Debug, I press F5 to continue and the check-in always occurs successfully.
I added the 10-second delay with Sleep 10000 because I was thinking that maybe the check-out was taking a while to propagate to the server. But no matter how much time I set for Sleep, this same issue keeps occurring. Any thoughts?
EDIT:
I tried using a looped check of .CanCheckIn as follows:
While Not bk.CanCheckIn
DoEvents
Wend
bk.checkIn True
This gave the same error.
For those finding this like I did, I had
Workbooks(logFileName).CheckIn SaveChanges:=True, Comments:="New row added from " & mainFile
This produced the error message like yours, however on entering debug and pressing f5 would action. So here is my complex solution.....I just split out the code to the following
Workbooks(logFileName).Save
Workbooks(logFileName).CheckIn Comments:="New row added from " & mainFile
Hope this helps others.
Use this:
Dim xl As Excel.Application
Set xl = CreateObject("Excel.Application")
xl.AutomationSecurity = msoAutomationSecurityForceDisable
xl.EnableEvents = False
xl.DisplayAlerts = False
'code to checkin/checkout
xl.EnableEvents = True
xl.DisplayAlerts = True
You probably already figured it out but I thought I'd post it for anyone else who comes here looking for an answer.
If you are setting SaveChanges to True then you MUST also set Comments to be a String (a null value won't do)
So in your example you would need to do this:
bk.CheckIn True, ""

Excel is waiting for another application to complete an OLE action

Before you go for the obvious: Application.DisplayAlerts = False has not solved my problem.
I have written a VBA procedure (initiated in Excel 2010) which loops around an array containing different Excel files. The loop opens the file, refreshes the data, saves and closes the file for each item in the array. I have written an error catch sub routine so I log which excel files have failed to open/refresh/save etc so a user can manually check them.
Some files are quite large and involve a large amount of data moving across the network; sometimes I get a dialog box with: Excel is waiting for another application to complete an OLE action.
I could use Application.DisplayAlerts = False to disable the message but this would presumably disable all alerts so I couldn't catch the errors?
Further I have tested using the line and it doesn't stop the dialog box pop-up. If I press enter it carries on but will likely pop-up again a few minutes later.
Is there a way to stop is message specifically without stopping other alerts?
NB. My process has a control instance of Excel which runs the VBA and opens the workbooks to be refreshed in a separate instance.
Thanks for your help
An extract of my code is below which contains the refresh elements
Sub Refresh_BoardPivots_Standard()
' On Error GoTo Errorhandler
Dim i
Dim errorText As String
Dim x
Dim objXL As Excel.Application
Set objXL = CreateObject("Excel.Application")
GetPivotsToRefresh ' populate array from SQL
For Each i In StandardBoardPiv
DoEvents
'If File_Exists(i) Then
If isFileOpen(i) = True Then
errorText = i
Failed(failedIndex) = errorText
failedIndex = failedIndex + 1
Else
objXL.Visible = True 'False
objXL.Workbooks.Open FileName:=i
If objXL.ActiveWorkbook.ReadOnly = False Then
BackgroundQuery = False
Application.DisplayAlerts = False
objXL.ActiveWorkbook.RefreshAll
objXL.Application.CalculateFull
objXL.Application.DisplayAlerts = False
objXL.ActiveWorkbook.Save
objXL.Application.DisplayAlerts = True
objXL.Quit
Else
errorText = i
Failed(failedIndex) = errorText
failedIndex = failedIndex + 1
objXL.Application.DisplayAlerts = False
objXL.Quit
Application.DisplayAlerts = True
End If
End If
' Else
' errorText = i
' Failed(failedIndex) = errorText
' failedIndex = failedIndex + 1
' End If
DoEvents
If Ref = False Then
Exit For
End If
Next i
Exit Sub
'Errorhandler:
'
'errorText = i
'Failed(failedIndex) = errorText
'failedIndex = failedIndex + 1
'Resume Next
End Sub
"Waiting for another application to complete an OLE action" isn't an alert message you can just turn off and forget, sometimes the macro will be able to continue on after, but in my experience if you are getting that error its only a matter of time until the problem crashes/freezes your whole macro so it should definitely be troubleshot and corrected.
I only get that error when I am using additional Microsoft Office Applications (other than the Excel that is running the code) as objects and one of them has an error- the Excel running the code doesn't know that an error occurred in one of the other applications so it waits and waits and waits and eventually you get the "Waiting for another application to complete an OLE action" message...
So to troubleshoot this sort of problem you got to look for the places you use other MSO apps... In your example, you have an additional instance of Excel and you are pulling data from Access, so its most likely one of those two that is causing the problems...
Below is how I would re-write this code, being more careful with where the code interacts with the other MSO apps, explicitly controlling what is happening in them.. The only piece I couldn't really do much is GetPivotsToRefresh because I cant see what exactly youre doing here, but in my code I just assumed it returned an array with a list of the excel files you want to update. See code below:
Sub Refresh_BoardPivots_Standard()
Dim pivotWB As Workbook
Dim fileList() As Variant
Dim fileCounter As Long
Application.DisplayAlerts = False
fileList = GetPivotsToRefresh 'populate array from SQL
For fileCounter = 1 To UBound(fileList, 1)
Set pivotWB = Workbooks.Open(fileList(fileCounter, 1), False, False)
If pivotWB.ReadOnly = False Then
Call refreshPivotTables(pivotWB)
pivotWB.Close (True)
Else
'... Error handler ...
pivotWB.Close (False)
End If
Next
End Sub
Public Sub refreshPivotTables(targetWB As Workbook)
Dim wsCounter As Long
Dim ptCounter As Long
For wsCounter = 1 To targetWB.Sheets.Count
With targetWB.Sheets(wsCounter)
If .PivotTables.Count > 0 Then
For ptCounter = 1 To .PivotTables.Count
.PivotTables(ptCounter).RefreshDataSourceValues
Next
.Calculate
End If
End With
Next
End Sub
So I created my own 'refreshPivotTables' but you could have embedded that into the master sub, I just thought the loops and loop counters might get a little messy at that point...
Hope this helps,
TheSilkCode