how to format Powershell output in specific way? - powershell

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__"

Related

PowerShell 5.1 using ExpandedProperty but not getting desired output

Given:
PowerShell 5.1
If I run the following:
Get-Service -ComputerName WKSP000D1E3F -DisplayName *bit*
I get this:
Status Name DisplayName
------ ---- -----------
Stopped BDESVC BitLocker Drive Encryption Service
Stopped Check Point Bit... Check Point Endpoint Security Bitlo...
But I want to expand both Name and DisplayName, so I do this:
Get-Service -ComputerName WKSP000D1E3F -DisplayName *bit* | Select -ExpandProperty Name
and get this:
BDESVC
Check Point Bitlocker Management
How do I get all the columns with column headers, like the first output above except without ellipses?
Status Name DisplayName
------ ---- -----------
Stopped BDESVC BitLocker Drive Encryption Service
Stopped Check Point Bit... Check Point Endpoint Security Bitlo...
Get-Service -DisplayName *wmi*
Status Name DisplayName
------ ---- -----------
Stopped wmiApSrv WMI Performance Adapter
Running WMIRegistrationSe… Intel(R) Management Engine WMI Provid…
I find myself using | Out-GridView if desired output isn't being shown.
Get-Service -DisplayName *wmi* |
Out-GridView
Out-GridView Result
Alternatively, you could use | Format-List if you're not set on using Table View
Get-Service -DisplayName *wmi* |
Format-List
Name : wmiApSrv
DisplayName : WMI Performance Adapter
Status : Stopped
DependentServices : {}
ServicesDependedOn : {}
CanPauseAndContinue : False
CanShutdown : False
CanStop : False
ServiceType : Win32OwnProcess
Name : WMIRegistrationService
DisplayName : Intel(R) Management Engine WMI Provider Registration
Status : Running
DependentServices : {}
ServicesDependedOn : {}
CanPauseAndContinue : False
CanShutdown : False
CanStop : True
ServiceType : Win32OwnProcess
If you only want Status,Name,DisplayName headers,
Get-Service -DisplayName *wmi* |
Select-Object -Property Status, Name, DisplayName |
Format-List
Status : Stopped
Name : wmiApSrv
DisplayName : WMI Performance Adapter
Status : Running
Name : WMIRegistrationService
DisplayName : Intel(R) Management Engine WMI Provider Registration
The ... are just a display problem (the data is there in full).
If you use Select-Object's -ExpandProperty parameter, you by definition only get property values, and since these values are strings in your case, that's all you get (albeit without truncation).
To prevent the for-display truncation, pipe to Format-Table -AutoSize, though note that this requires PowerShell to collect all output from the input command first, before starting to display output (see next section for alternatives).
Get-Service -ComputerName WKSP000D1E3F -DisplayName *bit* | Format-Table AutoSize
Note: Since the console (terminal) window width is always the limiting factor, -AutoSize can result in fewer columns getting displayed.
If possible, you can exclude columns that aren't of interest, by specifying only those that are, via -Property (you may omit this parameter name, becasue it is positionally implied); e.g.:
Get-Service -ComputerName WKSP000D1E3F -DisplayName *bit* |
Format-Table -Property Status, Name -AutoSize
Alternatives that avoid -AutoSize and its collect-everything-first behavior:
Use -Wrap, which doesn't truncate, but uses (artificial) line breaks to spread values that don't fit into the column across multiple lines.
Get-Service -ComputerName WKSP000D1E3F -DisplayName *bit* | Format-Table -Wrap
If you know the maximum width of a column of interest ahead of time, you can use a calculated property to prescribe a column's width; e.g.:
Get-Service -ComputerName WKSP000D1E3F -DisplayName *bit* |
Format-Table Status,
#{ Expression='Name'; Width=40 },
DisplayName

output not a trimmed value and causing the output error

