Getting Installed Programs via Registry - powershell

So I've been over at
https://blogs.technet.microsoft.com/heyscriptingguy/2013/11/15/use-powershell-to-find-installed-software/
trying to use this to get a list of installed programs on a remote machine. I already started the WinRM remotely via PS, and am using the command
Invoke-Command -cn MC-PKS-MCARDH-L -ScriptBlock {
Get-ItemProperty HKLM:\Software\Wow6432Node\* |
select PSPath, PSParentPath, PSChildName
}
The primary use of this is to get the Adobe versions of programs on the client's PC, but for some reason this doesn't return many of the folders. It just returns HP, ESRI, Malwarebytes, and a few others:
PSPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\Software\Wow6432Node\ESRI
PSParentPath : Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\Software\Wow6432Node
PSChildName : ESRI
PSComputerName : mc-pks-mcardh-l
RunspaceId : 76050648-eec5-4e90-960d-872264a894d4
PSShowComputerName : True
Any reason this is? I tried using the one from the page I linked:
HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*
but Adobe Reader doesn't even show up on that list.
I'm an AD Admin on the domain so why is it not showing all the folders? I'm looking via regedit now on the test machin and theres a folder called Adobe.

I'd recommend using, or at least trying, WMI:
Get-WmiObject -Class Win32_Product | Select-Object -Property Name, Vendor, Version | Format-Table;
You can also specify -ComputerName to query a remote machine.
To list all properties for dev. purposes, try:
Get-WmiObject -Class Win32_Product | Format-List -Property *;
Good luck!
--- ALTERNATIVELY, please try:
[String] $strKey = '';
[String] $strSubKey = '';
[PSCustomObject] $objData = $null;
#( 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall', 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' ) | Foreach-Object {
$strKey = $_;
Get-ChildItem -Path $strKey | Select-Object -ExpandProperty PSChildName | Foreach-Object {
$strSubKey = $_;
$objData = Get-ItemProperty -LiteralPath (Join-Path -Path $strKey -ChildPath $strSubKey) | Select-Object -Property DisplayName, DisplayVersion;
if ( $objData.DisplayName -ne $null ) {
Write-Output -InputObject $objData;
} #if
} #Foreach-Object
} #Foreach-Object

Related

Use powershell on remote machine for Edge Version

I have a script that returns Version numbers for 3rd Party software running on our Domain. Java, chrome, etc to ensure we are up to date.
This is all working OK.
However, I am trying to use:
Get-AppxPackage -Name Microsoft.MicrosoftEdge | select-object Version
within a remote Powershell session or 'invoke-command' but it is returning no results,
the command works fine if locally, and also when RDP'd onto the remote machine.
How can i use this cmdlet remotely to check Edge is version compliant?
Many Thanks.
EDIT:
import-module activedirectory
$workstations = Get-ADComputer -Filter "OperatingSystem -like 'Windows 10 *'" -Property * | select name -ExpandProperty Name
foreach ($workstation in $workstations)
{
$session = New-PSSession -Computername $workstation
$resultofsession = Invoke-Command -Session $Session -ScriptBlock{
$Path="HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
$path2 = "HKLM:\SOFTWARE\Microsoft\Internet Explorer\"
$java = Get-ItemProperty $Path | Select-Object DisplayName, DisplayVersion | where displayname -like "java*"
$chrome = Get-ItemProperty $path | Select-Object DisplayName, DisplayVersion | where displayname -ceq "Google Chrome"
$adobe = Get-ItemProperty $path | Select-Object DisplayName, DisplayVersion | where displayname -ceq "Adobe Acrobat Reader DC"
$edge = Get-AppxPackage -Name Microsoft.MicrosoftEdge | select-object Version
$ie = get-itemProperty $path2
$Object = New-Object PSObject -property #{
'chrome' = "CHROME: " + $chrome.displayversion + ","
'edge' = "EDGE: " + $edge + ","
'ie' = "IE: " + $ie.svcVersion + ","
'java' = "JAVA: " + $java.Displayversion + ","
'adobe' = "ADOBE: " + $adobe.displayversion + ","
'hostname' = hostname
}
Write-output $object
}
remove-pssession $session
write-output $resultofsession | format-table -HideTableHeaders -autosize -force | Out-File "C:\web\Version.txt" -append
}
Get-AppxPackage will return only information for the current users profile (in this case, the account running the script). You likely need to add the -AllUsers switch, but note this will return a result for each user logged in (and they may have different versions). You can use the -user parameter to specify a specific user.
AppX packages are only updated for the user profile when they log in, hence why different users can have different versions of an app on the same workstation. Assuming everything works as expected, the app should be updated when the user next logs in.
Try the following to return the version number for each user ID:
$edge = Get-AppxPackage -AllUsers -Name Microsoft.MicrosoftEdge | select-object #{N="User"; E={$_.packageUserInformation.UserSecurityId.Username}},Version
Example output:
User Version
---- -------
test 42.17127.1.0
S-1-5-18 44.17763.1.0
jacob 44.18252.1000.0

How to check if an application is installed on multiple server - powershell

$comps = get-content C:\xyz\test.txt
foreach($comp in $comps)
{
$result = Get-WmiObject -Class Win32_Product -Computer $comp | sort-object Name | select Name | where { $_.Name -match “abc”}
$result | out-file -Append out.txt
}
There is a application abc , im looking to know whether this is installed in multiple servers.
Check this "Hey Scripting Guy" link. This link describes how to query for installed applications via the registry.
From the link:
Win32_Product: The Good, the Bad, and the Ugly
[Good] The Win32_Product WMI class represents products as they are installed >by Windows Installer.
If you choose to query Win32_Product class by using Get-WmiObject, you’ll find >yourself [Bad] waiting for your query (or application) to return [Ugly] a >consistency check of packages that are installed as it attempts to verify and >repair installs. (For more information, see Event log message indicates that >the Windows Installer reconfigured all installed applications).
...
Based on above link you can query your installed products via:
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Format-Table –AutoSize
Additionally you can use Test-Path to check if a requested registry entry exists. Example:
if (test-path HKLM:\Software\abc) { write-host "Found" } else { Write-Host "Not found" }
To check for installed software on multiple servers:
$servers = Get-Content C:\xyz\servers.txt
$results = #()
foreach ($server in $servers) {
$session = New-PSSession -ComputerName $server -Credential (Get-Credential)
$results += Invoke-Command -Session $session -ScriptBlock {
test-path HKLM:\Software\abc
}
}
$results.GetType() # Dump type
$results | gm # Dump properties
$results | Format-Table
Hope that helps.

Remote Client Sofware Query bugs

I've pieced together this script that queries multiple remote clients and returns installed software names and versions. However, it seems to miss a few programs and I don't know why. Is there a problem with registry entry it queries. Here is my code:
Invoke-Command -cn SERVER2012, SERVER2012A -ScriptBlock {
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |
select DisplayName, DisplayVersion | Format-Table –AutoSize
} | Out-File (join-path ([environment]::GetFolderPath('Desktop')) "GetData.txt")
Don't use the Format-Table cmdlet if you want to use the result later, so it should work without it:
Invoke-Command -cn SERVER2012, SERVER2012A -ScriptBlock {
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |
Select-Object DisplayName, DisplayVersion
} | Out-File (join-path ([environment]::GetFolderPath('Desktop')) "GetData.txt")

Remove column name from PowerShell select-object results

