Initial commit: Wonderware / System Platform tools and reference

Five tools under one repo, all docs organized per DOCS-GUIDE.md:

- aalogcli: .NET 4.8 / x86 CliFx CLI for reading System Platform binary
  logs (*.aaLGX) for LLM debugging, built on aaOpenSource/aaLog. Commands:
  last, tail, range, unread, fields. Stable JSON envelope under --llm-json.
  Build template under lib/build/ for rebuilding aaLogReader.dll.

- aot: ArchestrA Object Toolkit 2014 v4.0 reference material. Dev guide
  (Markdown converted from CHM), API reference for the ArchestrA.Toolkit
  namespace, and the Monitor / Watchdog VS sample solutions.

- graccesscli: .NET 4.8 / x86 CliFx CLI that automates Galaxy
  configuration via the ArchestrA GRAccess COM interop. Includes session
  daemon, IPC protocol, and llm-json envelope contract.

- grdb: SQL/DDL exploration of the Galaxy Repository database. DDL
  captures, reusable queries, hierarchy / contained-name <-> tag-name
  translation notes.

- histdb: LLM-oriented reference for AVEVA Historian retrieval. INSQL
  linked-server, extension tables, every wwXxx time-domain extension,
  every retrieval mode, alarm/event SQL recipes, REST API. Distilled
  from the 243-page Historian Retrieval Guide.

