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,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.
|
||||
|
||||
Reference in New Issue
Block a user