Service Management Automation (SMA) is a capability in Windows Azure Pack and Orchestrator 2012 R2. Though SMA and Orchestrator are both built for IT process automation, orchestration, and integration, they are in many ways complimentary in their capabilities and use scenarios (see this post for a discussion of SMA versus Orchestrator). In general, SMA is optimal for use in Microsoft Cloud OS environments, while Orchestrator is best in traditional datacenter environments. IT groups that are moving some of their traditional workloads to the cloud may find it useful to leverage both SMA and Orchestrator; in this case, it may be common for some end-to-end processes to extend from SMA to Orchestrator and vice-versa.

In this post, I will discuss how SMA and Orchestrator can be integrated. In particular, I will focus on how SMA runbooks can call Orchestrator runbooks and how Orchestrator runbooks can call SMA runbooks, with input parameters and return data. Both scenarios use PowerShell modules that ship with the products as well as the OData web service endpoints that each product exposes.

Other useful posts on how to call Orchestrator from an SMA runbook by Tiander Turpijn should also be read: see Calling an Orchestrator Runbook from SMA Part 1 and Part 2.

PowerShell Modules for Integration with SMA and Orchestrator

To simplify integration with SMA and Orchestrator we have created PowerShell modules with cmdlets that expose the key functionality available via the respective OData web services.

  • SMA
    • The Microsoft.SystemCenter.ServiceManagementAutomation module can be installed on any host as a separate item from the Orchestrator 2012 R2 installer. This is the recommended way to install this module.
    • Alternatively, if you install SMA (from the Orchestrator 2012 R2 installer), then the ServiceManagementAutomation module is pre-imported into SMA and is also installed on the SMA host (located at C:\inetpub\Service Management Automation\Modules).
  • Orchestrator
    • If you install SMA, then the OrchestratorService module is pre-imported into SMA and is also installed on the SMA host (located at C:\inetpub\Service Management Automation\Modules). You can copy it from this location to any other host if you want.

The next two images show the cmdlets available in each module.

OrchestratorService module cmdlets

ServiceManagementAutomation module cmdlets

Calling Orchestrator Runbooks from SMA

There are a few key steps you will need to take in any SMA runbook that calls an Orchestrator runbook; these steps are the following:

  • Create a connection between SMA and Orchestrator
  • Get an Orchestrator runbook
  • Start the Orchestrator runbook, with or without input parameters
  • Monitor the resulting Orchestrator job until it completes
  • Get back any data returned from the Orchestrator runbook instance

Let’s take a look at each section in more detail. For illustration, we will look at the PowerShell workflow code from an SMA runbook called Start-ScoRunbook.  In the box below is the entire runbook script (also you can click here to DOWNLOAD the runbook and then import it to SMA).

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
########################################################################
# Copyright (c) Microsoft. All rights reserved.
# This code is licensed under the Microsoft Public License.
# THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER
# EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS
# FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
########################################################################

<#
.SYNOPSIS
     This runbook starts an Orchestrator runbook with or without parameters and gets back any output.
 
.DESCRIPTION
     This runbook illustrates how to start a runbook in System Center Orchestrator, pass in parameters,
     and get back return data.
 
     If the Orchestrator runbook has output, then this runbook will return it as a hashtable with
     key/value pairs, where the key is the output parameter name
 
.PARAMETER RunbookPath
     String. The full path to the runbook as defined in Orchestrator. For example, \folder1\folder2\runbookname.
 
.PARAMETER InputParams
     Object. Input parameters formatted as PSCustomObject with key/value pairs.
 
.PARAMETER JobCheckIntervalInSeconds
     Int. The amount of time, in seconds, to sleep between attempts to check the job for completeness.
     Set this to greater than the expected run time of the job.
 
.NOTES
     The runbook expects to connect with the web service for an installation of Orchestrator 2012 or 2012 R2.
 
     The runbook assumes that you have created an Automation Connection asset named "OrchestratorConnection"
     with the information required to connect with the Orchestrator web service.
