Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- Added `Add-PnPTenantSearchCrawledProperty` cmdlet which allows creation of a tenant level crawled property for SharePoint Online search. [#5373](https://github.com/pnp/powershell/pull/5373)
- Added `GitLab CI/CD` support to `Connect-PnPOnline -FederatedIdentity`. [#5395](https://github.com/pnp/powershell/pull/5395)
- Added `PreferredDataLocation` support to `Set-PnPMicrosoft365Group` and documented it for `Get-PnPMicrosoft365Group` and `New-PnPMicrosoft365Group`. [#5402](https://github.com/pnp/powershell/pull/5402)
- Added `Standard` and `EDU_Staff` template support to `New-PnPTeamsTeam` and provision Teams templates through Microsoft Graph team templates. Note that `EDU_Class` and `EDU_PLC` are now also provisioned through Microsoft Graph team templates (`POST /teams`) instead of the previous Microsoft 365 Group creation and teamify flow. [#999](https://github.com/pnp/powershell/issues/999)

### Changed

Expand Down
57 changes: 50 additions & 7 deletions documentation/New-PnPTeamsTeam.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ online version: https://pnp.github.io/powershell/cmdlets/New-PnPTeamsTeam.html
**Required Permissions**

* Microsoft Graph API: Group.ReadWrite.All
* Microsoft Graph API: Team.Create when using -Template

Creates a new team in Microsoft Teams or teamifies an existing Microsoft 365 Group. If the Microsoft 365 Group does not exist yet, it will create it first and then add a Microsoft Teams team to the group. If it does already exist, it will use the provided Microsoft 365 Group and just teamify it by adding a Microsoft Teams team to it.

Expand Down Expand Up @@ -46,7 +47,8 @@ New-PnPTeamsTeam -DisplayName <String> [-MailNickName <String>] [-Description <S
[-Owners <String[]>] [-Members <String[]>]
[-ResourceBehaviorOptions <TeamResourceBehaviorOptions>]
[-SensitivityLabels <GUID[]>]

[-Template <TeamsTemplateType>]

```

## DESCRIPTION
Expand Down Expand Up @@ -98,6 +100,34 @@ New-PnPTeamsTeam -DisplayName "myPnPDemo1" -Visibility Private -Owners "user1@co

This will create a new Microsoft Teams team called "myPnPDemo1" and sets the privacy to Private. User1 and user2 will be added as owners. User3 will be added as a member. The team will also get the sensitivity label value corresponding to the GUID specified.

### EXAMPLE 7
```powershell
New-PnPTeamsTeam -DisplayName "Staff Team" -Visibility Private -Template EDU_Staff -Owners "principal@contoso.onmicrosoft.com"
```

This will create a new Microsoft Teams education staff team called "Staff Team" and adds the provided user as owner. Owners are required when using application permissions.

### EXAMPLE 8
```powershell
New-PnPTeamsTeam -DisplayName "Biology 101" -Template EDU_Class -Owners "teacher@contoso.onmicrosoft.com" -Members "student@contoso.onmicrosoft.com"
```

This will create a new Microsoft Teams education class team called "Biology 101". Microsoft Graph sets class team visibility to HiddenMembership.

### EXAMPLE 9
```powershell
New-PnPTeamsTeam -DisplayName "Math PLC" -Visibility Private -Template EDU_PLC -Owners "lead@contoso.onmicrosoft.com"
```

This will create a new Microsoft Teams education professional learning community team called "Math PLC".

### EXAMPLE 10
```powershell
New-PnPTeamsTeam -DisplayName "Project Team" -Visibility Private -Template Standard -Owners "owner@contoso.onmicrosoft.com"
```

This will create a new standard Microsoft Teams team through Microsoft Graph team templates. Omitting Template keeps the existing Microsoft 365 Group creation and teamify behavior.

## PARAMETERS

### -AllowAddRemoveApps
Expand Down Expand Up @@ -381,7 +411,7 @@ Accept wildcard characters: False
```

### -MailNickName
The MailNickName parameter specifies the alias for the associated Microsoft 365 Group. This value will be used for the mail enabled object and will be used as PrimarySmtpAddress for this Microsoft 365 Group.The value of the MailNickName parameter has to be unique across your tenant.
The MailNickName parameter specifies the alias for the associated Microsoft 365 Group. This value will be used for the mail enabled object and will be used as PrimarySmtpAddress for this Microsoft 365 Group.The value of the MailNickName parameter has to be unique across your tenant. This parameter is ignored when used together with Template because Microsoft Graph creates the backing group.

```yaml
Type: String
Expand Down Expand Up @@ -410,6 +440,20 @@ Accept pipeline input: False
Accept wildcard characters: False
```

### -Template
The Microsoft Teams template to use for the new team. Use Standard for the standard team template, EDU_Class for the education class template, EDU_PLC for the education professional learning community template, or EDU_Staff for the education staff template. When this parameter is used, Microsoft Graph provisions the team using POST /teams with a teamsTemplates binding and creates the backing Microsoft 365 Group automatically. Owners are required when using application permissions. Owners and Members are added to the initial members collection when provisioning the team; Microsoft Graph enforces its own limit on the number of users allowed in that collection. Omit this parameter to keep the existing Microsoft 365 Group creation and teamify behavior.

```yaml
Type: TeamsTemplateType
Parameter Sets: For a new group
Accepted values: None, Standard, EDU_Class, EDU_PLC, EDU_Staff
Required: False
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
```

### -Visibility
Set to Public to allow all users in your organization to join the group by default. Set to Private to require that an owner approve the join request.

Expand Down Expand Up @@ -440,7 +484,7 @@ Accept wildcard characters: False
```

### -Owners
The User Principal Name(s) of the user(s) to be added to the Microsoft 365 Group as owners. If omitted and the cmdlet is run using a token containing a user identity, such as when logging on with -Interactive or -DeviceLogin, the user used to authenticate with would become the owner. You can provide as many owners as you want, as long as you stay within the [Microsoft 365 Groups limits](https://learn.microsoft.com/microsoft-365/admin/create-groups/office-365-groups?view=o365-worldwide#group-limits). Notice that e-mail addresses are not accepted, if they differ from the User Principal Name on the same account.
The User Principal Name(s) of the user(s) to be added to the Microsoft 365 Group as owners. If omitted and the cmdlet is run using a token containing a user identity, such as when logging on with -Interactive or -DeviceLogin, the user used to authenticate with would become the owner. You can provide as many owners as you want, as long as you stay within the [Microsoft 365 Groups limits](https://learn.microsoft.com/microsoft-365/admin/create-groups/office-365-groups?view=o365-worldwide#group-limits). When using Template, Owners and Members are added to the initial members collection and Microsoft Graph enforces its own limit on the number of users allowed in that collection. Notice that e-mail addresses are not accepted, if they differ from the User Principal Name on the same account.

```yaml
Type: String[]
Expand All @@ -454,7 +498,7 @@ Accept wildcard characters: False
```

### -Members
The User Principal Name(s) of the user(s) to be added to the Microsoft 365 Group as members. You can provide as many members as you want, as long as you stay within the [Microsoft 365 Groups limits](https://learn.microsoft.com/microsoft-365/admin/create-groups/office-365-groups?view=o365-worldwide#group-limits). Notice that e-mail addresses are not accepted, if they differ from the User Principal Name on the same account.
The User Principal Name(s) of the user(s) to be added to the Microsoft 365 Group as members. You can provide as many members as you want, as long as you stay within the [Microsoft 365 Groups limits](https://learn.microsoft.com/microsoft-365/admin/create-groups/office-365-groups?view=o365-worldwide#group-limits). When using Template, Owners and Members are added to the initial members collection and Microsoft Graph enforces its own limit on the number of users allowed in that collection. Notice that e-mail addresses are not accepted, if they differ from the User Principal Name on the same account.

```yaml
Type: String[]
Expand All @@ -468,7 +512,7 @@ Accept wildcard characters: False

### -ResourceBehaviorOptions

Allows providing ResourceBehaviorOptions which accepts multiple values that specify group behaviors for a Microsoft 365 Group. This will only work when you create a new Microsoft 365 Group, it will not work for existing groups.
Allows providing ResourceBehaviorOptions which accepts multiple values that specify group behaviors for a Microsoft 365 Group. This will only work when you create a new Microsoft 365 Group, it will not work for existing groups. This parameter cannot be used together with Template.

```yaml
Type: TeamResourceBehaviorOptions
Expand All @@ -483,7 +527,7 @@ Accept wildcard characters: False
```

### -SensitivityLabels
The Sensitivity label to be set to the Microsoft 365 Group and Team. To retrieve the sensitivity label you need to use the Graph API mentioned [here](https://learn.microsoft.com/en-us/graph/api/informationprotectionlabel-get?view=graph-rest-beta&tabs=http).
The Sensitivity label to be set to the Microsoft 365 Group and Team. To retrieve the sensitivity label you need to use the Graph API mentioned [here](https://learn.microsoft.com/en-us/graph/api/informationprotectionlabel-get?view=graph-rest-beta&tabs=http). This parameter cannot be used together with Template.

```yaml
Type: GUID[]
Expand All @@ -498,4 +542,3 @@ Accept wildcard characters: False
## RELATED LINKS

[Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp)

2 changes: 2 additions & 0 deletions src/Commands/Enums/TeamsTemplateType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ public enum TeamsTemplateType
None,
EDU_Class,
EDU_PLC,
EDU_Staff,
Standard
}
}
49 changes: 49 additions & 0 deletions src/Commands/Model/Graph/TeamsAsyncOperation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
namespace PnP.PowerShell.Commands.Model.Graph
{
/// <summary>
/// Represents a Microsoft Teams async operation returned by Microsoft Graph.
/// </summary>
public class TeamsAsyncOperation
{
/// <summary>
/// Unique identifier of the operation.
/// </summary>
public string Id { get; set; }

/// <summary>
/// Current status of the operation.
/// </summary>
public string Status { get; set; }

/// <summary>
/// Identifier of the team that was created or modified by the operation.
/// </summary>
public string TargetResourceId { get; set; }

/// <summary>
/// Location of the team that was created or modified by the operation.
/// </summary>
public string TargetResourceLocation { get; set; }

/// <summary>
/// Error details returned when the operation fails.
/// </summary>
public TeamsAsyncOperationError Error { get; set; }
}

/// <summary>
/// Represents Teams async operation error details returned by Microsoft Graph.
/// </summary>
public class TeamsAsyncOperationError
{
/// <summary>
/// Error code returned by Microsoft Graph.
/// </summary>
public string Code { get; set; }

/// <summary>
/// Error message returned by Microsoft Graph.
/// </summary>
public string Message { get; set; }
}
}
4 changes: 4 additions & 0 deletions src/Commands/Model/Teams/TeamSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public class TeamCreationInformation
[JsonConverter(typeof(JsonStringEnumConverter))]
public TeamGiphyContentRating GiphyContentRating { get; set; }

[JsonIgnore]
public bool GiphyContentRatingSpecified { get; set; }

public bool? AllowStickersAndMemes { get; set; }

public bool? AllowCustomMemes { get; set; }
Expand Down Expand Up @@ -69,6 +72,7 @@ public TeamCreationInformation(Team team)
Classification = team.Classification;
AllowGiphy = team.FunSettings.AllowGiphy;
GiphyContentRating = team.FunSettings.GiphyContentRating;
GiphyContentRatingSpecified = true;
AllowStickersAndMemes = team.FunSettings.AllowStickersAndMemes;
AllowCustomMemes = team.FunSettings.AllowCustomMemes;
AllowGuestCreateUpdateChannels = team.GuestSettings.AllowCreateUpdateChannels;
Expand Down
37 changes: 36 additions & 1 deletion src/Commands/Teams/NewTeamsTeam.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
using PnP.PowerShell.Commands.Utilities;
using System;
using System.Management.Automation;
using ConnectionMethod = PnP.PowerShell.Commands.Model.ConnectionMethod;

namespace PnP.PowerShell.Commands.Teams
{
[Cmdlet(VerbsCommon.New, "PnPTeamsTeam")]
[RequiredApiDelegatedOrApplicationPermissions("graph/Group.ReadWrite.All")]
[RequiredApiDelegatedOrApplicationPermissions("graph/Team.Create")]
public class NewTeamsTeam : PnPGraphCmdlet
{
private const string ParameterSet_EXISTINGGROUP = "For an existing group";
Expand Down Expand Up @@ -109,6 +111,31 @@ public class NewTeamsTeam : PnPGraphCmdlet

protected override void ExecuteCmdlet()
{
var contextSettings = Connection.Context.GetContextSettings();

if (Template != TeamsTemplateType.None)
{
if (ParameterSpecified(nameof(MailNickName)))
{
LogWarning($"{nameof(MailNickName)} is ignored when using {nameof(Template)} because Microsoft Graph creates the backing group when provisioning a team from a template.");
}

if (ParameterSpecified(nameof(ResourceBehaviorOptions)) || ParameterSpecified(nameof(SensitivityLabels)))
{
throw new PSInvalidOperationException($"{nameof(ResourceBehaviorOptions)} and {nameof(SensitivityLabels)} are not supported when using {nameof(Template)} because Microsoft Graph creates the backing group when provisioning a team from a template.");
}

if (IsApplicationConnection(Connection.ConnectionMethod) && (Owners == null || Owners.Length == 0))
{
throw new PSInvalidOperationException($"{nameof(Owners)} is required when using {nameof(Template)} with application permissions because Microsoft Graph requires a user in the members collection when creating a team with application permissions.");
}

if (Template == TeamsTemplateType.EDU_Class && ParameterSpecified(nameof(Visibility)))
{
LogWarning("The EDU_Class template sets team visibility to HiddenMembership and does not allow overriding it. The Visibility value will be ignored by Microsoft Graph.");
}
}

var teamCI = new TeamCreationInformation()
{
AllowAddRemoveApps = AllowAddRemoveApps,
Expand All @@ -130,13 +157,13 @@ protected override void ExecuteCmdlet()
Description = Description,
DisplayName = DisplayName,
GiphyContentRating = GiphyContentRating,
GiphyContentRatingSpecified = ParameterSpecified(nameof(GiphyContentRating)),
GroupId = GroupId,
ShowInTeamsSearchAndSuggestions = ShowInTeamsSearchAndSuggestions,
Visibility = (GroupVisibility)Enum.Parse(typeof(GroupVisibility), Visibility.ToString()),
AllowCreatePrivateChannels = AllowCreatePrivateChannels
};

var contextSettings = Connection.Context.GetContextSettings();
if (contextSettings.Type == Framework.Utilities.Context.ClientContextType.AzureADCertificate)
{
if (SensitivityLabels != null && SensitivityLabels.Length > 0)
Expand All @@ -148,5 +175,13 @@ protected override void ExecuteCmdlet()

WriteObject(TeamsUtility.NewTeam(GraphRequestHelper, GroupId, DisplayName, Description, Classification, MailNickName, (GroupVisibility)Enum.Parse(typeof(GroupVisibility), Visibility.ToString()), teamCI, Owners, Members, SensitivityLabels, Template, ResourceBehaviorOptions));
}

private static bool IsApplicationConnection(ConnectionMethod connectionMethod)
{
return connectionMethod == ConnectionMethod.AzureADAppOnly ||
connectionMethod == ConnectionMethod.ManagedIdentity ||
connectionMethod == ConnectionMethod.AzureADWorkloadIdentity ||
connectionMethod == ConnectionMethod.FederatedIdentity;
}
}
}
Loading
Loading