Run MS Office/Access on a MAC

Since the MAC version of MS Office does not include MS Access (there is no MAC version of Access and never has been) , we often see questions similar to:

How can I run Microsoft Access on a MAC?”

in various forums.  While it is true that you can’t get Access (and other programs) for MAC, there are still ways to run them on your MAC.  Let’s take a closer look at each of these.

Possible Ways to Run MS Access on a MAC

Here are a couple solutions that I am aware of:

Virtualization Software

You can install any number of virtualization software programs, such as Parallels or VMWare Fusion or Oracle VirtualBox, to create a virtual machine in which you install Windows and then MS Office/Access and or any other Windows program(s) of your choosing.  This does requires buying Parallels, and owing licenses for Windows and any other program you wish to install.

Once it is setup you can run launch your Windows Virtual Machine directly from within your MAC OS.

Bootcamp

Use Apple’s free Bootcamp software which will create a separate Windows formatted partition on the hard drive to install Windows and the appropriate drivers. It will run Windows as fast or faster than a PC of equivalent specification. The only downside (aside from the cost) is that you cannot run the Mac OS and the Windows OS simultaneously. Users who wanted to use Access would have to shut down the Mac OS  and reboot into Windows.

So basically you split/partition your MAC hard drive to create a spot to install Windows.  So your computer end up having both OSs installed and you pick which one to run at startup.

Remote Access Software

Another interesting option is to use remote access software, programs such as:

  • Microsoft’s Remote Desktop
  • TeamViewer
  • etc…

to connect to a another computer. The remote access software simply transmits the image of the other computer’s screen image directly back to your local PC.  So, you are in fact working directly on the other computer.

So, if you already have access to a Windows PC or server, you can use remote access software to connect to it and run your database.

CrossOver

I recently came across a program called CrossOver which states:

Run Microsoft Windows software on your Mac without buying a Windows license, rebooting or using a virtual machine

I don’t know anything more about this product, but it sounds very interesting and may be worth further investigation.  Furthermore, they offer a trial version so you can take it for a test drive.

Microsoft Query

If you simply need to access the data, but not interact with it (Add/Edit/Delete) and you have a copy of MS Office for MAC then you can use Microsoft Query to access the data via ODBC.

VBA – Automating Excel – Part 2

After making my original post VBA – Automating Excel I received a few e-mails and comments asking for some more in depth examples of how this approach can be extended further to make one life easier.

So, I thought I’d demonstrate a couple more functions that can be used/adapted. The entire idea is that you can create reusable functions for coding that you need to perform regularily.

Table Border Formatting

A concrete example, when coding Excel, is that I often need to add borders around a range to format it all pretty. So you could create a function such as the one provided below and then whenever you need to apply borders to a range, you can perform the formatting by calling a single line of code.

Sub Rng_ApplyBorder(Rng As Object, RngHeader As Object)
    On Error GoTo Error_Handler

    With RngHeader.Interior
        .Pattern = xlSolid
        .PatternColorIndex = xlAutomatic
        .Color = 192
        .TintAndShade = 0
        .PatternTintAndShade = 0
    End With
    With RngHeader.Font
        .ThemeColor = xlThemeColorDark1
        .TintAndShade = 0
    End With
    With Rng.Borders(xlEdgeLeft)
        .LineStyle = xlContinuous
        .ColorIndex = 0
        .TintAndShade = 0
        .Weight = xlThin
    End With
    With Rng.Borders(xlEdgeTop)
        .LineStyle = xlContinuous
        .ColorIndex = 0
        .TintAndShade = 0
        .Weight = xlThin
    End With
    With Rng.Borders(xlEdgeBottom)
        .LineStyle = xlContinuous
        .ColorIndex = 0
        .TintAndShade = 0
        .Weight = xlThin
    End With
    With Rng.Borders(xlEdgeRight)
        .LineStyle = xlContinuous
        .ColorIndex = 0
        .TintAndShade = 0
        .Weight = xlThin
    End With
    With Rng.Borders(xlInsideVertical)
        .LineStyle = xlContinuous
        .ColorIndex = 0
        .TintAndShade = 0
        .Weight = xlThin
    End With
    With Rng.Borders(xlInsideHorizontal)
        .LineStyle = xlContinuous
        .ColorIndex = 0
        .TintAndShade = 0
        .Weight = xlThin
    End With

    ' Auto-fit the column widths and row heights
    oExcel.Selection.CurrentRegion.Columns.AutoFit
    'Freeze the first row
    oExcelWrSht.Rows("2:2").Select
    oExcel.ActiveWindow.FreezePanes = True
    oExcelWrSht.Range("A1").Select  'Return to the top of the page

Error_Handler_Exit:
    On Error Resume Next
    Exit Sub

Error_Handler:
    MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Source: Rng_ApplyBorder" & vbCrLf & _
           "Error Description: " & Err.Description _
           , vbOKOnly + vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Sub

Calling the function then is no harder than

Call Rng_ApplyBorder(oExcelWrSht.Range("A1:F326"), oExcelWrSht.Range("A1:F1"))

or here is a slightly streamlined version of the Rng_ApplyBorder procedure:

Sub Rng_ApplyBorder2(Rng As Object, RngHeader As Object)
On Error GoTo Error_Handler
    Dim aOutsideBorders(3)    As Long
    Dim aInsideBorders(1)     As Long
    Dim i                     As Byte

    aOutsideBorders(0) = xlEdgeLeft
    aOutsideBorders(1) = xlEdgeRight
    aOutsideBorders(2) = xlEdgeTop
    aOutsideBorders(3) = xlEdgeBottom

    aInsideBorders(0) = xlInsideVertical
    aInsideBorders(1) = xlInsideHorizontal

    With RngHeader.Interior
        .Pattern = xlSolid
        .PatternColorIndex = xlAutomatic
        .Color = 192
        .TintAndShade = 0
        .PatternTintAndShade = 0
    End With
    With RngHeader.Font
        .ThemeColor = xlThemeColorDark1
        .TintAndShade = 0
    End With
    'Set the Outside Borders
    For i = 0 To UBound(aOutsideBorders)
        With Rng.Borders(aOutsideBorders(i))
            .LineStyle = xlContinuous
            .ColorIndex = 0
            .TintAndShade = 0
            .Weight = xlThick
        End With
    Next i
    'Set the Inside Borders
    For i = 0 To UBound(aInsideBorders)
        With Rng.Borders(aInsideBorders(i))
            .LineStyle = xlContinuous
            .ColorIndex = 0
            .TintAndShade = 0
            .Weight = xlThin
        End With
    Next i

    ' Auto-fit the column widths and row heights
    oExcel.Selection.CurrentRegion.Columns.AutoFit
    'Freeze the first row
    oExcelWrSht.Rows("2:2").Select
    oExcel.ActiveWindow.FreezePanes = True
    oExcelWrSht.Range("A1").Select  'Return to the top of the page

Error_Handler_Exit:
    On Error Resume Next
    Exit Sub

Error_Handler:
    MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Source: Rng_ApplyBorder2" & vbCrLf & _
           "Error Description: " & Err.Description _
           , vbOKOnly + vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Sub

Calling the function then is no harder than

Call Rng_ApplyBorder2(oExcelWrSht.Range("A1:F326"), oExcelWrSht.Range("A1:F1"))

PageSetup

Another concrete example, setting the various page setup settings for printing purposes. Once again, a very simple function like the one below can be created.

Sub WrkSht_SetupPage(Rng As Object)
    On Error GoTo Error_Handler
    
    With oExcelWrSht.PageSetup
        .PrintTitleRows = "$1:$1"
        .zoom = False
        .FitToPagesWide = 1
        .FitToPagesTall = False
        .PrintArea = Rng.Address
        .LeftHeader = ""
        .CenterHeader = ""
        .RightHeader = ""
        .LeftFooter = Format(Now, "yyyy-mmm-dd h:nn")
        .CenterFooter = "Page &P de &N"
        .RightFooter = ""
    End With

Error_Handler_Exit:
    On Error Resume Next
    Exit Sub

Error_Handler:
    MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Source: WrkSht_SetupPage" & vbCrLf & _
           "Error Description: " & Err.Description _
           , vbOKOnly + vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Sub

