Get-WannaCryPatchStatus.ps1

<#PSScriptInfo
.VERSION 1.3
.AUTHOR mike@riston.me
.RELEASENOTES Fixed logic error and added more information in help section.
.DESCRIPTION
Bulk of the script from :
      <<<<<<<<<<<<
      Version 1.08.00 @KieranWalsh May 2017
      Computer Talk LTD
         
      Thanks to https://github.com/TLaborde, and https://www.facebook.com/BlackV for notifying me about missing patches.
      >>>>>>>>>>>>
      Infinite thanks to @KieranWalsh for the huge help and all of the hard work.
       
#>

function Get-WannaCryPatchStatus
{
  <#
      .SYNOPSIS
      Queries computers in your domain for the status of their protection state against WCry
      .DESCRIPTION
      Bulk of the script from :
      <<<<<<<<<<<<
      Version 1.08.00 @KieranWalsh May 2017
      Computer Talk LTD
         
      Thanks to https://github.com/TLaborde, and https://www.facebook.com/BlackV for notifying me about missing patches.
      >>>>>>>>>>>>
      Infinite thanks to @KieranWalsh for the huge help and all of the hard work.
      .PARAMETER patch_location_5
      Path to the Windows XP .EXE update file you wish to invoke on remote computer.
      .PARAMETER patch_location_6
      Path to the Windows 7 .MSU update file you wish to invoke on remote computer.
      .PARAMETER patch_location_10
      Path to the Windows 10 .MSU update file you wish to invoke on remote computer.
      .EXAMPLE
      Get-WannaCryPatchStatus -patch_location_5 '\\server\wcry_xp_patch.exe' -patch_location_6 '\\server\wcry_win7_patch.msu' -patch_location_7 '\\server\wcry_win10_patch.msu'
      .EXAMPLE
      Get-WannaCryPatchStatus -patch_location_5 '\\server\wcry_xp_patch.exe' -patch_location_6 '\\server\wcry_win7_patch.msu' -patch_location_7 '\\server\wcry_win10_patch.msu' -ForceFullDomainScan
      Ignores the Offline Computers log from previous scan(s) and forces a full domain scan
  #>

  [CmdletBinding()]
  param
  (
    [Parameter(Mandatory = $true, Position = 0, HelpMessage = 'Path to the Windows XP .EXE update file you wish to invoke on remote computer.')]
    [System.String]
    $patch_location_5,
    
    [Parameter(Mandatory = $true, Position = 1, HelpMessage = 'Path to the Windows 7 .MSU update file you wish to invoke on remote computer.')]
    [System.String]
    $patch_location_6,
    
    [Parameter(Mandatory = $true, Position = 2, HelpMessage = 'Path to the Windows 10 .MSU update file you wish to invoke on remote computer.')]
    [System.String]
    $patch_location_10,
    
    [Parameter(Mandatory = $false, Position = 3)]
    [Switch]
    $ForceFullDomainScan = $false
  )
  
  $OffComputers = @()
  $CheckFail = @()
  $Patched = @()
  $Unpatched = @()
  
  
  $log = Join-Path -Path ([Environment]::GetFolderPath('MyDocuments')) -ChildPath "WannaCry patch state for $($ENV:USERDOMAIN).log"
  $offlinelog = Join-Path -Path ([Environment]::GetFolderPath('MyDocuments')) -ChildPath "WannaCry patch state for $($ENV:USERDOMAIN)-OfflineComputers.log"
  
  $Patches = @('KB4012212', 'KB4012213', 'KB4012214', 'KB4012215', 'KB4012216', 'KB4012217', 'KB4012598', 'KB4013429', 'KB4015217', 'KB4015438', 'KB4015549', 'KB4015550', 'KB4015551', 'KB4015552', 'KB4015553', 'KB4016635', 'KB4019215', 'KB4019216', 'KB4019264', 'KB4019472')
  
  $WindowsComputers = (Get-ADComputer -Filter {
      (OperatingSystem  -Like 'Windows*') -and (OperatingSystem -notlike '*Windows 10*') -and (Enabled -eq $true)
  }).Name|
  Sort-Object
  
  if (Test-Path $log) 
  {
    #Log Exists, move.
    Move-Item -Path $log -Destination "$($log)_old_$(Get-Date -Format yyyy-MM-dd.HH.mm.ss).log"
  }
  if (Test-Path $offlinelog) 
  {
    #Offline Files log exists. Run against this instead of AD to save time.
    #Disable this line to run another 'full scan' of your domain.
    if (!$ForceFullDomainScan) 
    {
      $WindowsComputers = Get-Content $offlinelog
    }

    #Log Exists, move.
    Move-Item -Path $offlinelog -Destination "$($offlinelog)_old_$(Get-Date -Format yyyy-MM-dd.HH.mm.ss).log"

  }
  
  "WannaCry patch status $(Get-Date -Format 'yyyy-MM-dd HH:mm')`tComputerName`tPatchStatus`tLogFile" |Out-File -FilePath $log
  
  $ComputerCount = $WindowsComputers.count
  "There are $ComputerCount computers to check"
  $loop = 0
  foreach($Computer in $WindowsComputers)
  {
    $ThisComputerPatches = @()
    $loop ++
    "$loop of $ComputerCount `t$Computer"
    try
    {
      $null = Test-Connection -ComputerName $Computer -Count 1 -ErrorAction Stop
      try
      {
        $Hotfixes = Get-HotFix -ComputerName $Computer -ErrorAction Stop
        
        $Patches | ForEach-Object -Process {
          if($Hotfixes.HotFixID -contains $_)
          {
            $ThisComputerPatches += $_
          }
        }
      }
      catch
      {
        $CheckFail += $Computer
        "***`t$Computer `tUnable to gather hotfix information" |Out-File -FilePath $log -Append
        continue
      }
      If($ThisComputerPatches)
      {
        "`t$Computer is patched with $($ThisComputerPatches -join (','))" |Out-File -FilePath $log -Append
        $Patched += $Computer
      }
      Else
      {
        $Unpatched += $Computer
        "*****`t$Computer IS UNPATCHED! *****`tInvoking Patch Installation. Log will be created if not-XP`t\\$Computer\c$\scripts\wcry_patch_install.evtx" |Out-File -FilePath $log -Append
        
        $version = (Get-WmiObject -ComputerName $Computer -Class Win32_OperatingSystem).Version
        New-Item \\$computer\c$\scripts -ItemType Directory -Force

        
        if ($version -like '5*')  
        {
          $FileToRun = $patch_location_5 
          $KB = '4012598'
          
          Copy-Item $FileToRun -Destination "\\$Computer\c$\scripts\wcry_patch_KB4012598.exe" #-Verbose
          #read-host go line 68?
            PsExec.exe -s \\$Computer cmd /c 'C:\scripts\wcry_patch_KB4012598.exe' /quiet
        }
        if ($version -like '6*')  
        {
          $FileToRun = $patch_location_6           
          $KB = '4012212'
          
          Copy-Item $FileToRun -Destination "\\$Computer\c$\scripts\Win7_KB4012212.msu" #-Verbose
          #read-host go line 77?
            PsExec.exe -s \\$Computer cmd /c wusa.exe 'C:\scripts\Win7_KB4012212.msu' /quiet /norestart /log:"C:\scripts\wcry_patch_install.evtx"
        }
        if ($version -like '10*') 
        {
          $FileToRun = $patch_location_10    
          $KB = '4013429'
          
          Copy-Item $FileToRun -Destination "\\$Computer\c$\scripts\Win10_KB4013429.msu" #-Verbose
          #read-host go line 87?
            PsExec.exe -s \\$Computer cmd /c wusa.exe 'C:\scripts\Win10_KB4013429.msu' /quiet /norestart /log:"C:\scripts\wcry_patch_install.evtx"
        }
      }
    }
    catch
    {
      $OffComputers += $Computer
      "****`t$Computer `tUnable to connect." |Out-File -FilePath $log -Append
      $Computer | Out-File -FilePath $offlinelog -Append
    }
  }
  ' '
  "Summary for domain: $ENV:USERDNSDOMAIN"
  "Unpatched ($($Unpatched.count)):" |Out-File -FilePath $log -Append
  $Unpatched -join (', ')  |Out-File -FilePath $log -Append
  '' |Out-File -FilePath $log -Append
  "Patched ($($Patched.count)):" |Out-File -FilePath $log -Append
  $Patched -join (', ') |Out-File -FilePath $log -Append
  '' |Out-File -FilePath $log -Append
  "Off/Untested($(($OffComputers + $CheckFail).count)):"|Out-File -FilePath $log -Append
  ($OffComputers + $CheckFail | Sort-Object)-join (', ')|Out-File -FilePath $log -Append
  
  "Of the $($WindowsComputers.count) windows computers in active directory, $($OffComputers.count) were off, $($CheckFail.count) couldn't be checked, $($Unpatched.count) were unpatched and $($Patched.count) were successfully patched."
  'Full details in the log file.'
  
  try
  {
    Start-Process -FilePath notepad++ -ArgumentList $log
  }
  catch
  {
    Start-Process -FilePath notepad.exe -ArgumentList $log
  }
}