Redgate - Deploy from Package (Worker Friendly)

Octopus.Script exported 2022-01-25 by markgould belongs to ‘Redgate’ category.

Uses Redgate’s SQL Change Automation to deploy a package containing a database schema to a SQL Server database, without a review step.

Requires SQL Change Automation version 3.0.2 or later.

Version date: 2022-01-24

This step template is worker friendly, you can pass in a package reference rather than having to reference a previous step which downloaded the package. This step requires Octopus Deploy 2019.10.0 or higher.

NOTE: This template requires the SQLCMD utility, if not found, the template will install the following:

  • Visual Studio 2017 C++ Redistributable
  • SQL Server 2017 ODBC driver
  • SQLCMD utility

Parameters

When steps based on the template are included in a project’s deployment process, the parameters below can be set.

Package

DLMAutomationPackageName =

The name of the package to extract and deploy

Target SQL Server instance

DLMAutomationTargetDatabaseServer =

The fully qualified SQL Server instance name for the target database.

Target database name

DLMAutomationTargetDatabaseName =

The name of the database to deploy changes to. This must be an existing database.

Username (optional)

DLMAutomationTargetUsername =

The SQL Server username used to connect to the database. If you leave this field and ‘Password’ blank, Windows authentication will be used to connect instead, using the account that runs the Tentacle service.

Password (optional)

DLMAutomationTargetPassword =

You must enter a password if you entered a username.

Filter path (optional)

DLMAutomationFilterPath =

Specify the location of a SQL Compare filter file (.scpf), which defines objects to include/exclude in the schema comparison. Filter files are generated by SQL Source Control.

For more help see Using SQL Compare filters in SQL Change Automation.

SQL Compare options (optional)

DLMAutomationCompareOptions =

Enter SQL Compare options to apply when generating the update script. Use a comma-separated list to enter multiple values. For more help see Using SQL Compare options in SQL Change Automation.

SQL Data Compare options (optional)

DLMAutomationDataCompareOptions =

Enter SQL Data Compare options to apply when generating the update script. Use a comma-separated list to enter multiple values. For more help see Using SQL Data Compare options in SQL Change Automation.

Transaction isolation level (optional)

DLMAutomationTransactionIsolationLevel = Serializable

Select the transaction isolation level to be used in deployment scripts.

Ignore static data

DLMAutomationIgnoreStaticData =

Exclude changes to static data when generating the deployment resources.

Query batch timeout (in seconds)

DLMAutomationQueryBatchTimeout = 30

The execution timeout, in seconds, for each batch of queries in the update script. The default value is 30 seconds. A value of zero indicates no execution timeout.

Skip post update schema check

DLMAutomationSkipPostUpdateSchemaCheck = False

Don’t check that the target database has the correct schema after the update has run.

SQL Change Automation version (optional)

SpecificModuleVersion =

If you wish to use a specific version of SQL Change Automation rather than the latest, enter the version number here.

SQL Change Automation Install Location (optional)

DLMModuleInstallLocation =

The SQL Change Automation cmdlets will be downloaded from the PowerShell gallery. Please specify the folder folder where those packages will be saved to. It can be relative or absolute.

If this is empty it will default $Home\Documents\WindowsPowerShell\Modules which is the recommended location from Microsoft.

Trust server certificate

DLMAutomationTrustServerCertificate =

Check this box to trust the SQL Server certificate.

Connection String (Optional)

DLMAutomationCustomConnectionString =

If set, uses this as the full connection string. Allows for advanced configuration.

Script body

Steps based on this template will execute the following PowerShell script.

$DlmAutomationModuleName = "DLMAutomation"
$SqlChangeAutomationModuleName = "SqlChangeAutomation"
$ModulesFolder = "$Home\Documents\WindowsPowerShell\Modules"


if ([string]::IsNullOrWhiteSpace($DLMModuleInstallLocation) -eq $false)
{
	if ((Test-Path $DLMModuleInstallLocation -IsValid) -eq $false)
    {
    	Write-Error "The path $DLMModuleInstallLocation is not valid, please use a relative or absolute path."
        exit 1
    }
    
    $ModulesFolder = [System.IO.Path]::GetFullPath($DLMModuleInstallLocation)            
}

Write-Host "Modules will be installed into $ModulesFolder"

$LocalModules = (New-Item "$ModulesFolder" -ItemType Directory -Force).FullName
$env:PSModulePath = "$LocalModules;$env:PSModulePath"

function IsScaAvailable
{
    if ((Get-Module $SqlChangeAutomationModuleName) -ne $null) {
        return $true
    }

    return $false
}

function InstallCorrectSqlChangeAutomation
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $false)]
        [Version]$requiredVersion
    )

    $moduleName = $SqlChangeAutomationModuleName

    # this will be null if $requiredVersion is not specified - which is exactly what we want
    $maximumVersion = $requiredVersion

    if ($requiredVersion) {
        if ($requiredVersion.Revision -eq -1) {
            #If provided with a 3 part version number (the 4th part, revision, == -1), we should allow any value for the revision
            $maximumVersion = [Version]"$requiredVersion.$([System.Int32]::MaxValue)"
        }

        if ($requiredVersion.Major -lt 3) {
            # If the specified version is below V3 then the user is requesting a version of DLMA. We should look for that module name instead
            $moduleName = $DlmAutomationModuleName
        }
    }

    $installedModule = GetHighestInstalledModule $moduleName -minimumVersion $requiredVersion -maximumVersion $maximumVersion

    if (!$installedModule) {
        #Either SCA isn't installed at all or $requiredVersion is specified but that version of SCA isn't installed
        Write-Verbose "$moduleName $requiredVersion not available - attempting to download from gallery"
        InstallLocalModule -moduleName $moduleName -minimumVersion $requiredVersion -maximumVersion $maximumVersion
    }
    elseif (!$requiredVersion) {
        #We've got a version of SCA installed, but $requiredVersion isn't specified so we might be able to upgrade
        $newest = GetHighestInstallableModule $moduleName
        if ($newest -and ($installedModule.Version -lt $newest.Version)) {
            Write-Verbose "Updating $moduleName to version $($newest.Version)"
            InstallLocalModule -moduleName $moduleName -minimumVersion $newest.Version
        }
    }

    # Now we're done with install/upgrade, try to import the highest available module that matches our version requirements

    # We can't just use -minimumVersion and -maximumVersion arguments on Import-Module because PowerShell 3 doesn't have them,
    # so we have to find the precise matching installed version using our code, then import that specifically. Note that
    # $requiredVersion and $maximumVersion might be null when there's no specific version we need.
    $installedModule = GetHighestInstalledModule $moduleName -minimumVersion $requiredVersion -maximumVersion $maximumVersion

    if (!$installedModule -and !$requiredVersion) {
        #Did not find SCA, and we don't have a required version so we might be able to use an installed DLMA instead.
        Write-Verbose "$moduleName is not installed - trying to fall back to $DlmAutomationModuleName"
        $installedModule = GetHighestInstalledModule $DlmAutomationModuleName
    }

    if ($installedModule) {
        Write-Verbose "Importing installed $($installedModule.Name) version $($installedModule.Version)"
        Import-Module $installedModule -Force
    }
    else {
        throw "$moduleName $requiredVersion is not installed, and could not be downloaded from the PowerShell gallery"
    }
}

