Async in VB not working as expected - vb.net

UPDATE:
It looks like the culprit is the status reporting
ffStatus = procFFMPEG.StandardError 'Send standard error to ffStatus
strFFout = ffStatus.ReadLine 'Read every line of output and send to strFFout
It look's like it breaks the async in that part. When commented out, marquee scrollbar behave as expected.
Is there a way to be able to get those data and update status while not breaking async to show marquee progressbar?
ORIGINAL POST
I have similar problem to this question
VB.NET Marquee Progress Until Process Exits
I use the accepted answer and gets to this code
Public Async Sub GoConvert(theVCodec As String, theHeight As String)
Dim theOptions As String, theApp As String, theSourcePath As String, theDestPath As String
Dim theFilename As String, theACodec As String, theFormat As String, theLosslessOpt As String
Dim theNewFilename As String, theNewFileTag As String, theInterlaced As String, theMsg As String
Dim thePreset As String, theCRF As String
Dim ffStatus As StreamReader, strFFout As String
'On Error GoTo Handler
'SET DEFAULT VALUES
theApp = "ffmpeg.exe"
theSourcePath = txtSource.Text
theDestPath = txtOutput.Text & "\"
theACodec = "libmp3lame"
thePreset = "veryfast"
theCRF = "22"
theInterlaced = ""
theNewFileTag = ""
theLosslessOpt = ""
Select Case theVCodec
Case "libx264"
theNewFileTag = "x264"
Case "libxvid"
theNewFileTag = "xvid"
Case "libx265"
theNewFileTag = "x265"
End Select
If cmbUseCodec.Text = "x264 vegas" Then
theACodec = "aac"
theNewFileTag = "x264forVegas"
End If
theMsg = "IF FILE EXISTS, IT WILL BE OVERWRITTEN!" & vbCrLf & vbCrLf & "Please make sure that there is no filename conflict in the destination folder," & vbCrLf & "Encoder will overwrite existing files." _
& vbCrLf & vbCrLf & "Do you want to continue?"
If MessageBox.Show(theMsg, "WARNING!", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) = DialogResult.Yes Then
For i As Integer = 0 To lstSourceFiles.Items.Count - 1
If chkToFileType.CheckedItems.Count <> 0 Then
Dim x As Integer
Dim forVegas As String
For x = 0 To chkToFileType.CheckedItems.Count - 1
theFormat = chkToFileType.CheckedItems(x).ToString
'GET FILENAMES ON FILES LISTBOX
theFilename = lstSourceFiles.Items(i).ToString
theNewFilename = System.IO.Path.GetFileNameWithoutExtension(theFilename)
If chkSameOutputFolder.CheckedItems.Count > 0 Then
theDestPath = Path.GetDirectoryName(theFilename) & "\"
End If
If (theVCodec = "libx265") Then
theCRF = "28"
thePreset = "medium"
End If
If (chkLossLess.CheckedItems.Count > 0) And (theVCodec = "libx265") Then
theLosslessOpt = "-x265-params lossless=1 "
End If
If theFormat = "mp3" Then
'-i "%%a" -qa 0 - map a "%%~na.mp3"
theOptions = " -i " & Chr(34) & theFilename & Chr(34) & " -y -q:a 0 -map a " & Chr(34) & theDestPath & theNewFilename & "." & theFormat & Chr(34)
Else
'PREPARE NEW FILENAME OF CONVERTED FILE
theNewFilename = theNewFilename & "-" & theNewFileTag & "-" & theHeight & "p"
theOptions = " -i " & Chr(34) & theFilename & Chr(34) & " -y -vcodec " & theVCodec
theOptions = theOptions & " -vf " & theInterlaced & "scale=" & Chr(34) & "trunc(oh*a/2)*2:" & theHeight & Chr(34)
If cmbUseCodec.Text = "x264 vegas" Then
forVegas = " -strict experimental -tune fastdecode -pix_fmt yuv420p -b:a 192k -ar 48000"
theOptions = theOptions & " -preset " & thePreset & " -crf " & theCRF & " -acodec " & theACodec & forVegas
theOptions = theOptions & " -threads 4 " & theLosslessOpt & Chr(34) & theDestPath & theNewFilename & "." & theFormat & Chr(34)
Else
theOptions = theOptions & " -b 1750k -preset " & thePreset & " -crf " & theCRF & " -acodec " & theACodec
theOptions = theOptions & " -ac 2 -ab 160k -threads 4 " & theLosslessOpt & Chr(34) & theDestPath & theNewFilename & "." & theFormat & Chr(34)
End If
End If
theOptions = theOptions & " -loglevel error -stats"
'LET'S GET READY TO CONVERT
ConvertProcessInfo.FileName = theApp
ConvertProcessInfo.Arguments = theOptions
'LET'S TRY TO CAPTURE STATUS
ConvertProcessInfo.RedirectStandardError = True
ConvertProcessInfo.RedirectStandardOutput = True
ConvertProcessInfo.UseShellExecute = False
ConvertProcessInfo.CreateNoWindow = True
'LET'S PROVIDE SOME MEANINGFUL INFO
procFFMPEG.StartInfo = ConvertProcessInfo
lstStatus.Items(i) = "Encoding: " & theFormat
txtProcessInfo.Text = "Encoding file: " & theNewFilename & "." & theFormat
'LET'S DISABLE CONTROLS WHILE CONVERT IS WORKING AND ENABLE PROGRESSBAR
prgrssConvert.Visible = True
DisableControls()
'LET'S CONVERT
procFFMPEG.Start()
Do
Application.DoEvents()
ffStatus = procFFMPEG.StandardError 'Send standard error to ffStatus
strFFout = ffStatus.ReadLine 'Read every line of output and send to strFFout
Debug.Print(strFFout)
txtProcessInfo.Text = strFFout
'THESE LINES IS NOT NEEDED IF ASYNC WILL WORK
txtProcessInfo.Refresh()
lstSourceFiles.Refresh()
lstStatus.Refresh()
prgrssConvert.Refresh()
Loop Until procFFMPEG.HasExited
'LET'S WAIT FOR PROCESS TO EXIT
Await Task.Run(Sub() procFFMPEG.WaitForExit())
'UPDATE STATUS AFTER EVERY FILE
prgrssConvert.Visible = False
lstStatus.Items(i) = "DONE"
Next
End If
Next
'WHEN ALL FILES DONE, UPDATE STATUS
txtProcessInfo.Text = "Encoding completed. Waiting for new task"
EnableControls()
End If
End Sub
My problem is that the progressbar (prgrssConvert.Visible = True) is not updating asynchronously that is why I have to add refresh in the DO LOOP but it is not that visually appealing because it is "robotic" and not smoothly flowing marquee.
It looks to me that async is not doing it's job. I am hoping to keep the progressbar marquee running while waiting for the ffmpeg process to complete.
Any idea why async is not working on my code?
Thanks

