Dynamics CRM + OData + PowerShell = Win

Many records in Microsoft Dynamics CRM support the ability to annotate them. This is commonly surfaced through the “Notes” property in the web interface. Both a title & body are available and it is possible to include file attachments. OneNote integration is even available in some versions but that takes this exercise way past 11.

I needed a simple way to add a note that a specific reminder was sent. This was generated on a server that did not have the SDK installed and I really wanted to try to avoid copying some libraries around for such a simple task. It was then that I remembered the OData services — and they were not just read-only.

This was my AHA! moment

I whipped up a function that takes a user record, retrieves their user id from CRM, then adds the annotation. The user record is important because like many — maybe even most or all — records in Microsoft Dynamics CRM this feature was built with relationships in mind. I needed to build that relationship.

The last piece of the puzzle was how to handle the information coming from the OData service and properly serializing data to push in. Lucky for me, PowerShell has great JSON and XML support. I opted for JSON as it feels better to my inner web developer. Bonus: it is easy to serialize a native PowerShell hash set to JSON with the ConvertTo-Json cmdlet. The hardest part was getting the ObjectId hash with the correct id for the record you want to annotate along with the correct LogicalName. Looking at existing notes is a huge help in validating the right values.

Here is the complete code for the function:

function Update-CRMNotes {
$org = "TEST"
$server = "crm-server:5555"
$svc = "http://$server/$org/XRMServices/2011/OrganizationData.svc"
$uri = "$svc/userSet()?`$filter=user_Email eq '$($user.Email)'"
$response = Invoke-WebRequest -Uri $uri -UseDefaultCredentials -UseBasicParsing -Headers @{Accept="application/json"}
if ($response.StatusCode -eq 200) {
$data = ConvertFrom-Json $response.Content
$id = $data.d.results[0].user_userId
$obj = @{
Subject = "Password Expiration Notice Sent"
NoteText = "Password expiration notice sent to $($user.Email)"
ObjectId = @{
Id = $id
LogicalName = "user"
$json = ConvertTo-Json $obj
Invoke-WebRequest -Method POST -Uri "$svc/AnnotationSet()" -Body $json -ContentType application/json -UseDefaultCredentials -UseBasicParsing | Out-Null

You may need to adjust a few of the properties depending on how your CRM organization is configured and how things are named.

Compatibility Note: This was developed and tested on Windows 2012 R2 and CRM 2013 on-premises. On Windows 7 (PS 3.0) the Invoke-WebRequest cmdlet throws an error “The ‘Accept’ header must be modified using the appropriate property or method.” I am sure this can be fixed/coded around but it is something to be aware of. I believe the CRM methods should work on 2011 and newer.