Chapter 6. Modular runbook example

This chapter presents an example of process automation using the framework described in previous chapters. It illustrates how the modularity prescribed in the runbook framework enables you to build an increasingly valuable library of runbooks and how the time you invest now will help you to do more in the future with less work.

This chapter builds on the previous chapters, using various patterns and scripts to illustrate a complete virtual machine provisioning process. Using this example, you will learn the basic information you need to apply the runbook framework to whatever automation solution you need to build.

Requirements

The provisioning of a new virtual machine (VM) is a time-consuming process that requires the routing pattern for implementation. The length of time involved will, of course, be determined by your requirements but even just a single instance of this process can take up to 25 minutes. It is not a good idea to have individual Microsoft System Center Orchestrator 2012 runbooks execute for an extended period of time because each runbook server has a fixed amount of runbooks that it can execute (50 by default, which can be increased but will typically be between 50 and 200).

For VM provisioning, some of the more time-consuming tasks, such as copying the VM image to the target host server, can take an extended amount of time. If a runbook is running and waiting for a task to complete, it is unnecessarily consuming Orchestrator resources. A better approach for long-running operations is to have a runbook trigger the operation, but then exit the runbook and have a separate runbook (that is, the routing pattern) periodically monitor that progress and continue executing the overall process once the long running steps have completed. The routing pattern enables a long-running process to be subdivided into component runbooks with the routing runbook managing the state and execution of the process.

As outlined in previous chapters, the process to be orchestrated and the required actions, operations, or approvals should all be optimized and mapped prior to building Orchestrator runbooks.

For the example outlined in this chapter, the following virtual machine provisioning process will be orchestrated:

  • The process will be initiated by a file monitor looking for new VM provisioning request files in a particular location

  • Input variables will be read from the file and validated

  • A routing engine runbook will control state and execution flow through several sequential steps

  • Operations—such as creating a VM, monitoring status of the VM deployment process, and post-installation tasks—are automated using runbooks

  • On completion, the request is completed and success status is returned

A System Center Virtual Machine Manager (VMM) template is used to create a new VM. The VM template is either created dynamically at run time by the runbook, called a dynamic template, or an existing VMM template from the VMM library can be utilized, called a static template.

Using the routing pattern described in earlier chapters, all required data for the initiation and state management of the VM provisioning request is stored in an XML-formatted file. This file contains all the related data such as the name of the new virtual machine, the template name, the name of the domain, and a process status field. The status field is updated as each step of the automation is performed so that the status represents the current state of the process execution. This file will be read up by a monitor runbook and process flow and execution will be determined based on the value of the status. This enables decisions to be made, such as whether to proceed or fail with an error message, based on the status written to the file by each step. This is a method of state management for the process. If a step in the process should fail, the status field will indicate the last successful step so that after troubleshooting, the automation can resume from the previous successful step. Some of the process steps include creating a template, receiving the host rating from VMM, creating a VM, and provisioning a System Center Operations Manager agent to the new VM. With each change in the status, an integer number is incremented representing the progress of the overall process.

If you use this approach, you will be able to extend the process later on to include additional functions such as provisioning storage upfront or installing more software to the new VM.

VM provisioning input XML file

An XML-formatted file is used to trigger a new instance of provisioning a new VM. This file contains all the relevant and required data for the process. A simple component runbook will read the XML file and publish all data to the data bus.

The following example of an XML-formatted file uses the RequestID GUID as the filename and the Status element is used to track progress and make sure the steps are called in the right sequence.

<xml>
  <RequestID>b7c87df1-4e93-4e7b-9a20-8309bb63bc16</RequestID>
  <Status>0</Status>
  <VMMServerName>VMM01</VMMServerName>
  <VMName>newVirtualMachine01</VMName>
  <VMDomain>contoso.com</VMDomain>
  <UserRoleName>Administrator</UserRoleName>
  <VMMAdministratorRoleName>Administrator</VMMAdministratorRoleName>
  <ProvisioningType>Windows</ProvisioningType>
  <CloudName>testcloud</CloudName>
  <IsWorkgroup>false</IsWorkgroup>
  <LocalAdminUser>Administrator</LocalAdminUser>
  <VMMTemplateType>Dynamic</VMMTemplateType>
  <HardwareProfileID>e4a4de67-fe1e-4fa9-b22e-abb5babd9c5f</HardwareProfileID>
  <OSProfileID>12313691-6847-45cf-8469-766f5183025b</OSProfileID>
  <OSVHDID>519d2a02-f79b-42fe-ad0d-d591db388870</OSVHDID>
  <NetworkName>Public</NetworkName>
  <VLANNumber>1067</VLANNumber>
  <ExistingTemplateID>773fc727-c4c5-498a-a1fe-030cb912bbc4</ExistingTemplateID>
  <SCOMMgmtServerFQDN>OpsMgr01.contoso.com</SCOMMgmtServerFQDN>
  <Error>
    <ErrorState></ErrorState>
    <ErrorMessage></ErrorMessage>
    <ActivityName></ActivityName>
    <RunbookName></RunbookName>
    <Trace></Trace>
    <RequestID></RequestID>
  </Error>
  <DataVHDs>
    <DataDisk Quantity="2" ID="4f7e5061-5e6b-4068-8103-5b558a829fdb" />
    <DataDisk Quantity="2" ID="2e70e3d8-45ce-470b-8d42-4d121053c978" />
  </DataVHDs>
</xml>

Component runbooks

