Send SMTP Email via Gmail

Just helped someone get this sorted and thought i woulds share. The trick was to allow Less Secure apps in your account. Here is the link

https://myaccount.google.com/lesssecureapps?pli=1

Turn it on and then just use the normal CDO code.

Dim oEmail, sEmailFrom, sEmailTo, sEmailSubject, sEmailBody,sBCC

' Send email

sEmailTo = "admin@SmarterDimensions.com"
sEmailFrom = "FromAddress@SmarterDimensions.com"
sEmailSubject = "Email Subject"
sEmailBody = "Email Body this is bold"
sBCC = "SmarterDimensions@live.com.au"


Set oEmail      = CreateObject("CDO.Message")

With oEmail
 With .Configuration.Fields
  .Item["http://schemas.microsoft.com/cdo/configuration/smtpusessl"] = True
  .Item["http://schemas.microsoft.com/cdo/configuration/smtpserver"] = "smtp.gmail.com"
  .Item["http://schemas.microsoft.com/cdo/configuration/sendusing"] = 2
  .Item["http://schemas.microsoft.com/cdo/configuration/sendusername"] = "douglas.porton@gmail.com"
  .Item["http://schemas.microsoft.com/cdo/configuration/sendpassword"] = "xxxxxxxxxxx"	
  .Item["http://schemas.microsoft.com/cdo/configuration/smtpserverport"] = 465
  .Item["http://schemas.microsoft.com/cdo/configuration/smtpconnectiontimeout"] = 60
  .Item["http://schemas.microsoft.com/cdo/configuration/smtpauthenticate"] = 1	
  .Update()		
 End With
End With	

    
oEmail.From     = sEmailFrom
oEmail.To       = sEmailTo
oEmail.BCC      = sBCC
oEmail.Subject  = sEmailSubject
oEmail.HTMLBody = sEmailBody

oEmail.Send()

set oEmail = Null

Open SAV file and create an XL map of contents

Needed to write some code today to open up an Statistics sav file and create an XL map file of the metadata. The next step in the process will be to write a IBM Statistics syntax script to change the content in the SAV file based on the information in the XL file. Here is the first bit of the code , lets call it stage 1. If you want stage 2 let me know.

So lets declare some vars

CONST sDataPath = "c:\temp"
CONST sDataName = "IBV_A069_EPH.sav"
CONST sXLMap = "SAV_MAP.xls"


Dim oDSCs, oDSC, oMDM, oVar,oElement
Dim oExcel,oSheet

Dim iVarCount,iRow

And now lets go ahead and open up the SAV file using the Statistics DCS

Set oDSCs = CreateObject("MRDSCReg.Components")
Set oDSC = oDSCs["mrSavDsc"]
Set oMDM = oDSC.Metadata.Open(sDataPath + sDataName)

Then lets open up our XL file. I am assuming it is already made and contains a tab called Labels in it. We are also going to give the columns some names

Set oSheet = oExcel.Sheets["Labels"]
oSheet.Activate()
	
iRow = 1
oSheet.cells[1][1].value = "Variable Name"
oSheet.cells[1][2].value = "New Variable Name"
oSheet.cells[1][3].value = "Type"
oSheet.cells[1][4].value = "Value"
oSheet.cells[1][5].value = "Label"

Next we need to loop the variables in the file outputting the data we find to the relevant places in our sheet

	for iVarCount = 0 to oMDM.Variables.count-1
		iRow = iRow + 1
		Set oVar = oMDM.Variables[iVarCount]	
		
			oSheet.cells[iRow][1].value = oVar.Name
			oSheet.cells[iRow][3].value = "L"
			oSheet.cells[iRow][5].value = TextOnly(oVar.Label)
		
			oSheet.cells[iRow][6].value = oVar.dataType

			For each oElement in oVar.Elements.Elements
	   			iRow = iRow + 1
	   			oSheet.cells[iRow][3].value = "C"
	   			oSheet.cells[iRow][4].value = oElement.Element.NativeValue
	   			oSheet.cells[iRow][5].value = TextOnly(oElement.Label)	   			
   			Next
   		
	Next
