MyITGuy
4/13/2017 - 3:30 AM

PowerShell: Get-MicrosoftUpdates

PowerShell: Get-MicrosoftUpdates

$MicrosoftUpdates = Get-MicrosoftUpdates -IncludeDISM
$GetHotfixOnlyUpdates = $MicrosoftUpdates | ? {$_.Source.Count -eq 1 -and $_.Source -contains 'Get-Hotfix'}
$MicrosoftUpdateOnlyUpdates = $MicrosoftUpdates | ? {$_.Source.Count -eq 1 -and $_.Source -contains 'MicrosoftUpdate'}
$DismOnlyUpdates = $MicrosoftUpdates | ? {$_.Source.Count -eq 1 -and $_.Source -contains 'DISM'}

$GetHotfixUpdates = $MicrosoftUpdates | ? {$_.Source -contains 'Get-Hotfix'}
$MicrosoftUpdateUpdates = $MicrosoftUpdates | ? {$_.Source -contains 'MicrosoftUpdate'}
$DismUpdates = $MicrosoftUpdates | ? {$_.Source -contains 'DISM'}

$MissingFromGetHotfix = $MicrosoftUpdates | ? {$_.Source -notcontains 'Get-Hotfix'}
$MissingFromMicrosoftUpdate = $MicrosoftUpdates | ? {$_.Source -notcontains 'MicrosoftUpdate'}
$MissingFromDism = $MicrosoftUpdates | ? {$_.Source -notcontains 'DISM'}

