Allow Custom Properties in all fields

Unfortunately you cannot use a custom property in a display name. It renders the connection name literally instead of evaluating the property value.

It makes it very hard to duplicate many sites (dozens of connections per site) and editing all connections one at a time (hundreds total).
It would be far better if I could just duplicate a folder with many connections, then edit the property value. This actually seems like it would be the expected behavior.

This seems like a very reasonable functionality of a connection manager. Thanks for any consideration.

HI Matt,

I just replied to your other thread here: Using variables in properties regarding using custom properties.

Currently, it is only possible to use tokens in the “Computer Name” field, since tokens, if used as the display name, would then also occur in the tree and would have to be rendered beforehand, which would impact the performance.

May I ask what your use case is exactly?

You can edit multiple connections using our Bulk-Edit feature, described here:

https://www.royalapps.com/go/kb-ts-mac-bulkedit

best regards,

Christoph

Hello, Performance impact is understandable, it’s just a major headache for me so I wanted to post the idea.

Here is my use-case:

  • Our company has many physical sites in datacenters across the world. This site inventory is always growing, so I’m always having to add new sites to Royal TSX
  • In each datacenter, we have over two dozen hosts.

Here is an example connection tree (this is only a partial example):

- paris3/
   - fw01.paris3.org.example.com
   - fw02.paris3.org.example.com
   - mg01.paris3.org.example.com
   - mg02.paris3.org.example.com
   - mg03.paris3.org.example.com
   - mg04.paris3.org.example.com
   - ph1.paris3.org.example.com
   - ph2.paris3.org.example.com
   - ph3.paris3.org.example.com
   - ph4.paris3.org.example.com
   - lb1.paris3.org.example.com
   - lb2.paris3.org.example.com
   - dbm1.paris3.org.example.com
   - dbm2.paris3.org.example.com
   - dbm3.paris3.org.example.com
   - xsvr1.paris3.org.example.com
   - xsvr2.paris3.org.example.com
   - xsvr3.paris3.org.example.com
   - xsvr4.paris3.org.example.com
   - xsvr5.paris3.org.example.com
   - xsvr6.paris3.org.example.com
   - usvr1.paris3.org.example.com
   - usvr2.paris3.org.example.com
   - usvr3.paris3.org.example.com
   - usvr4.paris3.org.example.com
   - usvr5.paris3.org.example.com
   - usvr6.paris3.org.example.com

- hongkong1/
   - fw01.hongkong1.org.example.com
   - fw02.hongkong1.org.example.com
   - mg01.hongkong1.org.example.com
   - mg02.hongkong1.org.example.com
   - mg03.hongkong1.org.example.com
   - mg04.hongkong1.org.example.com
   - ph1.hongkong1.org.example.com
   - ph2.hongkong1.org.example.com
   - ph3.hongkong1.org.example.com
   - ph4.hongkong1.org.example.com
   - lb1.hongkong1.org.example.com
   - lb2.hongkong1.org.example.com
   - dbm1.hongkong1.org.example.com
   - dbm2.hongkong1.org.example.com
   - dbm3.hongkong1.org.example.com
   - xsvr1.hongkong1.org.example.com
   - xsvr2.hongkong1.org.example.com
   - xsvr3.hongkong1.org.example.com
   - xsvr4.hongkong1.org.example.com
   - xsvr5.hongkong1.org.example.com
   - xsvr6.hongkong1.org.example.com
   - usvr1.hongkong1.org.example.com
   - usvr2.hongkong1.org.example.com
   - usvr3.hongkong1.org.example.com
   - usvr4.hongkong1.org.example.com
   - usvr5.hongkong1.org.example.com
   - usvr6.hongkong1.org.example.com

