Extract the values of a column and put them into an array - powershell

I want to find out how many java PIDs are running in order to stop them, put the values into an array, run it inside a loop and kill each process
Therefore I use the command
$a=ps | Where-Object -Property ProcessName -EQ -Value 'Java'`
Which shows me the following result:
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) **Id** ProcessName
------- ------ ----- ----- ----- ------ -- -----------
661 51 748504 421716 -282 **2008** java
331 22 358464 135808 -525 **14060** java
But I am stuck regarding how to extract the Id column values, in order to loop them later
Any help would be appreciated.
Many thanks

As Santiago Squarzon commented, you can directly pipe the output of Get-Process (alias ps) to Stop-Process:
ps Java | Stop-Process
In case you want to list the killed processes, you can use -PassThru:
ps Java | Stop-Process -PassThru
So in most cases you don't even need to write a loop.
If you still have a use case where you need to explicitly loop over the process IDs, you can do it as suggested by mclayton, using member-access enumeration:
$proc = ps Java
foreach( $pid in $proc.Id ) {
# do something with $pid
"PID: $pid"
}
# ...or even shorter:
(ps Java).Id.ForEach{
"PID: $_"
}

Related

How to use a function to get objects from a pipeline as strings?

Command that output the result in string instead of objects:
ls | Out-String -Stream
Output:
Directory: C:\MyPath\dir1
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2022-01-22 5:34 PM 0 1.txt
-a--- 2022-01-22 5:34 PM 0 2.txt
-a--- 2022-01-22 5:34 PM 0 3.txt
I tried to get the same result using a function:
function f {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline,
ValueFromPipelineByPropertyName)]
$Content
)
process {
$Content | Out-String -Stream
}
}
ls | f
However, the output is separated for each item:
Directory: C:\MyPath\dir1
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2022-01-22 5:34 PM 0 1.txt
Directory: C:\MyPath\dir1
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2022-01-22 5:34 PM 0 2.txt
Directory: C:\MyPath\dir1
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2022-01-22 5:34 PM 0 3.txt
How can I use the function to get the same result as the first command?
As Abraham pointed out in his comment, you could capture all objects coming from the pipeline first and then output the objects as a stream so that it is displayed properly in the console.
It's important to note that both examples displayed below, are not truly "streaming functions", as mklement0 has pointed out in his helpful answer, both functions are first collecting all input coming from pipeline and then outputting the objects as a stream of strings at once, rather than object by object.
function f {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
[object] $Content
)
begin { $output = [System.Collections.Generic.List[object]]::new() }
process { $output.Add($Content) }
end { $output | Out-String -Stream }
}
As an alternative to the advanced function posted above, the example below would also work because of how the automatic variable $input works in the end block by enumerating the collection of all input to the function:
function f { $input | Out-String -Stream }
I think this question requires a counter question:
Why do you want to get objects from a pipeline as strings?
I suspect that you either have a rare requirement or do not fully understand the PowerShell Pipeline
In general, I would avoid using Out-String in the middle of the pipeline¹ as although it can be called from the middle of a pipeline, it already formats the output similar to Format-Table which is usually done at the end of the stream. The point is also that it is hard to determine the width of the columns upfront without knowing what comes next.
With the exception mentioned by #mklement0:
There's one good reason to use Out-String -Stream: to make up for Select-String's useless behavior with non-string input objects. In fact, PowerShell ships with proxy function oss, which wraps Out-String -Stream - see also: GitHub issue #10726 and his helpful answer.
The Out-String -Stream parameter also doesn't help for this as all it does is breaking the multiline string into separate strings:
By default, Out-String outputs a single string formatted as you would see it in the console including any blank headers or trailing newlines. The Stream parameter enables Out-String to output each line one by one. The only exception to this are multiline strings. In that case, Out-String will still output the string as a single, multiline string.
(ls | Out-String -Stream).count
8
(ls | Out-String -Stream)[3]
Mode LastWriteTime Length Name
But if you really have a need to devaluate the PowerShell Objects (which are optimized for streaming) to strings (without choking the pipeline), you might actually do this:
function f {
[CmdletBinding()] param ([Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]$Content)
begin { $First = $True }
process {
if ($First) { $Content |Out-String -Stream |Select-Object -SkipLast 1 }
else { $Content |Out-String -Stream |Select-Object -Skip 5 |Select-Object -SkipLast 1 }
$First = $False
}
}
It shows what you are are trying to do, but as said, I would really recommend against this if you do not have a very good reason. The usual way to do this and respecting the pipeline is placing the Out-String outside your cmdlet at the end of the stream:
ls |f |Out-String -Stream
As you've experienced, calling Out-String -Stream for each input object doesn't work as intended, because - aside from being inefficient - formatting each object in isolation invariably repeats the header in the (table-)formatted output.
The solutions in Santiago's helpful answer are effective, but have the disadvantage of collecting all pipeline input first, before processing it, as the following example demonstrates:
function f { $input | Out-String -Stream }
# !! Output doesn't appear until after the sleep period.
& { Get-Item $PROFILE; Start-Sleep 2; Get-Item $PROFILE } | f
Note: Output timing is one aspect, another is memory use; neither aspect may or may not matter in a given use case.
To wrap a cmdlet call in streaming fashion, where objects are processed as they become available, you need a so-called proxy (wrapper) function that utilizes a steppable pipeline.
In fact, PowerShell ships with an oss function that is a proxy function precisely around Out-String -Stream, as a convenient shortcut to the latter:
# Streaming behavior via the built-in proxy function oss:
# First output object appears *right away*.
& { Get-Item $PROFILE; Start-Sleep 2; Get-Item $PROFILE } | oss
Definition of proxy function oss (wraps Out-String -Stream); function body obtained with $function:oss:
function oss {
[CmdletBinding()]
param(
[ValidateRange(2, 2147483647)]
[int]
${Width},
[Parameter(ValueFromPipeline = $true)]
[psobject]
${InputObject})
begin {
$PSBoundParameters['Stream'] = $true
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Out-String', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = { & $wrappedCmd #PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
}
process {
$steppablePipeline.Process($_)
}
end {
$steppablePipeline.End()
}
<#
.ForwardHelpTargetName Out-String
.ForwardHelpCategory Cmdlet
#>
}
Note:
Most of the body is generated code - only the name of the wrapped cmdlet - Out-String - and its argument - -Stream - are specific to the function.
See this answer for more information.

Garbage collection with COM objects in powershell

I've created the following function for use cleaning up all references to com objects at the end of a script:
function TrashCompactor ($reflist) {
foreach ($ref in $Reflist){
[System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) | out-null
[Runtime.InteropServices.Marshal]::FinalReleaseComObject($ref) | out-null
Remove-Variable $ref | out-null
}
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
Will Remove-variable work as I expected? Is there any harm to including [System.GC]::Collect()?
Yes, and no... as this...
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
... is a common and best practice.
Windows will always do a cleanup, but it's always clean up your environment when you are done.
As documented...
Clean Up Your PowerShell Environment by Tracking Variable Use
https://devblogs.microsoft.com/scripting/clean-up-your-powershell-environment-by-tracking-variable-use
And covered by this SO Q&A and accepted answer...
PowerShell release COM object
function Release-Ref ($ref) {
[System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$ref) | out-null
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
because I've noted that my comobject always stay alive, I think Powershell 2.0 is not able to remove comobject no more used.
[System.Runtime.InteropServices.Marshal]::ReleaseComObject( $ref )
and that SO is exactly what you are asking, so this question is really a duplicate.
My example, I use a prefix to my variable so they are easy to find and simple globally clean up.
# Assign results to a variable and output to the screen using variable squeezing
($ponMyShell = New-Object -com "Wscript.Shell")
($ponDate = Get-Date)
($ponProcess = Get-Process |
Select -First 3)
<#
# Results
Monday, 2 March, 2020 19:40:47
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
186 14 2648 6800 0.14 15336 0 aesm_service
465 27 24300 34064 0.33 27612 22 ApplicationFrameHost
158 8 1928 4848 0.02 14268 0 AppVShNotify
SpecialFolders CurrentDirectory
-------------- ----------------
System.__ComObject C:\Windows\system32
#>
Get-Variable -Name 'pon*'
<#
# Results
Name Value
---- -----
ponDate 02-Mar-20 19:46:59
ponMyShell System.__ComObject
ponProcess {System.Diagnostics.Process (aesm_service), System.Diagnostics.Process (ApplicationFrameHost), System.Diagnostics.Process (AppVShNotify)}
#>
# Clear resource environment
Get-PSSession |
Remove-PSSession
<#
# Results
#>
[System.Runtime.InteropServices.Marshal]::
ReleaseComObject([System.__ComObject]$ponMyShell) |
Out-Null
<#
# Results
#>
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
<#
# Results
#>
Get-Variable -Name 'pon*' |
ForEach { Get-Variable -Name $_ |
Remove-Variable -Force }
# Validate clean-up
Get-Variable -Name 'pon*'
<#
# Results
#>

how to format Powershell output in specific way?

I need to scan my network for specific processes on servers. I've done this script:
28..31 | ForEach-Object { Get-Process -ComputerName "192.168.0.$_" -Name svc* }
Now how can I format output so it shows on which IP address found process shown? Thank you.
I suggest switching to PowerShell's remoting, because:
it provides a framework for executing any command remotely - rather than relying on individual cmdlets to support a -ComputerName parameter and uses a firewall-friendly transport.
it will continue to be supported in PowerShell [Core] v6+, where the cmdlet-individual -ComputerName parameters aren't supported anymore; this obsolete remoting method uses an older, less firewall-friendly form of remoting that the - obsolete since v3 - WMI cmdlets also use (the latter were replaced by the CIM cmdlets).
It is therefore better to consistently use the firewall-friendly PowerShell remoting with its generic remoting commands (Invoke-Command, Enter-PSSession, ...).
If you use Invoke-Command to target (multiple) remote computers, the objects returned automatically contain and display the name of the originating computer, via the automatically added .PSComputerName property:
# Create the array of IP addresses to target:
$ips = 28..31 -replace '^', '192.168.0.'
# Use a *single* Invoke-Command call to target *all* computers at once.
# Note: The results will typically *not* reflect the input order of
# given IPs / computer names.
Invoke-Command -ComputerName $ips { Get-Process -Name svc* }
You'll see output such as the following - note the PSComputerName column:
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName PSComputerName
------- ------ ----- ----- ------ -- -- ----------- --------------
1013 18 7464 13732 52.72 8 0 svchost 192.168.0.30
...
Note that you can suppress automatic display of the .PSComputerName property with Invoke-Command's -HideComputerName parameter.
However, the property is still available programmatically, which allows you to do the following:
Invoke-Command -ComputerName $ips { Get-Process -Name svc* } -HideComputerName |
Format-Table -GroupBy PSComputerName
This yields display output grouped by computer name / IP:
PSComputerName: 192.168.0.30
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
1013 18 7464 13732 52.72 8 0 svchost
...
PSComputerName: 192.168.0.28
Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName
------- ------ ----- ----- ------ -- -- -----------
1007 17 7112 12632 65.45 11 0 svchost
...
If you wanted to sort by IP address before grouping, you could insert | Sort-Object { [version] $_.PSComputerName }[1] before the Format-Table call.
For sorting by computer names, just
| Sort-Object PSComputerName would do.
[1] Casting to [version] is a trick to ensure proper sorting of IP addresses; IP address strings can be interpreted as [version] (System.Version) instances, and such instances are directly comparable, using the numeric values of the version components (first by .MajorVersion, then by .MinorVersion, ...)
here's one way to do the job. [grin] what it does ...
builds the ip final octet range
sets the IPv4 base octets
builds the list of processes to search for
sets the "no response" text
iterates thru the 4th octet range
builds the IPv4 address
checks to see if it is online/responding
if so, gets the hostname
for my version of PoSh [win7, ps5.1] the Get-Process cmdlet will NOT accept an ip address. a hostname is required.
corrects for the damaged hostname returned when one uses ATT for inet service
creates an ordered hashtable to use for building the property list
builds the various props as needed
converts the hashtable to a PSCustomObject
sends that to the $Results collection
shows it on screen
sends it to a CSV file
here's the code ...
$4thOctetRange = 64..66
$IpBase = '192.168.1'
$ProcessList = #(
'AutoHotKey'
'BetterNotBeThere'
'DisplayFusion'
'Foobar2000'
)
$NoResponse = '__n/a__'
$Results = foreach ($4OR_Item in $4thOctetRange)
{
$Ip = '{0}.{1}' -f $IpBase, $4OR_Item
$Online = Test-Connection -ComputerName $Ip -Count 1 -Quiet
if ($Online)
{
# getting the hostname is REQUIRED by my version of Get-Process
# it will not accept an IP address
# version info = win7, ps5.1
# this may need adjusting for your returned hostname
# mine shows Hostname.attlocal.net
# that is not valid with any query i make, so i removed all after the 1st dot
$HostName = ([System.Net.Dns]::GetHostByAddress($Ip)).HostName.Split('.')[0]
}
else
{
$HostName = $NoResponse
}
$TempProps = [ordered]#{}
$TempProps.Add('IpAddress', $Ip)
$TempProps.Add('Online', $Online)
$TempProps.Add('HostName', $HostName)
foreach ($PL_Item in $ProcessList)
{
if ($TempProps['Online'])
{
# if the process aint found, the "SilentlyContinue" forces a $Null
# the "[bool]" then coerces that to a "False"
$TempProps.Add($PL_Item, [bool](Get-Process -ComputerName $HostName -Name $PL_Item -ErrorAction SilentlyContinue))
}
else
{
$TempProps.Add($PL_Item, $NoResponse)
}
}
# send the object out to the $Results collection
[PSCustomObject]$TempProps
}
# send to screen
$Results
# send to CSV file
$Results |
Export-Csv -LiteralPath "$env:TEMP\Senator14_RemoteProcessFinder.csv" -NoTypeInformation
truncated screen output ...
IpAddress : 192.168.1.65
Online : True
HostName : ZK_01
AutoHotKey : True
BetterNotBeThere : False
DisplayFusion : True
Foobar2000 : True
IpAddress : 192.168.1.66
Online : False
HostName : __n/a__
AutoHotKey : __n/a__
BetterNotBeThere : __n/a__
DisplayFusion : __n/a__
Foobar2000 : __n/a__
csv file content ...
"IpAddress","Online","HostName","AutoHotKey","BetterNotBeThere","DisplayFusion","Foobar2000"
"192.168.1.64","False","__n/a__","__n/a__","__n/a__","__n/a__","__n/a__"
"192.168.1.65","True","ZK_01","True","False","True","True"
"192.168.1.66","False","__n/a__","__n/a__","__n/a__","__n/a__","__n/a__"

Powershell killing processes - what's wrong with my string?

I know that working code is
Get-Process firefo* | Stop-Process
But my first guess was
Get-Process | findstr firefox | Stop-Process
It didn't work.
Stop-Process : The input object cannot be bound to any parameters for the command
either because the command does not take pipeline input or the input and its
properties do not match any of the parameters that take pipeline input.
At line:1 char:33
+ Get-Process | findstr firefox | Stop-Process
+ ~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: ( 1379 317... :PSObject) [Stop-Process], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.Commands.StopProcessCommand
I understand that string
1342 306 1228412 1279864 -1671 ...71,42 35912 firefox
is bad for process killing, but why?
PS C:\Users\adamg> Get-Process firefo*
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
1342 306 1228412 1279864 -1671 ...71,42 35912 firefox
The above works just fine, even with column headers in reply.
findstr is a commandline utility that produces string output. Get-Process outputs Process objects, which is what Stop-Process expects as input. It could also handle a list of process IDs, but it can't parse the formatted string from findstr.
In PowerShell you normally wouldn't use findstr anyway. Use a Where-Object filter instead:
Get-Process | Where-Object { $_.ProcessName -like '*firefox*' } | Stop-Process

Create Union of Multiple Tables in Powershell

I'm trying to create a union of multiple tables in Powershell to output in a user-friendly format as a report, similar to a UNION query in SQL.
I have the following code:
$ft = #{auto=$true; Property=#("MachineName", "Status", "Name", "DisplayName")}
$hosts = #("svr001", "svr002")
$report = #()
ForEach ($h in $hosts) {
$results = Get-Service -CN $h -Name MyService
$report += $results | Format-Table #ft
# Other things occur here, which is why I'm creating $report for output later.
}
Write-Output $report
The output of this code is as follows:
MachineName Status Name DisplayName
----------- ------ ---- -----------
svr001 Running MyService MyServiceDisplayName
MachineName Status Name DisplayName
----------- ------ ---- -----------
svr002 Running MyService MyServiceDisplayName
Since you simply add arrays to do a union in Powershell (i.e.,
$union = #(0, 1, 2) + #(3, 4, 5)), my initial thought was that I should
get the following output:
MachineName Status Name DisplayName
----------- ------ ---- -----------
svr001 Running MyService MyServiceDisplayName
svr002 Running MyService MyServiceDisplayName
In retrospect, I think I understand why I do not get this output, but I'm
unclear how to create a union of the two tables from the first output example into a single table as in the second, and I haven't been able to locate anything in the docs or examples online that would send me in the right direction.
Move the Format-Table to the last command. Format-* cmdlets create special format-objects that you can't work with manually so theres no point in saving them. When you save the result of Format-* to an array, you're saving the "report" which is why you get two tables in the output (array consists of two reports).
Collect the data first, then use Format-Table when you want to display the results.
$ft = #{auto=$true; Property=#("MachineName", "Status", "Name", "DisplayName")}
$hosts = #("svr001", "svr002")
$report = #()
ForEach ($h in $hosts) {
$results = Get-Service -ComputerName $h -Name MyService
$report += $results
# Other things occur here, which is why I'm creating $report for output later.
}
#Write-Output is not necessary as it is default behaviour
$report | Format-Table #ft
Sample output (used wuauserv as servicename):
MachineName Status Name DisplayName
----------- ------ ---- -----------
localhost Stopped wuauserv Windows Update
frode-pc Stopped wuauserv Windows Update
The Get-Service Cmdlet also supports an array of strings for the -ComputerName parameter. This works for me:
Get-Service -CN $hosts -Name MyService | Format-Table #ft
Sample Output using wuauserv:
MachineName Status Name DisplayName
----------- ------ ---- -----------
Tim-SRV1 Running wuauserv Windows Update
Tim-SRV2 Stopped wuauserv Windows Update