oMDM.Close()	

and finally we need out TextOnly Function. This will allow us to strip out HTML codes that may be in our labels

Function TextOnly(sValue)
	Dim sNew,iCount,bStart
	For iCount = 0 to len(sValue) -1
		
		if ( find( "<",mid(sValue,iCount,1) )>-1 ) then 
			bStart = true
		End if
			
		if ( bStart = true ) Then
		else
			sNew = sNew + mid(sValue,iCount,1)
		End If
			
		if ( find( ">",mid(sValue,iCount,1) )>-1 ) then 
			bStart = false
		End if
	Next
	TextOnly = sNew
End Function

JustCode : Delete a vdata record

This sub shows us how to delete a vdata record from a survey. It has 3 parms that you need to pass in. You will also need to enter a valid SQL user login & password.

 
' sSurvey: The name of the survey that is to be used.
' sID: The Respondent.serial that is to be deleted.
' sServer: The name or the IP of the SQL server
' ****************************************
' Designed by : Smarter Dimensions
' Last Updated : 8th July 2009
' Sub to delete a vdata record
' ****************************************

Sub DeleteVDataRecord(sSurvey,sID,sServer)

    Set oConnection = CreateObject("ADODB.Connection")

    oConnection.ConnectionString = "Provider=mrOleDB.Provider.2;Data Source=mrRdbDsc2;Location='Provider=SQLOLEDB.1;Password=XXXXXX;Persist Security Info=True;User ID=XX;Initial Catalog=" + sSurvey + ";Data Source=" + sServer + "';Initial Catalog=\" + sServer + "SPSSMR_FMROOTMaster" + sSurvey + "" + sSurvey + ".mdd;MR Init Project=" + sSurvey

    oConnection.Open()

    If ( oConnection.State = 1 ) Then

        Set oRecordset = CreateObject("ADODB.Recordset")
        oRecordset.Open("DELETE FROM VDATA WHERE Respondent.serial = '" + sID + "'" ,oConnection,3,1)
        Set oRecordset = Null

    End If

    oConnection.Close()
    Set oConnection = Null

End Sub

JustCode : Responses under radio buttons

This code shows us how to have our responses under the radio buttons.

Metadata(en-AU, Question, Label)
    Q1 "This is a Rating Scale {ReplaceMe}"
        categorical [1..1]
        {
        _1 "1 Poor",
        _2 "2",
        _3 "3",
        _4 "4",
        _5 "5 Good"
    };

End Metadata

And now for the routing.


Routing(Web)

    Q1.Style.Columns = 5

    Q1.Label.Inserts["ReplaceMe"] = "(Responses underneath)"
    Q1.Categories[..].Label.Style.ElementAlign=ElementAlignments.eaNewLine
    Q1.Categories[..].Style.Indent=0
    Q1.Categories[..].Style.Align=Alignments.alCenter
    Q1.Style.Orientation=orientations.orRow
    Q1.Ask()

End Routing

JustCode : Remove a language from MDD via script

This example show us how to open up an MDD file in read-write mode and remove a langauge from it.

' ****************************************
' Designed by : Smarter Dimensions
' Last Updated : 26th September 2009
' Open MDD file and remove language
' ****************************************

    Dim oMDM

    ' Create the MDM object and open the Short Drinks .mdd file in read-write mode
    Set oMDM = CreateObject("MDM.Document")
        oMDM.Open("c:\tempNew.mdd", ,2)

        oMDM.Languages.Remove("JPN")

        oMDM.Save()

        oMDM.Close()

JustCode : Randomize Cats V2

This function will randomize categories and make sure that some do not sit next to each other.

