Showing posts with label System Center Service Manager. Show all posts
Showing posts with label System Center Service Manager. Show all posts

Friday, March 11, 2016

SCSM View Manager

Hello everyone,

I was recently asked to create a tool that will allow our SCSM Administrators to more quickly and efficiently manage the Views and folders on SCSM, particularly speaking Cireson Views/Folders. The root cause was that they are on the plans of restructuring the company’s structure and the effort would take a long time with the built in methods.

My first thought was to create PowerShell scripts to automate this task, but they asked me if it was possible to create a GUI application for this instead. I was a bit hesitant at first because with GUIs you are normally bound to have to baby sit the user a lot more than with a cmdlet where the user normally has to perform their own research before being able to do much. Never the less I agreed, and here are the fruits of this work.



The application works by gathering the information from the exported Management Pack XML file, and making changes to it. It is fully developed in PowerShell working with PowerShell Studio 2015 (now 2016 edition) and I’ll get the source code into GitHub later this week.

The application has different features:

Folder Features:

  • Create a new Folder: this function is currently configured to create a Cireson folder but this can be easily modified or even a new function can be added to determine if you want to make a Cireson folder or a regular folder. (It goes without saying that you do need to have the Cireson suite installed or it won’t work) 
  • Create a new folder from template: this function will prompt you for a new name, and then will search for and clone the folder named ‘TemplateFolder’ as well as all subfolders and subitems. The function will then attempt to replace “TemplateFolder” with the new provided name on all of the created items. If a ‘TemplateFolder’ is not found then the function will fail and an error will be send out. 
    • Current Template folder example: 
  • Rename Folder: This function will prompt you for a new name and rename the name of the folder in the DisplayString XML Element 
  • Delete Folder: This function will delete the currently selected folder, and all of its subfolders and views, from the XML file. 
    • Removed Elements: 
      • <Categories> 
      • <Folder> 
      • <FolderItems> 
      • <ImageReference> 
      • <DisplayString> 
  • Clone Folder: This function will clone the folder into the same parent folder along with all of its subfolders and views.

View Features:

  • Copy View: This function will prompt you for a new name and copy the view with the new name and all of its properties to the same parent location. 
  • Delete View: This function will delete the currently selected view from the XML File. 
    • Removed Elements: 
      • <Categories> 
      • <View> 
      • <FolderItem> 
      • <ImageReference> 
      • <DisplayString> 
  • Rename View: This function will prompt you for a new name, and rename the View in the DisplayString XML Element.

Other Features:

  • The program also allows you for you to be able to drag and drop folders and views to any location. 

Requirements: 

  • This programs requires that you have Windows PowerShell 4.0 or above installed.
All of the changes that are performed are automatically saved to the XML file, so make sure you make a backup before you start making changes if you are not able to quickly export it.

I hope you’ll let me know what you think, if you have any other ideas, issues or concerns.

Download path:
https://www.dropbox.com/s/zmhvikam8snjv81/SCSM%20View%20Manager.exe?dl=0

I have made a new respository for this on GitHub that you can find here:
https://github.com/Jonatan-B/SCSMViewManager

Kind regards,
Me.

Wednesday, January 6, 2016

Modifying SCSM Management Pack to add Categories

Hello everyone!

There was some unfolding developments on my last post regarding the SCSM categories. The SCSM Admistrator asked me that he had received a request that required him to add about 50+ different categories through several parent categories, but that the way to add them through SCSM was extremely inefficient and was hoping that I could write a script or program to make it more streamlined.

I spoke with the Administrator and asked him to first tell me how it would be done manually, without the SCSM GUI, and found that it is all modified through the XML Management Pack. I first looked around for someone who maybe already created something for it, and I did found tools that allowed you to create MP through an Excel sheet, like this, but if you wanted to modify the existing MP I couldn't find anything.

I decided I'll have to create something. I originally planned to create him a desktop application with C#; however, my free trial for Visual Studio ran out, which I knew it was coming but I didn't want to request a license from the company because I have a feeling it would get denied. Anyways, so instead I decided to go with PowerShell.

I had been avoiding working with XML for a while, so it was a good opportunity to finally sit down and try to learn the ways. I got quite a big of help from the scripting guy, but mostly it was trial and error. In the end I was able to create 3 functions:

I created 3 cmdlets that will allow us to get quick information on the support group/category (list information, and treeview) and be able to add one or multiple lists in an instance.

Requirements:
Exported Management Pack containing the lists.
Name of the list that you wish to get information on/add more lists to.

