Victor Vogelpoel

Excellence is in the details

Putting PowerShell to good use: discovering the OpenText eDOCS WCF API

1 Comment


Intro to the series “Putting PowerShell to good use”

Another post in the series “Putting PowerShell to good use”. In my company, I am evangelizing PowerShell use for various purposes and it is starting to gain traction. PowerShell is not just for the system administrator, but a great tool for any developer as well. It lends itself well for all sort of tasks:

  • Discovering APIs;
  • Leveraging APIs;
  • Creating APIs;
  • Deployment;
  • Testing.

In these series “Putting PowerShell to good use”, I want to share here how I have been using PowerShell. In this second post: discovering APIs with PowerShell and learning about the Windows Communication Foundation (WCF) services API for eDOCS DM, a.k.a. the eDOCS DM WCF API.

The posts of the series so far:

  1. Discovering the eDOCS DM COM API
  2. Discovering the eDOCS DM WCF API (this post)

TL;DR

This new job at The ONE introduced me to OpenText eDOCS Document Management. This time I will be looking at the WCF API of eDOCS DM using PowerShell.

OpenText eDOCS

OpenText eDOCS DMeDOCS DM (previously Hummingbird eDOCS) is a Document Management system from OpenText. A lot of government, law firms and larger institutes are still maintaining large archives of documents in eDOCS DM. The 32-bit nature of the eDOCS DM server makes it sound dated, but it has some more modern touches, like a WCF web service.

Discovering the eDOCS WCF API

I found an OpenText eDOCS Windows 8.1 Phone/Desktop App in the Windows Store last year. Apparently, the eDOCS DM server has a web service exposed that the App exploits and this appears to be a self-hosted WCF service. Main advantage of using an eDOCS WCF service is that the client can be either 32 or 64-bits and does not need the eDOCS DM server API or client extensions installed to work with the eDOCS server.

What is WCF?

WCF definition from Microsoft’s MSDN page:

Windows Communication Foundation (WCF) is a framework by Microsoft for building service-oriented applications. Using WCF, you can send data as asynchronous messages from one service endpoint to another. A service endpoint can be part of a continuously available service hosted by IIS, or it can be a service hosted in an application. An endpoint can be a client of a service that requests data from a service endpoint. The messages can be as simple as a single character or word sent as XML, or as complex as a stream of binary data.

 

Enabling the eDOCS DM WCF service

To use the eDOCS WCF service, two things must be enabled: the HTTP (unencrypted) network binding must be enabled and the Windows service “Open Text eDOCS DM Server WCF Host” must be running.

 

The HTTP (unencrypted) binding must be enabled in the Network Binding Configuration of the eDOCS DM Administrator: eDOCS DM Administrator > Tab “FOLB Settings” > Button “Network bindings:

eDOCS DM Server - Configuratie netwerk

The Windows service “Open Text eDOCS DM Server WCF Host” must be running:

eDOCS WCF service running

On my eDOCS server VM, I had to start the eDOCS DM Server WCF host in the eDOCS Administrator. Back in the Windows 8 eDOCS app, I entered the eDOCS DM web service URL and port http://10.30.10.30:8080 and the eDOCS Windows 8.1 App started to work!

Enter PowerShell

Again enter PowerShell. In PowerShell 3+, it’s peanuts to access Web Services. Invoke-WebServiceProxy will create a client proxy to the web service and ready to call its operation methods.

In theory, then.

Looking at the eDOCS WCF web service operation methods, they seem to be as low level as the eDOCS COM API. The next PowerShell code snippet is getting the available libraries from the eDOCS, WCF style:

$DMClient = New-WebServiceProxy -Uri 'http://10.30.10.30:8080/DMSvr/?wsdl' -Namespace 'eDOCSDM'