function InstallPowerShellGet {
    [CmdletBinding()]
    Param()

    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
    $psget = GetHighestInstalledModule PowerShellGet
    if (!$psget)
    {
        Write-Warning @"
Cannot access the PowerShell Gallery because PowerShellGet is not installed.
To install PowerShellGet, either upgrade to PowerShell 5 or install the PackageManagement MSI.
See https://docs.microsoft.com/en-us/powershell/gallery/installing-psget for more details.
"@
        throw "PowerShellGet is not available"
    }

    if ($psget.Version -lt [Version]'1.6') {
        #Bootstrap the NuGet package provider, which updates NuGet without requiring admin rights
        Write-Debug "Installing NuGet package provider"
        Get-PackageProvider NuGet -ForceBootstrap | Out-Null

        #Use the currently-installed version of PowerShellGet
        Import-PackageProvider PowerShellGet

        #Download the version of PowerShellGet that we actually need
        Write-Debug "Installing PowershellGet"
        Save-Module -Name PowerShellGet -Path $LocalModules -MinimumVersion 1.6 -Force -ErrorAction SilentlyContinue
    }

    Write-Debug "Importing PowershellGet"
    Import-Module PowerShellGet -MinimumVersion 1.6 -Force
    #Make sure we're actually using the package provider from the imported version of PowerShellGet
    Import-PackageProvider ((Get-Module PowerShellGet).Path) | Out-Null
}

function InstallLocalModule {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [string]$moduleName,
        [Parameter(Mandatory = $false)]
        [Version]$minimumVersion,
        [Parameter(Mandatory = $false)]
        [Version]$maximumVersion
    )
    try {
        InstallPowerShellGet

        Write-Debug "Install $moduleName $requiredVersion"
        Save-Module -Name $moduleName -Path $LocalModules -Force -AcceptLicense -MinimumVersion $minimumVersion -MaximumVersion $maximumVersion -ErrorAction Stop
    }
    catch {
        Write-Warning "Could not install $moduleName $requiredVersion from any registered PSRepository"
    }
}

function GetHighestInstalledModule {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $moduleName,

        [Parameter(Mandatory = $false)]
        [Version]$minimumVersion,
        [Parameter(Mandatory = $false)]
        [Version]$maximumVersion
    )

    return Get-Module $moduleName -ListAvailable |
           Where {(!$minimumVersion -or ($_.Version -ge $minimumVersion)) -and (!$maximumVersion -or ($_.Version -le $maximumVersion))} |
           Sort -Property @{Expression = {[System.Version]($_.Version)}; Descending = $True} |
           Select -First 1
}

function GetHighestInstallableModule {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $moduleName
    )

    try {
        InstallPowerShellGet
        Find-Module SqlChangeAutomation -AllVersions |
            Sort -Property @{Expression = {[System.Version]($_.Version)}; Descending = $True} |
            Select -First 1
    }
    catch {
        Write-Warning "Could not find any suitable versions of $moduleName from any registered PSRepository"
    }
}

function GetInstalledSqlChangeAutomationVersion {
    $scaModule = (Get-Module $SqlChangeAutomationModuleName)

    if ($scaModule -ne $null) {
        return $scaModule.Version
    }

    $dlmaModule = (Get-Module $DlmAutomationModuleName)

    if ($dlmaModule -ne $null) {
        return $dlmaModule.Version
    }

    return $null
}

$ErrorActionPreference = 'Stop'
$VerbosePreference = 'Continue'

# Set process level FUR environment
$env:REDGATE_FUR_ENVIRONMENT = "Octopus Step Templates"

#Helper functions for paramter handling
function Required() {
    Param(
        [Parameter(Mandatory = $false)][string]$Parameter, 
        [Parameter(Mandatory = $true)][string]$Name
    )
    if ([string]::IsNullOrWhiteSpace($Parameter)) { throw "You must enter a value for '$Name'" }
}
function Optional() {
    #Default is untyped here - if we specify [string] powershell will convert nulls into empty string
    Param(
        [Parameter(Mandatory = $false)][string]$Parameter, 
        [Parameter(Mandatory = $false)]$Default
    )
    if ([string]::IsNullOrWhiteSpace($Parameter)) { 
        $Default
    } else { 
        $Parameter
    }
}
function RequireBool() {
    Param(
        [Parameter(Mandatory = $false)][string]$Parameter, 
        [Parameter(Mandatory = $true)][string]$Name
    )
    $Result = $False
    if (![bool]::TryParse($Parameter , [ref]$Result )) { throw "'$Name' must be a boolean value." }
    $Result
}
function RequirePositiveNumber() {
    Param(
        [Parameter(Mandatory = $false)][string]$Parameter, 
        [Parameter(Mandatory = $true)][string]$Name
    )
    $Result = 0
    if (![int32]::TryParse($Parameter , [ref]$Result )) { throw "'$Name' must be a numerical value." }
    if ($Result -lt 0) { throw "'$Name' must be >= 0." }
    $Result
}

