diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b20d9c10..eb2fff8ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Added `-KnowledgeAgentEnabled` and `-KnowledgeAgentSelectedSitesList` parameter to `Set-PnPTenant` cmdlets to support knowledge agents. - Added `-Force` parameter to `Remove-PnPTerm` cmdlet to remove terms without confirmation. - Added `Import-PnPFlow` cmdlet to import Power Automate in the tenant. [#4854](https://github.com/pnp/powershell/pull/4854) +- Added `Get-PnPListRule`, `Add-PnPListRule`, `Set-PnPListRule` and `Remove-PnPListRule` cmdlets to manage SharePoint Rules as a replacement for the retiring SharePoint Alerts feature [#5123](https://github.com/pnp/powershell/pull/5123) ### Changed - Improved `Get-PnPTerm` cmdlet to show a better error message. [#4933](https://github.com/pnp/powershell/pull/4933) diff --git a/documentation/Add-PnPListRule.md b/documentation/Add-PnPListRule.md new file mode 100644 index 000000000..fae7ec4fc --- /dev/null +++ b/documentation/Add-PnPListRule.md @@ -0,0 +1,208 @@ +--- +Module Name: PnP.PowerShell +schema: 2.0.0 +applicable: SharePoint Online +online version: https://pnp.github.io/powershell/cmdlets/Add-PnPListRule.html +external help file: PnP.PowerShell.dll-Help.xml +title: Add-PnPListRule +--- + +# Add-PnPListRule + +## SYNOPSIS +Adds a new SharePoint list or library rule. + +## SYNTAX + +```powershell +Add-PnPListRule -List -Title -TriggerEventType -ActionType + [-Description ] [-EmailRecipients ] [-EmailSubject ] [-EmailBody ] + [-Condition ] [-Enabled] [-Connection ] +``` + +## DESCRIPTION +Adds a new rule to a SharePoint list or library. SharePoint Rules are the replacement for SharePoint Alerts which are being retired. Rules can trigger actions like sending email notifications when items are created, modified, or deleted. + +## EXAMPLES + +### EXAMPLE 1 +```powershell +Add-PnPListRule -List "Demo List" -Title "Notify on new items" -TriggerEventType "create" -ActionType "sendEmail" -EmailRecipients "user@contoso.com" +``` + +Creates a rule that sends an email to the specified recipient when a new item is created in the "Demo List". + +### EXAMPLE 2 +```powershell +Add-PnPListRule -List "Documents" -Title "Document modified alert" -TriggerEventType "update" -ActionType "sendEmail" -EmailRecipients "team@contoso.com" -EmailSubject "Document Updated" -EmailBody "A document has been modified in the library" +``` + +Creates a rule that sends a custom email when a document is modified in the "Documents" library. + +### EXAMPLE 3 +```powershell +Add-PnPListRule -List "Tasks" -Title "Task deleted notification" -TriggerEventType "delete" -ActionType "sendEmail" -EmailRecipients "manager@contoso.com","admin@contoso.com" -Description "Notify managers when tasks are deleted" +``` + +Creates a rule that notifies multiple recipients when a task is deleted, with a description for the rule. + +## PARAMETERS + +### -ActionType +The type of action to perform when the rule is triggered (e.g., "sendEmail"). + +```yaml +Type: String +Parameter Sets: (All) + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Condition +Optional condition that must be met for the rule to trigger. + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Connection +Optional connection to be used by the cmdlet. Retrieve the value for this parameter by either specifying -ReturnConnection on Connect-PnPOnline or by executing Get-PnPConnection. + +```yaml +Type: PnPConnection +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Description +Optional description for the rule. + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EmailBody +The body content for email notifications. + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EmailRecipients +The email addresses to send notifications to. + +```yaml +Type: String[] +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EmailSubject +The subject line for email notifications. + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Enabled +Whether the rule is enabled. Enabled by default. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) + +Required: False +Position: Named +Default value: True +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -List +The ID, Title or Url of the list. + +```yaml +Type: ListPipeBind +Parameter Sets: (All) + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -Title +The title/name of the rule. + +```yaml +Type: String +Parameter Sets: (All) + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TriggerEventType +The type of event that triggers the rule (e.g., "create", "update", "delete"). + +```yaml +Type: String +Parameter Sets: (All) + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +## RELATED LINKS + +[Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) +[SharePoint Alerts Retirement](https://support.microsoft.com/en-us/office/sharepoint-alerts-retirement-813a90c7-3ff1-47a9-8a2f-152f48b2486f) diff --git a/documentation/Get-PnPListRule.md b/documentation/Get-PnPListRule.md new file mode 100644 index 000000000..161c289d7 --- /dev/null +++ b/documentation/Get-PnPListRule.md @@ -0,0 +1,94 @@ +--- +Module Name: PnP.PowerShell +schema: 2.0.0 +applicable: SharePoint Online +online version: https://pnp.github.io/powershell/cmdlets/Get-PnPListRule.html +external help file: PnP.PowerShell.dll-Help.xml +title: Get-PnPListRule +--- + +# Get-PnPListRule + +## SYNOPSIS +Retrieves SharePoint list or library rules. + +## SYNTAX + +```powershell +Get-PnPListRule -List [-Identity ] [-Connection ] +``` + +## DESCRIPTION +Retrieves all rules or a specific rule from a SharePoint list or library. SharePoint Rules are the replacement for SharePoint Alerts which are being retired. + +## EXAMPLES + +### EXAMPLE 1 +```powershell +Get-PnPListRule -List "Demo List" +``` + +Returns all rules configured on the "Demo List". + +### EXAMPLE 2 +```powershell +Get-PnPListRule -List "Demo List" -Identity "12345678-1234-1234-1234-123456789012" +``` + +Returns the rule with the specified ID from the "Demo List". + +### EXAMPLE 3 +```powershell +Get-PnPListRule -List "Demo List" -Identity "My Rule" +``` + +Returns rules with the title "My Rule" from the "Demo List". + +## PARAMETERS + +### -Connection +Optional connection to be used by the cmdlet. Retrieve the value for this parameter by either specifying -ReturnConnection on Connect-PnPOnline or by executing Get-PnPConnection. + +```yaml +Type: PnPConnection +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Identity +The ID or Title of the rule to retrieve. If not specified, all rules for the list will be returned. + +```yaml +Type: RulePipeBind +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -List +The ID, Title or Url of the list. + +```yaml +Type: ListPipeBind +Parameter Sets: (All) + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +## RELATED LINKS + +[Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) +[SharePoint Alerts Retirement](https://support.microsoft.com/en-us/office/sharepoint-alerts-retirement-813a90c7-3ff1-47a9-8a2f-152f48b2486f) diff --git a/documentation/Remove-PnPListRule.md b/documentation/Remove-PnPListRule.md new file mode 100644 index 000000000..f26ba5775 --- /dev/null +++ b/documentation/Remove-PnPListRule.md @@ -0,0 +1,101 @@ +--- +Module Name: PnP.PowerShell +schema: 2.0.0 +applicable: SharePoint Online +online version: https://pnp.github.io/powershell/cmdlets/Remove-PnPListRule.html +external help file: PnP.PowerShell.dll-Help.xml +title: Remove-PnPListRule +--- + +# Remove-PnPListRule + +## SYNOPSIS +Removes a SharePoint list or library rule. + +## SYNTAX + +```powershell +Remove-PnPListRule -List -Identity [-Force] [-Connection ] +``` + +## DESCRIPTION +Removes a rule from a SharePoint list or library. + +## EXAMPLES + +### EXAMPLE 1 +```powershell +Remove-PnPListRule -List "Demo List" -Identity "12345678-1234-1234-1234-123456789012" +``` + +Removes the rule with the specified ID from the "Demo List". A confirmation prompt will be displayed. + +### EXAMPLE 2 +```powershell +Remove-PnPListRule -List "Documents" -Identity "My Rule" -Force +``` + +Removes the rule with title "My Rule" from the "Documents" library without prompting for confirmation. + +## PARAMETERS + +### -Connection +Optional connection to be used by the cmdlet. Retrieve the value for this parameter by either specifying -ReturnConnection on Connect-PnPOnline or by executing Get-PnPConnection. + +```yaml +Type: PnPConnection +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Force +If specified, the rule will be removed without prompting for confirmation. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) + +Required: False +Position: Named +Default value: False +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Identity +The ID or Title of the rule to remove. + +```yaml +Type: RulePipeBind +Parameter Sets: (All) + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -List +The ID, Title or Url of the list. + +```yaml +Type: ListPipeBind +Parameter Sets: (All) + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +## RELATED LINKS + +[Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) +[SharePoint Alerts Retirement](https://support.microsoft.com/en-us/office/sharepoint-alerts-retirement-813a90c7-3ff1-47a9-8a2f-152f48b2486f) diff --git a/documentation/Set-PnPListRule.md b/documentation/Set-PnPListRule.md new file mode 100644 index 000000000..b49f5c06b --- /dev/null +++ b/documentation/Set-PnPListRule.md @@ -0,0 +1,222 @@ +--- +Module Name: PnP.PowerShell +schema: 2.0.0 +applicable: SharePoint Online +online version: https://pnp.github.io/powershell/cmdlets/Set-PnPListRule.html +external help file: PnP.PowerShell.dll-Help.xml +title: Set-PnPListRule +--- + +# Set-PnPListRule + +## SYNOPSIS +Updates an existing SharePoint list or library rule. + +## SYNTAX + +```powershell +Set-PnPListRule -List -Identity [-Title ] [-Description ] + [-TriggerEventType ] [-ActionType ] [-EmailRecipients ] [-EmailSubject ] + [-EmailBody ] [-Condition ] [-Enabled ] [-Connection ] +``` + +## DESCRIPTION +Updates an existing rule in a SharePoint list or library. Only the specified parameters will be updated. + +## EXAMPLES + +### EXAMPLE 1 +```powershell +Set-PnPListRule -List "Demo List" -Identity "12345678-1234-1234-1234-123456789012" -Title "Updated Rule Title" +``` + +Updates the title of the specified rule. + +### EXAMPLE 2 +```powershell +Set-PnPListRule -List "Documents" -Identity "My Rule" -Enabled $false +``` + +Disables the rule with title "My Rule". + +### EXAMPLE 3 +```powershell +Set-PnPListRule -List "Tasks" -Identity "12345678-1234-1234-1234-123456789012" -EmailRecipients "newuser@contoso.com","admin@contoso.com" -EmailSubject "New Subject" +``` + +Updates the email recipients and subject for the specified rule. + +## PARAMETERS + +### -ActionType +The type of action to perform when the rule is triggered. + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Condition +Optional condition that must be met for the rule to trigger. + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Connection +Optional connection to be used by the cmdlet. Retrieve the value for this parameter by either specifying -ReturnConnection on Connect-PnPOnline or by executing Get-PnPConnection. + +```yaml +Type: PnPConnection +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Description +The description for the rule. + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EmailBody +The body content for email notifications. + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EmailRecipients +The email addresses to send notifications to. + +```yaml +Type: String[] +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -EmailSubject +The subject line for email notifications. + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Enabled +Whether the rule is enabled or disabled. + +```yaml +Type: Boolean +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Identity +The ID or Title of the rule to update. + +```yaml +Type: RulePipeBind +Parameter Sets: (All) + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -List +The ID, Title or Url of the list. + +```yaml +Type: ListPipeBind +Parameter Sets: (All) + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -Title +The title/name of the rule. + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -TriggerEventType +The type of event that triggers the rule. + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +## RELATED LINKS + +[Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) +[SharePoint Alerts Retirement](https://support.microsoft.com/en-us/office/sharepoint-alerts-retirement-813a90c7-3ff1-47a9-8a2f-152f48b2486f) diff --git a/src/Commands/Base/PipeBinds/ListRulePipeBind.cs b/src/Commands/Base/PipeBinds/ListRulePipeBind.cs new file mode 100644 index 000000000..c8adbaaa4 --- /dev/null +++ b/src/Commands/Base/PipeBinds/ListRulePipeBind.cs @@ -0,0 +1,44 @@ +using System; +using PnP.PowerShell.Commands.Model.SharePoint; + +namespace PnP.PowerShell.Commands.Base.PipeBinds +{ + public sealed class ListRulePipeBind + { + public ListRule ListRuleInstance { get; private set; } + private readonly Guid _id; + private readonly string _title; + + public ListRulePipeBind(Guid guid) + { + _id = guid; + } + + public ListRulePipeBind(ListRule listRule) + { + ListRuleInstance = listRule ?? throw new ArgumentNullException(nameof(listRule)); + _id = listRule.RuleId; + _title = listRule.Title; + } + + public ListRulePipeBind(string input) + { + if (Guid.TryParse(input, out Guid guid)) + { + _id = guid; + } + else + { + _title = input; + } + } + + public Guid Id => _id; + public string Title => _title; + + public ListRulePipeBind() + { + _id = Guid.Empty; + } + } +} diff --git a/src/Commands/Model/SharePoint/ListRule.cs b/src/Commands/Model/SharePoint/ListRule.cs new file mode 100644 index 000000000..00cd14050 --- /dev/null +++ b/src/Commands/Model/SharePoint/ListRule.cs @@ -0,0 +1,71 @@ +using System; +using System.Text.Json.Serialization; + +namespace PnP.PowerShell.Commands.Model.SharePoint +{ + /// + /// Represents a SharePoint list or library rule + /// + public class ListRule + { + /// + /// The unique identifier of the rule + /// + [JsonPropertyName("ID")] + public Guid RuleId { get; set; } + + /// + /// The title/name of the rule + /// + [JsonPropertyName("title")] + public string Title { get; set; } + + /// + /// The description of the rule + /// + [JsonPropertyName("description")] + public string Description { get; set; } + + /// + /// Whether the rule is enabled or disabled + /// + [JsonPropertyName("isEnabled")] + public bool IsEnabled { get; set; } + + /// + /// The trigger conditions for the rule + /// + [JsonPropertyName("triggerCondition")] + public ListRuleTrigger TriggerCondition { get; set; } + + /// + /// The actions to execute when the rule is triggered + /// + [JsonPropertyName("actionParameters")] + public ListRuleAction ActionParameters { get; set; } + + /// + /// The creation date of the rule + /// + [JsonPropertyName("createdDate")] + public DateTime? CreatedDate { get; set; } + + /// + /// The last modified date of the rule + /// + [JsonPropertyName("lastModifiedDate")] + public DateTime? LastModifiedDate { get; set; } + + /// + /// The user who created the rule + /// + [JsonPropertyName("createdBy")] + public string CreatedBy { get; set; } + + /// + /// The user who last modified the rule + /// + [JsonPropertyName("modifiedBy")] + public string ModifiedBy { get; set; } + } +} diff --git a/src/Commands/Model/SharePoint/ListRuleAction.cs b/src/Commands/Model/SharePoint/ListRuleAction.cs new file mode 100644 index 000000000..e41e7c893 --- /dev/null +++ b/src/Commands/Model/SharePoint/ListRuleAction.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace PnP.PowerShell.Commands.Model.SharePoint +{ + /// + /// Represents the actions to execute when a SharePoint rule is triggered + /// + public class ListRuleAction + { + /// + /// The type of action to perform (e.g., "sendEmail", "createAlert") + /// + [JsonPropertyName("actionType")] + public string ActionType { get; set; } + + /// + /// The email addresses to send notifications to + /// + [JsonPropertyName("emailRecipients")] + public List EmailRecipients { get; set; } + + /// + /// The subject line for email notifications + /// + [JsonPropertyName("emailSubject")] + public string EmailSubject { get; set; } + + /// + /// The body content for email notifications + /// + [JsonPropertyName("emailBody")] + public string EmailBody { get; set; } + + /// + /// Additional parameters for the action + /// + [JsonPropertyName("parameters")] + public Dictionary Parameters { get; set; } + } +} diff --git a/src/Commands/Model/SharePoint/ListRuleTrigger.cs b/src/Commands/Model/SharePoint/ListRuleTrigger.cs new file mode 100644 index 000000000..805ff6110 --- /dev/null +++ b/src/Commands/Model/SharePoint/ListRuleTrigger.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; + +namespace PnP.PowerShell.Commands.Model.SharePoint +{ + /// + /// Represents the trigger conditions for a SharePoint rule + /// + public class ListRuleTrigger + { + /// + /// The type of event that triggers the rule (e.g., "create", "update", "delete") + /// + [JsonPropertyName("eventType")] + public string EventType { get; set; } + + /// + /// Optional conditions that must be met for the rule to trigger + /// + [JsonPropertyName("condition")] + public string Condition { get; set; } + + /// + /// Specifies which fields to monitor for changes + /// + [JsonPropertyName("fieldValues")] + public object FieldValues { get; set; } + } +} diff --git a/src/Commands/Rules/AddListRule.cs b/src/Commands/Rules/AddListRule.cs new file mode 100644 index 000000000..43e895985 --- /dev/null +++ b/src/Commands/Rules/AddListRule.cs @@ -0,0 +1,153 @@ +using Microsoft.SharePoint.Client; +using PnP.PowerShell.Commands.Base; +using PnP.PowerShell.Commands.Base.Completers; +using PnP.PowerShell.Commands.Base.PipeBinds; +using PnP.PowerShell.Commands.Model.SharePoint; +using PnP.PowerShell.Commands.Utilities.REST; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text.Json; + +namespace PnP.PowerShell.Commands.Rules +{ + [Cmdlet(VerbsCommon.Add, "PnPListRule")] + [OutputType(typeof(ListRule))] + public class AddListRule : PnPWebCmdlet + { + [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)] + [ArgumentCompleter(typeof(ListNameCompleter))] + public ListPipeBind List { get; set; } + + [Parameter(Mandatory = true)] + public string Title { get; set; } + + [Parameter(Mandatory = true)] + public string TriggerEventType { get; set; } + + [Parameter(Mandatory = true)] + public string ActionType { get; set; } + + [Parameter(Mandatory = false)] + public string[] EmailRecipients { get; set; } + + [Parameter(Mandatory = false)] + public string EmailSubject { get; set; } + + [Parameter(Mandatory = false)] + public string EmailBody { get; set; } + + [Parameter(Mandatory = false)] + public string Condition { get; set; } + + [Parameter(Mandatory = false)] + public SwitchParameter Enabled = true; + + protected override void ExecuteCmdlet() + { + var list = List?.GetList(CurrentWeb); + if (list == null) + { + throw new PSArgumentException("Unable to retrieve the specified list", nameof(List)); + } + + list.EnsureProperty(l => l.Id); + + try + { + // Build notification receivers in the expected format + var notificationReceivers = new List(); + if (EmailRecipients != null) + { + foreach (var email in EmailRecipients) + { + notificationReceivers.Add(new + { + name = email, // Using email as name for now - could be enhanced to parse display names + email = email, + userId = $"i:0#.f|membership|{email}" // Standard claims format for email users + }); + } + } + + var actionParams = new List(); + + // Add notification receivers if any + if (notificationReceivers.Count > 0) + { + actionParams.Add(new + { + Key = "NotificationReceivers", + Value = System.Text.Json.JsonSerializer.Serialize(notificationReceivers), + ValueType = "String" + }); + } + + // Add custom message if email body is provided + if (!string.IsNullOrEmpty(EmailBody)) + { + actionParams.Add(new + { + Key = "CustomMessage", + Value = $"'{EmailBody}'", // Wrapped in quotes as shown in sample + ValueType = "String" + }); + } + + // Add email subject if provided + if (!string.IsNullOrEmpty(EmailSubject)) + { + actionParams.Add(new + { + Key = "EmailSubject", + Value = EmailSubject, + ValueType = "String" + }); + } + + // Build the rule object to match the expected API format + var rule = new + { + title = Title, + condition = Condition ?? "true", // Default to "true" as shown in sample + triggerType = int.TryParse(TriggerEventType, out int triggerTypeValue) ? triggerTypeValue : 0, + action = new + { + ActionType = int.TryParse(ActionType, out int actionTypeValue) ? actionTypeValue : 0, + ActionParams = new + { + results = actionParams.ToArray() + } + } + }; + + var jsonContent = JsonSerializer.Serialize(rule, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + + // Call the CreateRuleEx endpoint + var endpoint = $"web/lists(guid'{list.Id}')/CreateRuleEx"; + var response = RestHelper.ExecutePostRequest(ClientContext, endpoint, jsonContent); + var responseContent = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + + // Parse and return the created rule + var createdRule = JsonSerializer.Deserialize(responseContent, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + WriteObject(createdRule); + } + catch (Exception ex) + { + WriteError(new ErrorRecord( + new Exception($"Failed to create rule: {ex.Message}", ex), + "FailedToCreateRule", + ErrorCategory.WriteError, + list)); + } + } + } +} diff --git a/src/Commands/Rules/GetListRule.cs b/src/Commands/Rules/GetListRule.cs new file mode 100644 index 000000000..f0b277dd7 --- /dev/null +++ b/src/Commands/Rules/GetListRule.cs @@ -0,0 +1,106 @@ +using Microsoft.SharePoint.Client; +using PnP.PowerShell.Commands.Base; +using PnP.PowerShell.Commands.Base.Completers; +using PnP.PowerShell.Commands.Base.PipeBinds; +using PnP.PowerShell.Commands.Model.SharePoint; +using PnP.PowerShell.Commands.Utilities.REST; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text.Json; + +namespace PnP.PowerShell.Commands.Rules +{ + [Cmdlet(VerbsCommon.Get, "PnPListRule")] + [OutputType(typeof(ListRule))] + public class GetListRule : PnPWebCmdlet + { + [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)] + [ArgumentCompleter(typeof(ListNameCompleter))] + public ListPipeBind List { get; set; } + + [Parameter(Mandatory = false)] + public ListRulePipeBind Identity { get; set; } + + protected override void ExecuteCmdlet() + { + var list = List?.GetList(CurrentWeb); + if (list == null) + { + throw new PSArgumentException("Unable to retrieve the specified list", nameof(List)); + } + + list.EnsureProperty(l => l.Id); + + try + { + // Call the GetAllRules endpoint + var endpoint = $"web/lists(guid'{list.Id}')/GetAllRules"; + var response = RestHelper.ExecutePostRequest(ClientContext, endpoint, "{}"); + var responseContent = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + + // Parse the response + var jsonDoc = JsonDocument.Parse(responseContent); + var rules = new List(); + + if (jsonDoc.RootElement.TryGetProperty("value", out var valueElement)) + { + foreach (var ruleElement in valueElement.EnumerateArray()) + { + var rule = JsonSerializer.Deserialize(ruleElement.GetRawText(), new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + rules.Add(rule); + } + } + else if (jsonDoc.RootElement.TryGetProperty("d", out var dElement) && dElement.TryGetProperty("GetAllRules", out var rulesElement)) + { + foreach (var ruleElement in rulesElement.EnumerateArray()) + { + var rule = JsonSerializer.Deserialize(ruleElement.GetRawText(), new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + rules.Add(rule); + } + } + + // Filter by Identity if specified + if (Identity != null) + { + if (Identity.Id != Guid.Empty) + { + var rule = rules.FirstOrDefault(r => r.RuleId == Identity.Id); + if (rule != null) + { + WriteObject(rule); + } + else + { + WriteWarning($"Rule with ID '{Identity.Id}' not found"); + } + } + else if (!string.IsNullOrEmpty(Identity.Title)) + { + var matchingRules = rules.Where(r => r.Title.Equals(Identity.Title, StringComparison.OrdinalIgnoreCase)).ToList(); + WriteObject(matchingRules, true); + } + } + else + { + WriteObject(rules, true); + } + } + catch (Exception ex) + { + WriteError(new ErrorRecord( + new Exception($"Failed to retrieve rules: {ex.Message}", ex), + "FailedToRetrieveRules", + ErrorCategory.ReadError, + list)); + } + } + } +} diff --git a/src/Commands/Rules/RemoveListRule.cs b/src/Commands/Rules/RemoveListRule.cs new file mode 100644 index 000000000..139abf41d --- /dev/null +++ b/src/Commands/Rules/RemoveListRule.cs @@ -0,0 +1,75 @@ +using Microsoft.SharePoint.Client; +using PnP.PowerShell.Commands.Base; +using PnP.PowerShell.Commands.Base.Completers; +using PnP.PowerShell.Commands.Base.PipeBinds; +using PnP.PowerShell.Commands.Utilities.REST; +using System; +using System.Management.Automation; +using System.Text.Json; + +namespace PnP.PowerShell.Commands.Rules +{ + [Cmdlet(VerbsCommon.Remove, "PnPListRule")] + [OutputType(typeof(void))] + public class RemoveListRule : PnPWebCmdlet + { + [Parameter(Mandatory = false, ValueFromPipeline = false, Position = 1)] + [ArgumentCompleter(typeof(ListNameCompleter))] + public ListPipeBind List { get; set; } + + [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)] + public ListRulePipeBind Identity { get; set; } + + [Parameter(Mandatory = false)] + public SwitchParameter Force { get; set; } + + protected override void ExecuteCmdlet() + { + var list = List?.GetList(CurrentWeb); + if (list == null) + { + throw new PSArgumentException("Unable to retrieve the specified list", nameof(List)); + } + + list.EnsureProperty(l => l.Id); + + if (Identity == null || Identity.Id == Guid.Empty) + { + throw new PSArgumentException("Identity must be specified with a valid Rule ID", nameof(Identity)); + } + + if (!Force && !ShouldContinue($"Remove rule '{Identity.Id}' from list '{list.Title}'?", Properties.Resources.Confirm)) + { + return; + } + + try + { + // Build the request body + var deleteData = new + { + ruleId = Identity.Id + }; + + var jsonContent = JsonSerializer.Serialize(deleteData, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + + // Call the DeleteRule endpoint + var endpoint = $"web/lists(guid'{list.Id}')/DeleteRule"; + RestHelper.ExecutePostRequest(ClientContext, endpoint, jsonContent); + + WriteVerbose($"Rule '{Identity.Id}' successfully removed from list '{list.Title}'"); + } + catch (Exception ex) + { + WriteError(new ErrorRecord( + new Exception($"Failed to remove rule: {ex.Message}", ex), + "FailedToRemoveRule", + ErrorCategory.WriteError, + list)); + } + } + } +} diff --git a/src/Commands/Rules/SetListRule.cs b/src/Commands/Rules/SetListRule.cs new file mode 100644 index 000000000..bf4ecabe2 --- /dev/null +++ b/src/Commands/Rules/SetListRule.cs @@ -0,0 +1,137 @@ +using Microsoft.SharePoint.Client; +using PnP.PowerShell.Commands.Base; +using PnP.PowerShell.Commands.Base.Completers; +using PnP.PowerShell.Commands.Base.PipeBinds; +using PnP.PowerShell.Commands.Model.SharePoint; +using PnP.PowerShell.Commands.Utilities.REST; +using System; +using System.Management.Automation; +using System.Text.Json; + +namespace PnP.PowerShell.Commands.Rules +{ + [Cmdlet(VerbsCommon.Set, "PnPListRule")] + [OutputType(typeof(ListRule))] + public class SetListRule : PnPWebCmdlet + { + [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)] + [ArgumentCompleter(typeof(ListNameCompleter))] + public ListPipeBind List { get; set; } + + [Parameter(Mandatory = true)] + public ListRulePipeBind Identity { get; set; } + + [Parameter(Mandatory = false)] + public string Title { get; set; } + + [Parameter(Mandatory = false)] + public string Description { get; set; } + + [Parameter(Mandatory = false)] + public string TriggerEventType { get; set; } + + [Parameter(Mandatory = false)] + public string ActionType { get; set; } + + [Parameter(Mandatory = false)] + public string[] EmailRecipients { get; set; } + + [Parameter(Mandatory = false)] + public string EmailSubject { get; set; } + + [Parameter(Mandatory = false)] + public string EmailBody { get; set; } + + [Parameter(Mandatory = false)] + public string Condition { get; set; } + + [Parameter(Mandatory = false)] + public bool? Enabled { get; set; } + + protected override void ExecuteCmdlet() + { + var list = List?.GetList(CurrentWeb); + if (list == null) + { + throw new PSArgumentException("Unable to retrieve the specified list", nameof(List)); + } + + list.EnsureProperty(l => l.Id); + + if (Identity == null || Identity.Id == Guid.Empty) + { + throw new PSArgumentException("Identity must be specified with a valid Rule ID", nameof(Identity)); + } + + try + { + // Build the update object with only specified parameters + var updateData = new System.Collections.Generic.Dictionary(); + + if (ParameterSpecified(nameof(Title))) + updateData["title"] = Title; + + if (ParameterSpecified(nameof(Description))) + updateData["description"] = Description; + + if (ParameterSpecified(nameof(Enabled))) + updateData["isEnabled"] = Enabled.Value; + + if (ParameterSpecified(nameof(TriggerEventType)) || ParameterSpecified(nameof(Condition))) + { + var triggerCondition = new System.Collections.Generic.Dictionary(); + if (ParameterSpecified(nameof(TriggerEventType))) + triggerCondition["eventType"] = TriggerEventType; + if (ParameterSpecified(nameof(Condition))) + triggerCondition["condition"] = Condition; + + updateData["triggerCondition"] = triggerCondition; + } + + if (ParameterSpecified(nameof(ActionType)) || ParameterSpecified(nameof(EmailRecipients)) || + ParameterSpecified(nameof(EmailSubject)) || ParameterSpecified(nameof(EmailBody))) + { + var actionParameters = new System.Collections.Generic.Dictionary(); + if (ParameterSpecified(nameof(ActionType))) + actionParameters["actionType"] = ActionType; + if (ParameterSpecified(nameof(EmailRecipients))) + actionParameters["emailRecipients"] = EmailRecipients; + if (ParameterSpecified(nameof(EmailSubject))) + actionParameters["emailSubject"] = EmailSubject; + if (ParameterSpecified(nameof(EmailBody))) + actionParameters["emailBody"] = EmailBody; + + updateData["actionParameters"] = actionParameters; + } + + updateData["ruleId"] = Identity.Id; + + var jsonContent = JsonSerializer.Serialize(updateData, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + + // Call the UpdateRule endpoint + var endpoint = $"web/lists(guid'{list.Id}')/UpdateRule"; + var response = RestHelper.ExecutePostRequest(ClientContext, endpoint, jsonContent); + var responseContent = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + + // Parse and return the updated rule + var updatedRule = JsonSerializer.Deserialize(responseContent, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + WriteObject(updatedRule); + } + catch (Exception ex) + { + WriteError(new ErrorRecord( + new Exception($"Failed to update rule: {ex.Message}", ex), + "FailedToUpdateRule", + ErrorCategory.WriteError, + list)); + } + } + } +} diff --git a/src/Tests/Rules/AddPnPListRuleTests.cs b/src/Tests/Rules/AddPnPListRuleTests.cs new file mode 100644 index 000000000..ca3a95bac --- /dev/null +++ b/src/Tests/Rules/AddPnPListRuleTests.cs @@ -0,0 +1,79 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Management.Automation.Runspaces; + +namespace PnP.PowerShell.Tests.Rules +{ + [TestClass] + public class AddListRuleTests + { + #region Test Setup/CleanUp + [ClassInitialize] + public static void Initialize(TestContext testContext) + { + // This runs on class level once before all tests run + } + + [ClassCleanup] + public static void Cleanup(TestContext testContext) + { + // This runs on class level once + } + + [TestInitialize] + public void Initialize() + { + using (var scope = new PSTestScope()) + { + // Example + // scope.ExecuteCommand("cmdlet", new CommandParameter("param1", prop)); + } + } + + [TestCleanup] + public void Cleanup() + { + using (var scope = new PSTestScope()) + { + try + { + // Do Test Cleanup - Note, this runs PER test + } + catch (Exception) + { + // Describe Exception + } + } + } + #endregion + + #region Scaffolded Cmdlet Tests + //TODO: This is a scaffold of the cmdlet - complete the unit test + //[TestMethod] + public void AddPnPListRuleTest() + { + using (var scope = new PSTestScope(true)) + { + // Complete writing cmd parameters + + // From Cmdlet Help: The ID, Title or Url of the list. + var list = ""; + // From Cmdlet Help: The title/name of the rule. + var title = ""; + // From Cmdlet Help: The type of event that triggers the rule. + var triggerEventType = ""; + // From Cmdlet Help: The type of action to perform when the rule is triggered. + var actionType = ""; + + var results = scope.ExecuteCommand("Add-PnPListRule", + new CommandParameter("List", list), + new CommandParameter("Title", title), + new CommandParameter("TriggerEventType", triggerEventType), + new CommandParameter("ActionType", actionType)); + + Assert.IsNotNull(results); + } + } + #endregion + } +} diff --git a/src/Tests/Rules/GetPnPListRuleTests.cs b/src/Tests/Rules/GetPnPListRuleTests.cs new file mode 100644 index 000000000..e6f6d95fe --- /dev/null +++ b/src/Tests/Rules/GetPnPListRuleTests.cs @@ -0,0 +1,73 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Management.Automation.Runspaces; + +namespace PnP.PowerShell.Tests.Rules +{ + [TestClass] + public class GetListRuleTests + { + #region Test Setup/CleanUp + [ClassInitialize] + public static void Initialize(TestContext testContext) + { + // This runs on class level once before all tests run + } + + [ClassCleanup] + public static void Cleanup(TestContext testContext) + { + // This runs on class level once + } + + [TestInitialize] + public void Initialize() + { + using (var scope = new PSTestScope()) + { + // Example + // scope.ExecuteCommand("cmdlet", new CommandParameter("param1", prop)); + } + } + + [TestCleanup] + public void Cleanup() + { + using (var scope = new PSTestScope()) + { + try + { + // Do Test Cleanup - Note, this runs PER test + } + catch (Exception) + { + // Describe Exception + } + } + } + #endregion + + #region Scaffolded Cmdlet Tests + //TODO: This is a scaffold of the cmdlet - complete the unit test + //[TestMethod] + public void GetPnPListRuleTest() + { + using (var scope = new PSTestScope(true)) + { + // Complete writing cmd parameters + + // From Cmdlet Help: The ID, Title or Url of the list. + var list = ""; + // From Cmdlet Help: The ID or Title of the rule to retrieve. + var identity = ""; + + var results = scope.ExecuteCommand("Get-PnPListRule", + new CommandParameter("List", list), + new CommandParameter("Identity", identity)); + + Assert.IsNotNull(results); + } + } + #endregion + } +} diff --git a/src/Tests/Rules/RemovePnPListRuleTests.cs b/src/Tests/Rules/RemovePnPListRuleTests.cs new file mode 100644 index 000000000..44c58ae3b --- /dev/null +++ b/src/Tests/Rules/RemovePnPListRuleTests.cs @@ -0,0 +1,73 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Management.Automation.Runspaces; + +namespace PnP.PowerShell.Tests.Rules +{ + [TestClass] + public class RemoveListRuleTests + { + #region Test Setup/CleanUp + [ClassInitialize] + public static void Initialize(TestContext testContext) + { + // This runs on class level once before all tests run + } + + [ClassCleanup] + public static void Cleanup(TestContext testContext) + { + // This runs on class level once + } + + [TestInitialize] + public void Initialize() + { + using (var scope = new PSTestScope()) + { + // Example + // scope.ExecuteCommand("cmdlet", new CommandParameter("param1", prop)); + } + } + + [TestCleanup] + public void Cleanup() + { + using (var scope = new PSTestScope()) + { + try + { + // Do Test Cleanup - Note, this runs PER test + } + catch (Exception) + { + // Describe Exception + } + } + } + #endregion + + #region Scaffolded Cmdlet Tests + //TODO: This is a scaffold of the cmdlet - complete the unit test + //[TestMethod] + public void RemovePnPListRuleTest() + { + using (var scope = new PSTestScope(true)) + { + // Complete writing cmd parameters + + // From Cmdlet Help: The ID, Title or Url of the list. + var list = ""; + // From Cmdlet Help: The ID or Title of the rule to remove. + var identity = ""; + + var results = scope.ExecuteCommand("Remove-PnPListRule", + new CommandParameter("List", list), + new CommandParameter("Identity", identity)); + + Assert.IsNotNull(results); + } + } + #endregion + } +} diff --git a/src/Tests/Rules/SetPnPListRuleTests.cs b/src/Tests/Rules/SetPnPListRuleTests.cs new file mode 100644 index 000000000..6b062b266 --- /dev/null +++ b/src/Tests/Rules/SetPnPListRuleTests.cs @@ -0,0 +1,73 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Management.Automation.Runspaces; + +namespace PnP.PowerShell.Tests.Rules +{ + [TestClass] + public class SetListRuleTests + { + #region Test Setup/CleanUp + [ClassInitialize] + public static void Initialize(TestContext testContext) + { + // This runs on class level once before all tests run + } + + [ClassCleanup] + public static void Cleanup(TestContext testContext) + { + // This runs on class level once + } + + [TestInitialize] + public void Initialize() + { + using (var scope = new PSTestScope()) + { + // Example + // scope.ExecuteCommand("cmdlet", new CommandParameter("param1", prop)); + } + } + + [TestCleanup] + public void Cleanup() + { + using (var scope = new PSTestScope()) + { + try + { + // Do Test Cleanup - Note, this runs PER test + } + catch (Exception) + { + // Describe Exception + } + } + } + #endregion + + #region Scaffolded Cmdlet Tests + //TODO: This is a scaffold of the cmdlet - complete the unit test + //[TestMethod] + public void SetPnPListRuleTest() + { + using (var scope = new PSTestScope(true)) + { + // Complete writing cmd parameters + + // From Cmdlet Help: The ID, Title or Url of the list. + var list = ""; + // From Cmdlet Help: The ID or Title of the rule to update. + var identity = ""; + + var results = scope.ExecuteCommand("Set-PnPListRule", + new CommandParameter("List", list), + new CommandParameter("Identity", identity)); + + Assert.IsNotNull(results); + } + } + #endregion + } +}