In our organization, we have SCCM for manual update deployment. I have a lot of servers I need to patch, on some regular basis.
Most of my servers reside on VmWare infrastructure, and before every update I need to create a snapshot of each and every one.
With the help of Powershell scripts and VmWare PowerCli I managed to create snapshot for my list of servers, patch servers with Function Install-SCCMPatchesAvailable, restart if necessary, and then check services with Automatic startup but not Started status. I separated Creation and Deletion of snaphots, patching and service checking, because It was easier for now. Script will be upgraded, maybe transfered to MS System Center Orchestrator or some other automatization software, but for now her it is.
# PARAMETERS
$Enviroment = "TEST"
$SnapCD = "D" # "C" "D" (Create or delete snapshots)
$patching = "NO" # "NO" "YES"
$serviceChecking = "YES" #"YES" "NO"
$pViServer = "vcenter.company.com"
$email = "someone@company.com"
Set-Location -Path "$PSScriptRoot"
#GetCurrent Directory of a script - $PSScriptRoot
switch ($Enviroment) {
"TEST" {
$ppath = "$PSScriptRoot\VM_Servers_TEST.txt"
}
"PROD" {
$ppath = "$PSScriptRoot\VM_Servers_Prod.txt"
}
}
$cred = Get-Credential
$ServerList = Get-Content -path $ppath
$Date = Get-Date -Format "yyyyMMdd"
$Description = "Powershell created Snapshot"
$excludeServices = @("BITS", "CDPSvc", "DoSvc", "gupdate", "MapsBroker", "RemoteRegistry", "RtkAudioService", "sppsvc", "WbioSrvc", "UALSVC") #some services don't need to be checked
$from = "Patching@company.com"
$mailsrv = "mail.company.com"
$mailencoding = ([System.Text.Encoding]::UTF8)
$HTML = @"
<style>
BODY{background-color :#FFFFF}
TABLE{Border-width:thin;border-style: solid;border-color:Black;border-collapse: collapse;}
TH{border-width: 1px;padding: 1px;border-style: solid;border-color: black;background-color: ThreeDShadow}
TD{border-width: 1px;padding: 0px;border-style: solid;border-color: black;background-color: Transparent}
H2{color: #457dcf;font-family: Arial, Helvetica, sans-serif;font-size: medium; margin-left: 40px;
</style>
"@
#This is Create Snapshot part
IF ($SnapCD -eq "C") {
#CONNECT TO Vmware
try {
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null
Connect-ViServer -server $pViServer -credential $cred -Verbose:$false | Out-Null
Write-Output "Succesfully connected to $pViServer"
}
catch {
Write-Output "error connecting to Vcenter - Force Disconnect"
Disconnect-VIServer -server $pViServer -Force -Confirm:$false
}
$Failed = @()
foreach ($server in $ServerList) {
$SnapshotName = "$server-$Date-$shortcode"
Try {
Write-Output "Snapshot create for server $server"
#CREATE VM Snapshot#
New-Snapshot -VM $server -Name $SnapshotName -Description $Description -memory:$false -Quiesce:$false -ErrorAction Stop | Out-Null
Write-Output "Snapshot created - $server"
}
Catch {
#Failed Snapshots
$ErrorMessage = $_.Exception.Message
Write-Output "Sumting wong with creating snapshot $server; $ErrorMessage `r`n"
$failed += "$server ;$ErrorMessage"
}
}
if ($Failed) {
$MailBody = ($Failed | Out-String)
send-mailmessage -to $email -from $from -Subject "Failed Snapshot CREATE" -body $MailBody -smtpserver $mailsrv -Encoding $mailencoding
}
#DISCONNECT FROM VmWARE
Disconnect-VIServer -server $pViServer -Force -Confirm:$false
Write-Output "Disconnected from $pViServer"
}
#Start patching segment
IF ($patching -eq "YES") {
#CONNECT TO Vmware
try {
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null
Connect-ViServer -server $pViServer -credential $cred -Verbose:$false | Out-Null
Write-Output "Succesfully connected to $pViServer"
}
catch {
Write-Output "Greška kod spajanja na Vcenter - Force Disconnect"
Disconnect-VIServer -server $pViServer -Force -Confirm:$false
}
$Failed = @()
foreach ($server in $ServerList) {
$VM = Get-Vm $server | Get-View
$toolsstatus = $VM.guest.ToolsStatus #if toolsOk then restart server - else just patch !!!Automatic tool install automatically restarts VM!!! this is the step
#Write-Output "$toolsstatus - $server"
#Write-Output "Patching started $server"
#IF server available - connect and run patching
if (Test-Connection -ComputerName $server ) {
try {
#Na serveru pusti patchiranje
Invoke-Command -ComputerName $server -Credential $cred -ScriptBlock {
Function Install-SCCMPatchesAvailable {
[CmdletBinding()]
param(
[Parameter(
Position = 0,
Mandatory = $false,
ValueFromPipelineByPropertyName = $true,
HelpMessage = "Do not reboot server after patches install")]
[ValidateNotNullOrEmpty()]
[switch]
$DoNotReboot
)
begin {
Write-Verbose "Install-SCCMPatchesAvailable: Started"
}
process {
try {
([wmiclass]'ROOT\ccm\ClientSDK:CCM_SoftwareUpdatesManager').InstallUpdates([System.Management.ManagementObject[]] `
(Get-WmiObject -Query 'SELECT * FROM CCM_SoftwareUpdate' -namespace 'ROOT\ccm\ClientSDK'))
while (-not((Get-WmiObject -Namespace 'ROOT\ccm\ClientSDK' -Class 'CCM_ClientUtilities' -list).DetermineIfRebootPending().RebootPending)) {
$Time = (get-date).ToShortTimeString()
Write-Output "Still Patching @ $Time"
Start-Sleep -s 20
}
if (-not $PSBoundParameters.ContainsKey('DoNotReboot')) {
if ((Get-WmiObject -Namespace 'ROOT\ccm\ClientSDK' -Class 'CCM_ClientUtilities' -list).DetermineIfRebootPending().RebootPending) {
(Get-WmiObject -Namespace 'ROOT\ccm\ClientSDK' -Class 'CCM_ClientUtilities' -list).RestartComputer()
}
}
}
catch {
Write-Error -Message "Something went wrong with Install-SCCMPatchesAvailable.`n`nError.Exception.Message : $($_.Exception.Message)`nError.Exception.FullName: $($_.Exception.GetType().FullName)"
}
}
end {
Write-Verbose "Install-SCCMPatchesAvailable: Completed"
}
} #End
#IF NO PATCHES
IF (-NOT(Get-WmiObject -Query 'SELECT * FROM CCM_SoftwareUpdate' -namespace 'ROOT\ccm\ClientSDK')) {
Write-Output "No patches for server $using:server `r`n"
}
else {
IF ($using:toolsstatus.value -eq "toolsOk") {
Write-Output "Install patches on $using:server and restart `r`n"
Install-SCCMPatchesAvailable
}
else {
Write-Output "Install patches on $using:server and DONT restart `r`n"
Install-SCCMPatchesAvailable DoNotReboot
}
}#else
} -ErrorAction Stop #scriptblock
}
catch {
$ErrorMessage = $_.Exception.Message
Write-Output "Sumting wong with patching $server; $ErrorMessage `r`n"
$failed += "$server ;$ErrorMessage"
}
}#if
}#foreach
if ($Failed) {
$MailBody = ($Failed | Out-String)
send-mailmessage -to $email -from $from -Subject "Failed Patch Installing" -body $MailBody -smtpserver $mailsrv -Encoding $mailencoding
}
}#IF
#Service checking segment
IF ($serviceChecking -eq "YES") {
$Failed = @()
foreach ($server in $ServerList) {
#IF server connection OK connect and run command
if (Test-Connection -ComputerName $server -Quiet) {
try {
Invoke-Command -ComputerName $server -Credential $cred -ScriptBlock {
If ($patching -eq "YES" -and $serviceChecking -eq "YES") {
#if service checking is after patching, then wait 3 minutes to check services
Start-Sleep -s 180 #wait 3 minutes
}
$services = Get-service | Where-Object { $_.Status -ne "Running" -and $_.StartType -eq "Automatic" -and $_.Name -notin $using:excludeServices } | Select-Object Name, Status, StartType, DisplayName
IF ($services) {
$MailBody = $services | ConvertTo-Html -Property "Name", "Status", "StartType", DisplayName -head $HTML -body "<H2> Services Not running $env:computername</H2>" | Out-String
#$enc = ([System.Text.Encoding]::UTF8)
send-mailmessage -to $using:email -from $using:from -Subject "$using:server - Services not started" -body $MailBody -smtpserver $using:mailsrv -BodyAsHtml
}#IF
else {
$MailBody = "<H2> Services OK - Patching Successfull $env:computername</H2>"
send-mailmessage -to $using:email -from $using:from -Subject "$using:server - Services not started" -body $MailBody -smtpserver $using:mailsrv -BodyAsHtml
}#else
} -ErrorAction Stop #Scriptblock
}
catch {
$ErrorMessage = $_.Exception.Message
Write-Output "Sumting wong with checking services $server; $ErrorMessage `r`n"
$failed += "$server ;$ErrorMessage"
}
}#test
}#foreach
if ($Failed) {
$MailBody = ($Failed | Out-String)
send-mailmessage -to $email -from $from -Subject "Failed Services Check" -body $MailBody -smtpserver $mailsrv -Encoding $mailencoding
}
} #IF
#Snapshot delete segment
#Delete only snapshot that has specific description (from parameters)
if ($SnapCD -eq "D") {
try {
#CONNECT TO Vmware
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null
Connect-ViServer -server $pViServer -credential $cred | Out-Null
Write-Output "Succesfully connected to $pViServer"
}
catch {
Write-Output "error connecting to Vcenter - Force Disconnect"
Disconnect-VIServer -server $pViServer -Force -Confirm:$false
}
$Failed = @()
$failed
foreach ($server in $ServerList) {
Try {
#FINALY DELETE VmWareSnapshot
Write-Output "Snapshot deletetion for server $server"
Get-Snapshot -VM $server -ErrorAction Stop | where-object { $_.Description -match $Description } | Remove-Snapshot -Confirm:$false -ErrorAction Stop | Out-Null
Write-Output "Snapshot deleted - $server"
}
Catch {
#Failed Snapshots
$failed += $server
}
}
if ($Failed) {
#IF failed snapshots send mail
$MailBody = ($Failed | Out-String)
send-mailmessage -to $email -from $from -Subject "Failed Snapshot DELETE" -body $MailBody -smtpserver $mailsrv -Encoding $mailencoding
}
#DISCONNECT FROM VmWARE
Disconnect-VIServer -server $pViServer -Force -Confirm:$false
}
So this is semi-automatic patch deployment, but for now is quicker than all manual work. Hope this helps somebody.
Good Luck