14 June 2017

MDT Windows Updates Build Report

I found it nice to be able to get a clean, filtered report on what Windows updates got installed during the build process. This allows me to inject those updates into the MDT Packages so they get injected into the image before it is laid down to speed the process up. I had published this tool two years ago and decided to revamp it to also include email functionality. The tool has given me a report, but there were times I forgot to look at it after a build completed. This reminds me by sending the report out via email.

The way this tool works is by reading the ZTIWindowsUpdate.log file from the c:\minint\smsosd\osdlogs directory and extracting the list of installed Windows Updates. The script filters out everything that is non-windows updates, such as Dell drivers. It also filters out the windows defender updates since those are cumulative and gets updated on a regular basis. 

This is a screenshot of what the logs look like when executed and output to the screen:



Here is a screenshot of what the same report looks like when opened up in Excel. 


The script extracts the KB article number and description and writes that information to an object. The object is then displayed on the screen and written to a .CSV file. It is sorted by KBArticle number. 

The firm I work at uses Dell machines and in doing so I excluded all Dell drivers from the list. There is also an exclusions.txt file it can read from to input items you may want to exclude from the list. I added "*Advanced Micro Devices*" as one item in my TXT file. The exclusions.txt file should reside in the same directory as the script. 

The script has been tested when a system is connected to the domain (Final Image) and when it belongs to a workgroup (Reference Image). It works in both instances.

I have pre-populated all parameters, except From, To, and SMTPServer. Those were left blank since you would likely want to populate them at the command line. 

Here is an example:

powershell.exe -file WindowsUpdatesReport.ps1 -email -From IT@Testcompany.com -To mickpletcher@testcompany.com -SMTPServer smtp.testcompany.com

I have pre-populated the -OutputFile, -ExclusionsFile, -Subject, and -Body. You can go into the script and change those or decide to override them by defining them at the command line. You could also populate the -From, -To, and -SMTPServer if you like. 

Here is a screenshot of how it is setup in the MDT task sequence the first time. This did not work. 


And this is a filtered screenshot of how it is setup under as an application install:



I tried one more way to execute it and it finally worked as shown below:


The command line I used is: powershell.exe -executionpolicy bypass -file <UNC path>\WindowsUpdatesReport.ps1 -Email -From <sender's email address> -To <recipient's email address> -SMTPServer <SMTP server address>

The start in contains the <UNC path> where the script resides.

You can download the file from my GitHub location


