PowerShell 4.0 – Get-MSIProperties

Anyone who uses SCCM for application deployments knows the frustration of having to find MSI product codes.

There are a few ways to make this process easier. My preferred method has always been to use the Detection Method tab to extract the product code that I can then copy/paste into the other tabs but when you are creating large amounts of applications this can be a very slow process.

So using PowerShell we can get a list of MSI properties without having to install the MSI or use additional steps in SCCM.

The function in this script can also be used in bigger scripts such as scripts to automate application installation (Something I will post about shortly)

I would like to thank Nickolaj Andersen (http://www.scconfigmgr.com/) for some of the code snippets


###############################################################################
####                                                                       ####
####                                                                       ####
####                          Get-MSIProperties                            ####
####                            Version 1.0.0                              ####
####                       Created by: Liam Matthews                       ####
####                                                                       ####
####                                                                       ####
###############################################################################

################################Release notes##################################
####                                                                       ####
################################### 1.0.0 #####################################
#### - Initial release                                                     ####
####                                                                       ####
###############################################################################

<#
.SYNOPSIS
    Get-MSIProperties retrieves all properties for the specified MSI(s)

.DESCRIPTION
    Get-MSIProperties retrieves all properties for the specified MSI(s)

.PARAMETER File
    Single msi file. Can be full path ot file name if in current location

    Example: C:\Temp\My.msi
    Example: my.msi

.PARAMETER Folder
    Specify a folder name. The all msi files is any subfolders will be included

    Example: C:\Temp

.PARAMETER Output
    Outputs the data to a html file in the script launch folder

.PARAMETER Log
    Outputs script running data to log file

.EXAMPLE
    Get-MSIProperties -File My.msi

    FileName                  Manufacturer ProductName               ProductVersion ProductLanguage ProductCode
    --------                  ------------ -----------               -------------- --------------- -----------
    H:\Software\7z920-x64.msi  Igor Pavlov  7-Zip 9.20 (x64 edition)  9.20.00.0      1033            {23170F69-40C1-2702-0920-000001000000}

    The above command gathers msi properties on the specified file 

.EXAMPLE
    Get-MSIProperties -Folder C:\Temp -Output

    FileName                                   Path                                               Manufacturer  ProductName       ProductVersion ProductLanguage ProductCode
    --------                                   ----                                               ------------  -----------       -------------- --------------- -----------
    GoogleChromeStandaloneEnterprise.msi       H:\Software\Google\Chrome                           Google, Inc.  Google Chrome     65.240.16509   1033            {80E666DA-3CC1-3476-9968-029D9F1FEB8F}
    Google Chrome Enterprise 32.0.1700.107.msi H:\Software\Google\Chrome\Enterprise\32.0.1700.107  Google, Inc.  Google Chrome     65.169.107     1033            {56B708CC-28A0-3CFC-A83B-BE70E5C4EA18}
    Google Chrome Enterprise 33.0.1750.154.msi H:\Software\Google\Chrome\Enterprise\33.0.1750.154  Google, Inc.  Google Chrome     65.181.32922   1033            {FBD50733-2ABE-3D23-88B4-7B0C0A0ADDA0}
    Google Earth.msi                           H:\Software\Google\Earth\Free\7.1.2.2041            Google        Google Earth      7.1.2.2041     1033            {4D2A6330-2F8B-11E3-9C40-B8AC6F97B88E}
    Google Earth Pro.msi                       H:\Software\Google\Earth\Pro\7.1.2.2041             Google        Google Earth Pro  7.1.2.2041     1033            {44FC61F0-2F8A-11E3-8CAE-B8AC6F97B88E}         

    The above command checks if the specified HotFixID is installed on the list of computers contained in a text file and outputs the results to a html file in the script launch folder

.NOTES
    Version 1.0.0
    Created by Liam Matthews
    Credit to Nickolaj Andersen for code snippets (http://www.scconfigmgr.com/)
#>

[CmdletBinding(DefaultParameterSetName="File")]
Param
(
    # Argument: File
    [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName="File",Position=0)]
    [String]$File,

    # Argument: Folder
    [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName="Folder",Position=0)]
    [String]$Folder = ".\",

    # Argument: Output
    [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)]
    [Switch]$Output

)

