Replace a variable Environ in vba - vba

Please, I had already create à variable Environ("MYXLSPATH")
Dim vr As String
vr = "SETX MYXLSPATH """ & ThisWorkbook.FullName & """"
Call Shell(vr)
And now, I want to replace the content of this variable by: "NAME"
Dim vr As String
Environ.RemoveItem ("MYXLSPATH")
vr = "SETX MYXLSPATH "" NAME """
Call Shell(vr)
But, It doesn't work, can you help me please ?

The second set of code should be:
Dim vr As String
vr = "SETX MYXLSPATH ""NAME"""
Call Shell(vr)
I've made two changes:
Removed the Environ.RemoveItem line. It's not needed and seems to be problematic.
Removed the space either side of NAME. So this means the environment variable is set to NAME and not  NAME 
End result:

Instead of running a CMD.EXE command you can call in to the Shell object to get to the environment variables directly. To replace, just call the set again.
Here are procedures to set and get:
Option Explicit
'#Description("Set environment variable value. Defaults to user space. System space requires elevated process to modify.")
Public Sub EnvironSetItem(ByVal environVariable As String, ByVal newValue As String, Optional ByVal strType As String = "User")
With CreateObject("WScript.Shell").Environment(strType)
.Item(environVariable) = newValue
End With
End Sub
'#Description("Get environment variable value. Defaults to userspace.")
Public Function EnvironGetItem(ByVal environVariable As String, Optional ByVal strType As String = "User") As String
With CreateObject("WScript.Shell").Environment(strType)
EnvironGetItem = .Item(environVariable)
End With
End Function

Related

VBA Function Passing Multi Variables back to Sub

I have a large string over 500 char which is called strEssay. I want to use a function(since I will need to look for several patterns) to return two values if (for example the name) Frank is found or not.
This is the function I'm trying to use:
Function NameFinder(strEssay as String, strName as String)
Dim varNameCounter as Variant
Dim strNameFinderResult as String
varNameCounter = 0
strNameFinderResult = ""
If strEssay like "*" & strName & "*" Then
strNameFinderResult = strName
varNameFinderCounter = 1
Else
strNameFinderResult = ""
varNameFinderCounter = .001
EndIf
End Function
I want to be able to return back to my subroutine both 'strNameFinderResult' and 'varNameFinderCounter'.
Is there any way that I can return both values?
If I can't return both simultaneously can I return one through the function and the other through a textbox or something? What would calling the function look like in the subroutine and/or how would I need to change my function?
NameFinder() function, returning array of 3 elements. It is called and returned by TestMe(), writing the following to the console:
Function NameFinder(essay As String, name As String)
Dim nameFinderResult As String
Dim namefinderCounter As String
nameFinderResult = "" & essay & name
namefinderCounter = 0.001 + 12
NameFinder = Array(nameFinderResult, namefinderCounter, "something else")
End Function
Public Sub TestMe()
Dim myArray As Variant
myArray = NameFinder("foo", "bar")
Dim i As Long
For i = LBound(myArray) To UBound(myArray)
Debug.Print myArray(i)
Next i
End Sub
As a general rule, you have to give the routine a type like this:
Function NameFinder(strEssay as String, strName as String) as string
But, that returns only ONE value.
So, a function (as opposed to a sub) returns one value (as a general rule).
However, you CAN also return parameters that you pass. I mean, in above, you can't make TWO assignments to one variable, can you?
So, you can use a Sub like this:
Sub NameFinder(strEssay as String, strName as String, _
strNameFinderResult as string, _
varNameFinderCounter as double)
If strEssay like "*" & strName & "*" Then
strNameFinderResult = strName
varNameFinderCounter = 1
Else
strNameFinderResult = ""
varNameFinderCounter = .001
EndIf
So in code, you now can go:
dim strMyResult as string
dim strFinderCount as Double
Call NameFinder("MyEassy", "Joe Blow", strMyResult, strFinderCount)
So, you can return values with the parameters.
Now, I suppose it possible for some strange reason, that you want to use a function to return two values with a single assignment?
What you would do is this in your code module.
Define a custom type, and use that.
eg this:
Option Compare Database
Option Explicit
Type SearchResult
strName As String
FindCount As Double
End Type
Function NameFinder(strEssay As String, strName As String) As SearchResult
NameFinder.FindCount = 0
NameFinder.strName = ""
If strEssay Like "*" & strName & "*" Then
NameFinder.strName = strName
NameFinder.FindCount = 1
Else
NameFinder.strName = ""
NameFinder.FindCount = 0.001
End If
End Function
So, now to use in code? You can go like this:
dim MyResults as SearchResult
MyResults = NameFinder("My eassy", "Joe Blow")
debug.print "Name found result = " & MyResults.strName
debug.print "Count of find = " & MyResult.FindCount
The VERY nice thing about above is you get full intel-sense in your code editor.
eg this:
So by building a custom data type, you can use "one" assignment for the return type. And you get nice type checking and inteli-sense in the VBA code editor.
And you can even do this:
But, to get both variables, then you would in theory wind up calling the function two times. So, you can actually use the function without declarer of variables like this:
Debug.Print NameFinder("MyEassy", "Joe blow").strName
Debug.Print NameFinder("MyEassy", "Joe blow").FindCount
So, I don't recommend the above, but in the case in which you ONLY want one of the return values, then the raw expression (function) like above would be a use case (and no need to even declare a return variable).
But, without a doubt, define a custom type in code as per above. The reason is now you get a really nice VBA editor type-checking, inteli-sense, and also that you only have to declare "one" variable that holds two values.
In fact, the results are very much like JavaScript, or even c# in which you declare a "class" type. So with a custom "type" you are declaring a data type of your own. And the beauty of this is if you need say 3 values, then once again you create a type with 3 "inside" values.
The you ONLY have to declare that one variable as the custom type.
With this you get:
Very valuable compile time syntax and data type checking of the var types you are using.
You get GREAT VBA inteli-sense while coding - which means less coding mistakes.
And you type far less typing in the VBA editor as it will pop-up the choices for you as you write code. And you can't type or choose the wrong sub - type, as the compiler will catch this.

