send automatic mails once a week VBA - excel

I have the bellow code to send automatic mails when some elements of my database are about to end ( 2 months before the ending dates), however I want the mails to be send once a week is there possible ?
How can I do that ?
Thanks for your suggestions
Dim Desti As String, Objet As String, Corps As String, olApp As Object
Sub AlertesDatesFormations() ' Formations externes
Dim Sh As Worksheet, Chaine As String, Lig As Integer, Alerte
Set olApp = CreateObject("Outlook.application")
Lig = 15 ' car les dates de validité se trouvent en ligne 15
For Each Sh In ActiveWorkbook.Sheets
If Sh.Range("A10") = "Formation externe " Then 'Formation concernée
Col = 2 ' car la premiére date de validité en en colonne B
While Sh.Cells(Lig - 5, Col) <> "" ' on regarde toutes les formations dans la colonne A10 (15-5=10)
If Sh.Cells(Lig, Col) <> "" And Sh.Cells(Lig, Col) < Date + 60 Then ' si formation et date
' on enrichit la chaine avec nom-date-formation
Chaine = Chaine & Sh.Name & vbTab & " Date: " & Sh.Cells(Lig, Col) & " " & Sh.Cells(Lig - 1, Col) & vbCrLf
End If
Col = Col + 1
Wend
If Chaine <> "" Then Chaine = Chaine & vbCrLf
End If
Next Sh
If Chaine <> "" Then
'MsgBox(Chaine, , "Alertes sur les dates de validit?formations.")
' Alerte = MsgBox(Chaine, , "Alertes sur les dates de validitéformations.")
MsgBox Chaine, , "Alertes sur les dates de validité formations."
Desti = "annabelle.delecour#nexans.com"
Objet = "Alertes sur les dates de validité des Formations Externes "
Corps = "Bonjour, ce message est un mail automatique, il vous informe sur la fin de valdité des formations externes, Merci "
EnvoiMail Desti, Objet, Corps
End If
End Sub
Sub EnvoiMail(Desti As String, Objet As String, Corps As String)
Dim M As Object
Set M = olApp.CreateItem(olMailItem)
With M
.Subject = Objet
.Body = Corps
.Recipients.Add Desti
.cc = "pascal.deguines#nexans.com;nicolas.debeyer#nexans.com"
.send
End With
End Sub

You have a concept problem. A macro doesn't run programatically and run only if the excel is open.
The problem to solve is easy, create an automatic task in windows, this task only have to do is open your excel file, in your excel file in vba in the workbookopen event call your macro and then close your file.
STEPS
1.Modify your Excel // VBA thiswoorbook event open call your macro sendmail
2.Create an automatic task weekly in windows this task only have to open your excel file

Related

Same macro crashes in Excel 365 but works perfectly on Excel 2007