function Get-SqlcmdInstalled
{
	# Define variables
    $searchPaths = @("c:\program files\microsoft sql server", "c:\program files (x86)\microsoft sql server")
    
    # Loop through search paths
    foreach ($searchPath in $searchPaths)
    {
    	# Ensure folder exists
        if (Test-Path -Path $searchPath)
        {
        	# Search the path
            return ($null -ne (Get-ChildItem -Path $searchPath -Recurse | Where-Object {$_.Name -eq "sqlcmd.exe"}))
        }
    }
    
    # Not found
    return $false
}

$SpecificModuleVersion = Optional -Parameter $SpecificModuleVersion
InstallCorrectSqlChangeAutomation -requiredVersion $SpecificModuleVersion

# Check if SQL Change Automation is installed.	
$powershellModule = Get-Module -Name SqlChangeAutomation	
if ($powershellModule -eq $null) { 	
    throw "Cannot find SQL Change Automation on your Octopus Tentacle. If SQL Change Automation is installed, try restarting the Tentacle service for it to be detected."	
}

# Check to for sqlcmd
$sqlCmdExists = Get-SqlCmdInstalled

if ($sqlCmdExists -eq $false)
{
	Write-Verbose "This template requires the sqlcmd utility, downloading ..."
	$tempPath = (New-Item "$PSScriptRoot\sqlcmd" -ItemType Directory -Force).FullName
    
	$sqlCmdUrl = ""
    $odbcUrl = ""
    $redistributableUrl = ""
    
    switch ($Env:PROCESSOR_ARCHITECTURE)
    {
    	"AMD64"
        {
        	$sqlCmdUrl = "https://go.microsoft.com/fwlink/?linkid=2142258"
            $odbcUrl = "https://go.microsoft.com/fwlink/?linkid=2168524"
            $redistributableUrl = "https://aka.ms/vs/17/release/vc_redist.x64.exe"
            break
        }
        "x86"
        {
        	$sqlCmdUrl = "https://go.microsoft.com/fwlink/?linkid=2142257"
            $odbcUrl = "https://go.microsoft.com/fwlink/?linkid=2168713"
            $redistributableUrl = "https://aka.ms/vs/17/release/vc_redist.x86.exe"
            break
        }
    }
    
    Invoke-WebRequest -Uri $sqlCmdUrl -OutFile "$tempPath\sqlcmd.msi" -UseBasicParsing
	Invoke-WebRequest -Uri $odbcUrl -OutFile "$tempPath\msodbc.msi" -UseBasicParsing
	Invoke-WebRequest -Uri $redistributableUrl -Outfile "$tempPath\vc_redist.exe" -UseBasicParsing

	Write-Verbose "Installing Visual Studio 2017 C++ redistrutable prequisite ..."
	Start-Process -FilePath "$tempPath\vc_redist.exe" -ArgumentList @("/install", "/passive", "/norestart") -NoNewWindow -Wait
    Write-Verbose "Installing SQL Server 2017 ODBC driver prequisite ..."
	Start-Process -FilePath "msiexec.exe" -ArgumentList @("/i", "$tempPath\msodbc.msi", "IACCEPTMSODBCSQLLICENSETERMS=YES", "/qn") -NoNewWindow -Wait
    Write-Verbose "Installing SQLCMD utility ..."
	Start-Process -FilePath "msiexec.exe" -ArgumentList @("/i", "$tempPath\sqlcmd.msi", "IACCEPTMSSQLCMDLNUTILSLICENSETERMS=YES", "/qn") -NoNewWindow -Wait

	Write-Verbose "Sqlcmd Installation complete!"
}

$currentVersion = $powershellModule.Version	
$minimumRequiredVersion = [version] '3.0.3'	
if ($currentVersion -lt $minimumRequiredVersion) { 	
    throw "This step requires SQL Change Automation version $minimumRequiredVersion or later. The current version is $currentVersion. The latest version can be found at http://www.red-gate.com/sca/productpage"	
}

$minimumRequiredVersionDataCompareOptions = [version] '3.3.0'

# Check the parameters.
$DLMAutomationCustomConnectionString = Optional -Parameter $DLMAutomationCustomConnectionString

if ([string]::IsNullOrWhiteSpace($DLMAutomationCustomConnectionString) -eq $true)
{
    Required -Parameter $DLMAutomationTargetDatabaseServer -Name 'Target SQL Server instance'
    Required -Parameter $DLMAutomationTargetDatabaseName -Name 'Target database name'
}

$DLMAutomationTargetUsername = Optional -Parameter $DLMAutomationTargetUsername
$DLMAutomationTargetPassword = Optional -Parameter $DLMAutomationTargetPassword
$DLMAutomationFilterPath = Optional -Parameter $DLMAutomationFilterPath
$DLMAutomationCompareOptions = Optional -Parameter $DLMAutomationCompareOptions
$DLMAutomationDataCompareOptions = Optional -Parameter $DLMAutomationDataCompareOptions
$DLMAutomationTransactionIsolationLevel = Optional -Parameter $DLMAutomationTransactionIsolationLevel -Default "Serializable"
$DLMAutomationIgnoreStaticData = Optional -Parameter $DLMAutomationIgnoreStaticData -Default 'False'
$DLMAutomationSkipPostUpdateSchemaCheck = Optional -Parameter $DLMAutomationSkipPostUpdateSchemaCheck -Default "False"
$DLMAutomationQueryBatchTimeout = Optional -Parameter $DLMAutomationQueryBatchTimeout -Default '30'
$DLMAutomationTrustServerCertificate = [Convert]::ToBoolean($OctopusParameters["DLMAutomationTrustServerCertificate"])

