A YAML Azure DevOps SSIS Pipeline

June 11, 2025    TFS/VSTS/AzureDevOps DevOps CI/CD YAML

A YAML Azure DevOps SSIS Pipeline

A MS Copilot generated image of ADO with conveyor belts

I needed to create an easy way to for my team to add a YAML pipeline to our many SSIS packages we maintain. I struggled for awhile as I was pushing the limits of using parameters and variables in templates. I still don’t have a good way to use Variable Groups, but hopefully this helps you

I could have named this post "I struggled so you don't have to" (as much, hopefully) 😁

I’ll have another post focused on using templates, but here is using SSIS.

ssisBuild.yml

# Build the SSIS package and create artifacts that can be deployed
# Create a ispac for the 'Development', 'Test' and 'Production' which are the same as the Visual Studio Build Configuration drop down options
# https://learn.microsoft.com/en-us/sql/integration-services/devops/ssis-devops-overview

## Usage
## see https://dev.azure.com/AveraHealthPlans/Software/_git/Examples/Pipelines/SSIS


parameters:
  - name: dtproj
    type: string
  - name: artifactName
    type: string
  - name: sourceFolder
    type: string
  - name: isMain
    type: string
    values:
     - 'true'
     - 'false'

jobs:
  - job: BuildAndCreateArtifacts
    displayName: "Build and Create .ispac Artifact"
    steps:
      - task: SSISBuild@1
        displayName: "Build SSIS Package - Development"
        inputs:
          projectPath: ${{ parameters.dtproj }}
          outputPath: '$(Build.ArtifactStagingDirectory)/Development'
          configuration: 'Development'
          stripSensitive: true
      - task: SSISBuild@1
        displayName: "Build SSIS Package - Test"
        inputs:
          projectPath: ${{ parameters.dtproj }}
          outputPath: '$(Build.ArtifactStagingDirectory)/Test'
          configuration: 'Test'
          stripSensitive: true
          # if Test isn't defined, continue on
          continueOnError: true
      - task: SSISBuild@1
        displayName: "Build SSIS Package - Production"
        inputs:
          projectPath: ${{ parameters.dtproj }}
          outputPath: '$(Build.ArtifactStagingDirectory)/Production'
          configuration: 'Production'
          stripSensitive: true

      - task: PublishBuildArtifacts@1
        displayName: "Publish The .ispac Build Artifacts"
        inputs:
          PathtoPublish: $(Build.ArtifactStagingDirectory)
          ArtifactName: ${{ parameters.artifactName }}

      # properties will need to be set manually in SSMS > package > configure
      # - task: PublishBuildArtifacts@1
      #   displayName: "Publish SSISSSISCatalog configuraiton Build Artifacts"
      #   inputs:
      #     PathtoPublish: '${{ parameters.sourceFolder }}/ssisConfiguration.json'
      #     ArtifactName: '${{ parameters.artifactName }}_config'

ssisDeployToEnvironment.yml

# Deploy/Publish the SSIS Package to the Database Server 
# Dev and Production Environments that are setup in the ADO UI under Pipelines > Environments
# https://learn.microsoft.com/en-us/sql/integration-services/devops/ssis-devops-overview

## Usage
## see https://dev.azure.com/AveraHealthPlans/Software/_git/Examples/Pipelines/SSIS
## Build an .ispac for each environment, then use those artifacts here

parameters:
  # should match the <name>.ispac
  - name: artifactName
    type: string
  # 'Development', 'Test', 'Production'
  - name: environmentName
    type: string
  # since we can't pull these directly from the variable group and use $(DatabaseAdoEnvironmentName) (it's not evaluated)
  # this should be from the variable Group
  - name: databaseAdoEnvironmentName
    type: string
  # the name of the agent under the ADO environment
  - name: databaseAdoResourceName
    type: string
  - name: databaseServer
    type: string
  # Example: '/SSISDB/MyFolder/MyProject'
  - name: destinationPath
    type: string


