Formula Written in Module Producing #VALUE - vba

With assistance from outside sources I have this code that takes latitude and longitude and extracts zipcodes. Here is the code:
Public Function ReverseGeoCode(myInput1 As String, myInput2 As String) As String
'You will need to reference Microsoft XML, v6.0 object library
Dim XMLDoc As New DOMDocument60
Dim XMLNODE As IXMLDOMNode
Dim I As Long
Dim lat, lng, myAddress, myZipcode, reportZipcode As String
Dim splitAddress, splitZipcode As Variant
lat = myInput1
lng = myInput2
XMLDoc.Load "http://maps.googleapis.com/maps/api/geocode/xml?latlng=" & lat & "," & lng & " &sensor=false"
Do Until XMLDoc.readyState = 4
DoEvents
Loop
If Len(XMLDoc.Text) = 0 Then
Call MsgBox("No Data!")
Exit Function
End If
Set XMLNode = XMLDoc.SelectSingleNode("/GeocodeResponse/result/formatted_address")
For i= 0 To XMLNode.ChildNodes.Length - 1
myAddress = XMLNode.ChildNodes(i).Text
Next i
splitAddress = Split(myAddress, ",")
myZipcode = splitAddress(2)
myZipcode = Trim(myZipcode)
splitZipcode = Split(myZipcode, " ")
reportZipcode = splitZipcode(1)
reportZipcode = Trim(reportZipcode)
ReverseGeoCode = reportZipcode
End Function
So the code works, and I know it might not be the cleanest. But the issue is when I call it in the Excel sheet using "=ReverseGeoCode(Cell1, Cell2)". Sometimes it works fine, other times it produces the return "#VALUE!" and I am not entirely sure why. I attached an image below to show you an example of the error. Does anyone have an idea of why this error is producing?

General observations:
So to pick up on what I wrote in the comments, here is an outline.
You don't want to use an User Defined Function. This will keep on making repeated calls. You definitely risk hitting a call limit to the API without an API key, and possibly with; it is inefficient and it is not necessary. Instead, write a sub which you call once and which loops all the required cells in the sheet and issues the API calls and returns the zip codes. An API key is a method of authentication used with many API calls. You shouldn't share it by the way.
These repeated calls, possibly hitting a limit and the fact that UDFs are frequently calculated maybe the source of your woes.
With efficiency in mind, first remove duplicates from the sheet to avoid calls that are not required. Switch of Screen-Updating and anything else e.g. CalculationMode to manual whilst performing.
From what I have read you require an API key once you have hit a daily limit. Not sure what the API limit is for free version or without API key.
Outline code (XML request with some psuedo code):
Option Explicit
Public Sub ListZipCodes()
Dim lat As Double, longitude As Double
Const APIKEY As String = "yourAPIkey"
Application.ScreenUpdating = False '<==Speed up code when actually working with sheet
'Code to remove duplicates
'Code to loop sheet and call function on each input set of values
'Example call. These would be picked up from cells
lat = 40.714224
longitude = -73.961452
Debug.Print GetZipCode(lat, longitude, APIKEY)
Application.ScreenUpdating = True
End Sub
Public Function GetZipCode(ByVal lat As Double, ByVal longitude As Double, ByVal APIKEY As String) As String
Dim sResponse As String
With CreateObject("MSXML2.XMLHTTP")
Dim URL As String
URL = "https://maps.googleapis.com/maps/api/geocode/xml?latlng=" & lat & "," & longitude & "&key=" & APIKEY
.Open "GET", URL, False
.send
If .Status <> 200 Then
GetZipCode = "API call failed"
Exit Function
End If
Dim XMLDoc As New DOMDocument60, XMLNODE As IXMLDOMNode
XMLDoc.Load .responseBody
If Len(XMLDoc.Text) = 0 Then GetZipCode = "No data"
Set XMLNODE = XMLDoc.SelectSingleNode("/GeocodeResponse/result/formatted_address")
GetZipCode = Split(Trim$(Split(XMLNODE.Text, Chr$(44))(2)), Chr$(32))(1)
End With
End Function
Requesting JSON rather than XML response:
The reason calling as JSON was falling over was that the response needed to be decoded. Here is the function re-written to handle a JSON response.
This requires the download of JSONConverter, which you then import and add a reference to Microsoft Scripting Runtime via VBE > Tools > References.
The example below was run with
latitude: 42.9865913391113,
longitude: -100.137954711914
VBA:
Public Function GetZipCode(ByVal lat As Double, ByVal longitude As Double, ByVal APIKEY As String) As String
Dim sResponse As String, json As Object
With CreateObject("MSXML2.XMLHTTP")
Dim URL As String, formattedAddress As String
URL = "https://maps.googleapis.com/maps/api/geocode/json?latlng=" & lat & "," & longitude & "&key=" & APIKEY
.Open "GET", URL, False
.send
If .Status <> 200 Then
GetZipCode = "API call failed"
Exit Function
End If
Set json = JsonConverter.ParseJson(StrConv(.responseBody, vbUnicode))
formattedAddress = json("results").item(1)("formatted_address")
GetZipCode = Split(Trim$(Split(formattedAddress, Chr$(44))(2)), Chr$(32))(1)
End With
End Function
With a JSON request the initial object you get back is a dictionary, as denoted by the opening "{" in the decoded response:
i.e. Set json = JsonConverter.ParseJson(StrConv(.responseBody, vbUnicode)) returns a dictionary object
The data of interest, in the dictionary, has the key "results", as you may observe from the above.
This can be accessed with json("results"), which returns a collection of dictionaries. This being denoted by the following "[", for collection, and subsequently by the start of the first dictionary within the collection, indicated again by "{".
I can grab the first dictionary in the collection by index with:
json("results").item(1)
An inspection of the keys in this dictionary shows that one of the keys is what we are after i.e."formatted_address".
It's associated value is a primitive datatype; in this case a string. This means we can directly access it using the key (a further object is not returned).
formattedAddress = json("results").item(1)("formatted_address")
Now that we have the address string, we can parse it as we did before:
GetZipCode = Split(Trim$(Split(formattedAddress, Chr$(44))(2)), Chr$(32))(1)
End note:
You can use Postman, amongst other tools, to test API calls, and in this case inspect the JSON response. Indeed, to see what kind of response you are getting full stop.
Help:
It is very quick and easy to set up a project, generate an API key and get started. Maybe 10 minutes to read through and perform.
Instructions on setting up a project and getting an API key
Enabling the API
Understanding how to make API calls to the Geocoding API