$skipPostUpdateSchemaCheck = RequireBool -Parameter $DLMAutomationSkipPostUpdateSchemaCheck -Name 'Skip post update schema check'
$queryBatchTimeout = RequirePositiveNumber -Parameter $DLMAutomationQueryBatchTimeout -Name 'Query Batch Timeout'

# Create and test connection to the database.
if ([string]::IsNullOrWhiteSpace($DLMAutomationCustomConnectionString) -eq $true)
{
    $databaseConnection = New-DatabaseConnection -ServerInstance $DLMAutomationTargetDatabaseServer `
                                                    -Database $DLMAutomationTargetDatabaseName `
                                                    -Username $DLMAutomationTargetUsername `
                                                    -Password $DLMAutomationTargetPassword `
                                                    -TrustServerCertificate $DLMAutomationTrustServerCertificate | Test-DatabaseConnection
} else {
    $databaseConnection = New-Object -TypeName RedGate.Versioning.Automation.Compare.SchemaSources.DatabaseConnection `
                                        -ArgumentList $DLMAutomationCustomConnectionString | Test-DatabaseConnection
}
$packageExtractPath = $OctopusParameters["Octopus.Action.Package[DLMAutomation.Package.Name].ExtractedPath"]
$importedBuildArtifact = Import-DatabaseBuildArtifact -Path $packageExtractPath

# Only allow sqlcmd variables that don't have special characters like spaces, colon or dashes
$regex = '^[a-zA-Z_][a-zA-Z0-9_]+$'
$sqlCmdVariables = @{}
$OctopusParameters.Keys | Where { $_ -match $regex } | ForEach {
	$sqlCmdVariables[$_] = $OctopusParameters[$_]
}

# Create database deployment resources from the NuGet package to the database
$releaseParams = @{
    Target = $databaseConnection
    Source = $importedBuildArtifact
    TransactionIsolationLevel = $DLMAutomationTransactionIsolationLevel
    IgnoreStaticData = [bool]::Parse($DLMAutomationIgnoreStaticData)
    FilterPath = $DLMAutomationFilterPath
    SQLCompareOptions = $DLMAutomationCompareOptions
    SqlCmdVariables = $sqlCmdVariables
}

if($currentVersion -ge $minimumRequiredVersionDataCompareOptions) {
    $releaseParams.SQLDataCompareOptions = $DLMAutomationDataCompareOptions
} elseif(-not [string]::IsNullOrWhiteSpace($DLMAutomationDataCompareOptions)) {
    Write-Warning "SQL Data Compare options requires SQL Change Automation version $minimumRequiredVersionDataCompareOptions or later. The current version is $currentVersion."
}

$release = New-DatabaseReleaseArtifact @releaseParams

# Deploy the source schema to the target database.
Write-Host "Timeout = $queryBatchTimeout"
$releaseUrl = $OctopusParameters['Octopus.Web.ServerUri'] + $OctopusParameters['Octopus.Web.DeploymentLink']; 
$release | Use-DatabaseReleaseArtifact -DeployTo $databaseConnection -SkipPreUpdateSchemaCheck -QueryBatchTimeout $queryBatchTimeout -ReleaseUrl $releaseUrl -SkipPostUpdateSchemaCheck:$skipPostUpdateSchemaCheck


Provided under the Apache License version 2.0.

Report an issue

To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.

{
  "Id": "02b5b305-92ca-4612-a632-92eb840bd2a8",
  "Name": "Redgate - Deploy from Package (Worker Friendly)",
  "Description": "Uses Redgate's [SQL Change Automation](http://www.red-gate.com/sca/productpage) to deploy a package containing a database schema to a SQL Server database, without a review step.\n\nRequires SQL Change Automation version 3.0.2 or later.\n\n*Version date: 2022-01-24*\n\nThis step template is worker friendly, you can pass in a package reference rather than having to reference a previous step which downloaded the package. This step requires Octopus Deploy **2019.10.0** or higher.\n\n**NOTE**: This template requires the SQLCMD utility, if not found, the template will install the following: \n - Visual Studio 2017 C++ Redistributable \n - SQL Server 2017 ODBC driver \n - SQLCMD utility",
  "Version": 5,
  "ExportedAt": "2022-01-25T16:00:25.269Z",
  "ActionType": "Octopus.Script",
  "Author": "markgould",
  "Packages": [
    {
      "Id": "8ec6200a-24c9-4273-bdcc-5ff46ce3111f",
      "Name": "DLMAutomation.Package.Name",
      "PackageId": null,
      "FeedId": null,
      "AcquisitionLocation": "Server",
      "Properties": {
        "Extract": "True",
        "SelectionMode": "deferred",
        "PackageParameterName": "DLMAutomationPackageName"
      }
    }
  ],
  "Parameters": [
    {
      "Id": "3aa8db8b-94e7-4a13-8ac3-054ad5456f3f",
      "Name": "DLMAutomationPackageName",
      "Label": "Package",
      "HelpText": "The name of the package to extract and deploy",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Package"
      }
    },
    {
      "Id": "529e5641-44a7-42fe-9a55-72c3c08d2b7d",
      "Name": "DLMAutomationTargetDatabaseServer",
      "Label": "Target SQL Server instance",
      "HelpText": "The fully qualified SQL Server instance name for the target database.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "29c48d57-c4f2-4dda-9866-b97a20f84c60",
      "Name": "DLMAutomationTargetDatabaseName",
      "Label": "Target database name",
      "HelpText": "The name of the database to deploy changes to. This must be an existing database.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "96c56aaf-ed86-4728-a68c-bac95e9e1040",
      "Name": "DLMAutomationTargetUsername",
      "Label": "Username (optional)",
      "HelpText": "The SQL Server username used to connect to the database. If you leave this field and 'Password' blank, Windows authentication will be used to connect instead, using the account that runs the Tentacle service.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "515c98af-ff2b-488c-bebd-81bff7b82761",
      "Name": "DLMAutomationTargetPassword",
      "Label": "Password (optional)",
      "HelpText": "You must enter a password if you entered a username.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "cfebb7c8-497b-4406-8d64-34973c12711c",
      "Name": "DLMAutomationFilterPath",
      "Label": "Filter path (optional)",
      "HelpText": "Specify the location of a SQL Compare filter file (.scpf), which defines objects to include/exclude in the schema comparison. Filter files are generated by SQL Source Control.\n\nFor more help see [Using SQL Compare filters in SQL Change Automation](http://www.red-gate.com/sca/ps/help/filters).",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "a305bef9-af25-44f6-a78b-d641d7b6609a",
      "Name": "DLMAutomationCompareOptions",
      "Label": "SQL Compare options (optional)",
      "HelpText": "Enter SQL Compare options to apply when generating the update script. Use a comma-separated list to enter multiple values. For more help see [Using SQL Compare options in SQL Change Automation](http://www.red-gate.com/sca/add-ons/compare-options).",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "1bbdec30-e207-4e5e-b08c-26bae87f78b3",
      "Name": "DLMAutomationDataCompareOptions",
      "Label": "SQL Data Compare options (optional)",
      "HelpText": "Enter SQL Data Compare options to apply when generating the update script. Use a comma-separated list to enter multiple values. For more help see [Using SQL Data Compare options in SQL Change Automation](http://www.red-gate.com/sca/ps/help/datacompareoptions).",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "367a4913-6c79-451a-bfe6-269462f784f8",
      "Name": "DLMAutomationTransactionIsolationLevel",
      "Label": "Transaction isolation level (optional)",
      "HelpText": "Select the transaction isolation level to be used in deployment scripts.",
      "DefaultValue": "Serializable",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Serializable\nSnapshot\nRepeatableRead\nReadCommitted\nReadUncommitted"
      }
    },
    {
      "Id": "b5e1544c-45c9-455c-9c5c-ab5c3ec37c8a",
      "Name": "DLMAutomationIgnoreStaticData",
      "Label": "Ignore static data",
      "HelpText": "Exclude changes to static data when generating the deployment resources.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "56644eed-84d2-4efb-a645-8a690770af01",
      "Name": "DLMAutomationQueryBatchTimeout",
      "Label": "Query batch timeout (in seconds)",
      "HelpText": "The execution timeout, in seconds, for each batch of queries in the update script. The default value is 30 seconds. A value of zero indicates no execution timeout.",
      "DefaultValue": "30",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "a5d870d2-bef0-43e2-888a-265a8c7bccf3",
      "Name": "DLMAutomationSkipPostUpdateSchemaCheck",
      "Label": "Skip post update schema check",
      "HelpText": "Don't check that the target database has the correct schema after the update has run.",
      "DefaultValue": "False",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "66a03c11-eb4c-4c59-81cf-e1f09c31aee7",
      "Name": "SpecificModuleVersion",
      "Label": "SQL Change Automation version (optional)",
      "HelpText": "If you wish to use a specific version of SQL Change Automation rather than the latest, enter the version number here.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "2d38d44f-409f-407b-9cc2-401a95f07d01",
      "Name": "DLMModuleInstallLocation",
      "Label": "SQL Change Automation Install Location (optional)",
      "HelpText": "The SQL Change Automation cmdlets will be downloaded from the [PowerShell gallery](https://www.powershellgallery.com/packages/SqlChangeAutomation).  Please specify the folder folder where those packages will be saved to.  It can be relative or absolute.\n\n\nIf this is empty it will default `$Home\\Documents\\WindowsPowerShell\\Modules` which is the [recommended location](https://docs.microsoft.com/en-us/powershell/scripting/developer/module/installing-a-powershell-module?view=powershell-7#where-to-install-modules) from Microsoft.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "4daa7ac9-65b8-47ff-b00a-616f94896664",
      "Name": "DLMAutomationTrustServerCertificate",
      "Label": "Trust server certificate",
      "HelpText": "Check this box to trust the SQL Server certificate.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "cb2b74bb-9481-4aab-b18f-b032746e3edd",
      "Name": "DLMAutomationCustomConnectionString",
      "Label": "Connection String (Optional)",
      "HelpText": "If set, uses this as the full connection string. Allows for advanced configuration.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    }
  ],
  "Properties": {
    "Octopus.Action.Script.ScriptBody": "$DlmAutomationModuleName = \"DLMAutomation\"\n$SqlChangeAutomationModuleName = \"SqlChangeAutomation\"\n$ModulesFolder = \"$Home\\Documents\\WindowsPowerShell\\Modules\"\n\n\nif ([string]::IsNullOrWhiteSpace($DLMModuleInstallLocation) -eq $false)\n{\n\tif ((Test-Path $DLMModuleInstallLocation -IsValid) -eq $false)\n    {\n    \tWrite-Error \"The path $DLMModuleInstallLocation is not valid, please use a relative or absolute path.\"\n        exit 1\n    }\n    \n    $ModulesFolder = [System.IO.Path]::GetFullPath($DLMModuleInstallLocation)            \n}\n\nWrite-Host \"Modules will be installed into $ModulesFolder\"\n\n$LocalModules = (New-Item \"$ModulesFolder\" -ItemType Directory -Force).FullName\n$env:PSModulePath = \"$LocalModules;$env:PSModulePath\"\n\nfunction IsScaAvailable\n{\n    if ((Get-Module $SqlChangeAutomationModuleName) -ne $null) {\n        return $true\n    }\n\n    return $false\n}\n\nfunction InstallCorrectSqlChangeAutomation\n{\n    [CmdletBinding()]\n    Param(\n        [Parameter(Mandatory = $false)]\n        [Version]$requiredVersion\n    )\n\n    $moduleName = $SqlChangeAutomationModuleName\n\n    # this will be null if $requiredVersion is not specified - which is exactly what we want\n    $maximumVersion = $requiredVersion\n\n    if ($requiredVersion) {\n        if ($requiredVersion.Revision -eq -1) {\n            #If provided with a 3 part version number (the 4th part, revision, == -1), we should allow any value for the revision\n            $maximumVersion = [Version]\"$requiredVersion.$([System.Int32]::MaxValue)\"\n        }\n\n        if ($requiredVersion.Major -lt 3) {\n            # If the specified version is below V3 then the user is requesting a version of DLMA. We should look for that module name instead\n            $moduleName = $DlmAutomationModuleName\n        }\n    }\n\n    $installedModule = GetHighestInstalledModule $moduleName -minimumVersion $requiredVersion -maximumVersion $maximumVersion\n\n    if (!$installedModule) {\n        #Either SCA isn't installed at all or $requiredVersion is specified but that version of SCA isn't installed\n        Write-Verbose \"$moduleName $requiredVersion not available - attempting to download from gallery\"\n        InstallLocalModule -moduleName $moduleName -minimumVersion $requiredVersion -maximumVersion $maximumVersion\n    }\n    elseif (!$requiredVersion) {\n        #We've got a version of SCA installed, but $requiredVersion isn't specified so we might be able to upgrade\n        $newest = GetHighestInstallableModule $moduleName\n        if ($newest -and ($installedModule.Version -lt $newest.Version)) {\n            Write-Verbose \"Updating $moduleName to version $($newest.Version)\"\n            InstallLocalModule -moduleName $moduleName -minimumVersion $newest.Version\n        }\n    }\n\n    # Now we're done with install/upgrade, try to import the highest available module that matches our version requirements\n\n    # We can't just use -minimumVersion and -maximumVersion arguments on Import-Module because PowerShell 3 doesn't have them,\n    # so we have to find the precise matching installed version using our code, then import that specifically. Note that\n    # $requiredVersion and $maximumVersion might be null when there's no specific version we need.\n    $installedModule = GetHighestInstalledModule $moduleName -minimumVersion $requiredVersion -maximumVersion $maximumVersion\n\n    if (!$installedModule -and !$requiredVersion) {\n        #Did not find SCA, and we don't have a required version so we might be able to use an installed DLMA instead.\n        Write-Verbose \"$moduleName is not installed - trying to fall back to $DlmAutomationModuleName\"\n        $installedModule = GetHighestInstalledModule $DlmAutomationModuleName\n    }\n\n    if ($installedModule) {\n        Write-Verbose \"Importing installed $($installedModule.Name) version $($installedModule.Version)\"\n        Import-Module $installedModule -Force\n    }\n    else {\n        throw \"$moduleName $requiredVersion is not installed, and could not be downloaded from the PowerShell gallery\"\n    }\n}\n\nfunction InstallPowerShellGet {\n    [CmdletBinding()]\n    Param()\n\n    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12\n    $psget = GetHighestInstalledModule PowerShellGet\n    if (!$psget)\n    {\n        Write-Warning @\"\nCannot access the PowerShell Gallery because PowerShellGet is not installed.\nTo install PowerShellGet, either upgrade to PowerShell 5 or install the PackageManagement MSI.\nSee https://docs.microsoft.com/en-us/powershell/gallery/installing-psget for more details.\n\"@\n        throw \"PowerShellGet is not available\"\n    }\n\n    if ($psget.Version -lt [Version]'1.6') {\n        #Bootstrap the NuGet package provider, which updates NuGet without requiring admin rights\n        Write-Debug \"Installing NuGet package provider\"\n        Get-PackageProvider NuGet -ForceBootstrap | Out-Null\n\n        #Use the currently-installed version of PowerShellGet\n        Import-PackageProvider PowerShellGet\n\n        #Download the version of PowerShellGet that we actually need\n        Write-Debug \"Installing PowershellGet\"\n        Save-Module -Name PowerShellGet -Path $LocalModules -MinimumVersion 1.6 -Force -ErrorAction SilentlyContinue\n    }\n\n    Write-Debug \"Importing PowershellGet\"\n    Import-Module PowerShellGet -MinimumVersion 1.6 -Force\n    #Make sure we're actually using the package provider from the imported version of PowerShellGet\n    Import-PackageProvider ((Get-Module PowerShellGet).Path) | Out-Null\n}\n\nfunction InstallLocalModule {\n    [CmdletBinding()]\n    Param(\n        [Parameter(Mandatory = $true)]\n        [string]$moduleName,\n        [Parameter(Mandatory = $false)]\n        [Version]$minimumVersion,\n        [Parameter(Mandatory = $false)]\n        [Version]$maximumVersion\n    )\n    try {\n        InstallPowerShellGet\n\n        Write-Debug \"Install $moduleName $requiredVersion\"\n        Save-Module -Name $moduleName -Path $LocalModules -Force -AcceptLicense -MinimumVersion $minimumVersion -MaximumVersion $maximumVersion -ErrorAction Stop\n    }\n    catch {\n        Write-Warning \"Could not install $moduleName $requiredVersion from any registered PSRepository\"\n    }\n}\n\nfunction GetHighestInstalledModule {\n    [CmdletBinding()]\n    Param(\n        [Parameter(Mandatory = $true, Position = 0)]\n        [string] $moduleName,\n\n        [Parameter(Mandatory = $false)]\n        [Version]$minimumVersion,\n        [Parameter(Mandatory = $false)]\n        [Version]$maximumVersion\n    )\n\n    return Get-Module $moduleName -ListAvailable |\n           Where {(!$minimumVersion -or ($_.Version -ge $minimumVersion)) -and (!$maximumVersion -or ($_.Version -le $maximumVersion))} |\n           Sort -Property @{Expression = {[System.Version]($_.Version)}; Descending = $True} |\n           Select -First 1\n}\n\nfunction GetHighestInstallableModule {\n    [CmdletBinding()]\n    Param(\n        [Parameter(Mandatory = $true, Position = 0)]\n        [string] $moduleName\n    )\n\n    try {\n        InstallPowerShellGet\n        Find-Module SqlChangeAutomation -AllVersions |\n            Sort -Property @{Expression = {[System.Version]($_.Version)}; Descending = $True} |\n            Select -First 1\n    }\n    catch {\n        Write-Warning \"Could not find any suitable versions of $moduleName from any registered PSRepository\"\n    }\n}\n\nfunction GetInstalledSqlChangeAutomationVersion {\n    $scaModule = (Get-Module $SqlChangeAutomationModuleName)\n\n    if ($scaModule -ne $null) {\n        return $scaModule.Version\n    }\n\n    $dlmaModule = (Get-Module $DlmAutomationModuleName)\n\n    if ($dlmaModule -ne $null) {\n        return $dlmaModule.Version\n    }\n\n    return $null\n}\n\n$ErrorActionPreference = 'Stop'\n$VerbosePreference = 'Continue'\n\n# Set process level FUR environment\n$env:REDGATE_FUR_ENVIRONMENT = \"Octopus Step Templates\"\n\n#Helper functions for paramter handling\nfunction Required() {\n    Param(\n        [Parameter(Mandatory = $false)][string]$Parameter, \n        [Parameter(Mandatory = $true)][string]$Name\n    )\n    if ([string]::IsNullOrWhiteSpace($Parameter)) { throw \"You must enter a value for '$Name'\" }\n}\nfunction Optional() {\n    #Default is untyped here - if we specify [string] powershell will convert nulls into empty string\n    Param(\n        [Parameter(Mandatory = $false)][string]$Parameter, \n        [Parameter(Mandatory = $false)]$Default\n    )\n    if ([string]::IsNullOrWhiteSpace($Parameter)) { \n        $Default\n    } else { \n        $Parameter\n    }\n}\nfunction RequireBool() {\n    Param(\n        [Parameter(Mandatory = $false)][string]$Parameter, \n        [Parameter(Mandatory = $true)][string]$Name\n    )\n    $Result = $False\n    if (![bool]::TryParse($Parameter , [ref]$Result )) { throw \"'$Name' must be a boolean value.\" }\n    $Result\n}\nfunction RequirePositiveNumber() {\n    Param(\n        [Parameter(Mandatory = $false)][string]$Parameter, \n        [Parameter(Mandatory = $true)][string]$Name\n    )\n    $Result = 0\n    if (![int32]::TryParse($Parameter , [ref]$Result )) { throw \"'$Name' must be a numerical value.\" }\n    if ($Result -lt 0) { throw \"'$Name' must be >= 0.\" }\n    $Result\n}\n\nfunction Get-SqlcmdInstalled\n{\n\t# Define variables\n    $searchPaths = @(\"c:\\program files\\microsoft sql server\", \"c:\\program files (x86)\\microsoft sql server\")\n    \n    # Loop through search paths\n    foreach ($searchPath in $searchPaths)\n    {\n    \t# Ensure folder exists\n        if (Test-Path -Path $searchPath)\n        {\n        \t# Search the path\n            return ($null -ne (Get-ChildItem -Path $searchPath -Recurse | Where-Object {$_.Name -eq \"sqlcmd.exe\"}))\n        }\n    }\n    \n    # Not found\n    return $false\n}\n\n$SpecificModuleVersion = Optional -Parameter $SpecificModuleVersion\nInstallCorrectSqlChangeAutomation -requiredVersion $SpecificModuleVersion\n\n# Check if SQL Change Automation is installed.\t\n$powershellModule = Get-Module -Name SqlChangeAutomation\t\nif ($powershellModule -eq $null) { \t\n    throw \"Cannot find SQL Change Automation on your Octopus Tentacle. If SQL Change Automation is installed, try restarting the Tentacle service for it to be detected.\"\t\n}\n\n# Check to for sqlcmd\n$sqlCmdExists = Get-SqlCmdInstalled\n\nif ($sqlCmdExists -eq $false)\n{\n\tWrite-Verbose \"This template requires the sqlcmd utility, downloading ...\"\n\t$tempPath = (New-Item \"$PSScriptRoot\\sqlcmd\" -ItemType Directory -Force).FullName\n    \n\t$sqlCmdUrl = \"\"\n    $odbcUrl = \"\"\n    $redistributableUrl = \"\"\n    \n    switch ($Env:PROCESSOR_ARCHITECTURE)\n    {\n    \t\"AMD64\"\n        {\n        \t$sqlCmdUrl = \"https://go.microsoft.com/fwlink/?linkid=2142258\"\n            $odbcUrl = \"https://go.microsoft.com/fwlink/?linkid=2168524\"\n            $redistributableUrl = \"https://aka.ms/vs/17/release/vc_redist.x64.exe\"\n            break\n        }\n        \"x86\"\n        {\n        \t$sqlCmdUrl = \"https://go.microsoft.com/fwlink/?linkid=2142257\"\n            $odbcUrl = \"https://go.microsoft.com/fwlink/?linkid=2168713\"\n            $redistributableUrl = \"https://aka.ms/vs/17/release/vc_redist.x86.exe\"\n            break\n        }\n    }\n    \n    Invoke-WebRequest -Uri $sqlCmdUrl -OutFile \"$tempPath\\sqlcmd.msi\" -UseBasicParsing\n\tInvoke-WebRequest -Uri $odbcUrl -OutFile \"$tempPath\\msodbc.msi\" -UseBasicParsing\n\tInvoke-WebRequest -Uri $redistributableUrl -Outfile \"$tempPath\\vc_redist.exe\" -UseBasicParsing\n\n\tWrite-Verbose \"Installing Visual Studio 2017 C++ redistrutable prequisite ...\"\n\tStart-Process -FilePath \"$tempPath\\vc_redist.exe\" -ArgumentList @(\"/install\", \"/passive\", \"/norestart\") -NoNewWindow -Wait\n    Write-Verbose \"Installing SQL Server 2017 ODBC driver prequisite ...\"\n\tStart-Process -FilePath \"msiexec.exe\" -ArgumentList @(\"/i\", \"$tempPath\\msodbc.msi\", \"IACCEPTMSODBCSQLLICENSETERMS=YES\", \"/qn\") -NoNewWindow -Wait\n    Write-Verbose \"Installing SQLCMD utility ...\"\n\tStart-Process -FilePath \"msiexec.exe\" -ArgumentList @(\"/i\", \"$tempPath\\sqlcmd.msi\", \"IACCEPTMSSQLCMDLNUTILSLICENSETERMS=YES\", \"/qn\") -NoNewWindow -Wait\n\n\tWrite-Verbose \"Sqlcmd Installation complete!\"\n}\n\n$currentVersion = $powershellModule.Version\t\n$minimumRequiredVersion = [version] '3.0.3'\t\nif ($currentVersion -lt $minimumRequiredVersion) { \t\n    throw \"This step requires SQL Change Automation version $minimumRequiredVersion or later. The current version is $currentVersion. The latest version can be found at http://www.red-gate.com/sca/productpage\"\t\n}\n\n$minimumRequiredVersionDataCompareOptions = [version] '3.3.0'\n\n# Check the parameters.\n$DLMAutomationCustomConnectionString = Optional -Parameter $DLMAutomationCustomConnectionString\n\nif ([string]::IsNullOrWhiteSpace($DLMAutomationCustomConnectionString) -eq $true)\n{\n    Required -Parameter $DLMAutomationTargetDatabaseServer -Name 'Target SQL Server instance'\n    Required -Parameter $DLMAutomationTargetDatabaseName -Name 'Target database name'\n}\n\n$DLMAutomationTargetUsername = Optional -Parameter $DLMAutomationTargetUsername\n$DLMAutomationTargetPassword = Optional -Parameter $DLMAutomationTargetPassword\n$DLMAutomationFilterPath = Optional -Parameter $DLMAutomationFilterPath\n$DLMAutomationCompareOptions = Optional -Parameter $DLMAutomationCompareOptions\n$DLMAutomationDataCompareOptions = Optional -Parameter $DLMAutomationDataCompareOptions\n$DLMAutomationTransactionIsolationLevel = Optional -Parameter $DLMAutomationTransactionIsolationLevel -Default \"Serializable\"\n$DLMAutomationIgnoreStaticData = Optional -Parameter $DLMAutomationIgnoreStaticData -Default 'False'\n$DLMAutomationSkipPostUpdateSchemaCheck = Optional -Parameter $DLMAutomationSkipPostUpdateSchemaCheck -Default \"False\"\n$DLMAutomationQueryBatchTimeout = Optional -Parameter $DLMAutomationQueryBatchTimeout -Default '30'\n$DLMAutomationTrustServerCertificate = [Convert]::ToBoolean($OctopusParameters[\"DLMAutomationTrustServerCertificate\"])\n\n$skipPostUpdateSchemaCheck = RequireBool -Parameter $DLMAutomationSkipPostUpdateSchemaCheck -Name 'Skip post update schema check'\n$queryBatchTimeout = RequirePositiveNumber -Parameter $DLMAutomationQueryBatchTimeout -Name 'Query Batch Timeout'\n\n# Create and test connection to the database.\nif ([string]::IsNullOrWhiteSpace($DLMAutomationCustomConnectionString) -eq $true)\n{\n    $databaseConnection = New-DatabaseConnection -ServerInstance $DLMAutomationTargetDatabaseServer `\n                                                    -Database $DLMAutomationTargetDatabaseName `\n                                                    -Username $DLMAutomationTargetUsername `\n                                                    -Password $DLMAutomationTargetPassword `\n                                                    -TrustServerCertificate $DLMAutomationTrustServerCertificate | Test-DatabaseConnection\n} else {\n    $databaseConnection = New-Object -TypeName RedGate.Versioning.Automation.Compare.SchemaSources.DatabaseConnection `\n                                        -ArgumentList $DLMAutomationCustomConnectionString | Test-DatabaseConnection\n}\n$packageExtractPath = $OctopusParameters[\"Octopus.Action.Package[DLMAutomation.Package.Name].ExtractedPath\"]\n$importedBuildArtifact = Import-DatabaseBuildArtifact -Path $packageExtractPath\n\n# Only allow sqlcmd variables that don't have special characters like spaces, colon or dashes\n$regex = '^[a-zA-Z_][a-zA-Z0-9_]+$'\n$sqlCmdVariables = @{}\n$OctopusParameters.Keys | Where { $_ -match $regex } | ForEach {\n\t$sqlCmdVariables[$_] = $OctopusParameters[$_]\n}\n\n# Create database deployment resources from the NuGet package to the database\n$releaseParams = @{\n    Target = $databaseConnection\n    Source = $importedBuildArtifact\n    TransactionIsolationLevel = $DLMAutomationTransactionIsolationLevel\n    IgnoreStaticData = [bool]::Parse($DLMAutomationIgnoreStaticData)\n    FilterPath = $DLMAutomationFilterPath\n    SQLCompareOptions = $DLMAutomationCompareOptions\n    SqlCmdVariables = $sqlCmdVariables\n}\n\nif($currentVersion -ge $minimumRequiredVersionDataCompareOptions) {\n    $releaseParams.SQLDataCompareOptions = $DLMAutomationDataCompareOptions\n} elseif(-not [string]::IsNullOrWhiteSpace($DLMAutomationDataCompareOptions)) {\n    Write-Warning \"SQL Data Compare options requires SQL Change Automation version $minimumRequiredVersionDataCompareOptions or later. The current version is $currentVersion.\"\n}\n\n$release = New-DatabaseReleaseArtifact @releaseParams\n\n# Deploy the source schema to the target database.\nWrite-Host \"Timeout = $queryBatchTimeout\"\n$releaseUrl = $OctopusParameters['Octopus.Web.ServerUri'] + $OctopusParameters['Octopus.Web.DeploymentLink']; \n$release | Use-DatabaseReleaseArtifact -DeployTo $databaseConnection -SkipPreUpdateSchemaCheck -QueryBatchTimeout $queryBatchTimeout -ReleaseUrl $releaseUrl -SkipPostUpdateSchemaCheck:$skipPostUpdateSchemaCheck\n\n",
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptSource": "Inline"
  },
  "Category": "Redgate",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/redgate-deploy-from-package-worker-friendly.json",
  "Website": "/step-templates/02b5b305-92ca-4612-a632-92eb840bd2a8",
  "Logo": "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",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Tuesday, January 25, 2022