One more thing I wanted to mention is SAPIEN's PowerShell Studio. This made writing this script an absolute breeze. I highly recommend using it. 


 <#  
      .SYNOPSIS  
           Generate Windows Updates Report  
        
      .DESCRIPTION  
           This script will extract the list of windows updates installed  
           during an MDT build.  
        
      .PARAMETER OutputFile  
           File to write the list of installed updates to.  
        
      .PARAMETER ExclusionsFile  
           Text file containing a list of update descriptions to exclude from the report  
        
      .PARAMETER Email  
           Send an email to the specified IT staff with the attached .csv file containing a list of all updates installed during the build process.  
        
      .PARAMETER From  
           Email Sender  
        
      .PARAMETER To  
           Email Recipient  
        
      .PARAMETER SMTPServer  
           SMTPServer  
        
      .PARAMETER Subject  
           Email Subject  
        
      .PARAMETER Body  
           Body contents  
        
      .EXAMPLE  
           powershell.exe -executionpolicy bypass -file WindowsUpdatesReport.ps1 -OutputFile BaseBuild.csv -Path \\NetworkLocation\Directory  
        
      .NOTES  
           ===========================================================================  
           Created with:    SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.139  
           Created on:      5/31/2017 10:12 AM  
           Created by:      Mick Pletcher  
           Filename:        WindowsUpdatesReport.ps1  
           ===========================================================================  
 #>  
 [CmdletBinding()]  
 param  
 (  
      [ValidateNotNullOrEmpty()][string]$OutputFile = 'WindowsUpdatesReport.csv',  
      [ValidateNotNullOrEmpty()][string]$ExclusionsFile = 'Exclusions.txt',  
      [switch]$Email,  
      [string]$From,  
      [string]$To,  
      [string]$SMTPServer,  
      [string]$Subject = 'Windows Updates Build Report',  
      [string]$Body = "List of windows updates installed during the build process"  
 )  
   
 function Get-RelativePath {  
 <#  
      .SYNOPSIS  
           Get the relative path  
        
      .DESCRIPTION  
           Returns the location of the currently running PowerShell script  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([string])]  
      param ()  
        
      $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
      Return $Path  
 }  
   
 function Remove-OutputFile {  
 <#  
      .SYNOPSIS  
           Delete Output File  
        
      .DESCRIPTION  
           This function deletes the old output file that contains a list of updates that were installed during a build.  
 #>  
        
      [CmdletBinding()]  
      param ()  
        
      #Get the path this script is executing from  
      $RelativePath = Get-RelativePath  
      #Define location of the output file  
      $File = $RelativePath + $OutputFile  
      If ((Test-Path -Path $File) -eq $true) {  
           Remove-Item -Path $File -Force  
      }  
 }  
   
 function Get-Updates {  
 <#  
      .SYNOPSIS  
           Retrieve the list of installed updates  
        
      .DESCRIPTION  
           This function retrieves the list of updates that were installed during the build process  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([array])]  
      param ()  
        
      $UpdateArray = @()  
      #Get the path this script is executing from  
      $RelativePath = Get-RelativePath  
      #File containing a list of exclusions  
      $ExclusionsFile = $RelativePath + $ExclusionsFile  
      #Get list of exclusions from exclusions file  
      $Exclusions = Get-Content -Path $ExclusionsFile  
      #Locate the ZTIWindowsUpdate.log file  
      $FileName = Get-ChildItem -Path $env:HOMEDRIVE"\minint" -filter ztiwindowsupdate.log -recurse  
      #Get list of all installed updates except for Windows Malicious Software Removal Tool, Definition Update for Windows Defender, and Definition Update for Microsoft Endpoint Protection  
      $FileContent = Get-Content -Path $FileName.FullName | Where-Object { ($_ -like "*INSTALL*") } | Where-Object { $_ -notlike "*Windows Defender*" } | Where-Object { $_ -notlike "*Endpoint Protection*" } | Where-Object { $_ -notlike "*Windows Malicious Software Removal Tool*" } | Where-Object { $_ -notlike "*Dell*" } | Where-Object { $_ -notlike $Exclusions }  
      #Filter out all unnecessary lines  
      $Updates = (($FileContent -replace (" - ", "~")).split("~") | where-object { ($_ -notlike "*LOG*INSTALL*") -and ($_ -notlike "*ZTIWindowsUpdate*") -and ($_ -notlike "*-*-*-*-*") })  
      foreach ($Update in $Updates) {  
           #Create object  
           $Object = New-Object -TypeName System.Management.Automation.PSObject  
           #Add KB article number to object  
           $Object | Add-Member -MemberType NoteProperty -Name KBArticle -Value ($Update.split("(")[1]).split(")")[0].Trim()  
           #Add description of KB article to object  
           $Description = $Update.split("(")[0]  
           $Description = $Description -replace (",", " ")  
           $Object | Add-Member -MemberType NoteProperty -Name Description -Value $Description  
           #Add the object to the array  
           $UpdateArray += $Object  
      }  
      If ($UpdateArray -ne $null) {  
           $UpdateArray = $UpdateArray | Sort-Object -Property KBArticle  
           #Define file to write the report to  
           $OutputFile = $RelativePath + $OutputFile  
           $UpdateArray | Export-Csv -Path $OutputFile -NoTypeInformation -NoClobber  
      }  
      Return $UpdateArray  
 }  
   
 Clear-Host  
 #Delete the old report file  
 Remove-OutputFile  
 #Get list of installed updates  
 Get-Updates  
 If ($Email.IsPresent) {  
      $RelativePath = Get-RelativePath  
      $Attachment = $RelativePath + $OutputFile  
      #Email Updates  
      Send-MailMessage -From $From -To $To -Subject $Subject -Body $Body -SmtpServer $SMTPServer -Attachments $Attachment  
 }