Powershell export nested object arrays into CSV - powershell

When i look into my object arra and type a $obj[2] (as exam) I'm getting
Name : audit_event
UUID : c6479a6f-f1bd-4759-9881-fcb493821aff
Timestamp : 17-06-20 13:30:48 +00:00
Fields : {[audit_schema_version, 1], [event_time, 17-06-20 13:30:48 +00:00], [sequence_number, 1], [action_id, 541934402]...}
Actions : {}
I would like to get all fields into a single csv file.
So first I started to find at least the fields, but dispite some solutions I saw i'm not getting it OK.
foreach ($UUID in $logsOBJECT[2].UUID) {
echo $UUID
foreach ($field in $logsOBJECT.$UUID.Keys) {
echo $field
}
}
This doesn't work.
I'm not a Powershell developer so quite novice.
I have to use Powershell because Synaps Analytics doesn't give a better option to read and process its logfiles.
Kind regards, Harry

Given an object that looks like this in JSON:
[{
"Name": "audit_event",
"UUID": "c6479a6f-f1bd-4759-9881-fcb493821aff",
"Timestamp": "17-06-20 13:30:48 +00:00",
"Fields": [["audit_schema_version", 1], ["event_time", "17-06-20 13:30:48 +00:00"], ["sequence_number", 1], ["action_id", 541934402]],
"Actions": {}
}]
(You can generate that by using $obj | ConvertTo-Json so it's easier for others to reproduce)
First we loop through the $obj list by passing it to ForEach-Object, or % for short. For each element we create a $result object that contains all the data, except the Fields property.
Then we loop through the fields property on the object. Each field is itself a list, where the first (0) element is the name, and the second (1) is the value. For each field, we add a property to the $result object with the name and value of the field.
When this is done we return the $result object to the pipeline, and that gets passed to Export-Csv which writes it to a file.
$obj | % {
$result = $_ | Select-Object * -ExcludeProperty Fields
$_.Fields | % {
$result | Add-Member -Name $_[0] -Value $_[1] -MemberType NoteProperty
}
return $result
} | Export-Csv -Path C:\test.csv -Encoding UTF8 -NoTypeInformation

I don't have your exact PS module of SynapsAnalystics...
But it seems to be a problem accessing nested arrays in Powershell.
Here I have an example with Windows services:
PS C:\WINDOWS\system32> $servic[20] | fl
Name : BrokerInfrastructure
DisplayName : Background Tasks Infrastructure Service
Status : Running
DependentServices : {workfolderssvc, WMPNetworkSvc, WSearch, embeddedmode}
ServicesDependedOn : {DcomLaunch, RpcSs, RpcEptMapper}
CanPauseAndContinue : False
CanShutdown : False
CanStop : False
ServiceType : Win32ShareProcess
Here, if I want the output of $servic.DependentServices I need to know the Keys\Propertys of $servic.DependentServices. ie,
You can get that by :
PS C:\WINDOWS\system32> $servic[20].DependentServices
Status Name DisplayName
------ ---- -----------
Stopped workfolderssvc Work Folders
Running WMPNetworkSvc Windows Media Player Network Sharin...
Running WSearch Windows Search
Stopped embeddedmode Embedded Mode
So the Propertys here are
Status Name DisplayName etc...
$servic[20].DependentServices would be similar to $obj[2].Fields in your case.
Try and see the Keys or Property's within $obj[2].Fields then decide which Property you want to loop through.
you can get that with
$obj[2].Fields | get-Module (this will give all parameters)
Then you can loop through the required Properties, like in my case:
foreach ($echserv in $servic[0-2])
{
write-host "*****************Service******************"
echo $echserv.Name
Write-Host "####DependentServices####"
foreach ($echDependServic in $servic.DependentServices.DisplayName)
{
echo $echDependServic
}
}
which would give output:
*****************Service******************
XboxGipSvc
####DependentServices####
Smartlocker Filter Driver
Agent Activation Runtime_ea2d3
Agent Activation Runtime
Windows Audio
Agent Activation Runtime_ea2d3
Agent Activation Runtime
Xbox Live Networking Service
.
.
.
Hope this helps with your problem.
PS: There are better ways to display your output using hashtables in Powershell. This can later be used to export to CSV etc..

Related

Select Object Property in for loop in 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.

How to filter for specific words using power shell

I am trying to figure out who has been printing from a 2008 print server. I generated a log file from server manager and now have this information in a csv file. My goal is to parse through this info and export it to a new csv to find out the only the associates user ID, computer host name and printer name, all included in the csv log file so I can determine who is printing from this server and make sure I can map them to our new print server. The csv consists of one column of data which has a pattern.
Each row in the csv follows the below pattern, but the wording is different for each row since the job name is longer/shorter or other bits of information that I don't want.
The overall pattern is:
Document #, job name owned by **user ID** on **computer host name** was printed on **printer name** through port ********
More information I don't want
My problem is that I can't hard code something like ignore the first 5 words of each row then the 6th word would be the user ID etc, since the format is different on each row.
What is the best way for me to ignore all words up until either the phrase "owned by", or even better, user ID, save that to a new csv in, then do the same thing for the computer host name and printer name?
This could be done easily enough using Regular Expression matching. Regular Expressions use pattern matching, so you could do something like:
Get-Content LogFile.csv | Where{$_ -match "^(.*?),.+? owned by (.+?) on (.+?) was printed on (.+?) through port (.+)"} | ForEach{
[PSCustomObject]#{
JobNumber=$Matches[1]
UserId=$Matches[2]
ComputerName=$Matches[3]
PrinterName=$Matches[4]
Port=$Matches[5]
}
}|Export-Csv NewLogFile.csv -NoTypeInfo
That would give you a CSV you could open in Excel or something with just the job number, user ID, the computer they used, the printer it went to, and the port it went on.
TheMadTechnician's Answer already covers a majority of this.
$a = Get-Content original.csv
$a[0] += ",Data"
$a | Set-Content updated.csv
$csv = Import-Csv updated.csv
$data = $csv.where({$_."Event ID" -eq 307}) | Select-String -pattern "(?<=owned by )(?<user>[^ ]+)(?: on )(?<host>.*?)(?: was printed on )(?<printer>.*?)(?= through )"
$(ForEach ($m in $data.matches) {
[pscustomobject][ordered]#{"User"=$m.groups["user"].value
"Host"=$m.groups["host"].value
"Printer"=$m.groups["printer"].value
}
}) | Export-Csv Output.csv -notypeinformation
There are some issues with the CSV that is exported from the Windows print server. If the numbered issues below do not matter in this case, then I can just remove this answer.
The CSV column that contains the data you care about has no name. The other columns have headers, but for some reason this one does not. Without that header, your Import-Csv command will be useless. The first four lines of the code cover adding the Data header to that file. So you can either use code to fix that or just open the file, add the column name, and save.
The event ID you care about is 307. There's a lot of other noise in that event log unless you pre-filtered it before saving it as a CSV, and that could impact the regex matching.
My method here is really no different than the other posted answer. I'm just matching less strings and access those matches with named indexes.
This is not an answer for how to extract information from the message text but rather how to avoid having to deal with formatted text in the first place. It appears you are trying to parse the message for Event Log events with ID 307. This code is adapted from PowerShell One-Liner to Audit Print Jobs on a Windows based Print Server.
Using the Get-WinEvent cmdlet you can query a specific log (Microsoft-Windows-PrintService/Operational) for specific events (307), and then it's just a matter of retrieving and adding a meaningful name to each property...
$eventFilter = #{
LogName = 'Microsoft-Windows-PrintService/Operational';
ID = 307;
}
Get-WinEvent -FilterHashTable $eventFilter `
| Select-Object -Property `
'TimeCreated', `
#{ Name = 'JobId'; Expression = { $_.Properties[0].Value }; }, `
#{ Name = 'JobName'; Expression = { $_.Properties[1].Value }; }, `
#{ Name = 'UserName'; Expression = { $_.Properties[2].Value }; }, `
#{ Name = 'MachineName'; Expression = { $_.Properties[3].Value }; }, `
#{ Name = 'PrinterName'; Expression = { $_.Properties[4].Value }; }, `
#{ Name = 'PortName'; Expression = { $_.Properties[5].Value }; }, `
#{ Name = 'ByteCount'; Expression = { $_.Properties[6].Value }; }, `
#{ Name = 'PageCount'; Expression = { $_.Properties[7].Value }; }
For an event with a message like this...
Document 1, Print Document owned by UserName on \\MachineName was
printed on Microsoft Print to PDF through port X:\Directory\File.ext.
Size in bytes: 12345. Pages printed: 1. No user action is required.
...the above code will output an object like this...
TimeCreated : 3/28/2019 5:36:41 PM
JobId : 1
JobName : Print Document
UserName : UserName
MachineName : \\MachineName
PrinterName : Microsoft Print to PDF
PortName : X:\Directory\File.ext
ByteCount : 12345
PageCount : 1
You could pipe the above command into Export-CSV to create your CSV file, or even just use Out-GridView to view and filter the data directly in PowerShell. Either way, no parsing necessary.

