Every failure of Get-TelesisPortConfig was filed as "no Telesis marker
configured on this PC" and exited 0. That is correct for the many PCs which are
not marker bays, and wrong for a bay whose registry key EXISTS and could not be
read: a half-configured marker was silently never backed up again, which is
exactly the failure shape this feature was built to prevent. The absent-key case
now throws a distinguishable exception and stays quiet; anything else is logged
every cycle and counted as a skip.
Get-TelesisFileList was unbounded. The receiver drives that loop, so a
controller repeating a name or answering noise on a marginal cable would spin
for ever - and not harmlessly, because it holds the MARKING port while
GE-Enforce waits on the script with no timeout, so one chatty controller wedges
the enforcement cycle and the operator cannot mark a part. Now capped by count,
by elapsed time, and by repeat detection, all of which THROW: a partial listing
presented as a complete snapshot would be worse than failing.
Baud and Data Bits parse with TryParse. Note the narrower reason than it looks:
[int]'' is 0 in PowerShell, so empty and non-numeric values were already handled
by the zero-check. What was not handled is a numeric string too large for Int32,
which throws on the cast and would have been filed as "not a marker bay".
Verified in the win11 VM as SYSTEM, against both the old and new module: absent
key stays quiet, a key without Port Id is loud, garbage and out-of-range baud
both fall back to 9600, a real 19200 is still honoured, a repeating controller
stops at once and endless unique names stop at the cap.
The snapshot path stored against a marker read "W:\backup\partmarker\...". That
mapping exists only inside the SYSTEM logon session, only while the GE-Enforce
cycle is running, and only on that one bay - so the path recorded in ShopDB
resolved to nothing for the person most likely to need it.
Writing still goes through the drive letter, deliberately: that is the
connection GE-Enforce authenticated, and reaching the same server by another
name costs a second authentication or fails outright with error 1219. Only the
recorded path changes.
Resolving a mapped drive to its share needs all three of DisplayRoot, Root and
the WMI ProviderName, because which one is populated depends on how the drive
was made: net use mappings (what GE-Enforce creates) fill DisplayRoot, while a
New-PSDrive to a UNC leaves DisplayRoot EMPTY and puts the share in Root. A
local drive and an already-UNC path both fall through untouched.
The script had no CmdletBinding, so PowerShell dropped unrecognised named
arguments into $args instead of failing. A typo in a manifest entry Args -
"-WindowStartMinutes 45" - would have been ignored silently, and the capture
would have run at the wrong time with nothing in the log to explain it. That
is exactly how an hour went into debugging a time gate that was working.
Also adds WindowStartMinute so the window can open at :45 rather than on the
hour. A shift change is a moment, not an hour, and the first run of the day is
easiest to watch when it lands when somebody expects it.
Shopfloor PCs intermittently lose S: and ask for credentials while navigating
the identical UNC path in Explorer works fine. Four different causes produce
that same appearance - a persistent reconnect that raced the network, a stored
credential whose target string does not cover the name form in use, two
identities contending for one server, or a password that rotated - and each
needs a different fix. From the desk they are indistinguishable.
Test-ShareAccess.ps1 collects what separates them: reachability of both name
forms, the mappings and the account each remembers, the exact credential target
strings, Kerberos tickets, live SMB sessions and the identities holding them,
and the SMBClient/Connectivity log. Then it names the causes the evidence
supports rather than leaving a wall of output to interpret.
Read-only by construction: it maps nothing, deletes nothing, stores no
credential, and prints usernames and a password-present flag but never a
password, because its log lands where techs read it. Safe on a bay while
somebody is working.
It reports which identity it is running as, because mappings and SMB sessions
are per logon session: run as the user to explain S:, as SYSTEM to explain W:
and the backup scripts. A drive missing in one is genuinely absent in the
other, and mistaking that for a fault is easy.
Also derives the marker backup's share root from the script's own location
rather than a hardcoded UNC. GE-Enforce mounts the share as W: with SFLD
credentials, and reaching the same server by a different name than the mount
used costs a second authentication - or fails outright with 1219 - for reasons
the error text never mentions. The script's own path is the one handle
guaranteed to name the connection that already exists.
This runs as SYSTEM, which authenticates to the network as the machine account.
A SYSTEM process reaching a UNC path without credentials gets an access-denied
that Test-Path reports as "not found", so a permissions problem reads as a
missing directory and sends the next person hunting the wrong thing. The
backups registry already carries that warning; the client had not acted on it.
On a write failure the log now names the likely cause, and the two conditions
that make it work: the entry must run under the GE-Enforce cycle, since the
share is mounted only for its duration and all SYSTEM processes share the logon
session that holds the connection, and the share ACL has to admit computer
accounts.
Verified against an unreachable UNC on a bay with a real controller: capture
succeeded, the write failed, the diagnostic was logged and the cycle was
counted as a missed backup.
A part marker has no machine number of its own - the device is what a backup
belongs to, and C:\Enrollment\asset-id.txt is what names it. Reading that file
inside the shared machine-number lookup looked like the tidy place for it and
was wrong: marker bays run eDNC too, so Backup-NtlarsSettings.ps1 reaches the
same lookup, and it would have started posting a machine's DNC settings under
the marker's asset number where today it exits quietly. The marker script asks
for the fallback explicitly instead, which is what -MachineNumberFallback is
for.
Also from review:
Returning a byte array bare lets the pipeline enumerate it, so an EMPTY file
came back as $null, ComputeHash threw, and with ErrorActionPreference=Continue
the manifest entry silently kept the previous file's hash - a corrupted
manifest stored as a good revision. Empty parameter files are plausible.
Reading registry values directly under StrictMode throws when one is absent, so
the "no Port Id" guard was unreachable and a partially configured marker bay
reported itself as having no marker at all, permanently and quietly. Values are
now read through a presence check with the fleet's own defaults.
The outside-window quiet state embedded the hour, so the say-once module said
it hourly. Command replies now have their checksum verified like data blocks
do, since a corrupted listing entry would otherwise be trusted and a file
requested under a name the controller does not have. A controller that
genuinely holds nothing is no longer reported as a failed capture. A snapshot
that could not be written to the share now counts as a missed backup like the
rest, and the header no longer claims orphaned snapshots get tidied - they are
kept on purpose, because a timed-out post may still have been recorded.
Telesis pinstamp controllers have never been backed up. The vendor's own
utilities, Backup470 and Backup420, are GUIs with no command line, so nothing
could capture a marker on a schedule - and a controller swap meant retyping
patterns from memory.
TelesisMarker.psm1 speaks the protocol directly, reversed from a capture of
Backup470 against a TMC470 and verified against hardware: a pattern pulled by
this code is byte-identical to the one the vendor tool saves, and a pattern
written back survives a power cycle. A TMC420 was captured the same way and
differs in exactly one respect - it exposes no parameter files - so one
implementation covers both families, with content classes chosen from a Type I
status probe rather than a per-model dialect.
Backup-PartMarker.ps1 captures everything the controller holds into a
timestamped snapshot on the share and records it in ShopDB against the marker
asset. Snapshots rather than a mirrored directory, because a mirror forces a
choice between deleting the only copy of a retired pattern and claiming a
pattern that no longer exists; a snapshot has neither problem, and the manifest
hash means an unchanged controller writes nothing.
The marker's serial port is shared with eDNC and is exclusive-open, so captures
happen only at shift changes - 14:00 normally, when someone is on site to fix a
problem, with 06:00 and 22:00 engaging only once the backup is overdue. The
capture yields the port within a second if the marker application starts
mid-run, and cycles missed to a busy port are counted and carried into the next
snapshot, since posting a no-op to keep the health badge green would hide
exactly the failure the badge exists to report.
TYPE 'R' deletes a file on the controller, immediately and without
confirmation. It is documented in the module header and deliberately not
implemented.
Sixteen files that run on every shopfloor PC existed only on the SFLD share.
The cost showed up while debugging the NTLARS backup: the script that posts to
ShopDB could not be read, reviewed or diffed, so its behaviour was inferred
from log output for most of a day. It turned out to hold a silent fallback that
had been governing the whole fleet for months.
Imported as-is from tsgwp00525-v2, no edits:
lib/ShopdbBackupClient.psm1 the shared backup client
scripts/Backup-NtlarsSettings.ps1 converted to use it
scripts/Set-ShopdbCollectorKey.ps1 collector credential delivery
scripts/Test-RegExport.ps1 exercises the .reg codec with mocks
scripts/Set-EventSaver*.ps1 kiosk power / screensaver / disable
scripts/Setup-OpenText.* OpenText install + toolbar
scripts/Migrate-PCType.ps1, Select-KioskType.ps1, Set-FmsHostsEntry.ps1,
scripts/ensure-vnc-firewall.ps1, Install-AcroReader.cmd, Install-Oracle11r2.cmd
lib/Install-FromManifest.ps1 is also updated from the share, which was 37 lines
AHEAD of this repo and purely additive: the Add-EnforceResult reporting added
during the kiosk API cutover, done live and never committed back. Nothing was
removed.
Checked for embedded secrets before committing; there are none.
Set-ShopdbCollectorKey deliberately reads its token from a sibling file on the
share rather than holding it, so the script is safe to track.
The share remains what actually runs. This makes it reviewable, and makes the
next drift visible as a diff rather than a surprise.
Install-FromManifest.ps1:
- Get-CurrentMachineNumber reads the eDNC/DNC registry FIRST (reassignment-
authoritative), falling back to C:\Enrollment\machine-number.txt. The txt is
written once at imaging and is NOT updated on reassignment, so txt-first
gated reassigned bays on a stale number.
- Per-entry try/catch in the app loop: a single entry that throws no longer
aborts the whole scope (skipping every later entry + the status write). It is
logged, counted failed, and the loop continues. This was silently killing the
collections scope at the MTConnect Makino entry, which also stopped the
ShopDB asset reporter (a later entry) from ever running.
Deploy-ShopfloorStartLayout.ps1 (new): local-DSC port of the Intune
desktop-weblinks + Start-menu pins (copies .url/.lnk to Public Desktop +
All-Users Start Menu, writes the ConfigureStartPins JSON policy, resets
start2.bin + restarts the shell). Verified on Win11: pins render after logon.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>