2

I have written the following script to check against an application name and echo back some properties if it's installed:

$input = "Microsoft Office Professional"
$appName = "*" + $input + "*"

$responseX64 = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |  Where-Object {$_.DisplayName -like $appName} | Select-Object @{Expression={$_.DisplayName + "|" + $_.DisplayVersion +"|x64"}} | Sort-Object -Unique | ft -HideTableHeaders
$responseX86 = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |  Where-Object {$_.DisplayName -like $appName} | Select-Object @{Expression={$_.DisplayName + "|" + $_.DisplayVersion +"|x86"}} | Sort-Object -Unique | ft -HideTableHeaders

If (([string]::IsNullOrEmpty($responseX64)) -and ([string]::IsNullOrEmpty($responseX86)))
{
    Write-Output "No matches found."
    exit
}
else 
{
    Write-Output $responseX64
    Write-Output $responseX86
}

For completeness I am checking both the x86 and x64 Uninstall registry keys. This works as expected when I run it in an x64 PowerShell session and correctly returns that I have a single x86 install of Microsoft Office:

Microsoft Office Professional Plus 2016|16.0.4266.1001|x86

However, when I run it on the same machine in an x86 session of PowerShell (as it will be run by my x86 management agent), I get the following return:

Microsoft Office Professional Plus 2016|16.0.4266.1001|x64

Microsoft Office Professional Plus 2016|16.0.4266.1001|x86

I have double-checked my registry entries under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall just to be sure there isn't some x64 artifact of Office in there, but there is nothing. How can I amend this script so that it returns accurate results when run in an x86 context?

Community
  • 1
  • 1
