Friday, January 15, 2016

SCOM Migration

Hello everyone! 

We are currently in the process of migrating our all of the servers from one domain to our 'main' domain and as part of this project we are migrating the servers from their current SCOM environment to ours. This is normally very simple to be done through the SCOM Management Console; however, I have found that about 25% of the servers that I have been migrating are failing either during the uninstall, or the installation of the new client. 

The failures are due to a number of things, because of permission issues, or ports not being opened, or just because the server doesn't feel like cooperating. Either way in order to make my work easier I created two functions to remotely install and uninstall the agent using Invoke-command and msiexec.exe and would like to share this scripts. 

<#
       .SYNOPSIS
              Install SCOM Agent in a remote computer using MSIExec

       .DESCRIPTION
              This function will Install the SCOM Agent on a remote computer using msiexec.exe. The function will require that you provide the name of the computer, and if different to modify the path where the SCOM Agent folder is located.

       .PARAMETER  computerName
              The computer where you wish to uninstall the SCOM Agent from.

       .PARAMETER  agentPath
              The script will use the MOMAgent.msi to install the SCOM Agent, therefor we'll need to copy the file over to the server.
              This is currently done by copying the folder where the file is located. The folder will be copied to the C drive under the folder
              name SCOMAgent and will clean it up after the installation is completed.

              You can hard code this parameter so that it will always use a default location under the 'Begin' section.

       .EXAMPLE
              PS C:\> Install-SCOMAgentMsiExec -ComputerName SERVERNAME

       .EXAMPLE
              PS C:\> Install-SCOMAgentMsiExec -ComputerName SERVERNAME -agentPath C:\agentPath\Folder
                     * Note: This is the folder where it is located, not the msi file itself.

       .INPUTS
              System.String, [System.String]

       .OUTPUTS
              System.String
#>
function Install-SCOMAgentMsiExec
{
       [CmdletBinding()]
       param (
              [Parameter(Position = 0, Mandatory = $true)]
              [System.String]$computerName,
              [Parameter(Position = 0, Mandatory = $false)]
              [System.String]$agentPath
       )
       begin
       {
              if ($agentPath.Length -eq 0 -or $agentPath -eq $null)
              {
                     $msiPath = # Default path here
              }
             
              $serverAgentPath = "\\$($computerName)\c$\SCOMAgent"
       } # endbegin
       process
       {
              if (-not (Test-Connection -ComputerName $computerName -Count 1 -Quiet)) # Testing if Computer is online.
              {
                     Write-Error -Message "$computerName cannot be pinged." -Category ResourceUnavailable -ErrorAction Stop
              }
              else
              {
                     Write-Verbose -Message "$computerName is accepting pings."
              } # endelse

              if (-not(Get-Service HealthService -ComputerName $computerName -ErrorAction SilentlyContinue)) # Ensure that the HealthService doesn't exists.
              {
                     # Install the Agent if the service is not found.
                     Write-Verbose -Message "The HealthService service is not installed. Installing."
                    
                     if (-not (Test-Path -Path $msiPath)) # Test to ensure the Agent Path exists.
                     {
                           Write-Error -Message "Unable to find the location $msiPath." -Category ObjectNotFound -ErrorAction Stop
                     } # endif
                    
                     try
                     {
                           if (Test-Path -Path $serverAgentPath) # Test if the destination folder exists.
                           {
                                  Write-Verbose -Message "The destination folder already exists. Ensure no files are missing."
                                  # If the Path exists, we'll compare the files to not have to re-write data.
                                  $sourceFiles = Get-ChildItem $msiPath
                                  $destinationFiles = Get-ChildItem $serverAgentPath
                                 
                                  if (-not (Compare-Object -ReferenceObject $sourceFiles -DifferenceObject $destinationFiles)) # Test if the folders are the same, and not missing files.
                                  {
                                         # If the folders do not match delete the current folder.
                                         Write-Verbose -Message "The folders do not match completely."
                                         Remove-Item $serverAgentPath -Recurse
                                         # Copy the correct Agent data into the local folder.
                                         Write-Verbose "Copying files from $msiPath to $serverAgentPath"
                                         Copy-Item $msiPath -Destination $serverAgentPath -Recurse -Force
                                  } # endif
                                  else
                                  {
                                         Write-Verbose -Message "The folders are identical. Proceeding."
                                  } # endelese
                           }
                           else
                           {
                                  # If the folder doesn't exist copy the Agent data into the local drive.
                                  Write-Verbose "Copying files from $msiPath to $serverAgentPath"
                                  Copy-Item $msiPath -Destination $serverAgentPath -Recurse -Force
                           }
                     } # endtry
                     catch
                     {
                           Write-Error -Message $Error[0].Exception -ErrorAction Stop
                     } # endcatch
                    
                     Write-Verbose -Message "Uninstalling the SCOM Agent."
                     Invoke-Command -ComputerName $computerName -ScriptBlock {
                           Start-Process "C:\Windows\System32\msiexec.exe" -ArgumentList "/i C:\SCOMAgent\MOMAgent.msi /qn USE_SETTINGS_FROM_AD=0 USE_MANUALLY_SPECIFIED_SETTINGS=1 MANAGEMENT_GROUP=[GROUPNAME] MANAGEMENT_SERVER_DNS=[SERVERNAME] SECURE_PORT=[PORT] AcceptEndUserLicenseAgreement=1" -Wait
                     }
                    
                     # We look in the computers eventlog to find the MsiExec log for the installation in the last 5 minutes.
                     Write-Verbose -Message "Confirming that the installation has been a success."
                     $eventLog = Get-EventLog -ComputerName $computerName -LogName Application -InstanceId 11707 -Newest 1 -Message "Product: Microsoft Monitoring Agent -- Installation operation completed successfully." -After $((Get-Date).AddMinutes(-5))
                    
                     if ($eventLog -ne $null) # Check if the EventLog was found.
                     {
                           Write-Verbose -Message "The HealthService service has been installed."
                     } # endif
                     else
                     {
                           Write-Error -Message "The installation failed." -Category NotInstalled -ErrorAction Stop
                     } # endcatch
              } # endif
              else
              {
                     Write-Error -Message "The HealthService is already installed." -Category InvalidOperation -RecommendedAction "Uninstall the agent before proceeding." -ErrorAction Stop
              } # endelse
       } # endprocess
       end
       {
              Write-Verbose -Message "Removing the Agent folder."
              Remove-Item $serverAgentPath -Recurse
       } # endEnd
} # endfunction