The first cmdlet is 'Get-SCSMListItem' this cmdlet will provide you with information about list, you'll need to know the name of it, or GUID, and will provide the following output:


Notice that if you use the name parameter you can get multiple results, so you must know which one you need to use in order to accurately assess which one should be the parent when you are creating new lists. 

The second cmdlet is 'Get-SCSMListChildItems' this cmdlet will print out a 'structure' of the lists, depending on which parent you give it, please see below for an example that will further explain this:


Also notice the same parameter here, which i did, it will print out the structure for both lists. Making it simple to see which one is the one you wish to work with, and they'll print out in the same order, so once you know which one you need to work with you can use the necessary IDs. 

The last cmdlet is Add-SCSMListItems this cmdlet supports the following parameters:
  • -Lists: This parameter will contain the name of the lists that you want to add. This parameter will accept array. For example:
    • Single List:  -List ‘My New Category’
    • Multiple Lists: -List ‘My New Category1’,’My New Category2’
  • -ParentName: This parameter will be the name of the Parent where you wish to add your lists. Keep in mind that This cmdlet will not support multiple results for the ‘Name’ parameter, if multiple lists contain the same name, as shown in the past for ‘Group IT’, the command will fail. 
  • -ParentID: This parameter will be the ID of the Parent where you wish to add the lists. This parameter cannot be used with the ParentName parameter.
  • -ManagementPackXML: This parameter requires that you provide the exact path of the Management Pack XML file you exported from SCSM.
  • -ExportLocation: This is the file path where the modified XML file will be exported. If this is left blank the export will overwrite the imported Management Pack XML. If the export path provided does not exist the command will fail and no changes will be saved. 
There is no console output for this command.

Here is the command will do:
For each given list, it will create a new DisplayString item in the Management Pack, worth noting that this is hard coded to add it to the ENU LanguagePack; however, it can be easily modified to accept a parameter for the language pack as well, see the XML code examples below:

<DisplayString ElementID="$newGUID">
       <Name>$providedName</Name>
</DisplayString>

<EnumerationValue ID="$newGUID" Accessibility="Public" Parent="$providedParent" Ordinal="$ordinal" />

I'd also like to point out that as long as you know the parent you can use a CSV file to quickly create multiple lists. 

First you’ll need to create a CSV file that will look like the one below:

Parent
ListName
ParentGUID
Cat1_UnderRootParent
ParentGUID
Cat2_UnderRootParent
ParentGUID
Cat3_UnderRootParent
ParentGUID2
Cat1_UnderDifferentParent
ParentGUID2
Cat2_UnderDifferentParent
ParentGUID3
Cat1_SCSMParent
ParentGUID3
Cat2_SCSMParent

The first column will specify the parent that you wish to use, and the second the category that you wish to create. Once you have the CSV create you’ll use it in PowerShell like so:

$csv = Import-Csv C:\myCSVFile.csv