user3342256
  • 1,238
  • 4
  • 21
  • 35
  • 4
    As an aside: `$input` is a [reserved automatic variable name](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables?view=powershell-6) in PowerShell. Better not use that here. – Theo Dec 08 '18 at 21:34
  • You append the `x64` hardcoded in your `$responseX64` so that's why it outputs `x64` but the registry key that's being read is the `x86` key for sure. You can use `procmon` from sysinternals to verify. – Lieven Keersmaekers Dec 08 '18 at 22:02
  • You do too much in your query lines. Don't do more than get and where... – Palansen Dec 08 '18 at 22:17
  • 1
    Possible duplicate of [How to access the 64-bit registry from a 32-bit Powershell instance?](https://stackoverflow.com/questions/630382/how-to-access-the-64-bit-registry-from-a-32-bit-powershell-instance) – Raymond Chen Dec 08 '18 at 22:21
  • `get-package '*Microsoft Office Professional*' -provider programs` – js2010 Dec 25 '22 at 15:37

3 Answers3

3

Your problem is that to a 32-bit process, the following key paths refer to the same location, namely the registry view for 32-bit applications:

# To both 64- and 32-bit processes: The registry view for 32-bit applications.
HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall

# To 32-bit processes: same as above
# To 64-bit processes: the 64-bit view of the registry.
HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*

In other words: your code, when run in a 32-bit process, does the same thing twice.


Using just a registry key path won't allow a 32-bit process to see the 64-bit registry.

However, there are workarounds:

  • Call the 64-bit instance of PowerShell as an external process, via directory $env:WinDir\SysNative - see below.

  • Use .NET types directly, as shown in this answer.

  • Call the standard reg.exe utility, which has /reg:32 and /reg:64 parameters for targeting the bitness-specific hives. However, you'll lose PowerShell's rich data-type support with this approach and you'll have to parse the results as text.


Calling 64-bit PowerShell from (32-bit) PowerShell with a script block { ... } automatically serializes the 64-bit instance's output objects in CLIXML format, which makes the calling instance automatically deserialize them with reasonable fidelity[1], so that your original filtering commands should work:

# Works only in a 32-bit process.
& $env:WINDIR\SysNative\WindowsPowerShell\v1.0\powershell.exe {
  Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*
} | 
  Where-Object {$_.DisplayName -like $appName} | 
    Select-Object @{Expression={$_.DisplayName + "|" + $_.DisplayVersion +"|x64"}} |
      Sort-Object -Unique

To determine whether your script is being run in a 32-bit process, use this:

# PowerShell v3 and above.
$is32Bit = [Environment]::Is64BitProcess

# PowerShell version 2 alternative
$is32Bit = ${env:ProgramFiles(x86)} -eq ${env:ProgramFiles} -or 
             -not ${env:ProgramFiles(x86)}

The -or -not ${env:ProgramFiles(x86)} part detects the case of running on a pure 32-bit Windows version, but note that, obviously, no 64-bit definitions exist there.

In PowerShell v3 and above you can easily test whether the OS (Windows installation) is 64-bit or not with [Environment]::Is64BitOperatingSystem

Theo's helpful answer provides a helper function that determines both the process and the OS bitness in a backward-compatible fashion.


[1] Except for a few well-known types, input objects are deserialized as [PSCustomObject] instances with static properties reflecting the original object's property values - see this answer for details.

mklement0
  • 382,024
  • 64
  • 607
  • 775
2

To add to @mklement0's correct answer, maybe a small test function can help to determine the Windows bitness as opposed to the current PowerShell process bitness:

function Get-Architecture {
    # What architecture does Windows use
    $windowsBitness = switch ([Environment]::Is64BitOperatingSystem) {   # needs .NET 4
        $true  { 64; break }
        $false { 32; break }   
        default { 
            (Get-WmiObject -Class Win32_OperatingSystem).OSArchitecture -replace '\D+', '' 
            # Or do any of these:
            # ((Get-WmiObject -Class Win32_ComputerSystem).SystemType -replace '\D+', '') -replace '86', '32'
            # (Get-WmiObject -Class Win32_Processor).AddressWidth   # slow...
        }
    }

    # What architecture does this PowerShell process use
    $processBitness = [IntPtr]::Size * 8
    # Or do any of these:
    # $processBitness = ($env:PROCESSOR_ARCHITECTURE -replace '\D+]', '') -replace '86', '32'
    # $processBitness = if ([Environment]::Is64BitProcess) { 64 } else { 32 }

    # Return the info as object
    return New-Object -TypeName PSObject -Property @{
        'ProcessArchitecture' = "{0}-bit" -f $processBitness
        'WindowsArchitecture' = "{0}-bit" -f $windowsBitness
    }
}

Possible returns are:

64-bit PowerShell on 64-bit Windows

ProcessArchitecture WindowsArchitecture
------------------- -------------------
64-bit              64-bit

32-bit PowerShell on 64-bit Windows

ProcessArchitecture WindowsArchitecture
------------------- -------------------
32-bit              64-bit

32-bit PowerShell on 32-bit Windows

ProcessArchitecture WindowsArchitecture
------------------- -------------------
32-bit              32-bit
mklement0
  • 382,024
  • 64
  • 607
  • 775
Theo
  • 57,719
  • 8
  • 24
  • 41
  • @mklement0 Thanks for the edit, although `(Get-WmiObject -Class Win32_OperatingSystem).OSArchitecture` actually also returns `64-bits` (plural) ;) – Theo Dec 10 '18 at 11:12
  • Intriguing; that must be a localized representation, because on my US-English systems I always get `64-bit` (W7, W10). – mklement0 Dec 10 '18 at 14:09
  • @mklement0 Really? I'm on a Dutch machine. Never would have thought this to be different on other system locales. – Theo Dec 10 '18 at 14:14
  • Yes, it is surprising - and worrisome. https://learn.microsoft.com/en-us/windows/desktop/wmisdk/localizing-wmi-class-information talks about support for localized classes, but says that the `.Locale` property would reflect that, but I don't see it. Note that in Dutch it is `64 bits` (plural, but no `-`); in German it is `64-Bits` (capitalized `B`). – mklement0 Dec 10 '18 at 15:00
  • 1
    @mklement0 It gets even stranger.. My Dutch Win7 Pro shows `64-bits` (with the dash), but my other machine (Windows 10 Pro, also Dutch) shows `64 bits` (no dash, but a space).. Good thing then to replace all non-digit characters in whatever it returns. – Theo Dec 10 '18 at 20:21
0

There's always get-package (5.1 only). It seems to work as both 64 & 32 bit (hello Kace client). There's both programs and msi provider listings, so I'm limiting it to programs. Only one entry appears in add/remove programs. Piping to format-table -autosize to get the full name to display. Pipe to format-list to see all the other properties, including $_.metadata['uninstallstring']. The msi version could be piped to uninstall-package.

get-package '*Microsoft Office Professional*' -provider programs | ft -a

Name                                    Version        Source ProviderName
----                                    -------        ------ ------------
Microsoft Office Professional Plus 2016 16.0.4266.1001        Programs
get-package '*Microsoft Office Professional*' -provider programs | 
  % {$_.metadata['uninstallstring']}

"C:\Program Files\Common Files\Microsoft Shared\OFFICE16\Office Setup Controller\setup.exe" /uninstall PROPLUS /dll OSETUP.DLL
js2010
  • 23,033
  • 6
  • 64
  • 66