I got a macro that opens 2 workbooks and make some calculations. It works perfectly on Excel 2007 32 bit.
But in Excel 365 64 bits it crashes right after opening the first workbook, with no messages errors. Excel quits directly with no warning.
After some testing, I think it fails right after asking first workbook. The code is:
Sub PROCESO(ByVal EstasHojas As String)
Dim WBSource As Workbook
Dim WBDestiny As Workbook
Dim WKSource As Worksheet
Dim WKDestiny As Worksheet
Dim WBintermedio As Workbook
Dim WKIntermedia As Worksheet
Dim Ruta As String
Dim MiMatriz As Variant
Dim MatrizCampos As Variant
Dim LR As Long
Dim LC As Long
Dim i As Long
Dim j As Long
Dim MiF As WorksheetFunction: Set MiF = WorksheetFunction
Dim FechaPrevista As Long
Dim FechaReal As Long
Dim PagoEur As Long
Dim Proveedor As Long
Dim MatrizHojas As Variant
Dim NoHayDatos As Byte
Dim STRColor As String
Dim MatrizFinal() As Variant
Dim DictFechas As Object
Dim FechaDict As Variant
RutaCostIncomes = ""
RutaCashflow = ""
Application.Calculation = xlCalculationManual
'primero total hojas
MatrizHojas = Split(EstasHojas, "||")
With Application.FileDialog(msoFileDialogFilePicker)
.Title = "Seleccione archivo COST AND INCOMES"
.AllowMultiSelect = False
If .Show = False Then
MsgBox "No se ha seleccionado ningún archivo.", vbCritical, "PROCESO ABORTADO"
GoTo Final
Else
Ruta = .SelectedItems(1)
Set WBSource = Application.Workbooks.Open(Ruta)
DoEvents
End If
End With
Stop
'//////////////////////////////////////añadimos primero comprobación de que cada campo sea del tipo que le corresponde.
' se crean variables solo para esta comprobación y no se usarán más
Dim HayDatosMal As Boolean
Dim WKErrores As Worksheet
Dim KK As Long
KK = 3
HayDatosMal = False
For j = 0 To UBound(MatrizHojas) - 1 Step 1
Set WKSource = Nothing
Set WKSource = WBSource.Worksheets(CByte(MatrizHojas(j))) 'la posición de la hoja
LR = WKSource.Range("A" & WKSource.Rows.Count).End(xlUp).Row
Dim ZZ As Long
For ZZ = 12 To 3 Step -1
Select Case ZZ
Case 3, 4, 9, 10 'son campos de fechas
For i = 2 To LR Step 1
If IsDate(WKSource.Cells(i, ThisWorkbook.Worksheets("PANEL CONTROL").Range("C" & ZZ).Value)) = False And WKSource.Cells(i, ThisWorkbook.Worksheets("PANEL CONTROL").Range("C" & ZZ).Value) <> "" Then
HayDatosMal = True
If WKErrores Is Nothing Then Set WKErrores = Application.Workbooks.Add.ActiveSheet
With WKErrores
.Range("A1").Value = "INFORME DE ERRORES ENCONTRADOS"
.Range("A3").Value = "HOJA"
.Range("B3").Value = "FILA"
.Range("C3").Value = "CAMPO"
KK = KK + 1
.Range("A" & KK).Value = UCase(WKSource.Name)
.Range("B" & KK).Value = i
.Range("C" & KK).Value = UCase(ThisWorkbook.Worksheets("PANEL CONTROL").Range("A" & ZZ).Value)
End With
End If
Next i
Case 5, 11 'tienen que ser numéricos
For i = 2 To LR Step 1
If IsNumeric(WKSource.Cells(i, ThisWorkbook.Worksheets("PANEL CONTROL").Range("C" & ZZ).Value)) = False And WKSource.Cells(i, ThisWorkbook.Worksheets("PANEL CONTROL").Range("C" & ZZ).Value) <> "" Then
HayDatosMal = True
If WKErrores Is Nothing Then Set WKErrores = Application.Workbooks.Add.ActiveSheet
With WKErrores
.Range("A1").Value = "INFORME DE ERRORES ENCONTRADOS"
.Range("A3").Value = "HOJA"
.Range("B3").Value = "FILA"
.Range("C3").Value = "CAMPO"
KK = KK + 1
.Range("A" & KK).Value = UCase(WKSource.Name)
.Range("B" & KK).Value = i
.Range("C" & KK).Value = UCase(ThisWorkbook.Worksheets("PANEL CONTROL").Range("A" & ZZ).Value)
End With
End If
Next i
Case Else 'son textos o están vacíos, no hacemos nada
DoEvents
End Select
Next ZZ
Next j
If HayDatosMal = True Then
'hay que abortar proceso
WBSource.Close False
WKErrores.Activate
WKErrores.Columns("A:C").EntireColumn.AutoFit
Set WKErrores = Nothing
MsgBox "Se cancela el proceso porque se han encontrado errores en los datos de origen. Se ha generado un informe de errores para consultar.", vbCritical, "PROCESO CANCELADO"
GoTo Final
End If
DoEvents
'////////////////////// fin comprobación
'compruebo que los campos coincida con mis datos del configurador
MatrizCampos = ThisWorkbook.Worksheets("PANEL CONTROL").Range("A2").CurrentRegion.Value
'compruebo todas las hojas
For j = 0 To UBound(MatrizHojas) - 1 Step 1
Set WKSource = Nothing
Set WKSource = WBSource.Worksheets(CByte(MatrizHojas(j))) 'la posición de la hoja
With WKSource
'los campos empiezan en la fila 2 de los datos de la matriz de campos
'comprobamos que en source estén con el mismo nombre en su posición
For i = 2 To UBound(MatrizCampos) Step 1
If MiF.CountIf(.Rows(1), MatrizCampos(i, 1)) = 0 Then
'el campo no está presente. Abortamos
MsgBox "El campo " & UCase(MatrizCampos(i, 1)) & " no está en la hoja " & WKSource.Index & " de COST AND INCOMES", vbCritical, "PROCESO ABORTADO"
WBSource.Close False
GoTo Final
Else
'compruebo que esté en su posición
LR = MiF.Match(MatrizCampos(i, 1), .Rows(1), 0)
If LR <> MatrizCampos(i, 3) Then
'no está donde marca el PANEL CONTROL
MsgBox "El campo " & UCase(MatrizCampos(i, 1)) & " no está en la posición que marca PANEL CONTROL en la hoja " & WKSource.Index & " de COST AND INCOMES", vbCritical, "PROCESO ABORTADO"
WBSource.Close False
GoTo Final
End If
End If
Next i
End With
Next j
Set WKSource = Nothing
'también comprobamos los campos de ingresos
MatrizCampos = ThisWorkbook.Worksheets("PANEL CONTROL").Range("A8").CurrentRegion.Value
'compruebo todas las hojas
For j = 0 To UBound(MatrizHojas) - 1 Step 1
Set WKSource = Nothing
Set WKSource = WBSource.Worksheets(CByte(MatrizHojas(j))) 'la posición de la hoja
With WKSource
'los campos empiezan en la fila 2 de los datos de la matriz de campos
'comprobamos que en source estén con el mismo nombre en su posición
For i = 2 To UBound(MatrizCampos) Step 1
If MiF.CountIf(.Rows(1), MatrizCampos(i, 1)) = 0 Then
'el campo no está presente. Abortamos
MsgBox "El campo " & UCase(MatrizCampos(i, 1)) & " no está en la hoja " & WKSource.Index & " de COST AND INCOMES", vbCritical, "PROCESO ABORTADO"
WBSource.Close False
GoTo Final
Else
'compruebo que esté en su posición
LR = MiF.Match(MatrizCampos(i, 1), .Rows(1), 0)
If LR <> MatrizCampos(i, 3) Then
'no está donde marca el PANEL CONTROL
MsgBox "El campo " & UCase(MatrizCampos(i, 1)) & " no está en la posición que marca PANEL CONTROL en la hoja " & WKSource.Index & " de COST AND INCOMES", vbCritical, "PROCESO ABORTADO"
WBSource.Close False
GoTo Final
End If
End If
Next i
End With
Next j
Set WKSource = Nothing
MatrizCampos = ThisWorkbook.Worksheets("PANEL CONTROL").Range("A2").CurrentRegion.Value
'the code never reachs this part when it crashes
EstasHojas is just a string that contains text like 1|2|
I've read this but could not find a solution.
VBA force closes Excel 365 but works fine in Excel 2019
64-bit Excel 365 crashes, 32-bit Excel 365 works fine
Also tried adding DoEvents right after opening the workbook with no luck.
No add-ins involved at all.
Now comes the funny part. If I add a Stop command right after opening the first workbook, and then VBa stops there, I press F5 so macro keeps going, everything works perfect!
With Application.FileDialog(msoFileDialogFilePicker)
.Title = "Seleccione archivo COST AND INCOMES"
.AllowMultiSelect = False
If .Show = False Then
MsgBox "No se ha seleccionado ningún archivo.", vbCritical, "PROCESO ABORTADO"
GoTo Final
Else
Ruta = .SelectedItems(1)
Set WBSource = Application.Workbooks.Open(Ruta)
DoEvents
End If
End With
Stop 'this fixes everything
So if I try to execute all at once, it crashed with no errors. But if I force it to make a break and then continue, it works.
I would like to know why adding the Stop makes the code works perfectly on Eccel 365 but without it it crashed and closes Excel with no errors. Tried DoEvents as I said, but it did not help in this case.
By the way, the workbooks opened are just data in XLSX files, no other macros or events. Just this code. I can post the full code if needed but it's really long.
Thanks in advance.

