Step One: PowerShell Connection To Server – To Run BPA Locally
One of the important steps in creating this script is creating a connection to the server in order to run the BPAs locally. The reason is that the BPAs cannot be connected to remotely. In order to do this, we need to first get a valid set of credentials to use for authenticating with a remote server:
write-host "Enter a user name and password for a Domain Admin. " $Credentials = Get-Credential
Using the above credentials, we can connect to the remote server using these two lines:
$Session = New-PSSession -ComputerName $Server -credential $Credentials Invoke-Command -Session $Session -Scriptblock $ScriptBlock2008
Notice in the Scriptblock being executed is for Windows 2008. An additional script block for 2012+ servers will need to be created because the BPAs for Windows 2008 is different from 2012 and 2016:
Invoke-Command -Session $Session -Scriptblock $ScriptBlock2012
If the server is local, then we don’t need to run use the session parameter and instead use these lines (depending on the local server version):
Invoke-Command -Scriptblock $ScriptBlock2008 Invoke-Command -Scriptblock $ScriptBlock2012
In order to check if the server where the BPAs need to be run on is we do two checks. One is to see if the server is remote or local and the other is if the server is available. First we check if the server is local, then we can assume its available. If it is not, then check to see if it is available with the ‘Test-Connection’ cmdlets.
# Check server availability $LocalServer = $env:computername if ($LocalServer -eq $Server) { $LiveServer = $True } Else { $LiveServer = Test-Connection -Cn $Server -BufferSize 16 -Count 1 -ea 0 -quiet }
Step Two: Operating System Check
Next we’ll need to build an OS version check so that the appropriate script block is run and the correct BPA check code is run.
#Check OS Version $Version = ((Get-WMIObject win32_OperatingSystem -ComputerName $Server).Version).Split([char]0x002E) $PartOne = $Version[0];$PartTwo = $Version[1] $Ver = $PartOne+"."+$PartTwo
Step Three: Loop for Multiple Servers
Because we need to run BPAs on multiple servers, a Foreach loop will be needed to loop through each server. First a variable will be needed to get a list of all the Domain Controllers in Active Directory (single Forest) and store it in the $DCs variable. Then the loop is constructed off that:
$DCs = Get-ADDomainController -Filter * Foreach ($line in $DCs) { ....code bulk goes here .... }
Final Step: Cleanup and Reporting
Once all of the BPA files have been generated on the remote servers, we need to copy them to a central location as well as remove the files after they are copied off the servers. For this, we’ll loop back through the DCs, copy them and store then locally.
Foreach ($line in $DCs) { # Variables for Loop $Server = $Line.Name # Copy remote results files to local server [HTML only] write-host " " write-host "Copying files from $Server......." -ForegroundColor Green write-host " " write-host "Files will be copied to $ResultsPath" -Foregroundcolor Yellow Copy-Item \\$Server\c$\BPATesting\*.html $ResultsPath # Uncomment the below line if you want the CSV files too # Copy-Item \\$Server\c$\BPATesting\*.CSV $FilePath # Cleanup of remotely generated files write-host " " Write-Host "Performing cleanup, removing test directory and files on server $Server......" -ForegroundColor Yellow Remove-Item \\$Server\c$\BPATesting\*.* -erroraction SilentlyContinue Remove-Item \\$Server\c$\BPATesting -erroraction SilentlyContinue # Pausing for report creation (on remote server) Write-host " " Write-host "Pausing for reports to finish on the remote servers......" -ForegroundColor Yellow Start-Sleep 60 }
Final Code
Putting all of the above steps together (and adding a few extras), we now have a script that can query Windows 2008 R2, Windows 2012, Windows 2012 R2 and Windows 2016 servers for the Best Practices Analyzers. The caveat is that it only works with Domain controllers, but in theory, if you modify the $DCs variable and provide your own list of servers, the script would work against those as well.
############################################################# # 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.8 # # History: 1.8 - Fixed bugs, test connections, etc. # # 1.7 - MultiServer Support (2012 and 2008) # # 1.6 - Correct some bugs # # 1.5 - Added support for Windows 2008 R2 # # # # Wish List # # # ############################################################# # Define Code to be executed remotely on 2008 R2 DCs $ScriptBlock2008 = { 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>" $Server = $env:computername $Date = get-date -Format "MM.dd.yyyy-hh.mm-tt" # $Path = $FilePath+"\Test\$name-BPA-Report-$Date.html" $Path = $FilePath+"\$name-BPA-Report-$Server-$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 - $Server" -Head "<h1>BPA Report - $Name for $Server</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 } # Load PowerShell Modules Import-Module ServerManager Import-Module BestPractices # Initialize test Array variable $BPA = @() # Check the BPAs on the remote server 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"} # Create test result path $PathTest = Test-Path C:\BPATesting if ($PathTest -eq $False) { MD C:\BPATesting $Verify = Test-Path C:\BPATesting if ($verify) { write-host "The directory C:\BPATesting has been created on $Server." -ForegroundColor White } } # Change Current Directory cd c:\BPATesting # Variables $Filepath = (Get-Item -Path ".\" -Verbose).FullName $Server = $env:computername foreach ($line in $bpa) { $Name = $Line.Replace("Microsoft/Windows/","") $Model = $Line $CSV = "BPA-Results-$Name-$Server.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 } } $ScriptBlock2012 = { # Variables for the loop $Server = $Line.Name $LiveServer = $False $PathTest = $False 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>" $Server = $env:computername $Date = get-date -Format "MM.dd.yyyy-hh.mm-tt" # $Path = $FilePath+"\Test\$name-BPA-Report-$Date.html" $Path = $FilePath+"\$name-BPA-Report-$Server-$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 - $Server" -Head "<h1>BPA Report - $Name for $Server</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 } $PathTest = Test-Path C:\BPATesting if ($PathTest -eq $False) { MD C:\BPATesting $Verify = Test-Path C:\BPATesting if ($verify) { write-host "The directory C:\BPATesting has been created." -ForegroundColor White } } # Change Current Directory cd c:\BPATesting # Variables $Filepath = (Get-Item -Path ".\" -Verbose).FullName $Server = $env:computername $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" Invoke-BPAModel $Model -ErrorAction SilentlyContinue 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 } } } } # Clear the screen CLS # Import the Active Directory module Import-Module activedirectory # Variable definition # $RootPath = 'C:\BPATesting' $ResultsPath = (Get-Item -Path ".\" -Verbose).FullName $DCs = Get-ADDomainController -Filter * $Server = $DCs.Name $LocalServer = $env:computername # Prompt for credentials ('flashing' red / white) $N = 1 Do { If (($N -eq 1) -or ($N -eq 3) -or ($N -eq 5) -or ($N -eq 7)) { [system.console]::ForegroundColor = "White" write-host "Enter a user name and password for a Domain Admin`r" -nonewline sleep 1 $N++ } If (($N -eq 2) -or ($N -eq 4) -or ($N -eq 6)) { [system.console]::ForegroundColor = "red" write-host "Enter a user name and password for a Domain Admin`r" -nonewline sleep 1 $N++ } } While ($N -lt 8) write-host "Enter a user name and password for a Domain Admin." -ForegroundColor Yellow $Credentials = Get-Credential Foreach ($line in $DCs) { # Variables for the loop $Server = $Null $Server = $Line.Name $LiveServer = $False $BPA = $Null $CSVFile = $Null $CSV = $Null $Model = $Null $PathTest = $False # Check server availability if ($LocalServer -eq $Server) { $LiveServer = $True } Else { $LiveServer = Test-Connection -Cn $Server -BufferSize 16 -Count 1 -ea 0 -quiet } If ($LiveServer) { Write-Host " " Write-Host "Processing BPAs on the server " -foregroundcolor White -NoNewLine Write-Host "$Server." -foregroundcolor Green Write-Host " " #Check OS Version # $Ver = (Get-WMIObject win32_OperatingSystem).Version $Version = ((Get-WMIObject win32_OperatingSystem -ComputerName $Server).Version).Split([char]0x002E) $PartOne = $Version[0];$PartTwo = $Version[1] $Ver = $PartOne+"."+$PartTwo # Create PS Sesstion to remote server $Session = New-PSSession -ComputerName $Server -credential $Credentials if ([Decimal]$ver -ge 6.1) { if ([Decimal]$ver -lt 6.2) { ####################### # Windows 2008 R2 SP1 # ####################### If ($Server -ne $LocalServer) { # Enter-PSSession $Server -credential $Credentials Invoke-Command -Session $Session -Scriptblock $ScriptBlock2008 # Close PS Session to remote server Remove-PSSession $Session } Else { # Invoke the BPA Code locally Invoke-Command -Scriptblock $ScriptBlock2008 } } } if ([Decimal]$ver -ge 6.2) { ################################# # Windows 2012 and 2016 Section # ################################# If ($Server -ne $LocalServer) { # Enter-PSSession $Server -credential $Credentials Invoke-Command -Session $Session -Scriptblock $ScriptBlock2012 # Close PS Session to remote server Remove-PSSession $Session } Else { # Invoke the BPA Code locally Invoke-Command -Scriptblock $ScriptBlock2012 } } } else { Write-Host " " Write-Host "The server $Server cannot be contacted. Please check the server." -ForegroundColor Red Write-Host " " } } # Pausing for report creation (on remote server) Write-host " " Write-host "Pausing for reports to finish on the remote servers......" -ForegroundColor Yellow Start-Sleep 60 Foreach ($line in $DCs) { # Variables for Loop $Server = $Line.Name # $Filepath = (Get-Item -Path ".\" -Verbose).FullName # Write-host "The filepath is $FilePAth." -foregroundcolor white # Copy remote results files to local server [HTML only] write-host " " write-host "Copying files from $Server......." -ForegroundColor Green write-host " " # $Filepath = (Get-Item -Path ".\" -Verbose).FullName # dir \\$Server\c$\BPATesting\ write-host "Files will be copied to $ResultsPath" -Foregroundcolor Yellow Copy-Item \\$Server\c$\BPATesting\*.html $ResultsPath # Uncomment the below line if you want the CSV files too # Copy-Item \\$Server\c$\BPATesting\*.CSV $FilePath # Cleanup of remotely generated files write-host " " Write-Host "Performing cleanup, removing test directory and files on server $Server......" -ForegroundColor Yellow Remove-Item \\$Server\c$\BPATesting\*.* -erroraction SilentlyContinue Remove-Item \\$Server\c$\BPATesting -erroraction SilentlyContinue # Remove local CSV generated files (if a DC) # Remove-Item c:\BPATesting\BPA*.csv # Pausing for report creation (on remote server) Write-host " " Write-host "Pausing for reports to finish on the remote servers......" -ForegroundColor Yellow Start-Sleep 60 } # Change Current Directory to Results Directory cd $ResultsPath # Finalize write-host " " Write-Host "Script is complete!" -foregroundcolor Yellow Write-Host "The HTML files from each server's BPAs are located in the $ResultsPath directory." -ForegroundColor Green write-host " "
Reports Generated
Just like the single server script, a stack of HTML reports is generated: