Audit of Installed Apps & Patches

by dagr8tim at 2012-10-03 09:25:29

I’m working on two scripts that will be roughly interchangeable. One’s based around a command to display all installed windows updates and the other is based around displaying all installed programs. I’m running into some issues with the for each loop because I want to script to be able to hit a list of servers. I’m sure my question is on the simplistic side. But I’ve been staring at it for 2 days and want to get phase 1 done to have a proof of concept. Phase two will write the output to a single file that is appended rather than a file for each server. The output will be CSV friendly so that it can be throw into Excel and sorted to verify that different machines have consistent software & patching installed.


With the code the way it is, I’m pulling the information for the machine the script is ran on.

#Change $FileFolder to the path you want the script to read the input from and output the reports
# Leave training "" off of folder path
$FileFolder = "C:\temp\PowerShellOutput\Patches"
$OutputFile = "$FileFolder$Computers.txt"


# Use if you want to parse an input file for server names
# Input file must be in the path above with the filename listed below
# $computers = Get-Content -Path "$FileFolder!list.txt"

# Use if you want to manually list computers to parse in the script
#Begin Internal Parse Section

#Use if you want to point at a single Computer
$Computers = "localhost"

#Use if you want to point at multiple Computers
#$Computers = (
#"localhost",
#"Server1",
#"Server2",
#"."
#)
# End Internal Parse Section

#foreach ($cname in $Computers)
#}
Get-WmiObject Win32_Product | Sort-Object Vendor, Name ` | Format-Table Vendor, Name, Version -groupBy Vendor | Format-Table -Property $a -wrap | Out-File "$OutputFile"
#}



The other script replaces the Get-WmiObject with the following code:
# Begin Patch Listing
$Session = New-Object -ComObject Microsoft.Update.Session
$Searcher = $Session.CreateUpdateSearcher()
$HistoryCount = $Searcher.GetTotalHistoryCount()
$Searcher.QueryHistory(1,$HistoryCount) | Select-Object Date, Title, Description | Format-Table -Property $a -wrap | Out-File "$OutputFile"
# End Patch Listing


The end goal is to write 1 script and be able to use it with two processes. I’m not sure if this or the remoting area is the proper place for it, but I’ll start here.
by poshoholic at 2012-10-03 10:23:25
A few points to help you take this further:

1. You need to tell Get-WmiObject which computers you want it to retrieve information from. Get-WmiObject has a -ComputerName parameter for that purpose. Pass your collection of computer names to that parameter, like this:
Get-WmiObject Win32_Product -ComputerName $Computers
Then you don’t even need the foreach loop.

2. You need to remove the backtick at the end of the Sort-object command. That isn’t necessary if you have the pipeline all on one line.

Once you’ve made those changes, if you’re still having issues come back and share the details and we’ll see how we can help take it further.
by JeffH at 2012-10-03 10:29:30
So you want to take a list of computers and run the WMI and MicrosoftUpdate commands on each, writing all results to a single file? If you want to do this from your desktop, this is going to take remoting. While you could use -Computername for the WMI part, the MS Update will have to run ON the remote computer which you can do using Invoke-Command. The other thing you may want to consider, although it adds complexity, is to run the WMI query as a job. Querying Win32_Product can take a long time, especially on an older system. A lot depends on what you want to do with the data.

Right now you are creating formatted text reports. Perhaps later you plan on using Export-CSV.
by dagr8tim at 2012-10-03 10:57:53
[quote="poshoholic"]Get-WmiObject Win32_Product -ComputerName $Computers[/quote][quote="poshoholic"]A few points to help you take this further:

1. You need to tell Get-WmiObject which computers you want it to retrieve information from. Get-WmiObject has a -ComputerName parameter for that purpose. Pass your collection of computer names to that parameter, like this:
Get-WmiObject Win32_Product -ComputerName $Computers
Then you don’t even need the foreach loop.

2. You need to remove the backtick at the end of the Sort-object command. That isn’t necessary if you have the pipeline all on one line.

Once you’ve made those changes, if you’re still having issues come back and share the details and we’ll see how we can help take it further.[/quote]

Thanks, I had discovered the back tick & removed it on my own. Now I’m going back to do some research because I have a slight understanding as to the other part.
by ArtB0514 at 2012-10-03 12:13:09
Please be careful when using Win32_Product. On newer operating systems it forces an automatic invocation of Windows Installer to performa a consistency check. This could have unintended consequences. Double check http://support.microsoft.com/kb/974524 for more details.
by dagr8tim at 2012-10-03 12:23:09
[quote="ArtB0514"]Please be careful when using Win32_Product. On newer operating systems it forces an automatic invocation of Windows Installer to performa a consistency check. This could have unintended consequences. Double check http://support.microsoft.com/kb/974524 for more details.[/quote]

It looks like I can get the same results by using the following line. The only reason I used that one was the ability to sort by vendor. Also, the below line works on 32 bit OS’s but won’t display 64 bit OS’s completely.

gp HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall* |Select DisplayName, DisplayVersion, Publisher, InstallDate | Format-Table -Property $a -wrap | Out-File "$OutputFile"
by Lembasts at 2012-10-03 14:26:21
[quote="ArtB0514"]Please be careful when using Win32_Product. On newer operating systems it forces an automatic invocation of Windows Installer to performa a consistency check. This could have unintended consequences. Double check http://support.microsoft.com/kb/974524 for more details.[/quote]

I would wholeheartedly endorse that. Win32_product not only is a performance dog but not the best at determining installed software. I use the uninstall registry key. I have compared the results of the uninstall registry key vs win32_product and found the registry to be far more accurate. A script using win32_product ran for 30 minutes, the change to use the registry key ran for 30 seconds.
by coderaven at 2012-10-03 14:34:56
Any time you go out and gather information with PowerShell, the best way to output it from your function or script using objects see Jeffery Hicks series on this here. The idea is if you output objects from your collection process, you can use any way that PowerShell can format data/object to get the data where and how you want it. For example, if you gather the information and output as an object like so

Get-InstalledSoftware | Export-CSV
Get-InstalledSoftware | ConvertTo-HTML
Get-InstalledSoftware | Foreach-Object { If ($
.Name -eq "BadSoftware" ) { <execute function to remove the software from that computer> }

See Jeffery Hicks post about it and start using custom objects :slight_smile: