I needed to add current working directory to a macro which I thought would be simple using an environment variable, but quickly learned Extra basic does not recognize "CD" like windows does. After searching I found the code below to see what all variables I have at my disposal and to my dismay none provide current working directory. Can someone suggest an efficient method of finding the path the Macro is currently running in?
Dim strEnviron As String
Dim i As Long
For i = 1 To 255
strEnviron = Environ(i)
If LenB(strEnviron) = 0& Then Exit For
msgbox strEnviron
Next
Related
This seems like an awfully basic question, but I still can't find an answer for it.
In VBScript, is there any harm in declaring the same scalar variable with the Dim statement more than once?
I'm working on some related projects and developing some code pieces I'd like to reuse. However, I'm slightly concerned if there's a problem with using code that uses the same variable name twice, like so:
Dim i
for i = 1 to Count
'* doin' somethin' here
next
Dim i
for i = 1 to UnrelatedOtherCount
'* doin' somethin' different yo
next
It's not just iteration loop variables, either; I may have multiple places in a script where regexes are used. If my script uses two sections of code being re-used, and each of them uses a "patternString" variable and starts by declaring the variables:
'* first section of code from the folder
Dim objPersonRegex, patternString
Set objPersonRegex = new RegExp
...
'* a completely different section of code
Dim objBuildingRegex, patternString
Set objBuildingRegex = new RegExp
My instinct is to say that it shouldn't be a problem, that Dim just creates a variable of the given name if it doesn't already exist, and if it does, just goes on because what it was told to do is done. But is this actually the case? In case it matters, these are scripts running on Windows Script Host.
(To clarify what I'm worried about, I'm not worried at all about the value of the variable getting clobbered. If I need to retain the value of the variable, I'll save it in a different variable, one with a unique name.)
All my attempts to look up the answer myself have failed; they only return information about declaring more than one variable on a line, and using ReDim on arrays.
Is it harmful to Dim a variable more than once?
It is impossible to Dim a variable more than once. Neither not using Option Explicit nor (mis)using On Error Resume Next will get more than one Dim for the same variable (name) in the same compilation unit past the 'compiler'.
Dim doesn't just create then re-create the variable. It allocates a memory address for it.
So, assuming you are coding with OPTION EXPLICIT turned on, it won't let you even do that.
However... What you're showing there as an example is not declaring the variable again. Not the same way, at any rate. That's not "dimmed". It's just temporarily used and thrown away when it's done. So, in that respect, you are doing it the right way.
If you were to create a new variable for every little thing that ran in a loop, when they don't interefere with each other, that becomes rather inefficient.
In the first example, I would say that it is harmful, because it means you're coding with OPTION EXPLICIT OFF. Don't do that. Keep that option on.
For the second example, if they really are different sections of code, in different "namespaces" (as much as vbscript has that concept... different modules, I guess), you're probably okay. However, that code is similar enough that I would look into writing a method that does whatever it is you're about to do. Then just call the method.
However, if snippets in the second example are in the same module, it would be a problem... partly for the reason in my first paragraph, and partly because it can lead to a very specific kind of bug. Imagine you make a mistake in your code, where it's possible somehow in the last sample for patternString to go unassigned. If it's a completely new variable, that results in an error, such that we know something went wrong. With the current code, it's possible to run the code with old pattern. This is worse than an error, because it may cause the program to do things like make unexpected changes to a database, or show sensitive data to the wrong user.
I have an old ActiveX Component written in VB6 to support (don't even bother asking to modernize it, that's just what I have currently) and it does some weird stuff when compiling the following code:
Dim connectedPrinter As printer
Dim printers() As String
For Each connectedPrinter In printers
printers(UBound(printers)) = connectedPrinter.DeviceName
Next
All it should do is make a list of all connected printers. But, when compiling, VB6 tells me that
For Each control variable on arrays must be Variant
What's odd about that is, that in another function of the same codebase, I use the exact same loop for a different task (setting the current printer als default)
Dim pPrinter As printer
For Each pPrinter In printers
If (pPrinter.DeviceName = sPrinterName) Then
Set printer = pPrinter
Exit For
End If
Next
Yet, that is accepted without hesitation, compiles and also demonstrably works in the production environment.
What's going on here?
In the problem snippet, you have a local array called printers hiding the Printers collection. You can rename the local array, or qualify access to the collection by referring to it as VB.Printers.
I need to write some vbscripts in my new project. I was told by other people that vbscripting is easy, but seems to me, it is not. For example, in the following example (provided by microsoft), these functions: CreateObject, CreateShortcut, as well as these property names: TargetPath, WindowStyle, Hotkey, etc, are used, but I just cannot find the corresponding API documentation about how to use them. In other words, how do you know you need to call these functions in your vbscripts? Visual Studio 2008/2010 do not have templates for vbscript either. Could anybody tell me what I am missing, and what the best way is to do vbscripting?
set WshShell = WScript.CreateObject("WScript.Shell")
strDesktop = WshShell.SpecialFolders("Desktop")
set oShellLink = WshShell.CreateShortcut(strDesktop _
& "\MyExcel.lnk")
oShellLink.TargetPath = _
"C:\Program Files\Microsoft Office\OFFICE11\EXCEL.EXE"
oShellLink.WindowStyle = 1
oShellLink.Hotkey = "CTRL+SHIFT+F"
oShellLink.IconLocation = _
"C:\Program Files\Microsoft Office\OFFICE11\EXCEL.EXE, 0"
oShellLink.Description = "My Excel Shortcut"
oShellLink.WorkingDirectory = strDesktop
oShellLink.Save
Take a look here (MSDN).
The objects you are working with are documented there (of course, it's MSDN documentation so it's not ideal, but it's documented nevertheless).
Specifically the WshShortcut Object and the WshShell etc.
I don't think VBScript is a very easy language, especially if you need to write larger scripts.
If you don't specifically need to write a script but it would be ok with an executable I'd look at using VB.Net instead, where you have a good development environment that makes everything much easier since you have Intellisense and you can just press F1 for the documentation. And since it's a typed language with a large framework it gets easier to avoid mistakes and many operations you need you can just call a method in the framework rather than writing your own code.
However, if you do need to do it in VBScript, I'd suggest trying to find some kind of IDE for it. I haven't used any, but at least this one seems worth looking at.
The language of VBScript is relatively easy. It's a subset of Visual Basic and VBA and simplifies some things from those environments (eg you don't need to declare variable types).
What you are dealing with above is working with the methods and properties of a given object, WshShell. There are many many objects out there, each with their own set of methods and properties to know about, many with common usage conventions, and many more with "unique" (ie idiosyncratic) usage requirements. This is where the complexity comes in, but it's not part of VBScript itself. You will run into this with any other language (JScript, Python, Delphi) that works with the myriad objects and APIs that are out there for Windows system management.
The plus side is that once you get used to the language of VBScript and the process of looking up object API references and examples on MSDN and other sites, it does become very easy to put together complicated and powerful scripts.
Like I frequently tell users, computers often make things faster the second time you do something. The first time usually requires some learning.
A great set of resources for learning VBScript and how you need to approach things is the
TechNet Script Center, their Hey, Scripting Guy! series, and the Script Repository.
This is somewhat related to my other question.
I've been using a dll to acompany an excel spreadsheet. Everything is currently working with the dll and excel is using it just fine. But is it possible to specify that a dll resides in the same directory as the excel file when declaring functions?
Declare Sub FortranCall Lib "Fcall.dll" (r1 As Long, ByVal num As String)
Unfortunetly this doesn't work, I have to use something like:
Declare Sub FortranCall Lib "C:\temp\Fcall.dll" (r1 As Long, ByVal num As String)
This works, but is going to cause headaches when distributing to my office mates. Placing the dll in c:\windows\system32 etc. is not really an option either.
Here are three possibilities for dynamically loading/calling into DLLs from VBA, including links to relevant info and some sample code. Can't say I've ever had to use any of the solutions described there, but it seems like a reasonable exploration of the options in light of VBA's need for a static path.
Create a new module at runtime (you could import a .bas file from disk, no need to hard-code the module with string literals), using the VBIDE Extensibility API. Drawback: no compile-time validation; you'll need to use stringly-typed Application.Run calls to invoke it. Requires trusted programmatic access to the VBIDE API (i.e. you allow VBA to execute code that generates code that is then executed... like macro viruses do).
Use the LoadLibrary Win32 API... and now you've got pointers and addresses: this scary code (.zip download) is essentially a huge unmaintainable hack that uses assembly language to enable invoking the API functions by name. Looks like it only works for a subset of supported Win32 API functions though.
Change the DLL search path, but then that also requires dynamic code added at run-time, so might as well go with the above.
Here's another potential solution that suggests programmatically updating the PATH environment variable prior to calling into your DLL. Not a bad idea, if it works, as you could add this to you workbook open event.
Good luck!
The way I generally take care of this is by adding:
Dim CurrentPath As String
CurrentPath = CurDir()
ChDir (ThisWorkbook.Path)
To: Private Sub Workbook_Open()
ChDir() should do the trick. It might not work on network folders though.
Declare Sub FortranCall Lib "Fcall.dll" (r1 As Long, ByVal num As String)
...
Dim CurrentPath As String
CurrentPath = CurDir()
ChDir (ThisWorkbook.Path)
Call FortranCall(r, n)
ChDir (CurrentPath) ' Change back to original directory
Now keep your .dll in the same folder as your workbook.
You can put the DLL in some directory and add it to the path EnVar.
ActiveWorkbook.Path gives you the full path to the folder containing the currently-active workbook. So try this:
Declare Sub FortranCall Lib ActiveWorkbook.Path & "\Fcall.dll" (r1 As Long, ByVal num As String)
On Windows, how can I use Ruby to permanently set an environment variable? I know I need to change the registry (through the win32ole module?) but I am a novice with regard to scripting the registry.
I understand that I can say ENV['FOO'] = "c:\bar\baz" to set the environment variable FOO for the session. However, I am instead interested in setting environment variables globally and permanently.
I did find the patheditor gem, which works great for permanently altering the Windows PATH. But I want to set other environment variables, for example, JAVA_HOME.
There is a past question about this. The basic gist is to set the variable in the registry via Win32::Registry (like runako said). Then you can broadcast a WM_SETTINGCHANGE message to make changes to the environment. Of course you could logoff/logon in between then too, but not very usable.
Registry code:
require 'win32/registry.rb'
Win32::Registry::HKEY_CURRENT_USER.open('Environment', Win32::Registry::KEY_WRITE) do |reg|
reg['ABC'] = '123'
end
WM_SETTINGCHANGE code:
require 'Win32API'
SendMessageTimeout = Win32API.new('user32', 'SendMessageTimeout', 'LLLPLLP', 'L')
HWND_BROADCAST = 0xffff
WM_SETTINGCHANGE = 0x001A
SMTO_ABORTIFHUNG = 2
result = 0
SendMessageTimeout.call(HWND_BROADCAST, WM_SETTINGCHANGE, 0, 'Environment', SMTO_ABORTIFHUNG, 5000, result)
Thanks to Alexander Prokofyev for the answer.
Also see a good discussion on Windows environment variables in general, including how to set them for the entire machine vs. just the current user ( in HKEY_LOCAL_MACHINE\ SYSTEM\ CurrentControlSet\ Control\ Session Manager\ Environment)
You're looking for Win32::Registry :
http://www.ruby-doc.org/stdlib/libdoc/Win32API/rdoc/classes/Win32/Registry.html
For reference, here's how I found it:
http://www.google.com/search?client=safari&rls=en-us&q=ruby+registry&ie=UTF-8&oe=UTF-8
Anyhow, then you will want to do something like:
registry.open("HKEY_WINDOWS_GUNK/path/to/your/key", Win32::Registry::KEY_WRITE) do |reg|
reg[regentry, Win32::Registry::REG_DWORD]=value
end
You might have to create a key first, if it doesn't already exist.
I am pleased to see such a comprehensive set of answers!
It should also be noted that when creating/writing to entries under reserved/system keys (such as HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node on 64-bit Windows operating system) using constant flags such as Win32::Registry::KEY_WRITE and Win32::Registry::KEY_ALL_ACCESS will not exhibit desired behaviour unless the MRI (the Ruby interpreter) instance is started from an "Administrator" kernel context. Starting cmd.exe (Windows shell program) by right-clicking the executable and selecting "Run as Administrator" allows this.