Variable not defined in VBA script

I'm getting into the good habit of using Option Explicit at the top of all my MS Access VBA scripts. However, for this function it's giving me a Variable not defined error highlighting the line that begins with Set objSysInfo =
Public Function GetUser(Optional whatpart = "username")
Dim returnthis As String
If whatpart = "username" Then GetUser = Environ("USERNAME"): Exit Function
Set objSysInfo = CreateObject("ADSystemInfo")
Set objUser = GetObject("LDAP://" & objSysInfo.UserName)
Select Case whatpart
Case "fullname": returnthis = objUser.FullName
Case "firstname", "givenname": returnthis = objUser.givenName
Case "lastname": returnthis = objUser.LastName
Case Else: returnthis = Environ("USERNAME")
End Select
GetUser = returnthis
End Function
Might I be missing a specific reference?
Option Explicit forces you to declare all variables.
This means you must declare all variables you're using, otherwise your code won't compile - as you've noticed.
You declared local variable returnthis as a String, using the Dim keyword:
Dim returnthis As String
That's how you declare a local (procedure scope) variable.
You could declare variables at module scope using the Private keyword (Dim works as well, but better keep Dim for locals)
Option Explicit
Private foo As Object
How you scope variables depends on where you're using them - if a variable is only ever used in one single procedure, then it's better to scope it to that procedure and declare it as a local variable.
In this case you have objSysInfo and objUser that aren't declared anywhere.
Dim objSysInfo As Object
Dim objUser As Object
I know we're not on Code Review, but while we're here there are other issues with your code:
The return type of the function is implicitly Variant. Append As String to the end of the function's signature.
The optional parameter whatpart is implicitly Variant as well, and implicitly passed ByRef, but you only need to read it, not write and return to the caller - so declare it Optional ByVal whatpart As String = "username".
Prefer the Environ$ function over Environ - both do the same thing, but Environ returns a Variant, that you're implicitly converting to a String - best use Environ$ and work with a string from the start.
Instead of relying on magic, hard-coded strings, use an Enum value and Select Case over the possible legal values:
Public Enum NamePart
EnvironmentDefined
FullName
FirstName
LastName
End Enum
The full code could look like this:
Public Enum NamePart
EnvironmentDefined
FullName
FirstName
LastName
End Enum
Public Function GetUserName(Optional ByVal part As NamePart = EnvironmentDefined) As String
Dim result As String
Dim sysInfo As Object
Dim userInfo As Object
If Namepart = EnvironmentDefined Then
GetUser = Environ$("USERNAME")
Exit Function
End If
Set sysInfo = CreateObject("ADSystemInfo")
Set userInfo = GetObject("LDAP://" & sysInfo.UserName)
Select Case part
Case FullName
result = userInfo.FullName
Case FirstName
result = userInfo.GivenName
Case LastName
result = userInfo.LastName
Case Else
result = Environ$("USERNAME")
End Select
GetUserName = result
End Function
Note, I didn't include a GivenName enum member, since it's redundant; calling code can do this:
Dim value As String
value = GetUserName(FirstName)