# DMProxy.GetLoginLibrariesReply GetLoginLibraries(DMProxy.GetLoginLibrariesCall call)
$loginCall = new-Object eDOCSDM.GetLoginLibrariesCall
# DMProxy.GetLoginLibrariesCall
$loginReply = $DMClient.GetLoginLibraries($loginCall)
if ($loginReply -ne $null)
{
  if ($loginReply.resultCode -eq 0)
  {
    $libs = $loginReply.libraries
    # Output the available eDOCS libraries
    $libs
  }
  else
  {
    Write-Host "Error: $($loginReply.resultCode)"
  }
}

Please note that the sample code does not have proper WCF channel closing, or aborting in case of an error. But, hey! We’re discovering the API, not writing production code! Still, a lot of plumbing is needed.
And the output of this script is:

libraries   ExtensionData                                      extProperties resultCode
---------   -------------                                      ------------- ----------
{SAMPLELIB} System.Runtime.Serialization.ExtensionDataObject                          0


Proxy port problems

OpenText actually has a sample client reference .NET application that leverages the eDOCS WCF service. I learned from that implementation that the eDOCS WCF service actually has two service ports: the ‘SVC‘ and ‘OBJ‘ port. Unfortunately, the ports do not seem to be properly exposed in the proxy that New-WebServiceProxy CmdLet constructs from the WSDL…

I couldn’t find a proper way to expose both ports and reverted to use the Visual Studio SvcUtil.exe tool to generate a proxy C# class. The compiled proxy class DLL does expose both eDOCS service ports and can easily be loaded and used in PowerShell.

Another advantage of this proxy DLL is that I can specify my own WCF bindings and binding settings. For example, I would need to define a custom binding when MTOM (Message Transmission Optimization Mechanism) needs to me used on the channel.

