Has your hotfix been installed?

Of course you may wonder, why do you not use SCCM or WSUS to check if your software was installed successfully. Sadly when working in an enterprise environment you do not always have access to all the tooling you require, even though you have administrator access to the servers you need to manage.

Recently I was asked if I could scan all our Hypervisors to see if the latest December update rollup was installed, and if possible to mail it to the team’s functional mailbox.

This resulted in the following simple script:

Function CheckHotfix {
<#
.SYNOPSIS
Checks if Microsoft hotfixes are installed against KB numbers (reports if missing).
.DESCRIPTION
The CheckHotfix will check the hosts against the list of KB numbers.
If a KB number is not found it will be reported back to you.
Please make use of the -verbose switch to see information of what the script is handling.
.PARAMETER Server 
A single Servername, or an array of multiple Servers, this field is mandatory.
.PARAMETER KBnumbers 
One or multiple KB numbers, this field is mandatory.
.EXAMPLE
Scan 1 server for a KB number
CheckHotfix -Server "Server01" -KBnumbers "292929"
.EXAMPLE
Scan multiple servers for FC Card information
$computernames = (Get-scvmhost).computername
$kbnumbers = "292929 - doesnt exist","KB3013769 - Update Rollup December 2014"
CheckHotfix -Server $computernames -KBnumbers $kbnumbers
.NOTES
You need to be local administrator on the Server(s) you are querying.
This script was written by Danny den Braver @2015, for questions please contact danny@denbraver.com
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param([array][Parameter(Mandatory=$true)]$Server,
      [array][Parameter(Mandatory=$true)]$KBnumbers)

ForEach ($Server_ in $Server){
    #Test Connection availability and continue if $true
    $testconnection = Test-Connection $Server_ -Quiet
    if ($testconnection -eq $true){
        Write-Verbose -Message "Scanning: KB Article for server $Server_"
        foreach ($kbnumber in $KBnumbers){
            $kba = $null
            $kbnumbersmall = ($kbnumber.split(" "))[0]
            $kba = Get-WmiObject -ComputerName $Server_ -query 'select * from win32_quickfixengineering' |  ? {$_.hotfixid -contains $kbnumbersmall}
            if ($kba -eq $null){
                $Server_ | select @{Name="Computername"; Expression={$Server_}},
                @{Name="Productionlevel"; Expression={(Get-WmiObject Win32_environment -computername $server_ | ? {$_.name -eq "Productionlevel"}).variablevalue}},
                @{Name="Not Installed"; Expression={$kbnumber}}
            }
        }
    }
    else {Write-Warning "Cannot connect to $Server_"}
}

}

Now you only need to add your results to an email message using “send-MailMessage”

SCOM Maintenance Mode

Have you ever had the hassle of needing to put multiple servers into SCOM maintenance mode, yet you dont have the Powershell commandlets installed, or you are using an older version of SCOM that just does not do what you would like it to do.

I ran into the same issue with a customer, that was using SCOM 2007R2, while at some days it would really come in handly to place servers into maintenance mode scripted.

The script below can be your answer to this problem. Simply use the function SCOMMaintenanceMode -servername “server” -maintenancemodeminutes “minutes” or even push an array of servers against it. It will first check if maintenance mode is already set and if the current duration is not longer then what you just told the script.

