diff --git a/.azure/pipelines/fast-pr-validation.yml b/.azure/pipelines/fast-pr-validation.yml
index 44029cecb3f54a166ab334338b3bb36579f176eb..143abd2e46f717108c4ea1c8617812b44e655b04 100644
--- a/.azure/pipelines/fast-pr-validation.yml
+++ b/.azure/pipelines/fast-pr-validation.yml
@@ -1,24 +1,17 @@
+
 trigger:
 - master
 - release/*
 
-# See https://github.com/aspnet/BuildTools
-resources:
-  repositories:
-  - repository: buildtools
-    type: github
-    endpoint: DotNet-Bot GitHub Connection
-    name: aspnet/BuildTools
-    ref: refs/heads/release/2.2
-
-phases:
-- template: .vsts-pipelines/templates/project-ci.yml@buildtools
+jobs:
+- template: project-ci.yml
   parameters:
     buildArgs: "/t:FastCheck"
-- phase: RepoBuilds
-  queue:
-    name: Hosted VS2017
-    parallel: 2
+- job: RepoBuilds
+  pool:
+    vmImage: vs2017-win2016
+  strategy:
+    maxParallel: 3
     matrix:
       DataProtection:
         _FolderName: DataProtection
@@ -33,3 +26,31 @@ phases:
     inputs:
       testRunner: vstest
       testResultsFiles: 'src/$(_FolderName)/artifacts/logs/**/*.trx'