#Declaring initial variables
$ScriptInfo = Get-PSCallStack
$StartTime = Get-Date -Format "HH:mm:ss.fff"
$StartDate = Get-Date -Format "MM-dd-yyyy"
$StartDateTime = Get-Date -Format "HHmmss"
$TZbias = (Get-WmiObject -Query "Select Bias from Win32_TimeZone").bias

#Function for fetching MSI properties
Function Get-MSIProperty ([string]$Path,[string]$Property)
{
    $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer
    $comObjWI = New-Object -ComObject WindowsInstaller.Installer
    $MSIDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase","InvokeMethod",$Null,$comObjWI,@("$Path",0))
    $Query = "SELECT Value FROM Property WHERE Property = '$Property'"
    $View = $MSIDatabase.GetType().InvokeMember("OpenView","InvokeMethod",$null,$MSIDatabase,($Query))
    $View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null)
    $Record = $View.GetType().InvokeMember("Fetch","InvokeMethod",$null,$View,$null)
    $Value = $Record.GetType().InvokeMember("StringData","GetProperty",$null,$Record,1)

    Return $Value
}

#Clear output table
$Results = @()

#Action if file specified
IF ($File)
{
    $Manufacturer = Get-MSIProperty -Path $File -Property "Manufacturer"
    $ProductName = Get-MSIProperty -Path $File -Property "ProductName"
    $ProductVersion = Get-MSIProperty -Path $File -Property "ProductVersion"
    $ProductLanguage = Get-MSIProperty -Path $File -Property "ProductLanguage"
    $ProductCode = Get-MSIProperty -Path $File -Property "ProductCode"

    #Build results table
    $Results += New-Object PSObject -Property ([ordered]@{
        "FileName" = "$File"
        "Manufacturer" = "$Manufacturer"
        "ProductName" = "$ProductName"
        "ProductVersion" = "$ProductVersion"
        "ProductLanguage" = "$ProductLanguage"
        "ProductCode" = "$ProductCode"
    })
}
#Action if folder specified
ELSE
{
    #Searching for MSI files in folder
    $AllMSIFiles = Get-ChildItem $Folder -Recurse -Filter *.msi

    #Run action for each MSI found
    foreach ($MSIFullName in $AllMSIFiles)
    {
        $Manufacturer = Get-MSIProperty -Path $MSIFullName.FullName -Property "Manufacturer"
        $ProductName = Get-MSIProperty -Path $MSIFullName.FullName -Property "ProductName"
        $ProductVersion = Get-MSIProperty -Path $MSIFullName.FullName -Property "ProductVersion"
        $ProductLanguage = Get-MSIProperty -Path $MSIFullName.FullName -Property "ProductLanguage"
        $ProductCode = Get-MSIProperty -Path $MSIFullName.FullName -Property "ProductCode"

        #Build results table
        $Results += New-Object PSObject -Property ([ordered]@{
            "FileName" = $MSIFullName.Name
            "Path" = $MSIFullName.Directory
            "Manufacturer" = "$Manufacturer"
           "ProductName" = "$ProductName"
            "ProductVersion" = "$ProductVersion"
            "ProductLanguage" = "$ProductLanguage"
            "ProductCode" = "$ProductCode"
        })

    }
}

#Output to file or screen
IF ($Output)
{
        #Output to file
        $Results | ConvertTo-Html | Out-File MSIProperties-$StartDateTime.html
}
ELSE
{
    #Output to screen
    $Results | Format-Table
}

MS16-072 – Don’t install this update

Yesterday Microsoft released a new security patch: KB3159398 which appears to be wreaking havoc for Systems Administrators everywhere

https://technet.microsoft.com/en-us/library/security/ms16-072.aspx?f=255&MSPPError=-2147217396

The update is supposed to fix a vulnerability in Group Policy that allows standard users to access elevated privileges but instead seems to be be preventing any group policies not user filtered by “Authenticated Users” from applying at all

For more information on this keep and eye on Reddit or Microsoft TechNet

Hopefully Microsoft can fix this issue quickly and release a new update

For anyone who uses SCCM and wants an uninstall application, one can be downloaded from here:

SCCM Export- Microsoft HotFix KB3159389

Windows PowerShell 4.0 – Get-HotFixIDStatus

Recently a client had a need for a way to check if a list of computers had a certain Microsoft HotFix without having to manually logon to each one and check. PowerShell to the rescue

For this script I have implemented several new things I have learned since my last post and have refined other processes


###############################################################################
####                                                                       ####
####                                                                       ####
####                          Get-HotFixIDStatus                           ####
####                            Version 3.1.2                              ####
####                       Created by: Liam Matthews                       ####
####                                                                       ####
####                                                                       ####
###############################################################################

################################Release notes##################################
####                                                                       ####
################################### 1.0.0 #####################################
#### - Initial release                                                     ####
####                                                                       ####
################################### 2.0.0 #####################################
#### - Added help context for PowerShell                                   ####
#### - Added input parameters                                              ####
#### - Modified to allow for single computer or list from txt file         ####
#### - Modified output function to be optional                             ####
#### - Created custom WMI function with timeout to fix hanging             ####
#### - Added error handling and output                                     ####
####                                                                       ####
################################### 2.1.0 #####################################
#### - Moved error handling to function                                    ####
#### - Fixed all updates showing as false                                  ####
#### - Additional queries no longer run if first one failed                ####
####                                                                       ####
################################### 2.2.0 #####################################
#### - Made global error variable to fix errors not outputting             ####
#### - Added shortened error messages                                      ####
#### - Added function to test connection to device                         ####
#### - Increased WMI query timeout to 60 seconds                           ####
####                                                                       ####
################################### 2.2.1 #####################################
#### - Added additional error messages for detection                       ####
####                                                                       ####
################################### 3.0.0 #####################################
#### - Added Support for logging                                           ####
#### - Added error handling for file import                                ####
#### - Change screen output to only occur if Output argument was not used  ####
####                                                                       ####
################################### 3.1.0 #####################################
#### - Added debug text                                                    ####
####                                                                       ####
################################### 3.1.1 #####################################
#### - Changed formatting                                                  ####
#### - fixed a bug causing multiple logs being written for the same error  ####
################################### 3.1.1 #####################################
#### - Removed positional requirement for output and log arguments         ####
####                                                                       ####
###############################################################################

<#
.SYNOPSIS
    Get-HotFixIDStatus Status retrieves all collections where the specified device has a membership

.DESCRIPTION
    Get-HotFixIDStatus Status retrieves all collections where the specified device has a membership

.PARAMETER HotFixID
    The HotFix humber as provided by Microsoft

    Example: KB1234567
    Example: 1234567

.PARAMETER Computer
    The name of the computer to fetch the information from

    Example: Client01

.PARAMETER ComputerList
    TXT file to fetch computer names from if more than one

    Example: List.txt

.PARAMETER Output
    Outputs the data to a html file in the script launch folder

.PARAMETER Log
    Outputs script running data to log file

.EXAMPLE
    Get-HotFixIDStatus -HotFixID KB1234567 -ComputerName Client01

    ComputerName                                           KB1234567 Installed                                    OperatingSystem
    ----                                                   -------------------                                    ---------------
    Client01                                               True                                                   Microsoft Windows Server 2003 R2 Standard Edition           

    The above command checks if the specified HotFixID is installed on the computer 

.EXAMPLE
    Get-HotFixIDStatus -HotFixID KB1234567 -ComputerList Server.txt -Output

    ComputerName                                           KB1234567 Installed                                    OperatingSystem
    ----                                                   -------------------                                    ---------------
    Client01                                               False                                                  Microsoft Windows Server 2003 R2 Standard Edition
    Client02                                               True                                                   Microsoft Windows Server 2003 R2 Enterprise Edition
    Client03                                               True                                                   Microsoft Windows Server 2003 R2 Standard Edition
    Client04                                               False                                                  Microsoft Windows Server 2003 R2 Standard Edition
    Client05                                               False                                                  Microsoft Windows Server 2003 R2 Enterprise Edition
    Client06                                               False                                                  Microsoft Windows Server 2003 R2 Standard Edition
    Client07                                               True                                                   Microsoft Windows Server 2003 R2 Standard Edition
    Client08                                               False                                                  Microsoft Windows Server 2003 R2 Standard Edition           

    The above command checks if the specified HotFixID is installed on the list of computers contained in a text file and outputs the results to a html file in the script launch folder