Root contains:
- CLAUDE.md: thin index pointing into each tool's README.
- DOCS-GUIDE.md: doctrine for organizing docs for LLM consumption.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-03 18:22:20 -04:00
commit 32f26272ae
411 changed files with 69973 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
aaLogReader.dll must be dropped into this folder before building.
See ../README.md "Provisioning aaLogReader.dll" for build instructions.
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 Andy Robinson (Phase 2 Automation)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+31
View File
@@ -0,0 +1,31 @@
# aaLogReader build template
Use these three files to rebuild `aaLogReader.dll` from upstream sources without depending on legacy MSBuild + `packages.config`. See [`../../README.md`](../../README.md) "Provisioning aaLogReader.dll" for the full step-by-step.
## Files
- [`aaLogReader.csproj`](aaLogReader.csproj) — SDK-style csproj targeting `net48`, references `Newtonsoft.Json 13.0.3` and `log4net 2.0.15`. Disables determinism (the upstream `AssemblyInfo.cs` uses a wildcarded `[assembly: AssemblyVersion("1.0.*")]`) and pulls in upstream source via relative `<Compile Include="..\aaLog\aaLogReader\..." />` globs. Two source files are excluded from those globs and replaced by the patched copies below.
- [`patched/LogRecord.cs`](patched/LogRecord.cs) — upstream `aaLogReader/Types/LogRecord.cs` with `using System.Runtime.CompilerServices;` added.
- [`patched/LogHeader.cs`](patched/LogHeader.cs) — same patch applied to `aaLogReader/Types/LogHeader.cs`.
## Why the patches
The upstream files reference `[CallerMemberName]` inside an `#if NET45_OR_GREATER` branch but never `using System.Runtime.CompilerServices;`. The original csproj only defined `NET45_OR_GREATER` for `TargetFrameworkVersion >= 4.5`; targeting net40 left the branch dead and the compiler never tripped. When SDK-style projects target net48, the compiler reaches that branch and fails because the attribute can't be resolved. The patched copies add the missing using directive and otherwise match upstream byte-for-byte.
## Layout the recipe expects
```
$env:TEMP\
aaLog\ (git clone https://github.com/aaOpenSource/aaLog.git)
aaLogReader\
aaLgxReader.cs
aaLogReader.cs
Enum\, Helpers\, Properties\, Struct\, Types\
aaLogReader-build\
aaLogReader.csproj (copied from this folder)
patched\
LogRecord.cs (copied from this folder)
LogHeader.cs (copied from this folder)
```
`dotnet build -c Release` from `aaLogReader-build/` produces `bin/Release/net48/aaLogReader.dll`. Copy that into `aalogcli/lib/aaLogReader.dll`.
+38
View File
@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<RootNamespace>aaLogReader</RootNamespace>
<AssemblyName>aaLogReader</AssemblyName>
<LangVersion>latest</LangVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<Deterministic>false</Deterministic>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.ComponentModel.DataAnnotations" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\aaLog\aaLogReader\aaLgxReader.cs" />
<Compile Include="..\aaLog\aaLogReader\aaLogReader.cs" />
<Compile Include="..\aaLog\aaLogReader\Enum\*.cs" />
<Compile Include="..\aaLog\aaLogReader\Helpers\*.cs" />
<Compile Include="..\aaLog\aaLogReader\Properties\AssemblyInfo.cs" />
<Compile Include="..\aaLog\aaLogReader\Struct\*.cs" />
<Compile Include="..\aaLog\aaLogReader\Types\ILogHeader.cs" />
<Compile Include="..\aaLog\aaLogReader\Types\ILogRecord.cs" />
<Compile Include="..\aaLog\aaLogReader\Types\aaLogReaderException.cs" />
<Compile Include="patched\LogRecord.cs" />
<Compile Include="patched\LogHeader.cs" />
</ItemGroup>
</Project>
+259
View File
@@ -0,0 +1,259 @@
using System;
using Newtonsoft.Json;
using System.Text;
using System.Runtime.CompilerServices;
namespace aaLogReader
{
public class LogHeader : ILogHeader
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public string LogFilePath { get; set; }
public ulong StartMsgNumber { get; set; }
public ulong MsgCount { get; set; }
public ulong EndMsgNumber
{
get
{
return (ulong)(checked(this.StartMsgNumber + this.MsgCount) - 1);
}
}
private ulong _startFileTime;
private DateTimeOffset _startDateTime;
public ulong StartFileTime
{
get { return _startFileTime; }
set
{
_startFileTime = value;
_startDateTime = DateTimeOffset.FromFileTime((long)value);
}
}
public DateTimeOffset StartDateTime
{
get { return _startDateTime; }
}
[JsonIgnore]
public DateTime StartDateTimeLocal
{
get { return _startDateTime.LocalDateTime; }
}
[JsonIgnore]
public DateTime StartDateTimeUtc
{
get { return _startDateTime.UtcDateTime; }
}
private ulong _endFileTime;
private DateTimeOffset _endDateTime;
public ulong EndFileTime
{
get { return _endFileTime; }
set
{
_endFileTime = value;
_endDateTime = DateTimeOffset.FromFileTime((long)value);
}
}
public DateTimeOffset EndDateTime
{
get { return _endDateTime; }
}
[JsonIgnore]
public DateTime EndDateTimeLocal
{
get { return _endDateTime.LocalDateTime; }
}
[JsonIgnore]
public DateTime EndDateTimeUtc
{
get { return _endDateTime.UtcDateTime; }
}
public int OffsetFirstRecord { get; set; }
public int OffsetLastRecord { get; set; }
public string ComputerName { get; set; }
public string Session { get; set; }
public string PrevFileName { get; set; }
public string HostFQDN { get; set; }
[JsonIgnore]
public ReturnCodeStruct ReturnCode { get; set; }
public string ToJSON()
{
return JsonConvert.SerializeObject(this);
}
/// <summary>
/// Return the log header data in the form of a Key-Value Pair
/// </summary>
/// <param name="format">Full or Minimal</param>
/// <returns></returns>
public string ToKVP()
{
string returnValue;
StringBuilder localSB = new StringBuilder();
try
{
localSB.AppendFormat("MsgStartingNumber=\"{0}\"", this.StartMsgNumber.ToString("yyyy-MM-dd HH:mm:ss.fff"));
localSB.AppendFormat(", MsgCount=\"{0}\"", this.MsgCount);
localSB.AppendFormat(", MsgLastNumber=\"{0}\"", this.EndMsgNumber);
localSB.AppendFormat(", StartDateTime=\"{0}\"", this.StartDateTime);
localSB.AppendFormat(", EndDateTime=\"{0}\"", this.EndDateTime);
localSB.AppendFormat(", OffsetFirstRecord=\"{0}\"", this.OffsetFirstRecord);
localSB.AppendFormat(", OffsetLastRecord=\"{0}\"", this.OffsetLastRecord);
localSB.AppendFormat(", ComputerName=\"{0}\"", this.ComputerName);
localSB.AppendFormat(", Session=\"{0}\"", this.Session);
localSB.AppendFormat(", PrevFileName=\"{0}\"", this.PrevFileName);
localSB.AppendFormat(", HostFQDN=\"{0}\"", this.HostFQDN);
returnValue = localSB.ToString();
}
catch (Exception ex)
{
LogException(ex);
returnValue = "";
}
return returnValue;
}
/// <summary>
/// Get a header for a series of log records with a delimiter
/// </summary>
/// <param name="Delimiter"></param>
/// <param name="format"></param>
/// <returns></returns>
private string localHeader(char Delimiter = ',')
{
string returnValue;
StringBuilder localSB = new StringBuilder();
try
{
localSB.Append("LogFilePath");
localSB.Append(Delimiter + "MsgStartingNumber");
localSB.Append(Delimiter + "MsgCount");
localSB.Append(Delimiter + "MsgLastNumber");
localSB.Append(Delimiter + "StartDateTime");
localSB.Append(Delimiter + "StartFileTime");
localSB.Append(Delimiter + "EndDateTime");
localSB.Append(Delimiter + "EndFileTime");
localSB.Append(Delimiter + "OffsetFirstRecord");
localSB.Append(Delimiter + "OffsetLastRecord");
localSB.Append(Delimiter + "ComputerName");
localSB.Append(Delimiter + "Session");
localSB.Append(Delimiter + "PrevFileName");
localSB.Append(Delimiter + "HostFQDN");
returnValue = localSB.ToString();
}
catch (Exception ex)
{
LogException(ex);
returnValue = "";
}
return returnValue;
}
public static string Header(char Delimiter = ',')
{
LogHeader lh = new LogHeader();
return lh.localHeader(Delimiter);
}
public static string HeaderCSV()
{
return LogHeader.Header(',');
}
public static string HeaderTSV()
{
return LogHeader.Header('\t');
}
/// <summary>
/// Get the lastRecordRead in the form of a delimited string
/// </summary>
/// <param name="Delimiter">Delimiter to Use</param>
/// <param name="format">Full or Minimal</param>
/// <returns></returns>
public string ToDelimitedString(char Delimiter = ',')
{
string returnValue;
StringBuilder localSB = new StringBuilder();
try
{
localSB.Append("\"" + this.LogFilePath + "\"");
localSB.Append(Delimiter + this.StartMsgNumber.ToString());
localSB.Append(Delimiter + this.MsgCount.ToString());
localSB.Append(Delimiter + this.EndMsgNumber.ToString());
localSB.Append(Delimiter + "\"" + this.StartDateTime.ToString("yyyy-MM-dd HH:mm:ss.fff") + "\"");
localSB.Append(Delimiter + this.StartFileTime.ToString());
localSB.Append(Delimiter + "\"" + this.EndDateTime.ToString("yyyy-MM-dd HH:mm:ss.fff") + "\"");
localSB.Append(Delimiter + this.EndFileTime.ToString());
localSB.Append(Delimiter + this.OffsetFirstRecord.ToString());
localSB.Append(Delimiter + this.OffsetLastRecord.ToString());
localSB.Append(Delimiter + "\"" + this.ComputerName + "\"");
localSB.Append(Delimiter + this.Session);
localSB.Append(Delimiter + "\"" + this.PrevFileName + "\"");
localSB.Append(Delimiter + "\"" + this.HostFQDN + "\"");
returnValue = localSB.ToString();
}
catch (Exception ex)
{
LogException(ex);
returnValue = "";
}
return returnValue;
}
public string ToCSV()
{
return this.ToDelimitedString(',');
}
public string ToTSV()
{
return this.ToDelimitedString('\t');
}
#if NET45_OR_GREATER
private void LogException(Exception ex, [CallerMemberName]string methodName = "")
{
#else
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
private void LogException(Exception ex)
{
string methodName = new System.Diagnostics.StackFrame(1, false).GetMethod().Name;
#endif
log.Error(string.Format("{0}: {1} - {2}", methodName, ex.GetType().Name, ex.Message), ex);
}
}
}
+278
View File
@@ -0,0 +1,278 @@
using System;
using Newtonsoft.Json;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.Runtime.CompilerServices;
namespace aaLogReader
{
/// <summary>
/// A standard log record
/// </summary>
public class LogRecord : ILogRecord
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
// Default constructor
public LogRecord()
{
this.ReturnCode.Status = false;
this.ReturnCode.Message = "";
}
[JsonIgnore]
public int RecordLength { get; set; }
[JsonIgnore]
public int OffsetToPrevRecord { get; set; }
[JsonIgnore]
public int OffsetToNextRecord { get; set; }
[Key]
public ulong MessageNumber { get; set; }
public uint ProcessID { get; set; }
public uint ThreadID { get; set; }
private ulong _eventFileTime;
private DateTimeOffset _eventDateTime;
public ulong EventFileTime
{
get { return _eventFileTime; }
set
{
_eventFileTime = value;
_eventDateTime = DateTimeOffset.FromFileTime((long)value);
}
}
// TODO: Add UTC Offset for Exact Timestamp
// public int EventUTCOffset;
public DateTimeOffset EventDateTime
{
get { return _eventDateTime; }
}
[JsonIgnore]
public DateTime EventDateTimeLocal
{
get { return _eventDateTime.LocalDateTime; }
}
[JsonIgnore]
public DateTime EventDateTimeUtc
{
get { return _eventDateTime.UtcDateTime; }
}
[JsonIgnore]
public DateTime EventDate
{
get
{
return this.EventDateTime.Date;
}
}
[JsonIgnore]
public string EventTime
{
get { return this.EventDateTime.ToString("hh:mm:ss.fff tt"); }
}
[JsonIgnore]
public int EventMillisec
{
get { return this.EventDateTime.Millisecond; }
}
public string LogFlag { get; set; }
public string Component { get; set; }
public string Message { get; set; }
public string ProcessName { get; set; }
public string SessionID { get; set; }
public string HostFQDN { get; set; }
[JsonIgnore]
public ReturnCodeStruct ReturnCode;
public string ToJSON()
{
return JsonConvert.SerializeObject(this);
}
/// <summary>
/// Return the log record in the form of a Key-Value Pair
/// </summary>
/// <param name="format">Full or Minimal</param>
/// <returns></returns>
public string ToKVP(ExportFormat format = ExportFormat.Full)
{
string returnValue;
StringBuilder localSB = new StringBuilder();
try
{
localSB.AppendFormat("Timestamp=\"{0}\"", this.EventDateTime.ToString("yyyy-MM-dd HH:mm:ss.fff"));
localSB.AppendFormat(", LogFlag=\"{0}\"", this.LogFlag);
localSB.AppendFormat(", Message=\"{0}\"", this.Message);
localSB.AppendFormat(", HostFQDN=\"{0}\"", this.HostFQDN);
if (format == ExportFormat.Full)
{
// Use all parameters if we want a full format
localSB.AppendFormat(", MessageNumber=\"{0}\"", this.MessageNumber);
localSB.AppendFormat(", ProcessID=\"{0}\"", this.ProcessID);
localSB.AppendFormat(", ThreadID=\"{0}\"", this.ThreadID);
localSB.AppendFormat(", Component=\"{0}\"", this.Component);
localSB.AppendFormat(", ProcessName=\"{0}\"", this.ProcessName);
localSB.AppendFormat(", SessionID=\"{0}\"", this.SessionID);
localSB.AppendFormat(", EventFileTime=\"{0}\"", this.EventFileTime);
}
returnValue = localSB.ToString();
}
catch (Exception ex)
{
LogException(ex);
returnValue = "";
}
return returnValue;
}
/// <summary>
/// Get a header for a series of log records with a delimiter
/// </summary>
/// <param name="Delimiter"></param>
/// <param name="format"></param>
/// <returns></returns>
private string localHeader(char Delimiter = ',', ExportFormat format = ExportFormat.Full)
{
string returnValue;
StringBuilder localSB = new StringBuilder();
try
{
localSB.Append("EventDateTime");
localSB.Append(Delimiter + "LogFlag");
localSB.Append(Delimiter + "Message");
localSB.Append(Delimiter + "HostFQDN");
if (format == ExportFormat.Full)
{
// Use all parameters if we want a full format
localSB.Append(Delimiter + "MessageNumber");
localSB.Append(Delimiter + "ProcessID");
localSB.Append(Delimiter + "ThreadID");
localSB.Append(Delimiter + "Component");
localSB.Append(Delimiter + "ProcessName");
localSB.Append(Delimiter + "SessionID");
localSB.Append(Delimiter + "EventFileTime");
}
returnValue = localSB.ToString();
}
catch (Exception ex)
{
LogException(ex);
returnValue = "";
}
return returnValue;
}
public static string Header(char Delimiter = ',', ExportFormat format = ExportFormat.Full)
{
LogRecord lr = new LogRecord();
return lr.localHeader(Delimiter, format);
}
public static string HeaderCSV(ExportFormat format = ExportFormat.Full)
{
return LogRecord.Header(',', format);
}
public static string HeaderTSV(ExportFormat format = ExportFormat.Full)
{
return LogRecord.Header('\t', format);
}
/// <summary>
/// Get the log record in the form of a delimited string
/// </summary>
/// <param name="Delimiter">Delimiter to Use</param>
/// <param name="format">Full or Minimal</param>
/// <returns></returns>
public string ToDelimitedString(char Delimiter = ',', ExportFormat format = ExportFormat.Full, DateTimeKind kind = DateTimeKind.Unspecified)
{
string returnValue;
StringBuilder localSB = new StringBuilder();
try
{
if (kind == DateTimeKind.Utc)
localSB.Append("\"" + this.EventDateTimeUtc.ToString("yyyy-MM-dd HH:mm:ss.fffZ") + "\"");
else
localSB.Append("\"" + this.EventDateTime.ToString("yyyy-MM-dd HH:mm:ss.fff") + "\"");
localSB.Append(Delimiter + this.LogFlag);
localSB.Append(Delimiter + "\"" + this.Message + "\"");
localSB.Append(Delimiter + this.HostFQDN);
if (format == ExportFormat.Full)
{
// Use all parameters if we want a full format
localSB.Append(Delimiter + this.MessageNumber.ToString());
localSB.Append(Delimiter + this.ProcessID.ToString());
localSB.Append(Delimiter + this.ThreadID.ToString());
localSB.Append(Delimiter + "\"" + this.Component + "\"");
localSB.Append(Delimiter + "\"" + this.ProcessName + "\"");
localSB.Append(Delimiter + this.SessionID);
localSB.Append(Delimiter + this.EventFileTime.ToString());
}
returnValue = localSB.ToString();
}
catch (Exception ex)
{
LogException(ex);
returnValue = "";
}
return returnValue;
}
public string ToCSV(ExportFormat format = ExportFormat.Full)
{
return this.ToDelimitedString(',', format);
}
public string ToTSV(ExportFormat format = ExportFormat.Full)
{
return this.ToDelimitedString('\t', format);
}
#if NET45_OR_GREATER
private void LogException(Exception ex, [CallerMemberName]string methodName = "")
{
#else
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
private void LogException(Exception ex)
{
string methodName = new System.Diagnostics.StackFrame(1, false).GetMethod().Name;
#endif
log.Error(string.Format("{0}: {1} - {2}", methodName, ex.GetType().Name, ex.Message), ex);
}
}
}