And again a single line of code now applies your preferred settings:

Call WrkSht_SetupPage(oExcelWrSht.Range("A1:F326"))

Charting

Another common need for automation in Excel is creating charts. Below is another example of how such a task can be automated and transformed into a reusable procedure.

Sub GenColumnChart(Rng As Object, RngChartLoc As Object, sTitle As String, sXAxisTitle As String, sYAxisTitle As String)
    On Error GoTo Error_Handler
    Dim oChart                As Object

    oExcelWrSht.Shapes.AddChart2(286, xl3DColumnClustered).Select
    'Position and size the Chart on the current page
    Set oChart = oExcel.ActiveChart.Parent
    oChart.Height = RngChartLoc.Height
    oChart.Width = RngChartLoc.Width
    oChart.Top = RngChartLoc.Top
    oChart.Left = RngChartLoc.Left
    'Format the Chart (Title, Axis Titles, remove legend)
    With oExcel.ActiveChart
        .HasLegend = False 'Remove the Legend
        .ChartType = xl3DColumnClustered 'Specify the chart type to create
        .SetSourceData Source:=Rng 'Specify the data series
        .HasTitle = True
        .ChartTitle.Characters.Text = sTitle 'Set the Chart Title
        .Axes(xlCategory, xlPrimary).HasTitle = True
        .Axes(xlCategory, xlPrimary).AxisTitle.Characters.Text = sXAxisTitle 'Set the X Axis Title
        .Axes(xlValue, xlPrimary).HasTitle = True
        .Axes(xlValue, xlPrimary).AxisTitle.Characters.Text = sYAxisTitle 'Set the Y Axis Title
    End With

Error_Handler_Exit:
    On Error Resume Next
    Set oChart = Nothing
    Exit Sub

Error_Handler:
    MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error Source: GenColumnChart" & vbCrLf & _
           "Error Description: " & Err.Description _
           , vbOKOnly + vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Sub

Then you’d call such a function like:

Call GenColumnChart(oExcelWrSht.Range("A1:F326"), _
                    oExcelWrSht.Range("H5:M35"), _
                    "Population by City", _
                    "City", _
                    "Population")

Taking it Further

The procedures given above are very simple procedures that I created for illustrative purposes. Obviously, you could make them much more versatile by adding more input variables and linking them to the various properties.

For instance:

  • the Rng_ApplyBorder Sub could include a variable for the color to apply.
  • the WrkSht_SetupPage Sub could include variables for the Number of page wide or tall, or the various headers/footers, etc…
  • the GenColumnChart sub could include variables to include or not the legend, specify the chart type to create, …

by doing so you will end up with extremely flexible reusable procedures that will work for any and all scenarios and that it were lies the true strength of such an approach!

What are the Benefits

So what are the benefits to such an approach you are probably asking yourself! I can simply create a single procedure which contains all the code and it will work just fine, no?!

Yes, this is true and in a one off situation one may not see the benefit to such an approach, but there are several.

  • One benefit is by breaking down big procedures into components is that troubleshooting becomes easier and error reporting can become clearer.
  • But the biggest benefit is that by creating a series of smaller procedures, you build yourself a library of reusable code that you can call whenever need be. You can now create an Excel VBA module, Word VBA module, … and quickly be able to import them into any new project you undertake and have all the functionality you need without having to dissect procedures scattered throughout other projects.
  • Lastly, by using reusable procedures, you minimize the amount of code you have to write.

And don’t forget that when trying to create your own procedure in Word, Excel, … the Macro recorder is one of your best friends to get the basic code. That said, often the macro recorder adds a lot of useless code so you always need to go through it and try and remove anything that is redundant or useless.

I hope this helps illustrate the principle a little bit more.

How to Repair MS Office

Whenever we see problems that common troubleshooting does seem to resolve, it is quite common to then perform a Repair of MS Office.  From my understanding Repair checks various Office files dll/ocx/…, registry keys and the likes to ensure the integrity of all the programs.  The entire process take a few minutes – on my PC it took approximately 10 minutes to Repair Microsoft Office Professional Plus 2013.

