VCF Automation Blog

from Stefan Schnell

A very interesting approach, to use PowerShell classes in an VCF Automation solution, can be derived from the use of third-party modules, at the document Using the VMware vRealize Orchestrator Client, in the section How Can I Use Third-Party Modules to Call the vRealize Automation Project API for vRealize Orchestrator 8.7. This is the last version in which this is documented. The documentation describes very precisely the procedure for using PowerShell modules. In this blog post is presented a variation of this approach to provide further perspectives.

How to use PowerShell Classes


  1. On your local machine, open a command-line shell.

  2. Create a vro-powershell-vra folder.

    mkdir vro-powershell-vra

  3. Navigate to the vro-powershell-vra folder.

    cd vro-powershell-vra

  4. Create a PowerShell script called handler.ps1.

    touch handler.ps1

    The handler.ps1 script must define one function that accepts two arguments, the context of the VCF Automation Orchestrator workflow run and the bound VCF Automation Orchestrator inputs.

    function Handler {
      Param($context, $inputs)
    
      $inputsString = $inputs | ConvertTo-Json -Compress
      Write-Host "Inputs were $inputsString"
    }

  5. Install your own PowerShell module.

    1. Create a Modules folder.

      mkdir Modules

    2. Create in the Modules folder a System folder.

      cd Modules
      mkdir System

    3. Create two files in the System folder.

      touch System.psd1
      touch System.psm1

      The psd1 file is a PowerShell module manifest and the psm1 file is the PowerShell script module, which contains the PowerShell code. You can find more detailed information about PowerShell modules here.

  6. Add the following content to the module manifest System.psd1.

    @{
      GUID = "ABFE3888-F38C-45F4-BA93-98DEDFCE1AF7"
      Author = "Stefan Schnell"
      CompanyName = "Stefan Schnell"
      Copyright = "Copyright (c) Stefan Schnell"
      ModuleVersion = "1.0.0.0"
      CompatiblePSEditions = @("Core")
      PowerShellVersion = "7.0"
      ModuleToProcess = "System.psm1"
      FunctionsToExport = @()
      CmdletsToExport = @()
      AliasesToExport = @()
    }

  7. Add the following class to the script module System.psm1.

    In this example I defined a class with the name Command. It is for executing operating system commands in the host operating system in VCF Automation. This class is almost equivalent to the VMware JavaScript System.Command class. Compared to the PowerShell call operator (&) this approach has the advantage that the result is formatted and the control is more defined.

    class Command {
    
      <#
       #  {int32} timeout = Specify a waiting period until the process
       #                          is killed, default inifinite.
       #  {string} output = The standard output from the command.
       #  {string} result = The return code from the command.
       #
       # 
       # $com = [Command]::new("ls", "-l");
       # $com.execute($true);
       # $output = $com.output;
       #>
    
      [string]$output;
      [int]$result;
      [int]$timeout = [System.Threading.Timeout]::Infinite;
    
      [string] hidden $progName = $null;
      [string] hidden $progArguments = $null;
    
      <# constructor
       #
       #  {string} programName - Name of the program to be executed.
       #  {string} programArguments - Arguments of the program.
       #
       #>
      command([string]$programName, [string]$programArguments) {
        $this.progName = $programName;
        $this.progArguments = $programArguments;
      }
    
      <# execute
       #
       # Executes the command.
       # Standard output is redirected to the output attribute.
       #
       #  {bool} wait - Wait for execution end ($true or $false).
       #
       #>
      [void]execute([bool]$wait) {
    
        $this.output = $null;
        $this.result = 0;
    
        try {
    
          [System.Diagnostics.ProcessStartInfo]$startInfo = `
            [System.Diagnostics.ProcessStartInfo]::new();
          $startInfo.FileName = $this.progName;
          if ($this.progArguments) {
            $startInfo.Arguments = $this.progArguments;
          }
          $startInfo.UseShellExecute = $false;
          $startInfo.CreateNoWindow = $true;
          $startInfo.RedirectStandardOutput = $true;
          $startInfo.RedirectStandardError = $true;
    
          [System.Diagnostics.Process]$proc = `
            [System.Diagnostics.Process]::new();
    
          $proc.StartInfo = $startInfo;
          if ($proc.Start() -eq $null) {
            $this.result = -1;
            return;
          }
    
          if ($wait) {
    
            if ($proc.WaitForExit($this.timeout) -eq $false) {
              $proc.Kill();
            }
    
            $this.result = $proc.ExitCode;
    
            [System.IO.StreamReader]$stdOut = $proc.StandardOutput;
            [System.IO.StreamReader]$stdErr = $proc.StandardError;
    
            if (($stdOut -eq $null) -or ($stdErr -eq $null)) {
              return;
            }
    
            $retValue = $stdOut.ReadToEnd();
    
            if ($retValue.Length -eq 0) {
              $this.output = $stdErr.ReadToEnd();
            } Else {
              $this.output = $retValue;
            }
    
          }
    
        } catch {
          $this.output = "$($_.Exception.Message)`n$($_.ScriptStackTrace)";
          $this.result = -1;
        }
    
      }
    
    }

  8. Add the System module to the handler.ps1 script, and a tiny code sequence for testing.

    Using Module ./Modules/System;
    
    function Handler($context, $inputs) {
    
      $inputsString = $inputs | ConvertTo-Json -Compress;
      # Write-Host "Inputs were $($inputsString)";
    
      $com = [Command]::new("ls", "-l /tmp");
      $com.execute($true);
      Write-Host $com.result;
      Write-Host $com.output;
    
      $output = @{status = "done"};
      return $output;
    
    }

  9. Create a ZIP package that contains the handler.ps1 file and Modules folder with your module.

After the zip file is created and loaded into VCF Automation, the execution of the handler script displays the content of the temporary directory tmp. I checked this approach successfully with VCF Automation 8.10.2.

Conclusion

With a slight variation, the approach to use third-party modules can be extended very easily. Beside PowerShell functions, we can also make classes available and so that be very equivalent to JavaScript and Java classes, e.g. to create cross-language approaches.