How can i convert the output of prnmngr into custom object?

output of cscript prnmngr.vbs -l
Server name abcd
Printer name \\abcd.com\mailroom
Share name mailroom
Driver name Canon iR-ADV 4225/4235 UFR II
Port name mailroom.com
Comment
Location
Print processor winprint
Data type RAW
Parameters
Attributes 536
Priority 1
Default priority 0
Average pages per minute 0
Printer status Idle
Extended printer status Unknown
Detected error state Unknown
Extended detected error state Unknown
Server name cdef
Printer name \\cdfet.com\mailroom3
Share name mailroom3
Driver name Canon iR-ADV 4225/4235 UFR II
Port name mailroomxxx.com
Comment
Location
Print processor winprint
Data type RAW
Parameters
Attributes 536
Priority 1
Default priority 0
Average pages per minute 0
Printer status Idle
Extended printer status Unknown
Detected error state Unknown
Extended detected error state Unknown
something like (note the modified output property names):
$CustomPrinterobjects = New-Object –TypeName PSObject
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name ComputerName –Value "$a"
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name Name –Value "$b"
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name ShareName –Value "$c"
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name DriverName –Value "$d"
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name PortName –Value "$e"
where $a, $b, $c,$d, $e represent property values looped over the output of cscript prnmngr.vbs -l
Kory Gill helpfully suggests using the W8+ / W2K12+ Get-Printer cmdlet instead.
Similarly, kuujinbo suggests Get-WmiObject -Class Win32_Printer for earlier OS versions.
In the spirit of PowerShell, both commands returns objects whose properties you can access directly - no need for text parsing.
In case you still have a need to parse the output from cscript prnmngr.vbs -l (if it provides extra information that the cited commands do not), use the following approach - note how much effort is needed to parse the textual output into structured objects:
Given that all information is space-separated and the property name part of each line is composed of varying numbers of tokens, the only predictable way to parse the text is to:
maintain a collection of well-known property names
consider whatever comes after the property name on the line the value.
A PSv3+ solution:
# Map the input property names of interest to output property names,
# using a hashtable.
$propNameMap = #{
'Server name ' = 'ComputerName'
'Printer name ' = 'Name'
'Share name ' = 'ShareName'
'Driver name ' = 'DriverName'
'Port name ' = 'PortName'
}
# Split the output of `cscript prnmngr.vbs -l` into paragraphs and
# parse each paragaph into a custom object with only the properties of interest.
$customPrinterObjs = (cscript prnmngr.vbs -l) -join "`n" -split "`n`n" | ForEach-Object {
$ohtFields = [ordered] #{}
foreach ($line in $_ -split "`n") {
foreach ($propNamePair in $propNameMap.GetEnumerator()) {
if ($line -like ($propNamePair.Key + '*')) {
$ohtFields[$propNamePair.Value] = $line.Substring($propNamePair.Key.length)
}
}
}
[pscustomobject] $ohtFields
}
# Output the resulting custom objects.
$customPrinterObjs
With your sample input, the above yields a 2-element [pscustomobject] array:
ComputerName : abcd
Name : \\abcd.com\mailroom
ShareName : mailroom
DriverName : Canon iR-ADV 4225/4235 UFR II
PortName : mailroom.com
ComputerName : cdef
Name : \\cdfet.com\mailroom3
ShareName : mailroom3
DriverName : Canon iR-ADV 4225/4235 UFR II
PortName : mailroomxxx.com
(cscript prnmngr.vbs -l) -join "`n" collects the output lines from cscript prnmngr.vbs -l in an array and then joins them to form a single multiline string.
-split "`n`n" splits the resulting multiline string into paragraphs, each representing a single printer's properties.
The ForEach-Object script block then processes each printer's properties paragraph:
foreach($line in $_ -split "`n") splits the multiline paragraph back into an array of lines and loops over them.
$ohtFields = [ordered] #{} initializes an empty ordered hashtable (where entries are reflected in definition order on output) to serve as the basis for creating a custom object.
The inner foreach loop then checks each line for containing a property of interest, and, if so, adds an entry to the output hashtable with the output property name and the property value, which is the part that follows the well-known property name on the line.
Finally, the ordered hashtable is output as a custom object by casting it to [pscustomobject].
Calling a vbscript-script from Powershell just feels like a "re-write it in Powershell sort of thing" (not to be judgy). #KoryGill asks an interesting question, "Why not just call Get-Printer"?
But, to your question, you can totally turn that into an object, but you'll have to do some text manipulation:
$printer_stuff = $(cscript prnmngr.vbs -l)
This will create a string array named $printer_stuff where each element has a separate line of output on it. You'll want to make a list of tokens for each printer property e.g., server name, printer name, etc. You'll iterate over the output (in the string array) copying the properties to a PSObject. Here is a cheap example to demonstrate the point:
## Make a list of tokens
$tokens = #('Server name', 'Printer name', 'Share name')
## This will be your printer object
$printer = New-Object -TypeName PSObject
## Parsing the string array and stuffing the good bits into your printer object
foreach ($thing in $printer_stuff[0..17]) {
foreach ($token in $tokens) {
if ($thing -match $token) {
Add-Member -InputObject $printer -MemberType NoteProperty -Name $token -Value $thing.Replace($token, '')
}
}
}
## Here is your object...
$printer
If the prnmgr.vbs script will be returning information on a bunch of printers, you can stuff that $printer object into an an array:
$printers = #()
....
$printers += $printer
You can pull each printer's data out of the string array with something like...
$min = 0
$max = $size
while ($min -lt $printer_stuff.length) {
$printer_stuff[$min..$max]
$min = $max + 1
$max += $size
}
As you can see, this is a big pain in the ass, which is why I suggest just to re-write the thing in Powershell. If you're slick enough to do this bit, you're slick enough to port the vbscript-script.
Good Luck,
A-

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

Get a list of all xml attributes

I'm making an API call that returns the value in System.Xml.XmlElement,
but it looks like this:
id : 5847538497
ipAddress : 192.168.110.1
status : RUNNING
upgradeStatus : UPGRADED
upgradeAvailable : false
Saving this in a local variable myData. How can I print all the attributes of this returned XML?
It works if I type:
> Write-Host myData.id
> Write-Host myData.status
but I don't know all the attributes as api call is dynamic and returns different attributes.
Take a look at the Attributes property on the XmlElement object in question:
$myData.Attributes |ForEach-Object {
'Name: {0}; Value: {1}' -f $_.LocalName,$_.Value
}
Take a look at the Format-List and the Get-Member cmdlet:
myData | Format-List * -force
myData | Get-Member