My PowerShell function extends the great work already created by Paul Flaherty, Jeff Guillet, Mark E. Smith and Jason Sherry. It is compatible with Exchange Server versions 2007, 2010 and 2013. Below the following code block you will find some helpful usage examples.
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | function Get-ExchangeServerPlus{ <#Get-ExchangeServerPlus Written By Paul Flaherty, http://blogs.flaphead.com Modified by Jeff Guillet, http://www.expta.com Modified by Mark E. Smith, http://marksmith.netrends.com Modified by Jason Sherry, http://blog.jasonsherry.net | Version 2.0 Modified yet again by Marc Weisel, http://binarynature.blogspot.com Let's just label it [v3.0] - 04/24/2013 #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [Alias('hostname')] [Alias('cn')] [string]$ComputerName ) BEGIN { # Set registry prefix path variables $regprod = "SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products" $reguninst = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" # Get a list of EXCH servers if ($ComputerName) { $exchservers = Get-ExchangeServer -Identity $ComputerName -ErrorAction Stop | Where-Object { $_.IsEdgeServer -eq $false } } else { $exchservers = Get-ExchangeServer | Where-Object { $_.IsEdgeServer -eq $false } } } PROCESS { foreach ($exchserver in $exchservers) { try { # Get OS info (version and service pack) $os = Get-WmiObject -ComputerName $exchserver.Name ` -Class Win32_OperatingSystem -ErrorAction Stop $osversion = $os.Caption $svcpack = $os.ServicePackMajorVersion # Get architecture type (32 or 64 bit) if ($osversion -like '*2003*') { $cs = Get-WmiObject -ComputerName $exchserver.Name ` -Class Win32_ComputerSystem -ErrorAction Stop $numbits = $cs.SystemType } else { $numbits = $os.OSArchitecture } } catch { # Check for common DCOM errors and display "friendly" output switch ($_) { { $_.Exception.ErrorCode -eq 0x800706ba } ` { $err = 'Unavailable (Host Offline or Firewall)'; break; } { $_.CategoryInfo.Reason -eq 'UnauthorizedAccessException' } ` { $err = 'Access denied (Check User Permissions)'; break; } default { $err = $_.Exception.Message } } Write-Warning "$($exchserver.Name) - $err" } # Get Build Number (Version) $major = ($exchserver.AdminDisplayVersion).major $minor = ($exchserver.AdminDisplayVersion).minor $build = ($exchserver.AdminDisplayVersion).build $revision = ($exchserver.AdminDisplayVersion).revision [Version]$exchbuildnum = "$major.$minor.$build.$revision" # Get EXCH Roles $whichroles = @() if ($exchserver.ServerRole -like '*Mailbox*') { $whichroles += 'MB' } if ($exchserver.ServerRole -like '*Hub*') { $whichroles += 'HT' } if ($exchserver.ServerRole -like '*Client*') { $whichroles += 'CAS' } if ($exchserver.ServerRole -like '*Unified*') { $whichroles += 'UM' } $roles = [System.String]::Join(',', $whichroles) # Set registry path for each EXCH version and populate version attribute if ($exchserver.IsExchange2007OrLater) { if ($exchserver.IsE14OrLater) { if ($exchserver.AdminDisplayVersion -like 'Version 14.*') { $key = "$regprod\AE1D439464EB1B8488741FFA028E291C\Patches\" $exchver = '2010' } else { $key = "$reguninst\Microsoft Exchange v15\" $altkey = "$reguninst\{4934D1EA-BE46-48B1-8847-F1AF20E892C1}\" $exchver = '2013' } } else { $key = "$regprod\461C2B4266EDEF444B864AD6D9E5B613\Patches\" $exchver = '2007' } } else { Write-Warning "Exchange server version could not be determined." } try { if ($key) { # Connect to the server's remote registry and # query for update data $srv = $exchserver.Name $type = [Microsoft.Win32.RegistryHive]::LocalMachine $regkey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $srv) $regkey = $regkey.OpenSubKey($key) $updates = @() # Query registry for EXCH 2007 and 2010 servers if ($exchver -match '^20[0|1][7|0]$') { foreach($sub in $regkey.GetSubKeyNames()) { $subkey = $key + $sub $subregkey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $srv) $subregkey = $subregkey.OpenSubKey($subkey) foreach($subx in $subregkey.GetValueNames()) { # Get installed date and displayname properties of the EXCH patch if ($subx -eq 'Installed') { $instdate = $subregkey.GetValue($subx) $instdate = $instdate.substring(4,2) + "/" + ` $instdate.substring(6,2) + "/" + $instdate.substring(0,4) } if ($subx -eq 'DisplayName') { $updval = $subregkey.GetValue($subx) $ur = $updval.SubString($updval.LastIndexOf(' ') + 1) $desc = $updval.Substring(0,$updval.LastIndexOf($ur) - 1) } } # Create object for update rollup info $updobj = New-Object -TypeName PSObject -Property @{ InstallDate=$instdate UpdateBuild=[Version]$ur Description=$desc } $updates += $updobj # Close connection $subregkey.Close() } } # Query registry for EXCH 2013 servers else { $altregkey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $srv) $altregkey = $altregkey.OpenSubKey($altkey) foreach ($keyprop in $regkey.GetValueNames()) { if ($keyprop -eq 'DisplayName') { $desc = $regkey.GetValue($keyprop) } } foreach ($altkeyprop in $altregkey.GetValueNames()) { if ($altkeyprop -eq 'InstallDate') { $instdate = $altregkey.GetValue($altkeyprop) $instdate = $instdate.substring(4,2) + "/" + ` $instdate.substring(6,2) + "/" + $instdate.substring(0,4) } if ($altkeyprop -eq 'DisplayVersion') { $cu = $altregkey.GetValue($altkeyprop) } } # Create PSObject for cumulative update info $updobj = New-Object -TypeName PSObject -Property @{ InstallDate=$instdate UpdateBuild=[Version]$cu Description=$desc } $updates += $updobj # Close connection $altregkey.Close() } # Close connection $regkey.Close() } else { Write-Warning "Could not retrieve update data for $($exchserver.Name)." } } catch { $err = $_.Exception.Message Write-Warning "$($exchserver.Name) - $err" } # Create (and populate) an ordered dictionary collection with # Exchange Server Plus object properties for cleaner default output (PS3 or later) $exchprops = New-Object System.Collections.Specialized.OrderedDictionary $exchprops.Add("Name",$exchserver.Name) $exchprops.Add("Version",$exchver) $exchprops.Add("Edition",$exchserver.Edition) $exchprops.Add("Build",$exchbuildnum) $exchprops.Add("Update",$updates) $exchprops.Add("Role",$roles) $exchprops.Add("OSVer",$osversion) $exchprops.Add("OSSP",$svcpack) $exchprops.Add("OSArch",$numbits) # Create Exchange Server Plus object $exchsrvplus = New-Object -TypeName PSObject -Property $exchprops $exchsrvplus.PSObject.TypeNames.Insert(0,'BinaryNature.ExchangeServerPlus') Write-Output $exchsrvplus } } } |
Usage
The main benefit for output to a PowerShell object, instead of text, is that we have a multitude of choices when it comes to the flexibility of operations thru the PowerShell pipeline.
My preferable method is to save the above Get-ExchangeServerPlus function in a script module, then import the module to access the function. For this specific scenario, we will just load the function using the "dot source" method. Let's first save the function as a script file. I've named my file exchtools.ps1. Now from the directory where you saved the script file, load the function into a PowerShell session using the Exchange Management Shell.
[PS] . .\exchtools.ps1
Run the following command to see what members are available from our custom object:
[PS] Get-ExchangeServerPlus | Get-Member
TypeName: BinaryNature.ExchangeServerPlus
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Build NoteProperty System.Version
Edition NoteProperty Microsoft.Exchange.Data.ServerEditionType
Name NoteProperty System.String
OSArch NoteProperty System.String
OSSP NoteProperty System.UInt16
OSVer NoteProperty System.String
Role NoteProperty System.String
Update NoteProperty System.Object[]
Version NoteProperty System.String
You will notice from the object member collection, the Update property actually contains a nested array of child objects. Each of those child objects represent data about the installed Update Rollups (or Cumulative Updates for Exchange Server 2013).
# Basic Output
The default output of the command will return all Exchange Servers in the organization each as a separate object. The default output (for PowerShell version 2) will also return the properties, of each object, in a random order because we don't have an explicit view attached to this custom object. We can "pretty up" the output with some examples in the following sections. Run the following command to get the default "raw" output:
[PS] Get-ExchangeServerPlus
Version : 2007
OSSP : 2
OSArch : x64-based PC
Name : EXCH01
Build : 8.3.83.6
Edition : Standard
Update : {@{UpdateBuild=8.3.279.5; InstallDate=11/10/2012; Description=Update Rollup 8-v2 for Exchange Server 2007 Service Pack 3 (KB2756497)}, @{UpdateBuild=8.3.298.3; InstallDate=02/16/2013; Description=Update Rollup 10 for Exchange Server 2007 Service Pack 3 (KB2788321)}}
Role : MB,HT,CAS
OSVer : Microsoft(R) Windows(R) Server 2003 Enterprise x64 Edition
Version : 2007
OSSP : 1
OSArch : 64-bit
Name : EXCH02
Build : 8.3.83.6
Edition : Enterprise
Update : {@{UpdateBuild=8.3.279.5; InstallDate=11/11/2012; Description=Update Rollup 8-v2 for Exchange Server 2007 Service Pack 3 (KB2756497)}, @{UpdateBuild=8.3.298.3; InstallDate=02/18/2013; Description=Update Rollup 10 for Exchange Server 2007 Service Pack 3 (KB2788321)}}
Role : MB,HT,CAS
OSVer : Microsoft Windows Server 2008 R2 Enterprise
Version : 2010
OSSP : 1
OSArch : 64-bit
Name : EXCH03
Build : 14.2.247.5
Edition : Enterprise
Update : {@{UpdateBuild=14.2.342.3; InstallDate=02/23/2013; Description=Update Rollup 6 for Exchange Server 2010 Service Pack 2 (KB2746164)}}
Role : MB,HT,CAS
OSVer : Microsoft Windows Server 2008 R2 Enterprise
# Get Installed Updates for a Single Server
The ComputerName parameter allows you to query a single Exchange Server in your organization. The following pipeline of commands will return only the Update child objects installed on this specific server.
[PS] Get-ExchangeServerPlus -cn exch02 | Select-Object -ExpandProperty Update | Format-Table -Property inst*,upd*,desc* -AutoSize
InstallDate UpdateBuild Description
----------- ----------- -----------
11/11/2012 8.3.279.5 Update Rollup 8-v2 for Exchange Server 2007 Service Pack 3 (KB2756497)
02/18/2013 8.3.298.3 Update Rollup 10 for Exchange Server 2007 Service Pack 3 (KB2788321)
# Output All Exchange Server Object Properties with Installed Updates
The previous example focused on the Update child objects for a single server. Let's take this a step further and display all properties (for each Exchange Server) in addition to the Update child objects. Run the following pipeline of commands:
[PS] Get-ExchangeServerPlus | % {"Name`t: $($_.Name)"; "Version`t: $($_.Version)"; "Edition`t: $($_.Edition)"; "Build`t: $($_.Build)"; "Role`t: $($_.Role)"; "OSVer`t: $($_.OSVer)"; "OSSP`t: $($_.OSSP)"; "OSArch`t: $($_.OSArch)"; if ($_.Update) { $_ | select -ExpandProperty update | ft inst*,upd*,desc* -auto } else { [Environment]::NewLine }}
Name : EXCH01
Version : 2007
Edition : Standard
Build : 8.3.83.6
Role : MB,HT,CAS
OSVer : Microsoft(R) Windows(R) Server 2003 Enterprise x64 Edition
OSSP : 2
OSArch : x64-based PC
InstallDate UpdateBuild Description
----------- ----------- -----------
11/10/2012 8.3.279.5 Update Rollup 8-v2 for Exchange Server 2007 Service Pack 3 (KB2756497)
02/16/2013 8.3.298.3 Update Rollup 10 for Exchange Server 2007 Service Pack 3 (KB2788321)
Name : EXCH02
Version : 2007
Edition : Enterprise
Build : 8.3.83.6
Role : MB,HT,CAS
OSVer : Microsoft Windows Server 2008 R2 Enterprise
OSSP : 1
OSArch : 64-bit
InstallDate UpdateBuild Description
----------- ----------- -----------
11/11/2012 8.3.279.5 Update Rollup 8-v2 for Exchange Server 2007 Service Pack 3 (KB2756497)
02/18/2013 8.3.298.3 Update Rollup 10 for Exchange Server 2007 Service Pack 3 (KB2788321)
Name : EXCH03
Version : 2010
Edition : Enterprise
Build : 14.2.247.5
Role : MB,HT,CAS
OSVer : Microsoft Windows Server 2008 R2 Enterprise
OSSP : 1
OSArch : 64-bit
InstallDate UpdateBuild Description
----------- ----------- -----------
02/23/2013 14.2.342.3 Update Rollup 6 for Exchange Server 2010 Service Pack 2 (KB2746164)
And here is the output from an Exchange Organization containing both a 2010 and 2013 Exchange server:
Name : EXCH160-01
Version : 2010
Edition : Enterprise
Build : 14.3.123.4
Role : MB,HT,CAS
OSVer : Microsoft Windows Server 2008 R2 Enterprise
OSSP : 1
OSArch : 64-bit
Name : EXCH160-02
Version : 2013
Edition : Enterprise
Build : 15.0.620.29
Role : MB,CAS
OSVer : Microsoft Windows Server 2012 Datacenter
OSSP : 0
OSArch : 64-bit
InstallDate UpdateBuild Description
----------- ----------- -----------
04/21/2013 15.0.620.29 Microsoft Exchange Server 2013 Cumulative Update 1
Notice the EXCH160-01 server doesn't have any updates listed. The reason is because this Exchange server was recently installed with SP3 and no Update Rollups have been installed for SP3 on this server, so we refer to the Build property for the current build number. The 3 in 14.3.123.4 represents the service pack number.
# Grid Output
We can also output the server objects to a sortable grid view. This view will display the latest (current) build number with the description of the Update Rollup or Cumulative Update. How is this different than the output of the default Get-ExchangeServer cmdlet? For Exchange 2007 and 2010 servers, the AdminDisplayVersion property will only show the current build number if no Update Rollups have been installed (ex. Service Pack w/ no Update Rollups). Run the following command to see what I mean:
[PS] Get-ExchangeServer | fl name,admin*
Name : EXCH01
AdminDisplayVersion : Version 8.3 (Build 83.6)
Name : EXCH02
AdminDisplayVersion : Version 14.2 (Build 247.5)
From our earlier examples, we know the "real" current build number because of the child objects of the Update property. The following pipeline of commands really exhibit the true power of PowerShell (pun intended ;-)). The flow of this pipeline essentially performs the operations of grabbing the "core" properties (for the Exchange Server objects), sorting on the UpdateBuild property (to show the latest version child object), applies a little logic and then displays the result.
Note: The Out-GridView cmdlet requires the Windows PowerShell Integrated Scripting Environment (ISE) feature to be installed.
And here is a screenshot capture from an Exchange Organization containing both a 2010 and 2013 Exchange server:
[PS] Get-ExchangeServerPlus | select -Property Name,Version,Edition,Role,@{n="Build";e={if ($_.Update) {$_.Update | sort updatebuild -desc | select -first 1 | select -expand updatebuild} else {$_.Build}}},@{n="Update Description";e={if ($_.Update) {$_.Update | sort UpdateBuild -desc | select -First 1 | select -expand desc*} else {'No Update Rollups or Cumulative Updates installed.'}}} | Out-GridView -Title "Exchange Servers"
# CSV File Output
If we swap the last cmdlet in the pipeline (from the previous example) from Out-GridView to Export-Csv, we now have the ability to save the data to a CSV file.
[PS] Get-ExchangeServerPlus | select -Property Name,Version,Edition,Role,@{n="Build";e={if ($_.Update) {$_.Update | sort updatebuild -desc | select -first 1 | select -expand updatebuild} else {$_.Build}}},@{n="Update Description";e={if ($_.Update) {$_.Update | sort UpdateBuild -desc | select -First 1 | select -expand desc*} else {'No Update Rollups or Cumulative Updates installed.'}}} | Export-Csv -Path $HOME\Documents\exch-server-list.csv -NoTypeInformation
[PS] Get-Content ~\Documents\exch-server-list.csv
"Name","Version","Edition","Role","Build","Update Description"
"EXCH01","2007","Enterprise","MB,HT,CAS","8.3.298.3","Update Rollup 10 for Exchange Server 2007 Service Pack 3 (KB2788321)"
"EXCH02","2010","Enterprise","MB,HT,CAS","14.2.342.3","Update Rollup 6 for Exchange Server 2010 Service Pack 2 (KB2746164)"



0 comments:
Post a Comment