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 »