<#
       .SYNOPSIS
              Uninstall SCOM Agent in a remote computer using MSIExec

       .DESCRIPTION
              This function will Uninstall the SCOM Agent on a remote computer using msiexec.exe. The function will require that you provide the
              name of the computer, and if different to modify the path where the SCOM Agent folder is located.

       .PARAMETER  computerName
              The computer where you wish to uninstall the SCOM Agent from.

       .PARAMETER  agentPath
              The script will use the MOMAgent.msi to uninstall the SCOM Agent, therefor we'll need to copy the file over to the server.
              This is currently done by copying the folder where the file is located. The folder will be copied to the C drive under the folder
              name SCOMAgent and will clean it up after the uninstallation is completed.

              You can hard code this parameter so that it will always use a default location under the 'Begin' section.

       .EXAMPLE
              PS C:\> Uninstall-SCOMAGentMsiExec -ComputerName SERVERNAME

       .EXAMPLE
              PS C:\> Uninstall-SCOMAGentMsiExec -ComputerName SERVERNAME -agentPath C:\agentPath\Folder
                     * Note: This is the folder where it is located, not the msi file itself.

       .INPUTS
              System.String, [System.String]

       .OUTPUTS
              System.String
#>
function Uninstall-SCOMAgentMsiExec
{
       [CmdletBinding()]
       param (
              [Parameter(Position = 0, Mandatory = $true)]
              [System.String]$computerName,
              [Parameter(Position = 0, Mandatory = $false)]
              [System.String]$agentPath
       )
       begin
       {
              if ($agentPath.Length -eq 0 -or $agentPath -eq $null)
              {
                     $msiPath = # Default path here
              }
             
              $serverAgentPath = "\\$($computerName)\c$\SCOMAgent"
       } # endbegin
       process
       {
              if (-not (Test-Connection -ComputerName $computerName -Count 1 -Quiet)) # Testing if Computer is online.
              {
                     Write-Error -Message "$computerName cannot be pinged." -Category ResourceUnavailable -ErrorAction Stop
              } # endif
              else
              {
                     Write-Verbose -Message "$computerName is accepting pings."
              } # endelse

              if (Get-Service HealthService -ComputerName $computerName -ErrorAction Stop) # Check if the HealthService exists.
              {
                     Write-Verbose -Message "The HealthService service is installed. Proceeding with uninstallation."
                    
                     if (-not (Test-Path -Path $msiPath)) # Test to ensure the Agent Path exists.
                     {
                           Write-Error -Message "Unable to find the location $msiPath." -Category ObjectNotFound -ErrorAction Stop
                     } # endif
                    
                     try
                     {
                           if (Test-Path -Path $serverAgentPath) # Test if the destination folder exists.
                           {
                                  Write-Verbose -Message "The destination folder already exists. Ensure no files are missing."
                                  # If the Path exists, we'll compare the files to not have to re-write data.
                                  $sourceFiles = Get-ChildItem $msiPath
                                  $destinationFiles = Get-ChildItem $serverAgentPath
                                 
                                  if (-not (Compare-Object -ReferenceObject $sourceFiles -DifferenceObject $destinationFiles)) # Test if the folders are the same, and not missing files.
                                  {
                                         # If the folders do not match delete the current folder.
                                         Write-Verbose -Message "The folders do not match completely."
                                         Remove-Item $serverAgentPath -Recurse
                                         # Copy the correct Agent data into the local folder.
                                         Write-Verbose "Copying files from $msiPath to $serverAgentPath"
                                         Copy-Item $msiPath -Destination $serverAgentPath -Recurse -Force
                                  } # endif
                                  else
                                  {
                                         Write-Verbose -Message "The folders are identical. Proceeding."
                                  } # endelese
                           }
                           else
                           {
                                   # If the folder doesn't exist copy the Agent data into the local drive.
                                  Write-Verbose "Copying files from $msiPath to $serverAgentPath"
                                  Copy-Item $msiPath -Destination $serverAgentPath -Recurse -Force
                           }
                     } # endtry
                     catch
                     {
                           Write-Error -Message $Error[0].Exception -ErrorAction Stop
                     } # endcatch
                    
                     Write-Verbose -Message "Uninstalling the SCOM Agent."
                     Invoke-Command -ComputerName $computerName -ScriptBlock { Start-Process "C:\Windows\System32\msiexec.exe" -ArgumentList "/x C:\SCOMAgent\MOMAgent.msi /qb" -Wait }
                    
                     # We look in the computers eventlog to find the MsiExec log for the uninstallation in the last 5 minutes.
                     Write-Verbose -Message "Confirming that the uninstallation has been a success."
                     $eventLog = Get-EventLog -ComputerName $computerName -LogName Application -InstanceId 11724 -Newest 1 -Message "Product: Microsoft Monitoring Agent -- Removal completed successfully." -After $((Get-Date).AddMinutes(-5))
                    
                     if ($eventLog -ne $null) # Check if the EventLog was found.
                     {
                           Write-Verbose -Message "The HealthService service has been uninstalled."
                     } # endif
                     else
                     {
                           Write-Error -Message "The uninstallation failed." -Category NotInstalled -ErrorAction Stop
                     } # endcatch
              } # endif
              else
              {
                     Write-Error -Message "The HealthService is not installed." -Category InvalidOperation -ErrorAction Stop
              } # endelse
       } # endprocess
       end
       {
              Write-Verbose -Message "Removing the Agent folder."
              Remove-Item $serverAgentPath -Recurse
       } # endEnd
} # endfunction



