deployment/Deploy.ps1 (530 lines of code) (raw):
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE file in the project root for license information.
#
# Powershell script to deploy the resources - Customer portal, Publisher portal and the Azure SQL Database
#
#.\Deploy.ps1 `
# -WebAppNamePrefix "amp_saas_accelerator_<unique>" `
# -Location "<region>" `
# -PublisherAdminUsers "<your@email.address>"
Param(
[string][Parameter(Mandatory)]$WebAppNamePrefix, # Prefix used for creating web applications
[string][Parameter()]$ResourceGroupForDeployment, # Name of the resource group to deploy the resources
[string][Parameter(Mandatory)]$Location, # Location of the resource group
[string][Parameter(Mandatory)]$PublisherAdminUsers, # Provide a list of email addresses (as comma-separated-values) that should be granted access to the Publisher Portal
[string][Parameter()]$TenantID, # The value should match the value provided for Active Directory TenantID in the Technical Configuration of the Transactable Offer in Partner Center
[string][Parameter()]$AzureSubscriptionID, # Subscription where the resources be deployed
[string][Parameter()]$ADApplicationID, # The value should match the value provided for Active Directory Application ID in the Technical Configuration of the Transactable Offer in Partner Center
[string][Parameter()]$ADApplicationSecret, # Secret key of the AD Application
[string][Parameter()]$ADApplicationIDAdmin, # Multi-Tenant Active Directory Application ID
[string][Parameter()]$ADMTApplicationIDPortal, #Multi-Tenant Active Directory Application ID for the Landing Portal
[string][Parameter()]$IsAdminPortalMultiTenant, # If set to true, the Admin Portal will be configured as a multi-tenant application. This is by default set to false.
[string][Parameter()]$SQLDatabaseName, # Name of the database (Defaults to AMPSaaSDB)
[string][Parameter()]$SQLServerName, # Name of the database server (without database.windows.net)
[string][Parameter()]$LogoURLpng, # URL for Publisher .png logo
[string][Parameter()]$LogoURLico, # URL for Publisher .ico logo
[string][Parameter()]$KeyVault, # Name of KeyVault
[switch][Parameter()]$Quiet #if set, only show error / warning output from script commands
)
# Define the warning message
$message = @"
The SaaS Accelerator is offered under the MIT License as open source software and is not supported by Microsoft.
If you need help with the accelerator or would like to report defects or feature requests use the Issues feature on the GitHub repository at https://aka.ms/SaaSAccelerator
Do you agree? (Y/N)
"@
# Display the message in yellow
Write-Host $message -ForegroundColor Yellow
# Prompt the user for input
$response = Read-Host
# Check the user's response
if ($response -ne 'Y' -and $response -ne 'y') {
Write-Host "You did not agree. Exiting..." -ForegroundColor Red
exit
}
# Proceed if the user agrees
Write-Host "Thank you for agreeing. Proceeding with the script..." -ForegroundColor Green
# Make sure to install Az Module before running this script
# Install-Module Az
# Install-Module -Name AzureAD
#region Select Tenant / Subscription for deployment
$currentContext = az account show | ConvertFrom-Json
$currentTenant = $currentContext.tenantId
$currentSubscription = $currentContext.id
#Get TenantID if not set as argument
if(!($TenantID)) {
Get-AzTenant | Format-Table
if (!($TenantID = Read-Host "⌨ Type your TenantID or press Enter to accept your current one [$currentTenant]")) { $TenantID = $currentTenant }
}
else {
Write-Host "🔑 Tenant provided: $TenantID"
}
#Get Azure Subscription if not set as argument
if(!($AzureSubscriptionID)) {
Get-AzSubscription -TenantId $TenantID | Format-Table
if (!($AzureSubscriptionID = Read-Host "⌨ Type your SubscriptionID or press Enter to accept your current one [$currentSubscription]")) { $AzureSubscriptionID = $currentSubscription }
}
else {
Write-Host "🔑 Azure Subscription provided: $AzureSubscriptionID"
}
#Set the AZ Cli context
az account set -s $AzureSubscriptionID
Write-Host "🔑 Azure Subscription '$AzureSubscriptionID' selected."
#endregion
$ErrorActionPreference = "Stop"
$startTime = Get-Date
#region Select Tenant / Subscription for deployment
$currentContext = az account show | ConvertFrom-Json
$currentTenant = $currentContext.tenantId
$currentSubscription = $currentContext.id
#Get TenantID if not set as argument
if(!($TenantID)) {
Get-AzTenant | Format-Table
if (!($TenantID = Read-Host "⌨ Type your TenantID or press Enter to accept your current one [$currentTenant]")) { $TenantID = $currentTenant }
}
else {
Write-Host "🔑 Tenant provided: $TenantID"
}
#Get Azure Subscription if not set as argument
if(!($AzureSubscriptionID)) {
Get-AzSubscription -TenantId $TenantID | Format-Table
if (!($AzureSubscriptionID = Read-Host "⌨ Type your SubscriptionID or press Enter to accept your current one [$currentSubscription]")) { $AzureSubscriptionID = $currentSubscription }
}
else {
Write-Host "🔑 Azure Subscription provided: $AzureSubscriptionID"
}
#Set the AZ Cli context
az account set -s $AzureSubscriptionID
Write-Host "🔑 Azure Subscription '$AzureSubscriptionID' selected."
#endregion
#region Set up Variables and Default Parameters
if ($ResourceGroupForDeployment -eq "") {
$ResourceGroupForDeployment = $WebAppNamePrefix
}
if ($SQLServerName -eq "") {
$SQLServerName = $WebAppNamePrefix + "-sql"
}
if ($SQLDatabaseName -eq "") {
$SQLDatabaseName = $WebAppNamePrefix +"AMPSaaSDB"
}
if($KeyVault -eq "")
{
# User did not define KeyVault, so we will create one.
# We need to check if the KeyVault already exists or purge before going forward
$KeyVault=$WebAppNamePrefix+"-kv"
# Check if the KeyVault exists under resource group
$kv_check=$(az keyvault show -n $KeyVault -g $ResourceGroupForDeployment) 2>$null
# If KeyVault does not exist under resource group, then we need to check if it deleted KeyVault
if($kv_check -eq $null)
{
#region Check If KeyVault Exists
$KeyVaultApiUri="https://management.azure.com/subscriptions/$AzureSubscriptionID/providers/Microsoft.KeyVault/checkNameAvailability?api-version=2019-09-01"
$KeyVaultApiBody='{"name": "'+$KeyVault+'","type": "Microsoft.KeyVault/vaults"}'
$kv_check=az rest --method post --uri $KeyVaultApiUri --headers 'Content-Type=application/json' --body $KeyVaultApiBody | ConvertFrom-Json
if( $kv_check.reason -eq "AlreadyExists")
{
Write-Host ""
Write-Host "🛑 KeyVault name " -NoNewline -ForegroundColor Red
Write-Host "$KeyVault" -NoNewline -ForegroundColor Red -BackgroundColor Yellow
Write-Host " already exists." -ForegroundColor Red
Write-Host " To Purge KeyVault please use the following doc:"
Write-Host " https://learn.microsoft.com/en-us/cli/azure/keyvault?view=azure-cli-latest#az-keyvault-purge."
Write-Host " You could use new KeyVault name by using parameter" -NoNewline
Write-Host " -KeyVault" -ForegroundColor Green
exit 1
}
#endregion
}
}
$SaaSApiConfiguration_CodeHash= git log --format='%H' -1
$azCliOutput = if($Quiet){'none'} else {'json'}
#endregion
#region Validate Parameters
if($WebAppNamePrefix.Length -gt 21) {
Throw "🛑 Web name prefix must be less than 21 characters."
exit 1
}
if(!($KeyVault -match "^[a-zA-Z][a-z0-9-]+$")) {
Throw "🛑 KeyVault name only allows alphanumeric and hyphens, but cannot start with a number or special character."
exit 1
}
#endregion
#region pre-checks
# check if dotnet 8 is installed
$dotnetversion = dotnet --version
if(!$dotnetversion.StartsWith('8.')) {
Throw "🛑 Dotnet 8 not installed. Install dotnet8 and re-run the script."
Exit
}
#endregion
Write-Host "Starting SaaS Accelerator Deployment..."
#region Check If SQL Server Exist
$sql_exists = Get-AzureRmSqlServer -ServerName $SQLServerName -ResourceGroupName $ResourceGroupForDeployment -ErrorAction SilentlyContinue
if ($sql_exists)
{
Write-Host ""
Write-Host "🛑 SQl Server name " -NoNewline -ForegroundColor Red
Write-Host "$SQLServerName" -NoNewline -ForegroundColor Red -BackgroundColor Yellow
Write-Host " already exists." -ForegroundColor Red
Write-Host "Please delete existing instance or use new sql Instance name by using parameter" -NoNewline
Write-Host " -SQLServerName" -ForegroundColor Green
exit 1
}
#endregion
#region Dowloading assets if provided
# Download Publisher's PNG logo
if($LogoURLpng) {
Write-Host "📷 Logo image provided"
Write-Host " 🔵 Downloading Logo image file"
Invoke-WebRequest -Uri $LogoURLpng -OutFile "../src/CustomerSite/wwwroot/contoso-sales.png"
Invoke-WebRequest -Uri $LogoURLpng -OutFile "../src/AdminSite/wwwroot/contoso-sales.png"
Write-Host " 🔵 Logo image downloaded"
}
# Download Publisher's FAVICON logo
if($LogoURLico) {
Write-Host "📷 Logo icon provided"
Write-Host " 🔵 Downloading Logo icon file"
Invoke-WebRequest -Uri $LogoURLico -OutFile "../src/CustomerSite/wwwroot/favicon.ico"
Invoke-WebRequest -Uri $LogoURLico -OutFile "../src/AdminSite/wwwroot/favicon.ico"
Write-Host " 🔵 Logo icon downloaded"
}
#endregion
#region Create AAD App Registrations
#Record the current ADApps to reduce deployment instructions at the end
$ISLoginAppProvided = ($ADApplicationIDAdmin -ne "" -or $ADMTApplicationIDPortal -ne "")
if($ISLoginAppProvided){
Write-Host "🔑 Multi-Tenant App Registrations provided."
Write-Host " ➡️ Admin Portal App Registration ID:" $ADApplicationIDAdmin
Write-Host " ➡️ Landing Page App Registration ID:" $ADMTApplicationIDPortal
}
else {
Write-Host "🔑 Multi-Tenant App Registrations not provided."
}
if($IsAdminPortalMultiTenant -eq "true"){
Write-Host "🔑 Admin Portal App Registration set as Multi-Tenant."
$IsAdminPortalMultiTenant = $true
}
else {
Write-Host "🔑 Admin Portal App Registration set as Single-Tenant."
$IsAdminPortalMultiTenant = $false
}
#Create App Registration for authenticating calls to the Marketplace API
if (!($ADApplicationID)) {
Write-Host "🔑 Creating Fulfilment API App Registration"
try {
$ADApplication = az ad app create --only-show-errors --sign-in-audience AzureADMYOrg --display-name "$WebAppNamePrefix-FulfillmentAppReg" | ConvertFrom-Json
$ADObjectID = $ADApplication.id
$ADApplicationID = $ADApplication.appId
sleep 5 #this is to give time to AAD to register
# create service principal
az ad sp create --id $ADApplicationID
$ADApplicationSecret = az ad app credential reset --id $ADObjectID --append --display-name 'SaaSAPI' --years 2 --query password --only-show-errors --output tsv
Write-Host " 🔵 FulfilmentAPI App Registration created."
Write-Host " ➡️ Application ID:" $ADApplicationID
}
catch [System.Net.WebException],[System.IO.IOException] {
Write-Host "🚨🚨 $PSItem.Exception"
break;
}
}
#Create Multi-Tenant App Registration for Admin Portal User Login
if (!($ADApplicationIDAdmin)) {
Write-Host "🔑 Creating Admin Portal SSO App Registration"
try {
$appCreateRequestBodyJson = @"
{
"displayName" : "$WebAppNamePrefix-AdminPortalAppReg",
"api":
{
"requestedAccessTokenVersion" : 2
},
"signInAudience" : "AzureADMyOrg",
"web":
{
"redirectUris":
[
"https://$WebAppNamePrefix-admin.azurewebsites.net",
"https://$WebAppNamePrefix-admin.azurewebsites.net/",
"https://$WebAppNamePrefix-admin.azurewebsites.net/Home/Index",
"https://$WebAppNamePrefix-admin.azurewebsites.net/Home/Index/"
],
"logoutUrl": "https://$WebAppNamePrefix-admin.azurewebsites.net/logout",
"implicitGrantSettings":
{ "enableIdTokenIssuance" : true }
},
"requiredResourceAccess":
[{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess":
[{
"id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
"type": "Scope"
}]
}]
}
"@
if ($PsVersionTable.Platform -ne 'Unix') {
#On Windows, we need to escape quotes and remove new lines before sending the payload to az rest.
# See: https://github.com/Azure/azure-cli/blob/dev/doc/quoting-issues-with-powershell.md#double-quotes--are-lost
$appCreateRequestBodyJson = $appCreateRequestBodyJson.replace('"','\"').replace("`r`n","")
}
$adminPortalAppReg = $(az rest --method POST --headers "Content-Type=application/json" --uri https://graph.microsoft.com/v1.0/applications --body $appCreateRequestBodyJson ) | ConvertFrom-Json
$ADApplicationIDAdmin = $adminPortalAppReg.appId
$ADMTObjectIDAdmin = $adminPortalAppReg.id
Write-Host " 🔵 Admin Portal SSO App Registration created."
Write-Host " ➡️ Application Id: $ADApplicationIDAdmin"
# Download Publisher's AppRegistration logo
if($LogoURLpng) {
Write-Host " 🔵 Logo image provided. Setting the Application branding logo"
Write-Host " ➡️ Setting the Application branding logo"
$token=(az account get-access-token --resource "https://graph.microsoft.com" --query accessToken --output tsv)
$logoWeb = Invoke-WebRequest $LogoURLpng
$logoContentType = $logoWeb.Headers["Content-Type"]
$logoContent = $logoWeb.Content
$uploaded = Invoke-WebRequest `
-Uri "https://graph.microsoft.com/v1.0/applications/$ADMTObjectIDAdmin/logo" `
-Method "PUT" `
-Header @{"Authorization"="Bearer $token";"Content-Type"="$logoContentType";} `
-Body $logoContent
Write-Host " ➡️ Application branding logo set."
}
}
catch [System.Net.WebException],[System.IO.IOException] {
Write-Host "🚨🚨 $PSItem.Exception"
break;
}
}
#Create Multi-Tenant App Registration for Landing Page User Login
if (!($ADMTApplicationIDPortal)) {
Write-Host "🔑 Creating Landing Page SSO App Registration"
try {
$appCreateRequestBodyJson = @"
{
"displayName" : "$WebAppNamePrefix-LandingpageAppReg",
"api":
{
"requestedAccessTokenVersion" : 2
},
"signInAudience" : "AzureADandPersonalMicrosoftAccount",
"web":
{
"redirectUris":
[
"https://$WebAppNamePrefix-portal.azurewebsites.net",
"https://$WebAppNamePrefix-portal.azurewebsites.net/",
"https://$WebAppNamePrefix-portal.azurewebsites.net/Home/Index",
"https://$WebAppNamePrefix-portal.azurewebsites.net/Home/Index/"
],
"logoutUrl": "https://$WebAppNamePrefix-portal.azurewebsites.net/logout",
"implicitGrantSettings":
{ "enableIdTokenIssuance" : true }
},
"requiredResourceAccess":
[{
"resourceAppId": "00000003-0000-0000-c000-000000000000",
"resourceAccess":
[{
"id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
"type": "Scope"
}]
}]
}
"@
if ($PsVersionTable.Platform -ne 'Unix') {
#On Windows, we need to escape quotes and remove new lines before sending the payload to az rest.
# See: https://github.com/Azure/azure-cli/blob/dev/doc/quoting-issues-with-powershell.md#double-quotes--are-lost
$appCreateRequestBodyJson = $appCreateRequestBodyJson.replace('"','\"').replace("`r`n","")
}
$landingpageLoginAppReg = $(az rest --method POST --headers "Content-Type=application/json" --uri https://graph.microsoft.com/v1.0/applications --body $appCreateRequestBodyJson ) | ConvertFrom-Json
$ADMTApplicationIDPortal = $landingpageLoginAppReg.appId
$ADMTObjectIDPortal = $landingpageLoginAppReg.id
Write-Host " 🔵 Landing Page SSO App Registration created."
Write-Host " ➡️ Application Id: $ADMTApplicationIDPortal"
# Download Publisher's AppRegistration logo
if($LogoURLpng) {
Write-Host " 🔵 Logo image provided. Setting the Application branding logo"
Write-Host " ➡️ Setting the Application branding logo"
$token=(az account get-access-token --resource "https://graph.microsoft.com" --query accessToken --output tsv)
$logoWeb = Invoke-WebRequest $LogoURLpng
$logoContentType = $logoWeb.Headers["Content-Type"]
$logoContent = $logoWeb.Content
$uploaded = Invoke-WebRequest `
-Uri "https://graph.microsoft.com/v1.0/applications/$ADMTObjectIDPortal/logo" `
-Method "PUT" `
-Header @{"Authorization"="Bearer $token";"Content-Type"="$logoContentType";} `
-Body $logoContent
Write-Host " ➡️ Application branding logo set."
}
}
catch [System.Net.WebException],[System.IO.IOException] {
Write-Host "🚨🚨 $PSItem.Exception"
break;
}
}
#endregion
#region Prepare Code Packages
Write-host "📜 Prepare publish files for the application"
if (!(Test-Path '../Publish')) {
Write-host " 🔵 Preparing Admin Site"
dotnet publish ../src/AdminSite/AdminSite.csproj -c release -o ../Publish/AdminSite/ -v q
Write-host " 🔵 Preparing Metered Scheduler"
dotnet publish ../src/MeteredTriggerJob/MeteredTriggerJob.csproj -c release -o ../Publish/AdminSite/app_data/jobs/triggered/MeteredTriggerJob/ -v q --runtime win-x64 --self-contained true
Write-host " 🔵 Preparing Customer Site"
dotnet publish ../src/CustomerSite/CustomerSite.csproj -c release -o ../Publish/CustomerSite/ -v q
Write-host " 🔵 Zipping packages"
Compress-Archive -Path ../Publish/AdminSite/* -DestinationPath ../Publish/AdminSite.zip -Force
Compress-Archive -Path ../Publish/CustomerSite/* -DestinationPath ../Publish/CustomerSite.zip -Force
}
#endregion
#region Deploy Azure Resources Infrastructure
Write-host "☁ Deploy Azure Resources"
#Set-up resource name variables
$WebAppNameService=$WebAppNamePrefix+"-asp"
$WebAppNameAdmin=$WebAppNamePrefix+"-admin"
$WebAppNamePortal=$WebAppNamePrefix+"-portal"
$VnetName=$WebAppNamePrefix+"-vnet"
$privateSqlEndpointName=$WebAppNamePrefix+"-db-pe"
$privateKvEndpointName=$WebAppNamePrefix+"-kv-pe"
$privateSqlDnsZoneName="privatelink.database.windows.net"
$privateKvDnsZoneName="privatelink.vaultcore.windows.net"
$privateSqlLink =$WebAppNamePrefix+"-db-link"
$privateKvlink =$WebAppNamePrefix+"-kv-link"
$WebSubnetName="web"
$SqlSubnetName="sql"
$KvSubnetName="kv"
$DefaultSubnetName="default"
#keep the space at the end of the string - bug in az cli running on windows powershell truncates last char https://github.com/Azure/azure-cli/issues/10066
$ADApplicationSecretKeyVault="@Microsoft.KeyVault(VaultName=$KeyVault;SecretName=ADApplicationSecret) "
$DefaultConnectionKeyVault="@Microsoft.KeyVault(VaultName=$KeyVault;SecretName=DefaultConnection) "
$ServerUri = $SQLServerName+".database.windows.net"
$ServerUriPrivate = $SQLServerName+".privatelink.database.windows.net"
$Connection="Server=tcp:"+$ServerUriPrivate+";Database="+$SQLDatabaseName+";TrustServerCertificate=True;Authentication=Active Directory Managed Identity;"
Write-host " 🔵 Resource Group"
Write-host " ➡️ Create Resource Group"
az group create --location $Location --name $ResourceGroupForDeployment --output $azCliOutput
Write-host " ➡️ Create VNET and Subnet"
az network vnet create --resource-group $ResourceGroupForDeployment --name $VnetName --address-prefixes "10.0.0.0/20" --output $azCliOutput
az network vnet subnet create --resource-group $ResourceGroupForDeployment --vnet-name $VnetName -n $DefaultSubnetName --address-prefixes "10.0.0.0/24" --output $azCliOutput
az network vnet subnet create --resource-group $ResourceGroupForDeployment --vnet-name $VnetName -n $WebSubnetName --address-prefixes "10.0.1.0/24" --service-endpoints Microsoft.Sql Microsoft.KeyVault --delegations Microsoft.Web/serverfarms --output $azCliOutput
az network vnet subnet create --resource-group $ResourceGroupForDeployment --vnet-name $VnetName -n $SqlSubnetName --address-prefixes "10.0.2.0/24" --output $azCliOutput
az network vnet subnet create --resource-group $ResourceGroupForDeployment --vnet-name $VnetName -n $KvSubnetName --address-prefixes "10.0.3.0/24" --output $azCliOutput
Write-host " ➡️ Create Sql Server"
$userId = az ad signed-in-user show --query id -o tsv
$userdisplayname = az ad signed-in-user show --query displayName -o tsv
az sql server create --name $SQLServerName --resource-group $ResourceGroupForDeployment --location $Location --enable-ad-only-auth --external-admin-principal-type User --external-admin-name $userdisplayname --external-admin-sid $userId --output $azCliOutput
Write-host " ➡️ Set minimalTlsVersion to 1.2"
az sql server update --name $SQLServerName --resource-group $ResourceGroupForDeployment --set minimalTlsVersion="1.2"
Write-host " ➡️ Add SQL Server Firewall rules"
az sql server firewall-rule create --resource-group $ResourceGroupForDeployment --server $SQLServerName -n AllowAzureIP --start-ip-address "0.0.0.0" --end-ip-address "0.0.0.0" --output $azCliOutput
if ($env:ACC_CLOUD -eq $null){
Write-host " ➡️ Running in local environment - Add current IP to firewall"
$publicIp = (Invoke-WebRequest -uri "https://api.ipify.org").Content
az sql server firewall-rule create --resource-group $ResourceGroupForDeployment --server $SQLServerName -n AllowIP --start-ip-address "$publicIp" --end-ip-address "$publicIp" --output $azCliOutput
}
Write-host " ➡️ Create SQL DB"
az sql db create --resource-group $ResourceGroupForDeployment --server $SQLServerName --name $SQLDatabaseName --edition Standard --capacity 10 --zone-redundant false --output $azCliOutput
Write-host " 🔵 KeyVault"
Write-host " ➡️ Create KeyVault"
az keyvault create --name $KeyVault --resource-group $ResourceGroupForDeployment --enable-rbac-authorization false --output $azCliOutput
Write-host " ➡️ Add Secrets"
az keyvault secret set --vault-name $KeyVault --name ADApplicationSecret --value="$ADApplicationSecret" --output $azCliOutput
az keyvault secret set --vault-name $KeyVault --name DefaultConnection --value $Connection --output $azCliOutput
Write-host " ➡️ Update Firewall"
az keyvault update --name $KeyVault --resource-group $ResourceGroupForDeployment --default-action Deny --output $azCliOutput
az keyvault network-rule add --name $KeyVault --resource-group $ResourceGroupForDeployment --vnet-name $VnetName --subnet $WebSubnetName --output $azCliOutput
Write-host " 🔵 App Service Plan"
Write-host " ➡️ Create App Service Plan"
az appservice plan create -g $ResourceGroupForDeployment -n $WebAppNameService --sku B1 --output $azCliOutput
Write-host " 🔵 Admin Portal WebApp"
Write-host " ➡️ Create Web App"
az webapp create -g $ResourceGroupForDeployment -p $WebAppNameService -n $WebAppNameAdmin --runtime dotnet:8 --output $azCliOutput
Write-host " ➡️ Assign Identity"
$WebAppNameAdminId = az webapp identity assign -g $ResourceGroupForDeployment -n $WebAppNameAdmin --identities [system] --query principalId -o tsv
Write-host " ➡️ Setup access to KeyVault"
az keyvault set-policy --name $KeyVault --object-id $WebAppNameAdminId --secret-permissions get list --key-permissions get list --resource-group $ResourceGroupForDeployment --output $azCliOutput
Write-host " ➡️ Set Configuration"
az webapp config connection-string set -g $ResourceGroupForDeployment -n $WebAppNameAdmin -t SQLAzure --output $azCliOutput --settings DefaultConnection=$DefaultConnectionKeyVault
az webapp config appsettings set -g $ResourceGroupForDeployment -n $WebAppNameAdmin --output $azCliOutput --settings KnownUsers=$PublisherAdminUsers SaaSApiConfiguration__AdAuthenticationEndPoint=https://login.microsoftonline.com SaaSApiConfiguration__ClientId=$ADApplicationID SaaSApiConfiguration__ClientSecret=$ADApplicationSecretKeyVault SaaSApiConfiguration__FulFillmentAPIBaseURL=https://marketplaceapi.microsoft.com/api SaaSApiConfiguration__FulFillmentAPIVersion=2018-08-31 SaaSApiConfiguration__GrantType=client_credentials SaaSApiConfiguration__MTClientId=$ADApplicationIDAdmin SaaSApiConfiguration__IsAdminPortalMultiTenant=$IsAdminPortalMultiTenant SaaSApiConfiguration__Resource=20e940b3-4c77-4b0b-9a53-9e16a1b010a7 SaaSApiConfiguration__TenantId=$TenantID SaaSApiConfiguration__SignedOutRedirectUri=https://$WebAppNamePrefix-admin.azurewebsites.net/Home/Index/ SaaSApiConfiguration_CodeHash=$SaaSApiConfiguration_CodeHash
az webapp config set -g $ResourceGroupForDeployment -n $WebAppNameAdmin --always-on true --output $azCliOutput
Write-host " 🔵 Customer Portal WebApp"
Write-host " ➡️ Create Web App"
az webapp create -g $ResourceGroupForDeployment -p $WebAppNameService -n $WebAppNamePortal --runtime dotnet:8 --output $azCliOutput
Write-host " ➡️ Assign Identity"
$WebAppNamePortalId= az webapp identity assign -g $ResourceGroupForDeployment -n $WebAppNamePortal --identities [system] --query principalId -o tsv
Write-host " ➡️ Setup access to KeyVault"
az keyvault set-policy --name $KeyVault --object-id $WebAppNamePortalId --secret-permissions get list --key-permissions get list --resource-group $ResourceGroupForDeployment --output $azCliOutput
Write-host " ➡️ Set Configuration"
az webapp config connection-string set -g $ResourceGroupForDeployment -n $WebAppNamePortal -t SQLAzure --output $azCliOutput --settings DefaultConnection=$DefaultConnectionKeyVault
az webapp config appsettings set -g $ResourceGroupForDeployment -n $WebAppNamePortal --output $azCliOutput --settings SaaSApiConfiguration__AdAuthenticationEndPoint=https://login.microsoftonline.com SaaSApiConfiguration__ClientId=$ADApplicationID SaaSApiConfiguration__ClientSecret=$ADApplicationSecretKeyVault SaaSApiConfiguration__FulFillmentAPIBaseURL=https://marketplaceapi.microsoft.com/api SaaSApiConfiguration__FulFillmentAPIVersion=2018-08-31 SaaSApiConfiguration__GrantType=client_credentials SaaSApiConfiguration__MTClientId=$ADMTApplicationIDPortal SaaSApiConfiguration__Resource=20e940b3-4c77-4b0b-9a53-9e16a1b010a7 SaaSApiConfiguration__TenantId=$TenantID SaaSApiConfiguration__SignedOutRedirectUri=https://$WebAppNamePrefix-portal.azurewebsites.net/Home/Index/ SaaSApiConfiguration_CodeHash=$SaaSApiConfiguration_CodeHash
az webapp config set -g $ResourceGroupForDeployment -n $WebAppNamePortal --always-on true --output $azCliOutput
#endregion
#region Deploy Code
Write-host "📜 Deploy Code"
Write-host " 🔵 Deploy Database"
Write-host " ➡️ Generate SQL schema/data script"
Set-Content -Path ../src/AdminSite/appsettings.Development.json -value "{`"ConnectionStrings`": {`"DefaultConnection`":`"$Connection`"}}"
dotnet-ef migrations script --output script.sql --idempotent --context SaaSKitContext --project ../src/DataAccess/DataAccess.csproj --startup-project ../src/AdminSite/AdminSite.csproj
Write-host " ➡️ Execute SQL schema/data script"
$dbaccesstoken = (Get-AzAccessToken -ResourceUrl https://database.windows.net).Token
Invoke-Sqlcmd -InputFile ./script.sql -ServerInstance $ServerUri -database $SQLDatabaseName -AccessToken $dbaccesstoken
Write-host " ➡️ Execute SQL script to Add WebApps"
$AddAppsIdsToDB = "CREATE USER [$WebAppNameAdmin] FROM EXTERNAL PROVIDER;ALTER ROLE db_datareader ADD MEMBER [$WebAppNameAdmin];ALTER ROLE db_datawriter ADD MEMBER [$WebAppNameAdmin]; GRANT EXEC TO [$WebAppNameAdmin]; CREATE USER [$WebAppNamePortal] FROM EXTERNAL PROVIDER;ALTER ROLE db_datareader ADD MEMBER [$WebAppNamePortal];ALTER ROLE db_datawriter ADD MEMBER [$WebAppNamePortal]; GRANT EXEC TO [$WebAppNamePortal];"
Invoke-Sqlcmd -Query $AddAppsIdsToDB -ServerInstance $ServerUri -database $SQLDatabaseName -AccessToken $dbaccesstoken
Write-host " 🔵 Deploy Code to Admin Portal"
az webapp deploy --resource-group $ResourceGroupForDeployment --name $WebAppNameAdmin --src-path "../Publish/AdminSite.zip" --type zip --output $azCliOutput
Write-host " 🔵 Deploy Code to Customer Portal"
az webapp deploy --resource-group $ResourceGroupForDeployment --name $WebAppNamePortal --src-path "../Publish/CustomerSite.zip" --type zip --output $azCliOutput
Write-host " 🔵 Update Firewall for WebApps and SQL"
az webapp vnet-integration add --resource-group $ResourceGroupForDeployment --name $WebAppNamePortal --vnet $VnetName --subnet $WebSubnetName --output $azCliOutput
az webapp vnet-integration add --resource-group $ResourceGroupForDeployment --name $WebAppNameAdmin --vnet $VnetName --subnet $WebSubnetName --output $azCliOutput
az sql server vnet-rule create --name $WebAppNamePrefix-vnet --resource-group $ResourceGroupForDeployment --server $SQLServerName --vnet-name $VnetName --subnet $WebSubnetName --output $azCliOutput
Write-host " 🔵 Clean up"
Remove-Item -Path ../src/AdminSite/appsettings.Development.json
Remove-Item -Path script.sql
#Remove-Item -Path ../Publish -recurse -Force
#endregion
#region Create SQL Private Endpoints
# Get SQL Server
$sqlServerId=az sql server show --name $SQLServerName --resource-group $ResourceGroupForDeployment --query id -o tsv
# Create a private endpoint
az network private-endpoint create --name $privateSqlEndpointName --resource-group $ResourceGroupForDeployment --vnet-name $vnetName --subnet $SqlSubnetName --private-connection-resource-id $sqlServerId --group-ids sqlServer --connection-name sqlConnection
# Create a SQL private DNS zone
az network private-dns zone create --name $privateSqlDnsZoneName --resource-group $ResourceGroupForDeployment
# Link the SQL private DNS zone to the VNet
az network private-dns link vnet create --name $privateSqlLink --resource-group $ResourceGroupForDeployment --virtual-network $vnetName --zone-name $privateSqlDnsZoneName --registration-enabled false
az network private-endpoint dns-zone-group create --resource-group $ResourceGroupForDeployment --endpoint-name $privateSqlEndpointName --name "sql-zone-group" --private-dns-zone $privateSqlDnsZoneName --zone-name "sqlserver"
#endregion
#region Create KV Private Endpoints
# Get KV Server
$keyVaultId=az keyvault show --name $KeyVault --resource-group $ResourceGroupForDeployment --query id -o tsv
# Create a KV private endpoint
az network private-endpoint create --name $privateKvEndpointName --resource-group $ResourceGroupForDeployment --vnet-name $vnetName --subnet $KvSubnetName --private-connection-resource-id $keyVaultId --group-ids vault --connection-name kvConnection
# Create a KV private DNS zone
az network private-dns zone create --name $privateKvDnsZoneName --resource-group $ResourceGroupForDeployment
# Link the KV private DNS zone to the VNet
az network private-dns link vnet create --name $privateKvLink --resource-group $ResourceGroupForDeployment --virtual-network $vnetName --zone-name $privateKvDnsZoneName --registration-enabled false
az network private-endpoint dns-zone-group create --resource-group $ResourceGroupForDeployment --endpoint-name $privateKvEndpointName --name "Kv-zone-group" --private-dns-zone $privateKvDnsZoneName --zone-name "Kv-zone"
#endregion
#region Present Output
Write-host "✅ If the intallation completed without error complete the folllowing checklist:"
if ($ISLoginAppProvided) { #If provided then show the user where to add the landing page in AAD, otherwise script did this already for the user.
Write-host " 🔵 Add The following URLs to the multi-tenant Landing Page AAD App Registration in Azure Portal:"
Write-host " ➡️ https://$WebAppNamePrefix-portal.azurewebsites.net"
Write-host " ➡️ https://$WebAppNamePrefix-portal.azurewebsites.net/"
Write-host " ➡️ https://$WebAppNamePrefix-portal.azurewebsites.net/Home/Index"
Write-host " ➡️ https://$WebAppNamePrefix-portal.azurewebsites.net/Home/Index/"
Write-host " 🔵 Add The following URLs to the multi-tenant Admin Portal AAD App Registration in Azure Portal:"
Write-host " ➡️ https://$WebAppNamePrefix-admin.azurewebsites.net"
Write-host " ➡️ https://$WebAppNamePrefix-admin.azurewebsites.net/"
Write-host " ➡️ https://$WebAppNamePrefix-admin.azurewebsites.net/Home/Index"
Write-host " ➡️ https://$WebAppNamePrefix-admin.azurewebsites.net/Home/Index/"
Write-host " 🔵 Verify ID Tokens checkbox has been checked-out ?"
}
Write-host " 🔵 Add The following URL in PartnerCenter SaaS Technical Configuration"
Write-host " ➡️ Landing Page section: https://$WebAppNamePrefix-portal.azurewebsites.net/"
Write-host " ➡️ Connection Webhook section: https://$WebAppNamePrefix-portal.azurewebsites.net/api/AzureWebhook"
Write-host " ➡️ Tenant ID: $TenantID"
Write-host " ➡️ AAD Application ID section: $ADApplicationID"
$duration = (Get-Date) - $startTime
Write-Host "Deployment Complete in $($duration.Minutes)m:$($duration.Seconds)s"
Write-Host "DO NOT CLOSE THIS SCREEN. Please make sure you copy or perform the actions above before closing."
#endregion