Skip to content

Issue with preview and customization form in the SCSM

Issue with preview and customization form in the SCSM published on 1 Comment on Issue with preview and customization form in the SCSM

There are several years already the one issue of SCSM make developers of forms customization really crazy: mixing form customization and preview form is really pain in the ass. There was bug opened in Microsoft, but it was closed without real issue. So I decide to close this question once and for all.

First of all, here is some basic concept of form engine in SCSM, so I will not repeat any statement from them:

Overview of the Forms Infrastructure and the Generic Form
Changing the Incident Preview Form

From here I assume that you already have some knowledge about form engine.

Now about the problem: for instance, you have customization form for Manual Activity form and preview form. In many cases only preview form will work, but customization form will be ignored. No error, nothing. Just doesn’t work. There is one article about this problem, but it contains wrong workaround.

The root cause of this problem (as usual) in the code. To find all forms available for current object SCSM use following logic:

  1. Get target class’s Id for the object
  2. Get all type projections based on this class Id (using ManagementPackTypeProjectionCriteria and EntityTypes.GetTypeProjections)
  3. Based on 1) and 2), get all forms (using ManagementPackFormCriteria and Presentation.GetForms)
  4. Check if BaseForm for each form is null. Drop form from list if BaseForm is not null (because this is extension form)
  5. Get first form from remaining list and use it to build list of extensions available for this form
  6. Check if form’s category doesn’t contains category “Microsoft.EnterpriseManagement.ServiceManager.UI.Console.OverviewForm”, drop such from the list (this is Preview forms)

You can add two main forms for same class, so after step #6 you have only one form. But what about extensions? Take a look on step #5. There is a huge mistake here: if you have preview form this form can be returned as first form, and extensions list will be loaded based on this form, and it’s completely wrong.

Now, let’s take a real sample. We want to install Preview form and customization form for Manual Activity. Base form for manual activity named “Microsoft.EnterpriseManagement.ServiceManager.ActivityManagement.Forms.ManualActivityForm”. So here is definition for our customization form (we are using custom type projection):

<Form ID="Company.Form.ManualActivity" Accessibility="Public" Target="Company.Projection.ManualActivity" BaseForm="ActivityManagement!Microsoft.EnterpriseManagement.ServiceManager.ActivityManagement.Forms.ManualActivityForm" TypeName="Microsoft.EnterpriseManagement.ServiceManager.ActivityManagement.Forms.ManualActivityForm">

When you will install this MP to SCSM the extension for you form will work.

Now we want to install Preview Form, I will use sample based on itnetX’s Preview Forms. Here is definition:

<Category ID="itnetX.PreviewForms.ManualActivityPreview.PreviewCategory" Target="itnetX.PreviewForms.ManualActivityPreviewForm" Value="Console!Microsoft.EnterpriseManagement.ServiceManager.UI.Console.OverviewForm" />
…..
<Form ID="itnetX.PreviewForms.IncidentPreviewForm" Accessibility="Public" Target="PreviewForms!itnetX.PreviewForms.IncidentPreview.ProjectionType" Assembly="PreviewForms!itnetX.PreviewForms.Assembly" TypeName="itnetX.PreviewForms.Controls.IRPreviewForm">
   <Category>Form</Category>
</Form>

Right after you will install it, your customization is gone. To illustrate why I did create some PowerShell snippet to illustrate the problem (SMLets required):

param([Parameter(Mandatory = $true)][guid]$targetClassId)
<# 
    This script emulate job of SCSM API to get form base on class.
    This part f code located in class Microsoft.EnterpriseManagement.UI.SdkDataAccess.DataAdapters.ManagementPackFormAdapter (Microsoft.EnterpriseManagement.UI.SdkDataAccess.dll)
    Method GetFormRecursiveHelper on this class contains foundamental error: if you have more than one base form for the class (for instance, main form and preview form) then code returns undeterministic state

    Our luck, looks like SDK returns data not randomly, but sorted by internal ID (Guid). So everything that you need to do is to build 
#>
Import-Module SMLets

$mg = New-SCSMSession -PassThru
$listCr = @("Target = '$targetClassId'")

$tpCr = new-object Microsoft.EnterpriseManagement.Configuration.ManagementPackTypeProjectionCriteria("Type='$targetClassId'")

$mg.EntityTypes.GetTypeProjections($tpCr) | % {
    $listCr += "Target = '$($_.Id)'"    
}

$fullCr = [string]::Join(" OR ", $listCr)

$crForm = new-object Microsoft.EnterpriseManagement.Configuration.ManagementPackFormCriteria($fullCr)

write-host "Emulating list order for main form (Base form must be on first place):" -ForegroundColor Green
$mg.Presentation.GetForms($crForm) | ? {!$_.BaseForm} | select Name, Id

When you will run this code you will get something like this:
image

Note: our preview form is first in the list. And of course, there are no any extensions available for the form, so no extensions will be loaded.

