How can you combine a split JSON schema and values into single PowerShell object

149 views Asked by At

I am working with the Microsoft Graph API, and trying to produce an app device install status report from Intune. The intent is to provide an Intune application ID, and get a list of devices with the application installed.

Using Invoke-RestMethod, I am able to retrieve the same data that is shown in the Intune portal, but the resulting JSON is not in what I would class as an ideal format for manipulating with Powershell (at least based on my current knowledge of working with PowerShell).

For example, the resulting object from Invoke-RestMethod looks like this:

TotalRowCount : 923
Schema        : {@{Column=AppInstallState; PropertyType=String}, @{Column=AppInstallState_loc; PropertyType=String}, @{Column=ApplicationId; PropertyType=String}, @{Column=DeviceId; PropertyType=String}…}
Values        : {S1 Installed 39c23e0b-0098-4eb4-9613-232a005eee82 5010bc04-d22f-46ba-94bb-4bd3213ad13f CPC-james-NSYIT  1 82633388-108c-4ea9-842e-ff9a849f5159 Allison, James [email protected], S1 Installed 39c23e0b-0098-4eb4-9613-232a005eee82 f8ec0b7a-044a-4098-be27-b680072a3b83
            FD-2do7AN5hEMv2  1 b7c24328-af42-408a-9f3b-dd23b7108fb4 Gowen, Michael [email protected], S1 Installed 39c23e0b-0098-4eb4-9613-232a005eee82 1204947b-45e4-44db-8276-c4cfa73c676d FD-3wqZZo23KVTG  1 ada75844-368f-445e-9e8c-21c57c8bae6e Penson, Mark
            [email protected], S1 Installed 39c23e0b-0098-4eb4-9613-232a005eee82 2747782c-fdd7-4b76-b983-8e5694021a60 FD-4MGi6Q4VYGMb  1 d61099b3-f4d3-4e19-bcb8-1481d098b848 Capp, Adrian [email protected]…}
SessionId     : 

The Schema property being an array of objects (I believe), means I can call a specific index, then retrieve a property by doing something like $Object.Schema[0].Column which would give me "AppInstallState". This can obviously be used to get various other column names, and their type (string, boolean, datetime etc).