#GetHotfixOnlyUpdates do show in Add/Remove Programs
#MicrosoftUpdateOnlyUpdates do not show in Add/Remove Programs
#DismOnlyUpdates do not show in Add/Remove Programs
#region Get-MicrosoftUpdates
	function Get-MicrosoftUpdates {
		[CmdletBinding(SupportsShouldProcess=$True,DefaultParameterSetName="None")]
		PARAM(
			[switch]$IncludeDISM
		)
		try {

			#region Windows Installer Patches
				$Installer = New-Object -ComObject WindowsInstaller.Installer
				$szProductCode = [String]::Empty
				$szUserSid = [String]::Empty
				$dwContext = 4
				$dwFilter = 15
				# PatchesEx implements MsiEnumPatchesEx, https://msdn.microsoft.com/en-us/library/aa370100(v=vs.85).aspx
				$Patches = $Installer.PatchesEx($szProductCode, $szUserSid, $dwContext, $dwFilter)
				$UseableWindowsInstallerPatchDetails = foreach ($Patch In $Patches) {
					# Returns a Patch object
					# Patch object: https://msdn.microsoft.com/en-us/library/windows/desktop/aa370594(v=vs.85).aspx
					$PatchProperties = [ordered]@{}
					# PatchProperty, https://msdn.microsoft.com/en-us/library/aa370598(v=vs.85).aspx
				  	$PatchProperties.Title = $Patch.PatchProperty('DisplayName')
					$PatchProperties.HotfixID = $null
					try {
						$MatchFound = $PatchProperties.Title -match 'KB\d+(?!^\d)'
						if ($MatchFound) {$PatchProperties.HotfixID = $Matches[0]}
					} catch {
					}
					try {
						$PatchProperties.Caption = $Patch.PatchProperty('MoreInfoURL')
					} catch {
						$PatchProperties.Caption = $null
					}
					$PatchProperties.InstallDate = ([datetime]::ParseExact($Patch.PatchProperty('InstallDate'), "yyyyMMdd", [System.Globalization.CultureInfo]::CurrentCulture))
					$PatchProperties.Source = "WindowsInstallerPatch"
					New-Object -TypeName PSObject -Property $PatchProperties | ? {$_.HotfixID}
				}
			#endregion Windows Installer Patches

			#region Windows Installer Products
				$Installer = New-Object -ComObject WindowsInstaller.Installer
				$szProductCode = [String]::Empty
				$szUserSid = [String]::Empty
				$dwContext = 4
				$dwIndex = 0
				# ProductsEx implements MsiEnumProductsEx, https://msdn.microsoft.com/en-us/library/aa370102(v=vs.85).aspx
				$Products = $Installer.ProductsEx($szProductCode, $szUserSid, $dwContext, $dwIndex)
				$UseableWindowsInstallerProductDetails = foreach ($Product In $Products) {
					# Returns a Product object
					# Product object: https://msdn.microsoft.com/en-us/library/windows/desktop/aa370867(v=vs.85).aspx
					$ProductProperties = [ordered]@{}
					# ProductProperty, https://msdn.microsoft.com/en-us/library/aa370598(v=vs.85).aspx
				  	$ProductProperties.Title = $Product.InstallProperty('ProductName')
					$ProductProperties.HotfixID = $null
					try {
						$MatchFound = $ProductProperties.Title -match 'KB\d+(?!^\d)'
						if ($MatchFound) {$ProductProperties.HotfixID = $Matches[0]}
					} catch {
					}
					try {
						$ProductProperties.InstallDate = ([datetime]::ParseExact($Product.InstallProperty('InstallDate'), "yyyyMMdd", [System.Globalization.CultureInfo]::CurrentCulture))
					} catch {
						$ProductProperties.InstallDate = $null
					}
					$ProductProperties.Source = "WindowsInstallerProduct"
					New-Object -TypeName PSObject -Property $ProductProperties | ? {$_.HotfixID}
				}
			#endregion Windows Installer Products
			
			#region Microsoft Updates
				$IUpdateSession = New-Object -ComObject Microsoft.Update.Session
				$IUpdateSearcher = $IUpdateSession.CreateUpdateSearcher()
				$HistoryCount = $IUpdateSearcher.GetTotalHistoryCount()
				$IUpdateHistoryEntryCollection = $IUpdateSearcher.QueryHistory(0, $HistoryCount)

				# Extract useable data
				$UseableMicrosoftUpdateDetails = $IUpdateHistoryEntryCollection | % {
					try {
						$MatchFound = $_.Title -match 'KB\d+(?!^\d)'
						if ($MatchFound) {$tmpHotfixId = $Matches[0]}
					} catch {
						$tmpHotfixId = [string]::Empty
					}
					New-Object -TypeName PSObject -Property @{
						InstallDate = ([datetime]$_.Date).ToLocalTime()
						HotfixID = $tmpHotfixId
						Title = $_.Title
						Source = "MicrosoftUpdate"
					}
				}
			#endregion Microsoft Updates

			#region DISM
				if ($IncludeDISM) {
					if ((New-Object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) {
						$DISMDetails = dism.exe /Online /Get-Packages /Format:Table | ? {$_.Contains("|") -and -not $_.StartsWith("-")} | % {($_.Split('|').Trim() -join ',')} | ConvertFrom-Csv
						$UseableDISMDetails = $DISMDetails | ? {@('Update','Security Update') -contains $_."Release Type" -and $_."Package Identity".StartsWith('Package_for_') -eq $true -and $_."Package Identity".StartsWith('Package_for_RollupFix') -eq $false} | foreach {
							New-Object -TypeName PSObject -Property @{
								InstallDate = ([datetime]$_."Install Time").ToLocalTime()
								HotfixID = (($_."Package Identity".TrimStart('Package_for_')).split('~'))[0]
								Source = "DISM"
							}
						}
					} else {
						Write-Error "IncludeDISM parameter can only be used with accounts holding the Adminstrator role."
					}
				}
			#endregion DISM

			#region Get-Hotfix
				# Get installed hotfixes, extract useable data
				$InstalledHotfixes = Get-HotFix | Select Caption, Description, HotfixID, InstalledBy, @{Name='InstallDate';Expression={$_.InstalledOn}}, @{Name='Source';Expression={'Get-Hotfix'}}, @{Name='Title';Expression={$null}}
			#endregion Get-Hotfix

			# Get only hotfix IDs
			$AllHotfixIDs = @()
			$AllHotfixIDs += $UseableWindowsInstallerProductDetails | Select -ExpandProperty HotfixID
			$AllHotfixIDs += $UseableWindowsInstallerPatchDetails | Select -ExpandProperty HotfixID
			$AllHotfixIDs += $UseableMicrosoftUpdateDetails | Select -ExpandProperty HotfixID
			$AllHotfixIDs += $UseableDISMDetails | Select -ExpandProperty HotfixID
			$AllHotfixIDs += $InstalledHotfixes | Select -ExpandProperty HotfixID
			$AllHotfixIDs = $AllHotfixIDs | Sort -Unique
			
			foreach ($SingleHotfixID IN $AllHotfixIDs) {
				$hash = @{}
				$hash.ComputerName = $env:COMPUTERNAME
				$hash.HotfixID = $SingleHotfixID

				$WindowsInstallerProductDetail = $UseableWindowsInstallerProductDetails | ? {$_.HotfixID -eq $hash.HotfixID}
				$WindowsInstallerPatchDetail = $UseableWindowsInstallerPatchDetails | ? {$_.HotfixID -eq $hash.HotfixID}
				$MicrosoftUpdateDetail = $UseableMicrosoftUpdateDetails | ? {$_.HotfixID -eq $hash.HotfixID}
				$UseableDISMDetail = $UseableDISMDetails | ? {$_.HotfixID -eq $hash.HotfixID}
				$GetHotfixDetail = $InstalledHotfixes | ? {$_.HotfixID -eq $hash.HotfixID}

				$hash.Source = @()
				$hash.InstallDate = $null
				$hash.Title = $null
				$hash.Caption = $null
				$hash.Description = $null
				$hash.InstalledBy = $null

				$PrioritizedDetails = @($GetHotfixDetail, $UseableDISMDetail, $MicrosoftUpdateDetail, $WindowsInstallerPatchDetail, $WindowsInstallerProductDetail)
				foreach ($PrioritizedDetail In $PrioritizedDetails) {
					if ($PrioritizedDetail.Source) {$hash.Source += $PrioritizedDetail.Source | Sort | Select -Last 1}
					if (!$hash.InstallDate -and $PrioritizedDetail.InstallDate) {$hash.InstallDate = $PrioritizedDetail.InstallDate | Sort | Select -Last 1}
					if (!$hash.Title -and $PrioritizedDetail.Title) {$hash.Title = $PrioritizedDetail.Title | Sort | Select -Last 1}
					if (!$hash.Caption -and $PrioritizedDetail.Caption) {$hash.Caption = $PrioritizedDetail.Caption | Sort | Select -Last 1}
					if (!$hash.Description -and $PrioritizedDetail.Description) {$hash.Description = $PrioritizedDetail.Description | Sort | Select -Last 1}
					if (!$hash.InstalledBy -and $PrioritizedDetail.InstalledBy) {$hash.InstalledBy = $PrioritizedDetail.InstalledBy | Sort | Select -Last 1}
				}
				New-Object -TypeName PSObject -Property $hash
			}
		} catch {
			throw $_
		} finally {
		}
	}
#endregion