Skills
TC Dispatcher Cicd Pipeline
Skill
tc-dispatcher-cicd-pipeline. Set up a Teamcenter Dispatcher translator end to end and use it as a CI/CD-style pipeline engine — a workflow-fired job runner with stages, gates, pass/fail routing and fan-out/fan-in. Full setup guide (translator registration, handler binding, site preferences, the Windows-service classpath trap) plus the router/config pattern for running many "pipeline stages" off one script. Use when standing up a new Dispatcher-triggered workflow integration, or explaining the pattern to someone else.
Teamcenter Dispatcher is marketed as CAD translation. Structurally it is a
queued, distributed job runner: something stages a work order, a worker
process picks it up, runs an arbitrary executable against a working
directory, and reports a terminal state Teamcenter can read back. Nothing
about that is CAD-specific. Read a workflow Do task as a pipeline
stage, a Review or OR-task as a gate, and a translator's exit
code as a pass/fail signal, and the whole thing behaves like a CI/CD
pipeline with Teamcenter as the orchestrator — proven live on TC 2606
(vm2606), 2026-08.
Two companion skills go deep on specific failure modes this one only
summarizes: tc-dispatcher-requests (creating/reading requests, the
preference system, direct-vs-indirect translators) and tc-workflow-authoring
(handler structs, task-graph semantics, performAction3). This skill is the
setup path and the pipeline pattern — read those two when something in
here breaks.
The mental model
| CI/CD concept | Teamcenter/Dispatcher equivalent |
|---|---|
| Pipeline / workflow | An EPMTaskTemplate process |
| Stage | A Do task, its Start action firing a translation request |
| Job runner | Dispatcher (Module + Scheduler + DispatcherClient) |
| Job script | The translator executable (translator.xml + Service.properties) |
| "Wait for the job" | EPM-hold on the Do task's Complete action |
| Job success signals the pipeline | The job performs the task's Complete action itself when it finishes |
| Fan-out (parallel stages) | Multiple Do tasks with dependency_task_templates pointing at the same predecessor |
| Fan-in / gate | A Review or EPMOrTaskTemplate, which starts only once every fan-out predecessor reaches Complete — proven live, see below |
| Pass/fail branch | Success (solid) vs Fail (dashed) paths out of a task, drawn in Workflow Designer |
| Pipeline parameterization | A JSON config file the router script reads, selecting behavior per invocation |
| Correlating which stage fired | The marker dataset's name, read out of the staged TranslationTask XML |
Part 1 — Setting up one translator/handler, start to finish
Do these in order. Skipping the order is exactly what costs the most time — several of these steps look independently optional and are not.
1. Register the translator
C:\apps\PLM\DSP\Module\conf\translator.xml:
<MyStage provider="SIEMENS" service="mystage" isactive="true" OutputNeeded="false">
<TransExecutable dir="C:\apps\PLM\DSP\Module/Translators/MyStage" name="MyStage.bat" />
</MyStage>
OutputNeeded="false" + no .Load binding = a direct translator (it
does its own I/O, nothing for basic.DatabaseOperation to load back). Use
this for pipeline-stage scripts; reserve indirect translators for the
classic CAD-file-in/CAD-file-out case (see tc-dispatcher-requests).
C:\apps\PLM\DSP\DispatcherClient\conf\Service.properties:
Translator.SIEMENS.mystage.Prepare=com.teamcenter.ets.translator.ugs.basic.TaskPrep
Translator.SIEMENS.mystage.Duplicate=false
☠ Provider name casing must match translator.xml's provider=
attribute byte for byte, everywhere else this string appears — the handler
argument, the preference name, all of it. SIEMENS (all-caps) here means
SIEMENS everywhere, not Siemens. A mismatch fails with an error that
names the WRONG value (echoes back what was supplied, not what's
registered), which reads exactly like a missing class/jar instead of a
four-character typo. Full incident writeup in tc-dispatcher-requests.
2. Regenerate the Windows services — do not just restart
The Dispatcher Windows services snapshot their classpath into the registry
at install time. Restart-Service reloads the process against that stale
snapshot; it does not re-read Service.properties/translator.xml.
After step 1, every time, on both services:
$vmCred = Import-Clixml "$env:USERPROFILE\.xcelerator\credentials\vm-admin.cred.xml"
$session = New-PSSession -ComputerName <host> -Credential $vmCred
Invoke-Command -Session $session -ScriptBlock {
Stop-Service -DisplayName "Teamcenter DispatcherClient*" -Force
Stop-Service -DisplayName "Teamcenter Dispatcher Module*" -Force
cmd /c "C:\apps\PLM\DSP\DispatcherClient\bin\runDispatcherClientWinService.bat -r"
cmd /c "C:\apps\PLM\DSP\Module\bin\moduleWinService.bat -r"
cmd /c "C:\apps\PLM\DSP\DispatcherClient\bin\runDispatcherClientWinService.bat -i"
cmd /c "C:\apps\PLM\DSP\Module\bin\moduleWinService.bat -i"
Start-Service -DisplayName "Teamcenter DispatcherClient*"
Start-Service -DisplayName "Teamcenter Dispatcher Module*"
}
-i/-c install, -r removes; install does not auto-start. Both scripts
need the TC environment sourced first if you're calling them directly rather
than through cmd /c against the whole batch file, which already does it
internally. Matches a real Siemens KB for a different symptom
(supportalorchestrationservice, missing jar) — same mechanism, "regenerate
the service" is the documented fix there too.
3. Create the extract preference — and know the object-type ceiling first
TSTK-CreateTranslationRequest (the workflow handler, step 4) can only
target a small, fixed set of OOTB dataset types — MISC, Text, and a
handful of others (confirm the current list before relying on a count; get
it from Siemens product documentation/support rather than assuming). It
cannot target a custom or BMIDE-defined dataset type (Mdw0MDModel, or
any site-authored type) — this is a real, confirmed product limitation, not
a config gap you can preference your way around. Decide the source dataset
type against that list before writing anything else.
Then, the preference: <Provider><Service>_ets_ds_types, literal case, no
separator — e.g. SIEMENSmystage_ets_ds_types. SOA cannot write
site-scope preferences — setPreferences2/setPreferencesDefinition
report 200 OK and write nothing (or only the calling user's own scope).
Use the CLI, and get the environment right or the exe dies at launch with
STATUS_DLL_NOT_FOUND before it parses a single argument:
call C:\apps\PLM\tc_root\tc_menu\tc_Vanilla_Env.bat
cd /d C:\apps\PLM\tc_root\bin
preferences_manager.exe -u=infodba -p=infodba -g=dba -mode=import -scope=SITE ^
-action=override -context=Teamcenter -file=C:\path\to\pref_import.xml
tem_init.bat is NOT this script — it sets three env vars and no PATH.
XML shape (get it right by exporting a working sibling preference first, not by hand-authoring from a guess):
<preferences version="10.0">
<category name="MyPipeline">
<preference name="SIEMENSmystage_ets_ds_types" type="String" array="true"
disabled="false" protectionScope="Site" envEnabled="false">
<preference_description>Dataset type(s) the mystage translator extracts from.</preference_description>
<context name="Teamcenter"><value>MISC</value></context>
</preference>
</category>
</preferences>
Write it with [System.IO.File]::WriteAllText + a no-BOM UTF8Encoding —
Set-Content -Encoding UTF8 on Windows PowerShell 5.1 adds a BOM this
importer rejects silently.
★ Verify with -mode=export, never trust the import's own stdout. A
"the import operation has succeeded" message is not proof — the admin guide
documents that preferences "not yet declared at the site level" are silently
skipped, logged only to the run's own report file
(C:\Temp\preferences_manager_<timestamp>.log), which is easy to never
read. Read that report file. Then call
Administration-2011-05-PreferenceManagement/refreshPreferences and confirm
with getPreferences as a different user than the importer.
4. Bind the handler to a Do task
{"input":[{
"clientID":"h1","handlerName":"TSTK-CreateTranslationRequest",
"taskTemplate":"<EPMDoTaskTemplate uid>","businessRule":"","handlerType":"Action",
"handlerToUpdate":"","action":4,
"ruleQuorum":0,"changeExecutionOrder":0,
"additionalData":{"-ProviderName":["SIEMENS"],"-ServiceName":["mystage"],
"-DatasetTypeName":["MISC"],"-Priority":["1"]}
}]}
(Workflow-2019-06-Workflow/createOrUpdateHandler; action is an integer —
Assign 1, Start 2, Complete 4.)
- Placement decides the pipeline semantics. Bind to Start if the job is fire-and-forget (the pipeline stage's own task completes immediately; the job runs in the background). Bind to Complete if you want the handler to fire only once the task is otherwise done.
- The target must be an item revision, carrying a dataset of the declared type. One request per matching dataset. It refuses site-custom dataset types at the object level regardless of what the preference says (step 3).
- Mandatory args:
-ProviderName,-ServiceName,-DatasetTypeName. Optional:-Priority. (getSupportedHandlerArgumentsconfirms this split live if it's ever in doubt on a given tier.)
5. Wire the "wait for the job" pattern, if the pipeline needs to block
Do task ("stage")
├─ Start action : TSTK-CreateTranslationRequest
└─ Complete action : EPM-hold (Do tasks carry this by default)
EPM-hold checks task_result; while it isn't Completed, the task stays
open. The translator itself performs the task's Complete action when it
finishes — that's what makes the pipeline actually wait on the job rather
than racing ahead. Siemens ships two integrations built exactly this way
(DOCMGT-render-document-revision, CAE-simulation-process-launch-handler),
so this is the supported pattern, not a workaround.
6. Fan-out / fan-in — running several stages in parallel with one gate
- Fan-out: point several Do task templates'
dependency_task_templatesat the same predecessor, same action code (2, the normal predecessor→successor edge). Each gets its own marker dataset + handler, so each fires its own translation request independently once the predecessor completes. - Fan-in / gate: a Review task (or
EPMOrTaskTemplatefor a pure OR-join) whose predecessors are all the fan-out stages, same edge code2. ★★★ Proven live, repeatedly, on TC 2606: this gate does not start until EVERY predecessor reachesCompleted— not merelyStarted. Watched a review sitPendingthrough 0/3, 1/3, 2/3 predecessors complete, and flip toStartedthe instant the third did, timestamped to the second. The dependency-edge constant is literally namedstartDependencyTaskRefin the underlying PLMXML — don't let that name mislead you; behaviorally it waits for Complete. - Pass/fail routing: a task's dashed Fail path and an
EPMOrTaskTemplate"Failure Accumulator" pattern let multiple independent failure sources (a rejected review, a failed condition, a stage that reports failure) converge on one terminal path — confirmed live via a real diagram capture: a rejected-review dashed edge and a failed-condition dashed edge landing on the identical downstream OR gate. - ⚠ Rewiring
dependency_task_templateson an already-built template is possible over SOA (createOrUpdateTemplate'sadditionalData, not PLMXML re-import) — useful for removing a stage or changing the graph after the fact without rebuilding from scratch. Full struct and the correction to an earlier "create-time only" claim are intc-workflow-authoring.
Part 2 — The router pattern: one script, many pipeline stages
Registering a new translator.xml/Service.properties/preference triple
per stage is real infrastructure work (Part 1, steps 1–3) each time. The
router pattern trades that off: one Dispatcher service, one script, many
stage behaviors selected by config, so adding a new pipeline stage becomes
a config-file edit instead of a new service registration.
node dispatcher-entry.mjs --model <id> --rev-id <A> --name "<n>" --workdir <dir> \
--config <adapters.json>
dispatcher-entry.mjsreads--config'spipelinearray and looks each name up in a fixed registry of stage functions (extract/resolve/checkin/verify/... — whatever your pipeline needs). Refuse an empty pipeline and refuse an unknown stage name outright rather than silently skipping —all([])istrue, so a pipeline with zero stages "succeeds" having done nothing, and a typo'd stage name silently dropped means the pipeline that ran isn't the one that was configured. Both are real, cheap mistakes to guard against explicitly.- Two Dispatcher services (two
translator.xmlentries, twoService.propertiesPrepare mappings, two Windows-service classpath regens) can point at the same script bytes, differing only in whichadapters.jsoneach service'stranslator.xml<TransExecutable>passes via--config. This is what makes "one router, N pipelines" actually true rather than aspirational — proven with three separate services sharing onedispatcher-entry.mjs. - Correlating which stage fired, without a custom clientoption:
TSTK-CreateTranslationRequestmakes the matched marker dataset the created request's primary object. The stagedTranslationTaskXML one level above the working directory carries<UserDefAttribute name="datasetname">— read that, notkeyValueArgs(which does not reach a translator as command-line options despite looking like it should — proven: atranslator.xml<Options>block usingoptionkeyreceives empty values even withkeyValueArgspopulated). A router keyed off the marker's name lets several identically-shaped Do tasks (each with its own uniquely-named marker) all hit the same service and still behave differently per task. - Deliberate per-stage delay + pass/fail, for testing the pipeline shape
itself: give the config a per-marker-name entry
(
{"delayMs": 12000, "fail": true}) and have the stage functionsetTimeoutthen throw/exit non-zero. This is how to prove staggered timing and a failure path actually propagate, independent of whatever real work a stage eventually does.
What "job success" looks like from the workflow's side
☠ A Do task's own completion is a side effect of the handler firing on
Start/Complete — it is NOT gated on the router script's actual exit code
unless you deliberately wire it that way (Part 1, step 5, EPM-hold).
Without EPM-hold, a Do task can show Completed in Teamcenter the instant
the handler fires, while the actual job is still running (or has already
failed) in the Dispatcher queue. If the pipeline needs "stage genuinely
succeeded" to gate the next stage, EPM-hold + the job performing Complete
itself (step 5) is the only mechanism that ties them together — read the
task state alone and you're reading "the trigger fired," not "the job
passed."
Verifying any of this actually ran — read the Dispatcher logs, not the workflow
getProperties on a DispatcherRequest returns almost nothing useful
(current_state/provider_name/service_name don't exist as readable
properties — see tc-dispatcher-requests for the queryDispatcherRequests
workaround). The ground truth is on the Dispatcher host:
C:\apps\PLM\DSP\Logs\Dispatcher\task\<taskid>\<taskid>_dc.log
Grep for the service name across the newest task directories after a run
(Get-ChildItem ... -Directory | Sort LastWriteTime -Descending). Real
failure signatures to recognize immediately rather than re-diagnosing from
scratch:
| Log line contains | Means |
|---|---|
Cannot find task preparation class specified by service configuration for <Provider> <service> |
Provider/service casing mismatch, OR the Windows service's classpath predates a Service.properties change — check both before assuming a missing jar |
Entering custom TaskPrep class for: ... TaskPrep@... with no following ERROR |
Class loaded and ran — a real signal of progress, check what follows |
SoaHelper.getPreference(String)" is null (NullPointerException in BasicConfigHelper) |
The _ets_ds_types preference isn't resolving — re-check step 3, including the report-file trap |
Dispatcher Module not available for Service: <name> |
Nothing has registered/regenerated the service on the Module side yet |
Task = <id>, Status = Started Translation!!! then Completed successfully! |
The job genuinely ran and finished — this is what real success looks like, not a clean-looking Task Saved alone |
Known-good vs still-open, as of this writing
Proven live, on real TC 2606 hardware: the fan-in gate behavior
(Complete, not Start), the ProviderName casing fix, the Windows-service
classpath regen fix, the router-serves-multiple-services pattern (three
services, one script, confirmed via distinct translator.xml entries all
resolving to the same .mjs file), and the site-preference CLI recipe
including the no-BOM/env-script/report-file traps.
Still open, not proven end to end: a hand-registered custom service's
_ets_ds_types preference resolving reliably at runtime — one instance of
this got stuck with no root cause found despite a confirmed-correct database
write, while a sibling service on the identical code path resolved fine.
See tc-dispatcher-requests's dated entry for the full trail before
re-deriving it. If you hit this, read the preferences_manager import
report file before anything else — that lead was never actually exercised.
Related: tc-dispatcher-requests, tc-workflow-authoring,
tc-soa-payload-shapes, tc-vm-operations, tc-verify-and-cleanup.
Generated from skills/tc-dispatcher-cicd-pipeline/SKILL.md in the tc-automation-skills library, which is the canonical copy and also serves as the agent skill set for Teamcenter work.