- frankfurt1/
   - fw01.frankfurt1.org.example.com
   - fw02.frankfurt1.org.example.com
   - mg01.frankfurt1.org.example.com
   - mg02.frankfurt1.org.example.com
   - mg03.frankfurt1.org.example.com
   - mg04.frankfurt1.org.example.com
   - ph1.frankfurt1.org.example.com
   - ph2.frankfurt1.org.example.com
   - ph3.frankfurt1.org.example.com
   - ph4.frankfurt1.org.example.com
   - lb1.frankfurt1.org.example.com
   - lb2.frankfurt1.org.example.com
   - dbm1.frankfurt1.org.example.com
   - dbm2.frankfurt1.org.example.com
   - dbm3.frankfurt1.org.example.com
   - xsvr1.frankfurt1.org.example.com
   - xsvr2.frankfurt1.org.example.com
   - xsvr3.frankfurt1.org.example.com
   - xsvr4.frankfurt1.org.example.com
   - xsvr5.frankfurt1.org.example.com
   - xsvr6.frankfurt1.org.example.com
   - usvr1.frankfurt1.org.example.com
   - usvr2.frankfurt1.org.example.com
   - usvr3.frankfurt1.org.example.com
   - usvr4.frankfurt1.org.example.com
   - usvr5.frankfurt1.org.example.com
   - usvr6.frankfurt1.org.example.com

- losangeles1/
   - fw01.losangeles1.org.example.com
   - fw02.losangeles1.org.example.com
   - mg01.losangeles1.org.example.com
   - mg02.losangeles1.org.example.com
   - mg03.losangeles1.org.example.com
   - mg04.losangeles1.org.example.com
   - ph1.losangeles1.org.example.com
   - ph2.losangeles1.org.example.com
   - ph3.losangeles1.org.example.com
   - ph4.losangeles1.org.example.com
   - lb1.losangeles1.org.example.com
   - lb2.losangeles1.org.example.com
   - dbm1.losangeles1.org.example.com
   - dbm2.losangeles1.org.example.com
   - dbm3.losangeles1.org.example.com
   - xsvr1.losangeles1.org.example.com
   - xsvr2.losangeles1.org.example.com
   - xsvr3.losangeles1.org.example.com
   - xsvr4.losangeles1.org.example.com
   - xsvr5.losangeles1.org.example.com
   - xsvr6.losangeles1.org.example.com
   - usvr1.losangeles1.org.example.com
   - usvr2.losangeles1.org.example.com
   - usvr3.losangeles1.org.example.com
   - usvr4.losangeles1.org.example.com
   - usvr5.losangeles1.org.example.com
   - usvr6.losangeles1.org.example.com

- losangeles2/
   - fw01.losangeles2.org.example.com
   - fw02.losangeles2.org.example.com
   - mg01.losangeles2.org.example.com
   - mg02.losangeles2.org.example.com
   - mg03.losangeles2.org.example.com
   - mg04.losangeles2.org.example.com
   - ph1.losangeles2.org.example.com
   - ph2.losangeles2.org.example.com
   - ph3.losangeles2.org.example.com
   - ph4.losangeles2.org.example.com
   - lb1.losangeles2.org.example.com
   - lb2.losangeles2.org.example.com
   - dbm1.losangeles2.org.example.com
   - dbm2.losangeles2.org.example.com
   - dbm3.losangeles2.org.example.com
   - xsvr1.losangeles2.org.example.com
   - xsvr2.losangeles2.org.example.com
   - xsvr3.losangeles2.org.example.com
   - xsvr4.losangeles2.org.example.com
   - xsvr5.losangeles2.org.example.com
   - xsvr6.losangeles2.org.example.com
   - usvr1.losangeles2.org.example.com
   - usvr2.losangeles2.org.example.com
   - usvr3.losangeles2.org.example.com
   - usvr4.losangeles2.org.example.com
   - usvr5.losangeles2.org.example.com
   - usvr6.losangeles2.org.example.com

- sanjose1/
   - fw01.sanjose1.org.example.com
   - fw02.sanjose1.org.example.com
   - mg01.sanjose1.org.example.com
   - mg02.sanjose1.org.example.com
   - mg03.sanjose1.org.example.com
   - mg04.sanjose1.org.example.com
   - ph1.sanjose1.org.example.com
   - ph2.sanjose1.org.example.com
   - ph3.sanjose1.org.example.com
   - ph4.sanjose1.org.example.com
   - lb1.sanjose1.org.example.com
   - lb2.sanjose1.org.example.com
   - dbm1.sanjose1.org.example.com
   - dbm2.sanjose1.org.example.com
   - dbm3.sanjose1.org.example.com
   - xsvr1.sanjose1.org.example.com
   - xsvr2.sanjose1.org.example.com
   - xsvr3.sanjose1.org.example.com
   - xsvr4.sanjose1.org.example.com
   - xsvr5.sanjose1.org.example.com
   - xsvr6.sanjose1.org.example.com
   - usvr1.sanjose1.org.example.com
   - usvr2.sanjose1.org.example.com
   - usvr3.sanjose1.org.example.com
   - usvr4.sanjose1.org.example.com
   - usvr5.sanjose1.org.example.com
   - usvr6.sanjose1.org.example.com

