Quick Code Summary
Get Last Patch Date Remotely Using PowerShell
Get-WmiObject -ComputerName "localhost" Win32_Quickfixengineering | select `
@{Name="InstalledOn";Expression={$_.InstalledOn -as [datetime]}} | Sort-Object -Property `
Installedon | select-object -property installedon -last 1
Sometimes you may need to know a server’s last patch date. There can be many reasons why. One of which is when you are patching servers and want to get a general idea of whether or not the patching actually took place on each server.
In this example we are are going to use a WMI query to get last patch date remotely using PowerShell. Let’s dive in…
Last Patch Date
Getting the last patch date only requires two lines of code. The first line of code queries WMI for the last InstalledOn value in Win32_QuickFixEngineering.
$lastpatch = Get-WmiObject -ComputerName "localhost" Win32_Quickfixengineering | select @{Name="InstalledOn";Expression={$_.InstalledOn -as [datetime]}} | Sort-Object -Property Installedon | select-object -property installedon -last 1
And, you guessed it, the second line of code converts this value to a preferred date format, yyyy-MM-dd. We choose this format for a reason none other than it sorts well in Excel.
Get-Date $lastpatch.InstalledOn -format yyyy-MM-dd
And to illustrate.
Another handy piece of information related to patching is last boot up time. If a server had just rebooted during patch night, more likely than not, it had already completed some kind of patching activity. Here is how to retrieve last boot up time using PowerShell and good old WMI.
Last Reboot Time
Last reboot time also requires only two lines of code. One, a WMI query of LastBootUpTime in Win32_OperatingSystem.
$lastboot = Get-WmiObject -ComputerName "localhost" win32_operatingsystem | select @{Name="LastBootUpTime";Expression={$_.ConverttoDateTime($_.lastbootuptime)}} | Select-Object -Property lastbootuptime
And, two, a date format conversion.
Get-Date $lastboot.lastbootuptime -Format "yyyy-MM-dd hh:mm:ss tt"
And to illustrate:
Bulk Report
Lastly, here is a quick and dirty script which takes, as input, a list of servers, and outputs last patch date and last reboot. The script requires PowerShell 3.0 which can be downloaded from the Microsoft Download Center. Please review and test the script before running it in a production environment.
Happy patching!
34 thoughts on “Get Last Patch Date Remotely Using PowerShell”
Your work is awesome sir….but when running bulk script the script stuck at groups attaching output. your help is very appreciated 🙂
Error screenshot
http://uploads.im/OtyrT.png
Hello Akshay, and thank you for your comment.
I am unable to reproduce the issue on my end. I suspect the “_worker.ps1” process is picking up other PowerShell instances for reporting on the completion status. Try exiting out of all PowerShell processes on the machine, and then run the script (unmodified) via PowerShell ISE or any other PowerShell “editor” just not directly from PowerShell? You did discover a bug I hadn’t realized was present and that is, when you run it from PowerShell command line, a PowerShell.exe process starts and then the script fails the process check. I’ll be making an amendment to fix this bug. Thank you and let me know if you end up figuring it out.
Thank you so much for replying to my query , i run the script from visual studio code without changing any thing but the script stuck at group 5 , i am only scanning one server and it stuck after running 2+ hours 🙁 :(.
I want to scan almost 10000 server and want to export its last patch date and last reboot/uptime date in excel/csv.
Error code > http://uploads.im/9iJG6.png
Please help sir.
In PowerShell, can you run and post the results of “get-process powershell”?
PS C:\Users\admakshay.kumar\Desktop\patch> get-process powershell
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
——- —— —– —– —– —— — ———–
610 33 218124 218000 772 4.15 20712 powershell
1308 153 189700 189824 731 2,389.34 38252 powershell
1065 110 313684 360692 3040 179.98 40340 powershell
625 36 100700 96632 655 4.66 57792 powershell
646 55 114984 13624 737 16.04 60928 powershell
589 35 83344 92336 651 6.88 71644 powershell
700 76 177084 212560 847 13.31 95524 powershell
Please note that i run your script for jump host where multiple sessions can work.
The script executes in multiple PowerShell processes to speed things up. Each process will query a sub-list of servers concurrently with other processes. However, one limitation with this is that the “tracking” of the progress will fail when you have other unrelated PowerShell processes running. Therefore, you need to end all other processes before running the script in the editor. Only then will the script properly execute from start to finish.
I’ll take note of this and work on an improved version of the script. Perhaps I can take inventory of existing processes and ignore those. For now, I believe your workaround is to end the other PowerShell sessions. Or you can modify the script to get rid of concurrent processing (around line 60 onward in getLastPatchDates.ps1). But without concurrent processing, querying 10,000 servers sequentially will take a really long time. One last point, if you’re querying 10,000 servers, you probably want to change $intDiv (line 61) from 5 to 200 or so. This way you won’t get too many concurrent processes.
I hope this helps. 🙂
Hi Akshay – I have uploaded a new version to fix the bug you are reporting. Thanks very much for the feedback and let me know how it goes.
Your work is great and the script worked like charm 🙂 Thank you very very much..Great Work.
Cheers!!
Hi ITomation,
Is getLastPatchDates.zip is updated version which you replied to Akshay.
I am facing the same problem from getLastPatchDates.zip
Hi Prajesh, yes, it is the latest version. Feel free to send a screenshot of what you are seeing. Thanks!
Hi ITomation,
I Tried the script in a test server, 2 line code gives me future date of patch installed like 08/10/2017 and the bulk servers script not giving the output as expected.
under fullreport.txt could see below content
server,lastpatch,lastreboot,rpcOK
l,,,False
Hi Rajesh,
When did you run it? It is now October and August was 2 months ago. If there is, in fact, a timestamp difference then it must somehow be related to the OS reporting that date. We’re getting the value right out of Win32_Quickfixengineering. One option to look at is the timezones. As an alternative, you can try running the command without the “-last 1” switch. This will list all patch install dates.
As far as the lack of data in your report – perhaps the account you are using to run the PowerShell query does not have proper permissions on the server? Or the server is somehow blocking the remote query.
Good luck!
Does this script work for PC’s as well? Every time I run it, it says all the PC’s are offline which is not the case.
Hi Sam John,
Yes, you should be able to run the script from a PC. It utilizes the “Test-Connection” command to determine whether a machine is online.
For example –> Test-Connection -ComputerName ServerToCheck -count 1
To troubleshoot, you can try running the Test-Connection command from your PC against one of your servers to see if it’s failing to make a connection to the server.
Hi Buddy,
I executed this script on one Windows 7 machine which is other than mine but giving strange result. Below is the output of fullreport text file. Please suggest
server,lastpatch,lastreboot,rpcOK
R,,,False
Even trying to run the script with Localhost in _computers input file giving result as server,lastpatch,lastreboot,rpcOK
l,,,False
please help to resolve the issue.
My laptop has Windows 7 professional and power shell 3.0
Hello Vivek, feel free to send me a private message via Skype. Nickname is ITOMATION.
I have run the same command on one of the windows server 2008 R2 server but many of update installation date are missing in output.
This is great but the only output I’m getting is the headers…
server,lastpatch,lastreboot,rpcOK
Has this been resolved yet?
Hi Ric – I haven’t had much time to look at this. It could be a number of things:
permissions, the PowerShell environment, server OS, firewall, etc..
When you run the single line command against your servers, do you get any output? That is, Get-WmiObject -ComputerName “COMPUTERNAME” Win32_Quickfixengineering | select @{Name=”InstalledOn”;Expression={$_.InstalledOn -as [datetime]}} | Sort-Object -Property Installedon | select-object -property installedon -last 1
Hi. Yes, the single command works. I ended up running the single command on each of my devices to get the info. I did notice (as it was running) that the script would not produce any individual .txt files…so there was nothing to append to fullreport.txt.
any updates on this my bulk script from the zip file full report only gives headers
I am having the same Ric when i run section by section i never see a individual.txt file for it to push to the fullreport.txt file
Excellent Thanks
Dom
Hello,
I ran the script and got discrepancies between the “last date patched” from the powershell and the “last date patched” from Windows updates… Any idea?
PS C:\Users\xxxxxx> Get-WmiObject -ComputerName “VSPBEDMSTR9″ Win32_Quickfixengineering | select @{Name=”InstalledOn”;Expression={$_.InstalledOn -as [datetime]}} | Sort-Object -Property Installedon | select-object -property installedon -last 1
InstalledOn
———–
4/30/2019 12:00:00 AM
but Windows updates is showing the KB890830 installed on 09/19/2019!!!
Thanks,
Dom(if I found the way to attach a screenshot I will send you the image from Windows Update)
https://1drv.ms/u/s!Au4n_Hp3KX3ktz9BxcGpRqouUVTc?e=Pl1l2g
Hi Dom,
Nice find. I have never come across or at least noticed such discrepancy. Does get-hotfix return the correct date for your server?
(Get-HotFix -ComputerName “name” | Sort-Object -Property InstalledOn | Select-Object -last 1).InstalledOn
It is possible that those last two updates (i.e. Windows Malicious Software Removal Tool and 2019-07 Servicing Stack Update for Windows Server 2016) are not part of Win32_QuickFixEngineering. See https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-quickfixengineering. It also goes on to say ‘output might vary on different operating systems’. This is certainly something worth looking into.
I like the script, but I have a question.
In the _worker.ps1 file your establish $date0 as today and $date1 as tomorrow?, then test the $installedOn date against those to place a candidate in the patched.txt or not-patched.txt.
Is this to catch patching over a midnight crossing?
It just seems a little odd. If my patching is scheduled to run Sunday and I run this script on Monday, for example, everything seems to be listed as not-patched.
#establish dates
$date0 = get-date -format “yyyy-MM-dd”
$date1 = get-date (get-date).adddays(+1) -format “yyyy-MM-dd”
[…]
#list results in separate files
if (($installedon -eq $date0) -or ($installedon -eq $date1))
{
#add patched server to the successfully patched list
write-output “$_” | out-file -filepath “$path\patched.txt” -append
}
else
{
#add none-patched server to the no yet patched list
write-output “$_” | out-file -filepath “$path\not-patched.txt” -append
}
Hi Paul,
Both date0 and date1 patches are put into the same file (patched.txt). But you are correct, the purpose of date1 is to account for the “midnight crossing”. The issue you you are seeing may be related to what Dom brought up where the patches are not in Win32_QuickFixEngineering? Or maybe something else altogether? I have not looked deeper into this yet though. Either way, I do not think the issue relates to date1 patches. In your case all patches “should” be date1 patches and added to patched.txt.
The script:
Get-WmiObject -ComputerName “VSPBEDMSTR9″ Win32_Quickfixengineering | select @{Name=”InstalledOn”;Expression={$_.InstalledOn -as [datetime]}} | Sort-Object -Property Installedon | select-object -property installedon -last 1
Works great in my environment. Thanks! Is there a way to also include the time?
-Adam
Need computer name along with the Last patched dated in output
Yes when run the bulk servers script Below is the output of fullreport text file. Please suggest
server,lastpatch,lastreboot,rpcOK
R,,,False
For some reason, a lot of these scripts I’m finding online state that random servers are “offline” when they’re not. I’m not sure why that is but this is a pretty decent script nonetheless
Hi Mike.
What establishes online vs. offline is “Test-Connection -computername $hname -count 1 -quiet -ErrorAction SilentlyContinue”. There are a couple of potential issues with testing this way: (1) servers whose firewall is configured to ignore/reject ping requests will not respond to this Test-Connection attempt and the server will show offline and (2) since we are doing a count of 1 for efficiency (i.e. we are sending a single ping request and moving on without retrying), in certain network environments you will find this is unreliable and may need to bump it up from 1 to, say, 4 tries –> “Test-Connection -computername $hname -count 4 -quiet -erroraction silentlycontinue…”.