Working with Outlook Item Properties

Helpful script writing tricks and HowTo's
badWithUserName
Posts: 11
Joined: 03 Feb 2022, 09:07
Contact:

Working with Outlook Item Properties

Post by badWithUserName » 18 Sep 2022, 11:17

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

*/

Return to “Tutorials (v1)”