/
Backup-AlteryxServer.ps1
369 lines (328 loc) · 16.6 KB
/
Backup-AlteryxServer.ps1
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
#requires -RunAsAdministrator
<#
.SYNOPSIS
Alteryx Server MongoDB Backup PowerShell Script
.DESCRIPTION
This script will backup the Alteryx Server MongoDB locally,
then will create a compressed copy of the backup on a remote system.
.PARAMETER dest
The path the local backup will be created in.
Defaults to [C:\Temp\MongoDB-Backup]
.PARAMETER destRetention
The number of days the local backup will be stored. This script will prune older backups/logs based on this.
By default, this is set to 1
.PARAMETER archive
The path that the remote, compressed, backup will be stored.
Defaults to [\\NAS.DOMAIN.COM\Backups\Alteryx-Node1\MongoDB-Backups]
.PARAMETER archiveRetention
The number of days the remote archives will be stored. This script will prune older backups based on this.
By default, this is set to 4
.PARAMETER source
This array contains the directories to include in the backup.
By default, this will be empty and the script will add to the array at runtime
.PARAMETER dbPath
The path to the MongoDB. This can be found in Alteryx's persistence settings.
Defaulst to [C:\ProgramData\Alteryx\Service\Persistence\MongoDB]
.PARAMETER binPath
The path to the Alteryx service binary/executable.
Defaults to [C:\Program Files\Alteryx\bin]
.PARAMETER fileList
This array contains additional files to include in the backup.
By default, this will contain the path to Alteryx's RuntimeSettings.xml, SystemAlias.xml, and SystemConnections.xml.
.PARAMETER backupSecrets
This switch parameter allows the backup of the controller token and MongoDB passwords.
By default, this is absent.
.EXAMPLE
Backup-AlteryxServer [[-dest] E:\Temp\MongoDB_Backup] [[-archive] \\FILER.DOMAIN.COM\Share\Alteryx\MongoDB-Backups]
This example uses a non-default destination
.EXAMPLE
Backup-AlteryxServer -backupSecrets
This example will backup the Alteryx server's secrets in addition to the database.
.NOTES
Author: Comrad Kite
Continuus Technologies
Date: 06/2023
Maintenance Window Considerations:
The Alteryx service must be stopped to perform a backup of the MongoDB. While
the service is stopped, scheduled workflows will not be triggered and the
gallery is inaccessible. Because of this, it's best to perform a backup during
a defined maintenance window.
When scheduling the backup, make sure it doesn't occur when Windows patching is
scheduled. Also review any scheduled workflows for conflicts with
the backup time. A running workflow can prevent the Alteryx service from
stopping and could require force stopping the process. It's a best practice that
the service is cleanly stopped before a backup.
The duration of the backup process directly correlates with the size of your
MongoDB. A 13GB database will takes approx 3 min to dump, while a 63GB database
full of content takes nearly an hour. Once the backup has been completed, the
Alteryx service is started so the server is accessible.
The backup is then copied to a compressed file on a remote SMB file share. On
average the overall process should take about 30 minutes but its all down to
the content of the DB and the speed of the network.
backupSecrets Considerations:
This script provides the ability to backup the Alteryx server's controller token
and MongoDB user and admin passwords. This isn't intended for a scheduled backup,
but rather to ensure that everything is captured for a server migration.
Multi-Node Deployment Considerations:
In a multi-node environment, where the role of an Alteryx server are on different hosts,
the Alteryx service will need to be stopped on the remote hosts before the MongoDB can
be stopped for a backup.
For Gallery and Worker nodes, this could be accomplished by a Scheduled Task on
the remote machines that stops the Alteryx service prior to the backup window and a
second task that starts the service at the end of the backup window.
- Stop-AlteryxServer.ps1
- Start-AlteryxServer.ps1
For Worker nodes, the task watches (queries) the Alteryx server API, which is served by
the Gallery, and waits to start the service until it responds with a HTTP 200 OK message.
- Watch-AlteryxBackup.ps1
A more sophisticated approach would be to leverage WinRM and connect to the remote
hosts to stop the Alteryx service as part of the backup script, starting the service
once the backup has completed.
Attribution: This script is loosely based on an example found on the Alteryx Community.
https://community.alteryx.com/t5/Alteryx-Server-Knowledge-Base/Alteryx-Server-Backup-and-Recovery-Part-2-Procedures/tac-p/357058/highlight/true#M305
#>
param (
[Parameter(Mandatory = $false,
HelpMessage = "The path the local backup will be created in.")]
[string]$dest = "C:\Temp\MongoDB-Backup",
[Parameter(Mandatory = $false,
HelpMessage = "The number of days to retain a backup.")]
[string]$destRetention = '1',
[Parameter(Mandatory = $false,
HelpMessage = "The path that the remote, compressed, backup will be stored.")]
[string]$archive = "\\USCE-DC-P01.ad.continuus-technologies.com\Data\Alteryx\MongoDB-Backups",
[Parameter(Mandatory = $false,
HelpMessage = "The number of days to retain a backup.")]
[string]$archiveRetention = '4',
[Parameter(Mandatory = $false,
HelpMessage = "Additional directories to include in the backup.")]
[System.Collections.ArrayList]$source = @{},
[Parameter(Mandatory = $false,
HelpMessage = "The path to the MongoDB.")]
[string]$dbPath = "C:\ProgramData\Alteryx\Service\Persistence\MongoDB",
[Parameter(Mandatory = $false,
HelpMessage = "The path to the Alteryx binary")]
[string]$binPath = "C:\Program Files\Alteryx\bin",
[Parameter(Mandatory = $false,
HelpMessage = "Additional files to include in the backup.")]
[System.Collections.ArrayList]$fileList = @(
"C:\ProgramData\Alteryx\RuntimeSettings.xml",
"C:\ProgramData\Alteryx\Engine\SystemAlias.xml",
"C:\ProgramData\Alteryx\Engine\SystemConnections.xml"
),
[Parameter(HelpMessage = "Backup controller token and mongo passwords")]
[switch]$backupSecrets
)
if (!(Test-Path $dest)) {
try {
New-Item -ItemType directory -Path $dest -ErrorAction Stop
Write-Output "The local backup path, $dest, was created automatically"
}
catch {
Write-Output "The local backup path, $dest, is inaccessible. Check path."
}
}
if (!(Test-Path $archive)) {
try {
New-Item -ItemType directory -Path $archive -ErrorAction Stop
Write-Output "The archive path, $archive, was missing and created automatically"
}
catch {
Write-Output "The archive path, $archive, is inaccessible. Check path."
}
}
$logdate = Get-Date -Format yyyy-MM-dd-HH-mm
[string]$log = $dest + "\BackupLog-" + $logdate + ".csv"
function SendOutput() {
param (
[string]
$output
)
$Now = Get-Date -Format "MM/dd/yyyy HH:mm:ss"
# Build a hashtable of the collected data + computername, faster than arrays
$Properties = @{ComputerName = $env:COMPUTERNAME
Now = $Now | Out-String
Output = $Output | Out-String
}
$Object = New-Object -TypeName PSObject -Property $Properties
Write-Output $Object | Select-Object -Property Now, ComputerName, Output | Export-Csv -Path $log -NoTypeInformation -Append
Write-Output $Object | Select-Object -Property Output | ConvertTo-CSV | Write-Host -ForegroundColor Magenta
}
$totalScriptTime = Measure-Command {
$Output = 'Beginning backup script'
SendOutput -Output $Output
# Stop and disable service to ensure MongoDB will be offline
Write-Warning "Stopping AlteryxService.exe"
Set-Service -ServiceName AlteryxService -StartupType Disabled
Stop-Service AlteryxService | Out-Null
# Wait for the service to stop
[int]$i = 0
[string]$processname = "AlteryxService"
$process = Get-Process -Name $processname -ErrorAction SilentlyContinue
if (!($process)) {
Write-Host "$processname stopped" -ForegroundColor Green
}
if ($process) {
Write-Host "$processname is still running" -ForegroundColor Yellow
Write-Verbose "$processname PID $($process.Id)" -Verbose
}
while ($process) {
if ($process.HasExited) {
Write-Host "$processname has stopped" -ForegroundColor Green
break
}
Write-Host "Pausing for 30 seconds"
Start-Sleep -Seconds 30
$process = Get-Process -Name $processname
if ($i -ge 20) {
# If this has been waiting 10 minutes, it will try to force stop the Alteryx service
Write-Verbose "Waited >20 attempts, attempting to force stop" -Verbose
Stop-Process -Force $process
}
if ($i -ge 22) {
# Force stopping the service has failed, terminate script, manual intervention required.
Write-Warning "Detected runaway loop"
break
}
Write-Host "Attempt $i"
$i++
Stop-Process $process
} # end while $process
Start-Sleep 2
# Check if the lock file is in use
$lockFile = $dbPath + "\Mongod.lock"
#$lockFile = "C:\ProgramData\Alteryx\Service\Persistence\MongoDB\Mongod.lock"
Remove-Item "$lockfile" -Force -ErrorAction SilentlyContinue
if (!(Test-Path $lockfile)) {
New-Item $lockfile
}
else {
$output = "Check the lockfile, $($lockFile)"; SendOutput -Output $Output
break
}
$Output = "Starting MongoDB backup"
SendOutput -Output $Output
# Creating the emondodump backup, calling Alteryx binary directly using &
$dumpname = "MongodbDump_$(Get-Date -f yyyy-MM-dd-HH-mm)"
$dump = & "$binPath\AlteryxService.exe" emongodump=$dest\$dumpname -Wait
$result = $dump.ExitCode
if ($result -eq "False") {
$output = "MongoDB backup failed during dump"
SendOutput -Output $Output
break
}
else {
$output = "MongoDB backup completed"
SendOutput -Output $Output
$Source.Add("$dest\$dumpname")
}
# Restarting the Alteryx service
Set-Service -ServiceName AlteryxService -StartupType Automatic
$output = "Starting Alteryx Service"
SendOutput -Output $Output
Start-Service AlteryxService | Out-Null
$output = "Starting Alteryx Service exit code: $lastexitcode"
SendOutput -Output $Output
if ($backupSecrets){
Write-Warning "The backupSecrets switch was set, this backup will contain secrets."
# Backup controller token
& "$binPath\AlteryxService.exe" getserversecret -Wait | Out-File $dest\$dumpname\serversecret.txt
# Backup database passwords
& "$binPath\AlteryxService.exe" getemongopassword -Wait | Out-File $dest\$dumpname\emongopassword.txt
$output = "Exported controller token and MongoDB secrets"
SendOutput -Output $Output
}else{
$output = "Exported controller token and MongoDB secrets"
SendOutput -Output $Output
}
# Add additional files to $dest
foreach ($file in $filelist) {
$sourceFile = $file
if (Test-Path $sourceFile){
Get-ChildItem $sourceFile | Copy-Item -Recurse -Destination $dest\$dumpname
$destFile = $sourceFile.Split('\') | Select-Object -Last 1
# Make sure the copy matches the source
if ((Get-FileHash $sourceFile).Hash -ne (Get-FileHash $dest\$dumpname\$destFile).Hash) {
$Output = "Copy to remote archive failed. $dest\$destFile - $file hashes don't match"
SendOutput -Output $Output
}
else {
$Output = "Copy to remote archive successful. $dest\$destFile - $file hashes match"
SendOutput -Output $Output
}
}else{
$Output = "$sourcefile does not exist, skipping."
SendOutput -Output $Output
}
}
# Create a compressed file on the $archive and copy the backup from $dest for long term storage
Write-Host "Beginning archival process" -ForegroundColor Cyan
$compressionDuration = Measure-Command {
foreach ($item in $Source) {
# If this is running as a Scheduled Task, validate that the account running the task has permissions
# setup to allow access the $archive location.
$destination = "$archive\AlteryxBackup-$logdate.zip"
$archiveDuration = Measure-Command {
if (Test-Path $destination) {
Remove-Item $destination
}
# Compression options: [Fastest / NoCompression / Optimal / SmallestSize]
$compressionLevel = [System.IO.Compression.CompressionLevel]::Fastest
# Calling the .NET API directly
Add-Type -Assembly "System.IO.Compression.Filesystem"
# Figure out if $item is a directory or a file
if ((Get-Item -Path $item) -is [System.IO.DirectoryInfo]) {
# This is directory, not a file
[System.IO.Compression.ZipFile]::CreateFromDirectory($item, $destination, $compressionLevel, $true)
# Below is another way to archive the contents of the directory
# Get-ChildItem $FilesDirectory | ForEach-Object {[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip, $_.FullName, (Split-Path $_.FullName -Leaf), $compressionLevel)}
}
else {
# This is a file, not a directory
$zip = [System.IO.Compression.ZipFile]::Open($destination, 'update')
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip, $item, (Split-Path $item -Leaf), $compressionLevel)
# This closes the file, if we didn't do this it would remain open
$zip.Dispose()
}
} # end measure compression
$archiveSeconds = $archiveDuration.Seconds
$Output = "$item took $archiveSeconds seconds to compress."
SendOutput -Output $Output
}
} # end measure archival
$compressionSeconds = $compressionDuration.Seconds
$Output = "Total compression time: $compressionSeconds"
SendOutput -Output $Output
$Output = "Backup archival to $archive complete"
SendOutput -Output $Output
# Define lastWriteTime based on $retention
$lastWrite = (Get-Date).AddDays(-$destRetention)
# Clean up the local backups based on lastwrite filter and folder filter
$backups = Get-ChildItem $dest -Directory -Filter "MongodbDump*" | Where-Object { $_.LastWriteTime -lt "$lastWrite" }
if ($backups -ne $null) {
foreach ($backup in $backups) {
$Output = "Removing backup $backup"; SendOutput -Output $Output
Remove-Item -Path $backup.FullName -Recurse -Force
}
}
# Clean up the local backup logs based on lastwrite filter and file name filter
$logFiles = Get-Childitem $dest -File -Filter "BackupLog-*.csv" | Where-Object { $_.LastWriteTime -lt "$lastWrite" }
if ($logFiles -ne $null) {
foreach ($logFile in $logFiles) {
$Output = "Removing log $logFile"; SendOutput -Output $Output
Remove-Item -Path $logFile.FullName -Recurse -Force
}
}
# Define lastWriteTime based on $retention
$lastWrite = (Get-Date).AddDays(-$archiveRetention)
# Clean up the archived remote backups based on lastwrite filter and file name
$archiveFiles = Get-Childitem "$archive\AlteryxBackup*.zip" -Recurse | Where-Object { $_.LastWriteTime -lt "$lastWrite" }
if ($archiveFiles -ne $null) {
foreach ($zipArchive in $archiveFiles) {
$Output = "Removing archive $zipArchive"; SendOutput -Output $Output
Remove-Item -Path $zipArchive.FullName -Recurse -Force
}
}
} # end measure backup script
$Output = 'Ending backup script'; SendOutput -Output $Output
$Output = "Total MongoDB backup time: $totalScriptTime"; SendOutput -Output $Output