Overall my goal is to get the VNC version for a list of remote computers along with the uninstall GUID so I can remotely uninstall VNC Viewer from certain computers. I have used the Get-WmiObject -Class Win32_Product but that is extremely slow.
I have the following script but in the results it includes the name of the select-object parameter.
$computers = Get-Content -Path "C:\Computers.txt"
$Results = #()
ForEach ($Computer in $Computers) {
$Results += New-Object PSObject -Property #{
"ComputerName" = $Computer
"Name" = Invoke-Command -ComputerName $Computer -ScriptBlock { Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* } `
| Where-Object -FilterScript {$_.DisplayName -like "VNC V*"} | select-object DisplayName
"DisplayVersion" = Invoke-Command -ComputerName $Computer -ScriptBlock { Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* } `
| Where-Object -FilterScript {$_.DisplayName -like "VNC V*"} | select-object DisplayVersion
"ModifyPath" = Invoke-Command -ComputerName $Computer -ScriptBlock { Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* } `
| Where-Object -FilterScript {$_.DisplayName -like "VNC V*"} | select-object ModifyPath
"Vendor" = Invoke-Command -ComputerName $Computer -ScriptBlock { Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* } `
| Where-Object -FilterScript {$_.DisplayName -like "VNC V*"} | select-object Publisher
}
}
$Results | Select-Object ComputerName,Name,DisplayVersion,ModifyPath,Vendor | Sort-Object ComputerName | Export-Csv C:\VNC.csv -notype ;
My results look like this:
ComputerName : ComputerName
Name : #{DisplayName=VNC Viewer 5.2.3}
DisplayVersion : #{DisplayVersion=5.2.3}
ModifyPath : #{ModifyPath=MsiExec.exe /I{18B1E36F-0DA3-4FDA-BC57-DD815B0DF3B2}}
Vendor : #{Publisher=RealVNC Ltd}
I would want it to look like this:
ComputerName : ComputerName
Name : VNC Viewer 5.2.3
DisplayVersion : 5.2.3
ModifyPath : MsiExec.exe /I{18B1E36F-0DA3-4FDA-BC57-DD815B0DF3B2}
Vendor : RealVNC Ltd
Is this possible or am I going about this script entirely wrong? I haven't figured out a way to run this Invoke-Command for multiple parameters and still output the results in individual columns any other way.
This script works but takes forever for 100's of computers:
if (Test-Path C:\VNCInstalled.csv) {Remove-Item C:\VNCInstalled.csv}
if (Test-Path C:\Computers.txt) {Remove-Item C:\Computers.txt}
$DirSearcher = New-Object System.DirectoryServices.DirectorySearcher([adsi]'')
$DirSearcher.Filter = '(&(objectClass=Computer)(!(cn=*esx*)) (!(cn=*slng*)) (!(cn=*dcen*)) )'
$DirSearcher.FindAll().GetEnumerator() | sort-object { $_.Properties.name } `
| ForEach-Object { $_.Properties.name }`
| Out-File -FilePath C:\Computers.txt
Get-Content -Path c:\Computers.txt `
| ForEach-Object {Get-WmiObject -Class Win32_Product -ComputerName $_} `
| Where-Object -FilterScript {$_.Name -like "VNC V*"} `
| select-object #{Name="ComputerName";Expression={$_.PSComputerName}},
Name,
#{Name="InstallLocation";Expression={$_.PackageCache}},
Vendor,
Version,
#{Name="GUID";Expression={$_.IdentifyingNumber}} `
| Sort-Object ComputerName `
| Export-CSV -path c:\VNCInstalled.csv -notype
Change all of your Select-Object commands to Select-Object
-ExpandProperty PropertyName, to discard the property name / column header.
This is the answer I gave three years ago and I think it was really a poor answer. Let me do a better job now.
Why your current code is slow
Your current code enumerates all machines from AD and puts them in a file called Computers.txt. Simple, and you do it fine.
Next up, your code performs this operation:
Get-Content -Path c:\Computers.txt |
ForEach-Object {Get-WmiObject -Class Win32_Product -ComputerName $_} `
| Where-Object -FilterScript {$_.Name -like "VNC V*"} [...]
This can be summarized as 'For each computer, request the full Win32_product table, and then after that, filter down to apps named VNC.' This is HUGELY performance impacting and for a few reasons.
Even on a fast modern computer, querying Win32_Product will take 30 seconds or more, because it returns every application installed. (on a new VM for me it took more than a minute with just a handful of apps installed!)
Querying Win32_Product also has this fun quirk which makes it take even longer, quoted from MSDN Documentation on the Win32_Product Class
Warning Win32_Product is not query optimized. Queries such as "select * from Win32_Product where (name like 'Sniffer%')" require WMI to use the MSI provider to enumerate all of the installed products and then parse the full list sequentially to handle the “where” clause. This process also initiates a consistency check of packages installed, verifying and repairing the install. With an account with only user privileges, as the user account may not have access to quite a few locations, may cause delay in application launch and an event 11708 stating an installation failure. For more information, see KB Article 794524.
So to summarize, querying Win32_Product is slow, AND it also triggers a consistency chceck on every app, AND we also have this query written to retrieve every single app before filtering. These add up to a process which probably takes ~3 minutes per pc, and will operate serially (one after the other) and take forever.
How to fix it
Software info can be retrieved reliably in two places:
If you have SCCM/ConfigMgr installed on your devices, it adds the Win32_AddRemoveProgram WMI Class you can query, which is a super fast version of Win32_Product
If not, we can always retrieve info from the registry.
Here's a short snippet to get applications like VLC installed on a computer (I don't have VNC like you, so I'm making due)
Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-object DisplayName -like "VLC*" |Select-Object DisplayName, DisplayVersion, Publisher, InstallDate,UninstallString
DisplayName : VLC media player
DisplayVersion : 3.0.8
Publisher : VideoLAN
InstallDate :
UninstallString : "C:\Program Files (x86)\VideoLAN\VLC\uninstall.exe"
This operation is much faster, only 400 MS or so. Sadly we cannot get much faster using the registry as it has a very weird PowerShell provider that doesn't implement the -Filter parameter, so we do have to retrieve all programs and then filter to our desired choice.
Updating your script to use this function instead
I took the liberty of rewriting your script to use this approach, and restructured it a bit for better readability.
$results = New-object System.Collections.ArrayList
$computers = Get-Content -Path c:\Computers.txt
foreach ($computer in $computers){
#get VNC entries from remote computers registry
$VNCKeys = Invoke-Command -ComputerName $computer -ScriptBlock {
Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |
Where-object DisplayName -like "VNC V*" |
Select-Object DisplayName, DisplayVersion, Publisher, UninstallString, #{Name=‘ComputerName‘;Expression={$computer}}
}#end of remote command
if ($VNCKeys -ne $null){
forEach($VNCKey in $VNCKeys){
[void]$results.Add($VNCKey)
}
}
}
$results | Sort-Object ComputerName | Export-CSV -path c:\VNCInstalled.csv -NoTypeInformation