Function SCOMMaintenanceMode {
<#
.SYNOPSIS
Sets or Stops SCOM Maintenance Mode for one or multiple Computers/Servers.
.DESCRIPTION
The SCOMMaintenanceMode function uses the PowerShell Snapin Microsoft.EnterpriseManagement.OperationsManager.Client to read the current maintenance duration (if set). If Maintenance is not set fot a computer for a duration that lasts longer then the user defined setting, it will try to place it into SCOM Maintenance mode with the reason Planned (Other).
Please make use of the -verbose switch to see information of what the script is handling.
.PARAMETER Servername 
A single computer name, or an array of computernames, this field is mandatory.
.PARAMETER Comment
The comment that is giving to the SCOM Maintenance mode, by Default: "Maintenance Mode Job" is given.
.PARAMETER MaintenanceModeMinutes 
The number of minutes the server or servers will be placed into maintenance mode.
.PARAMETER StopMaintenancemode
A switch that is given to the script to stop maintenance mode, instead of setting it.
.EXAMPLE
Place 1 server into maintenance mode.
SCOMMaintenanceMode -servername "Server01" -MaintenanceModeMinutes "60"
.EXAMPLE
Place multiple servers into maintenance mode.
$computernames = "server1","server2","server3"
SCOMMaintenanceMode -servername $computernames -MaintenanceModeMinutes "60"
.EXAMPLE
Give your personal comment to the maintenance mode.
SCOMMaintenanceMode -servername "Server01" -Comment "Because I can" -MaintenanceModeMinutes "60"
.EXAMPLE
Stop Maintenance Mode.
SCOMMaintenanceMode -servername "Server01" -Stop
.NOTES
It is required to have access to the computer account in SCOM to be able to place this server into maintenance mode. In older versions of Microsoft Powershell© it may be possible to receive a warning/alert when a server is not placed in maintenance mode. This is related to a check to see if the current maintenance mode has been set for a longer period, and the try/catch filter not applying propperly.
This script was written by Danny den Braver @2014, for questions please contact danny@denbraver.com
#>
[CmdletBinding(SupportsShouldProcess=$true)]

param( [array][Parameter(Mandatory=$true)]$Servername,
       [string]$Comment="Maintenance Mode Job",
       [int]$MaintenanceModeMinutes=60,
       [switch]$Stop)

#Load Snapin
Add-PSSnapin Microsoft.EnterpriseManagement.OperationsManager.Client

foreach ($server_ in $servername){

    $computerPrincipalName=(Get-ADComputer $server_).dnshostname
    $startTime=[System.DateTime]::Now
    $OpsMgrScriptName="OpsMgrScript"
    $OpsMgrScriptCount=(Get-PSDrive|where {$_.Name -eq $OpsMgrScriptName}|Measure-Object).Count
    
    if ($OpsMgrScriptCount -ne 1) {
	$tmp = New-PSDrive -Name:$OpsMgrScriptName -PSProvider:OperationsManagerMonitoring -Root:\
    }
    $tmp = New-ManagementGroupConnection OperationsManager
    $computerCriteria="PrincipalName='" + $computerPrincipalName + "'"
    $computerClass=get-monitoringclass -name:Microsoft.Windows.Computer -Path:"$($OpsMgrScriptName):\OperationsManager"
    $computerObject=get-monitoringobject -monitoringclass:$computerClass -criteria:$computerCriteria -Path:"$($OpsMgrScriptName):\OperationsManager"

    if ($Stop) {
        try{
            $endTime=([System.DateTime]::Now).AddSeconds(5)
            Set-MaintenanceWindow -endTime:$endTime -comment:"Stopping Maintenance Mode" -monitoringObject:$computerObject -Reason PlannedOther -ErrorAction "Stop"
            Write-Verbose -Message "Maintenance Mode stopped for server: '$server_'"
        }
        catch{
        Write-Warning "Failed to stop Maintenance mode for server '$server_' $(($_.Exception.Message).substring(0,71))"
        }
    } 
    else {    
    try{
        $endTime=([System.DateTime]::Now).AddMinutes($MaintenanceModeMinutes)
        $currentendtime=(Get-MaintenanceWindow -MonitoringObject:$computerObject).scheduledendtime
        if ($currentendtime -gt $endTime){
            Write-Warning "Maintenance Mode not set for server: '$server_' (Current Maintenance window is set for a longer period: $currentendtime)" -verbose
            break
        }
        New-MaintenanceWindow -startTime:$startTime -endTime:$endTime -monitoringObject:$computerObject -comment:$comment -ErrorAction "Stop"
        Write-Verbose -Message "Maintenance Mode set for server: '$server_'" -verbose
        }
        catch{
            Write-Warning "Failed to set Maintenance mode for server '$server_' $(($_.Exception.Message).substring(0,71))"
        }
    }

}
}