Class Viewer: An Easy Way to View Class Hierarchy and Hosting Relationships in the Operations Manager Console

Up until now there has been no easy way to get a good, visual perspective on the classes and hosting relationships that exist in your SCOM management group. I introduce ClassViewer: a script that will add a Web Page view into your Monitoring workspace so that you can see all classes in your management group as well as properties and hosting relationships.

(Original article)

Overview:

This script will generate an .html file for each known class in your management group and also a single “master’ file which contains a filter-enabled list of all the classes. The files will need to exist under a virtual directory on a web server.

Which web server?

This is up to you. I recommend using your existing SCOM Web Console server “OperationsManager” virtual directory.

How many classes are there? How many files will be created? 

This depends on your management group. All management groups are different, with different customizations and different management packs loaded. You can expect somewhere between 1000 and 2000. (My lab instance has 263 MPs and 1644 classes)

 

 

 

 

 

Installation Steps:

1) Download the .zip and extract the contents to a local folder on your web server. Usually this would be your SCOM Web Console server.

 

2) Open a PowerShell Console (ideally the Operations Manager Console). Navigate to the folder where you extracted the files.

3) Launch the script from the PowerShell Console. You will be prompted for two mandatory parameters:

OutDir and URL.

 

About the parameters: OutDir, URL

If either of these parameters are not provided at the command line, the user will be prompted to input values for each parameter. The value for OutDir should be where your web server virtual directory is located. Once this value is provided by the user, a subfolder, "classviewer", will be created automatically in this path.  It is in this subfolder where all of the individual .html files for each class description will be created. Remember, there will be upwards of 1000 individual files created inside this new subfolder.
(Note: For this parameter you can provide the path to any local folder as long as the new subfolder “classviewer” is located in your web server virtual directory where it can be accessed via HTTP/s. )


A good path to use for this parameter is the physical path to your "OperationsManager" virtual directory on your SCOM Web Console server. Most often this virtual directory is located at these typical locations depending on your version of SCOM:

C:\Program Files\Microsoft System Center 2016\Operations Manager\WebConsole\WebHost
C:\Program Files\Microsoft System Center 2012\Operations Manager\WebConsole\WebHost
C:\Program Files\Microsoft System Center 2012 R2\Operations Manager\WebConsole\WebHost

Typically the folder you choose for the OutDir parameter is made available by your web server through the value specified for the URL parameter.

Example: the physical (OutDir) path might be the following:
C:\Program Files\Microsoft System Center 2016\Operations Manager\WebConsole\WebHost

The subfolder will get created automatically by the script:
C:\Program Files\Microsoft System Center 2016\Operations Manager\WebConsole\WebHost\classviewer

This physical path would be accessible via this example URL for your Web Console server:
HTTP://yourMGMTserver/OperationsManager/classviewer

It is this OperationsManger virtual directory URL that you would specify for the URL parameter:
HTTP://yourMGMTserver/OperationsManager
Do not include "classviewer". This will get added automatically by the script.

Ultimately it is THIS path that will be added automatically to the View in the provided unsealed MP (ClassViewer.xml):
HTTP://yourMGMTserver/OperationsManager/classviewer/MasterClassFile.html

In this example, it is this URL above which must be accessible from the Console server (where the Console is launched; this could be your workstation or any other server where the Console is installed).

Note: If you have more than one Web Console server in your management group (example: in a load balanced scenario), you will need to perform this installation on any servers that may receive traffic at the designated URL. Each web server will need a set of the .html files.

After you provide the OutDir and URL values the script should proceed to generate the output files at the OutDir path that was previously provided.

 

In my lab I used this value for OutDir:

C:\Program Files\Microsoft System Center 2012 R2\Operations Manager\WebConsole\WebHost

I used this value for URL:

HTTP://ms01/OperationsManager

This is the value that got created inside the ClassViewer.xml file. As you will notice, it is encoded. 

HTTP%3a%2f%2fms01%2fOperationsManager%2fclassviewer%2fMasterClassFile.html

 It results to this (unencoded): HTTP://ms01/OperationsManager/classviewer/MasterClassFile.html

 

