Go Premium for a chance to win a PS4. Enter to Win

x
?
Solved

User-proofing an Access Application

Posted on 2011-03-11
28
Medium Priority
?
510 Views
Last Modified: 2012-05-11
I'm creating a new routine for all my apps that will "clean" everything up before I release the tool.
I'm looking for best practices and advice for what I should include in the lock-down of my tool.  Here are some of the things I'm looking to do:

1. Make my administrative forms and reports inaccessible or password protecting them somehow so I can access the stuff I need to but users can not.
2. Setting the current database properties to hide the application objects (tables, code, etc.)
3.  My user base isn't too very technically savvy, so some of the "tricks" (like holding down shift when opening the app) aren't going to be a problem.
4. I don't have any security issues with the data, but I want to keep users from hurting themselves :) or accidentally breaking something.

I am not a fluent coder (yet) and any code snippets would be greatly appreciated.
0
Comment
Question by:epuglise
  • 10
  • 10
  • 8
28 Comments
 
LVL 26

Expert Comment

by:Nick67
ID: 35113583
You'll look at this code and go

HUH

and for the most part, I do too.
It's Windows API code from Dev Ashish, and I don't pretend to understand it.
The upshot is you can put this code in a regular module, and then call
ReturnUserName()
ReturnComputerName()

That is monstrously useful...

Private sub Form_Open(Cancel as Integer)

if ReturnUserName <> "MyLoginName"
    Cancel = True ' the form bails
end if

end sub

OR

Private sub SomeButton_Click()
select case true
    case ReturnUserName = "JoeWho"
        docmd.openform "JoesForm"
    case ReturnUserName = "BobbyWhat"
       msgbox "forget Bob, no joy for you"
    case else
        Msgbox "I don't know you from Jack the Bear"
end select
end sub

Please see Dev Ashish's wonderful site at http://www.mvps.org/access/ for more

Option Compare Database
Option Explicit

Private Type USER_INFO_2
    usri2_name As Long
    usri2_password  As Long  ' Null, only settable
    usri2_password_age  As Long
    usri2_priv  As Long
    usri2_home_dir  As Long
    usri2_comment  As Long
    usri2_flags  As Long
    usri2_script_path  As Long
    usri2_auth_flags  As Long
    usri2_full_name As Long
    usri2_usr_comment  As Long
    usri2_parms  As Long
    usri2_workstations  As Long
    usri2_last_logon  As Long
    usri2_last_logoff  As Long
    usri2_acct_expires  As Long
    usri2_max_storage  As Long
    usri2_units_per_week  As Long
    usri2_logon_hours  As Long
    usri2_bad_pw_count  As Long
    usri2_num_logons  As Long
    usri2_logon_server  As Long
    usri2_country_code  As Long
    usri2_code_page  As Long
End Type

Public Declare Function GetUserName Lib "advapi32.dll" _
Alias "GetUserNameA" (ByVal lpBuffer As String, nSize As Long) As Long


Private Declare Function GetComputerName Lib "Kernel32" _
Alias "GetComputerNameA" _
(ByVal lpBuffer As String, nSize As Long) As Long

 
Private Declare Function apiNetGetDCName _
    Lib "netapi32.dll" Alias "NetGetDCName" _
    (ByVal servername As Long, _
    ByVal DomainName As Long, _
    bufptr As Long) As Long
 
' function frees the memory that the NetApiBufferAllocate
' function allocates.
Private Declare Function apiNetAPIBufferFree _
    Lib "netapi32.dll" Alias "NetApiBufferFree" _
    (ByVal buffer As Long) _
    As Long
 
' Retrieves the length of the specified wide string.
Private Declare Function apilstrlenW _
    Lib "Kernel32" Alias "lstrlenW" _
    (ByVal lpString As Long) _
    As Long
 
Private Declare Function apiNetUserGetInfo _
    Lib "netapi32.dll" Alias "NetUserGetInfo" _
    (servername As Any, _
    username As Any, _
    ByVal Level As Long, _
    bufptr As Long) As Long
 
' moves memory either forward or backward, aligned or unaligned,
' in 4-byte blocks, followed by any remaining bytes
Private Declare Sub sapiCopyMem _
    Lib "Kernel32" Alias "RtlMoveMemory" _
    (Destination As Any, _
    Source As Any, _
    ByVal Length As Long)
 
Private Declare Function apiGetUserName Lib _
    "advapi32.dll" Alias "GetUserNameA" _
    (ByVal lpBuffer As String, _
    nSize As Long) _
    As Long
 