foreach($category in $csv)
{
    Add-SCSMListItems -Lists $category.ListName `
                      -ParentID $category.Parent `
                      -ManagementPackXML C:\ManagementPackPath.xml
                      -ExportLocation C:\ManagementPackPath_new.xml [see edit for details]

Once the changes are done then you can simply export the MP XML file back into SCSM and voila all done. 

Now for the source code:


function Get-SCSMListItem
{
    <#
      .SYNOPSIS
      This function will get information on an SCSM List Item.
      .DESCRIPTION
      This function will get information on the SCSM List Item specified. It will provide the Name, Parent, ID and Ordinance. 
      .EXAMPLE
      Get-SCSMList -Name "MyArea/CategoryName" -ManagementPackXML C:\MP_xmlFile.xml
      .EXAMPLE
      Get-SCSMList -ID "Enum.GUID" -ManagementPackXML C:\MP_xmlFile.xml
      .PARAMETER Name
      The name of the list you want to get information on
      .PARAMETER ID
      The ID of the list you want to get information on
      .PARAMETER ManagementPackXML
      The location where the XML File is saved
    #>
    [CmdletBinding()]
    Param
    (
        #Name
        [Parameter(Mandatory=$true,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='What name of the list do you want to get?',
            ParameterSetName="Name")]
        [Alias('ListName')]
        [ValidateLength(3,250)]
        [string]$Name,
        #ID
        [Parameter(Mandatory=$true,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='What ID of the list do you want to get?',
            ParameterSetName="ID")]
        [Alias('ElementID')]
        [ValidateLength(0,250)]
        [string]$ID,
        #ManagementPackXML
        [Parameter(Mandatory=$True,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='Where is the Management Pack XML Located?')]
        [Alias('XMLPath')]
        [ValidateLength(3,255)]
        [string]$ManagementPackXML
    )
    begin
    {        
    }
    process
    {
        Write-Verbose "Checking the XML file exists."
        Test-Path $ManagementPackXML -ErrorAction Stop | Out-Null
        Write-Verbose "File exists."

        Write-Verbose "Opening XML File with UTF8 encoding."
        [xml]$xml = Get-Content $ManagementPackXML -Encoding UTF8
        Write-Verbose "File opened successful."

        if($ID.Length -ne 0)
        {
            Write-Verbose "ID Provided. Attempting to find in the XMLElement DisplayStrings."
            $listResult = $xml.ManagementPack.LanguagePacks.LanguagePack | ? ID -eq "ENU" | %{$_.DisplayStrings.DisplayString} | ? ElementID -eq $ID
        }
        else
        {
            Write-Verbose "No ID Provided. Attempting to find name in the XMLElement DisplayStrings."
            $listResult = $xml.ManagementPack.LanguagePacks.LanguagePack | ? ID -eq "ENU" | %{$_.DisplayStrings.DisplayString} | ? Name -eq $name
        }
        
        if($listResult -ne $null)
        {
            foreach($list in $listResult)
            {
                Write-Verbose "The element has been found in the XML file. Gathering information."
                $ListInformationObject = New-Object psobject
                $ListInformationObject | Add-Member -MemberType NoteProperty -Name "ID" -Value $list.ElementID
                $ListInformationObject | Add-Member -MemberType NoteProperty -Name "Name" -Value $list.Name
                $ListInformationObject | Add-Member -MemberType NoteProperty -Name "Parent" -Value ($Xml.GetElementsByTagName("EnumerationValue") | ? ID -eq $list.ElementID).Parent
                $ListInformationObject | Add-Member -MemberType NoteProperty -Name "Ordinal" -Value ($Xml.GetElementsByTagName("EnumerationValue") | ? ID -eq $list.ElementID).Ordinal
                $ListInformationObject
            }
        }        
        else
        {
            Write-Error -Message "No lists were found by the name $Name" -Category InvalidArgument -RecommendedAction "Please ensure that the list exists in the XML File." `
                        -CategoryActivity "Get-SCSMListItem, Filter-XMLElement(DisplayString) by Name" `
                        -CategoryReason "The List Name provided was not found." `
        }
    }
    end
    {
        Remove-Variable -Name xml
    }
}

function Get-SCSMListChildItems
{
    <#
      .SYNOPSIS
      This function will get a view of the child items.
      .DESCRIPTION
      This function will get a view of the child lists. Output:
        :: ListName
        ::    SubListName_L1_1
        ::    SubListName_L1_2
        ::       SubListName_L2_1
        ::    SubListName_L1_3
      .EXAMPLE
      Get-SCSMListChildItems -Name "MyArea/CategoryName" -ManagementPackXML C:\MP_xmlFile.xml
      .EXAMPLE
      Get-SCSMListChildItems -ID "Enum.GUID" -ManagementPackXML C:\MP_xmlFile.xml
      .PARAMETER Name
      The name of the list you want to get information on
      .PARAMETER ID
      The ID of the list you want to get information on
      .PARAMETER ManagementPackXML
      The location where the XML File is saved
    #>
    [CmdletBinding()]
    Param
    (
        #ID
        [Parameter(Mandatory=$true,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='What is the ID of the list you wish to get child items for?',
            ParameterSetName="ID")]
        [Alias('ElementID')]
        [ValidateLength(3,250)]
        [string]$ID,
        #Name
        [Parameter(Mandatory=$true,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='What is the name of the list you wish to get child items for?',
            ParameterSetName="Name")]
        [Alias('ListName')]
        [ValidateLength(0,250)]
        [string]$Name,
        #ManagementPackXML
        [Parameter(Mandatory=$True,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='Where is the Management Pack XML Located?')]
        [Alias('XMLPath')]
        [ValidateLength(3,255)]
        [string]$ManagementPackXML
    )
    begin
    {
    }
    process
    {
        Write-Verbose "Checking the XML file exists."
        Test-Path $ManagementPackXML -ErrorAction Stop | Out-Null
        Write-Verbose "File exists."

        Write-Verbose "Opening XML File with UTF8 encoding."
        [xml]$xml = Get-Content $ManagementPackXML -Encoding UTF8
        Write-Verbose "File opened successful."

        if($ID.Length -eq 0)
        {
            Write-Verbose "If the ID is not provided, search by name to get the ID."
            $nameSearch = Get-SCSMListItem -Name $Name -ManagementPackXML $ManagementPackXML
        }

        function Get-ChildNodes($parentNode, $depth)
        {
            $depth++
            $childNodes = $listNodes | ? Parent -eq $parentNode.ID
            foreach($child in $childNodes)
            {
                "$("`t"*$depth) - $((Get-SCSMListItem -ID $child.ID -ManagementPackXML $ManagementPackXML).Name) - $((Get-SCSMListItem -ID $child.ID -ManagementPackXML $ManagementPackXML).Ordinal)"
                get-childNodes $child $depth
            }
    
        }

        foreach($result in $nameSearch)
        {
            Write-Verbose "Get all of the Elements from XMLElement EnumerationValue."
            $listNodes = $xml.GetElementsByTagName("EnumerationValue")
            foreach($node in $listNodes)
            {
                $depth = 0
                if($node.ID -eq $result.ID)
                {
                    Write-Verbose "If the Node.ID is equal to the provided (acquired) ID, then Print information and get child elements."
                    " - $((Get-SCSMListItem -ID $node.ID -ManagementPackXML $ManagementPackXML).Name) - $((Get-SCSMListItem -ID $node.ID -ManagementPackXML $ManagementPackXML).Ordinal)"
                    get-childNodes $node, $depth
                }
            }
        }
    }
    end
    {
        Remove-Variable -Name xml
    }
}