+- template: jobs/iisintegration-job.yml
+  parameters:
+    TestGroupName: IIS
+    SkipIISTests: false
+    SkipIISExpressTests: true
+    SkipIISForwardsCompatibilityTests: true
+    SkipIISBackwardsCompatibilityTests: true
+- template: jobs/iisintegration-job.yml
+  parameters:
+    TestGroupName: IISExpress
+    SkipIISTests: true
+    SkipIISExpressTests: false
+    SkipIISForwardsCompatibilityTests: true
+    SkipIISBackwardsCompatibilityTests: true
+- template: jobs/iisintegration-job.yml
+  parameters:
+    TestGroupName: IISForwardCompat
+    SkipIISTests: true
+    SkipIISExpressTests: true
+    SkipIISForwardsCompatibilityTests: false
+    SkipIISBackwardsCompatibilityTests: true
+- template: jobs/iisintegration-job.yml
+  parameters:
+    TestGroupName: IISBackCompat
+    SkipIISTests: true
+    SkipIISExpressTests: true
+    SkipIISForwardsCompatibilityTests: true
+    SkipIISBackwardsCompatibilityTests: false
diff --git a/.azure/pipelines/jobs/default-build.yml b/.azure/pipelines/jobs/default-build.yml
new file mode 100644
index 0000000000000000000000000000000000000000..42c603e93f95319fd172060c668fd295af9ef75d
--- /dev/null
+++ b/.azure/pipelines/jobs/default-build.yml
@@ -0,0 +1,148 @@
+# default-build.yml
+# Description: Defines a build phase for invoking build.sh/cmd
+# Parameters:
+#   jobName: string
+#       The name of the job. Defaults to the name of the OS. No spaces allowed
+#   jobDisplayName: string
+#       The friendly job name to display in the UI. Defaults to the name of the OS.
+#   poolName: string
+#       The name of the VSTS agent queue to use.
+#   agentOs: string
+#       Used in templates to define variables which are OS specific. Typically from the set { Windows, Linux, macOS }
+#   buildArgs: string
+#       Additional arguments to pass to the build.sh/cmd script.
+#       Note: -ci is always passed
+#   beforeBuild: [steps]
+#       Additional steps to run before build.sh/cmd
+#   afterBuild: [steps]
+#       Additional steps to run after build.sh/cmd
+#   artifacts:
+#      publish: boolean
+#           Should artifacts be published
+#      path: string
+#           The file path to artifacts output
+#      name: string
+#           The name of the artifact container
+#   variables: { string: string }
+#     A map of custom variables
+#   matrix: { string: { string: string } }
+#     A map of matrix configurations and variables. https://docs.microsoft.com/en-us/vsts/pipelines/yaml-schema?view=vsts#matrix
+#   demands: string | [ string ]
+#     A list of agent demands. https://docs.microsoft.com/en-us/vsts/pipelines/yaml-schema?view=vsts#demands
+#   dependsOn: string | [ string ]
+#     For fan-out/fan-in. https://docs.microsoft.com/en-us/vsts/pipelines/yaml-schema?view=vsts#phase
+#   codeSign: boolean
+#       This build definition is enabled for code signing. (Only applies to Windows)
+#   buildDirectory: string
+#       Specifies what directory to run build.sh/cmd
+
+#
+# See https://docs.microsoft.com/en-us/vsts/pipelines/yaml-schema for details
+#
+
+parameters:
+  agentOs: 'Windows'
+  poolName: ''
+  buildArgs: ''
+  configuration: 'Release'
+  demands: []
+  beforeBuild: []
+  afterBuild: []
+  codeSign: false
+  variables: {}
+  dependsOn: ''
+  # buildSteps: ''- don't define an empty object default because there is no way in template expression yet to check "if isEmpty(parameters.buildSteps)"
+  # jobName: '' - use agentOs by default.
+  # jobDisplayName: '' - use agentOs by default.
+  # matrix: {} - don't define an empty object default because there is no way in template expression yet to check "if isEmpty(parameters.matrix)"
+  artifacts:
+    publish: true
+    path: 'artifacts/'
+  buildDirectory: ''
+  buildSteps: []
+
+jobs:
+- job: ${{ coalesce(parameters.jobName, parameters.agentOs) }}
+  displayName: ${{ coalesce(parameters.jobDisplayName, parameters.agentOs) }}
+  dependsOn: ${{ parameters.dependsOn }}
+  workspace:
+    clean: all
+  strategy:
+    ${{ if ne(parameters.matrix, '') }}:
+      maxParallel: 8
+      matrix: ${{ parameters.matrix }}
+  # Map friendly OS names to the right queue
+  pool:
+    ${{ if ne(parameters.poolName, '') }}:
+      name: ${{ parameters.poolName }}
+    ${{ if and(eq(parameters.poolName, ''), eq(parameters.agentOs, 'macOS')) }}:
+      name: Hosted macOS
+      vmImage: macOS-10.13
+    ${{ if and(eq(parameters.poolName, ''), eq(parameters.agentOs, 'Linux')) }}:
+      name: Hosted Ubuntu 1604
+      vmImage: ubuntu-16.04
+    ${{ if and(eq(parameters.poolName, ''), eq(parameters.agentOs, 'Windows')) }}:
+      ${{ if ne(parameters.codeSign, 'true') }}:
+        name: Hosted VS2017
+        vmImage: vs2017-win2016
+      ${{ if eq(parameters.codeSign, 'true') }}:
+        name: DotNetCore-Windows
+  variables:
+    AgentOsName: ${{ parameters.agentOs }}
+    ASPNETCORE_TEST_LOG_MAXPATH: "200" # Keep test log file name length low enough for artifact zipping
+    DOTNET_HOME: $(Agent.WorkFolder)/.dotnet
+    BuildScriptArgs: ${{ parameters.buildArgs }}
+    BuildConfiguration: ${{ parameters.configuration }}
+    BuildDirectory: ${{ parameters.buildDirectory }}
+    VSTS_OVERWRITE_TEMP: false # Workaround for https://github.com/dotnet/core-eng/issues/2812
+    ${{ if eq(parameters.codeSign, 'true') }}:
+      TeamName: AspNetCore
+      _SignType: real
+    ${{ if ne(parameters.codeSign, 'true') }}:
+      _SignType:
+    ${{ insert }}: ${{ parameters.variables }}
+  steps:
+  - checkout: self
+    clean: true
+  - ${{ if and(eq(parameters.agentOs, 'Windows'), eq(parameters.codeSign, 'true')) }}:
+    - task: MicroBuildSigningPlugin@1
+      displayName: Install MicroBuild Signing plugin
+      condition: and(succeeded(), in(variables['_SignType'], 'test', 'real'))
+      inputs:
+        signType: $(_SignType)
+        zipSources: false
+  - ${{ parameters.beforeBuild }}
+  - ${{ if eq(parameters.agentOs, 'Windows')}}:
+    - script: .\$(BuildDirectory)\build.cmd -ci /p:SignType=$(_SignType) /p:Configuration=$(BuildConfiguration) $(BuildScriptArgs)
+      displayName: Run build.cmd
+  - ${{ if ne(parameters.agentOs, 'Windows')}}:
+    - script: ./$(BuildDirectory)/build.sh -ci -p:Configuration=$(BuildConfiguration) $(BuildScriptArgs)
+      displayName: Run build.sh
+  - task: PublishTestResults@2
+    displayName: Publish test results
+    condition: always()
+    inputs:
+      testRunTitle: $(AgentOsName)-$(BuildConfiguration)
+      testRunner: vstest
+      testResultsFiles: 'artifacts/logs/**/*.trx'
+      mergeTestResults: true
+  - ${{ if eq(parameters.artifacts.publish, 'true') }}:
+    - task: PublishBuildArtifacts@1
+      displayName: Upload artifacts
+      condition: eq(variables['system.pullrequest.isfork'], false)
+      inputs:
+        ${{ if eq(parameters.buildDirectory, '') }}:
+          pathtoPublish: ${{ parameters.artifacts.path }}
+        ${{ if ne(parameters.artifacts.name, '') }}:
+          pathtoPublish: ${{ parameters.buildDirectory }}\${{ parameters.artifacts.path }}
+        ${{ if eq(parameters.artifacts.name, '') }}:
+          artifactName: artifacts-$(AgentOsName)-$(BuildConfiguration)
+        ${{ if ne(parameters.artifacts.name, '') }}:
+          artifactName: ${{ parameters.artifacts.name }}
+        artifactType: Container
+        parallel: true
+  - ${{ parameters.afterBuild }}
+  - ${{ if and(eq(parameters.agentOs, 'Windows'), eq(parameters.codeSign, 'true')) }}:
+    - task: MicroBuildCleanup@1
+      displayName: Cleanup MicroBuild tasks
+      condition: always()
diff --git a/.azure/pipelines/jobs/iisintegration-job.yml b/.azure/pipelines/jobs/iisintegration-job.yml
new file mode 100644
index 0000000000000000000000000000000000000000..01ce2d0ebe169e139bdad9957c93d3497a91fd70
--- /dev/null
+++ b/.azure/pipelines/jobs/iisintegration-job.yml
@@ -0,0 +1,22 @@
+jobs:
+- template: default-build.yml
+  parameters:
+    beforeBuild:
+      - powershell: "& ./src/IISIntegration/tools/UpdateIISExpressCertificate.ps1; & ./src/IISIntegration/tools/update_schema.ps1; & ./src/IISIntegration/tools/SetupTestEnvironment.ps1 Setup"
+        displayName: Prepare repo
+    afterBuild:
+      - powershell: "& ./src/IISIntegration/tools/SetupTestEnvironment.ps1 Shutdown"
+        displayName: Stop AppVerifier
+        condition: always()
+      - task: PublishBuildArtifacts@1
+        displayName: Upload logs
+        condition: eq(variables['system.pullrequest.isfork'], false)
+        inputs:
+          artifactName: logs
+          artifactType: Container
+          pathtoPublish: src/IISIntegration/artifacts/logs
+    buildDirectory: src/IISIntegration
+    buildArgs: "/p:SkipIISBackwardsCompatibilityTests=${{ parameters.SkipIISBackwardsCompatibilityTests }} /p:SkipIISTests=${{ parameters.SkipIISTests }} /p:SkipIISExpressTests=${{ parameters.SkipIISExpressTests }} /p:SkipIISForwardsCompatibilityTests=${{ parameters.SkipIISBackwardsCompatibilityTests }}"
+    jobName: IISIntegration_${{ parameters.TestGroupName }}
+    jobDisplayName: IISIntegration_${{ parameters.TestGroupName }}
+
diff --git a/.azure/pipelines/project-ci.yml b/.azure/pipelines/project-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fd0f20f20b2e5a37b419c3144191aa63b3feaf06
--- /dev/null
+++ b/.azure/pipelines/project-ci.yml
@@ -0,0 +1,58 @@
+# Description: Runs build.cmd/sh on macOS, Linux, and Windows
+# Parameters:
+#   buildArgs: string
+#       Additional arguments to pass to the build.sh/cmd script.
+#       Note: -ci is always passed
+#   beforeBuild: [steps]
+#       Additional steps to run before build.sh/cmd
+#   afterBuild: [steps]
+#       Additional steps to run after build.sh/cmd
+#   variables: {}
+#       VSTS build and environment variables
+#   matrix: {}
+#       The matrix of configurations to run. By default, it runs a Debug and Release build on all platforms
+#   codeSign: boolean
+#       This build definition is enabled for code signing. (Only applies to Windows)
+#   buildDirectory: string
+#       Specifies what directory to run build.sh/cmd
+parameters:
+  buildArgs: ''
+  beforeBuild: []
+  afterBuild: []
+  codeSign: false
+  variables: {}
+  matrix:
+    Release:
+      BuildConfiguration: Release
+    Debug:
+      BuildConfiguration: Debug
+  buildDirectory: ''
+jobs:
+- template: jobs/default-build.yml
+  parameters:
+    agentOs: Windows
+    matrix: ${{ parameters.matrix }}
+    buildArgs: ${{ parameters.buildArgs }}
+    beforeBuild: ${{ parameters.beforeBuild }}
+    afterBuild: ${{ parameters.afterBuild }}
+    codeSign: ${{ parameters.codeSign }}
+    variables: ${{ parameters.variables }}
+    buildDirectory: ${{ parameters.buildDirectory }}
+- template: jobs/default-build.yml
+  parameters:
+    agentOs: macOS
+    matrix: ${{ parameters.matrix }}
+    buildArgs: ${{ parameters.buildArgs }}
+    beforeBuild: ${{ parameters.beforeBuild }}
+    afterBuild: ${{ parameters.afterBuild }}
+    variables: ${{ parameters.variables }}
+    buildDirectory: ${{ parameters.buildDirectory }}
+- template: jobs/default-build.yml
+  parameters:
+    agentOs: Linux
+    matrix: ${{ parameters.matrix }}
+    buildArgs: ${{ parameters.buildArgs }}
+    beforeBuild: ${{ parameters.beforeBuild }}
+    afterBuild: ${{ parameters.afterBuild }}
+    variables: ${{ parameters.variables }}
+    buildDirectory: ${{ parameters.buildDirectory }}
\ No newline at end of file
diff --git a/src/IISIntegration/build/repo.targets b/src/IISIntegration/build/repo.targets
index 3e937d6bf97fe403e2b763cc37d4b47e1edda76d..51fae5d5881b46614b02957548c5f428e61c95f0 100644
--- a/src/IISIntegration/build/repo.targets
+++ b/src/IISIntegration/build/repo.targets
@@ -58,19 +58,12 @@
       </ArtifactInfo>
       <FilesToSign Include="$(AncmV2PackageOutputPath)" Certificate="$(PackageSigningCertName)" />
 