#>
 

workflow Start-ScoRunbook
{
    [OutputType( [hashtable] )]

    # define the input parameters to this runbook
    param (
        [Parameter(Mandatory=$true)]
            [string] $RunbookPath,
        [Parameter(Mandatory=$false)]
            [object] $InputParams,
        [Parameter(Mandatory=$false)]
            [int] $JobCheckIntervalInSeconds
    )
    
    # get the Orchestrator connection asset
    $con = Get-AutomationConnection -Name 'OrchestratorConnection'

    # create a Credential object
    $securepassword = ConvertTo-SecureString -AsPlainText -String $con.UserPassword -Force
    $domainuser = $con.UserDomain + "\" + $con.UserName
    $creds = New-Object -TypeName System.Net.NetworkCredential -ArgumentList ($domainuser, $securepassword)

    # create the URL for the Orchestrator service
    $url = Get-OrchestratorServiceUrl -Server $con.ServerName

    # get the SCO runbook we want to start
    $runbook = Get-OrchestratorRunbook -ServiceUrl $url -Credentials $creds -RunbookPath $RunbookPath
    if ($runbook -eq $null) {
        $msg = "Orchestrator runbook '$RunbookPath' could not be retrieved."
        Write-Error -message $msg
        Throw $msg
    }
    else {
        # start the runbook job
        if ($InputParams -ne $null) {
            # convert the input param names to their associated GUIDs
            $RBInputWithIds = getParamObjectWithIds -RBInputWithNames $InputParams -runbook $runbook
           
            # start the runbook with input parameters and get the job returned
            $job = Start-OrchestratorRunbook -Runbook $runbook -Credentials $creds -Parameters $RBInputWithIds
        }
        else {
            # start the runbook without any input parameters and get the job returned
            $job = Start-OrchestratorRunbook -Runbook $runbook -Credentials $creds
        }
           
        # if a job has been created then get any output
        if ($job -eq $null) {
            $msg = "Orchestrator runbook job is null: no job was created."
            Write-Error -message $msg
            Throw $msg
        }
        else {
         # get any output
            $out = getJobOutput -Job $job -Creds $creds -JobCheckIntervalInSeconds $JobCheckIntervalInSeconds

            # return the output object if there is output
            if ($out -ne $null) {
                Write-Output $out
            }
        }
    }
   
    #
    # Function that takes an input parameter object that has parameter names and values
    # and replaces the names with the ids
    #
    function getParamObjectWithIds
    {
        param (
            [object] $RBInputWithNames,
            [object] $runbook
        )
       
        # convert the PSCustomObject to a hashtable
        $NamesHt = @{}
        $RBInputWithNames.psobject.properties | Foreach { $NamesHt[$_.Name] = $_.Value }
       
        # get the runbook parameters
        $RBParams = $runbook.Parameters
       
        # create new input parameter hashtable with parameter ids as keys
        $RBInputWithIds = @{}
        foreach($key in @($NamesHt.keys)) {
            foreach ($pm in $RBParams) { 
                if ($pm.Name -eq $key) { 
                    $RBInputWithIds.Add($pm.Id,$NamesHt[$key]) 
                }
            }
        }
       
        # output the new parameter hashtable
        Write-Output $RBInputWithIds
    }
   
    #
    # Function that gets the runbook job output
    #
    function getJobOutput
    {
        param (
            [object] $Job,
            [object] $Creds,
            [int] $JobCheckIntervalInSeconds
        )
       
        # assure the job is complete
        while( ($job.Status -eq "Running") -or ($job.Status -eq "Pending") ) {
            Start-Sleep -s $JobCheckIntervalInSeconds
            $job = Get-OrchestratorJob -jobid $job.Id -serviceurl $job.Url_Service -credentials $creds
        }
       
        # get the runbook instance that has the job data
        $instance = Get-OrchestratorRunbookInstance -Job $job -Credentials $creds
        if ($instance -eq $null) {
            $msg = "Orchestrator runbook instance is null."
            Write-Error -message $msg
            Throw $msg
        }
        else {
            # there are instance parameters only if the runbook has input and/or output parameters
            $instparams = Get-OrchestratorRunbookInstanceParameter -RunbookInstance $instance -Credentials $creds
            if ($instparams -ne $null) {
                # any output will be in a hashtable
                $out = @{}
                # look through the runbook parameters for any that are for output
                foreach ($instparam in $instparams) {
                    if ($instparam.Direction -eq "Out") {
                        # write the output value (always a string, interger, date, or boolean)
                        $out.Add($instparam.Name,$instparam.Value)
                    }
                }
                Write-Output $out
            } else {
                Write-Verbose -message "The runbook has no output." -Verbose
                Write-Output $null
            }
        }
    }
   
}

 

