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.
Hi Damian, I’m working on implementing “the wheel” and I found your article 🙂
my problem is that I do not find the BPA module installed and I can not find a reference on how to install it or as an available module.
Can you help me?
Sorry for the delay, what operating system are you checking for the module? 2008, 2008R2, 2012, 2012R2, 2016?
Sorry for the very late reply on this one, but I used ‘Import-Module BestPractices’. If that does not work, let me know what OS you are running and I will review my scripts on how I made it work per OS.