function Add-SCSMListItems
{
    <#
      .SYNOPSIS
      This function will add a new item to the XML Management Pack.
      .DESCRIPTION
      This function will add a new item to the XML Management Pack. This will include the EnumerationValue and the DisplayString. 
        XML Example:
            <EnumerationTypes ID="Enum.6a70247ad154475fb9138d6d2c52c8a3" Accessibility="Public" Parent="ServiceRequest!ServiceRequestAreaEnum.Directory" Ordinal="500" />

            <DisplayString ElementID="Enum.6a70247ad154475fb9138d6d2c52c8a3">
                <Name>Test</Name>
            </DisplayString>
      .EXAMPLE
      Add-SCSMListItems -Lists "LIST ITEM NAME" -ParentName "Group IT" -ManagementPackXMl C:\MP_XML.xml -ExportLocation C:\MP_XML_New.xml
      .EXAMPLE
      Add-SCSMListItems -Name "LIST ITEM NAME" -ParentID "Enum.GUID" -ManagementPackXMl C:\MP_XML.xml -ExportLocation C:\MP_XML_New.xml
      .PARAMETER ParentName
      The name of the Parent that will store the new list item
      .PARAMETER ParentID
      The ID of the Parent that will store the new list item
      .PARAMETER Lists
      The name of the new list items
      .PARAMETER ManagementPackXML
      The location where the Management Pack XML file is located
      .PARAMETER ExportLocation
      The location where the new XML file will be saved. It can be the same but it is not recommended to over write the file.
    #>
    [CmdletBinding()]
    Param
    (
        #Lists
        [Parameter(Mandatory=$true,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='What is the name of the list you wish to get child items for?')]
        [Alias('ListName')]
        [ValidateLength(3,250)]
        [string[]]$Lists,
        #ParentName
        [Parameter(Mandatory=$true,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='What is the name of the Parent where the Lists should be added?',
            ParameterSetName="ParentName")]
        [Alias('Parent')]
        [ValidateLength(3,250)]
        [string]$ParentName,
        #ParentID
        [Parameter(Mandatory=$true,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='What is the ID of the Parent where the Lists should be added?',
            ParameterSetName="ParentID")]
        [Alias('PID')]
        [ValidateLength(3,250)]
        [string]$ParentID,
        #ManagementPackXML
        [Parameter(Mandatory=$True,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='Where is the Management Pack XML Located?')]
        [Alias('XMLPath')]
        [ValidateLength(3,255)]
        [string]$ManagementPackXML,
        #ExportLocation
        [Parameter(Mandatory=$false,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True,
            HelpMessage='Where is the Management Pack XML Located?')]
        [Alias('exportPath')]
        [ValidateLength(3,255)]
        [string]$ExportLocation
    )
    begin
    {
        if($ExportLocation -eq $null -or $ExportLocation.Length -eq 0)
        {
            $ExportLocation = $ManagementPackXML
        }
    }
    process
    {
        Test-Path $ManagementPackXML -ErrorAction Stop | Out-Null
        [xml]$xml = Get-Content $ManagementPackXML -Encoding UTF8
        
        if($ParentID.Length -eq 0)
        {
            $nameSearch = Get-SCSMListItem -Name $ParentName -ManagementPackXML $ManagementPackXML
            if($nameSearch.Count -eq 1)
            {
                $ParentID = $nameSearch.ID
            }
            else
            {
                Write-Error -Message "Unable to create the lists as multiple parents were found." -Category InvalidResult -RecommendedAction "Find the ID of the parent you wish to add the list and try again with the ID." -CategoryActivity "MultipleResultsError" -ErrorAction Stop
            }
            
        }

        function Get-NextOrdinal($parentID)
        {
            return ($xml.ManagementPack.TypeDefinitions.EntityTypes.EnumerationTypes.EnumerationValue | ? Parent -eq $parentID | Measure-Object -Property Ordinal -Maximum).Maximum + 1
        }

        foreach($list in $Lists)
        {
            $element_EnumartionTypes = $xml.CreateElement("EnumerationValue")
            $element_EnumartionTypes.SetAttribute('ID',"Enum.$(([GUID]::NewGuid().Guid).replace("-",''))")
            $element_EnumartionTypes.SetAttribute('Accessibility',"Public")
            $element_EnumartionTypes.SetAttribute('Parent',$ParentID)
            $element_EnumartionTypes.SetAttribute('Ordinal', $(Get-NextOrdinal -parentID $ParentID))
            $xml.ManagementPack.TypeDefinitions.EntityTypes.EnumerationTypes.AppendChild($element_EnumartionTypes) | Out-Null

            $Element_DisplayName = $xml.CreateElement("DisplayString")
            $Element_DisplayName.SetAttribute('ElementID',$element_EnumartionTypes.id)
            $Element_DisplayName_Name = $xml.CreateElement("Name")
            $Element_DisplayName_Name.InnerText = $list
            $Element_DisplayName.AppendChild($Element_DisplayName_Name) | Out-Null
            $xml.ManagementPack.LanguagePacks.LanguagePack | ? ID -eq "ENU" | %{$_.DisplayStrings.AppendChild($Element_DisplayName)} | Out-Null
        }
        
        if(Test-Path (Split-Path $ExportLocation))
        {
            $xml.Save($ExportLocation)
        }
        else
        {
            Write-Error -Message "The provided export file cannot be found." -RecommendedAction "Ensure that the file path exists and try again." -Category ResourceUnavailable -ErrorAction Stop
        }
        
    }
    end
    {
        Remove-Variable -Name element_EnumartionTypes
        Remove-Variable -Name Element_DisplayName_Name
        Remove-Variable -Name Element_DisplayName
    }
}

