StefanSchnell
Enthusiast
Enthusiast

Tip: How to use PowerShell Classes

A very interesting approach, to use PowerShell classes in a vRealize Automation solution, can be derived from the use of third-party modules. The documentation describes very precisely the procedure for using PowerShell modules. Therefore, here is a variation of this approach to provide further perspectives.

1. to 4. Follow the description.

5. Install your own PowerShell module.
a. Create a modules folder.

 

mkdir Modules​

 

b. Create in the modules folder a System folder.

 

cd Modules
mkdir System​

 

c. 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 VMware Aria. 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.

 

 

# Begin-----------------------------------------------------------------

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;
    }

  }

}

# End-------------------------------------------------------------------​

 

 

8. Add the System module to the handler.ps1 script, and a tiny code sequence for testing.
Instead of Import-Module I use here Using Module, on this way it is possible to import classes.

 

# Begin-----------------------------------------------------------------

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;

}

# End-------------------------------------------------------------------​

 

 

9. Follow the description.

After the zip file is created and loaded into the vRA, the execution of the handler script displays the content of the temporary directory tmp. I checked this approach successfully with vRA 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.

 

0 Replies