├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── config.yml │ ├── documentation-update.yml │ └── feature-request.yml └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Deployment ├── Flows │ └── TeamRequestApproval.zip ├── Power App │ └── Request-a-team.zip ├── Scripts │ ├── Settings │ │ └── SharePoint List items.xlsx │ ├── checksiteexists.json │ ├── connections.json │ ├── deploy.ps1 │ ├── keyvault.json │ ├── manifest.json │ ├── processteamrequest.json │ ├── refreshclientsecret.ps1 │ └── synclabels.json └── Templates │ └── requestateam-sitetemplate.xml ├── LICENSE ├── README.md └── SECURITY.md /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug or error report 2 | description: Report a suspected bug or error. 3 | labels: ['🔍 needs triage', 'bug'] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | - [x] Bug 10 | 11 | Thank you for reporting a bug! Use the sections below to submit a bug **only**. 12 | 13 | - All bug reports are welcome - we want to fix them! 14 | - This repo is maintained by volunteers. Please be courteous and patient as responses can take time. 🙂 15 | - Remember to include sufficient details and context. 16 | - If you have multiple questions, suggestions, or bugs, please submit them in separate issues. 17 | - We accept pull requests. If you know how to fix it - go ahead! Fork the repo and submit a PR and we can take a look at it. 18 | 19 | Please provide the following details about the issue you encountered. 20 | 21 | - type: textarea 22 | id: description 23 | attributes: 24 | label: Description 25 | description: Provide a short description of the issue you are facing. 26 | placeholder: Tell us in brief what is happening 27 | validations: 28 | required: true 29 | 30 | - type: textarea 31 | id: steps 32 | attributes: 33 | label: Steps to reproduce 34 | description: Provide the steps you have taken so that we can reproduce the error. 35 | value: | 36 | 1. 37 | 2. 38 | 3. 39 | validations: 40 | required: true 41 | 42 | - type: textarea 43 | id: expected 44 | attributes: 45 | label: Expected results 46 | description: Provide a description of what you expected to happen. 47 | placeholder: What should have happened 48 | validations: 49 | required: true 50 | 51 | - type: textarea 52 | id: actual-results 53 | attributes: 54 | label: Actual Results 55 | description: Provide a description of what actually happens. 56 | placeholder: Tell us what you see 57 | validations: 58 | required: true 59 | 60 | - type: dropdown 61 | id: component 62 | attributes: 63 | label: Solution component 64 | description: Let us know what component of the solution you are having trouble with. 65 | options: 66 | - Power App 67 | - Power Automate Flows 68 | - Deployment Script 69 | - Logic Apps 70 | validations: 71 | required: true 72 | 73 | - type: dropdown 74 | id: os 75 | attributes: 76 | label: Operating system (environment) 77 | description: Provide the operating system that you are using to run the deployment script on. 78 | options: 79 | - Windows 80 | - Windows Server 81 | - macOS 82 | - Linux 83 | - Other 84 | validations: 85 | required: true 86 | 87 | - type: textarea 88 | id: info 89 | attributes: 90 | label: Additional Info 91 | description: | 92 | Provide any additional information that may help with the identification of the root cause of this issue. 93 | validations: 94 | required: false 95 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation-update.yml: -------------------------------------------------------------------------------- 1 | name: "📃 Documentation Update" 2 | description: Help us provide accurate and concise documentation 3 | labels: ['🔍 needs triage', 'documentation'] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | - [x] Documentation update 10 | 11 | Please provide the following details about the inaccuracy or update to the documentation. 12 | 13 | - type: textarea 14 | id: inaccuracy 15 | attributes: 16 | label: Documentation inaccuracy 17 | description: Provide a description of the inaccuracy or update to the documentation. 18 | placeholder: A clear and concise description 19 | validations: 20 | required: true 21 | 22 | - type: textarea 23 | id: info 24 | attributes: 25 | label: Additional Info 26 | description: Provide any additional information that is relevant. 27 | placeholder: Any other context about the problem here 28 | validations: 29 | required: true 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: "🚀 Feature Request" 2 | description: Suggest a feature to make Request-a-team even better 3 | labels: ['🔍 needs triage', 'enhancement'] 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | - [x] Feature 10 | 11 | Thank you 💖 for your feature request! 12 | 13 | - All ideas are welcome - let's build something cool together! 14 | - This repo is maintained by volunteers. Please be courteous and patient as responses can take time. 🙂 15 | - Remember to include sufficient details and context. 16 | - If you have multiple feature requests, please submit them in separate issues. 17 | - Screenshots or mockups are always helpful. 18 | 19 | Please provide the following details about the issue you encountered. 20 | 21 | - type: textarea 22 | id: suggestion 23 | attributes: 24 | label: Suggestion 25 | description: What functionality would you like to see. 26 | placeholder: The more details, the better 27 | validations: 28 | required: true 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request 2 | 3 | By submitting this pull request, you agree to the [code of conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Please complete the following table to help us identify the category of this pull request. 6 | 7 | | Q | A | 8 | | --------------- | --------------------------------------- | 9 | | Bug fix? | N/Y | 10 | | New feature? | N/Y | 11 | | Doc fix | N/Y | 12 | | Related issues? | fixes #X, partially #Y, mentioned in #Z | 13 | 14 | ## What's in the Pull Request? 15 | 16 | > Please describe the changes in this PR. Description of bugs being fixed or new features being added. 17 | 18 | > _(DELETE THIS PARAGRAPH AFTER READING)_ 19 | 20 | ## Submitter Guidance (DELETE AFTER READING) 21 | 22 | > *Please update this PR information accordingly. We'll use this as part of our release notes.* 23 | > 24 | > *Pull requests that do not follow this template will be automatically rejected.* 25 | 26 | > *Please target your PR to `master` branch.* 27 | > 28 | > _(DELETE THIS SECTION AFTER READING)_ 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /Deployment/Flows/TeamRequestApproval.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-requestateam/a1909b26155936a69e57528aa62fb84b5569974d/Deployment/Flows/TeamRequestApproval.zip -------------------------------------------------------------------------------- /Deployment/Power App/Request-a-team.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-requestateam/a1909b26155936a69e57528aa62fb84b5569974d/Deployment/Power App/Request-a-team.zip -------------------------------------------------------------------------------- /Deployment/Scripts/Settings/SharePoint List items.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-apps-requestateam/a1909b26155936a69e57528aa62fb84b5569974d/Deployment/Scripts/Settings/SharePoint List items.xlsx -------------------------------------------------------------------------------- /Deployment/Scripts/checksiteexists.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "location": { 6 | "defaultvalue": "", 7 | "type": "string" 8 | }, 9 | "resourceGroupName": { 10 | "defaultValue": "", 11 | "type": "string" 12 | }, 13 | "subscriptionId": { 14 | "defaultValue": "", 15 | "type": "string" 16 | }, 17 | "spoTenantName": { 18 | "defaultValue": "", 19 | "type": "string" 20 | } 21 | 22 | }, 23 | "variables": {}, 24 | "resources": [ 25 | { 26 | "type": "Microsoft.Logic/workflows", 27 | "apiVersion": "2017-07-01", 28 | "name": "CheckSiteExists", 29 | "location": "[parameters('location')]", 30 | "properties": { 31 | "state": "Enabled", 32 | "definition": { 33 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 34 | "contentVersion": "1.0.0.0", 35 | "parameters": { 36 | "$connections": { 37 | "defaultValue": {}, 38 | "type": "Object" 39 | } 40 | }, 41 | "triggers": { 42 | "manual": { 43 | "type": "Request", 44 | "kind": "Http", 45 | "inputs": { 46 | "method": "POST", 47 | "schema": { 48 | "properties": { 49 | "siteUrl": { 50 | "type": "string" 51 | }, 52 | "tenantId": { 53 | "type": "string" 54 | } 55 | }, 56 | "type": "object" 57 | } 58 | } 59 | } 60 | }, 61 | "actions": { 62 | "Check_Status_code": { 63 | "actions": { 64 | "Set_ReturnBody_variable_-_exists_in_SPO": { 65 | "runAfter": {}, 66 | "type": "SetVariable", 67 | "inputs": { 68 | "name": "ReturnBody", 69 | "value": { 70 | "exists": true, 71 | "source": "tenant" 72 | } 73 | } 74 | } 75 | }, 76 | "runAfter": { 77 | "Check_if_site_collection_exists": [ 78 | "Succeeded" 79 | ] 80 | }, 81 | "else": { 82 | "actions": { 83 | "Check_ErrorInfo_is_null": { 84 | "actions": { 85 | "Set_ReturnBody_variable_-_exists_in_recycle_in": { 86 | "runAfter": {}, 87 | "type": "SetVariable", 88 | "inputs": { 89 | "name": "ReturnBody", 90 | "value": { 91 | "exists": true, 92 | "source": "recycle bin" 93 | } 94 | } 95 | } 96 | }, 97 | "runAfter": { 98 | "Get_site_collection_from_recycle_bin": [ 99 | "Succeeded" 100 | ] 101 | }, 102 | "else": { 103 | "actions": { 104 | "Set_ReturnBody_variable_-_not_found_in_recycle_bin": { 105 | "runAfter": {}, 106 | "type": "SetVariable", 107 | "inputs": { 108 | "name": "ReturnBody", 109 | "value": { 110 | "exists": false, 111 | "source": "@null" 112 | } 113 | } 114 | } 115 | } 116 | }, 117 | "expression": { 118 | "and": [ 119 | { 120 | "equals": [ 121 | "@body('Get_site_collection_from_recycle_bin')?[0].ErrorInfo", 122 | "@null" 123 | ] 124 | } 125 | ] 126 | }, 127 | "type": "If", 128 | "description": "Check ErrorInfo property is null - site exists in recycle bin. If the call fails (site doesn't exist) it will return a 404 error." 129 | }, 130 | "Get_access_token_for_admin_center": { 131 | "runAfter": {}, 132 | "type": "Http", 133 | "inputs": { 134 | "body": "@concat('client_id=',body('Get_Client_ID')?['value'],uriComponentToString('%40'),triggerBody()?['tenantId'],'&client_secret=',body('Get_Client_Secret')?['value'],'&grant_type=client_credentials&resource=00000003-0000-0ff1-ce00-000000000000/',replace(variables('SPOTenantName'),'.sharepoint.com','-admin.sharepoint.com'),uriComponentToString('%40'),triggerBody()?['tenantId'])", 135 | "headers": { 136 | "Content-Type": "application/x-www-form-urlencoded" 137 | }, 138 | "method": "POST", 139 | "uri": "https://accounts.accesscontrol.windows.net/@{triggerBody()?['tenantId']}/tokens/OAuth/2" 140 | } 141 | }, 142 | "Get_site_collection_from_recycle_bin": { 143 | "runAfter": { 144 | "Get_access_token_for_admin_center": [ 145 | "Succeeded" 146 | ] 147 | }, 148 | "type": "Http", 149 | "inputs": { 150 | "body": "\n\t\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\t@{triggerBody()?['siteUrl']}\n\t\t\t\n\t\t\n\t\n\n", 151 | "headers": { 152 | "Accept": "*/*", 153 | "Authorization": "Bearer @{body('Get_access_token_for_admin_center')['access_token']}", 154 | "Content-Type": "application/xml" 155 | }, 156 | "method": "POST", 157 | "uri": "https://@{replace(variables('SPOTenantName'),'.sharepoint.com','-admin.sharepoint.com')}/_vti_bin/client.svc/ProcessQuery" 158 | } 159 | } 160 | } 161 | }, 162 | "expression": { 163 | "and": [ 164 | { 165 | "equals": [ 166 | "@outputs('Get_Status_code')", 167 | 200 168 | ] 169 | } 170 | ] 171 | }, 172 | "type": "If" 173 | }, 174 | "Check_if_site_collection_exists": { 175 | "actions": { 176 | "Get_Access_Token": { 177 | "runAfter": {}, 178 | "type": "Http", 179 | "inputs": { 180 | "body": "@concat('client_id=',body('Get_Client_ID')?['value'],uriComponentToString('%40'),triggerBody()?['tenantId'],'&client_secret=',body('Get_Client_Secret')?['value'],'&grant_type=client_credentials&resource=00000003-0000-0ff1-ce00-000000000000/',variables('SPOTenantName'),uriComponentToString('%40'),triggerBody()?['tenantId'])", 181 | "headers": { 182 | "Content-Type": "application/x-www-form-urlencoded" 183 | }, 184 | "method": "POST", 185 | "uri": "https://accounts.accesscontrol.windows.net/@{triggerBody()?['tenantId']}/tokens/OAuth/2" 186 | } 187 | }, 188 | "Get_Status_code": { 189 | "runAfter": { 190 | "Get_site_collection_from_SPO_Tenant": [ 191 | "Succeeded", 192 | "Failed" 193 | ] 194 | }, 195 | "type": "Compose", 196 | "inputs": "@outputs('Get_site_collection_from_SPO_Tenant')['statusCode']" 197 | }, 198 | "Get_site_collection_from_SPO_Tenant": { 199 | "runAfter": { 200 | "Get_Access_Token": [ 201 | "Succeeded" 202 | ] 203 | }, 204 | "type": "Http", 205 | "inputs": { 206 | "headers": { 207 | "Accept": "application/json", 208 | "Authorization": "Bearer @{body('Get_access_token')?['access_token']}", 209 | "Content-Type": "application/json" 210 | }, 211 | "method": "GET", 212 | "uri": "@{triggerBody()?['siteUrl']}/_api/web" 213 | } 214 | } 215 | }, 216 | "runAfter": { 217 | "Initialize_ReturnBody_variable": [ 218 | "Succeeded" 219 | ] 220 | }, 221 | "type": "Scope" 222 | }, 223 | "Get_Client_ID": { 224 | "runAfter": {}, 225 | "type": "ApiConnection", 226 | "inputs": { 227 | "host": { 228 | "connection": { 229 | "name": "@parameters('$connections')['keyvault']['connectionId']" 230 | } 231 | }, 232 | "method": "get", 233 | "path": "/secrets/@{encodeURIComponent('appid')}/value" 234 | }, 235 | "description": "Get Azure ad app client id from key vault." 236 | }, 237 | "Get_Client_Secret": { 238 | "runAfter": { 239 | "Get_Client_ID": [ 240 | "Succeeded" 241 | ] 242 | }, 243 | "type": "ApiConnection", 244 | "inputs": { 245 | "host": { 246 | "connection": { 247 | "name": "@parameters('$connections')['keyvault']['connectionId']" 248 | } 249 | }, 250 | "method": "get", 251 | "path": "/secrets/@{encodeURIComponent('appsecret')}/value" 252 | }, 253 | "description": "Get Azure ad app secret from key vault." 254 | }, 255 | "Initialize_ReturnBody_variable": { 256 | "runAfter": { 257 | "Initialize_SPOTenantName_variable": [ 258 | "Succeeded" 259 | ] 260 | }, 261 | "type": "InitializeVariable", 262 | "inputs": { 263 | "variables": [ 264 | { 265 | "name": "ReturnBody", 266 | "type": "object" 267 | } 268 | ] 269 | } 270 | }, 271 | "Initialize_SPOTenantName_variable": { 272 | "runAfter": { 273 | "Get_Client_Secret": [ 274 | "Succeeded" 275 | ] 276 | }, 277 | "type": "InitializeVariable", 278 | "inputs": { 279 | "variables": [ 280 | { 281 | "name": "SPOTenantName", 282 | "type": "string", 283 | "value": "[parameters('spoTenantName')]" 284 | } 285 | ] 286 | } 287 | }, 288 | "Response": { 289 | "runAfter": { 290 | "Check_Status_code": [ 291 | "Succeeded" 292 | ] 293 | }, 294 | "type": "Response", 295 | "kind": "Http", 296 | "inputs": { 297 | "body": "@variables('ReturnBody')", 298 | "headers": { 299 | "Accept": "application/json", 300 | "Content-Type": "application/json" 301 | }, 302 | "statusCode": 200 303 | } 304 | } 305 | }, 306 | "outputs": {} 307 | }, 308 | "parameters": { 309 | "$connections": { 310 | "value": { 311 | "keyvault": { 312 | "connectionId": "[concat('/subscriptions/',parameters('subscriptionId'),'/resourceGroups/',parameters('resourceGroupName'),'/providers/Microsoft.Web/connections/requestateam-kv')]", 313 | "connectionName": "requestateam-kv", 314 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/keyvault')]" 315 | } 316 | } 317 | } 318 | } 319 | } 320 | } 321 | ] 322 | } -------------------------------------------------------------------------------- /Deployment/Scripts/connections.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "subscriptionId": { 6 | "defaultValue": "", 7 | "type": "string" 8 | }, 9 | "appId": { 10 | "defaultValue": "", 11 | "type": "string" 12 | }, 13 | "appSecret": { 14 | "defaultValue": "", 15 | "type": "string" 16 | }, 17 | "tenantId": { 18 | "defaultValue": "", 19 | "type": "string" 20 | }, 21 | "location": { 22 | "defaultvalue": "", 23 | "type": "string" 24 | } 25 | }, 26 | "resources": [ 27 | { 28 | "comments": "Provision the API connections before the logic apps.", 29 | "type": "Microsoft.Web/connections", 30 | "apiVersion": "2016-06-01", 31 | "name": "requestateam-spo", 32 | "location": "[parameters('location')]", 33 | "properties": { 34 | "displayName": "Request a team - SharePoint Online", 35 | "customParameterValues": { 36 | }, 37 | "api": { 38 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/sharepointonline')]" 39 | } 40 | } 41 | }, 42 | { 43 | "type": "Microsoft.Web/connections", 44 | "apiVersion": "2016-06-01", 45 | "name": "requestateam-o365outlook", 46 | "location": "[parameters('location')]", 47 | "properties": { 48 | "displayName": "Request a team - Office 365 Outlook", 49 | "customParameterValues": { 50 | }, 51 | "api": { 52 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/office365')]" 53 | } 54 | } 55 | }, 56 | { 57 | "type": "Microsoft.Web/connections", 58 | "apiVersion": "2016-06-01", 59 | "name": "requestateam-o365users", 60 | "location": "[parameters('location')]", 61 | "properties": { 62 | "displayName": "Request a team - Office 365 Users", 63 | "customParameterValues": { 64 | }, 65 | "api": { 66 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/office365users')]" 67 | } 68 | } 69 | }, 70 | { 71 | "type": "Microsoft.Web/connections", 72 | "apiVersion": "2016-06-01", 73 | "name": "requestateam-teams", 74 | "location": "[parameters('location')]", 75 | "properties": { 76 | "displayName": "Request a team - Microsoft Teams", 77 | "customParameterValues": { 78 | }, 79 | "api": { 80 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/teams')]" 81 | } 82 | } 83 | } 84 | ] 85 | } -------------------------------------------------------------------------------- /Deployment/Scripts/deploy.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Deploys the following assets of the Teams Automate solution - 4 | 5 | -SharePoint Site 6 | -Azure AD App Registration 7 | -Logic Apps 8 | 9 | .DESCRIPTION 10 | Deploys the Teams Automate solution (excluding the PowerApp and Flows). 11 | This script uses the Azure CLI, Azure Az PowerShell and SharePoint PnP PowerShell to perform the deployment. 12 | 13 | As part of the deployment, the script will grant admin consent for the Azure AD App to the required Graph permissions. 14 | 15 | The script requires input during execution, requires sign-in to a number of services and therefore should be monitored. 16 | 17 | The following section details the required parameters. 18 | 19 | To see this in a PowerShell session use Get-Help -Full 20 | 21 | .PARAMETER TenantName 22 | Name of the tenant to deploy to (excluding onmicrosoft.com) e.g. contoso 23 | 24 | .PARAMETER RequestsSiteName 25 | Name of the SharePoint site to store the requests, can include spaces (URL/Alias auomatically generated). If the site exists, it will prompt to overwrite and will apply the provisioning template. 26 | 27 | .PARAMETER RequestsSiteDesc 28 | Description for the site that will be created above. 29 | 30 | .PARAMETER ManagedPath 31 | Managed path configured in the tenant e.g. 'sites' or 'teams' (no forward slash). 32 | 33 | .PARAMETER SubscriptionId 34 | Azure subscription id to deploy the solution to. 35 | 36 | .PARAMETER Location 37 | Azure region to deploy the resources to (see below table - internal name should be used e.g. uksouth. 38 | 39 | .PARAMETER ResourceGroupName 40 | A name for a new resource group to deploy the solution to - the script will create this resoure group. 41 | 42 | .PARAMETER AppName 43 | Name for the Azure ad app that will be created. 44 | 45 | .PARAMETER ServiceAccountUPN 46 | UPN of Service Account to be used for the solution - used in the Logic App connections to connect to SharePoint, Outlook and Microsoft Teams. 47 | 48 | .PARAMETER IsEdu 49 | Specifies whether the current tenant is an Education tenant. If set to true, the Education Teams Templates will be deployed. These will be skipped if set to false or left blank. 50 | 51 | .PARAMETER KeyVaultName 52 | Name for the Key Vault that will be provisioned to store the Azure ad app ID and secret. The Key Vault name must be unique and not exist in another subscription. 53 | 54 | .PARAMETER EnableSensitivity 55 | Enable the sensitivity label functionality. 56 | 57 | .EXAMPLE 58 | deploy.ps1 -TenantName "M365x023142" -TenantId "xxxxxxxx-xxxx-xxx-xxxxxxxxxxx" -RequestsSiteName "Request a team app" -RequestsSiteDesc "Used to store Teams Requests" 59 | -ManagedPath "sites" -SubscriptionId 7ed1653b-228c-4d26-a0c0-2cd164xxxxxx -Location "westus" -ResourceGroupName "teamsgovernanceapp-rg" -AppName "Requestateamapp" -ServiceAccountUPN "erviceaccount@M365x023142.onmicrosoft.com" -IsEdu $false -KeyVaultName "requestateam-kv" -EnableSensitivity $false 60 | 61 | ----------------------------------------------------------------------------------------------------------------------------------- 62 | Script name : deploy.ps1 63 | Authors : Alex Clark (Customer Engineer, Microsoft) 64 | Version : 1.0 65 | Dependencies : 66 | ----------------------------------------------------------------------------------------------------------------------------------- 67 | ----------------------------------------------------------------------------------------------------------------------------------- 68 | Version Changes: 69 | Date: Version: Changed By: Info: 70 | ----------------------------------------------------------------------------------------------------------------------------------- 71 | DISCLAIMER 72 | THIS CODE IS SAMPLE CODE. THESE SAMPLES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. 73 | MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES 74 | OF MERCHANTABILITY OR OF FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR 75 | PERFORMANCE OF THE SAMPLES REMAINS WITH YOU. IN NO EVENT SHALL MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR 76 | ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS 77 | INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR 78 | INABILITY TO USE THE SAMPLES, EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 79 | BECAUSE SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR 80 | INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU. 81 | #> 82 | 83 | <# Valid Azure locations 84 | 85 | DisplayName Latitude Longitude Name 86 | ------------------- ---------- ----------- ------------------ 87 | East Asia 22.267 114.188 eastasia 88 | Southeast Asia 1.283 103.833 southeastasia 89 | Central US 41.5908 -93.6208 centralus 90 | East US 37.3719 -79.8164 eastus 91 | East US 2 36.6681 -78.3889 eastus2 92 | West US 37.783 -122.417 westus 93 | North Central US 41.8819 -87.6278 northcentralus 94 | South Central US 29.4167 -98.5 southcentralus 95 | North Europe 53.3478 -6.2597 northeurope 96 | West Europe 52.3667 4.9 westeurope 97 | Japan West 34.6939 135.5022 japanwest 98 | Japan East 35.68 139.77 japaneast 99 | Brazil South -23.55 -46.633 brazilsouth 100 | Australia East -33.86 151.2094 australiaeast 101 | Australia Southeast -37.8136 144.9631 australiasoutheast 102 | South India 12.9822 80.1636 southindia 103 | Central India 18.5822 73.9197 centralindia 104 | West India 19.088 72.868 westindia 105 | Canada Central 43.653 -79.383 canadacentral 106 | Canada East 46.817 -71.217 canadaeast 107 | UK South 50.941 -0.799 uksouth 108 | UK West 53.427 -3.084 ukwest 109 | West Central US 40.890 -110.234 westcentralus 110 | West US 2 47.233 -119.852 westus2 111 | Korea Central 37.5665 126.9780 koreacentral 112 | Korea South 35.1796 129.0756 koreasouth 113 | France Central 46.3772 2.3730 francecentral 114 | France South 43.8345 2.1972 francesouth 115 | Australia Central -35.3075 149.1244 australiacentral 116 | Australia Central 2 -35.3075 149.1244 australiacentral2 117 | South Africa North -25.731340 28.218370 southafricanorth 118 | South Africa West -34.075691 18.843266 southafricawest 119 | 120 | #> 121 | 122 | # Parameters 123 | Param( 124 | [Parameter(Mandatory = $true, 125 | ValueFromPipeline = $true)] 126 | [String] 127 | $TenantName, 128 | 129 | [Parameter(Mandatory = $true, 130 | ValueFromPipeline = $true)] 131 | [String] 132 | $TenantId, 133 | 134 | [Parameter(Mandatory = $true, 135 | ValueFromPipeline = $true)] 136 | [String] 137 | $RequestsSiteName, 138 | 139 | [Parameter(Mandatory = $true, 140 | ValueFromPipeline = $true)] 141 | [String] 142 | $RequestsSiteDesc, 143 | 144 | [Parameter(Mandatory = $true, 145 | ValueFromPipeline = $true)] 146 | [String] 147 | $ManagedPath, 148 | 149 | [Parameter(Mandatory = $true, 150 | ValueFromPipeline = $true)] 151 | [String] 152 | $SubscriptionId, 153 | 154 | [Parameter(Mandatory = $true, 155 | ValueFromPipeline = $true)] 156 | [String] 157 | $Location, 158 | 159 | [Parameter(Mandatory = $true, 160 | ValueFromPipeline = $true)] 161 | [String] 162 | $ResourceGroupName, 163 | 164 | [Parameter(Mandatory = $true, 165 | ValueFromPipeline = $true)] 166 | [String] 167 | $AppName, 168 | 169 | [Parameter(Mandatory = $true, 170 | ValueFromPipeline = $true)] 171 | [String] 172 | $ServiceAccountUPN, 173 | 174 | [Parameter(Mandatory = $false, 175 | ValueFromPipeline = $true)] 176 | [Bool] 177 | $IsEdu = $false, 178 | 179 | [Parameter(Mandatory = $true, 180 | ValueFromPipeline = $true)] 181 | [String] 182 | $KeyVaultName = $false, 183 | 184 | [Parameter(Mandatory = $true, 185 | ValueFromPipeline = $true)] 186 | [String] 187 | $EnableSensitivity = $false 188 | ) 189 | 190 | Add-Type -AssemblyName System.Web 191 | 192 | # Check for presence of Azure CLI 193 | If (-not (Test-Path -Path "C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2")) { 194 | Write-Host "AZURE CLI NOT INSTALLED!`nPLEASE INSTALL THE CLI FROM https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest and re-run this script in a new PowerShell session" -ForegroundColor Red 195 | break 196 | } 197 | # Variables 198 | $packageRootPath = "..\" 199 | $templatePath = "Templates\requestateam-sitetemplate.xml" 200 | $settingsPath = "Scripts\Settings\SharePoint List items.xlsx" 201 | 202 | # Required PS modules 203 | $preReqModules = "Microsoft.Online.SharePoint.PowerShell", "PnP.PowerShell", "Az", "AzureADPreview", "ImportExcel", "WriteAscii" 204 | 205 | # Worksheet 206 | $siteRequestSettingsWorksheetName = "Request Settings" 207 | $teamsTemplatesWorksheetName = "Teams Templates" 208 | 209 | # lists 210 | $requestsListName = "Teams Requests" 211 | $requestSettingsListName = "Team Request Settings" 212 | $teamsTemplatesListName = "Teams Templates" 213 | $ipLabelsListName = "IP Labels" 214 | 215 | # Field names 216 | $TitleFieldName = "Title" 217 | $TeamNameFieldName = "Team Name" 218 | 219 | $tenantUrl = "https://$tenantName.sharepoint.com" 220 | $tenantAdminUrl = "https://$tenantName-admin.sharepoint.com" 221 | 222 | # Remove any spaces in the site name to create the alias 223 | $requestsSiteAlias = $RequestsSiteName -replace (' ', '') 224 | $requestsSiteUrl = "https://$tenantName.sharepoint.com/$ManagedPath/$requestsSiteAlias" 225 | 226 | # API connection names 227 | $spoConnectionName = "requestateam-spo" 228 | $o365OutlookConnectionName = "requestateam-o365outlook" 229 | $o365UsersConnectionName = "requestateam-o365users" 230 | $teamsConnectionName = "requestateam-teams" 231 | 232 | # Global variables 233 | $global:context = $null 234 | $global:requestsListId = $null 235 | $global:teamsTemplatesListId = $null 236 | $global:appId = $null 237 | $global:appSecret = $null 238 | $global:appServicePrincipalId = $null 239 | $global:siteClassifications = $null 240 | $global:location = $null 241 | $global:requestSettingsListId = $null 242 | 243 | # Installs the required PowerShell modules 244 | function InstallModules ($modules) { 245 | if ((Get-PSRepository).InstallationPolicy -eq "Untrusted") { 246 | Set-PSRepository -Name PSGallery -InstallationPolicy Trusted 247 | $psTrustDisabled = $true 248 | } 249 | 250 | foreach ($module in $modules) { 251 | $instModule = Get-InstalledModule -Name $module -ErrorAction:SilentlyContinue 252 | if (!$instModule) { 253 | if ($module -eq "PnP.PowerShell") { 254 | $spModule = Get-InstalledModule -Name "SharePointPnPPowerShellOnline" -ErrorAction:SilentlyContinue 255 | if ($spModule) { 256 | throw('Please remove the older "SharePointPnPPowerShellOnline" module before the deployment can install the new cross-platform module "PnP.PowerShell"') 257 | } 258 | else { 259 | Install-Module -Name $module -Scope CurrentUser -AllowClobber -Confirm:$false -MaximumVersion 1.9.0 260 | } 261 | } 262 | else { 263 | try { 264 | Write-Host('Installing required PowerShell Module {0}' -f $module) -ForegroundColor Yellow 265 | Install-Module -Name $module -Scope CurrentUser -AllowClobber -Confirm:$false 266 | } 267 | catch { 268 | throw('Failed to install PowerShell module {0}: {1}' -f $module, $_.Exception.Message) 269 | } 270 | } 271 | 272 | } 273 | 274 | } 275 | 276 | if ($psTrustDisabled) { 277 | Set-PSRepository -Name PSGallery -InstallationPolicy Untrusted 278 | } 279 | } 280 | 281 | # Test for availability of Azure resources 282 | function Test-AzNameAvailability { 283 | param( 284 | [Parameter(Mandatory = $true)] [string] $AuthorizationToken, 285 | [Parameter(Mandatory = $true)] [string] $SubscriptionId, 286 | [Parameter(Mandatory = $true)] [string] $Name, 287 | [Parameter(Mandatory = $true)] [ValidateSet( 288 | 'ApiManagement', 'KeyVault', 'ManagementGroup', 'Sql', 'StorageAccount', 'WebApp')] 289 | $ServiceType 290 | ) 291 | 292 | $uriByServiceType = @{ 293 | ApiManagement = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.ApiManagement/checkNameAvailability?api-version=2019-01-01' 294 | KeyVault = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.KeyVault/checkNameAvailability?api-version=2019-09-01' 295 | ManagementGroup = 'https://management.azure.com/providers/Microsoft.Management/checkNameAvailability?api-version=2018-03-01-preview' 296 | Sql = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Sql/checkNameAvailability?api-version=2018-06-01-preview' 297 | StorageAccount = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Storage/checkNameAvailability?api-version=2019-06-01' 298 | WebApp = 'https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Web/checkNameAvailability?api-version=2019-08-01' 299 | } 300 | 301 | $typeByServiceType = @{ 302 | ApiManagement = 'Microsoft.ApiManagement/service' 303 | KeyVault = 'Microsoft.KeyVault/vaults' 304 | ManagementGroup = '/providers/Microsoft.Management/managementGroups' 305 | Sql = 'Microsoft.Sql/servers' 306 | StorageAccount = 'Microsoft.Storage/storageAccounts' 307 | WebApp = 'Microsoft.Web/sites' 308 | } 309 | 310 | $uri = $uriByServiceType[$ServiceType] -replace ([regex]::Escape('{subscriptionId}')), $SubscriptionId 311 | $body = '"name": "{0}", "type": "{1}"' -f $Name, $typeByServiceType[$ServiceType] 312 | 313 | $response = (Invoke-WebRequest -Uri $uri -Method Post -Body "{$body}" -ContentType "application/json" -UseBasicParsing -Headers @{Authorization = $AuthorizationToken }).content 314 | $response | ConvertFrom-Json | 315 | Select-Object @{N = 'Name'; E = { $Name } }, @{N = 'Type'; E = { $ServiceType } }, @{N = 'Available'; E = { $_ | Select-Object -ExpandProperty *available } }, Reason, Message 316 | } 317 | 318 | # Get Azure access token for current user 319 | function Get-AccessTokenFromCurrentUser { 320 | $azContext = Get-AzContext 321 | $azProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile 322 | $profileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList $azProfile 323 | $token = $profileClient.AcquireAccessToken($azContext.Subscription.TenantId) 324 | ('Bearer ' + $token.AccessToken) 325 | } 326 | 327 | # Create site and apply provisioning template 328 | function CreateRequestsSharePointSite { 329 | try { 330 | 331 | Write-Host "### TEAMS REQUESTS SITE CREATION ###`nCreating Teams Requests SharePoint site..." -ForegroundColor Yellow 332 | 333 | $site = Get-PnPTenantSite -Url $requestsSiteUrl -ErrorAction SilentlyContinue 334 | 335 | if (!$site) { 336 | 337 | # Site will be created with current user connected to PnP as the owner/primary admin 338 | New-PnPSite -Type TeamSite -Title $RequestsSiteName -Alias $requestsSiteAlias -Description $RequestsSiteDesc 339 | 340 | Write-Host "Site created`n**TEAMS REQUESTS SITE CREATION COMPLETE**" -ForegroundColor Green 341 | } 342 | 343 | else { 344 | Write-Host "Site already exists! Do you wish to overwrite?" -ForegroundColor Red 345 | $overwrite = Read-Host " ( y (overwrite) / n (exit) )" 346 | if ($overwrite -ne "y") { 347 | break 348 | } 349 | 350 | } 351 | } 352 | catch { 353 | $errorMessage = $_.Exception.Message 354 | Write-Host "Error occured while creating of the SharePoint site: $errorMessage" -ForegroundColor Red 355 | } 356 | } 357 | 358 | # Configure the new site 359 | function ConfigureSharePointSite { 360 | 361 | try { 362 | 363 | Write-Host "### REQUESTS SPO SITE CONFIGURATION ###`nConfiguring SharePoint site..." -ForegroundColor Yellow 364 | 365 | Write-Host "Applying provisioning template..." -ForegroundColor Yellow 366 | 367 | Invoke-PnPSiteTemplate -Path (Join-Path $packageRootPath $templatePath) -ClearNavigation 368 | 369 | Write-Host "Applied template" -ForegroundColor Green 370 | 371 | $context = Get-PnPContext 372 | # Ensure Site Assets 373 | $web = $context.Web 374 | $context.Load($web) 375 | $context.Load($web.Lists) 376 | $context.ExecuteQuery() 377 | 378 | # Rename Title field 379 | $siteRequestsList = Get-PnPList $requestsListName 380 | $global:requestsListId = $siteRequestsList.Id 381 | $fields = $siteRequestsList.Fields 382 | $context.Load($fields) 383 | $context.ExecuteQuery() 384 | 385 | $titleField = $fields | Where-Object { $_.InternalName -eq $TitleFieldName } 386 | $titleField.Title = $TeamNameFieldName 387 | $titleField.UpdateAndPushChanges($true) 388 | $context.ExecuteQuery() 389 | 390 | # Adding settings in Site request Settings list 391 | $siteRequestsSettingsList = Get-PnPList $requestSettingsListName 392 | $global:requestSettingsListId = $siteRequestsSettingsList.Id 393 | $context.Load($siteRequestsSettingsList) 394 | $context.ExecuteQuery() 395 | 396 | # Delete existing settings items 397 | $settingsItems = Get-PnPListItem -List $siteRequestsSettingsList 398 | 399 | foreach ($settingItem in $settingsItems) { 400 | Remove-PnPListItem -List $siteRequestsSettingsList -Identity $settingItem -Force 401 | } 402 | 403 | $siteRequestSettings = Import-Excel "$packageRootPath$settingsPath" -WorksheetName $siteRequestSettingsWorksheetName 404 | foreach ($setting in $siteRequestSettings) { 405 | if ($setting.Title -eq "TenantURL") { 406 | $setting.Value = $tenantUrl 407 | } 408 | if ($setting.Title -eq "SPOManagedPath") { 409 | $setting.Value = $ManagedPath 410 | } 411 | if ($setting.Title -eq "SiteClassifications") { 412 | $setting.Value = $global:siteClassifications 413 | } 414 | if ( $setting.Title -eq "EnableSensitivityLabels") { 415 | If ($EnableSensitivity) { 416 | $setting.Value = "true" 417 | } 418 | } 419 | $listItemCreationInformation = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation 420 | $newItem = $siteRequestsSettingsList.AddItem($listItemCreationInformation) 421 | $newitem["Title"] = $setting.Title 422 | $newitem["Description"] = $setting.Description 423 | # Hide site classifications option in Power App if no site classifications were found in the tenant 424 | if ($null -eq $global:siteClassifications -and $setting.Title -eq "HideSiteClassifications") { 425 | $newItem["Value"] = "true" 426 | } 427 | else { 428 | $newitem["Value"] = $setting.Value 429 | } 430 | $newitem.Update() 431 | $context.ExecuteQuery() 432 | 433 | } 434 | 435 | # Hide blocked words field in settings list 436 | $field = $siteRequestsSettingsList.Fields.GetByInternalNameOrTitle("BlockedWordsValue") 437 | $field.SetShowInEditForm($false) 438 | $context.ExecuteQuery() 439 | $field.SetShowInNewForm($false) 440 | $context.ExecuteQuery() 441 | $field.SetShowInDisplayForm($false) 442 | $context.ExecuteQuery() 443 | 444 | Write-Host "Added settings to Site Requests Settings list" -ForegroundColor Green 445 | 446 | # Adding templates to Teams Templates list 447 | $teamsTemplatesList = Get-PnPList $teamsTemplatesListName 448 | $context.Load($teamsTemplatesList) 449 | $context.ExecuteQuery() 450 | $global:teamsTemplatesListId = $teamsTemplatesList.Id 451 | 452 | # Delete existing template items 453 | $templateItems = Get-PnPListItem -List $teamsTemplatesList 454 | 455 | foreach ($templateItem in $templateItems) { 456 | Remove-PnPListItem -List $teamsTemplatesList -Identity $templateItem -Force 457 | } 458 | 459 | $teamsTemplates = Import-Excel "$packageRootPath$settingsPath" -WorksheetName $teamsTemplatesWorksheetName 460 | foreach ($template in $teamsTemplates) { 461 | If (!$isEdu -and ($template.BaseTemplateId -eq "educationStaff" -or $template.BaseTemplateId -eq "educationProfessionalLearningCommunity")) { 462 | # Tenant is not an EDU tenant - do nothing 463 | } 464 | else { 465 | $listItemCreationInformation = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation 466 | $newItem = $teamsTemplatesList.AddItem($listItemCreationInformation) 467 | $newItem["Title"] = $template.Title 468 | $newItem["TemplateId"] = $template.TemplateId 469 | $newItem["TeamId"] = $template.TeamId 470 | $newItem["Description"] = $template.Description 471 | $newItem["AdminCenterTemplate"] = $template.AdminCenterTemplate 472 | $newitem.Update() 473 | $context.ExecuteQuery() 474 | } 475 | } 476 | Write-Host "Added templates to Teams Templates list" -ForegroundColor Green 477 | 478 | # Get id of the ip labels list 479 | $ipLabelsList = Get-PnPList $ipLabelsListName 480 | $context.Load($ipLabelsList) 481 | $context.ExecuteQuery() 482 | $global:ipLabelsListId = $ipLabelsList.Id 483 | 484 | Write-Host "Adding Service Account to Owners group" -ForegroundColor Yellow 485 | 486 | # Check if service account already exists in the site (service account is the same user that is authenticated to PnP) 487 | $user = Get-PnPUser | Where-Object Email -eq $ServiceAccountUPN 488 | 489 | if ($null -eq $user) { 490 | # Get owners group 491 | $group = Get-PnPGroup | Where-Object Title -Match "Owners" 492 | 493 | # Add service account to owners group 494 | Add-PnPGroupMember -LoginName $ServiceAccountUPN -Identity $group 495 | } 496 | 497 | Write-Host "Finished configuring site" -ForegroundColor Green 498 | 499 | } 500 | catch { 501 | $errorMessage = $_.Exception.Message 502 | Write-Host "Error occured while configuring the SharePoint site: $errorMessage" -ForegroundColor Red 503 | } 504 | } 505 | 506 | # Get configured site classifications 507 | function GetSiteClassifications { 508 | $groupDirectorySetting = AzureADPreview\Get-AzureADDirectorySetting | Where-Object DisplayName -eq "Group.Unified" 509 | $classifications = $groupDirectorySetting.Values | Where-Object Name -eq "ClassificationList" | Select-Object Value 510 | 511 | $global:siteClassifications = $classifications.Value 512 | } 513 | 514 | # Gets the azure ad app 515 | function GetAzureADApp { 516 | param ($appName) 517 | 518 | $app = az ad app list --filter "displayName eq '$appName'" | ConvertFrom-Json 519 | 520 | return $app 521 | 522 | } 523 | 524 | function CreateAzureADApp { 525 | try { 526 | Write-Host "### AZURE AD APP CREATION ###" -ForegroundColor Yellow 527 | 528 | # Check if the app already exists - script has been previously executed 529 | $app = GetAzureADApp $appName 530 | 531 | if (-not ([string]::IsNullOrEmpty($app))) { 532 | 533 | # Update azure ad app registration using CLI 534 | Write-Host "Azure AD App '$appName' already exists - updating existing app..." -ForegroundColor Yellow 535 | 536 | az ad app update --id $app.appId --required-resource-accesses './manifest.json' 537 | 538 | $global:appId = $app.appId 539 | 540 | Write-Host "Waiting for app to finish updating..." 541 | 542 | Start-Sleep -s 60 543 | 544 | Write-Host "Updated Azure AD App" -ForegroundColor Green 545 | 546 | } 547 | else { 548 | # Create the app 549 | Write-Host "Creating Azure AD App - '$appName'..." -ForegroundColor Yellow 550 | 551 | # Create azure ad app registration using CLI 552 | $app = az ad app create --display-name $appName --required-resource-accesses './manifest.json' 553 | 554 | $appId = $app | ConvertFrom-Json | Select-Object appid 555 | 556 | $global:appId = $appId.appid 557 | 558 | Write-Host "Waiting for app to finish creating..." 559 | 560 | Start-Sleep -s 60 561 | 562 | Write-Host "Created Azure AD App" -ForegroundColor Green 563 | 564 | } 565 | 566 | Write-Host "Creating secret for Azure AD App - '$appName'..." -ForegroundColor Yellow 567 | 568 | # Create a secret - this will autogenerate a password 569 | $secret = az ad app credential reset --id $global:appId 570 | 571 | $secretValue = $secret | ConvertFrom-Json | Select-Object password 572 | 573 | $global:appSecret = $secretValue.password 574 | 575 | Write-Host "Created secret for app" -ForegroundColor Green 576 | 577 | Write-Host "Granting admin content for Microsoft Graph..." -ForegroundColor Yellow 578 | 579 | # Grant admin consent for app registration required permissions using CLI 580 | az ad app permission admin-consent --id $global:appId 581 | 582 | Write-Host "Waiting for admin consent to finish..." 583 | 584 | Start-Sleep -s 60 585 | 586 | Write-Host "Granted admin consent" -ForegroundColor Green 587 | 588 | # Get service principal id for the app we created 589 | $global:appServicePrincipalId = Get-AzADServicePrincipal -DisplayName $appName | Select-Object -ExpandProperty Id 590 | 591 | Write-Host "### AZURE AD APP CREATION FINISHED ###" -ForegroundColor Green 592 | } 593 | catch { 594 | $errorMessage = $_.Exception.Message 595 | Write-Host "Error occured while creating an Azure AD App: $errorMessage" -ForegroundColor Red 596 | } 597 | } 598 | 599 | function CreateConfigureKeyVault { 600 | Write-Host "Creating/Updating Key Vault and setting secrets..." -ForegroundColor Yellow 601 | 602 | # Check if the key vault already exists 603 | $keyVault = Get-AzKeyVault -Name $KeyVaultName 604 | 605 | if ($null -eq $keyVault) { 606 | # Use the tenant name in the key vault name to ensure it is unique - first 8 characters only due to maximum allowed length of key vault names 607 | $keyVault = New-AzKeyVault -Name $KeyVaultName -ResourceGroupName $ResourceGroupName -Location $Location 608 | } 609 | 610 | # Create/update the secrets for the ad app id and password 611 | Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name 'appid' -SecretValue (ConvertTo-SecureString -String $global:appId -AsPlainText -Force) | Out-Null 612 | Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name 'appsecret' -SecretValue (ConvertTo-SecureString -String $global:appSecret -AsPlainText -Force) | Out-Null 613 | 614 | If ($EnableSensitivity) { 615 | Write-Host "You chose to enable the sensitivity label functionality. Make sure the Service Account you use does NOT have MFA enabled." -ForegroundColor Yellow 616 | 617 | # Add service account credentials to key vault (Required for sensitivity label functionality due to the current Graph API restriction only supporting delegated permissions) 618 | $saCreds = Get-Credential -Message "Enter Service Account credentials (To enable sensitivity label functionality). Must NOT have MFA enabled." 619 | Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name 'sausername' -SecretValue (ConvertTo-SecureString -String $saCreds.UserName -AsPlainText -Force) | Out-Null 620 | Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name 'sapassword' -SecretValue (ConvertTo-SecureString -String $saCreds.GetNetworkCredential().Password -AsPlainText -Force) | Out-Null 621 | } 622 | 623 | 624 | 625 | Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -ObjectId $global:appServicePrincipalId -PermissionsToSecrets List, Get 626 | 627 | Write-Host "Finished creating/updating Key Vault and setting secrets" -ForegroundColor Green 628 | 629 | } 630 | 631 | # Deploy ARM template - currently only used for the logic app 632 | function DeployARMTemplate { 633 | try { 634 | # Deploy ARM templates 635 | Write-Host "Deploying api connections..." -ForegroundColor Yellow 636 | az deployment group create --resource-group $resourceGroupName --subscription $SubscriptionId --template-file 'connections.json' --parameters "subscriptionId=$subscriptionId" "tenantId=$TenantId" "appId=$global:appId" "appSecret=$global:appSecret" "location=$global:location" 637 | 638 | az deployment group create --resource-group $resourceGroupName --subscription $SubscriptionId --template-file 'keyvault.json' --parameters "subscriptionId=$subscriptionId" "tenantId=$TenantId" "appId=$global:appId" "appSecret=$global:appSecret" "location=$global:location" "keyvaultName=$KeyVaultName" 639 | 640 | Write-Host "Deploying logic apps..." -ForegroundColor Yellow 641 | 642 | az deployment group create --resource-group $resourceGroupName --subscription $SubscriptionId --template-file 'checksiteexists.json' --parameters "resourceGroupName=$resourceGroupName" "subscriptionId=$subscriptionId" "spoTenantName=$tenantName.sharepoint.com" "location=$location" 643 | 644 | az deployment group create --resource-group $resourceGroupName --subscription $SubscriptionId --template-file 'processteamrequest.json' --parameters "resourceGroupName=$resourceGroupName" "subscriptionId=$subscriptionId" "tenantId=$TenantId" "requestsSiteUrl=$requestsSiteUrl" "requestsListId=$global:requestsListId" "requestSettingsListsId=$global:requestSettingsListId" "location=$global:location" "serviceAccountUPN=$ServiceAccountUPN" 645 | 646 | az deployment group create --resource-group $resourceGroupName --subscription $SubscriptionId --template-file 'synclabels.json' --parameters "resourceGroupName=$resourceGroupName" "subscriptionId=$subscriptionId" "tenantId=$TenantId" "location=$location" "requestsSiteUrl=$requestsSiteUrl" "ipLabelsListId=$global:ipLabelsListId" 647 | 648 | Write-Host "Finished deploying logic apps" -ForegroundColor Green 649 | } 650 | catch { 651 | $errorMessage = $_.Exception.Message 652 | Write-Host "Error occured while deploying Azure resources: $errorMessage" -ForegroundColor Red 653 | } 654 | } 655 | 656 | # Shows OAuth sign-in window 657 | function ShowOAuthWindow { 658 | Add-Type -AssemblyName System.Windows.Forms 659 | $form = New-Object -TypeName System.Windows.Forms.Form -Property @{Width = 600; Height = 800 } 660 | $web = New-Object -TypeName System.Windows.Forms.WebBrowser -Property @{Width = 580; Height = 780; Url = ($url -f ($Scope -join "%20")) } 661 | $docComp = { 662 | $Global:uri = $web.Url.AbsoluteUri 663 | if ($Global:Uri -match "error=[^&]*|code=[^&]*") { $form.Close() } 664 | } 665 | $web.Add_DocumentCompleted($docComp) 666 | $form.Controls.Add($web) 667 | $form.Add_Shown( { $form.Activate() }) 668 | $form.ShowDialog() | Out-Null 669 | } 670 | 671 | function AuthoriseLogicAppConnection($resourceId) { 672 | $parameters = @{ 673 | "parameters" = , @{ 674 | "parameterName" = "token"; 675 | "redirectUrl" = "http://localhost" 676 | } 677 | } 678 | 679 | # Get the links needed for consent 680 | $consentResponse = Invoke-AzResourceAction -Action "listConsentLinks" -ResourceId $resourceId -Parameters $parameters -Force 681 | 682 | $url = $consentResponse.Value.Link 683 | 684 | # Show sign-in prompt window and grab the code after auth 685 | ShowOAuthWindow -URL $url 686 | 687 | $regex = '(code=)(.*)$' 688 | $code = ($uri | Select-string -pattern $regex).Matches[0].Groups[2].Value 689 | # Write-output "Received an accessCode: $code" 690 | 691 | if (-Not [string]::IsNullOrEmpty($code)) { 692 | $parameters = @{ } 693 | $parameters.Add("code", $code) 694 | # NOTE: errors ignored as this appears to error due to a null response 695 | 696 | #confirm the consent code 697 | Invoke-AzResourceAction -Action "confirmConsentCode" -ResourceId $resourceId -Parameters $parameters -Force -ErrorAction Ignore 698 | } 699 | 700 | # Retrieve the connection 701 | $connection = Get-AzResource -ResourceId $resourceId 702 | Write-Host "Connection " $connection.Name " now " $connection.Properties.Statuses[0] 703 | } 704 | 705 | function AuthoriseLogicAppConnections() { 706 | 707 | Write-Host "### LOGIC APP CONNECTIONS AUTHORISATION ###`nStarting authorisation for Logic App Connections`nPlease authenticate with the Service Account - $ServiceAccountUPN" -ForegroundColor Yellow 708 | $spoconnection = Get-AzResource -ResourceType "Microsoft.Web/connections" -ResourceGroupName $resourceGroupName -Name $spoConnectionName 709 | 710 | $o365OutlookConnection = Get-AzResource -ResourceType "Microsoft.Web/connections" -ResourceGroupName $resourceGroupName -Name $o365OutlookConnectionName 711 | 712 | $o365UsersConnection = Get-AzResource -ResourceType "Microsoft.Web/connections" -ResourceGroupName $resourceGroupName -Name $o365UsersConnectionName 713 | 714 | $teamsConnection = Get-AzResource -ResourceType "Microsoft.Web/connections" -ResourceGroupName $resourceGroupName -Name $teamsConnectionName 715 | 716 | Write-Host "SharePoint Connection" 717 | AuthoriseLogicAppConnection($spoConnection.ResourceId) 718 | Write-Host "Office 365 Outlook Connection" 719 | AuthoriseLogicAppConnection($o365OutlookConnection.ResourceId) 720 | Write-Host "Office 365 Users Connection" 721 | AuthoriseLogicAppConnection($o365UsersConnection.ResourceId) 722 | Write-Host "Microsoft Teams Connection" 723 | AuthoriseLogicAppConnection($teamsConnection.ResourceId) 724 | 725 | Write-Host "### LOGIC APP CONNECTIONS AUTHORISATION COMPLETE ###" -ForegroundColor Green 726 | } 727 | 728 | # Check that the provided location is a valid Azure location 729 | function ValidateAzureLocation { 730 | $locations = Get-AzLocation 731 | 732 | $global:location = $Location.Replace(" ", "").ToLower() 733 | 734 | # Validate that the location exists 735 | if ($null -eq ($locations | Where-Object Location -eq $global:location)) { 736 | throw "Invalid Azure Location. Please provide a valid location. See this list - https://azure.microsoft.com/en-gb/global-infrastructure/locations/" 737 | 738 | } 739 | } 740 | 741 | # Check that the Key Vault does not already exist and ensure the name is valid 742 | function ValidateKeyVault { 743 | Write-Host "Checking for availability of Key Vault..." -ForegroundColor Yellow 744 | 745 | $availabilityResult = $null 746 | 747 | $availabilityParams = @{ 748 | Name = $KeyVaultName 749 | ServiceType = 'KeyVault' 750 | AuthorizationToken = Get-AccessTokenFromCurrentUser 751 | SubscriptionId = $SubscriptionId 752 | } 753 | 754 | $availabilityResult = Test-AzNameAvailability @availabilityParams 755 | 756 | if ($availabilityResult.Available) { 757 | Write-Host "Key Vault is available." -ForegroundColor Green 758 | } 759 | 760 | if ($availabilityResult.Reason -eq "AlreadyExists") { 761 | 762 | #Check if the key vault exists in this subscription 763 | $keyVault = Get-AzKeyVault -Name $KeyVaultName 764 | 765 | if ($null -ne $keyVault) { 766 | Write-Host "Key Vault already exists in this Azure subscription. Do you wish to use it?" -ForegroundColor Red 767 | $update = Read-Host " ( y (yes) / n (exit) ) " 768 | if ($update -ne "y") { 769 | Write-Host "Script terminated. Please specify a different Key Vault name or choose to use the existing Key Vault when re-executing the script." -ForegroundColor Red 770 | break 771 | } 772 | else { 773 | Write-Host "Existing Key Vault '$KeyVaultName' will be used." -ForegroundColor Yellow 774 | 775 | } 776 | } 777 | else { 778 | throw "Key Vault already exists in another Azure subscription. Please specify a different name." 779 | } 780 | } 781 | 782 | if ($availabilityResult.reason -eq "Invalid") { 783 | 784 | throw $availabilityResult.message 785 | } 786 | 787 | } 788 | 789 | Write-Host "### DEPLOYMENT SCRIPT STARTED `n(c) Microsoft Corporation ###" -ForegroundColor Magenta 790 | 791 | # Install required PS Modules 792 | Write-Host "Installing required PowerShell Modules..." -ForegroundColor Yellow 793 | InstallModules -Modules $preReqModules 794 | foreach ($module in $preReqModules) { 795 | $instModule = Get-InstalledModule -Name $module -ErrorAction:SilentlyContinue 796 | if (!$instModule) { 797 | throw('Failed to install module {0}' -f $module) 798 | } 799 | } 800 | 801 | Write-Host "Installed modules" -ForegroundColor Green 802 | 803 | Write-Ascii -InputObject "Request-a-Team" -ForegroundColor Magenta 804 | 805 | # Initialise connections - Azure Az/CLI 806 | Write-Host "Launching Azure sign-in..." -ForegroundColor Yellow 807 | $azConnect = Connect-AzAccount -Subscription $SubscriptionId -Tenant $TenantId 808 | ValidateKeyVault 809 | ValidateAzureLocation 810 | Write-Host "Launching Azure AD sign-in..." -ForegroundColor Yellow 811 | Connect-AzureAD 812 | Write-Host "Launching Azure CLI sign-in..." -ForegroundColor Yellow 813 | $cliLogin = az login 814 | Write-Host "Connected to Azure" -ForegroundColor Green 815 | # Connect to PnP 816 | Write-Host "Launching PnP sign-in..." -ForegroundColor Yellow 817 | $pnpConnect = Connect-PnPOnline -Url $tenantAdminUrl -Interactive 818 | Write-Host "Connected to SPO" -ForegroundColor Green 819 | 820 | CreateAzureADApp 821 | GetSiteClassifications 822 | CreateRequestsSharePointSite 823 | # Connect to the new site 824 | $pnpConnect = Connect-PnPOnline $requestsSiteUrl -Interactive 825 | ConfigureSharePointSite 826 | 827 | Write-Host "### AZURE RESOURCES DEPLOYMENT ###`nStarting Azure resources deployment..." -ForegroundColor Yellow 828 | 829 | # Create resource group 830 | # Handle spaces in resource group name 831 | $ResourceGroupName = $ResourceGroupName.Replace(" ", "") 832 | Write-Host "Creating resource group $resourceGroupName..." -ForegroundColor Yellow 833 | New-AzResourceGroup -Name $resourceGroupName -Location $global:location 834 | Write-Host "Created resource group" -ForegroundColor Green 835 | 836 | CreateConfigureKeyVault 837 | DeployARMTemplate 838 | 839 | Write-Host "Azure resources deployed`n### AZURE RESOURCES DEPLOYMENT COMPLETE ###" -ForegroundColor Green 840 | 841 | AuthoriseLogicAppConnections 842 | 843 | Write-Host "DEPLOYMENT COMPLETED SUCCESSFULLY" -ForegroundColor Green -------------------------------------------------------------------------------- /Deployment/Scripts/keyvault.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "subscriptionId": { 6 | "defaultValue": "", 7 | "type": "string" 8 | }, 9 | "appId": { 10 | "defaultValue": "", 11 | "type": "string" 12 | }, 13 | "appSecret": { 14 | "defaultValue": "", 15 | "type": "string" 16 | }, 17 | "tenantId": { 18 | "defaultValue": "", 19 | "type": "string" 20 | }, 21 | "location": { 22 | "defaultvalue": "", 23 | "type": "string" 24 | }, 25 | "keyvaultName": { 26 | "defaultvalue": "", 27 | "type": "string" 28 | } 29 | }, 30 | "resources": [ 31 | { 32 | "type": "Microsoft.Web/connections", 33 | "apiVersion": "2016-06-01", 34 | "name": "requestateam-kv", 35 | "location": "[parameters('location')]", 36 | "properties": { 37 | "displayName": "Request a team - Key Vault", 38 | "parameterValues": { 39 | "vaultName": "[parameters('keyvaultName')]", 40 | "token:clientId": "[parameters('appId')]", 41 | "token:clientSecret": "[parameters('appSecret')]", 42 | "token:TenantId": "[parameters('tenantId')]", 43 | "token:grantType": "client_credentials" 44 | }, 45 | "api": { 46 | "id": "[concat('subscriptions/', parameters('subscriptionId'), '/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/keyvault')]" 47 | } 48 | } 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /Deployment/Scripts/manifest.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "resourceAppId": "00000003-0000-0000-c000-000000000000", 4 | "resourceAccess": [ 5 | { 6 | "id": "7ab1d382-f21e-4acd-a863-ba3e13f7da61", 7 | "type": "Role" 8 | }, 9 | { 10 | "id": "62a82d76-70ea-41e2-9197-370581804d09", 11 | "type": "Role" 12 | }, 13 | { 14 | "id": "df021288-bdef-4463-88db-98f22de89214", 15 | "type": "Role" 16 | }, 17 | { 18 | "id": "19da66cb-0fb0-4390-b071-ebc76a349482", 19 | "type": "Role" 20 | }, 21 | { 22 | "id": "4e46008b-f24c-477d-8fff-7bb4ec7aafe0", 23 | "type": "Scope" 24 | } 25 | ] 26 | }, 27 | { 28 | "resourceAppId": "00000003-0000-0ff1-ce00-000000000000", 29 | "resourceAccess": [ 30 | { 31 | "id": "678536fe-1083-478a-9c59-b99265e6b0d3", 32 | "type": "Role" 33 | } 34 | ] 35 | } 36 | ] -------------------------------------------------------------------------------- /Deployment/Scripts/refreshclientsecret.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Updates the Azure AD app secret used in Request-a-team. This script should be used once a new secret has been created in the App registrations blade. 4 | 5 | .DESCRIPTION 6 | Updates the following components of Request-a-team to use the updated secret - 7 | 8 | Key vault - The 'appsecret' secret in the key vault is updated to use the new secret value. The previous version of the secret is disabled. 9 | Key vault API Connection - The requestateam-kv API Connection is redeployed using the new secret value. 10 | 11 | PLEASE DESTROY THE COPIED SECRET VALUE ONCE SCRIPT EXECUTION HAS COMPLETED SUCCESSFULLY. 12 | 13 | .PARAMETER ClientId 14 | Id of the Request-a-team Azure AD app. Can be obtained trough the app registrations blade. 15 | 16 | .PARAMETER ClientSecret 17 | New secret value which has been generated. 18 | 19 | .PARAMETER SubscriptionId 20 | Azure subscription id where request-a-team is deployed. 21 | 22 | .PARAMETER Location 23 | Azure region where request-a-team is deployed. 24 | 25 | .PARAMETER ResourceGroupName 26 | Name of the resource group where request-a-team is deployed. 27 | 28 | .PARAMETER TenantId 29 | Id of the tenant. 30 | 31 | .PARAMETER KeyVaultName 32 | Name of the key vault that was used for the request-a-team deployment. 33 | 34 | .EXAMPLE 35 | refreshclientsecret.ps1 -ClientId "xxxxxxxx-xxxx-xxx-xxxxxxxxxxx" -ClientSecret "xxxxxxxx-xxxx-xxx-xxxxxxxxxxx" 36 | -SubscriptionId 7ed1653b-228c-4d26-a0c0-2cd164xxxxxx -Location "westus" -TenantId "xxxxxxxx-xxxx-xxx-xxxxxxxxxxx" -ResourceGroupName "teamsgovernanceapp-rg" -KeyVaultName "requestateam-kv" 37 | 38 | ----------------------------------------------------------------------------------------------------------------------------------- 39 | Script name : deploy.ps1 40 | Authors : Alex Clark (SharePoint PFE, Microsoft) 41 | Version : 1.0 42 | Dependencies : 43 | ----------------------------------------------------------------------------------------------------------------------------------- 44 | ----------------------------------------------------------------------------------------------------------------------------------- 45 | Version Changes: 46 | Date: Version: Changed By: Info: 47 | ----------------------------------------------------------------------------------------------------------------------------------- 48 | DISCLAIMER 49 | THIS CODE IS SAMPLE CODE. THESE SAMPLES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. 50 | MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES 51 | OF MERCHANTABILITY OR OF FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR 52 | PERFORMANCE OF THE SAMPLES REMAINS WITH YOU. IN NO EVENT SHALL MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR 53 | ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS 54 | INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR 55 | INABILITY TO USE THE SAMPLES, EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 56 | BECAUSE SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR 57 | INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU. 58 | #> 59 | 60 | 61 | # Parameters 62 | Param( 63 | [Parameter(Mandatory = $true, 64 | ValueFromPipeline = $true)] 65 | [String] 66 | $ClientId, 67 | 68 | [Parameter(Mandatory = $true, 69 | ValueFromPipeline = $true)] 70 | [String] 71 | $ClientSecret, 72 | 73 | [Parameter(Mandatory = $true, 74 | ValueFromPipeline = $true)] 75 | [String] 76 | $SubscriptionId, 77 | 78 | [Parameter(Mandatory = $true, 79 | ValueFromPipeline = $true)] 80 | [String] 81 | $Location, 82 | 83 | [Parameter(Mandatory = $true, 84 | ValueFromPipeline = $true)] 85 | [String] 86 | $ResourceGroupName, 87 | 88 | [Parameter(Mandatory = $true, 89 | ValueFromPipeline = $true)] 90 | [String] 91 | $TenantId, 92 | 93 | [Parameter(Mandatory = $true, 94 | ValueFromPipeline = $true)] 95 | [String] 96 | $KeyVaultName 97 | 98 | ) 99 | 100 | Write-Host "Launching Azure sign-in..." -ForegroundColor Yellow 101 | $azConnect = Connect-AzAccount -Subscription $SubscriptionId -Tenant $TenantId 102 | 103 | Write-Host "Launching Azure CLI sign-in..." -ForegroundColor Yellow 104 | $cliLogin = az login 105 | Write-Host "Connected to Azure" -ForegroundColor Green 106 | 107 | Write-Host "Updating secret value in key vault" -ForegroundColor Yellow 108 | $currSecret = Get-AzKeyVaultSecret -VaultName $KeyVaultName -Name "appsecret" 109 | 110 | # Create a new version of the secret - this will be enabled by default 111 | $secretValue = ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force 112 | Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name "appsecret" -SecretValue $secretValue 113 | 114 | # Disable previous version 115 | Update-AzKeyVaultSecret -VaultName $KeyVaultName -Name "appsecret" -Version $currSecret.Version -Enable 0 116 | 117 | Write-Host "Key vault secret value updated" -ForegroundColor Green 118 | 119 | Write-Host "Updating Key Vault API connection..." -ForegroundColor Yellow 120 | 121 | az deployment group create --resource-group $resourceGroupName --subscription $SubscriptionId --template-file 'keyvault.json' --parameters "subscriptionId=$SubscriptionId" "tenantId=$TenantId" "appId=$ClientId" "appSecret=$ClientSecret" "location=$Location" "keyvaultName=$KeyVaultName" 122 | 123 | Write-Host "Updated Key Vault API connection" -ForegroundColor Green 124 | -------------------------------------------------------------------------------- /Deployment/Scripts/synclabels.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "resourceGroupName": { 6 | "defaultValue": "", 7 | "type": "string" 8 | }, 9 | "subscriptionId": { 10 | "defaultValue": "", 11 | "type": "string" 12 | }, 13 | "tenantId": { 14 | "defaultValue": "", 15 | "type": "string" 16 | }, 17 | "location": { 18 | "defaultvalue": "", 19 | "type": "string" 20 | }, 21 | "requestsSiteUrl": { 22 | "defaultvalue": "", 23 | "type": "string" 24 | }, 25 | "ipLabelsListId": { 26 | "defaultvalue": "", 27 | "type": "string" 28 | } 29 | }, 30 | "variables": { 31 | "Singlequote": "'" 32 | }, 33 | "resources": [ 34 | { 35 | "type": "Microsoft.Logic/workflows", 36 | "apiVersion": "2017-07-01", 37 | "name": "SyncLabels", 38 | "location": "[parameters('location')]", 39 | "properties": { 40 | "state": "Enabled", 41 | "definition": { 42 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 43 | "contentVersion": "1.0.0.0", 44 | "parameters": { 45 | "$connections": { 46 | "defaultValue": {}, 47 | "type": "Object" 48 | } 49 | }, 50 | "triggers": { 51 | "Recurrence": { 52 | "recurrence": { 53 | "frequency": "Day", 54 | "interval": 1 55 | }, 56 | "evaluatedRecurrence": { 57 | "frequency": "Day", 58 | "interval": 1 59 | }, 60 | "type": "Recurrence" 61 | } 62 | }, 63 | "actions": { 64 | "Get_Client_ID": { 65 | "runAfter": { 66 | "Initialize_GraphURL_variable": [ 67 | "Succeeded" 68 | ] 69 | }, 70 | "type": "ApiConnection", 71 | "inputs": { 72 | "host": { 73 | "connection": { 74 | "name": "@parameters('$connections')['keyvault']['connectionId']" 75 | } 76 | }, 77 | "method": "get", 78 | "path": "/secrets/@{encodeURIComponent('appid')}/value" 79 | }, 80 | "description": "Get Azure ad app client id from key vault.", 81 | "runtimeConfiguration": { 82 | "secureData": { 83 | "properties": [ 84 | "inputs", 85 | "outputs" 86 | ] 87 | } 88 | } 89 | }, 90 | "Get_Client_Secret": { 91 | "runAfter": { 92 | "Get_Client_ID": [ 93 | "Succeeded" 94 | ] 95 | }, 96 | "type": "ApiConnection", 97 | "inputs": { 98 | "host": { 99 | "connection": { 100 | "name": "@parameters('$connections')['keyvault']['connectionId']" 101 | } 102 | }, 103 | "method": "get", 104 | "path": "/secrets/@{encodeURIComponent('appsecret')}/value" 105 | }, 106 | "description": "Get Azure ad app secret from key vault.", 107 | "runtimeConfiguration": { 108 | "secureData": { 109 | "properties": [ 110 | "inputs", 111 | "outputs" 112 | ] 113 | } 114 | } 115 | }, 116 | "Initialize_GraphURL_variable": { 117 | "runAfter": { 118 | "Initialize_TenantID_variable": [ 119 | "Succeeded" 120 | ] 121 | }, 122 | "type": "InitializeVariable", 123 | "inputs": { 124 | "variables": [ 125 | { 126 | "name": "GraphURL", 127 | "type": "string", 128 | "value": "https://graph.microsoft.com/beta" 129 | } 130 | ] 131 | } 132 | }, 133 | "Initialize_TenantID_variable": { 134 | "runAfter": {}, 135 | "type": "InitializeVariable", 136 | "inputs": { 137 | "variables": [ 138 | { 139 | "name": "TenantID", 140 | "type": "string", 141 | "value": "[parameters('tenantId')]" 142 | } 143 | ] 144 | } 145 | }, 146 | "List_sensitivity_labels": { 147 | "runAfter": { 148 | "Get_Client_Secret": [ 149 | "Succeeded" 150 | ] 151 | }, 152 | "type": "Http", 153 | "inputs": { 154 | "authentication": { 155 | "audience": "https://graph.microsoft.com", 156 | "clientId": "@body('Get_Client_ID')?['value']", 157 | "secret": "@body('Get_Client_Secret')?['value']", 158 | "tenant": "@variables('TenantID')", 159 | "type": "ActiveDirectoryOAuth" 160 | }, 161 | "headers": { 162 | "content-type": "application/json" 163 | }, 164 | "method": "GET", 165 | "uri": "@{variables('GraphURL')}/informationProtection/policy/labels" 166 | } 167 | }, 168 | "Loop_through_sensitivity_labels": { 169 | "foreach": "@body('Parse_sensitivity_labels_JSON')?['value']", 170 | "actions": { 171 | "Check_if_the_label_was_found_in_the_list": { 172 | "actions": { 173 | "Create_label_item": { 174 | "runAfter": {}, 175 | "type": "ApiConnection", 176 | "inputs": { 177 | "body": { 178 | "LabelDescription": "@items('Loop_through_sensitivity_labels')?['description']", 179 | "LabelId": "@items('Loop_through_sensitivity_labels')?['id']", 180 | "LabelName": "@items('Loop_through_sensitivity_labels')?['name']", 181 | "Title": "@items('Loop_through_sensitivity_labels')?['name']" 182 | }, 183 | "host": { 184 | "connection": { 185 | "name": "@parameters('$connections')['sharepointonline']['connectionId']" 186 | } 187 | }, 188 | "method": "post", 189 | "path": "[concat('/datasets/@{encodeURIComponent(encodeURIComponent(',variables('singlequote'),parameters('requestsSiteUrl'),variables('singlequote'),'))}/tables/@{encodeURIComponent(encodeURIComponent(',variables('singlequote'),parameters('ipLabelsListId'),variables('singlequote'),'))}/items')]" 190 | } 191 | } 192 | }, 193 | "runAfter": { 194 | "Get_label_from_the_SharePoint_list": [ 195 | "Succeeded" 196 | ] 197 | }, 198 | "else": { 199 | "actions": { 200 | "Update_label_list_item": { 201 | "runAfter": {}, 202 | "type": "ApiConnection", 203 | "inputs": { 204 | "body": { 205 | "LabelDescription": "@items('Loop_through_sensitivity_labels')?['description']", 206 | "LabelId": "@items('Loop_through_sensitivity_labels')?['id']", 207 | "LabelName": "@items('Loop_through_sensitivity_labels')?['name']", 208 | "Title": "@items('Loop_through_sensitivity_labels')?['name']" 209 | }, 210 | "host": { 211 | "connection": { 212 | "name": "@parameters('$connections')['sharepointonline']['connectionId']" 213 | } 214 | }, 215 | "method": "patch", 216 | "path": "[concat('/datasets/@{encodeURIComponent(encodeURIComponent(',variables('singlequote'),parameters('requestsSiteUrl'),variables('singlequote'),'))}/tables/@{encodeURIComponent(encodeURIComponent(',variables('singlequote'),parameters('ipLabelsListId'),variables('singlequote'),'))}/items/@{encodeURIComponent(first(body(',variables('singlequote'),'Get_label_from_the_sharepoint_list',variables('Singlequote'),')?[',variables('singlequote'),'Value',variables('singlequote'),'])?[',variables('singlequote'),'ID',variables('singlequote'),'])}')]" 217 | } 218 | } 219 | } 220 | }, 221 | "expression": { 222 | "and": [ 223 | { 224 | "equals": [ 225 | "@length(body('Get_label_from_the_SharePoint_list')?['value'])", 226 | 0 227 | ] 228 | } 229 | ] 230 | }, 231 | "type": "If" 232 | }, 233 | "Get_label_from_the_SharePoint_list": { 234 | "runAfter": {}, 235 | "type": "ApiConnection", 236 | "inputs": { 237 | "host": { 238 | "connection": { 239 | "name": "@parameters('$connections')['sharepointonline']['connectionId']" 240 | } 241 | }, 242 | "method": "get", 243 | "path": "[concat('/datasets/@{encodeURIComponent(encodeURIComponent(',variables('singlequote'),parameters('requestsSiteUrl'),variables('singlequote'),'))}/tables/@{encodeURIComponent(encodeURIComponent(',variables('singlequote'),parameters('ipLabelsListId'),variables('singlequote'),'))}/items')]", 244 | "queries": { 245 | "$filter": "LabelId eq '@{items('Loop_through_sensitivity_labels')?['id']}'" 246 | } 247 | } 248 | } 249 | }, 250 | "runAfter": { 251 | "Parse_sensitivity_labels_JSON": [ 252 | "Succeeded" 253 | ] 254 | }, 255 | "type": "Foreach" 256 | }, 257 | "Parse_sensitivity_labels_JSON": { 258 | "runAfter": { 259 | "List_sensitivity_labels": [ 260 | "Succeeded" 261 | ] 262 | }, 263 | "type": "ParseJson", 264 | "inputs": { 265 | "content": "@body('List_sensitivity_labels')", 266 | "schema": { 267 | "properties": { 268 | "@@odata.context": { 269 | "type": "string" 270 | }, 271 | "value": { 272 | "items": { 273 | "properties": { 274 | "color": { 275 | "type": "string" 276 | }, 277 | "description": { 278 | "type": "string" 279 | }, 280 | "id": { 281 | "type": "string" 282 | }, 283 | "isActive": { 284 | "type": "boolean" 285 | }, 286 | "name": { 287 | "type": "string" 288 | }, 289 | "parent": {}, 290 | "sensitivity": { 291 | "type": "integer" 292 | }, 293 | "tooltip": { 294 | "type": "string" 295 | } 296 | }, 297 | "required": [ 298 | "id", 299 | "name", 300 | "description", 301 | "color", 302 | "sensitivity", 303 | "tooltip", 304 | "isActive", 305 | "parent" 306 | ], 307 | "type": "object" 308 | }, 309 | "type": "array" 310 | } 311 | }, 312 | "type": "object" 313 | } 314 | } 315 | } 316 | }, 317 | "outputs": {} 318 | }, 319 | "parameters": { 320 | "$connections": { 321 | "value": { 322 | "keyvault": { 323 | "connectionId": "[concat('/subscriptions/',parameters('subscriptionId'),'/resourceGroups/',parameters('resourceGroupName'),'/providers/Microsoft.Web/connections/requestateam-kv')]", 324 | "connectionName": "keyvault", 325 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/keyvault')]" 326 | }, 327 | "sharepointonline": { 328 | "connectionId": "[concat('/subscriptions/',parameters('subscriptionId'),'/resourceGroups/',parameters('resourceGroupName'),'/providers/Microsoft.Web/connections/requestateam-spo')]", 329 | "connectionName": "requestateam-spo", 330 | "id": "[concat('/subscriptions/',parameters('subscriptionId'),'/providers/Microsoft.Web/locations/',parameters('location'),'/managedApis/sharepointonline')]" 331 | } 332 | } 333 | } 334 | } 335 | } 336 | } 337 | ] 338 | } -------------------------------------------------------------------------------- /Deployment/Templates/requestateam-sitetemplate.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 30 22 | clienttemplates.js 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 0 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 30 60 | 61 | clienttemplates.js 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | Department 74 | Company 75 | Office 76 | StateOrProvince 77 | CountryOrRegion 78 | JobTitle 79 | 80 | 81 | 82 | 0 83 | 84 | 85 | 86 | 87 | Department 88 | Company 89 | Office 90 | StateOrProvince 91 | CountryOrRegion 92 | JobTitle 93 | 94 | 95 | 96 | 0 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 30 141 | 142 | clienttemplates.js 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | Private 154 | 155 | Private 156 | Public 157 | 158 | 159 | 160 | 161 | Not Submitted 162 | 163 | Not Submitted 164 | Submitted 165 | Pending Approval 166 | Approved 167 | Rejected 168 | Team Creation 169 | Team Created 170 | Team Creation Failed 171 | 172 | 173 | 174 | 0 175 | 176 | 177 | 178 | 179 | Team Information 180 | 181 | Team Information 182 | Requirements 183 | Data Classification 184 | Template 185 | Submitted 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 0 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 30 237 | 238 | clienttemplates.js 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 0 249 | 250 | 251 | 252 | 253 | Department 254 | Company 255 | Office 256 | StateOrProvince 257 | CountryOrRegion 258 | JobTitle 259 | 260 | 261 | 262 | 0 263 | 264 | 265 | 266 | 267 | Department 268 | Company 269 | Office 270 | StateOrProvince 271 | CountryOrRegion 272 | JobTitle 273 | 274 | 275 | 276 | 0 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | products: 4 | - Power Apps 5 | - Power Automate 6 | - Microsoft Azure Logic Apps 7 | - SharePoint 8 | description: Power Apps solution that automates the team creation process based on core features and channel options 9 | urlFragment: microsoft-teams-apps-requestateam 10 | --- 11 | 12 | # Request-a-team App Template 13 | 14 | ## IMPORTANT NOTICE - This repo is no longer maintained and we cannot guarantee issues will be responded to. For a more up-to-date provisioning tool that provisions Teams AND much more please check out 'Provision Assist' - https://github.com/pnp/provision-assist-m365/. Provision Assist is an actively maintained project with ongoing updates. 15 | 16 | | [Documentation](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Home) | [Deployment guide](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Deployment-Guide) | [Architecture](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Solution-Overview) | [Teams Templates](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Teams-Templates) | [Naming Conventions](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Naming-Conventions) | [Sensitivity Labels](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Sensitivity-Labels) | [Refreshing Expired Secrets](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Refreshing-Expired-Secrets) | [V2](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/V2#request-a-team-v2) 17 | | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | 18 | 19 | Enterprise organizations have expressed a need to standardize and to promote best practices around the creation of new team instances. The **Request-a-team** App Template supports these goals by providing a framework that automates the team creation process based on core features and channel options which are relevant to optimizing usage. This enables faster response time for team requests and offers a wealth of personalization options for organizations to implement repeatable best practices on team collaboration. 20 | 21 | - Easy to use team request form for the collection of team scope, stakeholders (owners and members), and business justifications for new team instances 22 | 23 | - Embedded approval process for approval and/or rejection of requests submitted 24 | 25 | - Requestor and approver dashboards showing past and current requests with status 26 | 27 | - Automated team builds on approval, including creating new instances based on existing teams and channels 28 | 29 | ![Landing page](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Images/Landing_page.png) 30 | 31 | ### 4-Step request process wizard: 32 | 33 | 1. From a Microsoft Teams tab in a channel, end-users will use an easy 4-step wizard process to request new team instances, providing required details such as unique team name, owners, and scope (private, public), supplementary business questions give approvers the context they need for responding to requests 34 | 35 | 2. Once the request is submitted, an adaptive card will be posted to the designated team channel where approvers and admins will act upon the request 36 | 37 | 3. Once a request is approved by the app admins, the Azure Logic Apps service, which runs on periodic intervals, will provision the team using [Microsoft Graph APIs](https://docs.microsoft.com/en-us/graph/teams-concept-overview). The end-users and app admins will be able to track status of each request within the app. 38 | 39 | 40 | ![Request creation](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Images/Team_Info_2.png) 41 | 42 | ![Adaptive card in the team of approvers](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Images/Pending_approval.png) 43 | 44 | ![Approve submitted requests](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Images/Approve_requests.png) 45 | 46 | **Extending and optimizing the value of the Request-a-team App template**: 47 | 48 | End users can reference existing teams instances as templates during the request process. This is a great opportunity for the organization to build and promote previously tested team structures and services that best meet the desired departmental or information worker business outcomes (also see [here](https://support.microsoft.com/en-us/office/create-a-team-from-an-existing-team-f41a759b-3101-4af6-93bd-6aba0e5d7635?ui=en-us&rs=en-us&ad=us)). This means that the **Request-a-team** App template works right out-of-the-box to help in promoting and enabling everyone to reuse best practices to drive faster outcomes. 49 | 50 | 51 | ## Legal notice 52 | 53 | This app template is provided under the [MIT License](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/blob/master/LICENSE) terms. In addition to these terms, by using this app template you agree to the following: 54 | 55 | - You, not Microsoft, will license the use of your app to users or organization. 56 | 57 | - This app template is not intended to substitute your own regulatory due diligence or make you or your app compliant with respect to any applicable regulations, including but not limited to privacy, healthcare, employment, or financial regulations. 58 | 59 | - You are responsible for complying with all applicable privacy and security regulations including those related to use, collection and handling of any personal data by your app. This includes complying with all internal privacy and security policies of your organization if your app is developed to be sideloaded internally within your organization. Where applicable, you may be responsible for data related incidents or data subject requests for data collected through your app. 60 | 61 | - Any trademarks or registered trademarks of Microsoft in the United States and/or other countries and logos included in this repository are the property of Microsoft, and the license for this project does not grant you rights to use any Microsoft names, logos or trademarks outside of this repository. Microsoft’s general trademark guidelines can be found [here](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general.aspx). 62 | 63 | - If the app template enables access to any Microsoft Internet-based services (e.g., Office365), use of those services will be subject to the separately-provided terms of use. In such cases, Microsoft may collect telemetry data related to app template usage and operation. Use and handling of telemetry data will be performed in accordance with such terms of use. 64 | 65 | - Use of this template does not guarantee acceptance of your app to the Teams app store. To make this app available in the Teams app store, you will have to comply with the [submission and validation process](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/appsource/publish), and all associated requirements such as including your own privacy statement and terms of use for your app. 66 | 67 | 68 | ## Getting started 69 | 70 | Begin with the [Solution overview](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Solution-overview) to read about what the app does and how it works. 71 | 72 | When you're ready to try out Request-a-team app, or to use it in your own organization, follow the steps in the [Deployment guide](https://github.com/OfficeDev/microsoft-teams-apps-requestateam/wiki/Deployment-guide). 73 | 74 | ## Contributing 75 | 76 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 77 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 78 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 79 | 80 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 81 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 82 | provided by the bot. You will only need to do this once across all repos using our CLA. 83 | 84 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 85 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 86 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 87 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | --------------------------------------------------------------------------------