Haven’t posted in awhile. I use these scripts a bunch for my Lynx’s. It is always a pain to port methods from device to device, so I made the dumb powershell script to do. It isn’t well tested, but it gets it close enough so I can open up the sub-methods if anything went wrong. There is an assumption that your methods and scripts have to be contained in their own folders with the same name
\MethodManager4
|_Workspaces
|_LM999
|_Methods
| |_FOOBAR/*.met
| |...
|
|_Scripts
|_FOOBAR/*.cs
|...
import.ps1
param(
[Parameter(Mandatory=$true)]
[string]$workspace,
[Parameter(Mandatory=$true)]
[string]$method
)
# Ensure that the provided zip file exists
if (-not (Test-Path $method)) {
Write-Error "The provided zip file path does not exist."
exit
}
# Create a temporary directory to extract the zip contents
$tempDir = [System.IO.Path]::GetTempFileName()
Remove-Item $tempDir
New-Item -ItemType Directory -Path $tempDir
# Extract zip file to the temporary directory
Expand-Archive -Path $method -DestinationPath $tempDir
# Define base target paths
$baseMethodsPath = "C:\MethodManager4\Workspaces\$workspace\Methods"
$baseScriptsPath = "C:\MethodManager4\Workspaces\$workspace\Scripts"
# Check if Methods directory exists in the archive and copy if it does
if (Test-Path "$tempDir\Methods") {
Get-ChildItem "$tempDir\Methods" -Directory | ForEach-Object {
$destPath = Join-Path $baseMethodsPath $_.Name
Copy-Item -Path $_.FullName -Destination $destPath -Recurse -Force
#Resolve the deck location names in the methods
Write-Host "Resolving .met files in: $destPath"
& $env:USERPROFILE\cosmos\lynx\resolve.ps1 -workspace $workspace -method $destPath
}
}
# Check if Scripts directory exists in the archive and copy if it does
if (Test-Path "$tempDir\Scripts") {
Get-ChildItem "$tempDir\Scripts" -Directory | ForEach-Object {
$destPath = Join-Path $baseScriptsPath $_.Name
Copy-Item -Path $_.FullName -Destination $destPath -Recurse -Force
}
}
# Cleanup
Remove-Item -Path $tempDir -Recurse -Force
export.ps1
# Export a method from the lynx workspace and
# package up the folder in Methods/ and Scripts/
# into a zip folder.
param(
[Parameter(Mandatory=$true)]
[string]$workspace,
[Parameter(Mandatory=$true)]
[string]$method
)
# Define the source directories
$lynxActionsPath = "C:\MethodManager4\Workspaces\$workspace\Methods\$method"
$lynxScriptsPath = "C:\MethodManager4\Workspaces\$workspace\Scripts\$method"
# Check if the Actions directory exists
if (-not (Test-Path $lynxActionsPath)) {
Write-Error "The provided source path for Actions does not exist."
exit
}
# Create a temporary directory to organize the contents for zipping
$tempDir = [System.IO.Path]::GetTempFileName()
Remove-Item $tempDir
New-Item -ItemType Directory -Path $tempDir
# Copy the contents of lynxActions to the temp directory
Copy-Item -Path $lynxActionsPath -Destination "$tempDir\Methods\$method" -Recurse -Force
# Copy the contents of lynxScripts to the temp directory only if it exists
if (Test-Path $lynxScriptsPath) {
Copy-Item -Path $lynxScriptsPath -Destination "$tempDir\Scripts\$method" -Recurse -Force
}
# Create the zip file with the desired naming format
$dateTime = Get-Date -Format "MM-dd-yy--HH-mm-ss"
$zipName = "$method-$dateTime.zip"
$zipPath = "$env:USERPROFILE\Downloads\$zipName"
Compress-Archive -Path $tempDir\* -DestinationPath $zipPath
# Cleanup
Remove-Item -Path $tempDir -Recurse -Force
# Output the path of the created zip file
Write-Output "Zip file created at: $zipPath"
resolve.ps1
This is really the meat of the script that could use the most advancement (like importing to instruments with multiple heads or integrated features) - barebones works for me though.
# Given a target workspace $workspace and a Method source folder
# resolve the lynx deck position naming of the source to match the
# deck location names of the the target workspace
#
# ERRORS
# 1: Target workspace does not have enough locations to fit source method
param(
[Parameter(Mandatory=$true)]
[string]$workspace,
[Parameter(Mandatory=$true)]
[string]$method
)
function LoadNameValuesFromConfig {
# Load XML content from the configuration file and return the name values
Write-Debug "Loading name values from config..."
[xml]$configXml = Get-Content "C:\MethodManager4\Workspaces\$workspace\Configuration\Lynx.Left.Worktable.config"
$loadedNames = $configXml.SelectNodes("//MMLocation/Name").InnerText
Write-Debug "Loaded $($loadedNames.Count) name values."
return $loadedNames
}
Write-Debug "Loaded Name values from config: $($nameValues -join ', ')"
# Add the Levenshtein distance computation method
Add-Type -TypeDefinition @"
using System;
public static class levenshtein {
public static int Distance(string s, string t) {
int n = s.Length;
int m = t.Length;
int[,] d = new int[n + 1, m + 1];
if (n == 0) return m;
if (m == 0) return n;
for (int i = 0; i <= n; d[i, 0] = i++) ;
for (int j = 0; j <= m; d[0, j] = j++) ;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;
d[i, j] = Math.Min(
Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
d[i - 1, j - 1] + cost
);
}
}
return d[n, m];
}
}
"@ -Language CSharp
function FindBestMatchingString($str) {
# Returns the best matching string from $nameValues for a given string $str
Write-Debug "Finding best match for '$str'."
if (-not $nameValues -or $nameValues.Length -eq 0) {
Write-Debug "Name values list is empty or null. Unable to find a match."
return $null
}
$bestMatch = $nameValues[0] # Default to the first string in the list
$bestScore = [int]::MaxValue
foreach ($name in $nameValues) {
$score = [levenshtein]::Distance($str, $name)
if ($score -lt $bestScore) {
$bestMatch = $name
$bestScore = $score
}
}
Write-Debug "Best match for '$str' is '$bestMatch' with score $bestScore"
return $bestMatch
}
Get-ChildItem $method -Recurse -Filter "*.met" | ForEach-Object {
# Refresh name values from the config before processing the file
$nameValues = LoadNameValuesFromConfig
if (-not $nameValues -or $nameValues.Length -eq 0) {
Write-Error "Failed to load name values from the configuration. Ensure the XML file is present and contains <Name> nodes."
return
}
Write-Debug "Processing file: $_"
[xml]$xmlContent = Get-Content $_.FullName
# Select nodes for replacement
$nodes = $xmlContent.SelectNodes("//Collection[@name='WorktableResourceMaps']//Simple[@name='LocationName']")
Write-Debug "Found $($nodes.Count) nodes to process in $_.FullName"
foreach ($node in $nodes) {
$currentValue = $node.GetAttribute("value")
$replacement = FindBestMatchingString $currentValue
# If the best match is an exact match, use it directly
if ([levenshtein]::Distance($currentValue, $replacement) -eq 0) {
$node.SetAttribute("value", $replacement)
Write-Debug "Replaced value '$currentValue' with '$replacement'"
continue
}
# Check if the replacement is already used
while ($nodes | Where-Object { $_.GetAttribute("value") -eq $replacement }) {
$nameValues = $nameValues | Where-Object { $_ -ne $replacement }
$replacement = FindBestMatchingString $currentValue
}
$node.SetAttribute("value", $replacement)
Write-Debug "Replaced value '$currentValue' with '$replacement'"
}
$xmlContent.Save($_.FullName)
}
The reality is it is pretty stupid to do this with powershell, an executable would beable to do much more logic with less lines, however powershell is on every windows PC regardless of IT so can be kinda nice for that.