diff --git a/.editorconfig b/.editorconfig
index f7a39e1..a06e4ea 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -21,12 +21,15 @@ indent_size = 2
[*.cs]
# Code style defaults
-csharp_using_directive_placement = outside_namespace:suggestion
+csharp_using_directive_placement = outside_namespace:error
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
dotnet_sort_system_directives_first = true
dotnet_style_readonly_field = true:suggestion
+csharp_style_namespace_declarations = file_scoped:error
# License header
file_header_template = Copyright (c) Microsoft Corporation.\nLicensed under the MIT License.
+dotnet_diagnostic.IDE0073.severity = error
# Suggest more modern language features when available
dotnet_style_object_initializer = true:suggestion
@@ -42,9 +45,17 @@ dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
+# Pattern matching
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
+csharp_style_prefer_switch_expression = false:none
+csharp_style_prefer_pattern_matching = false:none
+
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_var_elsewhere = true:suggestion
# Define the 'private_fields' symbol group:
diff --git a/.github/workflows/analyze.yaml b/.github/workflows/analyze.yaml
index d861a1e..3f2bc6f 100644
--- a/.github/workflows/analyze.yaml
+++ b/.github/workflows/analyze.yaml
@@ -12,29 +12,52 @@
name: Analyze
on:
push:
- branches: [ main, 'release/*' ]
+ branches: [main, 'release/*']
pull_request:
- branches: [ main, 'release/*' ]
+ branches: [main, 'release/*']
schedule:
- - cron: '51 20 * * 0' # At 08:51 PM, on Sunday each week
+ - cron: '24 22 * * 0' # At 10:24 PM, on Sunday each week
workflow_dispatch:
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+
+permissions: {}
+
jobs:
oss:
name: Analyze with PSRule
runs-on: ubuntu-latest
permissions:
contents: read
+ security-events: write
steps:
+ - name: Checkout
+ uses: actions/checkout@v4
- - name: Checkout
- uses: actions/checkout@v3
+ - name: Run PSRule analysis
+ uses: microsoft/ps-rule@v2.9.0
+ with:
+ modules: PSRule.Rules.MSFT.OSS
+ prerelease: true
+ outputFormat: Sarif
+ outputPath: reports/ps-rule-results.sarif
- - name: Run PSRule analysis
- uses: microsoft/ps-rule@v2.9.0
- with:
- modules: PSRule.Rules.MSFT.OSS
- prerelease: true
+ - name: Upload results to security tab
+ uses: github/codeql-action/upload-sarif@v3
+ if: always()
+ with:
+ sarif_file: reports/ps-rule-results.sarif
+
+ - name: Upload results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: PSRule-Sarif
+ path: reports/ps-rule-results.sarif
+ retention-days: 1
+ if-no-files-found: error
devskim:
name: Analyze with DevSkim
@@ -44,20 +67,29 @@ jobs:
contents: read
security-events: write
steps:
-
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Run DevSkim scanner
uses: microsoft/DevSkim-Action@v1
with:
- directory-to-scan: src/
+ directory-to-scan: .
- name: Upload results to security tab
- uses: github/codeql-action/upload-sarif@v2
+ uses: github/codeql-action/upload-sarif@v3
+ if: always()
with:
sarif_file: devskim-results.sarif
+ - name: Upload results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: DevSkim-Sarif
+ path: devskim-results.sarif
+ retention-days: 1
+ if-no-files-found: error
+
codeql:
name: Analyze with CodeQL
runs-on: ubuntu-latest
@@ -66,17 +98,26 @@ jobs:
contents: read
security-events: write
steps:
+ - name: Checkout
+ uses: actions/checkout@v4
- - name: Checkout
- uses: actions/checkout@v3
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: 'csharp'
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v2
- with:
- languages: 'csharp'
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v3
- - name: Autobuild
- uses: github/codeql-action/autobuild@v2
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ id: codeql-analyze
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ - name: Upload results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: CodeQL-Sarif
+ path: ${{ steps.codeql-analyze.outputs.sarif-output }}
+ retention-days: 1
+ if-no-files-found: error
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
new file mode 100644
index 0000000..a8e2100
--- /dev/null
+++ b/.github/workflows/build.yaml
@@ -0,0 +1,143 @@
+#
+# CI Pipeline
+#
+
+# NOTES:
+# This workflow builds and tests module updates.
+
+name: Build
+on:
+ push:
+ branches: [main, 'release/*']
+ pull_request:
+ branches: [main, 'release/*']
+ workflow_dispatch:
+
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+
+permissions: {}
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.x
+
+ - name: Install dependencies
+ shell: pwsh
+ timeout-minutes: 3
+ run: ./scripts/pipeline-deps.ps1
+
+ - name: Build module
+ shell: pwsh
+ timeout-minutes: 5
+ run: Invoke-Build -Configuration Release -AssertStyle GitHubActions
+
+ - name: Upload module
+ uses: actions/upload-artifact@v4
+ with:
+ name: Module
+ path: ./out/modules/PSRule/*
+ retention-days: 3
+ if-no-files-found: error
+
+ # - name: Upload Test Results
+ # uses: actions/upload-artifact@v3
+ # if: always()
+ # with:
+ # name: Module.DotNet.TestResults
+ # path: ./reports/*.trx
+ # retention-days: 3
+ # if-no-files-found: error
+
+ - name: Upload PSRule Results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: Results-PSRule
+ path: ./reports/ps-rule*.xml
+ retention-days: 3
+ if-no-files-found: error
+
+ test:
+ name: Test (${{ matrix.rid }}-${{ matrix.shell }})
+ runs-on: ${{ matrix.os }}
+ needs: build
+ permissions:
+ contents: read
+
+ strategy:
+ # Get full test results from all platforms.
+ fail-fast: false
+
+ matrix:
+ os: ['ubuntu-latest']
+ rid: ['linux-x64']
+ shell: ['pwsh']
+ include:
+ - os: windows-latest
+ rid: win-x64
+ shell: pwsh
+ - os: windows-latest
+ rid: win-x64
+ shell: powershell
+ - os: ubuntu-latest
+ rid: linux-x64
+ shell: pwsh
+ - os: ubuntu-latest
+ rid: linux-musl-x64
+ shell: pwsh
+ - os: macos-latest
+ rid: osx-x64
+ shell: pwsh
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.x
+
+ - if: ${{ matrix.shell == 'pwsh' }}
+ name: Install dependencies (PowerShell)
+ shell: pwsh
+ timeout-minutes: 3
+ run: ./scripts/pipeline-deps.ps1
+
+ - if: ${{ matrix.shell == 'powershell' }}
+ name: Install dependencies (Windows PowerShell)
+ shell: powershell
+ timeout-minutes: 3
+ run: ./scripts/pipeline-deps.ps1
+
+ - name: Download module
+ uses: actions/download-artifact@v4
+ with:
+ name: Module
+ path: ./out/modules/PSRule
+
+ - if: ${{ matrix.shell == 'pwsh' }}
+ name: Test module (PowerShell)
+ shell: pwsh
+ timeout-minutes: 15
+ run: Invoke-Build TestModule -Configuration Release -AssertStyle GitHubActions
+
+ - if: ${{ matrix.shell == 'powershell' }}
+ name: Test module (Windows PowerShell)
+ shell: powershell
+ timeout-minutes: 30
+ run: Invoke-Build TestModule -Configuration Release -AssertStyle GitHubActions
diff --git a/.github/workflows/dependencies.yaml b/.github/workflows/dependencies.yaml
index 4c78074..be1ab49 100644
--- a/.github/workflows/dependencies.yaml
+++ b/.github/workflows/dependencies.yaml
@@ -8,12 +8,14 @@
name: Dependencies
on:
schedule:
- - cron: '45 1 * * 1' # At 01:45 AM, on Monday each week
+ - cron: '30 1 * * 1' # At 01:30 AM, on Monday each week
workflow_dispatch:
env:
WORKING_BRANCH: dependencies/powershell-bump
+permissions: {}
+
jobs:
dependencies:
name: Bump dependencies
@@ -23,9 +25,8 @@ jobs:
contents: write
pull-requests: write
steps:
-
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
fetch-depth: 0
diff --git a/.github/workflows/first-interaction.yaml b/.github/workflows/first-interaction.yaml
new file mode 100644
index 0000000..d7d2f70
--- /dev/null
+++ b/.github/workflows/first-interaction.yaml
@@ -0,0 +1,27 @@
+#
+# Stale item management
+#
+
+# NOTES:
+# This workflow greets a person for their a first issue or PR.
+
+name: First interaction
+
+on: [pull_request_target, issues]
+
+permissions: {}
+
+jobs:
+ greeting:
+ name: Greeting
+ runs-on: ubuntu-latest
+ if: github.repository == 'microsoft/PSRule.Monitor'
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - uses: actions/first-interaction@v1
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ issue-message: 'Thanks for raising your first issue, the team appreciates the time you have taken 😉'
+ pr-message: 'Thank you for your contribution, one of the team will evaluate shortly.'
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
new file mode 100644
index 0000000..96d0f80
--- /dev/null
+++ b/.github/workflows/stale.yaml
@@ -0,0 +1,40 @@
+#
+# Stale item management
+#
+
+# NOTES:
+# This workflow manages stale work items on the repository.
+
+name: Stale maintenance
+on:
+ schedule:
+ - cron: '30 1 * * *'
+ workflow_dispatch:
+
+permissions: {}
+
+jobs:
+ issue:
+ name: Close stale issues
+ runs-on: ubuntu-latest
+ if: github.repository == 'microsoft/PSRule.Monitor'
+ permissions:
+ issues: write
+ steps:
+ - uses: actions/stale@v9
+ with:
+ stale-issue-message: >
+ This issue has been automatically marked as stale because it has not had
+ recent activity. It will be closed if no further activity occurs within 7 days.
+ Thank you for your contributions.
+
+ close-issue-message: 'This issue was closed because it has not had any recent activity.'
+
+ days-before-stale: 14
+ days-before-pr-stale: -1
+
+ days-before-close: 7
+ days-before-pr-close: -1
+
+ any-of-labels: 'question,duplicate,incomplete,waiting-feedback'
+ stale-issue-label: stale
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9e022f4..c2cb50f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
## Unreleased
+What's changed since v0.6.0:
+
+- Engineering:
+ - Updated to .NET 8.0 for build process and testing.
+ [#150](https://github.com/microsoft/PSRule.Monitor/issues/150)
+
## v0.6.0
What's changed since v0.5.0:
diff --git a/pipeline.build.ps1 b/pipeline.build.ps1
index bd3ebc1..f381c5c 100644
--- a/pipeline.build.ps1
+++ b/pipeline.build.ps1
@@ -162,13 +162,13 @@ task TestDotNet {
if ($CodeCoverage) {
exec {
# Test library
- dotnet test --collect:"Code Coverage" --logger trx -r (Join-Path $PWD -ChildPath reports/) tests/PSRule.Monitor.Tests
+ dotnet test -f net8.0 --collect:"Code Coverage" --logger trx -r (Join-Path $PWD -ChildPath reports/) tests/PSRule.Monitor.Tests
}
}
else {
exec {
# Test library
- dotnet test --logger trx -r (Join-Path $PWD -ChildPath reports/) tests/PSRule.Monitor.Tests
+ dotnet test -f net8.0 --logger trx -r (Join-Path $PWD -ChildPath reports/) tests/PSRule.Monitor.Tests
}
}
}
diff --git a/.azure-pipelines/pipeline-deps.ps1 b/scripts/pipeline-deps.ps1
similarity index 100%
rename from .azure-pipelines/pipeline-deps.ps1
rename to scripts/pipeline-deps.ps1
diff --git a/src/PSRule.Common.props b/src/PSRule.Common.props
index 3035f26..dbf2d2b 100644
--- a/src/PSRule.Common.props
+++ b/src/PSRule.Common.props
@@ -3,7 +3,8 @@
netstandard2.0
- 9.0
+ 12.0
+
en-US
true
portable
@@ -30,7 +31,8 @@
© Microsoft Corporation. All rights reserved.
Log PSRule analysis results to Azure Monitor.
-This project uses GitHub Issues to track bugs and feature requests. See GitHub project for more information.
+ This project uses GitHub Issues to track bugs and feature requests. See GitHub project for
+ more information.
For a list of changes see https://aka.ms/ps-rule-monitor/changelog.
package_icon.png
@@ -64,4 +66,4 @@ This project uses GitHub Issues to track bugs and feature requests. See GitHub p
\
-
+
\ No newline at end of file
diff --git a/src/PSRule.Monitor/Common/JsonConverters.cs b/src/PSRule.Monitor/Common/JsonConverters.cs
index 017954b..99cd0f6 100644
--- a/src/PSRule.Monitor/Common/JsonConverters.cs
+++ b/src/PSRule.Monitor/Common/JsonConverters.cs
@@ -1,34 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using Newtonsoft.Json;
using System;
using System.Collections;
+using Newtonsoft.Json;
-namespace PSRule.Monitor
+namespace PSRule.Monitor;
+
+///
+/// A JSON converter to convert an object into a flat string.
+///
+internal sealed class StringifyMapConverter : JsonConverter
{
- ///
- /// A JSON converter to convert an object into a flat string.
- ///
- internal sealed class StringifyMapConverter : JsonConverter
+ public override bool CanConvert(Type objectType)
{
- public override bool CanConvert(Type objectType)
- {
- return typeof(Hashtable).IsAssignableFrom(objectType);
- }
+ return typeof(Hashtable).IsAssignableFrom(objectType);
+ }
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
- {
- throw new NotImplementedException();
- }
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ throw new NotImplementedException();
+ }
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
- {
- if (!(value is Hashtable map))
- return;
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ if (value is not Hashtable map)
+ return;
- var v = JsonConvert.SerializeObject(map);
- writer.WriteValue(v);
- }
+ var v = JsonConvert.SerializeObject(map);
+ writer.WriteValue(v);
}
}
diff --git a/src/PSRule.Monitor/Common/SecureStringAttribute.cs b/src/PSRule.Monitor/Common/SecureStringAttribute.cs
index 1112d38..3e506be 100644
--- a/src/PSRule.Monitor/Common/SecureStringAttribute.cs
+++ b/src/PSRule.Monitor/Common/SecureStringAttribute.cs
@@ -5,32 +5,31 @@
using System.Net;
using System.Security;
-namespace PSRule.Monitor
+namespace PSRule.Monitor;
+
+///
+/// A parameter transformation attribute for converting a string to a secure string.
+///
+public sealed class SecureStringAttribute : ArgumentTransformationAttribute
{
- ///
- /// A parameter transformation attribute for converting a string to a secure string.
- ///
- public sealed class SecureStringAttribute : ArgumentTransformationAttribute
- {
- public SecureStringAttribute()
- : base() { }
+ public SecureStringAttribute()
+ : base() { }
- public override bool TransformNullOptionalParameters => false;
+ public override bool TransformNullOptionalParameters => false;
- public override object Transform(EngineIntrinsics engineIntrinsics, object inputData)
- {
- return TrySecureString(inputData, out SecureString s) || (inputData is PSObject pso && TrySecureString(pso, out s)) ? s : null;
- }
+ public override object Transform(EngineIntrinsics engineIntrinsics, object inputData)
+ {
+ return TrySecureString(inputData, out var s) || (inputData is PSObject pso && TrySecureString(pso, out s)) ? s : null;
+ }
- private static bool TrySecureString(object o, out SecureString value)
+ private static bool TrySecureString(object o, out SecureString value)
+ {
+ value = null;
+ if (o is string s)
{
- value = null;
- if (o is string s)
- {
- value = new NetworkCredential("na", s).SecurePassword;
- return true;
- }
- return false;
+ value = new NetworkCredential("na", s).SecurePassword;
+ return true;
}
+ return false;
}
}
diff --git a/src/PSRule.Monitor/Configuration/PSRuleOption.cs b/src/PSRule.Monitor/Configuration/PSRuleOption.cs
index bb43605..5bc5630 100644
--- a/src/PSRule.Monitor/Configuration/PSRuleOption.cs
+++ b/src/PSRule.Monitor/Configuration/PSRuleOption.cs
@@ -4,50 +4,49 @@
using System.IO;
using System.Management.Automation;
-namespace PSRule.Monitor.Configuration
+namespace PSRule.Monitor.Configuration;
+
+///
+/// A delegate to allow callback to PowerShell to get current working path.
+///
+internal delegate string PathDelegate();
+
+public sealed class PSRuleOption
{
///
- /// A delgate to allow callback to PowerShell to get current working path.
+ /// A callback that is overridden by PowerShell so that the current working path can be retrieved.
///
- internal delegate string PathDelegate();
+ private static PathDelegate _GetWorkingPath = () => Directory.GetCurrentDirectory();
- public sealed class PSRuleOption
+ ///
+ /// Set working path from PowerShell host environment.
+ ///
+ /// An $ExecutionContext object.
+ ///
+ /// Called from PowerShell.
+ ///
+ public static void UseExecutionContext(EngineIntrinsics executionContext)
{
- ///
- /// A callback that is overridden by PowerShell so that the current working path can be retrieved.
- ///
- private static PathDelegate _GetWorkingPath = () => Directory.GetCurrentDirectory();
-
- ///
- /// Set working path from PowerShell host environment.
- ///
- /// An $ExecutionContext object.
- ///
- /// Called from PowerShell.
- ///
- public static void UseExecutionContext(EngineIntrinsics executionContext)
+ if (executionContext == null)
{
- if (executionContext == null)
- {
- _GetWorkingPath = () => Directory.GetCurrentDirectory();
- return;
- }
- _GetWorkingPath = () => executionContext.SessionState.Path.CurrentFileSystemLocation.Path;
+ _GetWorkingPath = () => Directory.GetCurrentDirectory();
+ return;
}
+ _GetWorkingPath = () => executionContext.SessionState.Path.CurrentFileSystemLocation.Path;
+ }
- public static string GetWorkingPath()
- {
- return _GetWorkingPath();
- }
+ public static string GetWorkingPath()
+ {
+ return _GetWorkingPath();
+ }
- ///
- /// Get a full path instead of a relative path that may be passed from PowerShell.
- ///
- ///
- ///
- internal static string GetRootedPath(string path)
- {
- return Path.IsPathRooted(path) ? path : Path.GetFullPath(Path.Combine(GetWorkingPath(), path));
- }
+ ///
+ /// Get a full path instead of a relative path that may be passed from PowerShell.
+ ///
+ ///
+ ///
+ internal static string GetRootedPath(string path)
+ {
+ return Path.IsPathRooted(path) ? path : Path.GetFullPath(Path.Combine(GetWorkingPath(), path));
}
}
diff --git a/src/PSRule.Monitor/Data/LogRecord.cs b/src/PSRule.Monitor/Data/LogRecord.cs
index 293be8e..454e299 100644
--- a/src/PSRule.Monitor/Data/LogRecord.cs
+++ b/src/PSRule.Monitor/Data/LogRecord.cs
@@ -1,47 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using Newtonsoft.Json;
using System;
using System.Collections;
+using Newtonsoft.Json;
+
+namespace PSRule.Monitor.Data;
-namespace PSRule.Monitor.Data
+///
+/// An Azure Monitor log record.
+///
+internal sealed class LogRecord
{
- ///
- /// An Azure Monitor log record.
- ///
- internal sealed class LogRecord
- {
- public string RuleId { get; set; }
+ public string RuleId { get; set; }
- public string RuleName { get; set; }
+ public string RuleName { get; set; }
- public string DisplayName { get; set; }
+ public string DisplayName { get; set; }
- public string ModuleName { get; set; }
+ public string ModuleName { get; set; }
- public string TargetName { get; set; }
+ public string TargetName { get; set; }
- public string TargetType { get; set; }
+ public string TargetType { get; set; }
- public string Outcome { get; set; }
+ public string Outcome { get; set; }
- [JsonIgnore]
- public string ResourceId { get; set; }
+ [JsonIgnore]
+ public string ResourceId { get; set; }
- [JsonConverter(typeof(StringifyMapConverter))]
- public Hashtable Field { get; set; }
+ [JsonConverter(typeof(StringifyMapConverter))]
+ public Hashtable Field { get; set; }
- [JsonConverter(typeof(StringifyMapConverter))]
- public Hashtable Data { get; set; }
+ [JsonConverter(typeof(StringifyMapConverter))]
+ public Hashtable Data { get; set; }
- [JsonConverter(typeof(StringifyMapConverter))]
- public Hashtable Annotations { get; set; }
+ [JsonConverter(typeof(StringifyMapConverter))]
+ public Hashtable Annotations { get; set; }
- public string RunId { get; set; }
+ public string RunId { get; set; }
- public Guid CorrelationId { get; set; }
+ public Guid CorrelationId { get; set; }
- public long Duration { get; set; }
- }
+ public long Duration { get; set; }
}
diff --git a/src/PSRule.Monitor/Pipeline/BatchQueue.cs b/src/PSRule.Monitor/Pipeline/BatchQueue.cs
index 9296fd8..522b5ff 100644
--- a/src/PSRule.Monitor/Pipeline/BatchQueue.cs
+++ b/src/PSRule.Monitor/Pipeline/BatchQueue.cs
@@ -1,54 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using PSRule.Monitor.Data;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using PSRule.Monitor.Data;
+
+namespace PSRule.Monitor.Pipeline;
-namespace PSRule.Monitor.Pipeline
+internal sealed class BatchQueue
{
- internal sealed class BatchQueue
+ private readonly ConcurrentQueue _Queue;
+
+ public BatchQueue()
+ {
+ _Queue = new ConcurrentQueue();
+ }
+
+ public int Count
+ {
+ get { return _Queue.Count; }
+ }
+
+ public bool IsEmpty
+ {
+ get { return _Queue.IsEmpty; }
+ }
+
+ public void Enqueue(LogRecord record)
+ {
+ if (record == null)
+ return;
+
+ _Queue.Enqueue(record);
+ }
+
+ public bool TryDequeue(int minSize, int maxSize, out LogRecord[] records)
{
- private readonly ConcurrentQueue _Queue;
-
- public BatchQueue()
- {
- _Queue = new ConcurrentQueue();
- }
-
- public int Count
- {
- get { return _Queue.Count; }
- }
-
- public bool IsEmpty
- {
- get { return _Queue.IsEmpty; }
- }
-
- public void Enqueue(LogRecord record)
- {
- if (record == null)
- return;
-
- _Queue.Enqueue(record);
- }
-
- public bool TryDequeue(int minSize, int maxSize, out LogRecord[] records)
- {
- records = null;
- if (_Queue.Count < minSize || _Queue.IsEmpty)
- return false;
-
- string resourceId = _Queue.TryPeek(out LogRecord record) ? record.ResourceId : null;
- var batchSize = _Queue.Count > maxSize ? maxSize : _Queue.Count;
- var batch = new List(batchSize);
- for (var i = 0; i < maxSize && !_Queue.IsEmpty && _Queue.TryPeek(out record) && record.ResourceId == resourceId; i++)
- if (_Queue.TryDequeue(out record))
- batch.Add(record);
-
- records = batch.ToArray();
- return true;
- }
+ records = null;
+ if (_Queue.Count < minSize || _Queue.IsEmpty)
+ return false;
+
+ var resourceId = _Queue.TryPeek(out var record) ? record.ResourceId : null;
+ var batchSize = _Queue.Count > maxSize ? maxSize : _Queue.Count;
+ var batch = new List(batchSize);
+ for (var i = 0; i < maxSize && !_Queue.IsEmpty && _Queue.TryPeek(out record) && record.ResourceId == resourceId; i++)
+ if (_Queue.TryDequeue(out record))
+ batch.Add(record);
+
+ records = batch.ToArray();
+ return true;
}
}
diff --git a/src/PSRule.Monitor/Pipeline/CollectionHash.cs b/src/PSRule.Monitor/Pipeline/CollectionHash.cs
index 6b4a6c3..c7c81ec 100644
--- a/src/PSRule.Monitor/Pipeline/CollectionHash.cs
+++ b/src/PSRule.Monitor/Pipeline/CollectionHash.cs
@@ -8,56 +8,55 @@
using System.Security.Cryptography;
using System.Text;
-namespace PSRule.Monitor.Pipeline
+namespace PSRule.Monitor.Pipeline;
+
+internal sealed class CollectionHash : IDisposable
{
- internal sealed class CollectionHash : IDisposable
- {
- private readonly string _WorkspaceId;
- private readonly HMACSHA256 _Algorithm;
+ private readonly string _WorkspaceId;
+ private readonly HMACSHA256 _Algorithm;
- private static readonly CultureInfo FormatCulture = new CultureInfo("en-US");
+ private static readonly CultureInfo FormatCulture = new("en-US");
- internal CollectionHash(string workspaceId, SecureString sharedKey)
- {
- _WorkspaceId = workspaceId;
- _Algorithm = new HMACSHA256(Convert.FromBase64String(new NetworkCredential(string.Empty, sharedKey).Password));
- }
+ internal CollectionHash(string workspaceId, SecureString sharedKey)
+ {
+ _WorkspaceId = workspaceId;
+ _Algorithm = new HMACSHA256(Convert.FromBase64String(new NetworkCredential(string.Empty, sharedKey).Password));
+ }
- internal string ComputeSignature(int length, DateTime date, string contentType)
- {
- var challenge = string.Concat("POST\n", length, "\n", contentType, "; charset=utf-8\n", "x-ms-date:", date.ToString("r", FormatCulture), "\n/api/logs");
- return string.Concat("SharedKey ", _WorkspaceId, ":", ComputeHash(challenge));
- }
+ internal string ComputeSignature(int length, DateTime date, string contentType)
+ {
+ var challenge = string.Concat("POST\n", length, "\n", contentType, "; charset=utf-8\n", "x-ms-date:", date.ToString("r", FormatCulture), "\n/api/logs");
+ return string.Concat("SharedKey ", _WorkspaceId, ":", ComputeHash(challenge));
+ }
- private string ComputeHash(string challenge)
- {
- byte[] challengeBytes = Encoding.UTF8.GetBytes(challenge);
- byte[] hash = _Algorithm.ComputeHash(challengeBytes);
- return Convert.ToBase64String(hash);
- }
+ private string ComputeHash(string challenge)
+ {
+ var challengeBytes = Encoding.UTF8.GetBytes(challenge);
+ var hash = _Algorithm.ComputeHash(challengeBytes);
+ return Convert.ToBase64String(hash);
+ }
- #region IDisposable
+ #region IDisposable
- private bool _Disposed; // To detect redundant calls
+ private bool _Disposed; // To detect redundant calls
- void Dispose(bool disposing)
+ void Dispose(bool disposing)
+ {
+ if (!_Disposed)
{
- if (!_Disposed)
+ if (disposing)
{
- if (disposing)
- {
- _Algorithm.Dispose();
- }
- _Disposed = true;
+ _Algorithm.Dispose();
}
+ _Disposed = true;
}
+ }
- // This code added to correctly implement the disposable pattern.
- public void Dispose()
- {
- Dispose(true);
- }
-
- #endregion IDisposable
+ // This code added to correctly implement the disposable pattern.
+ public void Dispose()
+ {
+ Dispose(true);
}
+
+ #endregion IDisposable
}
diff --git a/src/PSRule.Monitor/Pipeline/Exceptions.cs b/src/PSRule.Monitor/Pipeline/Exceptions.cs
index 601e59f..c699b05 100644
--- a/src/PSRule.Monitor/Pipeline/Exceptions.cs
+++ b/src/PSRule.Monitor/Pipeline/Exceptions.cs
@@ -6,92 +6,91 @@
using System.Runtime.Serialization;
using System.Security.Permissions;
-namespace PSRule.Monitor.Pipeline
+namespace PSRule.Monitor.Pipeline;
+
+///
+/// A base class for all pipeline exceptions.
+///
+public abstract class PipelineException : Exception
{
- ///
- /// A base class for all pipeline exceptions.
- ///
- public abstract class PipelineException : Exception
- {
- protected PipelineException()
- : base() { }
+ protected PipelineException()
+ : base() { }
- protected PipelineException(string message)
- : base(message) { }
+ protected PipelineException(string message)
+ : base(message) { }
- protected PipelineException(string message, Exception innerException)
- : base(message, innerException) { }
+ protected PipelineException(string message, Exception innerException)
+ : base(message, innerException) { }
- protected PipelineException(SerializationInfo info, StreamingContext context)
- : base(info, context) { }
- }
+ protected PipelineException(SerializationInfo info, StreamingContext context)
+ : base(info, context) { }
+}
- ///
- /// A base class for runtime exceptions.
- ///
- public abstract class RuntimeException : PipelineException
- {
- protected RuntimeException()
- : base() { }
+///
+/// A base class for runtime exceptions.
+///
+public abstract class RuntimeException : PipelineException
+{
+ protected RuntimeException()
+ : base() { }
+
+ protected RuntimeException(string message)
+ : base(message) { }
+
+ protected RuntimeException(string message, Exception innerException)
+ : base(message, innerException) { }
- protected RuntimeException(string message)
- : base(message) { }
+ protected RuntimeException(Exception innerException, InvocationInfo invocationInfo, string ruleId)
+ : base(innerException?.Message, innerException)
+ {
+ CommandInvocation = invocationInfo;
+ RuleId = ruleId;
+ }
- protected RuntimeException(string message, Exception innerException)
- : base(message, innerException) { }
+ protected RuntimeException(SerializationInfo info, StreamingContext context)
+ : base(info, context) { }
- protected RuntimeException(Exception innerException, InvocationInfo invocationInfo, string ruleId)
- : base(innerException?.Message, innerException)
- {
- CommandInvocation = invocationInfo;
- RuleId = ruleId;
- }
+ public InvocationInfo CommandInvocation { get; }
- protected RuntimeException(SerializationInfo info, StreamingContext context)
- : base(info, context) { }
+ public string RuleId { get; }
+}
- public InvocationInfo CommandInvocation { get; }
+///
+/// An exception when building the pipeline.
+///
+[Serializable]
+public sealed class PipelineBuilderException : PipelineException
+{
+ ///
+ /// Creates a pipeline builder exception.
+ ///
+ public PipelineBuilderException()
+ : base() { }
- public string RuleId { get; }
- }
+ ///
+ /// Creates a pipeline builder exception.
+ ///
+ /// The detail of the exception.
+ public PipelineBuilderException(string message)
+ : base(message) { }
///
- /// An exception when building the pipeline.
+ /// Creates a pipeline builder exception.
///
- [Serializable]
- public sealed class PipelineBuilderException : PipelineException
+ /// The detail of the exception.
+ /// A nested exception that caused the issue.
+ public PipelineBuilderException(string message, Exception innerException)
+ : base(message, innerException) { }
+
+ private PipelineBuilderException(SerializationInfo info, StreamingContext context)
+ : base(info, context) { }
+
+ [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
- ///
- /// Creates a pipeline builder exception.
- ///
- public PipelineBuilderException()
- : base() { }
-
- ///
- /// Creates a pipeline builder exception.
- ///
- /// The detail of the exception.
- public PipelineBuilderException(string message)
- : base(message) { }
-
- ///
- /// Creates a pipeline builder exception.
- ///
- /// The detail of the exception.
- /// A nested exception that caused the issue.
- public PipelineBuilderException(string message, Exception innerException)
- : base(message, innerException) { }
-
- private PipelineBuilderException(SerializationInfo info, StreamingContext context)
- : base(info, context) { }
-
- [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
- public override void GetObjectData(SerializationInfo info, StreamingContext context)
- {
- if (info == null)
- throw new ArgumentNullException(nameof(info));
-
- base.GetObjectData(info, context);
- }
+ if (info == null)
+ throw new ArgumentNullException(nameof(info));
+
+ base.GetObjectData(info, context);
}
}
diff --git a/src/PSRule.Monitor/Pipeline/InjestPipeline.cs b/src/PSRule.Monitor/Pipeline/InjestPipeline.cs
index 9f264fa..55cdc62 100644
--- a/src/PSRule.Monitor/Pipeline/InjestPipeline.cs
+++ b/src/PSRule.Monitor/Pipeline/InjestPipeline.cs
@@ -1,99 +1,98 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using PSRule.Monitor.Resources;
using System.Management.Automation;
using System.Security;
using System.Threading;
+using PSRule.Monitor.Resources;
-namespace PSRule.Monitor.Pipeline
-{
- public interface IInjestPipelineBuilder : IPipelineBuilder
- {
- void WorkspaceId(string workspaceId);
+namespace PSRule.Monitor.Pipeline;
- void SharedKey(SecureString sharedKey);
+public interface IInjestPipelineBuilder : IPipelineBuilder
+{
+ void WorkspaceId(string workspaceId);
- void LogName(string logName);
- }
+ void SharedKey(SecureString sharedKey);
- internal sealed class InjestPipelineBuilder : PipelineBuilderBase, IInjestPipelineBuilder
- {
- private const string DEFAULT_LOGNAME = "PSRule";
+ void LogName(string logName);
+}
- private string _WorkspaceId;
- private SecureString _SharedKey;
- private string _LogName = DEFAULT_LOGNAME;
+internal sealed class InjestPipelineBuilder : PipelineBuilderBase, IInjestPipelineBuilder
+{
+ private const string DEFAULT_LOGNAME = "PSRule";
- public void WorkspaceId(string workspaceId)
- {
- if (string.IsNullOrEmpty(workspaceId))
- throw new PipelineBuilderException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.InvalidWorkspaceId));
+ private string _WorkspaceId;
+ private SecureString _SharedKey;
+ private string _LogName = DEFAULT_LOGNAME;
- _WorkspaceId = workspaceId;
- }
+ public void WorkspaceId(string workspaceId)
+ {
+ if (string.IsNullOrEmpty(workspaceId))
+ throw new PipelineBuilderException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.InvalidWorkspaceId));
- public void SharedKey(SecureString sharedKey)
- {
- if (sharedKey == null)
- throw new PipelineBuilderException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.InvalidSharedKey));
+ _WorkspaceId = workspaceId;
+ }
- _SharedKey = sharedKey;
- }
+ public void SharedKey(SecureString sharedKey)
+ {
+ if (sharedKey == null)
+ throw new PipelineBuilderException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.InvalidSharedKey));
- public void LogName(string logName)
- {
- _LogName = logName;
- }
+ _SharedKey = sharedKey;
+ }
- public override IPipeline Build()
- {
- var logClient = new LogClient(_WorkspaceId, _LogName);
- return new InjestPipeline(PrepareContext(), PrepareReader(), _WorkspaceId, _SharedKey, logClient);
- }
+ public void LogName(string logName)
+ {
+ _LogName = logName;
}
- internal sealed class InjestPipeline : PipelineBase
+ public override IPipeline Build()
{
- private readonly WorkspaceClient _WorkspaceClient;
+ var logClient = new LogClient(_WorkspaceId, _LogName);
+ return new InjestPipeline(PrepareContext(), PrepareReader(), _WorkspaceId, _SharedKey, logClient);
+ }
+}
- // Track whether Dispose has been called.
- private bool _Disposed;
+internal sealed class InjestPipeline : PipelineBase
+{
+ private readonly WorkspaceClient _WorkspaceClient;
- internal InjestPipeline(PipelineContext context, PipelineReader reader, string workspaceId, SecureString sharedKey, ILogClient logClient)
- : base(context, reader)
- {
- _WorkspaceClient = new WorkspaceClient(workspaceId, sharedKey, logClient);
- }
+ // Track whether Dispose has been called.
+ private bool _Disposed;
- public override void Process(PSObject sourceObject)
- {
- Reader.Enqueue(sourceObject);
- while (Reader.TryDequeue(out PSObject next))
- _WorkspaceClient.Enqueue(next);
+ internal InjestPipeline(PipelineContext context, PipelineReader reader, string workspaceId, SecureString sharedKey, ILogClient logClient)
+ : base(context, reader)
+ {
+ _WorkspaceClient = new WorkspaceClient(workspaceId, sharedKey, logClient);
+ }
- _WorkspaceClient.Send(30, 100);
- }
+ public override void Process(PSObject sourceObject)
+ {
+ Reader.Enqueue(sourceObject);
+ while (Reader.TryDequeue(out var next))
+ _WorkspaceClient.Enqueue(next);
- public override void End()
- {
- _WorkspaceClient.Send();
- }
+ _WorkspaceClient.Send(30, 100);
+ }
+
+ public override void End()
+ {
+ _WorkspaceClient.Send();
+ }
- #region IDisposable
+ #region IDisposable
- protected override void Dispose(bool disposing)
+ protected override void Dispose(bool disposing)
+ {
+ if (!_Disposed)
{
- if (!_Disposed)
- {
- if (disposing)
- _WorkspaceClient.Dispose();
-
- _Disposed = true;
- }
- base.Dispose(disposing);
- }
+ if (disposing)
+ _WorkspaceClient.Dispose();
- #endregion IDisposable
+ _Disposed = true;
+ }
+ base.Dispose(disposing);
}
+
+ #endregion IDisposable
}
diff --git a/src/PSRule.Monitor/Pipeline/LogClient.cs b/src/PSRule.Monitor/Pipeline/LogClient.cs
index f96d952..3265b4f 100644
--- a/src/PSRule.Monitor/Pipeline/LogClient.cs
+++ b/src/PSRule.Monitor/Pipeline/LogClient.cs
@@ -6,92 +6,89 @@
using System.Net.Http;
using System.Text;
-namespace PSRule.Monitor.Pipeline
+namespace PSRule.Monitor.Pipeline;
+
+internal interface ILogClient : IDisposable
{
- internal interface ILogClient : IDisposable
- {
- void Post(string signature, DateTime date, string resourceId, string json);
- }
+ void Post(string signature, DateTime date, string resourceId, string json);
+}
- internal sealed class LogClient : ILogClient
- {
- private const string CONTENTTYPE = "application/json";
- private const string TIMESTAMPFIELD = "";
- private const string APIVERSION = "2016-04-01";
+internal sealed class LogClient : ILogClient
+{
+ private const string CONTENTTYPE = "application/json";
+ private const string TIMESTAMPFIELD = "";
+ private const string APIVERSION = "2016-04-01";
- private const string HEADER_ACCEPT = "Accept";
- private const string HEADER_AUTHORIZATION = "Authorization";
- private const string HEADER_LOGTYPE = "Log-Type";
- private const string HEADER_DATE = "x-ms-date";
- private const string HEADER_RESOURCEID = "x-ms-AzureResourceId";
- private const string HEADER_TIMEGENERATED = "time-generated-field";
+ private const string HEADER_ACCEPT = "Accept";
+ private const string HEADER_AUTHORIZATION = "Authorization";
+ private const string HEADER_LOGTYPE = "Log-Type";
+ private const string HEADER_DATE = "x-ms-date";
+ private const string HEADER_RESOURCEID = "x-ms-AzureResourceId";
+ private const string HEADER_TIMEGENERATED = "time-generated-field";
- private static readonly CultureInfo FormatCulture = new CultureInfo("en-US");
+ private static readonly CultureInfo FormatCulture = new("en-US");
- private readonly HttpClient _HttpClient;
- private readonly Uri _EndpointUri;
+ private readonly HttpClient _HttpClient;
+ private readonly Uri _EndpointUri;
- // Track whether Dispose has been called.
- private bool _Disposed;
+ // Track whether Dispose has been called.
+ private bool _Disposed;
- public LogClient(string workspaceId, string logName)
- {
- _EndpointUri = new Uri(string.Concat("https://", workspaceId, ".ods.opinsights.azure.com/api/logs?api-version=", APIVERSION));
- _HttpClient = GetClient(logName);
- }
+ public LogClient(string workspaceId, string logName)
+ {
+ _EndpointUri = new Uri(string.Concat("https://", workspaceId, ".ods.opinsights.azure.com/api/logs?api-version=", APIVERSION));
+ _HttpClient = GetClient(logName);
+ }
- ///
- /// Post log data to Azure Monitor endpoint.
- ///
- public void Post(string signature, DateTime date, string resourceId, string json)
- {
- using (var request = PrepareRequest(signature, date, resourceId, json))
- {
- var response = _HttpClient.SendAsync(request);
- response.Wait();
- var result = response.Result.Content.ReadAsStringAsync().Result;
- }
- }
+ ///
+ /// Post log data to Azure Monitor endpoint.
+ ///
+ public void Post(string signature, DateTime date, string resourceId, string json)
+ {
+ using var request = PrepareRequest(signature, date, resourceId, json);
+ var response = _HttpClient.SendAsync(request);
+ response.Wait();
+ var result = response.Result.Content.ReadAsStringAsync().Result;
+ }
- private static HttpClient GetClient(string logName)
- {
- var client = new HttpClient();
- client.DefaultRequestHeaders.Add(HEADER_ACCEPT, CONTENTTYPE);
- client.DefaultRequestHeaders.Add(HEADER_LOGTYPE, logName);
- return client;
- }
+ private static HttpClient GetClient(string logName)
+ {
+ var client = new HttpClient();
+ client.DefaultRequestHeaders.Add(HEADER_ACCEPT, CONTENTTYPE);
+ client.DefaultRequestHeaders.Add(HEADER_LOGTYPE, logName);
+ return client;
+ }
- private HttpRequestMessage PrepareRequest(string signature, DateTime date, string resourceId, string json)
- {
- var request = new HttpRequestMessage(HttpMethod.Post, _EndpointUri);
- request.Headers.Add(HEADER_AUTHORIZATION, signature);
- request.Headers.Add(HEADER_DATE, date.ToString("r", FormatCulture));
- request.Headers.Add(HEADER_TIMEGENERATED, TIMESTAMPFIELD);
- request.Headers.Add(HEADER_RESOURCEID, resourceId);
- request.Content = new StringContent(json, Encoding.UTF8, CONTENTTYPE);
- return request;
- }
+ private HttpRequestMessage PrepareRequest(string signature, DateTime date, string resourceId, string json)
+ {
+ var request = new HttpRequestMessage(HttpMethod.Post, _EndpointUri);
+ request.Headers.Add(HEADER_AUTHORIZATION, signature);
+ request.Headers.Add(HEADER_DATE, date.ToString("r", FormatCulture));
+ request.Headers.Add(HEADER_TIMEGENERATED, TIMESTAMPFIELD);
+ request.Headers.Add(HEADER_RESOURCEID, resourceId);
+ request.Content = new StringContent(json, Encoding.UTF8, CONTENTTYPE);
+ return request;
+ }
- #region IDisposable
+ #region IDisposable
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
- private void Dispose(bool disposing)
+ private void Dispose(bool disposing)
+ {
+ if (!_Disposed)
{
- if (!_Disposed)
+ if (disposing)
{
- if (disposing)
- {
- _HttpClient.Dispose();
- }
- _Disposed = true;
+ _HttpClient.Dispose();
}
+ _Disposed = true;
}
-
- #endregion IDisposable
}
+
+ #endregion IDisposable
}
diff --git a/src/PSRule.Monitor/Pipeline/MonitorClient.cs b/src/PSRule.Monitor/Pipeline/MonitorClient.cs
index 849a661..841eb5f 100644
--- a/src/PSRule.Monitor/Pipeline/MonitorClient.cs
+++ b/src/PSRule.Monitor/Pipeline/MonitorClient.cs
@@ -6,134 +6,133 @@
using System.Management.Automation;
using System.Reflection;
-namespace PSRule.Monitor.Pipeline
+namespace PSRule.Monitor.Pipeline;
+
+internal abstract class MonitorClient : IDisposable
{
- internal abstract class MonitorClient : IDisposable
+ // Track whether Dispose has been called.
+ private bool _Disposed;
+
+ protected static string GetPropertyValue(PSObject obj, string propertyName)
{
- // Track whether Dispose has been called.
- private bool _Disposed;
+ return obj.Properties[propertyName] == null || obj.Properties[propertyName].Value == null ? null : obj.Properties[propertyName].Value.ToString();
+ }
- protected static string GetPropertyValue(PSObject obj, string propertyName)
- {
- return obj.Properties[propertyName] == null || obj.Properties[propertyName].Value == null ? null : obj.Properties[propertyName].Value.ToString();
- }
+ protected static Guid? GetPropertyGuid(PSObject obj, string propertyName)
+ {
+ var result = GetPropertyValue(obj, propertyName);
+ if (result == null)
+ return null;
- protected static Guid? GetPropertyGuid(PSObject obj, string propertyName)
- {
- var result = GetPropertyValue(obj, propertyName);
- if (result == null)
- return null;
+ return Guid.Parse(result);
+ }
- return Guid.Parse(result);
- }
+ protected static T GetProperty(PSObject obj, string propertyName)
+ {
+ return obj.Properties[propertyName] == null ? default : (T)obj.Properties[propertyName].Value;
+ }
- protected static T GetProperty(PSObject obj, string propertyName)
- {
- return obj.Properties[propertyName] == null ? default(T) : (T)obj.Properties[propertyName].Value;
- }
+ protected static object GetProperty(object obj, string propertyName)
+ {
+ return TryProperty(obj, propertyName, out var value) ? value : null;
+ }
- protected static object GetProperty(object obj, string propertyName)
+ private static bool TryProperty(object obj, string propertyName, out object value)
+ {
+ value = null;
+ var typeInfo = obj.GetType();
+ if (obj is PSObject o && o.Properties[propertyName] != null)
{
- return TryProperty(obj, propertyName, out object value) ? value : null;
+ value = o.Properties[propertyName].Value;
+ return true;
}
-
- private static bool TryProperty(object obj, string propertyName, out object value)
+ else
{
- value = null;
- var typeInfo = obj.GetType();
- if (obj is PSObject o && o.Properties[propertyName] != null)
+ var propertyInfo = typeInfo.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty);
+ if (propertyInfo != null)
{
- value = o.Properties[propertyName].Value;
+ value = propertyInfo.GetValue(obj);
return true;
}
- else
- {
- var propertyInfo = typeInfo.GetProperty(propertyName, BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty);
- if (propertyInfo != null)
- {
- value = propertyInfo.GetValue(obj);
- return true;
- }
- }
- return false;
}
+ return false;
+ }
- protected static Hashtable GetPropertyMap(object o)
- {
- if (o == null)
- return null;
+ protected static Hashtable GetPropertyMap(object o)
+ {
+ if (o == null)
+ return null;
- var result = new Hashtable();
- if (o is IDictionary dictionary)
+ var result = new Hashtable();
+ if (o is IDictionary dictionary)
+ {
+ foreach (DictionaryEntry kv in dictionary)
{
- foreach (DictionaryEntry kv in dictionary)
- {
- if (HasValue(kv.Value))
- result[kv.Key] = kv.Value;
- }
+ if (HasValue(kv.Value))
+ result[kv.Key] = kv.Value;
}
- else if (o is PSObject pso)
+ }
+ else if (o is PSObject pso)
+ {
+ foreach (var p in pso.Properties)
{
- foreach (var p in pso.Properties)
- {
- if (p.MemberType == PSMemberTypes.NoteProperty && HasValue(p.Value))
- result[p.Name] = p.Value;
- }
+ if (p.MemberType == PSMemberTypes.NoteProperty && HasValue(p.Value))
+ result[p.Name] = p.Value;
}
- return result.Count == 0 ? null : result;
}
+ return result.Count == 0 ? null : result;
+ }
- private static bool HasValue(object value)
- {
- return !(value == null || (value is string s && string.IsNullOrEmpty(s)));
- }
+ private static bool HasValue(object value)
+ {
+ return !(value == null || (value is string s && string.IsNullOrEmpty(s)));
+ }
- protected static string GetField(object o, string propertyName)
- {
- if (o is IDictionary dictionary && TryDictionary(dictionary, propertyName, out object value) && value != null)
- return value.ToString();
+ protected static string GetField(object o, string propertyName)
+ {
+ if (o is IDictionary dictionary && TryDictionary(dictionary, propertyName, out var value) && value != null)
+ return value.ToString();
- if (o is PSObject pso)
- return GetPropertyValue(pso, propertyName);
+ if (o is PSObject pso)
+ return GetPropertyValue(pso, propertyName);
- return null;
- }
+ return null;
+ }
- protected static bool TryDictionary(IDictionary dictionary, string key, out object value)
+ protected static bool TryDictionary(IDictionary dictionary, string key, out object value)
+ {
+ value = null;
+ var comparer = StringComparer.OrdinalIgnoreCase;
+ foreach (var k in dictionary.Keys)
{
- value = null;
- var comparer = StringComparer.OrdinalIgnoreCase;
- foreach (var k in dictionary.Keys)
+ if (comparer.Equals(key, k))
{
- if (comparer.Equals(key, k))
- {
- value = dictionary[k];
- return true;
- }
+ value = dictionary[k];
+ return true;
}
- return false;
}
+ return false;
+ }
- #region IDisposable
+ #region IDisposable
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
- protected virtual void Dispose(bool disposing)
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_Disposed)
{
- if (!_Disposed)
+ if (disposing)
{
- if (disposing)
- {
- // Do nothing yet
- }
- _Disposed = true;
+ // Do nothing yet
}
+ _Disposed = true;
}
-
- #endregion IDisposable
}
+
+ #endregion IDisposable
}
diff --git a/src/PSRule.Monitor/Pipeline/PipelineBuilder.cs b/src/PSRule.Monitor/Pipeline/PipelineBuilder.cs
index 8633f9a..2cba1d5 100644
--- a/src/PSRule.Monitor/Pipeline/PipelineBuilder.cs
+++ b/src/PSRule.Monitor/Pipeline/PipelineBuilder.cs
@@ -1,135 +1,134 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using PSRule.Monitor.Configuration;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Management.Automation;
+using PSRule.Monitor.Configuration;
+
+namespace PSRule.Monitor.Pipeline;
-namespace PSRule.Monitor.Pipeline
+public static class PipelineBuilder
{
- public static class PipelineBuilder
+ public static IInjestPipelineBuilder Injest(PSRuleOption option)
{
- public static IInjestPipelineBuilder Injest(PSRuleOption option)
- {
- var builder = new InjestPipelineBuilder();
- builder.Configure(option);
- return builder;
- }
+ var builder = new InjestPipelineBuilder();
+ builder.Configure(option);
+ return builder;
}
+}
- public interface IPipelineBuilder
- {
- void UseCommandRuntime(ICommandRuntime2 commandRuntime);
+public interface IPipelineBuilder
+{
+ void UseCommandRuntime(ICommandRuntime2 commandRuntime);
- void UseExecutionContext(EngineIntrinsics executionContext);
+ void UseExecutionContext(EngineIntrinsics executionContext);
- [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords")]
- IPipelineBuilder Configure(PSRuleOption option);
+ [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords")]
+ IPipelineBuilder Configure(PSRuleOption option);
- IPipeline Build();
- }
+ IPipeline Build();
+}
- public interface IPipeline
- {
- void Begin();
+public interface IPipeline
+{
+ void Begin();
- void Process(PSObject sourceObject);
+ void Process(PSObject sourceObject);
- [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords")]
- void End();
- }
+ [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords")]
+ void End();
+}
- internal abstract class PipelineBuilderBase : IPipelineBuilder
- {
- protected readonly PSRuleOption Option;
+internal abstract class PipelineBuilderBase : IPipelineBuilder
+{
+ protected readonly PSRuleOption Option;
- protected PipelineBuilderBase()
- {
- Option = new PSRuleOption();
- }
+ protected PipelineBuilderBase()
+ {
+ Option = new PSRuleOption();
+ }
- public virtual void UseCommandRuntime(ICommandRuntime2 commandRuntime)
- {
- // Do nothing
- }
+ public virtual void UseCommandRuntime(ICommandRuntime2 commandRuntime)
+ {
+ // Do nothing
+ }
- public void UseExecutionContext(EngineIntrinsics executionContext)
- {
- // Do nothing
- }
+ public void UseExecutionContext(EngineIntrinsics executionContext)
+ {
+ // Do nothing
+ }
- public virtual IPipelineBuilder Configure(PSRuleOption option)
- {
- return this;
- }
+ public virtual IPipelineBuilder Configure(PSRuleOption option)
+ {
+ return this;
+ }
- public abstract IPipeline Build();
+ public abstract IPipeline Build();
- protected PipelineContext PrepareContext()
- {
- return new PipelineContext(Option);
- }
-
- protected virtual PipelineReader PrepareReader()
- {
- return new PipelineReader();
- }
+ protected PipelineContext PrepareContext()
+ {
+ return new PipelineContext(Option);
}
- internal abstract class PipelineBase : IDisposable, IPipeline
+ protected virtual PipelineReader PrepareReader()
{
- protected readonly PipelineContext Context;
- protected readonly PipelineReader Reader;
+ return new PipelineReader();
+ }
+}
- // Track whether Dispose has been called.
- private bool _Disposed;
+internal abstract class PipelineBase : IDisposable, IPipeline
+{
+ protected readonly PipelineContext Context;
+ protected readonly PipelineReader Reader;
- protected PipelineBase(PipelineContext context, PipelineReader reader)
- {
- Context = context;
- Reader = reader;
- }
+ // Track whether Dispose has been called.
+ private bool _Disposed;
- #region IPipeline
+ protected PipelineBase(PipelineContext context, PipelineReader reader)
+ {
+ Context = context;
+ Reader = reader;
+ }
- public virtual void Begin()
- {
- // Do nothing
- }
+ #region IPipeline
- public virtual void Process(PSObject sourceObject)
- {
- // Do nothing
- }
+ public virtual void Begin()
+ {
+ // Do nothing
+ }
- public virtual void End()
- {
- // Do nothing
- }
+ public virtual void Process(PSObject sourceObject)
+ {
+ // Do nothing
+ }
+
+ public virtual void End()
+ {
+ // Do nothing
+ }
- #endregion IPipeline
+ #endregion IPipeline
- #region IDisposable
+ #region IDisposable
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
- protected virtual void Dispose(bool disposing)
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_Disposed)
{
- if (!_Disposed)
+ if (disposing)
{
- if (disposing)
- {
- Context.Dispose();
- }
- _Disposed = true;
+ Context.Dispose();
}
+ _Disposed = true;
}
-
- #endregion IDisposable
}
+
+ #endregion IDisposable
}
diff --git a/src/PSRule.Monitor/Pipeline/PipelineContext.cs b/src/PSRule.Monitor/Pipeline/PipelineContext.cs
index cf0dd52..285ea57 100644
--- a/src/PSRule.Monitor/Pipeline/PipelineContext.cs
+++ b/src/PSRule.Monitor/Pipeline/PipelineContext.cs
@@ -1,43 +1,42 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using PSRule.Monitor.Configuration;
using System;
+using PSRule.Monitor.Configuration;
-namespace PSRule.Monitor.Pipeline
+namespace PSRule.Monitor.Pipeline;
+
+internal sealed class PipelineContext : IDisposable
{
- internal sealed class PipelineContext : IDisposable
- {
- internal readonly PSRuleOption Option;
+ internal readonly PSRuleOption Option;
- // Track whether Dispose has been called.
- private bool _Disposed;
+ // Track whether Dispose has been called.
+ private bool _Disposed;
- public PipelineContext(PSRuleOption option)
- {
- Option = option;
- }
+ public PipelineContext(PSRuleOption option)
+ {
+ Option = option;
+ }
- #region IDisposable
+ #region IDisposable
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
- private void Dispose(bool disposing)
+ private void Dispose(bool disposing)
+ {
+ if (!_Disposed)
{
- if (!_Disposed)
+ if (disposing)
{
- if (disposing)
- {
- // Add cleanup
- }
- _Disposed = true;
+ // Add cleanup
}
+ _Disposed = true;
}
-
- #endregion IDisposable
}
+
+ #endregion IDisposable
}
diff --git a/src/PSRule.Monitor/Pipeline/PipelineReader.cs b/src/PSRule.Monitor/Pipeline/PipelineReader.cs
index 2c06cd0..4125365 100644
--- a/src/PSRule.Monitor/Pipeline/PipelineReader.cs
+++ b/src/PSRule.Monitor/Pipeline/PipelineReader.cs
@@ -4,38 +4,37 @@
using System.Collections.Concurrent;
using System.Management.Automation;
-namespace PSRule.Monitor.Pipeline
+namespace PSRule.Monitor.Pipeline;
+
+internal sealed class PipelineReader
{
- internal sealed class PipelineReader
+ private readonly ConcurrentQueue _Queue;
+
+ public PipelineReader()
+ {
+ _Queue = new ConcurrentQueue();
+ }
+
+ public int Count
+ {
+ get { return _Queue.Count; }
+ }
+
+ public bool IsEmpty
+ {
+ get { return _Queue.IsEmpty; }
+ }
+
+ public void Enqueue(PSObject sourceObject)
+ {
+ if (sourceObject == null)
+ return;
+
+ _Queue.Enqueue(sourceObject);
+ }
+
+ public bool TryDequeue(out PSObject sourceObject)
{
- private readonly ConcurrentQueue _Queue;
-
- public PipelineReader()
- {
- _Queue = new ConcurrentQueue();
- }
-
- public int Count
- {
- get { return _Queue.Count; }
- }
-
- public bool IsEmpty
- {
- get { return _Queue.IsEmpty; }
- }
-
- public void Enqueue(PSObject sourceObject)
- {
- if (sourceObject == null)
- return;
-
- _Queue.Enqueue(sourceObject);
- }
-
- public bool TryDequeue(out PSObject sourceObject)
- {
- return _Queue.TryDequeue(out sourceObject);
- }
+ return _Queue.TryDequeue(out sourceObject);
}
}
diff --git a/src/PSRule.Monitor/Pipeline/WorkspaceClient.cs b/src/PSRule.Monitor/Pipeline/WorkspaceClient.cs
index 04d4e86..11bc183 100644
--- a/src/PSRule.Monitor/Pipeline/WorkspaceClient.cs
+++ b/src/PSRule.Monitor/Pipeline/WorkspaceClient.cs
@@ -1,144 +1,143 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using Newtonsoft.Json;
-using PSRule.Monitor.Data;
using System;
using System.Management.Automation;
using System.Security;
using System.Text;
+using Newtonsoft.Json;
+using PSRule.Monitor.Data;
+
+namespace PSRule.Monitor.Pipeline;
-namespace PSRule.Monitor.Pipeline
+internal static class WorkspaceClientExtensions
{
- internal static class WorkspaceClientExtensions
+ public static void Enqueue(this WorkspaceClient client, PSObject[] results)
{
- public static void Enqueue(this WorkspaceClient client, PSObject[] results)
- {
- if (results == null || results.Length == 0)
- return;
+ if (results == null || results.Length == 0)
+ return;
- for (var i = 0; i < results.Length; i++)
- client.Enqueue(results[i]);
- }
+ for (var i = 0; i < results.Length; i++)
+ client.Enqueue(results[i]);
}
+}
- internal sealed class WorkspaceClient : MonitorClient
- {
- private const string CONTENTTYPE = "application/json";
+internal sealed class WorkspaceClient : MonitorClient
+{
+ private const string CONTENTTYPE = "application/json";
- private readonly CollectionHash _Hash;
- private readonly BatchQueue _SubmissionQueue;
- private readonly ILogClient _LogClient;
- private readonly Guid _CorrelationId;
+ private readonly CollectionHash _Hash;
+ private readonly BatchQueue _SubmissionQueue;
+ private readonly ILogClient _LogClient;
+ private readonly Guid _CorrelationId;
- // Track whether Dispose has been called.
- private bool _Disposed;
+ // Track whether Dispose has been called.
+ private bool _Disposed;
- public WorkspaceClient(string workspaceId, SecureString sharedKey, ILogClient logClient)
- {
- _Hash = new CollectionHash(workspaceId, sharedKey);
- _SubmissionQueue = new BatchQueue();
- _LogClient = logClient;
- _CorrelationId = Guid.NewGuid();
- }
+ public WorkspaceClient(string workspaceId, SecureString sharedKey, ILogClient logClient)
+ {
+ _Hash = new CollectionHash(workspaceId, sharedKey);
+ _SubmissionQueue = new BatchQueue();
+ _LogClient = logClient;
+ _CorrelationId = Guid.NewGuid();
+ }
- public void Enqueue(PSObject result)
- {
- _SubmissionQueue.Enqueue(ProcessResult(result));
- }
+ public void Enqueue(PSObject result)
+ {
+ _SubmissionQueue.Enqueue(ProcessResult(result));
+ }
- public void Send(int minSize, int maxSize)
- {
- while (_SubmissionQueue.TryDequeue(minSize, maxSize, out LogRecord[] records))
- SubmitBatch(records);
- }
+ public void Send(int minSize, int maxSize)
+ {
+ while (_SubmissionQueue.TryDequeue(minSize, maxSize, out var records))
+ SubmitBatch(records);
+ }
- public void Send()
- {
- Send(0, 100);
- }
+ public void Send()
+ {
+ Send(0, 100);
+ }
- ///
- /// Submits a batch of records to Azure Monitor data collector.
- ///
- private void SubmitBatch(LogRecord[] records)
- {
- var json = JsonConvert.SerializeObject(records);
- var resourceId = records[0].ResourceId;
-
- // Create a hash for the API signature
- var date = DateTime.UtcNow;
- var data = Encoding.UTF8.GetBytes(json);
- var signature = _Hash.ComputeSignature(data.Length, date, CONTENTTYPE);
- PostData(signature, date, resourceId, json);
- }
+ ///
+ /// Submits a batch of records to Azure Monitor data collector.
+ ///
+ private void SubmitBatch(LogRecord[] records)
+ {
+ var json = JsonConvert.SerializeObject(records);
+ var resourceId = records[0].ResourceId;
+
+ // Create a hash for the API signature
+ var date = DateTime.UtcNow;
+ var data = Encoding.UTF8.GetBytes(json);
+ var signature = _Hash.ComputeSignature(data.Length, date, CONTENTTYPE);
+ PostData(signature, date, resourceId, json);
+ }
- ///
- /// Maps a RuleRecord to a LogRecord.
- ///
- private LogRecord ProcessResult(PSObject sourceObject)
+ ///
+ /// Maps a RuleRecord to a LogRecord.
+ ///
+ private LogRecord ProcessResult(PSObject sourceObject)
+ {
+ if (sourceObject == null)
+ return null;
+
+ var ruleId = GetPropertyValue(sourceObject, "ruleId");
+ var ruleName = GetPropertyValue(sourceObject, "ruleName");
+ var targetName = GetPropertyValue(sourceObject, "targetName");
+ var targetType = GetPropertyValue(sourceObject, "targetType");
+ var outcome = GetPropertyValue(sourceObject, "outcome");
+ var data = GetProperty