I have an alert sending aid built into an Access database that sends emails to all technicians with an expired/ing induction, all in one go. Every email requires permission to send.
Similar to this question and this question but I need to send multiple emails automatically all at once, without holding up other processes.
The code in the questions seems to send a single email, and the only accepted answer involves waiting five seconds up to three times to check that the actual message had been displayed so that SendKeys would work on that window.
If I use SendKeys with a 5 second pause built in to wait for confirmation as Graham Anderson suggests, this won't speed up the process, only avoid some clicking. The progress bar shown on the warning (below) takes roughly that time to fill, so I'm guessing that's the system building the email, which is what the wait is for.
The same problem applies to Julia's answer to the other question - the warning box won't let you click "Allow" until the progress bar is full, so code that auto clicks this button will have to wait to complete at best.
I tried DoCmd.SetWarnings (False) (and then DoCmd.SetWarnings (True) after sending) but that doesn't stop this particular one. This may be because the warning comes from Outlook rather than Access.
The coding I have:
Private Sub SendAlerts_Click()
Dim db As Database, rs As Recordset
Dim Subject As String, Body As String, EmailAddress As String
Dim Recipient As String, Induction As String, Expiry As String
Set db = CurrentDb
Set rs = db.OpenRecordset("qryExpiryAlert", dbOpenDynaset)
Do Until rs.EOF
EmailAddress = rs!Email
Recipient = rs!FullName
Induction = rs!Induction
Expiry = rs!ExpiryDate
Subject = "Inductions need renewal"
Body = "Hi " & Recipient & ", the expiry for your " & Induction & _
" certificate is " & Expiry & ". Please send a current certificate asap."
DoCmd.SendObject , , , EmailAddress, , , Subject, Body, False
rs.MoveNext
Loop
DoCmd.Close
End Sub
Here is the warning:
I wrote and compiled an Autohotkey script that looks for the Outlook confirmation dialog and automatically clicks Allow. (I call this from Access VBA and can set how long it run for before shutting itself off.) As you point out, though, there is still a substantial delay waiting for the Allow button to be enabled each time.
Ultimately, our company ended up purchasing Outlook Security Manager. As usual, you have to add an Outlook Security Manager Reference. reference to your project.
It basically has ON and OFF settings, so it's easy to use. If wrote a Sub in a standard module to simplify:
Public Sub SetSecurityManager(State As String)
' Turns the Security Manager on or off.
' The Security Manager allows the application to
' send emails through Outlook without having to
' manually confirm each outgoing email.
Select Case State
Case "On"
' Temporarily turn off Outlook security so we can send email
Set SecurityManager = CreateObject("AddInExpress.OutlookSecurityManager")
SecurityManager.DisableOOMWarnings = True
SecurityManager.DisableSMAPIWarnings = True
Case "Off"
'Restore Outlook security to normal
SecurityManager.DisableSMAPIWarnings = False
SecurityManager.DisableOOMWarnings = False
Set SecurityManager = Nothing
Case Else
End Select
End Sub
Whenever I need it, I add this line to my code:
SetSecurityManager "On"
It's frustrating to have to pay for a solution to a security problem created by software we purchased, but it's the best answer we've found. I ship between 150 and 225 reports a day and have no problem with OSM.
Thanks,
Keith
Related
So I have a function that allows the user to email an updated picture for an asset which works fine and good except if the user closes the email before sending. I have an error handler set up but it doesn't seem to capture the error. Here is my function code:
Function Email()
Globals.Logging "Opened Email for updating picture"
On Error GoTo ErrorHandler:
Dim strTagNumber As String
strTagNumber = Me.txtTagNumber.Value
Dim varName As Variant
Dim varCC As Variant
Dim varSubject As Variant
Dim varBody As Variant
varName = "myAnon#email.test"
varCC = ""
varSubject = "Updated Picture for Asset Number " & strTagNumber
varBody = "Sent by MS Access"
DoCmd.SendObject , , , varName, varCC, , varSubject, varBody, True, False
Globals.Logging "Sent Email"
Cleanup:
varName = Nothing
varCC = Nothing
varSubject = Nothing
varBody = Nothing
Exit Function
ErrorHandler:
Select Case Err.Number
Case 2501
MsgBox "Email message was Cancelled."
Globals.Logging "Canceled Email"
Case Else
MsgBox Err.Number & ": " & Err.Description
Globals.Logging "Email Error " & Err.Number & ": " & Err.Description
End Select
Resume Cleanup
End Function
Any help would be appreciated. Thank you in advance.
As described in online documentation, DoCmd.SendObjects
... uses the Mail Applications Programming Interface (MAPI)
In other words, Access (or Excel) does not actually have its own email capability. It is dependent upon a properly installed and configured MAPI email client. Unless you have purposefully installed and setup another default email client on Windows, the default is likely Outlook if you have it installed with MS Office. Windows email clients have changed with the many version of Windows, but the default might also be a simple Windows email client.
It is very likely that the MAPI client could be showing the error message, then not actually throwing/raising the error before it returns program flow back to the VBA module which initiated the call.
I recall being aware at some point of an Outlook setting that dictated certain behavior for MAPI and/or COM Automation interfaces, whether it showed errors or not. I usually would not throw out such wishy-washy info on Stack Overflow before verifying, but from what I see of the discussion around this issue, nobody really addresses this aspect of SendObjects.
Besides using Automation to send email via Outlook as other have suggested, you could inspect the Windows default email client settings. Perhaps test another email client to see if you get different results.
Tested on Access 2016. The error is captured and I can see the message box saying "Email message was Cancelled.".
Maybe you can try to use Outlook object for sending the email too.
Okay, I posted this question previously but only got half of the answer I require. To put simply I have an application which checks the log-in credentials with those stored in the My SQL Database. The awkward problem I have hear is that I've created it in the way that best suits me, and in a way that's simple for me to understand -- I'm not experienced with programming and self taught. The method I used - though prone to 'injection attacks' and other such dangers is suitable for my application and there's obviously security in my application (CD-Key checks & passwords.)
So, the actual details:
The connection is established successfully and I'm able to manipulate the data from the application.
Connection Code:
Public Sub conecDB()
'This is called upon program start.
Dim connectionString As String = SQLLoginIn.serveriptxt.Text 'This is the server IP/Server name. If server is intalled on your local machine, your IP should be 127.0.0.1 or you may use localhost
Dim strDbase As String = SQLLoginIn.databasenametxt.Text 'Database name
Dim strUser As String = SQLLoginIn.databaseusertxt.Text 'Database user
Dim strPass As String = "CENSORED" 'Database password
If connDB.State <> ConnectionState.Open Then connDB.ConnectionString = "Data Source=" & connectionString.Trim & ";Initial Catalog=" & strDbase.Trim & ";MultipleActiveResultSets=False;User ID=" & strUser.Trim & ";Password=" & strPass
If connDB.State <> ConnectionState.Open Then connDB.Open()
End Sub
That works great, for what I need it to do.
I add the records (of a separate table) to a listview in my program called lvRec
Code:
Public Sub dispRec(ByVal PstrSQL As String)
'This is called once the connection is established.
frmMain.lvRec.Items.Clear()
With comDB
.CommandText = PstrSQL
rdDB = .ExecuteReader
End With
Do While rdDB.Read
Item = frmMain.lvRec.Items.Add(rdDB!Members_ID.ToString)
Item.SubItems.Add(rdDB!Gamer_Tag.ToString.Trim)
'And a bunch of more
Item.SubItems.Add(rdDB!Games_Owned.ToString.Trim)
My.Application.DoEvents()
Loop
rdDB.Close()
End Sub
So now that you've seen how I'm working with SQL you're probably screaming "That's not how you do it!", but as I sayed, it works for me :) The problem occurs when I want to varifiy the user with their own Username & Password stored on a different Table called Logins.
I know I need to/can use the below code to query the users input with the log-in credentials in the Logins table, but I don't know how to return anything use-able like a Boolean result or something similar. I simply want to know if the credentials teh user entered are correct, so a TRUE for it existing would be great.
The SQL Query:
SQL = "Select * from logins " & _
"where Name like '%" & Me.Usernametxt.Text.Trim & _
"%' AND Passkey LIKE '" & Me.Passwordtxt.Text.Trim & "'"
I know this is a lot of detail for such a simple question but I needed people to understand the way I've went around this. Any help would be much appreciated.
Not an answer but to help you get started with authentication in a more robust fashion...
[Please bear in mind that you should never take an SO answer as gospel as all of us make mistakes and security is a very difficult topic to do right even for the professionals...]
The process you want to follow is something like the following (in pseudocode)
UserRecords = ExecuteSql("SELECT UserID, PasswordHash, Salt, IsEnabled FROM Users WHERE Username = '<Blah>'")
If UserRecords.Count <> 1
'Login Failed
End If
Dim UserRecord = UserRecords.First
If Hash(PasswordEnteredByUser & UserRecord.Salt) <> UserRecord.PasswordHash
'Login Failed
End If
If UserRecord.IsEnabled <> True
'Login Failed
End If
' Login was successful, continue as planned
A hashing function works by taking an input eg MyPassword! and converting it to something else eg AB0653AAAF330. Think of it as a sausage maker - you put a pig in one end and get a sausage out the other end. It's impossible to turn a sausage back into a pig. In theory, however, putting the same pig in twice will produce the same sausage. By storing the sausage, not the pig a database breach won't reveal users passwords
The "Salt" comes in to play when your database is breached. Imagine two users picked the same password. You know bob's password is MyPassword! and you can see it's stored as AB0653AAAF330. Anyone else with a password hash of AB0653AAAF330 has the same password - meaning multiple accounts could be breached at once.
If, on the other hand, there is a random string which is user-specific and appended to their password pre-hashing, Bob's password is treated as MyPassword!AAAAAAAAA which (say) hashes to 099DEF3333445 and Jane's (identical) password is treated as MyPassword!BBBBBBBBBwhich (say) hashes to AAAAE1234400.
In short, knowing Bob's password won't tell you that Jane has the same one.
This is still a hideously incomplete example. There are many other things to consider (eg the hashing algorithm. MD5 hashing is considered weak, bcrypt is intentionally computationally expensive to make brute-forcing more difficult, etc) but hopefully it will give you a rough idea to get you started.
You also want to make authentication binary - success or failure. If you tell the user "That username is invalid" (as opposed to a simple "Login Failed") as a help to them, they can use the feature to determine valid usernames by repetition.
I wholeheartedly recommend you read this article on how a company was compromised via SQL injection - once you know how it works in detail, you'll be able to see the pitfalls...
Another feature to consider is a failed login "window" - a sliding period of time in which a certain number of failed logins is allowed before that account becomes locked (possibly temporarily). This means brute-forcing passwords becomes impractical. This is usually handled by recording a FailedLoginAttempts and a FailedLoginTimeout against the user account. If a login fails, set the Timeout to now + 30 mins. If attempts > some threshold and the timeout hasn't expired, lock the account. If the login fails, extend the timeout. You get the idea.
Finally, if you're really security-conscious, when a user logs in successfully, tell them when they last logged in. If someone's been on holiday for a week and sees "Your last login was yesterday" they know instantly that something's wrong.
All the best.
On the general command on how to return a singular results from a SQL command:
Dim sqlConnection1 As New SqlConnection("Your Connection String")
Dim cmd As New SqlCommand
Dim success As Object
cmd.CommandText = "SELECT COUNT(*) FROM logins " & _
"WHERE Name = #Name " & _
"AND Passkey = #Passkey"
cmd.Parameters.AddWithValue("#Name",Me.Usernametxt.Text.Trim)
cmd.Parameters.AddWithValue("#Passkey",Me.Passwordtxt.Text.Trim)
cmd.CommandType = CommandType.Text
cmd.Connection = sqlConnection1
sqlConnection1.Open()
success = cmd.ExecuteScalar()
sqlConnection1.Close()
I am building a system using Microsoft Access 2013 that uses MySQL as a backend. I am trying to figure out a way to do some kind of basic user management, ideally in such a way that users would have to "log in" when they launch the database, and then their username would be easily accessible by the system while they are using it. I've tried searching for solutions, but most of them just tell me to use Office 365 or sharepoint, which are not options at the moment. Does anyone have an idea of how to accomplish this? Thanks in advance!
I recommend building your own user storage and login system. You'll basically need to create your own users table (in MySQL in your case), make forms to manage users, make a Login form, and write code to control the login process.
Logging in usually consists of checking some kind of credentials they type in against existing data in your users table. You can usually do this in Access with DLookup or DCount statements but I usually end up using a DAO or ADO recordset instead since I like to pull out more than one value from the User's table and I also like to write things back to it right away, like the LastLogin datetime, LastLogin computername, etc.
I actually wrote an example database which you can download here. It needs a rewrite. I've changed quite a few of my practices since Jan, 2011. But give me another year and it would need another rewrite.
I usually program the login form so that the user enters their initials and then a password. If you go this route you need to have a unique index setup on the Initials field to prevent duplicates. If you're going to have a lot of users you need to use a Username instead, which could still theoretically be the users initials.
Here's what my code would look like to authenticate a user. Be aware that this is far from truly secure. This assumes that passwords are stored in plain text. Users could theoretically try to do SQL Inject from here because I'm not use a parametrized query or stripping out special characters from their input such as # or ;.
Private Function AuthenticateUser() As Boolean
Dim sInitials As String
Dim sPassword As String
sInitials = Trim(Nz(Me.txtInitials, ""))
sPassword = Trim(Nz(Me.txtPassword, ""))
If sInitials = "" Or sPassword = "" Then
'Logging in with blank passwords is not allowed
AuthenticateUser = False
Exit Function
End If
If DCount("EmployeeID", "tblEmployees", "[Initials] = '" & Replace(sInitials, "'", "''") & "' AND Password = '" & Replace(sPassword, "'", "''") & "'", True) = 0 Then
MsgBox "Invalid Credentials."
AuthenticateUser = False
Exit Function
Else
Dim rs As New DAO.Recordset
Dim sSQL As String
sSQL = "SELECT * FROM tblEmployees WHERE initials = '" & Replace(sInitials, "'", "''") & "'"
Set rs = CurrentDb.OpenRecordset(sSQL)
If Not (rs.EOF And rs.BOF) Then
'Config is an instance of a User Defined Type. It could also be a class object.
Config.UserInitials = rs("Initials")
Config.UserFullName = rs("EmployeeName")
Config.UserID = rs("EmployeeID")
rs.Edit
rs("LastLoginDateTime") = Now()
rs("LastLoginComputer") = "Function Required to Get Computer Name"
rs("ProgVersion") = "Your Program Version Number"
rs("CurrentDbPath") = Left(CurrentProject.path & "\" & CurrentProject.Name, 254)
rs.Update
End If
rs.Close
Set rs = Nothing
AuthenticateUser = True
End If
End Function
In my applications I use a global object, in this case an instance of a User Defined Type, which I call Config. I store any kind of application runtime related settings in there for the duration of the runtime of the application. Of course this object gets destroyed when the user closes out of the application or when a code reset happens (which cannot happen in Access runtime, but does happen frequently during development). You could use a class object instead of a User Defined Type. Or you could use individual global variables for everything, which I don't recommend (that's what I used to do). A User Defined Type simply allows you to group global variables together and gives you an easy way to refer to them in your code during design time by typing in Config., which then brings up every option using Intellisense (assuming you have it enabled).
If you want your settings to survive a code reset, you need to use TempVars. TempVars became available with Access 2007. I do not use them now (contrary to my example database) because they are not strongly typed. There's no Intellisense to help you get the correct TempVar and you can technically refer to a TempVar that doesn't even exist and Access won't throw an error. I think TempVars is really just a Dictionary object with all of it's shortcomings, and the single benefit of surviving a code reset. I can imagine storing a Connection String in there, but I wonder if it's worth using TempVars for anything at all. If a code reset happens, my entire application needs to be reloaded anyway since I setup a lot of global objects and variables when the application first opens and the user first logs in.
FYI, in previous versions of Access there was user security built in. I think Microsoft discontinued that starting in 2007. I never really used it so I didn't miss it when it got discontinued.
I have a report in MS Access 2007 and I would like to send this report as pdf automatically every night without asking a user to run the macro manually or so. I have the following code in a macro:
Dim strRecipient As String
Dim strSubject As String
Dim strMessageBody As String
strRecipient = "emailaddr...#domain.com"
strSubject = "This is the email subject"
strMessageBody = "Here is a whole bunch of interesting text to accompany the attachment."
DoCmd.SendObject acSendReport, "rptNameofReport", acFormatPDF, strRecipient, , , strSubject, strMessageBody, False
In google, it says I can then name this macro as autoexec and use windows task scheduler. However, this does not work in my case becase when I run the code above, I get a security warning message from outlook saying "a program is trying to automatically send email on your behalf
do you want to allow this? if this is unexpected it may be virus and you should choose no"
I don't want this message to appear because I don't want a confirmation from a user since the plan is to send this email automatically at night. Any help will be appreciated!
Thanks in advance,
The only way I've found so far to do this is to make alterations on a user's PC on their version of MS Outlook to allow this bypass since the error is attached and run through outlook, not Access. Here is an article outlining a way to acheive this:
http://www.everythingaccess.com/tutorials.asp?ID=Outlook-Send-E-mail-Without-Security-Warning
A little in depth but does the job. Hopefully this helps! Unfortuantely because of the security model, you cannot just disable it and have to go a little further to allow this type of behavior on a user's machine. The annoying part is setting this up on each user machine if this is going to fire on each machine rather than a central place on the server somewhere.
Look at Outlook Redemption to avoid that Outlook security warning.
Also naming the macro "autoexec" means that Access will attempt to run the macro every time the database is opened. As an alternative, you could give the macro a different name, say "mcrMidniteEmail", then use the /x command-line switch in the task scheduler.
/x mcrMidniteEmail
Edit: If you have a GMail account available, you can also use VBA to send email through that account using SMTP ... without involving Outlook. See Sending Google Mail (Gmail) from MS Access, VBA, Excel, Word...
Following code always worked for me.
Sub CDO_Mail_Small_Text(Mailto, MailCC, Mailtext, Optional Subject = "", Optional Attachment = "")
Dim iMsg As Object
Dim iConf As Object
Dim strbody As String
Dim Flds As Variant
Set iMsg = CreateObject("CDO.Message")
Set iConf = CreateObject("CDO.Configuration")
iConf.Load -1 ' CDO Source Defaults
Set Flds = iConf.Fields
With Flds
.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") _
= "mail.Mybusiness.com"
.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
.Update
End With
strbody = "Enter the Email body."
With iMsg
Set .Configuration = iConf
.To = Mailto
.cc = MailCC
.BCC = ""
.From = """Do Not Reply "" <NoReply#MyBusiness.com>"
If Subject = "" Then
.Subject = "EMail Subject"
Else
.Subject = Subject
End If
.TextBody = Mailtext
If Attachment <> "" Then .AddAttachment Attachment
.send
End With
Set iConf = Nothing
Set iMsg = Nothing
End Sub
Of course you could make this code work with gmail or other email servers.
You can always use a Forms 'On Timer' event in conjunction with the 'Timer interval' to call the nightly routine that prepares and sends the email. Just include conditional code to verify that it's time to send the email. Some corporate environments lock down the Windows Task Scheduler, so this is a viable option.
I have an embarrassing question regarding Access. I can build relational databases in my sleep but I have always used PHP, mySQL and HTML to build my applications.
I've hated Access with a passion for various reasons ever since I can remember but now I'm stuck using it for a particular project.
I have a number of tables, one of which is customer, which among other things has a username and password field.
When I start the database, I want a login form to appear, which I can do using the AutoExec macro, I've made the form with a username and password field. After that, I get stuck with the logic of querying for the username/password and then showing a new form if correct or an error if not.
Could anyone help me out with making the macro and query work together?
Clarification: I am trying to do this without coding whole Visual Basic macros, if at all possible, I want to be able to do it using the macro builder thingumy.
Thanks
Given a form frmLogin, with 2 text boxes, txtUserName and txtPassword, and a command button, you can try the following
Private Sub Command0_Click()
Dim rec As Recordset2
Set rec = CurrentDb.OpenRecordset("SELECT * FROM Customer WHERE username = """ & txtUserName.Value & """ AND password = """ & txtPassword.Value & """")
If (rec.RecordCount > 0) Then
DoCmd.OpenForm "frmMain"
DoCmd.Close acForm, "frmLogin"
End If
End Sub
Malphas -
It is actually possible to do this without using VBA, but I am wondering whether the reason why you don't want to use VBA is because of the Trust issue. In which case, this won't be possible, because the macro actions Close and Quit are disallowed if the database is not trusted.
Whilst you can to run actions in the AutoExec macro beyond the point where you use the OpenForm command, I think it is neater to continue the next actions on the form itself. First because you can't really do branching in a macro; secondly because it is more modular to keep actions to do with the form actually on the form.
In the example below, my sample login form is called LoginForm, and the username text box is txtUserName, and the password text box is called txtPassword.
The first thing to do is to protect the dialogue from the simple act of letting the user close the dialogue and escape into the database design screen. The best way to do this is to set a flag called ValidLogin when the form loads. You will set this flag during the login process. When the form is closed, check whether the flag is true. If ValidLogin is false, then close the database.
On the OnLoad event of the Login form, click on the ellipsis button, and choose Macros Builder. In the Macro screen, use the following actions (note that the Condition column is hidden by default - but you'll only need for the next two macros):
Line Condition Action/Arguments
1 SetTempVar, Name = ValidLogin, Expression = False
On the OnUnload event of the Login form, do the same as above, and add:
Line Condition Action/Arguments
1 Not [TempVars]![ValidLogin]
Quit, Options = Exit.
If you run this now, as soon as you close the form, the database will close. To be useful, you need to add the following macro actions to the OnClick event of your Login button:
Line Condition Action/Arguments
1 SetTempVar, Name = Valid Login, Expression = DCount("*","Customer","[Username]=[Forms]![LoginForm]![txtUserName] And [Password]=[Forms]![LoginForm]![txtPassword]")>0
2 Not [TempVars]![ValidLogin]
MsgBox, Message = Invalid Login
3 ... StopMacro
4 OpenForm, Form Name = MainForm
5 Close, Object Type = Form, Object Name = LoginForm, Save = No
Note that in all these examples, I have used embedded macros, not named macros, so you can keep them together with the form. Also note the ellipsis (...) in the last macro, which represents the value of the last condition.
A slight tweak to the above as the code above would be open to SQL injection attacks (yes I know it is only access but it never hurts)
Public Function CheckUserPassword(strUserName As String, strPassword As String) As Boolean
Dim rst As DAO.Recordset
Set rst = DBEngine(0)(0).OpenRecordset("tblUsers", dbOpenTable)
With rst
.Index = "UserName"
.Seek "=", strUserName
If .NoMatch = False Then
If !Password = strPassword Then
CheckUserPassword = True
Else
CheckUserPassword = False
End If
Else
CheckUserPassword = False
End If
End With
rst.Close
Set rst = Nothing
End Function