Back up what a part marker holds, without the vendor tool
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.
This commit is contained in:
@@ -47,6 +47,13 @@ $script:SHOPDBREG = 'HKLM:\SOFTWARE\GE\ShopDB'
|
||||
$script:KEYFILE = 'C:\Enrollment\shopdb-key.txt'
|
||||
$script:PCCONFIG = 'C:\Enrollment\pc-config.txt'
|
||||
|
||||
# The action ShopDB reported for the most recent post, per kind: 'created',
|
||||
# 'noop', or '' when nothing has been posted. Send-ShopdbBackup returns only
|
||||
# accepted/not-accepted, and changing that would break its existing caller - but
|
||||
# a share kind has to know the difference, because it decides whether the
|
||||
# snapshot directory it just wrote is a new revision or a duplicate to remove.
|
||||
$script:LASTACTION = @{}
|
||||
|
||||
|
||||
function Get-ShopdbLogPath {
|
||||
param([Parameter(Mandatory)][string]$Kind)
|
||||
@@ -294,6 +301,12 @@ function Send-ShopdbBackup {
|
||||
[string]$ContentHash,
|
||||
[string]$SharePath,
|
||||
[string]$SourceFileName,
|
||||
# Optional description of a share kind's snapshot - what is in it, not
|
||||
# the bytes. ShopDB stores it so an asset panel can list the contents
|
||||
# and diff them against the previous snapshot; without it the server
|
||||
# would have to mount the share, which it refuses to do. Advisory:
|
||||
# dedup still keys on ContentHash.
|
||||
[hashtable]$Manifest,
|
||||
[int]$TimeoutSec = 30)
|
||||
|
||||
$kind = $Context.Kind
|
||||
@@ -327,6 +340,7 @@ function Send-ShopdbBackup {
|
||||
}
|
||||
if ($ContentHash) { $payload['contenthash'] = $ContentHash }
|
||||
if ($SharePath) { $payload['sharepath'] = $SharePath }
|
||||
if ($Manifest) { $payload['manifest'] = $Manifest }
|
||||
|
||||
$uri = '{0}/api/collector/backups' -f $Context.BaseUrl
|
||||
$size = if ($Bytes) { '{0} bytes' -f $Bytes.Length } else { $SharePath }
|
||||
@@ -336,11 +350,12 @@ function Send-ShopdbBackup {
|
||||
try {
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
$resp = Invoke-RestMethod -Uri $uri -Method Post `
|
||||
-Body ($payload | ConvertTo-Json -Compress) `
|
||||
-Body ($payload | ConvertTo-Json -Compress -Depth 6) `
|
||||
-ContentType 'application/json' `
|
||||
-Headers @{ 'X-API-Key' = $Context.CollectorKey } `
|
||||
-TimeoutSec $TimeoutSec -EA Stop
|
||||
|
||||
$script:LASTACTION[$kind] = "$($resp.data.action)"
|
||||
switch ("$($resp.data.action)") {
|
||||
'created' { Write-ShopdbBackupLog -Kind $kind -Message (
|
||||
'New revision {0} recorded.' -f $resp.data.backuprevisionid) }
|
||||
@@ -369,7 +384,99 @@ function Send-ShopdbBackup {
|
||||
}
|
||||
|
||||
|
||||
function Add-ShopdbBackupSkip {
|
||||
<#
|
||||
Record that a cycle was skipped, and why.
|
||||
|
||||
A skipped cycle posts NOTHING to ShopDB, deliberately: lastseenat means
|
||||
"this config was confirmed still current", so posting a no-op to keep a
|
||||
marker green would be a lie that hides a backup which has not run for a
|
||||
month. The staleness badge is what surfaces that.
|
||||
|
||||
What ShopDB cannot know is WHY, so the count and the reason are kept
|
||||
locally and carried into the next successful snapshot's manifest. A
|
||||
marker that captures once a week because its port is busy every shift
|
||||
change then says so on its own asset page.
|
||||
#>
|
||||
param([Parameter(Mandatory)][string]$Kind,
|
||||
[Parameter(Mandatory)][string]$Reason)
|
||||
|
||||
$file = Join-Path $script:LOGDIR ('{0}-backup.skips' -f $Kind)
|
||||
$count = 0
|
||||
$first = (Get-Date).ToUniversalTime().ToString('o')
|
||||
if (Test-Path $file) {
|
||||
try {
|
||||
$existing = Get-Content -LiteralPath $file -Raw | ConvertFrom-Json
|
||||
$count = [int]$existing.count
|
||||
if ($existing.first) { $first = [string]$existing.first }
|
||||
} catch { }
|
||||
}
|
||||
if (-not (Test-Path $script:LOGDIR)) {
|
||||
New-Item -ItemType Directory -Path $script:LOGDIR -Force -EA SilentlyContinue | Out-Null
|
||||
}
|
||||
[pscustomobject]@{
|
||||
count = $count + 1
|
||||
first = $first
|
||||
last = (Get-Date).ToUniversalTime().ToString('o')
|
||||
reason = $Reason
|
||||
} | ConvertTo-Json -Compress | Set-Content -Path $file -EA SilentlyContinue
|
||||
}
|
||||
|
||||
|
||||
function Get-ShopdbBackupSkips {
|
||||
<#
|
||||
Skipped cycles since the last successful capture: @{count; first; last;
|
||||
reason}, or $null when none. Read into the manifest, then cleared by
|
||||
Clear-ShopdbBackupSkips once a capture actually succeeds.
|
||||
#>
|
||||
param([Parameter(Mandatory)][string]$Kind)
|
||||
$file = Join-Path $script:LOGDIR ('{0}-backup.skips' -f $Kind)
|
||||
if (-not (Test-Path $file)) { return $null }
|
||||
try {
|
||||
$data = Get-Content -LiteralPath $file -Raw | ConvertFrom-Json
|
||||
return @{
|
||||
count = [int]$data.count
|
||||
first = [string]$data.first
|
||||
last = [string]$data.last
|
||||
reason = [string]$data.reason
|
||||
}
|
||||
} catch { return $null }
|
||||
}
|
||||
|
||||
|
||||
function Clear-ShopdbBackupSkips {
|
||||
param([Parameter(Mandatory)][string]$Kind)
|
||||
Remove-Item -LiteralPath (Join-Path $script:LOGDIR ('{0}-backup.skips' -f $Kind)) `
|
||||
-Force -EA SilentlyContinue
|
||||
}
|
||||
|
||||
|
||||
function Get-ShopdbLastBackupAction {
|
||||
<#
|
||||
What ShopDB did with the most recent post for this kind: 'created',
|
||||
'noop', or '' if nothing was posted or the post failed.
|
||||
#>
|
||||
param([Parameter(Mandatory)][string]$Kind)
|
||||
if ($script:LASTACTION.ContainsKey($Kind)) { return $script:LASTACTION[$Kind] }
|
||||
return ''
|
||||
}
|
||||
|
||||
|
||||
function Test-ShopdbBackupNoop {
|
||||
<#
|
||||
True when the last post for this kind changed nothing. A share kind uses
|
||||
this to delete the snapshot it just wrote: a no-op proves byte-identical
|
||||
content already exists in the previous snapshot, so removing the copy
|
||||
loses nothing and stops one identical directory accumulating per cycle.
|
||||
#>
|
||||
param([Parameter(Mandatory)][string]$Kind)
|
||||
return ((Get-ShopdbLastBackupAction -Kind $Kind) -eq 'noop')
|
||||
}
|
||||
|
||||
|
||||
Export-ModuleMember -Function Initialize-ShopdbBackup, Send-ShopdbBackup,
|
||||
Write-ShopdbBackupLog, Write-ShopdbQuietState, Clear-ShopdbQuietState,
|
||||
Get-ShopdbRegValue, Get-ShopdbCollectorKey, Get-ShopdbMachineNumber,
|
||||
Get-ShopdbIntervalHours, Get-ShopdbLogPath
|
||||
Get-ShopdbIntervalHours, Get-ShopdbLogPath,
|
||||
Get-ShopdbLastBackupAction, Test-ShopdbBackupNoop,
|
||||
Add-ShopdbBackupSkip, Get-ShopdbBackupSkips, Clear-ShopdbBackupSkips
|
||||
|
||||
Reference in New Issue
Block a user