Code: Select all
;Last tested 20220918111701 with AHK v 1.1.34.03
/* REFERENCES
https://learn.microsoft.com/en-us/office/vba/api/outlook.itemproperties
https://learn.microsoft.com/en-us/office/vba/api/outlook.oluserpropertytype
https://learn.microsoft.com/en-us/office/vba/api/outlook.userdefinedproperty.displayformat
*/
/* OBSERVATIONS
* Observations are based on 4 types of property objects:
1. built-in ("b") - included and created by Outlook
2. custom-folder-field ("cF") - user-created and set to be included in
parent folder's userDefinedProperties key.
3. custom-non-folder-field ("cN")- user-created and set to be excluded
from parent folder's userDefinedProperties keey.
4. non-extant ("n") - a property that hasn't been created yet.
* cF and CN are always read/write and may be added or or overwritten.
But removal differes between them.
* b are sometimes read-only.
* n sometimes throws errors, other times returns empty. Details below.
* There are four keys most used in dealing with property objects:
1. item.itemProperties
https://learn.microsoft.com/en-us/office/vba/api/outlook.itemproperties
2. item.userProperties
https://learn.microsoft.com/en-us/office/vba/api/outlook.userproperties
3. item.propertyAccessor
https://learn.microsoft.com/en-us/office/vba/api/outlook.propertyaccessor
4. item.parent.userDefinedProperties
https://learn.microsoft.com/en-us/office/vba/api/outlook.folder.userdefinedproperties
* Parens and brackets are interchangeable, so long as each pair is matched.
* The propertyAccessor key can access cN and CF using a schema prefix of :
http://schemas.microsoft.com/mapi/string/{00020329-0000-0000-C000-000000000046}/
* item.itemProperties enumerates, and its methods generally work on,
ANY b, cN, or CF. (E.g., add() and item() will return and affect cN and cF in
addition to b.) One of its methods (remove()) only works on cN and cF, b/c b
can't be removed, and effective removal is different for each of cN and CF.
* item.userProperties enumerates, and its methods (except find()) work on,
ONLY cF or cN.
* item.userProperties has a peculiar find() method that
item.itemProperties doesn't have. (See
https://learn.microsoft.com/en-us/office/vba/api/outlook.userproperties.find)
The find method can be used to find built-in properties by specifying 'false'
for the 2nd parameter, in which case custom properties won't be searched.
This is counterintuitive, but the result is as follows: to search only amoung
b properties use item.userProperties.find(name, false); to search only
among cN and cF use item.userProperties.find(name, true); and to seach
among both types use item.itemProperties(name), which is functionally
equivalent to find. Also counterintuitive is that for non-existing property
names, userProperties.find(name,true) returns empty without error, but
userProperties.find(name,false) throws an error. For conssitent, error-free
finding, item.itemProperties(name) is probably superior in most use cases.
* item.itemProperties.add() overwrites an existing property object of the
same name, even if the overwrite changes he type of the value stored in the
property (e.g., text to integer, etc.).
*/
/* TESTING ASSUMPTIONS
olPostItem := 6
; https://learn.microsoft.com/en-us/office/vba/api/outlook.olitemtype
; https://learn.microsoft.com/en-us/office/vba/api/outlook.postitem
olText := 1
olInteger :=20
; https://learn.microsoft.com/en-us/office/vba/api/outlook.oluserpropertytype
olFormatTextText := 1
olFormatIntegerPlain := 1
; https://learn.microsoft.com/en-us/office/vba/api/outlook.userdefinedproperty.displayformat
; https://learn.microsoft.com/en-us/office/vba/api/outlook.olformattext
; https://learn.microsoft.com/en-us/office/vba/api/outlook.olformatinteger
; i = variable for I*tem
; https://learn.microsoft.com/en-us/office/vba/api/outlook.postitem
; test results should work with all other item types, but here PostItem is used
i := comObjCreate("Outlook.Application").createItem(olPostItem)
b := builtInExample := "Subject"
cF := customFolderFieldExample := "customFolderField"
cN := customNonFolderFieldExample := "customNonFolderField"
d := doesntExistExample := "doesntExist"
cDASL := "http://schemas.microsoft.com/mapi/string/{00020329-0000-0000-C000-000000000046}/"
; add custom objects for testing purposes
i.itemProperties.add(cF,olText,true,olFormatTextText).value := "cF_value"
i.itemProperties.add(cN,olText,false,olFormatTextText).value := "cN_value"
; o = variable for O*bject (as in a property object)
; v = variable for V*alues (as in a property object's value)
*/
/* TESTED SUCCESSES
I. ENUMERATING PROPERTY OBJECTS
;PREFERRED APPROACH
for k, in i.itemProperties
msgbox % k.name
; enumerates BOTH built-in & custom
; OTHER APPROACHES
for k, in i.userProperties
msgbox % k.name
; enumerates ONLY custom
loop, % i.itemProperties.count
msgbox % i.itemProperties.item[a_index-1].name
; note the "a_index-1" as the object is zero-base-indexed
; enumerates BOTH built-in & custom
loop, % i.userProperties.count
msgbox % i.userroperties.item[a_index-1].name
; note the "a_index-1" as the object is zero-base-indexed
; enumerates ONLY custom
II. GETTING/FINDING PROPERTY OBJECTS
A. Via itemProperties (Preferred Approach)
o := i.itemProperties.item[b] ; works
o := i.itemProperties.item(b) ; works
o := i.itemProperties.item[cN] ; works
o := i.itemProperties.item(cN) ; works
o := i.itemProperties.item[cF] ; works
o := i.itemProperties.item(cF) ; works
o := i.itemProperties.item[n] ; returns empty w/o error
o := i.itemProperties.item(n) ; returns empty w/o error
; case-sensitive
B. Via userProperties itself
o := i.userProperties[cN] ; returns empty w/o error
o := i.userProperties(cN) ; returns empty w/o error
o := i.userProperties[cN] ; works
o := i.userProperties(cN) ; works
o := i.userProperties[cF] ; works
o := i.userProperties(cN) ; works
o := i.userProperties[n] ; returns empty w/o error
o := i.userProperties(n) ; returns empty w/o error
; case-sensitive
B. Via userProperties.item
o := i.userProperties.item[b] ; returns empty w/o error
o := i.userProperties.item(b) ; returns empty w/o error
o := i.userProperties.item[cN] ; works
o := i.userProperties.item(cN) ; works
o := i.userProperties.item[cF] ; works
o := i.userProperties.item(cF) ; works
o := i.userProperties.item[n] ; returns empty w/o error
o := i.userProperties.item(n) ; returns empty w/o error
; case-sensitve
C. Via propertyAccessor (Impossible)
; NOTE property accessor gets values only, not objects
; see below
D. Via For Loop of itemProperties
for k, in i.itemProperties
if k.name == b ; works
msgbox % k.value
; case-senssitive
for k, in i.itemProperties
if k.name == cN ; works
msgbox % k.value
; case-senssitive
for k, in i.itemProperties
if k.name == cF ; works
msgbox % k.value
; case-senssitive
for k, in i.itemProperties
if k.name == n
msgbox % k.value ; never executes b/c nonExtant
; case-senssitive
E. Via For Loop of userProperties
for k, in i.userProperties
if k.name == b
msgbox % k.value ; never executes b/c b not in userProperties
; case-senssitive
for k, in i.userProperties
if k.name == cN ; works
msgbox % k.value
; case-senssitive
for k, in i.userProperties
if k.name == cF ; works
msgbox % k.value
; case-senssitive
for k, in i.userProperties
if k.name == n
msgbox % k.value ; ; never executes b/c nonExtant
; case-senssitive
F. Via Simple Loop of itemProperties
;NOTE the 'a_index-1' in ea. e.g. below!
loop, % i.itemProperties.count
if i.itemProperties.item[a_index-1].name == b
msgbox % i.itemProperties.item[a_index-1].value ; works
; case-senssitive
loop, % i.itemProperties.count
if i.itemProperties.item[a_index-1].name == cF
msgbox % i.itemProperties.item[a_index-1].value ; works
; case-senssitive
loop, % i.itemProperties.count
if i.itemProperties.item[a_index-1].name == cN
msgbox % i.itemProperties.item[a_index-1].value ; works
; case-senssitive
loop, % i.itemProperties.count
if i.itemProperties.item[a_index-1].name == N
msgbox % i.itemProperties.item[a_index-1].value ; never executes b/c nonExtant
; case-senssitive
G. Via Simple Loop of userProperties
;NOTE the 'a_index' (sans '-1') in ea. e.g. below.
loop, % i.userProperties.count
if i.userProperties.item[a_index].name == b ; never executes b/c b not in userProperties
msgbox % i.userProperties.item[a_index].value
; case-senssitive
loop, % i.userProperties.count
if i.userProperties.item[a_index].name == cF ; works
msgbox % i.userProperties.item[a_index].value
; case-senssitive
loop, % i.userProperties.count
if i.userProperties.item[a_index].name == cN ; works
msgbox % i.userProperties.item[a_index].value
; case-senssitive
loop, % i.userProperties.count
if i.userProperties.item[a_index].name == n ; never executes b/c nonExtant
msgbox % i.userProperties.item[a_index].value
; case-senssitive
H. Via userProperties.find (Counterintuitive)
H.1 find(x)/find(x,true)
i.userProperties.find[b].value ; returns empty w/o error
i.userProperties.find[cN].value ; works
i.userProperties.find[cF].value ; works
i.userProperties.find[n].value ; returns empty w/o error
h.2 find(x,false)
i.userProperties.find[b,false].value ; works
i.userProperties.find[cN,false].value ; throws unknown name error
i.userProperties.find[cF,false].value ; throws unknown name error
i.userProperties.find[n,false].value ; throws unknown name error
III. ADDING PROPERTY OBJECTS
i.itemProperties.add("customProperty",olText,false,olFormatTextText)
; add custom-non-folder-field object
; FALSE makes it non-folder-field
i.itemProperties.add("customProperty",olText,false,olFormatTextText).value := "customPropertyValue"
; add custom-non-folder-field object with initial value
; FALSE makes it non-folder-field
i.itemProperties.add("customProperty",olText,true,olFormatTextText)
; add custom-folder-field object
; TRUE makes it folder-field
i.itemProperties.add("customProperty",olText,true,olFormatTextText).value := "customPropertyValue"
; add custom-folder-field object with initial value
; TRUE makes it folder-field
i.itemProperties.add("customProperty",olText,false,olFormatTextText).value := "customPropertyValue1"
i.itemProperties.add("customProperty",olText,false,olFormatTextText).value := "customPropertyValue2"
; add() overwrites without error
i.itemProperties.add("customProperty",olText,false,olFormatTextText).value := "customPropertyValue1"
i.itemProperties.add("customProperty",olInteger,false,olFormatIntegerPlain).value := 1
; add() overwrites even value type without error if both are non-folder
; BUT add() cannot change a folder-field to a non-folderfield
IV. REMOVING/DELETING PROPERTY OBJECTS
;PREFERRED APPROACH:
i.itemProperties.item[b].delete ; throws 'not implemented' error
i.itemProperties.item(b).delete ; throws 'not implemented' error
i.itemProperties.item[cF].delete ; deletes object & key, returns empty
i.itemProperties.item(cF).delete ; deletes object & key, returns empty
i.itemProperties.item[cN].delete ; deletes object & key, returns empty
i.itemProperties.item(cN).delete ; deletes object & key, returns empty
i.itemProperties.item[n].delete ; throws 'not implemented' error
i.itemProperties.item(n).delete ; throws 'not implemented' error
;OTHER APPROACHES:
; use .delete method with other approaches to getting object
; use itemProperties.remove(index) method with looping approaches above
; use userProperties.remove(index) method with looping approaches above
; use i.parent.userDefinedProperties(index) method with looping approaches
; above - this removes the property from the folder as well.
; better alternative is to .delete() the property object and then directly
; .delete() the userDefinedProperties object too.
; E.g.:
i.itemProperties[cF].delete
i.parent.userDefinedProperties.item[cF].delete ; deletes object & key, returns empty
; cN should not be in userDefinedProperties.item unless it gets converted to a cF
; but attempting .delete() of the userDefinedProperties object with cN doesnt' throw
; errors.
: E.g.:
i.parent.userDefinedProperties.item[cF].delete ; returns empty w/o error
V. GETTING PROPERTY OBJECTS' VALUES
;PREFERRED APPROACH:
v := i.itemProperties.item[b].value ; works
v := i.itemProperties.item(b).value ; works
v := i.itemProperties.item[cN].value ; works
v := i.itemProperties.item(cN).value ; works
v := i.itemProperties.item[cF].value ; works
v := i.itemProperties.item(cF).value ; works
v := i.itemProperties.item[n].value ; returns empty
v := i.itemProperties.item(n).value ; returns empty
; case-sensitive
;OTHER APPROACHES:
v := i.subject ; works with b only (not cF, CN, or n)
; not case-sensitive
v := i[b] ; works
v := i[cN] ; throws unknown name error
v := i[cF] ; throws unknown name error
v := i[n] : throws unknown name error
; case-sensitive
v := i.propertyAccessor.getProperty(cDASL . b) ; throws unknown name error
v := i.propertyAccessor.getProperty(cDASL . cN) ; works
v := i.propertyAccessor.getProperty(cDASL . cF) ; works
v := i.propertyAccessor.getProperty(cDASL . n) ; throws unknown name error
VI. SETTING PROPERTY OBJECTS' VALUES
;PREFERRED APPROACH:
i.itemProperties.item[b].value := "value" ; works - returns value set
i.itemProperties.item(b).value := "value" ; works - returns value set
i.itemProperties.item(cF).value := "value" ; works - returns value set
i.itemProperties.item(cF).value := "value" ; works - returns value set
i.itemProperties.item(cN).value := "value" ; works - returns value set
i.itemProperties.item(cN).value := "value" ; works - returns value set
i.itemProperties.item(n).value := "value" ; returns empty w/o error
i.itemProperties.item(n).value := "value" ; returns empty w/o error
;OTHER APPROACHES:
i.subject := "value" ; works with b only (not cF, CN, or n)
; not case-sensitive
i[b] := "value" ; works
i[cF] := "value" ; throws unknown name error
i[cN] := "value" ; throws unknown name error
i[n] := "value" ; throws unknown name error
; case-sensitive
i.propertyAccessor.setProperty(cDASL . b, "value") ; throws unknown name error
i.propertyAccessor.setProperty(cDASL . cF, "value") ; sets value, returns empty
i.propertyAccessor.setProperty(cDASL . cN, "value") ; sets value, returns empty
i.propertyAccessor.setProperty(cDASL . n, "value") ; throws unknown name error
*/
/* NOTABLE TESTED FAILURES
I. ENUMERATING PROPERTY OBJECTS
loop, % i.itemProperties.count
msgbox % i.itemProperties[a_index].name
; error: invalid number of parameters
n := "Subject"
for k, in i.userProperties
if k.name == n
msgbox % k.value
; always fails to find a built-in property
II. GETTING/FINDING PROPERTY OBJECTS
o := i.customProperty
; error: unknown name
o := i("Subject")
; error: nonexistant function
o := i.itemProperties["Subject"]
o := i.itemProperties["customProperty"]
; error: invalid number of parameters
o := i.itemProperties.Subject
o := i.userProperties.customProperty
; error: unknown name
n := "customProperty"
o := i.userProperties.find(n,false)
; error: unknown name b/c false searches only built-in
n := "Subject"
o := i.userProperties.find(n,true)
; fails silently b/c null returned when not found
III. ADDING PROPERTY OBJECTS
IV. REMOVING PROPERTY OBJECTS
i.itemProperties.remove("Subject")
; error: type mismatch
V. GETTING PROPERTY OBJECTS' VALUES
VI. SETTING PROPERTY OBJECTS' VALUES
i.itemProperties["Subject"].value := "i.itemProperties[""Subject""].value"
; error: invalid number of parameters
i.itemProperties.item["Subject"] := "i.itemProperties[""Subject""]"
; error: invalid number of parameters
i.itemProperties.item.Subject.value := "i.itemProperties.item.Subject.value"
; error: parameter is incorrect
*/