Sub q13aran(q,o)
    Dim cat, cat1, cat2, cat3, cats
    Dim idx, idx1, idx2, idx3
    Dim strFilt

    ' Randomize
    q.Categories.Order = o
    idx = 0
    idx1 = 0
    idx2 = 0
    idx3 = 0

    ' Find 3 categories interested in
    For Each cat in q.Categories
        idx = idx + 1
        If ( cat = {_18} Or cat = {_19} Or cat = {_20} ) Then
            If ( idx1 = 0 ) then
                idx1 = idx
                cat1 = cat.name
            ElseIf ( idx2 = 0 ) Then
                idx2 = idx
                cat2 = cat.name
            ElseIf ( idx3 = 0 ) Then
                idx3 = idx
                cat3 = cat.name
            End If
        End If
    Next

    ' If not next to each other, then keep what we have
    If ( (idx1 + 1) <> idx2 And (idx2 + 1) <> idx3 ) Then Exit Sub


    If ( (idx1 + 1) = idx2 And (idx2 + 1) = idx3 ) Then  ' If all 3 in a row
        If ( idx1 = 1 ) Then           ' First 3
            idx2 = idx2 + 1
            idx3 = idx3 + 2
        ElseIf ( idx3 = idx ) Then     ' Last 3
            idx1 = idx1 - 2
            idx2 = idx2 - 1
        Else                           ' in middle
            idx1 = idx1 - 1
            idx3 = idx3 + 1
        End If
    ElseIf ( idx1 = 1 And idx2 = 2 And idx3 = 4 ) Then  ' 1,2,4
        idx2 = 3
        idx3 = 5
    ElseIf ( idx1 = 1 And idx2 = 3 And idx3 = 4 ) Then  ' 1,3,4
        idx2 = 3
        idx3 = 5
    ElseIf ( (idx1 = (idx - 3)) And idx2 = (idx - 1) And idx3 = idx ) Then  ' last-3,last-1,last
        idx1 = idx1 - 1
        idx2 = idx2 - 1
    ElseIf ( (idx1 = (idx - 3)) And idx2 = (idx - 2) And idx3 = idx ) Then  ' last-3,last-2,last
        idx1 = idx3 - 4
        idx2 = idx3 - 2
    Else

        If ( (idx1 + 1) = idx2 ) Then
            If ( idx1 = 1 ) Then          ' idx1 is first category
                idx2 = 3
            Else
                idx1 = idx1 - 1
            End If
        End If

        If ( (idx2 + 1) = idx3 ) Then
            If ( idx3 = idx ) Then         ' idx3 is last category
                idx2 = idx2 - 1
            Else
                idx3 = idx3 + 1
            End If
        End If

    End If

    ' Convert list into a string of categories
    strFilt = ""
    idx = 1
    For Each cat in q.Categories
        If ( idx = idx1 ) Then
            strFilt = strFilt + cat1 + ","
            idx = idx + 1
        ElseIf ( idx = idx2 ) Then
            strFilt = strFilt + cat2 + ","
            idx = idx + 1
        ElseIf ( idx = idx3 ) Then
            strFilt = strFilt + cat3 + ","
            idx = idx + 1
        End If
        If ( cat <> {_18} And cat <> {_19} And cat <> {_20} ) Then
            strFilt = strFilt + cat.name + ","
            idx = idx + 1
        End If
    Next

    ' Put into custom order in .Categories
    strFilt = Left(strFilt,Len(strFilt) - 1)
    q.Categories.Filter = {}
    q.Categories.Order = OrderConstants.oCustom
    cats = split(strFilt,",")
    For idx = lbound(cats) to ubound(cats)
        q.Categories.Filter = q.Categories.Filter + CCAtegorical("{" + cats[idx] + "}")
    Next

    ' Catch all to ensure these are all captured - should never happen
    If ( FindItem(q.CAtegories,{_18}) is null ) Then q.Categories = q.Categories + {_18}
    If ( FindItem(q.CAtegories,{_19}) is null ) Then q.Categories = q.Categories + {_19}
    If ( FindItem(q.CAtegories,{_20}) is null ) Then q.Categories = q.Categories + {_20}

End Sub

JustCode : Randomize Cats V1

This function will randomize categories and make sure that some do not sit next to each other.

 Metadata(en-US, Question, Label)
