Get Last Patch Date Remotely Using PowerShell

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.

Get last patch date using PowerShell

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:

Get last reboot time using PowerShell


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.

getLastPatchDates.zip

Happy patching!

34 thoughts on “Get Last Patch Date Remotely Using PowerShell

  1. 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.

  2. 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.

  3. In PowerShell, can you run and post the results of “get-process powershell”?

  4. 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.

  5. 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. 🙂

  6. 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.

  7. Your work is great and the script worked like charm 🙂 Thank you very very much..Great Work.
    Cheers!!

  8. Hi ITomation,
    Is getLastPatchDates.zip is updated version which you replied to Akshay.
    I am facing the same problem from getLastPatchDates.zip

  9. Hi Prajesh, yes, it is the latest version. Feel free to send a screenshot of what you are seeing. Thanks!

  10. 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

  11. 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!

  12. 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.

  13. 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.

  14. 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

  15. 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

  16. Hello Vivek, feel free to send me a private message via Skype. Nickname is ITOMATION.

  17. 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.

  18. This is great but the only output I’m getting is the headers…
    server,lastpatch,lastreboot,rpcOK
    Has this been resolved yet?

  19. 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

  20. 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.

  21. any updates on this my bulk script from the zip file full report only gives headers

  22. 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

  23. 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)

  24. 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.

  25. 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
    }

  26. 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.

  27. 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

  28. Need computer name along with the Last patched dated in output

  29. Yes when run the bulk servers script Below is the output of fullreport text file. Please suggest
    server,lastpatch,lastreboot,rpcOK
    R,,,False

  30. 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

  31. 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…”.

Leave a Reply

Your email address will not be published. Required fields are marked *