Powershell Novice - Get all httpd processes and loop through them - powershell

How do I get all processes in power-shell and loop through them and restart them if their memory has reached a X threshold?
For example, I know of this command
PS C:\Users\me> gwmi -ComputerName "localhost" -Class Win32_PerfFormattedData_PerfProc_Process -Filte
r "name like 'httpd%'"
This will give me all httpd processes (httpd, httpd#1, ...).
I would like to loop through these and check if they memory consumption threshold has been reached and if so, restart that process.
My question is primarily how to write the loop, not about how to stop and restart the service. Please explain as I never wrote a power-shell script before.
UPDATE:
I added more information to better explain what my issue is. Below code has comments showing where is see problems (PROBLEM 1 and PROBLEM 2):
$ServiceExe="httpd#1"
$ServiceEXE2="httpd"
# Service to restart ('short' service name from Service's property)
$Service="httpd.exe"
# Working set threshold of 0.2 GB
$Threshold = 200000000
# Get service processes 'httpd' and 'httpd#1'
$Process = Get-WmiObject -ComputerName "localhost" -Class Win32_PerfFormattedData_PerfProc_Process -Filter "Name='$ServiceExe'"
$Process2 = Get-WmiObject -ComputerName "localhost" -Class Win32_PerfFormattedData_PerfProc_Process -Filter "Name='$ServiceExe2'"
# Get working set memory usage for both processes and convert to GB
$wsm = $Process.WorkingSet /1024/1024/1024;
$wsm2 = $Process2.WorkingSet /1024/1024/1024;
$thgb = $Threshold/1024/1024/1024
# Format to 3 dec places
$Fwsm = $("{0:0.000}" -f $wsm);
$Fthgb =$("{0:0.000}" -f $thgb);
echo $("WorkingSet : "+ $Fwsm + " GB / Threshold : " + $Fthgb + " GB.") >> C:\temp\pslog.txt;
if($Process.workingset -gt $Threshold)
{
#PROBLEM 1: THIS WILL ONLY EXECUTE ONCE PROCESS CAPS MAX MEMORY (2GB)
stop-process -name $Service -force
stop-Service apache2.2;
start-Service apache2.2;
echo "Restarted" >> C:\temp\pslog.txt;
}
else
{
#PROBLEM 2: THIS WILL NEVER EXECUTE FOR SOME REASON
$delta = $("{0:0.000}" -f ($thgb - $wsm));
echo $("No Restarting as " + $delta + " GB remains.");
}
I was thinking to replace variables holding process 'httpd' and 'httpd#1' with a single set that I could loop through but realized that Apache may have more than 2 'httpd' processes (i.e. 'httpd', 'httpd#1', 'httpd#2', 'httpd#3', 'httpd#4', ... ). Plus, I discovered that I have the 2 problems marked in the code in update above.
Any idea what I am doing wrong in the code above and how to rewrite it to use a loop to loop through all apache processes that might be initiated, not just 2 like in example above.
UPDATE 2:
So, I rewrote my ps script and I got most of it working but I am running into an issue, hopefully last one.
In my power-shell script, I have defined variable that has my service name like this:
$ServiceName = "APACHESRV[DBx14]";
APACHESRV[DBx14] service exists in my Windows->Services.
Then in my power-shell script, I use stop-service, start-service to start the service:
echo $("Stopping: " + $ServiceName) >> C:\temp\pslog.txt;
stop-Service $ServiceName;
echo $("Starting: " + $ServiceName) >> C:\temp\pslog.txt;
start-Service $ServiceName;
$stamp = Get-Date
echo $($stamp + " Started: " + $ServiceName) >> C:\temp\pslog.txt;
This echoes everything properly and power-shell shows no errors (red-lines), however my service is not started.
Much appreciated

At this point, I think you are just missing your loop. You are retrieving the two process objects, but you are only working on the first one. I took the liberty of restructuring it to work on all http processes. Try this:
# Working set threshold of 0.2 GB
$Threshold = 200000000
$thgb = $Threshold/1024/1024/1024
$Fthgb =$("{0:0.000}" -f $thgb)
# Get service processes 'httpd' and 'httpd#1' Get-WmiObject -ComputerName "localhost" -Class Win32_PerfFormattedData_PerfProc_Process | where { $_.Name -like "httpd*" } | foreach {
$procobj = $_
# Get working set memory usage for both processes and convert to GB
$wsm = $procobj.WorkingSet /1024/1024/1024;
# Format to 3 dec places
$Fwsm = $("{0:0.000}" -f $wsm)
echo $("WorkingSet : "+ $Fwsm + " GB / Threshold : " + $Fthgb + " GB.") >> C:\temp\pslog.txt;
if($procobj.workingset -gt $Threshold)
{
stop-process -name $procobj.Name -force #check this, probably wrong name
stop-Service apache2.2;
start-Service apache2.2;
echo "Restarted" >> C:\temp\pslog.txt;
}
else
{
$delta = $("{0:0.000}" -f ($thgb - $wsm));
echo $("No Restarting as " + $delta + " GB remains.");
}
}