Related

VBA Function Passing Multi Variables back to Sub

I have a large string over 500 char which is called strEssay. I want to use a function(since I will need to look for several patterns) to return two values if (for example the name) Frank is found or not.
This is the function I'm trying to use:
Function NameFinder(strEssay as String, strName as String)
Dim varNameCounter as Variant
Dim strNameFinderResult as String
varNameCounter = 0
strNameFinderResult = ""
If strEssay like "*" & strName & "*" Then
strNameFinderResult = strName
varNameFinderCounter = 1
Else
strNameFinderResult = ""
varNameFinderCounter = .001
EndIf
End Function
I want to be able to return back to my subroutine both 'strNameFinderResult' and 'varNameFinderCounter'.
Is there any way that I can return both values?
If I can't return both simultaneously can I return one through the function and the other through a textbox or something? What would calling the function look like in the subroutine and/or how would I need to change my function?
NameFinder() function, returning array of 3 elements. It is called and returned by TestMe(), writing the following to the console:
Function NameFinder(essay As String, name As String)
Dim nameFinderResult As String
Dim namefinderCounter As String
nameFinderResult = "" & essay & name
namefinderCounter = 0.001 + 12
NameFinder = Array(nameFinderResult, namefinderCounter, "something else")
End Function
Public Sub TestMe()
Dim myArray As Variant
myArray = NameFinder("foo", "bar")
Dim i As Long
For i = LBound(myArray) To UBound(myArray)
Debug.Print myArray(i)
Next i
End Sub
As a general rule, you have to give the routine a type like this:
Function NameFinder(strEssay as String, strName as String) as string
But, that returns only ONE value.
So, a function (as opposed to a sub) returns one value (as a general rule).
However, you CAN also return parameters that you pass. I mean, in above, you can't make TWO assignments to one variable, can you?
So, you can use a Sub like this:
Sub NameFinder(strEssay as String, strName as String, _
strNameFinderResult as string, _
varNameFinderCounter as double)
If strEssay like "*" & strName & "*" Then
strNameFinderResult = strName
varNameFinderCounter = 1
Else
strNameFinderResult = ""
varNameFinderCounter = .001
EndIf
So in code, you now can go:
dim strMyResult as string
dim strFinderCount as Double
Call NameFinder("MyEassy", "Joe Blow", strMyResult, strFinderCount)
So, you can return values with the parameters.
Now, I suppose it possible for some strange reason, that you want to use a function to return two values with a single assignment?
What you would do is this in your code module.
Define a custom type, and use that.
eg this:
Option Compare Database
Option Explicit
Type SearchResult
strName As String
FindCount As Double
End Type
Function NameFinder(strEssay As String, strName As String) As SearchResult
NameFinder.FindCount = 0
NameFinder.strName = ""
If strEssay Like "*" & strName & "*" Then
NameFinder.strName = strName
NameFinder.FindCount = 1
Else
NameFinder.strName = ""
NameFinder.FindCount = 0.001
End If
End Function
So, now to use in code? You can go like this:
dim MyResults as SearchResult
MyResults = NameFinder("My eassy", "Joe Blow")
debug.print "Name found result = " & MyResults.strName
debug.print "Count of find = " & MyResult.FindCount
The VERY nice thing about above is you get full intel-sense in your code editor.
eg this:
So by building a custom data type, you can use "one" assignment for the return type. And you get nice type checking and inteli-sense in the VBA code editor.
And you can even do this:
But, to get both variables, then you would in theory wind up calling the function two times. So, you can actually use the function without declarer of variables like this:
Debug.Print NameFinder("MyEassy", "Joe blow").strName
Debug.Print NameFinder("MyEassy", "Joe blow").FindCount
So, I don't recommend the above, but in the case in which you ONLY want one of the return values, then the raw expression (function) like above would be a use case (and no need to even declare a return variable).
But, without a doubt, define a custom type in code as per above. The reason is now you get a really nice VBA editor type-checking, inteli-sense, and also that you only have to declare "one" variable that holds two values.
In fact, the results are very much like JavaScript, or even c# in which you declare a "class" type. So with a custom "type" you are declaring a data type of your own. And the beauty of this is if you need say 3 values, then once again you create a type with 3 "inside" values.
The you ONLY have to declare that one variable as the custom type.
With this you get:
Very valuable compile time syntax and data type checking of the var types you are using.
You get GREAT VBA inteli-sense while coding - which means less coding mistakes.
And you type far less typing in the VBA editor as it will pop-up the choices for you as you write code. And you can't type or choose the wrong sub - type, as the compiler will catch this.