You will be prompted to automatically import the ClassViewer.xml unsealed management pack. This pack contains the preconfigured Web Page View. (SCOM Administrator rights will be required. )

 

Success!

 

Comments? Suggestions? Let’s Have’em.

Thanks to Jon Almquist for the idea.

 

 

PowerShell
Edit|Remove
<# 
.Synopsis 
   This script will create a Web Page view in your SCOM Console for viewing all of the existing class hierarchy.  
.DESCRIPTION 
    This script will create a Web Page view in your SCOM Console for viewing all of the existing class hierarchy. The view will include a filter-enabled list 
    of all classes as well as related management pack name and version. The list conains links to individual class definitions with property info in addition 
    to hosting class hierarchy.  
 
    About the parameters: OutDir, URL 
    If either of these parameters are not provided, the user will be prompted to input values. 
    The value for OutDir should be where your web server virtual directory is located. A subfolder, "classviewer", will be created  
    automatically in this path.  For this parameter you can provide the path to any local folder as long as you put the new subfolder (classviewer) 
    into your web server virtual directory where it can be accessed via HTTP/s.   
 
    All of the individual .html files for each class description will be created in this new subfolder.  
    A good path to use for this parameter is the physical path to your "OperationsManager" virtual directory on your  
    SCOM Web Console server. Most often this virtual directory is located at these typical locations depending on your version of SCOM: 
 
    C:\Program Files\Microsoft System Center 2016\Operations Manager\WebConsole\WebHost 
    C:\Program Files\Microsoft System Center 2012 R2\Operations Manager\WebConsole\WebHost 
    C:\Program Files\Microsoft System Center 2012\Operations Manager\WebConsole\WebHost 
 
    Typically the path you choose for this parameter is made available by your web server through the value specified for the URL parameter.  
    Example: the physical path might be the following:  
    C:\Program Files\Microsoft System Center 2016\Operations Manager\WebConsole\WebHost 
 
    Example: 
    The subfolder will get created:  
    C:\Program Files\Microsoft System Center 2016\Operations Manager\WebConsole\WebHost\classviewer 
 
    The physical path is accessed via this example URL for your Web Console server: 
    HTTP://yourMGMTserver/OperationsManager/classviewer 
 
    It is this OperationsManger root directory URL that you would specify for the URL parameter: 
    HTTP://yourMGMTserver/OperationsManager 
    Do not include "classviewer". This will get added automatically: 
 
 
    Ultimately it is THIS path that will be added automatically to the view in the provided unsealed MP (ClassViewer.xml): 
    HTTP://yourMGMTserver/OperationsManager/classviewer/MasterClassFile.html 
 
    Note: If you have more than one Web Console server in your management group (example: in a load balanced scenario), you will need to perform this installation on any servers 
    that may receive traffic at the designated URL. 
.EXAMPLE 
   PS C:\> .\ClassViewer_Install.ps1 -MGMTServer MS03 -Credential $CRED -OutDir 'C:\Program Files\Microsoft System Center 2012 R2\Operations Manager\WebConsole\WebHost' -URL 'HTTP://ms01/OperationsManager' 
.EXAMPLE 
   PS C:\> .\ClassViewer_Install.ps1 -OutDir "C:\inetpub\wwwroot" -URL 'http://Mywebserver.contos.com' 
.PARAMETER OutDir 
    Where to create the output files. Ideally this would be a virtual directory on your IIS server, perhaps the OperationsManager path.  
    This path needs to be accessible via your IIS/web service.  
    Or this could even be a local folder.  
    Example: C:\Program Files\Microsoft System Center 2012 R2\Operations Manager\WebConsole\WebHost 
.PARAMETER URL 
    URL of the virtual directory specified in the OutDir parameter 
