From crisp-tc
Analyze a Data Manager .fsf form file and convert it to an equivalent OpenAccess (OA) UserControlBase control. Use this skill whenever the user provides an .fsf file or describes a Data Manager form and asks to recreate, migrate, or build the equivalent OA control. Also use when the user mentions Data Manager, DM forms, ProfusionHierarchyCtrl1, VBScript event handlers, .fsf files, or legacy Profusion forms.
How this skill is triggered — by the user, by Claude, or both
Slash command
/crisp-tc:datamanager-to-openaccessThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
<context>
Read references/control-map.md for the complete mapping tables between DM controls,
VBScript patterns, and their OA/C# equivalents.
.fsf files are OLE Compound Document binary containers. They embed control metadata
and VBScript event-handler code as wide-character (UTF-16LE) strings. The companion
.fsf.dat is a compiled binary — do not attempt to read it.
How to extract useful content from the .fsf:
Use the Read tool on the .fsf file. Despite being binary, the embedded strings are
readable because they appear as wide-character text interspersed with binary. Look for:
Control type identifiers — appear near control names:
CObjCheckbox — checkboxCObjEdit — single-line or multi-line text inputCObjActiveX — embedded ActiveX control (look for ProgID nearby, e.g. ProfusionHierarchyCtrl.ProfusionHierarchyCtrl.1)CObjTextBlock — read-only labelCScriptManager — holds the VBScript code-behind (not a UI control)CShellObject — shell/command objectCObjCombo — combo box / dropdownControl names — appear as readable strings near their type identifier.
Example: ProfusionHierarchyCtrl1, chx_ExportByNode, txtFilePath, cmbSheetName
VBScript code — large block of readable text containing Sub and Function definitions,
Application. calls, ADODB.Command, etc.
Form title — appears near the top as a readable string.
When reading the .fsf, scan broadly for these patterns. The binary structure varies by form; there is no fixed offset. Focus on extracting:
Before writing any OA code, produce an inventory table:
| DM Control Name | DM Type | Purpose (inferred from name + usage in VBScript) | OA Equivalent |
|---|---|---|---|
| ProfusionHierarchyCtrl1 | CObjActiveX | Hierarchy node selector | SubscriptionManager PublishedKey.HierarchyDBKeySelectedNodeList |
| chx_ExportByNode | CObjCheckbox | Mode selection | <asp:CheckBox> in ASCX |
| txtFilePath | CObjEdit | File path input | <asp:TextBox> in ASCX |
| cmbSheetName | CObjCombo | Excel sheet picker | <asp:DropDownList> in ASCX |
| cmd_DoAction | CObjEdit (button) | Submit trigger | ICommandManager.GetCommands() toolbar button |
See references/control-map.md for the full mapping table.
Read the VBScript code-behind and categorize each Sub/Function into one of:
| Category | VBScript Pattern | OA Equivalent |
|---|---|---|
| Initialization | Sub Form_Load() | LoadUI() called from OnInit |
| Validation | Guard clauses at top of action Sub | Private Validate() method |
| Data read | Application.GetRecordset(strSQL) | CommandFactory query with Dapper |
| SP call | ADODB.Command with .Parameters.Append | CommandFactory method with DynamicParameters |
| File I/O | Scripting.FileSystemObject | System.IO.File / System.IO.Path |
| Excel COM | CreateObject("Excel.Application") | ClosedXML or EPPlus NuGet package |
| BCP bulk load | Shell "bcp ..." | Keep as Process.Start("bcp", ...) wrapped in CommandFactory |
| Status message | Application.ShowStatusMsg | OnDisplayMessage(MessageEventTypes.Success) |
| Wait cursor | Application.ShowWaitCursor | No equivalent needed (OA handles loading state) |
| Navigation | Application.NavigateToFrameworkMemberEx | Not applicable in OA controls |
| Message box | Application.MessageBox | OnDisplayMessage(MessageEventTypes.Error) |
| Hierarchy selection read | ProfusionHierarchyCtrl1.Object.SelectedLeafKeys | SubscriptionManager.GetPageStoreData(this, PublishedKey.HierarchyDBKeySelectedNodeList) |
Use the kraken:openaccess-controls skill to determine Derived Control vs. Standalone Control.
Data Manager forms almost always become Standalone Controls (UserControlBase) because:
Only use a Derived Control if the form was acting as a detail panel triggered from a grid row.
DM forms that use ProfusionHierarchyCtrl1 get their selections via direct ActiveX property access:
strKeys = ProfusionHierarchyCtrl1.Object.SelectedLeafKeys
strKeyText = ProfusionHierarchyCtrl1.Object.SelectedLeafKeyText
In OA, the hierarchy control is a separate registered control on the OA page. The selected
keys arrive via SubscriptionManager:
// In constructor
SubscriptionManager.AddKeyToSubscribe(PublishedKey.HierarchyDBKeySelectedNodeList);
// In OnMessageReceived
protected override void OnMessageReceived(object sender, DataStoreChangedEventArgs e)
{
if (e.Key.Equals(SubscriptionManager.SubscribedKey(PublishedKey.HierarchyDBKeySelectedNodeList),
StringComparison.OrdinalIgnoreCase))
{
var keys = SubscriptionManager.GetPageStoreData(this, PublishedKey.HierarchyDBKeySelectedNodeList)?.ToString();
_uiModel.NodeKeys = keys;
base.Refresh();
}
}
If the DM form also read SelectedStatus or GetSelectedDataKeys, use PublishedKey.DBStatusList
or PublishedKey.JsDBKeyList respectively.
' DM VBScript
Dim rs
Set rs = Application.GetRecordset("SELECT col1, col2 FROM ckbcustom.cx_mytable WHERE id = " & someId)
While Not rs.EOF
DoSomething rs("col1").Value
rs.MoveNext
Wend
// OA CommandFactory (Dapper)
internal IEnumerable<MyModel> GetMyData(int id)
{
_logger.Information("Starting GetMyData()");
try
{
using (var conn = GetDbConnection())
{
var p = new DynamicParameters();
p.Add("id", id, dbType: DbType.Int32);
var result = conn.Query<MyModel>($"{_customSchema}.cx_mydata_get", p,
commandType: CommandType.StoredProcedure);
ConnectionCleanup(conn);
return result;
}
}
catch (Exception ex) { _logger.Error(ex, "GetMyData Exception"); }
_logger.Information("Finished GetMyData()");
return Enumerable.Empty<MyModel>();
}
' DM VBScript
Dim cmd
Set cmd = CreateObject("ADODB.Command")
cmd.ActiveConnection = Application.ProfusionSupport.ADOConnection
cmd.CommandType = 4 ' adCmdStoredProc
cmd.CommandText = "ckbcustom.cx_automator_action_list_ins"
cmd.Parameters.Append cmd.CreateParameter("@username", 200, 1, 100, strUser)
cmd.Parameters.Append cmd.CreateParameter("@filepath", 200, 1, 255, strFile)
cmd.Execute
// OA CommandFactory
internal void InsertActionList(string username, string filepath)
{
_logger.Information("Starting InsertActionList()");
try
{
using (var conn = GetDbConnection())
{
var p = new DynamicParameters();
p.Add("username", username, dbType: DbType.String, size: 100);
p.Add("filepath", filepath, dbType: DbType.String, size: 255);
conn.Execute($"{_customSchema}.cx_automator_action_list_ins", p,
commandType: CommandType.StoredProcedure);
ConnectionCleanup(conn);
}
}
catch (Exception ex) { _logger.Error(ex, "InsertActionList Exception"); }
_logger.Information("Finished InsertActionList()");
}
Shell "bcp ..." does not appear in the known DM forms but may exist in others. Do not preserve it as Process.Start("bcp") in OA — OA uses Dapper for all database access. Convert as follows:
DynamicParameters.SqlBulkCopy in a dedicated CommandFactory method instead of shelling out to the BCP utility.cx_job and let the Automator/service process it asynchronously. See kraken:openaccess-controls skill → references/patterns.md → Job Queue Pattern.Never pass a file path or table name as a shell argument — always parameterize via stored proc or SqlBulkCopy.
' DM — convert xlsx to csv
Set oXL = CreateObject("Excel.Application")
Set oWB = oXL.Workbooks.Open(strFilePath)
Set oWS = oWB.Worksheets(cmbSheetName.Text)
oWB.SaveAs strCsvPath, 6 ' xlCSV
oXL.Quit
// OA — ClosedXML
using var wb = new XLWorkbook(filePath);
var ws = wb.Worksheet(sheetName);
// Write to CSV manually or use a library helper
Follow the kraken:openaccess-controls skill for project structure, using the inventory from Step 2 to
determine which files are needed. Minimum set for a typical DM form migration:
MyControl/
MyControl.csproj
CopyWebUI.bat
Libraries/ ← copy from SampleOAProject
Views/
MyControlUI.ascx ← HTML for all CObjEdit, CObjCheckbox, CObjCombo controls
MyControlUI.ascx.cs ← UserControlBase, ICommandManager, IPopupControlSubscriber (if needed)
HelperClasses/
CommandFactory.cs ← one method per VBScript data operation
ConfigurationHelper.cs
Models/
MyControlModel.cs ← one property per form input + result state
ASCX layout guidance from DM control inventory:
CObjCheckbox → <asp:CheckBox runat="server" ID="chkXxx" />CObjEdit (single line) → <asp:TextBox runat="server" ID="txtXxx" />CObjEdit (multi-line) → <asp:TextBox runat="server" ID="txtXxx" TextMode="MultiLine" />CObjCombo → <asp:DropDownList runat="server" ID="ddlXxx" />CObjTextBlock → <asp:Label runat="server" ID="lblXxx" />CObjActiveX (hierarchy) → No ASCX control needed; subscribe to PublishedKey.HierarchyDBKeySelectedNodeListcmd_Xxx or the primary action) → ICommandManager.GetCommands() toolbar button
npx claudepluginhub gocrisp-proservices/techteam_skills --plugin crisp-tcProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.