The issue I have, is the actual list of devices and associated data is stored in the Values property, which appears to be an array of strings? Therefore, it seems a bit more difficult to recall specific devices. I have no ability to lookup one of the Values based on the devicename (e.g $Values | Where-Object {$_.DeviceName -eq ''}

How can I take the schema referenced from the Schema property, combine it with the Values property, to give me an array of objects where the property names are the column names from the schema, and the values are the strings from the Values property.

Would I just have to manually iterate through all the values, and manually build a PSCustomObject?

EDIT: Details of Invoke-RestMethod commands:

$body = @{
    select = @(
        "DeviceName"
        "UserPrincipalName"
        "InstallState"
        "DeviceId"
        "ErrorCode"
        "UserName"
        "UserId"
        "ApplicationId"
        "AppInstallState"
    )
    skip = 0
    top = 50
    filter = "(ApplicationId eq '$ApplicationID')"
    orderBy = @(
    )
}
$body = $body | ConvertTo-Json
$GraphAPIResource = "https://graph.microsoft.com/$graphApiVersion/deviceManagement/reports/getDeviceInstallStatusReport"
$GraphAPIReturn = Invoke-RestMethod -Headers @{Authorization = "Bearer $($accesstoken)"} -Uri $GraphAPIResource -Method Post -Body $body -ContentType "application/json"
2

There are 2 answers

1
mclayton On BEST ANSWER

Using the data from your previous question there are two properties that define the data:

  • Schema - contains a list of column names
  • Values - contains a jagged array where each child item is an array of property values that run in parallel to the Schema column definitions

This format is presumably to reduce the amount of bytes needed to be sent when downloading a report with a large number of items in Values as it can contain a compact json array of values for each item rather than a json object that repeats column names in each entry.

Here's the sample data from your other question:

$json = @"
{"TotalRowCount":917,"Schema":[{"Column":"AppInstallState","PropertyType":"String"},{"Column":"AppInstallState_loc","PropertyType":"String"},{"Column":"AppInstallStateDetails","PropertyType":"String"},{"Column":"AppInstallStateDetails_loc","PropertyType":"String"},{"Column":"ApplicationId","PropertyType":"String"},{"Column":"AppVersion","PropertyType":"String"},{"Column":"AssignmentFilterIdsExist","PropertyType":"SByte"},{"Column":"AssignmentFilterIdsList","PropertyType":"String"},{"Column":"DeviceId","PropertyType":"String"},{"Column":"DeviceName","PropertyType":"String"},{"Column":"ErrorCode","PropertyType":"Int32"},{"Column":"HexErrorCode","PropertyType":"String"},{"Column":"InstallState","PropertyType":"Int32"},{"Column":"InstallStateDetail","PropertyType":"Int32"},{"Column":"LastModifiedDateTime","PropertyType":"DateTime"},{"Column":"Platform","PropertyType":"String"},{"Column":"UserId","PropertyType":"String"},{"Column":"UserName","PropertyType":"String"},{"Column":"UserPrincipalName","PropertyType":"String"}],"Values":[["S1","Installed","E0","","39c23e0b-0098-4eb4-9613-232a005eee82","6.96.170",false,"","5010bc04-d22f-46ba-94bb-4bd3213ad13f","CPC-james-NSYIT","","",1,0,"2023-10-10T10:13:30","Windows 10.0.19045.3570","82633388-108c-4ea9-842e-ff9a849f5159","[redacted]","[redacted]"],["S1","Installed","E0","","39c23e0b-0098-4eb4-9613-232a005eee82","6.96.170",false,"","f8ec0b7a-044a-4098-be27-b680072a3b83","FD-2do7AN5hEMv2","","",1,0,"2023-10-12T10:38:23","Windows 10.0.22621.2428","b7c24328-af42-408a-9f3b-dd23b7108fb4","[redacted]","[redacted]"]],"SessionId":""}
"@;

$data = $json | ConvertFrom-Json

$data | fl *

# TotalRowCount : 917
#  Schema        : {@{Column=AppInstallState; PropertyType=String}, # 
#  @{Column=AppInstallState_loc;
#                 PropertyType=String}, @{Column=AppInstallStateDetails; PropertyType=String},
#                 @{Column=AppInstallStateDetails_loc; PropertyType=String}…}
# Values        : {S1 Installed E0  39c23e0b-0098-4eb4-9613-232a005eee82 6.96.170 False
#                 5010bc04-d22f-46ba-94bb-4bd3213ad13f CPC-james-NSYIT   1 0 10/10/2023 10:13:30
#                 Windows 10.0.19045.3570 82633388-108c-4ea9-842e-ff9a849f5159 [redacted]
#                 [redacted], S1 Installed E0
#                 39c23e0b-0098-4eb4-9613-232a005eee82 6.96.170 False
#                 f8ec0b7a-044a-4098-be27-b680072a3b83 FD-2do7AN5hEMv2   1 0 12/10/2023 10:38:23
#                 Windows 10.0.22621.2428 b7c24328-af42-408a-9f3b-dd23b7108fb4 [redacted]
#                 [redacted]}
# SessionId     :

and to convert it into a simpler representation you can do this:

$objects = $data.Values | foreach-object `
    -Begin   {
        $propertyNames = @($data.Schema.Column)
    } `
    -Process {
        $properties = [ordered] @{};
        for( $i = 0; $i -lt $data.Schema.Length; $i++ )
        {
            $properties[$propertyNames[$i]] = $_[$i];
        }
        new-object PSCustomObject -Property $properties
    }

This basically loops over each item in Values and pairs the items in the child array with the column name in the Schema, and then builds a PSCustomObject for each one:

$objects | fl *

AssignmentFilterIdsExist   : False
InstallState               : 1
AppInstallStateDetails     : E0
AssignmentFilterIdsList    :
LastModifiedDateTime       : 10/10/2023 10:13:30
UserId                     : 82633388-108c-4ea9-842e-ff9a849f5159
AppInstallStateDetails_loc :
UserName                   : [redacted]
AppInstallState            : S1
DeviceId                   : 5010bc04-d22f-46ba-94bb-4bd3213ad13f
Platform                   : Windows 10.0.19045.3570
AppInstallState_loc        : Installed
DeviceName                 : CPC-james-NSYIT
ErrorCode                  :
InstallStateDetail         : 0
AppVersion                 : 6.96.170
UserPrincipalName          : [redacted]
ApplicationId              : 39c23e0b-0098-4eb4-9613-232a005eee82
HexErrorCode               :

AssignmentFilterIdsExist   : False
InstallState               : 1
AppInstallStateDetails     : E0
AssignmentFilterIdsList    :
LastModifiedDateTime       : 12/10/2023 10:38:23
UserId                     : b7c24328-af42-408a-9f3b-dd23b7108fb4
AppInstallStateDetails_loc :
UserName                   : [redacted]
AppInstallState            : S1
DeviceId                   : f8ec0b7a-044a-4098-be27-b680072a3b83
Platform                   : Windows 10.0.22621.2428
AppInstallState_loc        : Installed
DeviceName                 : FD-2do7AN5hEMv2
ErrorCode                  :
InstallStateDetail         : 0
AppVersion                 : 6.96.170
UserPrincipalName          : [redacted]
ApplicationId              : 39c23e0b-0098-4eb4-9613-232a005eee82
HexErrorCode               :
1
mklement0 On

Would I just have to manually iterate through all the values, and manually build a PSCustomObject?

Yes (at least if you're limited to PowerShell's built-in functionality).

The following demonstrates this; it is based on the sample JSON document shown in your previous question (which matches the for-display-formatted data in your question that results from having parsed the JSON document into a PowerShell object graph):

# Simulation of your Invoke-RestMethdod call.
$GraphAPIReturn = 
  @'
  {"TotalRowCount":917,"Schema":[{"Column":"AppInstallState","PropertyType":"String"},{"Column":"AppInstallState_loc","PropertyType":"String"},{"Column":"AppInstallStateDetails","PropertyType":"String"},{"Column":"AppInstallStateDetails_loc","PropertyType":"String"},{"Column":"ApplicationId","PropertyType":"String"},{"Column":"AppVersion","PropertyType":"String"},{"Column":"AssignmentFilterIdsExist","PropertyType":"SByte"},{"Column":"AssignmentFilterIdsList","PropertyType":"String"},{"Column":"DeviceId","PropertyType":"String"},{"Column":"DeviceName","PropertyType":"String"},{"Column":"ErrorCode","PropertyType":"Int32"},{"Column":"HexErrorCode","PropertyType":"String"},{"Column":"InstallState","PropertyType":"Int32"},{"Column":"InstallStateDetail","PropertyType":"Int32"},{"Column":"LastModifiedDateTime","PropertyType":"DateTime"},{"Column":"Platform","PropertyType":"String"},{"Column":"UserId","PropertyType":"String"},{"Column":"UserName","PropertyType":"String"},{"Column":"UserPrincipalName","PropertyType":"String"}],"Values":[["S1","Installed","E0","","39c23e0b-0098-4eb4-9613-232a005eee82","6.96.170",false,"","5010bc04-d22f-46ba-94bb-4bd3213ad13f","CPC-james-NSYIT","","",1,0,"2023-10-10T10:13:30","Windows 10.0.19045.3570","82633388-108c-4ea9-842e-ff9a849f5159","Allison, James","[email protected]"],["S1","Installed","E0","","39c23e0b-0098-4eb4-9613-232a005eee82","6.96.170",false,"","f8ec0b7a-044a-4098-be27-b680072a3b83","FD-2do7AN5hEMv2","","",1,0,"2023-10-12T10:38:23","Windows 10.0.22621.2428","b7c24328-af42-408a-9f3b-dd23b7108fb4","Gowen, Michael","[email protected]"]],"SessionId":""}
'@ | ConvertFrom-Json

# Build an ordered hashtable of all property (colum names)
# that will serve as the template for creating individual
# output objects
$props = [ordered] @{}
$GraphAPIReturn.Schema | ForEach-Object { $props[$_.Column] = $null }

# Loop over the nested arrays in .Values
$GraphAPIReturn.Values |
  ForEach-Object {
    # Loop over the individual values in each
    # nested array and assign them to the corresponding property.
    $i = 0
    foreach ($val in $_) { $props[$i++] = $val }
    # Convert to a [pscustomobject] and output.
    [pscustomobject] $props
  }

Display output (2 implicitly Format-List-formatted [pscustomobject] instances whose property names come from the .Schema.Column input property values and whose property values come from the individual value arrays in .Values):

AppInstallState            : S1
AppInstallState_loc        : Installed
AppInstallStateDetails     : E0
AppInstallStateDetails_loc : 
ApplicationId              : 39c23e0b-0098-4eb4-9613-232a005eee82
AppVersion                 : 6.96.170
AssignmentFilterIdsExist   : False
AssignmentFilterIdsList    : 
DeviceId                   : 5010bc04-d22f-46ba-94bb-4bd3213ad13f
DeviceName                 : CPC-james-NSYIT
ErrorCode                  : 
HexErrorCode               : 
InstallState               : 1
InstallStateDetail         : 0
LastModifiedDateTime       : 10/10/2023 10:13:30 AM
Platform                   : Windows 10.0.19045.3570
UserId                     : 82633388-108c-4ea9-842e-ff9a849f5159
UserName                   : Allison, James
UserPrincipalName          : [email protected]

AppInstallState            : S1
AppInstallState_loc        : Installed
AppInstallStateDetails     : E0
AppInstallStateDetails_loc : 
ApplicationId              : 39c23e0b-0098-4eb4-9613-232a005eee82
AppVersion                 : 6.96.170
AssignmentFilterIdsExist   : False
AssignmentFilterIdsList    : 
DeviceId                   : f8ec0b7a-044a-4098-be27-b680072a3b83
DeviceName                 : FD-2do7AN5hEMv2
ErrorCode                  : 
HexErrorCode               : 
InstallState               : 1
InstallStateDetail         : 0
LastModifiedDateTime       : 10/12/2023 10:38:23 AM
Platform                   : Windows 10.0.22621.2428
UserId                     : b7c24328-af42-408a-9f3b-dd23b7108fb4
UserName                   : Gowen, Michael
UserPrincipalName          : [email protected]