Concepts: Custom Objects
  • 11 Minutes to read
  • Dark
    Light
  • PDF

Concepts: Custom Objects

  • Dark
    Light
  • PDF

Article summary

Introduction

In this article, we explore the concepts behind custom objects and how to manage custom object entries through scripting. For the purposes of this article, we’ll create a new custom object named “Custodian” with the following fields:

  • Name (Basic Text. Make this field required and ensure that the “Make this field the reference link” is checked)

  • Data Retained On (Date Only)

  • Locations (Multiple Choice with the following choices: MN, CA, NY, TX)

  • Notes (Rich Text)

After creating this object, we’ll take note that our custodian object id is 14 within the grid. Go back to editing the object and write down the field IDs for each field. The field IDs can be found on the top left of each field card in the format CF_*. For us, our field IDs are the following (yours will likely be different).

  • CF_43: Name

  • CF_97: Data Retained On

  • CF_58: Locations

  • CF_44: Notes

Create a Custom Object Entry

After a custom object is created within Agility Blue, we can perform create/read/update/delete operations on the entries for that object using scripts. All custom objects come standard with the following system fields:

  • Id

  • CreatedByFullName

  • CreatedById

  • CreatedOn

  • LastUpdatedByFullName

  • LastUpdatedById

  • LastUpdatedOn

In addition to the system fields, all fields defined for the custom object will be named CF_*, where the asterisk represents the field id. When creating a new custom object instance, you will need to provide fields in this format. Here is an example of creating a custodian object given the fields we defined for the IDs they represent:

$custodianObjectId = 14

# Use the ConvertTo-DateString command to convert the current date to a specific time zone:
$currentDateTime = ConvertTo-DateString `
    -DateTime (Get-Date) `
    -TimeZone "Central Standard Time" `
    -IncludeTime

$custodianToCreate = @{
  CF_43 = "Mary White"                      # Name
  CF_97 = $currentDateTime.ToString()       # Data Retained On
  CF_58 = "MN`nFL"                          # Locations
  CF_44 = "Custodian created via a script." # Notes
}

$createdCustodian = Add-CustomObject `
    -Entry $custodianToCreate `
    -CustomObjectId 14

# Output the created custodian as JSON
$createdCustodian | ConvertTo-Json -Depth 10

The output of this script:

{
  "CreatedOn": "2024-06-21T13:27:19.8428055Z",
  "LastUpdatedOn": "2024-06-21T13:27:19.8428055Z",
  "CF_44": "Custodian created via a script.",
  "CF_43": "Mary White",
  "CF_97": "2024-06-21T13:27:19+00:00",
  "CF_58": "MN\nFL",
  "CreatedById": "25a34a2f-e3d8-4690-88f5-6b399cf88c4c",
  "Id": 14,
  "LastUpdatedById": "25a34a2f-e3d8-4690-88f5-6b399cf88c4c"
}

Points of Interest:

  • If you use a variable that comes from the ConvertTo-DateString cmdlet for a date value, you will need to apply a .ToString() method on it, otherwise the script will produce an error. A direct string or constructed string variable will work without this need.

  • For our multiple choice field, we separate the choices that we want selected with the newline character: `n

Creating a Custom Object Entry that has a Reference Field

When working with reference fields on custom objects, the behavior is different than that of system objects. The reference values need to be stored as a JSON object array with the following properties on each JSON object:

[
  {
    "objectId": number,
    "key": number or string,
    "value": string
  }
]

The objectId parameter represents the object id that is being referenced, the key is that object’s instance you’d like to reference, and the value is the display value for that instance. Check out the reference fields section on the custom fields deep dive article for a table that outlines the key types and formatting for values depending on the object being referenced.

Here is an example of a JSON string that references a media log entry with a MediaLogEntryId of 5:

[{"objectId":3,"key":5,"value":"#5 - Mobile Device"}]

To work with this information in a PowerShell script, first edit the Custodian object we’ve been working with and add the following field:

  • Media Log Entries (Reference: Media Log Entry)

For us, the field ID ends up being CF_98. To create a custodian object that references a media log entry, modify the script to look like the following:

$custodianObjectId = 14

# Use the ConvertTo-DateString command to convert the current date to a specific time zone:
$currentDateTime = ConvertTo-DateString `
    -DateTime (Get-Date) `
    -TimeZone "Central Standard Time" `
    -IncludeTime

# Be careful with making sure that you follow this pattern to create the correct JSON representation
# That reference fields require!
$mediaLogEntriesRef = , @(
  @{ objectId = 3; key = 1; value = "#1 - Hard Drive" }
) | ConvertTo-Json -Compress

$custodianToCreate = @{
  CF_43 = "Mary White"                      # Name
  CF_97 = $currentDateTime.ToString()       # Data Retained On
  CF_58 = "MN`nFL"                          # Locations
  CF_44 = "Custodian created via a script." # Notes
  CF_98 = $mediaLogEntriesRef.ToString()    # Media Log Entries
}