This should do it...
ps | where { $_.name -like "httpd*" } | foreach {
# interrogate the System.Diagnostics.Process object in $_ and do stuff with it
$_
}

Get all the httpd processes with the Get-Process cmdlet, loop through them with the ForEach-Object cmdlet:
# Set a threshold, eg. 120MB
$Threshold = 120 * 1024 * 1024
# Loop through the processes
Get-Process httpd* | ForEach-Object {
if($_.WS -gt $Threshold){
# Working set exceeded threshold, restart relevant service
}
}

Related

RDS User logoff Script Slow

With the help of the several online articles I was able to compile a powershell script that logs off all users for each of my RD Session hosts. I wanted something to be really gentle on logging off users and it writing profiles back to their roaming profile location on the storage system. However, this is too gentle and takes around four hours to complete with the amount of users and RDS servers I have.
This script is designed to set each RDS server drain but allow redirection if a server is available so the thought around this was within the first 15 minutes I would have the first few servers ready for users to log into.
All of this works but I would like like to see if there are any suggestions on speeding this up a little.
Here is the loop that goes through each server and logs users out and then sets the server logon mode to enabled:
ForEach ($rdsserver in $rdsservers){
try {
query user /server:$rdsserver 2>&1 | select -skip 1 | ? {($_ -split "\s+")[-5]} | % {logoff ($_ -split "\s+")[-6] /server:$rdsserver /V}
Write-Host "Giving the RDS Server time"
Write-Progress "Pausing Script" -status "Giving $rdsserver time to settle" -perc (5/(5/100))
Start-Sleep -Seconds 5
$RDSH=Get-WmiObject -Class "Win32_TerminalServiceSetting" -Namespace "root\CIMV2\terminalservices" -ComputerName $rdsserver -Authentication PacketPrivacy -Impersonation Impersonate
$RDSH.SessionBrokerDrainMode=0
$RDSH.put() > $null
Write-Host "$rdsserver is set to:"
switch ($RDSH.SessionBrokerDrainMode) {
0 {"Allow all connections."}
1 {"Allow incoming reconnections but until reboot prohibit new connections."}
2 {"Allow incoming reconnections but prohibit new connections."}
default {"The user logon state cannot be determined."}
}
}
catch {}
}
Not sure how many Servers you have but if its less than 50 or so you can do this in parallel with PSJobs. You'll have to wrap your code in a scriptblock, launch each server as a separate job, then wait for them to complete and retrieve any data returned. You won't be able to use Write-Host when doing this but I've swapped those to Out-Files. I also didn't parse out your code for collecting your list of servers but I'm going to assume that works and you can have it return a formatted list to a variable $rdsservers. You'll probably also want to modify the messages a bit so you can tell which server is which in the log file, or do different logs for each server. If you want anything other than the names of jobs to hit the console you'll have to output it with Write-Output or a return statement.
$SB = {
param($rdsserver)
Start-Sleep -Seconds 5
$RDSH=Get-WmiObject -Class "Win32_TerminalServiceSetting" -Namespace "root\CIMV2\terminalservices" -ComputerName $rdsserver -Authentication PacketPrivacy -Impersonation Impersonate
$RDSH.SessionBrokerDrainMode=0
$RDSH.put() > $null
"$rdsserver is set to:" | out-file $LogPath #Set this to whatever you want
switch ($RDSH.SessionBrokerDrainMode) {
0 {"Allow all connections." | out-file $LogPath}
1 {"Allow incoming reconnections but until reboot prohibit new connections." | out-file $LogPath}
2 {"Allow incoming reconnections but prohibit new connections." | out-file $LogPath}
default {"The user logon state cannot be determined." | out-file $LogPath}
}
foreach ($server in $rdsservers){
Start-Job -Scriptblock -ArgumentList $server
}
Get-Job | Wait-Job | Receive-Job
The foreach loop launches the jobs and then the last line waits for all of them to complete before getting any data that was output. You can also set a timeout on the wait if there is a chance your script never completes. If you've got a ton of boxes you may want to look into runspaces over jobs as they have better performance but take more work to use. This Link can help you out if you decide to go that way. I don't have an RDS deployment at the moment to test on so if you get any errors or have trouble getting it to work just post a comment and I'll see what I can do.
I have something ready for testing but it may break fantastically. You wizards out there may look at this and laugh. If i did this wrong please let me know.
$Serverperbatch = 2
$job = 0
$job = $Serverperbatch - 1
$batch = 1
While ($job -lt $rdsservers.count) {
$ServerBatch = $rdsservers[$job .. $job]
$jobname = "batch$batch"
Start-job -Name $jobname -ScriptBlock {
param ([string[]]$rdsservers)
Foreach ($rdsserver in $rdsservers) {
try {
query user /server:$rdsserver 2>&1 | select -skip 1 | ? {($_ -split "\s+")[-5]} | % {logoff ($_ -split "\s+")[-6] /server:$rdsserver /V}
$RDSH=Get-WmiObject -Class "Win32_TerminalServiceSetting" -Namespace "root\CIMV2\terminalservices" -ComputerName $rdsserver -Authentication PacketPrivacy -Impersonation Impersonate
$RDSH.SessionBrokerDrainMode=0
$RDSH.put() > $null
}
catch {}
} -ArgumentList (.$serverbatch)
$batch += 1
$Job = $job + 1
$job += $serverperbatch
If ($Job -gt $rdsservers.Count) {$Job = $rdsservers.Count}
If ($Job -gt $rdsservers.Count) {$Job = $rdsservers.Count}
}
}
Get-Job | Wait-Job | Receive-Job

PowerShell Job throttle blocking the server

I'm experiencing a strange issue with our script server being overloaded and running out of resources. We have a script that copies data from one location to another, this is defined in a large input file that contains over 200 lines of text in the format 'Source path, Destination path'.
We are now in the process of trying to throttle the maximum jobs we kick of at once and I think it's working fine. But for some reason or another we're still running out of resources on the server when the input file contains over 94 lines. This became apparent after some testing.
We tried to upgrade our Windows 2008 R2 server with PowerShell 4.0 to 4 processors and 8 GB of RAM, but no luck. So I assume my throttling isn't working as designed.
Error code:
Insufficient system resources exist to complete the requested service.
The code:
$MaxThreads = 4
$FunctionFeed = Import-Csv -Path $File -Delimiter ',' -Header 'Source', 'Destination'
$Jobs=#()
Function Wait-MaxRunningJobs {
Param (
$Name,
[Int]$MaxThreads
)
Process {
$Running = #($Name | where State -eq Running)
while ($Running.Count -ge $MaxThreads) {
$Finished = Wait-Job -Job $Name -Any
$Running = #($Name | where State -eq Running)
}
}
}
$ScriptBlock = {
Try {
Robocopy.exe $Using:Line.Source $Using:Line.Destination $Using:Line.File /MIR /Z /R:3 /W:15 /NP /MT:8 | Out-File $Using:LogFile
[PSCustomObject]#{
Source = if ($Using:Line.Source) {$Using:Line.Source} else {'NA'}
Target = if ($Using:Line.Destination) {$Using:Line.Destination} else {'NA'}
}
}
Catch {
"Robocopy | ERROR: $($Error[0].Exception.Message)" |
Out-File -LiteralPath $Using:LogFile
throw $($Error[0].Exception.Message)
}
}
ForEach ($Line in $FunctionFeed) {
$LogParams = #{
LogFolder = $LogFolder
Name = $Line.Destination + '.log'
Date = 'ScriptStartTime'
Unique = $True
}
$LogFile = New-LogFileNameHC #LogParams
' ' >> $LogFile # Avoid not being able to write to log
$Jobs += Start-Job -Name RoboCopy -ScriptBlock $ScriptBlock
Wait-MaxRunningJobs -Name $Jobs -MaxThreads $MaxThreads
}
if ($Jobs) {
Wait-Job -Job $Jobs
$JobResults = $Jobs | Receive-Job
}
Am I missing something here? Thank you for your help.
You're using background jobs, which actually run in remote sessions on the local machine. Remote sessions are intentionally resource restricted, according to settings set in the session configuration. You can check the current settings using
Get-PSSessionConfiguration
And adjust the settings to increase the resources available to the sessions with
Set-PSSessionConfiguration
You may need to do some testing to determine exactly what resource limit you're hitting, and what adjustments need to be made for this particular application to work.
Fixed the problem by enlarging the MaxMemoryPerShellMB for remote sessions from 1GB to 2 GB as described here. Keep in mind that Start-Job is using a remote PowerShell session as mjolinor already indicated, so this variable is applicable to PowerShell jobs.
Solution:
# 'System.OutOfMemoryException error message' when running Robocopy and over 94 PowerShell-Jobs:
Get-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB # Default 1024
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 2048
# Set PowerShell plugins memory from 1 GB to 2 GB
Get-Item WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB # Default 1024
Set-Item WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB 2048
Restart-Service winrm