One of the primary benefits of mapping out the optimized, end-to-end process on paper is that it serves as an input to the functional specifications of the runbooks that must be created. Using the framework described in this book, the first step in runbook authoring after the target process to automate has been defined is to create component runbooks for the primary functionality required. For the sample VM provisioning process in this chapter, the following component runbooks are required:

  • Read XML File. This component runbook reads the XML file and publishes the variables and values from the XML file to the Orchestrator data bus. While this is a component runbook, we recommend it be placed in the control runbook folder because it is specific to this process and unlikely that it will be reused by any other scenario.

  • Validate Inputs. This component runbook validates all data gathered from the XMLfile based on ranges, formats, and values. The validation runbook should be called in each step of the control runbook to ensure that the most current version of the file is used. Similar to the Read XML File runbook, this runbook is very specific to this scenario so we recommend placing it in the control runbook because it is specific to this process and unlikely that it will be reused by any other scenario.

  • Update Status and Set Status. These two component runbooks maintain the status field in the XML file. The Update Status runbook updates the status field while the Set Status runbook increments the status integer. These two runbooks ensure that the overall process status is up to date and the provisioning steps occur in the correct sequence.

  • Get VM Host Rating. This component runbook gets the host rating from VMM to make sure that there is a host available for the new VM. Windows PowerShell cmdlets will be utilized within an Orchestrator Run .Net Script activity to get the VM host rating required. If a host is not available, an error is returned and the control runbook will evaluate the returned result and call the error routine if the error cannot be handled by the control runbook.

  • Create New VM. This component runbook creates a new virtual machine. This runbook is called only if Get VM Host Rating is successful and a host is available for placement of the new VM. Windows PowerShell cmdlets will be utilized within an Orchestrator Run .Net Script activity to create the VM.

  • Get VMM Job Status. This component runbook is called to verify that the VMM job, in this case the creation of the new VM, was successful. This runs periodically in a loop using the XML file until the status returned is successful.

Component runbook detail: GetVMHostRating

The runbook shown in Figure 6-1 is one of the required component runbooks for provisioning a new VM.

An example of the GetVMHostRating component runbook.

Figure 6-1. An example of the GetVMHostRating component runbook.

The runbook performs the following activities:

  • Initializing data

  • Running the .NET script

  • Returning data (such as a success or error status)

An example of where this runbook would be placed is shown in Figure 6-2. Because this is a component runbook that uses System Center Virtual Machine Manager through PowerShell, the runbook is placed in the appropriate folder.

A view of the Orchestrator folder structure.

Figure 6-2. A view of the Orchestrator folder structure.

Initialize Data

In the first step, the Initialize Data activity defines the following input parameters (each with a data type: string):

  • VMName. The name of the VM

  • VMMServerName. The name of the VMM server

  • RequestID. A GUID that is generated and included in the XML file to give each VM provisioning request a unique identifier

  • CloudName. The name of the VMM cloud the VM should be provisioned to

  • UserRoleName. The name of the user role the VM should be provisioned for

  • ExistingVMTemplateName. The name of the existing template in VMM that will be used

Get VM Host Rating

In the second step, the Run .Net Script activity executes a Windows PowerShell script which takes the input data and parameters that the Initialize Data activity published to the data bus and executes a remote call to System Center VMM to execute the Get-SCVMHostRating cmdlet. This cmdlet will query the VMM server using all the parameters supplied (such as cloud and user role) to find the optimal Hyper-V host to provision the virtual machine to. This step will get the name of the recommended host and publish the name to the data bus.

In the Details tab, the following Windows PowerShell code is utilized. In this case, the Windows PowerShell script code contains the main functionality of the component runbook which determines the recommended host to place the VM being provisioned on.

# Get Input parameters
$RequestID = ""
$VMName = ""
$VMMServerName = ""
$ExistingVMTemplateName = ""
$UserRoleName= ""
$CloudName = ""

$ErrorState = 0
$ErrorMessage = ""
$Trace = ""
$Error.Clear()
try
{

   $Trace +=" Validating the VMMServer 'r'n"
          if($VMMServerName -eq "")
         {
            $ErrorState=2
            Throw "Error: Invalid Input Parameters."
         }
         if(!(Test-Connection $VMMServerName))
         {
               $ErrorState=2
               Throw "Error:Unable To Reach VMMServer .."
         }
     $Session = New-PSSession -ComputerName $VMMServerName
     If ($Session -eq $null) {
           $ErrorMessage =  $Error[0]
           $Trace += "Could not create PSSession on $VMMServerName"
           $ErrorState = 2
     }
     else
   {
        $ReturnArray = Invoke-Command -Session $Session -Argumentlist $RequestID, $VMMServerName,$UserRoleName,$CloudName,$ExistingVMTemplateName,$VMName   -scriptblock {
Param ($RequestID, $VMMServerName, $UserRoleName, $CloudName, $ExistingVMTemplateName, $VMName)

        $ErrorState = 0
        $ErrorMessage = ""
        $Trace = ""
        $Error.Clear()

        try    {
             $Trace += "Validating The Input Parameters.. 'r'n"
             if(($RequestID.length -lt 1)-or ($VMMServerName.length -lt 1) -or ($UserRoleName.length -lt 1)     -or ($CloudName.length -lt 1) -or ($ExistingVMTemplateName.length -lt 1) -or ($VMName.length -lt 1))
             {
                   Throw "Error: One or more required parameters is NULL"
             }
              if($VMName.Length -gt 15)
             {
                    $ErrorState=2
                    Throw "Error:VMName Length Should Not Increase More Than 15 Characters.."
             }
             $Trace += "Imporing the VMM Modules `r`n"
             try
             {
                    $Path = Get-ItemProperty "HKLM:SoftwareMicrosoftMicrosoft System Center Virtual    Machine Manager ServerSetup"
                    $FinalPath = $Path.InstallPath + "inpsModulesVirtualMachineManagervirtualmachinemanager.psd1"
                    Import-Module $FinalPath
              }
              catch
              {
                    $Trace += "Importing of the VMM module failed as the module was already present `r`n"
              }

                 $Trace += "Getting connection with VMM Server $VMMServerName 'r'n"
                 Get-SCVMMServer -ComputerName $VMMServerName -UserRoleName $UserRoleName | Out-Null

                 $Trace += "Getting the cloud object for $CloudName cloud 'r'n"
                 $Cloud = Get-SCCloud -Name $CloudName

                 $Trace += "Getting the VM Template object for template
$ExistingVMTemplateID 'r'n"
                 $VMTemplate = Get-SCVMTemplate  | where {$_.Name -eq
$ExistingVMTemplateName}

                          $Trace += "Checking if Cloud and Template were successfully fetched 'r'n"
                 if(($Cloud -ne $null) -and ($VMTemplate -ne $null))
                 {
                          $Trace += "Trying to get the Cloud Ratings'r'n"
                          $HostRatings = @(Get-SCVMHostRating -Cloud $Cloud -VMTemplate
$VMTemplate -DiskSpaceGB 20 -VMName $VMName | sort -Descending Rating)

                          $Trace += "Checking if Host Rating is non null and has valid
data 'r'n"
                          If (($HostRatings -ne $null) -and ($HostRatings.Count -gt 0))
                          {
                                  $HostRating = $HostRatings[0].Rating
                                  $Trace += "Host Rating is: $HostRating. 'r'n"
                                  if($HostRating -eq 0)
                                  {
                                           $Trace += "Host Rating is zero. Getting the reason for Zero Rating. 'r'n"
                                           $Trace += $HostRatings[0].ZeroRatingReasonList.Get_Item(0).Description
                                           Throw "Error: No non-zero host rating found for cloud $Cloud."
                                  }
                          }
                          else
                          {
                                  Throw "Error: No non-zero host rating found for cloud $Cloud and VM Template: $ExistingVMTemplateName."
                          }
                 }
                 else
                 {
                          Throw "Error: Not able to get the cloud:$Cloud or VM Template:$ExistingVMTemplateName."
                 }
    }
    Catch
    {
        $ErrorState = 2
        $ErrorMessage = $error[0].Exception.ToString()
    }
    Finally
    {
        $Trace += "'r'n"
        $Trace += "Exiting Script 'r'n"
        $Trace += "ErrorState:  $ErrorState 'r'n"
        $Trace += "ErrorMessage: $ErrorMessage 'r'n"
    }
    # end finally
    $Results = @($ErrorState, $ErrorMessage, $Trace, $HostRating)
    Return $Results
}
$ErrorState = $ReturnArray[0]
$ErrorMessage = $ReturnArray[1]
$Trace = $ReturnArray[2]
$HostRating = $ReturnArray[3]

Remove-PSSession -Session $Session
}
}
catch
{
      $ErrorState = 2
      $ErrorMessage = $error[0].Exception.ToString()


}
finally
{
         $Trace += "'r'n"
         $Trace += "Exiting New VM 'r'n"
         $Trace += "ErrorState:   $ErrorState 'r'n"
         $Trace += "ErrorMessage: $ErrorMessage 'r'n"
}

The above code leverages the Remote PowerShell session patterns from Chapter 5 where we described how to make remote PowerShell calls from a runbook to a management server and executing script code in the remote session. In this example, we are creating a remote Windows PowerShell session to the VMM server. There it imports the VMM Windows PowerShell module and cmdlets then connects to the VMM server by calling Get-SCVMMServer. Then the script gets the VMM cloud and the VMM template using the values from the data bus which were supplied by the process calling this component runbook and supplied to the Initialize Data starting point activity. The script must return data other than null. In a successful case it will return ErrorState with a value of zero.

Once the script code is entered, the Windows PowerShell variables in the script requiring values from previous steps in the runbook must be subscribed to from the data bus. This is one of the most powerful features of Orchestrator, the ability for one step to consume data from previous steps. Right-click within the brackets of each variable assignment, in the context menu that appears, go to Subscribe - Published Data, select the Initialize Data activity and then choose the appropriate variable from the data bus (see Figure 6-3).

An example of mapping published data to script.

Figure 6-3. An example of mapping published data to script.

With the script code entered and the variables linked to published data, the script output—consisting of additional published data—can be configured using the Published Data tab as shown in Figure 6-4. On this page, you define the variables to be published from the Run .Net Script activity. As described previously, any Windows PowerShell script for runbooks using the framework described in this book should always return at least three published data values including the ErrorState, ErrorMessage, and Trace values. Additionally, in an example such as this where the purpose of the runbook is to determine a specific output (such as the name of the Hyper-V host to place a VM) that output should also be published to the data bus. In this example, in addition to the standard three values returned, there is also a HostRating variable that will be published as well for subsequent steps to consume.

You can see the defined variable in the Publish Data tab.

Figure 6-4. You can see the defined variable in the Publish Data tab.

Return Data

The third step in the GetVMHostRating component runbook is to define the return data from the runbook. This is a two-step process. First the data to be returned must be defined on the runbook properties level and then the runbook itself must include one or more Return Data activities to map the data appropriately.

To define the return data on the runbook level, right-click the runbook tab at the top of the design console for the current runbook and choose Properties. On the Returned Data tab, you define the settings shown in Figure 6-5.

You can specify the data in the Returned Data tab.

