SQL - Deploy DACPAC using SqlPackage

Octopus.Script exported 2023-06-01 by twerthi belongs to ‘SQL Server’ category.

Calls SqlPackage commands such as:

As SqlPackage is cross-platform, this template is both Windows and Linux* compatible.

Results of Deploy script and deploy report options will upload to Octopus Deploy as an artifact. This allows you to put in place a manual intervention step if required. It is also useful for auditing purposes.

SqlCmd variables are now supported. To specify SqlCmd variables, create your Octopus variable with the following naming convention: SqlCmdVariable. (case insensitive) and then assign it a value. Examples:

  • SqlCmdVariable.Variable1
  • my.sqlcmdvariable.variable2

NOTE:

  • Requires version 2019.10 or above.
  • TrustServerCertificate=true is set by default
  • Requires PowerShell or *PowerShell Core

Parameters

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

DACPACPackageName

DACPACPackageName =

The name of the .dacpac file that contains the SSDT model. Include the .dacpac extensions. To use a specific folder, use the relative location ex: dacpac/mydacpac.dacpac

Publish profile file name

DACPACPublishProfile =

Searches the package for the specified file name. To use a specific folder, use the relative location ex: publish/publish-profile.xml

Report

DACPACReport = True

Whether a deployment report should be generated and loaded into OctopusDeploy as an artifact

Script

DACPACScript = True

Whether a deploy script should be generated and loaded into OctopusDeploy as an artifact

Deploy

DACPACDeploy = True

Whether a deployment of the dacpac should occur

Target Servername

DACPACTargetServer =

Name of the server to target this deployment against

Target Database

DACPACTargetDatabase =

Name of the database to target this deployment against

Authentication type

DACPACAuthenticationType =

Select the method to authenticate to the SQL Server.

Username

DACPACSQLUsername =

User name to use to connect to the server if we are not using Integrated Security.

If using the Azure Active Directory Service Principal Authentication Type, use the Azure Account variable here. For example, if your Azure Account variable is called MyAccount, the value for this input would be #{MyAccount.Client}

Password

DACPACSQLPassword =

Password to use to connect to the server if we are not using Integrated Security.

If using the Azure Active Directory Service Principal Authentication Type, use the Azure Account variable here. For example, if your Azure Account variable is called MyAccount, the value for this input would be #{MyAccount.Password}

DACPAC Package

DACPACPackage =

The package containing the .dacpac file from the specified repository.

Command Timeout

DACPACCommandTimeout =

Override the default command timeout for longer-running scripts.

SqlPackage executable location

DACPACExeLocation =

Location of the SqlPackage executable. Leave blank to dynamically download.
Examples:
Embedded within the package:#{Octopus.Action.Package[DACPACPackage].ExtractedPath}/MySubFolder
On disk:c:\sqlpackage\sqlpackage.exe or /etc/sqlpackage/sqlpackage

Additional arguments

DACPACAdditionalArguments =

A comma-delimited list of additional arguments to add to the SqlPackage command.

Script body

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

# Set TLS
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls11 -bor [System.Net.SecurityProtocolType]::Tls12

Write-Host "Determining Operating System..."
# Check to see if $IsWindows is available
if ($null -eq $IsWindows)
{
    switch ([System.Environment]::OSVersion.Platform)
    {
    	"Win32NT"
        {
        	# Set variable
            $IsWindows = $true
            $IsLinux = $false
        }
        "Unix"
        {
        	$IsWindows = $false
            $IsLinux = $true
        }
    }
}

if ($IsWindows)
{
	Write-Host "Detected OS is Windows"
    $ProgressPreference = 'SilentlyContinue'
}
else
{
	Write-Host "Detected OS is Linux"
}

<#
 .SYNOPSIS
 Finds the DAC File that you specify

 .DESCRIPTION
 Looks through the supplied PathList array and searches for the file you specify.  It will return the first one that it finds.

 .PARAMETER FileName
 Name of the file you are looking for

 .PARAMETER PathList
 Array of Paths to search through.

 .EXAMPLE
 Find-DacFile -FileName "Microsoft.SqlServer.TransactSql.ScriptDom.dll" -PathList @("${env:ProgramFiles}\Microsoft SQL Server", "${env:ProgramFiles(x86)}\Microsoft SQL Server")
#>
Function Find-DacFile {
    Param(
        [Parameter(Mandatory=$true)]
        [string]$FileName,
        [Parameter(Mandatory=$true)]
        [string[]]$PathList
    )

    $File = $null

    ForEach($Path in $PathList)
    {
        Write-Debug ("Searching: {0}" -f $Path)

        If (!($File))
        {
            $File = (
                Get-ChildItem $Path -ErrorAction SilentlyContinue -Filter $FileName -Recurse |
                    Sort-Object FullName -Descending |
                    Select-Object -First 1
                )

            If ($File)
            {
                Write-Debug ("Found: {0}" -f $File.FullName)
            }
        }
    }

    Return $File
}


<#
 .SYNOPSIS
 Generates a connection string

 .DESCRIPTION
 Derive a connection string from the supplied variables

 .PARAMETER ServerName
 Name of the server to connect to

 .PARAMETER Database
 Name of the database to connect to

 .PARAMETER UseIntegratedSecurity
 Boolean value to indicate if Integrated Security should be used or not

 .PARAMETER UserName
 User name to use if we are not using integrated security

 .PASSWORD Password
 Password to use if we are not using integrated security

 .PARAMETER EnableMultiSubnetFailover
 Flag as to whether we should enable multi subnet failover

 .EXAMPLE
 Get-ConnectionString -ServerName localhost -UseIntegratedSecurity -Database OctopusDeploy

 .EXAMPLE
 Get-ConnectionString -ServerName localhost -UserName sa -Password ProbablyNotSecure -Database OctopusDeploy