The two eDOCS WCF interface ports expose a lot of operations. (I’ve simplified the table by removing the async operations and C# interface-specific operation implementations).

TypeName: DMObjClient

Name MemberType Definition
---- ---------- ----------
Clone Method OpenText.DMSvr.InteropSvc.CloneReply Clone(OpenText.DMSvr.InteropSvc.CloneCall call)
CommitStream Method OpenText.DMSvr.InteropSvc.StreamObjectReply CommitStream(OpenText.DMSvr.InteropSvc.CommitStreamCall call)
GetData Method OpenText.DMSvr.InteropSvc.GetDataReply GetData(OpenText.DMSvr.InteropSvc.GetDataCall call)
GetDataW Method OpenText.DMSvr.InteropSvc.GetDataReply GetDataW(OpenText.DMSvr.InteropSvc.GetDataCall call)
GetMetaData Method OpenText.DMSvr.InteropSvc.GetMetaDataReply GetMetaData(OpenText.DMSvr.InteropSvc.GetMetaDataCall call)
GetMetaDataW Method OpenText.DMSvr.InteropSvc.GetMetaDataReply GetMetaDataW(OpenText.DMSvr.InteropSvc.GetMetaDataCall call)
GetMetaRowCount Method OpenText.DMSvr.InteropSvc.GetMetaRowCountReply GetMetaRowCount(OpenText.DMSvr.InteropSvc.GetMetaRowCountCall call)
GetReadStream Method OpenText.DMSvr.InteropSvc.GetReadStreamReply GetReadStream(OpenText.DMSvr.InteropSvc.GetReadStreamCall call)
GetRowContent Method OpenText.DMSvr.InteropSvc.GetRowContentReply GetRowContent(OpenText.DMSvr.InteropSvc.GetRowContentCall call)
GetRowCount Method OpenText.DMSvr.InteropSvc.GetRowCountReply GetRowCount(OpenText.DMSvr.InteropSvc.GetRowCountCall call)
GetUIMetaData Method OpenText.DMSvr.InteropSvc.GetUIMetaDataReply GetUIMetaData(OpenText.DMSvr.InteropSvc.GetUIMetaDataCall call)
GetUIMetaDataRowCount Method OpenText.DMSvr.InteropSvc.GetUIMetaDataRowCountReply GetUIMetaDataRowCount(OpenText.DMSvr.InteropSvc.GetUIMetaDataRowCountCall call)
GetUIMetaDataW Method OpenText.DMSvr.InteropSvc.GetUIMetaDataReply GetUIMetaDataW(OpenText.DMSvr.InteropSvc.GetUIMetaDataCall call)call)
GetWriteStream Method OpenText.DMSvr.InteropSvc.GetWriteStreamReply GetWriteStream(OpenText.DMSvr.InteropSvc.GetWriteStreamCall call)
NewEnum Method OpenText.DMSvr.InteropSvc.NewEnumReply NewEnum(OpenText.DMSvr.InteropSvc.NewEnumCall call)
NextData Method OpenText.DMSvr.InteropSvc.NextDataReply NextData(OpenText.DMSvr.InteropSvc.NextDataCall call)
QueryContentFormats Method OpenText.DMSvr.InteropSvc.QueryContentFormatsReply QueryContentFormats(OpenText.DMSvr.InteropSvc.QueryContentFormatsCall call)
ReadStream Method OpenText.DMSvr.InteropSvc.ReadStreamReply ReadStream(OpenText.DMSvr.InteropSvc.ReadStreamCall call)
ReleaseData Method OpenText.DMSvr.InteropSvc.ReleaseDataReply ReleaseData(OpenText.DMSvr.InteropSvc.ReleaseDataCall call)
ReleaseObject Method OpenText.DMSvr.InteropSvc.ReleaseObjectReply ReleaseObject(OpenText.DMSvr.InteropSvc.ReleaseObjectCall call)
Reset Method OpenText.DMSvr.InteropSvc.ResetReply Reset(OpenText.DMSvr.InteropSvc.ResetCall call)
SaveCostInfo Method OpenText.DMSvr.InteropSvc.SaveCostInfoReply SaveCostInfo(OpenText.DMSvr.InteropSvc.SaveCostInfoCall call)
SeekStream Method OpenText.DMSvr.InteropSvc.SeekStreamReply SeekStream(OpenText.DMSvr.InteropSvc.SeekStreamCall call)
Skip Method OpenText.DMSvr.InteropSvc.SkipReply Skip(OpenText.DMSvr.InteropSvc.SkipCall call)
StatStream Method OpenText.DMSvr.InteropSvc.StatStreamReply StatStream(OpenText.DMSvr.InteropSvc.StatStreamCall call)
StreamClose Method OpenText.DMSvr.InteropSvc.StreamCloseReply StreamClose(OpenText.DMSvr.InteropSvc.StreamCloseCall call)
StreamCount Method OpenText.DMSvr.InteropSvc.StreamCountReply StreamCount(OpenText.DMSvr.InteropSvc.StreamCountCall call)
WriteStream Method OpenText.DMSvr.InteropSvc.WriteStreamReply WriteStream(OpenText.DMSvr.InteropSvc.WriteStreamCall call)


TypeName: DMSvcClient