I have written few powershell command to perform some auditing on HyperV Clusters. The command works fine, But can anyone help me to trim the output, so that I can collect what I need ?
##Audit-CreatingDC
$AuditDC = Invoke-Command -ComputerName $ComputerName {Get-ChildItem -Path HKLM:\cluster\resources -recurse | get-itemproperty -name CreatingDC -erroraction 'silentlycontinue'}| ft CreatingDC,PSComputerName
####Audit-iSCSI
#Show which hosts are not communicating to the storage with the ‘-s’ and where there are duplicated targets:
$AuditISCSI = Invoke-Command -ComputerName $ComputerName { get-iscsisession } | FT PSComputerName, InitiatorPortalAddress, IsConnected -autosize
######Discover checkdsk errors - "Scan Needed". Execute using txt of one node from each cluster.
$AuditCHKDSK = Invoke-Command -ComputerName $ComputerName { get-volume | Where-Object –FilterScript { $_.HealthStatus -eq "Scan Needed" }} | FT PSComputerName, FileSystem, HealthStatus -autosize
And the output for each is below
CreatingDC PSComputerName
---------- --------------
\\dc-sc-02.oim.corp.com slcoc037
PSComputerName InitiatorPortalAddress IsConnected
-------------- ---------------------- -----------
slcoc037 10.214.61.107 True
PSComputerName FileSystem HealthStatus
-------------- ---------- ------------
slcoc037 CSVFS 1
But I need the output for above in this format
\\dc-sc-02.oim.corp.com
10.241.81.107
CSVFS 1
Can anyone help me to trim these 3 commands ?
You probably already know that almost all powershell outputs are objects. Objects have properties. Displaying a particular property would use the syntax $Object.Propertyname. In your case, CreatingDC is a property of the $AuditDC Variable object. Applying that logic, all you need to do is, display it like this:
$AuditDC.CreatingDC
$AuditISCSI.InitiatorPortalAddress
$AuditCHKDSK.FileSystem

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

In Powershell, how to make an array with custom objects of different sizes/types?

im writing a simple function in order to list subfolders of a particular server share. My problem is that i want all informations in the same array of custom objects. Here is what i did:
function Get-tDollar {
param(
[string]$srv
)
$share = Get-WmiObject -ComputerName $srv -Class Win32_Share -Filter "Name='share`$'" | Select __SERVER,#{n="nPath";e={"\\" + $_.__SERVER + ($_.Path -replace "C:")}}
$inc = 0
Get-ChildItem $share.nPath | % {
$inc++
Add-Member -InputObject $share -MemberType NoteProperty -Name "folder_$inc" -Value $_.Name
}
$share
}
And i use this function like that: #("srv1","srv2") | % { Get-TDollar -srv $_ }
All works fine when the number of subfolders is the same, but when its different, the array contains only the number of folders listed in the first share. For example, for my two first servers, i have this output:
__SERVER nPath folder_1
-------- ----- ------------
srv1 \\srv1\share scripts
srv2 \\srv2\share scripts
But, because srv2 has more folders than the first server, i want this output:
__SERVER nPath repertoire_1 repertoire_2
-------- ----- ------------ ------------
srv1 \\srv1\share scripts
srv2 \\srv2\share scripts config
I know i can first calculate which server has the higher number of folders and then place it in fisrt position, but it seems there are enough lines for something like that. Is there a more efficient/elegant way to do that?
You're getting bitten by the default formatting gremlin:
http://blogs.msdn.com/b/powershell/archive/2006/04/30/586973.aspx
Get-tDollar |
foreach { $_ | format-table }

PowerShell's Write-Debug won't output arrays, but Write-Output does. Is this on purpose?

