Although vCenter Orchestrator does offer an upgrade path for their appliances there are times where I have found myself needing to deploy a new one and migrate all of my configuration and workflows to it.  vCO has some great built in workflows that can configure itself, but nothing that really deals with workflows.  Sure, you can export and import workflows one at time using the client, which may be ok if you have 10 workflows, but what if you have 20, 40, 100 that you need to migrate.  That could get pretty monotonous.

The shear energy of a mouse click.

That’s right – who wants to exert all that energy of clicking on the mouse to get these workflows migrated when we can just orchestrate or automate it – after all, this is vCenter Orchestrator we are talking about.  vCO has a REST plugin that allows us to create workflows around any application that offers one, but did you know that vCO also has it’s own REST API available for us to use?  So that’s where I started with my task of migrating workflows and by the time I got to writing this post it truly ended up being a community effort.

“Steal from the best”

This was a quote that I seen on one of Alan Renouf’s slides during a VMworld presentation on PowerCLI.  “Steal from the best”, “Don’t re-invent the wheel” – sayings that have resonated with me for my entire career – Why re-do something if it has already been done.  So when I set out on this small project I ended up using two main sources; This post by William Lam on VirtuallyGhetto on how to use curl to export a single vCO workflow and this forum reply by igaydajiev who “showed me the light” on how to import a workflow back in!  Without these two guys I wouldn’t have been able to do any of this.

Enough already let’s get to it!

So I chose to go the PowerShell route to accomplish this as I’m not too familiar with the REST plugin for vCO.  As well, I am targeting only a certain category – so basically what the script does is take in the following parameters

  • OldvCOServer – the old vCO appliance
  • OldvCOUser/OldvCOPass – credentials for the old appliance
  • OldvCOCategory – Category Name to export workflows from
  • TempFolder – Location to store the exported workflows
  • NewvCOServer – The new vCO appliance
  • NewvCOUser/NewvCOPass – credentials for the new appliance
  • NewvCOCategory – Category name on new server where you would like the worfkflows imported.

As far as an explanation I’ll just let you follow the code and figure it out.  It’s basically broke into two different sections; the export and the import.  During the import routine there is a little bit of crazy wonky code that gets the ID of the targeted category.  This is the only way I could figure out how to get it and I’m sure there is a way more efficient way of doing so, but for now, this will have to do.  Anyways, the script is shown below and is downloadable here.  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
Param(
[string]$oldvCOServer="localhost",
[string]$oldvCOUser="vcoadmin",
[string]$oldvCOPass="vcoadmin",
[string]$oldvCOCategory="MyOldWorkflows",
[string]$newvCOServer="localhost",
[string]$newvCOUser="vcoadmin",
[string]$newvCOPass="vcoadmin",
[string]$newvCOCategory="MyWorkflows",
[string]$TempFolder
)
 
# vCO Port
$vcoPort="8281"
 
# Type to handle self-signed certificates
add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
[byte[]]$CRLF = 13, 10
 
function Get-AsciiBytes([String] $str) {
return [System.Text.Encoding]::ASCII.GetBytes($str)            
}
 
function ConvertTo-Base64($string) {
$bytes  = [System.Text.Encoding]::UTF8.GetBytes($string);
$encoded = [System.Convert]::ToBase64String($bytes); 
 
return $encoded;
}
 
clear
##############  EXPORT OLD WORKFLOWS ##############################
Write-Host "Beginning Export Routine" -ForegroundColor Black -BackgroundColor Yellow
Write-Host ""
# build uri
$uri = "https://"+$oldvCOServer+":"+$vCOPort+"/vco/api/workflows/?conditions=categoryName="+$oldvCOCategory
 
# Authentication token or old server
$token = ConvertTo-Base64("$($oldvCOUser):$($oldvCOPass)");
$auth = "Basic $($token)";
$header = @{"Authorization"= $auth; };   
 