NotesJsonNavigator and international characters

I have json data that I want to use in a lotusscript library.
I use NotesJsonNavigator to navigate through the data.
If I set preferUTF8 to false, the values in NotesJSONElements are strings, however it drops international characters like ö, å, ...
If I set the parameter preferUTF8 to true, the values in my NotesJSONElements are byte arrays.
How can I convert these byte arrays into Strings, taking international characters into account?
Example:
Dim session As New NotesSession
'URL Parameters have to be UTF-8 encoded.
Dim url As string
URL = "https://maps.googleapis.com/maps/api/geocode/json?address=Malm%C3%B6%2C%20Sweden&key=<My Google API Key>"
'Creating the request
Dim webRequest As NotesHTTPRequest
Dim response As Variant
Set webRequest = session.createhttprequest()
'Get response as byte array
webrequest.preferstrings = False
response = webrequest.Get(URL)
'Throw error if response status is not OK
If InStr(webRequest.Responsecode, "200 OK") = 0 Then
'Return Status is not OK
Error 1000, "Request returned response code " + webRequest.responseCode
End If
'Create the JSON NAVIGATOR
Dim jsnav As NotesJSONNavigator
If Not IsArray(response) Then Error 1000, "JSON is nothing"
Set jsnav = session.CreateJSONNavigator(response)
'Get data as Strings
jsnav.Preferutf8 = False
'Declaring json specific elements
Dim el_address As NotesJSONElement
Dim el_state As NotesJSONElement
'Retrieving the address...
Set el_address = jsnav.getelementbypointer("/results/0/formatted_address")
Print el_address.value
'Retrieving state
Set el_state = jsnav.getelementbypointer("/results/0/address_components/1/long_name")
Print el_state.value
This prints
Malm, Sweden
Skne
This is going to be fixed in the next 10.0.1 FP. Keep an eye out for DCONB8F6JV in the fix list.