There perfect answer for a good questions. I didn't see you were returning the output to a textbox. sorry. You have to use readlineasync otherwise your are waiting for a line from your output that might only come at the end. If it never comes your app will be stuck there.
This is for reading errors procFFMPEG.StandardError
If you actually want the output of your process use this
procMMFPEG.StandardOutput instead or both but you will need to adapt your code to it
some reference for StandardOutput
Public Async Sub GoConvert(theVCodec As String, theHeight As String)
Dim theOptions As String, theApp As String, theSourcePath As String, theDestPath As String
Dim theFilename As String, theACodec As String, theFormat As String, theLosslessOpt As String
Dim theNewFilename As String, theNewFileTag As String, theInterlaced As String, theMsg As String
Dim thePreset As String, theCRF As String
Dim ffStatus As StreamReader, strFFout As String
'On Error GoTo Handler
'SET DEFAULT VALUES
theApp = "ffmpeg.exe"
theSourcePath = txtSource.Text
theDestPath = txtOutput.Text & "\"
theACodec = "libmp3lame"
thePreset = "veryfast"
theCRF = "22"
theInterlaced = ""
theNewFileTag = ""
theLosslessOpt = ""
Select Case theVCodec
Case "libx264"
theNewFileTag = "x264"
Case "libxvid"
theNewFileTag = "xvid"
Case "libx265"
theNewFileTag = "x265"
End Select
If cmbUseCodec.Text = "x264 vegas" Then
theACodec = "aac"
theNewFileTag = "x264forVegas"
End If
theMsg = "IF FILE EXISTS, IT WILL BE OVERWRITTEN!" & vbCrLf & vbCrLf & "Please make sure that there is no filename conflict in the destination folder," & vbCrLf & "Encoder will overwrite existing files." _
& vbCrLf & vbCrLf & "Do you want to continue?"
If MessageBox.Show(theMsg, "WARNING!", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) = DialogResult.Yes Then
'LET'S DISABLE CONTROLS WHILE CONVERT IS WORKING AND ENABLE PROGRESSBAR
prgrssConvert.Visible = True
DisableControls()
prgrssConvert.value = 0 'I assumed this was a progressbar
prgrssConvert.maximum = lstSourceFiles.Items.Count * chkToFileType.CheckedItems.Count
For i As Integer = 0 To lstSourceFiles.Items.Count - 1
If chkToFileType.CheckedItems.Count <> 0 Then
Dim x As Integer
Dim forVegas As String
For x = 0 To chkToFileType.CheckedItems.Count - 1
theFormat = chkToFileType.CheckedItems(x).ToString
'GET FILENAMES ON FILES LISTBOX
theFilename = lstSourceFiles.Items(i).ToString
theNewFilename = System.IO.Path.GetFileNameWithoutExtension(theFilename)
If chkSameOutputFolder.CheckedItems.Count > 0 Then
theDestPath = Path.GetDirectoryName(theFilename) & "\"
End If
If (theVCodec = "libx265") Then
theCRF = "28"
thePreset = "medium"
End If
If (chkLossLess.CheckedItems.Count > 0) And (theVCodec = "libx265") Then
theLosslessOpt = "-x265-params lossless=1 "
End If
If theFormat = "mp3" Then
'-i "%%a" -qa 0 - map a "%%~na.mp3"
theOptions = " -i " & Chr(34) & theFilename & Chr(34) & " -y -q:a 0 -map a " & Chr(34) & theDestPath & theNewFilename & "." & theFormat & Chr(34)
Else
'PREPARE NEW FILENAME OF CONVERTED FILE
theNewFilename = theNewFilename & "-" & theNewFileTag & "-" & theHeight & "p"
theOptions = " -i " & Chr(34) & theFilename & Chr(34) & " -y -vcodec " & theVCodec
theOptions = theOptions & " -vf " & theInterlaced & "scale=" & Chr(34) & "trunc(oh*a/2)*2:" & theHeight & Chr(34)
If cmbUseCodec.Text = "x264 vegas" Then
forVegas = " -strict experimental -tune fastdecode -pix_fmt yuv420p -b:a 192k -ar 48000"
theOptions = theOptions & " -preset " & thePreset & " -crf " & theCRF & " -acodec " & theACodec & forVegas
theOptions = theOptions & " -threads 4 " & theLosslessOpt & Chr(34) & theDestPath & theNewFilename & "." & theFormat & Chr(34)
Else
theOptions = theOptions & " -b 1750k -preset " & thePreset & " -crf " & theCRF & " -acodec " & theACodec
theOptions = theOptions & " -ac 2 -ab 160k -threads 4 " & theLosslessOpt & Chr(34) & theDestPath & theNewFilename & "." & theFormat & Chr(34)
End If
End If
theOptions = theOptions & " -loglevel error -stats"
'LET'S GET READY TO CONVERT
ConvertProcessInfo.FileName = theApp
ConvertProcessInfo.Arguments = theOptions
'LET'S TRY TO CAPTURE STATUS
ConvertProcessInfo.RedirectStandardError = True
ConvertProcessInfo.RedirectStandardOutput = True
ConvertProcessInfo.UseShellExecute = False
ConvertProcessInfo.CreateNoWindow = True
'LET'S PROVIDE SOME MEANINGFUL INFO
procFFMPEG.StartInfo = ConvertProcessInfo
lstStatus.Items(i) = "Encoding: " & theFormat
txtProcessInfo.Text = "Encoding file: " & theNewFilename & "." & theFormat
'LET'S CONVERT
procFFMPEG.Start()
Do
ffStatus = procFFMPEG.StandardError 'Send standard error to ffStatus
strFFout = Await(ffStatus.ReadLineAsync()) 'Read every line of output and send to strFFout
Debug.Print(strFFout)
txtProcessInfo.Text = strFFout
Loop Until procFFMPEG.HasExited = True
'UPDATE STATUS AFTER EVERY FILE
prgrssConvert.value += 1
Next
lstStatus.Items(i) = "DONE"
End If
Next
prgrssConvert.Visible = False
'WHEN ALL FILES DONE, UPDATE STATUS
txtProcessInfo.Text = "Encoding completed. Waiting for new task"
EnableControls()
End If
End Sub