Private Const MAXCOMMENTSZ = 256
Private Const NERR_SUCCESS = 0
Private Const ERROR_MORE_DATA = 234&
Private Const MAX_CHUNK = 25
Private Const ERROR_SUCCESS = 0&


'******** Code Start ********
'This code was originally written by Dev Ashish.
'It is not to be altered or distributed,
'except as part of an application.
'You are free to use it in any application,
'provided the copyright notice is left unchanged.
'
'Code Courtesy of
'Dev Ashish
'
 
Function fGetFullNameOfLoggedUser(Optional strUserName As String) As String
'
' Returns the full name for a given UserID
'   NT/2000 only
' Omitting the strUserName argument will try and
' retrieve the full name for the currently logged on user
'
On Error GoTo ErrHandler
Dim pBuf As Long
Dim dwRec As Long
Dim pTmp As USER_INFO_2
Dim abytPDCName() As Byte
Dim abytUserName() As Byte
Dim lngRet As Long
Dim I As Long
 
    ' Unicode
    abytPDCName = fGetDCName() & vbNullChar
    If (Len(strUserName) = 0) Then strUserName = fGetUserName()
    abytUserName = strUserName & vbNullChar
 
    ' Level 2
    lngRet = apiNetUserGetInfo( _
                            abytPDCName(0), _
                            abytUserName(0), _
                            2, _
                            pBuf)
    If (lngRet = ERROR_SUCCESS) Then
        Call sapiCopyMem(pTmp, ByVal pBuf, Len(pTmp))
        fGetFullNameOfLoggedUser = fStrFromPtrW(pTmp.usri2_full_name)
    End If
 
    Call apiNetAPIBufferFree(pBuf)
ExitHere:
    Exit Function
ErrHandler:
    fGetFullNameOfLoggedUser = vbNullString
    Resume ExitHere
End Function
 
Private Function fGetUserName() As String
' Returns the network login name
Dim lngLen As Long, lngRet As Long
Dim strUserName As String
    strUserName = String$(254, 0)
    lngLen = 255
    lngRet = apiGetUserName(strUserName, lngLen)
    If lngRet Then
        fGetUserName = Left$(strUserName, lngLen - 1)
    End If
End Function
 
Function fGetDCName() As String
Dim pTmp As Long
Dim lngRet As Long
Dim abytBuf() As Byte
 
    lngRet = apiNetGetDCName(0, 0, pTmp)
    If lngRet = NERR_SUCCESS Then
        fGetDCName = fStrFromPtrW(pTmp)
    End If
    Call apiNetAPIBufferFree(pTmp)
End Function
 
Private Function fStrFromPtrW(pBuf As Long) As String
Dim lngLen As Long
Dim abytBuf() As Byte
 
    ' Get the length of the string at the memory location
    lngLen = apilstrlenW(pBuf) * 2
    ' if it's not a ZLS
    If lngLen Then
        ReDim abytBuf(lngLen)
        ' then copy the memory contents
        ' into a temp buffer
        Call sapiCopyMem( _
                abytBuf(0), _
                ByVal pBuf, _
                lngLen)
        ' return the buffer
        fStrFromPtrW = abytBuf
    End If
End Function


Function ReturnUserName() As String
' returns the NT Domain User Name
Dim rString As String * 255, sLen As Long, tString As String
    tString = ""
    On Error Resume Next
    sLen = GetUserName(rString, 255)
    sLen = InStr(1, rString, Chr(0))
    If sLen > 0 Then
        tString = Left(rString, sLen - 1)
    Else
        tString = rString
    End If
    On Error GoTo 0
    ReturnUserName = UCase(Trim(tString))
End Function

 
Function ReturnComputerName() As String
    Dim rString As String * 255, sLen As Long, tString As String
    tString = ""
    On Error Resume Next
    sLen = GetComputerName(rString, 255)
    sLen = InStr(1, rString, Chr(0))
    If sLen > 0 Then
        tString = Left(rString, sLen - 1)
    Else
        tString = rString
    End If
    On Error GoTo 0
    ReturnComputerName = UCase(Trim(tString))
End Function

Open in new window

0
 
LVL 75
ID: 35114511
I forget ... what Version of Access are you using ?

mx
0
 

Author Comment

by:epuglise
ID: 35131411
2007
0
Get your Conversational Ransomware Defense e‑book

