I have an Excel Template that has hardcoded Ms Access MDB path in the VBA code used to connect to the Access tables and save, retrieve data.
I migrated the MS Access Database over to SQL Server with Integrated Authentication for the Excel Template Users.
My question is, What is the Recommend Way / Best Practice for storing the SQL Server DB connection string and retreiving it in Excel 2007 VBA to save and retrieve data?
In the past, I have done the following.
Use a Registry Key setting that has the Connection String. Then in the VBA, write a function that reads the registry key and returns the connection string.
Have a "Settings" hidden sheet within the Excel Template, with named cell for the connection string. Read the connection string in VBA by accessing that named range.
Use a .INI txt file that goes with the Excel template. (This is not ideal and I want to avoid this as it builds a dependency on that external file)
I don't like # 1 because I want to avoid writing to/reading from Registry if possible.
# 2 feels ok, thought I am not sure if there is a better "cleaner" way for doing this.
Any thoughts?
This is what I would do safely store connection string credentials
Download and install Visual Studio Express 2012 for Windows (FREE)
Open it as Administrator and create a New Project. Select Visual C# then Class Library and rename it to HiddenConnectionString
In the Solution Explorer, rename Class1.cs to MyServer.cs
Right click your MyConnection project in the Solution Explorer and select Add Reference
Type activeX in the search box and tick the Microsoft ActiveX Data Objects 6.1 Library
Copy and paste the below code into the MyServer.cs completely replacing whatever is in the file.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.IO;
using ADODB;
namespace HiddenConnectionString
{
[InterfaceType(ComInterfaceType.InterfaceIsDual),
Guid("2FCEF713-CD2E-4ACB-A9CE-E57E7F51E72E")]
public interface IMyServer
{
Connection GetConnection();
void Shutdown();
}
[ClassInterface(ClassInterfaceType.None)]
[Guid("57BBEC44-C6E6-4E14-989A-B6DB7CF6FBEB")]
public class MyServer : IMyServer
{
private Connection cn;
private string cnStr = "Provider=SQLOLEDB; Data Source=SERVER\\INSTANCE; Initial Catalog=default_catalog; User ID=your_username; Password=your_password";
public MyServer()
{
}
public Connection GetConnection()
{
cn = new Connection();
cn.ConnectionString = cnStr;
cn.Open();
return cn;
}
public void Shutdown()
{
cn.Close();
}
}
}
Locate the cnStr variable in the code and update your connection string details.
Right click the *HiddenConnectionString* solution in the Solution Explorer and select Properties.
Click the Application tab on the left side, then Assembly Info and tick Make Assembly COM-Visible
Click the *Build* from the menu on the left and tick Register For COM Interop
Note: If you are developing for 64-bit Office then make sure you change the Platform Target on the Build menu to x64! This is mandatory for 64-bit Office COM libraries to avoid any ActiveX related errors.
Right click the HiddenConnectionString in the Solution Explorer and select Build from the menu.
If everything went OK then your HiddenConnectionString.dll and HiddenConnectionString.tlb should be successfully generated. Go to this path now
C:\Users\administrator\Documents\Visual Studio 2012\Projects\HiddenConnectionString\HiddenConnectionString\bin\Debug
and you should see your files.
Now open Excel and go to VBE. Click Tools and select References.
Click the Browse button and navigate to the HiddenConnectionString.tlb.
Also, add references to Microsoft ActiveX Object 6.1 Library - this is so you can use ADODB library.
Now right click anywhere in the Project Explorer window and Insert a new Module
copy and paste the below code to it
Option Explicit
Sub Main()
Dim myCn As MyServer
Set myCn = New MyServer
Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
rs.Open "Select * from [TABLE_NAME]", myCn.GetConnection
Range("A1").CopyFromRecordset rs
rs.Close
myCn.Shutdown
Set rs = Nothing
Set myCn = Nothing
Columns.AutoFit
End Sub
Replace the [TABLE_NAME] with an actual table name in your database.
Hit F5 or hit the green play button on the ribbon.
If everything went OK, you should now see the returned Table on your spreadsheet.
my sample:
As you can see. Adding references to your own COM-library and storing the login credentials and other sensitive data inside the compiled .dll protects your data(connection string). It's very difficult to decompile the *.dll file to get any sensible information from it. There are various coding techniques to protect your *.dll even more but I am not going to go into details now. This itself achieves what you asked for.
myCn.GetConnection returns the ADODB.Connection object that was initialized inside the referenced COM library. No Excel user will be presented with the connection string or sensitive data (actually nobody else neither).
You can modify the C# code to accept parameters from VBA i.e. login, password, initial catalog, query to execute etc... if you have users with different privileges on the instance of your SQL Server it wouldn't be a bad idea to allow people to log in.
Note: there is no error handling added in the C# code and VBA. I would strongly recommending working on it if you're planning to use the technique I have described above.
How about storing it under CustomDocumentProperties?
Note: I am not sure, if the workbook (based on a given template) will inherit the property defined using CustomDocumentProperties in the template.
Related
How I can use below VBA coding in SSIS vba. I want to clear cells(data) from multiple sheets from SSIS by VBA coding
sub cod()
ThisWorkbook.Worksheets("Case management details").Range("A2:K10000").clear
ThisWorkbook.Worksheets("interface Timeliness").Range("A2:G20000").clear
ThisWorkbook.Worksheets("Life Events").Range("A2:N10000").clear
End sub
I had the same problem here. If you decided to solve this using SSIS component try with Script Task.
After you create excel steps will be:
Add Script Task on control flow.
Double click on component.
Press 'Edit Script...' button.
Right-click on the project in new visual studio windows (should be something like 'ST_' and some guid) and click on Manage NuGet Packages...
From 'Browse' tab install:
Microsoft.Office.Interop.Excel
Microsoft.CSharp
Into namespaces region add these 2 lines:
using Microsoft.Office.Interop.Excel;
using Microsoft.CSharp.RuntimeBinder;
Copy below code into ScriptMain class:
var appExcel = new Excel.Application();
string filename = #"D:\PathToExcel\ExcelFile.xlsx";
var newBook = appExcel.Workbooks.Open(filename);
var oSheet1 = newBook.Worksheets["Case management details"];
oSheet1.Range("A2", "K10000").Delete();
newBook.Save();
appExcel.Workbooks.Close();
appExcel.Quit();
Close without saving the second visual studio window (where you write script code).
Run the package
Note:
Instead of hardcoded file location, you can replace it with a package variable. In this case 2nd line will be:
string filename = Dts.Variables["User::ExcelFilePath"].Value.ToString();
I tested this package before write answer and everything work perfect!
With this logic, create multiple sheet instance and delete what you want.
If something isn't clear or non-logical write me in a comment and I will make correction.
I am developing a VB.NET update system for a volunteer organisation’s MS Access database. The database is protected by a password as it contains personal information. I have created the application using the VB designer. I need to be able to code the application so that, if the owner decides to change the MS Access password, they will have no need to come back to me to change the code and rebuild the solution. In other words, I do not want the password to be hard coded in the app.config file or the settings.designer.vb file. My code should not need to know the password as a simple call to one of the Fill functions can test any password entered by the user. My problem is that I have found no way to alter the connection string that is tested in the setttings.designer.vb code whenever the database is accessed. I am using Visual Studio 2017.
I have spent a long time searching the web for answers and have tried various solutions involving the configurationmanager without success. I am new to this area so I would be most grateful if anyone here can help.
Here is my latest attempt which still produces an invalid password error even though the third debug statement suggests that the connection string, including the password, has been correctly set.
Public Sub UpdateConnString(connString As String)
Dim configFileMap As New ExeConfigurationFileMap()
Dim config As Configuration = ConfigurationManager.OpenExeConfiguration(configFileMap.ExeConfigFilename)
Dim connStringName As String = "TestConnectionString"
Debug.Print("0 " + config.ConnectionStrings.ConnectionStrings(connStringName).ConnectionString)
config.ConnectionStrings.ConnectionStrings(connStringName).ConnectionString = connString
Debug.Print("1 " + config.ConnectionStrings.ConnectionStrings(connStringName).ConnectionString)
config.Save(ConfigurationSaveMode.Modified, True)
Debug.Print("2 " + config.ConnectionStrings.ConnectionStrings(connStringName).ConnectionString)
End Sub
Just because a connection string is stored in the config file, you aren't required to use it as it is. You can read in that default value and then edit it before using it, e.g.
Dim builder As New OleDbConnectionStringBuilder(My.Settings.DefaultConnectionString)
builder.DataSource = dataSource
Dim connectionString = builder.ConnectionString
You can add or modify any part of a connection string you want that way at run time.
Thank you for your response. Unfortunately, the code throws a compilation error - "DefaultConnectionString is not a member of My.Settings".
Fortunatley I have now managed to find a working solution:
'My.Settings.Item("TestConnectionString") = connectionString
Edit MY goal is this, I need to do read data from a Remedy Database using a VB .Net app. /Edit
I am trying to read data from the AR System ODBC driver in a VS 2010 VB windows form.
I have added a connection in the data connections via , a system dsn, connection string pulled from a working excel data pull, and built a connection string based via the wizard.
I can test connection, and it comes back 'test connection succeeded'.
When I click ok and the Data connection goes to my server explorer, it expands to tables, view, and procedures and viewing/refreshing those causes errors such as:
Column 'Table_Cat' does not belong to table Tables.
Error [im00][ar system odbc driver] not supported
I am aware there is the api out there, however this has a problem in that the api acts like a login session from the client, which limits you to one login session. This not feasible for the task at hand as the user needs to remain logged into remedy, and the app I'm writing will be read only for data.
The goal is a billboard app that will display certain tickets, kind of like a stock ticker.
There will be no editing of ticket data from remedy, I just need to read the tickets and provide tallies and alerts for our helpdesk.
It seemed so easy when I sat down, but now 4 hours later and a mind numbing amount of irrelevant google results, I'm almost wondering if I'm going to have to report back that it cannot be done with out the api and creating more remedy accounts. Not having to create additional remedy accounts was part of of the request put to me.
Tags: Visual Studio 2010 BMC AR System Remedy ODBC VB.NET
Update
So I have done the code by hand and now seem a few steps forward, but still blind.
Ok, so if i export my excel query it looks like this from the dqy file
export.dqy
DRIVER={AR System ODBC Driver};ARServer=xxxxxx;ARServerPort=xxxxx;UID=xxxx;PWD=xxxx;ARAuthentication=;SERVER=NotTheServer
SELECT "EN:HelpDesk"."Company Name", "EN:HelpDesk"."Call Status", "EN:HelpDesk"."Caller Region", "EN:HelpDesk"."Next Action", "EN:HelpDesk"."Detailed Description(FTS)", "EN:HelpDesk".Device, "EN:HelpDesk"."Submitted-by", "EN:HelpDesk".Impact, "EN:HelpDesk"."Arrival Time", "EN:HelpDesk"."Call Id", "EN:HelpDesk".Market FROM "EN:HelpDesk" "EN:HelpDesk" WHERE ("EN:HelpDesk"."Call Status"<>'Closed') AND ("EN:HelpDesk"."Submitted-by"<>'nis') AND ("EN:HelpDesk".Impact='High') AND ("EN:HelpDesk".Market='DCIN') AND ("EN:HelpDesk"."Caller Region"<>'UK')
First off, these quotes look wrong to me, but it seems to work because if I leave them, executing the command doesn't fail, however it doesn't return data to my data grid view in vb.
Second the EN:Helpdesk looks odd to me but again same results as the quotes.
I have tried changing the quotes to brackets [ ] but receive errors once I do.
Here is my code:
VB Code
Imports SystemM My goal
Imports System.Data
Imports System.Data.Odbc
Imports System.Data.SqlClient
Sub Main()
Dim sql As String
Dim connectionString As String = "DRIVER={AR System ODBC Driver};ARServer=xxx;ARServerPort=xxx;UID=xxx;PWD=xxx;ARAuthentication=;SERVER=NotTheServer"
sql = "SELECT ""EN:HelpDesk"".""Company Name"", ""EN:HelpDesk"".""Call Status"", ""EN:HelpDesk"".""Caller Region"", ""EN:HelpDesk"".""Next Action"", ""EN:HelpDesk"".""Detailed Description(FTS)"", ""EN:HelpDesk"".Device, ""EN:HelpDesk"".""Submitted-by"", ""EN:HelpDesk"".Impact, ""EN:HelpDesk"".""Arrival Time"", ""EN:HelpDesk"".""Call Id"", ""EN:HelpDesk"".Market FROM EN:HelpDesk WHERE (""EN:HelpDesk"".""Call Status""<>'Closed') AND (""EN:HelpDesk"".""Submitted-by""<>'nis') AND (""EN:HelpDesk"".Impact='High') AND (""EN:HelpDesk"".Market='DCIN') AND (""EN:HelpDesk"".""Caller Region""<>'UK')"
Dim bindingSource1 As New BindingSource()
Me.SQLDS_reportresults.DataSource = bindingSource1
Dim myAdapter = New OdbcDataAdapter(sql, connectionString)
Dim commandBuilder As New OdbcCommandBuilder(myAdapter)
Dim table As New DataTable()
table.Locale = System.Globalization.CultureInfo.InvariantCulture
myAdapter.Fill(table)
bindingSource1.DataSource = table
Me.SQLDS_reportresults.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCellsExceptHeader)
End Sub
Project is a windows forms with a button to activate main(), a textbox (textbox1) and a datagridview(SQLDS_reportresults).
As the code stands, this causes no errors, but my datagridview never populates. Since there are no errors, I'm inclined to think my connection is working , and that my command is being executed,making my problem lay in code, however given the funky use of quotes and en:helpdesk in the sql command, I'm also concerned that is malformed and causing the where portion to return no results.
Anyone have any suggestions / ideas?
I need a way to display these results in app, and right now I don't care how just as long as I can see some data at all.
I've been googling and looked at the vb examples in the api zip but that is not related to opening a odbc connection and I found that of little help in my research.
I almost feel like I'm missing something simple or obvious.
When i run odbcad32.exe my ar system odbc driver shows 7.00.01.02 which is what I assume is installed on everyones machines as I used the remedy install set provided to me by the IT/Software admins to build this dev box.
I have this old Access database (2000 format) which i want to convert to the new .accdb 2007 format. I know just basic stuff about Access so I knew it was a bit of a challenge, I decided to do it anyway.
It has two files, one appears to have all the tables "WOSS_Tables.mdb" (duh), the other ("WOSS.mdb") has a bunch of Forms and a Module and lots of code when I go to Database Tools / Visual Basic.
I opened them both in Access 2010 and did a "Save and Publish", "Save ass .accdb" and everything went fine with that.
So the first thing that pops up when I open the WOSS.mdb is a Login Form, but when i try to log in it says:
So i get it, it does not find the new file.
Digging into the WOSS.mdb file (Now WOSS.accdb) i find a Module which has sort of a connectionString named ruta (route in spanish), so i changed it, pointing it to my desktop for now:
Option Compare Database
Option Explicit
Public xdescripcion As String
'** Ruta para abrir la base de datos
'Public Const Ruta = "\\the_super_secret_old_route\"
Public Const Ruta = "C:\Users\AlexXPS\Desktop\Woss"
Public M
And found on a bunch of Forms, lines like these:
Set dbs = DBEngine.Workspaces(0).OpenDatabase(Ruta + "WOSS_Tables.mdb", False, False, "")
Set dbs = OpenDatabase(Ruta + "WOSS_Tables.mdb", False, False, "")
Updated those with "WOSS_Tables.accdb" aswell.
Now, I click Save, then Debug menu, Compile then i close and reopen and i still get the same error as above, like it didnt compile at all?
If i check the Forms and the Module code, its like i changed it Ruta is correctly pointing at my desktop and the other changes were saved correctly aswell.
So, what am i doing wrong?
PS: Sorry for the long post :(
EDIT!!!!!!!
Okay, messing around with the code i found something out:
On the Logon screen theres a ComboBox which is supposed to show all the UserNames in the Users table, the ComboBox has a simple query on its "Row Source" Data Property
SELECT DISTINCTROW [Usuarios].[Name], [Usuarios].[Name] FROM Usuarios ORDER BY [Usuarios].[Name];
Which has nothing to do with the "Ruta" variable I changed!
So now the question is, how can I tell that ComboBox that the location of its source db changed? (Its not "\the_super_secret_old_route\WOSS_Tables.mdf" anymore, its "C:\Users\AlexXPS\Desktop\Woss/WOSS_Tables.accdb")
The previous error message was shown because of the ComboBox not finding the database :), not the other code (Which of course also needed to be changed)
I hope im explaining myself well, if not, heres a screenshot!
Look again at the change you made to the Ruta constant.
'Public Const Ruta = "\\the_super_secret_old_route\"
Public Const Ruta = "C:\Users\AlexXPS\Desktop\Woss"
Notice the previous version ended with a backslash; the new version doesn't. So in your OpenDatabase() statements, you're giving the full path to the database like this ...
"C:\Users\AlexXPS\Desktop\WossWOSS_Tables.mdb"
or maybe it's like this with your other changes ...
"C:\Users\AlexXPS\Desktop\WossWOSS_Tables.accdb"
Either way, it seems you're missing a backslash before the file name.
Edit: Your updated question indicates you have this as the row source for a combo box:
SELECT DISTINCTROW [Usuarios].[Name], [Usuarios].[Name]
FROM Usuarios ORDER BY [Usuarios].[Name];
Open the navigation pane, right-click its header, then choose Navigation Options ... Place a check mark in the box next to Show Hidden Objects. Then verify there is a linked table named Usuarios. You'll need to update the location of the db which contains the actual Usuarios source table. Do that by selecting the Database Tools section of the ribbon, then choose Linked Table Manager to update the link's location.
Edit2: You can examine the .Connect property of the Usuarios link to determine the location of the db file which contains the source table.
Public Function BackEndLocation() As String
BackEndLocation = Split(CurrentDb.TableDefs("Usuarios").Connect, "=")(1)
End Function
That way you might be able to get rid of the Ruta constant and avoid the need to update that constant and the name of the database file everywhere your code calls OpenDatabase(). Just use the Linked Table Manager to update the links when the file name or location changes, then rely on the BackEndLocation() function to tell your code where it its.
sorry for long text.
Problem: I have difficulty in removing the LDB generated by the "CREATE" method of ADOX in the following code segment. Please provides some hints/pointers to the solutions, and thanks.
Aims: Create (on the fly) a new access database and then export data (says Arena modules) to that newly created database.
Expected: The newly created access database should be able to be used by some external operations, say Access.exe, after the end of the subroutine and without exiting the current VB program.
I tested that the "Arena code" do nothing about the create/release of the ldb file.
I tested the "Exclusive Mode" in connection string, but the access file is still locked by the vb program.
I tested under both inside the VB environment, and directly invoke the generated from Explorer, and same results.
Other database formats is not the options to me. (due to Arena export limit)
It is not a web app.
Code:
Sub Method1()
Dim logs As New System.Collections.Generic.List(Of String)
Dim arenaApp As Arena.Application = Nothing
Try
logs.Add("Creating access database")
Try
Dim cat As New ADOX.Catalog
cat.Create("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=new.mdb;Jet OLEDB:Engine Type=5")
cat = Nothing
Catch ex As Exception
logs.Add(ex.Message)
logs.Add(ex.StackTrace)
Finally
logs.Add("End creating access database")
End Try
'Opening Arena model"
arenaApp = New Arena.Application()
arenaApp.Models.Open(fileName)
arenaApp.ActiveModel.ExportModules("", "new.mdb", "TableName", "", Arena.smExportType.smExportAll)
Catch ex As Exception
logs.Add(ex.Message)
logs.Add(ex.StackTrace)
Finally
...
End Try
End Sub
Platform:
Windows 7 64bit, Office 2010 (32)
VB 2010 express
Lib: MS ADO Ext. 2.8 for DDL and Security
What about closing the mdb after creation, then reopening it ?
I am not a VS expert, but in Access, many things in "Design mode" put the mdb in Exclusive mode...and you can't switch back without closing it first i suspect.
If you want to create a database, its probably simpler to PInvoke SQLConfigDataSource. Then you can connect to it via ADODB, ADO.NET or your VB.NET data access methodology of choice.
I don't have an example of doing it in VB.NET, but this C# class and this powershell script demonstrate how to call the function.
I know it's an old question. I encountered exactly the same issue today. Some comments ask why use ADOX, that's because first, it's from legacy code, and second, I didn't find any other ways to dynamically create mdb file.
In VB6, whenever you set adox = Nothing, this com object will be released immediately and so is the ldb file.
In .Net, you have to rely on GC to collect the adox before ldb file is unlocked.
So, I made the following trick. I know it's ugly, but I didn't find any other ways, and at least my trick worked.
Dim t As New Thread(
Sub
'All adox code should be here
'....
'....
adox = Nothing
End Sub
)
t.Start()
t.Join()
t = Nothing
GC.Collect()
Sleep(50)