q1loop "blah" loop
{
_1 "_1",
_2 "_2",
_3 "_3",
_4 "_4",
_5 "_5",
_6 "_6",
_7 "_7",
_8 "_8",
_9 "_9"
} fields -
(
q1 ""
categorical [1..1]
{
_1 "_1",
_2 "_2",
_3 "_3",
_4 "_4",
_5 "_5"
};

) expand;
End Metadata

And in the routing

Routing(Web)

Dim i

    q1loop.Categories.Order = OrderConstants.oCustom
    For i = 1 to 20   ' test 20 times
        q1loop.Categories.Filter = RanNotTogether(q1loop,{_3,_4,_5},Null)
        q1loop[..].q1.Response.Initial = {_1}
        q1loop.Ask()
    Next

'! Returns a category list of all categories in question.categories
where none of the categories in notTogether are next to one another
Caution should be taken to ensure that there is enough room to allow for
the notTogether codes to be separated by at least one other code
For example, a question with 9 items having 5 items that can't be together
is not possible.
ARGS:  question - question whose categories are used
notTogether - category list of categories that cannot appear together
seed - if not Null, then used as the random seed
RETURNS:  category list randomized so that none of the notTogether categories are next to each other
EXAMPLE:
q10loop.Categories.Order = OrderConstants.oCustom
q10loop.Categories.Filter = RanNotTogether,q10loop,{coke,pepsi,rccola},Null)
!'

Function RanNotTogether(question,notTogether,seed)
    Dim randomCodes,notTogetherCodes
    Dim randomCodeList,randomCode,codeCount
    Dim FinalArray[],strFilter

    ' Make array large enough for all categories
    ReDim(FinalArray,question.Categories.Count)

    ' Initialize array to blanks
    For codeCount = LBound(FinalArray) to UBound(FinalArray)
        FinalArray[codeCount] = ""
    Next

    ' Codes allowed to be together
    randomCodes = SelectRange(question.Categories - notTogether)

    ' Codes not allowed to be together
    notTogetherCodes = SelectRange(notTogether)

    codeCount = 0
    ' Place codes not allowed to be next to each other
    If ( seed is not null ) Then SetRandomSeed(seed)
    randomCodeList = ransequence(0,UBound(FinalArray),1)

    For Each randomCode in randomCodeList
        Select Case ( randomCode )
            Case 0 ' Can't have any to right
                If FinalArray[randomCode + 1] = "" Then
                    FinalArray[randomCode] = notTogetherCodes[codeCount]
                    codeCount = codeCount + 1
                End If
            Case UBound(FinalArray) ' Can't have any to left
                If FinalArray[randomCode - 1] = "" Then
                    FinalArray[randomCode] = notTogetherCodes[codeCount]
                    codeCount = codeCount + 1
                End If
            Case Else ' Can't have any to right or left
                If FinalArray[randomCode + 1] = "" And FinalArray[randomCode - 1] = "" Then
                    FinalArray[randomCode] = notTogetherCodes[codeCount]
                    codeCount = codeCount + 1
                End If
        End Select

        ' If placed all those that can't be together, then get out of loop
        if ( codeCount > UBound(notTogetherCodes) ) Then Exit For
    Next

    ' Add the remaining codes
    codeCount = 0
    For Each randomCode in randomCodeList
        ' If haven't used this position, then put in the next remaining code
        If FinalArray[randomCode] = "" Then
            FinalArray[randomCode] = randomCodes[codeCount]
            codeCount = codeCount + 1
        End If
    Next

    ' convert to a categorical list and return
    strFilter = ""
    For codeCount = LBound(FinalArray) to UBound(FinalArray)
        strFilter = strFilter + question.Categories[CCategorical(FinalArray[codeCount])].Name + ","
    Next
    RanNotTogether = CCategorical("{" + Left(strFilter,Len(strFilter)-1) + "}")
End Function

End Routing

JustCode : Adjust progress bar when asking Loops

This code shows us how to increment the EstimatedProgress counter to show a progress bar correctly when asking loop questions.

