arcane-os 0.78.0 → 0.79.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +6 -1
- package/browser-runtime/ai/core-onnx.mjs +6 -2
- package/browser-runtime/ai/speech-worker-runtime.mjs +1 -1
- package/browser-runtime/core/contracts.mjs +10 -0
- package/docs/publishing.md +20 -3
- package/docs/reference/ai/browser-speech.md +7 -1
- package/docs/reference/execution-devices.md +157 -0
- package/docs/reference/inventory/package-api.json +39 -11
- package/docs/reference/local-ai.md +104 -10
- package/docs/reference/native-decisions.md +65 -10
- package/package.json +4 -2
- package/src/core/services/decisions.mjs +51 -15
- package/src/core/services/execution-devices.mjs +32 -0
- package/src/core/services/local-ai.mjs +12 -5
- package/src/local-ai/decisions.mjs +93 -15
- package/src/local-ai/dev.mjs +7 -3
- package/src/local-ai/execution-devices-windows.ps1 +833 -0
- package/src/local-ai/execution-devices.mjs +288 -0
- package/src/local-ai/onnx-worker.mjs +149 -19
- package/src/local-ai/onnx.mjs +87 -27
- package/src/native/portable-layout.mjs +8 -1
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
# Invoked through stdin by the SDK's owned PowerShell process. Keep stdout JSON-only.
|
|
2
|
+
$ErrorActionPreference = 'Stop'
|
|
3
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
4
|
+
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
5
|
+
|
|
6
|
+
$inventoryDevices = [System.Collections.Generic.List[object]]::new()
|
|
7
|
+
$inventoryIssues = [System.Collections.Generic.List[object]]::new()
|
|
8
|
+
|
|
9
|
+
function Add-InventoryIssue {
|
|
10
|
+
param([string]$Source, [string]$Code, [string]$Message, $DeviceId = $null)
|
|
11
|
+
|
|
12
|
+
$inventoryIssues.Add([ordered]@{
|
|
13
|
+
source = $Source
|
|
14
|
+
code = $Code
|
|
15
|
+
message = $Message
|
|
16
|
+
deviceId = $DeviceId
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function Get-InventoryErrorText {
|
|
21
|
+
param([System.Management.Automation.ErrorRecord]$Record)
|
|
22
|
+
|
|
23
|
+
$details = [System.Collections.Generic.List[string]]::new()
|
|
24
|
+
$details.Add($Record.Exception.ToString())
|
|
25
|
+
if ($null -ne $Record.ErrorDetails) {
|
|
26
|
+
$details.Add($Record.ErrorDetails.Message)
|
|
27
|
+
}
|
|
28
|
+
if ($null -ne $Record.InvocationInfo) {
|
|
29
|
+
$details.Add($Record.InvocationInfo.PositionMessage)
|
|
30
|
+
}
|
|
31
|
+
if ($Record.ScriptStackTrace) {
|
|
32
|
+
$details.Add($Record.ScriptStackTrace)
|
|
33
|
+
}
|
|
34
|
+
return [string]::Join([Environment]::NewLine, $details)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function Add-HostCpu {
|
|
38
|
+
try {
|
|
39
|
+
$processors = [System.Collections.Generic.List[object]]::new()
|
|
40
|
+
$names = [System.Collections.Generic.List[string]]::new()
|
|
41
|
+
$reportedProcessors = @(Get-CimInstance -ClassName Win32_Processor -ErrorAction Stop)
|
|
42
|
+
foreach ($processor in $reportedProcessors) {
|
|
43
|
+
$processorName = [string]$processor.Name
|
|
44
|
+
$processorId = [string]$processor.DeviceID
|
|
45
|
+
if ($processorName -ne '') {
|
|
46
|
+
$names.Add($processorName)
|
|
47
|
+
}
|
|
48
|
+
if ($processorName -eq '' -or $processorId -eq '') {
|
|
49
|
+
Add-InventoryIssue 'cpu' 'property-unavailable' 'Win32_Processor did not report a processor Name or DeviceID.' 'cpu'
|
|
50
|
+
}
|
|
51
|
+
$processors.Add([ordered]@{
|
|
52
|
+
deviceId = $processorId
|
|
53
|
+
name = $processorName
|
|
54
|
+
socketDesignation = if ($null -eq $processor.SocketDesignation) { $null } else { [string]$processor.SocketDesignation }
|
|
55
|
+
cores = if ($null -eq $processor.NumberOfCores) { $null } else { [uint32]$processor.NumberOfCores }
|
|
56
|
+
logicalProcessors = if ($null -eq $processor.NumberOfLogicalProcessors) { $null } else { [uint32]$processor.NumberOfLogicalProcessors }
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
if ($processors.Count -eq 0) {
|
|
60
|
+
Add-InventoryIssue 'cpu' 'enumeration-failed' 'Win32_Processor returned no processor records.'
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
$cpuName = if ($names.Count -gt 0) { [string]::Join(' / ', $names) } else { 'Host CPU' }
|
|
64
|
+
$inventoryDevices.Add([ordered]@{
|
|
65
|
+
deviceId = 'cpu'
|
|
66
|
+
identitySource = 'host-cpu'
|
|
67
|
+
kind = 'cpu'
|
|
68
|
+
name = $cpuName
|
|
69
|
+
present = $true
|
|
70
|
+
isHardware = $null
|
|
71
|
+
isIntegrated = $null
|
|
72
|
+
dedicatedMemoryMiB = $null
|
|
73
|
+
addresses = [ordered]@{
|
|
74
|
+
adapterLuid = $null
|
|
75
|
+
pci = $null
|
|
76
|
+
dxgiAdapterIndex = $null
|
|
77
|
+
}
|
|
78
|
+
processors = $processors.ToArray()
|
|
79
|
+
})
|
|
80
|
+
} catch {
|
|
81
|
+
Add-InventoryIssue 'cpu' 'enumeration-failed' (Get-InventoryErrorText $_)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function Get-PresentPciDevices {
|
|
86
|
+
# One present-device census and one batched property query serve both kinds.
|
|
87
|
+
# A PCI bus address is a correlation fact, never a persisted selection key.
|
|
88
|
+
$devicesById = @{}
|
|
89
|
+
try {
|
|
90
|
+
$census = @(Get-PnpDevice -PresentOnly -ErrorAction Continue 2>&1 3>&1)
|
|
91
|
+
foreach ($item in $census) {
|
|
92
|
+
if ($item -is [System.Management.Automation.ErrorRecord]) {
|
|
93
|
+
Add-InventoryIssue 'identity' 'enumeration-failed' (Get-InventoryErrorText $item)
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
if ($item -is [System.Management.Automation.WarningRecord]) {
|
|
97
|
+
Add-InventoryIssue 'identity' 'enumeration-failed' $item.Message
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
$instanceId = [string]$item.InstanceId
|
|
101
|
+
if ($instanceId.StartsWith('PCI\', [StringComparison]::OrdinalIgnoreCase)) {
|
|
102
|
+
$devicesById[$instanceId] = [ordered]@{
|
|
103
|
+
instanceId = $instanceId
|
|
104
|
+
bus = $null
|
|
105
|
+
address = $null
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if ($devicesById.Count -gt 0) {
|
|
110
|
+
$properties = @(
|
|
111
|
+
Get-PnpDeviceProperty -InstanceId ([string[]]@($devicesById.Keys)) -KeyName 'DEVPKEY_Device_BusNumber', 'DEVPKEY_Device_Address' -ErrorAction Continue 2>&1 3>&1
|
|
112
|
+
)
|
|
113
|
+
foreach ($property in $properties) {
|
|
114
|
+
if ($property -is [System.Management.Automation.ErrorRecord]) {
|
|
115
|
+
Add-InventoryIssue 'identity' 'property-failed' (Get-InventoryErrorText $property)
|
|
116
|
+
continue
|
|
117
|
+
}
|
|
118
|
+
if ($property -is [System.Management.Automation.WarningRecord]) {
|
|
119
|
+
Add-InventoryIssue 'identity' 'property-failed' $property.Message
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
$entry = $devicesById[[string]$property.InstanceId]
|
|
123
|
+
if ($null -eq $entry -or $null -eq $property.Data) {
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
if ($property.KeyName -eq 'DEVPKEY_Device_BusNumber') {
|
|
127
|
+
$entry.bus = [uint32]$property.Data
|
|
128
|
+
} elseif ($property.KeyName -eq 'DEVPKEY_Device_Address') {
|
|
129
|
+
# DEVPKEY_Device_Address is INT32; preserve its unsigned bits.
|
|
130
|
+
$address = [uint32]([int64]$property.Data -band 4294967295L)
|
|
131
|
+
if ($address -eq 4294967295L) {
|
|
132
|
+
Add-InventoryIssue 'identity' 'property-unavailable' 'The PCI bus driver did not supply a device address.' $entry.instanceId
|
|
133
|
+
} else {
|
|
134
|
+
$entry.address = $address
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch [System.Management.Automation.CommandNotFoundException] {
|
|
140
|
+
Add-InventoryIssue 'identity' 'enumeration-unavailable' (Get-InventoryErrorText $_)
|
|
141
|
+
} catch {
|
|
142
|
+
Add-InventoryIssue 'identity' 'enumeration-failed' (Get-InventoryErrorText $_)
|
|
143
|
+
}
|
|
144
|
+
return $devicesById
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# SDK-authored ABI declarations for the Windows system libraries. Buffer lengths
|
|
148
|
+
# below are native-call framing only; no inventory content limit is imposed.
|
|
149
|
+
$nativeSource = @'
|
|
150
|
+
using System;
|
|
151
|
+
using System.Collections.Generic;
|
|
152
|
+
using System.Runtime.InteropServices;
|
|
153
|
+
using System.Text;
|
|
154
|
+
|
|
155
|
+
namespace Arcane.ExecutionDevices
|
|
156
|
+
{
|
|
157
|
+
public sealed class InventoryIssue
|
|
158
|
+
{
|
|
159
|
+
public string Source;
|
|
160
|
+
public string Code;
|
|
161
|
+
public string Message;
|
|
162
|
+
|
|
163
|
+
public InventoryIssue(string source, string code, string message)
|
|
164
|
+
{
|
|
165
|
+
Source = source;
|
|
166
|
+
Code = code;
|
|
167
|
+
Message = message;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
172
|
+
public struct AdapterLuid
|
|
173
|
+
{
|
|
174
|
+
public uint LowPart;
|
|
175
|
+
public int HighPart;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
179
|
+
public struct PciAddress
|
|
180
|
+
{
|
|
181
|
+
public uint Bus;
|
|
182
|
+
public uint Device;
|
|
183
|
+
public uint Function;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public sealed class Adapter
|
|
187
|
+
{
|
|
188
|
+
public string Kind;
|
|
189
|
+
public string Name;
|
|
190
|
+
public bool Present;
|
|
191
|
+
public bool? IsHardware;
|
|
192
|
+
public bool? IsIntegrated;
|
|
193
|
+
public double? DedicatedMemoryMiB;
|
|
194
|
+
public AdapterLuid? Luid;
|
|
195
|
+
public PciAddress? Pci;
|
|
196
|
+
public uint? DxgiAdapterIndex;
|
|
197
|
+
public List<InventoryIssue> Issues = new List<InventoryIssue>();
|
|
198
|
+
|
|
199
|
+
public Adapter(string kind)
|
|
200
|
+
{
|
|
201
|
+
Kind = kind;
|
|
202
|
+
Name = "Windows " + kind.ToUpperInvariant();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
public sealed class Inventory
|
|
207
|
+
{
|
|
208
|
+
public List<Adapter> Adapters = new List<Adapter>();
|
|
209
|
+
public List<InventoryIssue> Issues = new List<InventoryIssue>();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
public static class WindowsInventory
|
|
213
|
+
{
|
|
214
|
+
private enum AdapterProperty : uint
|
|
215
|
+
{
|
|
216
|
+
InstanceLuid = 0,
|
|
217
|
+
DriverDescription = 2,
|
|
218
|
+
DedicatedAdapterMemory = 7,
|
|
219
|
+
IsHardware = 11,
|
|
220
|
+
IsIntegrated = 12
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
224
|
+
private struct OpenAdapterFromLuid
|
|
225
|
+
{
|
|
226
|
+
public AdapterLuid Luid;
|
|
227
|
+
public uint Handle;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
231
|
+
private struct QueryAdapterInfo
|
|
232
|
+
{
|
|
233
|
+
public uint Handle;
|
|
234
|
+
public uint Type;
|
|
235
|
+
public IntPtr Data;
|
|
236
|
+
public uint DataLength;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
240
|
+
private struct CloseAdapter
|
|
241
|
+
{
|
|
242
|
+
public uint Handle;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
246
|
+
private struct DxgiAdapterDescription
|
|
247
|
+
{
|
|
248
|
+
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
|
249
|
+
public string Description;
|
|
250
|
+
public uint VendorId;
|
|
251
|
+
public uint DeviceId;
|
|
252
|
+
public uint SubSystemId;
|
|
253
|
+
public uint Revision;
|
|
254
|
+
public UIntPtr DedicatedVideoMemory;
|
|
255
|
+
public UIntPtr DedicatedSystemMemory;
|
|
256
|
+
public UIntPtr SharedSystemMemory;
|
|
257
|
+
public AdapterLuid Luid;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
[DllImport("dxcore.dll", ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
|
|
261
|
+
private static extern int DXCoreCreateAdapterFactory(ref Guid iid, out IntPtr factory);
|
|
262
|
+
|
|
263
|
+
[DllImport("dxgi.dll", ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
|
|
264
|
+
private static extern int CreateDXGIFactory(ref Guid iid, out IntPtr factory);
|
|
265
|
+
|
|
266
|
+
[DllImport("gdi32.dll", ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
|
|
267
|
+
private static extern int D3DKMTOpenAdapterFromLuid(ref OpenAdapterFromLuid adapter);
|
|
268
|
+
|
|
269
|
+
[DllImport("gdi32.dll", ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
|
|
270
|
+
private static extern int D3DKMTQueryAdapterInfo(ref QueryAdapterInfo query);
|
|
271
|
+
|
|
272
|
+
[DllImport("gdi32.dll", ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
|
|
273
|
+
private static extern int D3DKMTCloseAdapter(ref CloseAdapter adapter);
|
|
274
|
+
|
|
275
|
+
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
276
|
+
private delegate int CreateAdapterList(IntPtr self, uint count, ref Guid attribute, ref Guid iid, out IntPtr list);
|
|
277
|
+
|
|
278
|
+
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
279
|
+
private delegate int GetAdapter(IntPtr self, uint index, ref Guid iid, out IntPtr adapter);
|
|
280
|
+
|
|
281
|
+
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
282
|
+
private delegate uint GetAdapterCount(IntPtr self);
|
|
283
|
+
|
|
284
|
+
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
285
|
+
[return: MarshalAs(UnmanagedType.I1)]
|
|
286
|
+
private delegate bool GetNativeBoolean(IntPtr self);
|
|
287
|
+
|
|
288
|
+
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
289
|
+
[return: MarshalAs(UnmanagedType.I1)]
|
|
290
|
+
private delegate bool IsPropertySupported(IntPtr self, AdapterProperty property);
|
|
291
|
+
|
|
292
|
+
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
293
|
+
private delegate int GetProperty(IntPtr self, AdapterProperty property, UIntPtr bufferLength, IntPtr data);
|
|
294
|
+
|
|
295
|
+
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
296
|
+
private delegate int GetPropertySize(IntPtr self, AdapterProperty property, out UIntPtr bufferLength);
|
|
297
|
+
|
|
298
|
+
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
299
|
+
private delegate int EnumerateDxgiAdapter(IntPtr self, uint index, out IntPtr adapter);
|
|
300
|
+
|
|
301
|
+
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
|
302
|
+
private delegate int GetDxgiDescription(IntPtr self, out DxgiAdapterDescription description);
|
|
303
|
+
|
|
304
|
+
public static Inventory Read()
|
|
305
|
+
{
|
|
306
|
+
Inventory inventory = new Inventory();
|
|
307
|
+
IntPtr factory = IntPtr.Zero;
|
|
308
|
+
try
|
|
309
|
+
{
|
|
310
|
+
Guid factoryId = new Guid("78ee5945-c36e-4b13-a669-005dd11c0f06");
|
|
311
|
+
int result = DXCoreCreateAdapterFactory(ref factoryId, out factory);
|
|
312
|
+
RequireComResult(result, factory);
|
|
313
|
+
Guid gpuAttribute = new Guid("b69eb219-3ded-4464-979f-a00bd4687006");
|
|
314
|
+
Guid npuAttribute = new Guid("d46140c4-add7-451b-9e56-06fe8c3b58ed");
|
|
315
|
+
ReadAdapterKind(factory, "gpu", gpuAttribute, inventory);
|
|
316
|
+
ReadAdapterKind(factory, "npu", npuAttribute, inventory);
|
|
317
|
+
}
|
|
318
|
+
catch (Exception error)
|
|
319
|
+
{
|
|
320
|
+
string code = IsApiUnavailable(error) ? "enumeration-unavailable" : "enumeration-failed";
|
|
321
|
+
inventory.Issues.Add(
|
|
322
|
+
new InventoryIssue("gpu", code, "DXCore factory: " + error.ToString())
|
|
323
|
+
);
|
|
324
|
+
inventory.Issues.Add(
|
|
325
|
+
new InventoryIssue("npu", code, "DXCore factory: " + error.ToString())
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
finally
|
|
329
|
+
{
|
|
330
|
+
ReleaseCom(factory, "identity", inventory.Issues);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (inventory.Adapters.Count > 0)
|
|
334
|
+
{
|
|
335
|
+
ReadDxgiAddresses(inventory);
|
|
336
|
+
}
|
|
337
|
+
return inventory;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private static void ReadAdapterKind(IntPtr factory, string kind, Guid attribute, Inventory inventory)
|
|
341
|
+
{
|
|
342
|
+
IntPtr list = IntPtr.Zero;
|
|
343
|
+
try
|
|
344
|
+
{
|
|
345
|
+
Guid listId = new Guid("526c7776-40e9-459b-b711-f32ad76dfc28");
|
|
346
|
+
CreateAdapterList createList = GetMethod<CreateAdapterList>(factory, 3);
|
|
347
|
+
int result = createList(factory, 1, ref attribute, ref listId, out list);
|
|
348
|
+
RequireComResult(result, list);
|
|
349
|
+
GetAdapterCount getCount = GetMethod<GetAdapterCount>(list, 4);
|
|
350
|
+
GetAdapter getAdapter = GetMethod<GetAdapter>(list, 3);
|
|
351
|
+
uint count = getCount(list);
|
|
352
|
+
Guid adapterId = new Guid("f0db4c7f-fe5a-42a2-bd62-f2a6cf6fc83e");
|
|
353
|
+
for (uint index = 0; index < count; index++)
|
|
354
|
+
{
|
|
355
|
+
IntPtr pointer = IntPtr.Zero;
|
|
356
|
+
Adapter adapter = null;
|
|
357
|
+
try
|
|
358
|
+
{
|
|
359
|
+
result = getAdapter(list, index, ref adapterId, out pointer);
|
|
360
|
+
RequireComResult(result, pointer);
|
|
361
|
+
adapter = new Adapter(kind);
|
|
362
|
+
inventory.Adapters.Add(adapter);
|
|
363
|
+
GetNativeBoolean isValid = GetMethod<GetNativeBoolean>(pointer, 3);
|
|
364
|
+
adapter.Present = isValid(pointer);
|
|
365
|
+
ReadProperties(pointer, adapter);
|
|
366
|
+
if (adapter.Luid.HasValue)
|
|
367
|
+
{
|
|
368
|
+
ReadPciAddress(adapter);
|
|
369
|
+
}
|
|
370
|
+
if (!isValid(pointer))
|
|
371
|
+
{
|
|
372
|
+
adapter.Present = false;
|
|
373
|
+
adapter.Issues.Add(
|
|
374
|
+
new InventoryIssue(kind, "enumeration-failed", "The DXCore adapter became invalid during discovery.")
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
catch (Exception error)
|
|
379
|
+
{
|
|
380
|
+
List<InventoryIssue> issues = adapter == null ? inventory.Issues : adapter.Issues;
|
|
381
|
+
issues.Add(
|
|
382
|
+
new InventoryIssue(kind, "enumeration-failed", "DXCore adapter " + index + ": " + error.ToString())
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
finally
|
|
386
|
+
{
|
|
387
|
+
ReleaseCom(pointer, kind, adapter == null ? inventory.Issues : adapter.Issues);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
GetNativeBoolean isStale = GetMethod<GetNativeBoolean>(list, 5);
|
|
391
|
+
if (isStale(list))
|
|
392
|
+
{
|
|
393
|
+
inventory.Issues.Add(
|
|
394
|
+
new InventoryIssue(kind, "enumeration-failed", "The DXCore adapter list changed during discovery; refresh the inventory.")
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch (Exception error)
|
|
399
|
+
{
|
|
400
|
+
string code = IsApiUnavailable(error) ? "enumeration-unavailable" : "enumeration-failed";
|
|
401
|
+
inventory.Issues.Add(
|
|
402
|
+
new InventoryIssue(kind, code, "DXCore " + kind + " enumeration: " + error.ToString())
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
finally
|
|
406
|
+
{
|
|
407
|
+
ReleaseCom(list, kind, inventory.Issues);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private static void ReadProperties(IntPtr pointer, Adapter adapter)
|
|
412
|
+
{
|
|
413
|
+
IsPropertySupported isSupported = GetMethod<IsPropertySupported>(pointer, 5);
|
|
414
|
+
GetProperty getProperty = GetMethod<GetProperty>(pointer, 6);
|
|
415
|
+
GetPropertySize getPropertySize = GetMethod<GetPropertySize>(pointer, 7);
|
|
416
|
+
AdapterProperty[] properties =
|
|
417
|
+
{
|
|
418
|
+
AdapterProperty.InstanceLuid,
|
|
419
|
+
AdapterProperty.DriverDescription,
|
|
420
|
+
AdapterProperty.IsHardware,
|
|
421
|
+
AdapterProperty.IsIntegrated,
|
|
422
|
+
AdapterProperty.DedicatedAdapterMemory
|
|
423
|
+
};
|
|
424
|
+
foreach (AdapterProperty property in properties)
|
|
425
|
+
{
|
|
426
|
+
IntPtr data = IntPtr.Zero;
|
|
427
|
+
try
|
|
428
|
+
{
|
|
429
|
+
if (!isSupported(pointer, property))
|
|
430
|
+
{
|
|
431
|
+
adapter.Issues.Add(
|
|
432
|
+
new InventoryIssue(adapter.Kind, "property-unavailable", "DXCore does not expose " + property + " for this adapter.")
|
|
433
|
+
);
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
UIntPtr nativeLength;
|
|
437
|
+
int result = getPropertySize(pointer, property, out nativeLength);
|
|
438
|
+
Marshal.ThrowExceptionForHR(result);
|
|
439
|
+
int length = checked((int)nativeLength.ToUInt64());
|
|
440
|
+
int requiredLength = property == AdapterProperty.DriverDescription
|
|
441
|
+
|| property == AdapterProperty.IsHardware
|
|
442
|
+
|| property == AdapterProperty.IsIntegrated ? 1 : 8;
|
|
443
|
+
if (length < requiredLength)
|
|
444
|
+
{
|
|
445
|
+
throw new InvalidOperationException("DXCore returned an incomplete native " + property + " representation.");
|
|
446
|
+
}
|
|
447
|
+
data = Marshal.AllocHGlobal(length);
|
|
448
|
+
result = getProperty(pointer, property, nativeLength, data);
|
|
449
|
+
Marshal.ThrowExceptionForHR(result);
|
|
450
|
+
switch (property)
|
|
451
|
+
{
|
|
452
|
+
case AdapterProperty.InstanceLuid:
|
|
453
|
+
adapter.Luid = (AdapterLuid)Marshal.PtrToStructure(data, typeof(AdapterLuid));
|
|
454
|
+
break;
|
|
455
|
+
case AdapterProperty.DriverDescription:
|
|
456
|
+
byte[] description = new byte[length];
|
|
457
|
+
Marshal.Copy(data, description, 0, length);
|
|
458
|
+
int terminator = Array.IndexOf(description, (byte)0);
|
|
459
|
+
if (terminator < 0)
|
|
460
|
+
{
|
|
461
|
+
throw new InvalidOperationException("DXCore returned a driver description without its required null terminator.");
|
|
462
|
+
}
|
|
463
|
+
adapter.Name = Encoding.UTF8.GetString(description, 0, terminator);
|
|
464
|
+
break;
|
|
465
|
+
case AdapterProperty.IsHardware:
|
|
466
|
+
adapter.IsHardware = Marshal.ReadByte(data) != 0;
|
|
467
|
+
break;
|
|
468
|
+
case AdapterProperty.IsIntegrated:
|
|
469
|
+
adapter.IsIntegrated = Marshal.ReadByte(data) != 0;
|
|
470
|
+
break;
|
|
471
|
+
case AdapterProperty.DedicatedAdapterMemory:
|
|
472
|
+
adapter.DedicatedMemoryMiB = unchecked((ulong)Marshal.ReadInt64(data)) / 1048576.0;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
catch (Exception error)
|
|
477
|
+
{
|
|
478
|
+
adapter.Issues.Add(
|
|
479
|
+
new InventoryIssue(adapter.Kind, "property-failed", "DXCore " + property + ": " + error.ToString())
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
finally
|
|
483
|
+
{
|
|
484
|
+
FreeBuffer(data, adapter.Kind, adapter.Issues);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private static void ReadPciAddress(Adapter adapter)
|
|
490
|
+
{
|
|
491
|
+
OpenAdapterFromLuid opened = new OpenAdapterFromLuid();
|
|
492
|
+
opened.Luid = adapter.Luid.Value;
|
|
493
|
+
IntPtr data = IntPtr.Zero;
|
|
494
|
+
bool ownsHandle = false;
|
|
495
|
+
try
|
|
496
|
+
{
|
|
497
|
+
int status = D3DKMTOpenAdapterFromLuid(ref opened);
|
|
498
|
+
RequireNtSuccess(status, "D3DKMTOpenAdapterFromLuid");
|
|
499
|
+
ownsHandle = true;
|
|
500
|
+
int length = Marshal.SizeOf(typeof(PciAddress));
|
|
501
|
+
data = Marshal.AllocHGlobal(length);
|
|
502
|
+
QueryAdapterInfo query = new QueryAdapterInfo();
|
|
503
|
+
query.Handle = opened.Handle;
|
|
504
|
+
query.Type = 6; // KMTQAITYPE_ADAPTERADDRESS
|
|
505
|
+
query.Data = data;
|
|
506
|
+
query.DataLength = (uint)length;
|
|
507
|
+
status = D3DKMTQueryAdapterInfo(ref query);
|
|
508
|
+
RequireNtSuccess(status, "D3DKMTQueryAdapterInfo(KMTQAITYPE_ADAPTERADDRESS)");
|
|
509
|
+
adapter.Pci = (PciAddress)Marshal.PtrToStructure(data, typeof(PciAddress));
|
|
510
|
+
}
|
|
511
|
+
catch (Exception error)
|
|
512
|
+
{
|
|
513
|
+
string code = IsApiUnavailable(error) ? "property-unavailable" : "property-failed";
|
|
514
|
+
adapter.Issues.Add(
|
|
515
|
+
new InventoryIssue("identity", code, "LUID to PCI address: " + error.ToString())
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
finally
|
|
519
|
+
{
|
|
520
|
+
FreeBuffer(data, "identity", adapter.Issues);
|
|
521
|
+
if (ownsHandle)
|
|
522
|
+
{
|
|
523
|
+
try
|
|
524
|
+
{
|
|
525
|
+
CloseAdapter closed = new CloseAdapter();
|
|
526
|
+
closed.Handle = opened.Handle;
|
|
527
|
+
int status = D3DKMTCloseAdapter(ref closed);
|
|
528
|
+
RequireNtSuccess(status, "D3DKMTCloseAdapter");
|
|
529
|
+
}
|
|
530
|
+
catch (Exception error)
|
|
531
|
+
{
|
|
532
|
+
adapter.Issues.Add(
|
|
533
|
+
new InventoryIssue("identity", "cleanup-failed", error.ToString())
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
private static void ReadDxgiAddresses(Inventory inventory)
|
|
541
|
+
{
|
|
542
|
+
IntPtr factory = IntPtr.Zero;
|
|
543
|
+
List<AdapterLuid> luids = new List<AdapterLuid>();
|
|
544
|
+
List<uint> indexes = new List<uint>();
|
|
545
|
+
try
|
|
546
|
+
{
|
|
547
|
+
Guid factoryId = new Guid("7b7166ec-21c7-44ae-b21a-c9ae321ae369");
|
|
548
|
+
int factoryResult = CreateDXGIFactory(ref factoryId, out factory);
|
|
549
|
+
RequireComResult(factoryResult, factory);
|
|
550
|
+
EnumerateDxgiAdapter enumerate = GetMethod<EnumerateDxgiAdapter>(factory, 7);
|
|
551
|
+
for (uint index = 0; ; index++)
|
|
552
|
+
{
|
|
553
|
+
IntPtr pointer = IntPtr.Zero;
|
|
554
|
+
bool enumerated = false;
|
|
555
|
+
try
|
|
556
|
+
{
|
|
557
|
+
int result = enumerate(factory, index, out pointer);
|
|
558
|
+
if (result == unchecked((int)0x887A0002)) // DXGI_ERROR_NOT_FOUND
|
|
559
|
+
{
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
RequireComResult(result, pointer);
|
|
563
|
+
enumerated = true;
|
|
564
|
+
GetDxgiDescription getDescription = GetMethod<GetDxgiDescription>(pointer, 8);
|
|
565
|
+
DxgiAdapterDescription description;
|
|
566
|
+
int descriptionResult = getDescription(pointer, out description);
|
|
567
|
+
Marshal.ThrowExceptionForHR(descriptionResult);
|
|
568
|
+
luids.Add(description.Luid);
|
|
569
|
+
indexes.Add(index);
|
|
570
|
+
}
|
|
571
|
+
catch (Exception error)
|
|
572
|
+
{
|
|
573
|
+
string code = enumerated ? "property-failed" : "enumeration-failed";
|
|
574
|
+
inventory.Issues.Add(
|
|
575
|
+
new InventoryIssue("dxgi", code, "DXGI adapter " + index + ": " + error.ToString())
|
|
576
|
+
);
|
|
577
|
+
if (!enumerated)
|
|
578
|
+
{
|
|
579
|
+
// Only description failures leave later indices queryable.
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
finally
|
|
584
|
+
{
|
|
585
|
+
ReleaseCom(pointer, "dxgi", inventory.Issues);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
catch (Exception error)
|
|
590
|
+
{
|
|
591
|
+
string code = IsApiUnavailable(error) ? "enumeration-unavailable" : "enumeration-failed";
|
|
592
|
+
inventory.Issues.Add(
|
|
593
|
+
new InventoryIssue("dxgi", code, "DXGI enumeration: " + error.ToString())
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
finally
|
|
597
|
+
{
|
|
598
|
+
ReleaseCom(factory, "dxgi", inventory.Issues);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
foreach (Adapter adapter in inventory.Adapters)
|
|
602
|
+
{
|
|
603
|
+
if (!adapter.Luid.HasValue)
|
|
604
|
+
{
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
int matches = 0;
|
|
608
|
+
uint matchedIndex = 0;
|
|
609
|
+
for (int index = 0; index < luids.Count; index++)
|
|
610
|
+
{
|
|
611
|
+
if (SameLuid(adapter.Luid.Value, luids[index]))
|
|
612
|
+
{
|
|
613
|
+
matches++;
|
|
614
|
+
matchedIndex = indexes[index];
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
if (matches == 1)
|
|
618
|
+
{
|
|
619
|
+
adapter.DxgiAdapterIndex = matchedIndex;
|
|
620
|
+
}
|
|
621
|
+
else
|
|
622
|
+
{
|
|
623
|
+
string message = matches == 0
|
|
624
|
+
? "No DXGI adapter index was observed for this DXCore LUID."
|
|
625
|
+
: "More than one DXGI adapter index was observed for this DXCore LUID.";
|
|
626
|
+
adapter.Issues.Add(
|
|
627
|
+
new InventoryIssue("dxgi", "property-unavailable", message)
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
private static bool SameLuid(AdapterLuid left, AdapterLuid right)
|
|
634
|
+
{
|
|
635
|
+
return left.LowPart == right.LowPart && left.HighPart == right.HighPart;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
private static T GetMethod<T>(IntPtr instance, int slot) where T : class
|
|
639
|
+
{
|
|
640
|
+
IntPtr table = Marshal.ReadIntPtr(instance);
|
|
641
|
+
IntPtr method = Marshal.ReadIntPtr(table, slot * IntPtr.Size);
|
|
642
|
+
return (T)(object)Marshal.GetDelegateForFunctionPointer(method, typeof(T));
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
private static void RequireComResult(int result, IntPtr instance)
|
|
646
|
+
{
|
|
647
|
+
Marshal.ThrowExceptionForHR(result);
|
|
648
|
+
if (instance == IntPtr.Zero)
|
|
649
|
+
{
|
|
650
|
+
throw new InvalidOperationException("The Windows COM operation succeeded without returning an interface.");
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
private static void RequireNtSuccess(int status, string operation)
|
|
655
|
+
{
|
|
656
|
+
if (status < 0)
|
|
657
|
+
{
|
|
658
|
+
throw new InvalidOperationException(operation + " returned NTSTATUS 0x" + unchecked((uint)status).ToString("X8") + ".");
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
private static bool IsApiUnavailable(Exception error)
|
|
663
|
+
{
|
|
664
|
+
return error is DllNotFoundException
|
|
665
|
+
|| error is EntryPointNotFoundException
|
|
666
|
+
|| error is BadImageFormatException
|
|
667
|
+
|| error is PlatformNotSupportedException;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
private static void ReleaseCom(IntPtr instance, string source, List<InventoryIssue> issues)
|
|
671
|
+
{
|
|
672
|
+
if (instance == IntPtr.Zero)
|
|
673
|
+
{
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
try
|
|
677
|
+
{
|
|
678
|
+
Marshal.Release(instance);
|
|
679
|
+
}
|
|
680
|
+
catch (Exception error)
|
|
681
|
+
{
|
|
682
|
+
issues.Add(
|
|
683
|
+
new InventoryIssue(source, "cleanup-failed", error.ToString())
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
private static void FreeBuffer(IntPtr data, string source, List<InventoryIssue> issues)
|
|
689
|
+
{
|
|
690
|
+
if (data == IntPtr.Zero)
|
|
691
|
+
{
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
try
|
|
695
|
+
{
|
|
696
|
+
Marshal.FreeHGlobal(data);
|
|
697
|
+
}
|
|
698
|
+
catch (Exception error)
|
|
699
|
+
{
|
|
700
|
+
issues.Add(
|
|
701
|
+
new InventoryIssue(source, "cleanup-failed", error.ToString())
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
'@
|
|
708
|
+
|
|
709
|
+
function Add-NativeAdapters {
|
|
710
|
+
try {
|
|
711
|
+
# Collect all compiler diagnostics as data; none may contaminate stdout.
|
|
712
|
+
$compilerFailed = $false
|
|
713
|
+
$compilerOutput = @(Add-Type -TypeDefinition $nativeSource -Language CSharp -ErrorAction Continue -WarningAction Continue 2>&1 3>&1)
|
|
714
|
+
foreach ($diagnostic in $compilerOutput) {
|
|
715
|
+
if ($diagnostic -is [System.Management.Automation.ErrorRecord]) {
|
|
716
|
+
$compilerFailed = $true
|
|
717
|
+
$message = Get-InventoryErrorText $diagnostic
|
|
718
|
+
} else {
|
|
719
|
+
$message = $diagnostic.ToString()
|
|
720
|
+
}
|
|
721
|
+
Add-InventoryIssue 'identity' 'interop-diagnostic' $message
|
|
722
|
+
}
|
|
723
|
+
if ($compilerFailed) {
|
|
724
|
+
Add-InventoryIssue 'gpu' 'enumeration-unavailable' 'The Windows inventory interop could not be compiled; complete diagnostics are included in issues.'
|
|
725
|
+
Add-InventoryIssue 'npu' 'enumeration-unavailable' 'The Windows inventory interop could not be compiled; complete diagnostics are included in issues.'
|
|
726
|
+
return
|
|
727
|
+
}
|
|
728
|
+
$nativeInventory = [Arcane.ExecutionDevices.WindowsInventory]::Read()
|
|
729
|
+
foreach ($issue in $nativeInventory.Issues) {
|
|
730
|
+
Add-InventoryIssue $issue.Source $issue.Code $issue.Message
|
|
731
|
+
}
|
|
732
|
+
$pnpDevices = Get-PresentPciDevices
|
|
733
|
+
$deviceRows = [System.Collections.Generic.List[object]]::new()
|
|
734
|
+
foreach ($adapter in $nativeInventory.Adapters) {
|
|
735
|
+
$deviceId = $null
|
|
736
|
+
$identitySource = $null
|
|
737
|
+
$luid = $null
|
|
738
|
+
$pci = $null
|
|
739
|
+
if ($null -ne $adapter.Luid) {
|
|
740
|
+
$luid = [ordered]@{
|
|
741
|
+
highPart = $adapter.Luid.HighPart
|
|
742
|
+
lowPart = $adapter.Luid.LowPart
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if ($null -ne $adapter.Pci) {
|
|
746
|
+
$pci = [ordered]@{
|
|
747
|
+
bus = $adapter.Pci.Bus
|
|
748
|
+
device = $adapter.Pci.Device
|
|
749
|
+
function = $adapter.Pci.Function
|
|
750
|
+
}
|
|
751
|
+
$matchedDevices = [System.Collections.Generic.List[object]]::new()
|
|
752
|
+
foreach ($candidate in $pnpDevices.Values) {
|
|
753
|
+
if ($null -ne $candidate.bus -and $null -ne $candidate.address -and
|
|
754
|
+
$candidate.bus -eq $pci.bus -and
|
|
755
|
+
(($candidate.address -shr 16) -band 65535) -eq $pci.device -and
|
|
756
|
+
($candidate.address -band 65535) -eq $pci.function) {
|
|
757
|
+
$matchedDevices.Add($candidate)
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if ($matchedDevices.Count -eq 1 -and $adapter.Present) {
|
|
761
|
+
$deviceId = $matchedDevices[0].instanceId
|
|
762
|
+
$identitySource = 'windows-pnp-instance'
|
|
763
|
+
} elseif ($matchedDevices.Count -gt 1) {
|
|
764
|
+
Add-InventoryIssue 'identity' 'identity-ambiguous' 'Multiple present PnP instances share this adapter PCI address.'
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
$row = [ordered]@{
|
|
768
|
+
deviceId = $deviceId
|
|
769
|
+
identitySource = $identitySource
|
|
770
|
+
kind = $adapter.Kind
|
|
771
|
+
name = $adapter.Name
|
|
772
|
+
present = $adapter.Present
|
|
773
|
+
isHardware = $adapter.IsHardware
|
|
774
|
+
isIntegrated = $adapter.IsIntegrated
|
|
775
|
+
dedicatedMemoryMiB = $adapter.DedicatedMemoryMiB
|
|
776
|
+
addresses = [ordered]@{
|
|
777
|
+
adapterLuid = $luid
|
|
778
|
+
pci = $pci
|
|
779
|
+
dxgiAdapterIndex = $adapter.DxgiAdapterIndex
|
|
780
|
+
}
|
|
781
|
+
processors = @()
|
|
782
|
+
}
|
|
783
|
+
$deviceRows.Add([ordered]@{row = $row; issues = $adapter.Issues})
|
|
784
|
+
}
|
|
785
|
+
# Both sides of the join must be unique. A single PnP instance cannot
|
|
786
|
+
# establish distinct physical identities for multiple DXCore records.
|
|
787
|
+
$rowsById = @{}
|
|
788
|
+
foreach ($entry in $deviceRows) {
|
|
789
|
+
$deviceId = $entry.row.deviceId
|
|
790
|
+
if ($null -ne $deviceId) {
|
|
791
|
+
if (!$rowsById.ContainsKey($deviceId)) {
|
|
792
|
+
$rowsById[$deviceId] = [System.Collections.Generic.List[object]]::new()
|
|
793
|
+
}
|
|
794
|
+
$rowsById[$deviceId].Add($entry.row)
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
foreach ($rows in $rowsById.Values) {
|
|
798
|
+
if ($rows.Count -gt 1) {
|
|
799
|
+
foreach ($row in $rows) {
|
|
800
|
+
$row.deviceId = $null
|
|
801
|
+
$row.identitySource = $null
|
|
802
|
+
}
|
|
803
|
+
Add-InventoryIssue 'identity' 'identity-ambiguous' 'Multiple DXCore adapters correlate to the same present PnP instance.'
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
foreach ($entry in $deviceRows) {
|
|
807
|
+
$row = $entry.row
|
|
808
|
+
if ($null -eq $row.deviceId) {
|
|
809
|
+
Add-InventoryIssue 'identity' 'identity-unresolved' ("No unique present PnP instance could be correlated to the Windows {0} '{1}'." -f $row.kind, $row.name)
|
|
810
|
+
}
|
|
811
|
+
foreach ($issue in $entry.issues) {
|
|
812
|
+
Add-InventoryIssue $issue.Source $issue.Code $issue.Message $row.deviceId
|
|
813
|
+
}
|
|
814
|
+
$inventoryDevices.Add($row)
|
|
815
|
+
}
|
|
816
|
+
} catch {
|
|
817
|
+
$message = Get-InventoryErrorText $_
|
|
818
|
+
Add-InventoryIssue 'gpu' 'enumeration-failed' $message
|
|
819
|
+
Add-InventoryIssue 'npu' 'enumeration-failed' $message
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
Add-HostCpu
|
|
824
|
+
Add-NativeAdapters
|
|
825
|
+
|
|
826
|
+
$inventoryState = if ($inventoryDevices.Count -eq 0) { 'unavailable' } elseif ($inventoryIssues.Count -gt 0) { 'partial' } else { 'ready' }
|
|
827
|
+
$inventory = [ordered]@{
|
|
828
|
+
platform = 'win32'
|
|
829
|
+
state = $inventoryState
|
|
830
|
+
devices = $inventoryDevices.ToArray()
|
|
831
|
+
issues = $inventoryIssues.ToArray()
|
|
832
|
+
}
|
|
833
|
+
[Console]::Out.WriteLine((ConvertTo-Json -InputObject $inventory -Depth 12 -Compress))
|