#execute API call
$workflows = Invoke-RestMethod -URi $uri -Method Get -ContentType "application/xml" -Headers $header
Write-Host "Exporting $($workflows.total) workflows from $oldvCOCategory"
Write-Host "-------------------------------------------------------------------------------------------"
#loop through each workflow and export to TempFolder
foreach ($href in $workflows.link.href)
{
#retrieve information about the specific workflow
$header = @{"Authorization"= $auth; };   
$workflow = Invoke-RestMethod -URi $href -Method Get -ContentType "application/xml" -Headers $header
 
#replace all spaces in workflow name
$workflowname = [System.Text.RegularExpressions.Regex]::Replace($($workflow.name),"[^1-9a-zA-Z_]","")
$filename = $TempFolder + $workflowname + ".workflow"
# setup new header
$header = @{"Authorization"= $auth;
"Accept"="application/zip"; }; 
Write-Host "Exporting $($workflow.name) to $filename - " -NoNewline
Invoke-RestMethod -URi $href -Method Get -ContentType "application/xml" -Headers $header -OutFile $filename
Write-Host "Done" -ForegroundColor Green  
}
Write-Host ""
Write-Host "Export Routine Complete" -ForegroundColor Black -BackgroundColor Yellow
##################################################################
 
##############  IMPORT WORKFLOWS ##############################
Write-Host ""
Write-Host ""
Write-Host "Import Routines to new server" -ForegroundColor Black -BackgroundColor Yellow
Write-Host ""
 
#Generate auth for new vCO Server
$token = ConvertTo-Base64("$($newvCOUser):$($newVCOPass)");
$auth = "Basic $($token)";
 
#Get Category ID
$header = @{"Authorization"= $auth; };   
$uri = "https://"+$newvCOServer+":"+$vCOPort+"/vco/api/categories/"
$categories = Invoke-RestMethod -URi $uri -Method Get -Headers $header -ContentType "application/xml" 
foreach ($att in $categories.link)
{
if ($att.attributes.value -eq "HPEDSB")
{
foreach ($newatt in $att.attributes )
{
if ($newatt.name -eq "id")
{
$categoryID = $newatt.value
}
}
}
}
 
$impUrl = "https://$($newvCOServer):$($vcoPort)/vco/api/workflows?categoryId=$($categoryId)&overwrite=true";
$header = @{"Authorization"= $auth;
"Accept"= "application/zip";
"Accept-Encoding"= "gzip,deflate,sdch";};    
 
$workflows = Get-ChildItem $TempFolder -Filter *.workflow
Write-Host "Importing $($workflows.count) workflows to $newvCOCategory"
Write-Host "-------------------------------------------------------------------------------------------"
foreach ($workflow in $workflows)
{
Write-Host "Importing $($workflow.name) - " -NoNewline
$body = New-Object System.IO.MemoryStream
$boundary = [Guid]::NewGuid().ToString().Replace('-','')
$ContentType = 'multipart/form-data; boundary=' + $boundary
$b2 = Get-AsciiBytes ('--' + $boundary)
$body.Write($b2, 0, $b2.Length)
$body.Write($CRLF, 0, $CRLF.Length)           
$b = (Get-AsciiBytes ('Content-Disposition: form-data; name="categoryId"'))
$body.Write($b, 0, $b.Length)
$body.Write($CRLF, 0, $CRLF.Length)
$body.Write($CRLF, 0, $CRLF.Length)
$b = (Get-AsciiBytes $categoryId)
$body.Write($b, 0, $b.Length)
$body.Write($CRLF, 0, $CRLF.Length)
$body.Write($b2, 0, $b2.Length)
$body.Write($CRLF, 0, $CRLF.Length)     
$b = (Get-AsciiBytes ('Content-Disposition: form-data; name="file"; filename="$($workflow.Name)";'))
$body.Write($b, 0, $b.Length)
$body.Write($CRLF, 0, $CRLF.Length)            
$b = (Get-AsciiBytes 'Content-Type:application/octet-stream')
$body.Write($b, 0, $b.Length)
$body.Write($CRLF, 0, $CRLF.Length)
$body.Write($CRLF, 0, $CRLF.Length)
$b = [System.IO.File]::ReadAllBytes($workflow.FullName)
$body.Write($b, 0, $b.Length)
$body.Write($CRLF, 0, $CRLF.Length)
$body.Write($b2, 0, $b2.Length)
$b = (Get-AsciiBytes '--');
$body.Write($b, 0, $b.Length);
$body.Write($CRLF, 0, $CRLF.Length);
 
$header = @{"Authorization"= $auth;
"Accept"= "application/zip";
"Accept-Encoding"= "gzip,deflate,sdch";};    
Invoke-RestMethod -Method POST -Uri $impUrl -ContentType $ContentType -Body $body.ToArray() -Headers $header
Write-Host "Done" -foregroundcolor Green
}
 
Write-Host ""
Write-Host "Import Routine complete" -ForegroundColor Black -BackgroundColor Yellow
##################################################################