Powershell print sub-level objects in FT - powershell

I have the following generic structure:
Powershell command returns an object containing named fields and values. Some of those values are objects with another struct of names and values below them.
I want to simply print the results in a FL, but I want the sub-object values included.
The following code provides me the output I'm looking for, but I feel like I'm not doing this in a powershell-efficient way and I should be able to pipe this into a one-liner
foreach ($user in (Get-MsolUser | ?{$_.StrongAuthenticationmethods -ne $null})){
Write-host "$($user.UserPrincipalName) :: $($user.DisplayName)"
foreach($method in $user.StrongAuthenticationMethods){
write-host "`t$($method.MethodType)"
}}
I was hoping the above could be shortened to resemble the below non-functional code... is something like this possible to dump the property values when there could be a number of results between 0-X (max 4 in my case)?
Get-msolUser|?{$_.StrongAuthenticationmethods -ne $null} | select UserPrincipalName,Displayname,isLicensed,(StrongAuthenticationmethods | fl)

Use a calculated property:
Get-MsolUser |
Where-Object { $null -ne $_.StrongAuthenticationmethods } |
Select-Object UserPrincipalName, Displayname, isLicensed, #{
Name='StrongAuthenticationmethods'
Expression={ $_.StrongAuthenticationmethods.MethodType -join "`n" }
} |
Format-List
The above uses Format-List (fl), but if you prefer a tabular representation via Format-Table (ft) instead, replace | Format-List with | Format-Table -Wrap.

Related

PowerShell Export-CSV - Missing Columns [duplicate]