This e-book gives you an insight into the ransomware threat and reviews the fundamentals of top-notch ransomware preparedness and recovery. To help you protect yourself and your organization. The initial infection may be inevitable, so the best protection is to be fully prepared.

 
LVL 75

Accepted Solution

by:
DatabaseMX (Joe Anderson - Microsoft MVP, Access and Data Platform) earned 1400 total points
ID: 35131591
1) Since User Level Security (ULS) is no longer supported in A2007 and beyond, you pretty much have to roll your own security in that context.  You can determine the Windows logged in user ( see this code -  http://www.mvps.org/access/api/api0008.htm ) and then set Form properties accordingly.

2) See attached image

3) OK ... then implement the Shift Key Bypass code if  necessary to be sure (do you need this code?)

4) OK

You  can add a password to your VBA code - see screen shot 2 ... OR ... create an ACCDE which will prevent any design changes in code, forms and reports.

This should get you started ...

mx
Capture1.gif
Capture1.gif
0
 
LVL 75
ID: 35131667
Here is a KB on how to password protect a form or report ...

How to Create a Password Protected Form or Report
http://support.microsoft.com/?kbid=209871

mx
0
 

Author Comment

by:epuglise
ID: 35131706
Yay!!  Thanks so much!  I'll be working on this later today and can't wait to try this out.  What i'd love to have on my apps is a "Release Me" button that I click and that turns off all my debuggers, cleans everything up, and gets the app ready for users... oh yeah and makes the Release Me button invisible :)

this will go a long way to helping.
0
 
LVL 26

Expert Comment

by:Nick67
ID: 35131781
LOL

With the ReturnUserName code, on the form that has the cmdReleaseMe button, you put in the Form Open event

if returnusername <> "epuglise" them
    me.cmdReleaseMe.visible = false
end if

The API code I posted gets put in a standard module and can then get used ANYWHERE in the rest of your VBA code.
I use it to hide buttons, change what opens on click events, filter what combo boxes display, the whole nine yards.
0
 
LVL 75
ID: 35131818
The Get Login name code/link I posted is much simpler and accomplishes the same thing ... Gets the Windows logged in user name.

"Release Me"
or Rescue Me !

You can certainly add code that does this ... which you could call from a hidden Macro I suppose.

mx
0
 
LVL 26

Expert Comment

by:Nick67
ID: 35131948
@DatabaseMX

Both your code and mine come from Dev Ashish's site.
I find both the logon username and computername to be immensely useful.
Different printers, different install bases, different x86/x64 configurations...

I use them both across 50+ and reports and they're dead simple to use.
Each to their own, I guess.

Best page I ever googled when I started out
http://www.mvps.org/access/tencommandments.htm
0
 
LVL 75
ID: 35131990
0
 
LVL 26

Expert Comment

by:Nick67
ID: 35132131
The entire module I posted came in its entirety from Dev site -- I think!
I can't find it now, there, anymore -- but it was the first API code I ever used.
Never messed with it, add to it, or subtracted from it.

On the other hand, It is simpler syntax from what is posetd there now.
Maybe someone extended it -- and didn't post there own disclaimer in there, so now I think it was all Dev's code
I'm not sure.

One thing I am sure of, is it's dead simple.
0
 
LVL 75
ID: 35132180
"The entire module I posted came in its entirety from Dev site -- I think!"
I get that ... it came form this link:
http://www.mvps.org/access/api/api0066.htm

I'm just saying the other two code snippets are a lot shorter and is what I've been using for years.

mx
0
 
LVL 26

Expert Comment

by:Nick67
ID: 35132271
@DatabaseMX
I don't disagree.  What I posted is long, and I also don't understand it.
I like the syntax though!

Dim SomeString as string
SomeString = ReturnUserName

I can remember that when I find my next nifty use of it.
fOSUserName ... not so much.
But that's me :)
Looking at API alphabet soup makes my head hurt.
Doesn't mean I don't use it when it's needful.

Up to the OP now!
0
 
LVL 75
ID: 35132299
"fOSUserName ... not so much."
What do you mean ?

mx
0
 
LVL 26

Expert Comment

by:Nick67
ID: 35132786
LOL

"What was the name of that function that returned the username?  Oh yeah--ReturnUserName!"

LOL
0
 
LVL 75
ID: 35133020
What's so funny ?


Private Declare Function apiGetUserName Lib "advapi32.dll" Alias _
    "GetUserNameA" (ByVal lpBuffer As String, nSize As Long) As Long

