0

I have been trying to create a script that takes all of the running processes of a machine, and also the disk utilization.

I wanted to put the data into a JSON format, because CSV doesnt handle 2 different discrete variables well.

The CSV way to do it would be by:

$jobj= ""

$jobj += get-wmiobject Win32_PerfFormattedData_PerfProc_Process |where {$_.PercentProcessorTime -gt 1 -and $_.name -ne "Idle" -and $_.name -ne "_Total"}| select @{n="Application";e={$_.name}},@{n="CPU";e={$_.PercentProcessorTime}} | Sort-Object CPU -Descending | ConvertTo-Json ; 

$jobj = $jobj | convertfrom-json ;

Get-Date -UFormat "%A %B/%d/%Y %T %Z"
$Time = Get-Date
$Time = $Time.ToUniversalTime()

$toAdd = (get-counter -counter "\physicaldisk(_total)\% disk time").countersamples.cookedvalue

$jobj  | Add-Member -MemberType NoteProperty -Name "DiskUtilization" -Value $toAdd
$jobj  | Add-Member -MemberType NoteProperty -Name "Date" -Value $Time 

But to do it in JSON you would need to have a Depth 2 Object, First depth is Disk And Application, with the second depth being all the applications and %.

3 Answers 3

3

I'm just adding to the conversation out of interest. The question wasn't clear, I couldn't answer till I actually signed up for discord and could see the JSON sample. That said, my initial feeling was correct, there was no need for the intermediate conversion to JSON. Your focus should've been creating whole objects in PowerShell before converting them.

In my opinion hQWeedEater's solution is more effective, mainly because it doesn't use the += operator. Every time += is used PowerShell will create a new array and copy the contents to it. It's a best practice to stay away from it, and the best alternative is to let PowerShell create the array for you.

However, the approach of having two arrays isn't without merit, because we can avoid the ForEach in the middle of the object declaration, from hQWeedEater's solutions.

$Filter = "PercentProcessorTime >= 1 AND Name <> 'Idle' AND Name <> '_Total'"

[Object[]]$Applications =
Get-WmiObject Win32_PerfFormattedData_PerfProc_Process -Filter $Filter |
Select-Object @{ n = "Application"; e = {$_.name }},
    @{ n = "CPU"; e = { $_.PercentProcessorTime }} |
Sort-Object CPU -Descending

$Object =
[PSCustomObject]@{
    DateTime        = Get-Date -Format s
    DiskUtilization = (Get-Counter -Counter "\PhysicalDisk(_Total)\% Disk Time").CounterSamples.CookedValue
    Applications    = $Applications
}

$Object | ConvertTo-Json | Out-File .\example.json -Append

Select-Object is going to emit a [PsCustomObject] anyway so there's no need to hand craft it.

Also, the above moves filtering left into the WMI query, which is another PowerShell best practice. Native filtering in a cmdlet or in this case WMI is almost always faster than post filtering with Where{}.

I went with your first example using WMI, but this would be easily adapted to use Get-Process instead:

[Object[]]$Applications =
Get-Process |
Where-Object{ $_.Name -ne "Idle" -and $_.CPU -ge 1 } | 
Select-Object @{ n = "Application"; e = {$_.name }},
    @{ n = "CPU"; e = { [Double]$_.CPU }} |
Sort-Object CPU -Descending

$Object =
[PSCustomObject]@{
    DateTime        = Get-Date -Format s
    DiskUtilization = (Get-Counter -Counter "\PhysicalDisk(_Total)\% Disk Time").CounterSamples.CookedValue
    Applications    = $Applications
}

$Object | ConvertTo-Json | Out-File .\example.json -Append

Get-Process can return $null for the CPU property so I casted it to a [Double] above.

Note: I'm not sure the CPU value is scaled the same way as it would be from the performance data. Might have to research that and adjust to what you find.

In both cases I'm casting the $Applications variable into an object array Object[]]. This handles the edge case where only a 1 object is returned. Though I'm not sure it would matter in the JSON output.

Two arrays, no += with more best practices for better performance, readability etc... It should be faster than either of the Discord suggestions (not criticism...). Of course, that's relative you may not need that performance in your use case, but I thought you might like the demo.

Update

After following the Discord conversation I wanted to add an example that addresses the need to append the JSON output file with subsequent runs that create new instances of the object. I posted some code for that on the Discord thread, and JohnKubik later posted it here. but not being an expert in JSON there was a problem I had yet to pin down. So below I posting the final work product:

$Filter = "PercentProcessorTime >= 1 AND Name <> 'Idle' AND Name <> '_Total'"

# Predefine the expressions you'd like to use in the Select-Object command.
$AppPropsExoressions =
@(
    @{ Name = "Application"; Expression = { $_.Name } }
    @{ Name = "CPU"; Expression = { $_.PercentProcessorTime } }
)