#>
Function Get-ConnectionString {
    Param(
        [Parameter(Mandatory=$True)]
        [string]$ServerName,
        [string]$UserName,
        [string]$Password,
        [string]$Database,
        [string]$AuthenticationType
    )

    $ApplicationName = "OctopusDeploy"
    $connectionString = ("Application Name={0};Server={1}" -f $ApplicationName, $ServerName)

    switch ($AuthenticationType)
    {
    	"AzureADPassword"
        {
            Write-Verbose "Using Azure Active Directory username and password"
            $connectionString += (";Authentication='Active Directory Password';Uid={0};Pwd={1}" -f $UserName, $Password)                
            break
        }
        "AzureADIntegrated"
        {
            Write-Verbose "Using Azure Active Directory integrated"
            $connectionString += (";Authentication='Active Directory Integrated'")                
            break
        }
        "AzureADManaged"
        {
        	Write-Verbose "Using Azure Active Directory managed identity"
            break
        }
        "AzureADServicePrincipal"
        {
             Write-Verbose "Using Azure Active Directory username and password"
            $connectionString += (";Authentication='ActiveDirectoryServicePrincipal';Uid={0};Pwd={1}" -f $UserName, $Password)                
            break       	
        }
        "SqlAuthentication"
        {
            Write-Verbose "Using SQL Authentication username and password"
            $connectionString += (";Uid={0};Pwd={1}" -f $UserName, $Password)                
            break        
        }
        "WindowsIntegrated"
        {
            Write-Verbose "Using integrated security"
            $connectionString += ";Trusted_Connection=True"
            break
        }
    }
    
    if ($EnableMultiSubnetFailover)
    {
        Write-Verbose "Enabling multi subnet failover"
        $connectionString += ";MultisubnetFailover=True"
    }

    If ($Database)
    {
        $connectionString += (";Initial Catalog={0}" -f $Database)
    }

	$connectionString += ";TrustServerCertificate=true;"

    Return $connectionString
}

<#
 .SYNOPSIS
 Will find the full path of a given filename (For dacpac or publish profile)
 .DESCRIPTION
 Will search through an extracted package folder provided as the BasePath and hunt for any matches for the given filename.
 .PARAMETER BasePath
 String value of the root folder to begine the recursive search.
 .PARAMETER FileName
 String value of the name of the file to search for.
 .PARAMETER FileType
 String value of "DacPac" or "PublishProfile" to identify the type of file to search for.
 .EXAMPLE
 Get-DacFilePath -BasePath $ExtractPath -FileName $DACPACPackageName -FileType "DacPac"
#>
function Get-DacFilePath {
    [cmdletbinding()]
    param(
        [parameter(Mandatory=$true)]
        [string]$BasePath,

        [parameter(Mandatory=$true)]
        [string]$FileName,

        [parameter(Mandatory=$true)]
        [ValidateSet("DacPac","PublishProfile")]
        [string]$FileType
    )

    # Add file extension for a dacpac if it's missing
    if($FileName.Split(".")[-1] -ne "dacpac" -and $FileType -eq "DacPac"){
        $FileName = "$FileName.dacpac"
    }

    Write-Verbose "Looking for $FileType $FileName in $BasePath."

    $filePath = (Get-ChildItem -Path $BasePath -Recurse -Filter $FileName).FullName

    if(@($filePath).Length -gt 1){
        Write-Warning "Found $(@($filePath).Length) instances of $FileName. Using $($filePath[0])."
        Write-Warning "Multiple paths for $FileName`: $(@($filePath) -join "; ")"
        $filePath = $filePath[0]
    }
    elseif(@($filePath).Length -lt 1 -or $null -eq $filePath){
        Throw "Could not find $FileName."
    }

    return $filePath
}

function Add-SqlCmdVariables
{
	# Get all SqlCmdVariables
    $sqlCmdVariables = $OctopusParameters.Keys -imatch "SqlCmdVariable.*"
    $argumentList = @()
        
	# Check to see if something is there
	if ($null -ne $sqlCmdVariables)
    {
    	Write-Host "Adding SqlCmdVariables ..."
        
		# Loop through the variable collection
        foreach ($sqlCmdVariable in $sqlCmdVariables)
        {
        	# Add variable to the deploy options
            $sqlCmdVariableKey = $sqlCmdVariable.Substring(($sqlCmdVariable.ToLower().IndexOf("sqlcmdvariable.") + "sqlcmdvariable.".Length))
            
            Write-Host "Adding variable: $sqlCmdVariableKey with value: $($OctopusParameters[$sqlCmdVariable])"
            
            $argumentList += ("/variables:{0}={1}" -f $sqlCmdVariableKey, $OctopusParameters[$sqlCmdVariable])
        }
    }
    
    # return the list of variables
    return $argumentList
}

function Add-AdditionalArguments
{
	# Define parameters
    param (
    	$AdditionalArguments
    )
    
    # Define local variables
    $argumentsToAdd = @()
    
    # Check for emmpty or null
    if (![string]::IsNullOrWhitespace($AdditionalArguments))
    {
    	# Split the arguments
    	$argumentsToAdd += $AdditionalArguments.Split(',', [System.StringSplitOptions]::RemoveEmptyEntries).Trim()
    }
    
    # Return list
    return $argumentsToAdd
}

function Get-SqlPackage
{
	# Define local variables
    $workFolder = $OctopusParameters['Octopus.Action.Package[DACPACPackage].ExtractedPath']
    $downloadUrl = ""

	# Check to see if a folder needs to be created
    if((Test-Path -Path "$workFolder/sqlpackage") -eq $false)
    {
        # Create new folder
        New-Item -ItemType Directory -Path "$workFolder/sqlpackage"
    }
    
    Write-Host "Downloading SqlPackage ..."
    
    if ($IsWindows)
    {
    	# Set url
        $downloadUrl = "https://aka.ms/sqlpackage-windows"
    }
    
    if ($IsLinux)
    {
    	# Set url
        $downloadUrl = "https://aka.ms/sqlpackage-linux"
    }
    
    # Download sql package
    if ($PSVersionTable.PSVersion.Major -ge 6)
    {
    	# Download
        Invoke-WebRequest -Uri $downloadUrl -OutFile "$workFolder/sqlpackage/sqlpackage.zip"
    }
    else
    {
    	Invoke-WebRequest -Uri $downloadUrl -OutFile "$workFolder/sqlpackage/sqlpackage.zip" -UseBasicParsing
    }
    
    # Expand the archive
    Write-Host "Extracting .zip ..."
    Expand-Archive -Path "$workFolder/sqlpackage/sqlpackage.zip" -DestinationPath "$workFolder/sqlpackage"
    
    # Add to PATH
    $env:PATH = "$workFolder/sqlpackage$([IO.Path]::PathSeparator)" + $env:PATH
    
    # Make it executable
    if ($IsLinux)
    {
    	& chmod a+x "$workFolder/sqlpackage/sqlpackage"
    }
}

