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:
- Discovering the eDOCS DM COM API
- 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
eDOCS 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:
The Windows service “Open Text eDOCS DM Server WCF Host” must be 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…
July 7, 2015 at 09:19
Reblogged this on Dinesh Ram Kali..