jobs:
  - deployment: 'Deploy_${{ parameters.environmentName }}'
    displayName: 'Deploy ${{ parameters.environmentName }}'
    # dependsOn: ${{ parameters.stageDependsOn }} I kept getting Stage PublishProduction job Deploy_Production depends on unknown job Deploy_Development. so I removed this
    environment:
      name: ${{ parameters.databaseAdoEnvironmentName }}
      resourceName: ${{ parameters.databaseAdoResourceName }}
      resourceType: VirtualMachine
    strategy:
      runOnce:
        deploy:
          steps:
            - checkout: none
            # TODO: create json configuration files, publish them from the ssisBuild and use them here
            # for now: properties will need to be set manually in SSMS > package > configure on creation and if they change
            # https://learn.microsoft.com/en-us/sql/integration-services/devops/ssis-devops-overview?view=sql-server-ver16#ssis-catalog-configuration-task
            # - task: SSISCatalog@0
            #   inputs:
            #     # 'filePath', 'inline', 'none'
            #     configSource: 'filePath'
            #     # note: I'm not certain about using this, look at the documentation
            #     # configPath: '${{ parameters.artifactName }}/${{ parameters.artifactName }}_config/Project.params'
            #     # targetServer is where the SSISDB catalog is hosted and usually the same as destinationServer
            #     targetServer: ${{ parameters.databaseServer }}
            #     connectionStringSuffix: ''
            #     # 'win' or 'sql'
            #     authType: 'win'
            #     rollBackOnError: true

            # This will not create a new Package on the database in SSISDB
            # It will only update the existing ones
            # - task: SSISDeploy@1
            #   displayName: "Deploy SSIS Package"
            #   inputs:
            #     # Example: your build stage artifact from ssisBuild.yml
            #     # The auto download artifacts puts it in
            #     sourcePath: '$(Pipeline.Workspace)/${{ parameters.artifactName }}/${{ parameters.environmentName }}/${{ parameters.artifactName }}.ispac'
            #     # Example: 'SSISDB' or 'File System'
            #     destinationType: 'SSISDB'
            #     destinationServer: ${{ parameters.databaseServer }}
            #     destinationPath: ${{ parameters.destinationPath }}
            #     authType: 'win'
            #     whetherOverwrite: true
            #     whetherContinue: false

            # This will create or update the package.
            # If creating you still need to add the parameter overrides in SSMS > Project > Configure             
            - task: PowerShell@2
              displayName: "Deploy SSIS Package"
              inputs:
                targetType: 'inline'
                script: |
                  # SSISDeploy.exe needs to be on the VM with SSISDeploy.exe in the environment variable path
                  # https://learn.microsoft.com/en-us/sql/integration-services/devops/ssis-devops-standalone?view=sql-server-ver16
                  # Path to the SSISDeploy.exe
                  $ssisDeployPath = SSISDeploy.exe
                  
                  # Source path to the ISPAC file
                  $sourcePath = "$(Pipeline.Workspace)/${{ parameters.artifactName }}/${{ parameters.environmentName }}/${{ parameters.artifactName }}.ispac"
                  
                  # Destination type: CATALOG or FILE
                  $destinationType = "catalog"
                  
                  # Destination server (for CATALOG)
                  $destinationServer = "${{ parameters.databaseServer }}"
                  
                  # Destination path in SSISDB (for CATALOG)
                  $destinationPath = "${{ parameters.destinationPath }}"
                  
                  # Authentication type (WIN, SQL, ADPWD, ADINT)
                  $authType = "win"
                  
                  # Project password (if required)
                  $projectPassword = ""
                  
                  # Execute SSISDeploy.exe
                  ## SSISDeploy.exe -s:Example.ispac -d:"catalog;/SSISDB/ExampleA;AHDC790DBd01" -at:win
                  & SSISDeploy.exe -s:$sourcePath -d:"$destinationType;$destinationPath;$destinationServer;" -at:$authType

Future Improvements

  • Pull values from Variable Groups (I tried but didn’t find a way to make it happen with the -template)
  • Create json configuration files and use in the SSISCatalog@0 task

https://github.com/MicrosoftDocs/sql-docs/pull/10117

Use the templates in your pipeline


trigger:
  - main

pr:
  - main

pool:
  vmImage: 'windows-latest'

variables:
  artifactName: 'Example'
  folder: 'Pipelines\SSIS\Example\'
  dtproj: '${{ variables.folder }}Example.dtproj'
  ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}:
    isMain: 'true'
  ${{ else }}:
    isMain: 'false'

resources:
  repositories:
  - repository: CommonTools
    name: Software/CommonTools
    type: git
    ref: iis

stages:
  - stage: BuildAndCreateArtifacts
    jobs:
      - template: Pipelines/ssisBuild.yml@CommonTools
        parameters:
          dtproj: ${{ variables.dtproj }}
          sourceFolder: ${{ variables.folder }}
          artifactName: ${{ variables.artifactName }}
          isMain: ${{ variables.isMain }}

  - stage: PublishDevelopment
    condition: eq(variables.isMain, true)
    jobs:
      - template: Pipelines/ssisDeployToEnvironment.yml@CommonTools
        parameters:
          artifactName: ${{ variables.artifactName }}
          environmentName: 'Development'
          destinationPath: '/SSISDB/Example/ExamplePackage'
          databaseAdoEnvironmentName: 'Development-Database-OnPremise'
          databaseAdoResourceName: 'Dev-ResourceName'
          databaseServer: 'DB01-Dev'
  
  - stage: PublishProduction
    condition: eq(variables.isMain, true)
    jobs:            
      - template: Pipelines/ssisDeployToEnvironment.yml@CommonTools
        parameters:
          artifactName: ${{ variables.artifactName }}
          environmentName: 'Production'
          destinationPath: '/SSISDB/Example/ExamplePackage'
          databaseAdoEnvironmentName: 'Production-Database-OnPremise'
          databaseAdoResourceName: 'Prod-ResourceName'
          databaseServer: 'DB01'
      # avoid Stage PublishProduction must contain at least one job with no dependencies.
      - job: empty
        steps:
          - script: echo 'empty'

Azure DevOps Environments

We are deploying to OnPremise databases.

First we created a Development and Production environment . They are referenced by name in the databaseAdoEnvironmentName in the YAML above.

Then, we needed to setup [self-hosted agents] ( https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/windows-agent?view=azure-devops&tabs=IP-V4) . you’ll need an administrator to run theseThese are added as resources in the environment by clicking add resource and running the script provided.

Security

Read through Prepare Permissions .

Theoretically, I think it’d be best to have an Microsoft EntraId user created for each environment (even split up for database, IIS, other server) to give the least priveledge. Then create the agent with a PAT specific to that user after it is in ADO. I’m not sure if you could sign in with that user into ADO, then create the user to get the PAT? When I go through that I’ll update this post.

Resources

Here are a few of the many links I went through during the process.

It’s always nice to close those research tabs .



Watch the Story for Good News
I gladly accept BTC Lightning Network tips at strike.me/aligned

Please consider using Brave and adding me to your BAT payment ledger. Then you won't have to see ads! (when I get to $100 in Google Ads for a payout (I'm at $97.66!), I pledge to turn off ads)

Use Brave

Also check out my Resources Page for referrals that would help me.


Swan logo
Use Swan Bitcoin to onramp with low fees and automatic daily cost averaging and get $10 in BTC when you sign up.