# Predefine a script block to create the sub-objects:
$Applications = 
{
    Get-WmiObject Win32_PerfFormattedData_PerfProc_Process -Filter $Filter |
    Select-Object $AppPropsExoressions |
    Sort-Object CPU -Descending
}

# Was able to remove the ForEach loop by casting on both sides.
[Collections.ArrayList]$Objects = [Object[]]( Get-Content .\example.json | ConvertFrom-Json )

# Convert to ArrayList to make appending easier...
# $Objects = [Collections.ArrayList]$Objects

$Object =
[PSCustomObject]@{
    DateTime = Get-Date -Format s
    DiskUtilization = (Get-Counter -Counter "\PhysicalDisk(_Total)\% Disk Time").CounterSamples.CookedValue
    Applications = $Applications.Invoke()
}

[Void]$Objects.Add( $Object )

# Now overwrite the file! :
# To prevent converting app objects > hash syntax depth must be 3. Default = 2.
$Objects | ConvertTo-Json -Depth 3 | Out-File .\example.json 

A few points:

  1. I moved the applications array piece into a separate ScriptBlock variable and invoked it using the .Invoke() method inline with the creation of the parent object. This is similar to what hQWeedEater posted on the Discord, but it still avoids nesting a ForEach in the object declaration. It's easier to read than my last examples and allows for only 1 array.
  2. The expression hashes used with the Select-Object command are also predefined. Personally I like this approach, because at the start you don't always know what properties you're going to need. So, creating them at the top is a method of code segregation that helps readability further down.
  3. There was a problem casting scalar values encountered on the second run of the program to an ArrayList. In my last example I had ugly code using the .Add() method to force the $Objects variable to be an ArrayList. However, this version realized that if I cast appropriately on both sides of the assignment I can avoid all that.
  4. And finally I addressed the hash syntax issue by adding -Depth 3 to the ConvertTo-Json command.

One thing I don't like about this is the need to read and overwrite the entire file on each run. For all my overkill talking about performance this strikes me as inefficient. That said, and until I find something better this is working.

There may be more for you to do, like testing if the file already exists etc... However, I think the core of what you're doing is sufficiently addressed.

One final aside, is you/we shouldn't be using Get-WMIObject it's been deprecated and replaced with Get-CimInstance. It's already been removed from PowerShell 7+. It's an easy adjustment.

Sign up to request clarification or add additional context in comments.

1 Comment

This is brilliant. I am not sure why there arent more clear JSON powershell tutorials out there. But what you and the discord people provided is better than every JSON Powershell google result for the first 300 pages of google! Thanks again!
0

The only way I found out how to do it was going to an AMAZING discord community and having some Jedi Wizards help me out. I couldnt find out a way to do it from any other online source.

https://discord.gg/8XsSZhB

This guy names "Shockwave" and "hQWeedEater" came up with an ingenious way to take the PSObject, and convert it to an array, and then add additional parameters to the Array, and then convert the array to JSON.

Here is the code they created:

$procs = get-process | select @{n="Application";e={$_.name}},@{n="CPU";e={$_.CPU}}
$Applications = @()
$Applications += $procs | select -First 4 | ForEach-Object {
    New-Object -TypeName psobject -Property @{
        Application = $_.Application
        CPU = $_.CPU
    }
}
$Myarray = @()
$Myarray += New-Object -TypeName psobject -Property @{
    DateTime = Get-Date -Format "dd.MM.yyyy HH:mm:ss"
    Applications = $Applications
}


$Myarray | select DateTime, Applications | ConvertTo-Json 

Hope this helps someone in the future.

Comments

0

Here is another iteration of an answer!


[Collections.ArrayList]$Objects  = @()

Get-Content .\example.json |
ConvertFrom-Json |
ForEach-Object{ [Void]$Objects.Add( $_ )}

$Filter = "PercentProcessorTime >= 1 AND Name <> 'Idle' AND Name <> '_Total'"

[Object[]]$Applications =
Get-WmiObject Win32_PerfFormattedData_PerfProcProcess -Filter $Filter |
Select-Object @{ n = "Application"; e = {$.name }},
    @{ n = "CPU"; e = { $_.PercentProcessorTime }} |
Sort-Object CPU -Descending

$Object =
[PSCustomObject]@{
    DateTime = Get-Date -Format s
    DiskUtilization = (Get-Counter -Counter "\PhysicalDisk(_Total)% Disk Time").CounterSamples.CookedValue
    Applications = $Applications
}

[Void]$Objects.Add( $Object )

$Objects | ConvertTo-Json | Out-File .\example.json

1 Comment

I believe this is the code I posted on Discord for you. I noted later there's an issue where it's converting the applications elements to hash syntax. I'm still working that out. Understandably it's confusing to split between the 2 platforms. That said always try to clarify. Above is not an answer to the original question. However flawed at the moment it's an answer to the next step in your dev process, which is how to append the file each time you run. More to follow...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.