Function Format-OctopusArgument {

    Param(
        [string]$Value
    )

    $Value = $Value.Trim()

    # There must be a better way to do this
    Switch -Wildcard ($Value){

        "True" { Return $True }
        "False" { Return $False }
        "#{*}" { Return $null }
        Default { Return $Value }
    }
}

Function Get-ManagedIdentityToken
{
	# Get the identity token
    Write-Host "Getting Azure Managed Identity token ..."
    $token = $null
    $tokenUrl = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fdatabase.windows.net%2F"
    
    if ($PSVersionTable.PSVersion.Major -ge 6)
    {
    	$token = Invoke-RestMethod -Method GET -Uri $tokenUrl -Headers @{"MetaData" = "true"}
    }
    else
    {
    	$token = Invoke-RestMethod -Method GET -Uri $tokenUrl -Headers @{"MetaData" = "true"} -UseBasicParsing
    }
    
    # Return the token
    return $token.access_token
}

function Invoke-SqlPackage
{
	# Define parameters
    param (
    	$Action,
        $Arguments
    )
    
    # Add the action
    $Arguments += "/Action:$Action"

    # Display what's going to be run
    if (![string]::IsNullOrWhitespace($Password))
    {
        $displayArguments = $Arguments.PSObject.Copy()
        for ($i = 0; $i -lt $displayArguments.Count; $i++)
        {
            if ($null -ne $displayArguments[$i])
            {
                if ($displayArguments[$i].Contains($Password))
                {
                    $DisplayArguments[$i] = $displayArguments[$i].Replace($Password, "****")
                }
            }
        }

        Write-Host "Executing the following command: sqlpackage $displayArguments"
    }
    else 
    {
        Write-Host "Executing the following command: sqlpackage $Arguments"
    }    
    
    & sqlpackage $Arguments

	# Check exit code
	if ($lastExitCode -ne 0)
	{
		# Fail the step
    	Write-Error "Execution failed!"
	}
}

function Validate-Folder
{
	# Define parameters
    param (
    	$TestPath
    )
    
    # Check for folder
    if ((Test-Path -Path $TestPath) -eq $false)
    {
    	# Create the folder
        New-Item -Path "$TestPath" -ItemType "directory"
    }
}

Function Remove-InvalidFileNameChars {

	Param(
		[string]$FileName
	)

	[IO.Path]::GetinvalidFileNameChars() | ForEach-Object { $FileName = $FileName.Replace($_, "_") }
	Return $FileName
}

# Get the supplied parameters
$PublishProfile = $OctopusParameters["DACPACPublishProfile"]
$DACPACReport = Format-OctopusArgument -Value $OctopusParameters["DACPACReport"]
$DACPACScript = Format-OctopusArgument -Value $OctopusParameters["DACPACScript"]
$DACPACDeploy = Format-OctopusArgument -Value $OctopusParameters["DACPACDeploy"]
$DACPACTargetServer = $OctopusParameters["DACPACTargetServer"]
$DACPACTargetDatabase = $OctopusParameters["DACPACTargetDatabase"]
$DACPACAdditionalArguments = $OctopusParameters["DACPACAdditionalArguments"]
$DACPACExeLocation = $OctopusParameters["DACPACExeLocation"]
$DACPACDateTime = ((Get-Date).ToUniversalTime().ToString("yyyyMMddHHmmss"))

$Username = $OctopusParameters["DACPACSQLUsername"]
$Password = $OctopusParameters["DACPACSQLPassword"]
$PackageReferenceName = "DACPACPackage"

$authenticationType = $OctopusParameters["DACPACAuthenticationType"]

$ExtractPathKey = ("Octopus.Action.Package[{0}].ExtractedPath" -f $PackageReferenceName)
$ExtractPath = $OctopusParameters[$ExtractPathKey]

if(!(Test-Path $ExtractPath)) {
    Throw ("The package extraction folder '{0}' does not exist or the Octopus Tentacle does not have permission to access it." -f $ExtractPath)
}

# Get the DACPAC location
$dacpacFolderName = [System.IO.Path]::GetDirectoryName($DACPACPackageName)
$dacpacFileName = [System.IO.Path]::GetFileName($DACPACPackageName)
$DACPACPackagePath = Get-DacFilePath -BasePath ($ExtractPath + ([IO.Path]::DirectorySeparatorChar) + $dacpacFolderName) -FileName $dacpacFileName -FileType "DacPac"

