Public/Set-SCOMMPAliases.ps1

Function Set-SCOMMPAliases {
  <#
      .Synopsis
      Will standardize all of the aliases in one or more unsealed (.xml) management pack files.
      .DESCRIPTION
      This script will inventory all of the MP reference IDs that can be found in the single file or set of .XML files specified by the InputPath. It will create a customized set of condensed alias acronyms to be standardized across all of the unsealed management packs. It will then replace the aliases in the Manifest as well as throughout the elements where the aliases are used.
      .Parameter InputPath
      This can be a directory or full path to an unsealed management pack .xml file.
      .EXAMPLE
      PS C:\> Set-SCOMMPAliases -InputPath 'C:\UnSealedMPs\'
      .EXAMPLE
      PS C:\> Set-SCOMMPAliases -InputPath 'C:\UnSealedMPs\MyCustom.Monitoring.xml'
 
      .NOTES
      Author: Tyson Paul
      Blog: https://monitoringguys.com/
      Date: 2017/12/13
      History:
      2017/12/13: First version
  #>


  Param (
    [Parameter(Mandatory=$true)]
    [ValidateNotNull()]
    [ValidateNotNullOrEmpty()]
    [ValidateScript({Test-Path -Path $_})]
    [string]$InputPath #Either a folder or full file path
  )

  ##### TESTING ######
  #$InputPath = 'C:\Test\Test\TempTest'
  ##### TESTING ######

  # This will be used to seed the reference catalog with some hardcoded values for some common aliases
  $seed =  @'
Microsoft.SystemCenter = SC
Microsoft.SystemCenter.Apm.Infrastructure = APMInf
Microsoft.SystemCenter.Apm.Infrastructure.Monitoring = APMInfMon
Microsoft.SystemCenter.Apm.Library = APMLib
Microsoft.SystemCenter.Apm.NTServices = APMNT
Microsoft.SystemCenter.Apm.Wcf = APMWcf
Microsoft.SystemCenter.Apm.Web = APMWeb
Microsoft.SystemCenter.DataWarehouse.ApmReports.Library = APMReports
Microsoft.SystemCenter.InstanceGroup.Library = SCIGL
Microsoft.SystemCenter.Internal = SCInt
Microsoft.SystemCenter.Library =SCLib
Microsoft.SystemCenter.WebApplication.Library = WebAppLib
Microsoft.SystemCenter.WebApplicationSolutions.Library = WebAppSolLib
Microsoft.SystemCenter.WebApplicationSolutions.Library.Resources.ENU = WebAppLibRes
Microsoft.SystemCenter.WebApplicationTest.External.Library = WebAppTestExtLib
Microsoft.SystemCenter.WebApplicationTest.Library = WebAppTestLib
Microsoft.Windows.InternetInformationServices.2003 = IIS2003
Microsoft.Windows.Library = Windows
Microsoft.Windows.Server.Library = WinSrvLib
System.Health.Library = Health
System.Library = System
System.NetworkManagement.Library = NetManLib
System.NetworkManagement.Monitoring = NetManMon
System.NetworkManagement.Reports = NetManRpt
System.NetworkManagement.Templates = NetManTmp
System.Performance.Library = Performance
'@

  # $seed -split "`r`n" | Sort-Object

  ##############################################################
  # Will replace common/popular namespaces with smaller, friendly acronym
  Function AcronymCommonNames {
    Param (
      [Parameter(Mandatory=$true)]
      [ValidateNotNull()]
      [ValidateNotNullOrEmpty()]
      [string]$thisString
    )
    $acronyms = ConvertFrom-StringData -StringData @"
Microsoft.SQLServer = MSQL
Microsoft.Windows.Server = MWS
Microsoft.Windows.InternetInformationServices = IIS
"@


    If ( $acronyms.ContainsKey($thisString) ) {
      $arrNames = $acronyms.GetEnumerator().Name
      $arrNames | ForEach-Object {
        #Write-Host $_.Name $_.value
        $thisString = $thisString.Replace(($_),($acronyms.$_) )
      }
    }
    Return $thisString
  }


  ##############################################################
  Function Create-Alias {
    Param(
      [Parameter(Mandatory=$true)]
      [ValidateNotNull()]
      [ValidateNotNullOrEmpty()]
      [string]$Seed,

      [Parameter(Mandatory=$true)]
      [ValidateNotNull()]
      [ValidateNotNullOrEmpty()]
      # array of existing values, to prevent duplicates
      [system.Object[]]$Existing
    )

    $array = AcronymCommonNames -thisString $Seed
    $array = $array.Split('.')
    $newAlias = $array[0].Replace('Microsoft','').Replace('Windows','Win').Replace('HewlettPackard','HP')
    For($i = 1; $i -lt $array.Count; $i++) {
      $fragment = $array[$i]
      If ( $fragment -match '\d+' ) {
        $newAlias += $fragment
      }
      ElseIf ( $fragment -match '^ApplicationMonitoring.?' ) {
        $newAlias += 'AppMon'
      }
      ElseIf ( $fragment -match '^Dashboard.?' ) {
        $newAlias += 'Dash'
      }
      ElseIf ( $fragment -match '^Discovery.?' ) {
        $newAlias += 'Disc'
      }
      ElseIf ( $fragment -match '^Library.?' ) {
        $newAlias += 'Lib'
      }
      ElseIf ( $fragment -match '^Linux.?' ) {
        $newAlias += 'Linux'
      }
      ElseIf ( $fragment -match '^Monitor.?' ) {
        $newAlias += 'Mon'
      }
      ElseIf ( $fragment -match '^NetworkManagement.?' ) {
        $newAlias += 'NetMan'
      }
      ElseIf ( $fragment -match '^Network.?' ) {
        $newAlias += 'Net'
      }
      ElseIf ( $fragment -match '^Report.?' ) {
        $newAlias += 'Rpt'
      }
      ElseIf ( $fragment -match '^Server.?' ) {
        $newAlias += 'Ser'
      }
      ElseIf ( $fragment -match '^System.?' ) {
        $newAlias += 'Sys'
      }
      ElseIf ( $fragment -match '^Template.?' ) {
        $newAlias += 'Tmp'
      }
      ElseIf ( $fragment -match '^Unix.?' ) {
        $newAlias += 'Unix'
      }
      ElseIf ( $fragment -match '^Visual.?' ) {
        $newAlias += 'Vis'
      }
      ElseIf ( $fragment -match '^Windows.?' ) {
        $newAlias += 'Win'
      }
      Else {
        # Use capitalized letters in MP name to build the acronym
        $tempchar = ''
        [char[]]$fragment | ForEach-Object {
          If ([char]::IsUpper($_) ){$tempchar+=$_ }
        }
        # $newAlias += $fragment[0]
        $newAlias += $tempchar
      }
    }
    $i = 2
    $tempAlias = $newAlias
    While ($Existing -contains ($tempAlias)) {
      $tempAlias = $newAlias +"$i"
      $i++
    }

    Return $tempAlias
  }

  ##############################################################
  # This function will replace all instances of existing aliases with the newly customized aliases.
  Function Standardize-AllReferenceAliases {
    Param(
      [Parameter(Mandatory=$true)]
      [ValidateNotNull()]
      [ValidateNotNullOrEmpty()]
      [System.Object[]]$MPPaths,

      [Parameter(Mandatory=$true)]
      [ValidateNotNull()]
      [ValidateNotNullOrEmpty()]
      [Hashtable]$Catalog
    )
    ForEach ($MPPath in $MPPaths){

      $mpxml = [xml](Get-Content $MPPath)
      $arrReferences = $mpxml.GetEnumerator().Manifest.references.reference
      [int]$i = 0
      $content = (Get-Content $MPPath)
      # Replace aliases wherever used in xml
      ForEach ($ref in ($arrReferences) ) {
        <# Aliases typically appear in two scenarios...
            1) Examples: referring to some element property:
            <Property>$MPElement[Name="Windows!Microsoft.Windows.Computer"]/PrincipalName$</Property>
            <MonitoringClass>$MPElement[Name="SCLib!Microsoft.SystemCenter.ManagedComputer"]$</MonitoringClass>
            <RelationshipClass>$MPElement[Name="SCIGL!Microsoft.SystemCenter.InstanceGroupContainsEntities"]$
        #>

        $aliasrefbang = '="'+"$($ref.Alias)"+"!"
        $newAlias = ('="'+($Catalog.($ref.ID))+'!')
        $content = ($content -Replace ([regex]::Escape($aliasrefbang)), ([regex]::Escape($newAlias)) )

        <#
            2) Examples: referring to a workflow, type, datasource, etc.:
            <DataSource ID="GroupPopulationDataSource" TypeID="SCLib!Microsoft.SystemCenter.GroupPopulator">
            <DiscoveryRelationship TypeID="SCIGL!Microsoft.SystemCenter.InstanceGroupContainsEntities" />
        #>

        $aliasrefbang = '>'+"$($ref.Alias)"+"!"
        $newAlias = ('>'+($Catalog.($ref.ID))+'!')
        $content = ($content -Replace ([regex]::Escape($aliasrefbang)), ([regex]::Escape($newAlias)) )
        #$content | Set-Content $MPPath -Force -Encoding UTF8
      }

      # Reload the XML because it was just rewritten
      #$mpxml = [xml](Get-Content $MPPath)
      $mpxml = [xml]($content)
      $arrReferences = $mpxml.GetEnumerator().Manifest.references.reference
      [int]$i = 0
      # Replace aliases in Manifest
      ForEach ($ref in ($arrReferences) ) {
        $ref.Alias = $Catalog.($ref.ID)
        $i++
      }

      Write-Host "`t$i references updated! [ " -NoNewline -b Black -f Cyan
      Write-Host $(Split-Path -Path $MPPath -Leaf) -NoNewline
      Write-Host " ]" -b Black -f Cyan
      $mpxml.Save($MPPath)
    }
  } #endFunction

  ##############################################################
  <# This function is designed to append (or trim) a unique hash value to/from the aliases
      (in the Manifest as well as elsewhere in the management pack). This is necessary in the rare cases where existing
      aliases already match my customized new condensed acronyms in the $cat (catalog). "Windows!" is a good example
      of this. I've encountered random MPs which have the alias, "Windows" already assigned to a reference. This becomes
      problematic when I attempt to change references to the 'Microsoft.Windows.Library' after which the file ends up with
      multiple identical alias references (to different sealed MPs) using the exact same alias, "Windows". This is a clever way
      to make sure that all aliases are unique (no conflicts). This function should be run twice on the catalog; Once to
      append the unique string to aliases in the catalog (after the cat has been generated, of course), then modify the files.
      Then run again to set aliases to their final, desired values, then modify the files again to their final desired values.
  #>

  Function Modify-Aliases {
    Param(
      [Hashtable]$cat,
      [Switch]$Localized
    )
    $hash = Get-StringHash $env:COMPUTERNAME
    If ($Localized){
      @($cat.Keys) | ForEach-Object {
        $Cat[$_] = $cat[$_]+$hash
      }
    }
    Else{
      @($cat.Keys) | ForEach-Object {
        $Cat[$_] = [string]$cat[$_] -Replace $hash, ""
      }
    }
    Return $cat
  }

  ##############################################################
  #http://jongurgul.com/blog/get-stringhash-get-filehash/
  Function Get-StringHash
  {

    param
    (
      [string]
      $String,

      [string]
      $HashName = "MD5"
    )
    $StringBuilder = New-Object System.Text.StringBuilder
    [System.Security.Cryptography.HashAlgorithm]::Create($HashName).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String))|ForEach-Object{
      [Void]$StringBuilder.Append($_.ToString("x2"))
    }
    $StringBuilder.ToString()
  }

  #-------------------------------------------------------------
  #################### MAIN ################################


  ##############################################################
  # Create catalog of all existing references/aliases
  # The idea here is to seed the catalog with common pack names.
  # For other MPs dynamically create aliases based on doted name structure
  ##############################################################
  $cat = ConvertFrom-StringData -StringData $seed

  If (Test-Path -Path $InputPath -PathType Container -ErrorAction SilentlyContinue) {
    $MPPaths = (Get-ChildItem $InputPath -Recurse -Include *.xml -File ).FullName | Sort-Object
    Write-Host "$($MPPaths.Count) files found..." -F Yellow -B Black
  }
  ElseIf ((Test-Path -Path $InputPath -PathType Leaf -ErrorAction SilentlyContinue) -and ($InputPath -like "*.xml") ) {
    $MPPaths = $InputPath
  }
  Else { Write-Host "Something is wrong with `$InputPath: [$($InputPath)]."; Exit}

  $refIDs = @()
  [int]$i=0
  ForEach ($MPPath in $MPPaths) {
    Write-Host $MPPath -F Green
    $mpxml = [xml](Get-Content $MPPath)
    #$mpxml.SelectNodes("//Reference") | % {
    $mpxml.ManagementPack.Manifest.References.Reference | ForEach-Object {
      $refIDs += $_.ID
      $_
      Write-Progress -Activity "Creating master catalog of known References" -Status $_.ID -PercentComplete ($i / $MPPaths.Count*100)
    }
    $i++
  }
  # Create array of all unique IDs
  $AllRefIDs = ($refIDs | Group-Object ).Name | Sort-Object
  $AllRefIDs | ForEach-Object {
    #If ID not in catalog yet, add it with customized alias
    If ( -not($cat.ContainsKey($_.ToString())) ){
      $cat.Add($_,(Create-Alias -Seed $_ -Existing $cat.Values) )
    }
  }

  # Update all aliases to unique values to prevent naming conflicts when modifying files
  $cat = Modify-Aliases -Localized:$true -cat $cat
  Write-Host "Updating references...(first pass, randomized unique values)" -b Black -f Yellow
  Standardize-AllReferenceAliases -MPPaths $MPPaths -Catalog $cat

  # Update all aliases to final, correct values, and modify files
  $cat = Modify-Aliases -Localized:$false -cat $cat
  Write-Host "Updating references...(final pass)" -b Black -f Green
  Standardize-AllReferenceAliases -MPPaths $MPPaths -Catalog $cat

  # Display the reference/alias Catalog
  $arrCat = @()
  ForEach ($key in $cat.Keys ){
    #Write-Host $key " = " $cat[$key]
    $arrCat += ("$key = $($cat[$key])" )
  }
  Return ($arrCat | Sort-Object)

}