Userform doesn't fill the sheet I want it to

I'm new at VBA and would like to ask for help.
I have a userform that is supposed to help me fill a range in my worksheet.
It was working fine when I made it a few days ago.
Now though, it only adds an empty row to my table and I don't know where to look to find the error. I've been at it for a couple of hours now..
Any help would be appreciated.
This is my code :
Private Sub CommandButton2_Click()
Dim dl As Integer
Dim list_num As Integer
Dim ligne As Integer
list_num = Me.liste_com3.ListCount - 1
If Me.liste_com3.ListCount > 0 Then 'contrôl si la liste n'est pas vide
If MsgBox("Voulez-vous enregistrer cette transaction ?", vbYesNo) = vbYes Then
For ligne = 0 To list_num
'ajouter nouvelle ligne dans le tableau
Sheets(8).ListObjects(1).ListRows.Add
'chercher numéro prochaine ligne du tableau
dl = Sheets(8).Range("b9999").End(xlUp).Row
'ajouter les infos dans la bdd
Sheets(8).Range("Z" & dl) = Me.info1
Sheets(8).Range("C" & dl) = Format(Me.txt_fac3, """FAC-""00000")
Sheets(8).Range("D" & dl) = Format(CDate(Now()), "dd/mm/yyyy hh:mm:ss")
Sheets(8).Range("E" & dl) = Me.cbx_com
'controler si c'est un fournisseur ou un client
If Me.label_type = "Fournisseur :" Then
Sheets(8).Range("F" & dl) = Me.cbx_type3
Else
Sheets(8).Range("G" & dl) = Me.cbx_type3
End If
'ajouter les données de la zone de liste
Sheets(8).Range("H" & dl) = Me.liste_com3.List(ligne, 0)
Sheets(8).Range("J" & dl) = Int(Me.liste_com3.List(ligne, 1))
Next ligne
MsgBox "Enregistrement réussi !"
Unload Me
ThisWorkbook.Save
End If
End If
Sheets(8).Range("K6:N9999").NumberFormat = "#,##0 [$XOF]"
End Sub
Private Sub liste_com3_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
If Me.liste_com3.ListIndex >= 0 Then
If MsgBox("Voulez-vous supprimer cette entrée ?", vbYesNo) = vbYes Then
Me.liste_com3.RemoveItem Me.liste_com3.ListIndex
memo = memo - 1
End If
End If
End Sub
Private Sub opt_in_Click()
Me.label_type = "Fournisseur :"
Me.cbx_type3.RowSource = "col_fourni"
Me.cbx_com.RowSource = "col_comm"
Me.info1 = "Entrée"
End Sub
Private Sub opt_out_Click()
Me.label_type = "Client :"
Me.cbx_type3.RowSource = "col_clients"
Me.cbx_com.RowSource = ""
Me.info1 = "Sortie"
End Sub
Private Sub txt_fac3_Change()
If Not IsNumeric(txt_fac3) And txt_fac3 <> "" Then
MsgBox "Veuillez entrer un nombre..."
Me.txt_fac3 = ""
End If
End Sub
Private Sub txt_num3_Change()
'controle si numérique
If Not IsNumeric(txt_num3) And txt_num3 <> "" Then
MsgBox "Veuillez entrer un nombre..."
Me.txt_num3 = ""
End If
End Sub
Private Sub UserForm_Initialize()
Me.label_info_3.Caption = "Mouvements"
End Sub
Also the weird thing is that when I change the sheet that the userform is supposed to write to, it works. But not on the sheet I want.
This is a picture of my userform
Thanks in advance !

compare between two excel files with a macro

I need to compare between two excel files. i have data of Week S-1 and i need to know what are the transformations that happened to my data in the next week S1.
my data is grouped with a called " Code fonction"
For each "Code fonction" i need to know what are the transformations ( modifications, rows added or deleted rows).
i tried to make a code i worked with the logic to compare row per row, and i made the condition that the "code fonction" must be the same in the rows compared. but the code didn't give me the results that i wanted.
Option Explicit
Private Sub CommandButton1_Click()
Dim strRepFicA As String, strRepFicB As String
Dim wbFicA As Workbook, wbFicB As Workbook, wbFicAna As Workbook
Dim wsFicA As Worksheet, wsFicB As Worksheet, wsFicAna As Worksheet
Dim lgLig As Long, lgCol As Long
Dim lgLigDeb As Long
' Répertoire et Fichier
strRepFicA = ThisWorkbook.Path & "\" & "S-1.xlsx"
strRepFicB = ThisWorkbook.Path & "\" & "S1.xlsx"
' Classeur d'analyse
Set wbFicAna = ThisWorkbook
Set wsFicAna = wbFicAna.ActiveSheet
' Vérifier que les fichiers A et B se trouvent dans le répertoire
If Dir(strRepFicA) = "" Or Dir(strRepFicB) = "" Then
MsgBox "Le fichier A et/ou le fichier B sont introuvables", vbCritical + vbOKOnly, "Problème de fichiers..."
Exit Sub
End If
Application.ScreenUpdating = False
' Ouverture du fichier A et définition de la feuille de traitement
Set wbFicA = Workbooks.Open(Filename:=ThisWorkbook.Path & "\" & "S-1.xlsx")
Set wsFicA = wbFicA.Worksheets("Sheet1")
' Ouverture du fichier B et définition de la feuille de traitement
Set wbFicB = Workbooks.Open(Filename:=ThisWorkbook.Path & "\" & "S1.xlsx")
Set wsFicB = wbFicB.Worksheets("Sheet1")
' Vider les lignes du fichier d'analyse
wsFicAna.Range("A2:U" & Cells.Rows.Count).ClearContents
' Première ligne d'affichage des résultats dans le fichier d'analyse
lgLigDeb = 2
' Traitement des lignes des 2 fichiers
' Lignes : 2 à 1250
For lgLig = 2 To 7000
' Colonnes : D à AO
' Une différence est trouvée dans une ligne
'wsFicA.Cells(lgLig, lgCol).Value <> wsFicB.Cells(lgLig, lgCol).Value
If wsFicA.Cells(lgLig, 9).Value <> wsFicB.Cells(lgLig, 9).Value And wsFicA.Cells(lgLig, 5).Value = wsFicB.Cells(lgLig, 5).Value Then
' Affichage du nom du fichier en colonne A
wsFicAna.Range("A" & lgLigDeb).Value = wbFicA.Name
' Copier la ligne du fichier A dans le fichier d'analyse
wsFicA.Range("B" & lgLig & ":" & "U" & lgLig).Copy _
Destination:=wsFicAna.Range("B" & lgLigDeb)
' Affichage du nom du fichier en colonne A
wsFicAna.Range("A" & lgLigDeb + 1).Value = wbFicB.Name
' Copier la ligne du fichier B dans le fichier d'analyse
wsFicB.Range("B" & lgLig & ":" & "U" & lgLig).Copy _
Destination:=wsFicAna.Range("B" & lgLigDeb + 1)
lgLigDeb = lgLigDeb + 2
End If
Next lgLig
' Fermer les fichiers A et B
wbFicA.Close savechanges:=False
wbFicB.Close savechanges:=False
MsgBox "Traitement terminé"
Application.ScreenUpdating = True
End Sub

Error in updating UDF in Excel in non active sheets

I have created some UDF to automate some calculus me and some coworkers use regularly.
For the sake of simplicity I paste a MWE of what I have a problem with, my actual code is longer, but takes the same input, a range of cells with one of the dimensions being equal to one (so one line or one column)
Public Function Test(Donnees As Range)
Dim Nombre_Cellules, Temp As Double
Dim Format_Donnees As String
Temp = 0
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' '
' Parametres utiles generaux '
' '
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Nb_Lignes = Donnees.Rows.Count
Nb_Colonnes = Donnees.Columns.Count
Premiere_Ligne = Donnees.Row
Premiere_Colonne = Donnees.Column
Derniere_Ligne = Donnees.Row + Nb_Lignes - 1
Derniere_Colonne = Donnees.Column + Nb_Colonnes - 1
'On definit la frequence et la taille associee
If Nb_Lignes = 1 Then
Format_Donnees = "Colonnes"
Nombre_Cellules = Nb_Colonnes
End If
If Nb_Colonnes = 1 Then
Format_Donnees = "Lignes"
Nombre_Cellules = Nb_Lignes
End If
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' '
' Verifications des parametres et messages d'erreurs '
' '
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'On verifie que la plage renseignée est soit sur une seule ligne soit sur une seule colonne
If (Nb_Lignes <> 1 And Nb_Colonnes <> 1) Then
MsgBox _
"La plage de données considérée est incorrecte, il ne peut s'agir que " & vbNewLine & _
Chr(149) & " de données sur une seule ligne ou " & vbNewLine & _
Chr(149) & " de données sur une seule colonne" _
, , "Parametres incorrects"
Test = CVErr(xlErrRef)
Exit Function
End If
'On verifie que toute la période qui sert au calcul contient bien des valeurs numériques et ne contient pas de valeurs vides
If Format_Donnees = "Lignes" Then
For i = 0 To Nombre_Cellules - 1
If Not IsNumeric(Cells(Premiere_Ligne + i, Premiere_Colonne).Value) Then
MsgBox _
"La plage de donnée considérée est incorrecte" & vbNewLine & _
"Toutes les cellules nécessaires au calcul dans la colonne ne sont pas numériques" _
, , "Parametres incorrects"
Test = CVErr(xlErrRef)
Exit Function
End If
If (Cells(Premiere_Ligne + i, Premiere_Colonne).Value = "") Then
MsgBox _
"La plage de donnée considérée est incorrecte" & vbNewLine & _
"Une cellule de la colonne considérée est vide et semble avoir une valeur manquante" _
, , "Parametres incorrects"
Test = CVErr(xlErrRef)
Exit Function
End If
Next
End If
If Format_Donnees = "Colonnes" Then
For i = 0 To Nombre_Cellules - 1
If Not IsNumeric(Cells(Premiere_Ligne, Premiere_Colonne + i).Value) Then
MsgBox _
"La plage de donnée considérée est incorrecte" & vbNewLine & _
"Toutes les cellules nécessaires au calcul dans la ligne ne sont pas numériques" _
, , "Parametres incorrects"
Test = CVErr(xlErrRef)
Exit Function
End If
If (Cells(Premiere_Ligne, Premiere_Colonne + i).Value = "") Then
MsgBox _
"La plage de donnée considérée est incorrecte" & vbNewLine & _
"Une cellule de la ligne considérée est vide et semble avoir une valeur manquante" _
, , "Parametres incorrects"
Test = CVErr(xlErrRef)
Exit Function
End If
Next
End If
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' '
' Calculs a proprement parler '
' '
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
If Format_Donnees = "Lignes" Then
For i = 0 To Nombre_Cellules - 1
Temp = Temp + Cells(Premiere_Ligne + i, Premiere_Colonne).Value
Next
End If
If Format_Donnees = "Colonnes" Then
For i = 0 To Nombre_Cellules - 1
Temp = Temp + Cells(Premiere_Ligne, Premiere_Colonne + i).Value
Next
End If
Test = Temp
End Function
So as I will not be the sole user of this function, I have tried to include several checks and error messages.
One is to check if the range selected has any empty values and any non numerical value.
Now my functions work (at least they calculate the thing I want them to), but I have some troubles with how they update.
Note that I have pushed against user preferences so that all the value used are included in the range passed in input.
One of the issues I have been able to reproduce with this code is that If I use this function on several sheets of one workbook (so one Test() in worksheet1, and one Test() in worksheet2, and for one reason try to update the whole workbook (e.g. via Ctrl + alt + shift + F9), then I will get one warning I have set up ("Une cellule de la ligne considérée est vide et semble avoir une valeur manquante") in a non active sheet.
Can someone explain me one ?
You are using Cells() with no sheet qualification. This means that it refers to whatever the active sheet happens to be. So it won't work correctly unless all the calls to your UDF are on the currently active sheet
You need to change this to Donnees.Cells( ) and change the Cell indexes to refer to the cells within Donnees rather than cells within the whole sheet

Sending Multiple emails with Different Attachments

I am trying to send emails to a list of recipients in an Excel spreadsheet, with a different attachment for each of the emails.
I created a macro that generates the different emails, but when I added attachments, only the first email of the list is created with the correct attachment.
When the loop comes back to the second email it gives me an error message saying that the attachment was not found (I assume this is for the second message).
I checked and the file names and paths are correct according to the rules I set in the code. It doesn't create a draft of the second email, but simply tells me the file was not found.
How can I generate all of the emails with their proper attachments?
The code is as follows:
Sub clientemails()
Dim pfolio As String
Dim destino As String
Dim mo As String
Dim text As String
Dim subject As String
Dim CC As String
Dim signature As String
Dim officer As String
Dim yr As String
Dim date1 As String
Dim position As String
Dim analysis As String
Dim activities As String
Dim nl As Integer
Dim i As Integer
Dim OutlookApp As Outlook.Application
Dim MItem As Outlook.mailitem
Set OutlookApp = New Outlook.Application
nl = Cells(5, 1).End(xlDown).Row
i = 5
yr = Cells(1, 6).Value
date1 = Format(Cells(1, 4).Value, "mm.dd.yy")
While nl + 1 > i
pfolio = Cells(i, 2).Value
destino = Cells(i, 3).Value
officer = Cells(i, 10).Value
CC = Cells(i, 11).Value
Set MItem = OutlookApp.CreateItem(olmailitem)
If Cells(i, 9) = "P" Then
mo = Cells(1, 3)
subject = "Posição e Análise " & pfolio
text = "<p><font face=arial size=3>Bom Dia,</p>" _
& "<p>Segue em anexo a posição e análise da carteira " & pfolio & " referente ao mês de " & mo & ". Caso tenha quaisquer dúvidas, favor entrar em contato conosco.</p>" _
& "Atenciosamente,"
ElseIf Cells(i, 9) = "E" Then
month = Cells(2, 3)
subject = pfolio & " Statement and Analysis"
text = "<p><font face=arial size=3>Hello,</p>" _
& "<p>Please find attached the portfolio statement and analysis for the " & pfolio & " portfolio for the month of " & mo & ". Should you have any questions, please don't hesitate to contact us.</p>" _
& "Sincerely,"
End If
If Cells(i, 4) = "X" Then
position = "F:\Files\General Folders\3 Clients\" & officer & "\" & pfolio & "\Position\" & yr & "\" & pfolio & " Portfolio Statement Summary " & date1 & ".pdf"
With MItem
.Attachments.Add position
End With
End If
If Cells(i, 5) = "X" Then
analysis = "F:\Files\General Folders\3 Clients\" & officer & "\" & pfolio & "\Portfolio Analysis\" & yr & "\" & pfolio & " Portfolio Analysis " & date1 & ".pdf"
With MItem
.Attachments.Add analysis
End With
End If
If Cells(i, 6) = "X" Then
activities = "F:\Files\General Folders\3 Clients\" & officer & "\" & pfolio & "\Portfolio Activities\" & yr & "\" & pfolio & " Portfolio Activities " & date1 & ".pdf"
With MItem
.Attachments.Add activities
End With
End If
With MItem
.Display
End With
signature = MItem.HTMLBody
With MItem
.subject = subject
.To = destino
.CC = CC
.HTMLBody = text & signature
.Save
End With
i = i + 1
Wend
End Sub
I understand you are supposed to save your mail item before adding attachments. So you might need
MItem.SaveAs('some path name', olTXT)
before you add each attachment.
EDIT: Or perhaps it's best to simply use
MItem.Save
I recently make an library in order to send email with macros VBA. I use Microsoft CDO technology to do it, so it doesn't depend on client messagerie like Outlook or Thunderbird. It's only depend on SMTP server.
Enjoy.
'---------------------------------------------------------------------------
' Constantes and global variables
'---------------------------------------------------------------------------
Const CONFIG_ACTIVE_SEND_EMAIL = True
Const CONFIG_SMTP_SERVER As String = "smtp.host"
Const CONFIG_SMTP_PORT As String = "25"
Const CONFIG_SMTP_AUTHENTICATE = "0"
Const CONFIG_SMTP_USERNAME = "20100"
Const CONFIG_SMTP_PASSWORD = "seeyousoon"
Const CONFIG_SMTP_SSL = "false"
Dim SEND_TRACE_ACTIVATE As Boolean
Dim SEND_TRACE_EMAIL As String
Global LOG_ACTIVATE As Boolean
Global LOG_FILEPATH As String
'---------------------------------------------------------------------------
' Pour envoyer un email avec un serveur SMTP avec la technologie CDO.Message
'---------------------------------------------------------------------------
' #param String expediteur : l'expediteur de l'email
' #param String destinataires : le ou les destinataires de l'email (ex: "email1#aot.org; email2#aot.org")
' #param String sujet : le sujet de l'email
' #param String body : le contenu du message de l'email (ex: "Hello" & vbNewLine & "See you soon")
' #param Optional String carbon_copy : Addresse(s) pour Carbon-Copy (envoyer un email à plusieurs personnes)
' #param Optional String blind_carbon_copy : Idem que carbon copy sauf que dans l'en-tête la liste des personnes en copie est cachée.
' #param Optional Variant fichiers_joints : String ou Array(String) de chemin de fichiers à joindre dans l'email (max:8mo en tout)
' #param Optional Boolean opt_sendTraceActivate : pour activer la récuperation de la trace d'envoi (par défaut envoi à l'expediteur de l'email). Active forcement l'option opt_logActivate = true.
' #param Optional String opt_sendTraceEmail : pour changer l'email de reception de la trace d'envoi (par défaut envoi à l'expediteur de l'email)
' #param Optional Boolean opt_logActivate : pour activer le log
' #param Optional String opt_logFilePath : pour changer le chemin du fichier log (par defaut = Application.ActiveWorkbook.Path & "log.txt")
'---------------------------------------------------------------------------
Sub sendEmail( _
expediteur As String, _
destinataires As String, _
sujet As String, _
body As String, _
Optional carbon_copy As String = "", _
Optional blind_carbon_copy As String = "", _
Optional fichiers_joints As Variant, _
Optional opt_sendTraceActivate As Boolean = False, _
Optional opt_sendTraceEmail As String = "", _
Optional opt_logActivate As Boolean = False, _
Optional opt_logFilePath As String = "")
'-- Gestion option sendTrace
If opt_sendTraceActivate = True Then
opt_logActivate = True
SEND_TRACE_ACTIVATE = True
SEND_TRACE_EMAIL = expediteur
If Not opt_sendTraceEmail = "" Then
SEND_TRACE_EMAIL = opt_sendTraceEmail
End If
End If
'-- Gestion option log
If opt_logActivate = True Then
LOG_ACTIVATE = True
LOG_FILEPATH = Application.ActiveWorkbook.Path & "\log_email.txt"
If Not opt_logFilePath = "" Then
LOG_FILEPATH = opt_logFilePath
End If
LogFileDelete
End If
'-- Gestion option carbon_copy et blind_carbon_copy pour affichage dans le debug
Dim carbon_copy_str As String
Dim blind_carbon_copy_str As String
If carbon_copy = "" Then
carbon_copy_str = "#vide#"
End If
If blind_carbon_copy = "" Then
blind_carbon_copy_str = "#vide#"
End If
'-- Log du traitement (ne fonctionne que si l'option opt_logActivate est à true
date_now = Now()
LogInformation " "
If CONFIG_ACTIVE_SEND_EMAIL = True Then
LogInformation "---[ DEBUT DU TRAITEMENT]---"
Else
LogInformation "---[ DEBUT DU TRAITEMENT (mode simulation) ]---"
End If
LogInformation " _________________________________"
LogInformation " "
LogInformation " PADI-Excel email (v0.0.1) "
LogInformation " _________________________________"
LogInformation " "
LogInformation " * Informations *"
LogInformation " -> Macro du classeur = " & ThisWorkbook.Name
LogInformation " -> Utilisateur = " & Application.UserName
LogInformation " -> Debut traitement = " & Format(date_now, "yyyy-mm-dd hh:mm:ss")
LogInformation " -> SMTP Server = " & CONFIG_SMTP_SERVER
LogInformation " -> SMTP Port = " & CONFIG_SMTP_PORT
LogInformation " -> SMTP Username = " & CONFIG_SMTP_USERNAME
LogInformation " -> SMTP SSL = " & CONFIG_SMTP_SSL
LogInformation " -> Option sendTrace = " & SEND_TRACE_ACTIVATE
LogInformation " -> Email sendTrace = " & SEND_TRACE_EMAIL
LogInformation " "
LogInformation " * Propriétés email *"
LogInformation " -> Expediteur = " & expediteur
LogInformation " -> Destinataires = " & destinataires
LogInformation " -> Subject eMail = " & sujet
LogInformation " -> Carbon copy = " & carbon_copy_str
LogInformation " -> Blind CC = " & blind_carbon_copy_str
LogInformation " "
LogInformation " -> Body eMail:"
LogInformation "{{-------------------------------}}"
LogInformation body
LogInformation "{{-------------------------------}}"
LogInformation " "
'-- Création de l'objet CDO (pour créer en envoyer l'email + headers email)
Dim objCDO As Object
Set objCDO = CreateObject("CDO.Message")
'-- Configuration du serveur SMTP
With objCDO.Configuration.Fields
.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = CONFIG_SMTP_SERVER
.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = CONFIG_SMTP_PORT
.Item("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = CONFIG_SMTP_AUTHENTICATE
.Item("http://schemas.microsoft.com/cdo/configuration/smtpusessl") = CONFIG_SMTP_SSL
End With
'-- Configuration authentification SMTP (si nécessaire)
If CONFIG_SMTP_AUTHENTICATE = "1" Then
With objCDO.Configuration.Fields
.Item("http://schemas.microsoft.com/cdo/configuration/sendusername") = CONFIG_SMTP_USERNAME
.Item("http://schemas.microsoft.com/cdo/configuration/sendpassword") = CONFIG_SMTP_PASSWORD
End With
End If
'-- Mise à jour de la configuration CDO
objCDO.Configuration.Fields.Update
'-- Création du mail à envoyer
With objCDO
.From = expediteur
.To = destinataires
.Subject = sujet
.TextBody = body
.Cc = carbon_copy
.Bcc = blind_carbon_copy
End With
'-- Création du mail à envoyer : ajout de 1 ou plusieurs fichiers joint (si renseigné)
LogInformation " -> fichiers joints:"
LogInformation "{{-------------------------------}}"
If Not IsMissing(fichiers_joints) Then
If IsArray(fichiers_joints) Then
For i = LBound(fichiers_joints) To UBound(fichiers_joints)
objCDO.AddAttachment fichiers_joints ' "C:\temp\Bon de commande.pdf"
Next i
Else
LogInformation " 1 fichiers joints :"
LogInformation " -> " & fichiers_joints
objCDO.AddAttachment fichiers_joints ' "C:\temp\Bon de commande.pdf"
End If
Else
LogInformation "#aucun#"
End If
LogInformation "{{-------------------------------}}"
'-- Envoi de l'email
If CONFIG_ACTIVE_SEND_EMAIL = True Then
objCDO.Send
End If
LogInformation " "
LogInformation " -> email envoyé à " & Format(date_now, "yyyy-mm-dd hh:mm:ss")
LogInformation " "
LogInformation "---[ FIN DU TRAITEMENT]---"
LogInformation " "
End Sub
'----------------------------------------------------
' Ajoute un message dans le fichier log déclaré dans la variable LOG_FILEPATH
' uniquement si le log est activé avec la variable LOG_ACTIVATE
'----------------------------------------------------
' #param String logMessage : le message à ajouter dans le fichier log
'----------------------------------------------------
Sub LogInformation(logMessage As String)
If LOG_ACTIVATE = True Then
Dim FileNum As Integer
FileNum = FreeFile ' next file number
Open LOG_FILEPATH For Append As #FileNum ' creates the file if it doesn't exist
Print #FileNum, logMessage ' write information at the end of the text file
Close #FileNum ' close the file
End If
End Sub
'----------------------------------------------------
' Pour supprimer le fichier log en début de traitement
'----------------------------------------------------
Sub LogFileDelete()
On Error Resume Next ' ignore possible errors
Kill LOG_FILEPATH ' delete the file if it exists and it is possible
On Error GoTo 0 ' break on errors
End Sub
'----------------------------------------------------
' Fonction pour tester si un repertoire (ex: c:\test\foo\) existe
'----------------------------------------------------
' #param String folderPath : le répertoire à tester
' #return Boolean (true, le répertoire existe et false sinon)
'----------------------------------------------------
Function is_folder_exist(folderPath As String) As Boolean
Dim FSO As Object
Set FSO = CreateObject("scripting.filesystemobject")
If Right(folderPath, 1) <> "\" Then
folderPath = folderPath & "\"
End If
If FSO.FolderExists(folderPath) = False Then
is_folder_exist = False
Else
is_folder_exist = True
End If
End Function
'----------------------------------------------------
' Fonction pour tester si un fichier (ex: c:\test\foo\sample.txt) existe
'----------------------------------------------------
' #param String filePath : le fichier à tester
' #return Boolean (true, le répertoire existe et false sinon)
'----------------------------------------------------
Function is_file_exist(filePath As String) As Boolean
Dim FSO As Object
Set FSO = CreateObject("scripting.filesystemobject")
If FSO.FileExists(filePath) = False Then
is_file_exist = False
Else
is_file_exist = True
End If
End Function
Example of use it :
sendEmail _
"from_email#acme.com", _
"to_email#acme.com", _
"Subjet", _
"Hello," & vbCrLf & "rfxc", _
opt_logActivate:=True, _
fichiers_joints:="c:\test1.pdf"

Resources