1

I am running a PowerShell script on a server to check on other machines on the network. I want the result of the check to be outputted in JSON format so I can send this JSON data via an api request to a web dashboard build with angular.

My set up:

Get-Request from Angular front end -> Express server -> run PowerShell script with Node-Powershell

Now I want to return the result in proper format to be used in the front end. What is the best way to accomplish this? I want to use the data to fill a material data table.

PowerShell Script status.ps1:

$Computers = @('ExampleComputer01', 'ExampleComputer02', 'ExampleComputer03', 'ExampleComputer04')
foreach ($computer in $Computers) {
gsv -cn $computer -Name ExampleProgram -ErrorAction 'SilentlyContinue'| select-object machinename, status | ConvertTo-Json 
}

Api from express server.js (using Node-Powershell):

app.get('/api/psjson', (request, response) => {
  ps.addCommand('./status.ps1');
  ps.invoke().then(output => {
    console.log(output);
    response.send(JSON.parse(output));
  }).catch(err => {
    console.log(err);
    response.send(err);
    ps.dispose();
  });
});

I tried using | ConvertTo-Json inside the loop but it is causing in error in node:

SyntaxError: Unexpected token { in JSON at position 55 at JSON.parse ()

John Bauer
  • 27
  • 8
  • "but that is not having the desired effect." - well, what effect _is it_ having, and how does that deviate from your expectations? – Mathias R. Jessen Feb 24 '21 at 16:21
  • I am getting an error in node:SyntaxError: Unexpected token { in JSON at position 55 at JSON.parse () – John Bauer Feb 24 '21 at 16:25
  • And what is the raw value of `output`? The error indicates it's not valid JSON, but hard to say what's wrong with it without seeing it :-) – Mathias R. Jessen Feb 24 '21 at 16:28
  • you are correct. It is not valid json. Output is: { "MachinenName": "Computer01", "Status": 4 } { "MachinenName": "Computer02", "Status": 4 } and so on... I am looking for a way to get valid json output from the powershelgl script. – John Bauer Feb 24 '21 at 16:34
  • I think the problem is running ConvertTo-Json inside the loop. Is there a way to run through the loop, check all Machines and then output all the results as on valid JSON? – John Bauer Feb 24 '21 at 16:36

2 Answers2

1

Please try the following:

$Computers = @('ExampleComputer01', 'ExampleComputer02', 'ExampleComputer03', 'ExampleComputer04')
$Results = @()
foreach ($computer in $Computers) {
    $Result = $null
    $Result = gsv -cn $computer -Name ExampleProgram -ErrorAction 'SilentlyContinue'| select-object machinename, status
    If ($Result -ne $null){
        $Results += $Result
    }
}

$Results | ConvertTo-Json

This builds an array of the results and then converts the array to JSON.

I think the issue you are experiencing is due to converting inside a loop and therefore the structure is incorrect.

CraftyB
  • 721
  • 5
  • 11
  • 2
    Kudos for recognizing the need to feed all input objects to a _single_ `ConvertTo-Json` call, but note that, in general, incrementally "extending" an array in a loop is inefficient, because a _new_ array must be created behind the scenes _in every iteration_, because arrays are _immutable_; a much more efficient approach is to use the `foreach` loop as an _expression_ and let PowerShell automatically collect the outputs in an array: `[array] $outputs = foreach (...) { ... }` - see [this answer](https://stackoverflow.com/a/60708579/45375). – mklement0 Feb 24 '21 at 17:23
0

CraftyB's answer diagnoses the problem correctly, but there's a simpler, more PowerShell-idiomatic solution that uses a single pipeline:

$computers = 'ExampleComputer01', 'ExampleComputer02', 'ExampleComputer03', 'ExampleComputer04'

gsv -cn $computers -Name Example -ErrorAction SilentlyContinue |
  | Select-Object machinename, status | 
      ConvertTo-Json
  • The crucial aspect is that all input objects are passed to a single ConvertTo-Json call - see the bottom section for an explanation.

  • In Windows PowerShell, Get-Service (gsv) the -ComputerName (-cn) parameter directly accepts an array of computer names.

    • Note: In PowerShell (Core) 7+, this form of remoting is no longer supported, so there is no -ComputerName parameter; there, assuming that the target computers are set up for PowerShell remoting, you could use:

      Invoke-Command -ComputerName $computers { Get-Service -Name Example -ErrorAction SilentlyContinue }
      

As for what you tried:

If you call ConvertTo-Json inside a loop, per input object, you will implicitly output multiple, independent JSON strings instead of a single JSON string representing the input objects as a JSON array:

Given the following sample input objects:

$objects = [pscustomobject] @{ foo=1 }, [pscustomobject] @{ foo=2 }

It is the difference between:

# !! BROKEN: *multiple* ConvertTo-Json calls.
# !! When the result is stringified, you get a space-separated list of the 
# !! individual JSON strings, which is NOT valid JSON.
PS> @"
$(
  foreach ($object in $objects) { $object | ConvertTo-Json -Compress }
)
"@

{"foo":1} {"foo":2}  # NOT valid JSON.

and:

# OK: *Single* ConvertTo-Json call, which outputs a valid JSON array.
PS> $objects | ConvertTo-Json -Compress

[{"foo":1},{"foo":2}]  # OK: valid JSON array.
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • This works, and thank you for the further insight into PowerShell. Is it ok to add a follow up question? I have a "lookup-table" in my powershell script, assigning more easy to understand names to the computers (eg. W12345 = 'East Building PC 1') is there an elegant way to include this information into the json output? – John Bauer Feb 25 '21 at 10:53
  • Thanks for accepting, @JohnBauer. As for the follow-up question: You need to use a [calculated property](https://stackoverflow.com/a/39861920/45375), something along the lines of: `Select-Object machinename, status, @{ name='Comment'; e={ $lookupTable[$_.machinename] }` If you need further assistance, please create a _new_ question post. – mklement0 Feb 25 '21 at 14:01
  • 1
    thanks for the follow-up. this worked as well! – John Bauer Feb 25 '21 at 14:54