How-to Repair MS Office

Open the Control Panel

Launch Windows Control Panel

Select Uninstall a program

Control Panel - Programs - Uninstall Program

Select your Microsoft Office (in my case Microsoft Office Professional Plus 2013) from the program listing and then click on the Change button above the listing.

Change ProgramSelect Repair and click on the Continue button.

Repair Office 01

Simply wait for the process to complete.

Repair Office 02

MS Access Ribbon Images Gone?!

Well there appears to be a new bug with Access in which external images no longer appear in the Ribbon.  I am not 100% sure when the bug came to be, but this morning when I opened a couple of my databases (using Access 2013) I found:

Ribbon Images Gone 01

Ribbon Images Gone 02

Examining the Ribbon XML I came to determine that all the built-in imageMso continue to display fine, but external images such as a png, no longer display.

Just to be 100% clear, all the buttons did have images appearing just fine previously and no changes have been made to these databases in years.

I posted this on the Answers forum and Tom van Stiphout was kind enough to reproduce and confirm my findings.  After further testing, it was determined that the cause for the images not appearing in his case was different than mine.

I haven’t checked to see if this issue is more generalized, in that it applies to all of Office or is localized, only impacts Access.

What is more alarming to me is that this machine is set to never install updates!  So can someone explain to me how things could have changed?!  Truly makes me wonder what is going on behind the scenes with our computers that we aren’t aware of.  I don’t want to sound like a conspiracy theorist, but something is smelling very fishy with this whole situation.  Of course another potential explanation could be some type of code which involve a date/time component?!  Maybe????  I hope…!

Further tests seem to show that the issue is limited to only Access 2013.  When I opened the same database on Access 2010 & 2016 the ribbon displays just fine.

Ribbon Images Gone 03Update 2016-09-04

Went through the standard troubleshooting steps.

  • Decompile ..
  • New Blank db  and import everything …

Finally performed an MS Office Repair and the images are displaying again.  I’d love to understand what triggered the actual problem, the true root cause (what could have messed around with dlls, registry keys, … to the point of making the ribbon non-functional and why is the ribbon so fragile in the first place), but at least whatever it is/was has been resolved.  Let’s just hope it doesn’t happen again, or to any of my end-users.  At least now I know what to try first to resolve the problem.

Securing Your MS Access Database Front-End

In this post I’d like to discuss what steps you can take to try and secure, as best you can, your MS Access Front-End application.

Password Protect your FE

As I pointed out in my post entitled Why Ms Access Isn’t A Secure Database – Part 2, a MS Access Front-End is not secure and leaves sensitive information completely open to hacking.  As such, like your back-end (BE), you should always Encrypt with Password your front-end (FE).  This will, in the very least, stop prying eyes from being able to harvest BE passwords allowing anyone to have complete reign on your raw data.

Also, be sure to use a different password for the FE than used with the BE.

Distribute a Compiled FE to your End-Users

Another critical step is to always distribute a compiled version (mde/accde) of your FE to your end-users.  By converting your mdb/accdb into an mde/accde, you lock down certain type of objects within your database.  Users can not open Forms or Reports in design view, nor can the view any of the VBA code.  So in essence by distributing a compile version you minimize any opportunity to mess around with the database itself.

Now an mde/accde surprisingly does not lock down tables, queries or macros.

Also note that since all VBA code is locked down and unviewable, any unhandled errors will cause a fatal application crash, hence the crucial importance of bulletproof error handling throughout all of your VBA code.

Hide the Navigation Pane

As noted in the previous section, an mde/accde surprisingly does not lock down tables, queries or macros so it becomes critical to take the necessary steps to do as much as you can to limit users abilities to get into aspects of the databases they shouldn’t be in.  As such, by simply hiding the Navigation Pane, you eliminate your user’s ability to access these database objects.

Hide individual Database Objects

Of lesser importance, if you’ve taken the necessary steps to hide the Nav Pane altogether, changing the individual object Hidden attribute to True (so as to make it a hidden object) can keep most users from viewing objects in the first place unless they purposely go and changes their Navigation Display Options to Show Hidden Objects.