Powershell Wait for service to be stopped or started

I have searched both this forum and through google and can't find what I need.
I have a quite large script and I'm looking for some code that will check if the service is started or stopped before proceeding to the next step.
The function it self need to loop untill it's either stopped or started (Going to have a function for Stopped and one for Started).
In total 4 services which almost have the same name, so Service Bus * can be used as a wildcard.
I couldn't get the 'count' strategy, that Micky posted, to work, so here is how i solved it:
I created a function, that takes a searchString (this could be "Service Bus *") and the status that i expect the services should reach.
function WaitUntilServices($searchString, $status)
{
# Get all services where DisplayName matches $searchString and loop through each of them.
foreach($service in (Get-Service -DisplayName $searchString))
{
# Wait for the service to reach the $status or a maximum of 30 seconds
$service.WaitForStatus($status, '00:00:30')
}
}
The function can now be called with
WaitUntilServices "Service Bus *" "Stopped"
or
WaitUntilServices "Service Bus *" "Running"
If the timeout period is reached, a not so graceful exception is thrown:
Exception calling "WaitForStatus" with "2" argument(s): "Time out has expired and the operation has not been completed."
In addition to the answer of mgarde this one liner might be useful if you just want to wait for a single service (also inspired by a post from Shay Levy):
(Get-Service SomeInterestingService).WaitForStatus('Running')
The following will loop and verify the status of the given services until the number of services with the "Running" state is equal to zero (hence they are stopped), so you can use this if you are waiting for services to Stop.
I've added a $MaxRepeat variable, which will prevent this from running for ever. It will run 20 times max as defined.
$services = "Service Bus *"
$maxRepeat = 20
$status = "Running" # change to Stopped if you want to wait for services to start
do
{
$count = (Get-Service $services | ? {$_.status -eq $status}).count
$maxRepeat--
sleep -Milliseconds 600
} until ($count -eq 0 -or $maxRepeat -eq 0)
I had to tweak this a bit with multiple counters because this service purposely starts and stops slowly. The original script got me on the right track. I had to wait for the service to be in a completely stopped status before I could move on because I'm actually restarting that same service.
You could probably remove the "sleep," but I don't mind leaving it in.
You could probably remove everything and just use the $stopped variable. :)
# change to Stopped if you want to wait for services to start
$running = "Running"
$stopPending = "StopPending"
$stopped = "Stopped"
do
{
$count1 = (Get-Service $service | ? {$_.status -eq $running}).count
sleep -Milliseconds 600
$count2 = (Get-Service $service | ? {$_.status -eq $stopPending}).count
sleep -Milliseconds 600
$count3 = (Get-Service $service | ? {$_.status -eq $stopped}).count
sleep -Milliseconds 600
} until ($count1 -eq 0 -and $count2 -eq 0 -and $count3 -eq 1)
In my Azure build/deployment pipelines I use it like this to start and stop services (after already having sent a 'Stop' command asynchronously before) and which works for all transitional states like Starting, Stopping, Pausing and Resuming (which are called StartPending, StopPending, PausePending and ContinuePending in the status enumeration ServiceControllerStatus).
# Wait for services to be stopped or stop them
$ServicesToStop | ForEach-Object {
$MyService = Get-Service -Name $_ -ComputerName $Server;
while ($MyService.Status.ToString().EndsWith('Pending')) {
Start-Sleep -Seconds 5;
$MyService.Refresh();
};
$MyService | Stop-Service -WarningAction:SilentlyContinue;
$MyService.Dispose();
};
This needs a traditional powershell to function on a remote server, the cmdlet of pwsh.exe does not include parameter -ComputerName.
In my opinion no counters are needed as only transitional states cause the cmdlet to fail and they change anyway to one of the supported states in the near future (maximum 125 seconds for a Stop command).
To add more details to my response to #Christoph
here is a script i recently created to stop services and ensure the processes are also stopped. in our case the processes were predictable. it may be necessary to do more work to get the service/processid mapping if you have multiple services running off the same executeable.
$MaxWait = 180 #seconds
$ServiceNames = "MyServiceName*"
$ProcName = 'MyServiceProcName' #for the services
$sw = [System.Diagnostics.Stopwatch]::StartNew() # to keep track of
$WaitTS = (New-TimeSpan -Seconds $MaxServiceWait) #could also use a smaller interval if you want more progress updates
$InitialServiceState = get-service $ServiceNames | select Name,Status,StartType
write-Host "$ENV:COMPUTERNAME Stopping $ServiceNames"
$sw.Restart()
$Services = #()
$Services += Get-Service $ServiceNames | where Status -EQ Running | Stop-Service -PassThru -NoWait #nowait requires powershell 5+
$Services += Get-Service $ServiceNames | where Status -Like *Pending
#make sure the processes are actually stopped!
while (Get-Process | where Name -Match $ProcName)
{
#if there were services still running
if ($Services) {
Write-Host "$ENV:COMPUTERNAME ...waiting up to $MaxServiceWait sec for $($Services.Name)"
#wait for the service to stop
$Services.WaitForStatus("Stopped",$WaitTS)
}
#if we've hit our maximum wait time
if ($sw.Elapsed.TotalSeconds -gt $MaxServiceWait) {
Write-Host "$ENV:COMPUTERNAME Waited long enough, killing processes!"
Get-Process | where name -Match $ProcName | Stop-Process -Force
}
Start-Sleep -Seconds 1
#get current service state and try and stop any that may still be running
#its possible that another process tried to start a service while we were waiting
$Services = #()
$Services += Get-Service $ServiceNames | where Status -EQ Running | Stop-Service -PassThru -NoWait #nowait requires powershell 5+
$Services += Get-Service $ServiceNames | where Status -Like *Pending
}

