├── .gitattributes
├── .gitignore
├── README.md
├── ase-agent
├── DSC
│ ├── ConfigureASEBuildAgent.ps1
│ └── ConfigureASEBuildAgent.ps1.zip
├── README.md
└── azuredeploy.json
├── ase-devops
├── README.md
├── ase_devops.png
└── azuredeploy.json
├── ase
├── README.md
├── azuredeploy.json
└── nested-templates
│ └── ase-ilb-certificate.json
├── core-network
├── DSC
│ ├── ConfigureADBDC.ps1
│ ├── ConfigureADBDC.ps1.zip
│ ├── CreateADPDC.ps1
│ └── CreateADPDC.ps1.zip
├── README.md
├── azuredeploy.json
├── core-network.png
└── nested-templates
│ └── update-vnet-dns.json
├── devnet-tfs-ha
├── README.md
├── azuredeploy.json
└── private_ha_devops.png
├── devnet-tfs
├── README.md
├── azuredeploy.json
└── private_devops.png
├── gadgetron-workstation
├── README.md
├── azuredeploy.json
└── scripts
│ ├── bootstrap_gadgetron.sh
│ └── setup_gadgetron_workstation.sh
├── iaas-web
├── DSC
│ ├── ConfigureWeb.ps1
│ └── ConfigureWeb.ps1.zip
├── README.md
└── azuredeploy.json
├── primitives
├── encryptvm.json
├── functionapp.json
├── linuxvm.json
├── publicip.json
├── vnet.json
└── windowsvm.json
├── scripts
├── CreateKeyVault.ps1
├── PrepareAseDeployment.ps1
└── PrepareDevnetTfsDeployment.ps1
├── sql-alwayson
├── DSC
│ ├── AddDatabaseAG.ps1
│ ├── AddDatabaseAG.ps1.zip
│ ├── PrepareSQLServer.ps1
│ └── PrepareSQLServer.ps1.zip
├── README.md
├── adddbtoag.json
└── azuredeploy.json
├── tfs-ha
├── DSC
│ ├── InstallTFS.ps1
│ ├── InstallTFS.ps1.zip
│ ├── InstallVSTSAgent.ps1
│ └── InstallVSTSAgent.ps1.zip
├── README.md
├── azuredeploy.json
└── deployagents.json
└── tfs
├── DSC
├── TFSSQLServerConfig.ps1
└── TFSSQLServerConfig.ps1.zip
├── README.md
└── azuredeploy.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Infrastructure as Code (IaC)
2 | ----------------------------
3 |
4 | This repository contains a number of templates for deploying infrastructure in Microsoft [Azure](https://azure.microsoft.com/en-us/).
5 |
6 | * [core-network](core-network/) is a basic virtual network with two domain controllers in an availability set and a jump box for connecting to the VNET.
7 | * [tfs](tfs/) is a template for deploying [Team Foundation Server](https://www.visualstudio.com/tfs/) (TFS) into an existing virtual network with domain controllers already deployed.
8 | * [devnet-tfs](devnet-tfs) is a combination of core-network and tfs mentioned above. First the core network is deployed and then the tfs server with database and build agent.
9 | * [sql-alwayson](sql-alwayson) is a template automation for deploying SQL Server (2016 or 2017) with "Always On", high-availability configuration configuration.
10 | * [tfs-ha] is a template for deploying a high availability version of [Team Foundation Server](https://www.visualstudio.com/tfs/) (TFS). It is recommended to use the [SQL Always On](sql-alwayson) configuration on the backend for a true high availability configuration. * [devnet-tfs-ha](devnet-tfs-ha) is a combination of [core-network](core-network), [sql-alwayson](sql-alwayson), and [tfs-ha](tfs-ha) mentioned above (including build agents) for a complete end-to-end high-availability DevOps environment base on TFS.
11 | * [ase](ase/) is a template for deploy Azure App Service Environment (ASE) into an existing or new Virtual Network.
12 | * [ase-agent](ase-agent/) is a template for deploying a VSTS/TFS build agent into a Virtual Network and let it deploy to a Web App in an App Service Environment (ASE).
13 | * [ase-devops](ase-devops/) is a combination of [ase](ase/) and [ase-agent](ase-agent/) to demonstrate the complete ASE VSTS/TFS DevOps experience.
14 |
--------------------------------------------------------------------------------
/ase-agent/DSC/ConfigureASEBuildAgent.ps1:
--------------------------------------------------------------------------------
1 | configuration ConfigureASEBuildAgentDsc
2 | {
3 | param
4 | (
5 | [Parameter(Mandatory)]
6 | [String]$TSUrl,
7 |
8 | [Parameter(Mandatory)]
9 | [String]$AgentPool,
10 |
11 | [Parameter(Mandatory)]
12 | [String]$PAToken,
13 |
14 | [Parameter(Mandatory)]
15 | [String]$AseIp,
16 |
17 | [Parameter(Mandatory)]
18 | [String]$AppDns,
19 |
20 | [Parameter(Mandatory=$false)]
21 | [String]$VSTSAgentUrl = "https://vstsagentpackage.azureedge.net/agent/2.127.0/vsts-agent-win-x64-2.127.0.zip"
22 | )
23 |
24 | Import-DscResource -ModuleName xNetworking, 'PSDesiredStateConfiguration'
25 |
26 | $tmp = $AppDns.Split('.')
27 | $OFS='.'
28 | $AppScmDns = $tmp[0] + ".scm." + [String]$tmp[1..$tmp.Count]
29 |
30 | Node localhost
31 | {
32 | Script DownloadAgent
33 | {
34 | GetScript = {
35 | return @{ 'Result' = $true }
36 | }
37 | SetScript = {
38 | $agentUrl = $using:VSTSAgentUrl
39 | $agentZip = "$env:TEMP" + "\vsts_agent.zip"
40 | Write-Host "Downloading TFS: $agentUrl"
41 | Invoke-WebRequest -Uri $agentUrl -OutFile $agentZip
42 | }
43 | TestScript = {
44 | $agentZip = "$env:TEMP" + "\vsts_agent.zip"
45 | Test-Path $agentZip
46 | }
47 | }
48 |
49 |
50 | Script UnzipAgent
51 | {
52 | GetScript = {
53 | return @{ 'Result' = $true }
54 | }
55 | SetScript = {
56 | $agentZip = "$env:TEMP" + "\vsts_agent.zip"
57 | $agentPath = "C:\agent"
58 | If(!(Test-Path $agentPath))
59 | {
60 | New-Item -ItemType Directory -Force -Path $agentPath
61 | }
62 |
63 | $shell = New-Object -com shell.application
64 | $zip = $shell.NameSpace($agentZip)
65 | Foreach($item in $zip.items())
66 | {
67 | $shell.Namespace($agentPath).copyhere($item)
68 | }
69 | }
70 | TestScript = {
71 | Test-Path "C:\agent\config.cmd"
72 | }
73 | DependsOn = "[Script]DownloadAgent"
74 | }
75 |
76 |
77 | Script ConfigAgent
78 | {
79 | GetScript = { return @{ 'Result' = $true }}
80 | SetScript = {
81 | Set-Location "C:\agent"
82 | $tsurl = $using:TSUrl
83 | $tok = $using:PAToken
84 | $pool = $using:AgentPool
85 | $cmd = ".\config.cmd --unattended --runAsService --work _work --url $tsurl --auth pat --token $tok --pool $pool --agent $env:COMPUTERNAME"
86 | Invoke-Expression $cmd | Write-Verbose
87 | }
88 | TestScript = {
89 | Test-Path "C:\agent\.credentials"
90 | }
91 | DependsOn = "[Script]UnzipAgent"
92 | }
93 |
94 | xHostsFile HostEntry
95 | {
96 | HostName = $AppDns
97 | IPAddress = $AseIp
98 | Ensure = 'Present'
99 | }
100 |
101 | xHostsFile HostScmEntry
102 | {
103 | HostName = $AppScmDns
104 | IPAddress = $AseIp
105 | Ensure = 'Present'
106 | }
107 |
108 | Registry StrongCrypto1
109 | {
110 | Ensure = "Present"
111 | Key = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319"
112 | ValueName = "SchUseStrongCrypto"
113 | ValueType = "Dword"
114 | ValueData = "00000001"
115 | }
116 |
117 | Registry StrongCrypto2
118 | {
119 | Ensure = "Present"
120 | Key = "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319"
121 | ValueName = "SchUseStrongCrypto"
122 | ValueType = "Dword"
123 | ValueData = "00000001"
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/ase-agent/DSC/ConfigureASEBuildAgent.ps1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hansenms/iac/277b41b9f9a0ce395f577d8a74640000c91c5ce1/ase-agent/DSC/ConfigureASEBuildAgent.ps1.zip
--------------------------------------------------------------------------------
/ase-agent/README.md:
--------------------------------------------------------------------------------
1 | VSTS Build Agent in Private Virtual Network for ASE
2 | ---------------------------------------------------
3 |
4 | The Azure [App Service Environment (ASE)](https://docs.microsoft.com/en-us/azure/app-service/environment/intro) allows you to deploy Azure Web Apps into a private environment for enhanced security and access control. One challenge with this configuration is how to orchestrate Continuous Integration and Continuous Deployment (CI/CD) with [Visual Studio Team Services](https://www.visualstudio.com/team-services/) or [Team Foundation Server](https://www.visualstudio.com/tfs/) into such environments.
5 |
6 | This template deploys a VSTS/TFS build agent into the Virtual Network where the ASE is deployed and connects this agent to a VSTS or TFS instance. It also adds appropriate `hosts` file entries to the agent to allow it to deploy to a specific Web App in an ASE.
7 |
8 | To ensure that the configuration of the agen is correct, you need to supply:
9 |
10 | * `TSServerUrl`: Url of your VSTS/TFS instance
11 | * `AgentPool`: Name of the agent pool in the VSTS/TFS instance (needs to be created in advance)
12 | * `PAToken`: Personal Access Token for agent to register with the VSTS/TFS instance
13 | * `AseIp`: The IP address of the ASE environment.
14 | * `AppDns`: The DNS name of the app, e.g. myapp.contoso-internal.us
15 |
16 |
17 |
18 |
19 |
20 |
21 |
23 |
--------------------------------------------------------------------------------
/ase-agent/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "subnetId": {
6 | "type": "string"
7 | },
8 | "vmSize": {
9 | "type": "string",
10 | "allowedValues": [
11 | "Standard_DS1_v2",
12 | "Standard_DS2_v2",
13 | "Standard_DS3_v2",
14 | "Standard_DS4_v2",
15 | "Standard_DS5_v2",
16 | "Standard_DS11_v2",
17 | "Standard_DS12_v2",
18 | "Standard_DS13_v2",
19 | "Standard_DS14_v2",
20 | "Standard_DS15_v2"
21 | ],
22 | "defaultValue": "Standard_DS2_v2"
23 | },
24 | "adminUsername": {
25 | "type": "string",
26 | "defaultValue": "EnterpriseAdmin"
27 | },
28 | "adminPassword": {
29 | "type": "securestring"
30 | },
31 | "vmName": {
32 | "type": "string",
33 | "defaultValue": "ase-build-agent"
34 | },
35 | "ImagePublisher": {
36 | "type": "string",
37 | "defaultValue": "MicrosoftVisualStudio"
38 | },
39 | "ImageOffer": {
40 | "type": "string",
41 | "defaultValue": "VisualStudio"
42 | },
43 | "ImageSku": {
44 | "type": "string",
45 | "defaultValue": "VS-2017-Ent-Latest-WS2016"
46 | },
47 | "TSServerUrl": {
48 | "type": "string"
49 | },
50 | "AgentPool": {
51 | "type": "string"
52 | },
53 | "PAToken": {
54 | "type": "string"
55 | },
56 | "AseIp": {
57 | "type": "string"
58 | },
59 | "AppDns": {
60 | "type": "string"
61 | }
62 | },
63 | "variables": {
64 | "baseUri": "[deployment().properties.templateLink.uri]",
65 | "windowsVmTemplateURL": "[uri(variables('baseUri'),'../primitives/windowsvm.json')]",
66 | "AgentConfigureModuleURL": "[uri(variables('baseUri'), 'DSC/ConfigureASEBuildAgent.ps1.zip')]",
67 | "AgentConfigureFunction": "ConfigureASEBuildAgent.ps1\\ConfigureASEBuildAgentDsc",
68 | "agentDownloadUrl": "https://vstsagentpackage.azureedge.net/agent/2.127.0/vsts-agent-win-x64-2.127.0.zip",
69 |
70 | },
71 | "resources": [
72 | {
73 | "name": "AgentVM",
74 | "type": "Microsoft.Resources/deployments",
75 | "apiVersion": "2017-05-10",
76 | "properties": {
77 | "mode": "Incremental",
78 | "templateLink": {
79 | "uri": "[variables('windowsVmTemplateURL')]",
80 | "contentVersion": "1.0.0.0"
81 | },
82 | "parameters": {
83 | "vmName": {
84 | "value": "[parameters('vmName')]"
85 | },
86 | "vmSize": {
87 | "value": "[parameters('vmSize')]"
88 | },
89 | "subnetId": {
90 | "value": "[parameters('subnetId')]"
91 | },
92 | "adminUsername": {
93 | "value": "[parameters('adminUsername')]"
94 | },
95 | "adminPassword": {
96 | "value": "[parameters('adminPassword')]"
97 | },
98 | "assignPublicIP": {
99 | "value": false
100 | },
101 | "imagePublisher": {
102 | "value": "[parameters('ImagePublisher')]"
103 | },
104 | "imageOffer": {
105 | "value": "[parameters('ImageOffer')]"
106 | },
107 | "imageSku": {
108 | "value": "[parameters('ImageSku')]"
109 | }
110 | }
111 | }
112 | },
113 | {
114 | "type": "Microsoft.Compute/virtualMachines/extensions",
115 | "name": "[concat(parameters('vmName'), '/configureagent')]",
116 | "dependsOn": [
117 | "[resourceId('Microsoft.Resources/deployments', 'AgentVM')]"
118 | ],
119 | "apiVersion": "2016-03-30",
120 | "location": "[resourceGroup().location]",
121 | "properties": {
122 | "publisher": "Microsoft.Powershell",
123 | "type": "DSC",
124 | "typeHandlerVersion": "2.21",
125 | "autoUpgradeMinorVersion": true,
126 | "settings": {
127 | "modulesURL": "[variables('AgentConfigureModuleURL')]",
128 | "configurationFunction": "[variables('AgentConfigureFunction')]",
129 | "properties": {
130 | "TSUrl": "[parameters('TSServerUrl')]",
131 | "VSTSAgentUrl": "[variables('agentDownloadUrl')]",
132 | "AgentPool": "[parameters('AgentPool')]",
133 | "PAToken": "[parameters('PAToken')]",
134 | "AseIp": "[parameters('AseIp')]",
135 | "AppDns": "[parameters('AppDns')]"
136 | }
137 | }
138 | }
139 | }
140 | ]
141 | }
--------------------------------------------------------------------------------
/ase-devops/README.md:
--------------------------------------------------------------------------------
1 | DevOps Enabled App Service Environment
2 | --------------------------------------
3 |
4 | The Azure [App Service Environment (ASE)](https://docs.microsoft.com/en-us/azure/app-service/environment/intro) allows you to deploy Azure Web Apps into a private environment for enhanced security and access control. One challenge with this configuration is how to orchestrate Continuous Integration and Continuous Deployment (CI/CD) with [Visual Studio Team Services](https://www.visualstudio.com/team-services/) or [Team Foundation Server](https://www.visualstudio.com/tfs/) into such environments.
5 |
6 | The pattern in this template deployment illustrates how to combine ASE with CI/CD using VSTS or TFS. It uses the [ase](../ase) template to establish an App Service Environment with a Web App. It also uses the [ase-agent](../ase-agent) template to deploy a VSTS/TFS agent in the Virtual Network and connect this agent with a VSTS or TFS instance. Finally, a jump-box is also deploy in this case to allow access to the Virtual Network for testing purposes.
7 |
8 | The final deployment looks like this:
9 |
10 | 
11 |
12 | A number of parameters (including SSL certificate) are needed for proper deployment of this pattern. To assist with preparing these parameters, there is a [convenience script](https://github.com/hansenms/iac/blob/master/scripts/PrepareAseDeployment.ps1). You need an SSL certificate to support the DNS names `*.domainname` and `*.scm.domainname`, if you don't have one, the script with create a self-signed certificate for you. You can call the script with:
13 |
14 | ```
15 | iac\scripts\PrepareAseDeployment.ps1 -DomainName mydomain-internal.us `
16 | -TSServerUrl "https://.visualstudio.com" -AdminUsername EnterpriseAdmin`
17 | -AgentPool -PAToken `
18 | -OutFile C:\temp\myase-devops.parameters.json
19 | ```
20 |
21 | The `AgentPool` and `PAToken` parameters need to be established in your VSTS or TFS instance in advance. The script will save all the parameters in a template parameter json file that you can copy/paste from or use directly with CLI or Powershell commands to deploy the template.
22 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
--------------------------------------------------------------------------------
/ase-devops/ase_devops.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hansenms/iac/277b41b9f9a0ce395f577d8a74640000c91c5ce1/ase-devops/ase_devops.png
--------------------------------------------------------------------------------
/ase-devops/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "jumpvmSize": {
6 | "type": "string",
7 | "allowedValues": [
8 | "Standard_DS1_v2",
9 | "Standard_DS2_v2",
10 | "Standard_DS3_v2",
11 | "Standard_DS4_v2",
12 | "Standard_DS5_v2",
13 | "Standard_DS11_v2",
14 | "Standard_DS12_v2",
15 | "Standard_DS13_v2",
16 | "Standard_DS14_v2",
17 | "Standard_DS15_v2"
18 | ],
19 | "defaultValue": "Standard_DS2_v2"
20 | },
21 | "agentVmSize": {
22 | "type": "string",
23 | "allowedValues": [
24 | "Standard_DS1_v2",
25 | "Standard_DS2_v2",
26 | "Standard_DS3_v2",
27 | "Standard_DS4_v2",
28 | "Standard_DS5_v2",
29 | "Standard_DS11_v2",
30 | "Standard_DS12_v2",
31 | "Standard_DS13_v2",
32 | "Standard_DS14_v2",
33 | "Standard_DS15_v2"
34 | ],
35 | "defaultValue": "Standard_DS2_v2"
36 | },
37 | "adminUsername": {
38 | "type": "string",
39 | "defaultValue": "EnterpriseAdmin"
40 | },
41 | "adminPassword": {
42 | "type": "securestring"
43 | },
44 | "domainName": {
45 | "type": "string",
46 | "defaultValue": "contoso-internal.us"
47 | },
48 | "aseName": {
49 | "type": "string",
50 | "defaultValue": "GENERATE"
51 | },
52 | "siteName": {
53 | "type": "string",
54 | "defaultValue": "ase-site"
55 | },
56 | "TSServerUrl": {
57 | "type": "string"
58 | },
59 | "AgentPool": {
60 | "type": "string"
61 | },
62 | "PAToken": {
63 | "type": "string"
64 | },
65 | "pfxBlobString": {
66 | "type": "string"
67 | },
68 | "certificatePassword": {
69 | "type": "securestring"
70 | },
71 | "certificateThumbprint": {
72 | "type": "string"
73 | }
74 | },
75 | "variables": {
76 | "baseUri": "[deployment().properties.templateLink.uri]",
77 | "windowsVmTemplateURL": "[uri(variables('baseUri'),'../primitives/windowsvm.json')]",
78 | "agentTemplateURL": "[uri(variables('baseUri'),'../ase-agent/azuredeploy.json')]",
79 | "aseTemplateURL": "[uri(variables('baseUri'),'../ase/azuredeploy.json')]",
80 | "vnetName": "asevnet",
81 | "subnetName1": "subnet-1",
82 | "subnetName2": "subnet-2",
83 | "subnetId1": "[concat(resourceId('Microsoft.Network/virtualNetworks', variables('vnetName')), '/subnets/', variables('subnetName1'))]",
84 | "subnetId2": "[concat(resourceId('Microsoft.Network/virtualNetworks', variables('vnetName')), '/subnets/', variables('subnetName2'))]",
85 | "jumpVmName": "JumpBox",
86 | "buildAgentVmName": "build-agent",
87 | "AppDns": "[concat(parameters('siteName'),'.',parameters('domainName'))]",
88 | "AseIp": "10.0.1.11"
89 | },
90 | "resources": [
91 | {
92 | "name": "[variables('vnetName')]",
93 | "type": "Microsoft.Network/virtualNetworks",
94 | "location": "[resourceGroup().location]",
95 | "apiVersion": "2016-03-30",
96 | "dependsOn": [],
97 | "properties": {
98 | "addressSpace": {
99 | "addressPrefixes": [
100 | "10.0.0.0/16"
101 | ]
102 | },
103 | "subnets": [
104 | {
105 | "name": "[variables('subnetName1')]",
106 | "properties": {
107 | "addressPrefix": "10.0.0.0/24"
108 | }
109 | },
110 | {
111 | "name": "[variables('subnetName2')]",
112 | "properties": {
113 | "addressPrefix": "10.0.1.0/25"
114 | }
115 | }
116 | ]
117 | }
118 | },
119 | {
120 | "name": "[concat('jump',resourceGroup().name)]",
121 | "type": "Microsoft.Resources/deployments",
122 | "apiVersion": "2017-05-10",
123 | "dependsOn": [
124 | "[resourceId('Microsoft.Network/virtualNetworks',variables('vnetName'))]"
125 | ],
126 | "properties": {
127 | "mode": "Incremental",
128 | "templateLink": {
129 | "uri": "[variables('windowsVmTemplateURL')]",
130 | "contentVersion": "1.0.0.0"
131 | },
132 | "parameters": {
133 | "vmName": {
134 | "value": "[variables('jumpVmName')]"
135 | },
136 | "vmSize": {
137 | "value": "[parameters('jumpVmSize')]"
138 | },
139 | "subnetId": {
140 | "value": "[variables('subnetId1')]"
141 | },
142 | "adminUsername": {
143 | "value": "[parameters('adminUsername')]"
144 | },
145 | "adminPassword": {
146 | "value": "[parameters('adminPassword')]"
147 | },
148 | "assignPublicIP": {
149 | "value": true
150 | }
151 | }
152 | }
153 | },
154 | {
155 | "name": "[concat('agent',resourceGroup().name)]",
156 | "type": "Microsoft.Resources/deployments",
157 | "apiVersion": "2017-05-10",
158 | "dependsOn": [
159 | "[resourceId('Microsoft.Network/virtualNetworks',variables('vnetName'))]"
160 | ],
161 | "properties": {
162 | "mode": "Incremental",
163 | "templateLink": {
164 | "uri": "[variables('agentTemplateURL')]",
165 | "contentVersion": "1.0.0.0"
166 | },
167 | "parameters": {
168 | "TSServerUrl": {
169 | "value": "[parameters('TSServerUrl')]"
170 | },
171 | "AgentPool": {
172 | "value": "[parameters('AgentPool')]"
173 | },
174 | "PAToken": {
175 | "value": "[parameters('PAToken')]"
176 | },
177 | "AseIp": {
178 | "value": "[variables('AseIp')]"
179 | },
180 | "AppDns": {
181 | "value": "[variables('AppDns')]"
182 | },
183 | "vmName": {
184 | "value": "[variables('buildAgentVmName')]"
185 | },
186 | "vmSize": {
187 | "value": "[parameters('agentVmSize')]"
188 | },
189 | "subnetId": {
190 | "value": "[variables('subnetId1')]"
191 | },
192 | "adminUsername": {
193 | "value": "[parameters('adminUsername')]"
194 | },
195 | "adminPassword": {
196 | "value": "[parameters('adminPassword')]"
197 | }
198 | }
199 | }
200 | },
201 | {
202 | "name": "[concat('ase',resourceGroup().name)]",
203 | "type": "Microsoft.Resources/deployments",
204 | "apiVersion": "2017-05-10",
205 | "dependsOn": [
206 | "[resourceId('Microsoft.Network/virtualNetworks',variables('vnetName'))]"
207 | ],
208 | "properties": {
209 | "mode": "Incremental",
210 | "templateLink": {
211 | "uri": "[variables('aseTemplateURL')]",
212 | "contentVersion": "1.0.0.0"
213 | },
214 | "parameters": {
215 | "subnetId": {
216 | "value": "[variables('subnetId2')]"
217 | },
218 | "domainName": {
219 | "value": "[parameters('domainName')]"
220 | },
221 | "aseName": {
222 | "value": "[parameters('aseName')]"
223 | },
224 | "aseSiteName": {
225 | "value": "[parameters('siteName')]"
226 | },
227 | "pfxBlobString": {
228 | "value": "[parameters('pfxBlobString')]"
229 | },
230 | "certificatePassword": {
231 | "value": "[parameters('certificatePassword')]"
232 | },
233 | "certificateThumbprint": {
234 | "value": "[parameters('certificateThumbprint')]"
235 | }
236 | }
237 | }
238 | }
239 | ]
240 | }
--------------------------------------------------------------------------------
/ase/README.md:
--------------------------------------------------------------------------------
1 | App Service Environment with Website
2 | ------------------------------------
3 |
4 | This template illustrates how to deploy [Azure App Service Environment](https://docs.microsoft.com/en-us/azure/app-service/environment/intro) a.k.a. ASEv2 with ILB configuration.
5 |
6 | The template allows you to supply an existing Subnet for ASE deployment (there should be no resources in this Subnet). If you do not supply a Subnet, i.e. leave that parameter as `null`, a new Virtual Network will be created and the ASE deployed in it.
7 |
8 | The ASE needs a certificate, which supports the `*.domainname`, and `*.scm.domainname` DNS names. If you do not have a certificate, you can generate one with the `New-SelfSignedCertificate` command. The repository also includes a [convenience script](https://github.com/hansenms/iac/blob/master/scripts/PrepareAseDeployment.ps1), that prepares the details needed for this deployment. To use the script for this deployment:
9 |
10 | ```
11 | iac\scripts\PrepareAseDeployment.ps1 -DomainName mydomain-internal.us -OutFile C:\temp\myase.parameters.json
12 | ```
13 |
14 | This will generate a parameter file that you can use to either copy/paste from or with a CLI or Powershell command to deploy the template.
15 |
16 | Note: It would be possible (and better) to deploy the certificate using Key Vault, but this workflow is not supported in all clouds (including sovereign clouds); for compatibility reasons, the certificate is passed as a string blob in this template.
17 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
--------------------------------------------------------------------------------
/ase/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "subnetId": {
6 | "type": "string",
7 | "defaultValue": "null"
8 | },
9 | "domainName": {
10 | "type": "string",
11 | "defaultValue": "contoso-internal.us"
12 | },
13 | "internalLoadBalancingMode": {
14 | "type": "int",
15 | "defaultValue": 3,
16 | "allowedValues": [
17 | 0,
18 | 1,
19 | 2,
20 | 3
21 | ],
22 | "metadata": {
23 | "description": "0 = public VIP only, 1 = only ports 80/443 are mapped to ILB VIP, 2 = only FTP ports are mapped to ILB VIP, 3 = both ports 80/443 and FTP ports are mapped to an ILB VIP."
24 | }
25 | },
26 | "pfxBlobString": {
27 | "type": "string"
28 | },
29 | "certificatePassword": {
30 | "type": "securestring"
31 | },
32 | "certificateThumbprint": {
33 | "type": "string"
34 | },
35 | "aseName": {
36 | "type": "string",
37 | "defaultValue": "GENERATE"
38 | },
39 | "aseSiteName": {
40 | "type": "string",
41 | "defaultValue": "ase-site"
42 | },
43 | "pricingTier": {
44 | "type": "string",
45 | "allowedValues": [
46 | "1",
47 | "2",
48 | "3"
49 | ],
50 | "defaultValue": "1",
51 | "metadata": {
52 | "description": "Defines pricing tier for workers: 1 = Isolated 1, 2 = Isolated 2, 3 = Isolated 3."
53 | }
54 | },
55 | "capacity": {
56 | "type": "int",
57 | "defaultValue": 1,
58 | "metadata": {
59 | "description": "Defines the number of instances that will be allocated to the app service plan."
60 | }
61 | }
62 | },
63 | "variables": {
64 | "baseUri": "[deployment().properties.templateLink.uri]",
65 | "ilbCertTemplateURL": "[uri(variables('baseUri'),'nested-templates/ase-ilb-certificate.json')]",
66 | "aseName": "[if(equals(parameters('aseName'),'GENERATE'),concat('ASE', uniqueString(resourceGroup().id)), parameters('aseName'))]",
67 | "genVnetName": "[concat('ase-vnet',uniqueString(resourceGroup().id))]",
68 | "genVnetid": "[resourceId('Microsoft.Network/virtualNetworks', variables('genVnetName'))]",
69 | "genVnetSubnetName1": "default",
70 | "genVnetSubnetName2": "ase-subnet",
71 | "genSubnetId1": "[concat(variables('genVnetid'), '/subnets/', variables('genVnetSubnetName1'))]",
72 | "genSubnetId2": "[concat(variables('genVnetid'), '/subnets/', variables('genVnetSubnetName2'))]",
73 | "subnetId": "[if(equals(parameters('subnetId'),'null'), variables('genSubnetId2'), parameters('subnetId'))]",
74 | "lastSlash": "[lastIndexOf(variables('subnetId'),'/')]",
75 | "subnetName": "[substring(variables('subnetId'),add(variables('lastSlash'),1))]",
76 | "vnetId": "[substring(variables('subnetId'),0,sub(variables('lastSlash'),8))]",
77 | "certName": "[concat('DefaultCertificateFor_', variables('aseName'), '_InternalLoadBalancingASE')]",
78 | "appServicePlanName": "[concat(variables('aseName'),'-asp')]",
79 | "locationLUT": {
80 | "usgovvirginia": "USGov Virginia",
81 | "usgoviowa": "USGov Iowa",
82 | "usdodeast": "USDoD East",
83 | "usdodcentral": "USDoD Central",
84 | "usgovtexas": "USGov Texas",
85 | "usgovarizona": "USGov Arizona",
86 | "eastasia": "East Asia",
87 | "southeastasia": "Southeast Asia",
88 | "centralus": "Central US",
89 | "eastus": "East US",
90 | "eastus2": "East US 2",
91 | "westus": "West US",
92 | "northcentralus": "North Central US",
93 | "southcentralus": "South Central US",
94 | "northeurope": "North Europe",
95 | "westeurope": "West Europe",
96 | "japanwest": "Japan West",
97 | "japaneast": "Japan East",
98 | "brazilsouth": "Brazil South",
99 | "australiaeast": "Australia East",
100 | "australiasoutheast": "Australia Southeast",
101 | "southindia": "South India",
102 | "centralindia": "Central India",
103 | "westindia": "West India",
104 | "canadacentral": "Canada Central",
105 | "canadaeast": "Canada East",
106 | "uksouth": "UK South",
107 | "ukwest": "UK West",
108 | "westcentralus": "West Central US",
109 | "westus2": "West US 2",
110 | "koreacentral": "Korea Central",
111 | "koreasouth": "Korea South"
112 | }
113 | },
114 | "resources": [
115 | {
116 | "name": "[variables('genVnetName')]",
117 | "type": "Microsoft.Network/virtualNetworks",
118 | "location": "[resourceGroup().location]",
119 | "apiVersion": "2016-03-30",
120 | "condition": "[equals(parameters('subnetId'),'null')]",
121 | "dependsOn": [],
122 | "properties": {
123 | "addressSpace": {
124 | "addressPrefixes": [
125 | "10.0.0.0/16"
126 | ]
127 | },
128 | "subnets": [
129 | {
130 | "name": "[variables('genVnetSubnetName1')]",
131 | "properties": {
132 | "addressPrefix": "10.0.0.0/24"
133 | }
134 | },
135 | {
136 | "name": "[variables('genVnetSubnetName2')]",
137 | "properties": {
138 | "addressPrefix": "10.0.1.0/25"
139 | }
140 | }
141 | ]
142 | }
143 | },
144 | {
145 | "name": "[variables('aseName')]",
146 | "type": "Microsoft.Web/hostingEnvironments",
147 | "apiVersion": "2016-09-01",
148 | "dependsOn": [
149 | "[variables('genVnetId')]"
150 | ],
151 | "kind": "ASEV2",
152 | "location": "[variables('locationLUT')[resourceGroup().location]]",
153 | "properties": {
154 | "name": "[variables('aseName')]",
155 | "location": "[variables('locationLUT')[resourceGroup().location]]",
156 | "virtualNetwork": {
157 | "id": "[variables('vnetId')]",
158 | "subnet": "[variables('subnetName')]"
159 | },
160 | "ipSslAddressCount": 0,
161 | "internalLoadBalancingMode": "[parameters('internalLoadBalancingMode')]",
162 | "dnsSuffix": "[parameters('domainName')]"
163 | }
164 | },
165 | {
166 | "name": "SetILBCert",
167 | "type": "Microsoft.Resources/deployments",
168 | "apiVersion": "2017-05-10",
169 | "dependsOn": [
170 | "[concat('Microsoft.Web/hostingEnvironments/', variables('aseName'))]"
171 | ],
172 | "properties": {
173 | "mode": "Incremental",
174 | "templateLink": {
175 | "uri": "[variables('ilbCertTemplateURL')]",
176 | "contentVersion": "1.0.0.0"
177 | },
178 | "parameters": {
179 | "appServiceEnvironmentName": {
180 | "value": "[variables('aseName')]"
181 | },
182 | "existingAseLocation": {
183 | "value": "[variables('locationLUT')[resourceGroup().location]]"
184 | },
185 | "pfxBlobString": {
186 | "value": "[parameters('pfxBlobString')]"
187 | },
188 | "password": {
189 | "value": "[parameters('certificatePassword')]"
190 | },
191 | "certificateThumbprint": {
192 | "value": "[parameters('certificateThumbprint')]"
193 | },
194 | "certificateName": {
195 | "value": "[variables('certName')]"
196 | }
197 | }
198 | }
199 | },
200 | {
201 | "apiVersion": "2016-09-01",
202 | "name": "[variables('appServicePlanName')]",
203 | "type": "Microsoft.Web/serverfarms",
204 | "location": "[variables('locationLUT')[resourceGroup().location]]",
205 | "dependsOn": [
206 | "[resourceId('Microsoft.Web/hostingEnvironments', variables('aseName'))]",
207 | "[resourceId('Microsoft.Resources/deployments', 'SetILBCert')]"
208 | ],
209 | "properties": {
210 | "name": "[variables('appServicePlanName')]",
211 | "hostingEnvironmentProfile": {
212 | "id": "[resourceId('Microsoft.Web/hostingEnvironments', variables('aseName'))]"
213 | }
214 | },
215 | "sku": {
216 | "name": "[concat('I',parameters('pricingTier'))]",
217 | "tier": "Isolated",
218 | "size": "[concat('I',parameters('pricingTier'))]",
219 | "family": "I",
220 | "capacity": "[parameters('capacity')]"
221 | }
222 | },
223 | {
224 | "apiVersion": "2016-08-01",
225 | "name": "[parameters('aseSiteName')]",
226 | "type": "Microsoft.Web/sites",
227 | "location": "[variables('locationLUT')[resourceGroup().location]]",
228 | "dependsOn": [
229 | "[concat('Microsoft.Web/serverFarms/', variables('appServicePlanName'))]"
230 | ],
231 | "properties": {
232 | "name": "[parameters('aseSiteName')]",
233 | "serverFarmId": "[resourceId('Microsoft.Web/serverFarms', variables('appServicePlanName'))]",
234 | "hostingEnvironmentProfile": {
235 | "id": "[resourceId('Microsoft.Web/hostingEnvironments', variables('aseName'))]"
236 | }
237 | }
238 | }
239 | ]
240 | }
--------------------------------------------------------------------------------
/ase/nested-templates/ase-ilb-certificate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "appServiceEnvironmentName": {
6 | "type": "string",
7 | "metadata": {
8 | "description": "The default SSL certificate will be configured for this App Service Environment."
9 | }
10 | },
11 | "existingAseLocation": {
12 | "type": "string",
13 | "metadata": {
14 | "description": "Set this to the same location as the App Service Environment defined in appServiceEnvironmentName."
15 | }
16 | },
17 | "pfxBlobString": {
18 | "type": "string",
19 | "metadata": {
20 | "description": "A pfx file encoded as a base-64 string. The pfx contains the SSL certificate that will be configured as the default SSL certificate for the ASE."
21 | }
22 | },
23 | "password": {
24 | "type": "string",
25 | "metadata": {
26 | "description": "The password for the pfx filed contained in pfxBlobString."
27 | }
28 | },
29 | "certificateThumbprint": {
30 | "type": "string",
31 | "metadata": {
32 | "description": "The hexadecimal certificate thumbprint of the certificate contained in pfxBlobString. All spaces need to be removed from the hex string."
33 | }
34 | },
35 | "certificateName": {
36 | "type": "string",
37 | "metadata": {
38 | "description": "Name of the certificate. This can be any name you want to use to identify the certificate."
39 | }
40 | }
41 | },
42 | "resources": [
43 | {
44 | "apiVersion": "2015-08-01",
45 | "type": "Microsoft.Web/certificates",
46 | "name": "[parameters('certificateName')]",
47 | "location": "[parameters('existingAseLocation')]",
48 | "properties": {
49 | "pfxBlob": "[parameters('pfxBlobString')]",
50 | "password": "[parameters('password')]",
51 | "hostingEnvironmentProfile": {
52 | "id": "[resourceId('Microsoft.Web/hostingEnvironments',parameters('appServiceEnvironmentName'))]"
53 | }
54 | }
55 | },
56 | {
57 | "apiVersion": "2016-09-01",
58 | "type": "Microsoft.Web/hostingEnvironments",
59 | "name": "[parameters('appServiceEnvironmentName')]",
60 | "location": "[parameters('existingAseLocation')]",
61 | "properties": {
62 | "clusterSettings": [
63 | {
64 | "name": "DefaultSslCertificateThumbprint",
65 | "value": "[parameters('certificateThumbprint')]"
66 | },
67 | {
68 | "name": "DisableTls1.0",
69 | "value": "1"
70 | }
71 | ]
72 | },
73 | "dependsOn": [
74 | "[concat('Microsoft.Web/certificates/',parameters('certificateName'))]"
75 | ]
76 | }
77 | ]
78 | }
--------------------------------------------------------------------------------
/core-network/DSC/ConfigureADBDC.ps1:
--------------------------------------------------------------------------------
1 | configuration ConfigureADBDC
2 | {
3 |
4 | param
5 | (
6 | [Parameter(Mandatory)]
7 | [String]$DomainName,
8 |
9 | [Parameter(Mandatory)]
10 | [System.Management.Automation.PSCredential]$Admincreds,
11 |
12 | [Int]$RetryCount=20,
13 | [Int]$RetryIntervalSec=30
14 | )
15 |
16 | Import-DscResource -ModuleName xActiveDirectory, xStorage, xPendingReboot
17 |
18 | [System.Management.Automation.PSCredential ]$DomainCreds = New-Object System.Management.Automation.PSCredential ("${DomainName}\$($Admincreds.UserName)", $Admincreds.Password)
19 |
20 | Node localhost
21 | {
22 | LocalConfigurationManager
23 | {
24 | RebootNodeIfNeeded = $true
25 | }
26 |
27 | xWaitforDisk Disk2
28 | {
29 | DiskId = 2
30 | RetryIntervalSec =$RetryIntervalSec
31 | RetryCount = $RetryCount
32 | }
33 |
34 | xDisk ADDataDisk
35 | {
36 | DiskId = 2
37 | DriveLetter = "F"
38 | DependsOn = "[xWaitForDisk]Disk2"
39 | }
40 |
41 | WindowsFeature ADDSInstall
42 | {
43 | Ensure = "Present"
44 | Name = "AD-Domain-Services"
45 | }
46 |
47 | WindowsFeature ADDSTools
48 | {
49 | Ensure = "Present"
50 | Name = "RSAT-ADDS-Tools"
51 | DependsOn = "[WindowsFeature]ADDSInstall"
52 | }
53 |
54 | WindowsFeature ADAdminCenter
55 | {
56 | Ensure = "Present"
57 | Name = "RSAT-AD-AdminCenter"
58 | DependsOn = "[WindowsFeature]ADDSTools"
59 | }
60 |
61 | xWaitForADDomain DscForestWait
62 | {
63 | DomainName = $DomainName
64 | DomainUserCredential= $DomainCreds
65 | RetryCount = $RetryCount
66 | RetryIntervalSec = $RetryIntervalSec
67 | }
68 | xADDomainController BDC
69 | {
70 | DomainName = $DomainName
71 | DomainAdministratorCredential = $DomainCreds
72 | SafemodeAdministratorPassword = $DomainCreds
73 | DatabasePath = "F:\NTDS"
74 | LogPath = "F:\NTDS"
75 | SysvolPath = "F:\SYSVOL"
76 | DependsOn = "[xWaitForADDomain]DscForestWait"
77 | }
78 |
79 | xPendingReboot RebootAfterPromotion {
80 | Name = "RebootAfterDCPromotion"
81 | DependsOn = "[xADDomainController]BDC"
82 | }
83 |
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/core-network/DSC/ConfigureADBDC.ps1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hansenms/iac/277b41b9f9a0ce395f577d8a74640000c91c5ce1/core-network/DSC/ConfigureADBDC.ps1.zip
--------------------------------------------------------------------------------
/core-network/DSC/CreateADPDC.ps1:
--------------------------------------------------------------------------------
1 | configuration CreateADPDC
2 | {
3 | param
4 | (
5 | [Parameter(Mandatory)]
6 | [String]$DomainName,
7 |
8 | [Parameter(Mandatory)]
9 | [System.Management.Automation.PSCredential]$Admincreds,
10 |
11 | [Int]$RetryCount=20,
12 | [Int]$RetryIntervalSec=30
13 | )
14 |
15 | Import-DscResource -ModuleName xActiveDirectory, xStorage, xNetworking, PSDesiredStateConfiguration, xPendingReboot
16 | [System.Management.Automation.PSCredential ]$DomainCreds = New-Object System.Management.Automation.PSCredential ("${DomainName}\$($Admincreds.UserName)", $Admincreds.Password)
17 | $Interface=Get-NetAdapter|Where Name -Like "Ethernet*"|Select-Object -First 1
18 | $InterfaceAlias=$($Interface.Name)
19 |
20 | Node localhost
21 | {
22 | LocalConfigurationManager
23 | {
24 | RebootNodeIfNeeded = $true
25 | }
26 |
27 | WindowsFeature DNS
28 | {
29 | Ensure = "Present"
30 | Name = "DNS"
31 | }
32 |
33 | Script EnableDNSDiags
34 | {
35 | SetScript = {
36 | Set-DnsServerDiagnostics -All $true
37 | Write-Verbose -Verbose "Enabling DNS client diagnostics"
38 | }
39 | GetScript = { @{} }
40 | TestScript = { $false }
41 | DependsOn = "[WindowsFeature]DNS"
42 | }
43 |
44 | WindowsFeature DnsTools
45 | {
46 | Ensure = "Present"
47 | Name = "RSAT-DNS-Server"
48 | DependsOn = "[WindowsFeature]DNS"
49 | }
50 |
51 | xDnsServerAddress DnsServerAddress
52 | {
53 | Address = '127.0.0.1'
54 | InterfaceAlias = $InterfaceAlias
55 | AddressFamily = 'IPv4'
56 | DependsOn = "[WindowsFeature]DNS"
57 | }
58 |
59 | xWaitforDisk Disk2
60 | {
61 | DiskId = 2
62 | RetryIntervalSec =$RetryIntervalSec
63 | RetryCount = $RetryCount
64 | }
65 |
66 | xDisk ADDataDisk {
67 | DiskId = 2
68 | DriveLetter = "F"
69 | DependsOn = "[xWaitForDisk]Disk2"
70 | }
71 |
72 | WindowsFeature ADDSInstall
73 | {
74 | Ensure = "Present"
75 | Name = "AD-Domain-Services"
76 | DependsOn="[WindowsFeature]DNS"
77 | }
78 |
79 | WindowsFeature ADDSTools
80 | {
81 | Ensure = "Present"
82 | Name = "RSAT-ADDS-Tools"
83 | DependsOn = "[WindowsFeature]ADDSInstall"
84 | }
85 |
86 | WindowsFeature ADAdminCenter
87 | {
88 | Ensure = "Present"
89 | Name = "RSAT-AD-AdminCenter"
90 | DependsOn = "[WindowsFeature]ADDSTools"
91 | }
92 |
93 | xADDomain FirstDS
94 | {
95 | DomainName = $DomainName
96 | DomainAdministratorCredential = $DomainCreds
97 | SafemodeAdministratorPassword = $DomainCreds
98 | DatabasePath = "F:\NTDS"
99 | LogPath = "F:\NTDS"
100 | SysvolPath = "F:\SYSVOL"
101 | DependsOn = @("[WindowsFeature]ADDSInstall", "[xDisk]ADDataDisk")
102 | }
103 |
104 | xPendingReboot RebootAfterPromotion{
105 | Name = "RebootAfterPromotion"
106 | DependsOn = "[xADDomain]FirstDS"
107 | }
108 |
109 | }
110 | }
--------------------------------------------------------------------------------
/core-network/DSC/CreateADPDC.ps1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hansenms/iac/277b41b9f9a0ce395f577d8a74640000c91c5ce1/core-network/DSC/CreateADPDC.ps1.zip
--------------------------------------------------------------------------------
/core-network/README.md:
--------------------------------------------------------------------------------
1 | Core Virtual Network with Domain Controllers
2 | ---------------------------------------------
3 |
4 | This template deploys a virtual network with two private domain controllers in an availbility set and a jump box for accessing the network:
5 |
6 | 
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
16 |
--------------------------------------------------------------------------------
/core-network/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "adminUsername": {
6 | "type": "string",
7 | "defaultValue": "EnterpriseAdmin"
8 | },
9 | "adminPassword": {
10 | "type": "securestring"
11 | },
12 | "domainName": {
13 | "type": "string",
14 | "defaultValue": "devnet.contoso.us"
15 | },
16 | "jumpBoxVmSize": {
17 | "type": "string",
18 | "allowedValues": [
19 | "Standard_D1_v2",
20 | "Standard_D2_v2",
21 | "Standard_D3_v2",
22 | "Standard_D4_v2",
23 | "Standard_D5_v2",
24 | "Standard_D11_v2",
25 | "Standard_D12_v2",
26 | "Standard_D13_v2",
27 | "Standard_D14_v2",
28 | "Standard_D15_v2",
29 | "Standard_DS1_v2",
30 | "Standard_DS2_v2",
31 | "Standard_DS3_v2",
32 | "Standard_DS4_v2",
33 | "Standard_DS5_v2",
34 | "Standard_DS11_v2",
35 | "Standard_DS12_v2",
36 | "Standard_DS13_v2",
37 | "Standard_DS14_v2",
38 | "Standard_DS15_v2"
39 | ],
40 | "defaultValue": "Standard_D2_v2"
41 | },
42 | "DCVmSize": {
43 | "type": "string",
44 | "allowedValues": [
45 | "Standard_D1_v2",
46 | "Standard_D2_v2",
47 | "Standard_D3_v2",
48 | "Standard_D4_v2",
49 | "Standard_D5_v2",
50 | "Standard_D11_v2",
51 | "Standard_D12_v2",
52 | "Standard_D13_v2",
53 | "Standard_D14_v2",
54 | "Standard_D15_v2",
55 | "Standard_DS1_v2",
56 | "Standard_DS2_v2",
57 | "Standard_DS3_v2",
58 | "Standard_DS4_v2",
59 | "Standard_DS5_v2",
60 | "Standard_DS11_v2",
61 | "Standard_DS12_v2",
62 | "Standard_DS13_v2",
63 | "Standard_DS14_v2",
64 | "Standard_DS15_v2"
65 | ],
66 | "defaultValue": "Standard_D1_v2"
67 | }
68 | },
69 | "variables": {
70 | "diagStorageAccountName": "[concat(uniquestring(resourceGroup().id), 'vmdiag')]",
71 | "coreVnetName": "corevnet",
72 | "corevnetPrefix": "10.0.0.0/16",
73 | "corevnetDCSubnetName": "Subnet-1",
74 | "corevnetDCSubnetPrefix": "10.0.0.0/24",
75 | "corevnetDevOpsSubnetName": "Subnet-2",
76 | "corevnetDevOpsSubnetPrefix": "10.0.1.0/24",
77 | "jumpNICVnetID": "[resourceId('Microsoft.Network/virtualNetworks', variables('coreVnetName'))]",
78 | "jumpNICSubnetRef": "[concat(variables('jumpNICVnetID'), '/subnets/', variables('corevnetDevOpsSubnetName'))]",
79 | "ADSubnetRef": "[concat(variables('jumpNICVnetID'), '/subnets/', variables('corevnetDCSubnetName'))]",
80 | "jumpVMName": "JumpBox",
81 | "adPDCVMName": "DC1",
82 | "adBDCVMName": "DC2",
83 | "DCHASetName": "DCHASet",
84 | "baseUri": "[deployment().properties.templateLink.uri]",
85 | "windowsVmTemplateURL": "[uri(variables('baseUri'),'../primitives/windowsvm.json')]",
86 | "adPDCModulesURL": "[uri(variables('baseUri'),'DSC/CreateADPDC.ps1.zip')]",
87 | "adPDCConfigurationFunction": "CreateADPDC.ps1\\CreateADPDC",
88 | "adBDCModulesURL": "[uri(variables('baseUri'),'DSC/ConfigureADBDC.ps1.zip')]",
89 | "adBDCConfigurationFunction": "ConfigureADBDC.ps1\\ConfigureADBDC",
90 | "vnetwithDNSTemplateUri": "[uri(variables('baseUri'),'nested-templates/update-vnet-dns.json')]"
91 | },
92 | "resources": [
93 | {
94 | "type": "Microsoft.Storage/storageAccounts",
95 | "name": "[variables('diagStorageAccountName')]",
96 | "apiVersion": "2016-01-01",
97 | "location": "[resourceGroup().location]",
98 | "sku": {
99 | "name": "Standard_LRS"
100 | },
101 | "kind": "Storage",
102 | "properties": {}
103 | },
104 | {
105 | "name": "[variables('coreVnetName')]",
106 | "type": "Microsoft.Network/virtualNetworks",
107 | "location": "[resourceGroup().location]",
108 | "apiVersion": "2016-03-30",
109 | "dependsOn": [],
110 | "properties": {
111 | "addressSpace": {
112 | "addressPrefixes": [
113 | "[variables('corevnetPrefix')]"
114 | ]
115 | },
116 | "subnets": [
117 | {
118 | "name": "[variables('corevnetDCSubnetName')]",
119 | "properties": {
120 | "addressPrefix": "[variables('corevnetDCSubnetPrefix')]"
121 | }
122 | },
123 | {
124 | "name": "[variables('corevnetDevOpsSubnetName')]",
125 | "properties": {
126 | "addressPrefix": "[variables('corevnetDevOpsSubnetPrefix')]"
127 | }
128 | }
129 | ]
130 | }
131 | },
132 | {
133 | "name": "[variables('DCHASetName')]",
134 | "type": "Microsoft.Compute/availabilitySets",
135 | "location": "[resourceGroup().location]",
136 | "apiVersion": "2017-03-30",
137 | "dependsOn": [],
138 | "tags": {
139 | "displayName": "DCHASet"
140 | },
141 | "properties": {
142 | "platformUpdateDomainCount": 2,
143 | "platformFaultDomainCount": 2
144 | },
145 | "sku": {
146 | "name": "Aligned"
147 | }
148 | },
149 | {
150 | "name": "DC1Deploy",
151 | "type": "Microsoft.Resources/deployments",
152 | "apiVersion": "2017-05-10",
153 | "dependsOn": [
154 | "[resourceId('Microsoft.Network/virtualNetworks', variables('coreVnetName'))]",
155 | "[resourceId('Microsoft.Storage/storageAccounts', variables('diagStorageAccountName'))]"
156 | ],
157 | "properties": {
158 | "mode": "Incremental",
159 | "templateLink": {
160 | "uri": "[variables('windowsVmTemplateURL')]",
161 | "contentVersion": "1.0.0.0"
162 | },
163 | "parameters": {
164 | "vmName": {
165 | "value": "[variables('adPDCVMName')]"
166 | },
167 | "vmSize": {
168 | "value": "[parameters('DCVmSize')]"
169 | },
170 | "subnetId": {
171 | "value": "[variables('ADSubnetRef')]"
172 | },
173 | "fixedPrivateIp": {
174 | "value": "10.0.0.4"
175 | },
176 | "adminUsername": {
177 | "value": "[parameters('adminUsername')]"
178 | },
179 | "adminPassword": {
180 | "value": "[parameters('adminPassword')]"
181 | },
182 | "diagStorageAccountId": {
183 | "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('diagStorageAccountName'))]"
184 | },
185 | "availabilitySetId": {
186 | "value": "[resourceId('Microsoft.Compute/availabilitySets', variables('DCHASetName'))]"
187 | }
188 | }
189 | }
190 | },
191 | {
192 | "name": "DC2Deploy",
193 | "type": "Microsoft.Resources/deployments",
194 | "apiVersion": "2017-05-10",
195 | "dependsOn": [
196 | "[resourceId('Microsoft.Network/virtualNetworks', variables('coreVnetName'))]",
197 | "[resourceId('Microsoft.Storage/storageAccounts', variables('diagStorageAccountName'))]",
198 | "[resourceId('Microsoft.Resources/deployments', 'UpdateVNetDNS')]"
199 | ],
200 | "properties": {
201 | "mode": "Incremental",
202 | "templateLink": {
203 | "uri": "[variables('windowsVmTemplateURL')]",
204 | "contentVersion": "1.0.0.0"
205 | },
206 | "parameters": {
207 | "vmName": {
208 | "value": "[variables('adBDCVMName')]"
209 | },
210 | "vmSize": {
211 | "value": "[parameters('DCVmSize')]"
212 | },
213 | "subnetId": {
214 | "value": "[variables('ADSubnetRef')]"
215 | },
216 | "fixedPrivateIp": {
217 | "value": "10.0.0.5"
218 | },
219 | "adminUsername": {
220 | "value": "[parameters('adminUsername')]"
221 | },
222 | "adminPassword": {
223 | "value": "[parameters('adminPassword')]"
224 | },
225 | "diagStorageAccountId": {
226 | "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('diagStorageAccountName'))]"
227 | },
228 | "availabilitySetId": {
229 | "value": "[resourceId('Microsoft.Compute/availabilitySets', variables('DCHASetName'))]"
230 | }
231 | }
232 | }
233 | },
234 | {
235 | "name": "JumpDeploy",
236 | "type": "Microsoft.Resources/deployments",
237 | "apiVersion": "2017-05-10",
238 | "dependsOn": [
239 | "[resourceId('Microsoft.Network/virtualNetworks', variables('coreVnetName'))]",
240 | "[resourceId('Microsoft.Storage/storageAccounts', variables('diagStorageAccountName'))]",
241 | "[resourceId('Microsoft.Resources/deployments', 'UpdateVNetDNS')]"
242 | ],
243 | "properties": {
244 | "mode": "Incremental",
245 | "templateLink": {
246 | "uri": "[variables('windowsVmTemplateURL')]",
247 | "contentVersion": "1.0.0.0"
248 | },
249 | "parameters": {
250 | "vmName": {
251 | "value": "[variables('jumpVMName')]"
252 | },
253 | "vmSize": {
254 | "value": "[parameters('jumpBoxVmSize')]"
255 | },
256 | "subnetId": {
257 | "value": "[variables('jumpNICSubnetRef')]"
258 | },
259 | "adminUsername": {
260 | "value": "[parameters('adminUsername')]"
261 | },
262 | "adminPassword": {
263 | "value": "[parameters('adminPassword')]"
264 | },
265 | "diagStorageAccountId": {
266 | "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('diagStorageAccountName'))]"
267 | },
268 | "assignPublicIP": {
269 | "value": true
270 | }
271 | }
272 | }
273 | },
274 | {
275 | "apiVersion": "2016-03-30",
276 | "type": "Microsoft.Compute/virtualMachines/extensions",
277 | "name": "[concat(variables('adPDCVMName'),'/CreateADForest')]",
278 | "location": "[resourceGroup().location]",
279 | "dependsOn": [
280 | "[resourceId('Microsoft.Resources/deployments', 'DC1Deploy')]"
281 | ],
282 | "properties": {
283 | "publisher": "Microsoft.Powershell",
284 | "type": "DSC",
285 | "typeHandlerVersion": "2.19",
286 | "autoUpgradeMinorVersion": true,
287 | "settings": {
288 | "ModulesUrl": "[variables('adPDCModulesURL')]",
289 | "ConfigurationFunction": "[variables('adPDCConfigurationFunction')]",
290 | "Properties": {
291 | "DomainName": "[parameters('domainName')]",
292 | "AdminCreds": {
293 | "UserName": "[parameters('adminUserName')]",
294 | "Password": "PrivateSettingsRef:AdminPassword"
295 | }
296 | }
297 | },
298 | "protectedSettings": {
299 | "Items": {
300 | "AdminPassword": "[parameters('adminPassword')]"
301 | }
302 | }
303 | }
304 | },
305 | {
306 | "apiVersion": "2016-03-30",
307 | "type": "Microsoft.Compute/virtualMachines/extensions",
308 | "name": "[concat(variables('adBDCVMName'),'/ConfigureADBDC')]",
309 | "location": "[resourceGroup().location]",
310 | "dependsOn": [
311 | "[resourceId('Microsoft.Resources/deployments', 'DC2Deploy')]",
312 | "[resourceId('Microsoft.Resources/deployments', 'UpdateVNetDNS')]"
313 | ],
314 | "properties": {
315 | "publisher": "Microsoft.Powershell",
316 | "type": "DSC",
317 | "typeHandlerVersion": "2.19",
318 | "autoUpgradeMinorVersion": true,
319 | "settings": {
320 | "ModulesUrl": "[variables('adBDCModulesURL')]",
321 | "ConfigurationFunction": "[variables('adBDCConfigurationFunction')]",
322 | "Properties": {
323 | "DomainName": "[parameters('domainName')]",
324 | "AdminCreds": {
325 | "UserName": "[parameters('adminUserName')]",
326 | "Password": "PrivateSettingsRef:AdminPassword"
327 | }
328 | }
329 | },
330 | "protectedSettings": {
331 | "Items": {
332 | "AdminPassword": "[parameters('adminPassword')]"
333 | }
334 | }
335 | }
336 | },
337 | {
338 | "apiVersion": "2015-06-15",
339 | "type": "Microsoft.Compute/virtualMachines/extensions",
340 | "name": "[concat(variables('jumpVMName'),'/joindomain')]",
341 | "location": "[resourceGroup().location]",
342 | "dependsOn": [
343 | "[resourceId('Microsoft.Resources/deployments', 'JumpDeploy')]"
344 | ],
345 | "properties": {
346 | "publisher": "Microsoft.Compute",
347 | "type": "JsonADDomainExtension",
348 | "typeHandlerVersion": "1.3",
349 | "autoUpgradeMinorVersion": true,
350 | "settings": {
351 | "Name": "[parameters('domainName')]",
352 | "OUPath": "",
353 | "User": "[concat(parameters('domainName'), '\\', parameters('adminUserName'))]",
354 | "Restart": "true",
355 | "Options": "3"
356 | },
357 | "protectedSettings": {
358 | "Password": "[parameters('adminPassword')]"
359 | }
360 | }
361 | },
362 | {
363 | "name": "UpdateVNetDNS",
364 | "type": "Microsoft.Resources/deployments",
365 | "apiVersion": "2017-05-10",
366 | "dependsOn": [
367 | "[concat('Microsoft.Compute/virtualMachines/', variables('adPDCVMName'),'/extensions/CreateADForest')]"
368 | ],
369 | "properties": {
370 | "mode": "Incremental",
371 | "templateLink": {
372 | "uri": "[variables('vnetwithDNSTemplateUri')]",
373 | "contentVersion": "1.0.0.0"
374 | },
375 | "parameters": {
376 | "vnetName": {
377 | "value": "[variables('coreVnetName')]"
378 | },
379 | "dnsServers": {
380 | "value": [
381 | "10.0.0.4",
382 | "10.0.0.5"
383 | ]
384 | },
385 | "addressSpace": {
386 | "value": "[reference(concat('Microsoft.Network/virtualNetworks/', variables('coreVnetName'))).addressSpace]"
387 | },
388 | "subnets": {
389 | "value": "[reference(concat('Microsoft.Network/virtualNetworks/', variables('coreVnetName'))).subnets]"
390 | }
391 | }
392 | }
393 | }
394 | ],
395 | "outputs": {}
396 | }
397 |
--------------------------------------------------------------------------------
/core-network/core-network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hansenms/iac/277b41b9f9a0ce395f577d8a74640000c91c5ce1/core-network/core-network.png
--------------------------------------------------------------------------------
/core-network/nested-templates/update-vnet-dns.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "vnetName": {
6 | "type": "string"
7 | },
8 | "dnsServers": {
9 | "type": "array"
10 | },
11 | "addressSpace": {
12 | "type": "object"
13 | },
14 | "subnets": {
15 | "type": "array"
16 | }
17 | },
18 | "variables": {
19 | },
20 | "resources": [
21 | {
22 | "name": "[parameters('vnetName')]",
23 | "type": "Microsoft.Network/virtualNetworks",
24 | "location": "[resourceGroup().location]",
25 | "apiVersion": "2016-03-30",
26 | "properties": {
27 | "addressSpace": "[parameters('addressSpace')]",
28 | "subnets": "[parameters('subnets')]",
29 | "dhcpOptions": {
30 | "dnsServers": "[parameters('dnsServers')]"
31 | }
32 | }
33 | }
34 | ],
35 | "outputs": {
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/devnet-tfs-ha/README.md:
--------------------------------------------------------------------------------
1 | High Availability Team Foundation Server in Azure
2 | --------------------------------------------------
3 |
4 | This solution combined several other templates to deploy a high-availability [Team Foundation Server](https://www.visualstudio.com/tfs/) (TFS) in Azure. This solution is relevant for organizations where the SaaS [Vistual Studio Team Services](https://www.visualstudio.com/team-services/) (VSTS) is not an option. The deployed solution is outlined in the diagram below.
5 |
6 | 
7 |
8 | The components included are:
9 |
10 | * [core-network](../core-network) which establishes a Virtual Network and sets up Domain Controllers.
11 | * [sql-alwayson](../sql-alwayson) which sets up SQL server in Always On configuration.
12 | * [tfs-ha](../tfs-ha) which sets up a high-availability deployment of TFS.
13 |
14 | In addition to this, some build agents are deployed.
15 |
16 | Each of the components can be deployed on their own and for users that already have domain controllers established, they can simply specify a subnet and domain credentials to deploy SQL and TFS into those environments. This combined template is mostly provided as a way of testing the complete deployment.
17 |
18 | Since TFS establishes a web endpoint on the TFS servers, it is recommended that you secure that with SSL certificates. If you do not provide a certificate, a self-signed one will be generated. You can replace it after installation too. The way to provide it during deployment is through the `secrets` and `sslThumbprint` parameters. The `secrets` parameter is used to provide an array of certificates from [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/). The `sslThumbprint` parameter instructs the TFS installation script on which certificate to install. The repository includes a [convenience script](../scripts/PrepareDevnetTfsDeployment.ps1), which you can use to create a key vault to hold domain admin password and the SSL certificate. This script will create the key vault, upload all secrets, and generate a deployment parameters file with the details needed for deployment.
19 |
20 | Please consult the [tfs-ha](../tfs-ha) folder for more details including post-installation instructions, which include adding the established SQL databases to an availability group.
21 |
22 | Deploy
23 | ------
24 |
25 | Deploy this solution to either Azure Commercial or Azure Government:
26 |
27 |
28 |
29 |
30 |
31 |
32 |
34 |
--------------------------------------------------------------------------------
/devnet-tfs-ha/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "adminUsername": {
6 | "type": "string",
7 | "defaultValue": "EnterpriseAdmin"
8 | },
9 | "adminPassword": {
10 | "type": "securestring"
11 | },
12 | "domainName": {
13 | "type": "string",
14 | "defaultValue": "devnet.contoso.us"
15 | },
16 | "secrets": {
17 | "type": "array",
18 | "defaultValue": []
19 | },
20 | "sslThumbprint": {
21 | "type": "string",
22 | "defaultValue": "generate"
23 | },
24 | "jumpBoxVmSize": {
25 | "type": "string",
26 | "allowedValues": [
27 | "Standard_DS1_v2",
28 | "Standard_DS2_v2",
29 | "Standard_DS3_v2",
30 | "Standard_DS4_v2",
31 | "Standard_DS5_v2",
32 | "Standard_DS11_v2",
33 | "Standard_DS12_v2",
34 | "Standard_DS13_v2",
35 | "Standard_DS14_v2",
36 | "Standard_DS15_v2"
37 | ],
38 | "defaultValue": "Standard_DS2_v2"
39 | },
40 | "DCVmSize": {
41 | "type": "string",
42 | "allowedValues": [
43 | "Standard_DS1_v2",
44 | "Standard_DS2_v2",
45 | "Standard_DS3_v2",
46 | "Standard_DS4_v2",
47 | "Standard_DS5_v2",
48 | "Standard_DS11_v2",
49 | "Standard_DS12_v2",
50 | "Standard_DS13_v2",
51 | "Standard_DS14_v2",
52 | "Standard_DS15_v2"
53 | ],
54 | "defaultValue": "Standard_DS1_v2"
55 | },
56 | "tfsVmSize": {
57 | "type": "string",
58 | "allowedValues": [
59 | "Standard_DS1_v2",
60 | "Standard_DS2_v2",
61 | "Standard_DS3_v2",
62 | "Standard_DS4_v2",
63 | "Standard_DS5_v2",
64 | "Standard_DS11_v2",
65 | "Standard_DS12_v2",
66 | "Standard_DS13_v2",
67 | "Standard_DS14_v2",
68 | "Standard_DS15_v2"
69 | ],
70 | "defaultValue": "Standard_DS2_v2"
71 | },
72 | "sqlVmSize": {
73 | "type": "string",
74 | "allowedValues": [
75 | "Standard_DS1_v2",
76 | "Standard_DS2_v2",
77 | "Standard_DS3_v2",
78 | "Standard_DS4_v2",
79 | "Standard_DS5_v2",
80 | "Standard_DS11_v2",
81 | "Standard_DS12_v2",
82 | "Standard_DS13_v2",
83 | "Standard_DS14_v2",
84 | "Standard_DS15_v2"
85 | ],
86 | "defaultValue": "Standard_DS4_v2"
87 | },
88 | "tfsAgentVmSize": {
89 | "type": "string",
90 | "allowedValues": [
91 | "Standard_DS1_v2",
92 | "Standard_DS2_v2",
93 | "Standard_DS3_v2",
94 | "Standard_DS4_v2",
95 | "Standard_DS5_v2",
96 | "Standard_DS11_v2",
97 | "Standard_DS12_v2",
98 | "Standard_DS13_v2",
99 | "Standard_DS14_v2",
100 | "Standard_DS15_v2"
101 | ],
102 | "defaultValue": "Standard_DS2_v2"
103 | }
104 | },
105 | "variables": {
106 | "baseUri": "[deployment().properties.templateLink.uri]",
107 | "coreNetworkTemplateUri": "[uri(variables('baseUri'), '../core-network/azuredeploy.json')]",
108 | "sqlTemplateUri": "[uri(variables('baseUri'), '../sql-alwayson/azuredeploy.json')]",
109 | "tfsTemplateUri": "[uri(variables('baseUri'), '../tfs-ha/azuredeploy.json')]",
110 | "agentTemplateUri": "[uri(variables('baseUri'), '../tfs-ha/deployagents.json')]",
111 | "coreVnetName": "corevnet",
112 | "corevnetDevOpsSubnetName": "Subnet-2",
113 | "vnetid": "[resourceId('Microsoft.Network/virtualNetworks', variables('coreVnetName'))]",
114 | "devopsdubnetid": "[concat(variables('vnetid'), '/subnets/', variables('corevnetDevOpsSubnetName'))]",
115 | "tfsServerProtocol": "[if(equals(parameters('sslThumbprint'),'generate'),'http://','https://')]",
116 | "tfsServerUrl": "[concat(variables('tfsServerProtocol'),'tfs.',parameters('domainName'))]"
117 | },
118 | "resources": [
119 | {
120 | "name": "core-network",
121 | "type": "Microsoft.Resources/deployments",
122 | "apiVersion": "2017-05-10",
123 | "properties": {
124 | "mode": "Incremental",
125 | "templateLink": {
126 | "uri": "[variables('coreNetworkTemplateUri')]",
127 | "contentVersion": "1.0.0.0"
128 | },
129 | "parameters": {
130 | "adminUsername": {
131 | "value": "[parameters('adminUsername')]"
132 | },
133 | "adminPassword": {
134 | "value": "[parameters('adminPassword')]"
135 | },
136 | "domainName": {
137 | "value": "[parameters('domainName')]"
138 | },
139 | "jumpBoxVmSize": {
140 | "value": "[parameters('jumpBoxVmSize')]"
141 | },
142 | "DCVmSize": {
143 | "value": "[parameters('DCVmSize')]"
144 | }
145 | }
146 | }
147 | },
148 | {
149 | "name": "sqlalwayson",
150 | "type": "Microsoft.Resources/deployments",
151 | "apiVersion": "2017-05-10",
152 | "dependsOn": [
153 | "Microsoft.Resources/deployments/core-network"
154 | ],
155 | "properties": {
156 | "mode": "Incremental",
157 | "templateLink": {
158 | "uri": "[variables('sqlTemplateUri')]",
159 | "contentVersion": "1.0.0.0"
160 | },
161 | "parameters": {
162 | "subnetId": {
163 | "value": "[variables('devopsdubnetid')]"
164 | },
165 | "adminUsername": {
166 | "value": "[parameters('adminUsername')]"
167 | },
168 | "adminPassword": {
169 | "value": "[parameters('adminPassword')]"
170 | },
171 | "domainName": {
172 | "value": "[parameters('domainName')]"
173 | },
174 | "sqlVmSize": {
175 | "value": "[parameters('sqlVmSize')]"
176 | }
177 | }
178 | }
179 | },
180 | {
181 | "name": "tfs",
182 | "type": "Microsoft.Resources/deployments",
183 | "apiVersion": "2017-05-10",
184 | "dependsOn": [
185 | "Microsoft.Resources/deployments/sqlalwayson"
186 | ],
187 | "properties": {
188 | "mode": "Incremental",
189 | "templateLink": {
190 | "uri": "[variables('tfsTemplateUri')]",
191 | "contentVersion": "1.0.0.0"
192 | },
193 | "parameters": {
194 | "subnetId": {
195 | "value": "[variables('devopsdubnetid')]"
196 | },
197 | "adminUsername": {
198 | "value": "[parameters('adminUsername')]"
199 | },
200 | "adminPassword": {
201 | "value": "[parameters('adminPassword')]"
202 | },
203 | "domainName": {
204 | "value": "[parameters('domainName')]"
205 | },
206 | "vmSize": {
207 | "value": "[parameters('tfsVmSize')]"
208 | },
209 | "secrets": {
210 | "value": "[parameters('secrets')]"
211 | },
212 | "sslThumbprint": {
213 | "value": "[parameters('sslThumbprint')]"
214 | }
215 | }
216 | }
217 | },
218 | {
219 | "name": "tfsagents",
220 | "type": "Microsoft.Resources/deployments",
221 | "apiVersion": "2017-05-10",
222 | "dependsOn": [
223 | "Microsoft.Resources/deployments/tfs"
224 | ],
225 | "properties": {
226 | "mode": "Incremental",
227 | "templateLink": {
228 | "uri": "[variables('agentTemplateUri')]",
229 | "contentVersion": "1.0.0.0"
230 | },
231 | "parameters": {
232 | "subnetId": {
233 | "value": "[variables('devopsdubnetid')]"
234 | },
235 | "adminUsername": {
236 | "value": "[parameters('adminUsername')]"
237 | },
238 | "adminPassword": {
239 | "value": "[parameters('adminPassword')]"
240 | },
241 | "domainName": {
242 | "value": "[parameters('domainName')]"
243 | },
244 | "vmSize": {
245 | "value": "[parameters('tfsAgentVmSize')]"
246 | },
247 | "tfsServerUrl": {
248 | "value": "[variables('tfsServerUrl')]"
249 | }
250 | }
251 | }
252 | }
253 | ],
254 | "outputs": {}
255 | }
--------------------------------------------------------------------------------
/devnet-tfs-ha/private_ha_devops.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hansenms/iac/277b41b9f9a0ce395f577d8a74640000c91c5ce1/devnet-tfs-ha/private_ha_devops.png
--------------------------------------------------------------------------------
/devnet-tfs/README.md:
--------------------------------------------------------------------------------
1 | Private TFS DevOps in Azure
2 | ---------------------------
3 |
4 | Microsoft [Azure](https://azure.microsoft.com/en-us/) offers a hosted DevOps experience with [Vistual Studio Team Services](https://www.visualstudio.com/team-services/) (VSTS). It is great, but sometimes organizations would like to host their own [Team Foundation Server](https://www.visualstudio.com/tfs/) (TFS). A particularly relevant use case is users in the [Microsoft Government Cloud](https://azure.microsoft.com/en-us/overview/clouds/government/) where VSTS is not yet available. Deploying a TFS Server with database and build agents can be complicated.
5 |
6 | This template illustrates how to create a private DevOps network with TFS server and agents. The topology is illustrated below:
7 |
8 | 
9 |
10 | This deployment is actually a nested compbination of deployment of a virtual network with two domain controllers and a JumpBox. You can deploy that without the TFS installation using [this template](https://github.com/hansenms/iac/tree/master/core-network). You can then deploy the TFS resources (e.g. in a different resource group) using [this template](https://github.com/hansenms/iac/tree/master/tfs).
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
--------------------------------------------------------------------------------
/devnet-tfs/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "adminUsername": {
6 | "type": "string",
7 | "defaultValue": "EnterpriseAdmin"
8 | },
9 | "adminPassword": {
10 | "type": "securestring"
11 | },
12 | "domainName": {
13 | "type": "string",
14 | "defaultValue": "devnet.contoso.us"
15 | },
16 | "secrets": {
17 | "type": "array",
18 | "defaultValue": []
19 | },
20 | "sslThumbprint": {
21 | "type": "string",
22 | "defaultValue": "generate"
23 | },
24 | "jumpBoxVmSize": {
25 | "type": "string",
26 | "allowedValues": [
27 | "Standard_DS1_v2",
28 | "Standard_DS2_v2",
29 | "Standard_DS3_v2",
30 | "Standard_DS4_v2",
31 | "Standard_DS5_v2",
32 | "Standard_DS11_v2",
33 | "Standard_DS12_v2",
34 | "Standard_DS13_v2",
35 | "Standard_DS14_v2",
36 | "Standard_DS15_v2"
37 | ],
38 | "defaultValue": "Standard_DS2_v2"
39 | },
40 | "DCVmSize": {
41 | "type": "string",
42 | "allowedValues": [
43 | "Standard_DS1_v2",
44 | "Standard_DS2_v2",
45 | "Standard_DS3_v2",
46 | "Standard_DS4_v2",
47 | "Standard_DS5_v2",
48 | "Standard_DS11_v2",
49 | "Standard_DS12_v2",
50 | "Standard_DS13_v2",
51 | "Standard_DS14_v2",
52 | "Standard_DS15_v2"
53 | ],
54 | "defaultValue": "Standard_DS1_v2"
55 | },
56 | "tfsVmSize": {
57 | "type": "string",
58 | "allowedValues": [
59 | "Standard_DS1_v2",
60 | "Standard_DS2_v2",
61 | "Standard_DS3_v2",
62 | "Standard_DS4_v2",
63 | "Standard_DS5_v2",
64 | "Standard_DS11_v2",
65 | "Standard_DS12_v2",
66 | "Standard_DS13_v2",
67 | "Standard_DS14_v2",
68 | "Standard_DS15_v2"
69 | ],
70 | "defaultValue": "Standard_DS2_v2"
71 | },
72 | "tfsSqlVmSize": {
73 | "type": "string",
74 | "allowedValues": [
75 | "Standard_DS1_v2",
76 | "Standard_DS2_v2",
77 | "Standard_DS3_v2",
78 | "Standard_DS4_v2",
79 | "Standard_DS5_v2",
80 | "Standard_DS11_v2",
81 | "Standard_DS12_v2",
82 | "Standard_DS13_v2",
83 | "Standard_DS14_v2",
84 | "Standard_DS15_v2"
85 | ],
86 | "defaultValue": "Standard_DS4_v2"
87 | },
88 | "tfsAgentVmSize": {
89 | "type": "string",
90 | "allowedValues": [
91 | "Standard_DS1_v2",
92 | "Standard_DS2_v2",
93 | "Standard_DS3_v2",
94 | "Standard_DS4_v2",
95 | "Standard_DS5_v2",
96 | "Standard_DS11_v2",
97 | "Standard_DS12_v2",
98 | "Standard_DS13_v2",
99 | "Standard_DS14_v2",
100 | "Standard_DS15_v2"
101 | ],
102 | "defaultValue": "Standard_DS2_v2"
103 | },
104 | "buildAgentVmInstanceCount": {
105 | "type": "int",
106 | "defaultValue": 1
107 | }
108 | },
109 | "variables": {
110 | "baseUri": "[deployment().properties.templateLink.uri]",
111 | "coreNetworkTemplateUri": "[uri(variables('baseUri'), '../core-network/azuredeploy.json')]",
112 | "tfsTemplateUri": "[uri(variables('baseUri'), '../tfs/azuredeploy.json')]",
113 | "coreVnetName": "corevnet",
114 | "corevnetDevOpsSubnetName": "Subnet-2",
115 | "vnetid": "[resourceId('Microsoft.Network/virtualNetworks', variables('coreVnetName'))]",
116 | "devopsdubnetid": "[concat(variables('vnetid'), '/subnets/', variables('corevnetDevOpsSubnetName'))]"
117 | },
118 | "resources": [
119 | {
120 | "name": "core-network",
121 | "type": "Microsoft.Resources/deployments",
122 | "apiVersion": "2017-05-10",
123 | "properties": {
124 | "mode": "Incremental",
125 | "templateLink": {
126 | "uri": "[variables('coreNetworkTemplateUri')]",
127 | "contentVersion": "1.0.0.0"
128 | },
129 | "parameters": {
130 | "adminUsername": {
131 | "value": "[parameters('adminUsername')]"
132 | },
133 | "adminPassword": {
134 | "value": "[parameters('adminPassword')]"
135 | },
136 | "domainName": {
137 | "value": "[parameters('domainName')]"
138 | },
139 | "jumpBoxVmSize": {
140 | "value": "[parameters('jumpBoxVmSize')]"
141 | },
142 | "DCVmSize": {
143 | "value": "[parameters('DCVmSize')]"
144 | }
145 | }
146 | }
147 | },
148 | {
149 | "name": "tfs",
150 | "type": "Microsoft.Resources/deployments",
151 | "apiVersion": "2017-05-10",
152 | "dependsOn": [
153 | "Microsoft.Resources/deployments/core-network"
154 | ],
155 | "properties": {
156 | "mode": "Incremental",
157 | "templateLink": {
158 | "uri": "[variables('tfsTemplateUri')]",
159 | "contentVersion": "1.0.0.0"
160 | },
161 | "parameters": {
162 | "subnetId": {
163 | "value": "[variables('devopsdubnetid')]"
164 | },
165 | "adminUsername": {
166 | "value": "[parameters('adminUsername')]"
167 | },
168 | "adminPassword": {
169 | "value": "[parameters('adminPassword')]"
170 | },
171 | "domainName": {
172 | "value": "[parameters('domainName')]"
173 | },
174 | "tfsVmSize": {
175 | "value": "[parameters('tfsVmSize')]"
176 | },
177 | "tfsSqlVmSize": {
178 | "value": "[parameters('tfsSqlVmSize')]"
179 | },
180 | "tfsAgentVmSize": {
181 | "value": "[parameters('tfsAgentVmSize')]"
182 | },
183 | "secrets": {
184 | "value": "[parameters('secrets')]"
185 | },
186 | "sslThumbprint": {
187 | "value": "[parameters('sslThumbprint')]"
188 | },
189 | "buildAgentVmInstanceCount": {
190 | "value": "[parameters('buildAgentVmInstanceCount')]"
191 | }
192 | }
193 | }
194 | }
195 | ],
196 | "outputs": {}
197 | }
198 |
--------------------------------------------------------------------------------
/devnet-tfs/private_devops.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hansenms/iac/277b41b9f9a0ce395f577d8a74640000c91c5ce1/devnet-tfs/private_devops.png
--------------------------------------------------------------------------------
/gadgetron-workstation/README.md:
--------------------------------------------------------------------------------
1 | Gadgetron Workstation
2 | ---------------------
3 |
4 | This template deploys a Linux (Ubuntu 18.04 LTS) development workstation for the [Gadgetron](https://github.com/gadgetron/gadgetron) project. The workstation includes all the dependencies required to compile and run the Gadgetron. Additionally, an [x2go](https://wiki.x2go.org/) server is installed for remote desktop connection.
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/gadgetron-workstation/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "adminUsername": {
6 | "type": "string",
7 | "defaultValue": "azureuser"
8 | },
9 | "sshKeyData": {
10 | "type": "string"
11 | },
12 | "vmSize": {
13 | "type": "string",
14 | "allowedValues": [
15 | "Standard_DS1_v2",
16 | "Standard_DS2_v2",
17 | "Standard_DS3_v2",
18 | "Standard_DS4_v2",
19 | "Standard_DS5_v2",
20 | "Standard_DS11_v2",
21 | "Standard_DS12_v2",
22 | "Standard_DS13_v2",
23 | "Standard_DS14_v2",
24 | "Standard_DS15_v2"
25 | ],
26 | "defaultValue": "Standard_DS2_v2"
27 | },
28 | "vmName": {
29 | "type": "string",
30 | "defaultValue": "GadgetronWorkstation"
31 | },
32 | "assignPublicIp": {
33 | "type": "bool",
34 | "defaultValue": true
35 | },
36 | "subnetId": {
37 | "type": "string",
38 | "defaultValue": "null"
39 | }
40 | },
41 | "variables": {
42 | "baseUri": "[deployment().properties.templateLink.uri]",
43 | "vmTemplateURL": "[uri(variables('baseUri'),'../primitives/linuxvm.json')]",
44 | "gadgetronConfigScriptURL": "[uri(variables('baseUri'), 'scripts/setup_gadgetron_workstation.sh')]",
45 | "gadgetronBootstrapScriptURL": "[uri(variables('baseUri'), 'scripts/bootstrap_gadgetron.sh')]"
46 | },
47 | "resources": [
48 | {
49 | "name": "GadgetronVmDeploy",
50 | "type": "Microsoft.Resources/deployments",
51 | "apiVersion": "2017-05-10",
52 | "properties": {
53 | "mode": "Incremental",
54 | "templateLink": {
55 | "uri": "[variables('vmTemplateURL')]",
56 | "contentVersion": "1.0.0.0"
57 | },
58 | "parameters": {
59 | "vmName": {
60 | "value": "[parameters('vmName')]"
61 | },
62 | "vmSize": {
63 | "value": "[parameters('vmSize')]"
64 | },
65 | "subnetId": {
66 | "value": "[parameters('subnetId')]"
67 | },
68 | "adminUsername": {
69 | "value": "[parameters('adminUsername')]"
70 | },
71 | "sshKeyData": {
72 | "value": "[parameters('sshKeyData')]"
73 | },
74 | "assignPublicIP": {
75 | "value": "[parameters('assignPublicIp')]"
76 | }
77 | }
78 | }
79 | },
80 | {
81 | "type": "Microsoft.Compute/virtualMachines/extensions",
82 | "name": "[concat(parameters('vmName'), '/', '/configuregadgetron')]",
83 | "location": "[resourceGroup().location]",
84 | "apiVersion": "2018-06-01",
85 | "dependsOn": [
86 | "['Microsoft.Resources/deployments/GadgetronVmDeploy']"
87 | ],
88 | "tags": {
89 | "displayName": "gadgetronsetup"
90 | },
91 | "properties": {
92 | "publisher": "Microsoft.Azure.Extensions",
93 | "type": "CustomScript",
94 | "typeHandlerVersion": "2.0",
95 | "autoUpgradeMinorVersion": true,
96 | "settings": {
97 | "fileUris": [
98 | "[variables('gadgetronConfigScriptURL')]",
99 | "[variables('gadgetronBootstrapScriptURL')]"
100 | ],
101 | "commandToExecute": "[concat('bash ./setup_gadgetron_workstation.sh ', parameters('adminUsername'))]"
102 | }
103 | }
104 | }
105 | ]
106 | }
--------------------------------------------------------------------------------
/gadgetron-workstation/scripts/bootstrap_gadgetron.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | mkdir -p ${HOME}/code
4 | mkdir -p ${HOME}/local
5 |
6 | GADGETRON_HOME=${HOME}/local
7 | ISMRMRD_HOME=${HOME}/local
8 |
9 | #ISMRMRD
10 | cd ${HOME}/code
11 | git clone https://github.com/ismrmrd/ismrmrd.git
12 | cd ismrmrd
13 | mkdir build
14 | cd build
15 | cmake -DCMAKE_INSTALL_PREFIX=~/local -DCMAKE_PREFIX_PATH=~/local ../
16 | make -j $(nproc)
17 | make install
18 |
19 | #GADGETRON
20 | cd ${HOME}/code
21 | git clone https://github.com/gadgetron/gadgetron
22 | cd gadgetron
23 | mkdir build
24 | cd build
25 | cmake -DBUILD_WITH_PYTHON3=ON -DCMAKE_INSTALL_PREFIX=~/local -DCMAKE_PREFIX_PATH=~/local ../
26 | make -j $(nproc)
27 | make install
28 | cp ${GADGETRON_HOME}/share/gadgetron/config/gadgetron.xml.example ${GADGETRON_HOME}/share/gadgetron/config/gadgetron.xml
29 |
30 | #ISMRMRD PYTHON API
31 | cd ${HOME}/code
32 | git clone https://github.com/ismrmrd/ismrmrd-python.git
33 | cd ismrmrd-python
34 | python3 setup.py install --user
35 |
36 | #ISMRMRD PYTHON TOOLS
37 | cd ${HOME}/code
38 | git clone https://github.com/ismrmrd/ismrmrd-python-tools.git
39 | cd ismrmrd-python-tools
40 | python3 setup.py install --user
41 |
42 | #SIEMENS_TO_ISMRMRD
43 | cd ${HOME}/code
44 | git clone https://github.com/ismrmrd/siemens_to_ismrmrd.git
45 | cd siemens_to_ismrmrd
46 | mkdir build
47 | cd build
48 | cmake -DCMAKE_INSTALL_PREFIX=${HOME}/local -DCMAKE_PREFIX_PATH=${HOME}/local ../
49 | make -j $(nproc)
50 | make install
51 |
52 | #PHILIPS_TO_ISMRMRD
53 | cd ${HOME}/code
54 | git clone https://github.com/ismrmrd/philips_to_ismrmrd.git
55 | cd philips_to_ismrmrd
56 | mkdir build
57 | cd build
58 | cmake -DCMAKE_INSTALL_PREFIX=${HOME}/local -DCMAKE_PREFIX_PATH=${HOME}/local ../
59 | make -j $(nproc)
60 | make install
61 |
62 | #Setup environment
63 | echo "" >> ${HOME}/.bashrc
64 | echo 'export GADGETRON_HOME=${HOME}/local' >> ${HOME}/.bashrc
65 | echo 'export LD_LIBRARY_PATH=${GADGETRON_HOME}/lib:${LD_LIBRARY_PATH}' >> ${HOME}/.bashrc
66 | echo 'export PATH=${GADGETRON_HOME}/bin:${PATH}' >> ${HOME}/.bashrc
67 |
--------------------------------------------------------------------------------
/gadgetron-workstation/scripts/setup_gadgetron_workstation.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | USERNAME=$1
4 | CURRENT_FOLDER=${PWD}
5 | BOOTSTRAP_SCRIPT="bootstrap_gadgetron.sh"
6 |
7 | apt-get update
8 |
9 | apt-get install --yes software-properties-common apt-utils wget build-essential cython emacs python-dev python-pip python3-dev python3-pip libhdf5-serial-dev cmake git-core libboost-all-dev libfftw3-dev h5utils jq hdf5-tools liblapack-dev libopenblas-base libopenblas-dev libxml2-dev libfreetype6-dev pkg-config libxslt-dev libarmadillo-dev libace-dev gcc-multilib libgtest-dev python3-dev liblapack-dev liblapacke-dev libplplot-dev libdcmtk-dev cmake-curses-gui cmake neofetch net-tools cpio x2goserver ubuntu-mate-core emacs hdfview
10 |
11 | pip3 install --upgrade pip
12 | pip install -U pip setuptools
13 | apt-get install --no-install-recommends --no-install-suggests --yes python3-psutil python3-pyxb python3-lxml python3-numpy
14 | apt-get install --no-install-recommends --no-install-suggests --yes python3-pil
15 | apt-get install --no-install-recommends --no-install-suggests --yes python3-scipy
16 | apt-get install --no-install-recommends --no-install-suggests --yes python3-configargparse
17 | pip install Cython tk-tools matplotlib scikit-image opencv_python pydicom scikit-learn
18 | pip uninstall -y h5py
19 | apt-get install -y python3-h5py
20 | pip install --upgrade tensorflow
21 | pip install http://download.pytorch.org/whl/cpu/torch-0.4.0-cp36-cp36m-linux_x86_64.whl
22 | pip install torchvision
23 | pip install tensorboardx visdom
24 |
25 | mkdir -p
26 | cd /opt && \
27 | git clone https://github.com/hansenms/ZFP.git && \
28 | cd ZFP && \
29 | mkdir lib && \
30 | make && \
31 | make shared && \
32 | make -j $(nproc) install
33 |
34 | cd /opt && \
35 | wget https://github.com/mrirecon/bart/archive/v0.4.03.tar.gz && \
36 | tar -xzf v0.4.03.tar.gz && \
37 | cd bart-0.4.03 && \
38 | make -j $(nproc) && \
39 | ln -s /opt/bart-v0.4.03/bart /usr/local/bin/bart
40 |
41 | apt-get install --yes libgoogle-glog-dev libeigen3-dev libsuitesparse-dev
42 |
43 | cd /opt && \
44 | wget http://ceres-solver.org/ceres-solver-1.14.0.tar.gz && \
45 | tar zxf ceres-solver-1.14.0.tar.gz && \
46 | mkdir ceres-bin && \
47 | cd ceres-bin && \
48 | cmake ../ceres-solver-1.14.0 && \
49 | make -j $(nproc) && \
50 | make install
51 |
52 | cp ${CURRENT_FOLDER}/${BOOTSTRAP_SCRIPT} /home/${USERNAME}/
53 | chown ${USERNAME}:${USERNAME} /home/${USERNAME}/${BOOTSTRAP_SCRIPT}
54 | chmod +x /home/${USERNAME}/${BOOTSTRAP_SCRIPT}
55 | su -c /home/${USERNAME}/${BOOTSTRAP_SCRIPT} ${USERNAME}
--------------------------------------------------------------------------------
/iaas-web/DSC/ConfigureWeb.ps1:
--------------------------------------------------------------------------------
1 | configuration ConfigureWebDsc
2 | {
3 | param
4 | (
5 | )
6 |
7 | # Import the module that defines custom resources
8 | Import-DscResource -Module xWebAdministration, PSDesiredStateConfiguration
9 |
10 | Node localhost
11 | {
12 | WindowsFeature IIS
13 | {
14 | Ensure = 'Present'
15 | Name = 'Web-Server'
16 | }
17 |
18 | WindowsFeature aspnet45
19 | {
20 | Ensure = 'Present'
21 | Name = 'Web-Asp-Net45'
22 | DependsOn = '[WindowsFeature]IIS'
23 | }
24 |
25 | WindowsFeature NetFrameworkFeature
26 | {
27 | Ensure = 'Present'
28 | Name = 'NET-Framework-Features'
29 | DependsOn = '[WindowsFeature]aspnet45'
30 | }
31 |
32 | xWebsite DefaultSite
33 | {
34 | Ensure = 'Present'
35 | Name = 'Default Web Site'
36 | State = 'Started'
37 | PhysicalPath = 'C:\inetpub\wwwroot'
38 | DependsOn = '[WindowsFeature]NetFrameworkFeature'
39 | }
40 |
41 | Script InstallDotNetStuff
42 | {
43 | GetScript = {
44 | return @{ 'Result' = $true }
45 | }
46 |
47 | TestScript = {
48 | Test-Path $env:temp\dotnet-dev-win-x64.1.0.4.exe
49 | }
50 | SetScript = {
51 | # Install the .NET Core SDK
52 | Invoke-WebRequest https://go.microsoft.com/fwlink/?linkid=848827 -outfile $env:temp\dotnet-dev-win-x64.1.0.4.exe
53 | Start-Process $env:temp\dotnet-dev-win-x64.1.0.4.exe -ArgumentList '/quiet' -Wait
54 |
55 | # Install the .NET Core Windows Server Hosting bundle
56 | Invoke-WebRequest https://go.microsoft.com/fwlink/?LinkId=817246 -outfile $env:temp\DotNetCore.WindowsHosting.exe
57 | Start-Process $env:temp\DotNetCore.WindowsHosting.exe -ArgumentList '/quiet' -Wait
58 |
59 | # Restart the web server so that system PATH updates take effect
60 | net stop was /y
61 | net start w3svc
62 | }
63 | DependsOn = "[xWebsite]DefaultSite"
64 | }
65 |
66 | File WebContent
67 | {
68 | Ensure = 'Present'
69 | DestinationPath = 'C:\inetpub\wwwroot\index.html'
70 | Recurse = $true
71 | Type = 'File'
72 | Contents = 'This is the default website
'
73 | DependsOn = '[xWebsite]DefaultSite'
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/iaas-web/DSC/ConfigureWeb.ps1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hansenms/iac/277b41b9f9a0ce395f577d8a74640000c91c5ce1/iaas-web/DSC/ConfigureWeb.ps1.zip
--------------------------------------------------------------------------------
/iaas-web/README.md:
--------------------------------------------------------------------------------
1 | Simple IIS Web Server
2 | ---------------------
3 |
4 | This template deploys a Windows VM and deploys an HTML page in the default web site.
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/iaas-web/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "adminUsername": {
6 | "type": "string",
7 | "defaultValue": "EnterpriseAdmin"
8 | },
9 | "adminPassword": {
10 | "type": "securestring"
11 | },
12 | "vmSize": {
13 | "type": "string",
14 | "allowedValues": [
15 | "Standard_DS1_v2",
16 | "Standard_DS2_v2",
17 | "Standard_DS3_v2",
18 | "Standard_DS4_v2",
19 | "Standard_DS5_v2",
20 | "Standard_DS11_v2",
21 | "Standard_DS12_v2",
22 | "Standard_DS13_v2",
23 | "Standard_DS14_v2",
24 | "Standard_DS15_v2"
25 | ],
26 | "defaultValue": "Standard_DS2_v2"
27 | },
28 | "vmName": {
29 | "type": "string",
30 | "defaultValue": "webServer"
31 | },
32 | "assignPublicIp": {
33 | "type": "bool",
34 | "defaultValue": false
35 | },
36 | "subnetId": {
37 | "type": "string",
38 | "defaultValue": "null"
39 | }
40 | },
41 | "variables": {
42 | "baseUri": "[deployment().properties.templateLink.uri]",
43 | "windowsVmTemplateURL": "[uri(variables('baseUri'),'../primitives/windowsvm.json')]",
44 | "webConfigureModuleURL": "[uri(variables('baseUri'), 'DSC/ConfigureWeb.ps1.zip')]",
45 | "webConfigureFunction": "ConfigureWeb.ps1\\ConfigureWebDsc"
46 | },
47 | "resources": [
48 | {
49 | "name": "WebVmDeploy",
50 | "type": "Microsoft.Resources/deployments",
51 | "apiVersion": "2017-05-10",
52 | "properties": {
53 | "mode": "Incremental",
54 | "templateLink": {
55 | "uri": "[variables('windowsVmTemplateURL')]",
56 | "contentVersion": "1.0.0.0"
57 | },
58 | "parameters": {
59 | "vmName": {
60 | "value": "[parameters('vmName')]"
61 | },
62 | "vmSize": {
63 | "value": "[parameters('vmSize')]"
64 | },
65 | "subnetId": {
66 | "value": "[parameters('subnetId')]"
67 | },
68 | "adminUsername": {
69 | "value": "[parameters('adminUsername')]"
70 | },
71 | "adminPassword": {
72 | "value": "[parameters('adminPassword')]"
73 | },
74 | "assignPublicIP": {
75 | "value": "[parameters('assignPublicIp')]"
76 | }
77 | }
78 | }
79 | },
80 | {
81 | "type": "Microsoft.Compute/virtualMachines/extensions",
82 | "name": "[concat(parameters('vmName'), '/configureagent')]",
83 | "dependsOn": [
84 | "[resourceId('Microsoft.Resources/deployments', 'WebVmDeploy')]"
85 | ],
86 | "apiVersion": "2016-03-30",
87 | "location": "[resourceGroup().location]",
88 | "properties": {
89 | "publisher": "Microsoft.Powershell",
90 | "type": "DSC",
91 | "typeHandlerVersion": "2.21",
92 | "autoUpgradeMinorVersion": true,
93 | "settings": {
94 | "modulesURL": "[variables('webConfigureModuleURL')]",
95 | "configurationFunction": "[variables('webConfigureFunction')]",
96 | "properties": {
97 | }
98 | }
99 | }
100 | }
101 | ]
102 | }
--------------------------------------------------------------------------------
/primitives/encryptvm.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "vmName": {
6 | "type": "string",
7 | "metadata": {
8 | "description": "Name of the virtual machine"
9 | }
10 | },
11 | "aadClientID": {
12 | "type": "string",
13 | "metadata": {
14 | "description": "Client ID of AAD app which has permissions to KeyVault"
15 | }
16 | },
17 | "aadClientSecret": {
18 | "type": "securestring",
19 | "metadata": {
20 | "description": "Client Secret of AAD app which has permissions to KeyVault"
21 | }
22 | },
23 | "keyVaultResourceId": {
24 | "type": "string",
25 | "metadata": {
26 | "description": "Resource ID of the Keyvault"
27 | }
28 | },
29 | "keyEncryptionKeyURL": {
30 | "type": "string",
31 | "defaultValue": "",
32 | "metadata": {
33 | "description": "URL of the KeyEncryptionKey used to encrypt the volume encryption key"
34 | }
35 | },
36 | "volumeType": {
37 | "type": "string",
38 | "defaultValue": "All",
39 | "metadata": {
40 | "description": "Type of the volume OS or Data to perform encryption operation"
41 | }
42 | },
43 | "sequenceVersion": {
44 | "type": "string",
45 | "defaultValue": "1.0",
46 | "metadata": {
47 | "description": "Pass in an unique value like a GUID everytime the operation needs to be force run"
48 | }
49 | }
50 | },
51 | "variables": {
52 | "extensionName": "AzureDiskEncryption",
53 | "extensionVersion": "1.1",
54 | "encryptionOperation": "EnableEncryption",
55 | "keyEncryptionAlgorithm": "RSA-OAEP"
56 | },
57 | "resources": [
58 | {
59 | "type": "Microsoft.Compute/virtualMachines/extensions",
60 | "name": "[concat(parameters('vmName'),'/', variables('extensionName'))]",
61 | "apiVersion": "2016-04-30-preview",
62 | "location": "[resourceGroup().location]",
63 | "properties": {
64 | "publisher": "Microsoft.Azure.Security",
65 | "type": "AzureDiskEncryption",
66 | "typeHandlerVersion": "[variables('extensionVersion')]",
67 | "autoUpgradeMinorVersion": true,
68 | "forceUpdateTag": "[parameters('sequenceVersion')]",
69 | "settings": {
70 | "AADClientID": "[parameters('aadClientID')]",
71 | "KeyVaultURL": "[reference(parameters('keyVaultResourceId'), '2016-10-01').vaultUri]",
72 | "KeyEncryptionKeyURL": "[parameters('keyEncryptionKeyURL')]",
73 | "KeyEncryptionAlgorithm": "[variables('keyEncryptionAlgorithm')]",
74 | "VolumeType": "[parameters('volumeType')]",
75 | "EncryptionOperation": "[variables('encryptionOperation')]"
76 | },
77 | "protectedSettings": {
78 | "AADClientSecret": "[parameters('aadClientSecret')]"
79 | }
80 | }
81 | },
82 | {
83 | "apiVersion": "2016-04-30-preview",
84 | "type": "Microsoft.Compute/virtualMachines",
85 | "name": "[parameters('vmName')]",
86 | "location": "[resourceGroup().location]",
87 | "properties": {
88 | "storageProfile": {
89 | "osDisk": {
90 | "encryptionSettings": {
91 | "diskEncryptionKey": {
92 | "sourceVault": {
93 | "id": "[parameters('keyVaultResourceID')]"
94 | },
95 | "secretUrl": "[reference(resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('vmName'), variables('extensionName'))).instanceView.statuses[0].message]"
96 | },
97 | "keyEncryptionKey": {
98 | "sourceVault": {
99 | "id": "[parameters('keyVaultResourceID')]"
100 | },
101 | "keyUrl": "[parameters('keyEncryptionKeyURL')]"
102 | }
103 | }
104 | }
105 | }
106 | }
107 | }
108 | ],
109 | "outputs": {
110 | "BitLockerKey": {
111 | "type": "string",
112 | "value": "[reference(resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('vmName'), variables('extensionName'))).instanceView.statuses[0].message]"
113 | }
114 | }
115 | }
--------------------------------------------------------------------------------
/primitives/functionapp.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "appName": {
6 | "type": "string",
7 | "metadata": {
8 | "description": "The name of the function app that you wish to create."
9 | }
10 | },
11 | "sku": {
12 | "type": "string",
13 | "allowedValues": [
14 | "Free",
15 | "Shared",
16 | "Basic",
17 | "Standard"
18 | ],
19 | "defaultValue": "Standard",
20 | "metadata": {
21 | "description": "The pricing tier for the hosting plan (if one is needed)."
22 | }
23 | },
24 | "workerSize": {
25 | "type": "string",
26 | "allowedValues": [
27 | "0",
28 | "1",
29 | "2"
30 | ],
31 | "defaultValue": "0",
32 | "metadata": {
33 | "description": "The instance size of the hosting plan (small, medium, or large), if one is needed."
34 | }
35 | },
36 | "appServicePlanId": {
37 | "type": "string",
38 | "defaultValue": "null",
39 | "metadata": {
40 | "description": "Resource ID for the App Service Plan. If null, an App Service Plan will be created."
41 | }
42 | },
43 | "storageAccountType": {
44 | "type": "string",
45 | "defaultValue": "Standard_LRS",
46 | "allowedValues": [
47 | "Standard_LRS",
48 | "Standard_GRS",
49 | "Standard_RAGRS"
50 | ],
51 | "metadata": {
52 | "description": "Storage Account type"
53 | }
54 | }
55 | },
56 | "variables": {
57 | "functionAppName": "[parameters('appName')]",
58 | "hostingPlanName": "[parameters('appName')]",
59 | "genPlanId": "[resourceId('Microsoft.Web/serverFarms/', variables('hostingPlanName'))]",
60 | "hostingPlanId": "[if(equals(parameters('appServicePlanId'),'null'), variables('genPlanId'), parameters('appServicePlanId'))]",
61 | "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'functions')]"
62 | },
63 | "resources": [
64 | {
65 | "type": "Microsoft.Storage/storageAccounts",
66 | "name": "[variables('storageAccountName')]",
67 | "apiVersion": "2017-10-01",
68 | "location": "[resourceGroup().location]",
69 | "kind": "Storage",
70 | "sku": {
71 | "name": "[parameters('storageAccountType')]"
72 | }
73 | },
74 | {
75 | "type": "Microsoft.Web/serverfarms",
76 | "apiVersion": "2016-09-01",
77 | "condition": "[equals(parameters('appServicePlanId'),'null')]",
78 | "name": "[variables('hostingPlanName')]",
79 | "location": "[resourceGroup().location]",
80 | "properties": {
81 | "name": "[variables('hostingPlanName')]",
82 | "sku": "[parameters('sku')]",
83 | "workerSize": "[parameters('workerSize')]",
84 | "hostingEnvironment": "",
85 | "numberOfWorkers": 1
86 | }
87 | },
88 | {
89 | "apiVersion": "2016-08-01",
90 | "type": "Microsoft.Web/sites",
91 | "name": "[variables('functionAppName')]",
92 | "location": "[resourceGroup().location]",
93 | "kind": "functionapp",
94 | "properties": {
95 | "name": "[variables('functionAppName')]",
96 | "serverFarmId": "[variables('hostingPlanId')]",
97 | "hostingEnvironment": "",
98 | "clientAffinityEnabled": false,
99 | "siteConfig": {
100 | "alwaysOn": true,
101 | "cors":{
102 | "allowedOrigins":[
103 | "https://functions.azure.com",
104 | "https://functions-staging.azure.com",
105 | "https://functions-next.azure.com",
106 | "https://functions-usgov-iowa.azurewebsites.us",
107 | "https://functions-usgov-texas.azurewebsites.us",
108 | "https://functions.ext.azure.us"
109 | ]
110 | }
111 | }
112 | },
113 | "dependsOn": [
114 | "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
115 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
116 | ],
117 | "resources": [
118 | {
119 | "apiVersion": "2016-08-01",
120 | "name": "appsettings",
121 | "type": "config",
122 | "dependsOn": [
123 | "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]",
124 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
125 | ],
126 | "properties": {
127 | "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]",
128 | "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listkeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2015-05-01-preview').key1,';')]",
129 | "FUNCTIONS_EXTENSION_VERSION": "~1"
130 | }
131 | }
132 | ]
133 | }
134 | ]
135 | }
--------------------------------------------------------------------------------
/primitives/linuxvm.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "vmName": {
6 | "type": "string"
7 | },
8 | "adminUsername": {
9 | "type": "string",
10 | "defaultValue": "azureuser",
11 | "metadata": {
12 | "description": "User name for the Virtual Machine."
13 | }
14 | },
15 | "sshKeyData": {
16 | "type": "string",
17 | "metadata": {
18 | "description": "SSH rsa public key file as a string."
19 | }
20 | },
21 | "vmSize": {
22 | "type": "string",
23 | "defaultValue": "Standard_D2"
24 | },
25 | "subnetId": {
26 | "type": "string",
27 | "defaultValue": "null"
28 | },
29 | "imagePublisher": {
30 | "type": "string",
31 | "defaultValue": "Canonical"
32 | },
33 | "imageOffer": {
34 | "type": "string",
35 | "defaultValue": "UbuntuServer"
36 | },
37 | "imageSku": {
38 | "type": "string",
39 | "defaultValue": "18.04-LTS"
40 | },
41 | "diagStorageAccountId": {
42 | "type": "string",
43 | "defaultValue": "null"
44 | },
45 | "assignPublicIP": {
46 | "type": "bool",
47 | "defaultValue": false
48 | },
49 | "fixedPrivateIp": {
50 | "type": "string",
51 | "defaultValue": ""
52 | },
53 | "dataDiskSize": {
54 | "type": "int",
55 | "defaultValue": 1023
56 | },
57 | "numberOfDataDisks": {
58 | "type": "int",
59 | "minValue": 0,
60 | "maxValue": 4,
61 | "defaultValue": 1
62 | },
63 | "availabilitySetId": {
64 | "type": "string",
65 | "defaultValue": ""
66 | },
67 | "LoadBalancerBEId": {
68 | "type": "string",
69 | "defaultValue": ""
70 | },
71 | "secrets": {
72 | "type": "array",
73 | "defaultValue": []
74 | }
75 | },
76 | "variables": {
77 | "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]",
78 | "publicipTemplateUrl": "[uri(deployment().properties.templateLink.uri, 'publicip.json')]",
79 | "nicName": "[concat(parameters('vmName'),'-nic')]",
80 | "genDiagStorageAccountName": "[concat('vmdiag', uniqueString( resourceGroup().id, deployment().name ))]",
81 | "genStorageAccountId": "[resourceId('Microsoft.Storage/storageAccounts/', variables('genDiagStorageAccountName'))]",
82 | "genVnetName": "[concat(parameters('vmName'),'-vnet')]",
83 | "diagStorageAccountId": "[if(equals(parameters('diagStorageAccountId'),'null'), variables('genStorageAccountId'), parameters('diagStorageAccountId'))]",
84 | "genVnetid": "[resourceId('Microsoft.Network/virtualNetworks', variables('genVnetName'))]",
85 | "genSubnetId": "[concat(variables('genVnetid'), '/subnets/default')]",
86 | "subnetId": "[if(equals(parameters('subnetId'),'null'), variables('genSubnetId'), parameters('subnetId'))]",
87 | "availabilitySetId": {
88 | "id": "[parameters('availabilitySetId')]"
89 | },
90 | "loadBalancerBEIds": [
91 | {
92 | "id": "[parameters('LoadBalancerBEId')]"
93 | }
94 | ],
95 | "dataDisks": [
96 | {
97 | "diskSizeGB": "[parameters('dataDiskSize')]",
98 | "lun": 0,
99 | "createOption": "Empty"
100 | },
101 | {
102 | "diskSizeGB": "[parameters('dataDiskSize')]",
103 | "lun": 1,
104 | "createOption": "Empty"
105 | },
106 | {
107 | "diskSizeGB": "[parameters('dataDiskSize')]",
108 | "lun": 2,
109 | "createOption": "Empty"
110 | },
111 | {
112 | "diskSizeGB": "[parameters('dataDiskSize')]",
113 | "lun": 3,
114 | "createOption": "Empty"
115 | }
116 | ]
117 | },
118 | "resources": [
119 | {
120 | "type": "Microsoft.Storage/storageAccounts",
121 | "name": "[variables('genDiagStorageAccountName')]",
122 | "apiVersion": "2016-01-01",
123 | "condition": "[equals(parameters('diagStorageAccountId'),'null')]",
124 | "location": "[resourceGroup().location]",
125 | "sku": {
126 | "name": "Standard_LRS"
127 | },
128 | "kind": "Storage",
129 | "properties": {}
130 | },
131 | {
132 | "name": "[variables('genVnetName')]",
133 | "type": "Microsoft.Network/virtualNetworks",
134 | "location": "[resourceGroup().location]",
135 | "apiVersion": "2016-03-30",
136 | "condition": "[equals(parameters('subnetId'),'null')]",
137 | "dependsOn": [],
138 | "properties": {
139 | "addressSpace": {
140 | "addressPrefixes": [
141 | "10.0.0.0/16"
142 | ]
143 | },
144 | "subnets": [
145 | {
146 | "name": "default",
147 | "properties": {
148 | "addressPrefix": "10.0.0.0/24"
149 | }
150 | }
151 | ]
152 | }
153 | },
154 | {
155 | "name": "[variables('nicName')]",
156 | "type": "Microsoft.Network/networkInterfaces",
157 | "location": "[resourceGroup().location]",
158 | "apiVersion": "2016-03-30",
159 | "dependsOn": [
160 | "[variables('genVnetid')]"
161 | ],
162 | "properties": {
163 | "ipConfigurations": [
164 | {
165 | "name": "ipconfig1",
166 | "properties": {
167 | "privateIPAllocationMethod": "[if(empty(parameters('fixedPrivateIp')), 'Dynamic', 'Static')]",
168 | "privateIPAddress": "[parameters('fixedPrivateIp')]",
169 | "subnet": {
170 | "id": "[variables('subnetId')]"
171 | },
172 | "loadBalancerBackendAddressPools": "[if(not(empty(parameters('LoadBalancerBEId'))),variables('loadBalancerBEIds'),json('null'))]"
173 | }
174 | }
175 | ]
176 | }
177 | },
178 | {
179 | "apiVersion": "2017-05-10",
180 | "name": "[concat(parameters('vmName'),'-publicip')]",
181 | "type": "Microsoft.Resources/deployments",
182 | "condition": "[parameters('assignPublicIP')]",
183 | "dependsOn": [
184 | "[resourceId('Microsoft.Compute/virtualMachines',parameters('vmName'))]"
185 | ],
186 | "properties": {
187 | "mode": "Incremental",
188 | "templateLink": {
189 | "uri": "[variables('publicipTemplateUrl')]",
190 | "contentVersion": "1.0.0.0"
191 | },
192 | "parameters": {
193 | "nicName": {
194 | "value": "[variables('nicName')]"
195 | },
196 | "subnetId": {
197 | "value": "[variables('subnetId')]"
198 | }
199 | }
200 | }
201 | },
202 | {
203 | "apiVersion": "2017-03-30",
204 | "type": "Microsoft.Compute/virtualMachines",
205 | "name": "[parameters('vmName')]",
206 | "location": "[resourceGroup().location]",
207 | "dependsOn": [
208 | "[resourceId('Microsoft.Network/networkInterfaces/', variables('nicName'))]",
209 | "[variables('genStorageAccountId')]"
210 | ],
211 | "properties": {
212 | "availabilitySet": "[if(not(empty(parameters('availabilitySetId'))), variables('availabilitySetId'), json('null'))]",
213 | "hardwareProfile": {
214 | "vmSize": "[parameters('vmSize')]"
215 | },
216 | "osProfile": {
217 | "computerName": "[parameters('vmName')]",
218 | "adminUsername": "[parameters('adminUsername')]",
219 | "linuxConfiguration": {
220 | "disablePasswordAuthentication": true,
221 | "ssh": {
222 | "publicKeys": [
223 | {
224 | "path": "[variables('sshKeyPath')]",
225 | "keyData": "[parameters('sshKeyData')]"
226 | }
227 | ]
228 | }
229 | },
230 | "secrets": "[parameters('secrets')]"
231 | },
232 | "storageProfile": {
233 | "imageReference": {
234 | "publisher": "[parameters('imagePublisher')]",
235 | "offer": "[parameters('imageOffer')]",
236 | "sku": "[parameters('imageSku')]",
237 | "version": "latest"
238 | },
239 | "osDisk": {
240 | "createOption": "FromImage"
241 | },
242 | "dataDisks": "[take(variables('dataDisks'),parameters('numberOfDataDisks'))]"
243 | },
244 | "networkProfile": {
245 | "networkInterfaces": [
246 | {
247 | "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
248 | }
249 | ]
250 | },
251 | "diagnosticsProfile": {
252 | "bootDiagnostics": {
253 | "enabled": true,
254 | "storageUri": "[reference(variables('diagStorageAccountId'),'2016-01-01').primaryEndpoints.blob]"
255 | }
256 | }
257 | }
258 | }
259 | ],
260 | "outputs": {
261 | "diagStorageAccountId": {
262 | "type": "string",
263 | "value": "[variables('diagStorageAccountId')]"
264 | },
265 | "subnetId": {
266 | "type": "string",
267 | "value": "[variables('subnetId')]"
268 | }
269 | }
270 | }
--------------------------------------------------------------------------------
/primitives/publicip.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "nicName": {
6 | "type": "string"
7 | },
8 | "ipConfigName": {
9 | "type": "string",
10 | "defaultValue": "ipconfig1"
11 | },
12 | "subnetId": {
13 | "type": "string"
14 | }
15 | },
16 | "variables": {
17 | "publicIpName": "[concat(parameters('nicName'),'-ip')]"
18 | },
19 | "resources": [
20 | {
21 | "name": "[variables('publicIpName')]",
22 | "type": "Microsoft.Network/publicIPAddresses",
23 | "location": "[resourceGroup().location]",
24 | "apiVersion": "2016-03-30",
25 | "properties": {
26 | "publicIPAllocationMethod": "Dynamic"
27 | }
28 | },
29 | {
30 | "name": "[parameters('nicName')]",
31 | "type": "Microsoft.Network/networkInterfaces",
32 | "location": "[resourceGroup().location]",
33 | "dependsOn": [
34 | "[resourceId('Microsoft.Network/publicIpAddresses', variables('publicIpName'))]"
35 | ],
36 | "apiVersion": "2016-03-30",
37 | "properties": {
38 | "ipConfigurations": [
39 | {
40 | "name": "[parameters('ipConfigName')]",
41 | "properties": {
42 | "publicIPAddress": {
43 | "id": "[resourceId('Microsoft.Network/publicIpAddresses', variables('publicIpName'))]"
44 | },
45 | "subnet": {
46 | "id": "[parameters('subnetId')]"
47 | }
48 | }
49 | }
50 | ]
51 | }
52 | }
53 | ]
54 | }
--------------------------------------------------------------------------------
/primitives/vnet.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "vnetName": {
6 | "type": "string",
7 | "defaultValue": "vnet"
8 | },
9 | "addressPrefix": {
10 | "type": "string",
11 | "defaultValue": "10.0.0.0/16"
12 | },
13 | "subnets": {
14 | "type": "array",
15 | "defaultValue": [
16 | {
17 | "name": "default",
18 | "properties" : {
19 | "addressPrefix": "10.0.0.0/24"
20 | }
21 | }
22 | ]
23 | }
24 | },
25 | "resources": [
26 | {
27 | "name": "[parameters('vnetName')]",
28 | "type": "Microsoft.Network/virtualNetworks",
29 | "location": "[resourceGroup().location]",
30 | "apiVersion": "2016-03-30",
31 | "dependsOn": [],
32 | "properties": {
33 | "addressSpace": {
34 | "addressPrefixes": [
35 | "[parameters('addressPrefix')]"
36 | ]
37 | },
38 | "subnets": "[parameters('subnets')]"
39 | }
40 | }
41 | ],
42 | "outputs": {
43 | "subnets": {
44 | "type": "array",
45 | "value": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))).subnets]"
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/primitives/windowsvm.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "vmName": {
6 | "type": "string"
7 | },
8 | "adminUsername": {
9 | "type": "string"
10 | },
11 | "adminPassword": {
12 | "type": "securestring"
13 | },
14 | "vmSize": {
15 | "type": "string",
16 | "defaultValue": "Standard_D2"
17 | },
18 | "subnetId": {
19 | "type": "string",
20 | "defaultValue": "null"
21 | },
22 | "imagePublisher": {
23 | "type": "string",
24 | "defaultValue": "MicrosoftWindowsServer"
25 | },
26 | "imageOffer": {
27 | "type": "string",
28 | "defaultValue": "WindowsServer"
29 | },
30 | "imageSku": {
31 | "type": "string",
32 | "defaultValue": "2016-Datacenter"
33 | },
34 | "diagStorageAccountId": {
35 | "type": "string",
36 | "defaultValue": "null"
37 | },
38 | "assignPublicIP": {
39 | "type": "bool",
40 | "defaultValue": false
41 | },
42 | "fixedPrivateIp": {
43 | "type": "string",
44 | "defaultValue": ""
45 | },
46 | "dataDiskSize": {
47 | "type": "int",
48 | "defaultValue": 1023
49 | },
50 | "numberOfDataDisks": {
51 | "type": "int",
52 | "minValue": 0,
53 | "maxValue": 4,
54 | "defaultValue": 1
55 | },
56 | "availabilitySetId": {
57 | "type": "string",
58 | "defaultValue": ""
59 | },
60 | "LoadBalancerBEId": {
61 | "type": "string",
62 | "defaultValue": ""
63 | },
64 | "secrets": {
65 | "type": "array",
66 | "defaultValue": []
67 | }
68 | },
69 | "variables": {
70 | "publicipTemplateUrl": "[uri(deployment().properties.templateLink.uri, 'publicip.json')]",
71 | "nicName": "[concat(parameters('vmName'),'-nic')]",
72 | "genDiagStorageAccountName": "[concat('vmdiag', uniqueString( resourceGroup().id, deployment().name ))]",
73 | "genStorageAccountId": "[resourceId('Microsoft.Storage/storageAccounts/', variables('genDiagStorageAccountName'))]",
74 | "genVnetName": "[concat(parameters('vmName'),'-vnet')]",
75 | "diagStorageAccountId": "[if(equals(parameters('diagStorageAccountId'),'null'), variables('genStorageAccountId'), parameters('diagStorageAccountId'))]",
76 | "genVnetid": "[resourceId('Microsoft.Network/virtualNetworks', variables('genVnetName'))]",
77 | "genSubnetId": "[concat(variables('genVnetid'), '/subnets/default')]",
78 | "subnetId": "[if(equals(parameters('subnetId'),'null'), variables('genSubnetId'), parameters('subnetId'))]",
79 | "availabilitySetId": {
80 | "id": "[parameters('availabilitySetId')]"
81 | },
82 | "loadBalancerBEIds": [
83 | {
84 | "id": "[parameters('LoadBalancerBEId')]"
85 | }
86 | ],
87 | "dataDisks": [
88 | {
89 | "diskSizeGB": "[parameters('dataDiskSize')]",
90 | "lun": 0,
91 | "createOption": "Empty"
92 | },
93 | {
94 | "diskSizeGB": "[parameters('dataDiskSize')]",
95 | "lun": 1,
96 | "createOption": "Empty"
97 | },
98 | {
99 | "diskSizeGB": "[parameters('dataDiskSize')]",
100 | "lun": 2,
101 | "createOption": "Empty"
102 | },
103 | {
104 | "diskSizeGB": "[parameters('dataDiskSize')]",
105 | "lun": 3,
106 | "createOption": "Empty"
107 | }
108 | ]
109 | },
110 | "resources": [
111 | {
112 | "type": "Microsoft.Storage/storageAccounts",
113 | "name": "[variables('genDiagStorageAccountName')]",
114 | "apiVersion": "2016-01-01",
115 | "condition": "[equals(parameters('diagStorageAccountId'),'null')]",
116 | "location": "[resourceGroup().location]",
117 | "sku": {
118 | "name": "Standard_LRS"
119 | },
120 | "kind": "Storage",
121 | "properties": {}
122 | },
123 | {
124 | "name": "[variables('genVnetName')]",
125 | "type": "Microsoft.Network/virtualNetworks",
126 | "location": "[resourceGroup().location]",
127 | "apiVersion": "2016-03-30",
128 | "condition": "[equals(parameters('subnetId'),'null')]",
129 | "dependsOn": [],
130 | "properties": {
131 | "addressSpace": {
132 | "addressPrefixes": [
133 | "10.0.0.0/16"
134 | ]
135 | },
136 | "subnets": [
137 | {
138 | "name": "default",
139 | "properties" : {
140 | "addressPrefix": "10.0.0.0/24"
141 | }
142 | }
143 | ]
144 | }
145 | },
146 | {
147 | "name": "[variables('nicName')]",
148 | "type": "Microsoft.Network/networkInterfaces",
149 | "location": "[resourceGroup().location]",
150 | "apiVersion": "2016-03-30",
151 | "dependsOn": [
152 | "[variables('genVnetid')]"
153 | ],
154 | "properties": {
155 | "ipConfigurations": [
156 | {
157 | "name": "ipconfig1",
158 | "properties": {
159 | "privateIPAllocationMethod": "[if(empty(parameters('fixedPrivateIp')), 'Dynamic', 'Static')]",
160 | "privateIPAddress": "[parameters('fixedPrivateIp')]",
161 | "subnet": {
162 | "id": "[variables('subnetId')]"
163 | },
164 | "loadBalancerBackendAddressPools": "[if(not(empty(parameters('LoadBalancerBEId'))),variables('loadBalancerBEIds'),json('null'))]"
165 | }
166 | }
167 | ]
168 | }
169 | },
170 | {
171 | "apiVersion": "2017-05-10",
172 | "name": "[concat(parameters('vmName'),'-publicip')]",
173 | "type": "Microsoft.Resources/deployments",
174 | "condition": "[parameters('assignPublicIP')]",
175 | "dependsOn": [
176 | "[resourceId('Microsoft.Compute/virtualMachines',parameters('vmName'))]"
177 | ],
178 | "properties": {
179 | "mode": "Incremental",
180 | "templateLink": {
181 | "uri": "[variables('publicipTemplateUrl')]",
182 | "contentVersion": "1.0.0.0"
183 | },
184 | "parameters": {
185 | "nicName": {
186 | "value": "[variables('nicName')]"
187 | },
188 | "subnetId": {
189 | "value": "[variables('subnetId')]"
190 | }
191 | }
192 | }
193 | },
194 | {
195 | "apiVersion": "2017-03-30",
196 | "type": "Microsoft.Compute/virtualMachines",
197 | "name": "[parameters('vmName')]",
198 | "location": "[resourceGroup().location]",
199 | "dependsOn": [
200 | "[resourceId('Microsoft.Network/networkInterfaces/', variables('nicName'))]",
201 | "[variables('genStorageAccountId')]"
202 | ],
203 | "properties": {
204 | "availabilitySet": "[if(not(empty(parameters('availabilitySetId'))), variables('availabilitySetId'), json('null'))]",
205 | "hardwareProfile": {
206 | "vmSize": "[parameters('vmSize')]"
207 | },
208 | "osProfile": {
209 | "computerName": "[parameters('vmName')]",
210 | "adminUsername": "[parameters('adminUsername')]",
211 | "adminPassword": "[parameters('adminPassword')]",
212 | "secrets": "[parameters('secrets')]"
213 | },
214 | "storageProfile": {
215 | "imageReference": {
216 | "publisher": "[parameters('imagePublisher')]",
217 | "offer": "[parameters('imageOffer')]",
218 | "sku": "[parameters('imageSku')]",
219 | "version": "latest"
220 | },
221 | "osDisk": {
222 | "createOption": "FromImage"
223 | },
224 | "dataDisks": "[take(variables('dataDisks'),parameters('numberOfDataDisks'))]"
225 | },
226 | "networkProfile": {
227 | "networkInterfaces": [
228 | {
229 | "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
230 | }
231 | ]
232 | },
233 | "diagnosticsProfile": {
234 | "bootDiagnostics": {
235 | "enabled": true,
236 | "storageUri": "[reference(variables('diagStorageAccountId'),'2016-01-01').primaryEndpoints.blob]"
237 | }
238 | }
239 | }
240 | }
241 | ],
242 | "outputs": {
243 | "diagStorageAccountId": {
244 | "type": "string",
245 | "value": "[variables('diagStorageAccountId')]"
246 | },
247 | "subnetId": {
248 | "type": "string",
249 | "value": "[variables('subnetId')]"
250 | }
251 | }
252 | }
--------------------------------------------------------------------------------
/scripts/CreateKeyVault.ps1:
--------------------------------------------------------------------------------
1 | param(
2 | [Parameter(Mandatory = $true, Position = 1)]
3 | [String]$ResourceGroupName,
4 |
5 | [Parameter(Mandatory = $true, Position = 2)]
6 | [String]$KeyVaultName
7 | )
8 |
9 | $azcontext = Get-AzureRmContext
10 | if ([string]::IsNullOrEmpty($azcontext.Account)) {
11 | throw "User not logged into Azure."
12 | }
13 |
14 | $rg = Get-AzureRmResourceGroup -Name $ResourceGroupName
15 |
16 | # Create a new AD application
17 | $identifierUri = [string]::Format("http://localhost:8080/{0}", [Guid]::NewGuid().ToString("N"))
18 | $defaultHomePage = 'http://contoso.com'
19 | $now = [System.DateTime]::Now
20 | $oneYearFromNow = $now.AddYears(1)
21 | $aadClientSecret = [System.Convert]::ToBase64String($([guid]::NewGuid()).ToByteArray())
22 | $aadClientPassword = ConvertTo-SecureString -String $aadClientSecret -AsPlainText -Force
23 | $aadAppName = $KeyVaultName + "aadapp"
24 |
25 | $ADApp = New-AzureRmADApplication -DisplayName $aadAppName -HomePage $defaultHomePage -IdentifierUris $identifierUri -StartDate $now -EndDate $oneYearFromNow -Password $aadClientPassword
26 | $servicePrincipal = New-AzureRmADServicePrincipal -ApplicationId $ADApp.ApplicationId
27 | $SvcPrincipals = (Get-AzureRmADServicePrincipal -SearchString $aadAppName)
28 | if (-not $SvcPrincipals) {
29 | # AAD app wasn't created
30 | Write-Error "Failed to create AAD app $aadAppName. Please log-in to Azure using Login-AzureRmAccount and try again";
31 | return;
32 | }
33 | $aadClientID = $servicePrincipal.ApplicationId;
34 |
35 | $kv = New-AzureRmKeyVault -VaultName $KeyVaultName -Location $rg.Location -ResourceGroupName $ResourceGroupName
36 | $kek = Add-AzureKeyVaultKey -VaultName $KeyVaultName -Name "DiskKeyEncryptionKey" -Destination Software
37 |
38 | # Specify privileges to the vault for the AAD application - https://msdn.microsoft.com/en-us/library/mt603625.aspx
39 | Set-AzureRmKeyVaultAccessPolicy -VaultName $KeyVaultName -ServicePrincipalName $aadClientID -PermissionsToKeys wrapKey -PermissionsToSecrets set;
40 | Set-AzureRmKeyVaultAccessPolicy -VaultName $KeyVaultName -EnabledForDiskEncryption -EnabledForDeployment -EnabledForTemplateDeployment
41 |
42 | $keyVaultInfo = @{
43 | "AADClientID" = $aadClientID
44 | "AADClientSecret" = $aadClientSecret
45 | "KeyVaultURL" = $kv.VaultUri
46 | "KeyVaultResourceId" = $kv.ResourceId
47 | "KeyEncryptionKeyURL" = $kek.Key.Kid
48 | }
49 |
50 | return $keyVaultInfo
51 |
--------------------------------------------------------------------------------
/scripts/PrepareAseDeployment.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding(DefaultParameterSetName="nodevops")]
2 | param(
3 | [Parameter(Mandatory=$false)]
4 | [String]$CertificatePath,
5 |
6 | [Parameter(Mandatory=$false)]
7 | [String]$DomainName = "contoso-internal.us",
8 |
9 | [Parameter(Mandatory)]
10 | [securestring]$CertificatePassword,
11 |
12 | [Parameter(Mandatory=$false,ParameterSetName="devops")]
13 | [String]$AdminUsername = "EnterpriseAdmin",
14 |
15 | [Parameter(Mandatory=$true,ParameterSetName="devops")]
16 | [securestring]$AdminPassword,
17 |
18 | [Parameter(Mandatory=$true,ParameterSetName="devops")]
19 | [String]$TSServerUrl,
20 |
21 | [Parameter(Mandatory=$true,ParameterSetName="devops")]
22 | [String]$AgentPool,
23 |
24 | [Parameter(Mandatory=$true,ParameterSetName="devops")]
25 | [String]$PAToken,
26 |
27 | [Parameter(Mandatory=$false)]
28 | [String]$OutFile = ".\azuredeploy.parameters.json"
29 |
30 | )
31 |
32 | #Check if the user is administrator
33 | if (-not [bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")) {
34 | throw "You must have administrator priveleges to run this script."
35 | }
36 |
37 | if ([String]::IsNullOrEmpty($CertificatePath)) {
38 | $CertificatePath = [System.IO.Path]::GetTempFileName()
39 |
40 | $certificate = New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname "*.$DomainName","*.scm.$DomainName"
41 | $certThumbprint = "cert:\localMachine\my\" + $certificate.Thumbprint
42 | Export-PfxCertificate -cert $certThumbprint -FilePath $CertificatePath -Password $CertificatePassword
43 | } else {
44 | $certificate = Import-PfxCertificate -CertStoreLocation Cert:\LocalMachine\My -FilePath $CertificatePath -Password $CertificatePassword
45 | $certThumbprint = "cert:\localMachine\my\" + $certificate.Thumbprint
46 | }
47 |
48 | $fileContentBytes = Get-Content $CertificatePath -Encoding Byte
49 | $pfxBlobString = [System.Convert]::ToBase64String($fileContentBytes)
50 |
51 | $templateParameters = @{
52 | "pfxBlobString" = @{
53 | "value" = $pfxBlobString
54 | }
55 | "certificatePassword" = @{
56 | "value" = (New-Object PSCredential "user", $CertificatePassword).GetNetworkCredential().Password
57 | }
58 | "certificateThumbprint" = @{
59 | "value" = $certificate.Thumbprint
60 | }
61 | "domainName" = @{
62 | "value" = $DomainName
63 | }
64 | }
65 |
66 | if (-not [String]::IsNullOrEmpty($AdminPassword)) {
67 | $templateParameters.Add("AdminUsername", @{ "value" = $AdminUsername})
68 | $templateParameters.Add("AdminPassword", @{ "value" = (New-Object PSCredential "user", $AdminPassword).GetNetworkCredential().Password})
69 | $templateParameters.Add("TSServerUrl", @{ "value" = $TSServerUrl})
70 | $templateParameters.Add("AgentPool", @{ "value" = $AgentPool})
71 | $templateParameters.Add("PAToken", @{ "value" = $PAToken})
72 | }
73 |
74 |
75 | $templateParameters | ConvertTo-Json -Depth 10 | Out-File $OutFile
76 |
77 | Write-Host "Parameters written to $OutFile."
78 |
--------------------------------------------------------------------------------
/scripts/PrepareDevnetTfsDeployment.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding(DefaultParameterSetName="nossl")]
2 | param(
3 | [Parameter(Mandatory)]
4 | [String]$DomainName,
5 |
6 | [Parameter(Mandatory)]
7 | [String]$AdminUsername,
8 |
9 | [Parameter(Mandatory)]
10 | [SecureString]$AdminPassword,
11 |
12 | [Parameter(Mandatory)]
13 | [String]$KeyVaultResourceGroupName,
14 |
15 | [Parameter(Mandatory)]
16 | [String]$KeyVaultName,
17 |
18 | [Parameter(Mandatory=$false,ParameterSetName="ssl")]
19 | [String]$CertificatePath,
20 |
21 | [Parameter(Mandatory,ParameterSetName="ssl")]
22 | [SecureString]$CertificatePassword,
23 |
24 | [Parameter(Mandatory)]
25 | [String]$Location,
26 |
27 | [Parameter(Mandatory=$false)]
28 | [String]$OutFile = ".\azuredeploy.parameters.json"
29 | )
30 |
31 | if (Test-Path $OutFile) {
32 | throw "Output file already exists. Please delete or rename"
33 | }
34 |
35 | #Check if the user is administrator
36 | if (-not [bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")) {
37 | throw "You must have administrator priveleges to run this script."
38 | }
39 |
40 | $azcontext = Get-AzureRmContext
41 | if ([string]::IsNullOrEmpty($azcontext.Account)) {
42 | throw "User not logged into Azure."
43 | }
44 |
45 | #Some settings
46 | $DomainAdminPasswordSecretName = "DomainAdminPassword"
47 | $SslCertificateSecretName = "SslCert"
48 |
49 | $kvrg = New-AzureRmResourceGroup -Name $KeyVaultResourceGroupName -Location $Location
50 |
51 | $kv = New-AzureRmKeyVault -VaultName $KeyVaultName -Location $kvrg.Location -ResourceGroupName $KeyVaultResourceGroupName
52 | Set-AzureRmKeyVaultAccessPolicy -VaultName $KeyVaultName -EnabledForDiskEncryption -EnabledForDeployment -EnabledForTemplateDeployment
53 |
54 | #Store domain password in keyvault.
55 | $passwdsecret = Set-AzureKeyVaultSecret -VaultName $KeyVaultName -Name $DomainAdminPasswordSecretName -SecretValue $AdminPassword
56 |
57 | $secrets = @()
58 | if (-not [String]::IsNullOrEmpty($CertificatePath)) {
59 | #Upload SSL cert to keyvault
60 | $cer = Import-AzureKeyVaultCertificate -VaultName $KeyVaultName -Name $SslCertificateSecretName -FilePath $CertificatePath -Password $CertificatePassword
61 | $secret = @{
62 | "sourceVault" = @{
63 | "id" = $kv.ResourceId
64 | }
65 | "vaultCertificates" = @(
66 | @{
67 | "certificateUrl" = $cer.SecretId
68 | "certificateStore" = "My"
69 | }
70 | )
71 | }
72 | $secrets = @( $secret )
73 | }
74 |
75 |
76 | $templateParameters = @{
77 |
78 | "domainName" = @{
79 | "value" = $DomainName
80 | }
81 |
82 | "adminUsername" = @{
83 | "value" = $AdminUsername
84 | }
85 |
86 | "adminPassword" = @{
87 | "reference" = @{
88 | "keyvault" = @{
89 | "id" = $kv.ResourceId
90 | }
91 | "secretName" = $DomainAdminPasswordSecretName
92 | }
93 | }
94 | }
95 |
96 | if (-not [String]::IsNullOrEmpty($CertificatePath)) {
97 | $templateParameters.Add("secrets", @{ "value" = $secrets})
98 | $templateParameters.Add("sslThumbprint", @{ "value" = $cer.Thumbprint})
99 | }
100 |
101 | $templateParameters | ConvertTo-Json -Depth 10 | Out-File $OutFile
102 |
103 | Write-Host "Parameters written to $OutFile."
104 |
--------------------------------------------------------------------------------
/sql-alwayson/DSC/AddDatabaseAG.ps1:
--------------------------------------------------------------------------------
1 | configuration AddDatabaseAGDsc
2 | {
3 | param
4 | (
5 | [Parameter(Mandatory)]
6 | [String]$DomainName,
7 |
8 | [String]$DomainNetbiosName=(Get-NetBIOSName -DomainName $DomainName),
9 |
10 | [Parameter(Mandatory)]
11 | [String]$SQLServerAG,
12 |
13 | [Parameter(Mandatory)]
14 | [String[]]$SQLDatabases,
15 |
16 | [Parameter(Mandatory=$false)]
17 | [String]$SQLInstanceName = "MSSQLSERVER",
18 |
19 | [Parameter(Mandatory)]
20 | [System.Management.Automation.PSCredential]$Admincreds
21 |
22 | )
23 |
24 | Import-DscResource -ModuleName xComputerManagement, xNetworking, xStorage, SmbShare, xSMBShare, SqlServer, SqlServerDsc, PSDesiredStateConfiguration
25 |
26 | [System.Management.Automation.PSCredential]$DomainCreds = New-Object System.Management.Automation.PSCredential ("${DomainNetbiosName}\$($Admincreds.UserName)", $Admincreds.Password)
27 |
28 |
29 | Node localhost
30 | {
31 |
32 | File BackupDirectory
33 | {
34 | Ensure = "Present"
35 | Type = "Directory"
36 | DestinationPath = "F:\Backup"
37 | }
38 |
39 |
40 | xSMBShare DBBackupShare
41 | {
42 | Name = "DBBackup"
43 | Path = "F:\Backup"
44 | Ensure = "Present"
45 | FullAccess = $DomainCreds.UserName
46 | Description = "Backup share for SQL Server"
47 | DependsOn = "[File]BackupDirectory"
48 | }
49 |
50 | SqlAGDatabase AddDatabaseToAG
51 | {
52 | AvailabilityGroupName = $SQLServerAG
53 | BackupPath = "\\" + $env:COMPUTERNAME + "\DBBackup"
54 | DatabaseName = $SQLDatabases
55 | InstanceName = $SQLInstanceName
56 | ServerName = $env:COMPUTERNAME
57 | Ensure = 'Present'
58 | ProcessOnlyOnActiveNode = $true
59 | PsDscRunAsCredential = $DomainCreds
60 | DependsOn = "[xSMBShare]DBBackupShare"
61 | }
62 |
63 | }
64 |
65 | }
66 |
67 | function Get-NetBIOSName
68 | {
69 | [OutputType([string])]
70 | param(
71 | [string]$DomainName
72 | )
73 |
74 | if ($DomainName.Contains('.')) {
75 | $length=$DomainName.IndexOf('.')
76 | if ( $length -ge 16) {
77 | $length=15
78 | }
79 | return $DomainName.Substring(0,$length)
80 | }
81 | else {
82 | if ($DomainName.Length -gt 15) {
83 | return $DomainName.Substring(0,15)
84 | }
85 | else {
86 | return $DomainName
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/sql-alwayson/DSC/AddDatabaseAG.ps1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hansenms/iac/277b41b9f9a0ce395f577d8a74640000c91c5ce1/sql-alwayson/DSC/AddDatabaseAG.ps1.zip
--------------------------------------------------------------------------------
/sql-alwayson/DSC/PrepareSQLServer.ps1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hansenms/iac/277b41b9f9a0ce395f577d8a74640000c91c5ce1/sql-alwayson/DSC/PrepareSQLServer.ps1.zip
--------------------------------------------------------------------------------
/sql-alwayson/README.md:
--------------------------------------------------------------------------------
1 |
2 | SQL Server (2017) Always On Cluster
3 | -----------------------------------
4 |
5 | This template deploys a SQL Server (2017) Always On Cluster in an existing Virtual Network and Domain. There are several other such templates in the Azure Quickstart Templates archive, but this one has been updated with the latest PowerShell Dsc resources and uses a Cloud Witness instead of a witness VM, thus eliminating one VM from the deployment. In summary the deployment features:
6 |
7 | * SQL Server 2017 Always On
8 | * Cloud Witness (eliminating one VM)
9 | * Updated DSC scripts
10 | * Automated patching using the SQLIaaSAgent.
11 |
12 | The deployment does not install any databases on the servers. In order to add a database to the cluster, you need to:
13 |
14 | 1. Create the database
15 | 2. Do a full backup of the database
16 | 3. Use the Wizard in SSMS as described [here](https://docs.microsoft.com/en-us/sql/database-engine/availability-groups/windows/availability-group-add-a-database#SSMSProcedure)
17 |
18 | The repository includes a Powershell DSC script, called [`AddDatabaseAG.ps1`](AddDatabaseAG.ps1), which illustrates the steps needed to add a database to an availability group and there is also an [example template](adddbtoag.json) that could be used to deploy it. However, it is not possible to deploy multiple DSC script extensions to a VM and since DSC extensions were used to set up the always on cluster, it is not easy to deploy this without first deleting the previous deployment. That being said, the script is useful for capturing the steps needed and you can upload the script to the SQL VM and use it to add the database to the availabilit group.
19 |
20 | To Do List
21 | -----------
22 |
23 | Some items that still need work. Feel free to contribute back:
24 |
25 | * Create service user (domain user) for running SQL services.
26 | * Add more than one replica. This should be a matter of making sure it is appropriately parameterized and doing some testing.
27 | * Parameterize settings
28 | * Patching schedule
29 | * Availability Group settings (synchronous, asynchronous, etc)
30 |
31 |
32 | Deploy
33 | ------
34 |
35 |
36 |
37 |
38 |
39 |
40 |
42 |
--------------------------------------------------------------------------------
/sql-alwayson/adddbtoag.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "vmName": {
6 | "type": "string"
7 | },
8 | "domainName": {
9 | "type": "string"
10 | },
11 | "adminUserName": {
12 | "type": "string"
13 | },
14 | "adminPassword": {
15 | "type": "securestring"
16 | },
17 | "sqlServerAG": {
18 | "type": "string"
19 | },
20 | "sqlDatabases": {
21 | "type": "array"
22 | },
23 | "sqlInstanceName": {
24 | "type": "string",
25 | "defaultValue": "MSSQLSERVER"
26 | }
27 | },
28 | "variables": {
29 | "baseUri": "[deployment().properties.templateLink.uri]",
30 | "moduleURL": "[uri(variables('baseUri'), 'DSC/AddDatabaseAG.ps1.zip')]",
31 | "DscFunction": "AddDatabaseAG.ps1\\AddDatabaseAGDsc",
32 | },
33 | "resources": [
34 | {
35 | "type": "Microsoft.Compute/virtualMachines/extensions",
36 | "name": "[concat(parameters('vmName'), '/adddbtoag')]",
37 | "dependsOn": [
38 | ],
39 | "apiVersion": "2016-03-30",
40 | "location": "[resourceGroup().location]",
41 | "properties": {
42 | "publisher": "Microsoft.Powershell",
43 | "type": "DSC",
44 | "typeHandlerVersion": "2.21",
45 | "autoUpgradeMinorVersion": true,
46 | "settings": {
47 | "modulesURL": "[variables('moduleURL')]",
48 | "configurationFunction": "[variables('DscFunction')]",
49 | "properties": {
50 | "domainName": "[parameters('domainName')]",
51 | "adminCreds": {
52 | "userName": "[parameters('adminUserName')]",
53 | "password": "privateSettingsRef:adminPassword"
54 | },
55 | "SQLServerAG": "[parameters('sqlServerAG')]",
56 | "SQLDatabases": "[parameters('sqlDatabases')]",
57 | "SQLInstanceName": "[parameters('sqlInstanceName')]"
58 | }
59 | },
60 | "protectedSettings": {
61 | "items": {
62 | "adminPassword": "[parameters('adminPassword')]"
63 | }
64 | }
65 | }
66 | }
67 | ]
68 | }
--------------------------------------------------------------------------------
/sql-alwayson/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "subnetId": {
6 | "type": "string"
7 | },
8 | "nodeIpAddresses": {
9 | "type": "array",
10 | "defaultValue": [
11 | "10.0.1.10",
12 | "10.0.1.11"
13 | ]
14 | },
15 | "clusterIpAddress": {
16 | "type": "string",
17 | "defaultValue": "10.0.1.30"
18 | },
19 | "adminUsername": {
20 | "type": "string",
21 | "defaultValue": "EnterpriseAdmin"
22 | },
23 | "adminPassword": {
24 | "type": "securestring"
25 | },
26 | "domainName": {
27 | "type": "string",
28 | "defaultValue": "devnet.contoso.us"
29 | },
30 | "sqlVmSize": {
31 | "type": "string",
32 | "allowedValues": [
33 | "Standard_D1_v2",
34 | "Standard_D2_v2",
35 | "Standard_D3_v2",
36 | "Standard_D4_v2",
37 | "Standard_D5_v2",
38 | "Standard_D11_v2",
39 | "Standard_D12_v2",
40 | "Standard_D13_v2",
41 | "Standard_D14_v2",
42 | "Standard_D15_v2",
43 | "Standard_DS1_v2",
44 | "Standard_DS2_v2",
45 | "Standard_DS3_v2",
46 | "Standard_DS4_v2",
47 | "Standard_DS5_v2",
48 | "Standard_DS11_v2",
49 | "Standard_DS12_v2",
50 | "Standard_DS13_v2",
51 | "Standard_DS14_v2",
52 | "Standard_DS15_v2"
53 | ],
54 | "defaultValue": "Standard_DS2_v2"
55 | },
56 | "sqlVmImagePublisher": {
57 | "type": "string",
58 | "defaultValue": "MicrosoftSqlServer"
59 | },
60 | "sqlVmImageOffer": {
61 | "type": "string",
62 | "defaultValue": "SQL2017-WS2016"
63 | },
64 | "sqlVmImageSku": {
65 | "type": "string",
66 | "defaultValue": "Enterprise"
67 | }
68 | },
69 | "variables": {
70 | "diagStorageAccountName": "[concat(uniquestring(resourceGroup().id), 'sqlvmdiag')]",
71 | "cloudWitnessStorageAccountName": "[concat(uniquestring(resourceGroup().id), 'sqlw')]",
72 | "HASetName": "SqlHASet",
73 | "SqlVmPrefix": "SQL",
74 | "baseUri": "[deployment().properties.templateLink.uri]",
75 | "SQLPrepareModuleURL": "[uri(variables('baseUri'), 'DSC/PrepareSQLServer.ps1.zip')]",
76 | "SQLPrepareFunction": "PrepareSQLServer.ps1\\SQLServerPrepareDsc",
77 | "windowsVmTemplateURL": "[uri(variables('baseUri'),'../primitives/windowsvm.json')]",
78 | "LBName": "SQLLB",
79 | "LBBEName": "[concat(variables('LBName'),'BE')]",
80 | "LBFEName": "[concat(variables('LBName'),'FE')]",
81 | "LBProbeName": "[concat(variables('LBName'),'Probe')]",
82 | "sqlBEAddressPoolID": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('LBName')),'/backendAddressPools/',variables('LBBEName'))]",
83 | "sqllbFEConfigID": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('LBName')),'/frontendIPConfigurations/',variables('LBFEName'))]",
84 | "sqllbProbeID": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('LBName')),'/probes/',variables('LBProbeName'))]"
85 | },
86 | "resources": [
87 | {
88 | "type": "Microsoft.Storage/storageAccounts",
89 | "name": "[variables('diagStorageAccountName')]",
90 | "apiVersion": "2016-01-01",
91 | "location": "[resourceGroup().location]",
92 | "sku": {
93 | "name": "Standard_LRS"
94 | },
95 | "kind": "Storage",
96 | "properties": {}
97 | },
98 | {
99 | "type": "Microsoft.Storage/storageAccounts",
100 | "name": "[variables('cloudWitnessStorageAccountName')]",
101 | "apiVersion": "2016-01-01",
102 | "location": "[resourceGroup().location]",
103 | "sku": {
104 | "name": "Standard_LRS"
105 | },
106 | "kind": "Storage",
107 | "properties": {}
108 | },
109 | {
110 | "name": "[variables('HASetName')]",
111 | "type": "Microsoft.Compute/availabilitySets",
112 | "location": "[resourceGroup().location]",
113 | "apiVersion": "2017-03-30",
114 | "dependsOn": [],
115 | "properties": {
116 | "platformUpdateDomainCount": 2,
117 | "platformFaultDomainCount": 2
118 | },
119 | "sku": {
120 | "name": "Aligned"
121 | }
122 | },
123 | {
124 | "name": "[variables('LBName')]",
125 | "type": "Microsoft.Network/loadBalancers",
126 | "apiVersion": "2017-10-01",
127 | "location": "[resourceGroup().location]",
128 | "properties": {
129 | "frontendIPConfigurations": [
130 | {
131 | "name": "[variables('LBFEName')]",
132 | "properties": {
133 | "privateIPAddress": "[parameters('clusterIpAddress')]",
134 | "privateIPAllocationMethod": "Static",
135 | "subnet": {
136 | "id": "[parameters('subnetId')]"
137 | }
138 | }
139 | }
140 | ],
141 | "backendAddressPools": [
142 | {
143 | "name": "[variables('LBBEName')]"
144 | }
145 | ],
146 | "loadBalancingRules": [
147 | {
148 | "name": "SQLLBRule",
149 | "properties": {
150 | "frontendIPConfiguration": {
151 | "id": "[variables('sqllbFEConfigID')]"
152 | },
153 | "backendAddressPool": {
154 | "id": "[variables('sqlBEAddressPoolID')]"
155 | },
156 | "probe": {
157 | "id": "[variables('sqllbProbeID')]"
158 | },
159 | "frontendPort": 1433,
160 | "backendPort": 1433,
161 | "enableFloatingIP": true,
162 | "idleTimeoutInMinutes": 4,
163 | "protocol": "Tcp",
164 | "loadDistribution": "Default"
165 | }
166 | }
167 | ],
168 | "probes": [
169 | {
170 | "name": "[variables('LBProbeName')]",
171 | "properties": {
172 | "protocol": "Tcp",
173 | "port": 59999,
174 | "intervalInSeconds": 5,
175 | "numberOfProbes": 2
176 | }
177 | }
178 | ]
179 | }
180 | },
181 | {
182 | "name": "[concat('SqlVmDeploy', copyindex())]",
183 | "type": "Microsoft.Resources/deployments",
184 | "apiVersion": "2017-05-10",
185 | "dependsOn": [
186 | "[resourceId('Microsoft.Storage/storageAccounts', variables('diagStorageAccountName'))]",
187 | "[resourceId('Microsoft.Network/loadBalancers', variables('LBName'))]"
188 | ],
189 | "copy": {
190 | "name": "sqlvmloop",
191 | "count": 2
192 | },
193 | "properties": {
194 | "mode": "Incremental",
195 | "templateLink": {
196 | "uri": "[variables('windowsVmTemplateURL')]",
197 | "contentVersion": "1.0.0.0"
198 | },
199 | "parameters": {
200 | "vmName": {
201 | "value": "[concat(variables('SqlVmPrefix'), copyindex())]"
202 | },
203 | "vmSize": {
204 | "value": "[parameters('sqlVmSize')]"
205 | },
206 | "subnetId": {
207 | "value": "[parameters('subnetId')]"
208 | },
209 | "fixedPrivateIp": {
210 | "value": "[parameters('nodeIpAddresses')[copyIndex()]]"
211 | },
212 | "adminUsername": {
213 | "value": "[parameters('adminUsername')]"
214 | },
215 | "adminPassword": {
216 | "value": "[parameters('adminPassword')]"
217 | },
218 | "diagStorageAccountId": {
219 | "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('diagStorageAccountName'))]"
220 | },
221 | "availabilitySetId": {
222 | "value": "[resourceId('Microsoft.Compute/availabilitySets', variables('HASetName'))]"
223 | },
224 | "imagePublisher": {
225 | "value": "[parameters('sqlVmImagePublisher')]"
226 | },
227 | "imageOffer": {
228 | "value": "[parameters('sqlVmImageOffer')]"
229 | },
230 | "imageSku": {
231 | "value": "[parameters('sqlVmImageSku')]"
232 | },
233 | "LoadBalancerBEId": {
234 | "value": "[variables('sqlBEAddressPoolID')]"
235 | }
236 | }
237 | }
238 | },
239 | {
240 | "apiVersion": "2015-06-15",
241 | "type": "Microsoft.Compute/virtualMachines/extensions",
242 | "name": "[concat(variables('SqlVmPrefix'), copyindex(), '/SqlIaasExtension')]",
243 | "location": "[resourceGroup().location]",
244 | "dependsOn": [
245 | "[resourceId('Microsoft.Resources/deployments', concat('SqlVmDeploy', copyindex()))]"
246 | ],
247 | "copy": {
248 | "name": "sqliaasextensionsloop",
249 | "count": 2
250 | },
251 | "properties": {
252 | "type": "SqlIaaSAgent",
253 | "publisher": "Microsoft.SqlServer.Management",
254 | "typeHandlerVersion": "1.2",
255 | "autoUpgradeMinorVersion": "true",
256 | "settings": {
257 | "AutoTelemetrySettings": {
258 | "Region": "[resourceGroup().location]"
259 | },
260 | "AutoPatchingSettings": {
261 | "PatchCategory": "WindowsMandatoryUpdates",
262 | "Enable": true,
263 | "DayOfWeek": "Sunday",
264 | "MaintenanceWindowStartingHour": "2",
265 | "MaintenanceWindowDuration": "60"
266 | },
267 | "KeyVaultCredentialSettings": {
268 | "Enable": false,
269 | "CredentialName": ""
270 | },
271 | "ServerConfigurationsManagementSettings": {
272 | "SQLConnectivityUpdateSettings": {
273 | "ConnectivityType": "Private",
274 | "Port": "1433"
275 | },
276 | "SQLWorkloadTypeUpdateSettings": {
277 | "SQLWorkloadType": "GENERAL"
278 | },
279 | "SQLStorageUpdateSettings": {
280 | "DiskCount": "1",
281 | "NumberOfColumns": "1",
282 | "StartingDeviceID": "2",
283 | "DiskConfigurationType": "NEW"
284 | },
285 | "AdditionalFeaturesServerConfigurations": {
286 | "IsRServicesEnabled": "false"
287 | }
288 | }
289 | },
290 | "protectedSettings": {}
291 | }
292 | },
293 | {
294 | "apiVersion": "2015-06-15",
295 | "type": "Microsoft.Compute/virtualMachines/extensions",
296 | "name": "[concat(variables('SqlVmPrefix'), copyindex(), '/joindomain')]",
297 | "location": "[resourceGroup().location]",
298 | "dependsOn": [
299 | "[concat(resourceId('Microsoft.Compute/virtualMachines', concat(variables('SqlVmPrefix'), copyindex())), '/extensions/SqlIaasExtension')]"
300 | ],
301 | "copy": {
302 | "name": "sqlvmjoinloop",
303 | "count": 2
304 | },
305 | "properties": {
306 | "publisher": "Microsoft.Compute",
307 | "type": "JsonADDomainExtension",
308 | "typeHandlerVersion": "1.3",
309 | "autoUpgradeMinorVersion": true,
310 | "settings": {
311 | "Name": "[parameters('domainName')]",
312 | "OUPath": "",
313 | "User": "[concat(parameters('domainName'), '\\', parameters('adminUserName'))]",
314 | "Restart": "true",
315 | "Options": "3"
316 | },
317 | "protectedSettings": {
318 | "Password": "[parameters('adminPassword')]"
319 | }
320 | }
321 | },
322 | {
323 | "type": "Microsoft.Compute/virtualMachines/extensions",
324 | "name": "[concat(variables('SqlVmPrefix'), copyindex(), '/configuresql', copyindex())]",
325 | "dependsOn": [
326 | "[concat(resourceId('Microsoft.Compute/virtualMachines', concat(variables('SqlVmPrefix'), copyindex())), '/extensions/joindomain')]",
327 | "[resourceId('Microsoft.Storage/storageAccounts', variables('diagStorageAccountName'))]"
328 | ],
329 | "apiVersion": "2016-03-30",
330 | "copy": {
331 | "name": "sqlconfigloop",
332 | "count": 2,
333 | "mode": "Serial",
334 | "batchSize": 1
335 | },
336 | "location": "[resourceGroup().location]",
337 | "properties": {
338 | "publisher": "Microsoft.Powershell",
339 | "type": "DSC",
340 | "typeHandlerVersion": "2.21",
341 | "autoUpgradeMinorVersion": true,
342 | "settings": {
343 | "modulesURL": "[variables('SQLPrepareModuleURL')]",
344 | "configurationFunction": "[variables('SQLPrepareFunction')]",
345 | "properties": {
346 | "domainName": "[parameters('domainName')]",
347 | "adminCreds": {
348 | "userName": "[parameters('adminUserName')]",
349 | "password": "privateSettingsRef:adminPassword"
350 | },
351 | "ClusterName": "SQLClusterAG",
352 | "ClusterOwnerNode": "[concat(variables('SqlVmPrefix'), '0')]",
353 | "ClusterIP": "[parameters('clusterIpAddress')]",
354 | "witnessStorageBlobEndpoint": "[reference(variables('cloudWitnessStorageAccountName'), '2016-01-01').primaryEndpoints.blob]",
355 | "witnessStorageAccountKey": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('cloudWitnessStorageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value]"
356 | }
357 | },
358 | "protectedSettings": {
359 | "items": {
360 | "adminPassword": "[parameters('adminPassword')]"
361 | }
362 | }
363 | }
364 | }
365 | ],
366 | "outputs": {}
367 | }
--------------------------------------------------------------------------------
/tfs-ha/DSC/InstallTFS.ps1:
--------------------------------------------------------------------------------
1 | configuration TFSInstallDsc
2 | {
3 | param
4 | (
5 | [Parameter(Mandatory)]
6 | [string]$DomainName,
7 |
8 | [Parameter(Mandatory)]
9 | [System.Management.Automation.PSCredential]$Admincreds,
10 |
11 | [Parameter(Mandatory)]
12 | [String]$SqlServerInstance,
13 |
14 | [Parameter(Mandatory)]
15 | [String]$primaryInstance,
16 |
17 | [Parameter(Mandatory=$true)]
18 | [String]$GlobalSiteIP,
19 |
20 | [Parameter(Mandatory=$false)]
21 | [String]$GlobalSiteName = "TFS",
22 |
23 | [Parameter(Mandatory=$false)]
24 | [String]$DnsServer = "DC1",
25 |
26 | [Parameter(Mandatory=$false)]
27 | [String]$ProbePort = '59999',
28 |
29 | [Parameter(Mandatory=$false)]
30 | [String]$SslThumbprint = "generate",
31 |
32 | [Parameter(Mandatory=$false)]
33 | [ValidateSet("TFS2018", "TFS2017Update3","TFS2017Update2")]
34 | [String]$TFSVersion = "TFS2018"
35 | )
36 |
37 | [System.Management.Automation.PSCredential ]$DomainCreds = New-Object System.Management.Automation.PSCredential ("${DomainName}\$($Admincreds.UserName)", $Admincreds.Password)
38 |
39 | Import-DscResource -ModuleName xStorage, xPendingReboot, xDnsServer, xWebAdministration, xNetworking, 'PSDesiredStateConfiguration'
40 |
41 | <#
42 | Download links for TFS:
43 |
44 | 2017Update2: https://go.microsoft.com/fwlink/?LinkId=850949
45 | 2017Update3: https://go.microsoft.com/fwlink/?LinkId=857134
46 | 2018: https://go.microsoft.com/fwlink/?LinkId=856344
47 | #>
48 |
49 | $TFSDownloadLinks = @{
50 | "TFS2018" = "https://go.microsoft.com/fwlink/?LinkId=856344"
51 | "TFS2017Update2" = "https://go.microsoft.com/fwlink/?LinkId=850949"
52 | "TFS2017Update3" = "https://go.microsoft.com/fwlink/?LinkId=857134"
53 | }
54 |
55 | $currentDownloadLink = $TFSDownloadLinks[$TFSVersion]
56 | $installerDownload = $env:TEMP + "\tfs_installer.exe"
57 | $isTFS2017 = $false
58 | $hostName = $env:COMPUTERNAME
59 |
60 | $isPrimaryInstance = $primaryInstance -eq $env:COMPUTERNAME
61 |
62 | if ($TFSVersion.Substring(0,7) -eq "TFS2017") {
63 | $isTFS2017 = $true
64 | }
65 |
66 | $TfsConfigExe = "C:\Program Files\Microsoft Team Foundation Server 2018\Tools\TfsConfig.exe"
67 |
68 | if ($isTFS2017) {
69 | $TfsConfigExe = "C:\Program Files\Microsoft Team Foundation Server 15.0\Tools\TfsConfig.exe"
70 | }
71 |
72 | Node localhost
73 | {
74 |
75 | WindowsFeature ADPS
76 | {
77 | Name = "RSAT-AD-PowerShell"
78 | Ensure = "Present"
79 | }
80 |
81 |
82 | WindowsFeature DNSServer
83 | {
84 | Name = "RSAT-DNS-Server"
85 | Ensure = "Present"
86 | DependsOn = "[WindowsFeature]ADPS"
87 | }
88 |
89 | WindowsFeature IISPresent
90 | {
91 | Ensure = "Present"
92 | Name = "Web-Server"
93 | }
94 |
95 | xWebsite DefaultSite
96 | {
97 | Ensure = 'Present'
98 | Name = 'Default Web Site'
99 | State = 'Stopped'
100 | PhysicalPath = 'C:\inetpub\wwwroot'
101 | DependsOn = '[WindowsFeature]IISPresent'
102 | }
103 |
104 | xWaitforDisk Disk2
105 | {
106 | DiskId = 2
107 | RetryIntervalSec =$RetryIntervalSec
108 | RetryCount = $RetryCount
109 | DependsOn = "[WindowsFeature]DNSServer"
110 | }
111 |
112 | xDisk ADDataDisk
113 | {
114 | DiskId = 2
115 | DriveLetter = "F"
116 | DependsOn = "[xWaitForDisk]Disk2"
117 | }
118 |
119 | Script DownloadTFS
120 | {
121 | GetScript = {
122 | return @{ 'Result' = $true }
123 | }
124 | SetScript = {
125 | Write-Host "Downloading TFS: " + $using:currentDownloadLink
126 | Invoke-WebRequest -Uri $using:currentDownloadLink -OutFile $using:installerDownload
127 | }
128 | TestScript = {
129 | Test-Path $using:installerDownload
130 | }
131 | DependsOn = "[xDisk]ADDataDisk"
132 | }
133 |
134 | Script InstallTFS
135 | {
136 | GetScript = {
137 | return @{ 'Result' = $true }
138 | }
139 | SetScript = {
140 | Write-Verbose "Install TFS..."
141 |
142 | $cmd = $using:installerDownload + " /full /quiet /Log $env:TEMP\tfs_install_log.txt"
143 | Write-Verbose "Command to run: $cmd"
144 | Invoke-Expression $cmd | Write-Verbose
145 |
146 |
147 | #Sleep for 10 seconds to make sure installer is going
148 | Start-Sleep -s 10
149 |
150 | #The tfs installer will per default run in the background. We will wait for it.
151 | Wait-Process -Name "tfs_installer"
152 | }
153 | TestScript = {
154 | Test-Path $using:TfsConfigExe
155 | }
156 | DependsOn = "[Script]DownloadTFS"
157 | }
158 |
159 |
160 | xPendingReboot PostInstallReboot {
161 | Name = "Check for a pending reboot before changing anything"
162 | DependsOn = "[Script]InstallTFS"
163 | }
164 |
165 | LocalConfigurationManager{
166 | RebootNodeIfNeeded = $True
167 | }
168 |
169 | Script ConfigureTFS
170 | {
171 | GetScript = {
172 | return @{ 'Result' = $true }
173 | }
174 | SetScript = {
175 | $siteBindings = "https:*:443:" + $using:hostName + "." + $using:DomainName + ":My:" + $using:SslThumbprint
176 |
177 | if ($using:hostName -ne $using:GlobalSiteName) {
178 | $siteBindings += ",https:*:443:" + $using:GlobalSiteName + "." + $using:DomainName + ":My:" + $using:SslThumbprint
179 | }
180 |
181 | $siteBindings += ",http:*:80:"
182 |
183 | $publicUrl = "http://$using:hostName"
184 |
185 | $cmd = ""
186 | if ($using:isPrimaryInstance) {
187 | $cmd = "& '$using:TfsConfigExe' unattend /configure /continue /type:NewServerAdvanced /inputs:WebSiteVDirName=';'PublicUrl=$publicUrl';'SqlInstance=$using:SqlServerInstance';'SiteBindings='$siteBindings'"
188 | } else {
189 | $cmd = "& '$using:TfsConfigExe' unattend /configure /continue /type:ApplicationTierOnlyAdvanced /inputs:WebSiteVDirName=';'PublicUrl=$publicUrl';'SqlInstance=$using:SqlServerInstance';'SiteBindings='$siteBindings'"
190 | }
191 |
192 | Write-Verbose "$cmd"
193 | Invoke-Expression $cmd | Write-Verbose
194 |
195 | $publicUrl = "https://$using:GlobalSiteName" + "." + $using:DomainName
196 | $cmd = "& '$using:TfsConfigExe' settings /publicUrl:$publicUrl"
197 | Write-Verbose "$cmd"
198 | Invoke-Expression $cmd | Write-Verbose
199 |
200 | }
201 | TestScript = {
202 | $sites = Get-WebBinding | Where-Object {$_.bindingInformation -like "*$using:GlobalSiteName*" }
203 | -not [String]::IsNullOrEmpty($sites)
204 | }
205 | DependsOn = "[xPendingReboot]PostInstallReboot","[xWebsite]DefaultSite"
206 | PsDscRunAsCredential = $DomainCreds
207 | }
208 |
209 | xDnsRecord GlobalDNS
210 | {
211 | Name = $GlobalSiteName
212 | Target = $GlobalSiteIP
213 | Type = "ARecord"
214 | Zone = $DomainName
215 | DependsOn = "[Script]ConfigureTFS"
216 | DnsServer = $DnsServer
217 | PsDscRunAsCredential = $DomainCreds
218 | }
219 |
220 | xWebsite ProbeWebSite
221 | {
222 | Ensure = 'Present'
223 | Name = 'Probe Web Site'
224 | State = 'Started'
225 | PhysicalPath = 'C:\inetpub\wwwroot'
226 | BindingInfo = MSFT_xWebBindingInformation
227 | {
228 | Protocol = 'http'
229 | Port = $ProbePort
230 | IPAddress = '*'
231 | }
232 | DependsOn = '[xDnsRecord]GlobalDNS'
233 | }
234 |
235 | xFirewall DatabaseEngineFirewallRule
236 | {
237 | Direction = "Inbound"
238 | Name = "IIS Probe"
239 | DisplayName = "IIS Probe"
240 | Group = "TFS"
241 | Enabled = "True"
242 | Protocol = "TCP"
243 | LocalPort = $ProbePort
244 | Ensure = "Present"
245 | DependsOn = '[xWebsite]ProbeWebSite'
246 | }
247 |
248 | Script Reboot
249 | {
250 | TestScript = {
251 | return (Test-Path HKLM:\SOFTWARE\MyMainKey\RebootKey)
252 | }
253 | SetScript = {
254 | New-Item -Path HKLM:\SOFTWARE\MyMainKey\RebootKey -Force
255 | $global:DSCMachineStatus = 1
256 |
257 | }
258 | GetScript = { return @{result = 'result'}}
259 | DependsOn = '[xFirewall]DatabaseEngineFirewallRule'
260 | }
261 |
262 | xPendingReboot PostConfigReboot {
263 | Name = "Check for a pending reboot before changing anything"
264 | DependsOn = "[Script]Reboot"
265 | }
266 | }
267 | }
--------------------------------------------------------------------------------
/tfs-ha/DSC/InstallTFS.ps1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hansenms/iac/277b41b9f9a0ce395f577d8a74640000c91c5ce1/tfs-ha/DSC/InstallTFS.ps1.zip
--------------------------------------------------------------------------------
/tfs-ha/DSC/InstallVSTSAgent.ps1:
--------------------------------------------------------------------------------
1 | configuration VSTSAgentInstallDsc
2 | {
3 | param
4 | (
5 | [Parameter(Mandatory)]
6 | [string]$DomainName,
7 |
8 | [Parameter(Mandatory)]
9 | [System.Management.Automation.PSCredential]$Admincreds,
10 |
11 | [Parameter(Mandatory=$false)]
12 | [String]$TFSUrl = "https://TFS." + $DomainName,
13 |
14 | [Parameter(Mandatory=$false)]
15 | [String]$VSTSAgentUrl = "https://vstsagentpackage.azureedge.net/agent/2.127.0/vsts-agent-win-x64-2.127.0.zip"
16 | )
17 |
18 | [System.Management.Automation.PSCredential ]$DomainCreds = New-Object System.Management.Automation.PSCredential ("${DomainName}\$($Admincreds.UserName)", $Admincreds.Password)
19 |
20 | Import-DscResource -ModuleName 'PSDesiredStateConfiguration'
21 |
22 | Node localhost
23 | {
24 | Script DownloadAgent
25 | {
26 | GetScript = {
27 | return @{ 'Result' = $true }
28 | }
29 | SetScript = {
30 | $agentUrl = $using:VSTSAgentUrl
31 | $agentZip = "$env:TEMP" + "\vsts_agent.zip"
32 | Write-Host "Downloading TFS: $agentUrl"
33 | Invoke-WebRequest -Uri $agentUrl -OutFile $agentZip
34 | }
35 | TestScript = {
36 | $agentZip = "$env:TEMP" + "\vsts_agent.zip"
37 | Test-Path $agentZip
38 | }
39 | }
40 |
41 |
42 | Script UnzipAgent
43 | {
44 | GetScript = {
45 | return @{ 'Result' = $true }
46 | }
47 | SetScript = {
48 | $agentZip = "$env:TEMP" + "\vsts_agent.zip"
49 | $agentPath = "C:\agent"
50 | If(!(Test-Path $agentPath))
51 | {
52 | New-Item -ItemType Directory -Force -Path $agentPath
53 | }
54 |
55 | $shell = New-Object -com shell.application
56 | $zip = $shell.NameSpace($agentZip)
57 | Foreach($item in $zip.items())
58 | {
59 | $shell.Namespace($agentPath).copyhere($item)
60 | }
61 | }
62 | TestScript = {
63 | Test-Path "C:\agent\config.cmd"
64 | }
65 | DependsOn = "[Script]DownloadAgent"
66 | }
67 |
68 |
69 | Script ConfigAgent
70 | {
71 | GetScript = { return @{ 'Result' = $true }}
72 | SetScript = {
73 | Set-Location "C:\agent"
74 | $tfsurl = $using:TFSUrl
75 | $cmd = ".\config.cmd --unattended --runAsService --work _work --url $tfsurl --auth integrated --pool default --agent $env:COMPUTERNAME"
76 | Invoke-Expression $cmd | Write-Verbose
77 | }
78 | TestScript = {
79 | Test-Path "C:\agent\.credentials"
80 | }
81 | DependsOn = "[Script]UnzipAgent"
82 | PsDscRunAsCredential = $DomainCreds
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/tfs-ha/DSC/InstallVSTSAgent.ps1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hansenms/iac/277b41b9f9a0ce395f577d8a74640000c91c5ce1/tfs-ha/DSC/InstallVSTSAgent.ps1.zip
--------------------------------------------------------------------------------
/tfs-ha/README.md:
--------------------------------------------------------------------------------
1 | High Availability Team Foundation Server
2 | ----------------------------------------
3 |
4 | This folder contains two templates, [`azuredeploy.json`](azuredeploy.json) will deploy a high-availability version of the Team Foundation Server and [`deployagents.json`](deployagents.json) can be used to deploy build agents. The templates assume that you already have established some network infrastructure with domain controller(s). You can use the [core-network](../core-network) template to deploy that. You also need a SQL Server, for the high-availability deployment, you can use the [sql-alwayson](../sql-alwayson) template.
5 |
6 |
7 | Since TFS establishes a web endpoint on the TFS servers, it is recommended that you secure that with SSL certificates. If you do not provide a certificate, a self-signed one will be generated. You can replace it after installation too. The way to provide it during deployment is through the `secrets` and `sslThumbprint` parameters. The `secrets` parameter is used to provide an array of certificates from [Azure Key Vault](https://azure.microsoft.com/en-us/services/key-vault/). The `sslThumbprint` parameter instructs the TFS installation script on which certificate to install. The repository includes a [convenience script](../scripts/PrepareDevnetTfsDeployment.ps1), which you can use to create a key vault to hold domain admin password and the SSL certificate. This script will create the key vault, upload all secrets, and generate a deployment parameters file with the details needed for deployment.
8 |
9 | If you are looking for a complete template deployment of networking, SQL server, TFS, and agents, please use the [devnet-tfs-ha](../devnet-tfs-ha) template.
10 |
11 | Deploy
12 | ------
13 |
14 | Deploy this solution to either Azure Commercial or Azure Government:
15 |
16 |
17 |
18 |
19 |
20 |
21 |
23 |
24 | Post Deployment Steps
25 | ----------------------
26 |
27 | * Add databases to availability group
28 | * Log into the first database node in the SQL cluster.
29 | * Start SQL Server Management Studio (SSMS).
30 | * Backup the databases "Tfs_Configuration" and "Tfs_DefaultCollection"
31 | * Make a share that is accessible from all database nodes in the cluster, e.g. `F:\Backup`
32 | * In SSMS find "Availability Databases" by expanded a few levels under "Always On High Availability", right-click and choose "Add Databases"
33 | * Add the two TFS databases and follow the wizard. Choose "Full Database and Log Backup" option and point to the share you created.
34 | * Run the Wizard.
35 | * Install build agents.
36 | *Agents are not included in the base template, but you can use the template below if you would just like generic Windows agents.
37 |
38 | Deploy Build Agents
39 | -------------------
40 |
41 | Deploying build agents is not part of the high availability template itself, since they are likely to require specific configuration related to the workloads they are to support. However, you can use the `deployagents.json` template as inspiration or if you simply want some Windows build agents with Visual Studio installed, deploy build agents:
42 |
43 |
44 |
45 |
46 |
47 |
48 |
50 |
51 | _Note_ : If you are experiencing problems with registering build agents. Specifically, an error message like `TF400813: Resource not available for anonymous access. Client authentication required.`, reboot the TFS nodes and try again.
--------------------------------------------------------------------------------
/tfs-ha/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "subnetId": {
6 | "type": "string"
7 | },
8 | "adminPassword": {
9 | "type": "securestring"
10 | },
11 | "adminUsername": {
12 | "type": "string",
13 | "defaultValue": "EnterpriseAdmin"
14 | },
15 | "sqlServerName": {
16 | "type": "string",
17 | "defaultValue": "SQLClusterAG"
18 | },
19 | "domainName": {
20 | "type": "string",
21 | "defaultValue": "devnet.contoso.us"
22 | },
23 | "vmSize": {
24 | "type": "string",
25 | "allowedValues": [
26 | "Standard_D1_v2",
27 | "Standard_D2_v2",
28 | "Standard_D3_v2",
29 | "Standard_D4_v2",
30 | "Standard_D5_v2",
31 | "Standard_D11_v2",
32 | "Standard_D12_v2",
33 | "Standard_D13_v2",
34 | "Standard_D14_v2",
35 | "Standard_D15_v2",
36 | "Standard_DS1_v2",
37 | "Standard_DS2_v2",
38 | "Standard_DS3_v2",
39 | "Standard_DS4_v2",
40 | "Standard_DS5_v2",
41 | "Standard_DS11_v2",
42 | "Standard_DS12_v2",
43 | "Standard_DS13_v2",
44 | "Standard_DS14_v2",
45 | "Standard_DS15_v2"
46 | ],
47 | "defaultValue": "Standard_DS2_v2"
48 | },
49 | "imageSku": {
50 | "type": "string",
51 | "defaultValue": "2016-Datacenter"
52 | },
53 | "tfsVmInstanceCount": {
54 | "type": "int",
55 | "defaultValue": 2
56 | },
57 | "LBIpAddress": {
58 | "type": "string",
59 | "defaultValue": "10.0.1.40"
60 | },
61 | "secrets": {
62 | "type": "array",
63 | "defaultValue": []
64 | },
65 | "sslThumbprint": {
66 | "type": "string",
67 | "defaultValue": "generate"
68 | }
69 | },
70 | "variables": {
71 | "baseUri": "[deployment().properties.templateLink.uri]",
72 | "TFSInstallModuleURL": "[uri(variables('baseUri'), 'DSC/InstallTFS.ps1.zip')]",
73 | "TFSInstallFunction": "InstallTFS.ps1\\TFSInstallDsc",
74 | "windowsVmTemplateURL": "[uri(variables('baseUri'),'../primitives/windowsvm.json')]",
75 | "diagStorageAccountName": "[concat(uniquestring(resourceGroup().id), 'tfsvmdiag')]",
76 | "HASetName": "TFSHASet",
77 | "tfsVMPrefix": "TFS",
78 | "LBName": "TFSLB",
79 | "LBBEName": "[concat(variables('LBName'),'BE')]",
80 | "LBFEName": "[concat(variables('LBName'),'FE')]",
81 | "LBProbeName": "[concat(variables('LBName'),'Probe')]",
82 | "LBBEAddressPoolID": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('LBName')),'/backendAddressPools/',variables('LBBEName'))]",
83 | "LBFEConfigID": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('LBName')),'/frontendIPConfigurations/',variables('LBFEName'))]",
84 | "LBProbeID": "[concat(resourceId('Microsoft.Network/loadBalancers',variables('LBName')),'/probes/',variables('LBProbeName'))]",
85 | "globalSiteName": "TFS"
86 | },
87 | "resources": [
88 | {
89 | "type": "Microsoft.Storage/storageAccounts",
90 | "name": "[variables('diagStorageAccountName')]",
91 | "apiVersion": "2016-01-01",
92 | "location": "[resourceGroup().location]",
93 | "sku": {
94 | "name": "Standard_LRS"
95 | },
96 | "kind": "Storage",
97 | "properties": {}
98 | },
99 | {
100 | "name": "[variables('HASetName')]",
101 | "type": "Microsoft.Compute/availabilitySets",
102 | "location": "[resourceGroup().location]",
103 | "apiVersion": "2017-03-30",
104 | "dependsOn": [],
105 | "properties": {
106 | "platformUpdateDomainCount": 2,
107 | "platformFaultDomainCount": 2
108 | },
109 | "sku": {
110 | "name": "Aligned"
111 | }
112 | },
113 | {
114 | "name": "[variables('LBName')]",
115 | "type": "Microsoft.Network/loadBalancers",
116 | "apiVersion": "2017-10-01",
117 | "location": "[resourceGroup().location]",
118 | "properties": {
119 | "frontendIPConfigurations": [
120 | {
121 | "name": "[variables('LBFEName')]",
122 | "properties": {
123 | "privateIPAddress": "[parameters('LBIpAddress')]",
124 | "privateIPAllocationMethod": "Static",
125 | "subnet": {
126 | "id": "[parameters('subnetId')]"
127 | }
128 | }
129 | }
130 | ],
131 | "backendAddressPools": [
132 | {
133 | "name": "[variables('LBBEName')]"
134 | }
135 | ],
136 | "loadBalancingRules": [
137 | {
138 | "name": "LBRule1",
139 | "properties": {
140 | "frontendIPConfiguration": {
141 | "id": "[variables('LBFEConfigID')]"
142 | },
143 | "backendAddressPool": {
144 | "id": "[variables('LBBEAddressPoolID')]"
145 | },
146 | "probe": {
147 | "id": "[variables('LBProbeID')]"
148 | },
149 | "frontendPort": 80,
150 | "backendPort": 80,
151 | "idleTimeoutInMinutes": 4,
152 | "protocol": "Tcp",
153 | "loadDistribution": "SourceIP"
154 | }
155 | },
156 | {
157 | "name": "LBRule2",
158 | "properties": {
159 | "frontendIPConfiguration": {
160 | "id": "[variables('LBFEConfigID')]"
161 | },
162 | "backendAddressPool": {
163 | "id": "[variables('LBBEAddressPoolID')]"
164 | },
165 | "probe": {
166 | "id": "[variables('LBProbeID')]"
167 | },
168 | "frontendPort": 443,
169 | "backendPort": 443,
170 | "idleTimeoutInMinutes": 4,
171 | "protocol": "Tcp",
172 | "loadDistribution": "SourceIP"
173 | }
174 | }
175 | ],
176 | "probes": [
177 | {
178 | "name": "[variables('LBProbeName')]",
179 | "properties": {
180 | "protocol": "Http",
181 | "port": 59999,
182 | "intervalInSeconds": 5,
183 | "numberOfProbes": 2,
184 | "requestPath": "/"
185 | }
186 | }
187 | ]
188 | }
189 | },
190 | {
191 | "name": "[concat('TFSVmDeploy', copyindex())]",
192 | "type": "Microsoft.Resources/deployments",
193 | "apiVersion": "2017-05-10",
194 | "dependsOn": [
195 | "[resourceId('Microsoft.Storage/storageAccounts', variables('diagStorageAccountName'))]",
196 | "[resourceId('Microsoft.Network/loadBalancers', variables('LBName'))]"
197 | ],
198 | "copy": {
199 | "name": "tfsvmloop",
200 | "count": "[parameters('tfsVmInstanceCount')]"
201 | },
202 | "properties": {
203 | "mode": "Incremental",
204 | "templateLink": {
205 | "uri": "[variables('windowsVmTemplateURL')]",
206 | "contentVersion": "1.0.0.0"
207 | },
208 | "parameters": {
209 | "vmName": {
210 | "value": "[concat(variables('tfsVMPrefix'), copyindex())]"
211 | },
212 | "vmSize": {
213 | "value": "[parameters('vmSize')]"
214 | },
215 | "subnetId": {
216 | "value": "[parameters('subnetId')]"
217 | },
218 | "adminUsername": {
219 | "value": "[parameters('adminUsername')]"
220 | },
221 | "adminPassword": {
222 | "value": "[parameters('adminPassword')]"
223 | },
224 | "diagStorageAccountId": {
225 | "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('diagStorageAccountName'))]"
226 | },
227 | "availabilitySetId": {
228 | "value": "[resourceId('Microsoft.Compute/availabilitySets', variables('HASetName'))]"
229 | },
230 | "imageSku": {
231 | "value": "[parameters('imageSku')]"
232 | },
233 | "LoadBalancerBEId": {
234 | "value": "[variables('LBBEAddressPoolID')]"
235 | },
236 | "secrets": {
237 | "value": "[parameters('secrets')]"
238 | }
239 | }
240 | }
241 | },
242 | {
243 | "apiVersion": "2015-06-15",
244 | "type": "Microsoft.Compute/virtualMachines/extensions",
245 | "name": "[concat(variables('tfsVmPrefix'), copyindex(), '/joindomain')]",
246 | "location": "[resourceGroup().location]",
247 | "dependsOn": [
248 | "[resourceId('Microsoft.Resources/deployments', concat('TFSVmDeploy', copyindex()))]"
249 | ],
250 | "copy": {
251 | "name": "tfsvmjoinloop",
252 | "count": "[parameters('tfsVmInstanceCount')]"
253 | },
254 | "properties": {
255 | "publisher": "Microsoft.Compute",
256 | "type": "JsonADDomainExtension",
257 | "typeHandlerVersion": "1.3",
258 | "autoUpgradeMinorVersion": true,
259 | "settings": {
260 | "Name": "[parameters('domainName')]",
261 | "OUPath": "",
262 | "User": "[concat(parameters('domainName'), '\\', parameters('adminUserName'))]",
263 | "Restart": "true",
264 | "Options": "3"
265 | },
266 | "protectedSettings": {
267 | "Password": "[parameters('adminPassword')]"
268 | }
269 | }
270 | },
271 | {
272 | "type": "Microsoft.Compute/virtualMachines/extensions",
273 | "name": "[concat(variables('tfsVmPrefix'), copyindex(), '/configuretfs', copyindex())]",
274 | "dependsOn": [
275 | "[concat(resourceId('Microsoft.Compute/virtualMachines', concat(variables('tfsVmPrefix'), copyindex())), '/extensions/joindomain')]"
276 | ],
277 | "apiVersion": "2016-03-30",
278 | "copy": {
279 | "name": "tfsconfigloop",
280 | "count": "[parameters('tfsVmInstanceCount')]",
281 | "mode": "Serial",
282 | "batchSize": 1
283 | },
284 | "location": "[resourceGroup().location]",
285 | "properties": {
286 | "publisher": "Microsoft.Powershell",
287 | "type": "DSC",
288 | "typeHandlerVersion": "2.21",
289 | "autoUpgradeMinorVersion": true,
290 | "settings": {
291 | "modulesURL": "[variables('TFSInstallModuleURL')]",
292 | "configurationFunction": "[variables('TFSInstallFunction')]",
293 | "properties": {
294 | "domainName": "[parameters('domainName')]",
295 | "adminCreds": {
296 | "userName": "[parameters('adminUserName')]",
297 | "password": "privateSettingsRef:adminPassword"
298 | },
299 | "SqlServerInstance": "[parameters('sqlServerName')]",
300 | "primaryInstance": "[concat(variables('TfsVmPrefix'), '0')]",
301 | "GlobalSiteIP": "[parameters('LBIpAddress')]",
302 | "GlobalSiteName": "[variables('globalSiteName')]",
303 | "DnsServer": "DC1",
304 | "TFSVersion": "TFS2018",
305 | "SslThumbprint": "[parameters('sslThumbprint')]"
306 | }
307 | },
308 | "protectedSettings": {
309 | "items": {
310 | "adminPassword": "[parameters('adminPassword')]"
311 | }
312 | }
313 | }
314 | }
315 | ],
316 | "outputs": {}
317 | }
--------------------------------------------------------------------------------
/tfs-ha/deployagents.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "subnetId": {
6 | "type": "string"
7 | },
8 | "adminPassword": {
9 | "type": "securestring"
10 | },
11 | "adminUsername": {
12 | "type": "string",
13 | "defaultValue": "EnterpriseAdmin"
14 | },
15 | "domainName": {
16 | "type": "string",
17 | "defaultValue": "devnet.contoso.us"
18 | },
19 | "vmSize": {
20 | "type": "string",
21 | "allowedValues": [
22 | "Standard_DS1_v2",
23 | "Standard_DS2_v2",
24 | "Standard_DS3_v2",
25 | "Standard_DS4_v2",
26 | "Standard_DS5_v2",
27 | "Standard_DS11_v2",
28 | "Standard_DS12_v2",
29 | "Standard_DS13_v2",
30 | "Standard_DS14_v2",
31 | "Standard_DS15_v2"
32 | ],
33 | "defaultValue": "Standard_DS2_v2"
34 | },
35 | "buildAgentVmInstanceCount": {
36 | "type": "int",
37 | "defaultValue": 2
38 | },
39 | "tfsServerUrl": {
40 | "type": "string"
41 | }
42 | },
43 | "variables": {
44 | "baseUri": "[deployment().properties.templateLink.uri]",
45 | "VSTSAgentInstallModuleURL": "[uri(variables('baseUri'), 'DSC/InstallVSTSAgent.ps1.zip')]",
46 | "VSTSAgentInstallFunction": "InstallVSTSAgent.ps1\\VSTSAgentInstallDsc",
47 | "agentDownloadUrl": "https://vstsagentpackage.azureedge.net/agent/2.127.0/vsts-agent-win-x64-2.127.0.zip",
48 | "VSImagePublisher": "MicrosoftVisualStudio",
49 | "VSImageOffer": "VisualStudio",
50 | "VSImageSku": "VS-2017-Ent-Latest-WS2016",
51 | "windowsVmTemplateURL": "[uri(variables('baseUri'),'../primitives/windowsvm.json')]",
52 | "diagStorageAccountName": "[concat(uniquestring(resourceGroup().id), 'agvmdiag')]",
53 | "HASetName": "AgentHASet",
54 | "HASetId": "[if(greater(parameters('buildAgentVmInstanceCount'),1),resourceId('Microsoft.Compute/availabilitySets',variables('HASetName')),'')]",
55 | "agentVMPrefix": "AGENT"
56 | },
57 | "resources": [
58 | {
59 | "type": "Microsoft.Storage/storageAccounts",
60 | "name": "[variables('diagStorageAccountName')]",
61 | "apiVersion": "2016-01-01",
62 | "location": "[resourceGroup().location]",
63 | "sku": {
64 | "name": "Standard_LRS"
65 | },
66 | "kind": "Storage",
67 | "properties": {}
68 | },
69 | {
70 | "name": "[variables('HASetName')]",
71 | "type": "Microsoft.Compute/availabilitySets",
72 | "location": "[resourceGroup().location]",
73 | "apiVersion": "2017-03-30",
74 | "condition": "[greater(parameters('buildAgentVmInstanceCount'),1)]",
75 | "dependsOn": [],
76 | "properties": {
77 | "platformUpdateDomainCount": 2,
78 | "platformFaultDomainCount": 2
79 | },
80 | "sku": {
81 | "name": "Aligned"
82 | }
83 | },
84 | {
85 | "name": "[concat('agentVmDeploy', copyindex())]",
86 | "type": "Microsoft.Resources/deployments",
87 | "apiVersion": "2017-05-10",
88 | "dependsOn": [
89 | "[resourceId('Microsoft.Compute/availabilitySets', variables('HASetName'))]"
90 | ],
91 | "copy": {
92 | "name": "agentvmloop",
93 | "count": "[parameters('buildAgentVmInstanceCount')]"
94 | },
95 | "properties": {
96 | "mode": "Incremental",
97 | "templateLink": {
98 | "uri": "[variables('windowsVmTemplateURL')]",
99 | "contentVersion": "1.0.0.0"
100 | },
101 | "parameters": {
102 | "vmName": {
103 | "value": "[concat(variables('agentVMPrefix'), copyindex())]"
104 | },
105 | "vmSize": {
106 | "value": "[parameters('vmSize')]"
107 | },
108 | "subnetId": {
109 | "value": "[parameters('subnetId')]"
110 | },
111 | "adminUsername": {
112 | "value": "[parameters('adminUsername')]"
113 | },
114 | "adminPassword": {
115 | "value": "[parameters('adminPassword')]"
116 | },
117 | "diagStorageAccountId": {
118 | "value": "[resourceId('Microsoft.Storage/storageAccounts', variables('diagStorageAccountName'))]"
119 | },
120 | "availabilitySetId": {
121 | "value": "[variables('HASetId')]"
122 | },
123 | "imagePublisher": {
124 | "value": "[variables('VSImagePublisher')]"
125 | },
126 | "imageOffer": {
127 | "value": "[variables('VSImageOffer')]"
128 | },
129 | "imageSku": {
130 | "value": "[variables('VSImageSku')]"
131 | }
132 | }
133 | }
134 | },
135 | {
136 | "apiVersion": "2015-06-15",
137 | "type": "Microsoft.Compute/virtualMachines/extensions",
138 | "name": "[concat(variables('agentVmPrefix'), copyindex(), '/joindomain')]",
139 | "location": "[resourceGroup().location]",
140 | "dependsOn": [
141 | "[resourceId('Microsoft.Resources/deployments', concat('agentVmDeploy', copyindex()))]"
142 | ],
143 | "copy": {
144 | "name": "agentvmjoinloop",
145 | "count": "[parameters('buildAgentVmInstanceCount')]"
146 | },
147 | "properties": {
148 | "publisher": "Microsoft.Compute",
149 | "type": "JsonADDomainExtension",
150 | "typeHandlerVersion": "1.3",
151 | "autoUpgradeMinorVersion": true,
152 | "settings": {
153 | "Name": "[parameters('domainName')]",
154 | "OUPath": "",
155 | "User": "[concat(parameters('domainName'), '\\', parameters('adminUserName'))]",
156 | "Restart": "true",
157 | "Options": "3"
158 | },
159 | "protectedSettings": {
160 | "Password": "[parameters('adminPassword')]"
161 | }
162 | }
163 | },
164 | {
165 | "type": "Microsoft.Compute/virtualMachines/extensions",
166 | "name": "[concat(variables('agentVmPrefix'), copyindex(), '/configureagent', copyindex())]",
167 | "dependsOn": [
168 | "[concat(resourceId('Microsoft.Compute/virtualMachines', concat(variables('agentVmPrefix'), copyindex())), '/extensions/joindomain')]"
169 | ],
170 | "apiVersion": "2016-03-30",
171 | "copy": {
172 | "name": "agentconfigloop",
173 | "count": "[parameters('buildAgentVmInstanceCount')]",
174 | },
175 | "location": "[resourceGroup().location]",
176 | "properties": {
177 | "publisher": "Microsoft.Powershell",
178 | "type": "DSC",
179 | "typeHandlerVersion": "2.21",
180 | "autoUpgradeMinorVersion": true,
181 | "settings": {
182 | "modulesURL": "[variables('VSTSAgentInstallModuleURL')]",
183 | "configurationFunction": "[variables('VSTSAgentInstallFunction')]",
184 | "properties": {
185 | "domainName": "[parameters('domainName')]",
186 | "adminCreds": {
187 | "userName": "[parameters('adminUserName')]",
188 | "password": "privateSettingsRef:adminPassword"
189 | },
190 | "TFSUrl": "[parameters('tfsServerUrl')]",
191 | "VSTSAgentUrl": "[variables('agentDownloadUrl')]",
192 | }
193 | },
194 | "protectedSettings": {
195 | "items": {
196 | "adminPassword": "[parameters('adminPassword')]"
197 | }
198 | }
199 | }
200 | }
201 | ]
202 | }
--------------------------------------------------------------------------------
/tfs/DSC/TFSSQLServerConfig.ps1:
--------------------------------------------------------------------------------
1 | configuration TFSSQLServerDsc
2 | {
3 | param
4 | (
5 | [Parameter(Mandatory)]
6 | [String]$DomainName,
7 |
8 | [String]$DomainNetbiosName=(Get-NetBIOSName -DomainName $DomainName),
9 |
10 | [Parameter(Mandatory)]
11 | [System.Management.Automation.PSCredential]$Admincreds,
12 |
13 | [Int]$RetryCount=20,
14 | [Int]$RetryIntervalSec=30
15 | )
16 |
17 | Import-DscResource -ModuleName xComputerManagement, xNetworking, xActiveDirectory, xSQLServer, xSQLps
18 | [System.Management.Automation.PSCredential]$DomainCreds = New-Object System.Management.Automation.PSCredential ("${DomainNetbiosName}\$($Admincreds.UserName)", $Admincreds.Password)
19 |
20 | Node localhost
21 | {
22 | xFirewall DatabaseEngineFirewallRule
23 | {
24 | Direction = "Inbound"
25 | Name = "SQL-Server-Database-Engine-TCP-In"
26 | DisplayName = "SQL Server Database Engine (TCP-In)"
27 | Description = "Inbound rule for SQL Server to allow TCP traffic for the Database Engine."
28 | Group = "SQL Server"
29 | Enabled = "True"
30 | Protocol = "TCP"
31 | LocalPort = "1433"
32 | Ensure = "Present"
33 | }
34 |
35 | WindowsFeature ADPS
36 | {
37 | Name = "RSAT-AD-PowerShell"
38 | Ensure = "Present"
39 | }
40 |
41 | xWaitForADDomain DscForestWait
42 | {
43 | DomainName = $DomainName
44 | DomainUserCredential= $DomainCreds
45 | RetryCount = $RetryCount
46 | RetryIntervalSec = $RetryIntervalSec
47 | DependsOn = "[WindowsFeature]ADPS"
48 | }
49 |
50 | xComputer DomainJoin
51 | {
52 | Name = $env:COMPUTERNAME
53 | DomainName = $DomainName
54 | Credential = $DomainCreds
55 | DependsOn = "[xWaitForADDomain]DscForestWait"
56 | }
57 |
58 | xSqlServerLogin AddDomainAdminAccountToSqlServer
59 | {
60 | Name = $DomainCreds.UserName
61 | LoginType = "WindowsUser"
62 | SQLServer = "$env:COMPUTERNAME,1433"
63 | SQLInstanceName = $env:COMPUTERNAME
64 | }
65 |
66 | xSqlServerRole AddDomainAdminAccountToSysAdmin
67 | {
68 | Ensure = "Present"
69 | MembersToInclude = $DomainCreds.UserName
70 | ServerRoleName = "sysadmin"
71 | SQLServer = "$env:COMPUTERNAME,1433"
72 | SQLInstanceName = $env:COMPUTERNAME
73 | DependsOn = "[xSqlServerLogin]AddDomainAdminAccountToSqlServer"
74 | }
75 |
76 | LocalConfigurationManager
77 | {
78 | RebootNodeIfNeeded = $true
79 | }
80 |
81 | }
82 | }
83 |
84 | function Get-NetBIOSName
85 | {
86 | [OutputType([string])]
87 | param(
88 | [string]$DomainName
89 | )
90 |
91 | if ($DomainName.Contains('.')) {
92 | $length=$DomainName.IndexOf('.')
93 | if ( $length -ge 16) {
94 | $length=15
95 | }
96 | return $DomainName.Substring(0,$length)
97 | }
98 | else {
99 | if ($DomainName.Length -gt 15) {
100 | return $DomainName.Substring(0,15)
101 | }
102 | else {
103 | return $DomainName
104 | }
105 | }
106 | }
--------------------------------------------------------------------------------
/tfs/DSC/TFSSQLServerConfig.ps1.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hansenms/iac/277b41b9f9a0ce395f577d8a74640000c91c5ce1/tfs/DSC/TFSSQLServerConfig.ps1.zip
--------------------------------------------------------------------------------
/tfs/README.md:
--------------------------------------------------------------------------------
1 | Private TFS in Azure
2 | ---------------------------
3 |
4 | Microsoft [Azure](https://azure.microsoft.com/en-us/) offers a hosted DevOps experience with [Vistual Studio Team Services](https://www.visualstudio.com/team-services/) (VSTS). It is great, but sometimes organizations would like to host their own [Team Foundation Server](https://www.visualstudio.com/tfs/) (TFS). A particularly relevant use case is users in the [Microsoft Government Cloud](https://azure.microsoft.com/en-us/overview/clouds/government/) where VSTS is not yet available. Deploying a TFS Server with database and build agents can be complicated.
5 |
6 | This template illustrates how to a TFS server and agents. The topology is illustrated below:
7 |
8 | 
9 |
10 | The deployment needs an existing subnet (in a virtual network) and a domain to join. The basic networking and domain controllers can be deployed with the [core-network](../core-network) template, or the network and TFS deployment can be combined as in [devnet-tfs](../devnet-tfs)
11 |
12 |
13 |
14 |
15 |
16 |
17 |
19 |
--------------------------------------------------------------------------------