Optional Parameter without constants

I was wondering how I canspecify a Optional Parameter with a non-constant value?
Liek this for example:
Private Sub Foo(Optional ByVal Name as String = Application.ExecutablePath)
MsgBox("name: " & Name)
End Sub
is there a workarround?
So I can use a not constant value in the parameter as optional?
is there a workarround?
Yes:
Private Sub Foo(Optional ByVal Name As String = Nothing)
If Name Is Nothing Then
Name = Application.ExecutablePath
End If
MsgBox("name: " & Name)
End Sub
The most common way would be to avoid the Optional statement alltogether and use function overloading instead. This means you define a function with the same name multiple times with different declaration like so:
Private Sub Foo()
Foo(Application.ExecutablePath)
End Sub
Private Sub Foo(ByVal Name as String)
MsgBox("name: " & Name)
End Sub
That way, you can either supply a name or not when you call the function. The correct function is used depending on the declaration you use.
This approach seems more complicated, and in this simple case it probably is. However when your declaration gets more complicated, with more optional parameters in different orders mixed with non-optional parameters you will quickly learn to appreciate the possibilities of Overloading, I guarantee.
Expanding on Tim's correct answer
This code can be made more concise by using the If operator. This gives a one line setting of the alternate value if the parameter is Nothing
Private Sub Foo(Optional ByVal Name As String = Nothing)
Name = If(Name, Application.ExecutablePath)
MsgBox("name: " & Name)
End Sub
The If operator was introduced in 2010 IIRC so this code won't work in older versions of Visual Studio
You have to set it to an unused/reserved constant and then check for that in the method.
Private Sub(Optional ByVal Name as String = Nothing)
If Name Is Nothing Then Name = Application.ExecutablePath
MsgBox("name: " & Name)
End Sub
For some extra niceties, you can use xml comments and attributes to indicate via intellisense what the real "default" value is.

ByRef argument type mismatch in Excel VBA

