Powershell output formatting table - powershell

I have a line of script
`Connect-hpoa *servername* |get-HPOAServerName |format-list -property Hostname,ServerName`
that gives the following output:
Hostname : gblonblade1 ServerName : {#{Bay=1; ServerName=Absent;
SerialNumber=; Status=; Power=; UID=; Partner=}, #{Bay=2;
ServerName=GBLON1234.ops.test.net; SerialNumber=123456789
Status=OK; Power=On; UID=Off; Partner=}, #{Bay=3;
ServerName=GBLON5678; SerialNumber=987654321; Status=OK; Power=On;
UID=Off; Partner=}, #{Bay=4;ServerName=Absent; SerialNumber=; Status=;
Power=; UID=; Partner=}...}
how can i change the output to view just the Hostname and server name and bay number?
Hostname : gblonblade1
Servername: Absent
Bay=1
Hostname : gblonblade1
Servername: gblon1234.ops.test.net
Bay=2
etc....

Assuming that Get-HPOAServerName produces a property ServerName that contains a list of hashtables (which is what your output looks like), you could transform the output like this:
... | Get-HPOAServerName | % {
$hostname = $_.hostname
$_.Servername | % {
New-Object -Type PSCustomObject -Property $_ |
select -Property *,#{n='HostName';e={$hostname}}
}
}

Related

How to replace Filter Origin in this PowerShell command with Windows Firewall's display name?

Get-WinEvent -FilterHashtable #{ LogName="Security"; Id=5152; } | ? { $_.Message -like "*Outbound*" -and -not($_.message -like "*ICMP*")} | select Message | ft -wrap
Found that in here, after running it, the results look like this:
filter origin has this ID which is Firewall's unique name but I want to see a more user friendly name so I can understand immediately which Firewall rule, based on its display name that I set, blocked this connection.
Update:
I want to do something like this. but it doesn't work like this and I need help fixing it. basically, I want to keep the same output format that the original script shows and only replace things like this {a42a62ec-83d9-4ab5-9d54-4dbd20cfab17} with their display name.
$data = (Get-WinEvent -FilterHashtable #{ LogName="Security"; Id=5152; } |
? { $_.Message -like "*Outbound*" -and -not($_.message -like "*ICMP*")}).message
$data -replace "(?<=Filter Origin:[^{]+){.+?}",{(Get-NetFirewallRule -Name $Matches[0]).DisplayName}
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comparison_operators?view=powershell-7.2#replacement-with-a-script-block
You can turn events into xml and access each field seperately. I don't have your exact event type.
$a = Get-WinEvent #{ LogName='Security' } -maxevents 1
$xml = [xml]$a.toxml()
$xml.event.eventdata.data
Name #text
---- -----
SubjectUserSid S-1-5-19
SubjectUserName LOCAL SERVICE
SubjectDomainName NT AUTHORITY
SubjectLogonId 0x3e5
PreviousTime 2023-01-03T14:40:58.3894712Z
NewTime 2023-01-03T14:40:58.3975397Z
ProcessId 0x59c
ProcessName C:\Windows\System32\svchost.exe
$xml.event.eventdata.data | ? name -eq processname | % '#text'
C:\Windows\System32\svchost.exe
Get-WinEvent #{ LogName='Security' } | % { $xml = [xml]$_.toxml()
$xml.event.eventdata.data | ? name -eq 'processname' | % '#text' }
Did a quick google search and saw this documentation on troubleshooting firewalls, and it points to Get-NetFireWallRule being able to get the display name from the ID. That said, you can use some handy RegEx of (?<=Filter Origin:[^{]+){.+?} to get the unique ID and query its friendly name:
Get-WinEvent -FilterHashtable #{ LogName="Security"; Id=5152; } |
? { $_.Message -like "*Outbound*" -and $_.Message -notlike "*ICMP*" } |
Select TimeCreated, #{
Name = 'Msg'
Expression = {
if ($_.Message -match ($pattern = '(?<=Filter Origin:[^{]+){.+?}'))
{
$_.Message -replace $pattern, (Get-NetFirewallRule -Name $Matches[0]).DisplayName
}
else
{
$_.Message
}
}
} | Ft -Wrap
Placing it inside an if statement allows it to leave the message alone if no match was found for patterns that may be the unique ID. See RegEx101 for more info on the pattern itself.

Format-Table out of Two Lists?

