I recently wrote on how you can call ODATA actions via PowerShell. However, sometimes these actions return entity types that you cannot resolve easily in PowerShell. Normally you would resort to “Execute()”, but this is neither easily doable in PowerShell. But as most usual there is another way around this.

Instead of using “Execute()” to retrieve the data you rely on the (not so) good old “Invoke-RestMethod” cmdlet or something like “HttpClient” or “WebClient” (whatever you prefer) to POST a direct REST call against the Target URI of the action. The returned result is automatically converted to a “PSCustomObject” by the PowerShell runtime. You only have to deserialise this object to the native .NET object that was returned (if you had called it in a typefull way) – and there you are.

Continuing with the example in my previous post, in real life this would look similar to this:

# $action contains the action on the entity we would like to call
PS > $actionUrl = $action.Target.AbsoluteUri;
# $v contains our .NET DataService reference
# instead of using $v.Execute() we use regular REST call
# optionally we could specify a JSON encoded body to pass along
PS > $c = Invoke-RestMethod -Method POST -Uri $actionUrl;
# the returned content shows us that the data is actually a "Task" object
PS > $c
odata.metadata : http://www.example.com/Utilities.svc/$metadata#Tasks/@Element
Id       : 4ab50412-c0ab-499e-a775-6aa211067124
Type     : VirtualMachines('urn-deaddead-f2c9-477d-ac54-b1ae7c4d80af')/Restart
Status   : PENDING
Created  : 2014-09-14T16:19:38.9794091Z
Modified : 2014-09-14T16:19:38.9794091Z
Uri      : urn-deaddead-f2c9-477d-ac54-b1ae7c4d80af
AdditionalData : VirtualMachines('urn-deaddead-f2c9-477d-ac54-b1ae7c4d80af')/Restart

# ... and we see the returned data is converted to a PSCustomObject
PS > $c.GetType()
IsPublic IsSerial Name                   BaseType
-------- -------- ----                   --------
True   False  PSCustomObject               System.Object
# Converting this object back to JSON (how it was actually returned from the server) ...
PS > $json = $c | ConvertTo-Json;
{
  "odata.metadata":  "http://www.example.com/Utilities.svc/$metadata#Tasks/@Element",
  "Id":  "4ab50412-c0ab-499e-a775-6aa211067124",
  "Type":  "VirtualMachines(\u0027urn-deaddead-f2c9-477d-ac54-b1ae7c4d80af\u0027)/Restart",
  "Status":  "PENDING",
  "Created":  "2014-09-14T16:19:38.9794091Z",
  "Modified":  "2014-09-14T16:19:38.9794091Z",
  "Uri":  "urn-deaddead-f2c9-477d-ac54-b1ae7c4d80af",
  "AdditionalData":  "VirtualMachines(\u0027urn-deaddead-f2c9-477d-ac54-b1ae7c4d80af\u0027)/Restart"
}
# ... and with the help of the JavaScriptSerializer which allows us to specify an output type (second overload) ...
PS > $jss = New-Object System.Web.Script.Serialization.JavaScriptSerializer
PS > $jss.Deserialize

OverloadDefinitions
-------------------
T Deserialize[T](string input)
System.Object Deserialize(string input, type targetType)
# ... we convert our json string to a "real" .NET object
PS > $task = $jss.Deserialize($json, [OdataWrapper.Utilities.Task]);
PS > $task
Id       : 4ab50412-c0ab-499e-a775-6aa211067124
Type       : VirtualMachines('urn-deaddead-f2c9-477d-ac54-b1ae7c4d80af')/Restart
Status     : PENDING
Created    : 9/14/2014 4:19:38 PM +00:00
Modified     : 9/14/2014 4:19:38 PM +00:00
Uri      : urn-deaddead-f2c9-477d-ac54-b1ae7c4d80af
AdditionalData : VirtualMachines('urn-deaddead-f2c9-477d-ac54-b1ae7c4d80af')/Restart

# To dynamically determine the wrapped type we can query the metadata of the service
PS > [xml] $md = Invoke-RestMethod $v.GetMetadataUri()
PS > $md
xml                            Edmx
---                            ----
version="1.0" encoding="utf-8" Edmx

# ... and check its return type
PS > $mdAction = ($md.Edmx.DataServices.Schema |? Namespace -eq 'Default').EntityContainer.
  FunctionImport |? Name -eq $actionName;
Name             : Restart
ReturnType       : OdataWrapper.Utilities.Task
IsBindable       : true
EntitySet        : Tasks
IsAlwaysBindable : true
Parameter        : Parameter

# So you could also do it this way ...
PS > $task = New-Object $mdAction.ReturnType;
PS > $task.GetType()
IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    Task                                     System.Object
# ... and deserialise dynamically
PS > $task = $js.Deserialize($json, $task.GetType());

# We can optionally attach it to the entity tracker via "AttachTo()" ...
PS > $v.AttachTo($mdAction.EntitySet, $task);
# ... and verify it has been added by looking at $v.Entities

And the whole exercise in short reduces itself to a few lines of code:

PS > [xml] $md = Invoke-RestMethod $v.GetMetadataUri()
PS > $mdAction = ($md.Edmx.DataServices.Schema |? Namespace -eq 'Default').EntityContainer.FunctionImport |? Name -eq $actionName;
PS > $task = New-Object $mdAction.ReturnType;
PS > $c = Invoke-RestMethod -Method POST -Uri $actionUrl;
PS > $jss = New-Object System.Web.Script.Serialization.JavaScriptSerializer
PS > $task = $jss.Deserialize(($c | ConvertTo-Json), $task.GetType());
PS > $v.AttachTo($mdAction.EntitySet, $task);

1 Comment »

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.