{"id":17452,"date":"2026-10-21T18:01:25","date_gmt":"2026-10-21T18:01:25","guid":{"rendered":"https:\/\/dmsretail.com\/RetailNews\/vmware-nutanix-inventory-powershell-python-then-go\/"},"modified":"2026-10-21T18:01:25","modified_gmt":"2026-10-21T18:01:25","slug":"vmware-nutanix-inventory-powershell-python-then-go","status":"publish","type":"post","link":"https:\/\/dmsretail.com\/RetailNews\/vmware-nutanix-inventory-powershell-python-then-go\/","title":{"rendered":"VMware Nutanix inventory: PowerShell, Python, then Go"},"content":{"rendered":"<p> <p><a href=\"https:\/\/dmsretail.com\/online-workshops-list\/\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-496\" src=\"https:\/\/dmsretail.com\/RetailNews\/wp-content\/uploads\/2022\/05\/RETAIL-ONLINE-TRAINING-728-X-90.png\" alt=\"Retail Online Training\" width=\"729\" height=\"91\" srcset=\"https:\/\/dmsretail.com\/RetailNews\/wp-content\/uploads\/2022\/05\/RETAIL-ONLINE-TRAINING-728-X-90.png 729w, https:\/\/dmsretail.com\/RetailNews\/wp-content\/uploads\/2022\/05\/RETAIL-ONLINE-TRAINING-728-X-90-300x37.png 300w\" sizes=\"auto, (max-width: 729px) 100vw, 729px\" \/><\/a><\/p><br \/>\n<\/p>\n<div>\n<p>Someone asked me how many servers we run, and I couldn\u2019t tell them. Not in 30 seconds, anyway. I had a vCenter tab, a Prism tab, two CSV exports in two different shapes, and a spreadsheet I\u2019ve rebuilt more times than I\u2019d like to admit. What I didn\u2019t have was a VMware + Nutanix inventory I could export, hand to someone, and check against reality.<\/p>\n<p>I look after a sandbox data center on both stacks: VMware vCenter across seven clusters, Nutanix Prism Central across three, Cisco UCS and HyperFlex underneath. Both platforms have a perfectly good UI. Neither has the faintest idea the other exists.<\/p>\n<p>Four months later, one Go binary inventories 104 servers and 2,139 VMs across both platforms in a single run. Claude wrote a lot of it. Getting there took three languages, two throwaway versions, and three bugs that hid in plain sight for months.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"lazy lazy-hidden alignnone\" data-lazy-type=\"image\" src=\"https:\/\/blogs.cisco.com\/gcs\/ciscoblogs\/1\/2026\/10\/vmware-nutanix-inventory-go-claude-estate-at-a-glance.png\" alt=\"Stat grid of one toolkit run: 104 servers, 68 vCenter and 36 Nutanix hosts, 2,139 VMs, 300+ gold templates\" width=\"1600\" height=\"940\"\/><noscript><img loading=\"lazy\" decoding=\"async\" class=\"alignnone\" src=\"https:\/\/blogs.cisco.com\/gcs\/ciscoblogs\/1\/2026\/10\/vmware-nutanix-inventory-go-claude-estate-at-a-glance.png\" alt=\"Stat grid of one toolkit run: 104 servers, 68 vCenter and 36 Nutanix hosts, 2,139 VMs, 300+ gold templates\" width=\"1600\" height=\"940\"\/><\/noscript><\/p>\n<h3>The VMware Nutanix inventory nobody could give me<\/h3>\n<p>Even one platform\u2019s answer was thin. The vCenter host export didn\u2019t easily include the server model, so \u201cwhat hardware are we running?\u201d meant cross-referencing UCS Manager by hand. Once the tool reported it, the answer was 11 distinct models: 24 UCS X210C-M7 blades, 18 C225-M6S racks, 19 HyperFlex nodes across four variants, and a long tail of ones and twos.<\/p>\n<p>That was annoying. What made it urgent was a migration: moving our library of golden template VMs off vCenter and into Nutanix. There are 371 of them, spread unevenly across three sandbox clusters, and nobody had a definitive list. Try planning that from two browser tabs.<\/p>\n<h3>My internet connection influenced the design decision<\/h3>\n<p>I work remotely, over a fixed-wireless WiMAX link. Every API call crosses a connection with real latency and a habit of dropping without warning. I lost more than one inventory run to a dang timeout two-thirds of the way through, with nothing written to disk for my trouble. Therefore efficiency stops being an nice to have and becomes the difference between a report that finishes or not.<\/p>\n<p>So the link set the rules:<\/p>\n<ul>\n<li>Ask only for the data you actually read.<\/li>\n<li>Write output as you go.<\/li>\n<li>Ship something that runs from one file, with nothing to install.<\/li>\n<\/ul>\n<h3>I\u2019d already automated this. Claude moved the line<\/h3>\n<p>I already had working PowerShell I ran remotely against vCenter, one query at a time, whenever someone asked. The real question was how far to push it before the effort stopped paying off. Claude pushed that point a long way out.<\/p>\n<p>It was fast at producing something plausible, and I was the only one who could tell whether plausible was correct. Here\u2019s how the work split:<\/p>\n<ul>\n<li><strong>My calls:<\/strong> what the tool answers, the language (three times), the architecture, the safety model, and every check against real hardware.<\/li>\n<li><strong>Claude\u2019s work:<\/strong> boilerplate, translating between languages when I changed my mind, API mechanics I didn\u2019t want to memorize (pagination, the shape of a SOAP property request), and first drafts I argued with or sent back.<\/li>\n<\/ul>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"lazy lazy-hidden alignnone size-default\" data-lazy-type=\"image\" src=\"https:\/\/blogs.cisco.com\/gcs\/ciscoblogs\/1\/2026\/10\/vmware-nutanix-inventory-go-claude-three-attempts.png\" alt=\"Timeline of three attempts: PowerShell, then Python, then Go, the one that needs nothing to run and is still going\" width=\"1600\" height=\"980\"\/><noscript><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-default\" src=\"https:\/\/blogs.cisco.com\/gcs\/ciscoblogs\/1\/2026\/10\/vmware-nutanix-inventory-go-claude-three-attempts.png\" alt=\"Timeline of three attempts: PowerShell, then Python, then Go, the one that needs nothing to run and is still going\" width=\"1600\" height=\"980\"\/><\/noscript><\/p>\n<h3>PowerShell worked, then it multiplied<\/h3>\n<p>I wanted the whole object tree walked in one pass, with a structured CSV per layer: data centers, clusters, hosts, VMs, datastores. I knew exactly what that should look like. I just didn\u2019t want to spend two weeks of evenings typing it.<\/p>\n<p>So I described the shape I was after and Claude had the breadth covered in an afternoon. Trouble was, I was still treating it as a favor to myself, not a project. This is the folder, untouched, four months on:<\/p>\n<pre>Get-vCenterInventory.ps1       429 lines   5 Jun 12:43&#13;\nGet-vCenterInventory1.ps1      429 lines   5 Jun 14:48&#13;\nGet-vCenterInventoryFinal.ps1  509 lines   5 Jun 19:00&#13;\nGet-vCenterInventory3.ps1      545 lines   6 Jun 09:20<\/pre>\n<p>Four versions in under 24 hours. Note the order. <strong>Final<\/strong> landed at 7 p.m. Then <strong>3<\/strong> showed up the next morning. Every engineer reading this knows that filename personally. Heck, I\u2019d laugh if it weren\u2019t mine.<\/p>\n<p>That\u2019s what no version control looks like on disk, and it was entirely my doing. When a new version only cost me a few minutes, the mess never hurt enough to make me stop and fix it.<\/p>\n<p>I killed it for two reasons I\u2019d underestimated. PowerCLI on my Apple Silicon Mac was a pain in the neck: it runs under PowerShell 7, but I hit module and dependency friction every time something updated. And the long, sequential runs were exactly the kind my link loved to kill at 70%, with nothing saved.<\/p>\n<h3>Python gave me a faithful port, old structure and all<\/h3>\n<p>Same day, second try. VMware\u2019s <strong>pyVmomi<\/strong>, the Python SDK for the vSphere API, gives me the same API in Python, and Python runs natively on my Mac. I asked for a wholesale port, and wholesale is exactly what I got. The header comment still reads:<\/p>\n<pre>#!\/usr\/bin\/env python3&#13;\n# get_vcenter_inventory.py&#13;\n# Full vCenter Farm Inventory Collection Script (Python \/ pyVmomi)&#13;\n# Exact equivalent of Get-vCenterInventory.ps1<\/pre>\n<p>That\u2019s exactly what I got, including the parts I wanted to escape. It went 845 lines, then 922 lines 16 minutes later. If I\u2019d wanted a rethink, I needed to ask for one.<\/p>\n<p>Python did give me one keeper: load the entire object graph into memory first, so the inventory can\u2019t drift if a VM migrates mid-run.<\/p>\n<p>Then the wall. Every machine running it needs Python, a venv, and a <strong>pip install<\/strong>. When a colleague asked for the script, my honest answer was \u201csure, first set up a venv,\u201d which is where most people stop asking. And it was one file nearing a thousand lines, with Nutanix still to add.<\/p>\n<h3>Go: one file a colleague can actually run<\/h3>\n<p>Go solved what I cared about most: getting the thing into someone else\u2019s hands. It builds to a single self-contained binary: no interpreter, no runtime, nothing to install.<\/p>\n<p>For VMware there\u2019s <strong>govmomi<\/strong>, VMware\u2019s open-source Go library for the vSphere API, so I wasn\u2019t fighting the platform. For Nutanix a thin REST client of my own was used for Prism Central that paginates and smooths over the differences between API versions. More code to own, but proper support for the second platform.<\/p>\n<p>It\u2019s 7,437 lines, roughly eight times the Python, because it does far more: two platforms, a menu, VLAN and subnet queries, a MAC search across both, an orphan finder (abandoned VM folders, unattached disks, unused images), and session and audit logs.<\/p>\n<p>The payoff: a VMware Nutanix inventory of 104 servers and 2,139 VMs across vCenter and Prism Central, in a single run, from a file I can hand to anyone.<\/p>\n<p>It reads and reports. It doesn\u2019t monitor, run unattended, or write Excel yet. The one write path is orphan deletion. Before a line of it existed, I decided it would ask first, confirm twice, and log every delete.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"lazy lazy-hidden alignnone size-default\" data-lazy-type=\"image\" src=\"https:\/\/blogs.cisco.com\/gcs\/ciscoblogs\/1\/2026\/10\/vmware-nutanix-inventory-go-claude-architecture.png\" alt=\"vCenter via govmomi and Prism Central via a hand-rolled client feed one report engine that writes CSVs and logs\" width=\"1600\" height=\"1160\"\/><noscript><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-default\" src=\"https:\/\/blogs.cisco.com\/gcs\/ciscoblogs\/1\/2026\/10\/vmware-nutanix-inventory-go-claude-architecture.png\" alt=\"vCenter via govmomi and Prism Central via a hand-rolled client feed one report engine that writes CSVs and logs\" width=\"1600\" height=\"1160\"\/><\/noscript><\/p>\n<p>SOAP on one side, REST on the other. Everything below the adapters is shared, so a new report gets written once.<\/p>\n<h3>Three bugs that looked fine for months<\/h3>\n<p>All three sat in code that compiled and looked fine. Running it and looking hard at the output is what found them.<\/p>\n<h4>One property name cost nine minutes a run<\/h4>\n<p>Collecting 68 hosts took 9 minutes and 6 seconds. Over my link, that\u2019s nine minutes of hoping. Nothing in the code looked wrong, so I checked what it was asking vCenter for. It was pulling each host\u2019s complete configuration, network, storage, and firewall rules included, and never once reading it.<\/p>\n<p>The report reads the ESXi version from <strong>summary.config<\/strong>, which sure looks like it needs the top-level <strong>config<\/strong> block. It doesn\u2019t. They\u2019re two different objects that happen to share a word. I removed one string from the property list and the report dropped to 40 seconds, 13.7x faster. Timing the run is what caught it.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"lazy lazy-hidden alignnone size-default\" data-lazy-type=\"image\" src=\"https:\/\/blogs.cisco.com\/gcs\/ciscoblogs\/1\/2026\/10\/vmware-nutanix-inventory-go-claude-host-report-speedup.png\" alt=\"Bar chart: the 68-host report dropped from 9 min 06 s to 40 s, 13.7x faster, after removing the config property\" width=\"1600\" height=\"690\"\/><noscript><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-default\" src=\"https:\/\/blogs.cisco.com\/gcs\/ciscoblogs\/1\/2026\/10\/vmware-nutanix-inventory-go-claude-host-report-speedup.png\" alt=\"Bar chart: the 68-host report dropped from 9 min 06 s to 40 s, 13.7x faster, after removing the config property\" width=\"1600\" height=\"690\"\/><\/noscript><\/p>\n<h4>The first test I wrote found a crash<\/h4>\n<p>For months the repo had no tests. New features came so fast that I never stopped to write one. While adding the hardware model column, I finally wrote one: I fed the CSV export a normal host and one with an empty summary, to see what happened at the edges. The second one crashed the whole report.<\/p>\n<p>vCenter can leave some summary fields empty for hosts that are disconnected or not responding, and the code read them without checking, in three places. Every host in my estate happened to be healthy, so it had never fired. The first disconnected host would\u2019ve taken the whole report down with it.<\/p>\n<h4>My .gitignore was eating a whole package<\/h4>\n<p>This one\u2019s all mine. My ignore file had a line reading <strong>reports\/<\/strong>, to keep generated CSVs out of the repo. Without a leading slash, a gitignore pattern like that matches at any depth, so it was also excluding <strong>internal\/prism\/reports\/<\/strong>, the entire Nutanix reporting package.<\/p>\n<p>Four files, 1,311 lines, never committed. My own README described them, which is exactly why nobody noticed. A fresh clone of my own repo wouldn\u2019t have compiled. Well, crap. The fix was one character: <strong>\/reports\/<\/strong>.<\/p>\n<h3>Which node count do you believe?<\/h3>\n<p>Two Nutanix endpoints disagree about how many nodes a cluster has. One reports 13 and 25. The other enumerates 12 and 24, each node with a model and serial number I can point at. Measured two seconds apart. Neither one errors, and both look like they know what they\u2019re talking about.<\/p>\n<p>Reading the code won\u2019t tell you which to believe. The answer\u2019s in the estate. What settled it is something I know and the tool doesn\u2019t: every node we run is hyperconverged. No witness, compute-only, or storage-only nodes, so the enumerated count is the real one.<\/p>\n<p>The tool now prefers the enumerated count and logs a warning when they disagree. What the other endpoint is counting is still open.<\/p>\n<p>A quieter version of the same trap: three Nutanix fields came back blank or zero instead of raising an error (the server model, the cluster a host belongs to, and the node and VM counts). In a VMware Nutanix inventory someone will plan a migration from, that\u2019s the scary kind of wrong. It looks like data.<\/p>\n<p>Now, wherever a value might not arrive, the tool says so out loud instead of printing an empty column.<\/p>\n<h3>What I\u2019d tell you before you start<\/h3>\n<p><strong>The throwaway versions were the requirements.<\/strong> PowerShell taught me what the report needed. Python gave me load-everything-first. I couldn\u2019t have specified the Go version on day one, because I didn\u2019t know yet what I was building.<\/p>\n<p><strong>Ask for the rethink.<\/strong> Ask for a port and you\u2019ll get a good port. If you want a different design, say so before it writes a line.<\/p>\n<p><strong>Ask for the boring parts up front.<\/strong> Structured logging, an audit trail for deletes, a double confirmation before removing anything. Writing this by hand at 11 p.m., I\u2019d have skipped every one of them. They\u2019re why I\u2019m willing to point the delete path at a real datastore.<\/p>\n<h3>Try it on the report you rebuild by hand<\/h3>\n<p>Claude made it cheap to try three languages and throw two away. Generating code was never the bottleneck. Judging it was.<\/p>\n<p>Mine was the VMware Nutanix inventory. Yours is whatever you rebuild by hand every month. Have Claude write the first version, then run it against live infrastructure and check one number you already know before you trust the rest.<\/p>\n<p>Next: hardening the Nutanix side, settling that node count, and an unattended mode so snapshots become history. I\u2019ve also built a companion tool that moves golden templates from vCenter into Nutanix without the image ever crossing my WiMAX link. That one\u2019s a story for another post.<\/p>\n<\/div>\n<p><p><a href=\"https:\/\/dmsretail.com\/online-workshops-list\/\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-496\" src=\"https:\/\/dmsretail.com\/RetailNews\/wp-content\/uploads\/2022\/05\/RETAIL-ONLINE-TRAINING-728-X-90.png\" alt=\"Retail Online Training\" width=\"729\" height=\"91\" srcset=\"https:\/\/dmsretail.com\/RetailNews\/wp-content\/uploads\/2022\/05\/RETAIL-ONLINE-TRAINING-728-X-90.png 729w, https:\/\/dmsretail.com\/RetailNews\/wp-content\/uploads\/2022\/05\/RETAIL-ONLINE-TRAINING-728-X-90-300x37.png 300w\" sizes=\"auto, (max-width: 729px) 100vw, 729px\" \/><\/a><\/p><br \/><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Someone asked me how many servers we run, and I couldn\u2019t tell them. Not in 30 seconds, anyway. I had a vCenter tab, a Prism [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":17453,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-17452","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-technology"],"_links":{"self":[{"href":"https:\/\/dmsretail.com\/RetailNews\/wp-json\/wp\/v2\/posts\/17452","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dmsretail.com\/RetailNews\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dmsretail.com\/RetailNews\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dmsretail.com\/RetailNews\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dmsretail.com\/RetailNews\/wp-json\/wp\/v2\/comments?post=17452"}],"version-history":[{"count":0,"href":"https:\/\/dmsretail.com\/RetailNews\/wp-json\/wp\/v2\/posts\/17452\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dmsretail.com\/RetailNews\/wp-json\/wp\/v2\/media\/17453"}],"wp:attachment":[{"href":"https:\/\/dmsretail.com\/RetailNews\/wp-json\/wp\/v2\/media?parent=17452"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dmsretail.com\/RetailNews\/wp-json\/wp\/v2\/categories?post=17452"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dmsretail.com\/RetailNews\/wp-json\/wp\/v2\/tags?post=17452"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}