Now, when the problem is clear, what to do with it? Good for us, SDK returns date from DB in sorted way, and if no sorting specified (for management pack elements you can’t specified sorting at all) then sorting by key column in the SQL table used. The key column in all SCSM’s table is ObjectId (or internal Id). This is Guid (uniqueid in terms of SQL), so we have to build that id for our Preview Form. The internal ID of the preview form must be “more” (in terms of sorting weight) than internal ID of the base form. To simplify, you can use statement that Guid sorted alphabetically. So in my case, guid of my preview form must start with 8,9 and any character.

Next question, how to generate proper guid. And an another good news here is fact that the way how this Guid builds is known and defined in SCSM’s database. Copy-paste all things from management pack definition to T-SQL is not “our” way, so I build script to generate IDs for entire management pack (this script was adopted from one we using in dev process, so will contain additional data). To run it please change path to MP at the bottom of the script. This script will work for both, sealed and unsealed management packs (for sealed you can must define <Cataegory> section with ManagementPackPublicKeyToken element):

function Get-SCSMObjectId
{
   
    Param(
   [Parameter(Mandatory=$true)]
   [string]$MpName,
   [string]$publicKeyToken,
   [string]$objectName

)
    $resultString = "MPName=$mpName"
    if(![string]::IsNullOrEmpty($publicKeyToken))
    {
        $resultString = $resultString + ",KeyToken=$publicKeyToken"
    }
    if(![string]::IsNullOrEmpty($objectName))
    {
        $resultString = $resultString + ",ObjectId=$objectName"
    }

    $hash = [byte[]][System.Security.Cryptography.HashAlgorithm]::Create("SHA1").ComputeHash([System.Text.Encoding]::Unicode.GetBytes($resultString));

    # most bytes from the hash are copied straight to the bytes of the new GUID (steps 5-7, 9, 11-12)
    [byte[]]$newGuid = @(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
    [Array]::Copy($hash, 0, $newGuid, 0, 16);
    return new-object Guid(,$newGuid);
}

function Get-SCSMDevIdString
{
     Param(
   [Parameter(Mandatory=$true)]
   [string]$MpName,
   [string]$publicKeyToken,
   [string]$objectName,
   [string]$prefix

    )

    $id = Get-SCSMObjectId -MpName $MpName -publicKeyToken $publicKeyToken -objectName $objectName
    $retObj = New-Object PSObject
    Add-Member -InputObject $retObj -MemberType NoteProperty -Name Prefix -Value $prefix
    if(![string]::IsNullOrEmpty($objectName))
    {
        Add-Member -InputObject $retObj -MemberType NoteProperty -Name Name -Value $objectName
    }
    else
    {
        Add-Member -InputObject $retObj -MemberType NoteProperty -Name Name -Value $MpName
    }
    Add-Member -InputObject $retObj -MemberType NoteProperty -Name Id -Value $id
    
    return $retObj
}

function Get-SCSMMPIds
{
    Param(
   [Parameter(Mandatory=$true)]
   [string]$MpPath
    )

    $xml = (Get-Content $MpPath)
    $mpId = $xml.ManagementPack.Manifest.Identity.ID

    $categoryNodes = $xml.SelectNodes("//Category[contains(@Value, 'Microsoft.EnterpriseManagement.ServiceManager.ManagementPack')]")
    $publicKey = ""
    if($categoryNodes.Count -gt 0)
    {
        $publicKey = $categoryNodes[0].ManagementPackPublicKeyToken
    }

    $retList = @()

    $retList += Get-SCSMDevIdString -MpName $mpId -publicKeyToken $publicKey

    foreach($node in $xml.SelectNodes("//*[@ID]"))
    {

        if($node.Name -eq "ClassType"){ $prefix = "Class" }
        elseif($node.Name -eq "RelationshipType") { $prefix = "Rel" }
        elseif($node.Name -eq "EnumerationValue") { $prefix = "Enum" }
        elseif($node.Name -eq "TypeProjection") { $prefix = "TP" }
        elseif($node.Name -eq "Category") { 
            if($node.Attributes["Value"] -ne $null -and $node.Attributes["Value"].Value.Contains("Microsoft.EnterpriseManagement.ServiceManager.ManagementPack"))
            {
                continue;
            }
            $prefix = "CAT" 
        }
        elseif($node.Name -eq "Rule") { $prefix = "WF" }
        elseif($node.Name -eq "StringResource") { $prefix = "STR" }
        elseif($node.Name -eq "WriteActionModuleType") { $prefix = "Module" }
        elseif($node.Name -eq "View") { $prefix = "View" }
        elseif($node.Name -eq "Folder") { $prefix = "Folder" }
        elseif($node.Name -eq "Form") { $prefix = "Form" }
        else{ continue; }
    
        $retList += Get-SCSMDevIdString -MpName $mpId -publicKeyToken $publicKey -objectName $node.Attributes["ID"].Value -prefix $prefix
    }

    $retList


}



Get-SCSMMPIds "C:\Temp\itnetX.PreviewForms.Forms.xml"

Sample output of this script:

image

Your goal is to generate Id that will be after base form. In my case, that was enough to add “111” to form name:

image

How you can use preview forms and customization in same time Smile

Share

1 Comment

Leave a Reply

%d bloggers like this: