Building OpenShift PowerShell Cmdlets

Building OpenShift PowerShell Cmdlets

VMware to OpenShift Virtualization — Article 13 of 15

Technical baseline: OpenShift Virtualization 4.20 and MTV 2.11 documentation. Reviewed 31 October 2026. Examples are instructional and have not been executed against a production cluster; check your installed release, support matrix, and environment before use.

PowerShell wrappers can make OpenShift administration familiar to a Windows-focused operations team while retaining Kubernetes’ resource model. This guide develops a small module that returns objects, requires explicit cluster context, checks native exit codes, and supports -WhatIf for lifecycle changes.

Design the contract before writing functions

Use names such as Get-OcpVM and Start-OcpVM to avoid collisions with PowerCLI’s Get-VM and Start-VM. Require context and namespace rather than relying on whichever cluster happened to be selected earlier. Return domain data or an explicit request result, not formatted tables.

OcpVirtualization PowerShell module architecture: public Get-OcpVM and Start-OcpVM functions call a private native-command helper that passes an argument array to oc or virtctl, checks the exit code, and returns objects
Figure 1. Module design: public functions with explicit context and namespace, one private helper that invokes oc / virtctl safely, and objects — not tables — as output. Select the diagram to view it at full size.

For this example, Get-OcpVM returns the full VM API objects and optionally filters one name. Start-OcpVM submits a start request and reports that it was accepted. It deliberately does not claim application readiness. Both use installed, authenticated oc/virtctl clients. They do not accept raw tokens or replace login management.

Create the module

Create a folder OcpVirtualization containing OcpVirtualization.psm1. The following complete instructional module targets PowerShell 5.1 or later and uses argument-array invocation rather than constructing a shell command string.

Set-StrictMode -Version Latest

function Invoke-OcpNative {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('oc', 'virtctl')]
        [string]$Tool,
        [Parameter(Mandatory)]
        [string[]]$ArgumentList
    )
    $command = Get-Command $Tool -CommandType Application -ErrorAction Stop |
        Select-Object -First 1
    $errorFile = [System.IO.Path]::GetTempFileName()
    $savedPreference = $ErrorActionPreference
    try {
        # Handle the native exit code explicitly across PowerShell versions.
        $ErrorActionPreference = 'Continue'
        $PSNativeCommandUseErrorActionPreference = $false
        $lines = & $command.Source @ArgumentList 2> $errorFile
        $exitCode = $LASTEXITCODE
        $ErrorActionPreference = $savedPreference
        if ($exitCode -ne 0) {
            throw "$Tool failed with exit code $exitCode. Re-run the scoped command to inspect its diagnostic."
        }
        return ($lines -join "`n")
    }
    finally {
        $ErrorActionPreference = $savedPreference
        Remove-Item -LiteralPath $errorFile -Force -ErrorAction SilentlyContinue
    }
}

function Get-OcpVM {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Context,
        [Parameter(Mandatory)]
        [ValidatePattern('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$')]
        [ValidateLength(1,63)]
        [string]$Namespace,
        [ValidatePattern('^[a-z0-9]([-a-z0-9.]*[a-z0-9])?$')]
        [string]$Name
    )
    $arguments = @('--context', $Context, 'get', 'virtualmachines',
                   '-n', $Namespace, '-o', 'json', '--request-timeout=30s')
    if ($Name) { $arguments += @('--field-selector', "metadata.name=$Name") }
    $json = Invoke-OcpNative -Tool oc -ArgumentList $arguments
    $document = $json | ConvertFrom-Json -ErrorAction Stop
    if ($null -eq $document -or
        $document.PSObject.Properties.Name -notcontains 'items') {
        throw 'Unexpected VM list response.'
    }
    foreach ($item in $document.items) { Write-Output $item }
}

function Start-OcpVM {
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$Context,
        [Parameter(Mandatory)]
        [ValidatePattern('^[a-z0-9]([-a-z0-9]*[a-z0-9])?$')]
        [ValidateLength(1,63)]
        [string]$Namespace,
        [Parameter(Mandatory)]
        [ValidatePattern('^[a-z0-9]([-a-z0-9.]*[a-z0-9])?$')]
        [string]$Name
    )
    $target = "$Context/$Namespace/$Name"
    if ($PSCmdlet.ShouldProcess($target, 'Submit VM start request')) {
        $arguments = @('--context', $Context, 'start', $Name, '-n', $Namespace)
        $null = Invoke-OcpNative -Tool virtctl -ArgumentList $arguments
        [pscustomobject]@{
            Context = $Context
            Namespace = $Namespace
            Name = $Name
            Operation = 'Start'
            RequestAccepted = $true
            RequestedAtUtc = [DateTime]::UtcNow
        }
    }
}

Export-ModuleMember -Function Get-OcpVM, Start-OcpVM