This question already has an answer here:
Not all properties displayed
(1 answer)
Closed 1 year ago.
This is a follow-up question from PowerShell | EVTX | Compare Message with Array (Like)
I changed the tactic slightly, now I am collecting all the services installed,
$7045 = Get-WinEvent -FilterHashtable #{ Path="1system.evtx"; Id = 7045 } | select
#{N=’Timestamp’; E={$_.TimeCreated.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')}},
Id,
#{N=’Machine Name’; E={$_.MachineName}},
#{N=’Service Name’; E={$_.Properties[0].Value}},#{N=’Image Path’;E=$_.Properties[1].Value}},
#{N=’RunAsUser’; E={$_.Properties[4].Value}},#{N=’Installed By’; E={$_.UserId}}
Now I match each object for any suspicious traits and if found, I add a column 'Suspicious' with the value 'Yes'. This is because I want to leave the decision upto the analyst and pretty sure the bad guys might use something we've not seen before.
foreach ($Evt in $7045)
{
if ($Evt.'Image Path' -match $sus)
{
$Evt | Add-Member -MemberType NoteProperty -Name 'Suspicious' -Value 'Yes'
}
}
Now, I'm unable to get PowerShell to display all columns unless I specifically Select them
$7045 | Format-Table
Same goes for CSV Export. The first two don't include the Suspicious Column but the third one does but that's because I'm explicitly asking it to.
$7045 | select * | Export-Csv -Path test.csv -NoTypeInformation
$7045 | Export-Csv -Path test.csv -NoTypeInformation
$7045 | Select-Object Timestamp, Id, 'Machine Name', 'Service Name', 'Image Path', 'RunAsUser', 'Installed By', Suspicious | Export-Csv -Path test.csv -NoTypeInformation
I read the Export-CSV documentation on MS. Searched StackOverFlow for some tips, I think it has something to do with PS checking the first Row and then compares if the property exists for the second row and so on.
Thank you
The issue you're experiencing is partially because of how objects are displayed to the console, the first object's Properties determines the displayed Properties (Columns) to the console.
The bigger problem though, is that Export-Csv will not export those properties that do not match with first object's properties unless they're explicitly added to the remaining objects or the objects are reconstructed, for this one easy way is to use Select-Object as you have pointed out in the question.
Given the following example:
$test = #(
[pscustomobject]#{
A = 'ValA'
}
[pscustomobject]#{
A = 'ValA'
B = 'ValB'
}
[pscustomobject]#{
C = 'ValC'
D = 'ValD'
E = 'ValE'
}
)
Format-Table will not display the properties B to E:
$test | Format-Table
A
-
ValA
ValA
Format-List can display the objects properly, this is because each property with it's corresponding value has it's own console line in the display:
PS /> $test | Format-List
A : ValA
A : ValA
B : ValB
C : ValC
D : ValD
E : ValE
Export-Csv and ConvertTo-Csv will also miss properties B to E:
$test | ConvertTo-Csv
"A"
"ValA"
"ValA"
You have different options as a workaround for this, you could either add the Suspicious property to all objects and for those events that are not suspicious you could add $null as Value.
Another workaround is to use Select-Object explicitly calling the Suspicious property (this works because you know the property is there and you know it's Name).
If you did not know how many properties your objects had, a dynamic way to solve this would be to discover their properties using the PSObject intrinsic member.
using namespace System.Collections.Generic
function ConvertTo-NormalizedObject {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline, Mandatory)]
[object[]] $InputObject
)
begin {
$list = [List[object]]::new()
$props = [HashSet[string]]::new([StringComparer]::InvariantCultureIgnoreCase)
}
process {
foreach($object in $InputObject) {
$list.Add($object)
foreach($property in $object.PSObject.Properties) {
$null = $props.Add($property.Name)
}
}
}
end {
$list | Select-Object ([object[]] $props)
}
}
Usage:
# From Pipeline
$test | ConvertTo-NormalizedObject | Format-Table
# From Positional / Named parameter binding
ConvertTo-NormalizedObject $test | Format-Table
Lastly, a pretty easy way of doing it thanks to Select-Object -Unique:
$prop = $test.ForEach{ $_.PSObject.Properties.Name } | Select-Object -Unique
$test | Select-Object $prop
Using $test for this example, the result would become:
A B C D E
- - - - -
ValA
ValA ValB
ValC ValD ValE
Continuing from my previous answer, you can add a column Suspicious straight away if you take out the Where-Object filter and simply add another calculated property to the Select-Object cmdlet:
# create a regex for the suspicious executables:
$sus = '(powershell|cmd|psexesvc)\.exe'
# alternatively you can join the array items like this:
# $sus = ('powershell.exe','cmd.exe','psexesvc.exe' | ForEach-Object {[regex]::Escape($_)}) -join '|'
$7045 = Get-WinEvent -FilterHashtable #{ LogName = 'System';Id = 7045 } |
Select-Object Id,
#{N='Timestamp';E={$_.TimeCreated.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')}},
#{N='Machine Name';E={$_.MachineName}},
#{N='Service Name'; E={$_.Properties[0].Value}},
#{N='Image Path'; E={$_.Properties[1].Value}},
#{N='RunAsUser'; E={$_.Properties[4].Value}},
#{N='Installed By'; E={$_.UserId}},
#{N='Suspicious'; E={
if ($_.Properties[1].Value -match $sus) { 'Yes' } else {'No'}
}}
$7045 | Export-Csv -Path 'X:\Services.csv' -UseCulture -NoTypeInformation
Because you have many columns, this will not fit the console width anymore if you do $7045 | Format-Table, but the CSV file will hold all columns you wanted.
I added switch -UseCulture to the Export-Csv cmdlet, which makes sure you can simply double-click the csv file so it opens correctly in your Excel.
As sidenote: Please do not use those curly so-called 'smart-quotes' in code as they may lead to unforeseen errors. Straighten these ’ thingies and use normal double or single quotes (" and ')

PowerShell referencing object in a pipeline

I'm struggling with passing objects in a pipeline.
I have been going round the problem converting them to strings, but that cannot be the most efficient way of doing things.
$mapi = (Get-CASMailbox -Identity $user | fl mapiEnabled | Out-String ).Split(':')[-1]
if ($mapi -match "True") {
Set-CASMailbox -Identity $User -MAPIEnabled $false
}
I really want to directly access the bool returned instead of converting it to string
Similarly, I have been using below to do a for loop:
$groups = (Get-DistributionGroup | fl name | Out-String -Stream ).Replace("Name : ", "")
foreach ($group in $groups) {
echo $group
}
Both examples are from Exchange Online, below one more universal:
if (((Get-NetIPInterface -InterfaceAlias $adapters -AddressFamily Ipv4 | fl dhcp | Out-String -Stream ).Trim() -ne "").Replace("Dhcp : ","") -match "Disabled") {
echo disabled
}
I just wanted to take a second to see if I can help you understand what is happening in the pipeline and why #mathiasR.Jessen and #AdminOfThings comments will help you.
$mapi = (Get-CASMailbox -Identity $user | fl mapiEnabled | Out-String ).Split(':')[-1]
Breaking down that this line of code does:
Get-CASMailbox is going to return an object with multiple properties. Format-List (fl) is still going to return an object, but now it has been formatted so it's less malleable. Out-String is going to transform that formatted list into a single string. Putting those commands in parentheses runs them and allows you to execute a method on the resulting string object.
Using the same concept, we can use the parenthesis to execute the Get-CASMailbox command and get the singular property you are looking for:
$mapi = (Get-CASMailbox -Identity $user).mapiEnabled
Now we have set $mapi to the value of the mapiEnabled property returned by the command.
Hope this helps!

Powershell - Get-DistributionGroup exporting to csv issue

The below command will output the correct information to the screen, however when exporting to a csv file it looks nothing like what is shown on the screen. I have been able to export with other cmdlets, Get-DistributionGroup seems to corrupt the data when using with export.csv.
Outputting to screen works great!
Get-DistributionGroup | Where {$_.emailaddresses -like "*#adomain.com*"} | FT Name,Alias,EmailAddresses
Exporting to csv doesnt work correctly
Get-DistributionGroup | Where {$_.emailaddresses -like "*#adomain.com*"} | FT Name,Alias,EmailAddresses | Export-csv C:/thisis.csv
Instead of Format-Table (alias: ft) you should use Select-Object. Export-Csv expects objects as input, not formating instructions.
Format-Table, by definition, will convert objects to something that looks well in your output, but it's one way trip: you loose original objects as a part of the process.
Select-Object on the other hand can create objects with subset of properties, so that cmdlets like Export-Csv can still use data from original source.
EDIT
Thing I missed originally: you try to export a property that is a collection (EmailAddresses). That won't work unless you 'flatten' the collection first:
Get-DistributionGroup | Where {$_.emailaddresses -like "*#adomain.com*"} | select Name,Alias, #{
Name = 'EmailAddresses'
Expression = { $_.EmailAddresses -join '|' }
}
Thank you the answer has been resolved.
BartekB's answer
Get-DistributionGroup | Where {$.emailaddresses -like "*#adomain.com*"} | select Name,Alias, #{
Name = 'EmailAddresses'
Expression = { $.EmailAddresses -join '|' }
}

How to get the value of a particular propery from the result of a powershell command

I have a variable $ results which has the value :
SESSIONNAME USERNAME ID STATE TYPE DEVICE
rdp-tcp#1 account17 7 Active rdpwd
I want to get the value of ID alone and use it in a different query.
I tried the following ways :
1.$idValue = #($result | %{ $_.ID }) - but it was not getting the value.
2.$result |Select -ExpandProperty ID - I was getting the error 'Select-Object : Property "ID" cannot be found.'
How to get the value of the property ID alone from the result?
The output of the qwinsta/query commands are strings, not objects, so there isn't a property ID to print. You need to transform the strings into objects if you want the fields as properties:
query session | ? { $_ -match '^[ >](\S+) +(\S*?) +(\d+) +(\S+)' } |
select #{n='Service';e={$matches[1]}},
#{n='Username';e={$matches[2]}},
#{n='ID';e={$matches[3]}},
#{n='Status';e={$matches[4]}} | % {
$_.ID
}
Or, if you're just interested in the ID, you could do a regular expression replacement like this:
$account = 'account17'
$pattern = '^[ >]\S+ +\S*? +(\d+) +\S+.*'
(query session $account | select -Skip 1) -replace $pattern, '$1'
This is the format to refer to a single property properly. I don't see your command to create your RDP $result, so I'll example get-process, encapsulate it with () and tack an ().ID to the end. Works with any property, not just.ID
(get-process | where {$_.Name -eq "Powershell"}|select ID).ID
# or
$MYID = (get-process | where {$_.Name -eq "Powershell"}|select ID).ID
$MYID
Another option is -split:
One solution, using V4:
($result).ForEach({($_ -split '\s+')[2]}) -match '\d'

How can I take output of command and populate array?

I have non-working code similar to the following:
$list = Get-VM | format-table VMElementName -HideTableHeaders | out-string
$array=#($list)
Write-Host $array[1]
What I end up is $array[0] filled with a list of data and no values in $array[1] or higher.
String1
String2
String3
What is the best way to parse this list to populate the array?
The easiest way to get that is just select out the property you want with Select -ExpandProperty:
$array = Get-VM | select -ExpandProperty VMElementName
If you're running V3 or better, you can shorten that to:
$array = (Get-VM).VMElementName
You don't need extra transformation logic. Just us the following
$array = ( Get-VM | format-table VMElementName -HideTableHeaders )
Write-Host $array[0]