Social

mandag den 29. juni 2015

Authoring Type Projections in VSAE

It seems that VSAE is not that widely used. That may be a lack of information on the hows and the whats of things. So today I will just show something basic; authoring a type projection.

The VSAE can be downloaded from here. You will need Visual Studio 2012/13 Ultimate or Professional.

Now in Visual Studio create a new project. Select Templates->Management Pack->Service Manager and select the template that matches your environment version. I will pick the R2 one. I will name mine Codebeaver.IR.TypeProjection.Tutorial.

I will do a type projection on the incident class, hence we will need to reference the MP where this is defined. If you don't know this there are a few ways to find out. I prefer using powershell. Open a Service Manager Shell which will load the native service manager powershell module. Enter

Get-SCSMClass -Name "*incident" -ComputerName SM01

This tells it to look for classes that matches *incident (anything followed by incident) and the computername is the name of your management server.
This will give you three results. We are looking for the System.WorkItem.Incident class. Repeat the command with this more specific name. To get the management pack we can write

(Get-SCSMClass -Name "System.WorkItem.Incident" -ComputerName SM01).getmanagementpack()

Which tells us that the incident class is found in the System.WorkItem.Incident.Library management pack.
I find the easiest approach is to go to C:\Program Files (x86)\Microsoft System Center 2012\Service Manager Authoring (or where ever you have installed the authoring console), and then simply search for the managegement pack. When found right click and "open file location". Copy the path.

Now back in visual studio right click the references and "Add reference..."


Click the browse tab and paste in the path. Scroll and look for the System.WorkItem.Incident.Library.mp file. Now that it is added we can reference it using an auto genereated alias.

Now right click the project and select Add-> New item... Pick the "empty management pack fragment". I will name mine IncidentTypeProjection.mpx.
You will be presented with some xml. Type < and a number of possible XML-tags are suggested.

It will narrow down the list as you type. We want TypeDefinitions, and inside that EntityTypes, and finally inside that TypeProjections. VSAE will mostly present you with valid XML. 


Inside the TypeProjections tag we enter TypeProjection and then a space and you get to pick amongst a number of possible attributes for that tag. Sometimes it can even autocomplete the value for a given attribute, ex. Accesibility (as there are only two possible values). You can put anything into the ID. For type we want something that looks like: Alias!Class. To get the alias select the management pack reference that we just added and hit F4. Here you can see that the alias is SWIL. Hence we enter SWIL!System.WorkItem.Incident, and we can finish the type projection with a >. This is a good time to build (ctrl+shift+b). Resolve any errors (there should be none).



Now we must add a component to the type projection. You must provide the component with an alias (type in anything). the Path is a bit more tricky, and VSAE will not help you one bit. I suggest you read my bit on type projections before you continue reading. To get the relationship part we use the same trick as we did for getting the class. Here is a little help:

Get-SCSMRelationshipClass -Name "created" -ComputerName SM01 | fl Name

My component ends up looking

<Component Alias="IsCreatedBy" Path="$Target/Path[Relationship='SWL!System.WorkItemCreatedByUser']$"/>

And no errors when building! Be really carefull with $ and ' and [] at the right places.

We also need to add a section of display strings to finish up. After the closing TypeDefinitions add a LanguagePacks tag. It will end up looking like this

  <LanguagePacks>
    <LanguagePack ID="ENU" IsDefault="true">
      <DisplayStrings>
        <DisplayString ElementID="ThisCanBeAnythingAsLongAsItIsUnique.TypeProjection">
          <Name>
            Incident (Is created by)
          </Name>
        </DisplayString>
      </DisplayStrings>
    </LanguagePack>
  </LanguagePacks>

Note that the LanguagePack ID must a valid ID. I haven't found a table with all possible IDs (post in the comments below if you do).