Powershell Set ManagedPipeline for all Application Pools

I've got a command that can list all app pools on a machine:
Get-WmiObject -namespace "root/MicrosoftIISv2" -class IIsApplicationPool |Select-Object -property #{N="Name";E={$name = $_.Name; $name.Split("/")[2] }} | Format-Table
I want to set the managedpipeline of every app pool on the box. I've tried this:
Get-WmiObject -namespace "root/MicrosoftIISv2" -class IIsApplicationPool |Select-Object -property #{N="Name";E={$name = $_.Name; $name.Split("/")[2] }} | ForEach-Object {cmd /c "c:\windows\system32\inetsvr\appcmd.exe set apppool $name /managedPipleineMode:"Classic"'}
This is giving me a "cannot find the path specified" error. Any ideas how I can this to work?
In order to set the Managed Pipeline mode (or any property of the AppPool), you need to use Set-ItemProperty. But it gets more fun than that:
Set-ItemProperty takes a Path as its input. Get-ChildItem will
return you a collection of ConfigurationElement objects, not Path
strings.
ManagedPipelineMode is internally stored as an integer, so
you have to know the correct "magic" number to pass in.
Fortunately, that is documented here, in the "Remarks" section.
This did the trick for me:
Import-Module WebAdministration
Get-ChildItem IIS:\AppPools |
Select-Object -ExpandProperty PSPath |
ForEach-Object { Set-ItemProperty $_ ManagedPipelineMode 1 }
following the documentation :
$iisAppPoolName = "MyPool"
$appPool = New-WebAppPool -Name $iisAppPoolName
$appPool.managedPipelineMode = "Classic"
$appPool |Set-Item
I tested, IIS 8.0, Windows server 2012, and it works.
If you're on Powershell V2 I would use the WebAdministration module e.g. from an elevated prompt:
Import-Module WebAdministration
Get-ChildItem IIS:\AppPools | Foreach {$_.ManagedPipelineMode = 'Classic'}