.NOTES 
    Author: Tyson Paul ( https://blogs.msdn.microsoft.com/tysonpaul ) 
    Date: 2017.11.10 
    Originally inspired by: Jonathan Almquist. http://blog.scomskills.com/ 
#> 
Param( 
    [CmdletBinding(DefaultParameterSetName='Parameter Set 1',  
                  SupportsShouldProcess=$true,  
                  PositionalBinding=$false, 
                  HelpUri = 'https://blogs.msdn.microsoft.com/tysonpaul/2018/05/02/class-viewer-an-easy-way-to-view-class-hierarchy-and-hosting-relationships-in-the-operations-manager-console/')] 
 
    [string]$OutDir, 
 
    [string]$URL, 
 
    [string]$MGMTServer, 
 
    [System.Management.Automation.PSCredential]$Credential 
) 
 
# Warn the user if file/directory access will likely be a problem 
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) 
If (-NOT($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) ) { 
    Write-Host "`nNote: You are NOT running as Administrator. You will most likely be unable to generate the .html files in the output director [OutDir].`n" -F Red -B Yellow 
    Read-Host -Prompt "Press any key to continue..." 
} 
 
#Should include all opening elements including CSS 
Function Setup-HTMLDoc { 
 
$GLOBAL:arrTable = @" 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
    <head> 
        <title>$ClassName</title> 
    </head> 
    <body>  
 
$Global:CSS 
 
<p><a href=".\$($ClassCatalogFileName)">Master Class List</a></p> 
 
"@ 
} 
#-------------------------------------- 
 
# Should include closing tags for hmtl doc. 
Function Close-HTMLDoc { 
 
$GLOBAL:arrTable += @" 
<p><a href=".\$($ClassCatalogFileName)">Master Class List</a></p> 
<p> </p> 
<p><a href="$($blog)">Help</a></p> 
 </body></html> 
"@ 
} 
#-------------------------------------- 
 
#Should include Table opening elements 
Function Setup-Table { 
Param ( 
    #[string]$HeaderTxt1, 
    #[string]$HeaderTxt2 
    [string]$th 
) 
#<th class="headerstyle" colspan="2">$HeaderTxt1<br />($HeaderTxt2)</th> 
$GLOBAL:arrTable += @" 
 
<div class="tg-wrap"> 
    <table class="tg"> 
        <tr> 
            $th 
        </tr> 
 
        <tr> 
            <td class="columnNameStyle">Class</td> 
            <td class="columnNameStyle">Properties</td> 
        </tr> 
"@ 
} 
#-------------------------------------- 
 
# This simply closes the table tag in the HTML chunk 
Function Close-Table { 
 
$GLOBAL:arrTable += @" 
</table></div> 
<p> </p> 
 
"@ 
} 
#-------------------------------------- 
 
Function Add-TableRow{ 
Param ( 
    [string]$col1, 
    [string]$col1style='defaultstyle', 
    [string]$col2, 
    [string]$col2style='defaultstyle', 
    [switch]$Key=$false 
) 
 
    switch ($Key) { 
        $true { 
        #Key style is hardcoded 
$GLOBAL:arrTable += @" 
  <tr> 
    <td class="keystyle">KEY</td> 
    <td class="keypropertystyle">$col2</td> 
  </tr> 
 
"@ 
        }#end $true 
     
        $false { 
$GLOBAL:arrTable += @" 
  <tr> 
    <td class="$col1style">$col1</td> 
    <td class="$col2style">$col2</td> 
  </tr> 
 
"@ 
        }#end $false 
 
    }#end switch 
} 
#-------------------------------------- 
 