Shown below, an array works fine as input for Write-Output but not for Write-Debug (I expected them to be more similar than that).
PS C:\> [string[]]$test = #("test1", "test2", "test3")
PS C:\> Write-Output $test
test1
test2
test3
PS C:\> $DebugPreference = "Inquire"
PS C:\> Write-Debug $test
Write-Debug : Cannot convert 'System.String[]' to the type 'System.String' required by parameter 'Message'. Specified method is not supported.
At line:1 char:12
+ Write-Debug <<<< $test
+ CategoryInfo : InvalidArgument: (:) [Write-Debug], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.WriteDebugCommand
PS C:\>
I'm thinking this is just an unfortunate design, but hoping for a sensible explanation. Am I using Write-Debug correctly? If so, anyone have a favorite simple workaround?
I kept having the same problem, and none of the solutions I found above or anywhere else would work in the general case.
For example, the first answer above works only because the array is an array of strings. If it's an array of anything else, that solution breaks, and Write-Debug will output the object type, and not its value as one would expect.
Finally I found a general solution: The key point is to first convert the input object to a string using the Out-String command. Once everything is a string, the above solution works. Using "Out-String -stream" improves the output alignment.
Example:
PS C:\> gwmi win32_bios
SMBIOSBIOSVersion : 786F3 v01.34
Manufacturer : Hewlett-Packard
Name : Default System BIOS
SerialNumber : CZC8196Q8S
Version : HPQOEM - 20120709
PS C:\> gwmi win32_bios | ft -auto
SMBIOSBIOSVersion Manufacturer Name SerialNumber Version
----------------- ------------ ---- ------------ -------
786F3 v01.34 Hewlett-Packard Default System BIOS CZC8196Q8S HPQOEM - ...
PS C:\> $DebugPreference = "Continue"
PS C:\> gwmi win32_bios | ft -auto | Write-Debug
DEBUG: Microsoft.PowerShell.Commands.Internal.Format.FormatStartData
DEBUG: Microsoft.PowerShell.Commands.Internal.Format.GroupStartData
DEBUG: Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData
DEBUG: Microsoft.PowerShell.Commands.Internal.Format.GroupEndData
PS C:\> gwmi win32_bios | ft -auto | Out-String | Write-Debug
DEBUG: SMBIOSBIOSVersion Manufacturer Name SerialNumber Version
----------------- ------------ ---- ------------ -------
786F3 v01.34 Hewlett-Packard Default System BIOS CZC8196Q8S HPQOEM - ...
PS C:\> gwmi win32_bios | ft | Out-String -stream | Write-Debug
DEBUG:
DEBUG: SMBIOSBIOSVersi Manufacturer Name SerialNumber Version
DEBUG: on
DEBUG: --------------- ------------ ---- ------------ -------
DEBUG: 786F3 v01.34 Hewlett-Packard Default Syst... CZC8196Q8S HPQOEM - 201...
DEBUG:
DEBUG:PS C:\>
If you want write-debug to handle each one separately:
[string[]]$test = #("test1", "test2", "test3")
Write-Output $test
test1
test2
test3
$DebugPreference = "Inquire"
$test | Write-Debug
DEBUG: test1
DEBUG: test2
DEBUG: test3
Write-Debug is designed for outputting simple messages when debug preferences are set in a particular way. It takes only a string, not just anything like Write-Host does (and magically formats). You will have to format your output yourself into a single string.
You could combine Write-Host and Write-Debug if you have extra info to output before prompting the user:
if ($DebugPreference -ne 'SilentlyContinue') {
Write-Host 'such-and-such array:' $array
}
Write-Debug 'such-and-such array dumped'
Write-Host is used because it will always write to the console host, rather than to the script's output, as Write-Output does. If you where redirecting standard output of the script to a file, Write-Output would end up in the file, while Write-Host would still be seen in the console.
You could also try doing something like this if your array is of simply enough types that an automatic call to ToString() on them (if they're not strings already) gets you what you want:
$array = 'Alice','Bob','Charlie'
Write-Debug ([String]::Join("`n", $array))
Write-Debug:
Write-Debug [-Message] <string> [<CommonParameters>]
It expects a string. It is unable to convert a string array to a string as the error says. The reason why it expects a string is because it writes debug messages to the console from a script or command.
Note that Write-Output and Write-Host take an object:
Write-Output [-InputObject] <PSObject[]> [<CommonParameters>]
and
Write-Host [[-Object] <Object>] ...