.NOTES
    Version 3.1.2
    Created by Liam Matthews
#>

#
[CmdletBinding(DefaultParameterSetName="ComputerName")]
Param
(
    # Argument: HotFixID
    [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,Position=0)]
    [String]$HotFixID,

    # Argument: ComputerList
    [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName="ComputerList",Position=1)]
    [String]$ComputerList,

    # Argument: ComputerName
    [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName="ComputerName",Position=1)]
    [String]$ComputerName = "localhost",

    # Argument: Output
    [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)]
    [Switch]$Output,

    # Argument: Log
    [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)]
    [Switch]$Log
)

#Declaring initial variables
$ScriptInfo = Get-PSCallStack
$StartTime = Get-Date -Format "HH:mm:ss.fff"
$StartDate = Get-Date -Format "MM-dd-yyyy"
$StartDateTime = Get-Date -Format "HHmmss"
$TZbias = (Get-WmiObject -Query "Select Bias from Win32_TimeZone").bias

#Formatting HotFixID variable
$HotFixID = $HotFixID.Replace("KB","")

#Funtion for outputting data to log file
Function Output-Log([string]$LogText,[int]$Type = "1",[string]$Component = $ScriptInfo.Command,[string]$Context = "KB$HotFixID",[string]$OutputFile = "KB$HotFixID.log")
{
    $Time = Get-Date -Format "HH:mm:ss.fff"
    $Date = Get-Date -Format "MM-dd-yyyy"
    $LogOutput = "<![LOG[$($LogText)]LOG]!><time=`"$($Time)+$($TZBias)`" date=`"$($Date)`" component=`"$($Component)`" context=`"$($Context)`" type=`"$($Type)`" thread=`"$($StartDateTime)`" file=`"$($Component)`">"

    #Write to log
    Out-File -InputObject $LogOutput -Append -NoClobber -Encoding Default –FilePath "$OutputFile"
}

#Output current status to log
IF ($Log)
{
    Output-log -LogText "+++Starting new thread+++"
}
IF ($Log)
{
    Output-log -LogText "Script loaded from: $($ScriptInfo.ScriptName)"
}
IF ($Log)
{
    Output-log -LogText "Script loaded with arguments: $($ScriptInfo.Arguments)"
}
IF ($Log)
{
    Output-log -LogText "Script loaded by: $env:USERNAME" -Type "1"
}
IF ($Log)
{
    Output-log -LogText "Preloading functions" -Type "1"
}

#Function for
Function Get-WMICustom([string]$ComputerName,[string]$Namespace = "root\cimv2",[string]$Class,[string]$Filter = "",[int]$Timeout = 60)
{
    #Clear error queue
    $Error.Clear()
    #Set default error action
    $ErrorActionPreference = "Stop"

    #Output current status to log
    IF ($Log)
    {
        Output-log -LogText "Running function: Get-WMICustom with arguments: -ComputerName $ComputerName -Namespace $Namespace -class $Class -Filder $Filter -Timeout $Timeout"
    }

    #Attempting scriptblock
    Try
    {
        #Output current status to log
        IF ($Log)
        {
            Output-log -LogText "Building query block"
        }

        #Building query block
        $ConnectionOptions = New-Object System.Management.ConnectionOptions
        $EnumerationOptions = New-Object System.Management.EnumerationOptions
        $TimeoutSeconds = New-Timespan -seconds $Timeout
        $EnumerationOptions.set_timeout($timeoutseconds)
        $AssembledPath = "\\" + $ComputerName + "\" + $Namespace
        $Scope = New-Object System.Management.ManagementScope $AssembledPath, $ConnectionOptions
        $Scope.Connect()
        $QueryString = "SELECT * FROM " + $Class

        IF($Filter.Length -gt 0)
        {
            $QueryString += " WHERE " + $Filter
        }

        $Query = New-Object System.Management.ObjectQuery $QueryString
        $Searcher = New-Object System.Management.ManagementObjectSearcher
        $Searcher.set_options($EnumerationOptions)
        $Searcher.Query = $QueryString
        $Searcher.Scope = $Scope

        #Output current status to log
        IF ($Log)
        {
            Output-log -LogText "Running query"
        }

        #Run query
        Trap { $_ } $Result = $Searcher.get()

        #Return results
        Return $Result
    }
    #Action to take on error
    Catch
    {
        #Action if access denied error
        IF ($_.Exception.Message -match "Access is denied")
        {
            $Global:ErrorMessage = "Access denied"
            IF ($Log)
            {
                Output-log -LogText "Failed to run WMI query: Access Denied" -Type 2
            }
        }

        #Action if time out error
        IF ($_.Exception.Message -match "Timed Out")
        {
            $Global:ErrorMessage = "Timed out while connecting to target"
            IF ($Log)
            {
                Output-log -LogText "Failed to run WMI query: Timed out while connecting to target" -Type 2
            }
        }

        #Action if insufficient memory error
        IF ($_.Exception.Message -match "Insufficient memory to continue the execution")
        {
            $Global:ErrorMessage = "Insufficient memory on target"
            IF ($Log)
            {
                Output-log -LogText "Failed to run WMI query: Insufficient memory on target" -Type 2
            }
        }

        #Action if RPC server busy error
        IF ($_.Exception.Message -match "The RPC server is too busy to complete this operation")
        {
            $Global:ErrorMessage = "RPC server busy"
            IF ($Log)
            {
                Output-log -LogText "Failed to run WMI query: RPC server busy" -Type 2
            }
        }

        #Action if error is unknown
        IF
        (
            $_.Exception.Message -notmatch "Access is denied" -and,
            $_.Exception.Message -notmatch "Timed Out" -and,
            $_.Exception.Message -notmatch "Insufficient memory to continue the execution" -and,
            $_.Exception.Message -notmatch "The RPC server is too busy to complete this operation"
        )
        {
            $Global:ErrorMessage = $_.Exception.Message
        }
    }
}

#Output current status to log
IF ($Log)
{
    Output-log -LogText "Get-WMICustom function loaded"
}

Function Get-DeviceOnline([string]$ComputerName,[int]$Count = 1)
{
    #Clear error queue
    $Error.Clear()
    #Set default error action
    $ErrorActionPreference = "Stop"

    #Attempting scriptblock
    Try
    {

        #Pinging target
        $Ping = Test-Connection -ComputerName $ComputerName -Count $Count

        #Returning results
        Return $Ping

    }
    #Action to take on error
    Catch
    {
        #Setting error output
        $Global:ErrorMessage = "Device offline"
    }
}

#Output current status to log
IF ($Log)
{
    Output-log -LogText "Get-DeviceOnline function loaded"
}

#Clearing results variable from any previous use
$Results = @()

#Action if ComputerList argument defined
IF ($ComputerList)
{

    #Attempting scriptblock
    Try
    {
        #Output current status to log
        IF ($Log)
        {
            Output-log -LogText "Loading computers from defined list: $ComputerList"
        }

        #Reading file
        $ComputerNames = Get-Content $ComputerList -ErrorAction Stop
    }

    #Action of error
    Catch
    {
        #Output current status to log
        IF ($Log)
        {
            Output-log -LogText "Failed to load file" -Type 3
        }
        IF ($Log)
        {
            Output-log -LogText $_.Exception.Message -Type 3
        }
        IF ($Log)
        {
            Output-log -LogText "Script Terminating" -Type 3
        }
        Exit
    }

    #Loop to run for each computer in list
    ForEach ($ComputerName in $ComputerNames)
    {
        #Displaying progress bar
        $PercentComplete = ($Results.Count)/($ComputerNames.Count)*100
        $PercentComplete = [math]::Round($PercentComplete)
        Write-Progress -Activity "Gathering information" -Status "$PercentComplete%: $ComputerName" -PercentComplete $PercentComplete

        #Clearing variables
        $DeviceName = ""
        $HotFixIDExists = ""
        $FullOSName = ""
        $ShortOSName = ""
        $Global:ErrorMessage = ""

        #Output current status to log
        IF ($Log)
        {
            Output-log -LogText "Testing if $ComputerName is online"
        }

        $DeviceOnline = Get-DeviceOnline -ComputerName $ComputerName

        #Output current status to log
        IF ($Log -and $DeviceOnline)
        {
            Output-log -LogText "$ComputerName is responding to ping"}ELSE{Output-log -LogText "$ComputerName is not responding to ping. Skipping" -Type "2" | Continue
        }

        #Output current status to log
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Connecting to WMI and fetching computer name"
            }
        }

        #Connecting to WMI and fetching computer name
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            $DeviceName = (Get-WMICustom -ComputerName $ComputerName -Class "Win32_ComputerSystem").Name
        }

        #Output current status to log
        IF ($Global:ErrorMessage)
        {
            IF ($Log)
            {
                Output-log -LogText "Failed to connect to WMI: $Global:ErrorMessage" -Type "3"
                $WMIErrorOutput = $true
            }
        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Successfully fetched $DeviceName from WMI"
            }
        }
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Connecting to WMI and fetching Operating System"
            }
        }

        #Connecting to WMI and fetching Operating System
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            $FullOSName = Get-WMICustom -ComputerName $ComputerName -Class "Win32_OperatingSystem"
        }
        IF ($Global:ErrorMessage)
        {
            $ShortOSName = "N/A"
        }
        ELSE
        {
            $ShortOSName = ($FullOSName.Name.Split("|"))[0]
        }

        #Output current status to log
        IF ($Global:ErrorMessage)
        {
            IF ($WMIErrorOutput)
            {

            }
            ELSE
            {
                IF ($Log)
                {
                    Output-log -LogText "Failed to connect to WMI: $Global:ErrorMessage" -Type "3"
                    $WMIErrorOutput = $true
                }
            }
        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Successfully fetched $ShortOSName from WMI"
            }
        }
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Connecting to WMI and searching for KB$HotFixID"
            }
        }

        #Connecting to WMI and searching for hotfix
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            $HotFixIDSearch = Get-WMICustom -ComputerName $ComputerName -Class "Win32_QuickFixEngineering" -Filter "HotFixID = 'KB$HotFixID'"
        }

        #Output current status to log
        IF ($Global:ErrorMessage)
        {
            IF ($WMIErrorOutput)
            {

            }
            ELSE
            {
                IF ($Log)
                {
                    Output-log -LogText "Failed to connect to WMI: $Global:ErrorMessage" -Type "3"
                    $WMIErrorOutput = $true
                }
            }
        }
        ELSE
        {
            IF ($Log -and $HotFixIDSearch.HotFixID -eq "KB$HotFixID")
            {
                Output-log -LogText "Successfully connected to WMI and located KB$KBHotFixID"
            }
            ELSE
            {
                Output-log -LogText "Successfully connected to WMI but was unable to locate KB$KBHotFixID"
            }
        }
        IF ($Global:ErrorMessage)
        {

        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Finished connecting to WMI with no errors"
            }
        }

        #Defining variables on error
        IF ($Global:ErrorMessage)
        {
            $DeviceName = $ComputerName + ": " + $ErrorMessage
        }
        IF ($HotFixIDSearch.HotFixID -eq "KB$HotFixID")
        {
            $HotFixIDExists = "True"
        }
        ELSE
        {
            $HotFixIDExists = "False"
        }
        IF ($Global:ErrorMessage)
        {
            $HotFixIDExists = "N/A"
        }

        #Output current status to log
        IF ($Log)
        {
            Output-log -LogText "Returning results"
        }

        #Building table
        $Results += New-Object PSObject -Property @{
            "ComputerName" = $DeviceName
            "OperatingSystem" = $ShortOSName
            "KB$HotFixID Installed" = $HotFixIDExists
        }
    }
}
ELSE
{
    #Output current status to log
    IF ($Log)
    {
        Output-log -LogText "Testing if $ComputerName is online"
    }

    $DeviceOnline = Get-DeviceOnline -ComputerName $ComputerName

    #Output current status to log
    IF ($Log -and $DeviceOnline)
    {
        Output-log -LogText "$ComputerName is responding to ping"}ELSE{Output-log -LogText "$ComputerName is not responding to ping. Skipping" -Type "2" | Continue
    }

    #Output current status to log
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        IF ($Log)
        {
            Output-log -LogText "Connecting to WMI and fetching computer name"
        }
    }

    #Connecting to WMI and fetching computer name
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        $DeviceName = (Get-WMICustom -ComputerName $ComputerName -Class "Win32_ComputerSystem").Name
    }

    #Output current status to log
    IF ($Global:ErrorMessage)
    {
        IF ($Log)
        {
            Output-log -LogText "Failed to connect to WMI: $Global:ErrorMessage" -Type "3"
            $WMIErrorOutput = $true
        }
    }
    ELSE
    {
        IF ($Log)
        {
            Output-log -LogText "Successfully fetched $DeviceName from WMI"
        }
    }
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        IF ($Log)
        {
            Output-log -LogText "Connecting to WMI and fetching Operating System"
        }
    }

    #Connecting to WMI and fetching Operating System
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        $FullOSName = Get-WMICustom -ComputerName $ComputerName -Class "Win32_OperatingSystem"
    }
    IF ($Global:ErrorMessage)
    {
        $ShortOSName = "N/A"
    }
    ELSE
    {
        $ShortOSName = ($FullOSName.Name.Split("|"))[0]
    }

    #Output current status to log
    IF ($Global:ErrorMessage)
    {
        IF ($WMIErrorOutput)
        {

        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Failed to connect to WMI: $Global:ErrorMessage" -Type "3"
                $WMIErrorOutput = $true
            }
        }
    }
    ELSE
    {
        IF ($Log)
        {
            Output-log -LogText "Successfully fetched $ShortOSName from WMI"
        }
    }
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        IF ($Log)
        {
            Output-log -LogText "Connecting to WMI and searching for KB$HotFixID"
        }
    }

    #Connecting to WMI and searching for hotfix
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        $HotFixIDSearch = Get-WMICustom -ComputerName $ComputerName -Class "Win32_QuickFixEngineering" -Filter "HotFixID = 'KB$HotFixID'"
    }

    #Output current status to log
    IF ($Global:ErrorMessage)
    {
        IF ($WMIErrorOutput)
        {

        }
        ELSE
        {
            IF ($Log)
            {
                Output-log -LogText "Failed to connect to WMI: $Global:ErrorMessage" -Type "3"
                $WMIErrorOutput = $true
            }
        }
    }
    ELSE
    {
        IF ($Log -and $HotFixIDSearch.HotFixID -eq "KB$HotFixID")
        {
            Output-log -LogText "Successfully connected to WMI and located KB$KBHotFixID"
        }
        ELSE
        {
            Output-log -LogText "Successfully connected to WMI but was unable to locate KB$KBHotFixID"
        }
    }
    IF ($Global:ErrorMessage)
    {

    }
    ELSE
    {
        IF ($Log)
        {
            Output-log -LogText "Finished connecting to WMI with no errors"
        }
    }

    #Defining variables on error
    IF ($Global:ErrorMessage)
    {
        $DeviceName = $ComputerName + ": " + $ErrorMessage
    }
    IF ($HotFixIDSearch.HotFixID -eq "KB$HotFixID")
    {
        $HotFixIDExists = "True"
    }
    ELSE
    {
        $HotFixIDExists = "False"
    }
    IF ($Global:ErrorMessage)
    {
        $HotFixIDExists = "N/A"
    }

    #Output current status to log
    IF ($Log)
    {
        Output-log -LogText "Returning results"
    }

    #Building table
    $Results += New-Object PSObject -Property @{
        "ComputerName" = $DeviceName
        "OperatingSystem" = $ShortOSName
        "KB$HotFixID Installed" = $HotFixIDExists
    }
}

#Output current status to log
IF ($Log -and $Output)
{
    Output-log -LogText "Saving results to KB$HotFixID.html"
}

#Output to file or screen
IF ($Output)
{
    $Results | Sort-Object ComputerName,
    @{
        expression="ComputerName";Descending=$false
    } | ConvertTo-Html -Head $Header | Out-File KB$HotFixID.html
}
ELSE
{
    $Results
}