I've had this idea about getting the output from 2 separate functions, that return a PSCustomObject as a list, and formatting them into one table. My problem is simple... I don't know how to do it. lol
With the various of combinations that I tried, here's whats given me some promising results:
$Var1 = [PSCustomObject]#{
UserName = $env:USERNAME
Stuff1 = 'stuff1'
} | Format-List | Out-String -Stream
$Var2 = [PSCustomObject]#{
ComputerName = $env:COMPUTERNAME
Stuff2 = 'stuff2'
} | Format-List | Out-String -Stream
[PSCustomObject]#{
TableOne = $Var1.Trim().Foreach({$_})
TableTwo = $Var2.Trim()
} | Format-Table -AutoSize
The output:
TableOne TableTwo
-------- --------
{, , UserName : Abraham, Stuff1 : stuff1...} {, , ComputerName : DESKTOP-OEREJ77, Stuff2 : stuff2...}
I say promising in the respect that it shows the actual content of $var1 and 2, whereas my other attempts didn't. I also left the .foreach() operator there to show one of the many many different tricks I tried to get this working. For a quick second I thought the Out-String cmdlet would've done the trick for me, but was unsuccessful.
Has anyone ever done something similar to this?
EDIT:
Nevermind, I figured it out.
Used a for loop to iterate through each line assigning it the the PSCustomObject one at a time. Also used the .Where() operator to remove white spaces, and compared the two arrays to find the largest number to use it as the count.
$Var1 = $([PSCustomObject]#{
UserName = $env:USERNAME
Stuff1 = 'stuff1'
} | Format-List | Out-String -Stream).Where{$_ -ne ''}
$Var2 = $([PSCustomObject]#{
ComputerName = $env:COMPUTERNAME
Stuff2 = 'stuff2'
ExtraStuff = 'More'
} | Format-List | Out-String -Stream).Where{$_ -ne ''}
$Count = ($Var1.Count, $Var2.Count | Measure-Object -Maximum).Maximum
$(for($i=0;$i -lt $Count; $i++) {
[PSCustomObject]#{
TableOne = $Var1[$i]
TableTwo = $Var2[$i]
}
}) | Format-Table -AutoSize
Output:
TableOne TableTwo
-------- --------
UserName : Abraham ComputerName : DESKTOP-OEREJ77
Stuff1 : stuff1 Stuff2 : stuff2
ExtraStuff : More
It's an interesting way to format two collections with corresponding elements.
To indeed support two collections with multiple elements, a few tweaks to your approach are required:
# First collection, containing 2 sample objects.
$coll1 =
[PSCustomObject] #{
UserName = $env:USERNAME
Stuff1 = 'stuff1'
},
[PSCustomObject] #{
UserName = $env:USERNAME + '_2'
Stuff1 = 'stuff2'
}
# Second collection; ditto.
$coll2 =
[PSCustomObject] #{
ComputerName = $env:COMPUTERNAME
Stuff2 = 'stuff2'
ExtraStuff = 'More'
},
[PSCustomObject]#{
ComputerName = $env:COMPUTERNAME + '_2'
Stuff2 = 'stuff2_2'
ExtraStuff = 'More_2'
}
# Stream the two collections in tandem, and output a Format-List
# representation of each object in a pair side by side.
& {
foreach ($i in 0..([Math]::Max($coll1.Count, $coll2.Count) - 1)) {
[PSCustomObject] #{
TableOne = ($coll1[$i] | Format-List | Out-String).Trim() + "`n"
TableTwo = ($coll2[$i] | Format-List | Out-String).Trim() + "`n"
}
}
} | Format-Table -AutoSize -Wrap
The above ensures that multiple objects are properly placed next to each other, and yields something like the following:
TableOne TableTwo
-------- --------
UserName : jdoe ComputerName : WS1
Stuff1 : stuff1 Stuff2 : stuff2
ExtraStuff : More
UserName : jdoe_2 ComputerName : WS1_2
Stuff1 : stuff2 Stuff2 : stuff2_2
ExtraStuff : More_2

How to Get The Real RecordData from DnsServerResourceRecord Using Powershell?

