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 »