ms access form fields into a string vba - vba

I have a form (say) Data, which contains text boxes A,B,C.
I wish to write an email based on the form data. In my email body, I want the following format:
A is : (actual value of A in text box in the form) (a newline)
B is : ((actual value of B in text box in the form) ( a newline)
C is : ((actual value of C in text box in the form).
I know I can access values by Forms!Data!A_value (assuming I named the box as A_value). I am not able to combine them into a string and add a newline too.
I have tried the following:
Dim body as String
body = "A is : & Forms!Data!A_value &" & "B is : & Forms!Data!B_value &" & "C is : & Forms!Data!C_value &"
It is because I read an & results to a new line somewhere.
However, when i do that, the whole thing is concatenated as written in the code and no values are obtained from the form field.
Please suggest options:
Thanks in advance

You probably want something like:
Dim body as String
body = "A is : " & Forms!Data!A_value & vbNewLine & _
"B is : " & Forms!Data!B_value & vbNewLine & _
"C is : " & Forms!Data!C_value
Note: the fact that I wrote that code on 3 lines, using line continuation characters, is nothing to do with the insertion of the new line characters in the output. It could have also been written as
body = "A is : " & Forms!Data!A_value & vbNewLine & "B is : " & Forms!Data!B_value & vbNewLine & "C is : " & Forms!Data!C_value
but I find that harder to read.

The ampersand is used to concatenate text "hello " & "there".
If you quote the ampersand it does nothing, just reproduces the ampersand "bits & bobs".
You can use the character vbCrLf (carriage return/linefeed) to add (concatenate) a linebreak, "time for " & vbCrLf & "a break".

Another trick you can use is to create a single string template with placeholders for the values, then use Replace statements to fill them in, like:
body = "A is: {A} & B is: {B} & C is: {C}"
body = Replace(body, "{A}", Forms!Data!A_value)
body = Replace(body, "{B}", Forms!Data!B_value)
body = Replace(body, "{C}", Forms!Data!C_value)
And break out to multiple lines, like:
body = "A is: {A}{CR}B is: {B}{CR}C is: {C}{CR}"
body = Replace(body, "{A}", Forms!Data!A_value)
body = Replace(body, "{B}", Forms!Data!B_value)
body = Replace(body, "{C}", Forms!Data!C_value)
body = Replace(body, "{CR}", vbCrLf)

Related

MS-ACCESS VBA Multiple Search Criteria

In my GUI, I have several ways to filter a database. Due to my lack of knowledge, my VBA programming has exploded with nested IF statements. I am getting better at using ACCESS now, and would like to find a more succinct way to perform multiple filters. My form is continuous.
Is there a simple way to do the following task (I made a toy model example):
I have a combo box SITE where I can filter by work sites A, B, C. After filtering by SITE, I have three check boxes where the user can then filter by item number 1-10, 11-20, 21-30, depending on what the user selects.
Is there a way to append multiple filters (or filter filtered data)? For example, filter by SITE A, then filter A by item number 1-10?
Currently, for EACH check box, I then have an IF statement for each site. Which I then use Form.Filter = . . . And . . . and Form.FilterOn = True.
Can I utilize SQL on the property sheet to filter as opposed to using the VBA?
What I do for these types of filters is to construct a SQL statement whenever one of the filter controls is changed. All of them reference the same subroutine to save on code duplication.
What you do with this SQL statement depends on what you're trying to do. Access is pretty versatile with it; use it as a RecordSource, straight execute it, and use the results for something else, even just printing it to a label.
To try to modularize the process, here's an example of how I do it:
Dim str As String
str = "SELECT * FROM " & Me.cListBoxRowSource
Me.Field1.SetFocus
If Me.Field1.Text <> "" Then
str = AppendNextFilter(str)
str = str & " SQLField1 LIKE '*" & Me.Field1.Text & "*'"
End If
Me.Field2.SetFocus
If Me.Field2.Text <> "" Then
str = AppendNextFilter(str)
str = str & " SQLField2 LIKE '*" & Me.Field2.Text & "*'"
End If
Me.Field3.SetFocus
If Me.Field3.Text <> "" Then
str = AppendNextFilter(str)
str = str & " SQLField3 LIKE '*" & Me.Field3.Text & "*'"
End If
Me.cListBox.RowSource = str
Variables edited to protect the guilty.
My AppendNextFilter method just checks to see if WHERE exists in the SQL statement already. If it does, append AND. Otherwise, append WHERE.
Making quite a few assumptions (since you left out a lot of info in your question), you can do something like this:
Dim sSql as String
sSql = "Select * from MyTable"
Set W = Me.cboSite.Value
sSql = sSql & " WHERE MySite = " & W & ""
Set X = Me.Chk1
Set Y = Me.Chk2
Set Z = Me.Chk3
If X = True Then
sSql = sSql & " And MyItem between 1 and 10"
If Y = True Then
sSql = sSql & " And MyItem between 11 and 20"
If Z = True Then
sSql = sSql & " And MyItem between 21 and 30"
End If
DoCmd.ExecuteSQL sSql
Again, this is entirely "air code", unchecked and probably needing some edits as I haven't touched Access in some time and my VBA is likely rusty. But it should put you on the right track.
The way i use combobox filtering in access is first I design a Query that contains all the data to be filtered. The Query must contain fields to be used for filtering. QueryAllData => "SELECT Table.Site, Table.ItemNumber, FROM Table;" Then make a copy of the query and Name it QueryFilteredData and Design the report to display the data using QueryFilteredData.
Then create a form with a Site ComboBox, ItemNumber Combo Box, and Sub Report Object and Assign SourceObject the Report Name. Use Value List as the combo box Row Source type and type in the values for Row Source to get it working. To get the report to update I always unassign the SubReport.SourceOject update the QueryFilteredData and then Reassign the SubReport.SourceObject
Combobox_Site_AfterUpdate()
Combobox_ItemNumber_AfterUpdate
End Sub
Combobox_ItemNumber_AfterUpdate()
Select Case Combobox_ItemNumber.value
Case Is = "1-10"
Store_Filters 1,10
Case Is = "11-20"
Store_Filters 11,20
Case Is = "21-30"
Store_Filters 21,30
Case Else
Store_Filters 1,10
End Sub
Private Sub Store_Filters(Lowest as integer, Highest as integer)
Dim SRpt_Recset As Object
Dim Temp_Query As Variant
Dim Temp_SourceObject as Variant
Temp_SourceObject = SubReport.SourceObject
SubReport.SourceObject =""
Set SRpt_Recset = CurrentDb.QueryDefs("QueryFilteredData")
Filter_Combo_Box1 = " ((QueryAllData.[Sites])= " & Chr(39) & Combo_Box1 & Chr(39) & ") "
Filter_Combo_Box2 = (Filter_Combo_Box1 AND (QueryAllData.ItemNumber <= Highest)) OR (Filter_Combo_Box1 AND (QueryAllData.ItemNumber >= Lowest));"
Temp_Query = " SELECT " & Query_Name & ".* " & _
"FROM " & Query_Name & " " & _
"WHERE (" & Filter_Combo_Box2 & ") ORDER BY [Field_Name_For_Sorting];"
SRpt_Recset.SQL = Temp_Query
'Debug.print Temp_Query
SubReport.SourceObject = Temp_SourceObject
End Sub
After the Combo Boxes Work if the Data is going to Change like Site and Item Number then you might want to change the Row Source of the combo boxes to Use a Query that uses Select Distinct Site From QueryAllData. I don't know if Filter_Combo_Box2 step so it may need some correction. Hope this helps.

Changing the Caption of an AD-User in the AD-Management Console with VB.NET

I have a working code for creating a new AD user along with its distinct properties. The only thing I can't get to work is:
The users entry in the AD-Management window (caption) is the same as the CN. But I need the entry to be the same as the DisplayName attribute.
After manually changing the Name of the user entry, I found out, that the attribute 'name' changed as well.
(See screenshot)
Screenshot-1
So I tried using the attribute 'name' in my code as well.
But when I use the attribute during the first CommitChanges() (effectively creating the entitiy) it doesn't set to the Value I want it to be. Instead it stays the same as the login-name (SAMAccountName) of the user.
If I try to set the Value of the attribute during the later CommitChanges() it throws the following exception
"00002016: SvcErr: DSID-031A12D2, problem 5003 (WILL_NOT_PERFORM),
data 0" & vbLf
Do you have any Ideas what I can do to change the attribute 'name' of an AD-Object?
(BTW: My code contains some german words. Vorname = Given Name / Nachname = Surname etc. in case you're wondering)
Dim o As DirectoryEntry
Dim c As DirectoryEntries
Dim OUPath = ("LDAP://OU=" & OU & ",OU=Benutzer,DC=int,DC=xxxx,DC=xx")
o = New DirectoryEntry(OUPath, "xxxx", "xxxx", System.DirectoryServices.AuthenticationTypes.Secure)
c = o.Children()
o = c.Add("CN=" & UsernameV, "user")
o.Properties("SAMAccountName").Value = UsernameV
o.Properties("sn").Add(NachnameV)
o.Properties("givenName").Add(VornameV)
o.Properties("displayName").Add("" & NachnameV & ", " & VornameV & "")
o.Properties("name").Add("" & NachnameV & ", " & VornameV & "")
o.Properties("userPrincipalName").Add(UsernameV & "#xxxx")
o.CommitChanges()
o.Invoke("setPassword", New Object() {PWV})
o.CommitChanges()
'newUser.Properties("userAccountControl").Add(512)
'newUser.CommitChanges()
o.Properties("description").Add(UsernameV)
o.Properties("scriptPath").Add("logon.bat")
o.Properties("homeDrive").Add("H:")
o.Properties("homeDirectory").Add("\\datsrv-01\homes\" & UsernameV & "$")
o.Properties("company").Add(ComboBox_so.SelectedItem.ToString)
o.Properties("department").Add(ComboBox_abt.SelectedItem.ToString)
o.Properties("telephoneNumber").Add(TelV)
o.Properties("userAccountControl").Value = 65536
o.CommitChanges()
o.NativeObject.AccountDisabled = False
o.CommitChanges()
MsgBox("Erstellen erfolgreich")

VBA - Find all numbered lines in VBE Modules via pattern search

Task:
My goal is to find all numbered lines in procedures of my Code Modules.
The CodeModule.Find method can be used to check for search terms (target parameter).
Syntax:
object.Find(target, startline, startcol, endline, endcol [, wholeword] [, matchcase] [, patternsearch])
The referring help site https://msdn.microsoft.com/en-us/library/aa443952(v=vs.60).aspx states:
parameter patternsearch: Optional. A Boolean value specifying whether or not the target string is a regular expression pattern.
If True, the target string is a regular expression pattern. False is the default.
As explained above the find method allows a regex pattern search, which I would like to use in order to identify numbered lines in a precise way:
digits followed by a tab. The example below therefore defines a search string s and sets the last parameter PatternSearch in the .Find method to True.
Problem
AFAIK a valid regex definition could be
s = "[0-9]{1,4}[ \t]"
but that doesn't show anything, not even an error.
In order to show at least any results, I defined the search term
s = "[0-9]*[ \t]*)"
in the calling example procedure ListNumberedLines showing erratic results.
Question
Is there any possibility to use a valid regex patternsearch in the CodeModule.Find method?
Example code
Option Explicit
' ==============
' Example Search
' ==============
Sub ListNumberedLines()
' Declare search pattern string s
Dim S As String
10 S = "[0-9]*[ \t]*)"
20 Debug.Print "Search Term: " & S
30 Call findWordInModules(S)
End Sub
Public Sub findWordInModules(ByVal sSearchTerm As String)
' Purpose: find modules ('components') with lines containing a search term
' Method: .CodeModule.Find with last parameter patternsearch set to True
' Based on https://www.devhut.net/2016/02/24/vba-find-term-in-vba-modulescode/
' VBComponent requires reference to Microsoft Visual Basic for Applications Extensibility
' or keep it as is and use Late Binding instead
' Declare module variable oComponent
Dim oComponent As Object 'VBComponent
For Each oComponent In Application.VBE.ActiveVBProject.VBComponents
If oComponent.CodeModule.Find(sSearchTerm, 1, 1, -1, -1, False, False, True) = True Then
Debug.Print "Module: " & oComponent.Name 'Name of the current module in which the term was found (at least once)
'Need to execute a recursive listing of where it is found in the module since it could be found more than once
Call listLinesinModuleWhereFound(oComponent, sSearchTerm)
End If
Next oComponent
End Sub
Sub listLinesinModuleWhereFound(ByVal oComponent As Object, ByVal sSearchTerm As String)
' Purpose: list module lines containing a search term
' Method: .CodeModule.Find with last parameter patternsearch set to True
Dim lTotalNoLines As Long 'total number of lines within the module being examined
Dim lLineNo As Long 'will return the line no where the term is found
lLineNo = 1
With oComponent ' Module
lTotalNoLines = .CodeModule.CountOfLines
Do While .CodeModule.Find(sSearchTerm, lLineNo, 1, -1, -1, False, False, True) = True
Debug.Print vbTab & "Zl. " & lLineNo & "|" & _
Trim(.CodeModule.Lines(lLineNo, 1)) 'Remove any padding spaces
lLineNo = lLineNo + 1 'Restart the search at the next line looking for the next occurence
Loop
End With
End Sub
As #MatsMug says, parsing VBA with Regex is hard impossible, but line-numbers are a simpler case, and should be findable with regex alone.
Fortunately, line numbers can only appear within a procedure body (including before the End Sub/Function/Property statement), so we know they'll never be the first line of your code.
Unfortunately, you can prefix a line-label with 0 or more line continuations:
Sub Foo()
_
_
10 Beep
End Sub
Furthermore, a line number isn't always followed by a space - it can be followed by an instruction separator, giving the line-number the appearance of a line-label:
Sub foo()
10: Beep
End Sub
And if you're code is evil, you might encounter a negative line-number (entered by using hex notation - which VBE dutifully pretty prints back to the code-pane with a leading space and a negative number):
Sub foo()
10 Beep
-1 Beep
End Sub
And we also need to be able to identify numbers that appear on a continued line, that aren't line-numbers:
Sub foo()
Debug.Print _
5 & "is not a line-number"
End Sub
So, here's some evil line-numbering, with a mix of all of those edge-cases:
Option Explicit
Sub foo()
5: Beep
_
_
_
10 Beep
20 _
'Debug.Print _
30
50: Beep
40 Beep
_
-1 _
Beep 'The "-1" line number is achieved by entering "&HFFFFFFFF"
Debug.Print _
2 & "is not a line-number"
60 End Sub
And here's some regex that identifies the line-numbers:
(?<! _)\n( _\n)* ?(?<line_number>(?:\-)?\d+)[: ]
And here's a syntax highlight from regex101:
For the longest time, Rubberduck was struggling with properly/formally parsing line numbers - our work-around was to remove them (replacing them with spaces) before feeding the code module contents to our parser.
Recently we've managed to formally define line numbers:
// lineNumberLabel should actually be "statement-label" according to MS VBAL but they only allow lineNumberLabels:
// A <statement-label> that occurs as the first element of a <list-or-label> element has the effect
// as if the <statement-label> was replaced with a <goto-statement> containing the same
// <statement-label>. This <goto-statement> takes the place of <line-number-label> in
// <statement-list>.
listOrLabel :
lineNumberLabel (whiteSpace? COLON whiteSpace? sameLineStatement?)*
| (COLON whiteSpace?)? sameLineStatement (whiteSpace? COLON whiteSpace? sameLineStatement?)*
;
sameLineStatement : blockStmt;
And lineNumberLabel is defined as:
//Statement labels can only appear at the start of a line.
statementLabelDefinition : {_input.La(-1) == NEWLINE}? (combinedLabels | identifierStatementLabel | standaloneLineNumberLabel);
identifierStatementLabel : unrestrictedIdentifier whiteSpace? COLON;
standaloneLineNumberLabel :
lineNumberLabel whiteSpace? COLON
| lineNumberLabel;
combinedLabels : lineNumberLabel whiteSpace identifierStatementLabel;
lineNumberLabel : numberLiteral;
(full Antlr4 grammar here)
Notice the predicate {_input.La(-1) == NEWLINE}?, which force the parser rule to only match a statementLabelDefinition at the start of a line - a logical line of code.
You see VBA code has physical code lines, like what you're getting from the CodeModule's contents. But VBA code also has a concept of logical code lines, and it turns out that is all the parser cares about.
This would trip any typical regex:
Sub DoSomething()
Debug.Print _
42
End Sub
There's only 1 logical line of code between the signature and the End Sub token, but a simple Find will happily consider that 42 as a "line number" ...which it isn't - it's the argument passed to Debug.Print, in the same instruction, on the same logical code line, but on the next physical code line.
And you can't be dealing with logical code lines without first pre-processing your input, to take line continuation tokens into account.
And in order to do that, you need to actually parse the instructions you're seeing - at least know where they start and where they end... and that's no small undertaking! see ThunderFrame's answer
The VBIDE API is extremely limited, and won't be helpful for that.
TL;DR: You can't parse VBA code with regular expressions alone. So, nope. Sorry! you need a much more complex regex pattern than that - see ThunderFrame's answer.
Conclusion regarding CodeModule.Find via search pattern
Firstly, CodeModule.Find doesn't help via search pattern and its possible use is intransparent.
I agree that the VBIDE API is extremely limited and that there exist excellent professional tools which I highly recommand for any programmer :-)
Consequence: Work around via XML
Secondly I prefer household remedies if possible, so I tried to find an alternative solution using only the helpful parts of VBIDE.
Method
That is why I tried a simple xml conversation of the CodeModule.Lines allowing a flexible search within logical lines.
Instead of using regular expressions in requesting the xml data, I demonstrate a method to find leading numbers via a well defined XPath search (loop thru node list),
thus resolving most problems shown by #ThunderFrame. The search string in function showErls is defined as "line[substring(translate(.,'0123456789','¹¹¹¹¹¹¹¹¹¹'),1,1)="¹"]"
Furthermore function 'lineNumber' returns the logical line number within the module.
Note: To keep it simple, the search is restrained to one module only (user defined constant MYMODULE) and code avoids any regex.
Work around code - main sub
Option Explicit
' ==========================================
' User defined name of module to be analyzed
' ==========================================
Const MYMODULE = "modThunderFrame" ' << change to existing module name or userform
' Declare xml file as object
Dim xCMods As Object ' Late Binding; instead of Early Bd: Dim xCMods As MSXML2.DOMDocument6
Public Sub TestLineNumbers()
' =================
' A. Load/refresh code into xml
' =================
' set xml into memory - contains code module(s) lines
Set xCMods = CreateObject("MSXML2.Domdocument.6.0") ' L.Bd.; instead of E.Bd: Set xCMods = New MSXML2.DOMDocument60
xCMods.async = False
xCMods.validateOnParse = False
' read in user defined code module and load xml, if failed show error message
refreshCM MYMODULE
If xCMods Is Nothing Then Exit Sub
' ======================
' B. search line numbers
' ======================
showERLs
' =============================
' C. Save xml if needed
' =============================
' xCMods.Save ThisWorkbook.Path & "\VBE(" & MYMODULE & ").xml"
' MsgBox "Successfully exported Excel data to " & ThisWorkbook.Path & "\VBE(" & MYMODULE & ").XML!", _
' vbInformation, "Module " & MYMODULE & " to xml"
' =================
' D. terminate xml
' =================
Set xCMods = Nothing
End Sub
Sub procedures
Private Sub showERLs()
' Purpose: [B.] declare XPath search string and define special translate character
Dim s As String
Dim S1 As String: S1 = Chr(185) ' superior number 1 (hex B9) replaces any digit
' declare node and node list
Dim line As Object
Dim lines As Object
' define XPath search string for first digit in line (usual case)
s = "line[substring(translate(.,'0123456789','" & String(10, S1) & "'),1,1)=""" & _
S1 & _
"""]"
' start debugging
Debug.Print "**search string=""" & s & """" & vbNewLine & String(50, "-")
Debug.Print "Line #|Line Content" & vbNewLine & String(50, "-"); ""
' set node list
Set lines = xCMods.DocumentElement.SelectNodes(s)
' -------------------
' loop thru node list
' -------------------
For Each line In lines
Debug.Print Format(lineNumber(line), "00000") & "|" & line.Text ' return logical line number plus line content
Next line
End Sub
Private Sub refreshCM(sModName As String)
' Purpose: [A.] load xml string via LoadXML method
Dim sErrTxt As String
Dim line As Object
Dim lines As Object
Dim xpe As Object
Dim s As String ' xpath expression
Dim pos As Integer ' position of line number prefix
' ======================================
' 1. Read code module lines and load xml
' ======================================
If Not xCMods.LoadXML(readCM(sModName)) Then
' set ParseError object
Set xpe = xCMods.parseError
With xpe
sErrTxt = sErrTxt & vbNewLine & String(20, "-") & vbNewLine & _
"Loading Error No " & .ErrorCode & " of xml file " & vbCrLf & _
Replace(" " & Replace(.URL, "file:///", "") & " ", " ", "[No file found]") & vbCrLf & vbCrLf & _
xpe.reason & vbCrLf & _
"Source Text: " & .srcText & vbCrLf & _
"char?: " & """" & Mid(.srcText, .linepos, 1) & """" & vbCrLf & vbCrLf & _
"Line no: " & .line & vbCrLf & _
"Line pos: " & .linepos & vbCrLf & _
"File pos.: " & .filepos & vbCrLf & vbCrLf
End With
MsgBox sErrTxt, vbExclamation, "XML Loading Error"
Set xCMods = Nothing
Exit Sub
End If
' 2. resolve hex input problem of negative line numbers with leading space (thx #Thunderframe)
s = "line"
Set lines = xCMods.DocumentElement.SelectNodes(s)
' loop thru all logical lines
For Each line In lines
pos = ErlPosInLine(line.Text)
If pos <= Len(line.Text) Then
' to do: add attribute to line node, if wanted
' correct line content
line.Text = Mid(line.Text, pos)
End If
Next
End Sub
Private Function lineNumber(node As Object) As Long
' Purpose: [B.] return logical line number within code module lines
' Param.: IXMLDomNode
' Method: XPath via preceding-sibling count plus one
Dim tag As String: tag = "line"
lineNumber = node.SelectNodes("preceding-sibling::" & tag).Length + 1
End Function
Private Function readCM(Optional modName = "*") As String
' Purpose: return code module line string (VBIDE) of a user defined module to be read into xml
' Call: called from [A.] refreshCM
' xCMods.LoadXML(readCM(sModName))
' Declare variable
Dim s As String
Dim md As CodeModule
If modName = "*" Then Exit Function
On Error GoTo OOPS
' get code module lines into string
Set md = Application.VBE.ActiveVBProject.VBComponents(modName).CodeModule ' MSAccess: Modules("modVBELines")
' change to xml tags
s = getTags(md.lines(1, md.CountOfLines))
' return
readCM = s
OOPS:
End Function
Private Function getTags(ByVal s As String, Optional mode = False) As String
' Purpose: prepares xml string to be loaded
' define constant
Const HEAD = "<?xml version=""1.0"" encoding=""utf-8""?>" & vbCrLf & "<cm>" & vbCrLf
' 1. change tag characters
s = Replace(Replace(s, "<", "<"), ">", ">")
' 2. change special characters (ampersand)
s = Replace(s, "&", "&")
' 3. change "_" points
s = Replace(s, "_" & vbCrLf, Chr(133) & vbLf)
' 4. define logical line entities
If Right(s, 2) = vbCrLf Then s = Left(s, Len(s) - 2)
s = HEAD & " <line>" & Replace(s, vbCrLf, "</line>" & vbCrLf & " <line>") & "</line>" & vbCrLf & "</cm>"
' debug xml tags if second function parameter is true (mode = True)
If mode Then Debug.Print s
' return
getTags = s
End Function
Sub testErlPosInLine()
' Purpose: Test Thunderframe's problem with ERL prefixes (underscores, " ",..) and hex inputs
Dim s As String
s = " _" & vbLf & " -1 xx"
MsgBox "|" & Mid(s, ErlPosInLine(s)) & "|" & vbNewLine & _
"prefix = |" & Mid(s, 1, ErlPosInLine(s) - 1) & "|"
End Sub
Private Function ErlPosInLine(ByVal s As String) As Integer
' Purpose: remove prefix (underscore, tab, " ",.. ) from numbered line
' cf: http://stackoverflow.com/questions/42716936/vba-to-remove-numbers-from-start-of-string-cell
Dim i As Long
For i = 1 To Len(s) ' loop each char
Select Case Mid$(s, i, 1) ' examine current char
Case " " ' permitted chars
Case "_"
Case vbLf, Chr(133), Chr(34)
Case "0" To "9": Exit For ' cut off point
Case Else: Exit For ' i is the cut off point
End Select
Next
If Mid$(s, i, 1) = "-" And Len(s) > 1 Then
If IsNumeric(Mid$(s, i + 1, 1)) Then i = i + 1
End If
' return
ErlPosInLine = i
' debug.print Mid$(s, i) '//strip lead
End Function

recordset.GetString in Access VBA Query returns an extra character after the result

I have a query that I execute through VBA in Access 2010. The result of the query should be AFR, but it returns AFR with an extra line below it. I have added the "'" character to make the extra line visible.
TempHold = rs.GetString
Debug.Print "'" & TempHold & "'"
Returns this:
'AFR
'
But should return this:
'AFR'
I have tried using the below code, but none of the If statements evaluate as True. The code should check for a " ", a vbNewLine, or vbCrLf character but none evaluate as true. Does anyone know of any additional characters that would result in a new line?
If Right(TempHold, 1) = " " Then
TempHold = Left(TempHold, Len(TempHold) - 1)
ElseIf Right(TempHold, 2) = vbNewLine Or Right(TempHold, 2) = vbCrLf Then
TempHold = Left(TempHold, Len(TempHold) - 2)
End If
Use:
Asc(Right(TempHold, 1))
to get the Ascii character code.
Once you've found the character code (which, as you wrote in your comment, was 13), you can use your code to remove it:
If Right(TempHold, 1) = Chr(13) Then
TempHold = Left(TempHold, Len(TempHold) - 1)
End If
In this case, you can also use vbCr, which is the same as Chr(13).
The best way to get rid of the carriage return in my opinion is to stop it being created in the first place. This method is a lot tidier than having to remove the last character.
In the .GetString method there is a parameter for RowDelimiter which by default is set to be a carriage return. However you can change this to be whatever you want including a zero length string as follows:
rs.GetString(, , , "")
If you run your debug again with this code:
rs.GetString(, , , "")
Debug.Print "'" & TempHold & "'"
You will get this result:
'AFR'
Remember if you want something different to be placed between rows then just change the zero length string to whatever you need.

What does "& _" mean in VB?

I'm copying some query statements from a legacy VB app to a C# app. I am not familiar with VB, although looking at it makes me want a VB (Victoria Bitter). I have come across queries constructed like this:
*SELECT dp_duckbill_accounts.platypus_no AS duckbill, t_accounts.name AS Name " & _
"FROM t_accounts INNER JOIN dp_duckbill_accounts ON t_accounts.account_no = dp_duckbill_accounts.account_no " & _
"ORDER BY dp_duckbill_accounts.platypus_no*
The "& _" give me pause. If it was just "&" I would think it corresponds to "+" in C# to concatenate strings. But what in the world is the point of the underscore? Note the ampersand and the underscore are separated by a space.
The underscore is the line continuation character. It allows the concatenation to include a different line. Like so:
x = "Hello " & "World"
x = "Hello " & _
"World"
'this won't compile (pre vb.net 2010, anyway)
x = "Hello " &
"World"
Line Continuation on MSDN
How to: Break and Combine Statements in Code (Visual Basic)
_ means continue the statement on the following line.
so ... & _ means continue concatenating the string on the following line.
text = "One line string"
text = "Two line " & _
"string"
That is just a line continuation character that lets you continue to the next line.
& - is used for string concatenation in same line.
example - sConcatenatedString = "First" & "Second"
& _ - is used For string concatenation in different lines.
example - sConcatenatedString = "First" &_
"Second"