I'm using PowerShell to extract information from an Active Directory DNS server and I'm having trouble getting to the data I want.
Specifically, I'm trying to get the names of hosts that belong to a particular subnet, 10.104.128.x.
When I use the following commands:
Get-DnsServerResourceRecord -ComputerName AD_DNS_SERVER -ZoneName 104.10.in-addr.arpa -RRType Ptr | Where-Object {$_.HostName -like '*.128'}`
I get output that looks like this:
HostName RecordType Timestamp TimeToLive RecordData
-------- ---------- --------- ---------- ----------
104.128 PTR 10/19/2015 3:00:0... 00:15:00 adl5c260a86ba79.XYZ.net.
11.128 PTR 12/29/2015 6:00:0... 00:15:00 adl3c970e8d7166.XYZ.net.
110.128 PTR 1/29/2012 11:00:0... 00:15:00 nroxitow7tst.ABC.com.
114.128 PTR 1/20/2012 7:00:00 AM 00:15:00 adl5c260a86c29e.ABC.com
What I really want are the first column, (HostName), which has the last two octets of the IP; and the fifth column, (RecordData), which has the name of the host the IP is assigned to.
The hostname is the data I really want/need. And I see it right there!
So I used the select command to pare down the output in the pipe train. New command looks like this:
Get-DnsServerResourceRecord -ComputerName AD_DNS_SERVER -ZoneName 104.10.in-addr.arpa -RRType Ptr | Where-Object {$_.HostName -like '*.128'} | select HostName, RecordData
But the output looks like this:
HostName RecordData
-------- ----------
104.128 DnsServerResourceRecordPtr
11.128 DnsServerResourceRecordPtr
110.128 DnsServerResourceRecordPtr
114.128 DnsServerResourceRecordPtr
Dosen't get me the hostname though. Just the type of object the RecordData is but not the data that the object contains, perhaps?
I also tried piping the output to CSV and got the same result.
Then I tried looking at the DnsServerResourceRecord object properties with Get-Member. That showed me the object had a property called PSComputerName. I thought maybe that would have the name of the host but that came up blank when I tried to select it.
I then Googled around a bit and found a few pages that recommended a few ways to use RecordData.ipv4address to coax the data out of the DnsServerResourceRecordPtr object but I haven't gotten any of them to work yet. Output still prints blanks.
So my question is: does a reliable method exist for getting the actual hostname from a PTR record?
To select the PtrDomainName property from the DnsServerResourceRecordPtr object, use a calculated property:
... |Select-Object HostName, #{Name='RecordData';Expression={$_.RecordData.PtrDomainName}}
Yes it's really weird that you can't just call ToString on the DNS record data, it's all formatted using the PowerShell formatters which you can only access with Format-List or Format-Table, rather than just calling $resourceRecord.RecordData.ToString().
I've added more data types than Krzysztof Madej by just hacking out the PowerShell formatters from the XML file, the details are here.
http://david-homer.blogspot.com/2020/10/getting-text-representation-of.html
$dnsserver = "yourowndnsserver"
$dnszones = Get-DnsServerZone -ComputerName $dnsserver | Select-Object ZoneName
ForEach ($zone in $dnszones) {
$data = New-Object System.Object
$ZoneName = $zone.ZoneName
$data = Get-DnsServerResourceRecord $ZoneName -ComputerName $dnsserver
foreach ($registros in $data) {
$data = $ZoneName
$data += ","
$data += $registros.hostname;
$data += ","
$data += $RecordType = $registros.recordType;
$data += ","
if ($RecordType -like "PTR") {
$data += $registros.RecordData.PtrDomainName
}
elseif ($RecordType -like "A") {
$data += $([system.version]($registros.RecordData.ipv4address.IPAddressToString));
}
elseif ($RecordType -like "CNAME") {
$data += $registros.RecordData.HostNameAlias;
}
elseif ($RecordType -like "NS") {
$data += $registros.RecordData.nameserver;
}
elseif ($RecordType -like "MX") {
$data += $registros.RecordData.MailExchange;
}
elseif ($RecordType -like "SOA") {
$data += $registros.RecordData.PrimaryServer;
}
elseif ($RecordType -like "SRV") {
$data += $registros.RecordData.DomainName;
}
$data | out-file -FilePath $env:TEMP\$(Get-Date -Format dd_MM_yyyy)_DNSRecords.csv -Append
}
}

How to augment expression into select-object in Powershell?

I have the following code which prints out some properties of each SQL Server related service on a host:
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.SqlWmiManagement")|Out-Null;
$mc = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Wmi.ManagedComputer localhost;
$mc.Services | % {$_|select-object Name,DisplayName,ServiceState}
I want to output the result with the HostName appended. I've tried this:
$hostName = hostname
$mc.Services | % {$_|select-object Name,DisplayName,ServiceState,#{Name="HostName";Expression={$hostname}}
But I just get >> displayed. Any ideas on how to accomplish what I want?
I believe you are missing a }:
$hostName = hostname
$mc.Services | % {$_|select-object Name,DisplayName,ServiceState,#{Name="HostName";Expression={$hostname}}}
And you can do it without the foreach-object:
$mc.Services | select-object Name,DisplayName,ServiceState,#{Name="HostName";Expression={$hostname}}

How do you export objects with a varying amount of properties?

Warning - I've asked a similar question in the past but this is slightly different.
tl;dr; I want to export objects which have a varying number of properties. eg; object 1 may have 3 IP address and 2 NICs but object 2 has 7 IP addresses and 4 NICs (but not limited to this amount - it could be N properties).
I can happily capture and build objects that contain all the information I require. If I simply output my array to the console each object is shown with all its properties. If I want to out-file or export-csv I start hitting a problem surrounding the headings.
Previously JPBlanc recommended sorting the objects based on the amount of properties - ie, the object with the most properties would come first and hence the headings for the most amount of properties would be output.
Say I have built an object of servers which has varying properties based on IP addresses and NIC cards. For example;
ServerName: Mordor
IP1: 10.0.0.1
IP2: 10.0.0.2
NIC1: VMXNET
NIC2: Broadcom
ServerName: Rivendell
IP1: 10.1.1.1
IP2: 10.1.1.2
IP3: 10.1.1.3
IP4: 10.1.1.4
NIC1: VMXNET
Initially, if you were to export-csv an array of these objects the headers would be built upon the first object (aka, you would only get ServerName, IP1, IP2, NIC1 and NIC2) meaning for the second object you would lose any subsequent IPs (eg IP3 and IP4). To correct this, before an export I sort based on the number of IP properties - tada - the first object now has the most IPs in the array and hence none of the subsequent objects IPs are lost.
The downside is when you then have a second varying property - eg NICs. Once my sort is complete based on IP we then have the headings ServerName, IP1 - IP4 and NIC1. This means the subsequent object property of NIC2 is lost.
Is there a scalable way to ensure that you aren't losing data when exporting objects like this?
Try:
$o1 = New-Object psobject -Property #{
ServerName="Mordor"
IP1="10.0.0.1"
IP2="10.0.0.2"
NIC1="VMXNET"
NIC2="Broadcom"
}
$o2 = New-Object psobject -Property #{
ServerName="Rivendell"
IP1="10.1.1.1"
IP2="10.1.1.2"
IP3="10.1.1.3"
IP4="10.1.1.4"
NIC1="VMXNET"
}
$arr = #()
$arr += $o1
$arr += $o2
#Creating output
$prop = $arr | % { Get-Member -InputObject $_ -MemberType NoteProperty | Select -ExpandProperty Name } | Select -Unique | Sort-Object
$headers = #("ServerName")
$headers += $prop -notlike "ServerName"
$arr | ft -Property $headers
Output:
ServerName IP1 IP2 IP3 IP4 NIC1 NIC2
---------- --- --- --- --- ---- ----
Mordor 10.0.0.1 10.0.0.2 VMXNET Broadcom
Rivendell 10.1.1.1 10.1.1.2 10.1.1.3 10.1.1.4 VMXNET
If you know the types(NICS, IPS..), but not the count(ex. how many NICS) you could try:
#Creating output
$headers = $arr | % { Get-Member -InputObject $_ -MemberType NoteProperty | Select -ExpandProperty Name } | Select -Unique
$ipcount = ($headers -like "IP*").Count
$niccount = ($headers -like "NIC*").Count
$format = #("ServerName")
for ($i = 1; $i -le $ipcount; $i++) { $format += "IP$i" }
for ($i = 1; $i -le $niccount; $i++) { $format += "NIC$i" }
$arr | ft -Property $format
What about getting a list of all unique property headers and then doing a select on all the objects? When you do a select on an object for a nonexistent property it will create a blank one.
$allHeaders = $arrayOfObjects | % { Get-Member -inputobject $_ -membertype noteproperty | Select -expand Name } | Select -unique
$arrayOfObjects | Select $allHeaders
Granted you are looping through ever object to get the headers, so for a very large amount of objects it may take awhile.
Here's my attempt at a solution. I'm very tired now so hopefully it makes sense. Basically I'm calculating the largest amount of NIC and IP note properties, creating a place holder object that has those amounts of properties, adding it as the first item in a CSV, and then removing it from the CSV.
# Create example objects
$o1 = New-Object psobject -Property #{
ServerName="Mordor"
IP1="10.0.0.1"
IP2="10.0.0.2"
NIC1="VMXNET"
NIC2="Broadcom"
}
$o2 = New-Object psobject -Property #{
ServerName="Rivendell"
IP1="10.1.1.1"
IP2="10.1.1.2"
IP3="10.1.1.3"
IP4="10.1.1.4"
NIC1="VMXNET"
}
# Add to an array
$servers = #($o1, $o2)
# Calculate how many IP and NIC properties there are
$IPColSize = ($servers | Select IP* | %{($_ | gm -MemberType NoteProperty).Count} | Sort-Object -Descending)[0]
$NICColSize = ($servers | Select NIC* | %{($_ | gm -MemberType NoteProperty).Count} | Sort-Object -Descending)[0]
# Build a place holder object that will contain enough properties to cover all of the objects in the array.
$cmd = '$placeholder = "" | Select ServerName, {0}, {1}' -f (#(1..$IPColSize | %{"IP$_"}) -join ", "), (#(1..$NICColSize | %{"NIC$_"}) -join ", ")
Invoke-Expression $cmd
# Convert to CSV and remove the placeholder
$csv = $placeholder,$servers | %{$_ | Select *} | ConvertTo-Csv -NoTypeInformation
$csv | Select -First 1 -Last ($csv.Count-2) | ConvertFrom-Csv | Export-Csv Solution.csv -NoTypeInformation