mirror of
https://github.com/Yamato-Security/WELA.git
synced 2026-10-08 07:15:25 +02:00
Add typed native WEC runtime observations (#424)
* Observe typed native WEC subscription runtime status * Link typed WEC runtime changelog to PR 424 * Pass a native null source for subscription runtime queries * Ignore unused count storage for native null WEC variants * Keep unavailable WEC source inventories unknown and diagnose native XML reads * Read native WEC subscription XML with bounded explicit Unicode pipes * Use bounded Unicode subscription reads in runtime observations and cleanup * Decode native WEC XML BOMs without unsupported Unicode switch * Describe strict native WEC XML byte decoding * Reference System.Xml explicitly when compiling under Windows PowerShell * Regenerate website changelog snapshots with their proper headers
This commit is contained in:
1 parent
5ba53fbcfb
commit
913b1dfaee
19 files changed
+650
-5
No files matched your search
@@ -148,6 +148,8 @@ function Get-WelaRetentionSubscriptions {
|
||||
foreach ($id in $Ids) {
|
||||
$definition=Get-WelaRetentionNativeEvidence 'wecutil.exe' @('gs',$id,'/f:xml')
|
||||
$runtime=Get-WelaRetentionNativeEvidence 'wecutil.exe' @('gr',$id)
|
||||
try {$typedRuntime=Get-WelaWecRuntime -Id $id}
|
||||
catch {$typedRuntime=[pscustomobject]@{Status='Unknown';Diagnostic=$_.Exception.Message;ReadyRuleCredit=0}}
|
||||
$enabled=$null; $query=$null; $scope='Unknown'; $diagnostic=''
|
||||
if ($definition.Status -eq 'CommandSucceeded') {
|
||||
try {
|
||||
@@ -158,7 +160,7 @@ function Get-WelaRetentionSubscriptions {
|
||||
$query=ConvertFrom-WelaWefQuery $queryNodes[0].InnerText; $enabled=$enabledNodes[0].InnerText -eq 'true'; $scope='NativeQueryObserved'
|
||||
} catch { $diagnostic=$_.ToString() }
|
||||
}
|
||||
[pscustomobject]@{ Id=$id; Enabled=$enabled; QueryScope=$scope; Query=$query; Definition=$definition; Runtime=$runtime; Diagnostic=$diagnostic; DeliveryHealth='Unknown'; Backlog='Unknown'; ActualArrival='Not tested' }
|
||||
[pscustomobject]@{ Id=$id; Enabled=$enabled; QueryScope=$scope; Query=$query; Definition=$definition; Runtime=$runtime; TypedRuntime=$typedRuntime; Diagnostic=$diagnostic; DeliveryHealth='Unknown'; Backlog='Unknown'; ActualArrival='Not tested' }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
# Local, explicitly selected WEC runtime reads; no service/subscription changes.
|
||||
function Initialize-WelaWecRuntimeNative {
|
||||
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT -or -not [Environment]::Is64BitProcess) {throw 'Typed WEC runtime requires native 64-bit Windows.'}
|
||||
if (-not ('Wela.WecRuntime.Native' -as [type])) {Add-Type -Path (Join-Path $PSScriptRoot 'WecRuntimeNative.cs') -ErrorAction Stop}
|
||||
}
|
||||
function Read-WelaWecRuntimeValue {
|
||||
param([string]$Id,[AllowNull()][string]$Source,[int]$Property)
|
||||
Initialize-WelaWecRuntimeNative
|
||||
# PowerShell converts $null to an empty .NET string; the native API requires
|
||||
# a genuine null pointer to select subscription-level status.
|
||||
[Wela.WecRuntime.Native]::Read($Id,$(if ($Source) {$Source} else {[NullString]::Value}),$Property)
|
||||
}
|
||||
function ConvertTo-WelaWecRuntimeField {
|
||||
param($Value,[int]$Property)
|
||||
$row=[pscustomobject]@{Status=$Value.State;NativeType=$Value.NativeType;ErrorCode=$Value.ErrorCode;Value=$null;RawFileTime=$null;Diagnostic=$Value.Diagnostic}
|
||||
if ($Value.State -notin @('Observed','NotAvailable','Unknown')) {throw 'Invalid native observation status.'}
|
||||
if ($Value.State -ne 'Observed') {
|
||||
if ($Value.State -eq 'NotAvailable' -and $Property -in @(0,1,5)) {$row.Status='Unknown';$row.Diagnostic='Required native activity/error/source-inventory value is unavailable.'}
|
||||
return $row
|
||||
}
|
||||
switch ($Property) {
|
||||
{$_ -in @(0,1)} {
|
||||
if ($Value.NativeType -ne 2 -or $Value.Data -isnot [uint32]) {throw 'Runtime status/error requires native UInt32.'}
|
||||
$row.Value=$Value.Data
|
||||
if ($Property -eq 0 -and $Value.Data -notin @(1,2,3,4)) {$row.Status='Unknown';$row.Diagnostic='Unrecognized native activity enum; numeric value retained.'}
|
||||
}
|
||||
2 {if ($Value.NativeType -ne 4 -or $Value.Data -isnot [string]) {throw 'Runtime message requires native String.'};$row.Value=$Value.Data}
|
||||
{$_ -in @(3,4,6)} {
|
||||
if ($Value.NativeType -ne 3 -or $Value.Data -isnot [uint64]) {throw 'Runtime time requires native FILETIME.'}
|
||||
$row.RawFileTime=$Value.Data.ToString([Globalization.CultureInfo]::InvariantCulture)
|
||||
if ($Value.Data -eq 0) {$row.Status='NotAvailable';$row.Diagnostic='Zero FILETIME; no observed timestamp.'}
|
||||
else {try {$row.Value=[DateTime]::FromFileTimeUtc([long]$Value.Data).ToString('o')} catch {$row.Status='Unknown';$row.Diagnostic='Native FILETIME is outside the supported timestamp range.'}}
|
||||
}
|
||||
5 {
|
||||
if ($Value.NativeType -ne 132 -or $Value.Data -isnot [array] -or @($Value.Data).Count -ne $Value.Count) {throw 'Source inventory requires a native String array with matching count.'}
|
||||
$row.Value=@($Value.Data)
|
||||
}
|
||||
}
|
||||
$row
|
||||
}
|
||||
function Get-WelaWecRuntimeFields {
|
||||
param([string]$Id,[AllowNull()][string]$Source)
|
||||
$names=@{0='Activity';1='LastError';2='LastErrorMessage';3='LastErrorTimeUtc';4='NextRetryTimeUtc';6='LastHeartbeatTimeUtc'}
|
||||
$fields=[ordered]@{}
|
||||
foreach ($property in @(0,1,2,3,4,6)) {
|
||||
try {$fields[$names[$property]]=ConvertTo-WelaWecRuntimeField (Read-WelaWecRuntimeValue $Id $Source $property) $property}
|
||||
catch {$fields[$names[$property]]=[pscustomobject]@{Status='Unknown';NativeType=$null;ErrorCode=$null;Value=$null;RawFileTime=$null;Diagnostic=$_.Exception.Message}}
|
||||
}
|
||||
$activity='Unknown'
|
||||
if ($fields.Activity.Status -eq 'Observed') {$activity=@{1='Disabled';2='Active';3='Inactive';4='Trying'}[[int]$fields.Activity.Value]}
|
||||
[pscustomobject]@{Source=$Source;Activity=$activity;Fields=[pscustomobject]$fields;Complete=(@($fields.Values | Where-Object Status -eq 'Unknown').Count -eq 0)}
|
||||
}
|
||||
function Get-WelaWecRuntimeContext {
|
||||
Initialize-WelaWecRuntimeNative
|
||||
$hostContext=Get-WelaDefaultContext
|
||||
if (-not (Test-WelaDefaultContextComplete $hostContext)) {throw 'Actual collector host context is incomplete.'}
|
||||
$identity=[Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
try {
|
||||
[pscustomobject][ordered]@{Computer=[Environment]::MachineName;Host=$hostContext;ReaderSid=$identity.User.Value;ReaderName=$identity.Name;AuthenticationType=$identity.AuthenticationType;ImpersonationLevel=[string]$identity.ImpersonationLevel;GroupSids=@($identity.Groups | ForEach-Object Value | Sort-Object);IsSystem=$identity.IsSystem}
|
||||
} finally {$identity.Dispose()}
|
||||
}
|
||||
function Get-WelaWecRuntimeDefinition {
|
||||
param([string]$Id)
|
||||
$xml=Read-WelaWecSubscriptionXml -Id $Id
|
||||
$doc=Read-WelaWefXml $xml
|
||||
$ns=New-Object Xml.XmlNamespaceManager($doc.NameTable);$ns.AddNamespace('s','http://schemas.microsoft.com/2006/03/windows/events/subscription')
|
||||
$fields=@{}
|
||||
foreach ($name in @('SubscriptionId','SubscriptionType','Enabled','Query')) {
|
||||
$nodes=@($doc.SelectNodes('/s:Subscription/s:'+$name,$ns))
|
||||
if ($nodes.Count -ne 1) {throw "Native definition requires exactly one $name."}
|
||||
$fields[$name]=$nodes[0].InnerText
|
||||
}
|
||||
if ($fields.SubscriptionId -cne $Id -or $fields.SubscriptionType -cnotin @('SourceInitiated','CollectorInitiated') -or $fields.Enabled -cnotin @('true','false')) {throw 'Native subscription identity/type/enabled state is invalid.'}
|
||||
$query=ConvertFrom-WelaWefQuery $fields.Query
|
||||
[pscustomobject]@{Id=$Id;Type=$fields.SubscriptionType;Enabled=($fields.Enabled -eq 'true');Query=$query;RawXml=$xml;Key=(Get-WelaWefXmlKey $doc.DocumentElement)}
|
||||
}
|
||||
function Get-WelaWecRuntime {
|
||||
param([string]$Id,[ValidateRange(1,512)][int]$MaximumSources=128)
|
||||
$ErrorActionPreference='Stop'
|
||||
if ($Id -cnotmatch '^[A-Za-z0-9][A-Za-z0-9 ._-]{0,127}$') {throw 'Select an exact supported subscription ID.'}
|
||||
$report=[pscustomobject][ordered]@{Id=$Id;Status='Unknown';StartedUtc=[DateTime]::UtcNow.ToString('o');CompletedUtc=$null;CollectorBefore=$null;CollectorAfter=$null;DefinitionBefore=$null;DefinitionAfter=$null;Subscription=$null;SourceInventory=$null;Sources=@();MaximumSources=$MaximumSources;Capped=$false;SourceListChanged=$false;Diagnostic='';EventArrival='Not tested';TransmissionLatency='Not measured';Backlog='Unknown';ReadyRuleCredit=0}
|
||||
try {
|
||||
$report.CollectorBefore=Get-WelaWecRuntimeContext
|
||||
$report.DefinitionBefore=Get-WelaWecRuntimeDefinition $Id
|
||||
$report.Subscription=Get-WelaWecRuntimeFields $Id $null
|
||||
$inventory=ConvertTo-WelaWecRuntimeField (Read-WelaWecRuntimeValue $Id $null 5) 5
|
||||
$report.SourceInventory=[pscustomobject]@{Observation=$inventory;AfterObservation=$null;Meaning=$(if ($report.DefinitionBefore.Type -eq 'SourceInitiated') {'Sources the collector heard from in the past 30 days; persistent across reboot. This is not a current connection count.'} else {'Configured event sources, not a current connection count.'});ReportedCount=$null;QueriedCount=0}
|
||||
$sources=@()
|
||||
if ($inventory.Status -eq 'Observed') {$sources=@($inventory.Value);$report.SourceInventory.ReportedCount=$sources.Count}
|
||||
$seen=@{}
|
||||
foreach ($source in $sources) {if ($source -isnot [string] -or [string]::IsNullOrWhiteSpace($source) -or $source.Length -gt 32768 -or $source -match '[\x00-\x1f]' -or $seen.ContainsKey($source)) {throw 'Invalid or duplicate native source identity.'};$seen[$source]=$true}
|
||||
$report.Capped=$sources.Count -gt $MaximumSources
|
||||
$report.Sources=@(foreach ($source in ($sources | Select-Object -First $MaximumSources)) {Get-WelaWecRuntimeFields $Id $source})
|
||||
$report.SourceInventory.QueriedCount=$report.Sources.Count
|
||||
$afterInventory=ConvertTo-WelaWecRuntimeField (Read-WelaWecRuntimeValue $Id $null 5) 5
|
||||
$report.SourceInventory.AfterObservation=$afterInventory
|
||||
$afterSources=if ($afterInventory.Status -eq 'Observed') {@($afterInventory.Value | Sort-Object)} else {@()}
|
||||
if ($afterInventory.Status -ne $inventory.Status -or $afterInventory.Status -eq 'Unknown' -or (ConvertTo-Json -InputObject @($sources | Sort-Object) -Compress) -cne (ConvertTo-Json -InputObject @($afterSources) -Compress)) {$report.SourceListChanged=$true}
|
||||
$report.DefinitionAfter=Get-WelaWecRuntimeDefinition $Id
|
||||
$report.CollectorAfter=Get-WelaWecRuntimeContext
|
||||
if ($report.DefinitionAfter.Key -cne $report.DefinitionBefore.Key -or (ConvertTo-Json $report.CollectorBefore -Depth 16 -Compress) -cne (ConvertTo-Json $report.CollectorAfter -Depth 16 -Compress)) {throw 'Collector identity/context or subscription definition changed during observation.'}
|
||||
$report.Status=if ($report.Capped -or $report.SourceListChanged -or $inventory.Status -eq 'Unknown' -or -not $report.Subscription.Complete -or @($report.Sources | Where-Object {-not $_.Complete}).Count) {'Partial'} else {'Observed'}
|
||||
} catch {$report.Status=if ($null -ne $report.Subscription) {'Partial'} else {'Unknown'};$report.Diagnostic=$_.Exception.Message}
|
||||
finally {
|
||||
if ($null -ne $report.CollectorBefore -and $null -eq $report.CollectorAfter) {try {$report.CollectorAfter=Get-WelaWecRuntimeContext} catch {$report.Diagnostic+=' Final collector context unavailable: '+$_.Exception.Message}}
|
||||
$report.CompletedUtc=[DateTime]::UtcNow.ToString('o')
|
||||
}
|
||||
$report
|
||||
}
|
||||
function Invoke-WelaWecRuntime {
|
||||
param([string[]]$Ids,[ValidateRange(1,512)][int]$MaximumSources=128,[string]$ResultsPath)
|
||||
if (-not $Ids -or $Ids.Count -gt 32) {throw 'Select 1..32 explicit local subscription IDs.'}
|
||||
$seen=@{};foreach ($id in $Ids) {if ($id -cnotmatch '^[A-Za-z0-9][A-Za-z0-9 ._-]{0,127}$' -or $seen.ContainsKey($id)) {throw 'Invalid or duplicate subscription ID.'};$seen[$id]=$true}
|
||||
$output=$null
|
||||
if ($ResultsPath) {
|
||||
$provider=$null;$drive=$null;$output=$ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($ResultsPath,[ref]$provider,[ref]$drive)
|
||||
if ($provider.Name -ne 'FileSystem' -or $output -match '^[\\/]{2}' -or $output.Substring([IO.Path]::GetPathRoot($output).Length).Contains(':') -or (Test-Path -LiteralPath $output)) {throw 'Results require a new ordinary local file without streams.'}
|
||||
if ([Environment]::OSVersion.Platform -eq [PlatformID]::Win32NT -and ([IO.DriveInfo]::new([IO.Path]::GetPathRoot($output))).DriveType -ne [IO.DriveType]::Fixed) {throw 'Results require a local fixed drive.'}
|
||||
$parent=Get-Item -LiteralPath ([IO.Path]::GetDirectoryName($output)) -ErrorAction Stop
|
||||
for ($node=$parent;$null -ne $node;$node=$node.Parent) {if ([int]$node.Attributes -band [int][IO.FileAttributes]::ReparsePoint) {throw 'Results cannot traverse reparse points.'}}
|
||||
}
|
||||
$rows=@(foreach ($id in $Ids) {Get-WelaWecRuntime $id $MaximumSources})
|
||||
$report=[pscustomobject]@{SchemaVersion=1;Kind='WelaWecRuntime';CapturedUtc=[DateTime]::UtcNow.ToString('o');ExitCode=[int](@($rows | Where-Object Status -ne 'Observed').Count -gt 0);Subscriptions=$rows;ReadyRuleCredit=0;Scope='Local typed WEC runtime observations; API success/activity and historical source inventory do not establish successful event arrival, backlog, latency or rule readiness.'}
|
||||
if ($output) {
|
||||
$bytes=[Text.UTF8Encoding]::new($false).GetBytes((ConvertTo-Json -InputObject $report -Depth 30))
|
||||
$stream=[IO.File]::Open($output,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None)
|
||||
try {$stream.Write($bytes,0,$bytes.Length);$stream.Flush($true)} finally {$stream.Dispose()}
|
||||
}
|
||||
$report
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Read-only WEC runtime observations. No subscription create/save/retry API.
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
namespace Wela.WecRuntime {
|
||||
public sealed class Value {
|
||||
public string State="Unknown"; public uint? NativeType; public uint Count;
|
||||
public int ErrorCode; public string Diagnostic=""; public object Data;
|
||||
}
|
||||
public static class Native {
|
||||
[DllImport("wecapi.dll",CharSet=CharSet.Unicode,SetLastError=true)]
|
||||
[return:MarshalAs(UnmanagedType.Bool)]
|
||||
static extern bool EcGetSubscriptionRunTimeStatus(string subscription,int property,string source,uint flags,uint size,IntPtr buffer,out uint used);
|
||||
const uint MaximumBytes=1048576;
|
||||
static void Range(IntPtr buffer,uint size,IntPtr pointer,long length) {
|
||||
long offset=pointer.ToInt64()-buffer.ToInt64();
|
||||
if(pointer==IntPtr.Zero||offset<16||length<0||offset>size||length>size-offset)throw new InvalidOperationException("Native variant points outside its returned buffer.");
|
||||
}
|
||||
static string Text(IntPtr buffer,uint size,IntPtr pointer) {
|
||||
Range(buffer,size,pointer,2);long offset=pointer.ToInt64()-buffer.ToInt64();
|
||||
StringBuilder value=new StringBuilder();
|
||||
for(int i=0;i<32768&&offset+2L*i+2<=size;i++) {
|
||||
char c=(char)(ushort)Marshal.ReadInt16(pointer,i*2);if(c==0)return value.ToString();value.Append(c);
|
||||
}
|
||||
throw new InvalidOperationException("Native string is unterminated or exceeds the character limit.");
|
||||
}
|
||||
// Public only to permit safe allocated-buffer ABI/type regression tests.
|
||||
public static Value Decode(IntPtr buffer,uint size,int property) {
|
||||
if(buffer==IntPtr.Zero||size<16||size>MaximumBytes||property<0||property>6)throw new InvalidOperationException("Invalid runtime buffer or property.");
|
||||
uint count=unchecked((uint)Marshal.ReadInt32(buffer,8));uint type=unchecked((uint)Marshal.ReadInt32(buffer,12));
|
||||
Value value=new Value {NativeType=type,Count=count};
|
||||
// Count and union storage have no meaning for EcVarTypeNull; Windows may leave them untouched.
|
||||
if(type==0) {value.Count=0;value.State="NotAvailable";return value;}
|
||||
uint expected=property==0||property==1?2U:property==2?4U:property==5?132U:3U;
|
||||
if(type!=expected)throw new InvalidOperationException("Unexpected EC_VARIANT type for runtime property.");
|
||||
if(type==2) {value.Data=unchecked((uint)Marshal.ReadInt32(buffer));}
|
||||
else if(type==3) {value.Data=unchecked((ulong)Marshal.ReadInt64(buffer));}
|
||||
else if(type==4) {value.Data=Text(buffer,size,Marshal.ReadIntPtr(buffer));}
|
||||
else {
|
||||
if(count>4096)throw new InvalidOperationException("Native source inventory exceeds 4096 entries.");
|
||||
string[] values=new string[count];IntPtr pointers=Marshal.ReadIntPtr(buffer);
|
||||
if(count!=0)Range(buffer,size,pointers,(long)count*IntPtr.Size);
|
||||
for(int i=0;i<count;i++)values[i]=Text(buffer,size,Marshal.ReadIntPtr(pointers,i*IntPtr.Size));
|
||||
value.Data=values;
|
||||
}
|
||||
value.State="Observed";return value;
|
||||
}
|
||||
public static Value Read(string subscription,string source,int property) {
|
||||
if(String.IsNullOrWhiteSpace(subscription)||subscription.Length>128||subscription.IndexOf('\0')>=0||property<0||property>6||source!=null&&(source.Length>32768||source.IndexOf('\0')>=0))throw new ArgumentException("Invalid selected subscription/source/property.");
|
||||
uint size=16;
|
||||
for(int attempt=0;attempt<3;attempt++) {
|
||||
IntPtr buffer=Marshal.AllocHGlobal((int)size);
|
||||
try {
|
||||
uint used;bool success=EcGetSubscriptionRunTimeStatus(subscription,property,source,0,size,buffer,out used);
|
||||
int error=Marshal.GetLastWin32Error();
|
||||
if(success) {
|
||||
if(used<16||used>size)return new Value {ErrorCode=13,Diagnostic="Native runtime returned an invalid used-buffer length."};
|
||||
try{return Decode(buffer,used,property);}catch(Exception e){return new Value {ErrorCode=13,Diagnostic=e.Message};}
|
||||
}
|
||||
if(error!=122)return new Value {ErrorCode=error,Diagnostic=new Win32Exception(error).Message};
|
||||
if(used<=size||used>MaximumBytes)return new Value {ErrorCode=122,Diagnostic="Runtime buffer growth is invalid or exceeds one MiB."};
|
||||
size=used;
|
||||
}finally {Marshal.FreeHGlobal(buffer);}
|
||||
}
|
||||
return new Value {ErrorCode=122,Diagnostic="Runtime buffer changed repeatedly; observation is incomplete."};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,14 +253,16 @@ function Get-WelaWefInventory {
|
||||
foreach ($subscription in $InputModel.Subscriptions) {
|
||||
$channels=@()
|
||||
foreach ($name in $subscription.Query.Channels) { $channels += Get-WelaNativeChannel -Name $name }
|
||||
$runtime=$null; $observed=$null; $observationError=''
|
||||
$runtime=$null; $typedRuntime=$null; $observed=$null; $observationError=''
|
||||
if ($Role -eq 'Collector') {
|
||||
try { $observed=Get-WelaWefControlState Subscription @{ Id=$subscription.Id; SourceSids=$subscription.SourceSids } }
|
||||
catch { $observationError=$_.ToString() }
|
||||
try { $native=Invoke-WelaNative -FilePath 'wecutil.exe' -Arguments @('gr',$subscription.Id); $runtime=[pscustomobject]@{ State='CommandSucceeded'; Raw=$native.Diagnostic; Diagnostic='Localized native runtime status is retained without inferring event arrival.' } }
|
||||
catch { $runtime=[pscustomobject]@{ State='Unknown'; Raw=$null; Diagnostic=$_.ToString() } }
|
||||
try {$typedRuntime=Get-WelaWecRuntime -Id $subscription.Id}
|
||||
catch {$typedRuntime=[pscustomobject]@{Status='Unknown';Diagnostic=$_.Exception.Message;ReadyRuleCredit=0}}
|
||||
}
|
||||
[pscustomobject]@{ Id=$subscription.Id; RequestedEnabled=$subscription.Definition.Enabled; RequestedDefinition=$subscription.Definition; ObservedEnabled=$(if ($observed.Exists) { $observed.Definition.Enabled } else { $null }); ObservedSubscription=$observed; ObservationError=$observationError; Filters=$subscription.Query.Filters; SourceChannels=$channels; ChannelObservationLocation=$(if ($Role -eq 'Collector') { 'Collector only; remote source states are not observed' } else { 'Local source' }); Runtime=$runtime; EffectiveSourceReadAccess='Not tested'; EventArrival='Not tested'; ForwardedSigmaCoverage='Not assessed' }
|
||||
[pscustomobject]@{ Id=$subscription.Id; RequestedEnabled=$subscription.Definition.Enabled; RequestedDefinition=$subscription.Definition; ObservedEnabled=$(if ($observed.Exists) { $observed.Definition.Enabled } else { $null }); ObservedSubscription=$observed; ObservationError=$observationError; Filters=$subscription.Query.Filters; SourceChannels=$channels; ChannelObservationLocation=$(if ($Role -eq 'Collector') { 'Collector only; remote source states are not observed' } else { 'Local source' }); Runtime=$runtime; TypedRuntime=$typedRuntime; EffectiveSourceReadAccess='Not tested'; EventArrival='Not tested'; ForwardedSigmaCoverage='Not assessed' }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user