- tokyo5/
   - fw01.tokyo5.org.example.com
   - fw02.tokyo5.org.example.com
   - mg01.tokyo5.org.example.com
   - mg02.tokyo5.org.example.com
   - mg03.tokyo5.org.example.com
   - mg04.tokyo5.org.example.com
   - ph1.tokyo5.org.example.com
   - ph2.tokyo5.org.example.com
   - ph3.tokyo5.org.example.com
   - ph4.tokyo5.org.example.com
   - lb1.tokyo5.org.example.com
   - lb2.tokyo5.org.example.com
   - dbm1.tokyo5.org.example.com
   - dbm2.tokyo5.org.example.com
   - dbm3.tokyo5.org.example.com
   - xsvr1.tokyo5.org.example.com
   - xsvr2.tokyo5.org.example.com
   - xsvr3.tokyo5.org.example.com
   - xsvr4.tokyo5.org.example.com
   - xsvr5.tokyo5.org.example.com
   - xsvr6.tokyo5.org.example.com
   - usvr1.tokyo5.org.example.com
   - usvr2.tokyo5.org.example.com
   - usvr3.tokyo5.org.example.com
   - usvr4.tokyo5.org.example.com
   - usvr5.tokyo5.org.example.com
   - usvr6.tokyo5.org.example.com

- ny1/
   - fw01.ny1.org.example.com
   - fw02.ny1.org.example.com
   - mg01.ny1.org.example.com
   - mg02.ny1.org.example.com
   - mg03.ny1.org.example.com
   - mg04.ny1.org.example.com
   - ph1.ny1.org.example.com
   - ph2.ny1.org.example.com
   - ph3.ny1.org.example.com
   - ph4.ny1.org.example.com
   - lb1.ny1.org.example.com
   - lb2.ny1.org.example.com
   - dbm1.ny1.org.example.com
   - dbm2.ny1.org.example.com
   - dbm3.ny1.org.example.com
   - xsvr1.ny1.org.example.com
   - xsvr2.ny1.org.example.com
   - xsvr3.ny1.org.example.com
   - xsvr4.ny1.org.example.com
   - xsvr5.ny1.org.example.com
   - xsvr6.ny1.org.example.com
   - usvr1.ny1.org.example.com
   - usvr2.ny1.org.example.com
   - usvr3.ny1.org.example.com
   - usvr4.ny1.org.example.com
   - usvr5.ny1.org.example.com
   - usvr6.ny1.org.example.com

++ MANY MORE

Right now, I copy the “folder” of the site to a new folder, then have to edit each connection one-by-one. Ideally I could just put the site code (ex: tokyo1) in one single location (at the folder level) and the values will cascade to child objects in that folder. This is beginning to waste a bunch of time during my day as we continue to turn-up new sites and will continue throughout the year and beyond.

It works perfectly fine for the computer name field, but not the display name field.