This is untested, but can you try this:
Do
Await Task.Delay(TimeSpan.FromSeconds(0.1))
ffStatus = procFFMPEG.StandardError 'Send standard error to ffStatus
strFFout = ffStatus.ReadLine 'Read every line of output and send to strFFout
Debug.Print(strFFout)
txtProcessInfo.Text = strFFout
Loop Until Await Task.Run(Function() procFFMPEG.HasExited)
Here's a simple bit of code to test that this works:
Dim process As New Process()
process.StartInfo = New ProcessStartInfo("C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe")
process.Start()
Do
Await Task.Delay(TimeSpan.FromSeconds(0.1))
Console.WriteLine("!")
Loop Until Await Task.Run(Function() process.HasExited)
It produces lines of ! until the PowerShell console is closed.

Related

MS Access if statement on click event

I am using Ms Access forms and I have created an on click event that locates a folder location but now I want to locate the folder location based on different criteria but when I add the if statement it expects a sub,function or property. Below is some demo code. I really hope someone can explain what is missing?
Private Sub Open_Email_Click()
Dim stAppName As String
Dim stAppNameA As String
Dim stAppNameB As String
stAppName = "C:\Windows\explorer.exe C:\DEMO\TEST\" & Me.Office & " DEMO\B " & Me.BC & " " & Me.UC & "\"
stAppNameA = "C:\Windows\explorer.exe C:\DEMO\TEST\" & Me.Office & " DEMO\A\B " & Me.BC & " " & Me.UC & "\"
stAppNameB = "C:\Windows\explorer.exe C:\DEMO\TEST\" & Me.Office & " DEMO\B\B " & Me.BC & " " & Me.UC & "\"
If (Me.BC = "60") And Me.UC Like "REF123*" Then stAppNameA
ElseIf (Me.BC = "60") And Not Me.UC Like "REF123*" Then stAppNameB
Else: stAppName
End If
Call Shell(stAppName, 1)
End Sub
I think the logic of your function could be reduced to the following, which may be more readable with fewer repeating expressions:
Private Sub Open_Email_Click()
Dim strTmp As String
If Me.BC = "60" Then
If Me.UC Like "REF123*" Then
strTmp = " DEMO\A\B "
Else
strTmp = " DEMO\B\B "
End If
Else
strTmp = " DEMO\B "
End If
Call Shell("C:\Windows\explorer.exe C:\DEMO\TEST\" & Me.Office & strTmp & Me.BC & " " & Me.UC & "\", 1)
End Sub
Alternatively, using a Select Case statement:
Private Sub Open_Email_Click()
Dim strTmp As String
Select Case True
Case Me.BC <> "60"
strTmp = " DEMO\B "
Case Me.UC Like "REF123*"
strTmp = " DEMO\A\B "
Case Else
strTmp = " DEMO\B\B "
End Select
Call Shell("C:\Windows\explorer.exe C:\DEMO\TEST\" & Me.Office & strTmp & Me.BC & " " & Me.UC & "\", 1)
End Sub
To test the resulting path, change:
Call Shell("C:\Windows\explorer.exe C:\DEMO\TEST\" & Me.Office & strTmp & Me.BC & " " & Me.UC & "\", 1)
To:
Debug.Print "C:\Windows\explorer.exe C:\DEMO\TEST\" & Me.Office & strTmp & Me.BC & " " & Me.UC & "\"
I think your If block is just a bit messy in terms of where you have newlines, and continuation characters (:). Try reformatting your code like this:
If (Me.BC = "60") And Me.UC Like "REF123*" Then
stAppName =stAppNameA
ElseIf (Me.BC = "60") And Not Me.UC Like "REF123*" Then
stAppName = stAppNameB
Else
stAppName =stAppName
End If
Call Shell(stAppName, 1)

VBA how to check if download files from Server has success?

I'm able to use the below code to download files from server. However, this does tell me whether the files are downloaded successfully.
Sub DownloadFirstRunFilesPart2()
Application.StatusBar = "Downloading files..."
Dim wsh As Object
Dim errorcode4 As Integer
Dim cmd5 As Variant
Dim FirstRunFiles(5) As Variant
Dim var As Variant
FirstRunFiles(0) = ProN & "_KSParameter_UserInput.xlsx"
FirstRunFiles(1) = ProN & "_KSParameter_SysOutput.xlsx"
FirstRunFiles(2) = ProN & "_ModelParameter_UserInput.xlsx"
FirstRunFiles(3) = ProN & "_ModelParameter_SysOutput.xlsx"
FirstRunFiles(4) = ProN & "_VarClusParameter_UserInput.xlsx"
FirstRunFiles(5) = ProN & "_VarClusParameter_SysOutput.xlsx"
For Each var In FirstRunFiles
cmd5 = Chr(34) & "C:\Program Files (x86)" & "\PuTTY\pscp.exe" & Chr(34) & " -sftp -l " & pUser & " -pw " & pPass & _
" " & " " & pHost & ":" & ServerPath & "/" & var & " " & LocalPath & "\"
Set wsh = CreateObject("wscript.shell")
errorcode4 = wsh.Run(cmd5, vbHide)
'If errorcode4 = 0 Then MsgBox ("Error occurs. Fail to download " & var)
Next var
Application.StatusBar = "Download complete"
MsgBox ("Downloading process complete.")
End Sub
My error code always equals 0 no matter the file exists or not. How should I change this program?
Thanks in advance!
Update:
The new code that I tried:
Sub test()
Dim wsh As Object
Dim WshShellExec As Variant
Dim cmd3 As String
Dim pFirstRunFile1 As String
Const WshFinished = 1
Const WshFailed = 2
pFirstRunFile1 = "this_proj_name.txt"
cmd3 = Chr(34) & "C:\Program Files (x86)" & "\PuTTY\pscp.exe" & Chr(34) & " -sftp -l " & pUser & " -pw " & pPass & _
" " & " " & pHost & ":" & ServerPath & "/WOE/" & pFirstRunFile1 & " " & LocalPath & "\WOE"
Set wsh = CreateObject("wscript.shell")
WshShellExec = wsh.Exec(cmd3)
Select Case WshShellExec.Status
Case WshFinished
strOutput = WshShellExec.StdOut.ReadAll
Case WshFailed
strOutput = WshShellExec.StdErr.ReadAll
End Select
MsgBox strOutput 'write results in a message box
End Sub
However I'm getting error on this line:
WshShellExec = wsh.Exec(cmd3)
The error message says "Object does not support this property or method". Any ideas?

Output doesn't match input

I've created a macro that's meant to created a lump of CSS & HTML from a set of values in each sheet of a spreadsheet.
It's a little untidy as I created the function to write it from one sheet first as a proof of concept, and then updated it.
It doesn't throw any obvious errors, but the output varies, sometimes it shows the same thing both times, and then depending on where I've got debug MsgBoxs or watches in VBA seems to alter the output.
Any ideas what on earth i'm doing wrong?
Sub createCode()
Dim myWorkbook As Workbook
Dim mySheet As Worksheet
Set myWorkbook = Application.ActiveWorkbook
For Each mySheet In myWorkbook.Worksheets
Dim bannerCount As Integer
Dim BannerCollection() As Banner
Dim r As Range
Dim lastRow, lastCol
Dim allCells As Range
bannerCount = 0
lastCol = mySheet.Range("a2").End(xlToRight).Column
lastRow = mySheet.Range("a2").End(xlDown).Row
Set allCells = mySheet.Range("a2", mySheet.Cells(lastRow, lastCol))
' MsgBox (mySheet.Name)
' MsgBox ("lastRow:" & lastRow & "lastCol:" & lastCol)
ReDim BannerCollection(allCells.Rows.Count)
For Each r In allCells.Rows
Dim thisBanner As Banner
thisBanner.imagePath = ""
thisBanner.retImagePath = ""
thisBanner.bannerTitle = ""
thisBanner.urlPath = ""
bannerCount = bannerCount + 1
' MsgBox (bannerCount)
thisBanner.imagePath = Cells(r.Row, 2).Value
thisBanner.retImagePath = Cells(r.Row, 3).Value
thisBanner.bannerTitle = Cells(r.Row, 4).Value
thisBanner.urlPath = Cells(r.Row, 5).Value
'MsgBox (Cells(r.Row, 2).Value)
'MsgBox (Cells(r.Row, 3).Value)
'MsgBox (Cells(r.Row, 4).Value)
'MsgBox (Cells(r.Row, 5).Value)
BannerCollection(bannerCount - 1) = thisBanner
Next r
Dim i As Variant
Dim retinaCSS, imgCSS, firstBannerCode, otherBannersCode, bannerTracking As String
retinaCSS = ""
imgCSS = ""
firstBannerCode = ""
otherBannersCode = ""
bannerTracking = ""
For i = 0 To bannerCount - 1
bannerTracking = BannerCollection(i).bannerTitle
bannerTracking = Replace(bannerTracking, " ", "+")
bannerTracking = Replace(bannerTracking, "&", "And")
bannerTracking = Replace(bannerTracking, "%", "PC")
bannerTracking = Replace(bannerTracking, "!", "")
bannerTracking = Replace(bannerTracking, "£", "")
bannerTracking = Replace(bannerTracking, ",", "")
bannerTracking = Replace(bannerTracking, "'", "")
bannerTracking = Replace(bannerTracking, "#", "")
bannerTracking = Replace(bannerTracking, ".", "")
retinaCSS = retinaCSS & "#sliderTarget .banner-" & i + 1 & "{background-image: url('/assets/static/" & BannerCollection(i).retImagePath & "');}" & vbNewLine
imgCSS = imgCSS & "#sliderTarget .banner-" & i + 1 & "{background-image: url('/assets/static/" & BannerCollection(i).imagePath & "');}" & vbNewLine
If i = 0 Then
firstBannerCode = firstBannerCode & "<div class=" & Chr(34) & "banner banner-" & i + 1 & " staticBanner" & Chr(34) & ">" & vbNewLine
firstBannerCode = firstBannerCode & "" & vbNewLine
firstBannerCode = firstBannerCode & "</div>" & vbNewLine
Else
otherBannersCode = otherBannersCode & "<div class=" & Chr(34) & "banner banner-" & i + 1 & " staticBanner" & Chr(34) & ">" & vbNewLine
otherBannersCode = otherBannersCode & "" & vbNewLine
otherBannersCode = otherBannersCode & "</div>" & vbNewLine
End If
' MsgBox (BannerCollection(i).retImagePath & vbNewLine & BannerCollection(i).imagePath & vbNewLine & BannerCollection(i).bannerTitle & vbNewLine & BannerCollection(i).urlPath)
Next i
CodeString = ""
CodeString = CodeString & "<style type=" & Chr(34) & "text/css" & Chr(34) & ">" & vbNewLine
CodeString = CodeString & "/* Banners */" & vbNewLine
CodeString = CodeString & imgCSS
CodeString = CodeString & "/* Retina Banners */" & vbNewLine
CodeString = CodeString & "#media only screen and (-webkit-min-device-pixel-ratio: 2) {" & vbNewLine
CodeString = CodeString & retinaCSS
CodeString = CodeString & "}" & vbNewLine
CodeString = CodeString & "</style>" & vbNewLine
CodeString = CodeString & "<div id=" & Chr(34) & "sliderTarget" & Chr(34) & " class=" & Chr(34) & "slides" & Chr(34) & ">" & vbNewLine
CodeString = CodeString & firstBannerCode
CodeString = CodeString & "</div>" & vbNewLine
CodeString = CodeString & "<script id=" & Chr(34) & "sliderTemplate" & Chr(34) & " type=" & Chr(34) & "text/template" & Chr(34) & ">" & vbNewLine
CodeString = CodeString & otherBannersCode
CodeString = CodeString & "</script>"
FilePath = Application.DefaultFilePath & "\" & mySheet.Name & "code.txt"
Open FilePath For Output As #2
Print #2, CodeString
Close #2
MsgBox ("code.txt contains:" & CodeString)
MsgBox (Application.DefaultFilePath & "\" & mySheet.Name & "code.txt")
Erase BannerCollection
Next mySheet
End Sub
and here is the Banner type:
Public Type Banner
imagePath As String
retImagePath As String
urlPath As String
bannerTitle As String
End Type
I ended up doing a bit of a code review (oops spent too much time on the Code Review site). I'll post this here in addition to #Jeeped answer in case you get some value from it.
Option Explicit
You should specify Option Explicit at the top of each code module. What this does is tell the VBA compiler to check that every variable that you are trying to use has been declared (i.e. you've got Dim blah as String, Public blah as String or Private blah as String for each blah you're using).
If you attempt to use a variable which hasn't been declared, the compiler will give you a compilation error where the first problem occurs. This helps if you mistype a variable name, otherwise the compiler will think you are talking about something new.
Adding this to the top of your code requires a couple of declarations in your code but nothing major.
Multiple variable declaration on a single line
Don't do it. You have the following line: Dim retinaCSS, imgCSS, firstBannerCode, otherBannersCode, bannerTracking As String which declares 5 variables. The first 4 are declared as variants and the last one is a String. Now your code may work like this but you were probably expecting all 5 to be Strings. Other languages I believe do operate this way but VBA doesn't.
Declare them separately like:
Dim retinaCSS As String
Dim imgCSS As String
Dim firstBannerCode As String
Dim otherBannersCode As String
Dim bannerTracking As String
Don't initialise variables unnecessarily
I see code like:
CodeString = ""
CodeString = CodeString & "<style type=" & Chr(34) & "text/css" & Chr(34) & ">" & vbNewLine
Now the problem with this is that you're assigning the empty string value to CodeString but then you are immediately assigning something else to it in the very next line. The risk is that you might try to use a variable before you have assigned something to it. This isn't a risk for the string type since it implicitly assigned an empty string value when it is created.
You can safely remove the first assignment to it. The danger could come from object references. Say if you have a reference to a worksheet but do not assign a worksheet to the variable before you try to use it. In any case you want to make sure that your variable has the required value before you attempt to use the value it holds.
Use Collection instead of an array
The array code is cumbersome and inflexible. VBA has a simple collection type which allows you to add and remove items to and from it without having to declare a fixed size.
You can also iterate through the contents using a For Each loop.
Here is the code I'm recommending:
Dim BannerCollection As Collection
Set BannerCollection = New Collection
' ...
For Each r In allCells.Rows
Dim thisBanner As Banner
Set thisBanner = New Banner
' ...
BannerCollection.Add thisBanner
Next r
' ...
Dim b As Banner
For Each b In BannerCollection
' do something with the banner.
Next
Now to do this, Banner must be a Class not a Type. I think it makes life a lot easier though.
Split a big method up into single purpose methods.
For instance I extracted a method as follows:
Private Function UrlEncode(ByVal text As String) As String
text = Replace(text, " ", "+")
text = Replace(text, "&", "And")
text = Replace(text, "%", "PC")
text = Replace(text, "!", "")
text = Replace(text, "£", "")
text = Replace(text, ",", "")
text = Replace(text, "'", "")
text = Replace(text, "#", "")
text = Replace(text, ".", "")
UrlEncode = text
End Function
Now this can be referenced like bannerTracking = UrlEncode(b.bannerTitle).
You are setting allCells to a distinct range of cells correctly.
Set allCells = mySheet.Range("a2", mySheet.Cells(lastRow, lastCol))
Then you loop through each row in the allCells range.
For Each r In allCells.Rows
But when you actually go to use r, it is only to use the row number.
thisBanner.imagePath = Cells(r.Row, 2).Value
r.Row is a number between 1 and 1,048,576, nothing more. There is no guarantee that Cells(r.Row, 2).Value refers to something on mySheet; only that whatever worksheet it is coming from it will using whatever worksheet's row number that corresponds to r.row. You need to define some parentage. An With ... End With block within the For ... Next and properly annotated .Range and .Cell references should suffice.
Sub createCode()
Dim myWorkbook As Workbook
Dim mySheet As Worksheet
Dim bannerCount As Integer
Dim BannerCollection() As Banner
Dim r As Range
Dim lastRow, lastCol
Dim allCells As Range
Set myWorkbook = Application.ActiveWorkbook
For Each mySheet In myWorkbook.Worksheets
With mySheet
'declare your vars outside the loop and zero/null then here if necessary.
bannerCount = 0
lastCol = .Range("a2").End(xlToRight).Column
lastRow = .Range("a2").End(xlDown).Row
Set allCells = .Range("a2", .Cells(lastRow, lastCol))
' MsgBox (mySheet.Name)
' MsgBox ("lastRow:" & lastRow & "lastCol:" & lastCol)
ReDim BannerCollection(allCells.Rows.Count)
For Each r In allCells.Rows
Dim thisBanner As Banner
thisBanner.imagePath = ""
thisBanner.retImagePath = ""
thisBanner.bannerTitle = ""
thisBanner.urlPath = ""
bannerCount = bannerCount + 1
' MsgBox (bannerCount)
thisBanner.imagePath = .Cells(r.Row, 2).Value
thisBanner.retImagePath = .Cells(r.Row, 3).Value
thisBanner.bannerTitle = .Cells(r.Row, 4).Value
thisBanner.urlPath = .Cells(r.Row, 5).Value
'MsgBox (.Cells(r.Row, 2).Value)
'MsgBox (.Cells(r.Row, 3).Value)
'MsgBox (.Cells(r.Row, 4).Value)
'MsgBox (.Cells(r.Row, 5).Value)
BannerCollection(bannerCount - 1) = thisBanner
Next r
Dim i As Variant
Dim retinaCSS, imgCSS, firstBannerCode, otherBannersCode, bannerTracking As String
retinaCSS = ""
imgCSS = ""
firstBannerCode = ""
otherBannersCode = ""
bannerTracking = ""
For i = 0 To bannerCount - 1
bannerTracking = BannerCollection(i).bannerTitle
bannerTracking = Replace(bannerTracking, " ", "+")
bannerTracking = Replace(bannerTracking, "&", "And")
bannerTracking = Replace(bannerTracking, "%", "PC")
bannerTracking = Replace(bannerTracking, "!", "")
bannerTracking = Replace(bannerTracking, "£", "")
bannerTracking = Replace(bannerTracking, ",", "")
bannerTracking = Replace(bannerTracking, "'", "")
bannerTracking = Replace(bannerTracking, "#", "")
bannerTracking = Replace(bannerTracking, ".", "")
retinaCSS = retinaCSS & "#sliderTarget .banner-" & i + 1 & "{background-image: url('/assets/static/" & BannerCollection(i).retImagePath & "');}" & vbNewLine
imgCSS = imgCSS & "#sliderTarget .banner-" & i + 1 & "{background-image: url('/assets/static/" & BannerCollection(i).imagePath & "');}" & vbNewLine
If i = 0 Then
firstBannerCode = firstBannerCode & "<div class=" & Chr(34) & "banner banner-" & i + 1 & " staticBanner" & Chr(34) & ">" & vbNewLine
firstBannerCode = firstBannerCode & "" & vbNewLine
firstBannerCode = firstBannerCode & "</div>" & vbNewLine
Else
otherBannersCode = otherBannersCode & "<div class=" & Chr(34) & "banner banner-" & i + 1 & " staticBanner" & Chr(34) & ">" & vbNewLine
otherBannersCode = otherBannersCode & "" & vbNewLine
otherBannersCode = otherBannersCode & "</div>" & vbNewLine
End If
' MsgBox (BannerCollection(i).retImagePath & vbNewLine & BannerCollection(i).imagePath & vbNewLine & BannerCollection(i).bannerTitle & vbNewLine & BannerCollection(i).urlPath)
Next i
CodeString = ""
CodeString = CodeString & "<style type=" & Chr(34) & "text/css" & Chr(34) & ">" & vbNewLine
CodeString = CodeString & "/* Banners */" & vbNewLine
CodeString = CodeString & imgCSS
CodeString = CodeString & "/* Retina Banners */" & vbNewLine
CodeString = CodeString & "#media only screen and (-webkit-min-device-pixel-ratio: 2) {" & vbNewLine
CodeString = CodeString & retinaCSS
CodeString = CodeString & "}" & vbNewLine
CodeString = CodeString & "</style>" & vbNewLine
CodeString = CodeString & "<div id=" & Chr(34) & "sliderTarget" & Chr(34) & " class=" & Chr(34) & "slides" & Chr(34) & ">" & vbNewLine
CodeString = CodeString & firstBannerCode
CodeString = CodeString & "</div>" & vbNewLine
CodeString = CodeString & "<script id=" & Chr(34) & "sliderTemplate" & Chr(34) & " type=" & Chr(34) & "text/template" & Chr(34) & ">" & vbNewLine
CodeString = CodeString & otherBannersCode
CodeString = CodeString & "</script>"
FilePath = Application.DefaultFilePath & "\" & mySheet.Name & "code.txt"
Open FilePath For Output As #2
Print #2, CodeString
Close #2
MsgBox ("code.txt contains:" & CodeString)
MsgBox (Application.DefaultFilePath & "\" & mySheet.Name & "code.txt")
Erase BannerCollection
End With
Next mySheet
End Sub

Variable from VBA to VBScript

I am working on VBA, from which I have to call a vbscript by passing some values.
Here is the code:
''VBA
'Below values are on different cells of Excel file which I am reading
'into a global variable then pass it to vbscript.
'SFilename = VBscript file path
'QClogin = "abc"
'QCpassword = "abc"
'sDomain = "xyz"
'sProject = "xyz123"
'testPathALM = "Subject\xyz - Use it!\xyz_abc"
'QCurl = "http://xxx_yyy_zzz/qcbin/"
Set wshShell = CreateObject("Wscript.Shell")
Set proc = wshShell.exec("wscript " & SFilename & " " & QClogin & _
" " & "" & QCpassword & " " & "" & sDomain & " " & "" & sProject & _
" " & "" & testPathALM & " " & "" & QCurl & "")
''VBscript on some location
Dim strUserName, strPassword, strServer
strUserName = WScript.Arguments(0) '"abc"
Msgbox "strUserName : " & strUserName
strPassword = WScript.Arguments(1) '"abc"
Msgbox "strPassword : " & strPassword
strServer = WScript.Arguments(5) '"http://xxx_yyy_zzz/qcbin/"
Msgbox "strServer : " & strServer
Dim strDomain, strProject, strRootNode
strDomain = WScript.Arguments(2) '"xyz"
Msgbox "strDomain: " & strDomain
strProject = WScript.Arguments(3) '"xyz123"
Msgbox "strProject: " & strProject
strRootNode = WScript.Arguments(4) '"Subject\xyz - Use it!\xyz_abc"
Msgbox "strRootNode: " & strRootNode
Now, when I running the code, it is passing below values properly to vbscript:
QClogin = "abc"
QCpassword = "abc"
sDomain = "xyz"
sProject = "xyz123"
It is having issues with these:
testPathALM = "Subject\xyz - Use it!\xyz_abc"
QCurl = "http://xxx_yyy_zzz/qcbin/"
Now, wierd thing for me is, if I keep a cell empty for "testPathALM" which is having "Subject\xyz - Use it!\xyz_abc" as value, I am getting "QCurl" value properly in vbscript.
But, if I keep value "Subject\xyz - Use it!\xyz_abc" for "testPathALM", then I am getting "-" for strServer which suppose to be "QCurl" value and "Subject\xyz" for "strRootNode" which supposed to be "Subject\xyz - Use it!\xyz_abc".
I am unable to understand what is the issue here.
Thanks a ton in advance.
Safer to quote all of your parameters:
Set wshShell = CreateObject("Wscript.Shell")
Set proc = wshShell.exec("wscript """ & SFilename & """ """ & _
QClogin & """ """ & QCpassword & """ """ & _
sDomain & """ """ & sProject & """ """ & _
testPathALM & """ """ & QCurl & """")
Try a debug.print to make sure it looks as it should...

Send mail using VB Script?

I have the following code to monitor a drive. Now I an getting Echo for each file creation or deletion event.
Is there and way to modify the WScript.Echo to send a mail notification?
strDrive = "c"
arrFolders(0) = strDrive & "\\\\"
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
'Loop throught the array of folders setting up the monitor for Each
i = 0
For Each strFolder In arrFolders
'Create the event sink
strCommand = "Set EventSink" & i & " = WScript.CreateObject" & "(""WbemScripting.SWbemSink"", ""SINK" & i & "_"")"
ExecuteGlobal strCommand
'Setup Notification
strQuery = "SELECT * FROM __InstanceOperationEvent WITHIN 1 " & "WHERE Targetinstance ISA 'CIM_DirectoryContainsFile'" & " and TargetInstance.GroupComponent = " & "'Win32_Directory.Name=""" & strFolder & """'"
strCommand = "objWMIservice.ExecNotificationQueryAsync EventSink" & i & ", strQuery"
ExecuteGlobal strCommand
'Create the OnObjectReady Sub
strCommand = "Sub SINK" & i & "_OnObjectReady(objObject, " & "objAsyncContext)" & VbCrLf & vbTab & "Wscript.Echo objObject.TargetInstance.PartComponent" & VbCrLf & "End Sub"
WScript.Echo strCommand
ExecuteGlobal strCommand
i = i + 1
Next
WScript.Echo "Waiting for events..."
i = 0
While (True)
Wscript.Sleep(1000)
Wend
Instead of Echoing like below:
strCommand = "Sub SINK" & i & "_OnObjectReady(objObject, " & "objAsyncContext)" & VbCrLf & vbTab & "Wscript.Echo objObject.TargetInstance.PartComponent" & VbCrLf & "End Sub"
I want to send a mail like this:
strCommand = "Sub SINK" & i & "_OnObjectReady(objObject, " & "objAsyncContext)" & VbCrLf & vbTab & "
Set outobj = CreateObject("Outlook.Application")
Set mailobj = outobj.CreateItem(0)
With mailobj
.To = toAddress
.Subject = Subject
.HTMLBody = strHTML
.Send
End With
" & VbCrLf & "End Sub"
Is it possible or is there an other way to do this..?
I don't know what server do you use, but on Windows 2003 and 2008 e.g. you can use CDO object to create a email. You might use a smart host to send your email to.
Check this link: http://www.paulsadowski.com/wsh/cdo.htm
Also you can choose any free email component to create a email and use a smtp server to send your email. Or check this side where you can use a component including many examples how to do it: http://www.chilkatsoft.com/email-activex.asp.
** UPDATED **
This Script checks and send a email as you requestted:
strDrive = "d:"
Dim arrFolders(0) : arrFolders(0) = strDrive & "\\\\"
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2")
'Loop throught the array of folders setting up the monitor for Each
i = 0
For Each strFolder In arrFolders
'Create the event sink
WScript.Echo "setup for folder: " & strFolder & vbLf
strCommand = "Set EventSink" & i & " = WScript.CreateObject" & "(""WbemScripting.SWbemSink"", ""SINK" & i & "_"")"
ExecuteGlobal strCommand
'Setup Notification
strQuery = "SELECT * " _
& "FROM __InstanceOperationEvent " _
& "WITHIN 1 " _
& "WHERE Targetinstance ISA 'CIM_DirectoryContainsFile'" _
& " AND TargetInstance.GroupComponent = " & "'Win32_Directory.Name=""" & strFolder & """'"
strCommand = "objWMIservice.ExecNotificationQueryAsync EventSink" & i & ", strQuery"
ExecuteGlobal strCommand
'Create the OnObjectReady Sub
strCommand = "Sub SINK" & i & "_OnObjectReady(objObject, " & "objAsyncContext)" & vbLf _
& " Wscript.Echo objObject.TargetInstance.PartComponent" & vbLf _
& " SendMail(objObject.TargetInstance.PartComponent)" & vbLf _
& "End Sub"
'WScript.Echo strCommand
ExecuteGlobal strCommand
i = i + 1
Next
WScript.Echo "Waiting for events..."
i = 0
While (True)
Wscript.Sleep(1000)
Wend
Function SendMail(vBody)
Dim oMail : Set oMail = CreateObject("CDO.Message")
'Name or IP of Remote SMTP Server
oMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
oMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "your.smtp.server"
oMail.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
oMail.Configuration.Fields.Update
oMail.Subject = "Email Watch Info Message"
oMail.From = "alert#yourdomain.net"
oMail.To = "target#yourdomain.net"
oMail.TextBody = vBody
oMail.Send
End Function
Correct the settings in the send mail function and your are fine.
In theory, the VBSendMail DLL should be able to do what you want.