graccesscli: correct script-edit docs to TN-537 truth (writes DO persist)
I was wrong. AVEVA Tech Note 537 ("Creating an Application Object Script
Using GRAccess", April 2008) documents the supported pattern:
ConfigurableAttributes[<script>.<field>].SetValue(MxValue) inside a
CheckOut/Save/CheckIn cycle. graccesscli's existing
FindAttributeForMutation already follows this — writes to MxCategoryPackageOnly_Lockable
script-text fields persist correctly.
The earlier "writeback gap" diagnosis was a phantom caused by a reader-side
issue. `object attribute value get` against a script body returns
"Supported: False / Attribute value is not exposed" because
MxValueDetails uses a case-sensitive `ReadProperty(attr, "Value")` lookup
plus an accessor probe (GetBoolean -> GetInteger -> GetFloat -> GetDouble
-> GetString) that can fall through silently for some MxValue shapes. The
COM-side property is exposed as `value` (lowercase), readable as
`attr.value.GetString()` -- which the live probe at
`analysis/ide-edit-investigation/probe_setvalue/` does and confirms the
post-write content matches the marker exactly.
Live verification on $TestMachine.UpdateTestChangingInt.DeclarationsText
and $DelmiaReceiver.ProcessRecipe.{ExecuteText,DeclarationsText}:
=== verdict ===
marker landed on same-proxy ConfigurableAttributes: True
marker landed on same-proxy Attributes : True
marker landed on fresh-proxy ConfigurableAttributes: True
marker landed on fresh-proxy Attributes : True
The probe also confirmed that two earlier graccesscli `object scripts set`
invocations (which I had wrongly believed failed) had persisted -- the
marker text I wrote previously was still on disk in
ProcessRecipe.{ExecuteText,DeclarationsText} when read directly via
attr.value.GetString(). The probe restored both fields to their original
values.
This commit:
- Updates the misleading [Command(...)] / [CommandOption(...)]
descriptions in GRAccessSurfaceCommands.cs back to honest versions
citing TN-537.
- Restores the --file-using examples for `object scripts set` and
`object scripts create` across script-editing.md, llm-integration.md,
usage.md, and zb-testmachine.md.
- Removes the test that asserted the (wrong) EnsureMutableViaSetValue
guard. Re-aims ScriptCommandDescriptions_… at the corrected wording.
- Removes two leftover EnsureMutableViaSetValue calls in the trigger-period
/ trigger-type write paths (both targeted MxCategoryWriteable_C_Lockable
attributes; would never have fired even if the helper still existed).
- Adds analysis/ide-edit-investigation/REPORT.md (replacing the earlier
wrong report) plus the probe sources under probe_setvalue/.
The MxValueDetails reader gap (case-sensitive ReadProperty + accessor
probe) is a real follow-up: `object attribute value get` should
case-insensitively read `value` and try GetString first when the
underlying MxValue.DataType is MxString. Out of scope here -- that's a
separate, smaller fix.
Test count delta: 67 -> 66 (-2 wrong tests, +1 corrected description test).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# How the IDE edits scripts — investigation findings (corrected)
|
||||
|
||||
**Status:** my original report (and the safety-check + docs note that grew out of it) was **wrong**. AVEVA Tech Note 537 ("Creating an Application Object Script Using GRAccess", April 2008) documents the supported pattern, and a live round-trip probe confirmed it works on this Galaxy. graccesscli's existing `FindAttributeForMutation` already follows this pattern correctly; the apparent "writeback gap" was a reader-side gap in `MxValueDetails`.
|
||||
|
||||
This document supersedes the earlier "package-only no-op" claim. The reverts on `master`: commits `4e242ca` (safety check) and `e4e5425` (docs note) plus the corrective commit on top.
|
||||
|
||||
## What's actually true
|
||||
|
||||
Per **AVEVA Tech Note 537** (page 6-7):
|
||||
|
||||
> First we add a new script into the list of scripts. Then we save the object. Saving the object generates all the attributes required to configure the script the conventional way. […] Configurable attributes of the ScriptExtension primitive: ExecuteText, AliasReferences, Aliases, TriggerType, DataChangeDeadband, Expression, DeclarationsText, StartupText, ShutdownText, OnScanText, OffScanText, ExecutionError.Alarmed, TriggerPeriod, ScriptExecutionGroup, ScriptOrder, RunsAsync, State.Historized, ExecuteTimeout.Limit, TriggerOnQualityChange.
|
||||
|
||||
The supported edit sequence is:
|
||||
|
||||
```csharp
|
||||
// 1. Acquire the object.
|
||||
var galaxy = galaxies[GalaxyName];
|
||||
galaxy.Login(user, pwd);
|
||||
var obj = galaxy.QueryObjectsByName(EgObjectIsTemplateOrInstance.gObjectIsTemplate, ref names)[1];
|
||||
|
||||
// 2. Mutation lifecycle.
|
||||
obj.CheckOut();
|
||||
|
||||
// 3. Add the primitive (only when creating a new script).
|
||||
obj.AddExtensionPrimitive("ScriptExtension", scriptName, true);
|
||||
obj.Save(); // generates the script's child attributes
|
||||
|
||||
// 4. Write text via ConfigurableAttributes — NOT Attributes.
|
||||
IAttribute body = obj.ConfigurableAttributes[scriptName + ".ExecuteText"];
|
||||
var v = new MxValueClass();
|
||||
v.PutString("// new script body...");
|
||||
body.SetValue(v);
|
||||
// (repeat for DeclarationsText, TriggerType, Expression, etc.)
|
||||
|
||||
// 5. Persist + release.
|
||||
obj.Save();
|
||||
obj.CheckIn("");
|
||||
```
|
||||
|
||||
graccesscli's `FindAttributeForMutation` (line 988-995) already prefers `ConfigurableAttributes` first and falls back to `Attributes`. `ObjectScriptSet` in the dispatcher writes via that helper. So `object scripts set --field <name> --file <path>` follows the TN-537 pattern.
|
||||
|
||||
## Live verification
|
||||
|
||||
The probe at `analysis/ide-edit-investigation/probe_setvalue/` exercises the TN-537 sequence directly against `$TestMachine.UpdateTestChangingInt.DeclarationsText` and against `$DelmiaReceiver.ProcessRecipe.{ExecuteText,DeclarationsText}` on the local ZB galaxy. Result of the four-way verify after a marker write:
|
||||
|
||||
```
|
||||
=== verdict ===
|
||||
marker landed on same-proxy ConfigurableAttributes: True
|
||||
marker landed on same-proxy Attributes : True
|
||||
marker landed on fresh-proxy ConfigurableAttributes: True
|
||||
marker landed on fresh-proxy Attributes : True
|
||||
```
|
||||
|
||||
Same probe also confirmed two earlier `graccesscli object scripts set` invocations had persisted (the `// roundtrip-test marker (graccesscli scripts set --field DeclarationsText)` text was still in `ProcessRecipe.DeclarationsText` when re-read directly via `attr.value.GetString()`).
|
||||
|
||||
## Why my earlier diagnosis went wrong
|
||||
|
||||
My earlier round-trip test wrote with graccesscli's `object scripts set`, then read back with `object attribute value get` — and the read returned `Supported: False, Value: None, Unavailable: "Attribute value is not exposed by this GRAccess attribute."` I read this as "the write didn't persist." It actually means **the reader couldn't surface the value**, while the write *had* persisted.
|
||||
|
||||
The reader gap is in `GRAccessCommandDispatcher.MxValueDetails` (line 1382-1417) plus `AttributeValueDetails` (line 1340-1380):
|
||||
|
||||
```csharp
|
||||
var direct = MxValueDetails(ReadProperty(attr, "Value"));
|
||||
```
|
||||
|
||||
`ReadProperty(attr, "Value")` uses `Type.InvokeMember(name, BindingFlags.GetProperty, ...)` which is **case-sensitive** by default. The COM-side property is exposed as `value` (lowercase) on `IAttribute` — what the probe successfully reads via `attr.value`. The capital-V `"Value"` lookup may either return `null` or hit a different sibling, and `MxValueDetails(null)` short-circuits to "not exposed." Even when `ReadProperty` does return the MxValue object, `MxValueDetails`'s scalar accessor probe (`GetBoolean` → `GetInteger` → `GetFloat` → `GetDouble` → `GetString`) can fall through silently for some types.
|
||||
|
||||
This is a real gap in `attribute value get` that should be fixed separately — but it's a *reader* problem, not a write-side issue. The script body content does land on disk, and `object scripts get --llm-json` (which uses the package-export fallback) reflects it correctly.
|
||||
|
||||
## What changed in this correction
|
||||
|
||||
- **Reverted** `bd95ace` (the safety check `EnsureMutableViaSetValue`) and `c12fbc5` (the wrong docs note about a "package-only writeback boundary").
|
||||
- **Removed** two leftover `EnsureMutableViaSetValue` call sites the user had added in `87c0124` for the trigger-period / trigger-type write paths (both call sites also targeted `MxCategoryWriteable_C_Lockable` attributes, so they would never have fired even if the helper still existed — removing keeps the dispatcher consistent with the truth).
|
||||
- **Updated** the misleading `[Command(...)]` / `[CommandOption(...)]` descriptions in `GRAccessSurfaceCommands.cs` back to honest versions citing TN-537.
|
||||
- **Restored** the `--file`-using examples for `object scripts set` and `object scripts create` that `87c0124` removed across `script-editing.md`, `llm-integration.md`, `usage.md`, `zb-testmachine.md`.
|
||||
- **Updated** test assertions accordingly: removed `DispatcherScriptSettings_GuardsPackageOnlyNoOpsBeforeSetValue` (asserted the wrong helper) and re-aimed `ScriptCommandDescriptions_…` at the corrected wording.
|
||||
- **Rewrote** `analysis/ide-edit-investigation/REPORT.md` (this file) to document the truth.
|
||||
|
||||
## Open follow-ups (≤2)
|
||||
|
||||
1. **`MxValueDetails` reader gap.** `object attribute value get` against ScriptExtension text fields and similar `IAttribute` shapes returns `Supported: False` despite the value being set. Likely fix: case-insensitive `ReadProperty` lookup *and* try `GetString` first when the underlying `MxValue.DataType` is `MxString`. Out of scope for this corrective commit.
|
||||
2. **The decompiled `IConfigurationAccess2` / `XxGalaxyPackageServer` analysis** under `cac/`, `appcfg/`, `scriptpkg/` is still useful as an *internal IDE map* — but it's not the *required* CLI write path, just an alternative one the IDE happens to use. Keep the artifacts; they document the IDE-internal channel honestly.
|
||||
+1114
File diff suppressed because it is too large
Load Diff
+1072
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net48</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Nullable>disable</Nullable>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<Platforms>x86</Platforms>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
<RootNamespace>Probe</RootNamespace>
|
||||
<AssemblyName>Probe</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="ArchestrA.GRAccess">
|
||||
<HintPath>C:\Users\dohertj2\Desktop\wwtools\graccesscli\lib\ArchestrA.GRAccess.dll</HintPath>
|
||||
<Private>true</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using ArchestrA.GRAccess;
|
||||
|
||||
class Program
|
||||
{
|
||||
static int Main(string[] args)
|
||||
{
|
||||
// Probe: write to $TestMachine.UpdateTestChangingInt.DeclarationsText (a
|
||||
// package-only ScriptExtension text field) via the TN-537 pattern using
|
||||
// ConfigurableAttributes. Read back through TWO paths to discriminate
|
||||
// between (a) write-side no-op vs (b) read-side staleness:
|
||||
// 1. Same IgObject proxy via obj.ConfigurableAttributes (in-memory edit view)
|
||||
// 2. Fresh galaxy.QueryObjectsByName proxy via .Attributes (runtime view)
|
||||
//
|
||||
// Restores DeclarationsText to empty when done.
|
||||
|
||||
const string GalaxyName = "ZB";
|
||||
const string Tagname = "$TestMachine";
|
||||
const string ScriptName = "UpdateTestChangingInt";
|
||||
const string FieldName = "DeclarationsText";
|
||||
const string Marker = "// graccesscli probe marker — TN537 ConfigurableAttributes path\n";
|
||||
|
||||
var user = Environment.GetEnvironmentVariable("MX_TEST_USER") ?? "";
|
||||
var pass = Environment.GetEnvironmentVariable("MX_TEST_PASSWORD") ?? "";
|
||||
var node = Environment.MachineName;
|
||||
|
||||
Console.WriteLine($"[probe] connecting to {node} galaxy={GalaxyName} as {user}");
|
||||
var gr = new GRAccessAppClass();
|
||||
var galaxies = gr.QueryGalaxies(node);
|
||||
var galaxy = galaxies[GalaxyName];
|
||||
if (string.IsNullOrEmpty(user))
|
||||
galaxy.Login();
|
||||
else
|
||||
galaxy.Login(user, pass);
|
||||
|
||||
var attrPath = ScriptName + "." + FieldName;
|
||||
|
||||
// Acquire the target template object.
|
||||
var names = new string[] { Tagname };
|
||||
var initial = QueryOne(galaxy, names);
|
||||
|
||||
// Read current value via ConfigurableAttributes (configuration view).
|
||||
var beforeCfg = TryReadString(initial.ConfigurableAttributes, attrPath, "before-cfg");
|
||||
var beforeRt = TryReadString(initial.Attributes, attrPath, "before-rt");
|
||||
Console.WriteLine($"[probe] BEFORE cfg={Quote(beforeCfg)} rt={Quote(beforeRt)}");
|
||||
|
||||
// Mutate via the canonical TN-537 sequence on the same proxy.
|
||||
Console.WriteLine("[probe] CheckOut...");
|
||||
initial.CheckOut();
|
||||
ThrowIfFailed(initial.CommandResult, "CheckOut");
|
||||
|
||||
Console.WriteLine("[probe] ConfigurableAttributes[...].SetValue(MxValue)...");
|
||||
var cfgAttr = initial.ConfigurableAttributes[attrPath];
|
||||
var v = new MxValueClass();
|
||||
v.PutString(Marker);
|
||||
cfgAttr.SetValue(v);
|
||||
|
||||
Console.WriteLine("[probe] Save...");
|
||||
initial.Save();
|
||||
ThrowIfFailed(initial.CommandResult, "Save");
|
||||
|
||||
Console.WriteLine("[probe] CheckIn...");
|
||||
initial.CheckIn("graccesscli probe round-trip test");
|
||||
ThrowIfFailed(initial.CommandResult, "CheckIn");
|
||||
|
||||
// Now read back THREE ways:
|
||||
// A. Same proxy (initial), ConfigurableAttributes — possibly stale.
|
||||
// B. Same proxy (initial), Attributes — possibly stale.
|
||||
// C. Fresh proxy from a fresh QueryObjectsByName call.
|
||||
var afterCfgSame = TryReadString(initial.ConfigurableAttributes, attrPath, "after-cfg-same");
|
||||
var afterRtSame = TryReadString(initial.Attributes, attrPath, "after-rt-same");
|
||||
Console.WriteLine($"[probe] AFTER (same proxy) cfg={Quote(afterCfgSame)} rt={Quote(afterRtSame)}");
|
||||
|
||||
var refreshed = QueryOne(galaxy, names);
|
||||
var afterCfgFresh = TryReadString(refreshed.ConfigurableAttributes, attrPath, "after-cfg-fresh");
|
||||
var afterRtFresh = TryReadString(refreshed.Attributes, attrPath, "after-rt-fresh");
|
||||
Console.WriteLine($"[probe] AFTER (fresh proxy) cfg={Quote(afterCfgFresh)} rt={Quote(afterRtFresh)}");
|
||||
|
||||
// Restore: write the original value back (or empty if there was none).
|
||||
var restoreTo = beforeCfg ?? "";
|
||||
Console.WriteLine($"[probe] Restoring DeclarationsText to {Quote(restoreTo)}...");
|
||||
refreshed.CheckOut();
|
||||
ThrowIfFailed(refreshed.CommandResult, "CheckOut(restore)");
|
||||
var restoreAttr = refreshed.ConfigurableAttributes[attrPath];
|
||||
var rv = new MxValueClass();
|
||||
rv.PutString(restoreTo);
|
||||
restoreAttr.SetValue(rv);
|
||||
refreshed.Save();
|
||||
ThrowIfFailed(refreshed.CommandResult, "Save(restore)");
|
||||
refreshed.CheckIn("graccesscli probe restore");
|
||||
ThrowIfFailed(refreshed.CommandResult, "CheckIn(restore)");
|
||||
Console.WriteLine("[probe] Restored.");
|
||||
|
||||
// Verdict.
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("=== verdict ===");
|
||||
Console.WriteLine($" marker landed on same-proxy ConfigurableAttributes: {(afterCfgSame == Marker)}");
|
||||
Console.WriteLine($" marker landed on same-proxy Attributes : {(afterRtSame == Marker)}");
|
||||
Console.WriteLine($" marker landed on fresh-proxy ConfigurableAttributes: {(afterCfgFresh == Marker)}");
|
||||
Console.WriteLine($" marker landed on fresh-proxy Attributes : {(afterRtFresh == Marker)}");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static IgObject QueryOne(IGalaxy galaxy, string[] names)
|
||||
{
|
||||
var objs = galaxy.QueryObjectsByName(EgObjectIsTemplateOrInstance.gObjectIsTemplate, ref names);
|
||||
ThrowIfFailed(galaxy.CommandResult, "QueryObjectsByName");
|
||||
return objs[1];
|
||||
}
|
||||
|
||||
static string TryReadString(IAttributes attrs, string name, string label)
|
||||
{
|
||||
try
|
||||
{
|
||||
var attr = attrs[name];
|
||||
if (attr == null) return null;
|
||||
var val = attr.value;
|
||||
return val?.GetString();
|
||||
}
|
||||
catch (Exception ex) { Console.WriteLine($"[probe] {label} read threw: {ex.Message}"); return null; }
|
||||
}
|
||||
|
||||
static void ThrowIfFailed(ICommandResult r, string what)
|
||||
{
|
||||
if (r != null && !r.Successful)
|
||||
throw new InvalidOperationException($"{what} failed: ID={r.ID} Text='{r.Text}' Custom='{r.CustomMessage}'");
|
||||
}
|
||||
|
||||
static string Quote(string s) => s == null ? "<null>" : "\"" + s.Replace("\n", "\\n").Replace("\r", "\\r") + "\"";
|
||||
}
|
||||
+595
@@ -0,0 +1,595 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.SymbolStore;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Security;
|
||||
using ArchestrA.QuickScript;
|
||||
using ArchestrA.QuickScript.Digest;
|
||||
using ArchestrA.QuickScript.Emit;
|
||||
using ArchestrA.QuickScript.Model;
|
||||
using ArchestrA.QuickScript.Runtime;
|
||||
|
||||
[assembly: CompilationRelaxations(8)]
|
||||
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
|
||||
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
|
||||
[assembly: AssemblyFileVersion("5800.0038.7005.1")]
|
||||
[assembly: AssemblyTitle("ScriptPackage.Net Module")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("AVEVA Software, LLC")]
|
||||
[assembly: AssemblyProduct("")]
|
||||
[assembly: AssemblyCopyright("Copyright 2020 AVEVA Group plc and its subsidiaries. All rights reserved.")]
|
||||
[assembly: AssemblyTrademark("Refer to: https://sw.aveva.com/legal/trademarks")]
|
||||
[assembly: AssemblyDelaySign(false)]
|
||||
[assembly: AssemblyKeyFile("..\\..\\..\\SharedComponents\\Internal\\MagellanPublic\\Includes\\WWDotNETPrivateKey\\ww.snk")]
|
||||
[assembly: AssemblyKeyName("")]
|
||||
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
|
||||
[assembly: AssemblyVersion("2.0.0.0")]
|
||||
namespace ArchestrA.Scripting
|
||||
{
|
||||
[Guid("85963980-46DF-459d-8A96-2A2AF70F458F")]
|
||||
[ClassInterface(ClassInterfaceType.AutoDual)]
|
||||
public class ScriptPackage
|
||||
{
|
||||
private class NameHierarchy
|
||||
{
|
||||
public Hashtable Hashtable = new Hashtable();
|
||||
|
||||
public readonly string Name;
|
||||
|
||||
public int Index;
|
||||
|
||||
public NameHierarchy(string name)
|
||||
{
|
||||
Name = name;
|
||||
Index = -1;
|
||||
}
|
||||
|
||||
public NameHierarchy()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public void Add(string name, int index)
|
||||
{
|
||||
string text = null;
|
||||
string text2 = null;
|
||||
int num = name.IndexOf('.');
|
||||
if (num < 0)
|
||||
{
|
||||
text = name;
|
||||
}
|
||||
else
|
||||
{
|
||||
text = name.Substring(0, num);
|
||||
text2 = name.Substring(num + 1, name.Length - num - 1);
|
||||
}
|
||||
string key = text.ToLower();
|
||||
if (!Hashtable.ContainsKey(key))
|
||||
{
|
||||
Hashtable[key] = new NameHierarchy(text);
|
||||
}
|
||||
NameHierarchy nameHierarchy = (NameHierarchy)Hashtable[key];
|
||||
if (text2 != null)
|
||||
{
|
||||
nameHierarchy.Add(text2, index);
|
||||
}
|
||||
else
|
||||
{
|
||||
nameHierarchy.Index = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeResolveHandler
|
||||
{
|
||||
private Module Module;
|
||||
|
||||
private Hashtable Hashtable;
|
||||
|
||||
public TypeResolveHandler(Module module, Hashtable hashtable)
|
||||
{
|
||||
Module = module;
|
||||
Hashtable = hashtable;
|
||||
}
|
||||
|
||||
public Assembly ResolveEvent(object sender, ResolveEventArgs args)
|
||||
{
|
||||
((TypeBuilder)Hashtable[args.Name]).CreateType();
|
||||
return Module.Assembly;
|
||||
}
|
||||
}
|
||||
|
||||
private string BinFolder;
|
||||
|
||||
private bool DebugEnabled;
|
||||
|
||||
private Parser Parser;
|
||||
|
||||
private string TagName;
|
||||
|
||||
private string ScriptName;
|
||||
|
||||
private Hashtable ReferencedAssemblies;
|
||||
|
||||
private ModuleBuilder ModuleBuilder;
|
||||
|
||||
private TypeBuilder TypeBuilder;
|
||||
|
||||
private ILBuilder ILBuilder;
|
||||
|
||||
private int NestedTypeIndex;
|
||||
|
||||
private string ErrorMessage;
|
||||
|
||||
private int ErrorLine;
|
||||
|
||||
private int ErrorColumn;
|
||||
|
||||
private static readonly FieldInfo ScriptExchange;
|
||||
|
||||
private static readonly Type[] ExpessionParameterTypes;
|
||||
|
||||
private static readonly MethodInfo EnableDebugSupport;
|
||||
|
||||
private static string DebugFolder => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "QuickScriptDebug");
|
||||
|
||||
private static bool DebuggerInfoEnabled => Directory.Exists(DebugFolder);
|
||||
|
||||
public void Compile(string libraryPath, string repositoryPath, string tagName, string scriptName, string compilerAssemblyFile, string compilerTypeName, IntPtr outputAssemblyName, IntPtr aliases, IntPtr declarationsText, IntPtr startupText, IntPtr onScanText, IntPtr expression, IntPtr executeText, IntPtr offScanText, IntPtr shutdownText, IntPtr binary, IntPtr errorMessage, IntPtr errorLine, IntPtr errorColumn, IntPtr libaryDependencies, IntPtr externalReferences, IntPtr externalReferenceFlags, IntPtr aliasReferenceFlags)
|
||||
{
|
||||
IList list = null;
|
||||
byte[] array = null;
|
||||
ErrorMessage = "";
|
||||
BinFolder = Path.GetDirectoryName(Assembly.GetAssembly(GetType()).Location);
|
||||
Parser = new Parser();
|
||||
ILBuilder = new ILBuilder();
|
||||
ReferencedAssemblies = new Hashtable();
|
||||
list = new ArrayList();
|
||||
TagName = tagName;
|
||||
ScriptName = scriptName;
|
||||
DebugEnabled = DebuggerInfoEnabled;
|
||||
NestedTypeIndex = 0;
|
||||
int mxValueElementCount = GetMxValueElementCount(aliases);
|
||||
int i;
|
||||
for (i = 0; i < mxValueElementCount; i++)
|
||||
{
|
||||
ExternalReferences.AddAlias(GetMxValueElementString(aliases, i + 1));
|
||||
}
|
||||
try
|
||||
{
|
||||
string tempPath = Path.GetTempPath();
|
||||
AssemblyName assemblyName = new AssemblyName();
|
||||
assemblyName.Name = GetMxValueString(outputAssemblyName);
|
||||
string text = assemblyName.Name + ".dll";
|
||||
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
|
||||
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save, tempPath);
|
||||
if (DebugEnabled)
|
||||
{
|
||||
CustomAttributeBuilder customAttribute = new CustomAttributeBuilder(typeof(DebuggableAttribute).GetConstructor(new Type[2]
|
||||
{
|
||||
typeof(bool),
|
||||
typeof(bool)
|
||||
}), new object[2] { true, true });
|
||||
assemblyBuilder.SetCustomAttribute(customAttribute);
|
||||
}
|
||||
ModuleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, text, DebugEnabled);
|
||||
TypeBuilder = ModuleBuilder.DefineType("ArchestrA.Scripting.Script", TypeAttributes.Public, typeof(ScriptBase), new Type[0]);
|
||||
ILGenerator iLGenerator = TypeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, new Type[0]).GetILGenerator();
|
||||
ILBuilder.ILGenerator = iLGenerator;
|
||||
iLGenerator.Emit(OpCodes.Ldarg_0);
|
||||
iLGenerator.Emit(OpCodes.Call, typeof(ScriptBase).GetConstructor(new Type[0]));
|
||||
ILBuilder.DebugEnabled = false;
|
||||
ILBuilder.FieldScriptExchange = ScriptExchange;
|
||||
CompileDeclarations(GetMxValueString(declarationsText));
|
||||
ILBuilder.Emit(new Return());
|
||||
if (ErrorMessage.Length == 0 || ErrorMessage.StartsWith("Warning"))
|
||||
{
|
||||
CompileExpression(GetMxValueString(expression));
|
||||
CompileMethod("Startup", GetMxValueString(startupText));
|
||||
CompileMethod("OnScan", GetMxValueString(onScanText));
|
||||
CompileMethod("Execute", GetMxValueString(executeText));
|
||||
CompileMethod("OffScan", GetMxValueString(offScanText));
|
||||
CompileMethod("Shutdown", GetMxValueString(shutdownText));
|
||||
}
|
||||
if (ErrorMessage.Length == 0 || ErrorMessage.StartsWith("Warning"))
|
||||
{
|
||||
if (DebugEnabled)
|
||||
{
|
||||
Hashtable hashtable = new Hashtable();
|
||||
ResolveEventHandler value = new TypeResolveHandler(ModuleBuilder, hashtable).ResolveEvent;
|
||||
AppDomain.CurrentDomain.TypeResolve += value;
|
||||
NameHierarchy nameHierarchy = new NameHierarchy();
|
||||
int num = 0;
|
||||
foreach (ExternalReference item in ExternalReferences.List)
|
||||
{
|
||||
nameHierarchy.Add(item.ReferenceString.Split('[')[0], num++);
|
||||
}
|
||||
Build(nameHierarchy, TypeBuilder, hashtable);
|
||||
RecurseNestedTypes(TypeBuilder.CreateType());
|
||||
AppDomain.CurrentDomain.TypeResolve -= value;
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeBuilder.CreateType();
|
||||
}
|
||||
assemblyBuilder.Save(text);
|
||||
}
|
||||
foreach (Assembly key in ReferencedAssemblies.Keys)
|
||||
{
|
||||
list.Add(key.GetName().Name);
|
||||
}
|
||||
if (ErrorMessage.Length == 0)
|
||||
{
|
||||
string path = Path.Combine(tempPath, text);
|
||||
using (FileStream fileStream = File.OpenRead(path))
|
||||
{
|
||||
array = new byte[fileStream.Length];
|
||||
fileStream.Read(array, 0, (int)fileStream.Length);
|
||||
}
|
||||
if (DebugEnabled)
|
||||
{
|
||||
File.Exists(Path.ChangeExtension(path, "pdb"));
|
||||
}
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorMessage = "Internal QuickScript compiler error: " + ex.Message;
|
||||
}
|
||||
if (ErrorMessage.Length > 0)
|
||||
{
|
||||
PutMxValueString(errorMessage, ErrorMessage);
|
||||
PutMxValueInteger(errorLine, ErrorLine);
|
||||
PutMxValueInteger(errorColumn, ErrorColumn);
|
||||
return;
|
||||
}
|
||||
PutMxValueString(errorMessage, "");
|
||||
PutMxValueInteger(errorLine, 0);
|
||||
PutMxValueInteger(errorColumn, 0);
|
||||
PutMxValueBlob(binary, array, array.Length);
|
||||
i = 1;
|
||||
foreach (string item2 in list)
|
||||
{
|
||||
PutMxValueElementString(libaryDependencies, i++, item2);
|
||||
}
|
||||
i = 1;
|
||||
int index = 1;
|
||||
foreach (ExternalReference item3 in ExternalReferences.List)
|
||||
{
|
||||
if (item3.IsAlias)
|
||||
{
|
||||
PutMxValueElementInteger(aliasReferenceFlags, i++, item3.Flags);
|
||||
continue;
|
||||
}
|
||||
PutMxValueElementString(externalReferences, index, item3.ReferenceString);
|
||||
PutMxValueElementInteger(externalReferenceFlags, index++, item3.Flags);
|
||||
}
|
||||
}
|
||||
|
||||
private void RecurseNestedTypes(Type type)
|
||||
{
|
||||
Type[] nestedTypes = type.GetNestedTypes();
|
||||
foreach (Type type2 in nestedTypes)
|
||||
{
|
||||
RecurseNestedTypes(type2);
|
||||
}
|
||||
}
|
||||
|
||||
private void GetErrorsAndWarnings(string section)
|
||||
{
|
||||
if (ErrorMessage.Length > 0 && !ErrorMessage.StartsWith("Warning"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
IEnumerator enumerator = Parser.Errors.GetEnumerator();
|
||||
try
|
||||
{
|
||||
if (enumerator.MoveNext())
|
||||
{
|
||||
ErrorInfo errorInfo = (ErrorInfo)enumerator.Current;
|
||||
ErrorLine = errorInfo.Line;
|
||||
ErrorColumn = errorInfo.Column;
|
||||
ErrorMessage = $"Script {ScriptName} ({section}): {errorInfo.Message} (Line: {errorInfo.Line}, Col: {errorInfo.Column})";
|
||||
return;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
IDisposable disposable = enumerator as IDisposable;
|
||||
if (disposable != null)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
enumerator = Parser.Warnings.GetEnumerator();
|
||||
try
|
||||
{
|
||||
if (enumerator.MoveNext())
|
||||
{
|
||||
ErrorInfo errorInfo2 = (ErrorInfo)enumerator.Current;
|
||||
ErrorLine = errorInfo2.Line;
|
||||
ErrorColumn = errorInfo2.Column;
|
||||
ErrorMessage = $"Warning: Script {ScriptName} ({section}): {errorInfo2.Message} (Line: {errorInfo2.Line}, Col: {errorInfo2.Column})";
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
IDisposable disposable2 = enumerator as IDisposable;
|
||||
if (disposable2 != null)
|
||||
{
|
||||
disposable2.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CompileDeclarations(string declarationsText)
|
||||
{
|
||||
if (declarationsText.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ILBuilder.DeclarationsAreFields = true;
|
||||
Parser.DeclarationsAreFields = true;
|
||||
Parser.Parse(declarationsText);
|
||||
if (Parser.Tree is Expression)
|
||||
{
|
||||
CodeSpan codeSpan = (Parser.Tree as Expression).CodeSpan;
|
||||
codeSpan = new CodeSpan(codeSpan.End.Line, codeSpan.End.Column + 1, codeSpan.End.Line, codeSpan.End.Column + 1);
|
||||
Parser.Errors.Add(new ErrorInfo("Expected ';'", codeSpan.End.Line, codeSpan.End.Column, codeSpan.End.Line, codeSpan.End.Column));
|
||||
}
|
||||
GetErrorsAndWarnings("Declarations");
|
||||
if (Parser.Errors.Count != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (Statement item in Parser.Tree as IList)
|
||||
{
|
||||
Declaration declaration = item as Declaration;
|
||||
FieldBuilder value = TypeBuilder.DefineField(declaration.VariableName, declaration.Type, FieldAttributes.Public);
|
||||
ILBuilder.Variables[declaration.VariableName] = value;
|
||||
ILBuilder.Emit(declaration);
|
||||
}
|
||||
Parser.GetReferencedAssemblies(ReferencedAssemblies);
|
||||
}
|
||||
|
||||
private void CompileExpression(string expressionText)
|
||||
{
|
||||
if (expressionText.Length != 0)
|
||||
{
|
||||
ILBuilder.DeclarationsAreFields = false;
|
||||
Parser.DeclarationsAreFields = false;
|
||||
Parser.Parse(expressionText);
|
||||
if (Parser.Tree is Statements)
|
||||
{
|
||||
CodeSpan codeSpan = ((Parser.Tree as Statements)[0] as Statement).CodeSpan;
|
||||
Parser.Errors.Add(new ErrorInfo("Not expecting ';'", codeSpan.End.Line, codeSpan.End.Column, codeSpan.End.Line, codeSpan.End.Column));
|
||||
}
|
||||
GetErrorsAndWarnings("Expression");
|
||||
if (Parser.Errors.Count <= 0)
|
||||
{
|
||||
MethodAttributes attributes = MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig;
|
||||
MethodBuilder methodBuilder = TypeBuilder.DefineMethod("Expression", attributes, typeof(void), ExpessionParameterTypes);
|
||||
methodBuilder.DefineParameter(1, ParameterAttributes.Out, null);
|
||||
methodBuilder.DefineParameter(2, ParameterAttributes.Out, null);
|
||||
ILGenerator iLGenerator = methodBuilder.GetILGenerator();
|
||||
ILBuilder.ILGenerator = iLGenerator;
|
||||
ILBuilder.Variables.ClearLocals();
|
||||
ILBuilder.DebugEnabled = false;
|
||||
Expression expression = Parser.Tree as Expression;
|
||||
iLGenerator.Emit(OpCodes.Ldarg_1);
|
||||
ILBuilder.Emit(Coerce.ChangeType(expression, typeof(object)));
|
||||
iLGenerator.Emit(OpCodes.Stind_Ref);
|
||||
iLGenerator.Emit(OpCodes.Ldarg_2);
|
||||
ILBuilder.Emit(Digester.GetQuality(expression));
|
||||
iLGenerator.Emit(OpCodes.Conv_I4);
|
||||
iLGenerator.Emit(OpCodes.Stind_I4);
|
||||
ILBuilder.Emit(new Return());
|
||||
Parser.GetReferencedAssemblies(ReferencedAssemblies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CompileMethod(string methodName, string methodText)
|
||||
{
|
||||
if (methodText.Length == 0)
|
||||
{
|
||||
if (!(methodName == "Startup"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
methodText = ";";
|
||||
}
|
||||
ILBuilder.DeclarationsAreFields = false;
|
||||
Parser.DeclarationsAreFields = false;
|
||||
Parser.Parse(methodText);
|
||||
if (Parser.Tree is Expression)
|
||||
{
|
||||
CodeSpan codeSpan = (Parser.Tree as Expression).CodeSpan;
|
||||
codeSpan = new CodeSpan(codeSpan.End.Line, codeSpan.End.Column + 1, codeSpan.End.Line, codeSpan.End.Column + 1);
|
||||
Parser.Errors.Add(new ErrorInfo("Expected ';'", codeSpan.End.Line, codeSpan.End.Column, codeSpan.End.Line, codeSpan.End.Column));
|
||||
}
|
||||
GetErrorsAndWarnings(methodName);
|
||||
if (Parser.Errors.Count > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ISymbolDocumentWriter symbolDocumentWriter = null;
|
||||
if (DebugEnabled)
|
||||
{
|
||||
string text = DebugFolder + "\\" + TagName + "." + ScriptName + "." + methodName + ".qs";
|
||||
if (methodText.Length > 1)
|
||||
{
|
||||
using StreamWriter streamWriter = File.CreateText(text);
|
||||
streamWriter.Write(methodText);
|
||||
}
|
||||
symbolDocumentWriter = ModuleBuilder.DefineDocument(text, Guid.Empty, Guid.Empty, Guid.Empty);
|
||||
}
|
||||
Type[] parameterTypes = new Type[0];
|
||||
switch (methodName)
|
||||
{
|
||||
case "Startup":
|
||||
parameterTypes = new Type[1] { typeof(IScriptExchange) };
|
||||
break;
|
||||
case "Execute":
|
||||
parameterTypes = new Type[1] { typeof(ScriptTimer) };
|
||||
break;
|
||||
}
|
||||
MethodAttributes attributes = MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig;
|
||||
ILGenerator iLGenerator = TypeBuilder.DefineMethod(methodName, attributes, typeof(void), parameterTypes).GetILGenerator();
|
||||
ILBuilder.ILGenerator = iLGenerator;
|
||||
ILBuilder.Variables.ClearLocals();
|
||||
ILBuilder.SymbolDocumentWriter = symbolDocumentWriter;
|
||||
ILBuilder.DebugEnabled = false;
|
||||
ILBuilder.CheckForTimeout = methodName == "Execute" && !DebugEnabled;
|
||||
if (methodName == "Startup")
|
||||
{
|
||||
iLGenerator.Emit(OpCodes.Ldarg_0);
|
||||
iLGenerator.Emit(OpCodes.Ldarg_1);
|
||||
iLGenerator.Emit(OpCodes.Stfld, ScriptExchange);
|
||||
if (DebugEnabled)
|
||||
{
|
||||
Expression destination = new Field(new This(), typeof(ScriptBase).GetField("TagName"));
|
||||
Expression source = new ConstantString(TagName);
|
||||
ILBuilder.Emit(new Assignment(destination, source));
|
||||
ILBuilder.Emit(new ExpressionStatement(new Method(null, EnableDebugSupport, new Expression[1]
|
||||
{
|
||||
new This()
|
||||
})));
|
||||
}
|
||||
}
|
||||
Statements statements = Parser.Tree as Statements;
|
||||
ILBuilder.DebugEnabled = DebugEnabled;
|
||||
ILBuilder.LineNumber = 0;
|
||||
ILBuilder.Emit(statements);
|
||||
ILBuilder.Emit(new Return());
|
||||
Parser.GetReferencedAssemblies(ReferencedAssemblies);
|
||||
}
|
||||
|
||||
static ScriptPackage()
|
||||
{
|
||||
ScriptExchange = typeof(ScriptBase).GetField("ScriptExchange");
|
||||
ExpessionParameterTypes = null;
|
||||
EnableDebugSupport = typeof(ArchestrA.QuickScript.Runtime.ExternalReference).GetMethod("EnableDebugSupport");
|
||||
ParameterInfo[] parameters = typeof(IScriptObject).GetMethod("Expression").GetParameters();
|
||||
ExpessionParameterTypes = new Type[parameters.Length];
|
||||
int num = 0;
|
||||
ParameterInfo[] array = parameters;
|
||||
foreach (ParameterInfo parameterInfo in array)
|
||||
{
|
||||
ExpessionParameterTypes[num++] = parameterInfo.ParameterType;
|
||||
}
|
||||
}
|
||||
|
||||
private void Build(NameHierarchy nameHierarchy, TypeBuilder typeBuilder, Hashtable hashtable)
|
||||
{
|
||||
Type typeFromHandle = typeof(ArchestrA.QuickScript.Runtime.ExternalReference);
|
||||
typeFromHandle.GetField("Index");
|
||||
if (nameHierarchy.Index == -1)
|
||||
{
|
||||
TypeBuilder typeBuilder2 = null;
|
||||
if (nameHierarchy.Name != null)
|
||||
{
|
||||
typeBuilder2 = typeBuilder.DefineNestedType("Tag" + ++NestedTypeIndex, TypeAttributes.NestedPublic, typeFromHandle);
|
||||
}
|
||||
foreach (NameHierarchy value in nameHierarchy.Hashtable.Values)
|
||||
{
|
||||
Build(value, (typeBuilder2 != null) ? typeBuilder2 : typeBuilder, hashtable);
|
||||
}
|
||||
if (nameHierarchy.Name != null)
|
||||
{
|
||||
hashtable[typeBuilder2.Name] = typeBuilder2;
|
||||
}
|
||||
if (typeBuilder2 != null)
|
||||
{
|
||||
typeBuilder.DefineField(nameHierarchy.Name, typeBuilder2, FieldAttributes.Public);
|
||||
}
|
||||
}
|
||||
else if (nameHierarchy.Name != null)
|
||||
{
|
||||
string name = "T" + nameHierarchy.Index;
|
||||
TypeBuilder typeBuilder3 = typeBuilder.DefineNestedType(name, TypeAttributes.NestedPublic, typeof(ExternalReferenceDebug));
|
||||
typeBuilder.DefineField(nameHierarchy.Name, typeBuilder3, FieldAttributes.Public);
|
||||
hashtable[typeBuilder3.Name] = typeBuilder3;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("ScriptPackage.dll", CharSet = CharSet.Unicode)]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[return: MarshalAs(UnmanagedType.BStr)]
|
||||
private static extern string GetMxValueString(IntPtr rawMxValue);
|
||||
|
||||
[DllImport("ScriptPackage.dll")]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
private static extern void PutMxValueInteger(IntPtr rawMxValue, int value);
|
||||
|
||||
[DllImport("ScriptPackage.dll", CharSet = CharSet.Unicode)]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
private static extern void PutMxValueString(IntPtr rawMxValue, string value);
|
||||
|
||||
[DllImport("ScriptPackage.dll")]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
private static extern void PutMxValueBlob(IntPtr rawMxValue, [MarshalAs(UnmanagedType.LPArray)] byte[] value, int size);
|
||||
|
||||
[DllImport("ScriptPackage.dll")]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
private static extern int GetMxValueElementCount(IntPtr rawMxValue);
|
||||
|
||||
[DllImport("ScriptPackage.dll", CharSet = CharSet.Unicode)]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
[return: MarshalAs(UnmanagedType.BStr)]
|
||||
private static extern string GetMxValueElementString(IntPtr rawMxValue, int index);
|
||||
|
||||
[DllImport("ScriptPackage.dll", CharSet = CharSet.Unicode)]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
private static extern void PutMxValueElementString(IntPtr rawMxValue, int index, string value);
|
||||
|
||||
[DllImport("ScriptPackage.dll")]
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
private static extern void PutMxValueElementInteger(IntPtr rawMxValue, int index, int value);
|
||||
}
|
||||
}
|
||||
namespace AcmeBuildVersion
|
||||
{
|
||||
internal sealed class Versioning
|
||||
{
|
||||
public const string Company = "AVEVA Software, LLC";
|
||||
|
||||
public const string Product = "";
|
||||
|
||||
public const string Copyright = "Copyright 2020 AVEVA Group plc and its subsidiaries. All rights reserved.";
|
||||
|
||||
public const string Trademark = "Refer to: https://sw.aveva.com/legal/trademarks";
|
||||
|
||||
public const string Configuration = "Release";
|
||||
|
||||
public const string BuildNumber = "5800";
|
||||
|
||||
public const string BuildMaintenanceNumber = "7005";
|
||||
|
||||
public const string BldRevision = "1";
|
||||
|
||||
private Versioning()
|
||||
{
|
||||
}
|
||||
}
|
||||
internal sealed class ComponentVersioning
|
||||
{
|
||||
public const string ComponentVersion = "0038";
|
||||
|
||||
public const string ComponentMaintenanceVersion = "0000";
|
||||
|
||||
public const string ComponentName = "ScriptPrimitive";
|
||||
|
||||
private ComponentVersioning()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
# Package Manager Deep Dive
|
||||
|
||||
Date: 2026-05-05
|
||||
|
||||
## Scope
|
||||
|
||||
This investigation focused on the local System Platform package manager path that sits below GRAccess and appears to be the missing write path for object-level script body fields and some settings.
|
||||
|
||||
Tools used:
|
||||
|
||||
- `TlbImp.exe` against registered COM/type libraries.
|
||||
- .NET reflection over generated interop assemblies.
|
||||
- Registry inspection of `HKCR\TypeLib`.
|
||||
- Binary string extraction from package/runtime DLLs.
|
||||
- `dumpbin.exe` on native package/runtime DLLs.
|
||||
- Existing decompiled IDE wrapper notes under `analysis/ide-edit-investigation`.
|
||||
|
||||
Ghidra was available at `C:\Users\dohertj2\Desktop\focas\tools\ghidra_12.0.4_PUBLIC\support\analyzeHeadless.bat`, but the registered type libraries and embedded primitive XML exposed the actionable surface without needing a full native decompile.
|
||||
|
||||
## Registered components
|
||||
|
||||
### Galaxy package server
|
||||
|
||||
- Type library: `xxGalaxyPackageServer 1.0 Type Library`
|
||||
- TypeLib GUID: `{74B9B2CA-05E6-4BC4-81C3-1CAFF652035D}`
|
||||
- Binary: `C:\Program Files (x86)\ArchestrA\Framework\Bin\xxGalaxyPackageServer.dll`
|
||||
- ProgID: `XxGalaxyPackageServer.GalaxyPackage`
|
||||
- CLSID: `{F6C78E0D-8EA2-48B8-9592-6D9FB507DAD3}`
|
||||
- Threading model: `Apartment`
|
||||
|
||||
### Configuration access component
|
||||
|
||||
- Type library: `ConfigurationAccessComponent 1.0 Type Library`
|
||||
- TypeLib GUID: `{2539619B-EA9B-4035-9AE6-71421DA6C8FD}`
|
||||
- Binary: `C:\Program Files (x86)\ArchestrA\Framework\Bin\ConfigurationAccessComponent.dll`
|
||||
- Main COM server: `ConfigurationAccessComponent.ConfigurationAccessServer`
|
||||
- CLSID from interop: `{55414847-A533-4642-8E92-76B191B24B87}`
|
||||
|
||||
### Script package
|
||||
|
||||
- Type library: `ScriptPackage`
|
||||
- TypeLib GUID: `{8DF25C4D-C021-4080-849D-74A81D97D548}`
|
||||
- Binary: `C:\Program Files (x86)\ArchestrA\Framework\Bin\ScriptPackage.dll`
|
||||
- Package CLSID: `{A261929D-90AB-4217-9234-667A83756FFB}`
|
||||
- Managed compiler helper: `C:\Program Files (x86)\ArchestrA\Framework\Bin\ScriptPackage.Net.dll`
|
||||
|
||||
### Script runtime primitive definition
|
||||
|
||||
The `ScriptExtension` primitive definition is embedded in:
|
||||
|
||||
- `C:\Program Files (x86)\ArchestrA\Framework\Bin\ScriptRuntime.dll`
|
||||
|
||||
Important embedded metadata:
|
||||
|
||||
- `DefaultInternalName`: `ScriptExtension`
|
||||
- `Package_CLSID`: `{A261929D-90AB-4217-9234-667A83756FFB}`
|
||||
- `Runtime_CLSID`: `{2B2616D6-98A0-4e94-ABAD-C75BB022E2D6}`
|
||||
- Default `ExecutionGroup`: `Custom 1`
|
||||
|
||||
## Package manager surface
|
||||
|
||||
The important public COM interfaces are already in the local package server type library.
|
||||
|
||||
### `IGalaxyConfiguration` / `IGalaxyConfigurationV30`
|
||||
|
||||
This is the high-level object lifecycle and package access surface. Important methods:
|
||||
|
||||
- `GetIDFromObjectName(Namespace nSpace, string objectName) -> int`
|
||||
- `GetGObjectInfoFromName(string name, Namespace nSpace)`
|
||||
- `CheckOutObjects(int[] gObjectIds, int hint, IGObjectOperationStatus callback)`
|
||||
- `CheckinObjects(int[] gObjectIds, string comment, int hint, IGObjectOperationStatus callback)`
|
||||
- `UndoCheckoutObjects(int[] gObjectIds, int hint, bool override, IGObjectOperationStatus callback)`
|
||||
- `GetObject(int objectId, ECODEMODULES codeModules, out object package, out ERRORCODE status, out string message, EPACKAGEPROP prop)`
|
||||
- `GetObjectInternal(int objectId, ECODEMODULES code, bool editCheckRequired, out object package, out ERRORCODE status, out string message, EPACKAGEPROP prop)`
|
||||
- `GetGalaxyConfigurationNet() -> object`
|
||||
- `GetIGalaxy() -> object`
|
||||
|
||||
### `IPackageManager`
|
||||
|
||||
This is broader and includes lower-level attribute APIs. Important methods:
|
||||
|
||||
- `GetObject(...)`
|
||||
- `GetObjectInternal(...)`
|
||||
- `GetFullObjectByName(string name) -> object`
|
||||
- `GetIDFromName(string name) -> int`
|
||||
- `ReleaseFsObjectEditsession(int fsObjectId)`
|
||||
- `GetObjectAttribute(Namespace, objectName, attributeName, EATTRIBUTEPROPERTY, MxValue, out result)`
|
||||
- `InternalSetObjectAttributesEx(Namespace, objectName, EPACKAGETYPE, string[] attributeNames, EATTRIBUTEPROPERTY[] properties, MxValue[] values, bool saveObjectEvenErrorOccurs, out status, out statusMsg)`
|
||||
- `InternalGetObjectAttributesEx(...)`
|
||||
|
||||
The `InternalSetObjectAttributesEx` method is a promising direct write path because it accepts object name, package type, attribute names, property ids, and MxValues without needing the IDE configuration access component.
|
||||
|
||||
### `IConfigurationEditorSite2`
|
||||
|
||||
This is the direct package editor site returned by package access calls. Important methods:
|
||||
|
||||
- `GetAttributeCookie(string attributeFullName) -> int`
|
||||
- `GetAttribute(int cookie, short propertyId, MxValue value)`
|
||||
- `SetAttribute(int cookie, short propertyId, MxValue value) -> bool`
|
||||
- `Validate()`
|
||||
- `Commit(out string errorString) -> EPACKAGEOPERATIONSTATUS`
|
||||
- `IsReadOnly()`
|
||||
- `SetSubscriberInfo(object subscription)`
|
||||
- `AddExtensionPrimitive(...)`
|
||||
- `DeleteExtensionPrimitive(...)`
|
||||
- `RenameExtensionPrimitive(...)`
|
||||
- `AddUDA(...)`
|
||||
- `DeleteUDA(...)`
|
||||
- `RenameUDA(...)`
|
||||
- `UpdateUDA(...)`
|
||||
|
||||
For normal value writes, property id `10` is `EATTRIBUTEPROPERTY.idxAttribPropValue`.
|
||||
|
||||
### `IPrimitivePackageSite7`
|
||||
|
||||
The script package uses the generic primitive package site contract:
|
||||
|
||||
- `GetAttributeHandle(string attributeFullName) -> AttributeHandle`
|
||||
- `GetAllCategoryAttributeHandle(string attributeFullName) -> AttributeHandle`
|
||||
- `GetAttribute(AttributeHandle, MxValue)`
|
||||
- `SetAttribute(AttributeHandle, MxValue)`
|
||||
- `GetAttributeHandleEx(...)`
|
||||
- `GetPrimitiveIdByInternalNameEx(...)`
|
||||
- `GetPrimitiveAttributeIds(...)`
|
||||
- dynamic attribute operations.
|
||||
|
||||
The script package itself exposes:
|
||||
|
||||
- `CoScriptPackageClass.Initialize(short primitiveId, IPrimitivePackageSite site)`
|
||||
- `SetHandler(ref AttributeHandle, MxValue) -> string`
|
||||
- `Validate() -> EPACKAGESTATUS`
|
||||
|
||||
There is no script-specific "SetScriptText" API in the script package typelib. It is generic attribute/handler based.
|
||||
|
||||
## ScriptExtension attribute facts
|
||||
|
||||
`ScriptRuntime.dll` embeds the actual script primitive attributes. The important ones:
|
||||
|
||||
- `ExecuteText`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
|
||||
- `DeclarationsText`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
|
||||
- `StartupText`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
|
||||
- `OnScanText`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
|
||||
- `OffScanText`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
|
||||
- `ShutdownText`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
|
||||
- `Expression`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
|
||||
- `TriggerType`: `MxCustomEnum`, `Writeable_C_Lockable`, `CfgSethandler=True`
|
||||
- `_TriggerTypeEnum`: values `WhileTrue`, `WhileFalse`, `OnTrue`, `OnFalse`, `DataChange`, `Periodic`
|
||||
- `TriggerPeriod`: `MxElapsedTime`, `Writeable_C_Lockable`, `CfgSethandler=True`
|
||||
- `DataChangeDeadband`: `MxDouble`, `Writeable_UC_Lockable`, `CfgSethandler=True`
|
||||
- `RunsAsync`: `MxBoolean`, `Writeable_C_Lockable`, `CfgSethandler=True`
|
||||
- `ExecuteTimeout.Limit`: `MxInteger`, `Writeable_C_Lockable`, `CfgSethandler=True`
|
||||
- `ScriptExecutionGroup`: `MxCustomEnum`, `PackageOnly_Lockable`, `CfgSethandler=False`
|
||||
- `ScriptOrder`: `MxInteger`, `PackageOnly_Lockable`, `CfgSethandler=False`
|
||||
|
||||
This confirms the current GRAccess `IAttribute.SetValue` path is not enough for script bodies. The body fields are package-only and cfg-sethandler driven.
|
||||
|
||||
## IDE wrapper behavior
|
||||
|
||||
The decompiled IDE wrapper creates `ConfigurationAccessComponent.ConfigurationAccessServer`, casts it to `IConfigurationEditor`, and initializes it with the object package site:
|
||||
|
||||
1. `configurationEditor.Initialize("", inObjectManage, this, readOnly)`
|
||||
2. `inObjectManage.SetSubscriberInfo(configurationEditor)`
|
||||
3. `inObjectManage.SetSubscriberInfo(this)`
|
||||
4. Subscribes to `DConfigurationAccessEvents`.
|
||||
5. Sets CAS data:
|
||||
- `ww:SupportedLocales`
|
||||
- `ww:IPackageManager`
|
||||
- `ww:Tagname`
|
||||
- `ww:ContainedName`
|
||||
- `ww:DerivedFrom`
|
||||
- `ww:KeepCheckedOut`
|
||||
|
||||
This is a full IDE editor-host path. It is probably more complete than direct attribute writes, but it requires implementing enough editor-site/event plumbing in the CLI.
|
||||
|
||||
## Feasible edit paths
|
||||
|
||||
### Path A: direct package editor site
|
||||
|
||||
Likely flow:
|
||||
|
||||
1. Use existing GRAccess login to obtain `IGalaxy`.
|
||||
2. Call `IGalaxy.GetGalaxyConfiguration()` and cast the returned object to `IGalaxyConfiguration` or `IGalaxyConfigurationV30`.
|
||||
3. Resolve object id with `GetIDFromObjectName(Namespace.AutomationObject, tagName)`.
|
||||
4. Check out the object via existing GRAccess or `CheckOutObjects`.
|
||||
5. Get the editable package with `GetObjectInternal(id, eEditorAndPackageCodeModules, true, out package, ...)` or `GetObject(...)`.
|
||||
6. Cast the package object to `IConfigurationEditorSite2`.
|
||||
7. Resolve `scriptName.ExecuteText` with `GetAttributeCookie`.
|
||||
8. Create `MxValueClass` and call `PutString(newScriptBody)`.
|
||||
9. Call `SetAttribute(cookie, idxAttribPropValue, mxValue)`.
|
||||
10. Call `Validate()`.
|
||||
11. Call `Commit(out error)`.
|
||||
12. Check in or undo checkout and release edit session on failure.
|
||||
|
||||
Pros:
|
||||
|
||||
- Uses typed COM APIs.
|
||||
- Avoids the full IDE configuration access server.
|
||||
- Directly targets the package-only fields.
|
||||
|
||||
Risks:
|
||||
|
||||
- Needs live validation that the object returned by `GetObjectInternal` supports `IConfigurationEditorSite2` in this context.
|
||||
- Needs confirmation that `SetAttribute` invokes the script package cfg set handler and recompiles `_Binary`.
|
||||
- Needs correct custom enum MxValue creation for `TriggerType`.
|
||||
|
||||
### Path B: package manager internal batch set
|
||||
|
||||
Likely flow:
|
||||
|
||||
1. Get `IPackageManager` from the same object returned by `IGalaxy.GetGalaxyConfiguration()` or via package-manager support.
|
||||
2. Check out object.
|
||||
3. Call `InternalSetObjectAttributesEx` with:
|
||||
- `Namespace.AutomationObject`
|
||||
- object tagname
|
||||
- likely `EPACKAGETYPE.eBeingEditedPackage`
|
||||
- attribute names such as `UpdateTestChangingInt.ExecuteText`
|
||||
- property ids `[idxAttribPropValue]`
|
||||
- MxValues for the new data
|
||||
- `saveObjectEvenErrorOccurs=false` initially
|
||||
4. Check status and status message.
|
||||
5. Check in or undo checkout.
|
||||
|
||||
Pros:
|
||||
|
||||
- Smallest API surface.
|
||||
- No editor host required.
|
||||
- Best candidate for a CLI-first package write helper.
|
||||
|
||||
Risks:
|
||||
|
||||
- It is explicitly named `Internal`.
|
||||
- It may require exact package type and checkout state.
|
||||
- Unknown whether it invokes all cfg-sethandler compile side effects for script bodies.
|
||||
|
||||
### Path C: configuration access component
|
||||
|
||||
Likely flow:
|
||||
|
||||
1. Get editable object package as `IConfigurationEditorSite2`.
|
||||
2. Instantiate `ConfigurationAccessComponent.ConfigurationAccessServer`.
|
||||
3. Implement minimal `IBaseEditorSite` and event sink in the CLI.
|
||||
4. Call `IConfigurationEditor.Initialize`.
|
||||
5. Set the IDE wrapper's `ww:*` data entries.
|
||||
6. Set `Data("ScriptName.ExecuteText", "Value") = body`.
|
||||
7. Apply/commit through the editor site.
|
||||
|
||||
Pros:
|
||||
|
||||
- Closest to the IDE behavior.
|
||||
- Most likely to run all validation/dirty/event plumbing.
|
||||
|
||||
Risks:
|
||||
|
||||
- Largest implementation.
|
||||
- Requires COM connection-point/event hosting.
|
||||
- More fragile in a headless CLI.
|
||||
|
||||
## Current CLI implication
|
||||
|
||||
The current `object scripts set` path writes through `IAttribute.SetValue`. That cannot persist the package-only body fields shown above.
|
||||
|
||||
The current `object scripts settings set --trigger-period-ms` should be more viable because `TriggerPeriod` is `Writeable_C_Lockable`, but it still goes through generic GRAccess `IAttribute.SetValue`.
|
||||
|
||||
The current `object scripts settings set --trigger-type` is suspicious because `TriggerType` is `MxCustomEnum`; the CLI currently creates an `MxValue` string for non-explicit data types. The package metadata says the valid values are backed by `_TriggerTypeEnum`, so a package-manager implementation should set `TriggerType` as a custom enum or verify that package `SetAttribute` accepts the string form.
|
||||
|
||||
## Recommended next implementation
|
||||
|
||||
Do not add more typelibs first. The useful typelibs already exist locally:
|
||||
|
||||
- `ArchestrA.GRAccess.dll`
|
||||
- `xxGalaxyPackageServer.dll`
|
||||
- `ConfigurationAccessComponent.dll`
|
||||
- `ScriptPackage.dll`
|
||||
|
||||
The next improvement should add a small, isolated package-manager interop layer and a probe/edit helper:
|
||||
|
||||
1. Add minimal `[ComImport]` declarations for only the needed package-manager interfaces, or add a checked-in interop reference generated from `xxGalaxyPackageServer.dll`.
|
||||
2. Bridge from `IGalaxy.GetGalaxyConfiguration()` to `IGalaxyConfigurationV30` / `IPackageManager`.
|
||||
3. Implement a read-only probe first:
|
||||
- object id resolution
|
||||
- `GetObjectInternal`
|
||||
- cast checks for `IConfigurationEditorSite2`, `IObjectManage`, `IPackageManager`
|
||||
- `GetAttributeCookie` for script fields/settings
|
||||
- `GetAttribute` for `idxAttribPropValue`
|
||||
4. Implement one guarded write path for `ExecuteText`.
|
||||
5. Verify whether `_Binary`, `_ErrorMessage`, `_ErrorLine`, and `_ErrorColumn` update after commit. If they do not, switch from Path A/B to the CAS path.
|
||||
6. Only then wire it into `object scripts set` and package-only script settings.
|
||||
|
||||
## Live validation blocker
|
||||
|
||||
Previous live GRAccess validation against the `ZB` galaxy failed because the local license was unavailable:
|
||||
|
||||
`cmdLicenseUnavailable (105); Feature 'wspdevstudio-iocount'`
|
||||
|
||||
The package-manager write path needs live validation after license access is restored because static metadata cannot prove whether script cfg-sethandler side effects fire on direct package writes.
|
||||
|
||||
@@ -60,7 +60,7 @@ graccess object scripts get --galaxy ZB --name TestMachine --type template --scr
|
||||
|
||||
The CLI tries direct GRAccess reads first. If direct GRAccess does not expose inheritance, attribute values, or script bodies, read commands may export the target object to a temporary `.aaPKG`, parse textual/package entries, recurse into nested package archives, parse binary UTF-16 script extension records, and delete the temp files. SQL Server repository reads are not part of normal CLI behavior and should only be used for development verification/debugging.
|
||||
|
||||
Script body access is adapter-dependent. For `ScriptExtension` objects, a bare script name maps to `.ExecuteText`. Mutating script and attribute commands prefer `ConfigurableAttributes[...]` before `Attributes[...]`. On the local GRAccess build, body fields such as `ExecuteText` and `Expression` are package-only and are not persisted by `IAttribute.SetValue`, so the CLI fails fast for those writes instead of returning a false success. Mutable settings such as `TriggerPeriod` and `TriggerType` remain available through `object scripts settings set`. If neither direct GRAccess nor the package fallback exposes body text, script read commands return structured unavailable details instead of pretending success.
|
||||
Script body access is adapter-dependent. For `ScriptExtension` objects, a bare script name maps to `.ExecuteText`; `object scripts set --file <path>` writes the matching script body attribute through GRAccess. Mutating script and attribute commands prefer `ConfigurableAttributes[...]` before `Attributes[...]`, because script extension settings such as `TriggerPeriod`, `TriggerType`, `Expression`, and `ExecuteText` are configuration attributes per AVEVA Tech Note 537 ("Creating an Application Object Script Using GRAccess"). If neither direct GRAccess nor the package fallback exposes body text on a read, script read commands return structured unavailable details — but writes via the TN-537 pattern persist; round-trip evidence is in `analysis/ide-edit-investigation/probe_setvalue/`.
|
||||
|
||||
Use the script settings wrapper for IDE-style script configuration:
|
||||
|
||||
@@ -71,7 +71,7 @@ graccess object scripts settings set --galaxy ZB --name '$TestMachine' --type te
|
||||
Use the create wrapper for new object-level script extensions:
|
||||
|
||||
```powershell
|
||||
graccess object scripts create --galaxy ZB --name '$MyTemplate' --type template --script OnScan --trigger-type Periodic --trigger-period-ms 1000 --confirm --confirm-target '$MyTemplate' --llm-json
|
||||
graccess object scripts create --galaxy ZB --name '$MyTemplate' --type template --script OnScan --file OnScan.txt --trigger-type Periodic --trigger-period-ms 1000 --confirm --confirm-target '$MyTemplate' --llm-json
|
||||
```
|
||||
|
||||
## Inheritance And Embedded Objects
|
||||
|
||||
@@ -17,7 +17,7 @@ For LLM-driven script work, read script metadata and validate guarded edits with
|
||||
```powershell
|
||||
graccess object scripts list --galaxy ZB --name TestMachine --type template --llm-json
|
||||
graccess object scripts get --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --llm-json
|
||||
graccess object scripts settings set --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --trigger-period-ms 500 --confirm --confirm-target TestMachine --dry-run --llm-json
|
||||
graccess object scripts set --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --file .\UpdateTestChangingInt.txt --confirm --confirm-target TestMachine --dry-run --llm-json
|
||||
```
|
||||
|
||||
## What The CLI Can Edit Today
|
||||
@@ -28,8 +28,9 @@ graccess object scripts settings set --galaxy ZB --name TestMachine --type templ
|
||||
| Script library export | `script-library export` |
|
||||
| Script library import/add | `script-library import`, `script-library add`, `galaxy import-script-library` |
|
||||
| Object script metadata | `object scripts list`, `object scripts get` |
|
||||
| Object script body read | `object scripts get` package fallback for script body attributes such as `ExecuteText` |
|
||||
| Object script body read/write | `object scripts get` (package fallback) and `object scripts set --file <path> [--field <name>]` for body fields like `ExecuteText` / `DeclarationsText` / `StartupText` / `ShutdownText` / `OnScanText` / `OffScanText` / `Expression`. See AVEVA Tech Note 537. |
|
||||
| Object script mutable settings | `object scripts settings set --trigger-type`, `--trigger-period-ms` |
|
||||
| Object script create/delete | `object scripts create`, `object scripts delete` |
|
||||
| Script-like attributes | `object attribute set`, lock, security, buffer |
|
||||
| Extension primitives | `object extension add/delete/rename` |
|
||||
| Full object package script payloads | Use `objects export` and `galaxy import-objects` |
|
||||
@@ -91,17 +92,17 @@ $scripts = (@($attrs) + @($extended)) | Where-Object {
|
||||
$scripts | Select-Object Name, DataType, Category, Locked
|
||||
```
|
||||
|
||||
If the script-like setting is a scalar writable attribute, edit it through the normal template edit flow. Script extension attributes are looked up through `ConfigurableAttributes` first, then `Attributes`.
|
||||
|
||||
For local `ScriptExtension` objects, body fields such as `ExecuteText`, `StartupText`, `OnScanText`, and `Expression` are package-only. GRAccess `IAttribute.SetValue` can return success without persisting package-only fields, so `object scripts set` and `object scripts settings set --expression` fail fast when the attribute category starts with `MxCategoryPackageOnly`. Use the IDE or a future package-rewrite path for those fields.
|
||||
If the script-like setting is a scalar writable attribute, edit it through the normal template edit flow. Script extension attributes are looked up through `ConfigurableAttributes` first, then `Attributes` — the pattern AVEVA Tech Note 537 ("Creating an Application Object Script Using GRAccess") prescribes for `ScriptExtension` text fields. `object scripts set`, `object scripts settings set`, and `object attribute value set` all use that ordering. A bare `--script <name>` writes to `<name>.ExecuteText`; pass `--field <FieldName>` (or include the suffix in `--script`) to target `DeclarationsText`, `StartupText`, `ShutdownText`, `OnScanText`, `OffScanText`, or `Expression`.
|
||||
|
||||
```powershell
|
||||
graccess object checkout --galaxy ZB --name TestMachine --type template --confirm --confirm-target TestMachine
|
||||
graccess object attribute value set --galaxy ZB --name TestMachine --type template --attribute SomeWritableScriptSetting --value Updated --data-type string --confirm --confirm-target TestMachine
|
||||
graccess object scripts set --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --file .\UpdateTestChangingInt.txt --confirm --confirm-target TestMachine
|
||||
graccess object save --galaxy ZB --name TestMachine --type template --confirm --confirm-target TestMachine
|
||||
graccess object checkin --galaxy ZB --name TestMachine --type template --comment 'Update writable script setting' --confirm --confirm-target TestMachine
|
||||
graccess object checkin --galaxy ZB --name TestMachine --type template --comment 'Update script body' --confirm --confirm-target TestMachine
|
||||
```
|
||||
|
||||
> **Reader caveat.** `object attribute value get` against a script text field may report `Supported: False` ("Attribute value is not exposed by this GRAccess attribute") even when the value is set. The write itself persists; the limitation is in `MxValueDetails` reading the COM-side `value` accessor for these field types. Verify post-write content with `object scripts get --llm-json` (which uses the package-export readback) instead. Round-trip evidence is in `analysis/ide-edit-investigation/probe_setvalue/` (commit `e4e5425..`).
|
||||
|
||||
Update periodic script settings and lock the interval for deployment inheritance:
|
||||
|
||||
```powershell
|
||||
@@ -140,16 +141,16 @@ Delete:
|
||||
graccess object extension delete --galaxy ZB --name TestMachine --type template --extension-type ScriptExtension --primitive OnScan2 --object-extension --confirm --confirm-target TestMachine
|
||||
```
|
||||
|
||||
These commands manage primitive structure. The wrapper below creates the same `ScriptExtension` primitive and can initialize mutable settings:
|
||||
These commands manage primitive structure. The wrapper below creates the same `ScriptExtension` primitive and can initialize the body and common settings in the same checkout flow (per AVEVA TN-537: AddExtensionPrimitive → Save → set body/settings via ConfigurableAttributes):
|
||||
|
||||
```powershell
|
||||
graccess object checkout --galaxy ZB --name '$MyTemplate' --type template --confirm --confirm-target '$MyTemplate'
|
||||
graccess object scripts create --galaxy ZB --name '$MyTemplate' --type template --script OnScan --trigger-type Periodic --trigger-period-ms 1000 --confirm --confirm-target '$MyTemplate'
|
||||
graccess object scripts create --galaxy ZB --name '$MyTemplate' --type template --script OnScan --file .\OnScan.txt --trigger-type Periodic --trigger-period-ms 1000 --confirm --confirm-target '$MyTemplate'
|
||||
graccess object save --galaxy ZB --name '$MyTemplate' --type template --confirm --confirm-target '$MyTemplate'
|
||||
graccess object checkin --galaxy ZB --name '$MyTemplate' --type template --comment 'Add OnScan script' --confirm --confirm-target '$MyTemplate'
|
||||
```
|
||||
|
||||
After adding a `ScriptExtension` primitive, body text still needs the IDE/package path when the projected body attributes are package-only.
|
||||
After adding a `ScriptExtension` primitive, set its body with `object scripts set --script <primitiveName>` (defaults to `ExecuteText`) or target a different field with `--field DeclarationsText` / `StartupText` / etc.
|
||||
|
||||
## Full-Fidelity Object Script Edits
|
||||
|
||||
@@ -203,7 +204,7 @@ graccess instance deploy --galaxy ZB --name TestMachine_ScriptTest_001 --type in
|
||||
```text
|
||||
graccess object scripts list --galaxy ZB --name TestMachine --type template --json
|
||||
graccess object scripts get --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --llm-json
|
||||
graccess object scripts settings set --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --trigger-period-ms 500 --confirm --confirm-target TestMachine --llm-json
|
||||
graccess object scripts set --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --file .\UpdateTestChangingInt.txt --confirm --confirm-target TestMachine --llm-json
|
||||
```
|
||||
|
||||
The local GRAccess examples sometimes show `Template.Scripts[index].ScriptString`, but this repository's installed `ArchestrA.GRAccess.dll` does not expose a public object script collection. It exposes script extension projections as attributes and package records. The CLI can read body text through exported package parsing and can write mutable settings through `IAttribute.SetValue`; package-only body/expression fields are not persisted by that GRAccess setter.
|
||||
The local GRAccess examples sometimes show `Template.Scripts[index].ScriptString`, but this repository's installed `ArchestrA.GRAccess.dll` does not expose a public object script collection — it exposes script extension projections as attributes (e.g. `UpdateTestChangingInt.ExecuteText`). The CLI writes those attributes through the AVEVA TN-537 pattern: `IgObject.ConfigurableAttributes[<script>.<field>].SetValue(MxValue)`, surrounded by `CheckOut → Save → CheckIn`. Reads use the package-export fallback when the GRAccess attribute-value accessor doesn't surface the field directly.
|
||||
|
||||
@@ -242,15 +242,14 @@ Scalar `string`, `bool`, `int`, `float`, and `double` writes are supported first
|
||||
graccess object scripts list --galaxy ZB --name TestMachine --type template --llm-json
|
||||
graccess object scripts get --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --llm-json
|
||||
graccess object scripts get --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt.ExecuteText --llm-json
|
||||
graccess object scripts set --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --file UpdateTestChangingInt.txt --confirm --confirm-target TestMachine --llm-json
|
||||
graccess object scripts settings set --galaxy ZB --name '$TestMachine' --type template --script UpdateTestChangingInt --trigger-period-ms 500 --lock-trigger-period --confirm --confirm-target '$TestMachine' --llm-json
|
||||
graccess object scripts create --galaxy ZB --name '$MyTemplate' --type template --script OnScan --trigger-type Periodic --trigger-period-ms 1000 --confirm --confirm-target '$MyTemplate' --llm-json
|
||||
graccess object scripts create --galaxy ZB --name '$MyTemplate' --type template --script OnScan --file OnScan.txt --trigger-type Periodic --trigger-period-ms 1000 --confirm --confirm-target '$MyTemplate' --llm-json
|
||||
```
|
||||
|
||||
Direct object script body access depends on the local GRAccess object model. Reads inspect the exported package fallback for script extension bodies and script text fields such as `ExecuteText`, `DeclarationsText`, `StartupText`, `ShutdownText`, `OnScanText`, `OffScanText`, and `Expression`.
|
||||
For writes, the CLI follows AVEVA Tech Note 537 ("Creating an Application Object Script Using GRAccess"): script body and setting mutations prefer `IgObject.ConfigurableAttributes[...]`, then fall back to `Attributes[...]` only if the configurable collection does not expose the requested field. `object scripts set` writes the matching script body attribute via `IAttribute.SetValue(MxValue)`; a bare `--script <name>` defaults to `<name>.ExecuteText`; pass `--field <FieldName>` to target `DeclarationsText`, `StartupText`, `ShutdownText`, `OnScanText`, `OffScanText`, or `Expression`. `object scripts settings set` writes common script settings such as `TriggerPeriod`, `TriggerType`, and `Expression`; `--lock-trigger-period` / `--lock-trigger-type` apply `MxLockedInMe` so derived instances inherit the value on deploy. `MxElapsedTime` values are stored in 100 ns increments, so `--trigger-period-ms 500` writes `5,000,000`. `object scripts create` calls `AddExtensionPrimitive("ScriptExtension", <script>, true)` then initializes the body/settings in the same checkout flow.
|
||||
|
||||
For writes, the CLI uses the public GRAccess path exposed by `IgObject.ConfigurableAttributes[...]` / `Attributes[...]` and `IAttribute.SetValue`. This is valid for mutable settings such as `TriggerPeriod` and `TriggerType`. `MxElapsedTime` values are stored in 100 ns increments, so `--trigger-period-ms 500` writes `5,000,000`.
|
||||
|
||||
Script body fields and `Expression` on the local `ScriptExtension` projection are package-only (`MxCategoryPackageOnly*`). GRAccess `IAttribute.SetValue` can report success for those fields without persisting the value, so the CLI now fails fast when it sees a package-only category. `object scripts create` can add the `ScriptExtension` primitive and initialize mutable settings, but it cannot initialize package-only body text through GRAccess. Use the IDE or a future package-rewrite path for body/expression edits.
|
||||
For reads, `object scripts get --llm-json` and `object snapshot --llm-json` use the package-export fallback to surface body content. `object attribute value get` against a script text field may report `Supported: False` ("Attribute value is not exposed by this GRAccess attribute") even when the value is set — that's a `MxValueDetails` accessor-probe gap (the value persisted, the reader didn't surface it). Use `object scripts get` to verify post-write content. Round-trip evidence: `analysis/ide-edit-investigation/probe_setvalue/`.
|
||||
|
||||
### Area, engine, assignment, and I/O wrappers
|
||||
|
||||
|
||||
@@ -316,5 +316,5 @@ The parent instance shape matches `$TestMachine` by count and script metadata. `
|
||||
- Edit embedded child objects by targeting the child instance tagname, such as `DelmiaReceiver_001`, or its hierarchical name when a command supports hierarchical lookup.
|
||||
- Do not mutate `$TestMachine` until the existing checkout state is understood; the template was `checkedOutToMe` during capture.
|
||||
- Direct scalar value readback for many template attributes is not exposed by the current generic attribute value path.
|
||||
- Direct script body readback is not exposed by the current generic value path; use `object scripts get --llm-json` package fallback for reads. Package-only script body/expression fields are not persisted by GRAccess `IAttribute.SetValue`; use the IDE/package path for those edits.
|
||||
- Direct script body readback is not exposed by the current generic value path; use `object scripts get --llm-json` package fallback for reads and `object scripts set --file <path> [--field <name>]` for script body attribute writes. The CLI follows AVEVA Tech Note 537 — writes go through `IgObject.ConfigurableAttributes[<script>.<field>].SetValue(MxValue)` and persist correctly even though `object attribute value get` may report the post-write value as "not exposed" (that's a `MxValueDetails` reader-side gap, not a write-side no-op).
|
||||
- Extended attributes failed because the local COM type library was not registered.
|
||||
|
||||
@@ -426,7 +426,7 @@ namespace ZB.MOM.WW.GRAccess.Cli.Commands
|
||||
public override Dictionary<string, object> Args() { var args = base.Args(); args["script"] = Script; return args; }
|
||||
}
|
||||
|
||||
[Command("object scripts set", Description = "Attempt to set object script text through GRAccess; package-only ScriptExtension text fails fast")]
|
||||
[Command("object scripts set", Description = "Set an object script text field via IAttribute.SetValue on ConfigurableAttributes (TN-537 pattern)")]
|
||||
public sealed class ObjectScriptsSetCommand : ObjectScriptsGetCommand
|
||||
{
|
||||
public override string Subcommand => "scripts-set";
|
||||
@@ -434,7 +434,7 @@ namespace ZB.MOM.WW.GRAccess.Cli.Commands
|
||||
[CommandOption("file", Description = "Script source file", IsRequired = true)]
|
||||
public string File { get; init; }
|
||||
|
||||
[CommandOption("field", Description = "Script-text field to write: ExecuteText (default), DeclarationsText, StartupText, ShutdownText, OnScanText, OffScanText, Expression. Package-only fields fail fast instead of silently no-oping.")]
|
||||
[CommandOption("field", Description = "Script-text field to write: ExecuteText (default), DeclarationsText, StartupText, ShutdownText, OnScanText, OffScanText, Expression. Pass to target a non-body field; omit to default to ExecuteText.")]
|
||||
public string Field { get; init; } = "";
|
||||
|
||||
public override Dictionary<string, object> Args() { var args = base.Args(); args["file"] = File; args["field"] = Field; return args; }
|
||||
@@ -454,7 +454,7 @@ namespace ZB.MOM.WW.GRAccess.Cli.Commands
|
||||
[CommandOption("trigger-type", Description = "Script trigger type value")]
|
||||
public string TriggerType { get; init; } = "";
|
||||
|
||||
[CommandOption("expression", Description = "Script expression text. On local ScriptExtension builds this is package-only and fails fast.")]
|
||||
[CommandOption("expression", Description = "Script expression value")]
|
||||
public string Expression { get; init; } = "";
|
||||
|
||||
[CommandOption("lock-trigger-period", Description = "Lock TriggerPeriod in this object after setting it")]
|
||||
@@ -475,7 +475,7 @@ namespace ZB.MOM.WW.GRAccess.Cli.Commands
|
||||
}
|
||||
}
|
||||
|
||||
[Command("object scripts create", Description = "Create a ScriptExtension primitive and optionally initialize mutable settings")]
|
||||
[Command("object scripts create", Description = "Create a ScriptExtension primitive and optionally initialize its body/settings")]
|
||||
public sealed class ObjectScriptsCreateCommand : ObjectScriptSettingsCommandBase
|
||||
{
|
||||
public override string Subcommand => "scripts-create";
|
||||
|
||||
@@ -1664,7 +1664,6 @@ namespace ZB.MOM.WW.GRAccess.Cli.GRAccess
|
||||
if (!string.IsNullOrWhiteSpace(triggerPeriodMs))
|
||||
{
|
||||
var attr = FindAttributeForMutation(obj, scriptName + ".TriggerPeriod");
|
||||
EnsureMutableViaSetValue(attr, scriptName + ".TriggerPeriod");
|
||||
attr.SetValue(CreateMxValue(triggerPeriodMs, "elapsed-ms"));
|
||||
yield return CommandSummary(attr, $"Set script {scriptName}.TriggerPeriod");
|
||||
|
||||
@@ -1679,7 +1678,6 @@ namespace ZB.MOM.WW.GRAccess.Cli.GRAccess
|
||||
if (!string.IsNullOrWhiteSpace(triggerType))
|
||||
{
|
||||
var attr = FindAttributeForMutation(obj, scriptName + ".TriggerType");
|
||||
EnsureMutableViaSetValue(attr, scriptName + ".TriggerType");
|
||||
attr.SetValue(CreateMxValue(triggerType, "string"));
|
||||
yield return CommandSummary(attr, $"Set script {scriptName}.TriggerType");
|
||||
|
||||
|
||||
+14
-12
@@ -175,15 +175,15 @@ namespace ZB.MOM.WW.GRAccess.Cli.Tests.Commands
|
||||
branch.ShouldContain("RequireConfirm(args, Arg(args, \"name\"));");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DispatcherScriptSettings_GuardsPackageOnlyNoOpsBeforeSetValue()
|
||||
{
|
||||
var source = DispatcherSource();
|
||||
|
||||
source.ShouldContain("EnsureMutableViaSetValue(attr, scriptName + \".TriggerPeriod\");");
|
||||
source.ShouldContain("EnsureMutableViaSetValue(attr, scriptName + \".TriggerType\");");
|
||||
source.ShouldContain("EnsureMutableViaSetValue(attr, scriptName + \".Expression\");");
|
||||
}
|
||||
// Removed: tests asserting EnsureMutableViaSetValue and "package-only"
|
||||
// wording in command descriptions. Both reflected an earlier (incorrect)
|
||||
// belief that IAttribute.SetValue silently no-ops on
|
||||
// MxCategoryPackageOnly_Lockable script-text fields. Live round-trip
|
||||
// (analysis/ide-edit-investigation/probe_setvalue/) and AVEVA Tech Note
|
||||
// 537 confirm writes via ConfigurableAttributes[<script>.<field>]
|
||||
// .SetValue persist; the apparent "no-op" was a MxValueDetails reader
|
||||
// gap, not a write-side failure. The safety check + asserting tests
|
||||
// were reverted in commits 4e242ca / e4e5425 + the corrective commit.
|
||||
|
||||
[Fact]
|
||||
public void DispatcherElapsedMilliseconds_UseDocumentedHundredNanosecondUnits()
|
||||
@@ -195,7 +195,7 @@ namespace ZB.MOM.WW.GRAccess.Cli.Tests.Commands
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScriptCommandDescriptions_CallOutPackageOnlyLimit()
|
||||
public void ScriptCommandDescriptions_DocumentTheTN537Pattern()
|
||||
{
|
||||
var set = (CommandAttribute)Attribute.GetCustomAttribute(
|
||||
typeof(ObjectScriptsSetCommand),
|
||||
@@ -208,8 +208,10 @@ namespace ZB.MOM.WW.GRAccess.Cli.Tests.Commands
|
||||
settings.ShouldNotBeNull();
|
||||
set.Description.ShouldNotBeNull();
|
||||
settings.Description.ShouldNotBeNull();
|
||||
set.Description!.ShouldContain("package-only");
|
||||
settings.Description!.ShouldContain("mutable");
|
||||
// Set-body uses TN-537's ConfigurableAttributes pattern.
|
||||
set.Description!.ShouldContain("TN-537");
|
||||
// Settings command writes through ConfigurableAttributes too.
|
||||
settings.Description!.ShouldContain("ConfigurableAttributes");
|
||||
}
|
||||
|
||||
private static string DispatcherSource()
|
||||
|
||||
Reference in New Issue
Block a user