The main thing to notice here is that the script will need to have access to the location where the msi SCOM Installation file is located, and local administrator rights to the server. 

The script will then copy the files to the local drive of the server and run the msi file. Once the installation runs it will check for a successful installation/uninstall eventlog and delete the data that it copied to the C drive. 

I hope that this might help someone out there, and please let me know if you have any questions, feedback or concerns. 

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

Friday, October 23, 2015

PowerShell 5.0

Hello everyone! 

I might be a little late with this but the other day I found out a nice little feature of the new PowerShell 5.0, and that is that not only does it come with nice color coding but it also supports Control+C, Control-X and Control-V ! A feature that was long overdue. 

Finding this new feature out made me realize that I haven't even looked into the new features, and so I did. Here are some of the features that I'm really excited about:


  1. Microsoft.PowerShell.Archive: Allows us to manage, extract and even create compressed files (zip files)
  2. PowerShell Gallery: A central repository for all PowerShell things. If it is deployed correctly and it actually is used by the community it will be a great resources, specially with the new 'PowerShellGet' Module that allows you to get stuff from the Gallery.
  3. The new -Depth parameter for the Get-ChildItems cmdlet sounds great, so far I had to use a function that I wrote to get this accomplished. It'll be nice to have it already built in.
  4. Transcript is now supported in the ISE and a new GPO can be enabled to setup global transcripts.
  5. Get-Clipboard and Set-Clipboard cmdlet sound like might come in handy too! They support images, audio files, file lists, and text.
Overall it seems that this new version has come with quite a few changes  very interesting changes. It's all very exciting. For more details be sure to visit the technet article.


Let me know what you guys think about version 5.0, and if you are exited about any particular feature, and why!

Kind regards,
Me.

Edit: It has been pointed out to me that it is not PowerShell that is now able to handle the copy, cut and paste features but the shell. Thanks to https://www.reddit.com/user/No1Asked4MyOpinion for pointing it out.