One workaround I thought about doing is not having the full hostname in the display name field, and just truncate it to the host name. However that’s going to cause some sanity issues when connecting to prod systems (having tons of connections called just fw01 seems risky, even with folders. Just a lot of room for human error. Having the full hostname int he connection tree really is ideal.

Unfortunately bulk update wont work because I only need to update a part of the display name string (which seems like a perfect usage of variables/custom properties)

Hi Matt,

thanks for the explanation and the examples.

May I ask if the connections differ apart from the display and computer name, and if the display and computer name should always have the same value?

I’m asking, since use case could be covered by our Dynamic Folder functionality:

https://www.royalapps.com/go/help-ts-win-v6-ref-dynamic-folder

&

https://www.royalapps.com/go/rjson-documentation

Looking forward to your reply.

best regards,

Christoph

Hi Matt,

we’ve created a Dynamic Folder script (DynamicFolder_Topic_17000021782.rdfe), which would suit the file-format you posted in your previously reply.

Just import the attached script via File > Import > Dynamic Folder and change the “File Path” in the “Custom Properties” section to the file you want to refer to:

Afterwards, reload the Dynamic Folder and you should see your connection structure within Royal TS/X.

As an alternative, you can also use the ‘Python - Notes as Data Source.rdfe’ Dynamic Folder script, to import your connections via the “Notes” section of the script:

In this case, all headings elements will be created as folders, and all bullet points will be created as connections.

I hope this helps, and please let me know if you need further assistance.

Best regards,

Christoph

DynamicFolder_Topic_17000021782.rdfe:

{
  "Name": "Dynamic Folder Export",
  "Objects": [
    {
      "Type": "DynamicFolder",
      "Name": "DynamicFolder_Topic_17000021782",
      "CustomProperties": [
        {
          "Name": "File Path",
          "Type": "Text",
          "Value": "~/file.txt"
        }
      ],
      "Script": "import json\nimport os\n\n\nclass Item:\n    def __init__(self, name, is_folder):\n        self.name = name\n        self.is_folder = is_folder\n\n\ndef get_content_of_file(file_path):\n    file = open(file_path, 'r')\n    lines = file.readlines()\n    file.close()\n    return lines\n\n\ndef parse_file_content(file_content):\n    items = []\n\n    for line in file_content:\n        stripped_line = line.strip()\n\n        if not stripped_line:\n            continue\n\n        is_folder = False\n\n        if stripped_line.endswith('/'):\n            is_folder = True\n\n        stripped_line = stripped_line.replace(\"-\", \"\")\n        stripped_line = stripped_line.replace(\"/\", \"\")\n        stripped_line = stripped_line.strip()\n\n        item = Item(stripped_line, is_folder)\n        items.append(item)\n\n    return items\n\n\ndef create_rjson_model(items):\n    objects = []\n    current_folder = None\n\n    for item in items:\n        if item.is_folder:\n            current_folder = {\n                \"Type\": \"Folder\",\n                \"Name\": item.name,\n                \"Objects\": [],\n                \"CredentialsFromParent\": True\n            }\n\n            objects.append(current_folder)\n        else: # is_connection\n            connection = {\n                \"Type\": \"TerminalConnection\",\n                \"Name\": item.name,\n                \"ComputerName\": item.name,\n                \"CredentialsFromParent\": True\n            }\n\n            if current_folder:\n                current_folder[\"Objects\"].append(connection)\n            else:\n                objects.append(connection)\n\n    container = {\n        \"Objects\": objects\n    }\n\n    return container\n\n\ndef create_json_string(dict):\n    json_output = json.dumps(dict, indent=4)\n\n    return json_output\n\n\ncontent = get_content_of_file(os.path.expanduser(r\"$CustomProperty.FilePath$\"))\nitems = parse_file_content(content)\nrjson_model = create_rjson_model(items)\njson_string = create_json_string(rjson_model)\nprint(json_string)\n\n",
      "ScriptInterpreter": "python",
      "DynamicCredentialScriptInterpreter": "json",
      "DynamicCredentialScript": "{\n\t\"Username\": \"user\",\n\t\"Password\": \"pass\"\n}"
    }
  ]
}

Python - Notes as Data Source.rdfe:

{
  "Name": "Dynamic Folder Export",
  "Objects": [
    {
      "Type": "DynamicFolder",
      "Name": "Python - Notes as Data Source",
      "Description": "Inspired by https://community.royalapps.com/t/allow-custom-properties-in-all-fields/1612",
      "Notes": "<h1><span style=\"color: rgb(0, 0, 0); font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; font-variant-ligatures: normal; orphans: 2; white-space: pre-wrap; widows: 2; background-color: rgb(253, 253, 253); text-decoration-thickness: initial;\">paris3</span></h1>\n\n<ul>\n\t<li>fw01.paris3.org.example.com</li>\n\t<li>fw02.paris3.org.example.com</li>\n</ul>\n\n<h1><span style=\"color: rgb(0, 0, 0); font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; font-variant-ligatures: normal; orphans: 2; white-space: pre-wrap; widows: 2; background-color: rgb(253, 253, 253); text-decoration-thickness: initial;\">hongkong1</span></h1>\n\n<ul>\n\t<li>fw01.hongkong1.org.example.com</li>\n\t<li>fw02.hongkong1.org.example.com</li>\n</ul>\n\n<h1><span style=\"color: rgb(0, 0, 0); font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; font-variant-ligatures: normal; orphans: 2; white-space: pre-wrap; widows: 2; background-color: rgb(253, 253, 253); text-decoration-thickness: initial;\">frankfurt1</span></h1>\n\n<ul>\n\t<li>fw01.frankfurt1.org.example.com</li>\n\t<li>fw02.frankfurt1.org.example.com</li>\n</ul>\n\n<h1><span style=\"color: rgb(0, 0, 0); font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; font-variant-ligatures: normal; orphans: 2; white-space: pre-wrap; widows: 2; background-color: rgb(253, 253, 253); text-decoration-thickness: initial;\">losangeles1</span></h1>\n\n<ul>\n\t<li><span style=\"color: rgb(0, 0, 0); font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; font-variant-ligatures: normal; orphans: 2; white-space: pre-wrap; widows: 2; background-color: rgb(253, 253, 253); text-decoration-thickness: initial;\">fw01.losangeles1.org.example.com</span></li>\n\t<li><span style=\"color: rgb(0, 0, 0); font-family: Consolas, Monaco, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, monospace; font-variant-ligatures: normal; orphans: 2; white-space: pre-wrap; widows: 2; background-color: rgb(253, 253, 253); text-decoration-thickness: initial;\">fw02.losangeles1.org.example.com</span></li>\n</ul>\n",
      "Script": "import json\nfrom html.parser import HTMLParser\n\nclass Item:\n\tdef __init__(self, name, is_folder):\n\t\tself.name = name\n\t\tself.is_folder = is_folder\n\n\nclass NotesHTMLParser(HTMLParser):\n\tdef __init__(self):\n\t\tsuper().__init__()\n\n\t\tself.items = []\n\t\tself.is_folder = False\n\t\tself.is_connection = False\n\n\t\tself.reset()\n\n\tdef handle_starttag(self, tag, attrs):\n\t\tif tag == \"h1\":\n\t\t\tself.is_folder = True\n\t\telif tag == \"li\":\n\t\t\tself.is_connection = True\n\t\n\tdef handle_endtag(self, tag: str) -> None:\n\t\tif tag == \"h1\":\n\t\t\tself.is_folder = False\n\t\telif tag == \"li\":\n\t\t\tself.is_connection = False\n\t\n\tdef handle_data(self, data: str) -> None:\n\t\tstripped_data = data.strip()\n\n\t\tif not stripped_data:\n\t\t\treturn None\n\n\t\tif self.is_folder or self.is_connection:\n\t\t\tself.items.append(Item(data, self.is_folder))\n\ndef parse_html_content(html_content):\n\tparser = NotesHTMLParser()\n\tparser.feed(html_content)\n\n\treturn parser.items\n\n\ndef create_rjson_model(items):\n\tobjects = []\n\tcurrent_folder = None\n\n\tfor item in items:\n\t\tif item.is_folder:\n\t\t\tcurrent_folder = {\n\t\t\t\t\"Type\": \"Folder\",\n\t\t\t\t\"Name\": item.name,\n\t\t\t\t\"CredentialsFromParent\": True,\n\t\t\t\t\"Objects\": []\n\t\t\t}\n\n\t\t\tobjects.append(current_folder)\n\t\telse: # is_connection\n\t\t\tconnection = {\n\t\t\t\t\"Type\": \"TerminalConnection\",\n\t\t\t\t\"Name\": item.name,\n\t\t\t\t\"ComputerName\": item.name,\n\t\t\t\t\"CredentialsFromParent\": True\n\t\t\t}\n\n\t\t\tif current_folder:\n\t\t\t\tcurrent_folder[\"Objects\"].append(connection)\n\t\t\telse:\n\t\t\t\tobjects.append(connection)\n\n\tcontainer = {\n\t\t\"Objects\": objects\n\t}\n\n\treturn container\n\n\ndef create_json_string(dict):\n    json_output = json.dumps(dict, indent=4)\n\n    return json_output\n\ncontent = r\"\"\"\n$Notes$\n\"\"\"\n\nitems = parse_html_content(content)\nrjson_model = create_rjson_model(items)\njson_string = create_json_string(rjson_model)\nprint(json_string)",
      "ScriptInterpreter": "python",
      "DynamicCredentialScriptInterpreter": "json"
    }
  ]
}