# This simply constructs a local file link for each class name 
Function Make-Link { 
Param ( 
    [string]$href# classname 
    [string]$display # probably also classname 
) 
    $TmpOutFileName = "$($href).html" 
    $link = "<a href=`".\$($TmpOutFileName)`">$display</a>" 
    Return $link 
} 
#-------------------------------------- 
 
<# 
This recursive function does the bulk of the work for this solution. It will dig through all classes and base classes to identify the  
hierarchy and properties. It will build a table in HTML consisting of classes and properties.  
#> 
Function Dig-Class { 
Param ( 
    [Microsoft.EnterpriseManagement.Configuration.ManagementPackClass]$Class 
) 
 
    Add-TableRow -col1 "$(Make-Link -href $Class.Name -display $Class.Name )<br />($($Class.DisplayName))" -col1style 'classnamestyle' 
    $properties = $Class.getProperties() | Select-Object name 
        ForEach ($property in ($properties | Sort-Object -Property Name) ) { 
            If ( ($Class.PropertyCollection | Where {$_.Name.ToString() -eq $property.Name}).Key ){ 
                Add-TableRow -col2 $property.Name -Key $true 
            } 
            Else{ 
                Add-TableRow -col2 $property.Name -col2style 'propertystyle' 
            } 
        } 
    Try{ 
        $baseID = $Class.Base.Id.ToString() 
        $BaseClass = Get-SCOMClass -ID $baseID 
    }Catch { 
        $BaseClass = $NULL 
    } 
    If ($BaseClass -ne $NULL){ 
        Dig-Class -Class $BaseClass 
    } 
     
} 
#-------------------------------------- 
 
 
<# 
This will build a table in HTML consisting of classes and properties and write out to an individual file, one file per class. 
#> 
Function Write-ClassFile { 
param( 
    [string]$ClassName 
) 
 
 
    $GLOBAL:arrTable = @() 
    Setup-HTMLDoc 
 
    Try{ 
        New-Item -Path $outDir -ItemType Directory -ErrorAction SilentlyContinue 
    }catch{ 
        Write-Error "Unable to create temp directory at: [$($outDir)]. Verify permissions or run as administrator. " 
    } 
 
    $outFileName = "$($ClassName).html" 
    $outFilePath = (Join-Path $outDir $outFileName) 
    # 
    $Class = Get-SCOMClass -Name $ClassName 
    If (-NOT $Class) { 
        Write-Host "`n`t`tClass Name not found: $ClassName" -ForegroundColor Yellow 
        Return 
    } 
    $objClass = New-Object PSCustomObject 
    $objClass | Add-Member -Name Name -Value $class.Name -MemberType NoteProperty 
    $objClass | Add-Member -Name DisplayName -Value $class.DisplayName -MemberType NoteProperty 
    $objClass | Add-Member -Name IsAbstract -Value $class.Abstract.ToString() -MemberType NoteProperty 
    $objClass | Add-Member -Name 'MP Name' -Value $class.ManagementPackName -MemberType NoteProperty 
    $MP = Get-SCOMManagementPack -Name $class.ManagementPackName 
    $objClass | Add-Member -Name 'MP Version' -Value $MP.Version -MemberType NoteProperty 
    $objClass | Add-Member -Name 'MP DisplayName' -Value $MP.DisplayName -MemberType NoteProperty 
    $objClass | Add-Member -Name 'MP IsSealed' -Value $MP.Sealed.ToString() -MemberType NoteProperty 
    $Global:hashMain[$class.Name] = $objClass 
  
    $th = @" 
<th  colspan="2"> 
    <a class="headerstyle">$($class.Name)</a><br /> 
    ($($class.DisplayName)) 
</th> 
"@ 
    Setup-Table -th $th 
    Dig-Class -Class $Class  
     
    Close-Table 
     
    If ($Class.Hosted -eq "True") { 
$th = @" 
<th  colspan="2"> 
    <a class="headerstyleHOST">HOST CLASS for: </a><br /> 
    <a class="headerstyle">$($class.Name)</a><br /> 
    ($($class.DisplayName)) 
</th> 
"@ 
        Setup-Table -th $th 
        Try { 
            $HostClass = $Class.FindHostClass() 
            Dig-Class -Class $HostClass 
        } Catch { 
            $HostClass = $NULL 
        } 
        Close-Table 
    } 
    Close-HTMLDoc 
    Set-Content -Value $GLOBAL:arrTable -Path $outFilePath -Force -Encoding UTF8 
 
    Return $outFilePath 
} #end Function 
 
###################################################################### 
 
# The Master doc is the primary .html document of the WebPage view. It contains a filter-able list of all existing classes.  
Function Build-MasterClassDoc  { 
Param ( 
    [string]$outDir, 
    [string]$ClassCatalogFileName 
) 
 
    $arrList=@() 
    $arrList += @" 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
    <head> 
        <title>Class Catalog</title> 
    </head> 
    <body>  
 
$Global:CSS 
 
<script> 
function myFunction() { 
  // Declare variables  
  var input, filter, table, tr, td, i; 
  input = document.getElementById("myInput"); 
  filter = input.value.toUpperCase(); 
  table = document.getElementById("myTable"); 
  tr = table.getElementsByTagName("tr"); 
 
  // Loop through all table rows, and hide those who don't match the search query 
  for (i = 0; i < tr.length; i++) { 
    td = tr[i].getElementsByTagName("td")[1]; 
    if (td) { 
      if (td.innerHTML.toUpperCase().indexOf(filter) > -1) { 
        tr[i].style.display = ""; 
      } else { 
        tr[i].style.display = "none"; 
      } 
    }  
  } 
} 
</script> 
 
 
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search for names.."> 
 
<div class="tg-wrap"> 
    <table id="myTable" class="tg"> 
      <tr> 
        <th class="headerstyle" >#</th> 
        <th class="headerstyle">Class Name</th> 
        <th class="headerstyle">DisplayName</th> 
        <th class="headerstyle">IsAbstract</th> 
        <th class="headerstyle">MP Name</th> 
        <th class="headerstyle">MP Version</th> 
        <th class="headerstyle">MP DisplayName</th> 
        <th class="headerstyle">MP IsSealed</th> 
 
      </tr> 
 
"@ 
 
    [int]$index = 1 
    $classFileNames = Get-ChildItem -Path $outDir -Include *.html -Exclude $ClassCatalogFileName -Recurse -File  | Select-Object Name | Sort-Object -Property Name 
    $classFileNames | ForEach { 
    $tmpName = $_.Name.Replace('.html','') 
    $arrList += @" 
  <tr> 
    <td class="defaultstyle">$index</td> 
    <td class="defaultstyle"><a href=".\$($_.Name)">$($tmpName)</a></td> 
    <td class="defaultstyle">$($Global:hashMain[$tmpName].DisplayName)</td> 
    <td class="defaultstyle">$($Global:hashMain[$tmpName].IsAbstract)</td> 
    <td class="defaultstyle">$($Global:hashMain[$tmpName].'MP Name')</td> 
    <td class="defaultstyle">$($Global:hashMain[$tmpName].'MP Version')</td> 
    <td class="defaultstyle">$($Global:hashMain[$tmpName].'MP DisplayName')</td> 
    <td class="defaultstyle">$($Global:hashMain[$tmpName].'MP IsSealed')</td> 
  </tr> 
 
"@ 
 
    $index++ 
    } 
    $arrList += @" 
</table></div> 
</body></html> 
"@ 
 
    Set-Content -Value $arrList -Path (Join-Path -Path $outDir -ChildPath $ClassCatalogFileName-Encoding utf8 -Force 
} 
###################################################################### 
 
<# 
This will update the <Url> field (in the provided unsealed managment pack) with the correct url for the WebPage view. 
#> 
Function Update-MP { 
Param( 
    [string]$thisString, 
    [string]$thatString, 
    [string]$FilePath 
 
) 
 
    (Get-Content $FilePath-replace '<Url>.*<\/Url>',$thatString | Set-Content $FilePath -Encoding UTF8 
} 
###################################################################### 
 
# This is the CSS definition that will be used in the .html files 
$Global:CSS = @" 
<style type="text/css"> 
 
a { text-decoration: none; } 
a:link, a:visited { 
    color: blue; 
} 
a:hover { 
    color: red; 
} 
 
.tg  {border-collapse:collapse;border-spacing:0;} 
.tg td{font-family:Arial, sans-serif;font-size:14px;padding:1px 6px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;} 
.tg th{font-family:Arial, sans-serif;font-size:14px;font-weight:normal;padding:1px 6px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal;border-color:black;} 
.tg .defaultstyle{border-color:inherit;vertical-align:center;text-align:left} 
.tg .headerstyle{font-weight:bold;font-size:100%;border-color:inherit;vertical-align:center} 
.tg .headerstyleHOST{color:#f77802;font-weight:bold;font-size:100%;border-color:inherit;vertical-align:center} 
.tg .columnNameStyle{border-color:inherit;vertical-align:center;text-align:center} 
 
 
.tg .classnamestyle{font-weight:bold;border-color:inherit;vertical-align:center} 
.tg .keystyle{font-weight:bold;color:#fe0000;border-color:inherit;text-align:right;vertical-align:center} 
.tg .keypropertystyle{font-weight:bold;color:#fe0000;border-color:inherit;vertical-align:center} 
.tg .propertystyle{color:#22771a;border-color:inherit;vertical-align:center;horizontal-align:center} 
propertystyle 
@media screen and (max-width: 767px)  
 
{ 
    .tg {width: auto !important;} 
    .tg col {width: auto !important;} 
    .tg-wrap {overflow-x: auto;-webkit-overflow-scrolling: touch;} 
} 
 
#myInput { 
    background-image: url('./CSS/searchicon.png'); /* Add a search icon to input */ 
    background-position: 10px 12px; /* Position the search icon */ 
    background-repeat: no-repeat; /* Do not repeat the icon image */ 
    width: 100%; /* Full-width */ 
    font-size: 16px; /* Increase font-size */ 
    padding: 12px 20px 12px 40px; /* Add some padding */ 
    border: 1px solid #ddd; /* Add a grey border */ 
    margin-bottom: 6px; /* Add some space below the input */ 
} 
 
#myTable tr { 
    /* Add a bottom border to all table rows */ 
    border-bottom: 1px solid #ddd;  
} 
 
#myTable tr.header, #myTable tr:hover { 
    /* Add a grey background color to the table header and on hover */ 
    background-color: #f1f1f1; 
} 
 
</style> 
"@ 
 
[System.Byte[]]$SearchIcon = @(137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,21,0,0,0,21,8,6,0,0,0,169,23,165,150,0,0,0,1,115,82,71,66,0,174,206,28,233,0,0,0,4,103,65,77,65,0,0,177,143,11,252,97,5,0,0,0,9,112,72,89,115,0,0,18,116,0,0,18,116,1,222,102,31,120,0,0,0,2,98,75,71,68,0,255,135,143,204,191,0,0,0,9,118,112,65,103,0,0,1,42,0,0,1,41,0,80,22,101,49,0,0,0,37,116,69,88,116,100,97,116,101,58,99,114,101,97,116,101,0,50,48,49,51,45,48,52,45,49,48,84,48,54,58,53,57,58,48,55,45,48,55,58,48,48,142,65,137,81,0,0,0,37,116,69,88,116,100,97,116,101,58,109,111,100,105,102,121,0,50,48,49,51,45,48,52,45,49,48,84,48,54,58,53,57,58,48,55,45,48,55,58,48,48,255,28,49,237,0,0,0,25,116,69,88,116,83,111,102,116,119,97,114,101,0,119,119,119,46,105,110,107,115,99,97,112,101,46,111,114,103,155,238,60,26,0,0,0,17,116,69,88,116,84,105,116,108,101,0,115,101,97,114,99,104,45,105,99,111,110,194,131,236,125,0,0,2,42,73,68,65,84,56,79,165,148,73,171,234,64,16,133,43,237,172,32,142,224,74,17,87,46,149,44,4,17,197,127,237,74,112,4,5,193,165,162,162,91,39,156,16,231,251,238,41,210,33,209,168,240,238,7,33,73,119,245,169,234,170,234,86,126,126,33,3,247,251,157,166,211,41,173,86,43,58,30,143,60,230,118,187,41,24,12,82,34,145,224,239,111,152,68,7,131,1,141,199,99,114,58,157,36,132,32,69,81,120,28,38,143,199,131,174,215,43,197,98,49,202,100,50,60,254,14,93,180,211,233,208,102,179,33,135,195,193,19,136,24,66,0,226,118,187,157,191,49,14,155,98,177,200,255,86,176,40,34,156,205,102,28,161,20,75,165,82,20,14,135,57,98,56,27,141,70,60,7,241,219,237,70,129,64,128,84,85,213,100,204,40,191,6,63,149,74,133,60,30,15,139,33,103,249,124,94,155,54,211,235,245,104,189,94,179,240,233,116,162,92,46,199,226,207,8,20,5,17,2,136,190,19,4,217,108,150,109,97,135,20,32,255,86,8,84,25,91,196,214,176,229,111,164,211,105,182,197,154,229,114,169,141,154,17,104,27,20,2,222,35,145,136,54,252,158,80,40,196,182,88,35,187,226,25,161,21,159,145,45,244,9,68,104,196,82,84,54,51,4,183,219,45,127,127,2,54,50,74,32,91,205,136,192,73,129,55,155,205,198,109,243,141,225,112,200,66,16,181,170,60,16,241,120,156,46,151,11,111,11,253,215,239,247,181,169,87,224,20,61,43,11,155,76,38,181,25,51,194,235,245,82,52,26,101,65,68,48,159,207,169,217,108,114,63,74,118,187,29,117,187,93,154,76,38,220,82,136,18,15,214,89,161,31,211,106,181,170,159,24,164,3,78,100,17,16,25,210,131,7,230,50,167,216,97,185,92,214,143,182,196,116,161,180,219,109,46,4,162,121,238,4,152,193,41,222,16,193,60,156,226,146,41,149,74,228,114,185,52,203,39,81,176,88,44,248,164,32,119,82,24,139,253,126,63,31,14,116,11,156,227,45,35,62,159,207,124,193,224,168,131,23,81,35,48,6,198,40,0,118,211,106,181,94,132,11,133,2,249,124,190,207,162,159,216,239,247,212,104,52,76,194,184,100,16,241,127,139,130,195,225,64,245,122,93,23,70,154,80,232,63,137,2,220,29,181,90,77,191,233,208,41,127,22,5,200,39,250,24,157,160,170,42,253,3,11,167,101,180,126,138,179,206,0,0,0,0,73,69,78,68,174,66,96,130) #-replace "`r","" 
 
 
Import-Module OperationsManager 
$Connection = @{} 
If ($MGMTServer) { $Connection['ComputerName'] = $MGMTServer } 
If ($Credential) { $Connection['Credential'] = $Credential } 
Write-Host "Attempting to connect to mgmt server: $MGMTServer ..." 
$mg = New-SCOMManagementGroupConnection @Connection  -PassThru 
If (-NOT([bool]$mg)) { 
    Write-Error "Unable to connect to mgmt group. " 
    Return 
} 
Else { 
    Write-Host "Successfully connected to mgmt group." -F Green 
    $mg 
} 
 
If (-NOT($OutDir)) { 
    $notice = @" 
 
 
    The value for OutDir should be where your web server virtual directory is located. A subfolder, "classviewer", will be created  
    automatically in this path.  For this parameter you can provide the path to any local folder as long as you put the new subfolder (classviewer) 
    into your web server virtual directory where it can be accessed via HTTP/s.   
 
    All of the individual .html files for each class description will be created in this new subfolder.  
    A good path to use for this parameter is the physical path to your "OperationsManager" virtual directory on your  
    SCOM Web Console server. Most often this virtual directory is located at these typical locations depending on your version of SCOM: 
 
    C:\Program Files\Microsoft System Center 2016\Operations Manager\WebConsole\WebHost 
    C:\Program Files\Microsoft System Center 2012 R2\Operations Manager\WebConsole\WebHost 
    C:\Program Files\Microsoft System Center 2012\Operations Manager\WebConsole\WebHost 
 
    Typically the path you choose for this parameter is made available by your web server through the value specified for the URL parameter.  
    Example: the physical path might be the following:  
    C:\Program Files\Microsoft System Center 2016\Operations Manager\WebConsole\WebHost 
 
    Example: 
    The subfolder will get created:  
    C:\Program Files\Microsoft System Center 2016\Operations Manager\WebConsole\WebHost\classviewer 
 
    The physical path is accessed via this example URL for your Web Console server: 
    HTTP://yourMGMTserver/OperationsManager/classviewer 
 
    It is this OperationsManger root directory URL that you would specify for the URL parameter: 
    HTTP://yourMGMTserver/OperationsManager 
    Do not include "classviewer". This will get added automatically: 
 
 
    Ultimately it is THIS path that will be added automatically to the view in the provided unsealed MP (ClassViewer.xml): 
    HTTP://yourMGMTserver/OperationsManager/classviewer/MasterClassFile.html 
 
    Note: If you have more than one Web Console server in your management group (example: in a load balanced scenario), you will need to perform this installation on any servers 
    that may receive traffic at the designated URL. 
 
 
"@ 
    Write-Host $notice -ForegroundColor Cyan 
 
    Write-Host "Input value for OutDir. This is where your new subfolder will get created." -ForegroundColor Green 
    $OutDir = Read-Host -Prompt "OutDir" 
    $OutDir = $OutDir.Replace('classviewer','') 
} 
 
If (-NOT($URL)) { 
    Write-Host "Input value for URL. This is the URL which translates to the physical path above [OutDir]." -ForegroundColor Green 
    $URL = Read-Host -Prompt "URL" 
    $URL = $URL.Replace('classviewer','') 
} 
 
$blog = 'https://blogs.msdn.microsoft.com/tysonpaul/' 
$Global:hashMain = @{} 
$subFolder = "classviewer" 
$OutDir = Join-Path $OutDir $subFolder 
Get-ChildItem -Path $OutDir\*.html | Remove-Item -Force -ErrorAction SilentlyContinue 
$ClassCatalogFileName = 'MasterClassFile.html' 
$SearchIconFileName = 'searchicon.png' 
# Encode the URL, this is required in the unsealed MP Web Page view element 
[Reflection.Assembly]::LoadWithPartialName("System.Web"| Out-Null 
$newURL = [System.Web.HttpUtility]::UrlEncode("$URL/$subFolder/$ClassCatalogFileName")  
$PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition 
$MPName = 'ClassViewer.xml' 
$MPPath = (Join-Path $PSScriptRoot $MPName) 
 
If (-NOT(Test-Path -Path $MPPath -PathType Leaf )) { 
    Write-Error "File not found! Unsealed management pack [ $MPName ] must exist in same directory as this script." 
    Return 
} 
 
Update-MP -thisString '<Url>.*<\/Url>' -thatString "<Url>$newURL</Url>" -FilePath $MPPath 
 
# This is the cute, little 'search' icon that appears on the master class list search field 
$SearchIconPath = Join-Path $OutDir "CSS\$($SearchIconFileName)" 
If (-NOT(Test-Path -Path ($SearchIconPath-PathType Leaf )) { 
    New-Item -Path (Join-Path $OutDir "CSS"-ItemType Container -Force -ErrorAction SilentlyContinue | Out-Null 
    $SearchIcon | Set-Content -Path $SearchIconPath -Encoding BYTE 
} 
 
$Classes = (Get-SCOMClass) 
[int]$i=1 
ForEach ($ClassName in ($Classes.Name | Sort-Object)) { 
# For Testing 
#ForEach ($ClassName in ((Get-SCOMClass).Name) | Select -First 10 ) { 
    Write-Progress -Activity "Generating Class Definition Files" -Status "Documenting all classes... $("{0:p}" -F ($i/$classes.Count) )" -PercentComplete (($i/$classes.Count) *100) 
    $outFilePath = Write-ClassFile -ClassName $ClassName 
    Write-Host $outFilePath -ForegroundColor Green -BackgroundColor Black 
    $i++ 
} 
 
Build-MasterClassDoc -outDir $outDir -ClassCatalogFileName $ClassCatalogFileName 
 
Write-Host "`n`nAll you have to do is import the management pack to add the new View..." -ForegroundColor Yellow 
Write-Host "Import management pack [ " -NoNewline 
Write-Host  "$MPName" -NoNewline -ForegroundColor Green 
Write-Host " ] now? " 
 
While ($choice -notmatch '[y]|[n]'){ 
    $choice = Read-Host "Y/N?" 
} 
 
Switch ($choice){ 
    'y' { 
        Import-SCOMManagementPack -ManagementPack $MPPath -Verbose 
        If ($?){ Write-Host "Success! Go have a look!" } 
    } 
    'n' { 
        Write-Host "Management pack will not be imported. You must import manually." 
        Write-Host "File Location: $MPPath" 
    } 
} 
 
Write-Host "`n`nPress any key to exit..." 
$anykey = Read-Host -Prompt "Exit?"