This chapter covers accessing .NET types from PowerShell scripts generated by UnifyWeaver. You’ll learn about inline C# code, the dotnet_source plugin, NuGet package integration, and DLL caching for performance.
PowerShell is built on .NET, giving you access to:
UnifyWeaver’s dotnet_source plugin lets you embed C# code directly in Prolog predicates, generating PowerShell scripts that compile and execute .NET code at runtime.
PowerShell’s Add-Type compiles C# code at runtime:
Add-Type @'
using System;
public class StringHelper {
public static string Reverse(string input) {
char[] chars = input.ToCharArray();
Array.Reverse(chars);
return new string(chars);
}
}
'@
# Use the compiled class
[StringHelper]::Reverse("Hello World")
# Output: dlroW olleH
The dotnet_source plugin generates this pattern from Prolog:
:- use_module(library(dotnet_source)).
% Define C# code
CSharpCode = '
using System;
namespace UnifyWeaver.Generated.StringReverser {
public class StringReverserHandler {
public string ProcessStringReverser(string input) {
if (string.IsNullOrEmpty(input)) {
return input;
}
char[] charArray = input.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
}
}
',
% Compile to PowerShell
Config = [csharp_inline(CSharpCode)],
dotnet_source:compile_source(string_reverser/2, Config, [], PowerShellCode).
The plugin generates a complete PowerShell script:
# string_reverser - .NET inline compilation source
# Generated by UnifyWeaver
function string_reverser {
param([Parameter(ValueFromPipeline=$true)]$InputData)
begin {
# Compile C# code (first run only)
$csharpCode = @'
using System;
namespace UnifyWeaver.Generated.StringReverser {
public class StringReverserHandler {
public string ProcessStringReverser(string input) {
// ... implementation
}
}
}
'@
Add-Type -TypeDefinition $csharpCode -Language CSharp
# Create instance
$handler = New-Object UnifyWeaver.Generated.StringReverser.StringReverserHandler
}
process {
if ($InputData) {
$handler.ProcessStringReverser($InputData)
}
}
}
# Auto-execute when run directly
if ($MyInvocation.InvocationName -ne '.') {
string_reverser @args
}
For more complex scenarios requiring NuGet packages, the plugin can generate external compilation:
Config = [
csharp_inline(CSharpCode),
compile_mode(external), % Use dotnet build
references(['System.Text.Json']) % NuGet packages
],
dotnet_source:compile_source(json_validator/2, Config, [], PowerShellCode).
The generated script:
.csproj with NuGet referencesdotnet restore and dotnet buildfunction json_validator {
param([Parameter(ValueFromPipeline=$true)]$InputData)
begin {
$projectName = "json_validator_Handler"
$tempBase = if ($env:TEMP) { $env:TEMP } else { "/tmp" }
$projectDir = Join-Path $tempBase "unifyweaver_dotnet_build/$projectName"
# Write .csproj
$csprojContent = @'
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.0" />
</ItemGroup>
</Project>
'@
Set-Content -Path "$projectDir/$projectName.csproj" -Value $csprojContent
# Write C# code
$csFile = "$projectDir/Handler.cs"
Set-Content -Path $csFile -Value $csharpCode
# Build
dotnet restore $projectDir
dotnet build $projectDir --no-restore
# Load DLL
$dll = "$projectDir/bin/Debug/net8.0/$projectName.dll"
Add-Type -Path $dll
# Create instance
$handler = New-Object UnifyWeaver.Generated.JsonValidator.Handler
}
process {
$handler.Process($InputData)
}
end {
# Cleanup
Remove-Item -Recurse -Force $projectDir
}
}
Without caching, every invocation recompiles C#. The pre_compile option enables caching:
| Mode | First Run | Subsequent Runs |
|---|---|---|
| No cache | ~386ms | ~386ms |
| With cache | ~386ms | ~2.8ms |
That’s a 138x speedup for cached runs!
Config = [
csharp_inline(CSharpCode),
pre_compile(true) % Enable DLL caching
],
dotnet_source:compile_source(string_reverser/2, Config, [], PowerShellCode).
$env:TEMP/unifyweaver_dotnet_cache/{hash}.dllbegin {
$codeHash = Get-StringHash $csharpCode
$cachePath = "$env:TEMP/unifyweaver_dotnet_cache"
$cachedDll = "$cachePath/$codeHash.dll"
if (Test-Path $cachedDll) {
Write-Verbose "Loading from cache: $cachedDll"
Add-Type -Path $cachedDll
} else {
Write-Verbose "Compiling and caching..."
# ... compile ...
Copy-Item $compiledDll $cachedDll
Add-Type -Path $cachedDll
}
}
CSharpCode = '
using System;
using System.Text.Json;
namespace UnifyWeaver.Generated.JsonParser {
public class Handler {
public string Process(string jsonString, string path) {
using JsonDocument doc = JsonDocument.Parse(jsonString);
JsonElement element = doc.RootElement;
foreach (string part in path.Split(".")) {
element = element.GetProperty(part);
}
return element.GetRawText();
}
}
}
',
Config = [csharp_inline(CSharpCode)],
dotnet_source:compile_source(json_get/3, Config, [], Code).
Usage:
$json = '{"user": {"name": "Alice", "age": 30}}'
json_get $json "user.name"
# Output: "Alice"
CSharpCode = '
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace UnifyWeaver.Generated.CsvReader {
public class Handler {
public IEnumerable<string[]> Process(string filePath) {
return File.ReadAllLines(filePath)
.Select(line => line.Split(","));
}
}
}
',
Config = [csharp_inline(CSharpCode)],
dotnet_source:compile_source(csv_rows/2, Config, [], Code).
CSharpCode = '
using System;
using System.Collections.Generic;
using LiteDB;
namespace UnifyWeaver.Generated.LiteDbQuery {
public class Handler {
public IEnumerable<BsonDocument> Process(string dbPath, string collection) {
using var db = new LiteDatabase(dbPath);
var col = db.GetCollection<BsonDocument>(collection);
return col.FindAll().ToList();
}
}
}
',
Config = [
csharp_inline(CSharpCode),
compile_mode(external),
references(['LiteDB'])
],
dotnet_source:compile_source(litedb_query/3, Config, [], Code).
CSharpCode = '
using System;
using System.Xml.Linq;
using System.Linq;
using System.Collections.Generic;
namespace UnifyWeaver.Generated.XmlExtractor {
public class Handler {
public IEnumerable<string> Process(string xmlPath, string elementName) {
XDocument doc = XDocument.Load(xmlPath);
return doc.Descendants(elementName)
.Select(e => e.Value);
}
}
}
',
Config = [csharp_inline(CSharpCode)],
dotnet_source:compile_source(xml_extract/3, Config, [], Code).
| PowerShell | .NET Type |
|---|---|
[string] |
System.String |
[int] |
System.Int32 |
[double] |
System.Double |
[bool] |
System.Boolean |
[datetime] |
System.DateTime |
@(1,2,3) |
System.Object[] |
@{a=1} |
System.Collections.Hashtable |
.NET objects are automatically wrapped. Access properties directly:
# .NET List<string>
$list = [System.Collections.Generic.List[string]]::new()
$list.Add("item")
$list.Count # Property access
# Custom class from Add-Type
$handler = New-Object MyNamespace.MyClass
$handler.MyMethod("arg") # Method call
$handler.MyProperty # Property access
# Array to List
$array = @(1, 2, 3)
$list = [System.Collections.Generic.List[int]]$array
# List to Array
$array2 = $list.ToArray()
# Dictionary
$dict = [System.Collections.Generic.Dictionary[string,int]]::new()
$dict.Add("key", 42)
$dict["key"] # Access by key
UnifyWeaver uses consistent namespaces:
UnifyWeaver.Generated.<PredicateName>
└─ <PredicateName>Handler
└─ Process<PredicateName>(args...)
Example for json_validator/2:
namespace UnifyWeaver.Generated.JsonValidator {
public class JsonValidatorHandler {
public string ProcessJsonValidator(string input) {
// ...
}
}
}
This convention:
public string Process(string input) {
try {
// Risky operation
return DoSomething(input);
}
catch (ArgumentException ex) {
return $"ERROR: Invalid argument - {ex.Message}";
}
catch (Exception ex) {
return $"ERROR: {ex.GetType().Name} - {ex.Message}";
}
}
process {
$result = $handler.Process($InputData)
if ($result -match '^ERROR:') {
Write-Error $result
} else {
$result
}
}
Let’s build a complete pipeline that reads JSON, validates it, and stores it in LiteDB.
JsonValidatorCode = '
using System;
using System.Text.Json;
namespace UnifyWeaver.Generated.JsonValidator {
public class Handler {
public bool Process(string jsonString) {
try {
JsonDocument.Parse(jsonString);
return true;
}
catch {
return false;
}
}
}
}
'.
LiteDbWriterCode = '
using System;
using LiteDB;
namespace UnifyWeaver.Generated.LiteDbWriter {
public class Handler {
public int Process(string dbPath, string collection, string json) {
using var db = new LiteDatabase(dbPath);
var col = db.GetCollection<BsonDocument>(collection);
var doc = BsonMapper.Global.Deserialize<BsonDocument>(json);
col.Insert(doc);
return col.Count();
}
}
}
'.
:- use_module(library(dotnet_source)).
compile_pipeline :-
% JSON Validator (inline compile)
dotnet_source:compile_source(json_validate/2,
[csharp_inline(JsonValidatorCode), pre_compile(true)],
[], ValidatorCode),
% LiteDB Writer (external compile with NuGet)
dotnet_source:compile_source(litedb_write/4,
[csharp_inline(LiteDbWriterCode), compile_mode(external),
references(['LiteDB']), pre_compile(true)],
[], WriterCode),
% Write scripts
open('output/json_validate.ps1', write, S1),
write(S1, ValidatorCode), close(S1),
open('output/litedb_write.ps1', write, S2),
write(S2, WriterCode), close(S2).
# Load functions
. ./output/json_validate.ps1
. ./output/litedb_write.ps1
# Process JSON files
Get-ChildItem *.json | ForEach-Object {
$content = Get-Content $_ -Raw
if (json_validate $content) {
Write-Host "Valid: $_" -ForegroundColor Green
litedb_write "data.db" "records" $content
} else {
Write-Warning "Invalid JSON: $_"
}
}
In Chapter 5, you’ll learn about Windows automation:
| Option | Values | Description |
|---|---|---|
csharp_inline(Code) |
String | C# source code |
compile_mode(Mode) |
inline, external |
Compilation method |
pre_compile(Bool) |
true/false | Enable DLL caching |
references(List) |
Package names | NuGet dependencies |
| Namespace | Purpose |
|---|---|
System |
Core types |
System.IO |
File operations |
System.Text.Json |
JSON parsing |
System.Xml.Linq |
XML processing |
System.Collections.Generic |
Collections |
System.Linq |
LINQ queries |
System.Net.Http |
HTTP client |
System.Text.RegularExpressions |
Regex |
# Create instance
$obj = New-Object Namespace.Class
$obj = [Namespace.Class]::new()
# Static method
[System.IO.Path]::GetExtension("file.txt")
# Generic type
[System.Collections.Generic.List[string]]::new()
# Load assembly
Add-Type -AssemblyName System.Web
| Path | Description |
|---|---|
src/unifyweaver/sources/dotnet_source.pl |
Plugin implementation |
playbooks/powershell_inline_dotnet_playbook.md |
Detailed guide |
examples/powershell_dotnet_example.pl |
Working examples |
| ← Previous: Chapter 3: Cmdlet Generation | 📖 Book 12: PowerShell Target | Next: Chapter 5: Windows Automation → |