I'm working with VBA. I wrote a user define function that takes a string, process it and return a cleaned string. I am not sure what is wrong with it. I am not able to call it and ask it to process my string and return it. I am thinking there are a mistake in the way I am defining or returning it.
Public Function ProcessString(input_string As String) As String
' The temp string used throughout the function
Dim temp_string As String
For i = 1 To Len(input_string)
temp_string = Mid(input_string, i, 1)
If temp_string Like "[A-Z, a-z, 0-9, :, -]" Then
return_string = return_string & temp_string
End If
Next i
return_string = Mid(return_string, 1, (Len(return_string) - 1))
ProcessString = return_string & ", "
End Function
And I use this function like this
Worksheets(data_sheet).Range("C2").Value = ProcessString(last_name)
Last name is a string variable, usually looks like this Lastname*****, and I am trying to remove all the stars behind it. Have it return Lastname without the stars.
I received Compile error: ByRef arugment type mismatch when I tried to run this. I am using Windows XP with Office 2003.
EDIT: I added the basic struction of the code I have, I have about 20 lines of the similar code. Doing the same thing for each field I need.
Private Sub CommandButton2_Click()
' In my original production code I have a chain of these
' Like this Dim last_name, first_name, street, apt, city, state, zip As String
Dim last_name As String
' I get the last name from a fixed position of my file. Because I am
' processing it from another source which I copied and pasted into excel
last_name = Mid(Range("A4").Value, 20, 13)
' Insert the data into the corresponding fields in the database worksheet
Worksheets(data_sheet).Range("C2").Value = ProcessString(last_name)
I suspect you haven't set up last_name properly in the caller.
With the statement Worksheets(data_sheet).Range("C2").Value = ProcessString(last_name)
this will only work if last_name is a string, i.e.
Dim last_name as String
appears in the caller somewhere.
The reason for this is that VBA passes in variables by reference by default which means that the data types have to match exactly between caller and callee.
Two fixes:
1) Force ByVal -- Change your function to pass variable ByVal: Public Function ProcessString(ByVal input_string As String) As String, or
2) Dim varname -- put Dim last_name As String in the caller before you use it.
(1) works because for ByVal, a copy of input_string is taken when passing to the function which will coerce it into the correct data type. It also leads to better program stability since the function cannot modify the variable in the caller.
I don't know why, but it is very important to declare the variables separately if you want to pass variables (as variables) into other procedure or function.
For example there is a procedure which make some manipulation with data: based on ID returns Part Number and Quantity information. ID as constant value, other two arguments are variables.
Public Sub GetPNQty(ByVal ID As String, PartNumber As String, Quantity As Long)
the next main code gives me a "ByRef argument mismatch":
Sub KittingScan()
Dim BoxPN As String
Dim BoxQty, BoxKitQty As Long
Call GetPNQty(InputBox("Enter ID:"), BoxPN, BoxQty)
End sub
and the next one is working as well:
Sub KittingScan()
Dim BoxPN As String
Dim BoxQty As Long
Dim BoxKitQty As Long
Call GetPNQty(InputBox("Enter ID:"), BoxPN, BoxQty)
End sub
I changed a few things to work with Option Explicit, and the code ran fine against a cell containing "abc.123", which returned "abc.12,". There were no compile errors.
Option Explicit ' This is new
Public Function ProcessString(input_string As String) As String
' The temp string used throughout the function
Dim temp_string As String
Dim i As Integer ' This is new
Dim return_string As String ' This is new
For i = 1 To Len(input_string)
temp_string = Mid(input_string, i, 1)
If temp_string Like "[A-Z, a-z, 0-9, :, -]" Then
return_string = return_string & temp_string
End If
Next i
return_string = Mid(return_string, 1, (Len(return_string) - 1))
ProcessString = return_string & ", "
End Function
I'll suggest you post more of your relevant code (that calls this function). You've stated that last_name is a String, but it appears that may not be the case. Step through your code line by line and ensure that this is actually the case.
While looping through your string one character at a time is a viable method, there's no need. VBA has built-in functions for this kind of thing:
Public Function ProcessString(input_string As String) As String
ProcessString=Replace(input_string,"*","")
End Function
Something is wrong with that string try like this:
Worksheets(data_sheet).Range("C2").Value = ProcessString(CStr(last_name))
It looks like ByRef needs to know the size of the parameter. A declaration of
Dim last_name as string
doesn't specify the size of the string so it takes it as an error. Before using
Worksheets(data_sheet).Range("C2").Value = ProcessString(last_name)
The last_name has to be declared as
Dim last_name as string *10 ' size of string is up to you but must be a fix length
No need to change the function. Function doesn't take a fix length declaration.
For me the problem here was that I was declaring multiple variables in a row instead of separate rows.
For example, I was trying to pass i as an integer to my function.
Dim i,j as integer - gets me the error
Dim i as integer - doesn't get the error

Declaring constant in VBA from a INI file

I have some const in a VBA module but I would like to put them in a INI file because I have other applications that use the same constants. This works:
Public Const PATH_DB As String = "\\server\folder\bdd.mdb"
This however doesn't work:
Public Const PATH_DB As String = getFromIni("path","db","C:\config.ini")
Public Function getFromIni(ByVal strSectionName As String, ByVal strEntry As String, ByVal strIniPath As String) As String
Dim x As Long
Dim sSection As String, sEntry As String, sDefault As String
Dim sRetBuf As String, iLenBuf As Integer, sFileName As String
Dim sValue As String
sSection = strSectionName
sEntry = strEntry
sDefault = ""
sRetBuf = Strings.String$(256, 0) '256 null characters
iLenBuf = Len(sRetBuf$)
sFileName = strIniPath
x = GetPrivateProfileString(sSection, sEntry, "", sRetBuf, iLenBuf, sFileName)
sValue = Strings.Trim(Strings.Left$(sRetBuf, x))
If sValue <> "" Then
getFromIni = sValue
Else
getFromIni = vbNullChar
End If
End Function
# C:\config.ini
[path]
db=\\server\folder\bdd.mdb
My getFromIni function actually works pretty well but not when I want to declare constants (it doesn't compile at all). I tried a global variable instead but for some reason, it doesn't work either, the variable cannot be found when used from another form in the same project (it only works when it's declared as a const, but I can't declare it as a const when getting the value from a function).
What's wrong?
You cannot assign a function call as the value of a CONST string. I would expect you to receive the error "Constant expression required" when running this.
Change
Public Const PATH_DB As String = getFromIni("path","db","C:\config.ini")
to
Public PATH_DB As String
Place the following call that gets the value from INI file in a initialize method (say Database Open event)
PATH_DB = getFromIni("path","db","C:\config.ini")