Best Practices Analyzer(s) for a Server

In my day job as an IT consultant I do work outside of Exchange and end on projects that touch Active Directory, Identity Management, Office 365 and more. A lot of these projects require some sort of health check or simply a quick review of an environment before proceeding with any changes. The thought process is that if we can eliminate any issues at the beginning of a project, less issues will occur to cause problems during the project. As a part of these health checks, we can sometimes utilize built-in Best Practices Analyzers for various components on a server.

Why Is This Important?

Exchange really needs a healthy base to stand on. I once worked for a company where there constant infrastructure issues – storage going offline, Active Directory on the fritz, DNS issues, networking issues…. you name it. Exchange reacted badly to these problems. My boss would hound me that Exchange was ‘down again’. Well, of course its down if users can’t get to it or if storage is not available and databases are not mounted or networking is down and emails will not route. So having healthy infrastructure servers (such as Domain Controllers) is very important. Use a BPA to check these servers out will help determine the healthiness of the systems Exchange relies on.

What BPA’s Exist?

So how do we find these? And more importantly, how do we use PowerShell in order to use them? First, we can fire up either a Windows 2012 R2 or Windows 2016 server to see what cmdlets are available in Powershell:

Get-Command *BPA*


Let’s start out with the obvious which is ‘Get-BPAModel’. This cmdlet will enable us to display the BPA’s that are available. An initial run of the cmdlet provides a block of info for each BPA:


However, I like a more consolidate list so I understand what BPA’s I have at my disposal:

Get-BPAModel |ft ID,Name,Version,Modeltype -Auto

On a Windows 2012 R2 Domain Controller you might see something like this:



Now that we have a list of BPA’s, what can we do with it?

Running One BPA

Let’s take one available BPA, the DirectoryServices BPA. In order to run a BPA, review the PowerShell cmdlets from the top of the article. The best cmdlets appears to be ‘Invoke-BPAModel’. I chose that because it has the ‘BPAModel’ portion as well as a verb that seems to imply running or executing – ‘Invoke’. Reviewing TechNet documentation on the cmdlets HERE we see that this is the cmdlets we need to run BPA’s on a server. Sample usage of the cmdlets:

Invoke-BPAModel -BestPracticesModelId <Specified Model Id>

or for all BPAs

$BPAModels = (Get-BPAModel).Name
Foreach ($BPAModel in $BPAModels) {
      Invoke-BPAModel -BestPracticesModelId $BPAModel
}

Both of these methods will cause errors to be generated. The reason is that not all BPAModels reported are installed because the Feature or Role associated with it may not be installed. Thus you will receive errors like so:


It looks like we need some sort of filter to find which BPAs are valid for a given server. In Windows, BPAs are tied to Windows Features and in order to discover what BPAs are available, we need to find what features are installed with a valid BPA Model.

Get-WindowsFeature |ft name, BestPracticesModelId -auto

This will produce a table like this:


Notice that not all features have BPAs associated with them. We can limit this list down a bit by filtering for the BestPracticesModelID that matches “Microsoft*”:

(Get-WindowsFeature | where {$_.BestPracticesModelId -like "microsoft*"}).BestPracticesModelId

This produces a nice consolidated list:


Unfortunately this does not mean that a BPA is available because we don’t know which feature is installed. However, we can use this as a base to find the installed feature and thus an available BPA:

$BPA = (Get-WindowsFeature | where {$_.BestPracticesModelId -like "microsoft*"}).BestPracticesModelId
Foreach ($Line in $BPA) {
      $Installed = (Get-WindowsFeature | where {$_.BestPracticesModelID -eq $Line}).Installed
      if ($Installed) {
            Write-Host "The $Line BPA is available"
      }
}

This provides some visual feedback for which BPA Models are available:


Now that we have a consolidated list of BPAs, we can now run the Invoke-BPAModel cmdlets

$BPA = (Get-WindowsFeature | where {$_.BestPracticesModelId -like "microsoft*"}).BestPracticesModelId
Foreach ($Line in $BPA) {
      $Installed = (Get-WindowsFeature | where {$_.BestPracticesModelID -eq $Line}).Installed
      if ($Installed) {
            Invoke-BPAModel -BestPracticesModelId $Line
      }
}

The output from this leaves much to be desired as we don’t see any actual results.


So how do we see the results? How can we find out what the BPA has found on this server?

