Why is WFA with PowerShell Slower than WFA with Perl, or just PowerShell CLI?

Caveat Lector: I’m not a WFA expert so this might be a rubbish post. Please feel free to comment below if you disagree, or to tell me I’ve got this wrong.

In the previous post using row repetition in WFA to create 10 CIFS shares it took:

7 minutes 7 seconds - using the PowerShell version of the Create CIFS Share command
0 minutes 26 seconds - using the Perl version of the Create CIFS Share command

And then creating 10 CIFS shares just from the PowerShell CLI, it took only 13 seconds.

Q: So, why is WFA and PowerShell so slow?
A: The answer is because every PowerShell command in WFA is run in a brand new PowerShell instance, which means you’re loading the profile.ps1 and everything that’s inside it, for every single PowerShell command you run. In my example of creating 10 CIFS shares using the ‘Create CIFS Share’ command with row repetition, we were using 10 different PowerShell instances and each one had to wait for profile.ps1 and everything inside it to load - no wonder it was slow!

Note: If you don’t believe me, edit the file C:\Program Files\NetApp\WFA\PoSH\profile.ps1 with a line like the below at the end -
Date >> C:\TEMP\WFA_Profile.log
- then run your workflow and see how many times the date is logged.

Q: Is there a way to improve PowerShell performance with WFA?
A: I don’t do enough with WFA to offer a built solution. Personally I’d probably just use Perl rather than start messing around with WFA and perhaps putting it in an unsupported state, but this is what I’d consider doing:

i) Re-engineer the commands so that all required code is saved into a temporary PS1 file. Also make a note of which cmdlets we’re actually using, so we only need to ‘Import-Module DataONTAP -Cmdlet ...’ the cmdlets we actually need.
ii) Have a simple command to optimize profile.ps1 for loading the code
iii) Have a simple command to optimize profile.ps1 for running the code
iv) Have a simple command to execute the code

Then we run a workflow that’s something like this (before running the workflow we’ve already optimized profile.ps1 for loading code):

1) Compile all the commands into the temporary PS1 file
2) Optimize profile.ps1 for running code
3) Execute the temporary PS1 file
4) Optimize profile.ps1 for loading code

Step 1 is pretty much an “original” workflow, just with our modified commands. Steps 2, 3 and 4 are the additional elements.

Note: This method would likely break some WFA functionality, so it’s definitely not recommended.

APPENDIX: OnCommand Workflow Automation 4.1’s
C:\Program Files\NetApp\WFA\PoSH\profile.ps1

$POSHDir = split-path $MyInvocation.MyCommand.Path
$ModulesDir = "$POSHDir\Modules"

#Adding assemblies
$assemblies = @()
$dlls=(Get-ChildItem -Path $ModulesDir -Filter *.dll)
foreach ($dll in $dlls) {
  Add-type -path "$ModulesDir\$dll" -ErrorAction SilentlyContinue
  $assemblies +=$dll.Name

#Adding modules
$files=(Get-ChildItem -Path $ModulesDir)
foreach ($file in $files) {
  if ($assemblies -contains $file.Name){
    #"this is an assembly... skip it"
  Import-Module "$ModulesDir\$file" -ErrorAction SilentlyContinue

Image: profile.ps1
Image: The WFA > PoSH > Modules folder
Image: The WfaConfig.psm1 and WFAWrapper.psm1 files are worth a look to help your understanding of how WFA and PowerShell works