Select Object Property in for loop in Powershell - powershell

I am pretty sure there is a simple answer, but I cannot figure out how to ask it accurately enough as I am new to PowerShell.
Simple put, I am doing some API calls and then running through the results. The results include various properties that I converted from JSON into a Powershell Object.
A record looks simply like this:
id : 10123
path : /a/path/here/file.pptx
creationDate : 2019-06-28T09:37:32.000-04:00
modifiedDate : 2020-03-09T13:56:13.000-04:00
description : Record Description
creator : asmith
lastModifiedBy : jsmith
I then want to interact with the records, so I use a FOR loop on the IDs:
Foreach($SimpleID in $Records.id){
Write-Host $SimpleID + "`t|`t"+ $Records.path >> file.log
}
My question is, I want that output in the file to be:
10123 | /a/path/here/file.pptx
10124 | /next/path/file.txt
...etc
I know that the $Records.path is not correct there, but how do I filter it to only print the single path for the ID? I am sure there is a simple way, but I cannot figure out what to look up to start.
Any ideas on where to start?

You cannot use Write-Host to produce data output - see this post.
It sounds like you're looking for the following:
$Records | ForEach-Object { $_.id + "`t|`t"+ $_.path } > file.log

I would like to provide this alternative to mklement0's solution using Set-Content and Add-Content:
Set-Content -Path '.\log' -Value ''
$Records | ForEach-Object { Add-Content -Path '.\log' -Value "$_.id | $_.path" }
Loop over the Records objects and grab only what you need.

Related

Difficulty with a extracting responses from text file in Powershell

I am having a difficult time with finding and separating certain regular expressions in Powershell.
I have an example of the following contents of the text file that I need to search in the link below. Beneath each question is the response.
I need to find the response to each question and just store it in an excel file. It is possible that the questions might be in a different order in the text file so I need to be able to extract the correct responses to their respective questions.
I can extract the questions by doing
$question1 = $file | Where-Object { $_.Contains("Name") }
$question2 = $file | Where-Object { $_.Contains("Are you feeling ok?") }
$question3 = $file | Where-Object { $_.Contains("Did you do your homework?") }
$question4 = $file | Where-Object { $_.Contains("List your favourite subjects?") }
I am having difficulty with extracting the response right underneath it (because the questions could be in any order. In the future there could be more questions in the text file).
Assuming a text file exaclty as you mention:
* Name
Jeff
* Are you feeling ok?
Yes
* Did you do your homework?
No
* What are your favourite subjects?
Maths, science
Then you could use Select-String to get the questions and answers like this:
Get-Content -Path .\test.txt |
Select-String -Pattern '^\* .*$' -Context 1 |
ForEach-Object {
[PsCustomObject]#{
Question = $_.Line -replace '\* ', ''
Answer = $_.Context.PostContext[0].Trim()
}
}
Example output:
Question Answer
-------- ------
Name Jeff
Are you feeling ok? Yes
Did you do your homework? No
What are your favourite subjects? Maths, science
The order of the questions in the file doesn't matter, though they will appear in the output in the order they are found. You can use Export-Csv to create a CSV file, which can be opened in Excel, or look on-line for technique of manipulating XLSX files from PowerShell.

PowerShell: ConvertFrom-Json to export multiple objects to csv

