Getting installed applications with PowerShell

Hi, All.

I’m having a problem getting a list of applications installed on a remote system. There are three ways of getting this information;

  1. Using Win32_Product with Get-CimSession, or an Invoke-Command. But that doesn’t return all applications that are installed. It does get a lot of them, but it’s not a complete list.

  2. Using the Get-ItemProperty with an Invoke-Command to query the registry (HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall*). But that doesn’t return any of the x64 applications.

  3. Using the Get-ItemProperty with an Invoke-Command to query the registry (HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall*). But that doesn’t return any of the x86 applications.

So I guess I need some advice on how to gather up that list of applications on a remote system. My current idea is to store the x86 and x64 applications in different arrays/hashtables then combine the twi if one doesn’t exist in the other.

I don’t have any code for this to look at. I’m hoping someone has already figured this out and can drop some knowledge for me.

Thanks!
Dale

<$.02>

Avoid option 1, heres why: Win32_Product Is Evil. | Greg's Systems Management Blog

What I did was run both option 2 and 3, sort for unique and that seems to do the trick.

There is even a one liner solution here:

</$.02>

1 Like

So, that is a great article, and a nice little one-liner. I’m trying to modify it a little to get more than just the DisplayName in a humanly-readable format. Just need to tinker with it a bit. But it does the trick!

I’ve also been trying to figure out a way of running that without using the Invoke-Command. As I understand it, using Invoke-Command is deprecated and everything is going to Get-CIMSession. I haven’t figured out how to do that just yet. But it gives me a good foot-hold on the problem!

Thanks, TonyD!
Dale

Definitely not.

Your other option without Invoke-Command is remote registry which I’d say most orgs prefer the one single WinRM port being open (what Invoke-Command uses) versus opening additional ports. Remote Registry sevice would need to be running and the port allowed to query remotely.

1 Like

Oh, interesting. Someone, on another thread - on a different site, a while back told me that Invoke-Command was on it’s way out. I’m actually really glad to hear it isn’t. I haven’t found a good way to do remote queries without.

Thanks!

OK, I just found that using the Get-Package cmdlet returns everything listed in the AppWiz.cpl pane (plus one for Edge Updates).

invoke-command -computername Server1 -scriptblock {Get-Package -ProviderName Programs -IncludeWindowsInstaller | Sort Name | FT -Auto}

When going to the registry to retreive the installed applications list I run this (taken almost verbatim from your example URL, TonyD):

invoke-command -ComputerName Server1 -ScriptBlock {$MyProgs = Get-ItemProperty 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'; $MyProgs += Get-ItemProperty 'HKLM:SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' ; $MyProgs.DisplayName | sort -Unique

I get a different set of information back, more when pulling the information from the registry or course, but I guess I like using the Get-Package better as it’s a little more straight forward. What’s the benefit to querying the registry rather than those applications with an uninstall package?

<$.02>

For me the benefit of the Registry method is that I need to support powershell versions all the way back to 2.0 (dont laugh). That is my primary reason.

</$.02>

Still? You should quit your job and get one in a company using supported operating systems. :point_up:t3: :wink: :smirk: :stuck_out_tongue_winking_eye: :blush:

2 Likes

I’ve opted for the registry path as well. I noticed a couple things missing when using Get-Package. It just boggles my mind that there isn’t a straight forward way of not only getting the installed application, but to also uninstall those applications without jumping through 10,000 hoops to get there.

And to make things even more complicated, I also gather Installed Updates as well. You might find this useful. :slight_smile:

$PatchRegPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\*\Patches\*'
$Updates = Invoke-Command -ComputerName $hostToCheck -ScriptBlock {Get-ItemProperty -Path $Using:PatchRegPath} -Authentication 'Negotiate'
1 Like

Oh, yeah… I could use that as well…

So here is the command I’m running:

invoke-command -ComputerName Server1 -ScriptBlock { `
>> $AllProgs = @();FOREACH ($i in Get-ItemProperty 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'){ `
>> $AllProgs += $i;}; `
>> FOREACH ($i in Get-ItemProperty 'HKLM:SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'){ `
>> $AllProgs += $i;}; `
>> $Allprogs | select DisplayName,UninstallString,QuietUninstallString | Out-String}

What I’d like to do is make one call to the remote system, then filter the info on my local machine. So, say I want to look for an application that has a quiet uninstall associated with it. I can filter out everything on my local machine instead of make a call to the remote system to get the info, then make another call to get the uninstall info. How would I save that to a local var?

I tried creating an local array, then adding the info to the local array using the $Using:Whatever call. But it appears as though that is to inject into the invoke command – not store data retrieved from the remote system.

Any ideas on how to do that?

Working off what you posted (cleaned it up a bit, back tics are not necessary), it seems you are already there, but confused on how to get the data back. You are correct, $Using defines the value of the remote variable. I believe you need to define your local variable as your call to Invoke Command. Maybe this will work (NOT TESTED !!!) Your results would then be in the $Software variable.

$Software = invoke-command -ComputerName Server1 -ScriptBlock {
	$AllProgs = @()
	Get-ItemProperty 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*' | ForEach-Object {
		$Entry = [PSCustomObject][Ordered] @{
			'Display Name' = $_.DisplayName
			'Uninstall String' = $_.UninstallString			
			'Quiet Uninstall String' = $_.QuietUninstallString
		}
		$AllProgs += $Entry
	}
	Get-ItemProperty 'HKLM:SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' | ForEach-Object {
		$Entry = [PSCustomObject][Ordered] @{
			'Display Name' = $_.DisplayName
			'Uninstall String' = $_.UninstallString			
			'Quiet Uninstall String' = $_.QuietUninstallString
		}
		$AllProgs += $Entry
	}
	$Allprogs
} -Authentication Negotiate

That was the silver bullet! Thanks, TonyD.

I see you also created some PSCustomObjects as well in there. I still don’t understand those, but from what I know of them that’s the smart way to go.

I have tried it in a similar way to this, but there’s one important difference; at the end you called $AllProgs. Correct me if I’m wrong, but by calling that var that’s what will populate the $Software var so I can reference that later. Correct? If so that’s what I wasn’t doing in my attempts, and it makes sense now.

Thanks again,
Dale

Tony I’m disappointed. This is NOT 2.0 compatible. :stuck_out_tongue:

2 Likes

Yes, that is correct. The $Software variable should contain all the info you requested from the remote system.

And doug, thanks for the laugh :slight_smile:

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.