I hope that this may help someone out there. Do let me know if you have any questions, or issues, also if you find something else like it out there because I couldn't find that many people creating tools for SCSM. 

Kind regards,
Me.

Edit 1/14/2015: 

I found a bug when working with the batch addition of lists. The problem happens because for each loop the MP will overwrite the last change. 

In order to fix this you won't be able to use the 'Export-Path' parameter, and will have to overwrite the XML for every change. 

Sunday, December 20, 2015

Get-SCSMCategories

Hello everyone!

I have been doing quite a bit of DevOps work lately and I received a request the other day that might be worth sharing.

Our SCSM Administrator needed to get an export of our current categories, and he had found a script online to do so; however, it was only hard coded to go 3 levels deep and our categories would go much deeper than that. . He asked me if I could take a look into why this was happening, and it turned out that the way that the script works is that it was only hard coded to go 3 levels deep, so I wrote a function to recursively go through the main class, and its children and print out the categories regardless of the levels that you might have.

Import-Module SMLets

function get-childLists($listObjects)
{
    $count ++
    foreach($obj in $listObjects)
    {
        $id = $obj.id
        "$("`t"*$count) - $($obj.DisplayName)" | Out-File 'C:\FILE\PATH.txt' -Append
        $children = Get-SCSMEnumeration -ComputerName $scsm | ? Parent -match $id
        if($children -ne $null)
        {
            get-childLists $children, $listName
        }
    }

}

$scsm = 'YOURSCSMSERVER'
$areas = @("ActivityAreaEnum",
"IncidentClassificationEnum",
"ServiceRequestAreaEnum")

foreach($list in $areas)
{
    $count = -1
    $enumLists = Get-SCSMEnumeration -ComputerName $scsm | ? Name -eq $list
    get-childLists $enumLists
}

Let me know what you think, and if you all find this useful then I can probably make it into an actual advance function.

Kind regards,
Me