Figure 6-5. You can specify the data in the Returned Data tab.

With the runbook level returned data configured, now the published data to return it to the calling runbook must be configured. For all component runbooks, there should be a Return Success and Return Error path. Both should be configured with the data shown in Figure 6-6. The parameter list is populated from the runbook level configured previously, then each parameter should be mapped to published data to populate the values by right-clicking the field and subscribing to published data from the data bus.

Mappping published data to the script

Figure 6-6. Mappping published data to the script

Link and Conditional Logic

Once the activities in the runbook are configured, the links and conditional logic between the activities can be configured. The Initialize Data activity should be linked to the Get VM Host Rating activity and pass if Initialize Data results in success. From the Get VM Host Rating activity there will be two links, one for success conditions linked to the Return Success activity and another for error conditions linked to the Return Error activity. For the link to the Return Error activity, the include filters required are shown in Figure 6-7. These filters cover several scenarios. The first is if the Windows PowerShell script exits with a terminating error, in that case the Run .Net Script activity will return a warning or failed status. This is how unhandled errors are captured. The next filters cover logical errors that are handled by the script when the script exits with a status of two or three.

Include filters for the Return Error path.

Figure 6-7. Include filters for the Return Error path.

For the link to the Return Success activity, the include filters should look like Figure 6-8. In this framework, a status of zero or one indicates that the process should proceed along the success path.

Include filters for the Return Success path.

Figure 6-8. Include filters for the Return Success path.

Component runbook summary

This section outlined a completed component runbook. This runbook can be tested using the Runbook Tester (from within Orchestrator Runbook Designer) or by creating another runbook that will have an Initialize Data activity (with no parameters defined) and an Invoke Runbook activity (that will call your component runbook and specify all required test data). For all component runbooks created it is highly recommended to test as many possible initial conditions and as much data as possible since component runbooks are intended to be highly reusable and must be very robust. Test cases should include valid data, invalid data, missing data, and so on. The expected result is that all conditions should either result in success or in handled errors with trace data.

Component runbook detail: CreateNewVM

The CreateNewVM component runbook uses a very similar structure to the GetVMHostRating runbook, as shown in Figure 6-9. For brevity, only the differences and the underlying Windows PowerShell script will be described in this section.

The CreateNewVM component runbook.

Figure 6-9. The CreateNewVM component runbook.

Initialize Data

In the first step, the Initialize Data activity defines the following input parameters (each with a data type: string):

  • RequestID. A GUID that is generated and included in the XML file to give each VM provisioning request a unique identifier

  • IsWorkgroup. Indicates whether the VM will be a workgoup VM

  • UserRoleName. The name of the user role the VM should be provisioned for

  • certificateFindType. Utilized for securing credentials and passwords for local administration

  • certificateFindValue. Utilized for securing credentials and passwords for local administration

  • opalisHelper. Utilized for executing command line or console programs

  • IsLocalAdminPasswordEncrypted. Utilized for securing credentials and passwords for local administration

  • certificateStore. Utilized for securing credentials and passwords for local administration

  • LocalAdminPassword. Decrypted password for the local administrator

  • CertificateLocation. Utilized for securing credentials and passwords for local administration

  • ExistingVMTemplateName. The name of the existing template in VMM that will be used

  • CloudName. The name of the VMM cloud the VM should be provisioned to

  • ProvisioningType. Type of VM provisioning to be performed

  • VMMServerName. The name of the VMM server

  • WorkgroupName. The name of the workgroup if the VM will not be domain-joined

  • VMFQDN. The fully qualified domain name of the virtual machine

Create New VM

This component runbook utilizes a Run .Net Script activity. In the Details tab, the following Windows PowerShell code is utilized. In this case, the Windows PowerShell script code contains the main functionality of the component runbook which creates a new VM using the data from the Initialize Data activity.

#Inputs from published data
$RequestID = ""
$VMMServerName = ""
$ExistingVMTemplateID = ""
$VMFQDN = ""
$ProvisioningType = ""
$IsWorkgroup = ""
$LocalAdminPassword = ""
$WorkgroupName = ""
$LocalAdminUser = "administrator"
$CloudName = ""
$UserRoleName = ""

#encryption related Variables
$opalisHelper = ""
$IsLocalAdminPasswordEncrypted = ""
$executeCommand = "DecryptMessage"
$certificateLocation = ""
$certificateStore = ""
$certificateFindValue = ""
$certificateFindType = ""

$ErrorState = 2
$ErrorMessage = ""
$Trace = ""
$Error.Clear()

