# encoding: ascii
# api: powershell
# title: Install-Patches
# description: This module uses PSexec,vbscript and PowerShell to install patches on local or remote systems. This does require PSexec to be in the same directory as where you are running the function within this module. Save as a .psm1 file.
# version: 0.1
# type: module
# author: Boe Prox  
# license: CC0
# function: Create-UpdateVBS
# x-poshcode-id: 2689
# x-archived: 2011-05-27T22:43:50
# x-published: 2011-05-20T20:29:00
#
#
<#
Save this with a .PSM1 extension and use Import-Module to properly load the functions.
ie: Install-Patches.psm1
Import-Module Install-Patches.psm1
#>
#Validate user is an Administrator
Write-Verbose "Checking Administrator credentials"
If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(`
    [Security.Principal.WindowsBuiltInRole] "Administrator"))
{
    Write-Warning "You are not running this as an Administrator!`nPlease re-run this with an Administrator account."
    Break
}

Function Create-UpdateVBS {
Param ($computername)
#Create Here-String of vbscode to create file on remote system
$vbsstring = @"
ON ERROR RESUME NEXT
CONST ForAppending = 8
CONST ForWriting = 2
CONST ForReading = 1
strlocalhost = "."
Set oShell = CreateObject("WScript.Shell") 
set ofso = createobject("scripting.filesystemobject")
Set updateSession = CreateObject("Microsoft.Update.Session")
Set updateSearcher = updateSession.CreateupdateSearcher()
Set updatesToInstall = CreateObject("Microsoft.Update.UpdateColl")
Set searchResult = updateSearcher.Search("IsInstalled=0 and Type='Software'")
Set objWMI = GetObject("winmgmts:\\" & strlocalhost & "\root\CIMV2")
set colitems = objWMI.ExecQuery("SELECT Name FROM Win32_ComputerSystem")
	For Each objcol in colitems
		strcomputer = objcol.Name
	Next
set objtextfile = ofso.createtextfile("C:\" & strcomputer & "_patchlog.csv", True)
objtextfile.writeline "Computer,Title,KB,InstallResultCode"
If searchresult.updates.count = 0 Then
	Wscript.echo "No updates to install."
	objtextfile.writeline strcomputer & ",NA,NA,NA"
	Wscript.Quit
Else
For I = 0 To searchResult.Updates.Count-1
    set update = searchResult.Updates.Item(I)
	    If update.IsDownloaded = true Then
	       updatesToInstall.Add(update)	
	End If
Next
End If
err.clear
Wscript.Echo "Installing Updates"
Set installer = updateSession.CreateUpdateInstaller()
installer.Updates = updatesToInstall
Set installationResult = installer.Install()
	If err.number <> 0 Then
		objtextfile.writeline strcomputer & "," & update.Title & ",NA," & err.number
	Else		
		For I = 0 to updatesToInstall.Count - 1
		objtextfile.writeline strcomputer & "," & updatesToInstall.Item(i).Title & ",NA," & installationResult.GetUpdateResult(i).ResultCode 
		Next
	End If
Wscript.Quit
"@

Write-Verbose "Creating vbscript file on $computer"
Try {
    $vbsstring | Out-File "\\$computername\c$\update.vbs" -Force
    Return $True
    }
Catch {
    Write-Warning "Unable to create update.vbs!"
    Return $False
    }
}


Function Format-InstallPatchLog {
    [cmdletbinding()]
    param ($computername)
    
    #Create empty collection
    $installreport = @()
    #Check for logfile
    If (Test-Path "\\$computername\c$\$($computername)_patchlog.csv") {
        #Retrieve the logfile from remote server
        [array]$CSVreport = Import-Csv "\\$computername\c$\$($computername)_patchlog.csv"
        If ($csvreport[0].title -ne "NA") {
            #Iterate through all items in patchlog
            ForEach ($log in $CSVreport) {
                $temp = "" | Select Computer,Title,KB,InstallResultCode
                $temp.Computer = $log.Computer
                $temp.Title = $log.title.split('\(')[0]
                $temp.KB = ($log.title.split('\(')[1]).split('\)')[0]
                Switch ($log.InstallResultCode) {
                    1 {$temp.InstallResultCode = "No Reboot required"}
                    2 {$temp.InstallResultCode = "Reboot Required"}
                    4 {$temp.InstallResultCode = "Failed to Install Patch"}
                    "-2145124316" {$temp.InstallResultCode = "Update is not available to install"}
                    Default {$temp.InstallResultCode = "Unable to determine Result Code"}            
                    }
                $installreport += $temp
                }
            }
        Else {
            $temp = "" | Select Computer, Title, KB,InstallResultCode
            $temp.Computer = $computername
            $temp.Title = "NA"
            $temp.KB = "NA"
            $temp.InstallResultCode = "NA"  
            $installreport += $temp            
            }
        }
    Else {
        $temp = "" | Select Computer, Title, KB,InstallResultCode
        $temp.Computer = $computername
        $temp.Title = "NA"
        $temp.KB = "NA"
        $temp.InstallResultCode = "NA"  
        $installreport += $temp      
        }
    Write-Output $installreport
}

Function Install-Patches {
<#    
.SYNOPSIS    
    Install patches on a local or remote computer and generates a report.
.DESCRIPTION  
    Install patches on a local or remote computer and generates a report with status of installation.
.PARAMETER Computername  
    Name of the computer to install patches on.           
.NOTES    
    Name: Install-Patches 
    Author: Boe Prox  
    DateCreated: 19May2011   
        
.LINK    
    https://boeprox.wordpress.com  
.EXAMPLE    
    Install-Patches -Computername Server1
    
    Description
    -----------
    Installs patches on Server1 and displays report with installation status.

.EXAMPLE    
    Install-Patches -Computername Server1,Server2,Server3
    
    Description
    -----------
    Installs patches on Server1,Server2 and Server3 and displays report with installation status.    
#>
[cmdletbinding()]
Param(
    [Parameter(Mandatory = $True,Position = 0,ValueFromPipeline = $True)]  
    [string[]]$Computername
    )
Begin {
    If (-Not (Test-Path psexec.exe)) {
        Write-Warning "PSExec not in same directory as script!"  
        Break
        }
    }
Process {
    ForEach ($computer in $computername) {
        If ((Test-Connection -ComputerName $computer -Count 1 -Quiet)) {
            Write-Verbose "Creating update.vbs file on remote server."
            If (Create-UpdateVBS -computer $computer) {
                Write-Verbose "Patching computer: $($computer)"
                .\psexec.exe -accepteula -s -i \\$computer cscript.exe C:\update.vbs
                If ($LASTEXITCODE -eq 0) {
                    Write-Verbose "Successful run of install script!"
                    Write-Verbose "Formatting log file and adding to report"
                    Format-InstallPatchLog -computer $computer
                    }            
                Else {
                    Write-Warning "Unsuccessful run of install script!"
                    $report = "" | Select Computer,Title,KB,IsDownloaded,InstallResultCode
                    $report.Computer = $computer
                    $report.Title = "ERROR"
                    $report.KB = "ERROR"
                    $report.IsDownloaded = "ERROR"
                    $report.InstallResultCode = "ERROR" 
                    Write-Output $report
                    }
                }
            Else {
                Write-Warning "Unable to install patches on $computer"
                $report = "" | Select Computer,Title,KB,IsDownloaded,InstallResultCode
                $report.Computer = $computer
                $report.Title = "ERROR"
                $report.KB = "ERROR"
                $report.IsDownloaded = "ERROR"
                $report.InstallResultCode = "ERROR" 
                Write-Output $report              
                }
            }
        Else {
            Write-Warning "$($Computer): Unable to connect!"
            } 
        } 
    }   
}
Export-ModuleMember Install-Patches