Metadata(en-AU, Question, label)

    Q1 "Loop_Text_Goes_Here" loop
    {
        A,B,C,D,E,F
    } fields
    (
        Q1Rate "Please Rate {@}" categorical [1]
        {
        VeryBad,Bad,OK,Good,VeryGood
        };
    ) expand;
End Metadata

and now the routing

' ****************************************
' Designed by : Smarter Dimensions
' Last Updated : 4th August 2009
' Adjust progress bar to display properly when asking loops
' ****************************************

Routing(Web)
    Dim oCat

    For Each oCat in Q1.Categories
        Q1[oCat].Ask()
        IOM.Info.EstimatedProgress = IOM.Info.EstimatedProgress+1
    Next

End Routing

JustCode : Connect to a Survey and find a record

This code will show you how to connect to a data collection survey and find a specific record. Once found the function will return the ID of the found record.

This is the metadata

Metadata(en-AU, Question, Label)
    ConnectionInfo "{Info}" info;
    Name "Name" text;
End Metadata 

This is the Routing

 
Routing(Web1)

    Dim oInfo

    Set oInfo = CreateObject("Scripting.Dictionary")

    oInfo.Add("Message","")
    oInfo.Add("ID",-1)

    Set oInfo = FindVDataRecord("SERVERNAME","ADO","VDATA","Respondent.Serial","Name = 'Test'","LOGIN","PASSWORD",oInfo)

    ConnectionInfo.Label.Inserts["Info"] = Ctext(oInfo.item["Message"])
    ConnectionInfo.Show()

    If ( oInfo.item["ID"] <> -1  )  Then
        ' Do something
    End If

Name.Ask()

Function FindVDataRecord(sServer,sDatabase,sTable,sKey,sWhere,sUser,sPassword,oReturnObject)

Dim oConnection, oRecordset
Dim sInfo , sConnection, sSQL

On Error Goto ErrorMessage

    Set oConnection = CreateObject("ADODB.Connection")

        sConnection = "Provider=mrOleDB.Provider.2;Data Source=mrRdbDsc2;"
        sConnection = sConnection + "Location='Provider=SQLOLEDB.1;Password=" + sPassword
        sConnection = sConnection + ";Persist Security Info=True;User ID="
        sConnection = sConnection + sUser + ";Initial Catalog=" + sDatabase
        sConnection = sConnection + ";Data Source=" + sServer
        sConnection = sConnection + "';Initial Catalog=\\" + sServer
        sConnection = sConnection + "SPSSMR_FMROOT\Master" + sDatabase + ""
        sConnection = sConnection + sDatabase + ".mdd;MR Init Project=" + sDatabase

    oConnection.Open(sConnection)

    If ( oConnection.State = 1 ) Then
        sInfo = "Connection to the Database worked"
        Set oRecordset = CreateObject("ADODB.Recordset")

        sSQL = "SELECT " + sKey + " FROM " + sTable + " WHERE " + sWhere
        oRecordSet.Open(sSQL,oConnection,3,1)
        If ( oRecordSet.EOF = true and oRecordset.BOF = true ) Then
            sInfo = sInfo + "Name Not Found"
            oReturnObject.Remove("ID")
            ReturnObject.Add("ID", -1)
        Else
            sInfo = sInfo + "Name Found !!!"
            oReturnObject.Remove("ID")
            oReturnObject.Add("ID", oRecordset[sKey].Value)
        End if

        oRecordset.Close()
        oReturnObject.Remove("Message")
        oReturnObject.Add("Message", sInfo)

    Else
        oReturnObject.Remove("Message")
        oReturnObject.Add("Message", "Connection to the Database failed")
    End If

    oConnection.Close()

Goto EndOfFunction

ErrorMessage:

    sInfo = sInfo + "Error : "
    sInfo = sInfo + Err.Description + ""
    sInfo = sInfo + CTEXT(Err.LineNumber)
    oReturnObject.Remove("Message")
    oReturnObject.Add("Message", sInfo)

EndOfFunction:

    Set oConnection = Null
    Set FindVDataRecord = oReturnObject

End Function

End Routing