You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

5839 lines
273 KiB

<#
.NOTES
Name: HealthChecker.ps1
Original Author: Marc Nivens
Author: David Paulson
Contributor: Jason Shinbaum, Michael Schatte, Lukas Sassl
Requires: Exchange Management Shell and administrator rights on the target Exchange
server as well as the local machine.
Major Release History:
11/10/2020 - Initial Public Release of version 3.
1/18/2017 - Initial Public Release of version 2. - rewritten by David Paulson.
3/30/2015 - Initial Public Release.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
.SYNOPSIS
Checks the target Exchange server for various configuration recommendations from the Exchange product group.
.DESCRIPTION
This script checks the Exchange server for various configuration recommendations outlined in the
"Exchange 2013 Performance Recommendations" section on Microsoft Docs, found here:
https://docs.microsoft.com/en-us/exchange/exchange-2013-sizing-and-configuration-recommendations-exchange-2013-help
Informational items are reported in Grey. Settings found to match the recommendations are
reported in Green. Warnings are reported in yellow. Settings that can cause performance
problems are reported in red. Please note that most of these recommendations only apply to Exchange
2013/2016. The script will run against Exchange 2010/2007 but the output is more limited.
.PARAMETER Server
This optional parameter allows the target Exchange server to be specified. If it is not the
local server is assumed.
.PARAMETER OutputFilePath
This optional parameter allows an output directory to be specified. If it is not the local
directory is assumed. This parameter must not end in a \. To specify the folder "logs" on
the root of the E: drive you would use "-OutputFilePath E:\logs", not "-OutputFilePath E:\logs\".
.PARAMETER MailboxReport
This optional parameter gives a report of the number of active and passive databases and
mailboxes on the server.
.PARAMETER LoadBalancingReport
This optional parameter will check the connection count of the Default Web Site for every server
running Exchange 2013/2016 with the Client Access role in the org. It then breaks down servers by percentage to
give you an idea of how well the load is being balanced.
.PARAMETER CasServerList
Used with -LoadBalancingReport. A comma separated list of CAS servers to operate against. Without
this switch the report will use all 2013/2016 Client Access servers in the organization.
.PARAMETER SiteName
Used with -LoadBalancingReport. Specifies a site to pull CAS servers from instead of querying every server
in the organization.
.PARAMETER XMLDirectoryPath
Used in combination with BuildHtmlServersReport switch for the location of the HealthChecker XML files for servers
which you want to be included in the report. Default location is the current directory.
.PARAMETER BuildHtmlServersReport
Switch to enable the script to build the HTML report for all the servers XML results in the XMLDirectoryPath location.
.PARAMETER HtmlReportFile
Name of the HTML output file from the BuildHtmlServersReport. Default is ExchangeAllServersReport.html
.PARAMETER DCCoreRatio
Gathers the Exchange to DC/GC Core ratio and displays the results in the current site that the script is running in.
.PARAMETER Verbose
This optional parameter enables verbose logging.
.EXAMPLE
.\HealthChecker.ps1 -Server SERVERNAME
Run against a single remote Exchange server
.EXAMPLE
.\HealthChecker.ps1 -Server SERVERNAME -MailboxReport -Verbose
Run against a single remote Exchange server with verbose logging and mailbox report enabled.
.EXAMPLE
Get-ExchangeServer | ?{$_.AdminDisplayVersion -Match "^Version 15"} | %{.\HealthChecker.ps1 -Server $_.Name}
Run against all Exchange 2013/2016 servers in the Organization.
.EXAMPLE
.\HealthChecker.ps1 -LoadBalancingReport
Run a load balancing report comparing all Exchange 2013/2016 CAS servers in the Organization.
.EXAMPLE
.\HealthChecker.ps1 -LoadBalancingReport -CasServerList CAS01,CAS02,CAS03
Run a load balancing report comparing servers named CAS01, CAS02, and CAS03.
.LINK
https://docs.microsoft.com/en-us/exchange/exchange-2013-sizing-and-configuration-recommendations-exchange-2013-help
https://docs.microsoft.com/en-us/exchange/exchange-2013-virtualization-exchange-2013-help#requirements-for-hardware-virtualization
https://docs.microsoft.com/en-us/exchange/plan-and-deploy/virtualization?view=exchserver-2019#requirements-for-hardware-virtualization
#>
[CmdletBinding(DefaultParameterSetName="HealthChecker")]
param(
[Parameter(Mandatory=$false,ParameterSetName="HealthChecker")]
[Parameter(Mandatory=$false,ParameterSetName="MailboxReport")]
[string]$Server=($env:COMPUTERNAME),
[Parameter(Mandatory=$false)]
[ValidateScript({-not $_.ToString().EndsWith('\')})][string]$OutputFilePath = ".",
[Parameter(Mandatory=$false,ParameterSetName="MailboxReport")]
[switch]$MailboxReport,
[Parameter(Mandatory=$false,ParameterSetName="LoadBalancingReport")]
[switch]$LoadBalancingReport,
[Parameter(Mandatory=$false,ParameterSetName="LoadBalancingReport")]
[array]$CasServerList = $null,
[Parameter(Mandatory=$false,ParameterSetName="LoadBalancingReport")]
[string]$SiteName = ([string]::Empty),
[Parameter(Mandatory=$false,ParameterSetName="HTMLReport")]
[Parameter(Mandatory=$false,ParameterSetName="AnalyzeDataOnly")]
[ValidateScript({-not $_.ToString().EndsWith('\')})][string]$XMLDirectoryPath = ".",
[Parameter(Mandatory=$false,ParameterSetName="HTMLReport")]
[switch]$BuildHtmlServersReport,
[Parameter(Mandatory=$false,ParameterSetName="HTMLReport")]
[string]$HtmlReportFile="ExchangeAllServersReport.html",
[Parameter(Mandatory=$false,ParameterSetName="DCCoreReport")]
[switch]$DCCoreRatio,
[Parameter(Mandatory=$false,ParameterSetName="AnalyzeDataOnly")]
[switch]$AnalyzeDataOnly,
[Parameter(Mandatory=$false)][switch]$SaveDebugLog
)
$healthCheckerVersion = "3.1.1"
$VirtualizationWarning = @"
Virtual Machine detected. Certain settings about the host hardware cannot be detected from the virtual machine. Verify on the VM Host that:
- There is no more than a 1:1 Physical Core to Virtual CPU ratio (no oversubscribing)
- If Hyper-Threading is enabled do NOT count Hyper-Threaded cores as physical cores
- Do not oversubscribe memory or use dynamic memory allocation
Although Exchange technically supports up to a 2:1 physical core to vCPU ratio, a 1:1 ratio is strongly recommended for performance reasons. Certain third party Hyper-Visors such as VMWare have their own guidance.
VMWare recommends a 1:1 ratio. Their guidance can be found at https://www.vmware.com/files/pdf/Exchange_2013_on_VMware_Best_Practices_Guide.pdf.
Related specifically to VMWare, if you notice you are experiencing packet loss on your VMXNET3 adapter, you may want to review the following article from VMWare: http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=2039495.
For further details, please review the virtualization recommendations on Microsoft Docs at the following locations:
Exchange 2013: https://docs.microsoft.com/en-us/exchange/exchange-2013-virtualization-exchange-2013-help#requirements-for-hardware-virtualization.
Exchange 2016/2019: https://docs.microsoft.com/en-us/exchange/plan-and-deploy/virtualization?view=exchserver-2019.
"@
#this is to set the verbose information to a different color
if($PSBoundParameters["Verbose"]){
#Write verose output in cyan since we already use yellow for warnings
$Script:VerboseEnabled = $true
$VerboseForeground = $Host.PrivateData.VerboseForegroundColor
$Host.PrivateData.VerboseForegroundColor = "Cyan"
}
try {
#Enums and custom data types
Add-Type -TypeDefinition @"
using System;
using System.Collections;
namespace HealthChecker
{
public class HealthCheckerExchangeServer
{
public string ServerName; //String of the server that we are working with
public HardwareInformation HardwareInformation; // Hardware Object Information
public OperatingSystemInformation OSInformation; // OS Version Object Information
public ExchangeInformation ExchangeInformation; //Detailed Exchange Information
public string HealthCheckerVersion; //To determine the version of the script on the object.
}
// ExchangeInformation
public class ExchangeInformation
{
public ExchangeBuildInformation BuildInformation = new ExchangeBuildInformation(); //Exchange build information
public object GetExchangeServer; //Stores the Get-ExchangeServer Object
public ExchangeNetFrameworkInformation NETFramework = new ExchangeNetFrameworkInformation();
public bool MapiHttpEnabled; //Stored from organization config
public string ExchangeServicesNotRunning; //Contains the Exchange services not running by Test-ServiceHealth
public Hashtable ApplicationPools;
public ExchangeRegistryValues RegistryValues = new ExchangeRegistryValues();
public ExchangeServerMaintenance ServerMaintenance;
public System.Array ExchangeCertificates; //stores all the Exchange certificates on the servers.
}
public class ExchangeBuildInformation
{
public ExchangeServerRole ServerRole; //Roles that are currently set and installed.
public ExchangeMajorVersion MajorVersion; //Exchange Version (Exchange 2010/2013/2019)
public ExchangeCULevel CU; // Exchange CU Level
public string FriendlyName; //Exchange Friendly Name is provided
public string BuildNumber; //Exchange Build Number
public string ReleaseDate; // Exchange release date for which the CU they are currently on
public bool SupportedBuild; //Determines if we are within the correct build of Exchange.
public object ExchangeSetup; //Stores the Get-Command ExSetup object
public System.Array KBsInstalled; //Stored object IU or Security KB fixes
}
public class ExchangeNetFrameworkInformation
{
public NetMajorVersion MinSupportedVersion; //Min Supported .NET Framework version
public NetMajorVersion MaxSupportedVersion; //Max (Recommended) Supported .NET version.
public bool OnRecommendedVersion; //RecommendedNetVersion Info includes all the factors. Windows Version & CU.
public string DisplayWording; //Display if we are in Support or not
}
public class ExchangeServerMaintenance
{
public System.Array InactiveComponents;
public object GetServerComponentState;
public object GetClusterNode;
public object GetMailboxServer;
}
//enum for CU levels of Exchange
public enum ExchangeCULevel
{
Unknown,
Preview,
RTM,
CU1,
CU2,
CU3,
CU4,
CU5,
CU6,
CU7,
CU8,
CU9,
CU10,
CU11,
CU12,
CU13,
CU14,
CU15,
CU16,
CU17,
CU18,
CU19,
CU20,
CU21,
CU22,
CU23
}
//enum for the server roles that the computer is
public enum ExchangeServerRole
{
MultiRole,
Mailbox,
ClientAccess,
Hub,
Edge,
None
}
//enum for the Exchange version
public enum ExchangeMajorVersion
{
Unknown,
Exchange2010,
Exchange2013,
Exchange2016,
Exchange2019
}
public class ExchangeRegistryValues
{
public int CtsProcessorAffinityPercentage; //Stores the CtsProcessorAffinityPercentage registry value from HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ExchangeServer\v15\Search\SystemParameters
}
// End ExchangeInformation
// OperatingSystemInformation
public class OperatingSystemInformation
{
public OSBuildInformation BuildInformation = new OSBuildInformation(); // contains build information
public NetworkInformation NetworkInformation = new NetworkInformation(); //stores network information and settings
public PowerPlanInformation PowerPlan = new PowerPlanInformation(); //stores the power plan information
public PageFileInformation PageFile; //stores the page file information
public LmCompatibilityLevelInformation LmCompatibility; // stores Lm Compatibility Level Information
public bool ServerPendingReboot; // determines if the server is pending a reboot. TODO: Adjust to contain the registry values that we are looking at.
public TimeZoneInformation TimeZone = new TimeZoneInformation(); //stores time zone information
public Hashtable TLSSettings; // stores the TLS settings on the server.
public InstalledUpdatesInformation InstalledUpdates = new InstalledUpdatesInformation(); //store the install update
public ServerBootUpInformation ServerBootUp = new ServerBootUpInformation(); // stores the server boot up time information
public System.Array VcRedistributable; //stores the Visual C++ Redistributable
public OSNetFrameworkInformation NETFramework = new OSNetFrameworkInformation(); //stores OS Net Framework
public bool CredentialGuardEnabled;
public OSRegistryValues RegistryValues = new OSRegistryValues();
public Smb1ServerSettings Smb1ServerSettings = new Smb1ServerSettings();
}
public class OSBuildInformation
{
public OSServerVersion MajorVersion; //OS Major Version
public string VersionBuild; //hold the build number
public string FriendlyName; //string holder of the Windows Server friendly name
public object OperatingSystem; // holds Win32_OperatingSystem
}
public class NetworkInformation
{
public double TCPKeepAlive; // value used for the TCP/IP keep alive value in the registry
public double RpcMinConnectionTimeout; //holds the value for the RPC minimum connection timeout.
public string HttpProxy; // holds the setting for HttpProxy if one is set.
public object PacketsReceivedDiscarded; //hold all the packets received discarded on the server.
public double IPv6DisabledComponents; //value stored in the registry HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\DisabledComponents
public bool IPv6DisabledOnNICs; //value that determines if we have IPv6 disabled on some NICs or not.
public System.Array NetworkAdapters; //stores all the NICs on the servers.
public string PnPCapabilities; //Value from PnPCapabilities registry
public bool SleepyNicDisabled; //If the NIC can be in power saver mode by the OS.
}
public class PowerPlanInformation
{
public bool HighPerformanceSet; // If the power plan is High Performance
public string PowerPlanSetting; //value for the power plan that is set
public object PowerPlan; //object to store the power plan information
}
public class PageFileInformation
{
public object PageFile; //store the information that we got for the page file
public double MaxPageSize; //holds the information of what our page file is set to
}
public class OSRegistryValues
{
public int CurrentVersionUbr; // stores SOFTWARE\Microsoft\Windows NT\CurrentVersion\UBR
public int LanManServerDisabledCompression; // stores SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters\DisabledCompression
}
public class LmCompatibilityLevelInformation
{
public int RegistryValue; //The LmCompatibilityLevel for the server (INT 1 - 5)
public string Description; //description of the LmCompat that the server is set to
}
public class TimeZoneInformation
{
public string CurrentTimeZone; //stores the value for the current time zone of the server.
public int DynamicDaylightTimeDisabled; // the registry value for DynamicDaylightTimeDisabled.
public string TimeZoneKeyName; // the registry value TimeZoneKeyName.
public string StandardStart; // the registry value for StandardStart.
public string DaylightStart; // the registry value for DaylightStart.
public bool DstIssueDetected; // Determines if there is a high chance of an issue.
public System.Array ActionsToTake; //array of verbage of the issues detected.
}
public class ServerRebootInformation
{
public bool PendingFileRenameOperations; //bool "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\" item PendingFileRenameOperations.
public object SccmReboot; // object to store CimMethod for class name CCM_ClientUtilities
public bool SccmRebootPending; // SccmReboot has either PendingReboot or IsHardRebootPending is set to true.
public bool ComponentBasedServicingPendingReboot; // bool HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending
public bool AutoUpdatePendingReboot; // bool HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired
public bool PendingReboot; // bool if reboot types are set to true
}
public class InstalledUpdatesInformation
{
public System.Array HotFixes; //array to keep all the hotfixes of the server
public System.Array HotFixInfo; //object to store hotfix information
public System.Array InstalledUpdates; //store the install updates
}
public class ServerBootUpInformation
{
public string Days;
public string Hours;
public string Minutes;
public string Seconds;
}
//enum for the dword values of the latest supported VC++ redistributable releases
//https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads
public enum VCRedistVersion
{
Unknown = 0,
VCRedist2012 = 184610406,
VCRedist2013 = 201367256
}
public class SoftwareInformation
{
public string DisplayName;
public string DisplayVersion;
public string InstallDate;
public int VersionIdentifier;
}
public class OSNetFrameworkInformation
{
public NetMajorVersion NetMajorVersion; //NetMajorVersion value
public string FriendlyName; //string of the friendly name
public int RegistryValue; //store the registry value
public Hashtable FileInformation; //stores Get-Item information for .NET Framework
}
//enum for the OSServerVersion that we are
public enum OSServerVersion
{
Unknown,
Windows2008,
Windows2008R2,
Windows2012,
Windows2012R2,
Windows2016,
Windows2019,
WindowsCore
}
//enum for the dword value of the .NET frame 4 that we are on
public enum NetMajorVersion
{
Unknown = 0,
Net4d5 = 378389,
Net4d5d1 = 378675,
Net4d5d2 = 379893,
Net4d5d2wFix = 380035,
Net4d6 = 393295,
Net4d6d1 = 394254,
Net4d6d1wFix = 394294,
Net4d6d2 = 394802,
Net4d7 = 460798,
Net4d7d1 = 461308,
Net4d7d2 = 461808,
Net4d8 = 528040
}
public class Smb1ServerSettings
{
public object RegistryValue;
public object SmbServerConfiguration;
public object WindowsFeature;
public int Smb1Status;
}
// End OperatingSystemInformation
// HardwareInformation
public class HardwareInformation
{
public string Manufacturer; //String to display the hardware information
public ServerType ServerType; //Enum to determine if the hardware is VMware, HyperV, Physical, or Unknown
public double TotalMemory; //Stores the total memory available
public object System; //object to store the system information that we have collected
public ProcessorInformation Processor; //Detailed processor Information
public bool AutoPageFile; //True/False if we are using a page file that is being automatically set
public string Model; //string to display Model
}
//enum for the type of computer that we are
public enum ServerType
{
VMWare,
AmazonEC2,
HyperV,
Physical,
Unknown
}
public class ProcessorInformation
{
public string Name; //String of the processor name
public int NumberOfPhysicalCores; //Number of Physical cores that we have
public int NumberOfLogicalCores; //Number of Logical cores that we have presented to the os
public int NumberOfProcessors; //Total number of processors that we have in the system
public int MaxMegacyclesPerCore; //Max speed that we can get out of the cores
public int CurrentMegacyclesPerCore; //Current speed that we are using the cores at
public bool ProcessorIsThrottled; //True/False if we are throttling our processor
public bool DifferentProcessorsDetected; //true/false to detect if we have different processor types detected
public bool DifferentProcessorCoreCountDetected; //detect if there are a different number of core counts per Processor CPU socket
public int EnvironmentProcessorCount; //[system.environment]::processorcount
public object ProcessorClassObject; // object to store the processor information
}
//HTML & display classes
public class HtmlServerValues
{
public System.Array OverviewValues;
public System.Array ActionItems; //use HtmlServerActionItemRow
public System.Array ServerDetails; // use HtmlServerInformationRow
}
public class HtmlServerActionItemRow
{
public string Setting;
public string DetailValue;
public string RecommendedDetails;
public string MoreInformation;
public string Class;
}
public class HtmlServerInformationRow
{
public string Name;
public string DetailValue;
public string Class;
}
public class DisplayResultsLineInfo
{
public string DisplayValue;
public string Name;
public int TabNumber;
public object TestingValue; //Used for pester testing down the road.
public string WriteType;
public string Line
{
get
{
if (String.IsNullOrEmpty(this.Name))
{
return this.DisplayValue;
}
return String.Concat(this.Name, ": ", this.DisplayValue);
}
}
}
public class DisplayResultsGroupingKey
{
public string Name;
public int DefaultTabNumber;
public bool DisplayGroupName;
public int DisplayOrder;
}
public class AnalyzedInformation
{
public HealthCheckerExchangeServer HealthCheckerExchangeServer;
public Hashtable HtmlServerValues = new Hashtable();
public Hashtable DisplayResults = new Hashtable();
}
}
"@ -ErrorAction Stop
}
catch
{
Write-Warning "There was an error trying to add custom classes to the current PowerShell session. You need to close this session and open a new one to have the script properly work."
exit
}
function Write-Red($message)
{
Write-DebugLog $message
Write-Host $message -ForegroundColor Red
$message | Out-File ($OutputFullPath) -Append
}
function Write-Yellow($message)
{
Write-DebugLog $message
Write-Host $message -ForegroundColor Yellow
$message | Out-File ($OutputFullPath) -Append
}
function Write-Green($message)
{
Write-DebugLog $message
Write-Host $message -ForegroundColor Green
$message | Out-File ($OutputFullPath) -Append
}
function Write-Grey($message)
{
Write-DebugLog $message
Write-Host $message
$message | Out-File ($OutputFullPath) -Append
}
function Write-VerboseOutput($message)
{
Write-Verbose $message
Write-DebugLog $message
if($Script:VerboseEnabled)
{
$message | Out-File ($OutputFullPath) -Append
}
}
function Write-DebugLog($message)
{
if(![string]::IsNullOrEmpty($message))
{
$Script:Logger.WriteToFileOnly($message)
}
}
Function Write-Break {
Write-Host ""
}
#Function Version 1.1
Function Write-HostWriter {
param(
[Parameter(Mandatory=$true)][string]$WriteString
)
if($Script:Logger -ne $null)
{
$Script:Logger.WriteHost($WriteString)
}
elseif($HostFunctionCaller -eq $null)
{
Write-Host $WriteString
}
else
{
&$HostFunctionCaller $WriteString
}
}
Function Write-VerboseWriter {
param(
[Parameter(Mandatory=$true)][string]$WriteString
)
if($VerboseFunctionCaller -eq $null)
{
Write-Verbose $WriteString
}
else
{
&$VerboseFunctionCaller $WriteString
}
}
$Script:VerboseFunctionCaller = ${Function:Write-VerboseOutput}
#Function Version 1.0
Function Write-ScriptMethodHostWriter{
param(
[Parameter(Mandatory=$true)][string]$WriteString
)
if($this.LoggerObject -ne $null)
{
$this.LoggerObject.WriteHost($WriteString)
}
elseif($this.HostFunctionCaller -eq $null)
{
Write-Host $WriteString
}
else
{
$this.HostFunctionCaller($WriteString)
}
}
#Function Version 1.0
Function Write-ScriptMethodVerboseWriter {
param(
[Parameter(Mandatory=$true)][string]$WriteString
)
if($this.LoggerObject -ne $null)
{
$this.LoggerObject.WriteVerbose($WriteString)
}
elseif($this.VerboseFunctionCaller -eq $null -and
$this.WriteVerboseData)
{
Write-Host $WriteString -ForegroundColor Cyan
}
elseif($this.WriteVerboseData)
{
$this.VerboseFunctionCaller($WriteString)
}
}
Function Write-HealthCheckerVersion {
$currentVersion = Test-ScriptVersion -ApiUri "api.github.com" -RepoOwner "dpaulson45" `
-RepoName "HealthChecker" `
-CurrentVersion $healthCheckerVersion `
-DaysOldLimit 90 `
-CatchActionFunction ${Function:Invoke-CatchActions}
$Script:DisplayedScriptVersionAlready = $true
if($currentVersion)
{
Write-Green("Exchange Health Checker version {0}" -f $healthCheckerVersion)
}
else
{
Write-Yellow("Exchange Health Checker version {0}. This script is probably outdated. Please verify before relying on the results." -f $healthCheckerVersion)
}
}
Function Write-ResultsToScreen {
param(
[Hashtable]$ResultsToWrite
)
Write-VerboseOutput("Calling: Write-ResultsToScreen")
$indexOrderGroupingToKey = @{}
foreach ($keyGrouping in $ResultsToWrite.Keys)
{
$indexOrderGroupingToKey[$keyGrouping.DisplayOrder] = $keyGrouping
}
$sortedIndexOrderGroupingToKey = $indexOrderGroupingToKey.Keys | Sort-Object
foreach ($key in $sortedIndexOrderGroupingToKey)
{
Write-VerboseOutput("Working on Key: {0}" -f $key)
$keyGrouping = $indexOrderGroupingToKey[$key]
Write-VerboseOutput("Working on Key Group: {0}" -f $keyGrouping.Name)
Write-VerboseOutput("Total lines to write: {0}" -f ($ResultsToWrite[$keyGrouping].Count))
if ($keyGrouping.DisplayGroupName)
{
Write-Grey($keyGrouping.Name)
$dashes = [string]::empty
1..($keyGrouping.Name.Length) | %{$dashes = $dashes + "-"}
Write-Grey($dashes)
}
foreach ($line in $ResultsToWrite[$keyGrouping])
{
$tab = [string]::Empty
if ($line.TabNumber -ne 0)
{
1..($line.TabNumber) | %{$tab = $tab + "`t"}
}
$writeValue = "{0}{1}" -f $tab, $line.Line
switch ($line.WriteType)
{
"Grey" {Write-Grey($writeValue)}
"Yellow" {Write-Yellow($writeValue)}
"Green" {Write-Green($writeValue)}
"Red" {Write-Red($writeValue)}
}
}
Write-Grey("")
}
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-AllNicInformation/Get-AllNicInformation.ps1
Function Get-AllNicInformation {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$ComputerName,
[Parameter(Mandatory=$false)][string]$ComputerFQDN,
[Parameter(Mandatory=$false)][scriptblock]$CatchActionFunction
)
#Function Version 1.6
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-WmiObjectHandler/Get-WmiObjectHandler.ps1
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Invoke-RegistryGetValue/Invoke-RegistryGetValue.ps1
#>
Write-VerboseWriter("Calling: Get-AllNicInformation")
Write-VerboseWriter("Passed [string]ComputerName: {0} | [string]ComputerFQDN: {1}" -f $ComputerName, $ComputerFQDN)
Function Get-NicPnpCapabilitiesSetting {
[CmdletBinding()]
param(
[string]$NicAdapterComponentId
)
if ($NicAdapterComponentId -eq [string]::Empty)
{
throw [System.Management.Automation.ParameterBindingException] "Failed to provide valid NicAdapterDeviceId or NicAdapterComponentId"
}
$nicAdapterBasicPath = "SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002bE10318}"
Write-VerboseWriter("Probing started to detect NIC adapter registry path")
$nicAdapterRegKey = Invoke-RegistryGetValue -MachineName $ComputerName -Subkey $nicAdapterBasicPath -ReturnAfterOpenSubKey $true -CatchActionFunction $CatchActionFunction
$nicAdapterDeviceIDs = $nicAdapterRegKey.GetSubKeyNames() | Where-Object {$_.StartsWith("0")}
foreach($deviceID in $nicAdapterDeviceIDs)
{
$nicAdapterPnPCapabilitiesProbingKey = "{0}\{1}" -f $nicAdapterBasicPath, $deviceID
$netCfgInstanceId = Invoke-RegistryGetValue -MachineName $ComputerName -Subkey $nicAdapterPnPCapabilitiesProbingKey -GetValue "NetCfgInstanceId" -CatchActionFunction $CatchActionFunction
if ($netCfgInstanceId -eq $NicAdapterComponentId)
{
Write-VerboseWriter("Matching ComponentId found - now checking for PnPCapabilitiesValue")
$nicAdapterPnPCapabilitiesValue = Invoke-RegistryGetValue -MachineName $ComputerName -SubKey $nicAdapterPnPCapabilitiesProbingKey -GetValue "PnPCapabilities" -CatchActionFunction $CatchActionFunction
break
}
else
{
Write-VerboseWriter("No matching ComponentId found")
}
}
$obj = New-Object PSCustomObject
$sleepyNicDisabled = $false
if ($nicAdapterPnPCapabilitiesValue -eq 24 -or
$nicAdapterPnPCapabilitiesValue -eq 280)
{
$sleepyNicDisabled = $true
}
$obj | Add-Member -MemberType NoteProperty -Name "PnPCapabilities" -Value $nicAdapterPnPCapabilitiesValue
$obj | Add-Member -MemberType NoteProperty -Name "SleepyNicDisabled" -Value $sleepyNicDisabled
return $obj
}
Function Get-NetworkConfiguration {
[CmdletBinding()]
param(
[string]$ComputerName
)
try
{
$currentErrors = $Error.Count
$cimSession = New-CimSession -ComputerName $ComputerName -ErrorAction Stop
$networkIpConfiguration = Get-NetIPConfiguration -CimSession $CimSession -ErrorAction Stop | Where-Object {$_.NetAdapter.MediaConnectionState -eq "Connected"}
if ($CatchActionFunction -ne $null)
{
$index = 0
while ($index -lt ($Error.Count - $currentErrors))
{
& $CatchActionFunction $Error[$index]
$index++
}
}
return $networkIpConfiguration
}
catch
{
Write-VerboseWriter("Failed to run Get-NetIPConfiguration. Error {0}." -f $Error[0].Exception)
if ($CatchActionFunction -ne $null)
{
& $CatchActionFunction
}
throw
}
}
Function New-NICInformation {
param(
[array]$NetworkConfigurations,
[bool]$WmiObject
)
if($NetworkConfigurations -eq $null)
{
Write-VerboseWriter("NetworkConfigurations are null in New-NICInformation. Returning a null object.")
return $null
}
Function New-IpvAddresses {
$obj = New-Object PSCustomObject
$obj | Add-Member -MemberType NoteProperty -Name "Address" -Value ([string]::Empty)
$obj | Add-Member -MemberType NoteProperty -Name "Subnet" -Value ([string]::Empty)
$obj | Add-Member -MemberType NoteProperty -Name "DefaultGateway" -Value ([string]::Empty)
return $obj
}
if ($WmiObject)
{
$networkAdapterConfigurations = Get-WmiObjectHandler -ComputerName $ComputerName -Class "Win32_NetworkAdapterConfiguration" -Filter "IPEnabled = True" -CatchActionFunction $CatchActionFunction
}
[array]$nicObjects = @()
foreach($networkConfig in $NetworkConfigurations)
{
$dnsClient = $null
$rssEnabledValue = 2
$netAdapterRss = $null
if (!$WmiObject)
{
Write-VerboseWriter("Working on NIC: {0}" -f $networkConfig.InterfaceDescription)
$adapter = $networkConfig.NetAdapter
if($adapter.DriverFileName -ne "NdisImPlatform.sys")
{
$nicPnpCapabilitiesSetting = Get-NicPnpCapabilitiesSetting -NicAdapterComponentId $adapter.DeviceID
}
else
{
Write-VerboseWriter("Multiplexor adapter detected. Going to skip PnpCapabilities check")
$nicPnpCapabilitiesSetting = @{
PnPCapabilities = "MultiplexorNoPnP"
}
}
try
{
$dnsClient = $adapter | Get-DnsClient -ErrorAction Stop
Write-VerboseWriter("Got DNS Client information")
}
catch
{
Write-VerboseWriter("Failed to get the DNS Client information")
if ($CatchActionFunction -ne $null)
{
& $CatchActionFunction
}
}
try
{
$netAdapterRss = $adapter | Get-NetAdapterRss -ErrorAction Stop
Write-VerboseWriter("Got Net Adapter RSS information")
if ($netAdapterRss -ne $null)
{
[int]$rssEnabledValue = $netAdapterRss.Enabled
}
}
catch
{
Write-VerboseWriter("Failed to get RSS Information")
if ($CatchActionFunction -ne $null)
{
& $CatchActionFunction
}
}
}
else
{
Write-VerboseWriter("Working on NIC: {0}" -f $networkConfig.Description)
$adapter = $networkConfig
if($adapter.ServiceName -ne "NdisImPlatformMp")
{
$nicPnpCapabilitiesSetting = Get-NicPnpCapabilitiesSetting -NicAdapterComponentId $adapter.Guid
}
else
{
Write-VerboseWriter("Multiplexor adapter detected. Going to skip PnpCapabilities check")
$nicPnpCapabilitiesSetting = @{
PnPCapabilities = "MultiplexorNoPnP"
}
}
}
$nicInformationObj = New-Object PSCustomObject
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "WmiObject" -Value $WmiObject
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "Name" -Value ($adapter.Name)
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "LinkSpeed" -Value ((($adapter.Speed)/1000000).ToString() + " Mbps")
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "DriverDate" -Value [DateTime]::MaxValue
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "NICObject" -Value $networkConfig
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "NetAdapterRss" -Value $netAdapterRss
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "RssEnabledValue" -Value $rssEnabledValue
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "IPv6Enabled" -Value $false
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "Description" -Value $adapter.Description
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "DriverVersion" -Value [string]::Empty
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "MTUSize" -Value 0
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "PnPCapabilities" -Value ($nicPnpCapabilitiesSetting.PnPCapabilities)
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "SleepyNicDisabled" -Value ($nicPnpCapabilitiesSetting.SleepyNicDisabled)
if (!$WmiObject)
{
$nicInformationObj.MTUSize = $adapter.MtuSize
$nicInformationObj.DriverDate = $adapter.DriverDate
$nicInformationObj.DriverVersion = $adapter.DriverVersionString
$nicInformationObj.Description = $adapter.InterfaceDescription
foreach ($ipAddress in $networkConfig.AllIPAddresses.IPAddress)
{
if ($ipAddress.Contains(":"))
{
$nicInformationObj.IPv6Enabled = $true
}
}
$ipv4Address = @()
for ($i = 0; $i -lt $networkConfig.IPv4Address.Count; $i++)
{
$obj = New-IpvAddresses
if ($networkConfig.IPv4Address -ne $null -and
$i -lt $networkConfig.IPv4Address.Count)
{
$obj.Address = $networkConfig.IPv4Address[$i].IPAddress
$obj.Subnet = $networkConfig.IPv4Address[$i].PrefixLength
}
if ($networkConfig.IPv4DefaultGateway -ne $null -and
$i -lt $networkConfig.IPv4DefaultGateway.Count)
{
$obj.DefaultGateway = $networkConfig.IPv4DefaultGateway[$i].NextHop
}
$ipv4Address += $obj
}
$ipv6Address = @()
for ($i = 0; $i -lt $networkConfig.IPv6Address.Count; $i++)
{
$obj = New-IpvAddresses
if ($networkConfig.IPv6Address -ne $null -and
$i -lt $networkConfig.IPv6Address.Count)
{
$obj.Address = $networkConfig.IPv6Address[$i].IPAddress
$obj.Subnet = $networkConfig.IPv6Address[$i].PrefixLength
}
if ($networkConfig.IPv6DefaultGateway -ne $null -and
$i -lt $networkConfig.IPv6DefaultGateway.Count)
{
$obj.DefaultGateway = $networkConfig.IPv6DefaultGateway[$i].NextHop
}
$ipv6Address += $obj
}
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "IPv4Addresses" -Value $ipv4Address
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "Ipv6Addresses" -Value $ipv6Address
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "RegisteredInDns" -Value $dnsClient.RegisterThisConnectionsAddress
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "DnsServer" -Value $networkConfig.DNSServer.ServerAddresses
$nicInformationObj | Add-Member -MemberType NoteProperty -Name "DnsClientObject" -Value $dnsClient
}
else
{
$stopProcess = $false
foreach ($adapterConfiguration in $networkAdapterConfigurations)
{
Write-VerboseWriter("Working on '{0}' | SettingID: {1}" -f $adapterConfiguration.Description, ($settingId = $adapterConfiguration.SettingID))
if ($settingId -eq $networkConfig.GUID -or
$settingId -eq $networkConfig.InterfaceGuid)
{
foreach ($ipAddress in $adapterConfiguration.IPAddress)
{
if ($ipAddress.Contains(":"))
{
$nicInformationObj.IPv6Enabled = $true
$stopProcess = $true
break
}
}
}
if ($stopProcess)
{
break
}
}
}
$nicObjects += $nicInformationObj
}
Write-VerboseWriter("Found {0} active adapters on the computer." -f $nicObjects.Count)
Write-VerboseWriter("Exiting: Get-AllNicInformation")
return $nicObjects
}
try
{
try
{
$networkConfiguration = Get-NetworkConfiguration -ComputerName $ComputerName
}
catch
{
if ($CatchActionFunction -ne $null)
{
& $CatchActionFunction
}
if ($ComputerFQDN -ne [string]::Empty -and
$ComputerName -ne $null)
{
$networkConfiguration = Get-NetworkConfiguration -ComputerName $ComputerFQDN
}
else
{
$bypassCatchActions = $true
Write-VerboseWriter("No FQDN was passed, going to rethrow error.")
throw
}
}
return (New-NICInformation -NetworkConfigurations $networkConfiguration)
}
catch
{
if (!$bypassCatchActions -and
$CatchActionFunction -ne $null)
{
& $CatchActionFunction
}
$wmiNetworkCards = Get-WmiObjectHandler -ComputerName $ComputerName -Class "Win32_NetworkAdapter" -Filter "NetConnectionStatus ='2'" -CatchActionFunction $CatchActionFunction
return (New-NICInformation -NetworkConfigurations $wmiNetworkCards -WmiObject $true)
}
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-AllTlsSettingsFromRegistry/Get-AllTlsSettingsFromRegistry.ps1
Function Get-AllTlsSettingsFromRegistry {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$MachineName,
[Parameter(Mandatory=$false)][scriptblock]$CatchActionFunction
)
#Function Version 1.1
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Invoke-RegistryGetValue/Invoke-RegistryGetValue.ps1
#>
Write-VerboseWriter("Calling: Get-AllTlsSettingsFromRegistry")
Write-VerboseWriter("Passed: [string]MachineName: {0}" -f $MachineName)
$registryBase = "SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS {0}\{1}"
$tlsVersions = @("1.0","1.1","1.2")
$tlsResults = @{}
$keyValues = ("Enabled","DisabledByDefault")
Function Set-TLSMemberValue {
param(
[Parameter(Mandatory=$true)][string]$GetKeyType,
[Parameter(Mandatory=$false)][object]$KeyValue,
[Parameter(Mandatory=$true)][string]$ServerClientType,
[Parameter(Mandatory=$true)][string]$TlsVersion
)
switch($GetKeyType)
{
"Enabled" {
if($KeyValue -eq $null)
{
Write-VerboseWriter("Failed to get TLS {0} {1} Enabled Key on Server {2}. We are assuming this means it is enabled." -f $TlsVersion, $ServerClientType, $MachineName)
return $true
}
else
{
Write-VerboseWriter("{0} Enabled Value '{1}'" -f $ServerClientType, $serverValue)
if($KeyValue -eq 1)
{
return $true
}
return $false
}
}
"DisabledByDefault" {
if($KeyValue -eq $null)
{
Write-VerboseWriter("Failed to get TLS {0} {1} Disabled By Default Key on Server {2}. Setting to false." -f $TlsVersion, $ServerClientType, $MachineName)
return $false
}
else
{
Write-VerboseWriter("{0} Disabled By Default Value '{1}'" -f $ServerClientType, $serverValue)
if($KeyValue -eq 1)
{
return $true
}
return $false
}
}
}
}
Function Set-NETDefaultTLSValue {
param(
[Parameter(Mandatory=$false)][object]$KeyValue,
[Parameter(Mandatory=$true)][string]$NetVersion,
[Parameter(Mandatory=$true)][string]$KeyName
)
if($KeyValue -eq $null)
{
Write-VerboseWriter("Failed to get {0} registry value for .NET {1} version. Setting to false." -f $KeyName, $NetVersion)
return $false
}
else
{
Write-VerboseWriter("{0} value '{1}'" -f $KeyName, $KeyValue)
if($KeyValue -eq 1)
{
return $true
}
return $false
}
}
[hashtable]$allTlsObjects = @{}
foreach($tlsVersion in $tlsVersions)
{
$registryServer = $registryBase -f $tlsVersion, "Server"
$registryClient = $registryBase -f $tlsVersion, "Client"
$currentTLSObject = New-Object PSCustomObject
$currentTLSObject | Add-Member -MemberType NoteProperty -Name "TLSVersion" -Value $tlsVersion
foreach($getKey in $keyValues)
{
$memberServerName = "Server{0}" -f $getKey
$memberClientName = "Client{0}" -f $getKey
$serverValue = Invoke-RegistryGetValue -RegistryHive "LocalMachine" -MachineName $MachineName -SubKey $registryServer -GetValue $getKey -CatchActionFunction $CatchActionFunction
$clientValue = Invoke-RegistryGetValue -RegistryHive "LocalMachine" -MachineName $MachineName -SubKey $registryClient -GetValue $getKey -CatchActionFunction $CatchActionFunction
$currentTLSObject | Add-Member -MemberType NoteProperty -Name $memberServerName -Value (Set-TLSMemberValue -GetKeyType $getKey -KeyValue $serverValue -ServerClientType "Server" -TlsVersion $tlsVersion)
$currentTLSObject | Add-Member -MemberType NoteProperty -Name $memberClientName -Value (Set-TLSMemberValue -GetKeyType $getKey -KeyValue $clientValue -ServerClientType "Client" -TlsVersion $tlsVersion)
}
$allTlsObjects.Add($tlsVersion, $currentTLSObject)
}
$netVersions = @("v2.0.50727","v4.0.30319")
$registryBase = "SOFTWARE\{0}\.NETFramework\{1}"
foreach($netVersion in $netVersions)
{
$currentNetTlsDefaultVersionObject = New-Object PSCustomObject
$currentNetTlsDefaultVersionObject | Add-Member -MemberType NoteProperty -Name "NetVersion" -Value $netVersion
$SystemDefaultTlsVersions = Invoke-RegistryGetValue -RegistryHive "LocalMachine" -MachineName $MachineName -SubKey ($registryBase -f "Microsoft", $netVersion) -GetValue "SystemDefaultTlsVersions" -CatchActionFunction $CatchActionFunction
$WowSystemDefaultTlsVersions = Invoke-RegistryGetValue -RegistryHive "LocalMachine" -MachineName $MachineName -SubKey ($registryBase -f "Wow6432Node\Microsoft", $netVersion) -GetValue "SystemDefaultTlsVersions" -CatchActionFunction $CatchActionFunction
$currentNetTlsDefaultVersionObject | Add-Member -MemberType NoteProperty -Name "SystemDefaultTlsVersions" -Value (Set-NETDefaultTLSValue -KeyValue $SystemDefaultTlsVersions -NetVersion $netVersion -KeyName "SystemDefaultTlsVersions")
$currentNetTlsDefaultVersionObject | Add-Member -MemberType NoteProperty -Name "WowSystemDefaultTlsVersions" -Value (Set-NETDefaultTLSValue -KeyValue $WowSystemDefaultTlsVersions -NetVersion $netVersion -KeyName "WowSystemDefaultTlsVersions")
$hashKeyName = "NET{0}" -f ($netVersion.Split(".")[0])
$allTlsObjects.Add($hashKeyName, $currentNetTlsDefaultVersionObject)
}
return $allTlsObjects
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-DotNetDllFileVersions/Get-DotNetDllFileVersions.ps1
Function Get-DotNetDllFileVersions {
[CmdletBinding()]
param(
[string]$ComputerName,
[array]$FileNames,
[scriptblock]$CatchActionFunction
)
#Function Version 1.1
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Invoke-RegistryGetValue/Invoke-RegistryGetValue.ps1
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Invoke-ScriptBlockHandler/Invoke-ScriptBlockHandler.ps1
#>
Write-VerboseWriter("Calling: Get-DotNetDllFileVersions")
Function ScriptBlock-GetItem{
param(
[string]$FilePath
)
$getItem = Get-Item $FilePath
$returnObject = ([PSCustomObject]@{
GetItem = $getItem
LastWriteTimeUtc = $getItem.LastWriteTimeUtc
VersionInfo = ([PSCustomObject]@{
FileMajorPart = $getItem.VersionInfo.FileMajorPart
FileMinorPart = $getItem.VersionInfo.FileMinorPart
FileBuildPart = $getItem.VersionInfo.FileBuildPart
FilePrivatePart = $getItem.VersionInfo.FilePrivatePart
})
})
return $returnObject
}
$dotNetInstallPath = Invoke-RegistryGetValue -MachineName $ComputerName -SubKey "SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" -GetValue "InstallPath" -CatchActionFunction $CatchActionFunction
if ($dotNetInstallPath -eq [string]::Empty)
{
Write-VerboseWriter("Failed to determine .NET install path")
return
}
$files = @{}
foreach($filename in $FileNames)
{
Write-VerboseWriter("Query .NET DLL information for machine: {0}" -f $ComputerName)
$getItem = Invoke-ScriptBlockHandler -ComputerName $ComputerName -ScriptBlock ${Function:ScriptBlock-GetItem} -ArgumentList ("{0}\{1}" -f $dotNetInstallPath, $filename) -CatchActionFunction $CatchActionFunction
$files.Add($filename, $getItem)
}
return $files
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-ExchangeMajorVersion/Get-ExchangeMajorVersion.ps1
Function Get-ExchangeMajorVersion {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][object]$AdminDisplayVersion
)
#Function Version 1.0
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
#>
Write-VerboseWriter("Calling: Get-ExchangeMajorVersion")
Write-VerboseWriter("Passed: {0}" -f $AdminDisplayVersion.ToString())
if($AdminDisplayVersion.GetType().Name -eq "string")
{
$split = $AdminDisplayVersion.Substring(($AdminDisplayVersion.IndexOf(" ")) + 1, 4).split('.')
$build = [int]$split[0] + ($split[1] / 10)
}
else
{
$build = $AdminDisplayVersion.Major + ($AdminDisplayVersion.Minor / 10)
}
Write-VerboseWriter("Determing build based off of: {0}" -f $build)
$exchangeMajorVersion = [string]::Empty
switch($build)
{
14.3 {$exchangeMajorVersion = "Exchange2010"}
15 {$exchangeMajorVersion = "Exchange2013"}
15.1 {$exchangeMajorVersion = "Exchange2016"}
15.2 {$exchangeMajorVersion = "Exchange2019"}
default {$exchangeMajorVersion = "Unknown"}
}
Write-VerboseWriter("Returned: {0}" -f $exchangeMajorVersion)
return $exchangeMajorVersion
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-NETFrameworkVersion/Get-NETFrameworkVersion.ps1
Function Get-NETFrameworkVersion {
[CmdletBinding()]
param(
[string]$MachineName = $env:COMPUTERNAME,
[int]$NetVersionKey = -1,
[scriptblock]$CatchActionFunction
)
#Function Version 1.0
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Invoke-RegistryGetValue/Invoke-RegistryGetValue.ps1
#>
Write-VerboseWriter("Calling: Get-NETFrameworkVersion")
if ($NetVersionKey -eq -1)
{
[int]$NetVersionKey = Invoke-RegistryGetValue -MachineName $MachineName `
-SubKey "SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" `
-GetValue "Release" `
-CatchActionFunction $CatchActionFunction
}
#Using Minimum Version as per https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed?redirectedfrom=MSDN#minimum-version
if ($NetVersionKey -lt 378389)
{
$friendlyName = "Unknown"
$minValue = -1
}
elseif ($NetVersionKey -lt 378675)
{
$friendlyName = "4.5"
$minValue = 378389
}
elseif ($NetVersionKey -lt 379893)
{
$friendlyName = "4.5.1"
$minValue = 378675
}
elseif ($NetVersionKey -lt 393295)
{
$friendlyName = "4.5.2"
$minValue = 379893
}
elseif ($NetVersionKey -lt 394254)
{
$friendlyName = "4.6"
$minValue = 393295
}
elseif ($NetVersionKey -lt 394802)
{
$friendlyName = "4.6.1"
$minValue = 394254
}
elseif ($NetVersionKey -lt 460798)
{
$friendlyName = "4.6.2"
$minValue = 394802
}
elseif ($NetVersionKey -lt 461308)
{
$friendlyName = "4.7"
$minValue = 460798
}
elseif ($NetVersionKey -lt 461808)
{
$friendlyName = "4.7.1"
$minValue = 461308
}
elseif ($NetVersionKey -lt 528040)
{
$friendlyName = "4.7.2"
$minValue = 461808
}
elseif ($NetVersionKey -ge 528040)
{
$friendlyName = "4.8"
$minValue = 528040
}
$netObject = New-Object PSCustomObject
$netObject | Add-Member -MemberType NoteProperty -Name "FriendlyName" -Value $friendlyName
$netObject | Add-Member -MemberType NoteProperty -Name "RegistryValue" -Value $NetVersionKey
$netObject | Add-Member -MemberType NoteProperty -Name "MinimumValue" -Value $minValue
Write-VerboseWriter("Returning FriendlyName: {0} | RegistryValue: {1}" -f $friendlyName, $NetVersionKey)
return $netObject
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-ProcessorInformation/Get-ProcessorInformation.ps1
Function Get-ProcessorInformation {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$MachineName,
[Parameter(Mandatory=$false)][scriptblock]$CatchActionFunction
)
#Function Version 1.0
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-WmiObjectHandler/Get-WmiObjectHandler.ps1
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Invoke-ScriptBlockHandler/Invoke-ScriptBlockHandler.ps1
#>
Write-VerboseWriter("Calling: Get-ProcessorInformation")
$wmiObject = Get-WmiObjectHandler -ComputerName $MachineName -Class "Win32_Processor" -CatchActionFunction $CatchActionFunction
Write-VerboseWriter("Processor object type: {0}" -f ($wmiObjectType = $wmiObject.GetType().Name))
$multiProcessorsDetected = $false
if($wmiObjectType -eq "ManagementObject")
{
$processorName = $wmiObject.Name
$maxClockSpeed = $wmiObject.MaxClockSpeed
$multiProcessorsDetected = $true
}
else
{
$processorName = $wmiObject[0].Name
$maxClockSpeed = $wmiObject[0].MaxClockSpeed
}
Write-VerboseWriter("Getting the total number of cores in the processor(s)")
$processorIsThrottled = $false
$currentClockSpeed = 0
$previousProcessor = $null
$differentProcessorsDetected = $false
$differentProcessorCoreCountDetected = $false
foreach($processor in $wmiObject)
{
$numberOfPhysicalCores += $processor.NumberOfCores
$numberOfLogicalCores += $processor.NumberOfLogicalProcessors
$numberOfProcessors++
if($processor.CurrentClockSpeed -lt $processor.MaxClockSpeed)
{
Write-VerboseWriter("Processor is being throttled")
$processorIsThrottled = $true
$currentClockSpeed = $processor.CurrentClockSpeed
}
if($previousProcessor -ne $null)
{
if($processor.Name -ne $previousProcessor.Name -or
$processor.MaxClockSpeed -ne $previousProcessor.MaxMegacyclesPerCore)
{
Write-VerboseWriter("Different Processors are detected!!! This is an issue.")
$differentProcessorsDetected = $true
}
if($processor.NumberOfLogicalProcessors -ne $previousProcessor.NumberOfLogicalProcessors)
{
Write-VerboseWriter("Different Processor core count per processor socket detected. This is an issue.")
$differentProcessorCoreCountDetected = $true
}
}
$previousProcessor = $processor
}
Write-VerboseWriter("NumberOfPhysicalCores: {0} | NumberOfLogicalCores: {1} | NumberOfProcessors: {2} | ProcessorIsThrottled: {3} | CurrentClockSpeed: {4} | DifferentProcessorsDetected: {5} | DifferentProcessorCoreCountDetected: {6}" -f $numberOfPhysicalCores,
$numberOfLogicalCores, $numberOfProcessors, $processorIsThrottled, $currentClockSpeed, $differentProcessorsDetected, $differentProcessorCoreCountDetected)
$presentedProcessorCoreCount = Invoke-ScriptBlockHandler -ComputerName $MachineName -ScriptBlock {[System.Environment]::ProcessorCount} -ScriptBlockDescription "Trying to get the System.Environment ProcessorCount" -CatchActionFunction $CatchActionFunction
if($presentedProcessorCoreCount -eq $null)
{
Write-VerboseWriter("Wasn't able to get Presented Processor Core Count on the Server. Setting to -1.")
$presentedProcessorCoreCount = -1
}
else
{
Write-VerboseWriter("Presented Processor Core Count: {0}" -f $presentedProcessorCoreCount)
}
$processorInformationObject = New-Object PSCustomObject
$processorInformationObject | Add-Member -MemberType NoteProperty -Name "Name" -Value $processorName
$processorInformationObject | Add-Member -MemberType NoteProperty -Name "NumberOfPhysicalCores" -Value $numberOfPhysicalCores
$processorInformationObject | Add-Member -MemberType NoteProperty -Name "NumberOfLogicalCores" -Value $numberOfLogicalCores
$processorInformationObject | Add-Member -MemberType NoteProperty -Name "NumberOfProcessors" -Value $numberOfProcessors
$processorInformationObject | Add-Member -MemberType NoteProperty -Name "MaxMegacyclesPerCore" -Value $maxClockSpeed
$processorInformationObject | Add-Member -MemberType NoteProperty -Name "CurrentMegacyclesPerCore" -Value $currentClockSpeed
$processorInformationObject | Add-Member -MemberType NoteProperty -Name "ProcessorIsThrottled" -Value $processorIsThrottled
$processorInformationObject | Add-Member -MemberType NoteProperty -Name "DifferentProcessorsDetected" -Value $differentProcessorsDetected
$processorInformationObject | Add-Member -MemberType NoteProperty -Name "DifferentProcessorCoreCountDetected" -Value $differentProcessorCoreCountDetected
$processorInformationObject | Add-Member -MemberType NoteProperty -Name "EnvironmentProcessorCount" -Value $presentedProcessorCoreCount
$processorInformationObject | Add-Member -MemberType NoteProperty -Name "ProcessorClassObject" -Value $wmiObject
Write-VerboseWriter("Exiting: Get-ProcessorInformation")
return $processorInformationObject
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-ServerOperatingSystemVersion/Get-ServerOperatingSystemVersion.ps1
Function Get-ServerOperatingSystemVersion {
[CmdletBinding()]
param(
[string]$OsCaption
)
#Function Version 1.5
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
#>
if($OsCaption -eq [string]::Empty -or
$OsCaption -eq $null)
{
Write-VerboseWriter("Getting the local machine version build number")
$OsCaption = (Get-WmiObject -Class Win32_OperatingSystem).Caption
Write-VerboseWriter("Got '{0}' for the caption" -f $OsCaption)
}
else
{
Write-VerboseWriter("Passed - [string]OsCaption : {0}" -f $OsCaption)
}
$osReturnValue = [string]::Empty
switch -Wildcard ($OsCaption)
{
"*Server 2008 R2*" {$osReturnValue = "Windows2008R2"; break}
"*Server 2008*" {$osReturnValue = "Windows2008"}
"*Server 2012 R2*" {$osReturnValue = "Windows2012R2"; break}
"*Server 2012*" {$osReturnValue = "Windows2012"}
"*Server 2016*" {$osReturnValue = "Windows2016"}
"*Server 2019*" {$osReturnValue = "Windows2019"}
"Microsoft Windows Server Standard" {$osReturnValue = "WindowsCore"}
"Microsoft Windows Server Datacenter" {$osReturnValue = "WindowsCore"}
default {$osReturnValue = "Unknown"}
}
Write-VerboseWriter("Returned: {0}" -f $osReturnValue)
return [string]$osReturnValue
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-ServerRebootPending/Get-ServerRebootPending.ps1
Function Get-ServerRebootPending {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$ServerName,
[Parameter(Mandatory=$false)][scriptblock]$CatchActionFunction
)
#Function Version 1.0
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Invoke-ScriptBlockHandler/Invoke-ScriptBlockHandler.ps1
#>
Function Get-PendingFileReboot {
try
{
if((Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\" -Name PendingFileRenameOperations -ErrorAction Stop))
{
return $true
}
else
{
return $false
}
}
catch
{
throw
}
}
Function Get-PendingSCCMReboot {
try
{
$sccmReboot = Invoke-CimMethod -Namespace 'Root\ccm\clientSDK' -ClassName 'CCM_ClientUtilities' -Name 'DetermineIfRebootPending' -ErrorAction Stop
if($sccmReboot -and ($sccmReboot.RebootPending -or $sccmReboot.IsHardRebootPending))
{
return $true
}
}
catch
{
throw
}
}
Function Get-PathTestingReboot {
param(
[string]$TestingPath
)
if(Test-Path $TestingPath)
{
return $true
}
else
{
return $false
}
}
Write-VerboseWriter("Calling: Get-ServerRebootPending")
if(Invoke-ScriptBlockHandler -ComputerName $ServerName -ScriptBlock ${Function:Get-PendingFileReboot} -ScriptBlockDescription "Get-PendingFileReboot" -CatchActionFunction $CatchActionFunction)
{
Write-VerboseWriter("Get-PendingFileReboot Determined Reboot is pending. Registry HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\ item properties of PendingFileRenameOperations.")
return $true
}
if(Invoke-ScriptBlockHandler -ComputerName $ServerName -ScriptBlock ${Function:Get-PendingSCCMReboot} -ScriptBlockDescription "Get-PendingSCCMReboot" -CatchActionFunction $CatchActionFunction)
{
Write-VerboseWriter("Get-PendingSCCMReboot determined reboot is pending.")
return $true
}
if(Invoke-ScriptBlockHandler -ComputerName $ServerName -ScriptBlock ${Function:Get-PathTestingReboot} -ScriptBlockDescription "Get-PendingAutoUpdateReboot" -CatchActionFunction $CatchActionFunction -ArgumentList "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending")
{
Write-VerboseWriter("Get-PathTestingReboot for HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending determined reboot is pending")
return $true
}
if(Invoke-ScriptBlockHandler -ComputerName $ServerName -ScriptBlock ${Function:Get-PathTestingReboot} -ScriptBlockDescription "Get-PendingAutoUpdateReboot" -CatchActionFunction $CatchActionFunction -ArgumentList "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired")
{
Write-VerboseWriter("Get-PathTestingReboot for HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired determined reboot is pending")
return $true
}
Write-VerboseWriter("Passed all reboot checks.")
return $false
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-ServerType/Get-ServerType.ps1
Function Get-ServerType {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$ServerType
)
#Function Version 1.0
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
#>
Write-VerboseWriter("Calling: Get-ServerType")
$returnServerType = [string]::Empty
if($ServerType -like "VMware*") { $returnServerType = "VMware"}
elseif($ServerType -like "*Microsoft Corporation*") { $returnServerType = "HyperV" }
elseif($ServerType.Length -gt 0) {$returnServerType = "Physical"}
else { $returnServerType = "Unknown" }
Write-VerboseWriter("Returning: {0}" -f $returnServerType)
return $returnServerType
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-Smb1ServerSettings/Get-Smb1ServerSettings.ps1
Function Get-Smb1ServerSettings {
[CmdletBinding()]
param(
[string]$ServerName = $env:COMPUTERNAME,
[scriptblock]$CatchActionFunction
)
#Function Version 1.2
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Invoke-ScriptBlockHandler/Invoke-ScriptBlockHandler.ps1
#>
Write-VerboseWriter("Calling: Get-Smb1ServerSettings")
Write-VerboseWriter("Passed ServerName: {0}" -f $ServerName)
$smbServerConfiguration = Invoke-ScriptBlockHandler -ComputerName $ServerName -ScriptBlock {Get-SmbServerConfiguration} -CatchActionFunction $CatchActionFunction -ScriptBlockDescription "Get-SmbServerConfiguration"
<#
Unknown 0
Failed to get Install Setting 1
Install is set to true 2
Install is set to false 4
Failed to get Block Setting 8
SMB1 is not being blocked 16
SMB1 is being blocked 32
#>
$smb1Status = 0
try
{
$windowsFeature = Get-WindowsFeature "FS-SMB1" -ComputerName $ServerName -ErrorAction Stop
}
catch
{
Write-VerboseWriter("Failed to Get-WindowsFeature for FS-SMB1")
if ($CatchActionFunction -ne $null)
{
& $CatchActionFunction
}
}
if ($windowsFeature -eq $null)
{
$smb1Status += 1
}
elseif ($windowsFeature.Installed)
{
$smb1Status += 2
}
else
{
$smb1Status += 4
}
if ($smbServerConfiguration -eq $null)
{
$smb1Status += 8
}
elseif ($smbServerConfiguration.EnableSMB1Protocol)
{
$smb1Status += 16
}
else
{
$smb1Status += 32
}
$smb1ServerSettings = New-Object PSCustomObject
$smb1ServerSettings | Add-Member -MemberType NoteProperty -Name "SmbServerConfiguration" -Value $smbServerConfiguration
$smb1ServerSettings | Add-Member -MemberType NoteProperty -Name "WindowsFeature" -Value $windowsFeature
$smb1ServerSettings | Add-Member -MemberType NoteProperty -Name "Smb1Status" -Value $smb1Status
return $smb1ServerSettings
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-TimeZoneInformationRegistrySettings/Get-TimeZoneInformationRegistrySettings.ps1
Function Get-TimeZoneInformationRegistrySettings {
[CmdletBinding()]
param(
[string]$MachineName = $env:COMPUTERNAME,
[scriptblock]$CatchActionFunction
)
#Function Version 1.0
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Invoke-RegistryGetValue/Invoke-RegistryGetValue.ps1
#>
Write-VerboseWriter("Calling: Get-TimeZoneInformationRegistrySettings")
Write-VerboseWriter("Passed: [string]MachineName: {0}" -f $MachineName)
$timeZoneInformationSubKey = "SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
$dynamicDaylightTimeDisabled = Invoke-RegistryGetValue -MachineName $MachineName -SubKey $timeZoneInformationSubKey -GetValue "DynamicDaylightTimeDisabled" -CatchActionFunction $CatchActionFunction
$timeZoneKeyName = Invoke-RegistryGetValue -MachineName $MachineName -Subkey $timeZoneInformationSubKey -GetValue "TimeZoneKeyName" -CatchActionFunction $CatchActionFunction
$standardStart = Invoke-RegistryGetValue -MachineName $MachineName -SubKey $timeZoneInformationSubKey -GetValue "StandardStart" -CatchActionFunction $CatchActionFunction
$daylightStart = Invoke-RegistryGetValue -MachineName $MachineName -SubKey $timeZoneInformationSubKey -GetValue "DaylightStart" -CatchActionFunction $CatchActionFunction
$timeZoneInformationObject = New-Object PSCustomObject
$timeZoneInformationObject | Add-Member -MemberType NoteProperty -Name "DynamicDaylightTimeDisabled" -Value $dynamicDaylightTimeDisabled
$timeZoneInformationObject | Add-Member -MemberType NoteProperty -Name "TimeZoneKeyName" -Value $timeZoneKeyName
$timeZoneInformationObject | Add-Member -MemberType NoteProperty -Name "StandardStart" -Value $standardStart
$timeZoneInformationObject | Add-Member -MemberType NoteProperty -Name "DaylightStart" -Value $daylightStart
$actionsToTake = @()
if($timeZoneKeyName -eq $null -or
[string]::IsNullOrEmpty($timeZoneKeyName))
{
Write-VerboseWriter("TimeZoneKeyName is null or empty. Action should be taken to address this.")
$actionsToTake += "TimeZoneKeyName is blank. Need to switch your current time zone to a different value, then switch it back to have this value populated again."
}
foreach($value in $standardStart)
{
if($value -ne 0)
{
$standardStartNonZeroValue = $true
break
}
}
foreach($value in $daylightStart)
{
if($value -ne 0)
{
$daylightStartNonZeroValue = $true
break
}
}
if($dynamicDaylightTimeDisabled -ne 0 -and (
$standardStartNonZeroValue -or
$daylightStartNonZeroValue
))
{
Write-VerboseWriter("Determined that there is a chance the settings set could cause a DST issue.")
$dstIssueDetected = $true
$actionsToTake += "High Warning: DynamicDaylightTimeDisabled is set, Windows can not properly detect any DST rule changes in your time zone. `
It is possible that you could be running into this issue. Set 'Adjust for daylight saving time automatically to on'"
}
elseif($dynamicDaylightTimeDisabled -ne 0)
{
Write-VerboseWriter("Daylight savings auto adjustment is disabled.")
$actionsToTake += "Warning: DynamicDaylightTimeDisabled is set, Windows can not properly detect any DST rule changes in your time zone."
}
$timeZoneInformationObject | Add-Member -MemberType NoteProperty -Name "DstIssueDetected" -Value $dstIssueDetected
$timeZoneInformationObject | Add-Member -MemberType NoteProperty -Name "ActionsToTake" -Value $actionsToTake
return $timeZoneInformationObject
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Get-WmiObjectHandler/Get-WmiObjectHandler.ps1
Function Get-WmiObjectHandler {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)][string]$ComputerName = $env:COMPUTERNAME,
[Parameter(Mandatory=$true)][string]$Class,
[Parameter(Mandatory=$false)][string]$Filter,
[Parameter(Mandatory=$false)][string]$Namespace,
[Parameter(Mandatory=$false)][scriptblock]$CatchActionFunction
)
#Function Version 1.0
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
#>
Write-VerboseWriter("Calling: Get-WmiObjectHandler")
Write-VerboseWriter("Passed: [string]ComputerName: {0} | [string]Class: {1} | [string]Filter: {2} | [string]Namespace: {3}" -f $ComputerName, $Class, $Filter, $Namespace)
$execute = @{
ComputerName = $ComputerName
Class = $Class
}
if(![string]::IsNullOrEmpty($Filter))
{
$execute.Add("Filter", $Filter)
}
if(![string]::IsNullOrEmpty($Namespace))
{
$execute.Add("Namespace", $Namespace)
}
try
{
$wmi = Get-WmiObject @execute -ErrorAction Stop
return $wmi
}
catch
{
Write-VerboseWriter("Failed to run Get-WmiObject object on class '{0}'" -f $Class)
if($CatchActionFunction -ne $null)
{
& $CatchActionFunction
}
}
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Invoke-RegistryGetValue/Invoke-RegistryGetValue.ps1
Function Invoke-RegistryGetValue {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)][string]$RegistryHive = "LocalMachine",
[Parameter(Mandatory=$true)][string]$MachineName,
[Parameter(Mandatory=$true)][string]$SubKey,
[Parameter(Mandatory=$false)][string]$GetValue,
[Parameter(Mandatory=$false)][bool]$ReturnAfterOpenSubKey,
[Parameter(Mandatory=$false)][object]$DefaultValue,
[Parameter(Mandatory=$false)][scriptblock]$CatchActionFunction
)
#Function Version 1.2
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
#>
Write-VerboseWriter("Calling: Invoke-RegistryGetValue")
try
{
Write-VerboseWriter("Attempting to open the Base Key '{0}' on Server '{1}'" -f $RegistryHive, $MachineName)
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($RegistryHive, $MachineName)
Write-VerboseWriter("Attempting to open the Sub Key '{0}'" -f $SubKey)
$RegKey= $Reg.OpenSubKey($SubKey)
if ($ReturnAfterOpenSubKey)
{
Write-VerboseWriter("Returning OpenSubKey")
return $RegKey
}
Write-VerboseWriter("Attempting to get the value '{0}'" -f $GetValue)
$returnGetValue = $RegKey.GetValue($GetValue)
if ($null -eq $returnGetValue -and
$null -ne $DefaultValue)
{
Write-VerboseWriter("No value found in the registry. Setting to default value: {0}" -f $DefaultValue)
$returnGetValue = $DefaultValue
}
Write-VerboseWriter("Exiting: Invoke-RegistryHandler | Returning: {0}" -f $returnGetValue)
return $returnGetValue
}
catch
{
if ($CatchActionFunction -ne $null)
{
& $CatchActionFunction
}
Write-VerboseWriter("Failed to open the registry")
}
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Invoke-ScriptBlockHandler/Invoke-ScriptBlockHandler.ps1
Function Invoke-ScriptBlockHandler {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$ComputerName,
[Parameter(Mandatory=$true)][scriptblock]$ScriptBlock,
[Parameter(Mandatory=$false)][string]$ScriptBlockDescription,
[Parameter(Mandatory=$false)][object]$ArgumentList,
[Parameter(Mandatory=$false)][bool]$IncludeNoProxyServerOption = $true, #Default in HealthChecker
[Parameter(Mandatory=$false)][scriptblock]$CatchActionFunction
)
#Function Version 1.1
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
#>
Write-VerboseWriter("Calling: Invoke-ScriptBlockHandler")
if(![string]::IsNullOrEmpty($ScriptBlockDescription))
{
Write-VerboseWriter($ScriptBlockDescription)
}
try
{
if($ComputerName -ne $env:COMPUTERNAME)
{
$params = @{
ComputerName = $ComputerName
ScriptBlock = $ScriptBlock
ErrorAction = "Stop"
}
if ($IncludeNoProxyServerOption)
{
Write-VerboseWriter("Including SessionOption")
$params.Add("SessionOption", (New-PSSessionOption -ProxyAccessType NoProxyServer))
}
if($ArgumentList -ne $null)
{
$params.Add("ArgumentList", $ArgumentList)
Write-VerboseWriter("Running Invoke-Command with argument list.")
}
else
{
Write-VerboseWriter("Running Invoke-Command without argument list.")
}
$invokeReturn = Invoke-Command @params
return $invokeReturn
}
else
{
if($ArgumentList -ne $null)
{
Write-VerboseWriter("Running Script Block locally with argument list.")
$localReturn = & $ScriptBlock $ArgumentList
}
else
{
Write-VerboseWriter("Running Script Block locally without argument list.")
$localReturn = & $ScriptBlock
}
return $localReturn
}
}
catch
{
Write-VerboseWriter("Failed to Invoke-ScriptBlockHandler")
if($CatchActionFunction -ne $null)
{
& $CatchActionFunction
}
}
}
Function Get-ExchangeAppPoolsInformation {
Write-VerboseOutput("Calling: Get-ExchangeAppPoolsInformation")
Function Get-ExchangeAppPoolsScriptBlock
{
$windir = $env:windir
$Script:appCmd = "{0}\system32\inetsrv\appcmd.exe" -f $windir
$appPools = &$Script:appCmd list apppool
$exchangeAppPools = @()
foreach($appPool in $appPools)
{
$startIndex = $appPool.IndexOf('"') + 1
$appPoolName = $appPool.Substring($startIndex, ($appPool.Substring($startIndex).IndexOf('"')))
if($appPoolName.StartsWith("MSExchange"))
{
$exchangeAppPools += $appPoolName
}
}
$exchAppPools = @{}
foreach($appPool in $exchangeAppPools)
{
$status = &$Script:appCmd list apppool $appPool /text:state
$config = &$Script:appCmd list apppool $appPool /text:CLRConfigFile
if(!([System.String]::IsNullOrEmpty($config)) -and
(Test-Path $config))
{
$content = Get-Content $config
}
else
{
$content = $null
}
$statusObj = New-Object pscustomobject
$statusObj | Add-Member -MemberType NoteProperty -Name "Status" -Value $status
$statusObj | Add-Member -MemberType NoteProperty -Name "ConfigPath" -Value $config
$statusObj | Add-Member -MemberType NoteProperty -Name "Content" -Value $content
$exchAppPools.Add($appPool, $statusObj)
}
return $exchAppPools
}
$exchangeAppPoolsInfo = Invoke-ScriptBlockHandler -ComputerName $Script:Server -ScriptBlock ${Function:Get-ExchangeAppPoolsScriptBlock} -ScriptBlockDescription "Getting Exchange App Pool information" -CatchActionFunction ${Function:Invoke-CatchActions}
Write-VerboseOutput("Exiting: Get-ExchangeAppPoolsInformation")
return $exchangeAppPoolsInfo
}
Function Get-ExchangeInformation {
param(
[HealthChecker.OSServerVersion]$OSMajorVersion
)
Write-VerboseOutput("Calling: Get-ExchangeInformation")
Write-VerboseOutput("Passed: OSMajorVersion: {0}" -f $OSMajorVersion)
[HealthChecker.ExchangeInformation]$exchangeInformation = New-Object -TypeName HealthChecker.ExchangeInformation
$exchangeInformation.GetExchangeServer = (Get-ExchangeServer -Identity $Script:Server)
$exchangeInformation.ExchangeCertificates = Get-ExchangeServerCertificates
$buildInformation = $exchangeInformation.BuildInformation
$buildInformation.MajorVersion = ([HealthChecker.ExchangeMajorVersion](Get-ExchangeMajorVersion -AdminDisplayVersion $exchangeInformation.GetExchangeServer.AdminDisplayVersion))
$buildInformation.ServerRole = (Get-ServerRole -ExchangeServerObj $exchangeInformation.GetExchangeServer)
$buildInformation.ExchangeSetup = Get-ExSetupDetails
#Exchange 2013 or greater
if($buildInformation.MajorVersion -ge [HealthChecker.ExchangeMajorVersion]::Exchange2013)
{
$netFrameworkExchange = $exchangeInformation.NETFramework
$adminDisplayVersion = $exchangeInformation.GetExchangeServer.AdminDisplayVersion
$revisionNumber = if($adminDisplayVersion.Revision -lt 10) {$adminDisplayVersion.Revision / 10} else {$adminDisplayVersion.Revision / 100 }
$buildAndRevision = $adminDisplayVersion.Build + $revisionNumber
Write-VerboseOutput("The build and revision number: {0}" -f $buildAndRevision)
#Build Numbers: https://docs.microsoft.com/en-us/Exchange/new-features/build-numbers-and-release-dates?view=exchserver-2019
$buildInformation.BuildNumber = "{0}.{1}.{2}.{3}" -f $adminDisplayVersion.Major, $adminDisplayVersion.Minor, $adminDisplayVersion.Build, $adminDisplayVersion.Revision
if($buildInformation.MajorVersion -eq [HealthChecker.ExchangeMajorVersion]::Exchange2019)
{
Write-VerboseOutput("Exchange 2019 is detected. Checking build number...")
$buildInformation.FriendlyName = "Exchange 2019 "
#Exchange 2019 Information
if($buildAndRevision -lt 221.12) { $buildInformation.CU = [HealthChecker.ExchangeCULevel]::Preview; $buildInformation.FriendlyName += "Preview"; $buildInformation.ReleaseDate = "07/24/2018" }
elseif($buildAndRevision -lt 330.6) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::RTM; $buildInformation.FriendlyName += "RTM"; $buildInformation.ReleaseDate = "10/22/2018" }
elseif($buildAndRevision -lt 397.3) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU1; $buildInformation.FriendlyName += "CU1"; $buildInformation.ReleaseDate = "02/12/2019"}
elseif($buildAndRevision -lt 464.5) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU2; $buildInformation.FriendlyName += "CU2"; $buildInformation.ReleaseDate = "06/18/2019"}
elseif($buildAndRevision -lt 529.5) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU3; $buildInformation.FriendlyName += "CU3"; $buildInformation.ReleaseDate = "09/17/2019"}
elseif($buildAndRevision -lt 595.3) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU4; $buildInformation.FriendlyName += "CU4"; $buildInformation.ReleaseDate = "12/17/2019"}
elseif($buildAndRevision -lt 659.4) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU5; $buildInformation.FriendlyName += "CU5"; $buildInformation.ReleaseDate = "03/17/2020"}
elseif($buildAndRevision -lt 721.2) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU6; $buildInformation.FriendlyName += "CU6"; $buildInformation.ReleaseDate = "06/16/2020"}
elseif($buildAndRevision -lt 792.3) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU7; $buildInformation.FriendlyName += "CU7"; $buildInformation.ReleaseDate = "09/15/2020"; $buildInformation.SupportedBuild = $true}
elseif($buildAndRevision -ge 792.3) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU8; $buildInformation.FriendlyName += "CU8"; $buildInformation.ReleaseDate = "12/15/2020"; $buildInformation.SupportedBuild = $true}
#Exchange 2019 .NET Information
if($buildInformation.CU -lt [HealthChecker.ExchangeCULevel]::CU2){$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d7d2; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d7d2}
elseif($buildInformation.CU -lt [HealthChecker.ExchangeCULevel]::CU4){$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d7d2; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d8}
else { $netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d8; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d8 }
}
elseif($buildInformation.MajorVersion -eq [HealthChecker.ExchangeMajorVersion]::Exchange2016)
{
Write-VerboseOutput("Exchange 2016 is detected. Checking build number...")
$buildInformation.FriendlyName = "Exchange 2016 "
#Exchange 2016 Information
if($buildAndRevision -lt 225.42) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::Preview; $buildInformation.FriendlyName += "Preview"; $buildInformation.ReleaseDate = "07/22/2015"}
elseif($buildAndRevision -lt 396.30) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::RTM; $buildInformation.FriendlyName += "RTM"; $buildInformation.ReleaseDate = "10/01/2015"}
elseif($buildAndRevision -lt 466.34) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU1; $buildInformation.FriendlyName += "CU1"; $buildInformation.ReleaseDate = "03/15/2016"}
elseif($buildAndRevision -lt 544.27) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU2; $buildInformation.FriendlyName += "CU2"; $buildInformation.ReleaseDate = "06/21/2016"}
elseif($buildAndRevision -lt 669.32) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU3; $buildInformation.FriendlyName += "CU3"; $buildInformation.ReleaseDate = "09/20/2016"}
elseif($buildAndRevision -lt 845.34) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU4; $buildInformation.FriendlyName += "CU4"; $buildInformation.ReleaseDate = "12/13/2016"}
elseif($buildAndRevision -lt 1034.26) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU5; $buildInformation.FriendlyName += "CU5"; $buildInformation.ReleaseDate = "03/21/2017"}
elseif($buildAndRevision -lt 1261.35) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU6; $buildInformation.FriendlyName += "CU6"; $buildInformation.ReleaseDate = "06/24/2017"}
elseif($buildAndRevision -lt 1415.2) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU7; $buildInformation.FriendlyName += "CU7"; $buildInformation.ReleaseDate = "09/16/2017"}
elseif($buildAndRevision -lt 1466.3) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU8; $buildInformation.FriendlyName += "CU8"; $buildInformation.ReleaseDate = "12/19/2017"}
elseif($buildAndRevision -lt 1531.3) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU9; $buildInformation.FriendlyName += "CU9"; $buildInformation.ReleaseDate = "03/20/2018"}
elseif($buildAndRevision -lt 1591.10) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU10; $buildInformation.FriendlyName += "CU10"; $buildInformation.ReleaseDate = "06/19/2018"}
elseif($buildAndRevision -lt 1713.5) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU11; $buildInformation.FriendlyName += "CU11"; $buildInformation.ReleaseDate = "10/16/2018"}
elseif($buildAndRevision -lt 1779.2) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU12; $buildInformation.FriendlyName += "CU12"; $buildInformation.ReleaseDate = "02/12/2019"}
elseif($buildAndRevision -lt 1847.3) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU13; $buildInformation.FriendlyName += "CU13"; $buildInformation.ReleaseDate = "06/18/2019"}
elseif($buildAndRevision -lt 1913.5) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU14; $buildInformation.FriendlyName += "CU14"; $buildInformation.ReleaseDate = "09/17/2019"}
elseif($buildAndRevision -lt 1979.3) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU15; $buildInformation.FriendlyName += "CU15"; $buildInformation.ReleaseDate = "12/17/2019"}
elseif($buildAndRevision -lt 2044.4) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU16; $buildInformation.FriendlyName += "CU16"; $buildInformation.ReleaseDate = "03/17/2020"}
elseif($buildAndRevision -lt 2106.2) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU17; $buildInformation.FriendlyName += "CU17"; $buildInformation.ReleaseDate = "06/16/2020"}
elseif($buildAndRevision -lt 2176.2) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU18; $buildInformation.FriendlyName += "CU18"; $buildInformation.ReleaseDate = "09/15/2020"; $buildInformation.SupportedBuild = $true}
elseif($buildAndRevision -ge 2176.2) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU19; $buildInformation.FriendlyName += "CU19"; $buildInformation.ReleaseDate = "12/15/2020"; $buildInformation.SupportedBuild = $true}
#Exchange 2016 .NET Information
if($buildInformation.CU -lt [HealthChecker.ExchangeCULevel]::CU2){$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d5d2wFix; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d5d2wFix}
elseif($buildInformation.CU -eq [HealthChecker.ExchangeCULevel]::CU2){$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d5d2wFix; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d6d1wFix}
elseif($buildInformation.CU -eq [HealthChecker.ExchangeCULevel]::CU3)
{
if($OSMajorVersion -eq [HealthChecker.OSServerVersion]::Windows2016)
{
$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d5d2wFix
$netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d6d2
}
else
{
$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d5d2wFix
$netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d6d1wFix
}
}
elseif($buildInformation.CU -eq [HealthChecker.ExchangeCULevel]::CU4) {$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d5d2wFix; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d6d2}
elseif($buildInformation.CU -lt [HealthChecker.ExchangeCULevel]::CU8) {$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d6d2; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d6d2}
elseif($buildInformation.CU -lt [HealthChecker.ExchangeCULevel]::CU11) {$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d6d2; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d7d1}
elseif($buildInformation.CU -lt [HealthChecker.ExchangeCULevel]::CU13) {$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d7d1; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d7d2}
elseif($buildInformation.CU -lt [HealthChecker.ExchangeCULevel]::CU15) {$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d7d2; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d8}
else {$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d8; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d8}
}
else
{
Write-VerboseOutput("Exchange 2013 is detected. Checking build number...")
$buildInformation.FriendlyName = "Exchange 2013 "
#Exchange 2013 Information
if($buildAndRevision -lt 620.29) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::RTM; $buildInformation.FriendlyName += "RTM"; $buildInformation.ReleaseDate = "12/03/2012"}
elseif($buildAndRevision -lt 712.24) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU1; $buildInformation.FriendlyName += "CU1"; $buildInformation.ReleaseDate = "04/02/2013"}
elseif($buildAndRevision -lt 775.38) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU2; $buildInformation.FriendlyName += "CU2"; $buildInformation.ReleaseDate = "07/09/2013"}
elseif($buildAndRevision -lt 847.32) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU3; $buildInformation.FriendlyName += "CU3"; $buildInformation.ReleaseDate = "11/25/2013"}
elseif($buildAndRevision -lt 913.22) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU4; $buildInformation.FriendlyName += "CU4"; $buildInformation.ReleaseDate = "02/25/2014"}
elseif($buildAndRevision -lt 995.29) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU5; $buildInformation.FriendlyName += "CU5"; $buildInformation.ReleaseDate = "05/27/2014"}
elseif($buildAndRevision -lt 1044.25) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU6; $buildInformation.FriendlyName += "CU6"; $buildInformation.ReleaseDate = "08/26/2014"}
elseif($buildAndRevision -lt 1076.9) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU7; $buildInformation.FriendlyName += "CU7"; $buildInformation.ReleaseDate = "12/09/2014"}
elseif($buildAndRevision -lt 1104.5) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU8; $buildInformation.FriendlyName += "CU8"; $buildInformation.ReleaseDate = "03/17/2015"}
elseif($buildAndRevision -lt 1130.7) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU9; $buildInformation.FriendlyName += "CU9"; $buildInformation.ReleaseDate = "06/17/2015"}
elseif($buildAndRevision -lt 1156.6) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU10; $buildInformation.FriendlyName += "CU10"; $buildInformation.ReleaseDate = "09/15/2015"}
elseif($buildAndRevision -lt 1178.4) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU11; $buildInformation.FriendlyName += "CU11"; $buildInformation.ReleaseDate = "12/15/2015"}
elseif($buildAndRevision -lt 1210.3) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU12; $buildInformation.FriendlyName += "CU12"; $buildInformation.ReleaseDate = "03/15/2016"}
elseif($buildAndRevision -lt 1236.3) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU13; $buildInformation.FriendlyName += "CU13"; $buildInformation.ReleaseDate = "06/21/2016"}
elseif($buildAndRevision -lt 1263.5) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU14; $buildInformation.FriendlyName += "CU14"; $buildInformation.ReleaseDate = "09/20/2016"}
elseif($buildAndRevision -lt 1293.2) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU15; $buildInformation.FriendlyName += "CU15"; $buildInformation.ReleaseDate = "12/13/2016"}
elseif($buildAndRevision -lt 1320.4) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU16; $buildInformation.FriendlyName += "CU16"; $buildInformation.ReleaseDate = "03/21/2017"}
elseif($buildAndRevision -lt 1347.2) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU17; $buildInformation.FriendlyName += "CU17"; $buildInformation.ReleaseDate = "06/24/2017"}
elseif($buildAndRevision -lt 1365.1) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU18; $buildInformation.FriendlyName += "CU18"; $buildInformation.ReleaseDate = "09/16/2017"}
elseif($buildAndRevision -lt 1367.3) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU19; $buildInformation.FriendlyName += "CU19"; $buildInformation.ReleaseDate = "12/19/2017"}
elseif($buildAndRevision -lt 1395.4) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU20; $buildInformation.FriendlyName += "CU20"; $buildInformation.ReleaseDate = "03/20/2018"}
elseif($buildAndRevision -lt 1473.3) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU21; $buildInformation.FriendlyName += "CU21"; $buildInformation.ReleaseDate = "06/19/2018"}
elseif($buildAndRevision -lt 1497.2) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU22; $buildInformation.FriendlyName += "CU22"; $buildInformation.ReleaseDate = "02/12/2019"}
elseif($buildAndRevision -ge 1497.2) {$buildInformation.CU = [HealthChecker.ExchangeCULevel]::CU23; $buildInformation.FriendlyName += "CU23"; $buildInformation.ReleaseDate = "06/18/2019"; $buildInformation.SupportedBuild = $true}
#Exchange 2013 .NET Information
if($buildInformation.CU -lt [HealthChecker.ExchangeCULevel]::CU12){$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d5d2wFix; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d5d2wFix}
elseif($buildInformation.CU -lt [HealthChecker.ExchangeCULevel]::CU15) {$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d5d2wFix; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d6d1wFix}
elseif($buildInformation.CU -eq [HealthChecker.ExchangeCULevel]::CU15) {$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d5d2wFix; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d6d2}
elseif($buildInformation.CU -lt [HealthChecker.ExchangeCULevel]::CU19) {$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d6d2; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d6d2}
elseif($buildInformation.CU -lt [HealthChecker.ExchangeCULevel]::CU21) {$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d6d2; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d7d1}
elseif($buildInformation.CU -le [HealthChecker.ExchangeCULevel]::CU22) {$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d7d1; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d7d2}
else {$netFrameworkExchange.MinSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d7d2; $netFrameworkExchange.MaxSupportedVersion = [HealthChecker.NetMajorVersion]::Net4d8}
}
$exchangeInformation.MapiHttpEnabled = (Get-OrganizationConfig).MapiHttpEnabled
if ($buildInformation.ServerRole -ne [HealthChecker.ExchangeServerRole]::Edge)
{
$exchangeInformation.ApplicationPools = Get-ExchangeAppPoolsInformation
}
$buildInformation.KBsInstalled = Get-ExchangeUpdates -ExchangeMajorVersion $buildInformation.MajorVersion
$exchangeInformation.RegistryValues.CtsProcessorAffinityPercentage = Invoke-RegistryGetValue -MachineName $Script:Server -SubKey "SOFTWARE\Microsoft\ExchangeServer\v15\Search\SystemParameters" -GetValue "CtsProcessorAffinityPercentage" -CatchActionFunction ${Function:Invoke-CatchActions}
$exchangeInformation.ServerMaintenance = Get-ExchangeServerMaintenanceState -ComponentsToSkip "ForwardSyncDaemon","ProvisioningRps"
if($buildInformation.ServerRole -ne [HealthChecker.ExchangeServerRole]::ClientAccess)
{
$exchangeInformation.ExchangeServicesNotRunning = Test-ServiceHealth -Server $Script:Server | %{$_.ServicesNotRunning}
}
}
elseif($buildInformation.MajorVersion -eq [HealthChecker.ExchangeMajorVersion]::Exchange2010)
{
Write-VerboseOutput("Exchange 2010 detected.")
$buildInformation.FriendlyName = "Exchange 2010"
$buildInformation.BuildNumber = $exchangeInformation.GetExchangeServer.AdminDisplayVersion.ToString()
}
Write-VerboseOutput("Exiting: Get-ExchangeInformation")
return $exchangeInformation
}
Function Get-ExchangeServerCertificates {
Write-VerboseOutput("Calling: Get-ExchangeServerCertificates")
try
{
Write-VerboseOutput("Trying to receive certificates from Exchange server: {0}" -f $Script:Server)
$exchangeServerCertificates = Get-ExchangeCertificate -Server $Script:Server -ErrorAction Stop
if($null -ne $exchangeServerCertificates)
{
try
{
$authConfig = Get-AuthConfig -ErrorAction Stop
$authConfigDetected = $true
}
catch
{
$authConfigDetected = $false
Invoke-CatchActions
}
[array]$certObject = @()
foreach ($cert in $exchangeServerCertificates)
{
try
{
$certificateLifetime = ([DateTime]($cert.NotAfter) - (Get-Date)).Days
$sanCertificateInfo = $false
if ($null -ne $cert.DnsNameList -and
($cert.DnsNameList).Count -gt 1)
{
$sanCertificateInfo = $true
}
if($authConfigDetected)
{
$isAuthConfigInfo = $false
if ($cert.Thumbprint -eq $authConfig.CurrentCertificateThumbprint)
{
$isAuthConfigInfo = $true
}
}
else
{
$isAuthConfigInfo = "InvalidAuthConfig"
}
$certInformationObj = New-Object PSCustomObject
$certInformationObj | Add-Member -MemberType NoteProperty -Name "FriendlyName" -Value $cert.FriendlyName
$certInformationObj | Add-Member -MemberType NoteProperty -Name "Thumbprint" -Value $cert.Thumbprint
$certInformationObj | Add-Member -MemberType NoteProperty -Name "PublicKeySize" -value $cert.PublicKeySize
$certInformationObj | Add-Member -MemberType NoteProperty -Name "IsSanCertificate" -Value $sanCertificateInfo
$certInformationObj | Add-Member -MemberType NoteProperty -Name "Namespaces" -Value $cert.DnsNameList
$certInformationObj | Add-Member -MemberType NoteProperty -Name "Services" -Value $cert.Services
$certInformationObj | Add-Member -MemberType NoteProperty -Name "IsCurrentAuthConfigCertificate" -Value $isAuthConfigInfo
$certInformationObj | Add-Member -MemberType NoteProperty -Name "LifetimeInDays" -Value $certificateLifetime
$certInformationObj | Add-Member -MemberType NoteProperty -Name "Status" -Value $cert.Status
$certInformationObj | Add-Member -MemberType NoteProperty -Name "CertificateObject" -Value $cert
$certObject += $certInformationObj
}
catch
{
Write-VerboseOutput("Unable to process certificate: {0}" -f $cert.Thumbprint)
Invoke-CatchActions
}
}
Write-VerboseOutput("Processed: {0} certificates" -f $certObject.Count)
return $certObject
}
else
{
Write-VerboseOutput("Failed to find any Exchange certificates")
return $null
}
}
catch
{
Write-VerboseWriter("Failed to run Get-ExchangeCertificate. Error: {0}." -f $Error[0].Exception)
Invoke-CatchActions
}
}
Function Get-ExchangeServerMaintenanceState {
param(
[Parameter(Mandatory=$false)][array]$ComponentsToSkip
)
Write-VerboseOutput("Calling Function: Get-ExchangeServerMaintenanceState")
[HealthChecker.ExchangeServerMaintenance]$serverMaintenance = New-Object -TypeName HealthChecker.ExchangeServerMaintenance
$serverMaintenance.GetServerComponentState = Get-ServerComponentState -Identity $Script:Server -ErrorAction SilentlyContinue
try
{
$serverMaintenance.GetMailboxServer = Get-MailboxServer -Identity $Script:Server -ErrorAction SilentlyContinue
}
catch
{
Write-VerboseOutput("Failed to run Get-MailboxServer")
Invoke-CatchActions
}
try
{
$serverMaintenance.GetClusterNode = Get-ClusterNode -Name $Script:Server -ErrorAction Stop
}
catch
{
Write-VerboseOutput("Failed to run Get-ClusterNode")
Invoke-CatchActions
}
Write-VerboseOutput("Running ServerComponentStates checks")
foreach ($component in $serverMaintenance.GetServerComponentState)
{
if (($ComponentsToSkip -ne $null -and
$ComponentsToSkip.Count -ne 0) -and
$ComponentsToSkip -notcontains $component.Component)
{
if ($component.State -ne "Active")
{
$latestLocalState = $null
$latestRemoteState = $null
if ($component.LocalStates -ne $null)
{
$latestLocalState = ($component.LocalStates | Sort-Object {$_.TimeStamp} -ErrorAction SilentlyContinue)[-1]
}
if ($component.RemoteStates -ne $null)
{
$latestRemoteState = ($component.RemoteStates | Sort-Object {$_.TimeStamp} -ErrorAction SilentlyContinue)[-1]
}
Write-VerboseOutput("Component: {0} LocalState: '{1}' RemoteState: '{2}'" -f $component.Component, $latestLocalState.State, $latestRemoteState.State)
if ($latestLocalState.State -eq $latestRemoteState.State)
{
$serverMaintenance.InactiveComponents += "'{0}' is in Maintenance Mode" -f $component.Component
}
else
{
if (($latestLocalState -ne $null) -and
($latestLocalState.State -ne "Active"))
{
$serverMaintenance.InactiveComponents += "'{0}' is in Local Maintenance Mode only" -f $component.Component
}
if (($latestRemoteState -ne $null) -and
($latestRemoteState.State -ne "Active"))
{
$serverMaintenance.InactiveComponents += "'{0}' is in Remote Maintenance Mode only" -f $component.Component
}
}
}
else
{
Write-VerboseOutput("Component '{0}' is Active" -f $component.Component)
}
}
else
{
Write-VerboseOutput("Component: {0} will be skipped" -f $component.Component)
}
}
return $serverMaintenance
}
Function Get-ExchangeUpdates {
param(
[Parameter(Mandatory=$true)][HealthChecker.ExchangeMajorVersion]$ExchangeMajorVersion
)
Write-VerboseOutput("Calling: Get-ExchangeUpdates")
Write-VerboseOutput("Passed: {0}" -f $ExchangeMajorVersion.ToString())
$RegLocation = [string]::Empty
if([HealthChecker.ExchangeMajorVersion]::Exchange2013 -eq $ExchangeMajorVersion)
{
$RegLocation = "SOFTWARE\Microsoft\Updates\Exchange 2013"
}
elseif([HealthChecker.ExchangeMajorVersion]::Exchange2016 -eq $ExchangeMajorVersion)
{
$RegLocation = "SOFTWARE\Microsoft\Updates\Exchange 2016"
}
else
{
$RegLocation = "SOFTWARE\Microsoft\Updates\Exchange 2019"
}
$RegKey = Invoke-RegistryGetValue -MachineName $Script:Server -SubKey $RegLocation -ReturnAfterOpenSubKey $true -CatchActionFunction ${Function:Invoke-CatchActions}
if($null -ne $RegKey)
{
$IU = $RegKey.GetSubKeyNames()
if($null -ne $IU)
{
Write-VerboseOutput("Detected fixes installed on the server")
$fixes = @()
foreach($key in $IU)
{
$IUKey = $RegKey.OpenSubKey($key)
$IUName = $IUKey.GetValue("PackageName")
Write-VerboseOutput("Found: " + $IUName)
$fixes += $IUName
}
return $fixes
}
else
{
Write-VerboseOutput("No IUs found in the registry")
}
}
else
{
Write-VerboseOutput("No RegKey returned")
}
Write-VerboseOutput("Exiting: Get-ExchangeUpdates")
return $null
}
Function Get-ExSetupDetails {
Write-VerboseOutput("Calling: Get-ExSetupDetails")
$exSetupDetails = [string]::Empty
Function Get-ExSetupDetailsScriptBlock {
Get-Command ExSetup | ForEach-Object{$_.FileVersionInfo}
}
$exSetupDetails = Invoke-ScriptBlockHandler -ComputerName $Script:Server -ScriptBlock ${Function:Get-ExSetupDetailsScriptBlock} -ScriptBlockDescription "Getting ExSetup remotely" -CatchActionFunction ${Function:Invoke-CatchActions}
Write-VerboseOutput("Exiting: Get-ExSetupDetails")
return $exSetupDetails
}
Function Get-HealthCheckerExchangeServer {
Write-VerboseOutput("Calling: Get-HealthCheckerExchangeServer")
[HealthChecker.HealthCheckerExchangeServer]$HealthExSvrObj = New-Object -TypeName HealthChecker.HealthCheckerExchangeServer
$HealthExSvrObj.ServerName = $Script:Server
$HealthExSvrObj.HardwareInformation = Get-HardwareInformation
$HealthExSvrObj.OSInformation = Get-OperatingSystemInformation
$HealthExSvrObj.ExchangeInformation = Get-ExchangeInformation -OSMajorVersion $HealthExSvrObj.OSInformation.BuildInformation.MajorVersion
if($HealthExSvrObj.ExchangeInformation.BuildInformation.MajorVersion -ge [HealthChecker.ExchangeMajorVersion]::Exchange2013)
{
$netFrameworkVersion = Get-NETFrameworkVersion -MachineName $Script:Server -CatchActionFunction ${Function:Invoke-CatchActions}
$HealthExSvrObj.OSInformation.NETFramework.FriendlyName = $netFrameworkVersion.FriendlyName
$HealthExSvrObj.OSInformation.NETFramework.RegistryValue = $netFrameworkVersion.RegistryValue
$HealthExSvrObj.OSInformation.NETFramework.NetMajorVersion = $netFrameworkVersion.MinimumValue
$HealthExSvrObj.OSInformation.NETFramework.FileInformation = Get-DotNetDllFileVersions -ComputerName $Script:Server -FileNames @("System.Data.dll","System.Configuration.dll") -CatchActionFunction ${Function:Invoke-CatchActions}
if ($netFrameworkVersion.MinimumValue -eq $HealthExSvrObj.ExchangeInformation.NETFramework.MaxSupportedVersion)
{
$HealthExSvrObj.ExchangeInformation.NETFramework.OnRecommendedVersion = $true
}
}
$HealthExSvrObj.HealthCheckerVersion = $healthCheckerVersion
Write-VerboseOutput("Finished building health Exchange Server Object for server: " + $Script:Server)
return $HealthExSvrObj
}
Function Get-CredentialGuardEnabled {
Write-VerboseOutput("Calling: Get-CredentialGuardEnabled")
$registryValue = Invoke-RegistryGetValue -MachineName $Script:Server -SubKey "SYSTEM\CurrentControlSet\Control\LSA" -GetValue "LsaCfgFlags" -CatchActionFunction ${Function:Invoke-CatchActions}
if ($registryValue -ne $null -and
$registryValue -ne 0)
{
return $true
}
return $false
}
Function Get-HardwareInformation {
Write-VerboseOutput("Calling: Get-HardwareInformation")
[HealthChecker.HardwareInformation]$hardware_obj = New-Object HealthChecker.HardwareInformation
$system = Get-WmiObjectHandler -ComputerName $Script:Server -Class "Win32_ComputerSystem" -CatchActionFunction ${Function:Invoke-CatchActions}
$hardware_obj.Manufacturer = $system.Manufacturer
$hardware_obj.System = $system
$hardware_obj.AutoPageFile = $system.AutomaticManagedPagefile
$hardware_obj.TotalMemory = $system.TotalPhysicalMemory
$hardware_obj.ServerType = (Get-ServerType -ServerType $system.Manufacturer)
$processorInformation = Get-ProcessorInformation -MachineName $Script:Server -CatchActionFunction ${Function:Invoke-CatchActions}
#Need to do it this way because of Windows 2012R2
$processor = New-Object HealthChecker.ProcessorInformation
$processor.Name = $processorInformation.Name
$processor.NumberOfPhysicalCores = $processorInformation.NumberOfPhysicalCores
$processor.NumberOfLogicalCores = $processorInformation.NumberOfLogicalCores
$processor.NumberOfProcessors = $processorInformation.NumberOfProcessors
$processor.MaxMegacyclesPerCore = $processorInformation.MaxMegacyclesPerCore
$processor.CurrentMegacyclesPerCore = $processorInformation.CurrentMegacyclesPerCore
$processor.ProcessorIsThrottled = $processorInformation.ProcessorIsThrottled
$processor.DifferentProcessorsDetected = $processorInformation.DifferentProcessorsDetected
$processor.DifferentProcessorCoreCountDetected = $processorInformation.DifferentProcessorCoreCountDetected
$processor.EnvironmentProcessorCount = $processorInformation.EnvironmentProcessorCount
$processor.ProcessorClassObject = $processorInformation.ProcessorClassObject
$hardware_obj.Processor = $processor
$hardware_obj.Model = $system.Model
Write-VerboseOutput("Exiting: Get-HardwareInformation")
return $hardware_obj
}
Function Get-HttpProxySetting {
$httpProxy32 = [String]::Empty
$httpProxy64 = [String]::Empty
Write-VerboseOutput("Calling: Get-HttpProxySetting")
Function Get-WinHttpSettings {
param(
[Parameter(Mandatory=$true)][string]$RegistryLocation
)
$connections = Get-ItemProperty -Path $RegistryLocation
$Proxy = [string]::Empty
if(($connections -ne $null) -and ($Connections | gm).Name -contains "WinHttpSettings")
{
foreach($Byte in $Connections.WinHttpSettings)
{
if($Byte -ge 48)
{
$Proxy += [CHAR]$Byte
}
}
}
return $(if($Proxy -eq [string]::Empty){"<None>"} else {$Proxy})
}
$httpProxy32 = Invoke-ScriptBlockHandler -ComputerName $Script:Server -ScriptBlock ${Function:Get-WinHttpSettings} -ArgumentList "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Connections" -ScriptBlockDescription "Getting 32 Http Proxy Value" -CatchActionFunction ${Function:Invoke-CatchActions}
$httpProxy64 = Invoke-ScriptBlockHandler -ComputerName $Script:Server -ScriptBlock ${Function:Get-WinHttpSettings} -ArgumentList "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Internet Settings\Connections" -ScriptBlockDescription "Getting 64 Http Proxy Value" -CatchActionFunction ${Function:Invoke-CatchActions}
Write-VerboseOutput("Http Proxy 32: {0}" -f $httpProxy32)
Write-VerboseOutput("Http Proxy 64: {0}" -f $httpProxy64)
Write-VerboseOutput("Exiting: Get-HttpProxySetting")
if($httpProxy32 -ne "<None>")
{
return $httpProxy32
}
else
{
return $httpProxy64
}
}
Function Get-LmCompatibilityLevelInformation {
Write-VerboseOutput("Calling: Get-LmCompatibilityLevelInformation")
[HealthChecker.LmCompatibilityLevelInformation]$ServerLmCompatObject = New-Object -TypeName HealthChecker.LmCompatibilityLevelInformation
$ServerLmCompatObject.RegistryValue = Invoke-RegistryGetValue -RegistryHive "LocalMachine" -MachineName $Script:Server -SubKey "SYSTEM\CurrentControlSet\Control\Lsa" -GetValue "LmCompatibilityLevel" -CatchActionFunction ${Function:Invoke-CatchActions} -DefaultValue 3
Switch ($ServerLmCompatObject.RegistryValue)
{
0 {$ServerLmCompatObject.Description = "Clients use LM and NTLM authentication, but they never use NTLMv2 session security. Domain controllers accept LM, NTLM, and NTLMv2 authentication." }
1 {$ServerLmCompatObject.Description = "Clients use LM and NTLM authentication, and they use NTLMv2 session security if the server supports it. Domain controllers accept LM, NTLM, and NTLMv2 authentication." }
2 {$ServerLmCompatObject.Description = "Clients use only NTLM authentication, and they use NTLMv2 session security if the server supports it. Domain controller accepts LM, NTLM, and NTLMv2 authentication." }
3 {$ServerLmCompatObject.Description = "Clients use only NTLMv2 authentication, and they use NTLMv2 session security if the server supports it. Domain controllers accept LM, NTLM, and NTLMv2 authentication." }
4 {$ServerLmCompatObject.Description = "Clients use only NTLMv2 authentication, and they use NTLMv2 session security if the server supports it. Domain controller refuses LM authentication responses, but it accepts NTLM and NTLMv2." }
5 {$ServerLmCompatObject.Description = "Clients use only NTLMv2 authentication, and they use NTLMv2 session security if the server supports it. Domain controller refuses LM and NTLM authentication responses, but it accepts NTLMv2." }
}
Write-VerboseOutput("Exiting: Get-LmCompatibilityLevelInformation")
Return $ServerLmCompatObject
}
Function Get-OperatingSystemInformation {
Write-VerboseOutput("Calling: Get-OperatingSystemInformation")
[HealthChecker.OperatingSystemInformation]$osInformation = New-Object HealthChecker.OperatingSystemInformation
$win32_OperatingSystem = Get-WmiObjectHandler -ComputerName $Script:Server -Class Win32_OperatingSystem -CatchActionFunction ${Function:Invoke-CatchActions}
$win32_PowerPlan = Get-WmiObjectHandler -ComputerName $Script:Server -Class Win32_PowerPlan -Namespace 'root\cimv2\power' -Filter "isActive='true'" -CatchActionFunction ${Function:Invoke-CatchActions}
$currentDateTime = Get-Date
$lastBootUpTime = [Management.ManagementDateTimeConverter]::ToDateTime($win32_OperatingSystem.lastbootuptime)
$osInformation.BuildInformation.VersionBuild = $win32_OperatingSystem.Version
$osInformation.BuildInformation.MajorVersion = (Get-ServerOperatingSystemVersion -OsCaption $win32_OperatingSystem.Caption)
$osInformation.BuildInformation.FriendlyName = $win32_OperatingSystem.Caption
$osInformation.BuildInformation.OperatingSystem = $win32_OperatingSystem
$osInformation.ServerBootUp.Days = ($currentDateTime - $lastBootUpTime).Days
$osInformation.ServerBootUp.Hours = ($currentDateTime - $lastBootUpTime).Hours
$osInformation.ServerBootUp.Minutes = ($currentDateTime - $lastBootUpTime).Minutes
$osInformation.ServerBootUp.Seconds = ($currentDateTime - $lastBootUpTime).Seconds
if($win32_PowerPlan -ne $null)
{
if($win32_PowerPlan.InstanceID -eq "Microsoft:PowerPlan\{8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c}")
{
Write-VerboseOutput("High Performance Power Plan is set to true")
$osInformation.PowerPlan.HighPerformanceSet = $true
}
else{Write-VerboseOutput("High Performance Power Plan is NOT set to true")}
$osInformation.PowerPlan.PowerPlanSetting = $win32_PowerPlan.ElementName
}
else
{
Write-VerboseOutput("Power Plan Information could not be read")
$osInformation.PowerPlan.PowerPlanSetting = "N/A"
}
$osInformation.PowerPlan.PowerPlan = $win32_PowerPlan
$osInformation.PageFile = Get-PageFileInformation
$osInformation.NetworkInformation.NetworkAdapters = (Get-AllNicInformation -ComputerName $Script:Server -CatchActionFunction ${Function:Invoke-CatchActions} -ComputerFQDN $Script:ServerFQDN)
foreach($adapter in $osInformation.NetworkInformation.NetworkAdapters)
{
if (!$adapter.IPv6Enabled)
{
$osInformation.NetworkInformation.IPv6DisabledOnNICs = $true
break
}
}
$osInformation.NetworkInformation.IPv6DisabledComponents = Invoke-RegistryGetValue -RegistryHive "LocalMachine" -MachineName $Script:Server -SubKey "SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters" -GetValue "DisabledComponents" -CatchActionFunction ${Function:Invoke-CatchActions}
$osInformation.NetworkInformation.TCPKeepAlive = Invoke-RegistryGetValue -RegistryHive "LocalMachine" -MachineName $Script:Server -SubKey "SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" -GetValue "KeepAliveTime" -CatchActionFunction ${Function:Invoke-CatchActions}
$osInformation.NetworkInformation.RpcMinConnectionTimeout = Invoke-RegistryGetValue -RegistryHive "LocalMachine" -MachineName $Script:Server -SubKey "Software\Policies\Microsoft\Windows NT\RPC\" -GetValue "MinimumConnectionTimeout" -CatchActionFunction ${Function:Invoke-CatchActions}
$osInformation.NetworkInformation.HttpProxy = Get-HttpProxySetting
$osInformation.InstalledUpdates.HotFixes = (Get-HotFix -ComputerName $Script:Server -ErrorAction SilentlyContinue) #old school check still valid and faster and a failsafe
$osInformation.LmCompatibility = Get-LmCompatibilityLevelInformation
$counterSamples = (Get-CounterSamples -MachineNames $Script:Server -Counters "\Network Interface(*)\Packets Received Discarded")
if($counterSamples -ne $null)
{
$osInformation.NetworkInformation.PacketsReceivedDiscarded = $counterSamples
}
$osInformation.ServerPendingReboot = (Get-ServerRebootPending -ServerName $Script:Server -CatchActionFunction ${Function:Invoke-CatchActions})
$timeZoneInformation = Get-TimeZoneInformationRegistrySettings -MachineName $Script:Server -CatchActionFunction ${Function:Invoke-CatchActions}
$osInformation.TimeZone.DynamicDaylightTimeDisabled = $timeZoneInformation.DynamicDaylightTimeDisabled
$osInformation.TimeZone.TimeZoneKeyName = $timeZoneInformation.TimeZoneKeyName
$osInformation.TimeZone.StandardStart = $timeZoneInformation.StandardStart
$osInformation.TimeZone.DaylightStart = $timeZoneInformation.DaylightStart
$osInformation.TimeZone.DstIssueDetected = $timeZoneInformation.DstIssueDetected
$osInformation.TimeZone.ActionsToTake = $timeZoneInformation.ActionsToTake
$osInformation.TimeZone.CurrentTimeZone = Invoke-ScriptBlockHandler -ComputerName $Script:Server -ScriptBlock {([System.TimeZone]::CurrentTimeZone).StandardName} -ScriptBlockDescription "Getting Current Time Zone" -CatchActionFunction ${Function:Invoke-CatchActions}
$osInformation.TLSSettings = Get-AllTlsSettingsFromRegistry -MachineName $Script:Server -CatchActionFunction ${Function:Invoke-CatchActions}
$osInformation.VcRedistributable = Get-VisualCRedistributableVersion
$osInformation.CredentialGuardEnabled = Get-CredentialGuardEnabled
$osInformation.RegistryValues.CurrentVersionUbr = Invoke-RegistryGetValue `
-MachineName $Script:Server `
-SubKey "SOFTWARE\Microsoft\Windows NT\CurrentVersion" `
-GetValue "UBR" `
-CatchActionFunction ${Function:Invoke-CatchActions}
$osInformation.RegistryValues.LanManServerDisabledCompression = Invoke-RegistryGetValue `
-MachineName $Script:Server `
-SubKey "SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters" `
-GetValue "DisableCompression" `
-CatchActionFunction ${Function:Invoke-CatchActions}
$getSmb1ServerSettings = Get-Smb1ServerSettings -ServerName $Script:Server -CatchActionFunction ${Function:Invoke-CatchActions}
$osInformation.Smb1ServerSettings.SmbServerConfiguration = $getSmb1ServerSettings.SmbServerConfiguration
$osInformation.Smb1ServerSettings.WindowsFeature = $getSmb1ServerSettings.WindowsFeature
$osInformation.Smb1ServerSettings.Smb1Status = $getSmb1ServerSettings.Smb1Status
Write-VerboseOutput("Exiting: Get-OperatingSystemInformation")
return $osInformation
}
Function Get-PageFileInformation {
Write-VerboseOutput("Calling: Get-PageFileInformation")
[HealthChecker.PageFileInformation]$page_obj = New-Object HealthChecker.PageFileInformation
$pagefile = Get-WmiObjectHandler -ComputerName $Script:Server -Class "Win32_PageFileSetting" -CatchActionFunction ${Function:Invoke-CatchActions}
if($pagefile -ne $null)
{
if($pagefile.GetType().Name -eq "ManagementObject")
{
$page_obj.MaxPageSize = $pagefile.MaximumSize
}
$page_obj.PageFile = $pagefile
}
else
{
Write-VerboseOutput("Return Null value")
}
Write-VerboseOutput("Exiting: Get-PageFileInformation")
return $page_obj
}
Function Get-ServerRole {
param(
[Parameter(Mandatory=$true)][object]$ExchangeServerObj
)
Write-VerboseOutput("Calling: Get-ServerRole")
$roles = $ExchangeServerObj.ServerRole.ToString()
Write-VerboseOutput("Roll: " + $roles)
#Need to change this to like because of Exchange 2010 with AIO with the hub role.
if($roles -like "Mailbox, ClientAccess*")
{
return [HealthChecker.ExchangeServerRole]::MultiRole
}
elseif($roles -eq "Mailbox")
{
return [HealthChecker.ExchangeServerRole]::Mailbox
}
elseif($roles -eq "Edge")
{
return [HealthChecker.ExchangeServerRole]::Edge
}
elseif($roles -like "*ClientAccess*")
{
return [HealthChecker.ExchangeServerRole]::ClientAccess
}
else
{
return [HealthChecker.ExchangeServerRole]::None
}
}
Function Get-VisualCRedistributableVersion {
Write-VerboseOutput("Calling: Get-VisualCRedistributableVersion")
$installedSoftware = Invoke-ScriptBlockHandler -ComputerName $Script:Server -ScriptBlock {Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*} -ScriptBlockDescription "Quering for software" -CatchActionFunction ${Function:Invoke-CatchActions}
$softwareInfos = @()
foreach ($software in $installedSoftware)
{
if($software.DisplayName -like "Microsoft Visual C++ *")
{
Write-VerboseOutput("Microsoft Visual C++ Redistributable found: {0}" -f $software.DisplayName)
[HealthChecker.SoftwareInformation]$softwareInfo = New-Object Healthchecker.SoftwareInformation
$softwareInfo.DisplayName = $software.DisplayName
$softwareInfo.DisplayVersion = $software.DisplayVersion
$softwareInfo.InstallDate = $software.InstallDate
$softwareInfo.VersionIdentifier = $software.Version
$softwareInfos += $softwareInfo
}
}
Write-VerboseOutput("Exiting: Get-VisualCRedistributableVersion")
return $softwareInfos
}
Function Add-AnalyzedResultInformation {
param(
[object]$Details,
[string]$Name,
[string]$HtmlName,
[object]$DisplayGroupingKey,
[int]$DisplayCustomTabNumber = -1,
[object]$DisplayTestingValue,
[string]$DisplayWriteType = "Grey",
[bool]$AddDisplayResultsLineInfo = $true,
[bool]$AddHtmlDetailRow = $true,
[string]$HtmlDetailsCustomValue = "",
[bool]$AddHtmlOverviewValues = $false,
[bool]$AddHtmlActionRow = $false,
[string]$ActionSettingClass = "",
[string]$ActionSettingValue,
[string]$ActionRecommendedDetailsClass = "",
[string]$ActionRecommendedDetailsValue,
[string]$ActionMoreInformationClass = "",
[string]$ActionMoreInformationValue,
[HealthChecker.AnalyzedInformation]$AnalyzedInformation
)
Write-VerboseOutput("Calling Add-AnalyzedResultInformation: {0}" -f $name)
if ($AddDisplayResultsLineInfo)
{
if (!($AnalyzedInformation.DisplayResults.ContainsKey($DisplayGroupingKey)))
{
Write-VerboseOutput("Adding Display Grouping Key: {0}" -f $DisplayGroupingKey.Name)
[System.Collections.Generic.List[HealthChecker.DisplayResultsLineInfo]]$list = New-Object System.Collections.Generic.List[HealthChecker.DisplayResultsLineInfo]
$AnalyzedInformation.DisplayResults.Add($DisplayGroupingKey, $list)
}
$lineInfo = New-Object HealthChecker.DisplayResultsLineInfo
$lineInfo.DisplayValue = $Details
$lineInfo.Name = $Name
if ($DisplayCustomTabNumber -ne -1)
{
$lineInfo.TabNumber = $DisplayCustomTabNumber
}
else
{
$lineInfo.TabNumber = $DisplayGroupingKey.DefaultTabNumber
}
if ($DisplayTestingValue -ne $null)
{
$lineInfo.TestingValue = $DisplayTestingValue
}
else
{
$lineInfo.TestingValue = $Details
}
$lineInfo.WriteType = $DisplayWriteType
$AnalyzedInformation.DisplayResults[$DisplayGroupingKey].Add($lineInfo)
}
if ($AddHtmlDetailRow)
{
if (!($analyzedResults.HtmlServerValues.ContainsKey("ServerDetails")))
{
[System.Collections.Generic.List[HealthChecker.HtmlServerInformationRow]]$list = New-Object System.Collections.Generic.List[HealthChecker.HtmlServerInformationRow]
$AnalyzedInformation.HtmlServerValues.Add("ServerDetails", $list)
}
$detailRow = New-Object HealthChecker.HtmlServerInformationRow
if ($displayWriteType -ne "Grey")
{
$detailRow.Class = $displayWriteType
}
if ([string]::IsNullOrEmpty($HtmlName))
{
$detailRow.Name = $Name
}
else
{
$detailRow.Name = $HtmlName
}
if ([string]::IsNullOrEmpty($HtmlDetailsCustomValue))
{
$detailRow.DetailValue = $Details
}
else
{
$detailRow.DetailValue = $HtmlDetailsCustomValue
}
$AnalyzedInformation.HtmlServerValues["ServerDetails"].Add($detailRow)
}
if ($AddHtmlOverviewValues)
{
if (!($analyzedResults.HtmlServerValues.ContainsKey("OverviewValues")))
{
[System.Collections.Generic.List[HealthChecker.HtmlServerInformationRow]]$list = New-Object System.Collections.Generic.List[HealthChecker.HtmlServerInformationRow]
$AnalyzedInformation.HtmlServerValues.Add("OverviewValues", $list)
}
$overviewValue = New-Object HealthChecker.HtmlServerInformationRow
if ($displayWriteType -ne "Grey")
{
$overviewValue.Class = $displayWriteType
}
if ([string]::IsNullOrEmpty($HtmlName))
{
$overviewValue.Name = $Name
}
else
{
$overviewValue.Name = $HtmlName
}
if ([string]::IsNullOrEmpty($HtmlDetailsCustomValue))
{
$overviewValue.DetailValue = $Details
}
else
{
$overviewValue.DetailValue = $HtmlDetailsCustomValue
}
$AnalyzedInformation.HtmlServerValues["OverviewValues"].Add($overviewValue)
}
if ($AddHtmlActionRow)
{
#TODO
}
return $AnalyzedInformation
}
Function New-DisplayResultsGroupingKey {
param(
[string]$Name,
[bool]$DisplayGroupName = $true,
[int]$DisplayOrder,
[int]$DefaultTabNumber = 1
)
$obj = New-Object HealthChecker.DisplayResultsGroupingKey
$obj.Name = $Name
$obj.DisplayGroupName = $DisplayGroupName
$obj.DisplayOrder = $DisplayOrder
$obj.DefaultTabNumber = $DefaultTabNumber
return $obj
}
Function Start-AnalyzerEngine {
param(
[HealthChecker.HealthCheckerExchangeServer]$HealthServerObject
)
Write-VerboseOutput("Calling: Start-AnalyzerEngine")
$analyzedResults = New-Object HealthChecker.AnalyzedInformation
$analyzedResults.HealthCheckerExchangeServer = $HealthServerObject
#Display Grouping Keys
$order = 0
$keyBeginningInfo = New-DisplayResultsGroupingKey -Name "BeginningInfo" -DisplayGroupName $false -DisplayOrder ($order++) -DefaultTabNumber 0
$keyExchangeInformation = New-DisplayResultsGroupingKey -Name "Exchange Information" -DisplayOrder ($order++)
$keyOSInformation = New-DisplayResultsGroupingKey -Name "Operating System Information" -DisplayOrder ($order++)
$keyHardwareInformation = New-DisplayResultsGroupingKey -Name "Processor/Hardware Information" -DisplayOrder ($order++)
$keyNICSettings = New-DisplayResultsGroupingKey -Name "NIC Settings Per Active Adapter" -DisplayOrder ($order++) -DefaultTabNumber 2
$keyFrequentConfigIssues = New-DisplayResultsGroupingKey -Name "Frequent Configuration Issues" -DisplayOrder ($order++)
$keySecuritySettings = New-DisplayResultsGroupingKey -Name "Security Settings" -DisplayOrder ($order++)
$keyWebApps = New-DisplayResultsGroupingKey -Name "Exchange Web App Pools" -DisplayOrder ($order++)
#Set short cut variables
$exchangeInformation = $HealthServerObject.ExchangeInformation
$osInformation = $HealthServerObject.OSInformation
$hardwareInformation = $HealthServerObject.HardwareInformation
if (!$Script:DisplayedScriptVersionAlready)
{
$analyzedResults = Add-AnalyzedResultInformation -Name "Exchange Health Checker Version" -Details $Script:healthCheckerVersion `
-DisplayGroupingKey $keyBeginningInfo `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
if ($HealthServerObject.HardwareInformation.ServerType -eq [HealthChecker.ServerType]::VMWare -or
$HealthServerObject.HardwareInformation.ServerType -eq [HealthChecker.ServerType]::HyperV)
{
$analyzedResults = Add-AnalyzedResultInformation -Details $VirtualizationWarning -DisplayWriteType "Yellow" `
-DisplayGroupingKey $keyBeginningInfo `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
#########################
# Exchange Information
#########################
Write-VerboseOutput("Working on Exchange Information")
$analyzedResults = Add-AnalyzedResultInformation -Name "Name" -Details ($HealthServerObject.ServerName) `
-DisplayGroupingKey $keyExchangeInformation `
-AddHtmlOverviewValues $true `
-HtmlName "Server Name" `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Version" -Details ($exchangeInformation.BuildInformation.FriendlyName) `
-DisplayGroupingKey $keyExchangeInformation `
-AddHtmlOverviewValues $true `
-HtmlName "Exchange Version" `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Build Number" -Details ($exchangeInformation.BuildInformation.BuildNumber) `
-DisplayGroupingKey $keyExchangeInformation `
-AnalyzedInformation $analyzedResults
if ($exchangeInformation.BuildInformation.SupportedBuild -eq $false)
{
$daysOld = ($date - ([System.Convert]::ToDateTime([DateTime]$exchangeInformation.BuildInformation.ReleaseDate))).Days
$analyzedResults = Add-AnalyzedResultInformation -Name "Error" -Details ("Out of date Cumulative Update. Please upgrade to one of the two most recently released Cumulative Updates. Currently running on a build that is {0} days old." -f $daysOld) `
-DisplayGroupingKey $keyExchangeInformation `
-DisplayWriteType "Red" `
-DisplayCustomTabNumber 2 `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
if ($exchangeInformation.BuildInformation.KBsInstalled -ne $null)
{
$analyzedResults = Add-AnalyzedResultInformation -Details ("Exchange IU or Security Hotfix Detected.") `
-DisplayGroupingKey $keyExchangeInformation `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
foreach ($kb in $exchangeInformation.BuildInformation.KBsInstalled)
{
$analyzedResults = Add-AnalyzedResultInformation -Details $kb `
-DisplayGroupingKey $keyExchangeInformation `
-DisplayCustomTabNumber 2 `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Server Role" -Details ($exchangeInformation.BuildInformation.ServerRole) `
-DisplayGroupingKey $keyExchangeInformation `
-AddHtmlOverviewValues $true `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "MAPI/HTTP Enabled" -Details ($exchangeInformation.MapiHttpEnabled) `
-DisplayGroupingKey $keyExchangeInformation `
-AnalyzedInformation $analyzedResults
if ($exchangeInformation.BuildInformation.MajorVersion -eq [HealthChecker.ExchangeMajorVersion]::Exchange2013 -and
$exchangeInformation.BuildInformation.ServerRole -ne [HealthChecker.ExchangeServerRole]::Edge)
{
$content = [xml]$exchangeInformation.ApplicationPools["MSExchangeMapiFrontEndAppPool"].Content
[bool]$enabled = $content.Configuration.Runtime.gcServer.Enabled -eq "true"
[bool]$unknown = $content.Configuration.Runtime.gcServer.Enabled -ne "true" -and $content.Configuration.Runtime.gcServer.Enabled -ne "false"
$warning = [string]::Empty
$displayWriteType = "Green"
$displayValue = "Server"
if ($hardwareInformation.TotalMemory -ge 21474836480 -and
$enabled -eq $false)
{
$displayWriteType = "Red"
$displayValue = "Workstation --- Error"
$warning = "To Fix this issue go into the file MSExchangeMapiFrontEndAppPool_CLRConfig.config in the Exchange Bin directory and change the GCServer to true and recycle the MAPI Front End App Pool"
}
elseif ($unknown)
{
$displayValue = "Unknown --- Warning"
$displayWriteType = "Yellow"
}
elseif (!($enabled))
{
$displayWriteType = "Yellow"
$displayValue = "Workstation --- Warning"
$warning = "You could be seeing some GC issues within the Mapi Front End App Pool. However, you don't have enough memory installed on the system to recommend switching the GC mode by default without consulting a support professional."
}
$analyzedResults = Add-AnalyzedResultInformation -Name "MAPI Front End App Pool GC Mode" -Details $displayValue `
-DisplayGroupingKey $keyExchangeInformation `
-DisplayCustomTabNumber 2 `
-DisplayWriteType $displayWriteType `
-AnalyzedInformation $analyzedResults
if ($warning -ne [string]::Empty)
{
$analyzedResults = Add-AnalyzedResultInformation -Details $warning `
-DisplayGroupingKey $keyExchangeInformation `
-DisplayCustomTabNumber 2 `
-DisplayWriteType "Yellow" `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
}
##############################
# Exchange Server Maintenance
##############################
Write-VerboseOutput("Working on Exchange Server Maintenance")
$serverMaintenance = $exchangeInformation.ServerMaintenance
if (($serverMaintenance.InactiveComponents).Count -eq 0 -and
($serverMaintenance.GetClusterNode -eq $null -or
$serverMaintenance.GetClusterNode.State -eq "Up") -and
($serverMaintenance.GetMailboxServer -eq $null -or
($serverMaintenance.GetMailboxServer.DatabaseCopyActivationDisabledAndMoveNow -eq $false -and
$serverMaintenance.GetMailboxServer.DatabaseCopyAutoActivationPolicy -eq "Unrestricted")))
{
$analyzedResults = Add-AnalyzedResultInformation -Name "Exchange Server Maintenance" -Details "Server is not in Maintenance Mode" `
-DisplayGroupingKey $keyExchangeInformation `
-DisplayWriteType "Green" `
-AnalyzedInformation $analyzedResults
}
else
{
$analyzedResults = Add-AnalyzedResultInformation -Details "Exchange Server Maintenance" `
-DisplayGroupingKey $keyExchangeInformation `
-AnalyzedInformation $analyzedResults
if (($serverMaintenance.InactiveComponents).Count -ne 0)
{
foreach ($inactiveComponent in $serverMaintenance.InactiveComponents)
{
$analyzedResults = Add-AnalyzedResultInformation -Name "Component" -Details $inactiveComponent `
-DisplayGroupingKey $keyExchangeInformation `
-DisplayCustomTabNumber 2 `
-DisplayWriteType "Yellow" `
-AnalyzedInformation $analyzedResults
}
}
if ($serverMaintenance.GetMailboxServer.DatabaseCopyActivationDisabledAndMoveNow -or
$serverMaintenance.GetMailboxServer.DatabaseCopyAutoActivationPolicy -eq "Blocked")
{
$displayValue = "`r`n`t`tDatabaseCopyActivationDisabledAndMoveNow: {0} --- should be 'false'`r`n`t`tDatabaseCopyAutoActivationPolicy: {1} --- should be 'unrestricted'" -f `
$serverMaintenance.GetMailboxServer.DatabaseCopyActivationDisabledAndMoveNow,
$serverMaintenance.GetMailboxServer.DatabaseCopyAutoActivationPolicy
$analyzedResults = Add-AnalyzedResultInformation -Name "Database Copy Maintenance" -Details $displayValue `
-DisplayGroupingKey $keyExchangeInformation `
-DisplayCustomTabNumber 2 `
-DisplayWriteType "Yellow" `
-AnalyzedInformation $analyzedResults
}
if ($serverMaintenance.GetClusterNode -ne $null -and $serverMaintenance.GetClusterNode.State -ne "Up")
{
$analyzedResults = Add-AnalyzedResultInformation -Name "Cluster Node" -Details ("'{0}' --- should be 'Up'" -f $serverMaintenance.GetClusterNode.State) `
-DisplayGroupingKey $keyExchangeInformation `
-DisplayCustomTabNumber 2 `
-DisplayWriteType "Yellow" `
-AnalyzedInformation $analyzedResults
}
}
#########################
# Operating System
#########################
Write-VerboseOutput("Working on Operating System")
$analyzedResults = Add-AnalyzedResultInformation -Name "Version" -Details ($osInformation.BuildInformation.FriendlyName) `
-DisplayGroupingKey $keyOSInformation `
-AddHtmlOverviewValues $true `
-HtmlName "OS Version" `
-AnalyzedInformation $analyzedResults
$upTime = "{0} day(s) {1} hour(s) {2} minute(s) {3} second(s)" -f $osInformation.ServerBootUp.Days,
$osInformation.ServerBootUp.Hours,
$osInformation.ServerBootUp.Minutes,
$osInformation.ServerBootUp.Seconds
$analyzedResults = Add-AnalyzedResultInformation -Name "System Up Time" -Details $upTime `
-DisplayGroupingKey $keyOSInformation `
-DisplayTestingValue ($osInformation.ServerBootUp) `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Time Zone" -Details ($osInformation.TimeZone.CurrentTimeZone) `
-DisplayGroupingKey $keyOSInformation `
-AddHtmlOverviewValues $true `
-AnalyzedInformation $analyzedResults
$writeValue = $false
$warning = @("Windows can not properly detect any DST rule changes in your time zone. Set 'Adjust for daylight saving time automatically to on'")
if ($osInformation.TimeZone.DstIssueDetected)
{
$writeType = "Red"
}
elseif ($osInformation.TimeZone.DynamicDaylightTimeDisabled -ne 0)
{
$writeType = "Yellow"
}
else
{
$warning = [string]::Empty
$writeValue = $true
$writeType = "Grey"
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Dynamic Daylight Time Enabled" -Details $writeValue `
-DisplayGroupingKey $keyOSInformation `
-DisplayWriteType $writeType `
-AnalyzedInformation $analyzedResults
if ($warning -ne [string]::Empty)
{
$analyzedResults = Add-AnalyzedResultInformation -Details $warning `
-DisplayGroupingKey $keyOSInformation `
-DisplayWriteType "Yellow" `
-DisplayCustomTabNumber 2 `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
if ([string]::IsNullOrEmpty($osInformation.TimeZone.TimeZoneKeyName))
{
$analyzedResults = Add-AnalyzedResultInformation -Name "Time Zone Key Name" -Details "Empty --- Warning Need to switch your current time zone to a different value, then switch it back to have this value populated again." `
-DisplayGroupingKey $keyOSInformation `
-DisplayWriteType "Yellow" `
-AnalyzedInformation $analyzedResults
}
if ($exchangeInformation.NETFramework.OnRecommendedVersion)
{
$analyzedResults = Add-AnalyzedResultInformation -Name ".NET Framework" -Details ($osInformation.NETFramework.FriendlyName) `
-DisplayGroupingKey $keyOSInformation `
-DisplayWriteType "Green" `
-AddHtmlOverviewValues $true `
-AnalyzedInformation $analyzedResults
}
else
{
$testObject = New-Object PSCustomObject
$testObject | Add-Member -MemberType NoteProperty -Name "CurrentValue" -Value ($osInformation.NETFramework.FriendlyName)
$testObject | Add-Member -MemberType NoteProperty -Name "MaxSupportedVersion" -Value ($exchangeInformation.NETFramework.MaxSupportedVersion)
$displayFriendly = Get-NETFrameworkVersion -NetVersionKey $exchangeInformation.NETFramework.MaxSupportedVersion
$displayValue = "{0} - Warning Recommended .NET Version is {1}" -f $osInformation.NETFramework.FriendlyName, $displayFriendly.FriendlyName
$analyzedResults = Add-AnalyzedResultInformation -Name ".NET Framework" -Details $displayValue `
-DisplayGroupingKey $keyOSInformation `
-DisplayWriteType "Yellow" `
-DisplayTestingValue $testObject `
-HtmlDetailsCustomValue ($osInformation.NETFramework.FriendlyName) `
-AddHtmlOverviewValues $true `
-AnalyzedInformation $analyzedResults
}
$displayValue = [string]::Empty
$displayWriteType = "Yellow"
Write-VerboseOutput("Total Memory: {0}" -f ($totalPhysicalMemory = $hardwareInformation.TotalMemory))
Write-VerboseOutput("Page File: {0}" -f ($maxPageSize = $osInformation.PageFile.MaxPageSize))
$testingValue = New-Object PSCustomObject
$testingValue | Add-Member -MemberType NoteProperty -Name "TotalPhysicalMemory" -Value $totalPhysicalMemory
$testingValue | Add-Member -MemberType NoteProperty -Name "MaxPageSize" -Value $maxPageSize
$testingValue | Add-Member -MemberType NoteProperty -Name "MultiPageFile" -Value ($osInformation.PageFile.PageFile.Count -gt 1)
$testingValue | Add-Member -MemberType NoteProperty -Name "RecommendedPageFile" -Value 0
if ($maxPageSize -eq 0)
{
$displayValue = "Error: System is set to automatically manage the pagefile size."
$displayWriteType = "Red"
}
elseif ($osInformation.PageFile.PageFile.Count -gt 1)
{
$displayValue = "Multiple page files detected. `r`n`t`tError: This has been know to cause performance issues please address this."
$displayWriteType = "Red"
}
elseif ($exchangeInformation.BuildInformation.MajorVersion -eq [HealthChecker.ExchangeMajorVersion]::Exchange2019)
{
$testingValue.RecommendedPageFile = ($recommendedPageFileSize = [Math]::Truncate(($totalPhysicalMemory / 1MB) / 4))
Write-VerboseOutput("Recommended Page File Size: {0}" -f $recommendedPageFileSize)
if ($recommendedPageFileSize -ne $maxPageSize)
{
$displayValue = "{0}MB `r`n`t`tWarning: Page File is not set to 25% of the Total System Memory which is {1}MB. Recommended is {2}MB" -f $maxPageSize, ([Math]::Truncate($totalPhysicalMemory / 1MB)), $recommendedPageFileSize
}
else
{
$displayValue = "{0}MB" -f $recommendedPageFileSize
$displayWriteType = "Grey"
}
}
#32GB = 1024 * 1024 * 1024 * 32 = 34,359,738,368
elseif ($totalPhysicalMemory -ge 34359738368)
{
if ($maxPageSize -eq 32778)
{
$displayValue = "{0}MB" -f $maxPageSize
$displayWriteType = "Grey"
}
else
{
$displayValue = "{0}MB `r`n`t`tWarning: Pagefile should be capped at 32778MB for 32GB plus 10MB - Article: https://docs.microsoft.com/en-us/exchange/exchange-2013-sizing-and-configuration-recommendations-exchange-2013-help#pagefile" -f $maxPageSize
}
}
else
{
$testingValue.RecommendedPageFile = ($recommendedPageFileSize = [Math]::Round(($totalPhysicalMemory / 1MB) + 10))
if ($recommendedPageFileSize -ne $maxPageSize)
{
$displayValue = "{0}MB `r`n`t`tWarning: Page File is not set to Total System Memory plus 10MB which should be {1}MB" -f $maxPageSize, $recommendedPageFileSize
}
else
{
$displayValue = "{0}MB" -f $maxPageSize
$displayWriteType = "Grey"
}
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Page File Size" -Details $displayValue `
-DisplayGroupingKey $keyOSInformation `
-DisplayWriteType $displayWriteType `
-DisplayTestingValue $testingValue `
-AnalyzedInformation $analyzedResults
if ($osInformation.PowerPlan.HighPerformanceSet)
{
$analyzedResults = Add-AnalyzedResultInformation -Name "Power Plan" -Details ($osInformation.PowerPlan.PowerPlanSetting) `
-DisplayGroupingKey $keyOSInformation `
-DisplayWriteType "Green" `
-AnalyzedInformation $analyzedResults
}
else
{
$displayValue = "{0} --- Error" -f $osInformation.PowerPlan.PowerPlanSetting
$analyzedResults = Add-AnalyzedResultInformation -Name "Power Plan" -Details $displayValue `
-DisplayGroupingKey $keyOSInformation `
-DisplayWriteType "Red" `
-AnalyzedInformation $analyzedResults
}
if ($osInformation.NetworkInformation.HttpProxy -eq "<None>")
{
$analyzedResults = Add-AnalyzedResultInformation -Name "Http Proxy Setting" -Details ($osInformation.NetworkInformation.HttpProxy) `
-DisplayGroupingKey $keyOSInformation `
-HtmlDetailsCustomValue "None" `
-AnalyzedInformation $analyzedResults
}
else
{
$displayValue = "{0} --- Warning this can cause client connectivity issues." -f $osInformation.NetworkInformation.HttpProxy
$analyzedResults = Add-AnalyzedResultInformation -Name "Http Proxy Setting" -Details $displayValue `
-DisplayGroupingKey $keyOSInformation `
-DisplayWriteType "Yellow" `
-DisplayTestingValue ($osInformation.NetworkInformation.HttpProxy) `
-AnalyzedInformation $analyzedResults
}
$displayWriteType2012 = "Yellow"
$displayWriteType2013 = "Yellow"
$displayValue2012 = "Unknown"
$displayValue2013 = "Unknown"
if ($osInformation.VcRedistributable -ne $null)
{
Write-VerboseOutput("VCRedist2012 Testing value: {0}" -f [HealthChecker.VCRedistVersion]::VCRedist2012.value__)
Write-VerboseOutput("VCRedist2013 Testing value: {0}" -f [HealthChecker.VCRedistVersion]::VCRedist2013.value__)
$vc2013Required = $exchangeInformation.BuildInformation.ServerRole -ne [HealthChecker.ExchangeServerRole]::Edge
$displayValue2012 = "Redistributable is outdated"
$displayValue2013 = "Redistributable is outdated"
foreach ($detectedVisualRedistVersion in $osInformation.VcRedistributable)
{
Write-VerboseOutput("Testing {0} version id '{1}'" -f $detectedVisualRedistVersion.DisplayName, $detectedVisualRedistVersion.VersionIdentifier)
if ($detectedVisualRedistVersion.VersionIdentifier -eq [HealthChecker.VCRedistVersion]::VCRedist2012)
{
$displayValue2012 = "{0} Version is current" -f $detectedVisualRedistVersion.DisplayVersion
$displayWriteType2012 = "Green"
}
elseif ($vc2013Required -and
$detectedVisualRedistVersion.VersionIdentifier -eq [HealthChecker.VCRedistVersion]::VCRedist2013)
{
$displayWriteType2013 = "Green"
$displayValue2013 = "{0} Version is current" -f $detectedVisualRedistVersion.DisplayVersion
}
}
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Visual C++ 2012" -Details $displayValue2012 `
-DisplayGroupingKey $keyOSInformation `
-DisplayWriteType $displayWriteType2012 `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Visual C++ 2013" -Details $displayValue2013 `
-DisplayGroupingKey $keyOSInformation `
-DisplayWriteType $displayWriteType2013 `
-AnalyzedInformation $analyzedResults
if ($osInformation.VcRedistributable -ne $null -and
($displayWriteType2012 -eq "Yellow" -or
$displayWriteType2013 -eq "Yellow"))
{
$analyzedResults = Add-AnalyzedResultInformation -Details "Note: For more information about the latest C++ Redistributeable please visit: https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads`r`n`t`tThis is not a requirement to upgrade, only a notification to bring to your attention." `
-DisplayGroupingKey $keyOSInformation `
-DisplayCustomTabNumber 2 `
-DisplayWriteType "Yellow" `
-AnalyzedInformation $analyzedResults
}
$displayValue = "False"
$writeType = "Grey"
if ($osInformation.ServerPendingReboot)
{
$displayValue = "True --- Warning a reboot is pending and can cause issues on the server."
$writeType = "Yellow"
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Server Pending Reboot" -Details $displayValue `
-DisplayGroupingKey $keyOSInformation `
-DisplayWriteType $writeType `
-DisplayTestingValue ($osInformation.ServerPendingReboot) `
-AnalyzedInformation $analyzedResults
################################
# Processor/Hardware Information
################################
Write-VerboseOutput("Working on Processor/Hardware Information")
$analyzedResults = Add-AnalyzedResultInformation -Name "Type" -Details ($hardwareInformation.ServerType) `
-DisplayGroupingKey $keyHardwareInformation `
-AddHtmlOverviewValues $true `
-Htmlname "Hardware Type" `
-AnalyzedInformation $analyzedResults
if ($hardwareInformation.ServerType -eq [HealthChecker.ServerType]::Physical -or
$hardwareInformation.ServerType -eq [HealthChecker.ServerType]::AmazonEC2)
{
$analyzedResults = Add-AnalyzedResultInformation -Name "Manufacturer" -Details ($hardwareInformation.Manufacturer) `
-DisplayGroupingKey $keyHardwareInformation `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Model" -Details ($hardwareInformation.Model) `
-DisplayGroupingKey $keyHardwareInformation `
-AnalyzedInformation $analyzedResults
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Processor" -Details ($hardwareInformation.Processor.Name) `
-DisplayGroupingKey $keyHardwareInformation `
-AnalyzedInformation $analyzedResults
$value = $hardwareInformation.Processor.NumberOfProcessors
$processorName = "Number of Processors"
if ($hardwareInformation.ServerType -ne [HealthChecker.ServerType]::Physical)
{
$analyzedResults = Add-AnalyzedResultInformation -Name $processorName -Details $value `
-DisplayGroupingKey $keyHardwareInformation `
-AnalyzedInformation $analyzedResults
if ($hardwareInformation.ServerType -eq [HealthChecker.ServerType]::VMWare)
{
$analyzedResults = Add-AnalyzedResultInformation -Details "Note: Please make sure you are following VMware's performance recommendation to get the most out of your guest machine. VMware blog 'Does corespersocket Affect Performance?' https://blogs.vmware.com/vsphere/2013/10/does-corespersocket-affect-performance.html" `
-DisplayGroupingKey $keyHardwareInformation `
-DisplayCustomTabNumber 2 `
-AnalyzedInformation $analyzedResults
}
}
elseif ($value -gt 2)
{
$analyzedResults = Add-AnalyzedResultInformation -Name $processorName -Details ("{0} - Error: Recommended to only have 2 Processors" -f $value) `
-DisplayGroupingKey $keyHardwareInformation `
-DisplayWriteType "Red" `
-DisplayTestingValue $value `
-HtmlDetailsCustomValue $value `
-AnalyzedInformation $analyzedResults
}
else
{
$analyzedResults = Add-AnalyzedResultInformation -Name $processorName -Details $value `
-DisplayGroupingKey $keyHardwareInformation `
-DisplayWriteType "Green" `
-AnalyzedInformation $analyzedResults
}
$physicalValue = $hardwareInformation.Processor.NumberOfPhysicalCores
$logicalValue = $hardwareInformation.Processor.NumberOfLogicalCores
$displayWriteType = "Green"
if (($logicalValue -gt 24 -and
$exchangeInformation.BuildInformation.MajorVersion -lt [HealthChecker.ExchangeMajorVersion]::Exchange2019) -or
$logicalValue -gt 48)
{
$displayWriteType = "Yellow"
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Number of Physical Cores" -Details $physicalValue `
-DisplayGroupingKey $keyHardwareInformation `
-DisplayWriteType $displayWriteType `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Number of Logical Cores" -Details $logicalValue `
-DisplayGroupingKey $keyHardwareInformation `
-DisplayWriteType $displayWriteType `
-AddHtmlOverviewValues $true `
-AnalyzedInformation $analyzedResults
if ($logicalValue -gt $physicalValue)
{
$analyzedResults = Add-AnalyzedResultInformation -Name "Hyper-Threading" -Details "Enabled --- Error: Having Hyper-Threading enabled goes against best practices and can cause performance issues. Please disable as soon as possible." `
-DisplayGroupingKey $keyHardwareInformation `
-DisplayWriteType "Red" `
-DisplayTestingValue $true `
-AnalyzedInformation $analyzedResults
if ($hardwareInformation.ServerType -eq [HealthChecker.ServerType]::AmazonEC2)
{
$analyzedResults = Add-AnalyzedResultInformation -Details "Error: For high-performance computing (HPC) application, like Exchange, Amazon recommends that you have Hyper-Threading Technology disabled in their service. More informaiton: https://aws.amazon.com/blogs/compute/disabling-intel-hyper-threading-technology-on-amazon-ec2-windows-instances/" `
-DisplayGroupingKey $keyHardwareInformation `
-DisplayCustomTabNumber 2 `
-DisplayWriteType "Red" `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
if ($hardwareInformation.Processor.Name.StartsWith("AMD"))
{
$analyzedResults = Add-AnalyzedResultInformation -Details "This script may incorrectly report that Hyper-Threading is enabled on certain AMD processors. Check with the manufacturer to see if your mondel supports SMT." `
-DisplayGroupingKey $keyHardwareInformation `
-DisplayCustomTabNumber 2 `
-DisplayWriteType "Yellow" `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
}
else
{
$analyzedResults = Add-AnalyzedResultInformation -Name "Hyper-Threading" -Details "Disabled" `
-DisplayGroupingKey $keyHardwareInformation `
-DisplayWriteType "Green" `
-DisplayTestingValue $false `
-AnalyzedInformation $analyzedResults
}
#NUMA BIOS CHECK - AKA check to see if we can properly see all of our cores on the box
$displayWriteType = "Yellow"
$testingValue = "Unknown"
$displayValue = [string]::Empty
if ($hardwareInformation.Model.Contains("ProLiant"))
{
$name = "NUMA Group Size Optimization"
if ($hardwareInformation.Processor.EnvironmentProcessorCount -eq -1)
{
$displayValue = "Unknown `r`n`t`tWarning: If this is set to Clustered, this can cause multiple types of issues on the server"
}
elseif ($hardwareInformation.Processor.EnvironmentProcessorCount -ne $logicalValue)
{
$displayValue = "Clustered `r`n`t`tError: This setting should be set to Flat. By having this set to Clustered, we will see multiple different types of issues."
$testingValue = "Clustered"
$displayWriteType = "Red"
}
else
{
$displayValue = "Flat"
$testingValue = "Flat"
$displayWriteType = "Green"
}
}
else
{
$name = "All Processor Cores Visible"
if ($hardwareInformation.Processor.EnvironmentProcessorCount -eq -1)
{
$displayValue = "Unknown `r`n`t`tWarning: If we aren't able to see all processor cores from Exchange, we could see performance related issues."
}
elseif ($hardwareInformation.Processor.EnvironmentProcessorCount -ne $logicalValue)
{
$displayValue = "Failed `r`n`t`tError: Not all Processor Cores are visible to Exchange and this will cause a performance impact"
$displayWriteType = "Red"
$testingValue = "Failed"
}
else
{
$displayWriteType = "Green"
$displayValue = "Passed"
$testingValue = "Passed"
}
}
$analyzedResults = Add-AnalyzedResultInformation -Name $name -Details $displayValue `
-DisplayGroupingKey $keyHardwareInformation `
-DisplayWriteType $displayWriteType `
-DisplayTestingValue $testingValue `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Max Processor Speed" -Details ($hardwareInformation.Processor.MaxMegacyclesPerCore) `
-DisplayGroupingKey $keyHardwareInformation `
-AnalyzedInformation $analyzedResults
if ($hardwareInformation.Processor.ProcessorIsThrottled)
{
$currentSpeed = $hardwareInformation.Processor.CurrentMegacyclesPerCore
$analyzedResults = Add-AnalyzedResultInformation -Name "Current Processor Speed" -Details ("{0} --- Error: Processor appears to be throttled." -f $currentSpeed) `
-DisplayGroupingKey $keyHardwareInformation `
-DisplayWriteType "Red" `
-DisplayTestingValue $currentSpeed `
-AnalyzedInformation $analyzedResults
$displayValue = "Error: Power Plan is NOT set to `"High Performance`". This change doesn't require a reboot and takes affect right away. Re-run script after doing so"
if ($osInformation.PowerPlan.HighPerformanceSet)
{
$displayValue = "Error: Power Plan is set to `"High Performance`", so it is likely that we are throttling in the BIOS of the computer settings."
}
$analyzedResults = Add-AnalyzedResultInformation -Details $displayValue `
-DisplayGroupingKey $keyHardwareInformation `
-DisplayWriteType "Red" `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
$totalPhysicalMemory = [System.Math]::Round($hardwareInformation.TotalMemory / 1024 / 1024 / 1024)
$displayWriteType = "Yellow"
$displayDetails = [string]::Empty
if ($exchangeInformation.BuildInformation.MajorVersion -eq [HealthChecker.ExchangeMajorVersion]::Exchange2019)
{
if ($totalPhysicalMemory -gt 256)
{
$displayDetails = "{0} GB `r`n`t`tWarning: We recommend for the best performance to be scaled at or below 256 GB of Memory" -f $totalPhysicalMemory
}
elseif ($totalPhysicalMemory -lt 64 -and
$exchangeInformation.BuildInformation.ServerRole -eq [HealthChecker.ExchangeServerRole]::Edge)
{
$displayDetails = "{0} GB `r`n`t`tWarning: We recommend for the best performance to have a minimum of 64GB of RAM installed on the machine." -f $totalPhysicalMemory
}
elseif ($totalPhysicalMemory -lt 128)
{
$displayDetails = "{0} GB `r`n`t`tWarning: We recommend for the best performance to have a minimum of 128GB of RAM installed on the machine." -f $totalPhysicalMemory
}
else
{
$displayDetails = "{0} GB" -f $totalPhysicalMemory
$displayWriteType = "Grey"
}
}
elseif ($totalPhysicalMemory -gt 192 -and
$exchangeInformation.BuildInformation.MajorVersion -eq [HealthChecker.ExchangeMajorVersion]::Exchange2016)
{
$displayDetails = "{0} GB `r`n`t`tWarning: We recommend for the best performance to be scaled at or below 192 GB of Memory." -f $totalPhysicalMemory
}
elseif ($totalPhysicalMemory -gt 96 -and
$exchangeInformation.BuildInformation.MajorVersion -eq [HealthChecker.ExchangeMajorVersion]::Exchange2013)
{
$displayDetails = "{0} GB `r`n`t`tWarning: We recommend for the best performance to be scaled at or below 96GB of Memory." -f $totalPhysicalMemory
}
else
{
$displayDetails = "{0} GB" -f $totalPhysicalMemory
$displayWriteType = "Grey"
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Physical Memory" -Details $displayDetails `
-DisplayGroupingKey $keyHardwareInformation `
-DipslayTestingValue $totalPhysicalMemory `
-DisplayWriteType $displayWriteType `
-AddHtmlOverviewValues $true `
-AnalyzedInformation $analyzedResults
################################
#NIC Settings Per Active Adapter
################################
Write-VerboseOutput("Working on NIC Settings Per Active Adapter Information")
foreach ($adapter in $osInformation.NetworkInformation.NetworkAdapters)
{
if ($adapter.Description -eq "Remote NDIS Compatible Device")
{
Write-VerboseOutput("Remote NDSI Compatible Device found. Ignoring NIC.")
continue
}
$value = "{0} [{1}]" -f $adapter.Description, $adapter.Name
$analyzedResults = Add-AnalyzedResultInformation -Name "Interface Description" -Details $value `
-DisplayGroupingKey $keyNICSettings `
-DisplayCustomTabNumber 1 `
-AnalyzedInformation $analyzedResults
if ($osInformation.BuildInformation.MajorVersion -ge [HealthChecker.OSServerVersion]::Windows2012R2)
{
Write-VerboseOutput("On Windows 2012 R2 or new. Can provide more details on the NICs")
$driverDate = $adapter.DriverDate
$detailsValue = $driverDate
if ($hardwareInformation.ServerType -eq [HealthChecker.ServerType]::Physical -or
$hardwareInformation.ServerType -eq [HealthChecker.ServerType]::AmazonEC2)
{
if ($driverDate -eq $null -or
$driverDate -eq [DateTime]::MaxValue)
{
$detailsValue = "Unknown"
}
elseif ((New-TimeSpan -Start $date -End $driverDate).Days -lt [int]-365)
{
$analyzedResults = Add-AnalyzedResultInformation -Details "Warning: NIC driver is over 1 year old. Verify you are at the latest version." `
-DisplayGroupingKey $keyNICSettings `
-DisplayWriteType "Yellow" `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Driver Date" -Details $detailsValue `
-DisplayGroupingKey $keyNICSettings `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Driver Version" -Details ($adapter.DriverVersion) `
-DisplayGroupingKey $keyNICSettings `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "MTU Size" -Details ($adapter.MTUSize) `
-DisplayGroupingKey $keyNICSettings `
-AnalyzedInformation $analyzedResults
$writeType = "Yellow"
$testingValue = $null
if ($adapter.RssEnabledValue -eq 0)
{
$detailsValue = "False --- Warning: Enabling RSS is recommended."
$testingValue = $false
}
elseif ($adapter.RssEnabledValue -eq 1)
{
$detailsValue = "True"
$testingValue = $true
$writeType = "Green"
}
else
{
$detailsValue = "No RSS Feature Detected."
}
$analyzedResults = Add-AnalyzedResultInformation -Name "RSS Enabled" -Details $detailsValue `
-DisplayGroupingKey $keyNICSettings `
-DisplayWriteType $writeType `
-DisplayTestingValue $testingValue `
-AnalyzedInformation $analyzedResults
}
else
{
Write-VerboseOutput("On Windows 2012 or older and can't get advanced NIC settings")
}
$linkSpeed = $adapter.LinkSpeed
$displayValue = "{0} --- This may not be accurate due to virtualized hardware" -f $linkSpeed
if ($hardwareInformation.ServerType -eq [HealthChecker.ServerType]::Physical -or
$hardwareInformation.ServerType -eq [HealthChecker.ServerType]::AmazonEC2)
{
$displayValue = $linkSpeed
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Link Speed" -Details $displayValue `
-DisplayGroupingKey $keyNICSettings `
-DisplayTestingValue $linkSpeed `
-AnalyzedInformation $analyzedResults
$displayValue = "{0}" -f $adapter.IPv6Enabled
$displayWriteType = "Grey"
$testingValue = $adapter.IPv6Enabled
if ($osInformation.NetworkInformation.IPv6DisabledComponents -ne 255 -and
$adapter.IPv6Enabled -eq $false)
{
$displayValue = "{0} --- Warning" -f $adapter.IPv6Enabled
$displayWriteType = "Yellow"
$testingValue = $false
}
$analyzedResults = Add-AnalyzedResultInformation -Name "IPv6 Enabled" -Details $displayValue `
-DisplayGroupingKey $keyNICSettings `
-DisplayWriteType $displayWriteType `
-DisplayTestingValue $TestingValue `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "IPv4 Address" `
-DisplayGroupingKey $keyNICSettings `
-AnalyzedInformation $analyzedResults
foreach ($address in $adapter.IPv4Addresses)
{
$displayValue = "{0}\{1}" -f $address.Address, $address.Subnet
if ($address.DefaultGateway -ne [string]::Empty)
{
$displayValue += " Gateway: {0}" -f $address.DefaultGateway
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Address" -Details $displayValue `
-DisplayGroupingKey $keyNICSettings `
-DisplayCustomTabNumber 3 `
-AnalyzedInformation $analyzedResults
}
$analyzedResults = Add-AnalyzedResultInformation -Name "IPv6 Address" `
-DisplayGroupingKey $keyNICSettings `
-AnalyzedInformation $analyzedResults
foreach ($address in $adapter.IPv6Addresses)
{
$displayValue = "{0}\{1}" -f $address.Address, $address.Subnet
if ($address.DefaultGateway -ne [string]::Empty)
{
$displayValue += " Gateway: {0}" -f $address.DefaultGateway
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Address" -Details $displayValue `
-DisplayGroupingKey $keyNICSettings `
-DisplayCustomTabNumber 3 `
-AnalyzedInformation $analyzedResults
}
$analyzedResults = Add-AnalyzedResultInformation -Name "DNS Server" -Details $adapter.DnsServer `
-DisplayGroupingKey $keyNICSettings `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Registered In DNS" -Details $adapter.RegisteredInDns `
-DisplayGroupingKey $keyNICSettings `
-AnalyzedInformation $analyzedResults
#Assuming that all versions of Hyper-V doesn't allow sleepy NICs
if (($hardwareInformation.ServerType -ne [HealthChecker.ServerType]::HyperV) -and ($adapter.PnPCapabilities -ne "MultiplexorNoPnP"))
{
$displayWriteType = "Grey"
$displayValue = $adapter.SleepyNicDisabled
if (!$adapter.SleepyNicDisabled)
{
$displayWriteType = "Yellow"
$displayValue = "False --- Warning: It's recommended to disable NIC power saving options`r`n`t`t`tMore Information: http://support.microsoft.com/kb/2740020"
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Sleepy NIC Disabled" -Details $displayValue `
-DisplayGroupingKey $keyNICSettings `
-DisplayWriteType $displayWriteType `
-DisplayTestingValue $adapter.SleepyNicDisabled `
-AnalyzedInformation $analyzedResults
}
$adapterDescription = $adapter.Description
$cookedValue = 0
$foundCounter = $false
if ($osInformation.NetworkInformation.PacketsReceivedDiscarded -eq $null)
{
Write-VerboseOutput("PacketsReceivedDiscarded is null")
continue
}
foreach ($prdInstance in $osInformation.NetworkInformation.PacketsReceivedDiscarded)
{
$instancePath = $prdInstance.Path
$startIndex = $instancePath.IndexOf("(") + 1
$charLength = $instancePath.Substring($startIndex, ($instancePath.IndexOf(")") - $startIndex)).Length
$instanceName = $instancePath.Substring($startIndex, $charLength)
$possibleInstanceName = $adapterDescription.Replace("#","_")
if ($instanceName -eq $adapterDescription -or
$instanceName -eq $possibleInstanceName)
{
$cookedValue = $prdInstance.CookedValue
$foundCounter = $true
break
}
}
$displayWriteType = "Yellow"
$displayValue = $cookedValue
$baseDisplayValue = "{0} --- {1}: This value should be at 0."
$knownIssue = $false
if ($foundCounter)
{
if ($cookedValue -eq 0)
{
$displayWriteType = "Green"
}
elseif ($cookedValue -lt 1000)
{
$displayValue = $baseDisplayValue -f $cookedValue, "Warning"
}
else
{
$displayWriteType = "Red"
$displayValue = [string]::Concat(($baseDisplayValue -f $cookedValue, "Error"), "We are also seeing this value being rather high so this can cause a performance impacted on a system.")
}
if ($adapterDescription -like "*vmxnet3*" -and
$cookedValue -gt 0)
{
$knownIssue = $true
}
}
else
{
$displayValue = "Couldn't find value for the counter."
$cookedValue = $null
$displayWriteType = "Grey"
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Packets Received Discarded" -Details $displayValue `
-DisplayGroupingKey $keyNICSettings `
-DisplayTestingValue $cookedValue `
-DisplayWriteType $displayWriteType `
-AnalyzedInformation $analyzedResults
if ($knownIssue)
{
$analyzedResults = Add-AnalyzedResultInformation -Details "Known Issue with vmxnet3: 'Large packet loss at the guest operating system level on the VMXNET3 vNIC in ESXi (2039495)' - https://kb.vmware.com/s/article/2039495" `
-DisplayGroupingKey $keyNICSettings `
-DisplayWriteType "Yellow" `
-DisplayCustomTabNumber 3 `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
}
if ($osInformation.NetworkInformation.NetworkAdapters.Count -gt 1)
{
$analyzedResults = Add-AnalyzedResultInformation -Details "Multiple active network adapters detected. Exchange 2013 or greater may not need separate adapters for MAPI and replication traffic. For details please refer to https://docs.microsoft.com/en-us/exchange/planning-for-high-availability-and-site-resilience-exchange-2013-help#NR" `
-DisplayGroupingKey $keyNICSettings `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
if ($osInformation.NetworkInformation.IPv6DisabledOnNICs)
{
$displayWriteType = "Grey"
$displayValue = "True"
$testingValue = $true
if ($osInformation.NetworkInformation.IPv6DisabledComponents -ne 255)
{
$displayWriteType = "Red"
$testingValue = $false
$displayValue = "False `r`n`t`tError: IPv6 is disabled on some NIC level settings but not fully disabled. DisabledComponents registry key currently set to '{0}'. For details please refer to the following articles: `r`n`t`thttps://docs.microsoft.com/en-us/archive/blogs/rmilne/disabling-ipv6-and-exchange-going-all-the-way `r`n`t`thttps://support.microsoft.com/en-us/help/929852/guidance-for-configuring-ipv6-in-windows-for-advanced-users" -f $osInformation.NetworkInformation.DisabledComponents
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Disable IPv6 Correctly" -Details $displayValue `
-DisplayGroupingKey $keyNICSettings `
-DisplayWriteType $displayWriteType `
-DisplayCustomTabNumber 1 `
-AnalyzedInformation $analyzedResults
}
################
#TCP/IP Settings
################
Write-VerboseOutput("Working on TCP/IP Settings")
$tcpKeepAlive = $osInformation.NetworkInformation.TCPKeepAlive
if ($tcpKeepAlive -eq 0)
{
$displayValue = "Not Set `r`n`t`tError: Without this value the KeepAliveTime defaults to two hours, which can cause connectivity and performance issues between network devices such as firewalls and load balancers depending on their configuration. `r`n`t`tMore details: https://techcommunity.microsoft.com/t5/Exchange-Team-Blog/Checklist-for-troubleshooting-Outlook-connectivity-in-Exchange/ba-p/604792"
$displayWriteType = "Red"
}
elseif ($tcpKeepAlive -lt 900000 -or
$tcpKeepAlive -gt 1800000)
{
$displayValue = "{0} `r`n`t`tWarning: Not configured optimally, recommended value between 15 to 30 minutes (900000 and 1800000 decimal). `r`n`t`tMore details: https://techcommunity.microsoft.com/t5/Exchange-Team-Blog/Checklist-for-troubleshooting-Outlook-connectivity-in-Exchange/ba-p/604792" -f $tcpKeepAlive
$displayWriteType = "Yellow"
}
else
{
$displayValue = $tcpKeepAlive
$displayWriteType = "Green"
}
$analyzedResults = Add-AnalyzedResultInformation -Name "TCP/IP Settings" -Details $displayValue `
-DisplayGroupingKey $keyFrequentConfigIssues `
-DisplayWriteType $displayWriteType `
-DisplayTestingValue $tcpKeepAlive `
-HtmlName "TCPKeepAlive" `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "RPC Min Connection Timeout" -Details ("{0} `r`n`t`tMore Information: https://blogs.technet.microsoft.com/messaging_with_communications/2012/06/06/outlook-anywhere-network-timeout-issue/" -f $osInformation.NetworkInformation.RpcMinConnectionTimeout) `
-DisplayGroupingKey $keyFrequentConfigIssues `
-HtmlName "RPC Minimum Connection Timeout" `
-AnalyzedInformation $analyzedResults
$displayValue = $exchangeInformation.RegistryValues.CtsProcessorAffinityPercentage
$displayWriteType = "Green"
if ($exchangeInformation.RegistryValues.CtsProcessorAffinityPercentage -ne 0)
{
$displayWriteType = "Red"
$displayValue = "{0} `r`n`t`tError: This can cause an impact to the server's search performance. This should only be used a temporary fix if no other options are available vs a long term solution." -f $exchangeInformation.RegistryValues.CtsProcessorAffinityPercentage
}
$analyzedResults = Add-AnalyzedResultInformation -Name "CTS Processor Affinity Percentage" -Details $displayValue `
-DisplayGroupingKey $keyFrequentConfigIssues `
-DisplayWriteType $displayWriteType `
-DisplayTestingValue ($exchangeInformation.RegistryValues.CtsProcessorAffinityPercentage) `
-HtmlName "CtsProcessorAffinityPercentage" `
-AnalyzedInformation $analyzedResults
$displayValue = $osInformation.CredentialGuardEnabled
$displayWriteType = "Grey"
if($osInformation.CredentialGuardEnabled)
{
$displayValue = "{0} `r`n`t`tError: Credential Guard is not supported on an Exchange Server. This can cause a performance hit on the server." -f $osInformation.CredentialGuardEnabled
$displayWriteType = "Red"
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Credential Guard Enabled" -Details $displayValue `
-DisplayGroupingKey $keyFrequentConfigIssues `
-DisplayWriteType $displayWriteType `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "LmCompatibilityLevel Settings" -Details ($osInformation.LmCompatibility.RegistryValue) `
-DisplayGroupingKey $keySecuritySettings `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Description" -Details ($osInformation.LmCompatibility.Description) `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
##############
# TLS Settings
##############
Write-VerboseOutput("Working on TLS Settings")
$tlsVersions = @("1.0","1.1","1.2")
$currentNetVersion = $osInformation.TLSSettings["NETv4"]
foreach ($tlsKey in $tlsVersions)
{
$currentTlsVersion = $osInformation.TLSSettings[$tlsKey]
$analyzedResults = Add-AnalyzedResultInformation -Details ("TLS {0}" -f $tlsKey) `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 1 `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name ("Server Enabled") -Details ($currentTlsVersion.ServerEnabled) `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name ("Server Disabled By Default") -Details ($currentTlsVersion.ServerDisabledByDefault) `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name ("Client Enabled") -Details ($currentTlsVersion.ClientEnabled) `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name ("Client Disabled By Default") -Details ($currentTlsVersion.ClientDisabledByDefault) `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-AnalyzedInformation $analyzedResults
if ($currentTlsVersion.ServerEnabled -ne $currentTlsVersion.ClientEnabled)
{
$detectedTlsMismatch = $true
$analyzedResults = Add-AnalyzedResultInformation -Details ("Error: Mismatch in TLS version for client and server. Exchange can be both client and a server. This can cause issues within Exchange for communication.") `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 3 `
-DisplayWriteType "Red" `
-AnalyzedInformation $analyzedResults
}
if (($tlsKey -eq "1.0" -or
$tlsKey -eq "1.1") -and (
$currentTlsVersion.ServerEnabled -eq $false -or
$currentTlsVersion.ClientEnabled -eq $false -or
$currentTlsVersion.ServerDisabledByDefault -or
$currentTlsVersion.ClientDisabledByDefault) -and
($currentNetVersion.SystemDefaultTlsVersions -eq $false -or
$currentNetVersion.WowSystemDefaultTlsVersions -eq $false))
{
$analyzedResults = Add-AnalyzedResultInformation -Details ("Error: Failed to set .NET SystemDefaultTlsVersions. Please visit on how to properly enable TLS 1.2 https://techcommunity.microsoft.com/t5/Exchange-Team-Blog/Exchange-Server-TLS-guidance-Part-2-Enabling-TLS-1-2-and/ba-p/607761") `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 3 `
-DisplayWriteType "Red" `
-AnalyzedInformation $analyzedResults
}
}
if ($detectedTlsMismatch)
{
$displayValues = @("Exchange Server TLS guidance Part 1: Getting Ready for TLS 1.2: https://techcommunity.microsoft.com/t5/Exchange-Team-Blog/Exchange-Server-TLS-guidance-part-1-Getting-Ready-for-TLS-1-2/ba-p/607649",
"Exchange Server TLS guidance Part 2: Enabling TLS 1.2 and Identifying Clients Not Using It: https://techcommunity.microsoft.com/t5/Exchange-Team-Blog/Exchange-Server-TLS-guidance-Part-2-Enabling-TLS-1-2-and/ba-p/607761",
"Exchange Server TLS guidance Part 3: Turning Off TLS 1.0/1.1: https://techcommunity.microsoft.com/t5/Exchange-Team-Blog/Exchange-Server-TLS-guidance-Part-3-Turning-Off-TLS-1-0-1-1/ba-p/607898")
$analyzedResults = Add-AnalyzedResultInformation -Details "For More Information on how to properly set TLS follow these blog posts:" `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-DisplayWriteType "Yellow" `
-AnalyzedInformation $analyzedResults
foreach ($displayValue in $displayValues)
{
$analyzedResults = Add-AnalyzedResultInformation -Details $displayValue `
-DisplayGroupingKey $keySecuritySettings `
-DisplayWriteType "Yellow" `
-DisplayCustomTabNumber 3 `
-AnalyzedInformation $analyzedResults
}
}
foreach($certificate in $exchangeInformation.ExchangeCertificates)
{
if($certificate.LifetimeInDays -ge 60)
{
$displayColor = "Green"
}
elseif($certificate.LifetimeInDays -ge 30)
{
$displayColor = "Yellow"
}
else
{
$displayColor = "Red"
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Certificate" `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 1 `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "FriendlyName" -Details $certificate.FriendlyName `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Thumbprint" -Details $certificate.Thumbprint `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Lifetime in days" -Details $certificate.LifetimeInDays `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-DisplayWriteType $displayColor `
-AnalyzedInformation $analyzedResults
if($certificate.PublicKeySize -lt 2048)
{
$additionalDisplayValue = "It's recommended to use a key size of at least 2048 bit."
$analyzedResults = Add-AnalyzedResultInformation -Name "Key size" -Details $certificate.PublicKeySize `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-DisplayWriteType "Red" `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Details $additionalDisplayValue `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-DisplayWriteType "Red" `
-AnalyzedInformation $analyzedResults
}
else
{
$analyzedResults = Add-AnalyzedResultInformation -Name "Key size" -Details $certificate.PublicKeySize `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-AnalyzedInformation $analyzedResults
}
$analyzedResults = Add-AnalyzedResultInformation -Name "Bound to services" -Details $certificate.Services `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Current Auth Certificate" -Details $certificate.IsCurrentAuthConfigCertificate `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "SAN Certificate" -Details $certificate.IsSanCertificate `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Name "Namespaces" `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-AnalyzedInformation $analyzedResults
foreach($namespace in $certificate.Namespaces)
{
$analyzedResults = Add-AnalyzedResultInformation -Details $namespace `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 3 `
-AnalyzedInformation $analyzedResults
}
}
if($exchangeInformation.ExchangeCertificates.IsCurrentAuthConfigCertificate.Contains($true))
{
$analyzedResults = Add-AnalyzedResultInformation -Name "Valid Auth Certificate Found On Server" -Details $true `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 1 `
-DisplayWriteType "Green" `
-AnalyzedInformation $analyzedResults
}
else
{
$analyzedResults = Add-AnalyzedResultInformation -Name "Valid Auth Certificate Found On Server" -Details $false `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 1 `
-DisplayWriteType "Red" `
-AnalyzedInformation $analyzedResults
$analyzedResults = Add-AnalyzedResultInformation -Details "No valid Auth Certificate found. This may cause several problems --- Error" `
-DisplayGroupingKey $keySecuritySettings `
-DisplayCustomTabNumber 2 `
-DisplayWriteType "Red" `
-AnalyzedInformation $analyzedResults
}
$additionalDisplayValue = [string]::Empty
$smb1Status = $osInformation.Smb1ServerSettings.Smb1Status
if ($osInformation.BuildInformation.MajorVersion -gt [HealthChecker.OSServerVersion]::Windows2012)
{
$displayValue = "False"
$writeType = "Green"
if ($smb1Status -band 1)
{
$displayValue = "Failed to get install status"
$writeType = "Yellow"
}
elseif ($smb1Status -band 2)
{
$displayValue = "True"
$writeType = "Red"
$additionalDisplayValue = "SMB1 should be uninstalled"
}
$analyzedResults = Add-AnalyzedResultInformation -Name "SMB1 Installed" -Details $displayValue `
-DisplayGroupingKey $keySecuritySettings `
-DisplayWriteType $writeType `
-AnalyzedInformation $analyzedResults
}
$writeType = "Green"
$displayValue = "True"
if ($smb1Status -band 8)
{
$displayValue = "Failed to get block status"
$writeType = "Yellow"
}
elseif ($smb1Status -band 16)
{
$displayValue = "False"
$writeType = "Red"
$additionalDisplayValue += " SMB1 should be blocked"
}
$analyzedResults = Add-AnalyzedResultInformation -Name "SMB1 Blocked" -Details $displayValue `
-DisplayGroupingKey $keySecuritySettings `
-DisplayWriteType $writeType `
-AnalyzedInformation $analyzedResults
if ($additionalDisplayValue -ne [string]::Empty)
{
$additionalDisplayValue += "`r`n`t`tMore Information: https://techcommunity.microsoft.com/t5/exchange-team-blog/exchange-server-and-smbv1/ba-p/1165615"
$analyzedResults = Add-AnalyzedResultInformation -Details $additionalDisplayValue.Trim() `
-DisplayGroupingKey $keySecuritySettings `
-DisplayWriteType "Yellow" `
-DisplayCustomTabNumber 2 `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
##########################
#Exchange Web App GC Mode#
##########################
if ($exchangeInformation.BuildInformation.ServerRole -ne [HealthChecker.ExchangeServerRole]::Edge)
{
Write-VerboseOutput("Working on Exchange Web App GC Mode")
$analyzedResults = Add-AnalyzedResultInformation -Name "Web App Pool" -Details "GC Server Mode Enabled | Status" `
-DisplayGroupingKey $keyWebApps `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
foreach ($webAppKey in $exchangeInformation.ApplicationPools.Keys)
{
$xmlData = [xml]$exchangeInformation.ApplicationPools[$webAppKey].Content
$testingValue = New-Object PSCustomObject
$testingValue | Add-Member -MemberType NoteProperty -Name "GCMode" -Value ($enabled = $xmlData.Configuration.Runtime.gcServer.Enabled -eq 'true')
$testingValue | Add-Member -MemberType NoteProperty -Name "Status" -Value ($status = $exchangeInformation.ApplicationPools[$webAppKey].Status)
$analyzedResults = Add-AnalyzedResultInformation -Name $webAppKey -Details ("{0} | {1}" -f $enabled, $status) `
-DisplayGroupingKey $keyWebApps `
-DisplayTestingValue $testingValue `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
}
######################
# Vulnerability Checks
######################
Function Test-VulnerabilitiesByBuildNumbersForDisplay {
param(
[Parameter(Mandatory=$true)][string]$ExchangeBuildRevision,
[Parameter(Mandatory=$true)][array]$SecurityFixedBuilds,
[Parameter(Mandatory=$true)][array]$CVENames
)
[int]$fileBuildPart = ($split = $ExchangeBuildRevision.Split("."))[0]
[int]$filePrivatePart = $split[1]
$Script:breakpointHit = $false
foreach ($securityFixedBuild in $SecurityFixedBuilds)
{
[int]$securityFixedBuildPart = ($split = $securityFixedBuild.Split("."))[0]
[int]$securityFixedPrivatePart = $split[1]
if($fileBuildPart -eq $securityFixedBuildPart)
{
$Script:breakpointHit = $true
}
if (($fileBuildPart -lt $securityFixedBuildPart) -or
($fileBuildPart -eq $securityFixedBuildPart -and
$filePrivatePart -lt $securityFixedPrivatePart))
{
foreach ($cveName in $CVENames)
{
$Script:AnalyzedInformation = Add-AnalyzedResultInformation -Name "Security Vulnerability" -Details ("{0}`r`n`t`tSee: https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/{0} for more information." -f $cveName) `
-DisplayGroupingKey $keySecuritySettings `
-DisplayTestingValue $cveName `
-DisplayWriteType "Red" `
-AddHtmlDetailRow $false `
-AnalyzedInformation $Script:AnalyzedInformation
}
$Script:AllVulnerabilitiesPassed = $false
break
}
if($Script:breakpointHit)
{
break
}
}
}
$Script:AllVulnerabilitiesPassed = $true
$Script:AnalyzedInformation = $analyzedResults
[string]$buildRevision = ("{0}.{1}" -f $exchangeInformation.BuildInformation.ExchangeSetup.FileBuildPart, $exchangeInformation.BuildInformation.ExchangeSetup.FilePrivatePart)
Write-VerboseOutput("Exchange Build Revision: {0}" -f $buildRevision)
Write-VerboseOutput("Exchange CU: {0}" -f ($exchangeCU = $exchangeInformation.BuildInformation.CU))
if ($exchangeInformation.BuildInformation.MajorVersion -eq [HealthChecker.ExchangeMajorVersion]::Exchange2013)
{
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU19)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1347.5","1365.3" -CVENames "CVE-2018-0924","CVE-2018-0940"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU20)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1365.7","1367.6" -CVENames "CVE-2018-8151","CVE-2018-8154","CVE-2018-8159"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU21)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1367.9","1395.7" -CVENames "CVE-2018-8302"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1395.8" -CVENames "CVE-2018-8265","CVE-2018-8448"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1395.10" -CVENames "CVE-2019-0586","CVE-2019-0588"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU22)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1473.3" -CVENames "CVE-2019-0686","CVE-2019-0724"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1473.4" -CVENames "CVE-2019-0817","CVE-2019-0858"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1473.5" -CVENames "ADV190018"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU23)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1497.3" -CVENames "CVE-2019-1084","CVE-2019-1136","CVE-2019-1137"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1497.4" -CVENames "CVE-2019-1373"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1497.6" -CVENames "CVE-2020-0688","CVE-2020-0692"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1497.7" -CVENames "CVE-2020-16969"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1497.8" -CVENames "CVE-2020-17083","CVE-2020-17084","CVE-2020-17085"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1497.10" -CVENames "CVE-2020-17117","CVE-2020-17132","CVE-2020-17142","CVE-2020-17143"
}
}
elseif ($exchangeInformation.BuildInformation.MajorVersion -eq [HealthChecker.ExchangeMajorVersion]::Exchange2016)
{
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU8)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1261.39","1415.4" -CVENames "CVE-2018-0924","CVE-2018-0940","CVE-2018-0941"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU9)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1415.7","1466.8" -CVENames "CVE-2018-8151","CVE-2018-8152","CVE-2018-8153","CVE-2018-8154","CVE-2018-8159"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU10)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1466.9","1531.6" -CVENames "CVE-2018-8374","CVE-2018-8302"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1531.8" -CVENames "CVE-2018-8265","CVE-2018-8448"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU11)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1531.8","1591.11" -CVENames "CVE-2018-8604"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1531.10","1591.13" -CVENames "CVE-2019-0586","CVE-2019-0588"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU12)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1591.16","1713.6" -CVENames "CVE-2019-0817","CVE-2018-0858"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1591.17","1713.7" -CVENames "ADV190018"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1713.5" -CVENames "CVE-2019-0686","CVE-2019-0724"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU13)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1713.8","1779.4" -CVENames "CVE-2019-1084","CVE-2019-1136","CVE-2019-1137"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1713.9","1779.5" -CVENames "CVE-2019-1233","CVE-2019-1266"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU14)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1779.7","1847.5" -CVENames "CVE-2019-1373"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU15)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1847.7","1913.7" -CVENames "CVE-2020-0688","CVE-2020-0692"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1847.10","1913.10" -CVENames "CVE-2020-0903"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU17)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "1979.6","2044.6" -CVENames "CVE-2020-16875"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU18)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "2044.7","2106.3" -CVENames "CVE-2020-16969"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "2044.8","2106.4" -CVENames "CVE-2020-17083","CVE-2020-17084","CVE-2020-17085"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "2044.12","2106.6" -CVENames "CVE-2020-17117","CVE-2020-17132","CVE-2020-17141","CVE-2020-17142","CVE-2020-17143"
}
if ($exchangeCU -ge [HealthChecker.ExchangeCULevel]::CU19)
{
Write-VerboseOutput("There are no known vulnerabilities in this Exchange Server Build.")
}
}
elseif ($exchangeInformation.BuildInformation.MajorVersion -eq [HealthChecker.ExchangeMajorVersion]::Exchange2019)
{
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU1)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "221.14" -CVENames "CVE-2019-0586","CVE-2019-0588"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "221.16","330.7" -CVENames "CVE-2019-0817","CVE-2019-0858"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "221.17","330.8" -CVENames "ADV190018"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "330.6" -CVENames "CVE-2019-0686","CVE-2019-0724"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU2)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "330.9","397.5" -CVENames "CVE-2019-1084","CVE-2019-1137"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "397.6","330.10" -CVENames "CVE-2019-1233","CVE-2019-1266"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU3)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "397.9","464.7" -CVENames "CVE-2019-1373"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU4)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "464.11","529.8" -CVENames "CVE-2020-0688","CVE-2020-0692"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "464.14","529.11" -CVENames "CVE-2020-0903"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU6)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "595.6","659.6" -CVENames "CVE-2020-16875"
}
if ($exchangeCU -le [HealthChecker.ExchangeCULevel]::CU7)
{
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "659.7","721.3" -CVENames "CVE-2020-16969"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "659.8","721.4" -CVENames "CVE-2020-17083","CVE-2020-17084","CVE-2020-17085"
Test-VulnerabilitiesByBuildNumbersForDisplay -ExchangeBuildRevision $buildRevision -SecurityFixedBuilds "659.11","721.6" -CVENames "CVE-2020-17117","CVE-2020-17132","CVE-2020-17141","CVE-2020-17142","CVE-2020-17143"
}
if ($exchangeCU -ge [HealthChecker.ExchangeCULevel]::CU8)
{
Write-VerboseOutput("There are no known vulnerabilities in this Exchange Server Build.")
}
}
else
{
Write-VerboseOutput("Unknown Version of Exchange")
$Script:AllVulnerabilitiesPassed = $false
}
#Description: Check for CVE-2020-0796 SMBv3 vulnerability
#Affected OS versions: Windows 10 build 1903 and 1909
#Fix: KB4551762
#Workaround: Disable SMBv3 compression
if ($exchangeInformation.BuildInformation.MajorVersion -eq [HealthChecker.ExchangeMajorVersion]::Exchange2019)
{
Write-VerboseOutput("Testing CVE: CVE-2020-0796")
$buildNumber = $osInformation.BuildInformation.VersionBuild.Split(".")[2]
if (($buildNumber -eq 18362 -or
$buildNumber -eq 18363) -and
($osInformation.RegistryValues.CurrentVersionUbr -lt 720))
{
Write-VerboseOutput("Build vulnerable to CVE-2020-0796. Checking if workaround is in place.")
$writeType = "Red"
$writeValue = "System Vulnerable"
if ($osInformation.RegistryValues.LanManServerDisabledCompression -eq 1)
{
Write-VerboseOutput("Workaround to disable affected SMBv3 compression is in place.")
$writeType = "Yellow"
$writeValue = "Workaround is in place"
}
else
{
Write-VerboseOutput("Workaround to disable affected SMBv3 compression is NOT in place.")
$Script:AllVulnerabilitiesPassed = $false
}
$analyzedResults = Add-AnalyzedResultInformation -Name "CVE-2020-0796" -Details ("{0}`r`n`t`tSee: https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2020-0796 for more information." -f $writeValue) `
-DisplayGroupingKey $keySecuritySettings `
-DisplayWriteType $writeType `
-AddHtmlDetailRow $false `
-AnalyzedInformation $analyzedResults
}
else
{
Write-VerboseOutput("System NOT vulnerable to CVE-2020-0796. Information URL: https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/CVE-2020-0796")
}
}
else
{
Write-VerboseOutput("Operating System NOT vulnerable to CVE-2020-0796.")
}
#Description: Check for CVE-2020-1147
#Affected OS versions: Every OS supporting .NET Core 2.1 and 3.1 and .NET Framework 2.0 SP2 or above
#Fix: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1147
#Workaround: N/A
$dllFileBuildPartToCheckAgainst = 3630
if ($osInformation.NETFramework.NetMajorVersion -eq [HealthChecker.NetMajorVersion]::Net4d8)
{
$dllFileBuildPartToCheckAgainst = 4190
}
Write-VerboseOutput("System.Data.dll FileBuildPart: {0} | LastWriteTimeUtc: {1}" -f ($systemDataDll = $osInformation.NETFramework.FileInformation["System.Data.dll"]).VersionInfo.FileBuildPart, `
$systemDataDll.LastWriteTimeUtc)
Write-VerboseOutput("System.Configuration.dll FileBuildPart: {0} | LastWriteTimeUtc: {1}" -f ($systemConfigurationDll = $osInformation.NETFramework.FileInformation["System.Configuration.dll"]).VersionInfo.FileBuildPart, `
$systemConfigurationDll.LastWriteTimeUtc)
if($systemDataDll.VersionInfo.FileBuildPart -ge $dllFileBuildPartToCheckAgainst -and
$systemConfigurationDll.VersionInfo.FileBuildPart -ge $dllFileBuildPartToCheckAgainst -and
$systemDataDll.LastWriteTimeUtc -ge ([System.Convert]::ToDateTime("06/05/2020", [System.Globalization.DateTimeFormatInfo]::InvariantInfo)) -and
$systemConfigurationDll.LastWriteTimeUtc -ge ([System.Convert]::ToDateTime("06/05/2020", [System.Globalization.DateTimeFormatInfo]::InvariantInfo)))
{
Write-VerboseOutput("System NOT vulnerable to {0}. Information URL: https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/{0}" -f "CVE-2020-1147")
}
else
{
$Script:AllVulnerabilitiesPassed = $false
$Script:AnalyzedInformation = Add-AnalyzedResultInformation -Name "Security Vulnerability" -Details ("{0}`r`n`t`tSee: https://portal.msrc.microsoft.com/en-us/security-guidance/advisory/{0} for more information." -f "CVE-2020-1147") `
-DisplayGroupingKey $keySecuritySettings `
-DisplayWriteType "Red" `
-AddHtmlDetailRow $false `
-AnalyzedInformation $Script:AnalyzedInformation
}
if ($Script:AllVulnerabilitiesPassed)
{
$Script:AnalyzedInformation = Add-AnalyzedResultInformation -Details "All known security issues in this version of the script passed." `
-DisplayGroupingKey $keySecuritySettings `
-DisplayWriteType "Green" `
-AddHtmlDetailRow $false `
-AnalyzedInformation $Script:AnalyzedInformation
}
Write-Debug("End of Analyzer Engine")
return $Script:AnalyzedInformation
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Confirm-Administrator/Confirm-Administrator.ps1
Function Confirm-Administrator {
#Function Version 1.1
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal( [Security.Principal.WindowsIdentity]::GetCurrent() )
if($currentPrincipal.IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator ))
{
return $true
}
else
{
return $false
}
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Confirm-ExchangeShell/Confirm-ExchangeShell.ps1
Function Confirm-ExchangeShell {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)][bool]$LoadExchangeShell = $true,
[Parameter(Mandatory=$false)][bool]$LoadExchangeVariables = $true,
[Parameter(Mandatory=$false)][bool]$ByPassLocalExchangeServerTest = $false,
[Parameter(Mandatory=$false)][scriptblock]$CatchActionFunction
)
#Function Version 1.6
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-HostWriters/Write-HostWriter.ps1
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-VerboseWriter.ps1
#>
$passed = $false
Write-VerboseWriter("Calling: Confirm-ExchangeShell")
Write-VerboseWriter("Passed: [bool]LoadExchangeShell: {0} | [bool]LoadExchangeVariables: {1} | [bool]ByPassLocalExchangeServerTest: {2}" -f $LoadExchangeShell,
$LoadExchangeVariables, $ByPassLocalExchangeServerTest)
#Test that we are on Exchange 2010 or newer
if(($isLocalExchangeServer = (Test-Path 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v14\Setup')) -or
($isLocalExchangeServer = (Test-Path 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\Setup')) -or
$ByPassLocalExchangeServerTest)
{
Write-VerboseWriter("We are on Exchange 2010 or newer")
if((Test-Path 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v14\EdgeTransportRole') -or
(Test-Path 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\EdgeTransportRole'))
{
Write-VerboseWriter("We are on Exchange Edge Transport Server")
$IsEdgeTransport = $true
}
try
{
if(((Get-PSSession | Where-Object {($_.Availability -eq 'Available') -and
($_.ConfigurationName -eq 'Microsoft.Exchange')}).Count -eq 0) -and
((Get-Module -Name RemoteExchange).Count -eq 1))
{
Write-VerboseWriter("Removing RemoteExchange module")
Remove-Module -Name RemoteExchange
$currentPSModules = Get-Module
foreach ($PSModule in $currentPSModules)
{
if(($PSModule.ModuleType -eq "Script") -and
($PSModule.ModuleBase -like "*\Microsoft\Exchange\RemotePowerShell\*"))
{
Write-VerboseWriter("Removing module {0} for implicit remoting" -f $PSModule.Name)
Remove-Module -Name $PSModule.Name
}
}
}
Get-ExchangeServer -ErrorAction Stop | Out-Null
Write-VerboseWriter("Exchange PowerShell Module already loaded.")
$passed = $true
}
catch
{
Write-VerboseWriter("Failed to run Get-ExchangeServer")
if($CatchActionFunction -ne $null)
{
& $CatchActionFunction
$watchErrors = $true
}
if($LoadExchangeShell -and
$isLocalExchangeServer)
{
Write-HostWriter "Loading Exchange PowerShell Module..."
try
{
if($watchErrors)
{
$currentErrors = $Error.Count
}
if($IsEdgeTransport)
{
[xml]$PSSnapIns = Get-Content -Path "$env:ExchangeInstallPath\Bin\exshell.psc1" -ErrorAction Stop
ForEach($PSSnapIn in $PSSnapIns.PSConsoleFile.PSSnapIns.PSSnapIn)
{
Write-VerboseWriter("Trying to add PSSnapIn: {0}" -f $PSSnapIn.Name)
Add-PSSnapin -Name $PSSnapIn.Name -ErrorAction Stop
}
Import-Module $env:ExchangeInstallPath\bin\Exchange.ps1 -ErrorAction Stop
$passed = $true #We are just going to assume this passed.
}
else
{
Import-Module $env:ExchangeInstallPath\bin\RemoteExchange.ps1 -ErrorAction Stop
Connect-ExchangeServer -Auto -ClientApplication:ManagementShell
$passed = $true #We are just going to assume this passed.
}
if($watchErrors)
{
$index = 0
while($index -lt ($Error.Count - $currentErrors))
{
& $CatchActionFunction $Error[$index]
$index++
}
}
}
catch
{
Write-HostWriter("Failed to Load Exchange PowerShell Module...")
}
}
}
finally
{
if($LoadExchangeVariables -and
$passed -and
$isLocalExchangeServer)
{
#Diff from master not using Get-ExchangeInstallDirectory because of required functions
if($ExInstall -eq $null -or $ExBin -eq $null)
{
if(Test-Path 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v14\Setup')
{
$Global:ExInstall = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\ExchangeServer\v14\Setup).MsiInstallPath
}
else
{
$Global:ExInstall = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\Setup).MsiInstallPath
}
$Global:ExBin = $Global:ExInstall + "\Bin"
Write-VerboseWriter("Set ExInstall: {0}" -f $Global:ExInstall)
Write-VerboseWriter("Set ExBin: {0}" -f $Global:ExBin)
}
}
}
}
else
{
Write-VerboseWriter("Does not appear to be an Exchange 2010 or newer server.")
}
Write-VerboseWriter("Returned: {0}" -f $passed)
return $passed
}
#Master Template: https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/New-LoggerObject/New-LoggerObject.ps1
Function New-LoggerObject {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)][string]$LogDirectory = ".",
[Parameter(Mandatory=$false)][string]$LogName = "Script_Logging",
[Parameter(Mandatory=$false)][bool]$EnableDateTime = $true,
[Parameter(Mandatory=$false)][bool]$IncludeDateTimeToFileName = $true,
[Parameter(Mandatory=$false)][int]$MaxFileSizeInMB = 10,
[Parameter(Mandatory=$false)][int]$CheckSizeIntervalMinutes = 10,
[Parameter(Mandatory=$false)][int]$NumberOfLogsToKeep = 10,
[Parameter(Mandatory=$false)][bool]$VerboseEnabled,
[Parameter(Mandatory=$false)][scriptblock]$HostFunctionCaller,
[Parameter(Mandatory=$false)][scriptblock]$VerboseFunctionCaller
)
#Function Version 1.2
<#
Required Functions:
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-HostWriters/Write-ScriptMethodHostWriter.ps1
https://raw.githubusercontent.com/dpaulson45/PublicPowerShellScripts/master/Functions/Write-VerboseWriters/Write-ScriptMethodVerboseWriter.ps1
#>
########################
#
# Template Functions
#
########################
Function Write-ToLog {
param(
[object]$WriteString,
[string]$LogLocation
)
$WriteString | Out-File ($LogLocation) -Append
}
########################
#
# End Template Functions
#
########################
########## Parameter Binding Exceptions ##############
# throw [System.Management.Automation.ParameterBindingException] "Failed to provide valid ParameterName"
if($LogDirectory -eq ".")
{
$LogDirectory = (Get-Location).Path
}
if([string]::IsNullOrEmpty($LogName))
{
throw [System.Management.Automation.ParameterBindingException] "Failed to provide valid LogName"
}
if(!(Test-Path $LogDirectory))
{
throw [System.Management.Automation.ParameterBindingException] "Failed to provide valid LogDirectory"
}
$loggerObject = New-Object pscustomobject
$loggerObject | Add-Member -MemberType NoteProperty -Name "FileDirectory" -Value $LogDirectory
$loggerObject | Add-Member -MemberType NoteProperty -Name "FileName" -Value $LogName
$loggerObject | Add-Member -MemberType NoteProperty -Name "FullPath" -Value $fullLogPath
$loggerObject | Add-Member -MemberType NoteProperty -Name "InstanceBaseName" -Value ([string]::Empty)
$loggerObject | Add-Member -MemberType NoteProperty -Name "EnableDateTime" -Value $EnableDateTime
$loggerObject | Add-Member -MemberType NoteProperty -Name "IncludeDateTimeToFileName" -Value $IncludeDateTimeToFileName
$loggerObject | Add-Member -MemberType NoteProperty -Name "MaxFileSizeInMB" -Value $MaxFileSizeInMB
$loggerObject | Add-Member -MemberType NoteProperty -Name "CheckSizeIntervalMinutes" -Value $CheckSizeIntervalMinutes
$loggerObject | Add-Member -MemberType NoteProperty -Name "NextFileCheckTime" -Value ((Get-Date).AddMinutes($CheckSizeIntervalMinutes))
$loggerObject | Add-Member -MemberType NoteProperty -Name "InstanceNumber" -Value 1
$loggerObject | Add-Member -MemberType NoteProperty -Name "NumberOfLogsToKeep" -Value $NumberOfLogsToKeep
$loggerObject | Add-Member -MemberType NoteProperty -Name "WriteVerboseData" -Value $VerboseEnabled
$loggerObject | Add-Member -MemberType NoteProperty -Name "PreventLogCleanup" -Value $false
$loggerObject | Add-Member -MemberType ScriptMethod -Name "ToLog" -Value ${Function:Write-ToLog}
$loggerObject | Add-Member -MemberType ScriptMethod -Name "WriteHostWriter" -Value ${Function:Write-ScriptMethodHostWriter}
$loggerObject | Add-Member -MemberType ScriptMethod -Name "WriteVerboseWriter" -Value ${Function:Write-ScriptMethodVerboseWriter}
if($HostFunctionCaller -ne $null)
{
$loggerObject | Add-Member -MemberType ScriptMethod -Name "HostFunctionCaller" -Value $HostFunctionCaller
}
if($VerboseFunctionCaller -ne $null)
{
$loggerObject | Add-Member -MemberType ScriptMethod -Name "VerboseFunctionCaller" -Value $VerboseFunctionCaller
}
$loggerObject | Add-Member -MemberType ScriptMethod -Name "WriteHost" -Value {
param(
[object]$LoggingString
)
if($LoggingString -eq $null)
{
throw [System.Management.Automation.ParameterBindingException] "Failed to provide valid LoggingString"
}
if($this.EnableDateTime)
{
$LoggingString = "[{0}] : {1}" -f [System.DateTime]::Now, $LoggingString
}
$this.WriteHostWriter($LoggingString)
$this.ToLog($LoggingString, $this.FullPath)
$this.LogUpKeep()
}
$loggerObject | Add-Member -MemberType ScriptMethod -Name "WriteVerbose" -Value {
param(
[object]$LoggingString
)
if($LoggingString -eq $null)
{
throw [System.Management.Automation.ParameterBindingException] "Failed to provide valid LoggingString"
}
if($this.EnableDateTime)
{
$LoggingString = "[{0}] : {1}" -f [System.DateTime]::Now, $LoggingString
}
$this.WriteVerboseWriter($LoggingString)
$this.ToLog($LoggingString, $this.FullPath)
$this.LogUpKeep()
}
$loggerObject | Add-Member -MemberType ScriptMethod -Name "WriteToFileOnly" -Value {
param(
[object]$LoggingString
)
if($LoggingString -eq $null)
{
throw [System.Management.Automation.ParameterBindingException] "Failed to provide valid LoggingString"
}
if($this.EnableDateTime)
{
$LoggingString = "[{0}] : {1}" -f [System.DateTime]::Now, $LoggingString
}
$this.ToLog($LoggingString, $this.FullPath)
$this.LogUpKeep()
}
$loggerObject | Add-Member -MemberType ScriptMethod -Name "UpdateFileLocation" -Value{
if($this.FullPath -eq $null)
{
if($this.IncludeDateTimeToFileName)
{
$this.InstanceBaseName = "{0}_{1}" -f $this.FileName, ((Get-Date).ToString('yyyyMMddHHmmss'))
$this.FullPath = "{0}\{1}.txt" -f $this.FileDirectory, $this.InstanceBaseName
}
else
{
$this.InstanceBaseName = "{0}" -f $this.FileName
$this.FullPath = "{0}\{1}.txt" -f $this.FileDirectory, $this.InstanceBaseName
}
}
else
{
do{
$this.FullPath = "{0}\{1}_{2}.txt" -f $this.FileDirectory, $this.InstanceBaseName, $this.InstanceNumber
$this.InstanceNumber++
}while(Test-Path $this.FullPath)
$this.WriteVerbose("Updated to New Log")
}
}
$loggerObject | Add-Member -MemberType ScriptMethod -Name "LogUpKeep" -Value {
if($this.NextFileCheckTime -gt [System.DateTime]::Now)
{
return
}
$this.NextFileCheckTime = (Get-Date).AddMinutes($this.CheckSizeIntervalMinutes)
$this.CheckFileSize()
$this.CheckNumberOfFiles()
$this.WriteVerbose("Did Log Object Up Keep")
}
$loggerObject | Add-Member -MemberType ScriptMethod -Name "CheckFileSize" -Value {
$item = Get-ChildItem $this.FullPath
if(($item.Length / 1MB) -gt $this.MaxFileSizeInMB)
{
$this.UpdateFileLocation()
}
}
$loggerObject | Add-Member -MemberType ScriptMethod -Name "CheckNumberOfFiles" -Value {
$filter = "{0}*" -f $this.InstanceBaseName
$items = Get-ChildItem -Path $this.FileDirectory | ?{$_.Name -like $filter}
if($items.Count -gt $this.NumberOfLogsToKeep)
{
do{
$items | Sort-Object LastWriteTime | Select -First 1 | Remove-Item -Force
$items = Get-ChildItem -Path $this.FileDirectory | ?{$_.Name -like $filter}
}while($items.Count -gt $this.NumberOfLogsToKeep)
}
}
$loggerObject | Add-Member -MemberType ScriptMethod -Name "RemoveLatestLogFile" -Value {
if(!$this.PreventLogCleanup)
{
$item = Get-ChildItem $this.FullPath
Remove-Item $item -Force
}
}
$loggerObject.UpdateFileLocation()
try
{
"[{0}] : Creating a new logger instance" -f [System.DateTime]::Now | Out-File ($loggerObject.FullPath) -Append
}
catch
{
throw
}
return $loggerObject
}
Function Get-CounterSamples {
param(
[Parameter(Mandatory=$true)][array]$MachineNames,
[Parameter(Mandatory=$true)][array]$Counters
)
Write-VerboseOutput("Calling: Get-CounterSamples")
try
{
$counterSamples = (Get-Counter -ComputerName $MachineNames -Counter $Counters -ErrorAction Stop).CounterSamples
}
catch
{
Invoke-CatchActions
Write-VerboseOutput("Failed to get counter samples")
}
Write-VerboseOutput("Exiting: Get-CounterSamples")
return $counterSamples
}
Function Get-ErrorsThatOccurred {
if($Error.Count -gt $Script:ErrorStartCount)
{
Write-Grey(" "); Write-Grey(" ")
Function Write-Errors {
$index = 0;
"`r`n`r`nErrors that occurred that wasn't handled" | Out-File ($Script:OutputFullPath) -Append
$Script:Logger.WriteToFileOnly("`r`n`r`nErrors that occurred that wasn't handled")
while($index -lt ($Error.Count - $Script:ErrorStartCount))
{
#for 2008R2 can't use .Contains on an array object, need to do something else.
$goodError = $false
foreach($okayErrors in $Script:ErrorsExcluded)
{
if($okayErrors.Equals($Error[$index]))
{
$goodError = $true
break
}
}
if(!($goodError))
{
$Script:Logger.WriteToFileOnly($Error[$index])
$Error[$index] | Out-File ($Script:OutputFullPath) -Append
}
$index++
}
Write-Grey(" "); Write-Grey(" ")
"Errors that were handled" | Out-File ($Script:OutputFullPath) -Append
$Script:Logger.WriteToFileOnly("`r`n`r`nErrors that were handled")
foreach($okayErrors in $Script:ErrorsExcluded)
{
$okayErrors | Out-File ($Script:OutputFullPath) -Append
$Script:Logger.WriteToFileOnly($okayErrors)
}
}
if(($Error.Count - $Script:ErrorStartCount) -ne $Script:ErrorsExcludedCount)
{
Write-Red("There appears to have been some errors in the script. To assist with debugging of the script, please send the HealthChecker-Debug_*.txt and .xml file to ExToolsFeedback@microsoft.com.")
$Script:Logger.PreventLogCleanup = $true
Write-Errors
}
elseif($Script:VerboseEnabled -or
$SaveDebugLog)
{
Write-VerboseOutput("All errors that occurred were in try catch blocks and was handled correctly.")
$Script:Logger.PreventLogCleanup = $true
Write-Errors
}
}
else
{
Write-VerboseOutput("No errors occurred in the script.")
}
}
Function Get-HealthCheckFilesItemsFromLocation{
$items = Get-ChildItem $XMLDirectoryPath | Where-Object{$_.Name -like "HealthCheck-*-*.xml"}
if($items -eq $null)
{
Write-Host("Doesn't appear to be any Health Check XML files here....stopping the script")
exit
}
return $items
}
Function Get-OnlyRecentUniqueServersXMLs {
param(
[Parameter(Mandatory=$true)][array]$FileItems
)
$aObject = @()
foreach($item in $FileItems)
{
$obj = New-Object PSCustomobject
[string]$itemName = $item.Name
$ServerName = $itemName.Substring(($itemName.IndexOf("-") + 1), ($itemName.LastIndexOf("-") - $itemName.IndexOf("-") - 1))
$obj | Add-Member -MemberType NoteProperty -Name ServerName -Value $ServerName
$obj | Add-Member -MemberType NoteProperty -Name FileName -Value $itemName
$obj | Add-Member -MemberType NoteProperty -Name FileObject -Value $item
$aObject += $obj
}
$grouped = $aObject | Group-Object ServerName
$FilePathList = @()
foreach($gServer in $grouped)
{
if($gServer.Count -gt 1)
{
#going to only use the most current file for this server providing that they are using the newest updated version of Health Check we only need to sort by name
$groupData = $gServer.Group #because of win2008
$FilePathList += ($groupData | Sort-Object FileName -Descending | Select-Object -First 1).FileObject.VersionInfo.FileName
}
else
{
$FilePathList += ($gServer.Group).FileObject.VersionInfo.FileName
}
}
return $FilePathList
}
Function Import-MyData {
param(
[Parameter(Mandatory=$true)][array]$FilePaths
)
[System.Collections.Generic.List[System.Object]]$myData = New-Object -TypeName System.Collections.Generic.List[System.Object]
foreach($filePath in $FilePaths)
{
$importData = Import-Clixml -Path $filePath
$myData.Add($importData)
}
return $myData
}
Function Invoke-CatchActions{
param(
[object]$CopyThisError
)
Write-VerboseOutput("Calling: Invoke-CatchActions")
$Script:ErrorsExcludedCount++
if($CopyThisError -eq $null)
{
$Script:ErrorsExcluded += $Error[0]
}
else
{
$Script:ErrorsExcluded += $CopyThisError
}
}
Function Set-ScriptLogFileLocation {
param(
[Parameter(Mandatory=$true)][string]$FileName,
[Parameter(Mandatory=$false)][bool]$IncludeServerName = $false
)
$endName = "-{0}.txt" -f $dateTimeStringFormat
if($IncludeServerName)
{
$endName = "-{0}{1}" -f $Script:Server, $endName
}
$Script:OutputFullPath = "{0}\{1}{2}" -f $OutputFilePath, $FileName, $endName
$Script:OutXmlFullPath = $Script:OutputFullPath.Replace(".txt",".xml")
if($AnalyzeDataOnly -or
$BuildHtmlServersReport)
{
return
}
$byPassLocalExchangeServerTest = $false
if ($Script:Server -ne $env:COMPUTERNAME)
{
$byPassLocalExchangeServerTest = $true
}
if(!(Confirm-ExchangeShell `
-ByPassLocalExchangeServerTest $byPassLocalExchangeServerTest `
-CatchActionFunction ${Function:Invoke-CatchActions} ))
{
Write-Yellow("Failed to load Exchange Shell... stopping script")
exit
}
}
Function Test-RequiresServerFqdn {
Write-VerboseOutput("Calling: Test-RequiresServerFqdn")
try
{
$Script:ServerFQDN = (Get-ExchangeServer $Script:Server).FQDN
Invoke-Command -ComputerName $Script:Server -ScriptBlock {Get-Date | Out-Null} -ErrorAction Stop
Write-VerboseOutput("Connected successfully using NetBIOS name.")
}
catch
{
Invoke-CatchActions
Write-VerboseOutput("Failed to connect to {0} using NetBIOS name. Fallback to Fqdn: {1}" -f $Script:Server, $Script:ServerFQDN)
$Script:Server = $Script:ServerFQDN
}
}
Function Test-IsCurrentVersion {
param(
[Parameter(Mandatory=$true)][string]$CurrentVersion,
[Parameter(Mandatory=$true)][string]$TestingVersion
)
Write-VerboseOutput("Calling: Test-IsCurrentVersion")
$splitCurrentVersion = $CurrentVersion.Split(".")
$splitTestingVersion = $TestingVersion.Split(".")
if($splitCurrentVersion.Count -eq $splitTestingVersion.Count)
{
for($i = 0; $i -lt $splitCurrentVersion.Count; $i++)
{
if($splitCurrentVersion[$i] -lt $splitTestingVersion[$i])
{
return $false
}
}
return $true
}
else
{
Write-VerboseOutput("Split count isn't the same, assuming that we are not on current version.")
return $false
}
}
Function Test-ScriptVersion {
param(
[Parameter(Mandatory=$true)][string]$ApiUri,
[Parameter(Mandatory=$true)][string]$RepoOwner,
[Parameter(Mandatory=$true)][string]$RepoName,
[Parameter(Mandatory=$true)][string]$CurrentVersion,
[Parameter(Mandatory=$true)][int]$DaysOldLimit,
[Parameter(Mandatory=$false)][Scriptblock]$CatchActionFunction
)
Write-VerboseOutput("Calling: Test-ScriptVersion")
$isCurrent = $false
if(Test-Connection -ComputerName $ApiUri -Count 1 -Quiet)
{
try
{
$ScriptBlock = {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
ConvertFrom-Json(Invoke-WebRequest -Uri ($uri = "https://$($args[0])/repos/$($args[1])/$($args[2])/releases/latest"))
}
$WebRequestJob = Start-Job -ScriptBlock $ScriptBlock -Name "WebRequestJob" -ArgumentList $ApiUri,$RepoOwner,$RepoName
do
{
$i++
if((Get-Job -Id $WebRequestJob.Id).State -eq "Completed")
{
Write-VerboseOutput("WebRequest after {0} attempts successfully completed. Receiving results." -f $i)
try
{
$releaseInformation = Receive-Job -Id $WebRequestJob.Id -Keep -ErrorAction Stop
}
catch
{
if ($CatchActionFunction -ne $null)
{
& $CatchActionFunction
}
}
Write-VerboseOutput("Removing background worker job")
Remove-Job -Id $WebRequestJob.Id
Break
}
else
{
Write-VerboseOutput("Attempt: {0} WebRequest not yet complete." -f $i)
if($i -eq 30)
{
Write-VerboseOutput("Reached 30 attempts. Removing background worker job.")
Remove-Job -Id $WebRequestJob.Id
}
Start-Sleep -Seconds 1
}
}
while($i -lt 30)
}
catch
{
Invoke-CatchActions
Write-VerboseOutput("Failed to run Invoke-WebRequest")
}
if($releaseInformation -ne $null)
{
Write-VerboseOutput("We're online: {0} connected successfully." -f $uri)
$latestVersion = ($releaseInformation.tag_name).Split("v")[1]
if(Test-IsCurrentVersion -CurrentVersion $CurrentVersion -TestingVersion $latestVersion)
{
Write-VerboseOutput("Version '{0}' is the latest version." -f $latestVersion)
$isCurrent = $true
}
else
{
Write-VerboseOutput("Version '{0}' is outdated. Lastest version is '{1}'" -f $CurrentVersion, $latestVersion)
}
}
else
{
Write-VerboseOutput("Release information was null.")
}
}
else
{
Write-VerboseOutput("We're offline: Unable to connect to '{0}" -f $ApiUri)
Write-VerboseOutput("Unable to determine if this version '{0}' is current. Checking to see if the file is older than {1} days." -f $CurrentVersion, $DaysOldLimit)
$writeTime = (Get-ChildItem ($MyInvocation.ScriptName)).LastWriteTime
if($writeTime -gt ($testDate = ([datetime]::Now).AddDays(-$DaysOldLimit)))
{
Write-VerboseOutput("Determined that the script write time '{0}' is new than our our test date '{1}'." -f $writeTime, $testDate)
$isCurrent = $true
}
else
{
Write-VerboseOutput("Script doesn't appear to be on the latest possible version. Script write time '{0}' vs out test date '{1}'" -f $writeTime, $testDate)
}
}
Write-VerboseOutput("Exiting: Test-ScriptVersion | Returning: {0}" -f $isCurrent)
return $isCurrent
}
Function Create-HtmlServerReport {
param(
[Parameter(Mandatory=$true)][array]$AnalyzedHtmlServerValues
)
Write-VerboseOutput("Calling: Create-HtmlServerReport")
$htmlHeader = "<html>
<style>
BODY{font-family: Arial; font-size: 8pt;}
H1{font-size: 16px;}
H2{font-size: 14px;}
H3{font-size: 12px;}
TABLE{border: 1px solid black; border-collapse: collapse; font-size: 8pt;}
TH{border: 1px solid black; background: #dddddd; padding: 5px; color: #000000;}
TD{border: 1px solid black; padding: 5px; }
td.Green{background: #7FFF00;}
td.Yellow{background: #FFE600;}
td.Red{background: #FF0000; color: #ffffff;}
td.Info{background: #85D4FF;}
</style>
<body>
<h1 align=""center"">Exchange Health Checker v$($Script:healthCheckerVersion)</h1><br>
<h2>Servers Overview</h2>"
[array]$htmlOverviewTable += "<p>
<table>
<tr>"
foreach ($tableHeaderName in $AnalyzedHtmlServerValues[0]["OverviewValues"].Name)
{
$htmlOverviewTable += "<th>{0}</th>" -f $tableHeaderName
}
$htmlOverviewTable += "</tr>"
foreach ($serverHtmlServerValues in $AnalyzedHtmlServerValues)
{
$htmlTableRow = @()
[array]$htmlTableRow += "<tr>"
foreach ($htmlTableDataRow in $serverHtmlServerValues["OverviewValues"])
{
$htmlTableRow += "<td class=`"{0}`">{1}</td>" -f $htmlTableDataRow.Class, `
$htmlTableDataRow.DetailValue
}
$htmlTableRow += "</tr>"
$htmlOverviewTable += $htmlTableRow
}
$htmlOverviewTable += "</table></p>"
[array]$htmlServerDetails += "<p><h2>Server Details</h2><table>"
foreach ($serverHtmlServerValues in $AnalyzedHtmlServerValues)
{
foreach ($htmlTableDataRow in $serverHtmlServerValues["ServerDetails"])
{
if ($htmlTableDataRow.Name -eq "Server Name")
{
$htmlServerDetails += "<tr><th>{0}</th><th>{1}</th><tr>" -f $htmlTableDataRow.Name, `
$htmlTableDataRow.DetailValue
}
else
{
$htmlServerDetails += "<tr><td class=`"{0}`">{1}</td><td class=`"{0}`">{2}</td><tr>" -f $htmlTableDataRow.Class, `
$htmlTableDataRow.Name, `
$htmlTableDataRow.DetailValue
}
}
}
$htmlServerDetails += "</table></p>"
$htmlReport = $htmlHeader + $htmlOverviewTable + $htmlServerDetails + "</body></html>"
$htmlReport | Out-File $HtmlReportFile -Encoding UTF8
}
Function Get-CASLoadBalancingReport {
Write-VerboseOutput("Calling: Get-CASLoadBalancingReport")
Write-Yellow("Note: CAS Load Balancing Report has known issues with attempting to get counter from servers. If you see errors regarding 'Get-Counter path not valid', please ignore for the time being. This is going to be addressed in later versions")
#Connection and requests per server and client type values
$CASConnectionStats = @{}
$TotalCASConnectionCount = 0
$AutoDStats = @{}
$TotalAutoDRequests = 0
$EWSStats = @{}
$TotalEWSRequests = 0
$MapiHttpStats = @{}
$TotalMapiHttpRequests = 0
$EASStats = @{}
$TotalEASRequests = 0
$OWAStats = @{}
$TotalOWARequests = 0
$RpcHttpStats = @{}
$TotalRpcHttpRequests = 0
$CASServers = @()
if($CasServerList -ne $null)
{
Write-Grey("Custom CAS server list is being used. Only servers specified after the -CasServerList parameter will be used in the report.")
foreach($cas in $CasServerList)
{
$CASServers += (Get-ExchangeServer $cas)
}
}
elseif($SiteName -ne [string]::Empty)
{
Write-Grey("Site filtering ON. Only Exchange 2013/2016 CAS servers in {0} will be used in the report." -f $SiteName)
$CASServers = Get-ExchangeServer | Where-Object{($_.IsClientAccessServer -eq $true) -and ($_.AdminDisplayVersion -Match "^Version 15") -and ($_.Site.Name -eq $SiteName)}
}
else
{
Write-Grey("Site filtering OFF. All Exchange 2013/2016 CAS servers will be used in the report.")
$CASServers = Get-ExchangeServer | Where-Object{($_.IsClientAccessServer -eq $true) -and ($_.AdminDisplayVersion -Match "^Version 15")}
}
if($CASServers.Count -eq 0)
{
Write-Red("Error: No CAS servers found using the specified search criteria.")
Exit
}
#Request stats from perfmon for all CAS
$PerformanceCounters = @()
$PerformanceCounters += "\Web Service(Default Web Site)\Current Connections"
$PerformanceCounters += "\ASP.NET Apps v4.0.30319(_LM_W3SVC_1_ROOT_Autodiscover)\Requests Executing"
$PerformanceCounters += "\ASP.NET Apps v4.0.30319(_LM_W3SVC_1_ROOT_EWS)\Requests Executing"
$PerformanceCounters += "\ASP.NET Apps v4.0.30319(_LM_W3SVC_1_ROOT_mapi)\Requests Executing"
$PerformanceCounters += "\ASP.NET Apps v4.0.30319(_LM_W3SVC_1_ROOT_Microsoft-Server-ActiveSync)\Requests Executing"
$PerformanceCounters += "\ASP.NET Apps v4.0.30319(_LM_W3SVC_1_ROOT_owa)\Requests Executing"
$PerformanceCounters += "\ASP.NET Apps v4.0.30319(_LM_W3SVC_1_ROOT_Rpc)\Requests Executing"
try
{
$AllCounterResults = Get-Counter -ComputerName $CASServers -Counter $PerformanceCounters
}
catch
{
Invoke-CatchActions
Write-VerboseOutput("Failed to get counter samples")
}
ForEach($Result in $AllCounterResults.CounterSamples)
{
$CasName = ($Result.Path).Split("\\",[System.StringSplitOptions]::RemoveEmptyEntries)[0]
$ResultCookedValue = $Result.CookedValue
if($Result.Path -like "*{0}*{1}" -f $CasName,$PerformanceCounters[0])
{
#Total connections
$CASConnectionStats.Add($CasName,$ResultCookedValue)
$TotalCASConnectionCount += $ResultCookedValue
}
elseif($Result.Path -like "*{0}*{1}" -f $CasName,$PerformanceCounters[1])
{
#AutoD requests
$AutoDStats.Add($CasName,$ResultCookedValue)
$TotalAutoDRequests += $ResultCookedValue
}
elseif($Result.Path -like "*{0}*{1}" -f $CasName,$PerformanceCounters[2])
{
#EWS requests
$EWSStats.Add($CasName,$ResultCookedValue)
$TotalEWSRequests += $ResultCookedValue
}
elseif($Result.Path -like "*{0}*{1}" -f $CasName,$PerformanceCounters[3])
{
#MapiHttp requests
$MapiHttpStats.Add($CasName,$ResultCookedValue)
$TotalMapiHttpRequests += $ResultCookedValue
}
elseif($Result.Path -like "*{0}*{1}" -f $CasName,$PerformanceCounters[4])
{
#EAS requests
$EASStats.Add($CasName,$ResultCookedValue)
$TotalEASRequests += $ResultCookedValue
}
elseif($Result.Path -like "*{0}*{1}" -f $CasName,$PerformanceCounters[5])
{
#OWA requests
$OWAStats.Add($CasName,$ResultCookedValue)
$TotalOWARequests += $ResultCookedValue
}
elseif($Result.Path -like "*{0}*{1}" -f $CasName,$PerformanceCounters[6])
{
#RPCHTTP requests
$RpcHttpStats.Add($CasName,$ResultCookedValue)
$TotalRpcHttpRequests += $ResultCookedValue
}
}
#Report the results for connection count
Write-Grey("")
Write-Grey("Connection Load Distribution Per Server")
Write-Grey("Total Connections: {0}" -f $TotalCASConnectionCount)
#Calculate percentage of connection load
$CASConnectionStats.GetEnumerator() | Sort-Object -Descending | ForEach-Object {
Write-Grey($_.Key + ": " + $_.Value + " Connections = " + [math]::Round((([int]$_.Value/$TotalCASConnectionCount)*100)) + "% Distribution")
}
#Same for each client type. These are request numbers not connection numbers.
#AutoD
if($TotalAutoDRequests -gt 0)
{
Write-Grey("")
Write-Grey("Current AutoDiscover Requests Per Server")
Write-Grey("Total Requests: {0}" -f $TotalAutoDRequests)
$AutoDStats.GetEnumerator() | Sort-Object -Descending | ForEach-Object {
Write-Grey($_.Key + ": " + $_.Value + " Requests = " + [math]::Round((([int]$_.Value/$TotalAutoDRequests)*100)) + "% Distribution")
}
}
#EWS
if($TotalEWSRequests -gt 0)
{
Write-Grey("")
Write-Grey("Current EWS Requests Per Server")
Write-Grey("Total Requests: {0}" -f $TotalEWSRequests)
$EWSStats.GetEnumerator() | Sort-Object -Descending | ForEach-Object {
Write-Grey($_.Key + ": " + $_.Value + " Requests = " + [math]::Round((([int]$_.Value/$TotalEWSRequests)*100)) + "% Distribution")
}
}
#MapiHttp
if($TotalMapiHttpRequests -gt 0)
{
Write-Grey("")
Write-Grey("Current MapiHttp Requests Per Server")
Write-Grey("Total Requests: {0}" -f $TotalMapiHttpRequests)
$MapiHttpStats.GetEnumerator() | Sort-Object -Descending | ForEach-Object {
Write-Grey($_.Key + ": " + $_.Value + " Requests = " + [math]::Round((([int]$_.Value/$TotalMapiHttpRequests)*100)) + "% Distribution")
}
}
#EAS
if($TotalEASRequests -gt 0)
{
Write-Grey("")
Write-Grey("Current EAS Requests Per Server")
Write-Grey("Total Requests: {0}" -f $TotalEASRequests)
$EASStats.GetEnumerator() | Sort-Object -Descending | ForEach-Object {
Write-Grey($_.Key + ": " + $_.Value + " Requests = " + [math]::Round((([int]$_.Value/$TotalEASRequests)*100)) + "% Distribution")
}
}
#OWA
if($TotalOWARequests -gt 0)
{
Write-Grey("")
Write-Grey("Current OWA Requests Per Server")
Write-Grey("Total Requests: {0}" -f $TotalOWARequests)
$OWAStats.GetEnumerator() | Sort-Object -Descending | ForEach-Object {
Write-Grey($_.Key + ": " + $_.Value + " Requests = " + [math]::Round((([int]$_.Value/$TotalOWARequests)*100)) + "% Distribution")
}
}
#RpcHttp
if($TotalRpcHttpRequests -gt 0)
{
Write-Grey("")
Write-Grey("Current RpcHttp Requests Per Server")
Write-Grey("Total Requests: {0}" -f $TotalRpcHttpRequests)
$RpcHttpStats.GetEnumerator() | Sort-Object -Descending | ForEach-Object {
Write-Grey($_.Key + ": " + $_.Value + " Requests = " + [math]::Round((([int]$_.Value/$TotalRpcHttpRequests)*100)) + "% Distribution")
}
}
Write-Grey("")
}
Function Get-ComputerCoresObject {
param(
[Parameter(Mandatory=$true)][string]$Machine_Name
)
Write-VerboseOutput("Calling: Get-ComputerCoresObject")
Write-VerboseOutput("Passed: {0}" -f $Machine_Name)
$returnObj = New-Object pscustomobject
$returnObj | Add-Member -MemberType NoteProperty -Name Error -Value $false
$returnObj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $Machine_Name
$returnObj | Add-Member -MemberType NoteProperty -Name NumberOfCores -Value ([int]::empty)
$returnObj | Add-Member -MemberType NoteProperty -Name Exception -Value ([string]::empty)
$returnObj | Add-Member -MemberType NoteProperty -Name ExceptionType -Value ([string]::empty)
try
{
$wmi_obj_processor = Get-WmiObjectHandler -ComputerName $Machine_Name -Class "Win32_Processor" -CatchActionFunction ${Function:Invoke-CatchActions}
foreach($processor in $wmi_obj_processor)
{
$returnObj.NumberOfCores +=$processor.NumberOfCores
}
Write-Grey("Server {0} Cores: {1}" -f $Machine_Name, $returnObj.NumberOfCores)
}
catch
{
Invoke-CatchActions
$thisError = $Error[0]
if($thisError.Exception.Gettype().FullName -eq "System.UnauthorizedAccessException")
{
Write-Yellow("Unable to get processor information from server {0}. You do not have the correct permissions to get this data from that server. Exception: {1}" -f $Machine_Name, $thisError.ToString())
}
else
{
Write-Yellow("Unable to get processor information from server {0}. Reason: {1}" -f $Machine_Name, $thisError.ToString())
}
$returnObj.Exception = $thisError.ToString()
$returnObj.ExceptionType = $thisError.Exception.Gettype().FullName
$returnObj.Error = $true
}
return $returnObj
}
Function Get-ExchangeDCCoreRatio {
Set-ScriptLogFileLocation -FileName "HealthCheck-ExchangeDCCoreRatio"
Write-VerboseOutput("Calling: Get-ExchangeDCCoreRatio")
Write-Grey("Exchange Server Health Checker Report - AD GC Core to Exchange Server Core Ratio - v{0}" -f $healthCheckerVersion)
$coreRatioObj = New-Object pscustomobject
try
{
Write-VerboseOutput("Attempting to load Active Directory Module")
Import-Module ActiveDirectory
Write-VerboseOutput("Successfully loaded")
}
catch
{
Write-Red("Failed to load Active Directory Module. Stopping the script")
exit
}
$ADSite = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]::GetComputerSite().Name
[array]$DomainControllers = (Get-ADForest).Domains | %{ Get-ADDomainController -Filter {isGlobalCatalog -eq $true -and Site -eq $ADSite} -Server $_ }
[System.Collections.Generic.List[System.Object]]$DCList = New-Object System.Collections.Generic.List[System.Object]
$DCCoresTotal = 0
Write-Break
Write-Grey("Collecting data for the Active Directory Environment in Site: {0}" -f $ADSite)
$iFailedDCs = 0
foreach($DC in $DomainControllers)
{
$DCCoreObj = Get-ComputerCoresObject -Machine_Name $DC.Name
$DCList.Add($DCCoreObj)
if(-not ($DCCoreObj.Error))
{
$DCCoresTotal += $DCCoreObj.NumberOfCores
}
else
{
$iFailedDCs++
}
}
$coreRatioObj | Add-Member -MemberType NoteProperty -Name DCList -Value $DCList
if($iFailedDCs -eq $DomainControllers.count)
{
#Core count is going to be 0, no point to continue the script
Write-Red("Failed to collect data from your DC servers in site {0}." -f $ADSite)
Write-Yellow("Because we can't determine the ratio, we are going to stop the script. Verify with the above errors as to why we failed to collect the data and address the issue, then run the script again.")
exit
}
[array]$ExchangeServers = Get-ExchangeServer | Where-Object {$_.Site -match $ADSite}
$EXCoresTotal = 0
[System.Collections.Generic.List[System.Object]]$EXList = New-Object System.Collections.Generic.List[System.Object]
Write-Break
Write-Grey("Collecting data for the Exchange Environment in Site: {0}" -f $ADSite)
foreach($svr in $ExchangeServers)
{
$EXCoreObj = Get-ComputerCoresObject -Machine_Name $svr.Name
$EXList.Add($EXCoreObj)
if(-not ($EXCoreObj.Error))
{
$EXCoresTotal += $EXCoreObj.NumberOfCores
}
}
$coreRatioObj | Add-Member -MemberType NoteProperty -Name ExList -Value $EXList
Write-Break
$CoreRatio = $EXCoresTotal / $DCCoresTotal
Write-Grey("Total DC/GC Cores: {0}" -f $DCCoresTotal)
Write-Grey("Total Exchange Cores: {0}" -f $EXCoresTotal)
Write-Grey("You have {0} Exchange Cores for every Domain Controller Global Catalog Server Core" -f $CoreRatio)
if($CoreRatio -gt 8)
{
Write-Break
Write-Red("Your Exchange to Active Directory Global Catalog server's core ratio does not meet the recommended guidelines of 8:1")
Write-Red("Recommended guidelines for Exchange 2013/2016 for every 8 Exchange cores you want at least 1 Active Directory Global Catalog Core.")
Write-Yellow("Documentation:")
Write-Yellow("`thttps://techcommunity.microsoft.com/t5/Exchange-Team-Blog/Ask-the-Perf-Guy-Sizing-Exchange-2013-Deployments/ba-p/594229")
Write-Yellow("`thttps://docs.microsoft.com/en-us/exchange/exchange-2013-sizing-and-configuration-recommendations-exchange-2013-help#active-directory")
}
else
{
Write-Break
Write-Green("Your Exchange Environment meets the recommended core ratio of 8:1 guidelines.")
}
$XMLDirectoryPath = $OutputFullPath.Replace(".txt",".xml")
$coreRatioObj | Export-Clixml $XMLDirectoryPath
Write-Grey("Output file written to {0}" -f $OutputFullPath)
Write-Grey("Output XML Object file written to {0}" -f $XMLDirectoryPath)
}
Function Get-MailboxDatabaseAndMailboxStatistics {
Write-VerboseOutput("Calling: Get-MailboxDatabaseAndMailboxStatistics")
$AllDBs = Get-MailboxDatabaseCopyStatus -server $Script:Server -ErrorAction SilentlyContinue
$MountedDBs = $AllDBs | Where-Object{$_.ActiveCopy -eq $true}
if($MountedDBs.Count -gt 0)
{
Write-Grey("`tActive Database:")
foreach($db in $MountedDBs)
{
Write-Grey("`t`t" + $db.Name)
}
$MountedDBs.DatabaseName | ForEach-Object{Write-VerboseOutput("Calculating User Mailbox Total for Active Database: $_"); $TotalActiveUserMailboxCount += (Get-Mailbox -Database $_ -ResultSize Unlimited).Count}
Write-Grey("`tTotal Active User Mailboxes on server: " + $TotalActiveUserMailboxCount)
$MountedDBs.DatabaseName | ForEach-Object{Write-VerboseOutput("Calculating Public Mailbox Total for Active Database: $_"); $TotalActivePublicFolderMailboxCount += (Get-Mailbox -Database $_ -ResultSize Unlimited -PublicFolder).Count}
Write-Grey("`tTotal Active Public Folder Mailboxes on server: " + $TotalActivePublicFolderMailboxCount)
Write-Grey("`tTotal Active Mailboxes on server " + $Script:Server + ": " + ($TotalActiveUserMailboxCount + $TotalActivePublicFolderMailboxCount).ToString())
}
else
{
Write-Grey("`tNo Active Mailbox Databases found on server " + $Script:Server + ".")
}
$HealthyDbs = $AllDBs | Where-Object{$_.Status -match 'Healthy'}
if($HealthyDbs.count -gt 0)
{
Write-Grey("`r`n`tPassive Databases:")
foreach($db in $HealthyDbs)
{
Write-Grey("`t`t" + $db.Name)
}
$HealthyDbs.DatabaseName | ForEach-Object{Write-VerboseOutput("`tCalculating User Mailbox Total for Passive Healthy Databases: $_"); $TotalPassiveUserMailboxCount += (Get-Mailbox -Database $_ -ResultSize Unlimited).Count}
Write-Grey("`tTotal Passive user Mailboxes on Server: " + $TotalPassiveUserMailboxCount)
$HealthyDbs.DatabaseName | ForEach-Object{Write-VerboseOutput("`tCalculating Passive Mailbox Total for Passive Healthy Databases: $_"); $TotalPassivePublicFolderMailboxCount += (Get-Mailbox -Database $_ -ResultSize Unlimited -PublicFolder).Count}
Write-Grey("`tTotal Passive Public Mailboxes on server: " + $TotalPassivePublicFolderMailboxCount)
Write-Grey("`tTotal Passive Mailboxes on server: " + ($TotalPassiveUserMailboxCount + $TotalPassivePublicFolderMailboxCount).ToString())
}
else
{
Write-Grey("`tNo Passive Mailboxes found on server " + $Script:Server + ".")
}
}
Function Main {
if(-not (Confirm-Administrator) -and
(-not $AnalyzeDataOnly -and
-not $BuildHtmlServersReport))
{
Write-Warning "The script needs to be executed in elevated mode. Start the Exchange Management Shell as an Administrator."
$Script:ErrorStartCount = $Error.Count
Start-Sleep -Seconds 2;
exit
}
if ($Error.Count -gt 175)
{
Write-Verbose("Clearing Error to avoid script issues")
$Error.Clear()
}
$Script:ErrorStartCount = $Error.Count #useful for debugging
$Script:ErrorsExcludedCount = 0 #this is a way to determine if the only errors occurred were in try catch blocks. If there is a combination of errors in and out, then i will just dump it all out to avoid complex issues.
$Script:ErrorsExcluded = @()
$Script:date = (Get-Date)
$Script:dateTimeStringFormat = $date.ToString("yyyyMMddHHmmss")
if($BuildHtmlServersReport)
{
Set-ScriptLogFileLocation -FileName "HealthChecker-HTMLServerReport"
$files = Get-HealthCheckFilesItemsFromLocation
$fullPaths = Get-OnlyRecentUniqueServersXMLs $files
$importData = Import-MyData -FilePaths $fullPaths
Create-HtmlServerReport -AnalyzedHtmlServerValues $importData.HtmlServerValues
sleep 2;
return
}
if((Test-Path $OutputFilePath) -eq $false)
{
Write-Host "Invalid value specified for -OutputFilePath." -ForegroundColor Red
return
}
if($LoadBalancingReport)
{
Set-ScriptLogFileLocation -FileName "LoadBalancingReport"
Write-HealthCheckerVersion
Write-Green("Client Access Load Balancing Report on " + $date)
Get-CASLoadBalancingReport
Write-Grey("Output file written to " + $OutputFullPath)
Write-Break
Write-Break
return
}
if($DCCoreRatio)
{
$oldErrorAction = $ErrorActionPreference
$ErrorActionPreference = "Stop"
try
{
Get-ExchangeDCCoreRatio
return
}
finally
{
$ErrorActionPreference = $oldErrorAction
}
}
if($MailboxReport)
{
Set-ScriptLogFileLocation -FileName "HealthCheck-MailboxReport" -IncludeServerName $true
Get-MailboxDatabaseAndMailboxStatistics
Write-Grey("Output file written to {0}" -f $Script:OutputFullPath)
return
}
if ($AnalyzeDataOnly)
{
Set-ScriptLogFileLocation -FileName "HealthChecker-Analyzer"
$files = Get-HealthCheckFilesItemsFromLocation
$fullPaths = Get-OnlyRecentUniqueServersXMLs $files
$importData = Import-MyData -FilePaths $fullPaths
$analyzedResults = @()
foreach ($serverData in $importData)
{
$analyzedServerResults = Start-AnalyzerEngine -HealthServerObject $serverData.HealthCheckerExchangeServer
Write-ResultsToScreen -ResultsToWrite $analyzedServerResults.DisplayResults
$analyzedResults += $analyzedServerResults
}
Create-HtmlServerReport -AnalyzedHtmlServerValues $analyzedResults.HtmlServerValues
return
}
Set-ScriptLogFileLocation -FileName "HealthCheck" -IncludeServerName $true
Test-RequiresServerFqdn
Write-HealthCheckerVersion
[HealthChecker.HealthCheckerExchangeServer]$HealthObject = Get-HealthCheckerExchangeServer
$analyzedResults = Start-AnalyzerEngine -HealthServerObject $HealthObject
Write-ResultsToScreen -ResultsToWrite $analyzedResults.DisplayResults
$currentErrors = $Error.Count
try
{
$analyzedResults | Export-Clixml -Path $OutXmlFullPath -Encoding UTF8 -Depth 6 -ErrorAction SilentlyContinue
}
catch
{
Write-VerboseOutput("Failed to Export-Clixml. Converting HealthCheckerExchangeServer to json")
$jsonHealthChecker = $analyzedResults.HealthCheckerExchangeServer | ConvertTo-Json
$testOuputxml = [PSCustomObject]@{
HealthCheckerExchangeServer = $jsonHealthChecker | ConvertFrom-Json
HtmlServerValues = $analyzedResults.HtmlServerValues
DisplayResults = $analyzedResults.DisplayResults
}
$testOuputxml | Export-Clixml -Path $OutXmlFullPath -Encoding UTF8 -Depth 6 -ErrorAction Stop
}
finally
{
if ($currentErrors -ne $Error.Count)
{
$index = 0
while ($index -lt ($Error.Count - $currentErrors))
{
Invoke-CatchActions $Error[$index]
$index++
}
}
Write-Grey("Output file written to {0}" -f $Script:OutputFullPath)
Write-Grey("Exported Data Object Written to {0} " -f $Script:OutXmlFullPath)
}
}
try
{
$Script:Logger = New-LoggerObject -LogName "HealthChecker-Debug" -LogDirectory $OutputFilePath -VerboseEnabled $true -EnableDateTime $false -ErrorAction SilentlyContinue
Main
}
finally
{
Get-ErrorsThatOccurred
if($Script:VerboseEnabled)
{
$Host.PrivateData.VerboseForegroundColor = $VerboseForeground
}
$Script:Logger.RemoveLatestLogFile()
if($Script:Logger.PreventLogCleanup)
{
Write-Host("Output Debug file written to {0}" -f $Script:Logger.FullPath)
}
}