Files
wwtools/graccesscli/analysis/package-manager-deep-dive.md
Joseph Doherty c52d8d0171 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>
2026-05-05 21:33:51 -04:00

13 KiB

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.

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.