-      <ArtifactInfo Include="$(AncmZipOutputPath)">
-        <ArtifactType>ZipArchive</ArtifactType>
-        <RepositoryRoot>$(RepositoryRoot)</RepositoryRoot>
-        <Category>shipoob</Category>
-      </ArtifactInfo>
-
       <ArtifactInfo Include="$(StressTestWebSiteZipOutputPath)">
         <ArtifactType>ZipArchive</ArtifactType>
         <RepositoryRoot>$(RepositoryRoot)</RepositoryRoot>
         <Category>noship</Category>
       </ArtifactInfo>
 
-      <FilesToSign Include="$(AncmZipOutputPath)" Certificate="None" IsContainer="true" />
       <FilesToExcludeFromSigning Include="$(StressTestWebSiteZipOutputPath)" />
       <FilesToExcludeFromSigning Include="AspNetCore\ancm.mof" Container="$(AncmV2PackageOutputPath)" />
     </ItemGroup>
@@ -109,10 +102,6 @@
       <AncmFiles Include="$(SourceBase)AspNetCoreModuleV2\AspNetCore\ancm.mof" Link="AspNetCoreModuleV2\ancm.mof" />
     </ItemGroup>
 
-    <ZipArchive File="$(AncmZipOutputPath)"
-      Overwrite="true"
-      SourceFiles="@(AncmFiles)"
-      WorkingDirectory="$(RepositoryRoot)" />
   </Target>
 
   <Target Name="RunNativeTest" DependsOnTargets="GetToolsets">