Function ReturnUserName() As String
' Returns the network login name
Dim lngLen As Long, lngX As Long
Dim strUserName As String
    strUserName = String$(254, 0)
    lngLen = 255
    lngX = apiGetUserName(strUserName, lngLen)
    If ( lngX > 0 ) Then
        ReturnUserName = Left$(strUserName, lngLen - 1)
    Else
        ReturnUserName = vbNullString
    End If
End Function


How hard is that ?

mx
0
 
LVL 26

Expert Comment

by:Nick67
ID: 35133466
:)
I'd have to understand the code to do that
API code is where I come closest to breaking Dev Ashish's commandment #8

'Thou shalt not copy and paste other people's code without at least attempting to understand what it does.'

I don't really understand the API code--so I don't monkey with any farther than functionality requires, lest it destroy the entire universe.  I honestly wouldn't dare to attempt what you did there--because most of the dependencies are incomprehensible to me.  Any time I do monkey with it, I break it.  And then I wind up copying and pasting in the code without alterations anyway.

API -- the magical black box.  Not that I like black boxes.  I'd prefer to understand my code--but I haven't got the time to become an API coder.  So I use it when VBA can't or won't do what I need.
0
 
LVL 75
ID: 35133504
But the code you propose (and the link I referenced) has API code ...

All I did was change the name of the Function ...  which could be anything you want to call it.

mx
0
 
LVL 26

Expert Comment

by:Nick67
ID: 35133611
@DatabaseMX
But lets say I get it in my head to change this:

Private Declare Function GetComputerName Lib "Kernel32" _
Alias "GetComputerNameA" _
(ByVal lpBuffer As String, nSize As Long) As Long

Almost certainly it'll get busted.  My latest foray into API is here
http://www.experts-exchange.com/Microsoft/Development/MS_Access/Q_26878741.html
All I wanted to do at the end was pass in a path to this function:

Function EnumChildProc(ByVal lhWnd As Long, ByVal lParam As Long) As Long
Dim RetVal As Long
Dim WinClassBuf As String * 255, WinTitleBuf As String * 255
Dim WinClass As String, WinTitle As String
Dim WinRect As RECT
Dim WinWidth As Long, WinHeight As Long

RetVal = GetClassName(lhWnd, WinClassBuf, 255)
WinClass = StripNulls(WinClassBuf)  ' remove extra Nulls & spaces
RetVal = GetWindowText(lhWnd, WinTitleBuf, 255)
WinTitle = StripNulls(WinTitleBuf)
ChildCount = ChildCount + 1
' see the Windows Class and Title for each Child Window enumerated
'Debug.Print "   Child Class = "; WinClass; ", Title = "; WinTitle
' You can find any type of Window by searching for its WinClass
If WinClass = "ShellTabWindowClass" Then
    Call ChangeView(lhWnd)
    Exit Function
End If

If WinClass = "ThunderTextBox" Then    ' TextBox Window
   RetVal = GetWindowRect(lhWnd, WinRect)  ' get current size
   WinWidth = WinRect.Right - WinRect.Left ' keep current width
   WinHeight = (WinRect.Bottom - WinRect.Top) * 2 ' double height
   RetVal = MoveWindow(lhWnd, 0, 0, WinWidth, WinHeight, True)
   EnumChildProc = False
Else
   EnumChildProc = True
End If
End Function


Crash!  Bang!  Boom!  MS Access has encountered an error and is restarting....

So I don't mess with the API code I google up ... much :)
It can be very unforgiving if mistreated...and I only have a vague idea of how to treat it.
But that's me.

YMMV, epuglise's too, No Biggie
0
 

Author Comment

by:epuglise
ID: 35139207
Ok, I think I understand the shorter bit of code from DatabaseMX better and I hate using code i don't understand (make debugging even harder for me).

DbMX:  I'm using your steps with the images embedded and I think I do need the shift bypass code you reference.

Nick67: thx for the explicit code for opening the db with admin functions if it is "me".

My head hurts :(

0
 
LVL 26

Assisted Solution

by:Nick67
Nick67 earned 600 total points
ID: 35139305
API code'll do that for you :)
Now, I built a custom toolbar that shows only the items that I think the end-user needs.  I then turn off the regular toolbars and turn on the custom one, based on logon name.

Here's more code to hurt your head -- but not as badly 'cause it's just nicely commented VBA

Call ToolbarsOff to turn them off
Call ResetToolbars to turn them back on
Option Compare Database
Option Explicit

Public Function ToolbarsOff()
'On Error GoTo Err_ToolbarsOff

' Turns off the Database toolbar.
DoCmd.ShowToolbar "Database", acToolbarNo
' Turns off the Relationship toolbar.
DoCmd.ShowToolbar "Relationship", acToolbarNo
' Turns off the Table Design toolbar.
DoCmd.ShowToolbar "Table Design", acToolbarNo
' Turns off the Table Datasheet toolbar.
DoCmd.ShowToolbar "Table Datasheet", acToolbarNo
' Turns off the Query Design toolbar.
DoCmd.ShowToolbar "Query Design", acToolbarNo
' Turns off the Query Datasheet toolbar.
DoCmd.ShowToolbar "Query Datasheet", acToolbarNo
' Turns off the Form Design toolbar.
DoCmd.ShowToolbar "Form Design", acToolbarNo
' Turns off the Form View toolbar.
DoCmd.ShowToolbar "Form View", acToolbarNo
' Turns off the Filter/Sort toolbar.
DoCmd.ShowToolbar "Filter/Sort", acToolbarNo
' Turns off the Report Design toolbar.
DoCmd.ShowToolbar "Report Design", acToolbarNo
' Turns off the Print Preview toolbar.
DoCmd.ShowToolbar "Print Preview", acToolbarNo
' Turns off the Toolbox toolbar.
DoCmd.ShowToolbar "Toolbox", acToolbarNo
' Turns off the Formatting (Form/Report) toolbar.
DoCmd.ShowToolbar "Formatting (Form/Report)", acToolbarNo
' Turns off the Formatting (Datasheet) toolbar.
DoCmd.ShowToolbar "Formatting (Datasheet)", acToolbarNo
' Turns off the Macro Design toolbar.
DoCmd.ShowToolbar "Macro Design", acToolbarNo
' Turns off the Utility1 toolbar.
DoCmd.ShowToolbar "Utility 1", acToolbarNo
' Turns off the Utility2 toolbar.
DoCmd.ShowToolbar "Utility 2", acToolbarNo
' Turns off the Web toolbar.
DoCmd.ShowToolbar "Web", acToolbarNo
' Turns off the Source Code Control toolbar.
DoCmd.ShowToolbar "Source Code Control", acToolbarNo

Exit_ToolbarsOff:
Exit Function

Err_ToolbarsOff:
MsgBox Err.Number & " " & Err.Description
Resume Exit_ToolbarsOff
End Function

Public Function ResetToolbars()
'On Error GoTo Err_ResetToolbars


' Turns on the Database toolbar.
DoCmd.ShowToolbar "Database", acToolbarWhereApprop
' Turns on the Relationship toolbar.
DoCmd.ShowToolbar "Relationship", acToolbarWhereApprop
' Turns on the Table Design toolbar.
DoCmd.ShowToolbar "Table Design", acToolbarWhereApprop
' Turns on the Table Datasheet toolbar.
DoCmd.ShowToolbar "Table Datasheet", acToolbarWhereApprop
' Turns on the Query Design toolbar.
DoCmd.ShowToolbar "Query Design", acToolbarWhereApprop
' Turns on the Query Datasheet toolbar.
DoCmd.ShowToolbar "Query Datasheet", acToolbarWhereApprop
' Turns on the Form Design toolbar.
DoCmd.ShowToolbar "Form Design", acToolbarWhereApprop
' Turns on the Form View toolbar.
DoCmd.ShowToolbar "Form View", acToolbarWhereApprop
' Turns on the Filter/Sort toolbar.
DoCmd.ShowToolbar "Filter/Sort", acToolbarWhereApprop
' Turns on the Report Design toolbar.
DoCmd.ShowToolbar "Report Design", acToolbarWhereApprop
' Turns on the Print Preview toolbar.
DoCmd.ShowToolbar "Print Preview", acToolbarWhereApprop
' Turns on the Toolbox toolbar.
DoCmd.ShowToolbar "Toolbox", acToolbarWhereApprop
' Turns on the Formatting (Form/Report) toolbar.
DoCmd.ShowToolbar "Formatting (Form/Report)", acToolbarWhereApprop
' Turns on the Formatting (Datasheet) toolbar.
DoCmd.ShowToolbar "Formatting (Datasheet)", acToolbarWhereApprop
' Turns on the Macro Design toolbar.
DoCmd.ShowToolbar "Macro Design", acToolbarWhereApprop
'Turns on the Utility1 toolbar.
DoCmd.ShowToolbar "Utility 1", acToolbarWhereApprop
'Turns on the Utility2 toolbar.
DoCmd.ShowToolbar "Utility 2", acToolbarWhereApprop
'Turns on the Web toolbar.
DoCmd.ShowToolbar "Web", acToolbarWhereApprop
'Turns on the Source Code Control toolbar.
DoCmd.ShowToolbar "Source Code Control", acToolbarWhereApprop

Exit_ResetToolbars:
Exit Function

Err_ResetToolbars:
MsgBox Err.Number & " " & Err.Description
Resume Exit_ResetToolbars
End Function

Open in new window

0
 

Author Comment

by:epuglise
ID: 35139308
Ok another question.  I'm scared to death of globals because i think some coding class at one time made a big deal about not using globals... BUT... do you think it makes sense to create a global that identifies the user as either an "Admin" or a "User" (which would be determined based on who the user is (from the get user id function)? I was thinking that the user type could be set when the app is opened the first time?

Then depending on what form needs what access, I could determine on a form-by-form basis what is visible and available and what is not.

Thank you for your advice.
0
 

Author Comment

by:epuglise
ID: 35139313
(PS if that is a different question let me know and I'll open a separate question)
0
 

Author Comment

by:epuglise
ID: 35139376
@Nick: so those two functions turn on or off the entire tool bar; I assume a custom toolbar would consist of:
1. turning everything off
2. turning on only those things that apply to a particular user?
0
 
LVL 26

Assisted Solution

by:Nick67
Nick67 earned 600 total points
ID: 35139505
No, I created a custom toolbar, which is separate from the standard ones.  It is then the only one left on for the end-users.  Nicely enough, I have a button on it that calls ResetToolbars -- so if they need them all turned on, they can be.

The Ribbon is a different monster -- and would probably need to be a new question.

As for globals -- IF -- IF you split your database to a front-end and back-end, and you push out the front-end to each individual workstation, so everyone is using their own copy of the front-end -- then globals are wonderful.

Otherwise, Danger Will Robinson!

Using the ReturnUserName and a Select Case structure, I give all my users a UserClass from 1 to 5 in the Switchboard On_Open event.  I then store that in an invisible, unbound textbox on the Switchboard.  Everything else then references that control when it comes to user customization
0
 
LVL 75

Assisted Solution

by:DatabaseMX (Joe Anderson - Microsoft MVP, Access and Data Platform)
DatabaseMX (Joe Anderson - Microsoft MVP, Access and Data Platform) earned 1400 total points
ID: 35139878
"and I think I do need the shift bypass code you reference."

The link below contains an MDB which allows you to 'set' and 'reset' the Shift Key Bypass from a REMOTE mdb.  
It's very cool. Scroll down the page to 'By Pass Shift Key Code'

http://www.members.shaw.ca/AlbertKallal/msaccess/msaccess.html

" I'm scared to death of globals because"
Starting in A2007, you not have TempVars ... look it up in VBA Help.  Basically globals that do not get trashed when crashes/errors occur.  Also, you can store info in small local tables - I'm definitely a table driven guy.

The best Ribbon site - for info and a GREAT tool is:

http://www.accessribbon.de/en/

mx
0
 

Author Closing Comment

by:epuglise
ID: 35141876
Lots of good suggestions (and code) that got me to my solution: a global that identifies the type of user and then makes visible (or not) various program elements depending on that user type.

Thanks for the advice and code.
0
 

Author Comment

by:epuglise
ID: 35142025
PS @dbmx:  i've seen other references to locking a db remotely, but the www.members.shaw link isn't working.
0

Featured Post

Important Lessons on Recovering from Petya

In their most recent webinar, Skyport Systems explores ways to isolate and protect critical databases to keep the core of your company safe from harm.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

If you need a simple but flexible process for maintaining an audit trail of who created, edited, or deleted data from a table, or multiple tables, and you can do all of your work from within a form, this simple Audit Log will work for you.
Code that checks the QuickBooks schema table for non-updateable fields and then disables those controls on a form so users don't try to update them.
In Microsoft Access, learn how to “cascade” or have the displayed data of one combo control depend upon what’s entered in another. Base the dependent combo on a query for its row source: Add a reference to the first combo on the form as criteria i…
With Microsoft Access, learn how to specify relationships between tables and set various options on the relationship. Add the tables: Create the relationship: Decide if you’re going to set referential integrity: Decide if you want cascade upda…
Suggested Courses

876 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question