The temporary diagnostic file is removed in finally, including failures. The public error reports the tool and exit code without dumping arbitrary server output into an automation log. This small example trades rich diagnostics for a narrow output contract; a production module should add structured, sanitized diagnostics and a bounded process timeout for every external invocation.

The validation patterns are a preliminary input guard, not a replacement for server-side name validation. The Kubernetes API remains authoritative. The context is passed as one argument, and neither function uses Invoke-Expression.

Package and use it

From the directory containing the module folder:

Start-OcpVM flow: validate input, ShouldProcess check, with -WhatIf no virtctl call is made, otherwise virtctl start runs, exit code is checked, and an accepted result object is returned
Figure 2. -WhatIf must prevent the native mutation entirely — it is not just a message printed before a write. Select the diagram to view it at full size.
New-ModuleManifest -Path '.\OcpVirtualization\OcpVirtualization.psd1' `
    -RootModule 'OcpVirtualization.psm1' `
    -ModuleVersion '0.1.0' `
    -PowerShellVersion '5.1' `
    -FunctionsToExport @('Get-OcpVM','Start-OcpVM') `
    -CmdletsToExport @() -AliasesToExport @() -VariablesToExport @()

Import-Module '.\OcpVirtualization\OcpVirtualization.psd1' -Force
Get-OcpVM -Context lab-cluster -Namespace apps |
    Select-Object @{n='Name';e={$_.metadata.name}},
                  @{n='RunStrategy';e={$_.spec.runStrategy}}

Start-OcpVM -Context lab-cluster -Namespace apps -Name app01 -WhatIf
Start-OcpVM -Context lab-cluster -Namespace apps -Name app01 -Confirm

The final command can start a VM after confirmation. -WhatIf must prevent the native mutation call entirely; it is not merely a message printed before a write. Microsoft’s advanced-function methods and CmdletBinding reference describe ShouldProcess behavior.

Add readiness as a separate operation

A future Wait-OcpVM should have an explicit deadline and return a result containing the observed condition, elapsed time, and last known state. It should first handle VMI creation, then readiness, then any optional application check. A missing VMI, a forbidden response, and an unreachable API are different outcomes.

Do not retry every error indiscriminately. A wrong namespace will not improve with 100 retries, and a forbidden response needs permission correction. A transient network failure may warrant a limited retry with backoff. If a start request times out, inspect current state before submitting another mutation.

Unit tests that protect the behavior

Use Pester or your team’s preferred framework to test behavior at the native-command boundary. Mock the private helper when testing the public functions and test the helper separately with a harmless local executable.

Testing layers: Pester unit tests mock the native-command boundary for JSON parsing, errors and WhatIf; integration tests run against a dedicated lab namespace to validate client, API and RBAC
Figure 3. Unit tests protect behaviour at the native-command boundary; a separate lab integration suite proves client versions, RBAC and API behaviour. Select the diagram to view it at full size.
Test Expected result
Valid VM list JSON One object per VM
Empty items array Empty result, not an exception
Malformed JSON Terminating error
Nonzero native exit Terminating error, no false success object
Context/namespace selection Correct argument array
Start-OcpVM -WhatIf No invocation of virtctl
Start accepted Result says accepted, not ready
Unsupported/invalid input Rejected before a mutation

Add an integration suite against a dedicated lab namespace to validate the installed client and API versions. Unit tests do not prove RBAC, admission, or guest behavior. Never point a test that starts or deletes VMs at the default production context.

Operational hardening

Pin and record supported client versions. Add module help with examples, expected errors, output types, and privileges. Keep logging structured with context, namespace, name, operation, timestamp, duration, and result; exclude credentials and full secret-bearing manifests.

Use signing and a controlled distribution channel where organizational policy requires them. For scheduled automation, use an appropriate narrowly scoped identity and a supported credential lifecycle. Audit the executed operation separately from the application’s health result.

For deletion and stop functions, define graceful behavior, retention handling, confirmation impact, and timeout escalation before implementation. A function named Remove-OcpVM must not silently imply that related disks are retained. The VM/VMI guide and storage guide explain why that contract matters.


Explore the complete series

  1. VMware vs OpenShift Virtualization: Complete Technical Comparison
  2. OpenShift Virtualization Architecture Explained
  3. Understanding KubeVirt, VM and VMI
  4. VMware to OpenShift: Complete Migration Strategy
  5. MTV Architecture and Internal Working
  6. OpenShift Virtualization Networking Deep Dive
  7. OpenShift Virtualization Storage Deep Dive
  8. OpenShift VM Live Migration Explained
  9. OpenShift Cluster Sizing for Virtualization
  10. Migrating Windows VMs Using MTV
  11. Migrating Linux VMs Using MTV
  12. VMware PowerCLI vs OpenShift CLI
  13. Building OpenShift PowerShell Cmdlets
  14. OpenShift Virtualization Troubleshooting Guide
  15. Enterprise-Scale VMware to OpenShift Migration Architecture
Comments are closed.