Name MemberType Definition
---- ---------- ----------
CreateObject Method OpenText.DMSvr.InteropSvc.DocObjectReply CreateObject(OpenText.DMSvr.InteropSvc.SecureObjectCall call)
DeleteObject Method OpenText.DMSvr.InteropSvc.BaseReplyDescriptor DeleteObject(OpenText.DMSvr.InteropSvc.DocObjectCall call)
FetchObject Method OpenText.DMSvr.InteropSvc.DocObjectReply FetchObject(OpenText.DMSvr.InteropSvc.DocObjectCall call)
GetClientParameters Method OpenText.DMSvr.InteropSvc.GetClientParametersReply GetClientParameters(OpenText.DMSvr.InteropSvc.GetClientParametersCall call)
GetDoc Method OpenText.DMSvr.InteropSvc.GetDocReply GetDoc(OpenText.DMSvr.InteropSvc.GetDocCall call)
GetDocsForm Method OpenText.DMSvr.InteropSvc.GetFormReply GetDocsForm(OpenText.DMSvr.InteropSvc.GetFormCall call)
GetDocSvr3 Method OpenText.DMSvr.InteropSvc.GetDocReplySvr3 GetDocSvr3(OpenText.DMSvr.InteropSvc.GetDocCallSvr3 call)
GetLoginLibraries Method OpenText.DMSvr.InteropSvc.GetLoginLibrariesReply GetLoginLibraries(OpenText.DMSvr.InteropSvc.GetLoginLibrariesCall call)
GetNetwareServers Method OpenText.DMSvr.InteropSvc.NetwareServersReply GetNetwareServers(OpenText.DMSvr.InteropSvc.GetNetwareServersCall call)
GetServerList Method OpenText.DMSvr.InteropSvc.GetServerListReply GetServerList(OpenText.DMSvr.InteropSvc.GetServerListCall call)
GetTrustees Method OpenText.DMSvr.InteropSvc.SecureObjectReply GetTrustees(OpenText.DMSvr.InteropSvc.DocObjectCall call)
LoginSvr2 Method OpenText.DMSvr.InteropSvc.LoginReplySvr2 LoginSvr2(OpenText.DMSvr.InteropSvc.LoginCallSvr2 call)
LoginSvr5 Method OpenText.DMSvr.InteropSvc.LoginReplySvr5 LoginSvr5(OpenText.DMSvr.InteropSvc.LoginCallSvr5 call)
Lookup Method OpenText.DMSvr.InteropSvc.LookupReply Lookup(OpenText.DMSvr.InteropSvc.LookupCall call)
NetworkInfoService Method OpenText.DMSvr.InteropSvc.NetworkInfoServiceReply NetworkInfoService(OpenText.DMSvr.InteropSvc.NetworkInfoServiceCall call)
PutDoc Method OpenText.DMSvr.InteropSvc.PutDocReply PutDoc(OpenText.DMSvr.InteropSvc.PutDocCall call)
QueryIdleProcess Method OpenText.DMSvr.InteropSvc.QueryIdleProcessReply QueryIdleProcess(OpenText.DMSvr.InteropSvc.QueryIdleProcessCall call)
RecentDocs Method OpenText.DMSvr.InteropSvc.RecentDocsReply RecentDocs(OpenText.DMSvr.InteropSvc.RecentDocsCall call)
RecentDocsSvr2 Method OpenText.DMSvr.InteropSvc.RecentDocsReplySvr2 RecentDocsSvr2(OpenText.DMSvr.InteropSvc.RecentDocsCallSvr2 call)
Search Method OpenText.DMSvr.InteropSvc.SearchReply Search(OpenText.DMSvr.InteropSvc.SearchCall call)
SearchSvr2 Method OpenText.DMSvr.InteropSvc.SearchReplySvr2 SearchSvr2(OpenText.DMSvr.InteropSvc.SearchCallSvr2 call)
SearchSvr3 Method OpenText.DMSvr.InteropSvc.SearchReplySvr3 SearchSvr3(OpenText.DMSvr.InteropSvc.SearchCallSvr3 call)
SetTrustees Method OpenText.DMSvr.InteropSvc.BaseReplyDescriptor SetTrustees(OpenText.DMSvr.InteropSvc.SecureObjectCall call)
SQLService Method OpenText.DMSvr.InteropSvc.SQLServiceReply SQLService(OpenText.DMSvr.InteropSvc.SQLServiceCall call)
UpdateObject Method OpenText.DMSvr.InteropSvc.DocObjectReply UpdateObject(OpenText.DMSvr.InteropSvc.DocObjectCall call)

 

eDOCS DM WCF code sample

Now show me the code! How do we use the eDOCS DM WCF API from PowerShell?

The eDOCS DM WCF call model is:

  • Create a proxy to the WCF web service by combining binding, port and endpoint;
  • Create a operation “Call” object and fill the object;
  • Execute the operation with the Call object;
  • Use information in the operation “Reply” object;
  • Close the channel (or abort if in exception).

As I mentioned before, I will be using a compiled C# client proxy to the WCF server. In the code below this is “DMSvrInteropSvcImpl.dll”.