Parsing XML Response in VBA and extracting only last data

I'm trying to do a XML request through excel vba for one of the internal links (in our company). when i send request and receive response using the code below, i get the following as response text:
[{"CPN":"700-42887-01","ExtractDt":"2018-04-02
00:00:00","Demand":"8645"},"CPN":"700-42887-01","ExtractDt":"2018-04-09
00:00:00","Demand":"8985"},{"CPN":"700-42887-01","ExtractDt":"2018-04-16
00:00:00","Demand":"9341"},{"CPN":"700-42887-01","ExtractDt":"2018-04-23
00:00:00","Demand":"9589"},{"CPN":"700-42887-01","ExtractDt":"2018-04-30
00:00:00","Demand":"9210"},{"CPN":"700-42887-01","ExtractDt":"2018-05-07
00:00:00","Demand":"9698"},{"CPN":"700-42887-01","ExtractDt":"2018-05-14
00:00:00","Demand":"9542"},{"CPN":"700-42887-01","ExtractDt":"2018-05-21
00:00:00","Demand":"9692"},{"CPN":"700-42887-01","ExtractDt":"2018-05-28
00:00:00","Demand":"10416"},{"CPN":"700-42887-01","ExtractDt":"2018-06-04
00:00:00","Demand":"6777"},{"CPN":"700-42887-01","ExtractDt":"2018-06-11
00:00:00","Demand":"12774"},{"CPN":"700-42887-01","ExtractDt":"2018-06-18
00:00:00","Demand":"12912"},{"CPN":"700-42887-01","ExtractDt":"2018-06-25
00:00:00","Demand":"12693"},{"CPN":"700-42887-01","ExtractDt":"2018-07-02
00:00:00","Demand":"12895"},{"CPN":"700-42887-01","ExtractDt":"2018-07-09
00:00:00","Demand":"13366"},{"CPN":"700-42887-01","ExtractDt":"2018-07-16
00:00:00","Demand":"13550"},{"CPN":"700-42887-01","ExtractDt":"2018-07-23
00:00:00","Demand":"7971"},{"CPN":"700-42887-01","ExtractDt":"2018-07-30
00:00:00","Demand":"12442"},{"CPN":"700-42887-01","ExtractDt":"2018-08-06
00:00:00","Demand":"12960"},{"CPN":"700-42887-01","ExtractDt":"2018-08-13
00:00:00","Demand":"14106"},{"CPN":"700-42887-01","ExtractDt":"2018-08-20
00:00:00","Demand":"13543"},{"CPN":"700-42887-01","ExtractDt":"2018-08-27
00:00:00","Demand":"13570"},{"CPN":"700-42887-01","ExtractDt":"2018-09-03
00:00:00","Demand":"13506"},{"CPN":"700-42887-01","ExtractDt":"2018-09-10
00:00:00","Demand":"13914"},{"CPN":"700-42887-01","ExtractDt":"2018-09-17
00:00:00","Demand":"13241"},{"CPN":"700-42887-01","ExtractDt":"2018-09-24
00:00:00","Demand":"13449"}]
I want to extract only the last Value - Namely 13449. What is the code that i need to write to accomplish this.
Thanks in Advance!`
Code used
Sub xmlparsing()
Dim jstring As String
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", "**INTERNAL COMPANY LINK HERE**", False
.send
If .Status <> 200 Then Exit Sub
jstring = .responseText
Debug.Print jstring
End With`
End Sub
You could use InStrRev
Mid$(responseText, InStrRev(responseText, ":") + 2, (InStrRev(responseText, "}") - 1) - (InStrRev(responseText, ":") + 2))
InStrRev walks the string from right to left. We know you want the value at the end of the string so this direction is useful. We specify as an argument the character to find. The overall string is the responseText.
The first character to find is ":", from right to left. This will be where you have :"13449"}]. Offset from this + 2 to get the actual start of the value you want, in this case the 1 in 13449.
Same logic to determine end point of string. I use "}" as end point then make an adjustment to move forward to the numbers. Mid allows you to specify a string, start point and number of characters. I pass the arguments to extract the required string to Mid. I used typed functions (with the $ at the end) as more efficient when working with strings.
Considering the fact, that you have already parsed the XML to string, then the easiest fact is to try to slice the string. To see how it works, put the string from .responseText to A1 range and run this:
Sub TestMe()
Dim responseText As String
responseText = Range("A1")
Dim myArr As Variant
myArr = Split(responseText, "Demand"":""")
Debug.Print Left(myArr(UBound(myArr)), Len(myArr(UBound(myArr))) - 4)
End Sub
What it does is to split the string into array by the word Demand":" and to take anything but the last 4 characters of the last unit of the array.