$createdCustodian = Add-CustomObject `
    -Entry $custodianToCreate `
    -CustomObjectId 14

# Output the created custodian as JSON
$createdCustodian | ConvertTo-Json -Depth 10

The output of this script:

{
  "CreatedOn": "2024-06-21T14:08:16.0605779Z",
  "LastUpdatedOn": "2024-06-21T14:08:16.0605779Z",
  "CF_98": "[{\"objectId\":3,\"key\":1,\"value\":\"#1 - Hard Drive\"}]",
  "CF_44": "Custodian created via a script.",
  "CF_43": "Mary White",
  "CF_97": "2024-06-21T14:08:15+00:00",
  "CF_58": "MN\nFL",
  "CreatedById": "25a34a2f-e3d8-4690-88f5-6b399cf88c4c",
  "Id": 15,
  "LastUpdatedById": "25a34a2f-e3d8-4690-88f5-6b399cf88c4c"
}

Be Careful!

Reference fields for custom objects are saved as JSON strings directly on the field. You need to be very careful with providing the correct structure: an array of objects with the properties objectId, key and value. Failing to do this will result in an error on the grid page for end users and you will need to use a script to delete the entries with incorrect structures.

Get a Custom Object Entry

Use the Get-CustomObject command to retreive an existing custom object entry. The command requires you to pass in a custom object id and the id of the entry. Here is an example script that retrieves a custodian entry that we created earlier in this article:

$custodian = Get-CustomObject -CustomObjectId 14 -Id 15

$custodian | ConvertTo-Json -Depth 10

The output of this script:

{
  "LastUpdatedByFullName": "Max Miller",
  "LastUpdatedOn": "2024-06-21T14:08:16.0605779Z",
  "CF_98": "[{\"objectId\":3,\"key\":1,\"value\":\"#1 - Hard Drive\"}]",
  "CF_44": "Custodian created via a script.",
  "CreatedByFullName": "Max Miller",
  "CF_43": "Mary White",
  "CF_97": "2024-06-21T14:08:15+00:00",
  "CF_58": "MN\nFL",
  "CreatedById": "25a34a2f-e3d8-4690-88f5-6b399cf88c4c",
  "CreatedOn": "2024-06-21T14:08:16.0605779Z",
  "Id": 15,
  "LastUpdatedById": "25a34a2f-e3d8-4690-88f5-6b399cf88c4c"
}

Points of Interest:

  • The CustomObjectId parameter that the command requires is the object id of the object. You can find an object’s id by navigating to the objects page within the application under the settings menu and bringing the “Id” field in for the objects grid.

  • The fields that start with CF_* represent the field ids that were created for this object. Field Ids can be found while editing the object where each field card shows the field id on the top left of each field.

Update an Existing Custom Object Entry

After retrieving a custom object entry, you may modify any of the CF_* fields on it and save it back to the system by issuing the Set-CustomObject command. This command expects a custom object id and the custom object entry to be passed in as parameters. Here is an example of modifying the locations field on the custodian object we retrieved in the last section:

$objectId = 14
$custodianId = 15

# Retrieve the custodian
$custodian = Get-CustomObject `
    -CustomObjectId $objectId `
    -Id $custodianId

# Null check just to make sure our custodian exists
if ($null -eq $custodian) {
    Write-Error "Unable to find a custodian with an id of $custodianId"
    exit
}

# Add "TX" as a location to the existing locations
$custodian.CF_58 += "`nTX"

# Update the custodian
$updatedCustodian = Set-CustomObject `
    -CustomObjectId $objectId `
    -Entry $custodian

# Place the locations in a single-line csv to output the contents nicer
$locationsCsv = $updatedCustodian.CF_58.Replace("`n", ", ")

Write-Output "The locations for $($updatedCustodian.CF_43) (Id #$($updatedCustodian.Id)) has been updated to: $locationsCsv"

The output of this script:

The locations for Mary White (Id #15) has been updated to: MN, FL, TX

Points of Interest:

  • The recommended pattern for updating data as shown in the example above is to first retrieve an instance of the entry, update the properties you want to modify of that entry, and then save the entry back.

Delete an Existing Custom Object Entry

Deleting custom object entries can be achieved by using the Remove-CustomObject command. This command expects a custom object id and the id of the entry. Here is an example that shows how to delete an entry:

$objectId = 14
$custodianId = 15

Write-Output "Deleting custodian id $custodianId..."

Remove-CustomObject `
    -CustomObjectId $objectId `
    -Id $custodianId

# Try to retrieve the custodian to prove that it was deleted
$custodian = Get-CustomObject `
    -CustomObjectId $objectId `
    -Id $custodianId

if ($null -eq $custodian) {
    Write-Output "Custodian id $custodianId doesn't exist"
    exit
}

# The next line should not happen
Write-Error "Uh-oh, custodian id $custodianId still exists after attempting to delete it"

The output of this script:

Deleting custodian id 15...
Custodian id 15 doesn't exist

Points of Interest:

  • In the script above, we deleted the custom object first and then tried to retrieve it to prove that it was deleted. This isn’t a bad idea if you want to ensure something was deleted, but in optimized situations, you would only need to delete the entry because if something went wrong during the deletion process, the server would throw an exception that would be logged in the output.

Be Careful!

Deleting data cannot be reversed, so make sure you thoroughly test your code to minimize mistakes!

Object Entries Attached to Event Triggers

If you are interested in attaching an event trigger to a custom object entry, you will need to use the Get-InboundObjectId command first to extract the custom object id and determine if the inbound object instance is the one you want to run your script against. The custom object id and entry id are provided as a concatenated string separated by a colon : so you can use PowerShell’s -split function to achieve this. Here is an example of a script that only processes if the event provides a custom custodian object:

# We only care about custodian entries. We set this variable based on the fact that we
# know that our custodian object id is 14
$custodianObjectId = 14

# Get the inbound object id. The scripting pipeline provides this for any event trigger.
# It will be null if we execute the script manually.
$inboundObjectId = Get-InboundObjectId

# If the inbound object id is null, it means we're executing this script manually.
# Set it by using a test parameter or hard coded value
if ($null -eq $inboundObjectId) {
    $inboundObjectId = "14:201"
}

# The Get-InboundObjectId command provides custom object entries as a string that is 
# split by a colon ":". The left side of the colon is the custom object id, and the right 
# side is the entry id.
$objectId = $inboundObjectId -split ":" | Select-Object -First 1

# Note: You could alternatively use the array index accessor method, if you prefer:
# $objectId = ($inboundObjectId -split ":")[0]

# We can now conditionally check against the object id we want
if ($objectId -ne $custodianObjectId) {
    Write-Output "Inbound entry $inboundObjectId doesn't match the custodian object."
    
    # The event is coming from a different object. 
    # Return the inbound object instance back to the pipeline so no error is encountered for
    # "before save" events
    return Get-InboundObjectInstance
}

# The event is coming from the custom custodian object. Process what you want and return
# It back to the pipeline
$custodian = Get-InboundObjectInstance
 
# Again, this is only here in case we've executed the script manually for testing. It saves
# us time so we don't have to go out and actually trigger the event. The inbound object
# instance would not be null for the event.
if ($null -eq $custodian) {
    $entryId = $inboundObjectId -split ":" | Select-Object -Skip 1 -First 1

    # Again, you could use the array index accessor method instead, if you prefer:
    # $entryId = ($inboundObjectId -split ":")[1]
    
    $custodian = Get-CustomObject -CustomObjectId $custodianObjectId -Id $entryId
}

Write-Output "Custodian Id: $($custodian.Id)"
Write-Output ""
Write-Output "JSON Representation:"
$custodian | ConvertTo-Json -Depth 10

return $custodian

Triggering this script on our custodian object produces the following result:

Custodian Id: 15

JSON Representation:
{
  "LastUpdatedByFullName": "Max Miller",
  "LastUpdatedOn": "2024-07-10T08:05:16.0606652Z",
  "CF_98": "[{\"objectId\":3,\"key\":1,\"value\":\"#1 - Hard Drive\"}]",
  "CF_44": "Custodian created via a script.",
  "CreatedByFullName": "Max Miller",
  "CF_43": "Mary White",
  "CF_97": "2024-06-21T14:08:15+00:00",
  "CF_58": "MN\nFL",
  "CreatedById": "25a34a2f-e3d8-4690-88f5-6b399cf88c4c",
  "CreatedOn": "2024-06-21T14:08:16.0605779Z",
  "Id": 15,
  "LastUpdatedById": "25a34a2f-e3d8-4690-88f5-6b399cf88c4c"
}

Triggering this script on a different object produces the following result:

Inbound entry 15:2 doesn't match the custodian object.
System.Collections.Generic.Dictionary`2[System.String,System.Object]

Points of Interest:

  • The example above is a complete example with conditional guards that checks if the script was executed manually or by an event trigger

  • The important parts to highlight is that the Get-InboundObjectId command returns a concatenated string separated by a colon with two elements: The first element is the custom object id and the second element is the entry id. You split the string into an array using PowerShell’s -split command and then you can use the custom object id to check if the inbound object is the type of object you want to run your script against.

  • Because we’re writing the script for an event trigger, we need to make sure that we return an instance of the same object type we received, particularly for “Before Save” event triggers. The scripting pipeline is designed to emit an error and not save an object if it does not receive the same type back from the script for this type of event trigger action. This allows you to write custom validations where if they fail in the logic of the script, you could return a null value and the object will not be saved. Returning an object is not necessary if the script is being triggered on an “After Save” event action because the object has already been saved at the point the script begins to execute.