Stop and then start a process in powershell

I would like to stop/kill a certain process and then start it again after I am done doing what I have to do.
This is what I already have.
Clear-host
$processes = Get-Process devenv
$processes.Count
if($processes.Count -gt 1)
{
$i = 0
Write-host "There are multiple processes for devenv."
foreach($process in $processes)
{
$i++
$i.ToString() + '. ' + $process.MainWindowTitle
}
$in = Read-host "Give a number of the process to kill: "
write-host
write-host "killing and restarting: " + $processes[$in-1].MainWindowTitle
$processes[$in-1].Kill()
$processes[$in-1].WaitForExit()
$processes[$in-1].Start()
}
else
{
write-host "something else"
}
But the Start needs some parameter which I thought I could get from the process. But I'm not really sure I know what to give it.
The $processes[$in-1].Start() will not work. You need to capture the processinfo you are killing and start the same app again. You can get the process binary and commandline information using Win32_Process WMI class.
For example,
Clear-host
$processes = Get-Process notepad
$processes.Count
if($processes.Count -gt 1)
{
$i = 0
Write-host "There are multiple processes for notepad."
foreach($process in $processes)
{
$i++
$i.ToString() + '. ' + $process.MainWindowTitle
}
$in = Read-host "Give a number of the process to kill: "
write-host
write-host "killing and restarting: " + $processes[$in-1].MainWindowTitle
#Get the process details
$procID = $processes[$in-1].Id
$cmdline = (Get-WMIObject Win32_Process -Filter "Handle=$procID").CommandLine
$processes[$in-1].Kill()
$processes[$in-1].WaitForExit()
}
In the above example, I am using WMI to get the commandline information for a process selected. If that were a notepad process with some open text file, the commandline for that process would look like "C:\WINDOWS\system32\NOTEPAD.EXE" C:\Users\ravikanth_chaganti\Desktop\debug.log
Now, all you need to do is: Invoke that commandline somehow (this part is not there in example I wrote). A very blunt way to do that is:
Start-Process -FilePath $cmdline.Split(' ')[0] -ArgumentList $cmdline.Split(' ')[1]
But, in your case, there may not be any argument list.
Hope this gives you an idea. Other PowerShell experts may have a different & efficient approach. This is just a quick hack.