First, realize that we’ve invoked the BPA. So the process has run (we can see that the scan shows ‘success’ is True. In order to see the results generated, we need a second cmdlets to do so Get-BPAResult. If we check the Get-Help Get-BPAResult -Full you will see one way to use the cmdlets:


Using just the base options for a single BPA report:

Get-BPAResult -ModelID 'Microsoft/Windows/WebServer'

This provides results in bunches:


These results are hard to use or really look at it. So what can we do? First, we need to decide the relevant fields to export or use. For our example, we will use these fields:

ResultNumber,Severity,Category,Title,Problem,Impact,Resolution

Sometimes the Problem field comes up with Null, which means success and not failure. So we will have to filter those result and only process the ones with errors:

Get-BPAResult -Model $Model -ErrorAction SilentlyContinue | Where-Object {$_.Problem -ne $Null} | Select-Object ResultNumber,Severity,Category,Title,Problem,Impact,Resolution

The results from this are a lot more useful:


Now we need to take the list of issues and convert it from that to an HTML report for sharing. In order to do s, we need to get the results into a CSF file using this at the end of the above cmdlets – | Export-Csv $CSV -NoTypeInformation -Encoding UTF8. After a CSV file has been generated, we can then convert it to HTML. This process takes a bit to explain, but there plenty of how-to’s that show how to do this. Search for ‘How to Create HTML Reports’ on your favorite search engine to get some insight.

That’s it. Make sure to create a loop around the BPAs on the server to generate all the HTML reports properly. The next section is wholly dedicated to displaying the code needed to make this happen.

Sample HTML Report


Script for One Server

#############################################################
# BPA Report for local Server                               #
#                                                           #
# Author:     Damian Scoles                                 #
# Purpose:    To generate an HTML version of all available  #
#                  BPA reports on the local server.         #
#                                                           #
# Version:    1.5                                           #
# History:    1.5 - Added support for Windows 2008 R2       #
#                                                           #
#############################################################

Function GenerateHTMLReport {

    param($name,$CSVFile,$FilePath)

    # Variables
    $css = "<style>"
    $css = $css + "TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}"
    $css= $css + "TH{border-width: 1px;padding: 2px;border-style: solid;border-color: black;}"
    $css = $css + "TD{border-width: 1px;padding: 2px;border-style: solid;border-color: black;}"
    $css = $css + "</style>"

    $Date = get-date -Format "MM.dd.yyyy-hh.mm-tt"
    $Path = $FilePath+"\Test\$name-BPA-Report-$Date.html"

    Write-Verbose "HTML report will be saved $FilePath"
    # $CSVFile
    $Info = $CSVFile | ConvertTo-Html -Fragment -As Table | Out-String
    $Report = ConvertTo-Html -Title "BPA Report - $Name" -Head "<h1>BPA Report - $Name</h1><br>This report was run on: $Date" -Body "$Info $Css"
    $Report | Out-File $path
    write-host "The file " -ForegroundColor White -nonewline
    write-host "$Path" -foregroundcolor Cyan -nonewline
    write-host " has been created." -ForegroundColor White
}

#Check OS Version
$Ver = (Get-WMIObject win32_OperatingSystem).Version
$Filepath = (Get-Item -Path ".\" -Verbose).FullName

# Operating System Check
if ($ver -gt '6.1.7599') {
   if ($ver -lt '6.2') {

        # Load PowerShell Modules
        Import-Module ServerManager
        Import-Module BestPractices

        #######################
        # Windows 2008 R2 SP1 #
        #######################

        # Initialize test Array variable
        $BPA = @() 
 
        # Get a list of installed features that have valid BPAs associated
        if ((Get-WindowsFeature Application-Server).Installed) {$BPA += "Microsoft/Windows/ApplicationServer"} 
        if ((Get-WindowsFeature AD-Certificate).Installed) {$BPA += "Microsoft/Windows/CertificateServices"} 
        if ((Get-WindowsFeature DHCP).Installed) {$BPA += "Microsoft/Windows/DHCP"} 
        if ((Get-WindowsFeature AD-Domain-Services).Installed) {$BPA += "Microsoft/Windows/DirectoryServices"} 
        if ((Get-WindowsFeature DNS).Installed) {$BPA += "Microsoft/Windows/DNSServer"} 
        if ((Get-WindowsFeature File-Services).Installed) {$BPA += "Microsoft/Windows/FileServices"} 
        if ((Get-WindowsFeature Hyper-V).Installed) {$BPA += "Microsoft/Windows/Hyper-V"} 
        if ((Get-WindowsFeature NPAS).Installed) {$BPA += "Microsoft/Windows/NPAS"} 
        if ((Get-WindowsFeature Remote-Desktop-Services).Installed) {$BPA += "Microsoft/Windows/TerminalServices"} 
        if ((Get-WindowsFeature Web-Server).Installed) {$BPA += "Microsoft/Windows/WebServer"} 
        if ((Get-WindowsFeature OOB-WSUS).Installed) {$BPA += "Microsoft/Windows/WSUS"}

        foreach ($line in $bpa) {
            $Name = $Line.Replace("Microsoft/Windows/","") 
            $Model = $Line
            $CSV = "BPA-Results-$Name.csv"
            Invoke-BPAModel $Model -ErrorAction SilentlyContinue
            Get-BPAResult -BestPracticesModelId $Model -ErrorAction SilentlyContinue | Where-Object {$_.Problem -ne $Null} | Select-Object ResultNumber,Severity,Category,Title,Problem,Impact,Resolution | Export-Csv $CSV -NoTypeInformation -Encoding UTF8
            $CSVFile = Import-CSV $CSV
            GenerateHTMLReport $Name $CSVFile $Filepath
        }
    }

    if ($ver -ge '6.2') {

        #################################
        # Windows 2012 and 2016 Section #
        #################################



        $BPA = (Get-WindowsFeature).BestPracticesModelId

        foreach ($line in $bpa) {
            $Installed = $False

            if ($line -ne "") {
                $Installed = (Get-WindowsFeature | where {$_.BestPracticesModelID -eq $Line}).Installed
            }

            if ($Installed -eq $True) {
                [string]$Test = $Line
                if ($Installed = $True) {
                    $BPA = Get-BPAModel $Test
	                $Name = $BPA.Name
                    $Model = $BPA.ID
                    $CSV = "BPA-Results-$Name.csv"
                    Get-BPAResult -Model $Model -ErrorAction SilentlyContinue | Where-Object {$_.Problem -ne $Null} | Select-Object ResultNumber,Severity,Category,Title,Problem,Impact,Resolution | Export-Csv $CSV -NoTypeInformation -Encoding UTF8
                    $CSVFile = Import-CSV $CSV
                    GenerateHTMLReport $Name $CSVFile $Filepath
                }
            } 
        }
    }
} else {
    write-host "No BPAs are available because the Server OS of the Doamin Controller is too old."
}

Follow-up

This script will work on a single server. At the end of the week, I will show you how to run this same script against more than one server at a time.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s