How can I download attachments using EWS (Exchange Web Services) in VB .NET

I have a mechanism that downloads attachments from email messages sent into an inbox to a folder for processing by another service.
Originally this was done using some VBA that was triggered by a rule in Outlook. This wasn't build with large amounts of information going in originally however it has got to the point now that lots of data is being passed through and it is causing me headaches using this Outlook VBA and SSIS Package combination.
Therefore I am working on a new version built entirely in VB .NET, this obviously will mean its more robust and a lot easier to debug problems.
I have started using the EWS API and have managed to successfully connect to the Exchange and I am able to read the relevant messages and store their Subject etc. to variables.
I am struggling however to find ANY documentation / help regarding downloading of attachements with EWS with VB .NET code.
Everything seems to be in C# and I unfortunately have no experience with this. I am totally open to 3rd Party Solutions that may need to be purchased or even pointed in the right direction of a book or documentation, this is not being done on a shoe string and is quite important.
Try converters between C# and VB.
Regarding EWS, many VB examples contains "Exchange Web Services .NET". The same examples are in C# and VB
Hopefully this will get you started in the right direction. NOTE: I have not had a chance to test this VB code, since I do not have access to an Exchange 2007 server from home (as far as I know). However, I wrote this code carefully, basing it on C# code that I know works, because I used it at my workplace. See this link: http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=105257. And this one: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/dd2b465b-b1d2-4c0d-82ec-c36c6c482d5d/populating-sql-server-from-emails?forum=sqltools
FURTHER NOTE: This code will DELETE each email after saving the attachments from it. I think it will be a hard delete as well. In other words, you won't be able to pull the emails back out of the Deleted Items folder. You have been warned.
(It is possible to just mark each email as read instead of deleting it, but I don't have time to write the code for that just now. Hopefully this will be enough for your needs. If you still need this question answered.)
Namespace StephenGTuggy.Examples.SaveEWS2007Attachments
Public Module EWS2007ExampleMain
Enum AuthenticationMethod
Windows
Basic
End Enum
Private Const sUserName As String = "SampleUserName"
Private Const sPassword As String = "SamplePassword1"
Private Const sDomain As String = "mycompany.com"
Private Const eAuthenticationMethod As AuthenticationMethod = AuthenticationMethod.Basic
Private Const sEWS_URL As String = "https://mailserver.mycompany.com/EWS/Exchange.asmx"
'Private Const sEmailSender As String = "SampleUserName2#SomeOtherCompany.com"
Private Const sSaveAttachmentsToDirectory As String = "C:\"
Public Sub Main()
' Set up credentials to use to connect to the Exchange server.
Dim nc As System.Net.NetworkCredential = Nothing
Select Case eAuthenticationMethod
Case AuthenticationMethod.Windows
nc = System.Net.CredentialCache.DefaultNetworkCredentials
Case Else
nc = New System.Net.NetworkCredential(sUserName, sPassword, sDomain)
End Select
' Now bind to Exchange.
Dim esb As New ExchangeWebServices.ExchangeServiceBinding
esb.Url = sEWS_URL
esb.Credentials = nc
' Main code....
Dim findItemRequest As New ExchangeWebServices.FindItemType
findItemRequest.Traversal = ExchangeWebServices.ItemQueryTraversalType.Shallow
' Define which item properties Exchange should return for each email.
Dim itemProperties As New ExchangeWebServices.ItemResponseShapeType
itemProperties.BaseShape = ExchangeWebServices.DefaultShapeNamesType.AllProperties
findItemRequest.ItemShape = itemProperties
' Identify which folders to search to find items.
Dim folderIDInbox As New ExchangeWebServices.DistinguishedFolderIdType
folderIDInbox.Id = ExchangeWebServices.DistinguishedFolderIdNameType.inbox
Dim folderIDArray As ExchangeWebServices.DistinguishedFolderIdType() = {folderIDInbox}
findItemRequest.ParentFolderIds = folderIDArray
' Limit result set to unread emails only.
Dim restriction As New ExchangeWebServices.RestrictionType
Dim isEqualTo As New ExchangeWebServices.IsEqualToType
Dim pathToFieldType As New PathToUnindexedFieldType
pathToFieldType.FieldURI = ExchangeWebServices.UnindexedFieldURIType.messageIsRead '.messageFrom
Dim constantType As New ExchangeWebServices.FieldURIOrConstantType
Dim constantValueType As New ExchangeWebServices.ConstantValueType
constantValueType.Value = "0" 'sEmailSender
constantType.Item = constantValueType
isEqualTo.Item = pathToFieldType
isEqualTo.FieldURIOrConstant = constantType
restriction.Item = isEqualTo
findItemRequest.Restriction = restriction
' Send the request to Exchange and get the response back.
System.Diagnostics.Trace.TraceInformation("Sending FindItem request....")
Dim findItemResponse As ExchangeWebServices.FindItemResponseType = esb.FindItem(findItemRequest)
System.Diagnostics.Trace.TraceInformation("Received response to FindItem request.")
' Process response from Exchange server.
Dim folder As ExchangeWebServices.FindItemResponseMessageType = _
CType(findItemResponse.ResponseMessages.Items(0), ExchangeWebServices.FindItemResponseMessageType)
Dim folderContents As ExchangeWebServices.ArrayOfRealItemsType = _
CType(folder.RootFolder.Item, ExchangeWebServices.ArrayOfRealItemsType)
Dim items As ExchangeWebServices.ItemType() = folderContents.Items
For Each curItem As ExchangeWebServices.ItemType In items
Dim iAttCount As Integer = GetFileAttachmentsCount(esb, curItem.ItemId)
System.Diagnostics.Trace.TraceInformation("Subject: {0} DisplayTo: {1} DateTimeReceived: {2} ItemClass: {3} AttachmentCount: {4}", _
curItem.Subject.Trim(), _
curItem.DisplayTo.Trim(), _
curItem.DateTimeReceived.TimeOfDay.ToString(), _
curItem.ItemClass.Trim(), _
iAttCount)
If iAttCount > 0 Then
GetAttachmentsOnItem(esb, curItem.ItemId, sSaveAttachmentsToDirectory)
If Not MarkItemAsProcessed(esb, curItem.ItemId) Then
System.Diagnostics.Trace.TraceError("Unable to mark email as processed.")
End If
End If
Next
System.Diagnostics.Trace.TraceInformation("Finished processing emails and attachments.")
End Sub
Function GetFileAttachmentsCount(binding As ExchangeWebServices.ExchangeServiceBinding, _
id As ExchangeWebServices.ItemIdType) As Integer
Dim iAttachmentCount As Integer = 0
' Use GetItem on the Id to get the Attachments collection.
Dim getItemRequest As New ExchangeWebServices.GetItemType
getItemRequest.ItemIds = New ExchangeWebServices.ItemIdType() {id}
getItemRequest.ItemShape = New ExchangeWebServices.ItemResponseShapeType
getItemRequest.ItemShape.BaseShape = ExchangeWebServices.DefaultShapeNamesType.AllProperties
Dim hasAttachPath As New ExchangeWebServices.PathToUnindexedFieldType
hasAttachPath.FieldURI = ExchangeWebServices.UnindexedFieldURIType.itemHasAttachments
Dim attachmentsPath As New ExchangeWebServices.PathToUnindexedFieldType
attachmentsPath.FieldURI = ExchangeWebServices.UnindexedFieldURIType.itemAttachments
' Add additional properties?
getItemRequest.ItemShape.AdditionalProperties = New ExchangeWebServices.BasePathToElementType() { _
hasAttachPath, attachmentsPath}
Dim getItemResponse As ExchangeWebServices.GetItemResponseType = binding.GetItem(getItemRequest)
Dim getItemResponseMessage As ExchangeWebServices.ItemInfoResponseMessageType = TryCast( _
getItemResponse.ResponseMessages.Items(0), ExchangeWebServices.ItemInfoResponseMessageType)
If getItemResponseMessage.ResponseCode = ExchangeWebServices.ResponseCodeType.NoError Then
Dim item As ExchangeWebServices.ItemType = getItemResponseMessage.Items.Items(0)
' Don't rely on HasAttachments -- it does not mean what you think it would.
If (item.Attachments IsNot Nothing) AndAlso (item.Attachments.Length > 0) Then
For attachmentIndex As Integer = 0 To item.Attachments.Length - 1
Dim almostAnAttachment As ExchangeWebServices.FileAttachmentType = TryCast( _
item.Attachments(attachmentIndex), ExchangeWebServices.FileAttachmentType)
If almostAnAttachment IsNot Nothing Then
iAttachmentCount = iAttachmentCount + 1
End If
Next
End If
End If
Return iAttachmentCount
End Function
Function MarkItemAsProcessed(esb As ExchangeWebServices.ExchangeServiceBinding, _
id As ExchangeWebServices.ItemIdType) As Boolean
Dim bReturn As Boolean = False
' Create the DeleteItem request.
Dim dit As New ExchangeWebServices.DeleteItemType
dit.ItemIds = New ExchangeWebServices.BaseItemIdType() {id}
' Delete the message.
Dim diResponse As ExchangeWebServices.DeleteItemResponseType = esb.DeleteItem(dit)
' Check the result.
If (diResponse.ResponseMessages.Items.Length > 0) AndAlso _
(diResponse.ResponseMessages.Items(0).ResponseClass = _
ExchangeWebServices.ResponseClassType.Success) Then
bReturn = True
End If
Return bReturn
End Function
Sub GetAttachmentsOnItem(binding As ExchangeWebServices.ExchangeServiceBinding, _
id As ExchangeWebServices.ItemIdType, _
destinationPath As String)
' STEP 1: We need to call GetItem on the Id so that we can get the Attachments collection back.
Dim getItemRequest As New ExchangeWebServices.GetItemType
getItemRequest.ItemIds = New ExchangeWebServices.ItemIdType() {id}
getItemRequest.ItemShape = New ExchangeWebServices.ItemResponseShapeType
' For this example, all we really need is the HasAttachments property and the Attachment collection.
' As such, let's just request those properties to reduce network traffic.
getItemRequest.ItemShape.BaseShape = ExchangeWebServices.DefaultShapeNamesType.IdOnly
Dim hasAttachPath As New ExchangeWebServices.PathToUnindexedFieldType
hasAttachPath.FieldURI = ExchangeWebServices.UnindexedFieldURIType.itemHasAttachments
Dim attachmentsPath As New ExchangeWebServices.PathToUnindexedFieldType
attachmentsPath.FieldURI = ExchangeWebServices.UnindexedFieldURIType.itemAttachments
' Add these to the list of properties to fetch....
getItemRequest.ItemShape.AdditionalProperties = New ExchangeWebServices.BasePathToElementType() { _
hasAttachPath, attachmentsPath}
' Now make the call.
Dim getItemResponse As ExchangeWebServices.GetItemResponseType = binding.GetItem(getItemRequest)
' getItem returns ItemInfoResponseMessages. Since we only requested one item, we should only
' get back one response message.
Dim getItemResponseMessage As ExchangeWebServices.ItemInfoResponseMessageType = TryCast( _
getItemResponse.ResponseMessages.Items(0), ExchangeWebServices.ItemInfoResponseMessageType)
' Like all good, happy and compliant developers [sic], we should check our response code....
If getItemResponseMessage.ResponseCode = ExchangeWebServices.ResponseCodeType.NoError Then
' STEP 2: Grab the Attachment IDs from our item
Dim item As ExchangeWebServices.ItemType = getItemResponseMessage.Items.Items(0)
If item.HasAttachments AndAlso item.Attachments IsNot Nothing AndAlso item.Attachments.Length > 0 Then
Dim attachmentIds As New List(Of ExchangeWebServices.RequestAttachmentIdType)
For attachmentIndex As Integer = 0 To item.Attachments.Length - 1
' For now, let's only consider file attachments instead of item attachments.
Dim almostAnAttachment As ExchangeWebServices.FileAttachmentType = TryCast( _
item.Attachments(attachmentIndex), ExchangeWebServices.FileAttachmentType)
If almostAnAttachment IsNot Nothing Then
' VERY IMPORTANT! The attachment collection returned by GetItem only has meta data
' about the attachments, but DOES NOT INCLUDE THE ACTUAL CONTENT. We must use
' GetAttachment to get the actual attachment.
Dim requestId As New ExchangeWebServices.RequestAttachmentIdType
requestId.Id = almostAnAttachment.AttachmentId.Id
attachmentIds.Add(requestId)
End If
Next
' Now that we have all of the attachment IDs, let's make a single GetAttachment call to
' get them all in one shot.
Dim getAttachmentRequest As New ExchangeWebServices.GetAttachmentType
' Oddly enough, just create an EMPTY (non-null) attachment response shape.
getAttachmentRequest.AttachmentShape = New ExchangeWebServices.AttachmentResponseShapeType
getAttachmentRequest.AttachmentIds = attachmentIds.ToArray()
Dim getAttachmentResponse As ExchangeWebServices.GetAttachmentResponseType = _
binding.GetAttachment(getAttachmentRequest)
' Now, here we asked for multiple items. As such, we will get back multiple response
' messages.
For Each attachmentResponseMessage As ExchangeWebServices.AttachmentInfoResponseMessageType _
In getAttachmentResponse.ResponseMessages.Items
If attachmentResponseMessage.ResponseCode = ExchangeWebServices.ResponseCodeType.NoError Then
' We only asked for file attachments above, so we should only get FileAttachments.
' If you are really paranoid, you can check for null after this again.
Dim fileAttachment As ExchangeWebServices.FileAttachmentType = TryCast( _
attachmentResponseMessage.Attachments(0), ExchangeWebServices.FileAttachmentType)
' Now, just save out the file contents.
Using file As System.IO.FileStream = System.IO.File.Create(System.IO.Path.Combine(destinationPath, fileAttachment.Name))
file.Write(fileAttachment.Content, 0, fileAttachment.Content.Length)
file.Flush()
file.Close()
End Using
End If
Next
End If
End If
End Sub
End Module
End Namespace
One final note: You will use the same .asmx link both to create the web reference to EWS and to connect to the server to make the actual calls. This had me stumped for a bit.
Good luck!

How to pass request through HTTP " GET " statement

I'm trying to fetch data from a currency api. I'm fairly new to VBA and very new to JSON, but I can't figure out how to pass a request through the HTTP.... I can fetch any data I want that does not require a request though to pin it down.
The API is here: https://www.bitfinex.com/pages/api
I'm trying to pass a timestamp(time) request. See instructions ( pasted form their website just below )
Trades
GET /trades/:symbol
Get a list of the most recent trades for the given symbol.
Request
timestamp (time): Optional. Only show trades at or after this timestamp.
limit_trades (int): Optional. Limit the number of trades returned. Must be >= 1. Default is 50.
Response
An array of dictionaries:
tid (integer)
timestamp (time)
price (price)
amount (decimal)
exchange (string)
type (string) "sell" or "buy" (can be "" if undetermined)
My code so far
Private Function get_price() As String
Dim xml_a As Object
Set xml_a = CreateObject("MSXML2.XMLHTTP")
With xml_a
.Open "Get", "https://api.bitfinex.com/v1/trades/BTCUSD", False
'' <<< something should be written here right? I just don't know what... >>>''
.send
get_price = .responseText
End With
Set xml_a = Nothing
End Function
Sub tester()
Dim JSON_return As Object
Set JSON_return = JSON.parse(get_price())
Debug.Print JSON_return.Item("last_price")
End Sub