Introduction
For IT administrators and Managed Service Providers (MSPs), managing Exchange Server environments effectively is critical. Staying on top of server versions ensures compatibility, performance, and security within the enterprise ecosystem. This PowerShell script offers a streamlined way to check Exchange Server versions, validate their compliance against supported versions, and take actionable steps to maintain an up-to-date environment.
Background
Microsoft Exchange remains a cornerstone in enterprise communication systems. However, outdated versions can lead to security vulnerabilities, compatibility issues, and performance degradation. Identifying the exact version of Exchange installed across environments can be cumbersome, especially in large-scale deployments.
This script automates that process, enabling administrators to check installed Exchange versions against specified benchmarks for versions 2010, 2013, 2016, and 2019. Additionally, it provides flexibility for managing custom fields, enabling seamless integration with management tools like NinjaOne.
The Script:
<# .SYNOPSIS Checks the version of Exchange installed and if it is outdated against the supplied versions. .DESCRIPTION Checks the version of Exchange installed and if it is outdated against the supplied versions. The version of Exchange that can be checked against are the following versions: 2010, 2013, 2016, and 2019. If the version is outdated, a warning is displayed. The script will not alert if the version is up to date. CheckboxCustomField can be used to skip the check, and the custom field must be a checkbox or a text field with a value of "true" or "1". CustomFieldName can be used to save the version to a text custom field. Exchange 2010 starts with 14.3, 2013 starts with 15.0, 2016 starts with 15.1, and 2019 starts with 15.2. By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use. Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA). .EXAMPLE (No Parameters) ## EXAMPLE OUTPUT WITHOUT PARAMS ## [Error] Exchange 2010, 2013, 2016, and 2019 versions are required to run this script. PARAMETER: -CustomFieldName "ExchangeVersion" -Exchange2010Version "14.3.513.0" -Exchange2013Version "15.0.1497.48" -Exchange2016Version "15.1.0.0" -Exchange2019Version "15.2.0.0" Saves the Exchange version to a custom field named "ExchangeVersion" .EXAMPLE -CustomFieldName "ExchangeVersion" -Exchange2010Version "14.3.513.0" -Exchange2013Version "15.0.1497.48" -Exchange2016Version "15.1.0.0" -Exchange2019Version "15.2.0.0" ## EXAMPLE OUTPUT WITH CustomFieldName ## [Info] Exchange version is up to date. Found version: 15.2.0.0 [Info] Attempting to set Custom Field 'ExchangeVersion'. [Info] Successfully set Custom Field 'ExchangeVersion'! PARAMETER: -CheckboxCustomField "SkipExchangeCheck" -Exchange2010Version "14.3.513.0" -Exchange2013Version "15.0.1497.48" -Exchange2016Version "15.1.0.0" -Exchange2019Version "15.2.0.0" Skips the Exchange version check if the checkbox custom field "SkipExchangeCheck" is checked or if a text custom field is set to "true" or "1". .EXAMPLE -CheckboxCustomField "SkipExchangeCheck" -Exchange2010Version "14.3.513.0" -Exchange2013Version "15.0.1497.48" -Exchange2016Version "15.1.0.0" -Exchange2019Version "15.2.0.0" ## EXAMPLE OUTPUT WITH CustomFieldName ## [Info] Skipping Exchange version check. .NOTES Minimum OS Architecture Supported: Windows Server 2008 R2 Release Notes: Initial Release #> [CmdletBinding()] param ( [String]$CustomFieldName, [String]$Exchange2010Version, [String]$Exchange2013Version, [String]$Exchange2016Version, [String]$Exchange2019Version, [String]$CheckboxCustomField ) begin { function Test-IsElevated { $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() $p = New-Object System.Security.Principal.WindowsPrincipal($id) $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } function Test-IsWorkstation { $OS = Get-WmiObject -Class Win32_OperatingSystem return $OS.ProductType -eq 1 } function Set-NinjaProperty { [CmdletBinding()] Param( [Parameter(Mandatory = $True)] [String]$Name, [Parameter()] [String]$Type, [Parameter(Mandatory = $True, ValueFromPipeline = $True)] $Value, [Parameter()] [String]$DocumentName ) $Characters = $Value | Measure-Object -Character | Select-Object -ExpandProperty Characters if ($Characters -ge 10000) { throw [System.ArgumentOutOfRangeException]::New("Character limit exceeded, value is greater than 10,000 characters.") } # If we're requested to set the field value for a Ninja document we'll specify it here. $DocumentationParams = @{} if ($DocumentName) { $DocumentationParams["DocumentName"] = $DocumentName } # This is a list of valid fields that can be set. If no type is given, it will be assumed that the input doesn't need to be changed. $ValidFields = "Attachment", "Checkbox", "Date", "Date or Date Time", "Decimal", "Dropdown", "Email", "Integer", "IP Address", "MultiLine", "MultiSelect", "Phone", "Secure", "Text", "Time", "URL", "WYSIWYG" if ($Type -and $ValidFields -notcontains $Type) { Write-Warning "$Type is an invalid type! Please check here for valid types. https://ninjarmm.zendesk.com/hc/en-us/articles/16973443979789-Command-Line-Interface-CLI-Supported-Fields-and-Functionality" } # The field below requires additional information to be set $NeedsOptions = "Dropdown" if ($DocumentName) { if ($NeedsOptions -contains $Type) { # We'll redirect the error output to the success stream to make it easier to error out if nothing was found or something else went wrong. $NinjaPropertyOptions = Ninja-Property-Docs-Options -AttributeName $Name @DocumentationParams 2>&1 } } else { if ($NeedsOptions -contains $Type) { $NinjaPropertyOptions = Ninja-Property-Options -Name $Name 2>&1 } } # If an error is received it will have an exception property, the function will exit with that error information. if ($NinjaPropertyOptions.Exception) { throw $NinjaPropertyOptions } # The below types require values not typically given in order to be set. The below code will convert whatever we're given into a format ninjarmm-cli supports. switch ($Type) { "Checkbox" { # While it's highly likely we were given a value like "True" or a boolean datatype it's better to be safe than sorry. $NinjaValue = [System.Convert]::ToBoolean($Value) } "Date or Date Time" { # Ninjarmm-cli expects the GUID of the option to be selected. Therefore, the given value will be matched with a GUID. $Date = (Get-Date $Value).ToUniversalTime() $TimeSpan = New-TimeSpan (Get-Date "1970-01-01 00:00:00") $Date $NinjaValue = $TimeSpan.TotalSeconds } "Dropdown" { # Ninjarmm-cli is expecting the guid of the option we're trying to select. So we'll match up the value we were given with a guid. $Options = $NinjaPropertyOptions -replace '=', ',' | ConvertFrom-Csv -Header "GUID", "Name" $Selection = $Options | Where-Object { $_.Name -eq $Value } | Select-Object -ExpandProperty GUID if (-not $Selection) { throw [System.ArgumentOutOfRangeException]::New("Value is not present in dropdown") } $NinjaValue = $Selection } default { # All the other types shouldn't require additional work on the input. $NinjaValue = $Value } } # We'll need to set the field differently depending on if its a field in a Ninja Document or not. if ($DocumentName) { $CustomField = Ninja-Property-Docs-Set -AttributeName $Name -AttributeValue $NinjaValue @DocumentationParams 2>&1 } else { $CustomField = Ninja-Property-Set -Name $Name -Value $NinjaValue 2>&1 } if ($CustomField.Exception) { throw $CustomField } } function Get-NinjaProperty { [CmdletBinding()] Param( [Parameter(Mandatory = $True, ValueFromPipeline = $True)] [String]$Name, [Parameter()] [String]$Type, [Parameter()] [String]$DocumentName ) # If we're requested to get the field value from a Ninja document we'll specify it here. $DocumentationParams = @{} if ($DocumentName) { $DocumentationParams["DocumentName"] = $DocumentName } # These two types require more information to parse. $NeedsOptions = "DropDown", "MultiSelect" # Grabbing document values requires a slightly different command. if ($DocumentName) { # Secure fields are only readable when they're a device custom field if ($Type -Like "Secure") { throw [System.ArgumentOutOfRangeException]::New("$Type is an invalid type! Please check here for valid types. https://ninjarmm.zendesk.com/hc/en-us/articles/16973443979789-Command-Line-Interface-CLI-Supported-Fields-and-Functionality") } # We'll redirect the error output to the success stream to make it easier to error out if nothing was found or something else went wrong. Write-Host "Retrieving value from Ninja Document..." $NinjaPropertyValue = Ninja-Property-Docs-Get -AttributeName $Name @DocumentationParams 2>&1 # Certain fields require more information to parse. if ($NeedsOptions -contains $Type) { $NinjaPropertyOptions = Ninja-Property-Docs-Options -AttributeName $Name @DocumentationParams 2>&1 } } else { # We'll redirect error output to the success stream to make it easier to error out if nothing was found or something else went wrong. $NinjaPropertyValue = Ninja-Property-Get -Name $Name 2>&1 # Certain fields require more information to parse. if ($NeedsOptions -contains $Type) { $NinjaPropertyOptions = Ninja-Property-Options -Name $Name 2>&1 } } # If we received some sort of error it should have an exception property and we'll exit the function with that error information. if ($NinjaPropertyValue.Exception) { throw $NinjaPropertyValue } if ($NinjaPropertyOptions.Exception) { throw $NinjaPropertyOptions } if (-not $NinjaPropertyValue) { throw [System.NullReferenceException]::New("The Custom Field '$Name' is empty!") } # This switch will compare the type given with the quoted string. If it matches, it'll parse it further; otherwise, the default option will be selected. switch ($Type) { "Attachment" { # Attachments come in a JSON format this will convert it into a PowerShell Object. $NinjaPropertyValue | ConvertFrom-Json } "Checkbox" { # Checkbox's come in as a string representing an integer. We'll need to cast that string into an integer and then convert it to a more traditional boolean. [System.Convert]::ToBoolean([int]$NinjaPropertyValue) } "Date or Date Time" { # In Ninja Date and Date/Time fields are in Unix Epoch time in the UTC timezone the below should convert it into local time as a DateTime object. $UnixTimeStamp = $NinjaPropertyValue $UTC = (Get-Date "1970-01-01 00:00:00").AddSeconds($UnixTimeStamp) $TimeZone = [TimeZoneInfo]::Local [TimeZoneInfo]::ConvertTimeFromUtc($UTC, $TimeZone) } "Decimal" { # In ninja decimals are strings that represent a decimal this will cast it into a double data type. [double]$NinjaPropertyValue } "Device Dropdown" { # Device Drop-Downs Fields come in a JSON format this will convert it into a PowerShell Object. $NinjaPropertyValue | ConvertFrom-Json } "Device MultiSelect" { # Device Multi-Select Fields come in a JSON format this will convert it into a PowerShell Object. $NinjaPropertyValue | ConvertFrom-Json } "Dropdown" { # Drop-Down custom fields come in as a comma-separated list of GUIDs; we'll compare these with all the options and return just the option values selected instead of a GUID. $Options = $NinjaPropertyOptions -replace '=', ',' | ConvertFrom-Csv -Header "GUID", "Name" $Options | Where-Object { $_.GUID -eq $NinjaPropertyValue } | Select-Object -ExpandProperty Name } "Integer" { # Casts the Ninja provided string into an integer. [int]$NinjaPropertyValue } "MultiSelect" { # Multi-Select custom fields come in as a comma-separated list of GUIDs we'll compare these with all the options and return just the option values selected instead of a guid. $Options = $NinjaPropertyOptions -replace '=', ',' | ConvertFrom-Csv -Header "GUID", "Name" $Selection = ($NinjaPropertyValue -split ',').trim() foreach ($Item in $Selection) { $Options | Where-Object { $_.GUID -eq $Item } | Select-Object -ExpandProperty Name } } "Organization Dropdown" { # Turns the Ninja provided JSON into a PowerShell Object. $NinjaPropertyValue | ConvertFrom-Json } "Organization Location Dropdown" { # Turns the Ninja provided JSON into a PowerShell Object. $NinjaPropertyValue | ConvertFrom-Json } "Organization Location MultiSelect" { # Turns the Ninja provided JSON into a PowerShell Object. $NinjaPropertyValue | ConvertFrom-Json } "Organization MultiSelect" { # Turns the Ninja provided JSON into a PowerShell Object. $NinjaPropertyValue | ConvertFrom-Json } "Time" { # Time fields are given as a number of seconds starting from midnight. This will convert it into a DateTime object. $Seconds = $NinjaPropertyValue $UTC = ([TimeSpan]::FromSeconds($Seconds)).ToString("hh\:mm\:ss") $TimeZone = [TimeZoneInfo]::Local $ConvertedTime = [TimeZoneInfo]::ConvertTimeFromUtc($UTC, $TimeZone) Get-Date $ConvertedTime -DisplayHint Time } default { # If no type was given or not one that matches the above types just output what we retrieved. $NinjaPropertyValue } } } $ExitCode = 0 } process { if (-not (Test-IsElevated)) { Write-Host "[Error] Access Denied. Please run with Administrator privileges." exit 1 } # Check that the NT Version is at least 6.1 for Windows Server 2008 R2 $WindowsVersion = $(Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name CurrentVersion).CurrentVersion if ($WindowsVersion -lt 6.1 -or $(Test-IsWorkstation)) { Write-Host "[Error] This script requires Windows Server 2008 R2 or higher." exit 1 } # Get parameters from Script Variables if ($env:customFieldName -and $env:customFieldName -notlike "null") { $CustomFieldName = $env:customFieldName } if ($env:exchange2010Version -and $env:exchange2010Version -notlike "null") { $Exchange2010Version = $env:exchange2010Version } if ($env:exchange2013Version -and $env:exchange2013Version -notlike "null") { $Exchange2013Version = $env:exchange2013Version } if ($env:exchange2016Version -and $env:exchange2016Version -notlike "null") { $Exchange2016Version = $env:exchange2016Version } if ($env:exchange2019Version -and $env:exchange2019Version -notlike "null") { $Exchange2019Version = $env:exchange2019Version } if ($env:checkboxCustomField -and $env:checkboxCustomField -notlike "null") { $CheckboxCustomField = $env:checkboxCustomField } # Required Parameters if ($Exchange2010Version -and $Exchange2013Version -and $Exchange2016Version -and $Exchange2019Version) { # Check if the versions are valid if ([System.Version]::TryParse($Exchange2010Version, [ref]$null) -eq $false) { Write-Host "[Error] Exchange 2010 version is not a valid version: $Exchange2010Version" exit 1 } if ([System.Version]::TryParse($Exchange2013Version, [ref]$null) -eq $false) { Write-Host "[Error] Exchange 2013 version is not a valid version: $Exchange2013Version" exit 1 } if ([System.Version]::TryParse($Exchange2016Version, [ref]$null) -eq $false) { Write-Host "[Error] Exchange 2016 version is not a valid version: $Exchange2016Version" exit 1 } if ([System.Version]::TryParse($Exchange2019Version, [ref]$null) -eq $false) { Write-Host "[Error] Exchange 2019 version is not a valid version: $Exchange2019Version" exit 1 } # Requirements are met } else { Write-Host "[Error] Exchange 2010, 2013, 2016, and 2019 versions are required to run this script." exit 1 } if ($CheckboxCustomField -and $CheckboxCustomField -notlike "null") { # Check if the text custom field is set to 1 as a check box or true as a string try { $boolCheck = Get-NinjaProperty -Name $CheckboxCustomField -Type "Checkbox" $stringCheck = Get-NinjaProperty -Name $CheckboxCustomField if ( $true -eq $boolCheck -or $stringCheck -eq "1" ) { Write-Host "[Info] Skipping Exchange version check." exit 0 } } catch { Write-Host "[Warn] Failed to get the value of the checkbox custom field." Write-Host "[Info] Continuing with Exchange version check." } } # Check if Exchange is installed if (-not $(Get-Service -Name MSExchangeServiceHost -ErrorAction SilentlyContinue)) { Write-Host "[Error] This script requires Exchange to be installed and running." exit 1 } # Find the location of ExSetup.exe $ExSetupPath = Get-ChildItem -Path "C:\Program Files\Microsoft\Exchange Server\*" -Recurse -Filter "ExSetup.exe" | Select-Object -ExpandProperty FullName # Check if this is an Exchange server if (-not (Test-Path -Path $ExSetupPath)) { Write-Host "[Error] Exchange Server is not installed." exit 1 } # Get the installed Exchange version from ExSetup.exe $ExchangeFileVersion = Get-Command $ExSetupPath | ForEach-Object { $_.FileVersionInfo } # Determine the edition of Exchange $ExchangeYearVersion = switch ($ExchangeFileVersion.FileVersionRaw.Major) { 14 { "2010" } 15 { switch ($ExchangeFileVersion.FileVersionRaw.Minor) { 0 { "2013" } 1 { "2016" } 2 { "2019" } Default { "Unknown" } } } Default { "Unknown" } } # Check if the Exchange version is outdated switch ($ExchangeYearVersion) { "2010" { if ($ExchangeFileVersion.FileVersionRaw -lt $Exchange2010Version) { Write-Host "[Warn] Exchange 2010 version is outdated. Found version: $($ExchangeFileVersion.FileVersionRaw)" } else { Write-Host "[Info] Exchange 2010 version is up to date. Found version: $($ExchangeFileVersion.FileVersionRaw)" } } "2013" { if ($ExchangeFileVersion.FileVersionRaw -lt $Exchange2013Version) { Write-Host "[Warn] Exchange 2013 version is outdated. Found version: $($ExchangeFileVersion.FileVersionRaw)" } else { Write-Host "[Info] Exchange 2013 version is up to date. Found version: $($ExchangeFileVersion.FileVersionRaw)" } } "2016" { if ($ExchangeFileVersion.FileVersionRaw -lt $Exchange2016Version) { Write-Host "[Warn] Exchange 2016 version is outdated. Found version: $($ExchangeFileVersion.FileVersionRaw)" } else { Write-Host "[Info] Exchange 2016 version is up to date. Found version: $($ExchangeFileVersion.FileVersionRaw)" } } "2019" { if ($ExchangeFileVersion.FileVersionRaw -lt $Exchange2019Version) { Write-Host "[Warn] Exchange 2019 version is outdated. Found version: $($ExchangeFileVersion.FileVersionRaw)" } else { Write-Host "[Info] Exchange 2019 version is up to date. Found version: $($ExchangeFileVersion.FileVersionRaw)" } } Default { Write-Host "[Error] Unknown Exchange version." exit 1 } } if ($CustomFieldName -and $CustomFieldName -notlike "null") { try { Write-Host "[Info] Attempting to set Custom Field '$CustomFieldName'." Set-NinjaProperty -Name $CustomFieldName -Value $($ExchangeFileVersion.FileVersion | Out-String) Write-Host "[Info] Successfully set Custom Field '$CustomFieldName'!" } catch { Write-Host "[Error] Failed to set Custom Field '$CustomFieldName'." $ExitCode = 1 } } if ($ExitCode -gt 0) { exit $ExitCode } } end { }
Save time with over 300+ scripts from the NinjaOne Dojo.
Detailed Breakdown
This PowerShell script is structured into several key components that work together to check, validate, and act upon Exchange Server version information. Here’s a step-by-step look:
1. Initialization and Validation
a. The script ensures it is executed with administrative privileges.
b. It checks the system’s compatibility by validating the OS version (Windows Server 2008 R2 or higher) and confirms that Exchange is installed by locating the ExSetup.exe file.
2. Parameter Input
a. The script accepts version benchmarks for Exchange 2010, 2013, 2016, and 2019. For example, you can specify -Exchange2019Version “15.2.0.0” to define the minimum acceptable version for Exchange 2019.
b. Optional parameters like -CustomFieldName and -CheckboxCustomField allow administrators to manage custom fields in NinjaOne or skip the version check if a certain condition is met.
3. Version Retrieval
a. It retrieves the version of Exchange installed by analyzing the file version of ExSetup.exe. Based on the major and minor version numbers, it determines the Exchange edition (e.g., 2010, 2013).
4. Version Comparison
a. The script compares the retrieved version with the specified benchmarks. If the version is outdated, a warning is displayed. Up-to-date versions are acknowledged with an informational message.
5. Custom Field Integration
a. If the -CustomFieldName parameter is provided, the script sets the version information in a custom field, enabling easy tracking within tools like NinjaOne.
6. Error Handling and Exit Codes
a. The script is designed to gracefully handle errors, such as missing Exchange installations or invalid version inputs, and exits with appropriate status codes.
Potential Use Cases
Case Study: Managing Exchange Versions in a Multi-Site Environment
Imagine an IT administrator overseeing multiple Exchange deployments across regional offices. The administrator uses this script as part of a scheduled maintenance task to check Exchange versions, validate compliance with the organization’s policies, and update NinjaOne’s custom fields with the latest version data. If any versions are outdated, the admin is alerted to prioritize updates, ensuring the environment remains secure and compliant.
Comparisons
This script’s automated approach offers distinct advantages over manual methods or GUI-based tools:
- Manual Methods: Manually checking Exchange versions involves logging into each server and running individual commands—a time-consuming and error-prone process.
- GUI Tools: While some management tools provide graphical insights, they often lack the customization and automation capabilities that this script provides, especially for MSPs managing multiple environments.
FAQs
Q: What happens if Exchange is not installed?
The script terminates and displays an error message, ensuring administrators are aware of the issue.
Q: Can I use this script to check Exchange versions in bulk?
Yes, when integrated with tools like NinjaOne, you can automate version checks across multiple servers.
Q: What if the provided version benchmarks are invalid?
The script validates each version input and terminates with an error if any benchmarks are not valid version formats.
Q: How do custom fields work in this script?
Custom fields allow administrators to save version information within management systems. The script interacts with NinjaOne to set these fields dynamically.
Implications
By identifying outdated Exchange Server versions, this script helps prevent potential security vulnerabilities and ensures compliance with supported configurations. Regular use of such automation reduces the risk of human error, improves efficiency, and enables proactive IT management.
Recommendations
- Test in a Non-Production Environment: Always test scripts in a staging environment to ensure compatibility with your setup.
- Schedule Regular Checks: Use task scheduling to automate periodic checks, keeping your Exchange environments up to date.
- Leverage Custom Fields Wisely: Utilize the -CustomFieldName parameter to maintain comprehensive records of your environment.
Final Thoughts
Managing Exchange Server environments is a critical responsibility for IT professionals. This PowerShell script simplifies the process of checking and validating Exchange versions, enabling organizations to maintain secure, compliant, and high-performing systems. For those using NinjaOne, the script’s integration capabilities enhance operational efficiency, making it an invaluable tool for modern IT management.