Monday, August 10, 2015

Powershell Script - Push-Patch - remote installation for KB hotfix, MSI, MSU, or EXE files

Here's a wickedly useful function I threw together based on a strong need to push emergency patches to thousands of servers without the standard route of using a patching deployment system like SCCM, WSUS, Altiris, or HPSA.  All of these tools have their strengths and are generally considered superior to just manually pushing patches, but the sheer versatility of ease of being able to quickly deploy patches is what I required to make tight patching deadlines.

I recommend using this function with Get-HotFix, or Get-WmiObject -class Win32_product for a list of installed patches/software to determine if installation is needed and/or was successful.  You can also come in after the push and look at the log files on the servers in an automated fashion to see how things went.

This script does require PSRemoting to be enabled on target servers, which is true by default on all Windows Server 2012 R2 installs, but needs to be turned on either by GPO or by hand on previous versions.  There's a built in workaround if you pull down PSExec which will work on older servers without PSRemoting.

#Requires -Version 3.0
Function Push-Patch{
<#
.SYNOPSIS
Deploys KB hotfix patch, MSI setup file, or an install-shield based program to remote servers
.DESCRIPTION
There's no "smart" installer type detection, it relies on the file naming convention.
Only works with single file installers. Automatically creates install log file in destination temp.
Requires all destination servers have PSRemoting enabled, or PSExec is installed locally and specified.
(https://technet.microsoft.com/en-us/sysinternals/bb897553.aspx)
.PARAMETER ComputerName
Specifies one or more server names to install to.
.PARAMETER PatchPath
Full local or UNC path to the file to be pushed out.
.PARAMETER NoRestart
Prevents installer from automatically rebooting servers.
.PARAMETER ForceRestart
Forces servers to restart even if it's not required.
.PARAMETER DestinationTemp
Temporary directory used on target servers for staging patch file.
.PARAMETER UsePSExec
Used as a last resort; it's better to have PSRemoting enabled on your target servers as PSexec is much slower (10-100X).
.EXAMPLE
Get-Content .\serverlist.txt | Push-Patch -PatchPath "\\server1\share\KB999999.msi" -NoRestart
Pushes the KB hotfix to all the servers on the serverlist.txt and remotely runs them using MSIEXEC and will prevent automatic restarts
.EXAMPLE
Push-Patch -ComputerName ServerA,ServerB -PatchPath "c:\softwarerepo\AdobeReader\Fullsetup.exe" -ForceRestart
Pushes the adobe reader setup to two servers and runs it and forces a reboot after installation is completed.
.LINK
http://www.bryanvine.com/2015/08/powershell-script-push-patch-remote.html
.LINK
Invoke-Command
.NOTES
Author: Bryan Vine
Last updated: 8/10/2015
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,valuefrompipeline=$true,Position=0)]
[alias("CN","MachineName","ServerName","Server")][ValidateNotNullOrEmpty()][string[]]
$ComputerName,
[Parameter(Mandatory=$true,Position=1)][ValidateNotNullOrEmpty()]
[ValidateScript({Test-Path $_})][string]
$PatchPath,
[switch]$NoRestart,
[switch]$ForceRestart,
[String]$DestinationTemp = "C:\TEMP",
[switch]$UsePSExec
)
BEGIN{
if($NoRestart){$NoRestartstr="/norestart"}
if($ForceRestart){$ForceRestartstr="/forcerestart"}
$file = dir $PatchPath | select -ExpandProperty Name
$logfile = "$destinationTemp\$file-install.log"
$AllComputers = @()
#building wrapper for remote exection
$runcommand = @()
if($file -like "*.exe"){
if($ForceRestart){$ForceRestartstr="/forcerestart /forceappsclose"}
$runcommand += "$file /quiet /log:$logfile $NoRestartstr $ForceRestartstr"
}
if($file -like "*.msu"){
if($ForceRestart){$ForceRestartstr="/forcerestart"}
$runcommand += "wusa $file /quiet /log:$logfile $NoRestartstr $ForceRestartstr"
}
if($file -like "*.msi"){
if($ForceRestart){$ForceRestartstr="/forcerestart"}
$runcommand += "msiexec /i $file /qn /quiet /log $logfile $NoRestartstr $ForceRestartstr"
}
}
PROCESS{
#Stage files
$ComputerName | %{
if(test-path "\\$_\c$"){
if(!(test-path "\\$_\$($DestinationTemp.replace(":","$"))")){
New-Item -Path "\\$_\$($DestinationTemp.replace(":","$"))" -ItemType directory -Force
}
Write-Verbose "Copying Patch to $_"
Copy-Item -Path $PatchPath -Destination "\\$_\$($DestinationTemp.replace(":","$"))"
#writing remote wrapper.
"$($DestinationTemp.Substring(0,2)) & cd $DestinationTemp & $runcommand" |
out-file -Encoding ascii -FilePath "\\$_\$($DestinationTemp.replace(":","$"))\runpatch.cmd"
$AllComputers += $_
}
else{
Write-Warning "Unable to access $_"
}
}
}
END{
#Remotely executing patch
if($UsePSExec){
$serverlist = "$env:TEMP\serverlist.txt"
$AllComputers | Set-Content $serverlist
psexec `@$serverlist -accepteula -e -s -h -d -n 20 "$DestinationTemp\runpatch.cmd"
Remove-Item $serverlist
}
else{
Invoke-Command -ComputerName $AllComputers -ThrottleLimit 500 -AsJob -ScriptBlock ([scriptblock]::Create("$DestinationTemp\runpatch.cmd")) |
Receive-Job -AutoRemoveJob -Wait
}
#cleanup
$AllComputers | %{
Remove-Item "\\$_\$($DestinationTemp.replace(":","$"))\runpatch.cmd"
Remove-Item "\\$_\$($DestinationTemp.replace(":","$"))\$file"
}
}
}

3 comments:

  1. Wonderful script Bryan.
    Thanks a lot for this.
    However I am getting the following error, could you please advice?
    Windows update could not be installed because of error 2147942405 "Access is denied."

    ReplyDelete
  2. Make sure the account you are running as is also a local administrator on the remote server. Also look at the log file created in the default temp location on the remote server, it should have more details. You might need to tweak the install parameters for certain patches.

    ReplyDelete
  3. Thanks Bryan for the quick response.
    Yes, the account is a local admin on the target machine.
    Regarding the generated log file, unable to read it using notepad as it is showing as follows.

    ø ø ø ! À¬ t D ßf
    “ €% ‡Í c ÕÐ Zb (
    @ t z r e s . d l l , - 2 6 2
    @ t z r e s . d l l , - 2 6 1 Äÿÿÿ ` êAÆÒÐ 6¾& žiÈb ÕÐ W U S A 5 9 5 6 C : \ T E M P \ W i n d o w s 8 . 1 - K B 2 9 8 8 9 4 8 - x 6 4 . m s u - i n s t a l l . l o g ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ

    ReplyDelete