Checking What Office version is installed on network PCs exporting-append to csv

I am attempting to see what Office version is installed on network pcs so we can update only those that need our recent version per procedures. I really don’t want to walk up to 500+ pcs and check what Office is installed.
I understand that PSRemoting must be enabled on those machines.
I am trying to use Get-RemoteProgram.ps1
I want to get a list of PCs from a file (which is working), I want to get all listed versions of office (which is also working in my tests), and exclude anything that has “Update” in the program name (“Update” or “Security Update” things like that).

This code works, but gives me all the updates in my output which is not desired.

Get-Content -Path .\PClist.txt | ForEach-Object -Begin {. .\Get-RemoteProgram.ps1} -Process {Get-RemoteProgram -ComputerName $_} | Where-Object {$_.ProgramName -like '*Office*'} | Select-Object -Property ComputerName, ProgramName  | Export-Csv -Append -Path .\InstalledPrograms.csv -NoTypeInformation

This code fails with a parsing error where

Where-Object {$_.ProgramName -like ‘Office’ -notmatch ‘Update’} 

is located.

Get-Content -Path .\PClist.txt | ForEach-Object -Begin {. .\Get-RemoteProgram.ps1} -Process {Get-RemoteProgram -ComputerName $_} | Where-Object {$_.ProgramName -like '*Office*' -notmatch '*Update*'} | Select-Object -Property ComputerName, ProgramName  | Export-Csv -Path .\InstalledPrograms.csv -NoTypeInformation

Here is the exact error

parsing “Update” - Quantifier {x,y} following nothing.
At line:1 char:146

  • … ere-Object {$_.ProgramName -like ‘Office’ -notmatch ‘Update’} | S …
  •             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    • CategoryInfo : OperationStopped: (:slight_smile: , ArgumentException
    • FullyQualifiedErrorId : System.ArgumentException

I have also tried

{$_.ProgramName -like "*Office*" -notmatch "*Update*"'} |

and I have also tried

{$_.ProgramName -like "*Office*" -notlike "*Update*"'} |

Am I using the wrong selector(s) in the Where-Object?

Here is the Get-RemoteProgram.ps1

Function Get-RemoteProgram {

    [CmdletBinding(SupportsShouldProcess=$true)]
    param(
        [Parameter(ValueFromPipeline              =$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0
        )]
        [string[]]
            $ComputerName = $env:COMPUTERNAME,
        [Parameter(Position=0)]
        [string[]]
            $Property,
        [switch]
            $ExcludeSimilar,
        [int]
            $SimilarWord
    )

    begin {
        $RegistryLocation = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\',
                            'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\'
        $HashProperty = @{}
        $SelectProperty = @('ProgramName','ComputerName')
        if ($Property) {
            $SelectProperty += $Property
        }
    }

    process {
        foreach ($Computer in $ComputerName) {
            $RegBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$Computer)
            $RegistryLocation | ForEach-Object {
                $CurrentReg = $_
                if ($RegBase) {
                    $CurrentRegKey = $RegBase.OpenSubKey($CurrentReg)
                    if ($CurrentRegKey) {
                        $CurrentRegKey.GetSubKeyNames() | ForEach-Object {
                            if ($Property) {
                                foreach ($CurrentProperty in $Property) {
                                    $HashProperty.$CurrentProperty = ($RegBase.OpenSubKey("$CurrentReg$_")).GetValue($CurrentProperty)
                                }
                            }
                            $HashProperty.ComputerName = $Computer
                            $HashProperty.ProgramName = ($DisplayName = ($RegBase.OpenSubKey("$CurrentReg$_")).GetValue('DisplayName'))
                            if ($DisplayName) {
                                New-Object -TypeName PSCustomObject -Property $HashProperty |
                                Select-Object -Property $SelectProperty
                            } 
                        }
                    }
                }
            } | ForEach-Object -Begin {
                if ($SimilarWord) {
                    $Regex = [regex]"(^(.+?\s){$SimilarWord}).*$|(.*)"
                } else {
                    $Regex = [regex]"(^(.+?\s){3}).*$|(.*)"
                }
                [System.Collections.ArrayList]$Array = @()
            } -Process {
                if ($ExcludeSimilar) {
                    $null = $Array.Add($_)
                } else {
                    $_
                }
            } -End {
                if ($ExcludeSimilar) {
                    $Array | Select-Object -Property *,@{
                        name       = 'GroupedName'
                        expression = {
                            ($_.ProgramName -split $Regex)[1]
                        }
                    } |
                    Group-Object -Property 'GroupedName' | ForEach-Object {
                        $_.Group[0] | Select-Object -Property * -ExcludeProperty GroupedName
                    }
                }
            }
        }
    }
}

You need to specify the operator (and\or) and the object that -notlike is compared against, try:

{($_.ProgramName -like "*Office*") -and ($_.ProgramName -notlike "*Update*"')}

That did indeed work. On your code there is an extra single ’

This code did work. Thanks. I should have seen it, next time I will be more observant.

Get-Content -Path .\PClist.txt | ForEach-Object -Begin {. .\Get-RemoteProgram.ps1} -Process {Get-RemoteProgram -ComputerName $_} | Where-Object {($_.ProgramName -like "*Office*") -and ($_.ProgramName -notlike "*Update*")} | Select-Object -Property ComputerName, ProgramName  | Export-Csv -Append -Path .\InstalledPrograms.csv -NoTypeInformation