Create a connection between SMA and Orchestrator and get an Orchestrator runbook

The first section of code above shows how to create the connection from SMA to Orchestrator and get the Orchestrator runbook. Before authoring the runbook I created an SMA Connection setting with the parameters for connecting to my Orchestrator web service (read this to learn more about the Orchestrator web service).

SMA Connection Setting for connecting with Orchestrator

The Get-AutomationConnection activity that is installed with SMA is used to retrieve the connection object. From that connection object we extract the UserDomain, UserName, and UserPassword fields that we use to create the credential object that will be used for authorization with Orchestrator. We also extract the ServerName that the Get-OrchestratorServiceUrl cmdlet used to create the full URL to the Orchestrator web service. With the URL, the credentials, and the runbook path we then get the Orchestrator runbook object using the Get-OrchestratorRunbook cmdlet.

Start the Orchestrator runbook

Now that we have the runbook object and any input parameters we are ready to start the runbook. To start a runbook we use the Start-OrchestratorRunbook cmdlet, which takes the runbook object, the credentials, and any input parameters. If there are input parameters to the runbook, we first must get the GUIDs for each named parameter and create a hashtable of the GUIDs and values (the getParamObjectWithIds function does this).

Monitor the Orchestrator job for completion and get any output

If Start-OrchestratorRunbook is successful, then it returns the Orchestrator job object. The next step is to monitor the job for completion and then get any output: the getJobOutput function performs this work.

To monitor a job and determine if it has completed we use the Get-OrchestratorJob cmdlet and wait until the job status is not Running or Pending. Note that the sleep interval is configurable: you will want to make this number greater than the expected job run time. If the job is not one of these states, then it has reached a final state and we can attempt to get the runbook instance associated with the job, from which we can extract any runbook instance parameter that has the Direction metadata equal to Out (which contain the runbook output). If there are output parameters then we package them in a hashtable and return it.

Calling SMA Runbooks from Orchestrator

The key logic in an Orchestrator runbook that calls an SMA runbook is quite similar to that discussed above.

  • Create a connection between Orchestrator and SMA
  • Start the SMA runbook, with or without input parameters
  • Monitor the resulting SMA job until it completes
  • Get back any output data from the SMA job

The main difference between this runbook and that discussed in the previous section is that this Orchestrator runbook will contain all of the core logic in a PowerShell script that uses the SMA PowerShell module. This script will be contained in an Orchestrator Run .Net Script activity (click here to DOWNLOAD the script). Because the SMA PowerShell module requires a 64-bit PowerShell process to run, and Orchestrator is constrained to a 32-bit process, the script uses PowerShell remoting to run the SMA cmdlets on the 64-bit Windows server where the ServiceManagementAutomation module is installed. Remember to run “Set-ExecutionPolicy RemoteSigned” in the PowerShell console on the remote machine so that the remoting is allowed.

This next screenshot shows a simple Orchestrator runbook that takes in input parameters, uses the Run .Net Script activity to run the script that calls SMA, and then returns any output. In general, your runbooks will be more complex than this, however the key logic for integrating with SMA will be contained in the single Run .Net Script activity.