# Invoke the DacPac utility
try
{
	# Declare working variables
    $sqlPackageArguments = @()
    
    # Build arugment list
    $sqlPackageArguments += "/SourceFile:`"$DACPACPackagePath`""
    $sqlPackageArguments += "/TargetConnectionString:`"$(Get-ConnectionString -ServerName $DACPACTargetServer -Database $DACPACTargetDatabase -UserName $UserName -Password $Password -AuthenticationType $AuthenticationType)`""
    
	# Check to see if a publish profile was designated
	If ($PublishProfile){
    	$profileFolderName = [System.IO.Path]::GetDirectoryName($PublishProfile)
        $profileFileName = [System.IO.Path]::GetFileName($PublishProfile)
    	$PublishProfilePath = Get-DacFilePath -BasePath ($ExtractPath + ([IO.Path]::DirectorySeparatorChar) + $profileFolderName) -FileName $profileFileName -FileType "PublishProfile"
    
    	# Add to arguments
    	$sqlPackageArguments += "/Profile:`"$PublishProfilePath`""
	}    
    
    # Check to see if it's using managed identity
    if ($authenticationType -eq "AzureADManaged")
    {
    	# Add access token
        $Password = Get-ManagedIdentityToken
        $sqlPackageArguments += "/AccessToken:$Password"
    }
    
    # Add sqlcmd variables
    $sqlPackageArguments += Add-SqlCmdVariables
    
	# Add addtional arguments
    $sqlPackageArguments += Add-AdditionalArguments -AdditionalArguments $DACPACAdditionalArguments
    
    # Check to see if command timeout was specified
    if (![string]::IsNullOrWhitespace($DACPACCommandTimeout))
    {
    	# Add timeout parameter
        $sqlPackageArguments += "/Properties:CommandTimeout=$DACPACCommandTimeout"
    }
    
    # Check to see if sqlpackage needs to be downloaded
    if ([string]::IsNullOrWhitespace($DACPACExeLocation))
    {
    	# Download and extract sqlpackage
        Get-SqlPackage
    }
    else
    {
    	# Add folder location to path
        $env:PATH = "$([IO.Path]::GetDirectoryName($DACPACExeLocation))$([IO.Path]::PathSeparator)" + $env:PATH
        Write-Host "It is $($env:PATH)"
    }
    
    # Execute the actions
    if ($DACPACReport)
    {
    	$workFolder = "$($OctopusParameters['Octopus.Action.Package[DACPACPackage].ExtractedPath'])/reports"
        $sqlReportArguments = @()
        $reportArtifact = Remove-InvalidFileNameChars -FileName ("{0}.{1}.{2}.{3}" -f $DACPACTargetServer, $DACPACTargetDatabase, $DACPACDateTime, "DeployReport.xml")
        $sqlReportArguments += "/OutputPath:$workFolder/$reportArtifact"
        
        # Validate the folder
        Validate-Folder -TestPath $workFolder
        
        # Execute the action
        Invoke-SqlPackage -Action "DeployReport" -Arguments ($sqlPackageArguments + $sqlReportArguments)
        
        # Attach artifacts
        foreach ($item in (Get-ChildItem -Path $workFolder))
        {
        	# Upload artifact
            New-OctopusArtifact $item.FullName
        }
    }
    
    if ($DACPACScript)
    {
    	$workFolder = "$($OctopusParameters['Octopus.Action.Package[DACPACPackage].ExtractedPath'])/scripts"
        $sqlScriptArguments = @()
        $scriptArtifact = Remove-InvalidFileNameChars -FileName ("{0}.{1}.{2}.{3}" -f $DACPACTargetServer, $DACPACTargetDatabase, $DACPACDateTime, "DeployScript.sql")
        $sqlScriptArguments += "/OutputPath:$workFolder/$scriptArtifact"
        
        # Validate folder
        Validate-Folder -TestPath $workFolder
        
        # Execute the action
        Invoke-SqlPackage -Action "Script" -Arguments ($sqlPackageArguments + $sqlScriptArguments)
        
        # Attach artifacts
        foreach ($item in (Get-ChildItem -Path $workFolder))
        {
        	# Upload artifact
            New-OctopusArtifact $item.FullName
        }        
    }
    
    if ($DACPACDeploy)
    {
    	# Execute action
        Invoke-SqlPackage -Action "Publish" -Arguments $sqlPackageArguments
    }
}
catch
{
    Write-Host $_.Exception.ToString()
    throw;
}

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": "c323cbcd-aab8-4229-b07c-e6c26f7e9a8a",
  "Name": "SQL - Deploy DACPAC using SqlPackage",
  "Description": "Calls SqlPackage commands such as:\n * [Deploy](https://learn.microsoft.com/en-us/sql/tools/sqlpackage/sqlpackage-publish?view=sql-server-ver16)\n * [Script](https://learn.microsoft.com/en-us/sql/tools/sqlpackage/sqlpackage-script?view=sql-server-ver16)\n * [DeployReport](https://learn.microsoft.com/en-us/sql/tools/sqlpackage/sqlpackage-deploy-drift-report?view=sql-server-ver16)\n\nAs SqlPackage is cross-platform, this template is both Windows and Linux* compatible.\n\nResults of `Deploy script` and `deploy report` options will upload to Octopus Deploy as an artifact. This allows you to put in place a manual intervention step if required. It is also useful for auditing purposes.\n\nSqlCmd variables are now supported.  To specify SqlCmd variables, create your Octopus variable with the following naming convention: SqlCmdVariable.<Variable name> (case insensitive) and then assign it a value.  Examples:\n* SqlCmdVariable.Variable1\n* my.sqlcmdvariable.variable2\n\nNOTE: \n - Requires version 2019.10 or above. \n - `TrustServerCertificate=true` is set by default\n - Requires PowerShell or *PowerShell Core",
  "Version": 6,
  "ExportedAt": "2023-06-01T22:27:25.564Z",
  "ActionType": "Octopus.Script",
  "Author": "twerthi",
  "Packages": [
    {
      "Id": "edff7d94-0feb-48a9-8185-48feb084a94f",
      "Name": "DACPACPackage",
      "PackageId": null,
      "FeedId": null,
      "AcquisitionLocation": "Server",
      "Properties": {
        "Extract": "True",
        "SelectionMode": "deferred",
        "PackageParameterName": "DACPACPackage"
      }
    }
  ],
  "Parameters": [
    {
      "Id": "f2fcbf76-89ad-4fa2-9be3-cd80de2e39a1",
      "Name": "DACPACPackageName",
      "Label": "DACPACPackageName",
      "HelpText": "The name of the .dacpac file that contains the SSDT model.  Include the .dacpac extensions.  To use a specific folder, use the relative location ex: dacpac/mydacpac.dacpac",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "0d21c2a7-3e6e-4411-81d7-0e5866faa8fd",
      "Name": "DACPACPublishProfile",
      "Label": "Publish profile file name",
      "HelpText": "Searches the package for the specified file name.  To use a specific folder, use the relative location ex: publish/publish-profile.xml",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "7b302914-8d6c-4df2-b392-e45a44e0147c",
      "Name": "DACPACReport",
      "Label": "Report",
      "HelpText": "Whether a deployment report should be generated and loaded into OctopusDeploy as an artifact",
      "DefaultValue": "True",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "aa612324-c2ea-4e86-921a-8ad7494df752",
      "Name": "DACPACScript",
      "Label": "Script",
      "HelpText": "Whether a deploy script should be generated and loaded into OctopusDeploy as an artifact",
      "DefaultValue": "True",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "94c4da0b-5c55-4d3d-ab0b-e175a767694f",
      "Name": "DACPACDeploy",
      "Label": "Deploy",
      "HelpText": "Whether a deployment of the dacpac should occur",
      "DefaultValue": "True",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "d2265a95-ee58-4430-8db0-c7bb03826de0",
      "Name": "DACPACTargetServer",
      "Label": "Target Servername",
      "HelpText": "Name of the server to target this deployment against",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "74397a3b-b007-43cf-bc26-7a1fc2690e24",
      "Name": "DACPACTargetDatabase",
      "Label": "Target Database",
      "HelpText": "Name of the database to target this deployment against",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "e1710f37-5f38-4d77-8a05-5fbb6d79132e",
      "Name": "DACPACAuthenticationType",
      "Label": "Authentication type",
      "HelpText": "Select the method to authenticate to the SQL Server.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "SqlAuthentication|SQL Authentication\nWindowsIntegrated|Windows Integrated\nAzureADManaged|Azure Active Directory Managed Identity\nAzureADPassword|Azure Active Directory Username/Password\nAzureADIntegrated|Azure Active Directory Integrated\nAzureADServicePrincipal|Azure Active Directory Service Principal"
      }
    },
    {
      "Id": "a51747d3-514d-4110-bf6b-e5f3932d4f22",
      "Name": "DACPACSQLUsername",
      "Label": "Username",
      "HelpText": "User name to use to connect to the server if we are not using Integrated Security.\n\nIf using the Azure Active Directory Service Principal Authentication Type, use the Azure Account variable here. For example, if your Azure Account variable is called MyAccount, the value for this input would be `#{MyAccount.Client}`",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "1afb7ff1-4447-425e-8625-33d76f32c321",
      "Name": "DACPACSQLPassword",
      "Label": "Password",
      "HelpText": "Password to use to connect to the server if we are not using Integrated Security.\n\nIf using the Azure Active Directory Service Principal Authentication Type, use the Azure Account variable here. For example, if your Azure Account variable is called MyAccount, the value for this input would be `#{MyAccount.Password}`",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "fa1d3a63-f2c0-4969-a4ac-d797df53bed1",
      "Name": "DACPACPackage",
      "Label": "DACPAC Package",
      "HelpText": "The package containing the `.dacpac` file from the specified repository.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Package"
      }
    },
    {
      "Id": "2afcffb6-6c2c-4012-8ea8-80ce9f3f4a19",
      "Name": "DACPACCommandTimeout",
      "Label": "Command Timeout",
      "HelpText": "Override the default command timeout for longer-running scripts.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "17444647-1f19-42e9-bde2-839601de5347",
      "Name": "DACPACExeLocation",
      "Label": "SqlPackage executable location",
      "HelpText": "Location of the SqlPackage executable.  Leave blank to dynamically download.<br />\nExamples:<br />\nEmbedded within the package:`#{Octopus.Action.Package[DACPACPackage].ExtractedPath}/MySubFolder`<br />\nOn disk:`c:\\sqlpackage\\sqlpackage.exe` or `/etc/sqlpackage/sqlpackage`",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "99d63959-edbb-4f06-a4be-15dd7cf24c35",
      "Name": "DACPACAdditionalArguments",
      "Label": "Additional arguments",
      "HelpText": "A comma-delimited list of additional arguments to add to the SqlPackage command.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    }
  ],
  "Properties": {
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptBody": "# Set TLS\n[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls11 -bor [System.Net.SecurityProtocolType]::Tls12\n\nWrite-Host \"Determining Operating System...\"\n# Check to see if $IsWindows is available\nif ($null -eq $IsWindows)\n{\n    switch ([System.Environment]::OSVersion.Platform)\n    {\n    \t\"Win32NT\"\n        {\n        \t# Set variable\n            $IsWindows = $true\n            $IsLinux = $false\n        }\n        \"Unix\"\n        {\n        \t$IsWindows = $false\n            $IsLinux = $true\n        }\n    }\n}\n\nif ($IsWindows)\n{\n\tWrite-Host \"Detected OS is Windows\"\n    $ProgressPreference = 'SilentlyContinue'\n}\nelse\n{\n\tWrite-Host \"Detected OS is Linux\"\n}\n\n<#\n .SYNOPSIS\n Finds the DAC File that you specify\n\n .DESCRIPTION\n Looks through the supplied PathList array and searches for the file you specify.  It will return the first one that it finds.\n\n .PARAMETER FileName\n Name of the file you are looking for\n\n .PARAMETER PathList\n Array of Paths to search through.\n\n .EXAMPLE\n Find-DacFile -FileName \"Microsoft.SqlServer.TransactSql.ScriptDom.dll\" -PathList @(\"${env:ProgramFiles}\\Microsoft SQL Server\", \"${env:ProgramFiles(x86)}\\Microsoft SQL Server\")\n#>\nFunction Find-DacFile {\n    Param(\n        [Parameter(Mandatory=$true)]\n        [string]$FileName,\n        [Parameter(Mandatory=$true)]\n        [string[]]$PathList\n    )\n\n    $File = $null\n\n    ForEach($Path in $PathList)\n    {\n        Write-Debug (\"Searching: {0}\" -f $Path)\n\n        If (!($File))\n        {\n            $File = (\n                Get-ChildItem $Path -ErrorAction SilentlyContinue -Filter $FileName -Recurse |\n                    Sort-Object FullName -Descending |\n                    Select-Object -First 1\n                )\n\n            If ($File)\n            {\n                Write-Debug (\"Found: {0}\" -f $File.FullName)\n            }\n        }\n    }\n\n    Return $File\n}\n\n\n<#\n .SYNOPSIS\n Generates a connection string\n\n .DESCRIPTION\n Derive a connection string from the supplied variables\n\n .PARAMETER ServerName\n Name of the server to connect to\n\n .PARAMETER Database\n Name of the database to connect to\n\n .PARAMETER UseIntegratedSecurity\n Boolean value to indicate if Integrated Security should be used or not\n\n .PARAMETER UserName\n User name to use if we are not using integrated security\n\n .PASSWORD Password\n Password to use if we are not using integrated security\n\n .PARAMETER EnableMultiSubnetFailover\n Flag as to whether we should enable multi subnet failover\n\n .EXAMPLE\n Get-ConnectionString -ServerName localhost -UseIntegratedSecurity -Database OctopusDeploy\n\n .EXAMPLE\n Get-ConnectionString -ServerName localhost -UserName sa -Password ProbablyNotSecure -Database OctopusDeploy\n#>\nFunction Get-ConnectionString {\n    Param(\n        [Parameter(Mandatory=$True)]\n        [string]$ServerName,\n        [string]$UserName,\n        [string]$Password,\n        [string]$Database,\n        [string]$AuthenticationType\n    )\n\n    $ApplicationName = \"OctopusDeploy\"\n    $connectionString = (\"Application Name={0};Server={1}\" -f $ApplicationName, $ServerName)\n\n    switch ($AuthenticationType)\n    {\n    \t\"AzureADPassword\"\n        {\n            Write-Verbose \"Using Azure Active Directory username and password\"\n            $connectionString += (\";Authentication='Active Directory Password';Uid={0};Pwd={1}\" -f $UserName, $Password)                \n            break\n        }\n        \"AzureADIntegrated\"\n        {\n            Write-Verbose \"Using Azure Active Directory integrated\"\n            $connectionString += (\";Authentication='Active Directory Integrated'\")                \n            break\n        }\n        \"AzureADManaged\"\n        {\n        \tWrite-Verbose \"Using Azure Active Directory managed identity\"\n            break\n        }\n        \"AzureADServicePrincipal\"\n        {\n             Write-Verbose \"Using Azure Active Directory username and password\"\n            $connectionString += (\";Authentication='ActiveDirectoryServicePrincipal';Uid={0};Pwd={1}\" -f $UserName, $Password)                \n            break       \t\n        }\n        \"SqlAuthentication\"\n        {\n            Write-Verbose \"Using SQL Authentication username and password\"\n            $connectionString += (\";Uid={0};Pwd={1}\" -f $UserName, $Password)                \n            break        \n        }\n        \"WindowsIntegrated\"\n        {\n            Write-Verbose \"Using integrated security\"\n            $connectionString += \";Trusted_Connection=True\"\n            break\n        }\n    }\n    \n    if ($EnableMultiSubnetFailover)\n    {\n        Write-Verbose \"Enabling multi subnet failover\"\n        $connectionString += \";MultisubnetFailover=True\"\n    }\n\n    If ($Database)\n    {\n        $connectionString += (\";Initial Catalog={0}\" -f $Database)\n    }\n\n\t$connectionString += \";TrustServerCertificate=true;\"\n\n    Return $connectionString\n}\n\n<#\n .SYNOPSIS\n Will find the full path of a given filename (For dacpac or publish profile)\n .DESCRIPTION\n Will search through an extracted package folder provided as the BasePath and hunt for any matches for the given filename.\n .PARAMETER BasePath\n String value of the root folder to begine the recursive search.\n .PARAMETER FileName\n String value of the name of the file to search for.\n .PARAMETER FileType\n String value of \"DacPac\" or \"PublishProfile\" to identify the type of file to search for.\n .EXAMPLE\n Get-DacFilePath -BasePath $ExtractPath -FileName $DACPACPackageName -FileType \"DacPac\"\n#>\nfunction Get-DacFilePath {\n    [cmdletbinding()]\n    param(\n        [parameter(Mandatory=$true)]\n        [string]$BasePath,\n\n        [parameter(Mandatory=$true)]\n        [string]$FileName,\n\n        [parameter(Mandatory=$true)]\n        [ValidateSet(\"DacPac\",\"PublishProfile\")]\n        [string]$FileType\n    )\n\n    # Add file extension for a dacpac if it's missing\n    if($FileName.Split(\".\")[-1] -ne \"dacpac\" -and $FileType -eq \"DacPac\"){\n        $FileName = \"$FileName.dacpac\"\n    }\n\n    Write-Verbose \"Looking for $FileType $FileName in $BasePath.\"\n\n    $filePath = (Get-ChildItem -Path $BasePath -Recurse -Filter $FileName).FullName\n\n    if(@($filePath).Length -gt 1){\n        Write-Warning \"Found $(@($filePath).Length) instances of $FileName. Using $($filePath[0]).\"\n        Write-Warning \"Multiple paths for $FileName`: $(@($filePath) -join \"; \")\"\n        $filePath = $filePath[0]\n    }\n    elseif(@($filePath).Length -lt 1 -or $null -eq $filePath){\n        Throw \"Could not find $FileName.\"\n    }\n\n    return $filePath\n}\n\nfunction Add-SqlCmdVariables\n{\n\t# Get all SqlCmdVariables\n    $sqlCmdVariables = $OctopusParameters.Keys -imatch \"SqlCmdVariable.*\"\n    $argumentList = @()\n        \n\t# Check to see if something is there\n\tif ($null -ne $sqlCmdVariables)\n    {\n    \tWrite-Host \"Adding SqlCmdVariables ...\"\n        \n\t\t# Loop through the variable collection\n        foreach ($sqlCmdVariable in $sqlCmdVariables)\n        {\n        \t# Add variable to the deploy options\n            $sqlCmdVariableKey = $sqlCmdVariable.Substring(($sqlCmdVariable.ToLower().IndexOf(\"sqlcmdvariable.\") + \"sqlcmdvariable.\".Length))\n            \n            Write-Host \"Adding variable: $sqlCmdVariableKey with value: $($OctopusParameters[$sqlCmdVariable])\"\n            \n            $argumentList += (\"/variables:{0}={1}\" -f $sqlCmdVariableKey, $OctopusParameters[$sqlCmdVariable])\n        }\n    }\n    \n    # return the list of variables\n    return $argumentList\n}\n\nfunction Add-AdditionalArguments\n{\n\t# Define parameters\n    param (\n    \t$AdditionalArguments\n    )\n    \n    # Define local variables\n    $argumentsToAdd = @()\n    \n    # Check for emmpty or null\n    if (![string]::IsNullOrWhitespace($AdditionalArguments))\n    {\n    \t# Split the arguments\n    \t$argumentsToAdd += $AdditionalArguments.Split(',', [System.StringSplitOptions]::RemoveEmptyEntries).Trim()\n    }\n    \n    # Return list\n    return $argumentsToAdd\n}\n\nfunction Get-SqlPackage\n{\n\t# Define local variables\n    $workFolder = $OctopusParameters['Octopus.Action.Package[DACPACPackage].ExtractedPath']\n    $downloadUrl = \"\"\n\n\t# Check to see if a folder needs to be created\n    if((Test-Path -Path \"$workFolder/sqlpackage\") -eq $false)\n    {\n        # Create new folder\n        New-Item -ItemType Directory -Path \"$workFolder/sqlpackage\"\n    }\n    \n    Write-Host \"Downloading SqlPackage ...\"\n    \n    if ($IsWindows)\n    {\n    \t# Set url\n        $downloadUrl = \"https://aka.ms/sqlpackage-windows\"\n    }\n    \n    if ($IsLinux)\n    {\n    \t# Set url\n        $downloadUrl = \"https://aka.ms/sqlpackage-linux\"\n    }\n    \n    # Download sql package\n    if ($PSVersionTable.PSVersion.Major -ge 6)\n    {\n    \t# Download\n        Invoke-WebRequest -Uri $downloadUrl -OutFile \"$workFolder/sqlpackage/sqlpackage.zip\"\n    }\n    else\n    {\n    \tInvoke-WebRequest -Uri $downloadUrl -OutFile \"$workFolder/sqlpackage/sqlpackage.zip\" -UseBasicParsing\n    }\n    \n    # Expand the archive\n    Write-Host \"Extracting .zip ...\"\n    Expand-Archive -Path \"$workFolder/sqlpackage/sqlpackage.zip\" -DestinationPath \"$workFolder/sqlpackage\"\n    \n    # Add to PATH\n    $env:PATH = \"$workFolder/sqlpackage$([IO.Path]::PathSeparator)\" + $env:PATH\n    \n    # Make it executable\n    if ($IsLinux)\n    {\n    \t& chmod a+x \"$workFolder/sqlpackage/sqlpackage\"\n    }\n}\n\nFunction Format-OctopusArgument {\n\n    Param(\n        [string]$Value\n    )\n\n    $Value = $Value.Trim()\n\n    # There must be a better way to do this\n    Switch -Wildcard ($Value){\n\n        \"True\" { Return $True }\n        \"False\" { Return $False }\n        \"#{*}\" { Return $null }\n        Default { Return $Value }\n    }\n}\n\nFunction Get-ManagedIdentityToken\n{\n\t# Get the identity token\n    Write-Host \"Getting Azure Managed Identity token ...\"\n    $token = $null\n    $tokenUrl = \"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fdatabase.windows.net%2F\"\n    \n    if ($PSVersionTable.PSVersion.Major -ge 6)\n    {\n    \t$token = Invoke-RestMethod -Method GET -Uri $tokenUrl -Headers @{\"MetaData\" = \"true\"}\n    }\n    else\n    {\n    \t$token = Invoke-RestMethod -Method GET -Uri $tokenUrl -Headers @{\"MetaData\" = \"true\"} -UseBasicParsing\n    }\n    \n    # Return the token\n    return $token.access_token\n}\n\nfunction Invoke-SqlPackage\n{\n\t# Define parameters\n    param (\n    \t$Action,\n        $Arguments\n    )\n    \n    # Add the action\n    $Arguments += \"/Action:$Action\"\n\n    # Display what's going to be run\n    if (![string]::IsNullOrWhitespace($Password))\n    {\n        $displayArguments = $Arguments.PSObject.Copy()\n        for ($i = 0; $i -lt $displayArguments.Count; $i++)\n        {\n            if ($null -ne $displayArguments[$i])\n            {\n                if ($displayArguments[$i].Contains($Password))\n                {\n                    $DisplayArguments[$i] = $displayArguments[$i].Replace($Password, \"****\")\n                }\n            }\n        }\n\n        Write-Host \"Executing the following command: sqlpackage $displayArguments\"\n    }\n    else \n    {\n        Write-Host \"Executing the following command: sqlpackage $Arguments\"\n    }    \n    \n    & sqlpackage $Arguments\n\n\t# Check exit code\n\tif ($lastExitCode -ne 0)\n\t{\n\t\t# Fail the step\n    \tWrite-Error \"Execution failed!\"\n\t}\n}\n\nfunction Validate-Folder\n{\n\t# Define parameters\n    param (\n    \t$TestPath\n    )\n    \n    # Check for folder\n    if ((Test-Path -Path $TestPath) -eq $false)\n    {\n    \t# Create the folder\n        New-Item -Path \"$TestPath\" -ItemType \"directory\"\n    }\n}\n\nFunction Remove-InvalidFileNameChars {\n\n\tParam(\n\t\t[string]$FileName\n\t)\n\n\t[IO.Path]::GetinvalidFileNameChars() | ForEach-Object { $FileName = $FileName.Replace($_, \"_\") }\n\tReturn $FileName\n}\n\n# Get the supplied parameters\n$PublishProfile = $OctopusParameters[\"DACPACPublishProfile\"]\n$DACPACReport = Format-OctopusArgument -Value $OctopusParameters[\"DACPACReport\"]\n$DACPACScript = Format-OctopusArgument -Value $OctopusParameters[\"DACPACScript\"]\n$DACPACDeploy = Format-OctopusArgument -Value $OctopusParameters[\"DACPACDeploy\"]\n$DACPACTargetServer = $OctopusParameters[\"DACPACTargetServer\"]\n$DACPACTargetDatabase = $OctopusParameters[\"DACPACTargetDatabase\"]\n$DACPACAdditionalArguments = $OctopusParameters[\"DACPACAdditionalArguments\"]\n$DACPACExeLocation = $OctopusParameters[\"DACPACExeLocation\"]\n$DACPACDateTime = ((Get-Date).ToUniversalTime().ToString(\"yyyyMMddHHmmss\"))\n\n$Username = $OctopusParameters[\"DACPACSQLUsername\"]\n$Password = $OctopusParameters[\"DACPACSQLPassword\"]\n$PackageReferenceName = \"DACPACPackage\"\n\n$authenticationType = $OctopusParameters[\"DACPACAuthenticationType\"]\n\n$ExtractPathKey = (\"Octopus.Action.Package[{0}].ExtractedPath\" -f $PackageReferenceName)\n$ExtractPath = $OctopusParameters[$ExtractPathKey]\n\nif(!(Test-Path $ExtractPath)) {\n    Throw (\"The package extraction folder '{0}' does not exist or the Octopus Tentacle does not have permission to access it.\" -f $ExtractPath)\n}\n\n# Get the DACPAC location\n$dacpacFolderName = [System.IO.Path]::GetDirectoryName($DACPACPackageName)\n$dacpacFileName = [System.IO.Path]::GetFileName($DACPACPackageName)\n$DACPACPackagePath = Get-DacFilePath -BasePath ($ExtractPath + ([IO.Path]::DirectorySeparatorChar) + $dacpacFolderName) -FileName $dacpacFileName -FileType \"DacPac\"\n\n# Invoke the DacPac utility\ntry\n{\n\t# Declare working variables\n    $sqlPackageArguments = @()\n    \n    # Build arugment list\n    $sqlPackageArguments += \"/SourceFile:`\"$DACPACPackagePath`\"\"\n    $sqlPackageArguments += \"/TargetConnectionString:`\"$(Get-ConnectionString -ServerName $DACPACTargetServer -Database $DACPACTargetDatabase -UserName $UserName -Password $Password -AuthenticationType $AuthenticationType)`\"\"\n    \n\t# Check to see if a publish profile was designated\n\tIf ($PublishProfile){\n    \t$profileFolderName = [System.IO.Path]::GetDirectoryName($PublishProfile)\n        $profileFileName = [System.IO.Path]::GetFileName($PublishProfile)\n    \t$PublishProfilePath = Get-DacFilePath -BasePath ($ExtractPath + ([IO.Path]::DirectorySeparatorChar) + $profileFolderName) -FileName $profileFileName -FileType \"PublishProfile\"\n    \n    \t# Add to arguments\n    \t$sqlPackageArguments += \"/Profile:`\"$PublishProfilePath`\"\"\n\t}    \n    \n    # Check to see if it's using managed identity\n    if ($authenticationType -eq \"AzureADManaged\")\n    {\n    \t# Add access token\n        $Password = Get-ManagedIdentityToken\n        $sqlPackageArguments += \"/AccessToken:$Password\"\n    }\n    \n    # Add sqlcmd variables\n    $sqlPackageArguments += Add-SqlCmdVariables\n    \n\t# Add addtional arguments\n    $sqlPackageArguments += Add-AdditionalArguments -AdditionalArguments $DACPACAdditionalArguments\n    \n    # Check to see if command timeout was specified\n    if (![string]::IsNullOrWhitespace($DACPACCommandTimeout))\n    {\n    \t# Add timeout parameter\n        $sqlPackageArguments += \"/Properties:CommandTimeout=$DACPACCommandTimeout\"\n    }\n    \n    # Check to see if sqlpackage needs to be downloaded\n    if ([string]::IsNullOrWhitespace($DACPACExeLocation))\n    {\n    \t# Download and extract sqlpackage\n        Get-SqlPackage\n    }\n    else\n    {\n    \t# Add folder location to path\n        $env:PATH = \"$([IO.Path]::GetDirectoryName($DACPACExeLocation))$([IO.Path]::PathSeparator)\" + $env:PATH\n        Write-Host \"It is $($env:PATH)\"\n    }\n    \n    # Execute the actions\n    if ($DACPACReport)\n    {\n    \t$workFolder = \"$($OctopusParameters['Octopus.Action.Package[DACPACPackage].ExtractedPath'])/reports\"\n        $sqlReportArguments = @()\n        $reportArtifact = Remove-InvalidFileNameChars -FileName (\"{0}.{1}.{2}.{3}\" -f $DACPACTargetServer, $DACPACTargetDatabase, $DACPACDateTime, \"DeployReport.xml\")\n        $sqlReportArguments += \"/OutputPath:$workFolder/$reportArtifact\"\n        \n        # Validate the folder\n        Validate-Folder -TestPath $workFolder\n        \n        # Execute the action\n        Invoke-SqlPackage -Action \"DeployReport\" -Arguments ($sqlPackageArguments + $sqlReportArguments)\n        \n        # Attach artifacts\n        foreach ($item in (Get-ChildItem -Path $workFolder))\n        {\n        \t# Upload artifact\n            New-OctopusArtifact $item.FullName\n        }\n    }\n    \n    if ($DACPACScript)\n    {\n    \t$workFolder = \"$($OctopusParameters['Octopus.Action.Package[DACPACPackage].ExtractedPath'])/scripts\"\n        $sqlScriptArguments = @()\n        $scriptArtifact = Remove-InvalidFileNameChars -FileName (\"{0}.{1}.{2}.{3}\" -f $DACPACTargetServer, $DACPACTargetDatabase, $DACPACDateTime, \"DeployScript.sql\")\n        $sqlScriptArguments += \"/OutputPath:$workFolder/$scriptArtifact\"\n        \n        # Validate folder\n        Validate-Folder -TestPath $workFolder\n        \n        # Execute the action\n        Invoke-SqlPackage -Action \"Script\" -Arguments ($sqlPackageArguments + $sqlScriptArguments)\n        \n        # Attach artifacts\n        foreach ($item in (Get-ChildItem -Path $workFolder))\n        {\n        \t# Upload artifact\n            New-OctopusArtifact $item.FullName\n        }        \n    }\n    \n    if ($DACPACDeploy)\n    {\n    \t# Execute action\n        Invoke-SqlPackage -Action \"Publish\" -Arguments $sqlPackageArguments\n    }\n}\ncatch\n{\n    Write-Host $_.Exception.ToString()\n    throw;\n}\n",
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.RunOnServer": "false"
  },
  "Category": "SQL Server",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/sql-deploy-dacpac-sqlpackage.json",
  "Website": "/step-templates/c323cbcd-aab8-4229-b07c-e6c26f7e9a8a",
  "Logo": "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",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Thursday, June 1, 2023