Have you ever tried to use ODATA Actions from PowerShell and realized that the Service Reference in your class library does not expose them? You can actually do it via the model’s ‘Execute()’ method. Here is a quick description on how to do it without writing too much URL style code.
Let’s presume we have a model referenced in a class library that contains a ‘Tasks’ EntitySet with a ‘Task’ entity that has a ‘Restart’ action as seen in the following extract:
<?xml version="1.0" encoding="utf-8"?> <edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx"> <edmx:DataServices m:DataServiceVersion="3.0" m:MaxDataServiceVersion="3.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"> <Schema Namespace="dfch.Models" xmlns="http://schemas.microsoft.com/ado/2009/11/edm"> <EntityType Name="Task"> <Key> <PropertyRef Name="Id" /> </Key> <Property Name="Id" Type="Edm.String" Nullable="false" /> <Property Name="Type" Type="Edm.String" Nullable="false" /> <Property Name="Status" Type="Edm.String" Nullable="false" /> <Property Name="Created" Type="Edm.DateTimeOffset" Nullable="false" /> <Property Name="Modified" Type="Edm.DateTimeOffset" Nullable="false" /> <Property Name="Uri" Type="Edm.String" /> <Property Name="AdditionalData" Type="Edm.String" /> </EntityType> </Schema> <Schema Namespace="Default" xmlns="http://schemas.microsoft.com/ado/2009/11/edm"> <EntityContainer Name="Utilities" m:IsDefaultEntityContainer="true"> <EntitySet Name="Tasks" EntityType="dfch.Models.Task" /> <FunctionImport Name="Restart" ReturnType="dfch.Models.Task" IsBindable="true" EntitySet="Tasks" m:IsAlwaysBindable="true"> <Parameter Name="bindingParameter" Type="dfch.Models.Task" /> </FunctionImport> </EntityContainer> </Schema> </edmx:DataServices> </edmx:Edmx>
You can then instantiate your model via the standard procedure and retrieve a ‘Task’ entity on which to invoke the ‘Restart’ action. Though the action itself it not visible on the ‘Task’ entity itself it is retrievable via the ‘EntityDescriptor’ and its ‘OperationDescriptors’ which lists all available actions on that entity.
A note towards ‘transient’ actions: as it seems, all actions are listed – even if the transient action would not be available for that specific entity. So you better be prepared to handle an HTTP 405 or similar. The target of the action contains the full URL plus some ‘garbage’ that seems to have been wrongly generated by the service reference. But after removing you can do a regular ‘Execute()’ on the model itself.
PS > # bind model PS > Add-Type -Path ".\VcdPoSHWrapper.dll"; PS > Add-Type -AssemblyName System.Data.Services.Client; PS > $v = New-Object VcdPoSHWrapper.Utilities.Container('http://www.example.com/Utilities.svc'); PS > $password = 'Q3VyaW91c0ZvclBhc3N3b3Jkcw' | ConvertTo-SecureString -asPlainText -Force PS > $username = "Edgar.Schnittenfittich"; PS > $cred = New-Object System.Management.Automation.PSCredential($username,$password); PS > $v.Credentials = $cred; PS > $v.Format.UseJson(); PS > # get a Task entity to act upon - you would probably do a smarter pick than this one here ... PS > $task = $v.Tasks | Select -First 1; PS > $task Id : 060d2220-23a9-41b8-85f4-7a96ee46f0a5 Type : SYSTEM Status : COMPLETED Created : 8/14/2014 9:19:03 AM +00:00 Modified : 8/14/2014 9:19:03 AM +00:00 Uri : http://www.example.com AdditionalData : PS > $entityDescriptor = $v.GetEntityDescriptor($task); PS > $entityDescriptor Identity : http://www.example.com/Utilities.svc/Tasks('060d2220-23a9-41b8-85f4-7a96ee46f0a5') SelfLink : http://www.example.com/Utilities.svc/Tasks('060d2220-23a9-41b8-85f4-7a96ee46f0a5') EditLink : http://www.example.com/Utilities.svc/Tasks('060d2220-23a9-41b8-85f4-7a96ee46f0a5') ReadStreamUri : EditStreamUri : Entity : VcdPoSHWrapper.Utilities.Task ETag : StreamETag : ParentForInsert : ParentPropertyForInsert : ServerTypeName : LightSwitchApplication.Models.Task LinkInfos : {} StreamDescriptors : {} OperationDescriptors : {System.Data.Services.Client.ActionDescriptor, System.Data.Services.Client.ActionDescriptor, System.Data.Services.Client.ActionDescriptor, System.Data.Services.Client.ActionDescriptor} State : Unchanged PS > # get action PS > $actionName = "Restart"; PS > $action = $entityDescriptor.OperationDescriptors |? Title -Match $actionName PS > $action Title Metadata Target State ----- -------- ------ ----- Container.Restart http://www.example.com/Utilities... http://www.example.com/Utilities... Unchanged PS > $actionUrl = $action.Target.AbsoluteUri.Replace(('{0}/' -f $entityDescriptor.ServerTypeName), ''); PS > $actionUrl = $actionUrl.Replace($action.Title, $actionName); PS > $abp = @(); PS > $bp = New-Object System.Data.Services.Client.BodyOperationParameter("Force", $true); PS > $abp += $bp; PS > # execute the action PS > $v.Execute($actionUrl, "POST", $abp);
[UPDATE 2014-09-14] You actually do not have to use the “Replace()” methods above. Interestingly it does not matter if you call the method with or without the ServerTypeName (regardless how it is defined in the schema).
So though it is not a single method call it is still doable and more feasible approach than to code the API call manually…
[UPDATE 2014-09-14] If you want to call one of the “Execute” overloads you first have to create a generic method via “MakeGenericMethod()” so you can call it from PowerShell. For an example on how to do this you might want to have a look at Lee Holmes’ blog post Invoking Generic Methods on Non-Generic Classes in PowerShell .
1 Comment »