$ErrorInDecryption = 0
#Checking and Decrypting Local Admin Password
$Trace += "Checking if the password is encrypted 'r'n"
$Trace += "IsLocalAdminPasswordEncrypted: $IsLocalAdminPasswordEncrypted 'r'n"
if($IsLocalAdminPasswordEncrypted -eq "True")
{
    $Trace += "Password is encrypted. Trying to decrypt it. 'r'n"
    try
    {
       $Trace += "Creating a script block for decryption. 'r'n"
       $command = [ScriptBlock]::Create("$opalisHelper $executeCommand

'$LocalAdminPassword' $certificateLocation $certificateStore $certificateFindValue $certificateFindType")

       $Trace += "Invoking the script block to decrypt the encrypted password. 'r'n"
       $output = Invoke-Command -ScriptBlock $command

       $Trace += "Checking if the decryption was successful. 'r'n"
       if($output.StartsWith("SUCCESS:") -eq $true)
       {
                       $Trace += "Decription successful. Getting the decrypted password. 'r'n"
                       $LocalAdminPassword = $output.TrimStart("SUCCESS:");
       }
       else
       {
                       Throw "Decryption process did not succeeded."
       }
    }
    catch
    {
                $Trace += "Not able to decrypt password. 'r'n"
                $ErrorState = 2
                $ErrorMessage = $error[0].Exception.ToString()
                #$Trace += "Exiting script. Please make sure that the certificates are properly installed and then retry. 'r'n"
                $ErrorInDecryption = 1
    }
}
else
{
    $Trace += "Password is not encrypted. 'r'n"
}

if($ErrorInDecryption -eq 1)
{
    $ErrorState = 2
    $Trace += "Exiting Script as there was error in the Decryption process. 'r'n"
}
else
{
     $Session=""
     try
     {
     $Trace=" Validating The VMMServer 'r'n"
         if($VMMServerName -eq "")
         {
                        $ErrorState=2
                        Throw "Error: Invalid Input Parameters."
         }
                if(!(Test-Connection $VMMServerName))
                    {
                        $ErrorState=2
                        Throw "Error:Unable To Reach VMMServer .."
                    }

    $Session = New-PSSession -ComputerName $VMMServerName

   If ($Session -eq $null -or $Session -eq "") {
        $ErrorMessage =  $Error[0]
        $Trace += "Could not create PSSession on $VMMServerName or Invalid VMMSever 'r'n"
        $ErrorState = 2

    }
         else
         {
             $ReturnArray = Invoke-Command -Session $Session -Argumentlist $RequestID,
$VMMServerName,$ExistingVMTemplateID, $VMFQDN, $ProvisioningType, $IsWorkgroup,
$LocalAdminUser, $LocalAdminPassword, $WorkgroupName, $CloudName, $UserRoleName  -
scriptblock {
                 Param ($RequestID, $VMMServerName,$ExistingVMTemplateID, $VMFQDN,
$ProvisioningType, $IsWorkgroup, $LocalAdminUser, $LocalAdminPassword, $WorkgroupName,
$CloudName, $UserRoleName)

             $ErrorState = 0
             $ErrorMessage = "No Error"
             $Trace = ""
             $Error.Clear()

       Try
       {
                                   $Trace = "Beginning Create Virtual Machine Script 'r'n"
            $Trace += "'r'n"
            $Trace += "Request ID:                                        $RequestID 'r'n"
            $Trace += "VMMServerName:                              $VMMServerName 'r'n"
            $Trace += "ExistingVMTemplateName:               $ExistingVMTemplateID 'r'n"
            $Trace += "VMFQDN:                                             $VMFQDN 'r'n"
            $Trace += "ProvisioningType:                         $ProvisioningType 'r'n"
            $Trace += "IsWorkgroup:                                   $IsWorkgroup 'r'n"
            $Trace += "LocalAdminUser:                             $LocalAdminUser 'r'n"
            $Trace += "WorkgroupName:                               $WorkgroupName 'r'n"
            $Trace += "CloudName:                                      $CloudName `r`n"
            $Trace += "UserRoleName:                                 $UserRoleName 'r'n"
            #Validating The Input Parameters
            $Trace += "Validating The Input Parameters.. 'r'n"
                if(($RequestID -eq "" -or $RequestID -eq $null) -or ($VMMServerName -eq
"" -or $VMMServerName -eq $null)   -or ($VMFQDN -eq "" -or $VMFQDN -eq $null) -or
($UserRoleName -eq "" -or $UserRoleName -eq $null)  -or ($ProvisioningType -eq "" -or
$ProvisioningType -eq $null) -or ($CloudName -eq "" -or $CloudName -eq $null) -or
($IsWorkgroup -eq "" -or $IsWorkgroup -eq $null) -or ($ExistingVMTemplateID -eq "" -or
$ExistingVMTemplateID -eq $null) )
                {
                    $ErrorState=2
                    Throw "Error: Invalid Input Parameters."
                }
                 if(!($IsWorkgroup.ToLower() -eq "true") -and !($IsWorkgroup.ToLower()
-eq "false"))
                    {
                         $ErrorState=2
                         Throw "Error: Invalid IsWorkgroup."
                    }
                     if(($LocalAdminUser -eq "" -or $LocalAdminUser -eq $null) -and $IsWorkgroup.ToLower() -eq "true")
                    {
                        $ErrorState=2
                        Throw "Error: Invalid LocalAdmin."
                    }
                     if(($ProvisioningType.ToLower() -ne "windows") -and ($ProvisioningType.ToLower() -ne "linux"))
                    {
                        $ErrorState=2
                        Throw "Error: Invalid ProvisioningType."
                    }

                #Convert VMFQDN to Netbios Name
                $VMName = $VMFQDN.Split('.')[0]
           if($VMName.Length -gt 15)
            {
                 $ErrorState=2
                Throw "Error:VMName Length Should Not Increase More Than 15 Characters.."
            }

                 $JobGroup = [System.Guid]::NewGuid()
                 $Trace += "Importing VMM module 'r'n"
                 try
                 {
                $Path = Get-ItemProperty "HKLM:SoftwareMicrosoftMicrosoft System Center Virtual Machine Manager ServerSetup"
                $FinalPath = $Path.InstallPath + "inpsModulesVirtualMachineManagervirtualmachinemanager.psd1"
                ipmo $FinalPath
            }
                 Catch
                 {
                $Trace += "Importing of the VMM module failed as the module was already present `r`n"
                 }
           # $Trace += "Getting VM Template with id $ExistingVMTemplateID... 'r'n"
           # $existingVMTemplate = Get-SCVMTemplate | where { $_.Name -eq $ExistingVMTemplateID }
            #If ($existingVMTemplate -eq $null)
          #  {
             #   Throw "Error: VM Template with id $ExistingVMTemplateID not found."
        #    }
                Get-SCVMMServer -ComputerName $VMMServerName -UserRoleName $UserRoleName| Out-Null

                 $Trace += "Checking if the VM is Linux VM 'r'n"
                 if (($ProvisioningType -eq "Hyper-V Linux") -or ($ProvisioningType -eq "VMWare Linux"))
                 {
                                                       $Trace += "Setting Virtual COM Port for Linux VM `r`n"
                     Set-VirtualCOMPort -NamedPipe "\.pipe$VMName" -GuestPort 1
-JobGroup $JobGroup
                 }

            $Trace += "Checking if the VM is Windows Workgroup VM 'r'n"
            if (($IsWorkgroup -eq "true") -and ($ProvisioningType -like "*Windows*"))
            {
                 #Creating local administrator credentials when the Provisioning type is windows and it is a workgroup VM
                $LocalAdminCred = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $LocalAdminUser,(ConvertTo-SecureString -AsPlainText -Force -String $LocalAdminPassword)
            }

           $Trace += "Getting UserRole $UserRoleName 'r'n"
           $UserRole = Get-SCUserRole -Name $UserRoleName

            $Trace += "Getting VM Template $ExistingVMTemplateID ... 'r'n"
            ($VMTemplate = Get-SCVMTemplate | where {$_.Name -eq $ExistingVMTemplateID}) | Out-Null
            If ($VMTemplate -ne $null)
            {
               $Trace += "Creating the VM Configurations based on the VMTemplate ... 'r'n"
               $virtualMachineConfiguration = New-SCVMConfiguration -VMTemplate $VMTemplate -Name $VMName

                $Trace += "Getting the cloud object on which to deploy the VM ... 'r'n"
                $Cloud = Get-SCCloud -Name $CloudName

                $Trace += "Checking if Cloud object is null... 'r'n"
                if($Cloud -eq $null)
                {
                    $ErrorState = 2
                    Throw "Error: Cannot find the specified Cloud : $CloudName"
                }
                If ($Cloud -ne $null)
                {
                 $Trace += "Checking Provisioning type and Creating VM $VMName ... 'r'n"
                    $Trace += "Checking if Provisioning type is valid ... 'r'n"
                    if( ($ProvisioningType -eq $null) -or ($ProvisioningType -eq ""))
                    {
                          $Trace += "Provisioning type is not valid. 'r'n"
                        Throw "Error: Invalid Provisioning Type $ProvisioningType"
                    }
                    elseif (($ProvisioningType -eq "Hyper-V Linux") -or ($ProvisioningType -eq "VMWare Linux"))
                    {
                          $Trace += "Creating the Linux VM ... 'r'n"
                        $VM = New-SCVirtualMachine -Name $VMName -UserRole $UserRole
-VMConfiguration $virtualMachineConfiguration -Cloud $Cloud -RunAsynchronously -JobGroup
$JobGroup -Description "VM Provisioning Request ID: $RequestID" -
SkipInstallVirtualizationGuestServices -StartVM | Out-Null
                    }
                    elseif ($IsWorkgroup -eq "true")
                    {
                          $Trace += "Creating Workgroup VM ... 'r'n"
                        $Trace += "Checking if LocalAdministratorCredential is not null ... 'r'n"
                        if($LocalAdminCred -eq $null)
                        {
                          Throw "Error: Local Admin Credential is null"
                                                                                              }
                            $Trace += "Credentials have valid data. Creating the VM. ... 'r'n"
                            $VM = New-SCVirtualMachine -Name $VMName -UserRole $UserRole
-VMConfiguration $virtualMachineConfiguration  -Cloud $Cloud -
LocalAdministratorCredential $LocalAdminCred -Workgroup $WorkgroupName -
RunAsynchronously -JobGroup $JobGroup -Description "VM Provisioning Request ID:
$RequestID" -ComputerName $VMName -SkipInstallVirtualizationGuestServices -StartVM |
Out-Null
                        }
                        elseif ($IsWorkgroup -eq "false")
                        {
                                        $Trace += "Creating Domain joined VM ... 'r'n"
                                       $VM = New-SCVirtualMachine -Name $VMName -
UserRole $UserRole -VMConfiguration $virtualMachineConfiguration  -Cloud $Cloud -
RunAsynchronously -JobGroup $JobGroup -Description "VM Provisioning Request ID:
$RequestID" -ComputerName $VMName -SkipInstallVirtualizationGuestServices -StartVM |
Out-Null
                        }
                        If ($? -eq "True")
                        {
                          $ErrorState = 0
                        }
                        Else
                        {
                          Throw $Error[0]
                        }
                    }
                    Else
                    {
                        Throw "VM Host $VMHostName not found."
                    }
                }
                Else
                {
                    Throw "VM Template $VMTemplateName not found."
                }
            }
            Catch
            {
                    $ErrorState = 2
                    $ErrorMessage = $error[0].Exception.ToString()
            }
            Finally
            {
                $Trace += "`r`n"
                $Trace += "Exiting New VM `r`n"
                $Trace += "ErrorState:   $ErrorState `r`n"
                $Trace += "ErrorMessage: $ErrorMessage `r`n"
            }
            $Results = @($ErrorState, $ErrorMessage, $Trace, $VM.ID)
            Return $Results
        }
            $ErrorState = $ReturnArray[0]
            $ErrorMessage = $ReturnArray[1]
            $Trace += $ReturnArray[2]
            $VMID = $ReturnArray[3]
            Remove-PSSession -Session $Session
        }
}
catch
{
        $ErrorState = 2
         $ErrorMessage = $error[0].Exception.ToString()
}
finally
{
                $Trace += "'r'n"
                $Trace += "Exiting New VM 'r'n"
                $Trace += "ErrorState:   $ErrorState 'r'n"
                $Trace += "ErrorMessage: $ErrorMessage 'r'n"

}
}

The above code leverages the Remote PowerShell session patterns from Chapter 5 where we described how to make remote PowerShell calls from a runbook to a management server and executing script code in the remote session. In this example, we are creating a remote Windows PowerShell session to the VMM server. There it imports the VMM Windows PoweShell module and cmdlets then connects to the VMM server by calling Get-SCVMMServer. Then the script gets the VMM cloud and the VMM template using the values from the data bus which were supplied by the process calling this component runbook and supplied to the Initialize Data starting point activity. The script then uses the input data to run the New-SCVirtualMachine cmdlet on the VMM server to create a new virtual machine. The script must return data other than null. In a successful case it will return ErrorState with a value of zero. Critical to the framework outlined in this book, the script and this component runbook do not wait for the VM to be created, the task is run asynchronously, meaning the component runbook will end as soon as the command is executed but the process will continue to run on the VMM server. This pattern is utilized so that in high volume scenarios there are not dozens or hundreds of long-running runbooks open on the Orchestrator server waiting for VMs to be created.

Return Data

The third step in the CreateNewVM component runbook is to define the return data from the runbook. The process is the same as for the GetVMHostRating runbook described previously so we will not repeat it in this section.

Links and Conditional Logic

Once the activities in the runbook are configured, the links and conditional logic between the activities can be configured. The process is the same as for the GetVMHostRating runbook described previously so we will not repeat it in this section.

Control runbooks

With the component runbooks created, the next step in creating the VM provisioning process is to author the required control runbooks. The control runbooks are where the process logic and state management of the process are contained. For the VM provisioning process in this example, several control runbooks are defined.

Control runbook detail: Monitor VM Provisioning

The first control runbook is called Monitor VM Provisioning, which is shown in Figure 6-10. This runbook is the only runbook in this example that is set to start and stay running in Orchestrator on an ongoing basis. This runbook uses a Monitor File starting point activity and monitors a particular folder for a new instance of an XML input file for VM creation.

A diagram of the Monitor VM Provisioning runbook.

Figure 6-10. A diagram of the Monitor VM Provisioning runbook.

This runbook monitors a file share (called the Active folder based on the routing pattern described in the previous chapter) and waits for an XML file to be created in the monitored location. It will then process the file, get all the different paths to the folders that are required for the routing pattern, read the file, validate the input, and move the file to the InProcess folder. It then calls the VM Provisioning Engine control runbook without waiting for completion. After that this instance is completed and it will again wait for another XML file.

Control runbook: VM Provisioning Engine

The next control runbook is the VM Provisioning Engine runbook. This runbook contains the majority of the process flow and state management logic. This control runbook, which is shown in Figure 6-11, is called VM Provisioning Engine and its purpose is to make sure all required steps are invoked in the right order and that management of the state of the overall process is tracked to enable the ability to restart the process from the last successful step should a correctable error be encountered.

A diagram of the VM Provisioning Engine runbook.

Figure 6-11. A diagram of the VM Provisioning Engine runbook.

The VM Provisioning Engine will process the XML file from the previous runbook (Monitor VM Provisioning) from the InProcess folder and continue the process execution. As you can see in Figure 6-10 and Figure 6-11, both control runbooks share the first few component runbook calls, the difference between Monitor VM Provisioning and VM Provisioning Engine starts with updating the status field in the XML file. In this case, it will increase the status value by one to indicate that the next phase of the process has begun.

As mentioned earlier, the status is responsible for the steps (control runbooks) involved and in which order they are executed. Based on the status value, it will call one of the five additional control runbooks, each executing a specific step:

  • Create a new VM from a fixed template

  • Create a new VM from a dynamic template

  • Check VMM job status

  • SCOM Agent installation

  • Complete VM provisioning

The Prepare Filter Logic activity is a simple script activity that will prepare and analyze the current status information. For some activities, Orchestrator allows only the usage of data from the connected activity, not the activities previous to it. So in this example you are not able to access data from Read File, but only from Update Status. However, the VM Provisioning process needs to use a parameter from the XML file that will identify whether a static or dynamic template should be used to determine which runbook to call next or take a different path through the process flow. So we introduce the Prepare Filter Logic script which can easily access all published data, such as that from the Read File activity. The output of that script will then be used as the filter criteria for the different paths.

The VM Provisioning Engine control runbook and the other related control runbooks should be created in the folder structure outlined previously and as shown in Figure 6-12.

Control Runbooks folder hierarchy in Orchestrator.

Figure 6-12. Control Runbooks folder hierarchy in Orchestrator.

The control runbook is built using the following runbook activities:

  • Initialize Data

  • Invoke Runbook (multiple times, one for each component runbook call)

  • .NET Script

  • Return Data

The key is to understand that all Invoke Runbook activities call component runbooks that are either located in the Component Runbooks folder or—if they are specific to the IT process (such as Read XML File)—component runbooks that are located in the Control Runbooks folder itself.

It is important to check the settings such as Invoke By Path (to make sure your runbook works in other environments as well) and Wait For Completion as shown in Figure 6-13. Wait for completion is required in many cases otherwise your control runbook would not wait for the data that will be returned by the component runbook. In other cases, there may be situations where you don’t want to wait for completion. It is important to consider the desired outcome each time and use the appropriate setting for the circumstance.

The Details tab with the Invoke Runbook settingfor ReadFile.

Figure 6-13. The Details tab with the Invoke Runbook settingfor ReadFile.

After the ReadFile step, the Update Status step calls a component runbook while supplying input parameters including the FilePath, FileName, and Status value. This value will be used to increase the status in the file. In this case it will be increased by one. It will then publish the new updated value back the data bus. We will use this value to determine the right path for the VM Provisioning Engine runbook to take. The reasoning behind this pattern is that VM provisioning is a multistep process where some steps are long running and where some steps might fail. This pattern and its associated state management using a file and status numbers lets us model a wide range of process flows using a single runbook. This runbook will be called several times through the VM provisioning process and the execution path taken will depend on the status code.

The Prepare Filter Logic activity uses the following script so that it can use data in addition to the Status field alone. It will take the two variables, the VMTemplateType and the updatedStatus to calculate another output value called outStatus.

#Inputs
$outVMTemplateType = "{VMMTemplateType from "ReadFile"}"
$inStatus = "{updatedStatus from "Update Status"}"

#Standard Variables
$ErrorState=0
$ErrorMessage=""
$Trace=""
$Error.Clear()

try
{
    $Trace="Preparing Filter logic 'r'n"
    $Trace +="Input Parameters: 'r'n"
    $Trace +="outVMTemplateType:$outVMTemplateType 'r'n"
    $Trace +="inStatus:$inStatus  'r'n"

    $outStatus=$inStatus
    if($outVMTemplateType -eq "fixed" -and $inStatus -eq 1) { $outStatus=1 }
    if($outVMTemplateType -eq "dynamic" -and $inStatus -eq 1) { $outStatus=2 }
    $Trace+=" Preparing Filter Logic Completed Successfully... 'r'n"
}
catch
{
    $Trace += "Exception caught in remote action '$Action'... 'r'n"
    $ErrorState = 2
    $ErrorMessage = $error[0].Exception.tostring()
}
finally
{
    $Trace += "Exiting remote action $Action.. 'r'n"
    $Trace += "ErrorState: $ErrorState.. 'r'n"
    $Trace += "ErrorMessage: $ErrorMessage'r'n"
}

The outStatus will be published as filterStatus to the data bus and then be leveraged to define the path and the next step, as shown in Figure 6-14.

Filter logic for the VM Provisioning Engine runbook.

Figure 6-14. Filter logic for the VM Provisioning Engine runbook.

This concept becomes powerful when you consider that one activity can have multiple output links and possible execution paths. While this can increase complexity, the use of link labels to indicate process flow can be helpful, as shown in Figure 6-15.

Link logic for the VM Provisioning Engine control runbook process flow.

Figure 6-15. Link logic for the VM Provisioning Engine control runbook process flow.

All Invoke Runbook calls to the various control runbooks that implement the specific steps, such as Create Fixed VMM Template, are configured without Wait For Completion checked (see Figure 6-16). That will help to avoid long-running runbooks by not holding open the VM Provisioning Engine runbook. The instance of the VM Provisioning Engine is completed within a few seconds since it would not wait until the VM is created. The reason this is possible in such a multistep process is the use of the XML file and status codes to maintain state across multiple runbooks.

Invoke Runbook activity without waiting for completion.

Figure 6-16. Invoke Runbook activity without waiting for completion.

Remaining control runbooks

Figure 6-17 illustrates that there are a number of additional runbooks in the VM provisioning process. For brevity, these are not described in detail; they are either very similar to existing runbooks already documented or they are unique to a particular design such as the step to install the System Center Operations Manager agent in the VM after provisioning. They are included in the figures as examples of how the process can be extended with different steps specific to your particular requirements and how the same framework and design patterns can be utilized.

Initiation runbook

Both the component and control runbooks required for the VM provisioning process have been described in this chapter. The final level of framework is the initiation runbook level. As discussed in previous chapters, the reason for this modularity is to support multiple methods of triggering a given process. This example uses the simplest model, which is creating an initiation runbook that creates a file in a particular location to trigger the Monitor VM Provisioning control runbook. In another environment it might be desirable to have a portal or help desk application initiate a VM provisioning request and this could be easily implemented by creating new initiation runbooks without having to modify the control or component runbooks at all, provided the required data is supplied to them.

Initiation runbook: Initiate VM Provisioning

In the example documented here, a runbook will generate an XML file that will trigger a new VM provisioning instance. This runbook is implemented as an initiation runbook that will again validate the input to make sure all values are in a given range or from a specific type. It will get the folder path of the active folder and create the XML file there. With that approach, every file will be picked up automatically by the Monitor VM Provisioning runbook and kick off a new process immediately.

The Initiate VM Provisioning runbook is shown in Figure 6-17.

Initiation runbook for VM provisioning.

Figure 6-17. Initiation runbook for VM provisioning.

The runbook uses the following activities:

  • Initialize Data

  • Invoke Runbook

  • Return Data

Initialize Data now defines all input parameters from the XML file that was shown earlier in this chapter. The same Validation and Get Path component runbooks used in the previous examples to validate the input data and get the appropriate folders are utilized.

Next, another component runbook that creates the XML file will be utilized as shown in Figure 6-18.

The Create XML File Runbook.

Figure 6-18. The Create XML File Runbook.

The Create XML File component runbook called by the Initiate VM Provisioning control runbook takes all the input data and creates an XML file in the given path, as shown in Figure 6-19.

The results of Invoke Runbook activity of the Create XML File Properties.

Figure 6-19. The results of Invoke Runbook activity of the Create XML File Properties.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.16.184