I’ve created a PowerShell script that can be used to generate an (offline) backup of a Citrix NetScaler.
If you want you can use the supplied batchfile for example to schedule the backup in Scheduled Tasks to run everyday.
Some more information about the parameters used:
| Parameter | Alias | Example value | Required | Explanation |
|---|---|---|---|---|
| -NSManagementURL | -URL | "http://192.168.100.1" | Yes | Specify how to connect to the NetScaler |
| -NSUserName | -Username or -User | "nsroot" | Yes* | The username used to connect to the NetScaler |
| -NSPassword | -Password | "nsroot" | Yes* | Password for the User |
| -NSCredential | -Credential | $(get-credential) | Yes* | You can pass a credential object to this script (Use Get-Credential to create a Credential object) |
| -BackupTargetLocation | -Target | "c:\backup" | Yes | Specify a target location where to place the Backup files. If the target does not exists, it will try to create it |
| -WinSCPAssembly | ".\WinSCPnet.dll" | Optional | Specify the location for the WinSCP .Net Assembly.** | |
| -NSBackupLevel | -Level | "basic" | Optional | The backup level, possible options are "full" or "basic" Default: "full" |
* Use the -NSCredential parameter or -NSUsername & -NSPassword, default is Username & Password
** Make sure to install WinSCP (msi) to use the default values or specify the location to the “WinSCPnet.dll” .Net assembly. You can download it here
If you need to create a user just for the backup purpose, you can give it these minimal rights. This will be enough to create and download the backup.
(^sftp.*)|(^scp.*)|(^(create|rm)\s+system\s+backup)|(^(create|rm)\s+system\s+backup\s+.*)|(^(save|show)\s+ns\s+config)|(^(save|show)\s+ns\s+config\s+.*)
PowerShell Script (BackupNS.ps1):
<#
.SYNOPSIS
Create a backup from the NetScaler and download a copy
.DESCRIPTION
Create a backup from the NetScaler and download a copy
.PARAMETER NSManagementURL
Management URL, used to connect to the NetScaler
.PARAMETER NSUserName
NetScaler username with enough access to configure it
.PARAMETER NSPassword
NetScaler username password
.PARAMETER NSCredential
Use a PSCredential object instead of a username or password. Use "Get-Credential" to generate a credential object
C:\PS> $Credential = Get-Credential
.PARAMETER WinSCPAssembly
Specify the location for the WinSCP .NET assembly (Optional)
When not specified the default location in the %ProgramFiles% / %ProgramFiles(x86)% will be used.
.PARAMETER BackupTargetLocation
Specify the target location where to store the configuration and logfile
.PARAMETER NSBackupLevel
Level to be used for the Backup. `"basic`" or `"full`" (Optional)
.EXAMPLE
.\BackupNS.ps1 -NSManagementURL "http://nsvpx01.domain.local" -NSPassword "P@ssw0rd" -NSUserName "nsroot" -BackupTargetLocation "C:\Backup" -Verbose
Create and download a backup from netscaler `"nsvpx01.domain.local`" and store it in `"C:\Backup`". And generate verbose output.
.EXAMPLE
.\BackupNS.ps1 -NSManagementURL "http://192.168.100.1" -Credential $(get-credential) -Target "C:\Backup" -Verbose
Create and download a backup from netscaler `"192.168.100.1`" and store it in `"C:\Backup`". And generate verbose output.
.NOTES
File Name : BackupNS.ps1
Version : v0.3
Author : John Billekens
Requires : PowerShell v3 and up
NetScaler 11.x and up
Run As Administrator
WinSCP
.LINK
https://blog.j81.nl
#>
[cmdletbinding(DefaultParametersetName="UsernamePassword")]
param(
[ValidateNotNullOrEmpty()]
[alias("URL")]
[string]$NSManagementURL,
[Parameter(ParameterSetName="UsernamePassword",Mandatory=$true)]
[alias("User", "Username")]
[string]$NSUserName,
[Parameter(ParameterSetName="UsernamePassword",Mandatory=$true)]
[alias("Password")]
[string]$NSPassword,
[Parameter(ParameterSetName="Credential",Mandatory=$true)]
[alias("Credential")]
[ValidateScript({
if ($_ -is [System.Management.Automation.PSCredential]) {
$true
} elseif ($_ -is [string]) {
$Script:Credential=Get-Credential -Credential $_
$true
} else {
Write-Error "You passed an unexpected object type for the credential (-NSCredential)"
}
})][object]$NSCredential,
[Parameter(Mandatory=$true)]
[alias("Target")]
[string]$BackupTargetLocation,
[Parameter(Mandatory=$false)]
[ValidateSet("full", "basic")]
[alias("Level")]
[string]$NSBackupLevel="full",
[Parameter(Mandatory=$false)]
[string]$WinSCPAssembly = $null
)
#requires -version 3.0
#requires -runasadministrator
#region Functions
function InvokeNSRestApi {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[PSObject]$Session,
[Parameter(Mandatory=$true)]
[ValidateSet('DELETE', 'GET', 'POST', 'PUT')]
[string]$Method,
[Parameter(Mandatory=$true)]
[string]$Type,
[string]$Resource,
[string]$Action,
[hashtable]$Arguments = @{},
[switch]$Stat = $false,
[ValidateScript({$Method -eq 'GET'})]
[hashtable]$Filters = @{},
[ValidateScript({$Method -ne 'GET'})]
[hashtable]$Payload = @{},
[switch]$GetWarning = $false,
[ValidateSet('EXIT', 'CONTINUE', 'ROLLBACK')]
[string]$OnErrorAction = 'EXIT'
)
if ([string]::IsNullOrEmpty($($Session.ManagementURL))) {
throw "ERROR. Probably not logged into the NetScaler"
}
if ($Stat) {
$uri = "$($Session.ManagementURL)/nitro/v1/stat/$Type"
} else {
$uri = "$($Session.ManagementURL)/nitro/v1/config/$Type"
}
if (-not ([string]::IsNullOrEmpty($Resource))) {
$uri += "/$Resource"
}
if ($Method -ne 'GET') {
if (-not ([string]::IsNullOrEmpty($Action))) {
$uri += "?action=$Action"
}
if ($Arguments.Count -gt 0) {
$queryPresent = $true
if ($uri -like '*?action*') {
$uri += '&args='
} else {
$uri += '?args='
}
$argsList = @()
foreach ($arg in $Arguments.GetEnumerator()) {
$argsList += "$($arg.Name):$([System.Uri]::EscapeDataString($arg.Value))"
}
$uri += $argsList -join ','
}
} else {
$queryPresent = $false
if ($Arguments.Count -gt 0) {
$queryPresent = $true
$uri += '?args='
$argsList = @()
foreach ($arg in $Arguments.GetEnumerator()) {
$argsList += "$($arg.Name):$([System.Uri]::EscapeDataString($arg.Value))"
}
$uri += $argsList -join ','
}
if ($Filters.Count -gt 0) {
$uri += if ($queryPresent) { '&filter=' } else { '?filter=' }
$filterList = @()
foreach ($filter in $Filters.GetEnumerator()) {
$filterList += "$($filter.Name):$([System.Uri]::EscapeDataString($filter.Value))"
}
$uri += $filterList -join ','
}
}
Write-Verbose -Message "URI: $uri"
$jsonPayload = $null
if ($Method -ne 'GET') {
$warning = if ($GetWarning) { 'YES' } else { 'NO' }
$hashtablePayload = @{}
$hashtablePayload.'params' = @{'warning' = $warning; 'onerror' = $OnErrorAction; <#"action"=$Action#>}
$hashtablePayload.$Type = $Payload
$jsonPayload = ConvertTo-Json -InputObject $hashtablePayload -Depth 100
Write-Verbose -Message "JSON Payload:`n$jsonPayload"
}
$response = $null
$restError = $null
try {
$restError = @()
$restParams = @{
Uri = $uri
ContentType = 'application/json'
Method = $Method
WebSession = $Session.WebSession
ErrorVariable = 'restError'
Verbose = $false
}
if ($Method -ne 'GET') {
$restParams.Add('Body', $jsonPayload)
}
$response = Invoke-RestMethod @restParams
if ($response) {
if ($response.severity -eq 'ERROR') {
throw "Error. See response: `n$($response | Format-List -Property * | Out-String)"
} else {
Write-Verbose -Message "Response:`n$(ConvertTo-Json -InputObject $response | Out-String)"
if ($Method -eq "GET") { return $response }
}
}
}
catch [Exception] {
if ($Type -eq 'reboot' -and $restError[0].Message -eq 'The underlying connection was closed: The connection was closed unexpectedly.') {
Write-Verbose -Message 'Connection closed due to reboot'
} else {
throw $_
}
}
}
function Connect-NetScaler {
[cmdletbinding()]
param(
[parameter(Mandatory)]
[string]$ManagementURL,
[parameter(Mandatory)]
[pscredential]$Credential = (Get-Credential -Message 'NetScaler credential'),
[int]$Timeout = 3600,
[switch]$PassThru
)
Write-Verbose -Message "Connecting to $ManagementURL..."
try {
$login = @{
login = @{
username = $Credential.UserName;
password = $Credential.GetNetworkCredential().Password
timeout = $Timeout
}
}
$loginJson = ConvertTo-Json -InputObject $login
Write-Verbose "JSON Data:`n$($loginJson | Out-String)"
$saveSession = @{}
$params = @{
Uri = "$ManagementURL/nitro/v1/config/login"
Method = 'POST'
Body = $loginJson
SessionVariable = 'saveSession'
ContentType = 'application/json'
ErrorVariable = 'restError'
Verbose = $false
}
$response = Invoke-RestMethod @params
if ($response.severity -eq 'ERROR') {
throw "Error. See response: `n$($response | Format-List -Property * | Out-String)"
} else {
Write-Verbose -Message "Response:`n$(ConvertTo-Json -InputObject $response | Out-String)"
}
} catch [Exception] {
throw $_
}
$session = [PSObject]@{
ManagementURL=[string]$ManagementURL;
WebSession=[Microsoft.PowerShell.Commands.WebRequestSession]$saveSession;
}
$Script:NSSession = $session
if($PassThru){
return $session
}
}
#endregion Functions
#region Script variables
[string]$ScriptDateTime = (Get-Date).ToString("yyyyMMddHHmm")
[string]$WinSCPSite = "https://winscp.net/eng/download.php"
[string]$WinSCPErrorSite = "https://winscp.net/eng/docs/message_net_operation_not_supported"
[string]$WinSCPAssemblyx86 = "C:\Program Files\WinSCP\WinSCPnet.dll"
[string]$WinSCPAssemblyx64 = "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
[string]$WinSCPAssemblyScript = Join-Path $(Split-Path $MyInvocation.MyCommand.Path -Parent) "WinSCPnet.dll"
[ipaddress]$NSHostIP = [System.Net.Dns]::GetHostAddresses($NSManagementURL.replace("https://","").replace("http://","").replace("/","")) | select-object IPAddressToString -expandproperty IPAddressToString
[string]$BackupFilename = "ns-backup-$($NSHostIP)-$($ScriptDateTime)"
[string]$BackupTargetLocation = $BackupTargetLocation.Trim("\")
#endregion Script variables
#region Target Directory
if ( -Not (Test-Path $BackupTargetLocation)) {
New-Item -Path $BackupTargetLocation -ItemType Directory -Force | out-null
}
#endregion Target Directory
#region NSCredential
if (-not([string]::IsNullOrWhiteSpace($NSCredential))) {
Write-Verbose "Using NSCredential"
} elseif ((-not([string]::IsNullOrWhiteSpace($NSUserName))) -and (-not([string]::IsNullOrWhiteSpace($NSPassword)))){
Write-Verbose "Using NSUsername / NSPassword"
[pscredential]$NSCredential = new-object -typename System.Management.Automation.PSCredential -argumentlist $NSUserName, $(ConvertTo-SecureString -String $NSPassword -AsPlainText -Force)
} else {
Write-Verbose "No valid username/password or credential specified. Enter a username and password, e.g. `"nsroot`""
[pscredential]$NSCredential = Get-Credential -Message "NetScaler username and password:"
}
#endregion NSCredential
#region Backup
try {
Write-Verbose "Login to NetScaler and save session to global variable"
$NSSession = Connect-NetScaler -ManagementURL $NSManagementURL -Credential $NSCredential -PassThru
Write-Verbose "Saving NetScaler configuration"
$response = InvokeNSRestApi -Session $NSSession -Method POST -Type nsconfig -Action save
$payload = @{"filename"="$($BackupFilename)";"level"="$($NSBackupLevel)";"comment"="Backup created by BackupNS.ps1 PoSH Script"}
$response = InvokeNSRestApi -Session $NSSession -Method POST -Type systembackup -Payload $payload -Action create
try {
Write-Verbose "Loading WinSCP .NET assembly"
if (-not [string]::IsNullOrWhiteSpace($WinSCPAssembly)){
if (Test-Path $WinSCPAssembly) {
Write-Verbose "`"$WinSCPAssembly`" will be used"
}
} else {
if (Test-Path $WinSCPAssemblyx64) {
$WinSCPAssembly = $WinSCPAssemblyx64
} elseif (Test-Path $WinSCPAssemblyx86) {
$WinSCPAssembly = $WinSCPAssemblyx86
} elseif (Test-Path $WinSCPAssemblyScript) {
$WinSCPAssembly = $WinSCPAssemblyScript
} else {
start $WinSCPSite
throw "The .NET Assembly could not be found"
}
Write-Verbose "using: $WinSCPAssembly"
}
Add-Type -Path "$WinSCPAssembly"
Write-Verbose "assembly successfully locaded"
Write-Verbose "Setup WinSCP session options"
$WinSCPSessionOptions = New-Object WinSCP.SessionOptions
$WinSCPSessionOptions.Protocol = [WinSCP.Protocol]::sftp
$WinSCPSessionOptions.HostName = "$($NSHostIP.IPAddressToString)"
$WinSCPSessionOptions.UserName = "$($NSCredential.UserName)"
$WinSCPSessionOptions.Password = "$($NSCredential.GetNetworkCredential().Password)"
$WinSCPSessionOptions.GiveUpSecurityAndAcceptAnySshHostKey = $true
$WinSCPSession = New-Object WinSCP.Session
Write-Verbose "Enable Logging"
$WinSCPSession.SessionLogPath = "$($BackupTargetLocation)\$($BackupFilename)-log.txt"
try {
Write-Verbose "Connecting"
$WinSCPSession.Open($WinSCPSessionOptions)
Write-Verbose "Try to download the backup file"
$WinSCPTransferOptions = New-Object WinSCP.TransferOptions
$WinSCPTransferOptions.TransferMode = [WinSCP.TransferMode]::Binary
$WinSCPTransferResult = $WinSCPSession.GetFiles("/var/ns_sys_backup/$($BackupFilename).tgz", "$($BackupTargetLocation)\$($BackupFilename).tgz", $False, $WinSCPTransferOptions)
Write-Verbose "Throw on any error"
$WinSCPTransferResult.Check()
Write-Verbose "Print results"
foreach ($transfer in $WinSCPTransferResult.Transfers) {
Write-Host ("Upload of {0} succeeded" -f $transfer.FileName)
}
} finally {
Write-Verbose "Disconnect, clean up"
$WinSCPSession.Dispose()
}
} catch [System.IO.IOException]{
Start $WinSCPErrorSite
Write-Error "DLL was probably downloaded with Internet Explorer, unblock before extracting"
throw $($_.Exception.Message)
} catch {
throw $($_.Exception.Message)
}
} catch {
throw $($_.Exception.Message)
} finally {
Write-Verbose "Removing Backup file from NetScaler"
$response = InvokeNSRestApi -Session $NSSession -Method DELETE -Type systembackup -Resource "$($BackupFilename).tgz"
}
#endregion Backup
Batchfile (BackupNS.cmd):
@ECHO OFF
setlocal EnableDelayedExpansion
REM --> Check for permissions
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
REM --> If error flag set, we do not have admin.
if '%errorlevel%' NEQ '0' (
echo Requesting administrative privileges...
goto UACPrompt
) else ( goto gotAdmin )
:UACPrompt
echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
set params = %*:"=""
echo UAC.ShellExecute "cmd.exe", "/c %~s0 %params%", "", "runas", 1 >> "%temp%\getadmin.vbs"
"%temp%\getadmin.vbs"
del "%temp%\getadmin.vbs"
exit /B
:gotAdmin
pushd "%CD%"
CD /D "%~dp0"
goto StartScript
rem ===== Help Example =====
SET OPTIONS=-NSManagementURL "http://nsvpx01.domain.local"
SET OPTIONS=%OPTIONS% -NSPassword "P@ssw0rd"
SET OPTIONS=%OPTIONS% -NSUsername "nsroot"
SET OPTIONS=%OPTIONS% -BackupTargetLocation "C:\Backup"
SET OPTIONS=%OPTIONS% -Verbose
NOTE: Use the "-Verbose" parameter to get diagnostic output
rem ===== End Help Example =====
:StartScript
SET OPTIONS=-NSManagementURL "http://nsvpx01.domain.local"
SET OPTIONS=%OPTIONS% -NSPassword "P@ssw0rd"
SET OPTIONS=%OPTIONS% -NSUsername "nsroot"
SET OPTIONS=%OPTIONS% -BackupTargetLocation "C:\Backup"
SET OPTIONS=%OPTIONS% -Verbose
%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -NoLogo -NonInteractive -ExecutionPolicy Bypass -File "%~dp0BackupNS.ps1" %OPTIONS%
Edit (08-04-2017, 0.2) Added extra info for when the dll was downloaded with IE, and returned an error.
Edit (29-01-2019, 0.3) Added rights for a possible backup user and changed some typo’s, thank you Chris for pointing that out to me.
Hope this can help you. If you have questions, please let me know.
Why does this need “Run As Administrator” permissions?
You can run the script without Administrative permissions, you must then make sure a target directory exists with the right permissions or it must have permissions to create the folder.
To run without Admin permissions remove the line “#requires -runasadministrator” from the powershell script, and only use the part of the batchfile after the label “:StartScript”.
Hi John
1. Can’t seem to get get-credential working:
Cannot process argument transformation on parameter ‘Credential’. Cannot convert the “$(get-credential)” value of type
“System.String” to type “System.Management.Automation.PSCredential”.
2. How would the restore process work ?
The offline backup is not listed in Netscaler (should be in /var/ns_sys_backup/nsbackupmap.txt ?)
Thanks
Never mind about the restore process :
untar backup file, pull nsbackupmap.txt from it and copy to /var/ns_sys_backup/. along with the .tgz backup file. After reboot NS will see the backup.
Could also add this line to your PS script to have a nsbackupmap.txt with date extension that matches the backup.tgz file:
$WinSCPTransferResult = $WinSCPSession.GetFiles(“/var/ns_sys_backup/nsbackupmap.txt”, “$($BackupTargetLocation)\nsbackupmap-$($ScriptDateTime).txt”, $False, $WinSCPTransferOptions)
Thanks for the tip!
Thanks very much for this script! Just a quick comment to say that NSPassword and NSUsername are reversed in the BackupNS.cmd file, caught me out for a minute 🙂
Thank you Chris, I updated the post.
Hi John,
thanks for the backup script.
For the people who are having problems with connection via https and self-signed certs.
Add this to PowerShell script: “[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }”
Regards
Julian
Thanks, much appreciated!
Hi John! Thanks for this script. I wanted to let you know that it appears Citrix ADC 13.1.x.x requires TLS 1.2
Powershell uses TLS 1.0 by default on the Invoke-RestMethod
We found this out when the script stopped working after an upgrade.
Fix is to add this line for 13.1 (but seems to work for 13.0 as well)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Hope this helps others who might run into this issue.
Thanks for your input, I haven’t used or looked at this code for a while.
Maybe I will have a look and also implement your suggestion.