1

Ok, I am trying to get a list off all Office versions and how many of each. We are migrating to Windows 10 and I am trying to talk him into upgrading office to 2016. We have Office as old as 2010. I need a list of how many of each version we have. Even if i can get a list of what computer has what version. I am trying not to run an audit on every computer individually, we have 200 computers.

I have tried several different approaches.

Get-ADComputer -Filter * -Property * | Select-Object Name |
 Export-CSV ADcomputerslist.csv -NoTypeInformation -Encoding UTF8

This doesnt actually save to a file

foreach ($computer in (Get-Content "c:\computers.txt")){
  Write-Verbose "Working on $computer..." -Verbose
  Invoke-Command -ComputerName "$Computer" -ScriptBlock {
    Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\O365ProPlusRetail* |
    Select-Object DisplayName, DisplayVersion, Publisher
  } | export-csv C:\results.csv -Append -NoTypeInformation
}
codewario
  • 19,553
  • 20
  • 90
  • 159
  • What is your question? Are you getting any specific errors or issues? Also, `Get-WmiObject` would be able to get all the installed programs on the machine as well :) – I.T Delinquent Aug 28 '19 at 15:34
  • Using `Get-WmiObject` can have the side effect of triggering repair installs on programs when you run it - generally I check the registry instead – codewario Aug 28 '19 at 16:37

3 Answers3

0

It's generally considered unsafe to use Get-WmiObject to check the Win32_Product class because this can unintentionally trigger repair installs on software. It's safer to check the registry for installed programs:

# We need to check for both 64-bit and 32-bit software
$regPaths = "HKLM:\SOFTWARE\Wow6432node\Microsoft\Windows\CurrentVersion\Uninstall",
  "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"

# Get the name of all installed software registered in the registry with Office in the name
# (you can search for exact strings if you know them for specific versions)
$regPaths | Foreach-Object {
  ( Get-ItemProperty "${_}\*" DisplayName -EA SilentlyContinue ).DisplayName | Where-Object {
    $_ -match 'office'
  }
}

The way this works is that for both registry paths, we want to get the DisplayName value every key underneath the base path from $regPaths (these are mostly going to be GUID-named keys, not much value for just identifying the software by name). We ignore errors since they will clutter the output and for this operation it's expected that some keys may not have a DisplayName property. We don't care about those.

Once we have the DisplayName enumerated for all of the subkeys, we want to filter out the ones that do not have 'Office' in the name. Note that the -match operator is case-insensitive, so casing doesn't matter here. So the Where-Object clause only returns a DisplayName of it finds the string office in it. You can tweak this regular expression if you know exact DisplayName strings for each version of Office you support, as inherently this will return anything with Office in the name.

codewario
  • 19,553
  • 20
  • 90
  • 159
0

As an alternative you could read the (default) registry value for one of the Office applications like Word and translate the version number:

foreach ($computer in (Get-Content "c:\computers.txt")){
  Write-Verbose "Working on computer '$computer'..." -Verbose

  Invoke-Command -ComputerName $computer -ScriptBlock {
    (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Word.Application\CurVer").'(default)' | ForEach-Object {
        [PsCustomObject] @{
            'Computer' = $computer
            'OfficeVersion' = 
                    switch ([int]($_ -split '\.')[-1]) {
                            16 {'MS Office 2016 OR MS Office 2019 or MS Office 365'; break}
                            15 {'MS Office 2013'; break}
                            14 {'MS Office 2010'; break}
                            12 {'MS Office 2007'; break}
                            11 {'MS Office 2003'}
                        }
        }
    }
  } 
}

Output (something like):

Computer OfficeVersion 
-------- ------------- 
PC_01    MS Office 2013
PC_02    MS Office 2010

Unfortunately, Office 2019 and Office 2016 are no longer differentiated by a different version number in this registry value..

Theo
  • 57,719
  • 8
  • 24
  • 41
-1

I would do it like this :-

First start the WinRM serviice

Get-Service -Name WinRM  -ComputerName machinename | Start-service

Then once we have that we can query WinRM for all the installed applications.

Get-CimInstance -ComputerName machinename -ClassName win32_product | Select-Object PSComputerName, Name, PackageName, InstallDate 

Then its good practice disable WinRM when you have finished

Get-Service -Name WinRM  -ComputerName df-ps-sitpc17 | Stop-service
FrankU32
  • 311
  • 1
  • 3
  • 18
  • Why would you want WinRM disabled? This makes remote administration a nightmare. Also `Win32_Product` is unsafe to use as it can trigger repair installs on installed software. – codewario Aug 30 '19 at 14:30
  • Why would you leave something active that could potentially be exploited. especially if you aren't using it. They can always start it remotely if its needed. Can you show some proof that win32_product does what you say it does. I have never experienced it and cannot find any reference that says it can potentially do this. – FrankU32 Aug 30 '19 at 15:13
  • Here's a [Microsoft Support Article](https://support.microsoft.com/en-us/help/974524/event-log-message-indicates-that-the-windows-installer-reconfigured-al) and [blog post](https://gregramsey.net/2012/02/20/win32_product-is-evil/) which are good places to start. I've personally seen this on Windows 7/2008R2 as well, especially in the case where services are stopped and set to manual/disabled, and the repair install starts the service back up again. – codewario Aug 30 '19 at 15:21
  • I did not know that. Why would a product rebuild be an issue? Just because its potentially disruptive? – FrankU32 Aug 30 '19 at 15:24
  • The problem isn't that the capability is there, it's that it does it when you simply *query the namespace*. If I run a *read-only* query on something, **I expect it to be read only, and make ZERO changes.** This can be very problematic in production environments, where you may have modified files provided by installation, or disabled a service known to be having issues while troubleshooting. A repair install can cause changed files to revert to default, or re-enabled a service expected to be disabled. What happens if this causes a service outage outside of a change window? – codewario Aug 30 '19 at 15:28
  • Fair enough, I have this running on hundreds of production machines and have never seen an issue but i guess its good to know the risks. – FrankU32 Aug 30 '19 at 15:29
  • To be fair, you're not the only one who has used this and hasn't seen problems. I used to use it myself before it turned on a service I was expecting to be disabled, which is when I learned about the problem with `Win32_Product` queries. But the potential for unintentional change is there and its use should be avoided in any controlled environment, until Microsoft fixes this. – codewario Aug 30 '19 at 15:34