Orchestrator runbook that calls SMA

For the connection and credential values in the script you will probably want to use variables stored in Orchestrator Global Settings so that this connection information can be re-used in many runbooks.

Global settings for SMA connection fields

The PowerShell script for starting an SMA runbook, monitoring the job, and getting return data

In the box below you can see the entire script. This script was first authored and tested in the PowerShell ISE and then pasted into the Run .Net Script activity.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
$ErrorActionPreference = "Stop"
try
{
    # credentials
    $user = "domain\username"
    $pwd = ConvertTo-SecureString –String "password" –AsPlainText -Force
    $cred = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $user, $pwd

    # web service endpoint
    $computername = "sma-server"
    $wsep = "https://$computername"
    $port = "9090"
   
    # create persistent connection to remote computer
    $session = New-PSSession -ComputerName $computername -Credential $cred

    # run the script block in the remote session and get back the output
    $out = Invoke-Command -Session $session -ArgumentList $cred,$wsep,$port -scriptblock {
        $cred = $args[0]
        $wsep = $args[1]
        $port = $args[2]
        
        # set the runbook name and any input parameters
        $RBName = "SMARunbook"
        $params = @{"Param1"="StringValue";"Param2"=123}

        # start the SMA runbook
        $thejobid = Start-SmaRunbook -Name $RBName -Parameters $params -WebServiceEndpoint $wsep -Port $port -AuthenticationType Basic -Credential $cred

        # monitor the SMA job until completed
        $shouldLoop = $true
        while($shouldLoop) {
            # the sleep interval, in seconds, should be greater than the expected run time of the SMA job
            $sleepinterval = 5
            Start-Sleep -s $sleepinterval
            $job = Get-SmaJob -Id $thejobid -WebServiceEndpoint $wsep -Port $port -AuthenticationType Basic -Credential $cred
            $status = $job.JobStatus
            $shouldLoop = (($status -ne "Completed") -and ($status -ne "Failed") -and ($status -ne "Suspended") -and ($status -ne "Stopped"))
        }

        # get the job output
        $jobout = Get-SmaJobOutput -Id $thejobid -Stream Output -WebServiceEndpoint $wsep -Port $port -AuthenticationType Basic -Credential $cred
        $jobout.StreamText
    }

    $out
}
catch
{
    Throw $_.Exception
}

In the first part of the script the credential and connection objects are created. Then a connection session is created to the remote server.

The key part of the script occurs next when we use the Invoke-Command cmdlet to execute a script block in a PowerShell session on the remote computer. Parameters are passed into the script block using the –ArgumentList parameter.

Within the script block the SMA runbook is started using the Start-SmaRunbook cmdlet, and parameters are passed to it in a hashtable object.

A job id is returned from the Start-SmaRunbook cmdlet, and this job id is used to monitor the job using the Get-SmaJob cmdlet. When the job status has reached a final state, then we use the StreamText property in the objects returned from Get-SmaJobOutput to get any output. Note that in this example we don’t check that the final state is actually “Completed”, so you will want to add logic to handle cases where the runbook is in some other state.

Also note that the Get-SmaJob cmdlet in a single collection and does not guarantee that the order of records returned is chronological. If you want them in chronological order then PowerShell script like this can help:

Get-SmaJobOutput <params here> | %{ $_ } | sort StreamTIme

To get the output from the script into the Orchestrator databus where it can be used by other activities in the Orchestrator runbook, we configure the Run .Net Script activity Published Data to put the contents of the script “out” variable into the databus property named “Return from SMA”.

Set the returned data from the script

Summary

As you can see, once you understand the mechanics of connecting between SMA and Orchestrator, starting runbooks, monitoring jobs, and getting output data, the goal of creating end-to-end processes is relatively straightforward. Go ahead and write a simple runbook in SMA that calls Orchestrator, and vice-versa; from those experiments you should be able to get a good baseline of understanding from which you can build more complex scenarios.