As you probably understand from the title, I'm new to PowerShell, having a hard time even trying to describe my question. So please forgive my terminology.
Scenario
I am using PowerShell to query the audit log of Office 365. The cmdlet Search-UnifiedAuditLog returns "multiple sets of objects"(?), one of which has an array of other objects(?). The output is JSON if I got this right.
Here is an example of what is returned (I will call it one "Set of Objects"):
RunspaceId : xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
RecordType : AzureActiveDirectoryStsLogon
CreationDate : 21/02/2017 12:05:23
UserIds : xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Operations : UserLoggedIn
AuditData : {"CreationTime":"2017-02-21T12:05:23","Id":"{"ID":"00000000000000000","Type":3}],"ActorContextId":"xxxxxxxxxxxxxxxxxxxxxxxxx","ActorIpAddress":"xxxxxxxxxxxxx","InterSystemsId":"xxxxxxxxxxxxxxxxx","IntraSystemId":"000000000000-000000-000","Target":[{"ID":"00-0000-0000-c000-000000000000","Type":0}],"TargetContextId":"xxxxxxxxxxxxxxxxxx","ApplicationId":"xxxxxxxxxxxxxxxxxxxxxxxxxx"}
ResultIndex : 1
ResultCount : 16
Identity : xxxxxxxxxxxxxxxxxxxxxxxxxxx
IsValid : True
ObjectState : Unchanged
Now, I want some of the content of the AuditData line exported to a csv (normally containing much more data than copied here). This works fine for one "set of objects" (like the one above). To do this I use:
$LogOutput = Search-UnifiedAuditLog -StartDate 2/20/2017 -EndDate 2/23/2017 -ResultSize 1
$ConvertedOutput = ConvertFrom-Json -InputObject $LogOutput.AuditData
$ConvertedOutput | Select-Object CreationTime,UserId,ClientIP | Export-Csv -Path "C:\users\some.user\desktop\users.csv
ResultSize returns 1 instead of multiple "sets of objects". The ConvertFrom-Json does not work if I remove ResultSize.
So the question is:
Can I loop through all the "set of objects" and convert from json and have this exported line-by-line on a csv? Resulting in something like this:
UserId,Activity,UserIP
this#user.com, loggedIn, 10.10.10.10
that#user.org, accessedFile, 11.11.11.11
A pedagogic answer would be very, very much appreciated. Many thanks!
Instead of -ResultSize, try using Search-UnifiedAuditLog <args> | Select-Object -ExpandProperty AuditData | ConvertFrom-Json
This will make only the AuditData property get forwarded into ConvertFrom-Json and ignore the rest of the object from Search-UnifiedAuditLog

Add character end of line in PowerShell

I'm looking for a solution for the following challenge:
I run the following command in PowerShell.
Import-Module servermanager
Get-WindowsFeature | where {$_.Installed -eq "True"} | ft DisplayName, Installed > output.txt
Now I want to add a character at the end of each row. How can I do that?
I think I have to add the content into an array, but I don't know how to finish the code.
At the end I have to load the content into the EventViewer. If I send it directly, the event description isn't formatted well.
You could add another field to the records like this:
Get-WindowsFeature | ? { $_.Installed } |
select DisplayName, Installed, #{n='Other',e={'c'}} | ft
It sounds like instead of using ft > output.txt, you want something like:
foreach { echo ( $_.DisplayName + " " + $_.Installed + " extra_stuff" ) } > output.txt
It doesn't give you a nicely formatted table though.
It's a little outside the scope of what you directly asked, but I'd suggest skipping the 'write to text file' stage and pass directly to the destination.
Import-Module servermanager
$installedFeatures = #() # Creates a blank array for the features
$output = #() # Creates a blank array for formatted results
$yourChar # The character/ string you want to add at the end of each row
$installedFeatures = Get-WindowsFeature | Where-Object {$_.Installed -eq "True"}
foreach($feature in $installedFeatures)
{
$output += "$($feature.displayName) $($feature.installed) $yourChar"
}
Once you've iterated through all the Windows features, your $output variable will have an array of strings in the format of displayName installed $yourChar. You can then write to disk, or send the object somewhere else (this is the beauty of PowerShell objects!).

Powershell hashtable does not write to file as expected - receive only "System.Collections" rows

Can someone please explain Why my first examples don't work, and why adding in a ForEach-Object solves the problem? Thanks in advance!
I parsed the return from a command into a hashtable (sample at end of post) and want to log the information to a file as part of my processing. I know that $ht.GetEnumerator() | Sort-Object Name will return the full hash to screen, sorted. However, once I try sending things to file, it breaks.
$ht | Add-Content log.txt
only logs a single row of System.Collections.Hashtable. So, I've also tried
$ht.GetEnumerator() | Sort-Object Name | Add-Content log.txt
and end up with rows of
System.Collections.DictionaryEntry
System.Collections.DictionaryEntry
System.Collections.DictionaryEntry
So then I tried to loop through and handle each individually with
foreach ($key in $ht.keys) {
Add-Content log.txt "$key : $ht.$key" }
and end up with
Server address : System.Collections.Hashtable.Server address
Client address : System.Collections.Hashtable.Client address
User name : System.Collections.Hashtable.User name
Solved with:
$ht.GetEnumerator() | Sort-Object Name |
ForEach-Object {"{0} : {1}" -f $_.Name,$_.Value} |
Add-Content log.txt
For reference, the hashtable sample:
$ht = #{
"Server address" = "server.net";
"Client address" = "10.20.121.153";
"User name" = "myuser"
}
Answering the why part, you obviously have a solution :)
In your first example
$ht | Add-Content log.txt
PowerShell takes $ht and tries to somehow convert it to a string so that it can be stored via Add-Content. Because there is no conversion defined for the hashtable, only the type name is returned from the conversion. Same as for example new-Object Random|Add-Content d:\log.txt. Again, only type name is written.
Next
$ht.GetEnumerator() | Sort-Object Name | Add-Content log.txt
is similar. GetEnumerator returns object that is used for iteration; objects of type System.Collections.DictionaryEntry are returned. Again, there is no conversion to string, so type names are returned.
Personally, I think PowerShell should be smart enough and help here. The question is "how?". Designers probably didn't want to hardcode the output. It might be "{key}: {value}" or "{key} = {value}", or "{key}/{value}", or ... The format is not clear, so they left it for us to decide and format as you did it with the foreach statement.
I agree with mjolinor... just not enough points to vote up... plus i'll add that you dont need the GetEnumerator
$ht | out-string | add-content log.txt
will do it.
Your first example does not work, or better, partially works, because you are trying to get a property value within the string. Normally, inside strings, the parser is able to resolve only direct variables (like $key). To resolve more complex variable you need parenthesis.
For the loop, this should work:
foreach ($key in $ht.keys) {
Add-Content log.txt "$key : $($ht.$key)" }
or even better
$ht.keys | %{ Add-Content log.txt "$_ : $($ht.$_)" }
As you can see in Microsoft documentation a hash table is simply a collection of name-value pairs.
So $ht is really System.Collections.Hashtable composed of System.Collections.DictionaryEntry.
A good way to use it is
foreach ($i in $ht.keys)
{
add-content log.txt ("{0} {1}" -f $i, $ht[$i])
}
How about:
$ht.GetEnumerator() | Sort-Object Name | out-string | Add-Content log.txt
You can write a "raw" dump of a hash table to an ASCII file using Out-File:
$data = #{
Name = "Something"
Type = "123"
}
$data | Out-File "myfile.txt" -Encoding ascii

