Please checkout Part I if you landed directly on this page.
This post is a continuation how to Forms and Power Automate can be used to create an approval workflow to automatically provision a Team. We will see the user’s experience here.
When user submits the Microsoft Form, the approver gets an email.
User’s responses
In my scenario, I’m using a service account called ‘Teams admin’ and I can see the email as below,
Email received by approver
In the Outlook.office.com, the approver can directly click on ‘Approve’ or ‘Reject’ and also add comments if they wish to.
Approver submitting approval in email
Once approved, the rest of the flow runs and at the end the user gets an email confirming the Team creation.
User receiving confirmation email
User can also launch Teams and check that he is part of the new Team that was created.
Created Team shows up in user’s Teams
Requested owners
As you can see the ‘Teams admin’ service account is also a owner in the Team it created. This might become an issue during compliance audits and this can be fixed by adding a step in Power Automate with a HTTP request.
The HTTP request option is a premium feature and hence no screenshot here.
Teams has become more popular in the recent work from home era and the list of features it offers are great and no doubt about it. Microsoft keeps adding more appealing features for remote and users working from office locations. As more companies adopt Teams for its collaboration features which help increase productivity, no one wants to lose control of the M365 environment because of what is bein allowed to be created or provisioned by the users.
In this post, I’ll go over a on a high-level of what is a Team and what happens when Team is created. Also I’ll go over steps which I recently used to implement a Teams provisioning with an approval workflow process using Microsoft Forms and Power Automate.
Table of Contents
What is a Team?
In Microsoft Teams, Teams are groups of people brought together for work, projects, or common interests. Teams are made up of two types of channels,
Standard
Is open for all members and anything posted is searchable by others
Private
Focused, private conversations with a specific audience. Example: Specific audience for a particular subject
Files shared are only viewable by the members of the channel and are stored in a separate SharePoint site from the rest of the team’s files
Shared channels
For collaborating with people inside and outside your team or organization
Only team owners can create shared channels
Only shared channel owners can add members or share the channel with a team
Can’t be changed to standard or private channel and vice versa
What happens when a Team is created?
When a Team is created, the following also gets provisioned,
A corresponding O365 group
Owner of the O365 Group is the Owner of the team
The members of the Group are the Members of the team, as added by the person who created the Team
An Exchange mailbox with a Group email address
Will appear in everyone’s Outlook client
A new SharePoint site is created
Sharing files via Teams leads to data being stored either in SharePoint or OneDrive
Files uploaded to a Teams channel are automatically saved to the Team’s SharePoint folder
Files uploaded in a 1:1 or group chat are stored in the OneDrive folder and shared only with colleagues in that conversation
A connection to OneNote
Team Creation Options
As I put together above, it is easy to see that adding a Team might seem like a simple task and it is but from a data security and governance point of view, it can easily get out of hands if controls are not put it place. When you role out Microsoft Teams, you get a few options on how to handle Team creation.
Allow all users to create Teams
This will allow users to create their own Teams without any restrictions. This is the easiest option but easy to lose control and will eventually add more burden on the IT admins
Block all users from creating Teams
This is the most restrictive option and users have to reach out to IT to create Teams. There is also a risk of users moving onto more accessible solutions that don’t take compliance and security policies seriously and ending up shadow IT in the process
Limit to a certain security group
With this option, we can limit a certain group of users to be able to create groups/Teams. I like this idea and to add to it, I’ll have someone from the compliance team in charge of this if that was an option. I’ve covered how to limit a certain group in an earlier post
That being said, IMO it is best to have an approval workflow and a controlled provisioning process for Teams creation. This will help any organization to prevent sprawl, uncontrollable growth, help IT admins reduce some unnecessary work and more importantly allow a controlled adoption of Microsoft Teams while helpdesk and IT admins catchup.
Teams Provisioning and Approval Workflow with Power Automate
I’ll detail the steps needed to create a provisioning workflow with the native capabilities in Forms and Power Automate.
Components involved in creating this workflow,
Microsoft Forms
Power Automate
SharePoint (optional)
I’ve put together a flowchart of the thought process,
Some considerations to keep in mind,
It is better to create the Microsoft Forms and Power Automate Flow using a service account which has a mailbox enabled and permissions within Teams to create Team. This is usually a good idea because, if this was created under a specific Teams administrator account and if he or she wins a lottery and figures not to show up for work anymore, this process can go on without disruption and is one less thing for you to worry while disabling the administrator’s account.😊
In my scenario, I’m using a service account to setup the components needed.
Microsoft Forms
Microsoft Forms is a great product and I love the simplicity. With enough time and effort, I think organizations can leverage Forms to get rid of most of what their users are doing on paper forms.
I’ve created a simple form titled ‘New Team Creation Request Form’ to collect the information needed to create a Team. You can also use SharePoint instead of Forms but this approach is much easier.
I’ve added branching to the 6th question.
Logic behind 6th question: If private Team, provide justification. If not, go to question 8
If you’d like to get fancy, there is PowerApps which will require more effort and depends on your knowledge into it.
Power Automate
Open Power Automate
Click My Flows
Click on New Flow and Select Automated cloud flow
Click Skip on the Build an automated cloud flow pop-up window
automated cloud flow
Search for ‘forms’ and select Microsoft Forms and then When a new response is submitted
Note: It is good practice to rename every step while you create it with the purpose of what that step does.
When a new response is submitted
As I created the Form with the same account, it shows up in the drop-down
Click New Step, Microsoft Forms –> Get response details
This action retrieves a form response
Select the Form in Form Id and in Response Id, click Add dynamic content and select Response Id
Add step, search for Azure AD and in actions select Get user
The next two steps are to get information about the requestor and the owner from Azure AD
This is useful while sending email to the approver, these values can be used
User Id or Principal Name –> Add dynamic content and select Responder’s Email from the form
Similarly in the second step, User Id or Principal Name –> Add dynamic content and select ‘Please enter email address of who will be this Team’s owner’ in under ‘Get response details’
In the next step, I’m initializing a variable called varCoOwners. And in the step after, I’m using the compose action to do a split based on comma
The reason why we need these two steps is, in the Form we have an entry to input co-owners and if more than one co-owner email address is entered by the user comma-separated, the entire value is considered a string
Power Automate is looking for an array
The split function breaks down a string into an array of strings using a delimiter we define
We are going to use comma
split(variables('varCoOwners'),',')
Compose split
Next step, add Start and wait for an approval
This starts an automated approval process and then waits for it to complete. The approval is cancelable
I’m choosing the Approval type as Approve/Reject – First to respond
Start and wait for an approval
Optional Step. Updating a SharePoint list for tracking.
SharePoint list
SharePoint – Create item
Update SharePoint
We add a condition to see if the request was approved
We add condition control which performs one or more tasks only if a condition is true or false
Outcome = Approve
Add control – Condition
Outcome = Approve
Continuing from the previous condition step,
If yes –> We create a Team, add Team owner, add Team co-owner(s) and send an email to the requestor
Create a Team based on submitted Form
Add Team Owner
Team = New Team ID
A user AAD ID for the user to add to a team = UPN from ‘Get Team owner’s info’ Azure AD step
Should the newly added user be an owner of the team = Yes
Add Team Co-owners
Search Control -> Apply to each -> In Select an output from previous steps, select Output of compose step
Azure AD -> Get User ->In User Id or Principal Name, select Current item
Teams -> Add a member to a team
Team = New Team ID
A user AAD ID for the user to add to a team = UPN from ‘Get Co-Owners’ Azure AD step
Should the newly added user be an owner of the team = Yes
If no –> We send an email to the requestor with comments from the approver
See below on how the steps are done,
If yes and If no
Continuation on ‘If yes’ after ‘Add co-owners to team’ step,
Send ‘request approved’ email to requestor
Final Automated Flow
This is how the final automated flow looks,
New Teams Request Workflow
I added a step to remove a member from group. This is for removing the service account which created the Team from being a member. As Teams are in turn Azure AD security groups, I used a remove member from group action to remove the service account. I noticed that this step failing often and the reason was the Azure AD group was not found and hence I added a delay of a 3 minutes and then run the action.
This might seem a little too overwhelming at first glance but once you understand the power of Microsoft Forms, Power Automate, SharePoint and the potential it brings to organizations, you’ll be complaining about what more features you’d like to have in this in no time.
I’ll cover the user experience of this process in a following post.
Azure Key Vault is used to safely manage Secrets, Certificates and Crytographic Keys used by cloud and on-premise applications. After the secrets are stored in Azure Key Vault, we can assign access policies to user accounts or SPNs to retrieve and use them.
In this post, I will cover,
How to create Azure Key Vault
Create and update secrets in Azure Key Vault
Create a new Azure AD application and SPN
Create a client Secret
Assign a policy in Azure Key Vault to allow access to the SPN we create
Store the Azure AD application ID and client secret in the SecretStore vault
Retrieve Secret from Azure Key vault using credentials stored in SecretStore vault
I’ll go through the steps in both the portal and via PowerShell.
Before proceeding further, make sure you are connected to Azure AD and Azure via PowerShell. You may or may not use the same global admin account to connect to both Azure AD and Azure, either way you can use the below cmdlets and adjust it accordingly where necessary.
Leave the other options default and click Review + create
The other options in the creation steps,
Access policy = I’ll go through it later in this post
Networking = All networks to make it publicly accessible
Tags = As necessary
Below are my settings,
Create a key vault
Create SPN in Azure AD
In this step, we’re creating a service principal in Azure AD. We will assign permissions for this SP to retrieve secrets from the Azure Key vault in later step.
In Azure AD, the application registration is the template used to create the SP. Also, the SP is what can be authenticated and authorized. Application and SP are associated by Application ID and they differ in it Object ID.
To create a new Azure AD application,
$appname = Read-Host "Enter a name for the Azure AD application"
New-AzureADApplication -DisplayName $appname
To create a service principal,
$appname = Read-Host "Enter name of Azure AD application"
$AppId = (Get-AzureADApplication -Filter "DisplayName eq '$appname'").AppId
New-AzureADServicePrincipal -AccountEnabled $true -AppId $AppId -DisplayName $appname
Create Client Secret
Next, we create a new client secret using the Get-AzureADApplicationPasswordCredential cmdlet,
$appname = Read-Host "Enter Azure AD application name to determine Object ID"
$appObjID = (Get-AzureADApplication -Filter "DisplayName eq '$appname'").Objectid
$KeyId = Read-Host "Enter value for secret identifier"
New-AzureADApplicationPasswordCredential -ObjectId $appObjID -CustomKeyIdentifier $KeyId
Copy the value in the output to a notepad as I have highlighted above. This value will not be available to copy later.
Assign Permissions
Using PowerShell
We can assign necessary permissions to the Azure AD application we created in above step, using the Set-AzKeyVaultAccessPolicy cmdlet,
$appname = Read-Host "Enter Azure AD application name to determine Object ID"
$Appid = (Get-AzureADApplication -Filter "DisplayName eq '$appname'").AppId
$kvName = Read-Host "Enter a name for Key Vault"
Set-AzKeyVaultAccessPolicy -VaultName $kvName -ServicePrincipalName $Appid -PermissionsToSecrets list,get
In the left navigation menu, click on Access policies
Select Permission model as Vault access policy
Click +Add Access Policy
In the Add access policy window
For Secret permissions, select Get and List
For Select principal, select the SPN we created earlier
Click Add
Click Save to save policy
Manage Secrets in Key Vault
Applications, scripts or users can create, update, delete and retrieve secrets if they have the necessary policy assigned to them
Creating/Updating Secrets
To create a new secret, we can use the Set-AzureKeyVaultSecret cmdlet,
$Secret = ConvertTo-SecureString -String 'Password' -AsPlainText -Force
$kvName = Read-Host "Enter a name for Key Vault"
$SecName = Read-Host "Enter a name for secret"
Set-AzKeyVaultSecret -VaultName $kvName -Name $SecName -SecretValue $Secret
The secret can be updated to a new value using the same Set-AzureKeyVaultSecret cmdlet,
$Secret = ConvertTo-SecureString -String 'Password' -AsPlainText -Force
$kvName = Read-Host "Enter a name for Key Vault"
$SecName = Read-Host "Enter a name for secret"
$Expires = (Get-Date).AddYears(2).ToUniversalTime()
$NBF =(Get-Date).ToUniversalTime()
$Tags = @{ 'Department' = 'IT'; 'Environment' = 'Production'}
Set-AzKeyVaultSecret -VaultName $kvName -Name $SecName -SecretValue $Secret -Expires $Expires -NotBefore $NBF -Tags $Tags
Retrieving Secrets
To retrieve the current version of a secret, we use the Get-AzureKeyVaultSecret cmdlet,
$kvName = Read-Host "Enter a name for Key Vault"
$SecName = Read-Host "Enter a name for secret"
$secruretext = (Get-AzKeyVaultSecret -VaultName $kvName -Name $SecName).SecretValue
This will assign the stored secret to the $secruretext variable as a SecureString. We can now pass this to any other cmdlets that require a SecureString.
As I’ve already covered the Microsoft.PowerShell.SecretManagement and Microsoft.PowerShell.SecretStore PS modules in an earlier post, I’ll follow on the premise and store the client secret we created in the local vault. This way, I don’t have to save the client secret in the code as plaintext. To do this, we can store the Application ID and Client secret in a PSCredential object to the store,
In the Windows PowerShell Credential request window, for User Name input the Application (client) ID of the Azure AD application and for password input the Client Secret value we copied into a notepad earlier.
I’ve also created another secret as string in the local vault with my tenant ID value.
Putting this all together, we can use these below lines in PowerShell automation scripts,
To retrieve the secure string stored in the Azure Key vault, I’m using these lines below. Also for demo purposes, I’m including the -AsPlainText to the Get-AzKeyVaultSecret cmdlet but as I mentioned earlier, we can store this secure string to a variable and pass it on to other cmdlets.
$kvName = Read-Host "Enter a name for Key Vault"
$SecName = Read-Host "Enter a name for secret"
Get-AzKeyVaultSecret -VaultName $kvName -Name $SecName -AsPlainText
#or
$secruretext = (Get-AzKeyVaultSecret -VaultName $kvName -Name $SecName).SecretValue
I know this was a lengthy post and it may have gotten a little confusing right at the end with too many things named vault🤷♂️
Hope this helped you out in figuring out in including Azure Key Vault in your PowerShell automations.
When it comes to automation with PowerShell we come across scenarios where credentials are needed for the script to run and I’ve seen scripts being used by admins where the passwords are there in plain-text. It starts with..’this is only for testing‘,’oh!..that’s just a service account‘ and next thing you know, it is in production and the said service account has more previleged roles attached to it than when it was in testing.
I’ve done it too but I’ve realized that I can spend some time to understand and make it a practice on storing the passwords securely. I went over this issue briefly in a different post when earlier. In this post, I will go over the steps on how to use the Microsoft’s SecretManagement and Secret Store modules to manage passwords securely in a script and interactively.
And yes, there is a long list of 3rd party secret vaults that can be used to accomplish this like HashiCorp Vault, LastPass, KeePass, etc. I’ve started using Azure Key Vault which is great and doesn’t cost a lot. The Azure Key Vault is great for storing and sharing secrets in an organization and also to set a process around it. I will cover this in a future post.
Table of Contents
Installing the modules
To start storing and managing passwords from a encrypted vault, we need to install the PowerShell SecretManagement and SecretStore modules. These modules require Windows PowerShell version 5.1 or PowerShell Core 6.x, 7.x
Microsoft.PowerShell.SecretManagement – Provides a convenient way to store and retrieve secrets Microsoft.PowerShell.SecretStore – Provides local secure store extension vault for Microsoft.PowerShell.SecretManagement module
First, we need to create a local secret vault. I will name mine CredsDB
$vaultName = Read-Host "Enter a name for the vault"
$vaultDesc = Read-Host "Enter vault description"
Register-SecretVault -Name $vaultName -ModuleName Microsoft.PowerShell.SecretStore -Description $vaultDesc
To display the registered vault,
Get-SecretVault
To set a master password to the SecretStore vault
To create a master password to access the SecretStore,
Get-SecretStoreConfiguration
The following settings determine access to the password stores,
Scope – The Scope is always CurrentUser. AllUsers Scope is not supported
Authentication – Access vault using a master password
PasswordTimeout – 900 seconds, duration of the session before we need to enter the master password
Interaction – Prompt – to make changes
Note: If you forget the vault master password, you won’t be able to access stored data.
To change master password,
$oldPassword = Read-Host "Enter old password" -AsSecureString
$newPassword = Read-Host "Enter new password" -AsSecureString
Set-SecretStorePassword -NewPassword $newPassword -Password $oldPassword
Storing and updating secrets
Now that we have created a new secret vault, we are ready to start storing sensitive information into it. The secret store accepts the following data types as secrets,
Byte[]
Hashtable
PSCredential
SecureString
String
To add a new username and password, PSCredential object to the store,
If you missed to set the master password in the earlier step I described, you’ll be prompted to set the master password while running the Set-Secret cmdlet for the first time.
If you need to update the secret at a later point in time, use the same Set-Secret cmdlet to overwrite the existing secret
Retrieving secrets
To retrieve the entries in the vault, we can use the below cmdlet to unlock the vault first. Type the vault’s password and press enter,
Unlock-SecretStore
To display entries in the secret vault,
Get-SecretInfo
To retrieve a secret’s value shown as System.Security.SecureString,
$Secret = Read-Host "Enter name of the secret"
Get-Secret -Name $Secret
To view the password in plaintext,
$Secret = Read-Host "Enter name of the secret"
(Get-Secret -Name $Secret).GetNetworkCredential() | Select UserName, Password
Using secrets in PowerShell automation
We’ve registered, created a new vault and also created a new secret and also retrieved the stored secret in the above steps. The above retrieval process needs manual interaction and in automation we need to avoid that. I know what you are thinking, if there is a master password, we’ll need to type that in anyway to unlock the stored secret.
You might be tempted to disable the request of the master password request to access the secret by using the below cmdlet, but please don’t. This method might be good to do some quick testing but is not recommended in production environments.
Set-SecretStoreConfiguration -Authentication None
There are many options to unlocking the secret store without manually entering the master password while making sure it is not stored anywhere in plain text. One method is to save the master password in an encrypted xml file.
Use the below command to save master password in a CliXml file.
The command will prompt for credentials
Type any username
Type the master password
You can name the xml file to your preference
It is recommended to store the xml file in a location where you can lock down the permissions
Get-Credential | Export-CliXml c:\scripts\vpd.xml
We can confirm the credential file exists and contains the encrypted master password. The XML file will not display the password in plain text as you can see in the screenshot.
Get-Content c:\scripts\vpd.xml
Now we can import the encrypted password from the xml file to a variable
We can use this above variable to unlock the Secret Store,
Unlock-SecretStore -Password $vpwd
To retrieve the secret,
$Secret = Read-Host "Enter name of the secret"
(Get-Secret -Name $Secret).GetNetworkCredential() | Select Username,Password
With this in place, to keep things simple you can use the variable and Unlock-SecretStore in PowerShell automation scripts. We can use this to connect to Azure AD tenant or to O365.
In a O365 tenant I manage, I had rolled out the Azure Information Protection labels from earlier. The recent requirement was to make sure the sensitivity labels will apply to group across services like Outlook, Microsoft Teams and SharePoint online.
When I checked the sensitivity label, I noticed the ‘Groups and sites’ option greyed out and which lead me to research a bit into this and write my findings below,
Groups & sites greyed out
Enable sensitivity labels for containers in Azure AD
Sensitivity labeling for containers i.e., groups and sites, should enabled before we can configure the settings in the sensitivity labeling wizard. Else, it will be greyed out as in screenshot above.
To determine current group settings for your Azure AD organization, use the below cmdlet. If no group settings are defined, this cmdlet won’t return any output value.
Get-AzureADDirectorySetting | fl
In my scenario, I have only one setting and it was easier to see it. But your organization might have more than one setting and in that case, you can use this below cmdlet to search and determine the setting.
Get-AzureADDirectorySetting -Id (Get-AzureADDirectorySetting | where -Property DisplayName -Value "Group.Unified" -EQ).id
EnableMIPLabels = false
Below, I’m storing the value of the cmdlet’s output into the $Setting variable. And once stored, I’m setting ‘True’ as the value for ‘EnableMIPLabels’. I’m listing out both methods, what I used and what you can potentially use. The second method is much easier.
$Setting = Get-AzureADDirectorySetting -Id <Group.Unified policy's Id from your tenant>
$Setting["EnableMIPLabels"] = "True"
Set-AzureADDirectorySetting -Id $Setting.Id -DirectorySetting $Setting