Private/Kinds/OfficeAddins.ps1

# The OfficeAddins Kind: what else is loaded inside Word, Excel or Outlook.
#
# An add-in is code from a third party running inside the application, and it is the
# faulting module in most Office crashes a Technician will ever see. The count is the
# Finding; the list beneath it is what names the one to disable.

# LoadBehavior, as Office records it. 3 is "loaded, and load at startup", which is the only
# value that means the add-in is actually in the process. 2 and 0 are installed and idle;
# 9 and 8 are load-on-demand. Numeric because it is a registry value, not a display string.
$script:OfficeAddinLoadBehaviorActive = 3

function Get-OfficeAddinsData {
    [CmdletBinding()]
    [OutputType([psobject])]
    param([hashtable]$Parameters = @{})

    $application = Get-Parameter $Parameters 'OfficeApp' ''
    if (-not $application) {
        return [pscustomobject]@{
            PSTypeName    = 'Gutcheck.Data.OfficeAddins'
            OfficeApp     = ''
            Addins        = @()
            DisabledItems = $null
        }
    }

    # Click-to-Run keeps a virtualised copy of the machine hive, so an add-in registered
    # by an installer shows up there and nowhere else.
    $roots = @(
        "HKCU:\Software\Microsoft\Office\$application\Addins"
        "HKLM:\Software\Microsoft\Office\$application\Addins"
        "HKLM:\Software\WOW6432Node\Microsoft\Office\$application\Addins"
        "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\REGISTRY\MACHINE\Software\Microsoft\Office\$application\Addins"
        "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\REGISTRY\MACHINE\Software\WOW6432Node\Microsoft\Office\$application\Addins"
    )

    $addins = @(foreach ($root in $roots) {
        if (-not (Test-Path $root)) { continue }
        Get-ChildItem $root -ErrorAction SilentlyContinue | ForEach-Object {
            $properties = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue
            [pscustomobject]@{
                ProgId       = $_.PSChildName
                Name         = $properties.FriendlyName
                LoadBehavior = $properties.LoadBehavior
                Key          = $root
            }
        }
    })

    # Office disables what crashed it and never mentions it again, so a Customer whose
    # add-in stopped working has a machine that will not say why.
    $disabled = $null
    $resiliency = Get-Item "HKCU:\Software\Microsoft\Office\16.0\$application\Resiliency\DisabledItems" `
        -ErrorAction SilentlyContinue
    if ($resiliency) { $disabled = $resiliency.ValueCount }

    [pscustomobject]@{
        PSTypeName    = 'Gutcheck.Data.OfficeAddins'
        OfficeApp     = $application
        Addins        = $addins
        DisabledItems = $disabled
    }
}

function ConvertTo-OfficeAddinsFinding {
    [CmdletBinding()]
    [OutputType([psobject])]
    param(
        [AllowNull()]$Data,
        [hashtable]$Parameters = @{}
    )

    $warnAbove = Get-Parameter $Parameters 'LoadedAddinWarnAbove' 6
    $failAbove = Get-Parameter $Parameters 'LoadedAddinFailAbove' ([double]::MaxValue)

    $application = Get-DataProperty $Data 'OfficeApp'
    if (-not $application) {
        return New-Finding -Category Apps -Check (Get-Text 'Check.OfficeAddins.OfficeAddIns') -Severity INFO `
            -Value (Get-Text 'Value.OfficeAddins.NoOfficeApp') `
            -Hint (Get-Text 'Hint.OfficeAddins.ThisCheckDefinitionNamesNo')
    }

    $loaded = @((Get-DataCollection $Data 'Addins') |
        Where-Object { (ConvertTo-Number $_.LoadBehavior) -eq $script:OfficeAddinLoadBehaviorActive } |
        Sort-Object ProgId -Unique)

    New-Finding -Category Apps -Check ((Get-Text 'Check.OfficeAddins.ComAddinsSetToLoad') -f $application) `
        -Severity (Get-Severity $loaded.Count $warnAbove $failAbove) `
        -Value ('{0}: {1}' -f $loaded.Count, (($loaded | ForEach-Object { $_.ProgId }) -join ', ')).TrimEnd(': ') `
        -Hint ((Get-Text 'Hint.OfficeAddins.AddinsSlowDown') -f $application)

    $disabled = ConvertTo-Number (Get-DataProperty $Data 'DisabledItems')
    if ($null -ne $disabled -and $disabled -gt 0) {
        New-Finding -Category Apps -Check ((Get-Text 'Check.OfficeAddins.DisabledItems') -f $application) `
            -Severity WARN -Value $disabled `
            -Hint ((Get-Text 'Hint.OfficeAddins.HasDisabledItems') -f $application)
    }
}

function ConvertTo-OfficeAddinsSection {
    [CmdletBinding()]
    [OutputType([psobject])]
    param([AllowNull()]$Data)

    $application = Get-DataProperty $Data 'OfficeApp'
    if (-not $application) { return }

    New-Section -Title ((Get-Text 'Title.OfficeAddins.ComAddins') -f $application) -Row (Get-DataCollection $Data 'Addins')
}