Script to authenticate (logging on) to the eDOCS DM server using WCF proxy:

# Load my own eDOCS proxy DLL
add-type -LiteralPath 'C:\TEMP\eDOCSWCF\DMSvrInteropSvcImpl.dll'

# Create the client proxy for the SVC port
$bindingObj = New-Object System.Servicemodel.BasicHttpBinding
$bindingObj.MaxReceivedMessageSize = 0x7fffffff;
$bindingObj.ReaderQuotas.MaxArrayLength = 0x7fffffff;
$bindingObj.ReaderQuotas.MaxStringContentLength = 0x7fffffff;
$addrObj = New-Object System.ServiceModel.EndpointAddress('http://10.30.10.30:8080/DMSvr/Svc');

$newSvcClient = New-Object DMSvcClient($bindingObj, $addrObj)
# Output the client object for reference
$newSvcClient

# Now Login. First create a LoginInfo object

$loginInfo = New-Object OpenText.DMSvr.Serializable.DMSvrLoginInfo
$loginInfo.username = 'ADMINISTRATOR'
$loginInfo.password = 'myvoiceismypassword'
$loginInfo.loginContext = 'SAMPLELIB'
$loginInfo.network = 0

# And then an LoginCall object
$logincall = new-object OpenText.DMSvr.InteropSvc.LoginCallSvr5
$logincall.dstIn = ''
$logincall.loginInfo = ,$loginInfo
$logincall.authen = 1

try
{
  $loginReply = $newSvcClient.LoginSvr5($logincall)
  # Output the reply object and some of its properties
  $loginReply
  $loginReply.loginProperties.propertyNames
  $loginReply.loginProperties.propertyValues
  $newSvcClient.Close()
}
catch
{
  $newSvcClient.Abort()
  Write-Host "ERROR: $($_.Exception.Message)"
}

And the output of the script is:

ChannelFactory    : System.ServiceModel.ChannelFactory`1[IDMSvc]
ClientCredentials : System.ServiceModel.Description.ClientCredentials
State             : Created
InnerChannel      : IDMSvc
Endpoint          : System.ServiceModel.Description.ServiceEndpoint
DSTOut            : 68f40667fdef7422773f688e6ad8d756506a00d3…df8353a3e147df44bb2e7a572e
loginProperties   : OpenText.DMSvr.Serializable.NamedProperties
ExtensionData     : System.Runtime.Serialization.ExtensionDataObject
extProperties     :
resultCode        : 0

%TARGET_LIBRARY
%DOCS_PRIMARY_GROUP
%DOCS_PRIMARY_GROUP_SYSID
%DOCS_USER_ID
%LAST_LOGIN_TIME
%LAST_LOGIN_DATE
%LOGIN_TIME
%LOGIN_DATE

SAMPLELIB
DOCS_SUPERVISORS
1
ADMINISTRATOR
15:27:15
16-5-2015
15:27:49
16-5-2015

 
Again, a lot of plumbing is necessary to do a single (eDOCS) WCF call; WCF does add another layer of complexity. In this sample case, the WCF channel is properly closed or aborted.

Concluding

Discovering the eDOCS APIs is one thing, using the APIs to discover the eDOCS DM workings is another. eDOCS is still a complex product to master and even with the WCF ports, it takes several API calls to accomplish something. The eDOCS WCF API is as low-level as the COM API. It would be nice to have a more high-level eDOCS API.

PowerShell helps me to interactively discover the eDOCS WCF APIs and work out proper method calling order to accomplish something. I think it is putting PowerShell to great use.

In the next episode of the series “Putting PowerShell to good use” is leveraging APIs to get stuff done with APIs.

To be continued…

Advertisements

Author: Victor Vogelpoel

Dad, SharePoint technical specialist, PowerShell architect, photographer and just a guy whose life happens while trying planning it.

One thought on “Putting PowerShell to good use: discovering the OpenText eDOCS WCF API

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s