Calling OTRS Web Services from .NET

In one of the current projects I have to connect to OTRS ticketing back-end.

OTRS is an open-source ticketing application and has a web service interface. I thought that querying the OTRS from NET should be straightforward because there were some examples for PHP that looked easy enough, but I was wrong.


The Symptoms

First of all, the WSDL descriptor for the web service has to be crafted almost by hand, as the generic interface for OTRS allows you to make your own web service interfaces, picking what operations you want to allow and how you should name them. It involved a lot of work for one of the customer IT guys 😉

The second stumble is that the SOAP interface for OTRS methods uses wrapped types, so you have to make the proxy class using SVCUTIL.EXE tool with /wrapped option against the WSDL.

The third problem was that choice types in WSDL is not supported by the WCF default serializer, and you have to "downgrade" it to the old XML serializer calling SVCUTIL.EXE with additional /serializer:XmlSerializer option, too.

Finally, I managed to make a call with the C# proxy client class against OTRS, but my joy was short-lived. The call to OTRS methods always returned null as a result.

The Cause

I fired up Fiddler to investigate the SOAP call. The call was made well and the OTRS responded in kind with a SOAP response filled with data. But, a slight mismatch was detected: the namespace of the response message element was not the same as the request message. Consequently, the NET proxy didn’t deserialize the message to the right type as the namespaces of the data type in NET and the SOAP response didn’t match.

The Solution

In the generated proxy class I looked up the method response data types. In one of them I found this attribute:

[System.ServiceModel.MessageBodyMemberAttribute(Namespace=, Order=0)]

and I changed it into

[System.ServiceModel.MessageBodyMemberAttribute(Namespace=, Order=0)]

Save, build, run and enjoy the correctly-deserialized response from OTRS.

Helpful links

How to Set Printer Default Paper Bin in .NET

Yesterday I was challenged with a task that seemed trivial but it was a real pain to solve.

The customer had a VB.NET application that spawns Adobe Reader via command line options to force a specific PDF file to be printed on a specified printer (using the “semi-clandestine” /t switch). The problem that they were facing was that the VB.NET Compatibility Power Pack’s Printer object (used to simulate the equivalent object in VB6) didn’t change the default paper bin (or tray) for the printer. The change just sticks to the application’s session lifetime.

I turned to Win32 API for the solution. Indeed, there is a winspool.drv library that has the necessary functions to change several printer parameters. I used many blogs and forum posts for a source, which I list here:

Finally, the solution involved coaxing the Win32 API structures into the .NET world using marshalling (using System.Marshal namespace and its methods). I had no success until I set the CharSet attribute to Auto encoding for the API structures. It seems that by default it uses Ansi encoding and the data is messed up.

The final code for the tray change routine is here (it’s in VB.NET but easily translated into C#). It has four main methods:

  • SetTray (sets the default printer tray to a tray number. In VB.NET it’s the Printer.PaperBin or RawKind property)
  • GetPrinterName (gets the default printer name)
  • SavePrinterSettings (copies the printer settings into memory)
  • RestorePrinterSettings (replaces the printer settings with previously saved ones)
Code Snippet
  1. Imports System.Drawing.Printing
  2. Imports System.Runtime.InteropServices
  5. Public Class PrinterNative
  8.     Private Declare Auto Function DocumentProperties Lib "winspool.drv" _
  9.      (ByVal hWnd As IntPtr, ByVal hPrinter As IntPtr, ByVal pDeviceName As String, _
  10.       ByVal pDevModeOutput As IntPtr, ByVal pDevModeInput As IntPtr, ByVal fMode As Int32) As Integer
  12.     Public Declare Function GetPrinter Lib "winspool.drv" Alias "GetPrinterW" _
  13.      (ByVal hPrinter As IntPtr, ByVal Level As Integer, ByVal pPrinter As IntPtr, _
  14.       ByVal cbBuf As Integer, ByRef pcbNeeded As Integer) As Integer
  16.     Private Declare Function SetPrinter Lib "winspool.drv" Alias "SetPrinterA" _
  17.      (ByVal hPrinter As IntPtr, ByVal level As Integer, ByVal pPrinterInfoIn As IntPtr, _
  18.       ByVal command As Int32) As Boolean
  20.     <DllImport("winspool.drv", EntryPoint:="OpenPrinterA", ExactSpelling:=True, _
  21.        SetLastError:=True, CallingConvention:=CallingConvention.StdCall, _
  22.        CharSet:=CharSet.Ansi)> _
  23.     Private Shared Function OpenPrinter(ByVal pPrinterName As String, _
  24.   ByRef hPrinter As IntPtr, ByRef pDefault As PRINTER_DEFAULTS) As Boolean
  25.     End Function
  27.     <DllImport("winspool.drv", EntryPoint:="ClosePrinter", SetLastError:=True, ExactSpelling:=True, _
  28.      CallingConvention:=CallingConvention.StdCall)> _
  29.     Private Shared Function ClosePrinter(ByVal hPrinter As Int32) As Boolean
  30.     End Function
  32.     Declare Function GetDefaultPrinter Lib "winspool.drv" Alias "GetDefaultPrinterA" _
  33.      (ByVal pszBuffer As System.Text.StringBuilder, ByRef pcchBuffer As Int32) As Boolean
  35.     Declare Function SetDefaultPrinter Lib "winspool.drv" Alias "SetDefaultPrinterA" _
  36.      (ByVal pszPrinter As String) As Boolean
  38.     Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
  39.      (ByVal hpvDest As IntPtr, ByVal hpvSource As IntPtr, ByVal cbCopy As Long)
  42.     Private Structure PRINTER_DEFAULTS
  43.         Dim pDatatype As String
  44.         Dim pDevMode As Long
  45.         Dim pDesiredAccess As Long
  46.     End Structure
  48.     Private Const STANDARD_RIGHTS_REQUIRED = &HF0000
  49.     Private Const PRINTER_ACCESS_ADMINISTER = &H4
  50.     Private Const PRINTER_ACCESS_USE = &H8
  53.     Private Const DM_IN_BUFFER As Integer = 8
  54.     Private Const DM_IN_PROMPT As Integer = 4
  55.     Private Const DM_OUT_BUFFER As Integer = 2
  57.     Private Structure PRINTER_INFO_9
  58.         Dim pDevMode As IntPtr
  59.     End Structure
  63.     <StructLayout(LayoutKind.Sequential)> _
  64.     Private Structure PRINTER_INFO_2
  65.         <MarshalAs(UnmanagedType.LPTStr)> Public pServerName As String
  66.         <MarshalAs(UnmanagedType.LPTStr)> Public pPrinterName As String
  67.         <MarshalAs(UnmanagedType.LPTStr)> Public pShareName As String
  68.         <MarshalAs(UnmanagedType.LPTStr)> Public pPortName As String
  69.         <MarshalAs(UnmanagedType.LPTStr)> Public pDriverName As String
  70.         <MarshalAs(UnmanagedType.LPTStr)> Public pComment As String
  71.         <MarshalAs(UnmanagedType.LPTStr)> Public pLocation As String
  73.         Public pDevMode As IntPtr
  75.         <MarshalAs(UnmanagedType.LPTStr)> Public pSepFile As String
  76.         <MarshalAs(UnmanagedType.LPTStr)> Public pPrintProcessor As String
  77.         <MarshalAs(UnmanagedType.LPTStr)> Public pDatatype As String
  78.         <MarshalAs(UnmanagedType.LPTStr)> Public pParameters As String
  80.         Public pSecurityDescriptor As IntPtr
  81.         Public Attributes As Integer
  82.         Public Priority As Integer
  83.         Public DefaultPriority As Integer
  84.         Public StartTime As Integer
  85.         Public UntilTime As Integer
  86.         Public Status As Integer
  87.         Public cJobs As Integer
  88.         Public AveragePPM As Integer
  89.     End Structure
  92.     <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
  93.     Public Structure DEVMODE
  94.         <MarshalAs(UnmanagedType.ByValTStr, Sizeconst:=32)> Public pDeviceName As String
  95.         Public dmSpecVersion As Short
  96.         Public dmDriverVersion As Short
  97.         Public dmSize As Short
  98.         Public dmDriverExtra As Short
  99.         Public dmFields As Integer
  100.         Public dmOrientation As Short
  101.         Public dmPaperSize As Short
  102.         Public dmPaperLength As Short
  103.         Public dmPaperWidth As Short
  104.         Public dmScale As Short
  105.         Public dmCopies As Short
  106.         Public dmDefaultSource As Short
  107.         Public dmPrintQuality As Short
  108.         Public dmColor As Short
  109.         Public dmDuplex As Short
  110.         Public dmYResolution As Short
  111.         Public dmTTOption As Short
  112.         Public dmCollate As Short
  113.         <MarshalAs(UnmanagedType.ByValTStr, Sizeconst:=32)> Public dmFormName As String
  114.         Public dmUnusedPadding As Short
  115.         Public dmBitsPerPel As Integer
  116.         Public dmPelsWidth As Integer
  117.         Public dmPelsHeight As Integer
  118.         Public dmNup As Integer
  119.         Public dmDisplayFrequency As Integer
  120.         Public dmICMMethod As Integer
  121.         Public dmICMIntent As Integer
  122.         Public dmMediaType As Integer
  123.         Public dmDitherType As Integer
  124.         Public dmReserved1 As Integer
  125.         Public dmReserved2 As Integer
  126.         Public dmPanningWidth As Integer
  127.         Public dmPanningHeight As Integer
  129.     End Structure
  133.     Private pOriginalDEVMODE As IntPtr
  136.     Public Sub SavePrinterSettings(ByVal printerName As String)
  137.         Dim Needed As Integer
  138.         Dim hPrinter As IntPtr
  139.         If printerName = "" Then Exit Sub
  141.         Try
  142.             If OpenPrinter(printerName, hPrinter, Nothing) = False Then Exit Sub
  143.             'Save original printer settings data (DEVMODE structure)
  144.             Needed = DocumentProperties(Form1.Handle, hPrinter, printerName, Nothing, Nothing, 0)
  145.             Dim pFullDevMode As IntPtr = Marshal.AllocHGlobal(Needed) 'buffer for DEVMODE structure
  146.             DocumentProperties(Form1.Handle, hPrinter, printerName, pFullDevMode, Nothing, DM_OUT_BUFFER)
  147.             pOriginalDEVMODE = Marshal.AllocHGlobal(Needed)
  148.             CopyMemory(pOriginalDEVMODE, pFullDevMode, Needed)
  149.         Catch ex As Exception
  150.             MsgBox(ex.Message)
  151.         End Try
  152.     End Sub
  154.     Public Sub RestorePrinterSettings(ByVal printerName As String)
  155.         Dim hPrinter As IntPtr
  156.         If printerName = "" Then Exit Sub
  158.         Try
  159.             If OpenPrinter(printerName, hPrinter, Nothing) = False Then Exit Sub
  160.             Dim PI9 As New PRINTER_INFO_9
  161.             PI9.pDevMode = pOriginalDEVMODE
  162.             Dim pPI9 As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(PI9))
  163.             Marshal.StructureToPtr(PI9, pPI9, True)
  164.             SetPrinter(hPrinter, 9, pPI9, 0&)
  165.             Marshal.FreeHGlobal(pPI9) 'pOriginalDEVMODE will be free too
  166.             ClosePrinter(hPrinter)
  169.         Catch ex As Exception
  170.             MsgBox(ex.Message)
  171.         End Try
  174.     End Sub
  176.     Function GetPrinterName() As String
  177.         Dim buffer As New System.Text.StringBuilder(256)
  178.         Dim PrinterName As String = String.Empty
  179.         'Get default printer's name
  180.         GetDefaultPrinter(buffer, 256)
  181.         PrinterName = buffer.ToString
  182.         If PrinterName = "" Then
  183.             MsgBox("Can't find default printer.")
  184.         End If
  185.         Return PrinterName
  186.     End Function
  188.     Sub SetTray(ByVal printerName As String, ByVal trayNumber As Integer)
  189.         Dim hPrinter As IntPtr
  190.         Dim Needed As Integer
  192.         OpenPrinter(printerName, hPrinter, Nothing)
  194.         'Get original printer settings data (DEVMODE structure)
  195.         Needed = DocumentProperties(IntPtr.Zero, hPrinter, printerName, Nothing, Nothing, 0)
  196.         Dim pFullDevMode As IntPtr = Marshal.AllocHGlobal(Needed) 'buffer for DEVMODE structure
  197.         DocumentProperties(IntPtr.Zero, hPrinter, printerName, pFullDevMode, Nothing, DM_OUT_BUFFER)
  199.         Dim pDevMode9 As DEVMODE = Marshal.PtrToStructure(pFullDevMode, GetType(DEVMODE))
  201.         ' Tray change
  202.         pDevMode9.dmDefaultSource = trayNumber
  204.         Marshal.StructureToPtr(pDevMode9, pFullDevMode, True)
  206.         Dim PI9 As New PRINTER_INFO_9
  207.         PI9.pDevMode = pFullDevMode
  209.         Dim pPI9 As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(PI9))
  210.         Marshal.StructureToPtr(PI9, pPI9, True)
  211.         SetPrinter(hPrinter, 9, pPI9, 0&)
  212.         Marshal.FreeHGlobal(pPI9) 'pFullDevMode will be free too
  214.         ClosePrinter(hPrinter)
  215.     End Sub
  217. End Class

I hope it can be useful to somebody and spare him or her a day or two looking for an answer.

Microsoft SharePoint Services and Windows Azure

I’m in the process of digesting the wealth of information that is pouring from PDC 2008 that’s in progress right now. I was unable to participate but I’m monitoring the news and blogs of the attendees.

There’s one thing that caught my attention. On day one the new cloud services platform was announced under the name of Windows Azure.

What is Windows Azure?

As I understand, it’s a cloud-services platform that exposes storage and application services that are hosted in Microsoft datacenters. It allows .NET applications to run “in the cloud” (i.e. not on-site or on-premises) and to benefit from the distributed services that it offers.


What’s in there for SharePoint?

One of the services that you can see in the diagram above is “Microsoft SharePoint Services”. First thing that should be clear is that this is notWindows SharePoint Services” (the free, basic SharePoint platform) norSharePoint Online” (Microsoft-hosted SharePoint farm). I think it’s SharePoint functionality exposed as services API. It can be seen that SharePoint Online uses these “services”.

Microsoft® SharePoint® Services & Dynamics® CRM Services (from Microsoft site)

In the future, developers will have access to SharePoint & CRM functionality for collaboration and building stronger customer relationships. With the flexibility to use familiar developer tools like Visual Studio, developers will be able to rapidly build applications that utilize SharePoint and CRM capabilities as developer services for their own applications. Developers can expect a breadth of SharePoint & CRM capabilities across the spectrum of on-premises, online & the Azure Services Platform.

So, it’s still not available (but it will be). I’ll keep a close watch on it, sure.

Content Management Interoperability Services, Good News for SharePoint?

A couple of weeks ago, Microsoft, EMC and IBM announced that they agreed on a joint specification of interoperability between their content management platforms. This new protocol is called Content Management Interoperability Services (CMIS) and it is based on SOAP, Atom and REST, all of them public and well-known. It will enable service-oriented architecture in a heterogeneous ECM environment.

What will this mean for SharePoint? Probably Microsoft will launch a CMIS-compliant connector for SharePoint that will expose its content for CMIS-capable applications and consume their content for indexing or query, as announced vaguely on SharePoint ECM blog. It will surely ease the life of a integration-project developer that juggles the information between different ECM systems (SharePoint, Documentum, FileNet…).

CMIS Overview

This new protocol also allows for rich mashups to be created while technology-agnostic (at least, in theory). The protocol also caters for security trimming and authorisation.

I’ll be tuned for the news around this new protocol. In the meanwhile you can download the draft specification yourself and take a look.