Use a Custom Ribbon

By completely hiding the standard Ribbon and using your own Ribbon, you can control which commands your users have access to and thus minimize any trouble they can get into.

Disable the Shift Bypass

Even if you prepare your database and hide this and that, you must disable the Shift Bypass property so as to close the back door which enables people to get around all of the above steps you’ve taken to harden your application.

Disable Special Keys

Change your database Use Access Special Keys property.

Create an AutoKeys Macro

By creating an AutoKeys macro, you can disable certain built-in key sequences, such as:

Ctrl+G -> Opens VBE
Ctrl+Break
F11 -> Shows/Hides Navigation Pane

In an ideal world we’d also include the following key combinations, but for whatever reason Microsoft does not allow the use of Alt in AutoKeys, go figure?!

Atl+F11 -> Opens VBE
Alt+F1

This step is somewhat redundant since we disabled Access’ Special Keys in the previous step, but I prefer to do both.  It provides an extra layer of security in case someone manages to change the database property itself.

Create an AutoExec Macro

I personally like to use an AutoExec macro which calls my startup procedure.  Typically, as part of my startup procedure I call a procedure that simply sets/resets the various properties we’ve been discussing (Shift Bypass, Hide Nav Pane, Check that we’re running an compiled copy, …)

Lockdown Where Your Database Can Run

If you are lucky enough that you are running your db on say a CITRIX server, you can easily add code, as part of your startup mechanism, that verifies which computer is running the current application.  If it doesn’t match up with the computer/server name it should be running on, simply close the application.

Note: I actually send an e-mail to the db admin when this occurs with certain information (PC name, Username, IP address, Date/Time, …).

Lockdown Which User Can Run Your Database

Another easy process to implement is to have a table in your database that lists all the windows usernames that are allowed running the database.  At startup, retrieve the current user’s network username and then see if it is in your user table.  If it isn’t close the application.

Note: I actually send an e-mail to the db admin when this occurs with certain information (PC name, Username, IP address, Date/Time, …).

Lockdown Folder Permissions

Yes, simple folder permissions are still one of your best friends when trying to harden any application, not just MS Access databases!  By controlling who can access what, you minimize any potentials for disaster.

When it comes to Access, setting up the BE folder to have Travers permission thus allowing user access to the data but not to the folder itself stops people from nosing around and being able to copy everything and walking away with it.  Then you distribute the FE as you always do (one copy per user locally installed on their PC).

Implement Virtual Password Protected Connection (vPPC)

If you truly want to take things to the next level, you may wish to examine and implement Virtual Password Protected Connection (vPPC).  Alan Cossey was kind enough to provide me with a copy to the initial concept which you can download below since all the other download links are no longer valid.  Do note this is offered with no support whatsoever.

Download “Access - vPPC Security Toolkit” vPPCSecurityToolkit.zip – Downloaded 45009 times – 1.24 MB

 

As always, if you have any tips to share please feel free to leave a comment.

MS Access Contact Database Template Sample

Probably the most common requests we see in forums is for an Microsoft Access Contact database templates and how to structure the tables and build the data entry forms.  So I thought I’d provide a basic sample database to help beginners get off to a good start.

The basic structure that I created is as follows:

Contact Database Relationships and then I created a simple form to manage everything from:

Contact Database Form

This is a simple sample as things can get very complex, very quickly depending on your needs, but for the average home user this would fill the need for a contact management database or at the very least be a great starting point to build more complex applications from.

This demo is unlocked and fully editable with no strings attached.

Continue reading

Why MS Access isn’t a Secure Database – Part 2

It is one things to explain how Access stores back-end passwords in plain English text in a hidden system table as I did in my article entitled Why MS Access isn’t a Secure Database, but I didn’t just want you to take my word for it. So I quickly put together what I have dubbed my BE Password Extractor.

Do note, this utility only works for Access database using Access back-ends. It will not retrieve SQL Server passwords …

Simply pick the Front-End to use to extract Back-End information about, specify the Password for the Front-End if applicable and then click on the Retrieve BE Info.  In a few split seconds, you should see a list of Back-End file(s) and their respective Password and Connection String.

