# Getting installed applications with PowerShell

**URL:** <https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726>\
**Category:** PowerShell Help\
**Created:** [March 6, 2024, 10:11pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726 "2024-03-06T22:11:34Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![dalebert](https://avatars.discourse-cdn.com/v4/letter/d/f17d59/32.png) [@dalebert](https://forums.powershell.org/u/dalebert)\
**Post date:** [March 6, 2024, 10:11pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/1 "2024-03-06T22:11:34Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![tonyd](https://sea1.discourse-cdn.com/flex019/user_avatar/forums.powershell.org/tonyd/32/1041_2.png) [@tonyd](https://forums.powershell.org/u/tonyd)\
**Post date:** [March 6, 2024, 10:19pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/2 "2024-03-06T22:19:28Z")

</div>

\<$.02\>

Avoid option 1, heres why: [Win32\_Product Is Evil. | Greg's Systems Management Blog](https://gregramsey.net/2012/02/20/win32_product-is-evil/)

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:

> <https://stackoverflow.com/questions/75347701/how-to-get-all-installed-software-in-windows-machine-using-get-itemproperty-fr>

\</$.02\>

---

<div class="post-metadata">

**Author:** ![dalebert](https://avatars.discourse-cdn.com/v4/letter/d/f17d59/32.png) [@dalebert](https://forums.powershell.org/u/dalebert)\
**Post date:** [March 7, 2024, 4:27pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/3 "2024-03-07T16:27:36Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![krzydoug](https://sea1.discourse-cdn.com/flex019/user_avatar/forums.powershell.org/krzydoug/32/1008_2.png) [@krzydoug](https://forums.powershell.org/u/krzydoug)\
**Post date:** [March 7, 2024, 4:52pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/4 "2024-03-07T16:52:46Z")

</div>

> [@dalebert](#):
>
> Invoke-Command is deprecated

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.

---

<div class="post-metadata">

**Author:** ![dalebert](https://avatars.discourse-cdn.com/v4/letter/d/f17d59/32.png) [@dalebert](https://forums.powershell.org/u/dalebert)\
**Post date:** [March 7, 2024, 5:31pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/5 "2024-03-07T17:31:35Z")

</div>

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!

---

<div class="post-metadata">

**Author:** ![dalebert](https://avatars.discourse-cdn.com/v4/letter/d/f17d59/32.png) [@dalebert](https://forums.powershell.org/u/dalebert)\
**Post date:** [March 8, 2024, 5:08pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/6 "2024-03-08T17:08:48Z")

</div>

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

```auto
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):

```auto
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?

---

<div class="post-metadata">

**Author:** ![tonyd](https://sea1.discourse-cdn.com/flex019/user_avatar/forums.powershell.org/tonyd/32/1041_2.png) [@tonyd](https://forums.powershell.org/u/tonyd)\
**Post date:** [March 11, 2024, 3:05pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/7 "2024-03-11T15:05:13Z")

</div>

\<$.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\>

---

<div class="post-metadata">

**Author:** ![Olaf](https://sea1.discourse-cdn.com/flex019/user_avatar/forums.powershell.org/olaf/32/992_2.png) [@Olaf](https://forums.powershell.org/u/Olaf)\
**Post date:** [March 11, 2024, 3:13pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/8 "2024-03-11T15:13:14Z")

</div>

> [@tonyd](#):
>
> I need to support powershell versions all the way back to 2.0

Still? You should quit your job and get one in a company using supported operating systems. ☝🏼 😉 😏 😜 😊

---

<div class="post-metadata">

**Author:** ![dalebert](https://avatars.discourse-cdn.com/v4/letter/d/f17d59/32.png) [@dalebert](https://forums.powershell.org/u/dalebert)\
**Post date:** [March 11, 2024, 4:25pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/9 "2024-03-11T16:25:34Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![tonyd](https://sea1.discourse-cdn.com/flex019/user_avatar/forums.powershell.org/tonyd/32/1041_2.png) [@tonyd](https://forums.powershell.org/u/tonyd)\
**Post date:** [March 11, 2024, 5:37pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/10 "2024-03-11T17:37:53Z")

</div>

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

```auto
$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'

```

---

<div class="post-metadata">

**Author:** ![dalebert](https://avatars.discourse-cdn.com/v4/letter/d/f17d59/32.png) [@dalebert](https://forums.powershell.org/u/dalebert)\
**Post date:** [March 11, 2024, 7:14pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/11 "2024-03-11T19:14:56Z")

</div>

Oh, yeah… I could use that as well…

So here is the command I’m running:

```auto
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?

---

<div class="post-metadata">

**Author:** ![tonyd](https://sea1.discourse-cdn.com/flex019/user_avatar/forums.powershell.org/tonyd/32/1041_2.png) [@tonyd](https://forums.powershell.org/u/tonyd)\
**Post date:** [March 11, 2024, 8:19pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/12 "2024-03-11T20:19:55Z")

</div>

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.

```auto
$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

```

---

<div class="post-metadata">

**Author:** ![dalebert](https://avatars.discourse-cdn.com/v4/letter/d/f17d59/32.png) [@dalebert](https://forums.powershell.org/u/dalebert)\
**Post date:** [March 11, 2024, 8:57pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/13 "2024-03-11T20:57:58Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![krzydoug](https://sea1.discourse-cdn.com/flex019/user_avatar/forums.powershell.org/krzydoug/32/1008_2.png) [@krzydoug](https://forums.powershell.org/u/krzydoug)\
**Post date:** [March 11, 2024, 9:08pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/14 "2024-03-11T21:08:58Z")

</div>

Tony I’m disappointed. This is _NOT_ 2.0 compatible. 😛

---

<div class="post-metadata">

**Author:** ![tonyd](https://sea1.discourse-cdn.com/flex019/user_avatar/forums.powershell.org/tonyd/32/1041_2.png) [@tonyd](https://forums.powershell.org/u/tonyd)\
**Post date:** [March 11, 2024, 9:59pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/15 "2024-03-11T21:59:33Z")

</div>

> [@dalebert](#):
>
> calling that var that’s what will populate the $Software var so I can reference that late

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

And doug, thanks for the laugh 🙂

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/flex019/uploads/powershell/original/1X/385e4f9fe133561ad30a814262fe0e0ae6bc3ef0.png) [@system](https://forums.powershell.org/u/system)\
**Post date:** [April 10, 2024, 10:00pm UTC](https://forums.powershell.org/t/getting-installed-applications-with-powershell/23726/16 "2024-04-10T22:00:24Z")

</div>

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