Powershell script cannot get applications list data from windows 7 machine

Recently, I made a script to list all the installed applications in local & remote machine & give the output in a structured manner in an excelsheet.
It looks like this:
$a = Read-Host "Enter machine name" | Out-File -filepath C:\machine.txt
$computerName = Get-Content C:\machine.txt
$a = New-Object -comobject Excel.Application
$a.visible = $True
$b = $a.Workbooks.Add()
$c = $b.Worksheets.Item(1)
$c.Cells.Item(1,1) = "Name"
$c.Cells.Item(1,2) = "Publisher"
$c.Cells.Item(1,3) = "InstalledDate"
$c.Cells.Item(1,4) = "Version"
$c.Cells.Item(1,5) = "UninstallString"
$d = $c.UsedRange
$d.Interior.ColorIndex = 19
$d.Font.ColorIndex = 11
$d.Font.Bold = $True
$i = 2
function Get-InstalledAppReg ([string]$ComputerName) {
$RegPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
$BaseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine", $ComputerName)
$OpenSubKey = $BaseKey.OpenSubKey($RegPath)
$i =2
$OpenSubKey.GetSubKeyNames() | ForEach {
$Path = "$RegPath\$_"
$BaseKey.OpenSubKey($Path).GetValue("DisplayName")
$BaseKey.OpenSubKey($Path).GetValue("Publisher")
$BaseKey.OpenSubKey($Path).GetValue("InstalledDate")
$BaseKey.OpenSubKey($Path).GetValue("Version")
$BaseKey.OpenSubKey($Path).GetValue("UninstallString")
$c.Cells.Item($i,1) = $BaseKey.OpenSubKey($Path).GetValue("DisplayName")
$c.Cells.Item($i,2) = $BaseKey.OpenSubKey($Path).GetValue("Publisher")
$c.Cells.Item($i,3) = $BaseKey.OpenSubKey($Path).GetValue("InstalledDate")
$c.Cells.Item($i,4) = $BaseKey.OpenSubKey($Path).GetValue("Version")
$c.Cells.Item($i,5) = $BaseKey.OpenSubKey($Path).GetValue("UninstallString")
$i ++
}
}
Get-InstalledAppReg($computerName)
$d.EntireColumn.AutoFit()
$b.SaveAs("c:\softhive.xlsx")
$b.Close()
$a.Quit()
Get-Process | Where { $_.Name -Eq "Excel" } | Kill
This script ran perfectly for all remote machines which has XP as a OS.
Problem started when I started running it in windows & machines remotely.
Initially it gave wrong path error, when I realized that for windows 7, I probably have to use
"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" instead of
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall".
With this different path, when I run the same script again, I get an error:
Exception calling "OpenRemoteBaseKey" with "2" argument(s): "The network path was not found.
"
At :line:24 char:62
$BaseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey( <<<< "LocalMachine", $ComputerName)
Probably, I need to change other things too in the script?
My machine, from where I run the script, is a windows XP SP3 machine.
Unfortunately the WMI Win32_Product class does not report all apps found in Control Panel's "Add or Remove Programs"...
The registry walk seems to be unavoidable, see:
http://powergui.org/thread.jspa?threadID=17068
http://learningpcs.blogspot.fr/2011/10/powershell-get-installed-software.html
Rather than comb the registry, I would use WMI for this. See Win32_Product and friends e.g.:
Get-WmiObject Win32_Product
Note that if I run this on my Windows 7 x64 system in a 64bit PowerShell prompt it shows all installed apps (32-bit and 64-bit):
Get-WmiObject Win32_Product| sort Vendor | Format-Table Name,InstallDate,Vendor
To see all the properties available execute:
Get-WmiObject Win32_Product | Select -First 1 | Format-List *
I remember a while back I did something like this at an IT firm and we simply searched the C: directory for the names of all programs ending in .exe, in order to optimize we would hone in on specific apps that we were looking for. We set up a batch that would pass or fail based on if what we wanted. Keep in mind this is a batch file, however the idea is similar.
echo ================= >>Software_Scan.txt
echo Below is a list of all wireless networks. Saved networks will be found in the Wireless Profiles folder
set filePath=
for /R "C:\Program Files (x86)" /D %%a in (*) do if exist "%%a\YahooMessenger.exe" set filePath=%%a& goto continue
:continue
if defined filePath echo %COMPUTERNAME% FAIL Yahoo Messenger >> Software_Scan.txt
if NOT defined filePath echo %COMPUTERNAME% PASS Yahoo Messenger >> Software_Scan.txt