Continue reading

Flaw in TableDefs Collection?

As we all know, there are often numerous approaches to accomplish them same things. I was trying to help someone on a forum regarding the number of tables in their database returned by the TableDefs Collection vs. what the visually saw listed in the Nav Pane. This got me to perform a few tests and I discovered a little something which IMHO is a bug with the TableDefs Collection. At the very least it is something to simply be aware of.

What I did.
I created a new blank 2013 Access database. That’s it! Then I compared the following 2 functions results.

'Aproach 1 using TableDefs
'*******************************************************************
Function GetTableCount1() As Long
    Dim db                    As DAO.Database

    Set db = CurrentDb
    db.TableDefs.Refresh
    GetTableCount1 = db.TableDefs.Count
    Set db = Nothing
End Function
'Approach 2 using a MSysObjects Query
'*******************************************************************
Function GetTableCount2() As Long
    Dim db                    As DAO.Database
    Dim rs                    As DAO.Recordset
    Dim sSQL                  As String

    Set db = CurrentDb
    sSQL = "SELECT Count(MSysObjects.Name) AS NumberOfTables" & _
           " FROM MSysObjects" & _
           " GROUP BY MSysObjects.Type" & _
           " HAVING (MSysObjects.Type In (1,4,6))"
    Set rs = db.OpenRecordset(sSQL, dbOpenSnapshot)
    With rs
        If .RecordCount <> 0 Then
            GetTableCount2 = ![NumberOfTables]
        Else
            GetTableCount2 = 0
        End If
    End With
    rs.Close
    Set rs = Nothing
    Set db = Nothing
End Function

Now, GetTableCount1 returned a value of 11 while GetTableCount2 returned a value of 21. So which one was accurate I wondered? And what was the difference exactly?

So I set out to figure out this little mystery out of simply personal curiosty and created the following 2 function following suit with my original 2 functions.

'Aproach 1 using TableDefs
'*******************************************************************
Function EnumerateTables1()
    Dim db                    As DAO.Database
    Dim i                     As Long


    Set db = CurrentDb
    db.TableDefs.Refresh
    For i = 0 To db.TableDefs.Count - 1
        Debug.Print i + 1, db.TableDefs(i).Name
    Next i
    Set db = Nothing
End Function
'Approach 2 using a MSysObjects Query
'*******************************************************************
Function EnumerateTables2()
    Dim db                    As DAO.Database
    Dim rs                    As DAO.Recordset
    Dim sSQL                  As String
    Dim i                     As Long

    Set db = CurrentDb
    sSQL = "SELECT MSysObjects.Name" & _
           " FROM MSysObjects" & _
           " WHERE (MSysObjects.Type In (1,4,6))" & _
           " ORDER BY MSysObjects.Name;"
    Set rs = db.OpenRecordset(sSQL, dbOpenSnapshot)
    With rs
        If .RecordCount <> 0 Then
            Do While Not .EOF
                i = i + 1
                Debug.Print i, ![Name]
                .MoveNext
            Loop
        End If
    End With
    rs.Close
    Set rs = Nothing
    Set db = Nothing
End Function

and this is where the difference became apparent.

EnumerateTables1 returned the following

 1            MSysAccessStorage
 2            MSysACEs
 3            MSysComplexColumns
 4            MSysNavPaneGroupCategories
 5            MSysNavPaneGroups
 6            MSysNavPaneGroupToObjects
 7            MSysNavPaneObjectIDs
 8            MSysObjects
 9            MSysQueries
 10           MSysRelationships
 11           MSysResources

and EnumerateTables2 returned the following

 1            f_72DC15AD7C104BFE9ABC43CF6F539822_Data
 2            MSysAccessStorage
 3            MSysACEs
 4            MSysComplexColumns
 5            MSysComplexType_Attachment
 6            MSysComplexType_Decimal
 7            MSysComplexType_GUID
 8            MSysComplexType_IEEEDouble
 9            MSysComplexType_IEEESingle
 10           MSysComplexType_Long
 11           MSysComplexType_Short
 12           MSysComplexType_Text
 13           MSysComplexType_UnsignedByte
 14           MSysNavPaneGroupCategories
 15           MSysNavPaneGroups
 16           MSysNavPaneGroupToObjects
 17           MSysNavPaneObjectIDs
 18           MSysObjects
 19           MSysQueries
 20           MSysRelationships
 21           MSysResources

comparing the 2 sets of results, we can see the the TableDefs collection omits

              f_72DC15AD7C104BFE9ABC43CF6F539822_Data
              MSysComplexType_Attachment
              MSysComplexType_Decimal
              MSysComplexType_GUID
              MSysComplexType_IEEEDouble
              MSysComplexType_IEEESingle
              MSysComplexType_Long
              MSysComplexType_Short
              MSysComplexType_Text
              MSysComplexType_UnsignedByte

Now what are these tables exactly? I’m not sure and Google doesn’t appear to have much information on them.

Now, for most people, this discrepancy is of no importance since the difference is merely in MS Access hidden system tables, stuff that no one should be messing around with in any event. On the other hand, if the TableDefs collection counts some system tables, then why not these? This is why I personally consider it to be a bug, not an important one, but one none the less.

If you have any insight into this discrepancy please leave a comment.

Upgrading to Windows 10 Results in “Your network access was interrupted. To continue, close the database and then open it again.”

I’ve been seeing a series of threads in the past few months regarding people experiencing the vague error

Your network access was interrupted. To continue, close the database and then open it again.

after upgrading their system to Windows 10.

Now, I won’t bore you with the details, but the gist of it is as follows.  If you use mapped drives (possibly when done through network Group Policies) to link your tables, it would seem the upgrade process, or Windows 10, screws things up.  There are 2 solutions to the problem:

  1. Remap the drives manually
  2. Use UNC paths when linking tables (this is the preferred solution as it eliminates any possible problem coming from drive mapping and this problem would never have occurred in the first place)

A few useful links on the subject with more information:

Your network access was interrupted. To continue, close the database and then open it again.
Access 2013 – Your network access was interrupted w/Windows 10
Your network access was interrupted error since Windows 10 upgrade

 

Why MS Access isn’t a Secure Database

I love Access. It is an exceptional application. I don’t think another database exists on the face of the earth that has everything Access has to offer and is so easy (relatively speaking) to learn and exploit. That being said, it simply is not secure.

Why would I say this? Simple, even when you take the time to encrypt the BE data, Access stores the password for linked table in plain text format in the Front-End!

As shown below, if you set the Navigation Pane Navigation Options to Show System Objects and then open the MSysObjects table you can clearly see that for each linked table in your database Access stores the connection string including the BE’s password (in the case below the password is MyPassword) and the BE’s full path.

MS Access MSysObjects Password

As such, one can easily query the database to determine the back-end source and their password very easily using something along the lines of:

SELECT DISTINCT MSysObjects.Connect, MSysObjects.Database, MSysObjects.ForeignName, MSysObjects.Name
FROM MSysObjects
WHERE (((MSysObjects.Database) Is Not Null))
ORDER BY MSysObjects.Database;

Normally, we say to secure a database simply convert it into an mde/accde, but even this will not stop someone from being able to create a query such as the shown one above.

This demonstrates 2 things:

  1. Access’s security is limited (I’m still floored that in 2016 the Access Dev Team has not encrypted such information!)
  2. The critical importance if you are locking down an Access database to hide the Navigation Pane, Disabling the Shift ByPass, Disabling common shortcut keys in an effort to limit any advanced malicious users from being in a position to extract such critical information from your BE.

Now keeping this in mind, Access is a great solution most of the time, but if security is a true concern then it become apparent because of the above and other reasons that one must look at using an alternative BE (SQL Server Express perhaps) to truly lockdown your data.

Updates

2016-08-26
Be sure to checkout my 2nd post on the subject, Why MS Access isn’t a Secure Database – Part 2 in which I provide a utility to demonstrate just how easy it is to extract back-end locations and password to illustrate MS Access’ innate security vulnerability.

2016-09-01
This post also made me create a post entitled Securing Your MS Access Database Front-End in which I go over what steps you can take to secure your database for distribution to your end-users.