PowerShell: Format-Table without headers

In a PowerShell script, I have some objects that I pass to the Format-Table CmdLet.
The output of my script looks like this:
Something...
Operation AttributeName AttributeValue
--------- ------------- --------------
Delete Member John Doe
Something else...
Since the meaning of the fields is pretty self-explanatory, I would like to remove the headers, the '---' separators and the blank lines at the beginning and at the end from the output of Format-Table.
I don't think that the CmdLet supports this (or at least if there's a parameter to do this I couldn't find it).
What would the best way to leave only the lines with the actual values from the output of Format-Table?
Try the -HideTableHeaders parameter to Format-Table:
gci | ft -HideTableHeaders
(I'm using PowerShell v2. I don't know if this was in v1.)
Try -ExpandProperty. For example, I use this for sending the clean variable to Out-Gridview -PassThru , otherwise the variable has the header info stored. Note that these aren't great if you want to return more than one property.
An example:
Get-ADUser -filter * | select name -expandproperty name
Alternatively, you could do this:
(Get-ADUser -filter * ).name
The -HideTableHeaders parameter unfortunately still causes the empty lines to be printed (and table headers appearently are still considered for column width). The only way I know that could reliably work here would be to format the output yourself:
| % { '{0,10} {1,20} {2,20}' -f $_.Operation,$_.AttributeName,$_.AttributeValue }
Here is how I solve this. I just pipe the output to Out-String and then pass that output to the .NET Trim function:
(gci | ft -HideTableHeaders | Out-String).Trim()
This will strip out the line breaks before and after the table.
You can also use TrimStart to just take care of the header's line break if you still want the trailing line breaks.
(gci | ft -HideTableHeaders | Out-String).TrimStart()
Another approach is to use ForEach-Object to project individual items to a string and then use the Out-String CmdLet to project the final results to a string or string array:
gci Microsoft.PowerShell.Core\Registry::HKEY_CLASSES_ROOT\CID | foreach { "CID Key {0}" -f $_.Name } | Out-String
#Result: One multi-line string equal to:
#"
CID Key HKEY_CLASSES_ROOT\CID\2a621c8a-7d4b-4d7b-ad60-a957fd70b0d0
CID Key HKEY_CLASSES_ROOT\CID\2ec6f5b2-8cdc-461e-9157-ffa84c11ba7d
CID Key HKEY_CLASSES_ROOT\CID\5da2ceaf-bc35-46e0-aabd-bd826023359b
CID Key HKEY_CLASSES_ROOT\CID\d13ad82e-d4fb-495f-9b78-01d2946e6426
"#
gci Microsoft.PowerShell.Core\Registry::HKEY_CLASSES_ROOT\CID | foreach { "CID Key {0}" -f $_.Name } | Out-String -Stream
#Result: An array of single line strings equal to:
#(
"CID Key HKEY_CLASSES_ROOT\CID\2a621c8a-7d4b-4d7b-ad60-a957fd70b0d0",
"CID Key HKEY_CLASSES_ROOT\CID\2ec6f5b2-8cdc-461e-9157-ffa84c11ba7d",
"CID Key HKEY_CLASSES_ROOT\CID\5da2ceaf-bc35-46e0-aabd-bd826023359b",
"CID Key HKEY_CLASSES_ROOT\CID\d13ad82e-d4fb-495f-9b78-01d2946e6426")
The benefit of this approach is that you can store the result to a variable and it will NOT have any empty lines.
I know it's 2 years late, but these answers helped me to formulate a filter function to output objects and trim the resulting strings. Since I have to format everything into a string in my final solution I went about things a little differently.
Long-hand, my problem is very similar, and looks a bit like this
$verbosepreference="Continue"
write-verbose (ls | ft | out-string) # this generated too many blank lines
Here is my example:
ls | Out-Verbose # out-verbose formats the (pipelined) object(s) and then trims blanks
My Out-Verbose function looks like this:
filter Out-Verbose{
Param([parameter(valuefrompipeline=$true)][PSObject[]]$InputObject,
[scriptblock]$script={write-verbose "$_"})
Begin {
$val=#()
}
Process {
$val += $inputobject
}
End {
$val | ft -autosize -wrap|out-string |%{$_.split("`r`n")} |?{$_.length} |%{$script.Invoke()}
}
}
Note1: This solution will not scale to like millions of objects(it does not handle the pipeline serially)
Note2: You can still add a -noheaddings option.
If you are wondering why I used a scriptblock here, that's to allow overloading like to send to disk-file or other output streams.