We are almost there. Go to properties of the project and in the build tab check "generate sealed and signed management pack". Browse for a key file and select your snk-file (if you don't know this part read this to get up to speed - you just need to read the part on Create your SNK).
In the Management group tab click Add and enter your Service Manager Management Server (you only need add it once), if already added select it and click "Set as Default". Finally in the Deployment tab under "start action" select "Deploy projects to default management group only".

Build again and make sure there are not errors. Now we can deploy the type projection directly to the server by hitting F5. That is pretty sweet and can really speed up your development even for basic stuff such as type projections.

The entirety of the fragment looks as below

<ManagementPackFragment SchemaVersion="SM2.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <TypeDefinitions>
    <EntityTypes>
      <TypeProjections>
        <TypeProjection ID="ThisCanBeAnythingAsLongAsItIsUnique.TypeProjection" 
                        Accessibility="Public" 
                        Type="SWIL!System.WorkItem.Incident">
          <Component Alias="IsCreatedBy" 
                     Path="$Target/Path[Relationship='SWL!System.WorkItemCreatedByUser']$"
                     />
        </TypeProjection>
      </TypeProjections>
    </EntityTypes>
  </TypeDefinitions>
  <LanguagePacks>
    <LanguagePack ID="ENU" IsDefault="true">
      <DisplayStrings>
        <DisplayString ElementID="ThisCanBeAnythingAsLongAsItIsUnique.TypeProjection">
          <Name>
            Incident (Is created by)
          </Name>
        </DisplayString>
      </DisplayStrings>
    </LanguagePack>
  </LanguagePacks>
</ManagementPackFragment>

onsdag den 24. juni 2015

Custom Task for Work Item Search in Service Manager

You can often find what you are looking for using the advanced search feature in Service Manager, but the process is tedious as it only remembers the class you last searched for. If you often use the exact same search queries then the great Anton Gritsenko have a solution with the option of saving your queries. Unfortunately this solution cannot change the query once saved.
A middle ground would be to have a custom task that takes one or more inputs and queries a specific class based on that input.

Beaver Dams Inc. sometimes include invoice numbers in the description field of incidents and service request. This does not happen often enough to justify extending both classes with a specific field for the invoice number. Also both types of work items are not annotated in any way in order to identify which incidents and service requests contains the invoice numbers.

The solution is a query on the description property of the work item class. The description must contain a variable string (the invoice number). This is fairly easy to do using advanced search, but does require you to add the desription property in each search (it remembers the class picked in each session). As I will show it is also not that difficult (once you know how) to create a custom console task that executes the query with a variable property value.

Disclaimer: A lot of this code is heavily inspired by the aforementioned solution by Anton. 

First order of business is actually installing the "Advanced Search with Saver". It will cleverly hook into the default advanced search and ask if you wish to save the query. Search for objects of the class Work Item and add the property Description. Doesn't matter what you input, this will be the variable component of the query in the console task.


Likely nothing will turn up. After closing the window you will be prompted to save the query. Click "yes" and enter a query name. Name it something that you can easily find in the registry later on, ex. iwetmybed. Start the registry editor and search for the query name in HKEY_USERS. I will not show it here as it is quite a lot of text. The part we want to concern ourselves with looks somewhat like this:

<Expression>
  <SimpleExpression>
    <ValueExpressionLeft>
      <Property>$Context/Property[Type='f59821e2-0364-ed2c-19e3-752efbb1ece9']/e5162c95-9469-924c-2298-9e351e0dc383$</Property>
    </ValueExpressionLeft>
    <Operator>Like</Operator>
    <ValueExpressionRight>
      <Value>%whatever%</Value>
    </ValueExpressionRight>
  </SimpleExpression>
</Expression>

That looks familiar! The entire query will be inserted as a giant block of text into our code (remeber to escape the double quotes) where whatever will be replaced by a string variable. This variable could come from anywhere; I have a window popup and ask for the invoice number.

The core of the code then looks like (omitting some of the query for brevity)

SearchNodeProvider searchNodeProvider = (SearchNodeProvider)FrameworkServices.GetService(typeof(SearchNodeProvider));

String sDescriptionContains = (String)inputWindow.InputText.Text;

String advancedSearchConfiguration = "<Data>..." +
  "<Expression>" +
    "<SimpleExpression>" +
      "<ValueExpressionLeft>" +
        "<Property>$Context/Property[Type='f59821e2-0364-ed2c-19e3-752efbb1ece9']/e5162c95-9469-924c-2298-9e351e0dc383$</Property>" +
      "</ValueExpressionLeft>" +
      "<Operator>Like</Operator>" +
      "<ValueExpressionRight>" +
        "<Value>%" + sDescriptionContains + "%</Value>" +
      "</ValueExpressionRight>" +
    "</SimpleExpression>" +
  "</Expression>" +
"</Criteria> ...";

searchNodeProvider.AdvancedSearchConfig = advancedSearchConfiguration;

Uri uri = new Uri(((object)NavigationModel.NavigationRoot).ToString() + 
                    "Windows/Search/ConsoleDisplay/Advanced");
NavigationModel.BeginOpenLink(
    NavigationModel.FindView((object)null, 
    (Uri)Microsoft.EnterpriseManagement.UI.Core.Shared.NavigationConstants.ConsoleWindowUri, 
    (FindViewCriteria)1), 
    uri, 
    null, 
    (object)null);


I will not pretend to know exactly what is going on. I was "inspired" by a decompiled version of the Advanced Search with Saver. But as you can see the SearchNodeProvider is fed a search configuration (the one we got from registry). The NavigationalModel is then told to open the specified URI, and voila the search results window is opened with the found objects that matches the search configuration we fed the SearchNodeProvider.

You can download the entire source here. Happy coding.

Edit: It is needed to change the thisMPID variable in LocalizationHelper.cs to the guid of the management pack that contains the stringresources used. In the example code, this is also where the console task is defined, hence you will have to import the bundle, get the MP id, change the code, compile, bundle, and import again. I suggest looking at my previous blogpost to automate this process.


tirsdag den 23. juni 2015

Automating Custom Code Deployment in Service Manager

Sounds too good to be true? Well it is. Almost. The script I will be sharing today will get you a long way though. A high level rundown of what it will do

  •  stop console
  •  seal MP
  •  bundle MP
  •  remove old MP
  •  import new MP bundle
  •  start console
That almost gets you all the way. You will have to do the clicking yourself. You can then debug the hell out of your code following this.

Script as follows, and download it here.


# Authored by Anders Spælling, spaelling@gmail.com

# this script automates some of the tasks needed to test custom code in the Service Manager console
# a high level rundown:
# stop console
# seal MP
# bundle MP
# remove old MP
# import new MP bundle
# start console

# This script requires 
# module ScsmPx installed. Just run this if you haven't got it already:  & ([scriptblock]::Create((iwr -uri http://tinyurl.com/Install-GitHubHostedModule).Content)) -ModuleName ScsmPx,SnippetPx
# need to have fastseal.exe in working dir, download from here: http://blogs.technet.com/cfs-file.ashx/__key/communityserver-components-postattachments/00-03-30-25-60/FastSeal.zip
# guide on fastseal.exe http://scsmnz.net/sealing-a-management-pack-using-fastseal-exe/

$DebugPreference = "Continue"
# this needs to point at where the compiled dll will end up        
$FullPathDLL = "C:\Users\Administrator\Documents\Visual Studio 2013\Projects\CB.SCSM.CustomWISearch\CB.SCSM.CustomWISearch\bin\Debug\CB.SCSM.CustomWISearch.dll"

# you will need an snk file. create your own. Follow this guide if you don't know how: http://scsmnz.net/sealing-a-management-pack-using-fastseal-exe/

# files distributed with this script also needs to go to working dir

# this is the working dir
cd c:\temp

# point at your SCSM management server
$ManagementServer = "SM01"
# change to match your own snk file
$KeyFile = "CB.snk"
# MP name without extension
$MPName = "CB.ConsoleTask.CustomWISearch"
$Company = "Codebeaver"

# not much need to edit below

Import-Module ScsmPx -ErrorAction Stop

# stop console
Write-Debug "Stopping console..."
Stop-Process -Name Microsoft.EnterpriseManagement.ServiceManager.UI.Console -ErrorAction SilentlyContinue

# seal MP
Write-Debug "Sealing MP..."
$ArgumentList = ".\$MPNameh.xml /keyfile $KeyFile /company $Company"; #Write-Host "fastseal.exe $ArgumentList"
Start-Process -FilePath ".\fastseal.exe" -ArgumentList $ArgumentList -Wait -NoNewWindow

# copy dll to c:\temp
Copy-Item $FullPathDLL -Destination c:\temp

# bundle MP and dll
Write-Debug "Bundling MP..."
$DLL = $FullPathDLL.Split("\")[-1]
New-SCSMManagementPackBundle -Name "$($MPName).mpb" -ManagementPack ".\$($MPName).mp" -Resource ".\$($DLL)" -ComputerName $ManagementServer -Force -ErrorAction Stop

# remove previous MP (cannot overwrite sealed MP with same  MP version)
# If you delete a sealed MP, all of the data that it defined such as new classes (and all instances of these classes) or class extensions (and all extension data) will be lost.
$MP = Get-SCSMManagementPack -Name CB.ConsoleTask.CustomWISearch # name is the ID of the MP (as defined in xml)
if($MP)
{
    Write-Debug "Removing old MP..."
    Remove-SCSMManagementPack -ManagementPack $MP
}

# import MP bundle in new process - current process will block the file otherwise
Write-Debug "Importing new MP bundle..."
$MPName | powershell.exe {
    Import-SCSMManagementPack ".\$($input[0]).mpb"
    }

# start console
Write-Debug "Starting console..."
& "C:\Program Files\Microsoft System Center 2012 R2\Service Manager\Microsoft.EnterpriseManagement.ServiceManager.UI.Console.exe"

Søg i denne blog