SCSM – Passing Attachments via Web Service e.g. SMA Web Service

If you do automation in a “Microsoft World” you probably start these days with Service Manager and Service Management Automation (SMA). At one point you will have a need for building a connector to another system for exchanging data. One example could be that you need to submit incident data from Service Manager to another ticket system e.g. ServiceNow. If you are lucky the target system offers a web service and you just need to pass the data in XML. That sounds pretty easy but how are you going to pass the attachments to the foreign system via web service? One approach is to get the attachments from the source, save it as files on a file share and then the files are pushed to or pulled from the target system. Yes this is one way, but why should you make this extra step and saving the attachment files on a file share? Isn’t it possible just passing the data in one shot? Well, this is the purpose of this blog post.

I don’t have access to ServiceNow nor another ticketing system which would offer a web service interface. For that reason I will use the SMA web service to show how to convert the attachment and passing it via string and XML data.

For a better understanding I created a graphic…

image

The first runbook is called Get-Attachment and has an input parameter WorkItemID. The runbook connects to Service Manager and iterates through the attachments of the specified work item in this example IR1234 and converts it to a Base64String. The last line of Get-Attachment runbook uses the Start-SmaRunbook cmdlet to call the Add-Attachment runbook. The Start-SmaRunbook cmdlet uses the SMA web service to trigger the runbook and submit the attachment data.  This is the step which proofs, that you can submit the attachment data through a web service call.

The Add-Attachment runbook uses the “XML” output string from Get-Attachment runbook and converts it to XML. The actual attachment content is converted back from the Base64String and finally added as attachment to the work item.

Well, yes it adds the attachments to the same work item where it got the items from (IR1234), but the purpose of this post is to show the round trip of the attachments. It does not make sense in a real-world scenario I know, again it is just to show how to solve this problem technically.

There are few posts on the internet which show how to export the attachments from Service Manager. One example is provided by the guys from Litware, you can find it here. I used some of this code to retrieve the attachments from the work item. But instead of saving the attachments to the file system, the content is converted to a Base64String.

Workflow Get-Attachment:

workflow Get-Attachment
{

[OutputType([string[]])]
Param
(
        #Get the work item ID
        [Parameter(Mandatory=$true)][string]$WorkItemID
)
            #Define the web service endpoint, target SCSM & credentials in SMA (Assets)
            $WebServiceEndpoint = Get-AutomationVariable -Name "VARG-SMAWebServiceEndPoint"
            $SCSMServer = Get-AutomationVariable -Name "VARG-SCSMServer"
            $Creds = Get-AutomationPSCredential -Name "VARG-SCSMServerCredential"
            #Run the next code as InlineScript on Service Manager
            [string]$XMLString = InlineScript 
            {
                #Define classes and realtionships
                $ClassWorkItem = Get-SCSMClass -Name "System.WorkItem$"
                $ClassWIhasAttachment = Get-SCSMRelationshipClass -Name "System.WorkItemHasFileAttachment"
                $WorkItemObj = Get-SCSMObject -Class $ClassWorkItem -Filter "Id -eq $Using:WorkItemID"
                $Attachments = Get-SCSMRelatedObject -SMObject $WorkItemObj -Relationship $ClassWIhasAttachment

                $XMLString = ""
                $Content =""
                $Content= @()
                #If the incident contains attachments
                If($Attachments -ne $Null)
                {
                        #Iterate thorough each attachment and "convert" it into a memory stream
                        ForEach ($Attachment in $Attachments)
                        {      
                                $MemoryStream = New-Object IO.MemoryStream
                                $Buffer = New-Object byte[] 8192
                                [int]$BytesRead | Out-Null
                                while (($BytesRead = $Attachment.Content.Read($Buffer, 0, $Buffer.Length)) -gt 0)
                                {
                                    $MemoryStream.Write($Buffer, 0, $BytesRead)
                                } 

                                $Memory = $MemoryStream.Toarray() 

                        #Convert the bytes (attachments) in memory to a Base64String                          
                        $Content = [convert]::ToBase64String($Memory)

#This here-string is used to concatenate the data in a string format for passing it to the next runbook via web service                     
$XMLString +=@"
<Attachment>
<DisplayName>$($Attachment.DisplayName)</DisplayName>
<Description>$($Attachment.Description)</Description>
<Extension>$($Attachment.Extension)</Extension>
<Content>$Content</Content>
</Attachment>
"@
                            #Set memory stream to Null
                            $MemoryStream.Close()
                            $Memory = $Null
                        }
                 #Return the previously built here-string with all the attachment data     
                 Return $XMLString
             }         
         }    -PSComputerName $SCSMServer -PSCredential $Creds   
    #Start the SMA runbook via Start-SMARunbook cmdlet, which uses the SMA webservice to pass the data
    Start-SmaRunbook -WebServiceEndpoint  $WebServiceEndpoint -Name "Add-Attachment" -Parameters @{"XMLString"=$XMLString;"WorkItemID"=$WorkItemID}
}

The next step is adding the attachments to the work item. There are also few sources on the internet which discuss this topic, but not many really show how to do it. The best “hint” I found was this post here on TechNet forum . To complete our solution I needed to modify the code accordingly. Basically I create an XML object from the $XMLString input containing all the information of the attachments. The attachment content itself is converted back from the Base64String and finally used to create and relate the attachment object.

Workflow Add-Attachment:

workflow Add-Attachment
{
Param
(
        #Pass attachment data and work item ID
        [Parameter(Mandatory=$true)][string]$XMLString,
        [Parameter(Mandatory=$true)][string]$WorkItemID
)
        #Get the SMA automation variable (Asset)
        $SCSMServer = Get-AutomationVariable -Name "VARG-SCSMServer"
        $Creds = Get-AutomationPSCredential -Name "VARG-SCSMServerCredential"
        #Run the next code as InlineScript remotely on the SCSM server
        InlineScript
        {
            #Define the classes and relationships
            $FileAttachmentRel = Get-SCSMRelationshipClass "System.WorkItemHasFileAttachment"
            $FileaAttachmentClass = Get-SCSMClass -Name "System.FileAttachment"
            $WorkItemProjection = Get-SCSMObjectProjection System.WorkItem.Projection -Filter "id -eq $Using:WorkItemID"
            $ManagementGroup = New-Object Microsoft.EnterpriseManagement.EnterpriseManagementGroup $Using:SCSMServer
            #Embedd the $XMLString into <Attachments></Attachments> nodes and convert it to XML
            [xml]$XML = "<Attachments>$($Using:XMLString)</Attachments>"
                #Iterate through each attachment node and get the properties
                ForEach ($Attachment in $XML.Attachments.Attachment)
                {
                    #Convert the Base64String back to bytes
                    $AttachmentContent = [convert]::FromBase64String($Attachment.Content)
                    #Create a new MemoryStream object out of the attachment data
                    $MemoryStream = New-Object System.IO.MemoryStream($AttachmentContent,0,$AttachmentContent.length)
                    #Create the attachment object itself and adding the attachment properties from the received XML
                    $NewFile = new-object Microsoft.EnterpriseManagement.Common.CreatableEnterpriseManagementObject($ManagementGroup, $FileaAttachmentClass)
                    $NewFile.Item($FileaAttachmentClass, "Id").Value = [Guid]::NewGuid().ToString()
                    $NewFile.Item($FileaAttachmentClass, "DisplayName").Value = $Attachment.DisplayName
                    $NewFile.Item($FileaAttachmentClass, "Description").Value = $Attachment.Description
                    $NewFile.Item($FileaAttachmentClass, "Extension").Value = 	$Attachment.Extension
                    $NewFile.Item($FileaAttachmentClass, "Size").Value = 		$MemoryStream.Length
                    $NewFile.Item($FileaAttachmentClass, "AddedDate").Value = 	[DateTime]::Now.ToUniversalTime()
                    $NewFile.Item($FileaAttachmentClass, "Content").Value = 	$MemoryStream
                    #Add the attachment to the work item and commit the changes
                    $WorkItemProjection.__base.Add($NewFile, $FileAttachmentRel.Target)
                    $WorkItemProjection.__base.Commit()
                }
        } -PSComputerName $SCSMServer -PSCredential $Creds   
} 

It was a bit tricky to figure everything out but now I hope providing this example helps you automating your environment. Of course if you need to build e.g. a ServiceNow SOAP request you would have to construct the XML according to the ServiceNow SOAP specification.

I uploaded the PowerShell scripts to TechNet Gallery here.

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