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">
     <PropertyRef Name="Id" />
    <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" />
  <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" />

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 »

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.