diff --git a/src/Applications/wrapper/v1.0/Cmdlets/GetMgApplicationDelta.g.cs b/src/Applications/wrapper/v1.0/Cmdlets/GetMgApplicationDelta.g.cs index cc3fd3604b1..82d6d754d30 100644 --- a/src/Applications/wrapper/v1.0/Cmdlets/GetMgApplicationDelta.g.cs +++ b/src/Applications/wrapper/v1.0/Cmdlets/GetMgApplicationDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Applications { [GraphRoute("GET", "/applications/delta()")] - [Cmdlet(VerbsCommon.Get, "MgApplicationDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Applications.Client.Applications.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgApplicationDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Applications.Client.Models.Application))] public class GetMgApplicationDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Applications.Client.Applications.Delta.DeltaGetResponse? result; try { - result = client.Applications.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Applications.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Applications.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Applications.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Applications/wrapper/v1.0/Cmdlets/GetMgServicePrincipalDelta.g.cs b/src/Applications/wrapper/v1.0/Cmdlets/GetMgServicePrincipalDelta.g.cs index 29331824d34..00b84dd0494 100644 --- a/src/Applications/wrapper/v1.0/Cmdlets/GetMgServicePrincipalDelta.g.cs +++ b/src/Applications/wrapper/v1.0/Cmdlets/GetMgServicePrincipalDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Applications { [GraphRoute("GET", "/servicePrincipals/delta()")] - [Cmdlet(VerbsCommon.Get, "MgServicePrincipalDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Applications.Client.ServicePrincipals.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgServicePrincipalDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Applications.Client.Models.ServicePrincipal))] public class GetMgServicePrincipalDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Applications.Client.ServicePrincipals.Delta.DeltaGetResponse? result; try { - result = client.ServicePrincipals.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.ServicePrincipals.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.ServicePrincipals.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.ServicePrincipals.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Calendar/wrapper/v1.0/Cmdlets/GetMgGroupEventDelta.g.cs b/src/Calendar/wrapper/v1.0/Cmdlets/GetMgGroupEventDelta.g.cs index e22458b153c..60ee4337996 100644 --- a/src/Calendar/wrapper/v1.0/Cmdlets/GetMgGroupEventDelta.g.cs +++ b/src/Calendar/wrapper/v1.0/Cmdlets/GetMgGroupEventDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Calendar { [GraphRoute("GET", "/groups/{group-id}/events/delta()")] - [Cmdlet(VerbsCommon.Get, "MgGroupEventDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Calendar.Client.Groups.Item.Events.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgGroupEventDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Calendar.Client.Models.Event))] public class GetMgGroupEventDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string GroupId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Calendar.Client.Groups.Item.Events.Delta.DeltaGetResponse? result; try { - result = client.Groups[GroupId].Events.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Groups[GroupId].Events.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Groups[GroupId].Events.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Groups[GroupId].Events.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, GroupId); return; } - - WriteObject(result); } } } diff --git a/src/Calendar/wrapper/v1.0/Cmdlets/GetMgGroupEventInstanceDelta.g.cs b/src/Calendar/wrapper/v1.0/Cmdlets/GetMgGroupEventInstanceDelta.g.cs index 6146944b8c3..468c6fa73e0 100644 --- a/src/Calendar/wrapper/v1.0/Cmdlets/GetMgGroupEventInstanceDelta.g.cs +++ b/src/Calendar/wrapper/v1.0/Cmdlets/GetMgGroupEventInstanceDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Calendar { [GraphRoute("GET", "/groups/{group-id}/events/{event-id}/instances/delta()")] - [Cmdlet(VerbsCommon.Get, "MgGroupEventInstanceDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Calendar.Client.Groups.Item.Events.Item.Instances.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgGroupEventInstanceDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Calendar.Client.Models.Event))] public class GetMgGroupEventInstanceDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string GroupId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string EventId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Calendar.Client.Groups.Item.Events.Item.Instances.Delta.DeltaGetResponse? result; try { - result = client.Groups[GroupId].Events[EventId].Instances.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Groups[GroupId].Events[EventId].Instances.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Groups[GroupId].Events[EventId].Instances.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Groups[GroupId].Events[EventId].Instances.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, EventId); return; } - - WriteObject(result); } } } diff --git a/src/Calendar/wrapper/v1.0/Cmdlets/GetMgUserEventDelta.g.cs b/src/Calendar/wrapper/v1.0/Cmdlets/GetMgUserEventDelta.g.cs index d88d43bbc00..db05ec4fc54 100644 --- a/src/Calendar/wrapper/v1.0/Cmdlets/GetMgUserEventDelta.g.cs +++ b/src/Calendar/wrapper/v1.0/Cmdlets/GetMgUserEventDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Calendar { [GraphRoute("GET", "/users/{user-id}/events/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserEventDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Calendar.Client.Users.Item.Events.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserEventDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Calendar.Client.Models.Event))] public class GetMgUserEventDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Calendar.Client.Users.Item.Events.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].Events.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].Events.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].Events.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].Events.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, UserId); return; } - - WriteObject(result); } } } diff --git a/src/Calendar/wrapper/v1.0/Cmdlets/GetMgUserEventInstanceDelta.g.cs b/src/Calendar/wrapper/v1.0/Cmdlets/GetMgUserEventInstanceDelta.g.cs index 4381c56320e..c935d094bde 100644 --- a/src/Calendar/wrapper/v1.0/Cmdlets/GetMgUserEventInstanceDelta.g.cs +++ b/src/Calendar/wrapper/v1.0/Cmdlets/GetMgUserEventInstanceDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Calendar { [GraphRoute("GET", "/users/{user-id}/events/{event-id}/instances/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserEventInstanceDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Calendar.Client.Users.Item.Events.Item.Instances.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserEventInstanceDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Calendar.Client.Models.Event))] public class GetMgUserEventInstanceDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string EventId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Calendar.Client.Users.Item.Events.Item.Instances.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].Events[EventId].Instances.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].Events[EventId].Instances.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].Events[EventId].Instances.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].Events[EventId].Instances.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, EventId); return; } - - WriteObject(result); } } } diff --git a/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationAdhocCallRecordingDelta.g.cs b/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationAdhocCallRecordingDelta.g.cs index a3c4656fa6e..9d90383b787 100644 --- a/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationAdhocCallRecordingDelta.g.cs +++ b/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationAdhocCallRecordingDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.CloudCommunications { [GraphRoute("GET", "/communications/adhocCalls/{adhocCall-id}/recordings/delta()")] - [Cmdlet(VerbsCommon.Get, "MgCommunicationAdhocCallRecordingDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.CloudCommunications.Client.Communications.AdhocCalls.Item.Recordings.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgCommunicationAdhocCallRecordingDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.CloudCommunications.Client.Models.CallRecording))] public class GetMgCommunicationAdhocCallRecordingDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string AdhocCallId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.CloudCommunications.Client.Communications.AdhocCalls.Item.Recordings.Delta.DeltaGetResponse? result; try { - result = client.Communications.AdhocCalls[AdhocCallId].Recordings.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Communications.AdhocCalls[AdhocCallId].Recordings.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Communications.AdhocCalls[AdhocCallId].Recordings.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Communications.AdhocCalls[AdhocCallId].Recordings.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, AdhocCallId); return; } - - WriteObject(result); } } } diff --git a/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationAdhocCallTranscriptDelta.g.cs b/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationAdhocCallTranscriptDelta.g.cs index 432adaa4617..4b289b3a0e5 100644 --- a/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationAdhocCallTranscriptDelta.g.cs +++ b/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationAdhocCallTranscriptDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.CloudCommunications { [GraphRoute("GET", "/communications/adhocCalls/{adhocCall-id}/transcripts/delta()")] - [Cmdlet(VerbsCommon.Get, "MgCommunicationAdhocCallTranscriptDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.CloudCommunications.Client.Communications.AdhocCalls.Item.Transcripts.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgCommunicationAdhocCallTranscriptDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.CloudCommunications.Client.Models.CallTranscript))] public class GetMgCommunicationAdhocCallTranscriptDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string AdhocCallId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.CloudCommunications.Client.Communications.AdhocCalls.Item.Transcripts.Delta.DeltaGetResponse? result; try { - result = client.Communications.AdhocCalls[AdhocCallId].Transcripts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Communications.AdhocCalls[AdhocCallId].Transcripts.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Communications.AdhocCalls[AdhocCallId].Transcripts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Communications.AdhocCalls[AdhocCallId].Transcripts.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, AdhocCallId); return; } - - WriteObject(result); } } } diff --git a/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationOnlineMeetingRecordingDelta.g.cs b/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationOnlineMeetingRecordingDelta.g.cs index 1ecf321510e..426dc6edab7 100644 --- a/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationOnlineMeetingRecordingDelta.g.cs +++ b/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationOnlineMeetingRecordingDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.CloudCommunications { [GraphRoute("GET", "/communications/onlineMeetings/{onlineMeeting-id}/recordings/delta()")] - [Cmdlet(VerbsCommon.Get, "MgCommunicationOnlineMeetingRecordingDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.CloudCommunications.Client.Communications.OnlineMeetings.Item.Recordings.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgCommunicationOnlineMeetingRecordingDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.CloudCommunications.Client.Models.CallRecording))] public class GetMgCommunicationOnlineMeetingRecordingDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string OnlineMeetingId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.CloudCommunications.Client.Communications.OnlineMeetings.Item.Recordings.Delta.DeltaGetResponse? result; try { - result = client.Communications.OnlineMeetings[OnlineMeetingId].Recordings.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Communications.OnlineMeetings[OnlineMeetingId].Recordings.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Communications.OnlineMeetings[OnlineMeetingId].Recordings.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Communications.OnlineMeetings[OnlineMeetingId].Recordings.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, OnlineMeetingId); return; } - - WriteObject(result); } } } diff --git a/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationOnlineMeetingTranscriptDelta.g.cs b/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationOnlineMeetingTranscriptDelta.g.cs index 743286f4dd5..eb6159a1a8d 100644 --- a/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationOnlineMeetingTranscriptDelta.g.cs +++ b/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgCommunicationOnlineMeetingTranscriptDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.CloudCommunications { [GraphRoute("GET", "/communications/onlineMeetings/{onlineMeeting-id}/transcripts/delta()")] - [Cmdlet(VerbsCommon.Get, "MgCommunicationOnlineMeetingTranscriptDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.CloudCommunications.Client.Communications.OnlineMeetings.Item.Transcripts.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgCommunicationOnlineMeetingTranscriptDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.CloudCommunications.Client.Models.CallTranscript))] public class GetMgCommunicationOnlineMeetingTranscriptDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string OnlineMeetingId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.CloudCommunications.Client.Communications.OnlineMeetings.Item.Transcripts.Delta.DeltaGetResponse? result; try { - result = client.Communications.OnlineMeetings[OnlineMeetingId].Transcripts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Communications.OnlineMeetings[OnlineMeetingId].Transcripts.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Communications.OnlineMeetings[OnlineMeetingId].Transcripts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Communications.OnlineMeetings[OnlineMeetingId].Transcripts.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, OnlineMeetingId); return; } - - WriteObject(result); } } } diff --git a/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgUserOnlineMeetingRecordingDelta.g.cs b/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgUserOnlineMeetingRecordingDelta.g.cs index 3dce067593c..d0f53de3840 100644 --- a/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgUserOnlineMeetingRecordingDelta.g.cs +++ b/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgUserOnlineMeetingRecordingDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.CloudCommunications { [GraphRoute("GET", "/users/{user-id}/onlineMeetings/{onlineMeeting-id}/recordings/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserOnlineMeetingRecordingDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.CloudCommunications.Client.Users.Item.OnlineMeetings.Item.Recordings.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserOnlineMeetingRecordingDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.CloudCommunications.Client.Models.CallRecording))] public class GetMgUserOnlineMeetingRecordingDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string OnlineMeetingId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.CloudCommunications.Client.Users.Item.OnlineMeetings.Item.Recordings.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].OnlineMeetings[OnlineMeetingId].Recordings.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].OnlineMeetings[OnlineMeetingId].Recordings.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].OnlineMeetings[OnlineMeetingId].Recordings.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].OnlineMeetings[OnlineMeetingId].Recordings.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, OnlineMeetingId); return; } - - WriteObject(result); } } } diff --git a/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgUserOnlineMeetingTranscriptDelta.g.cs b/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgUserOnlineMeetingTranscriptDelta.g.cs index 64b3df097b8..f2033988dc2 100644 --- a/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgUserOnlineMeetingTranscriptDelta.g.cs +++ b/src/CloudCommunications/wrapper/v1.0/Cmdlets/GetMgUserOnlineMeetingTranscriptDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.CloudCommunications { [GraphRoute("GET", "/users/{user-id}/onlineMeetings/{onlineMeeting-id}/transcripts/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserOnlineMeetingTranscriptDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.CloudCommunications.Client.Users.Item.OnlineMeetings.Item.Transcripts.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserOnlineMeetingTranscriptDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.CloudCommunications.Client.Models.CallTranscript))] public class GetMgUserOnlineMeetingTranscriptDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string OnlineMeetingId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.CloudCommunications.Client.Users.Item.OnlineMeetings.Item.Transcripts.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].OnlineMeetings[OnlineMeetingId].Transcripts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].OnlineMeetings[OnlineMeetingId].Transcripts.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].OnlineMeetings[OnlineMeetingId].Transcripts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].OnlineMeetings[OnlineMeetingId].Transcripts.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, OnlineMeetingId); return; } - - WriteObject(result); } } } diff --git a/src/DirectoryObjects/wrapper/v1.0/Cmdlets/GetMgDirectoryObjectDelta.g.cs b/src/DirectoryObjects/wrapper/v1.0/Cmdlets/GetMgDirectoryObjectDelta.g.cs index 08237bbbc1c..2623ef569bf 100644 --- a/src/DirectoryObjects/wrapper/v1.0/Cmdlets/GetMgDirectoryObjectDelta.g.cs +++ b/src/DirectoryObjects/wrapper/v1.0/Cmdlets/GetMgDirectoryObjectDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.DirectoryObjects { [GraphRoute("GET", "/directoryObjects/delta()")] - [Cmdlet(VerbsCommon.Get, "MgDirectoryObjectDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.DirectoryObjects.Client.DirectoryObjects.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgDirectoryObjectDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.DirectoryObjects.Client.Models.DirectoryObject))] public class GetMgDirectoryObjectDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.DirectoryObjects.Client.DirectoryObjects.Delta.DeltaGetResponse? result; try { - result = client.DirectoryObjects.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.DirectoryObjects.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.DirectoryObjects.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.DirectoryObjects.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationClassAssignmentCategoryDelta.g.cs b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationClassAssignmentCategoryDelta.g.cs index dbf5f145050..0de925ebc52 100644 --- a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationClassAssignmentCategoryDelta.g.cs +++ b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationClassAssignmentCategoryDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Education { [GraphRoute("GET", "/education/classes/{educationClass-id}/assignmentCategories/delta()")] - [Cmdlet(VerbsCommon.Get, "MgEducationClassAssignmentCategoryDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Education.Client.Education.Classes.Item.AssignmentCategories.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgEducationClassAssignmentCategoryDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Education.Client.Models.EducationCategory))] public class GetMgEducationClassAssignmentCategoryDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string EducationClassId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Education.Client.Education.Classes.Item.AssignmentCategories.Delta.DeltaGetResponse? result; try { - result = client.Education.Classes[EducationClassId].AssignmentCategories.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Education.Classes[EducationClassId].AssignmentCategories.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Education.Classes[EducationClassId].AssignmentCategories.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Education.Classes[EducationClassId].AssignmentCategories.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, EducationClassId); return; } - - WriteObject(result); } } } diff --git a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationClassAssignmentDelta.g.cs b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationClassAssignmentDelta.g.cs index f7fe37f3ab7..eb5482c2889 100644 --- a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationClassAssignmentDelta.g.cs +++ b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationClassAssignmentDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Education { [GraphRoute("GET", "/education/classes/{educationClass-id}/assignments/delta()")] - [Cmdlet(VerbsCommon.Get, "MgEducationClassAssignmentDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Education.Client.Education.Classes.Item.Assignments.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgEducationClassAssignmentDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Education.Client.Models.EducationAssignment))] public class GetMgEducationClassAssignmentDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string EducationClassId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Education.Client.Education.Classes.Item.Assignments.Delta.DeltaGetResponse? result; try { - result = client.Education.Classes[EducationClassId].Assignments.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Education.Classes[EducationClassId].Assignments.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Education.Classes[EducationClassId].Assignments.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Education.Classes[EducationClassId].Assignments.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, EducationClassId); return; } - - WriteObject(result); } } } diff --git a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationClassDelta.g.cs b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationClassDelta.g.cs index 1cac4ba855e..9948ee92764 100644 --- a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationClassDelta.g.cs +++ b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationClassDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Education { [GraphRoute("GET", "/education/classes/delta()")] - [Cmdlet(VerbsCommon.Get, "MgEducationClassDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Education.Client.Education.Classes.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgEducationClassDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Education.Client.Models.EducationClass))] public class GetMgEducationClassDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Education.Client.Education.Classes.Delta.DeltaGetResponse? result; try { - result = client.Education.Classes.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Education.Classes.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Education.Classes.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Education.Classes.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationMeAssignmentCategoryDelta.g.cs b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationMeAssignmentCategoryDelta.g.cs index fb1a2b9c79d..42d3a07b852 100644 --- a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationMeAssignmentCategoryDelta.g.cs +++ b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationMeAssignmentCategoryDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Education { [GraphRoute("GET", "/education/me/assignments/{educationAssignment-id}/categories/delta()")] - [Cmdlet(VerbsCommon.Get, "MgEducationMeAssignmentCategoryDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Education.Client.Education.Me.Assignments.Item.Categories.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgEducationMeAssignmentCategoryDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Education.Client.Models.EducationCategory))] public class GetMgEducationMeAssignmentCategoryDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string EducationAssignmentId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Education.Client.Education.Me.Assignments.Item.Categories.Delta.DeltaGetResponse? result; try { - result = client.Education.Me.Assignments[EducationAssignmentId].Categories.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Education.Me.Assignments[EducationAssignmentId].Categories.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Education.Me.Assignments[EducationAssignmentId].Categories.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Education.Me.Assignments[EducationAssignmentId].Categories.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, EducationAssignmentId); return; } - - WriteObject(result); } } } diff --git a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationMeAssignmentDelta.g.cs b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationMeAssignmentDelta.g.cs index b2287320c6c..a00eb15f185 100644 --- a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationMeAssignmentDelta.g.cs +++ b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationMeAssignmentDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Education { [GraphRoute("GET", "/education/me/assignments/delta()")] - [Cmdlet(VerbsCommon.Get, "MgEducationMeAssignmentDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Education.Client.Education.Me.Assignments.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgEducationMeAssignmentDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Education.Client.Models.EducationAssignment))] public class GetMgEducationMeAssignmentDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Education.Client.Education.Me.Assignments.Delta.DeltaGetResponse? result; try { - result = client.Education.Me.Assignments.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Education.Me.Assignments.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Education.Me.Assignments.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Education.Me.Assignments.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationSchoolDelta.g.cs b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationSchoolDelta.g.cs index 41180a845ec..b28781fce14 100644 --- a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationSchoolDelta.g.cs +++ b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationSchoolDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Education { [GraphRoute("GET", "/education/schools/delta()")] - [Cmdlet(VerbsCommon.Get, "MgEducationSchoolDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Education.Client.Education.Schools.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgEducationSchoolDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Education.Client.Models.EducationSchool))] public class GetMgEducationSchoolDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Education.Client.Education.Schools.Delta.DeltaGetResponse? result; try { - result = client.Education.Schools.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Education.Schools.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Education.Schools.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Education.Schools.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationUserAssignmentCategoryDelta.g.cs b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationUserAssignmentCategoryDelta.g.cs index 5603737d583..bb2c0b7f0ea 100644 --- a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationUserAssignmentCategoryDelta.g.cs +++ b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationUserAssignmentCategoryDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Education { [GraphRoute("GET", "/education/users/{educationUser-id}/assignments/{educationAssignment-id}/categories/delta()")] - [Cmdlet(VerbsCommon.Get, "MgEducationUserAssignmentCategoryDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Education.Client.Education.Users.Item.Assignments.Item.Categories.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgEducationUserAssignmentCategoryDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Education.Client.Models.EducationCategory))] public class GetMgEducationUserAssignmentCategoryDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string EducationUserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string EducationAssignmentId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Education.Client.Education.Users.Item.Assignments.Item.Categories.Delta.DeltaGetResponse? result; try { - result = client.Education.Users[EducationUserId].Assignments[EducationAssignmentId].Categories.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Education.Users[EducationUserId].Assignments[EducationAssignmentId].Categories.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Education.Users[EducationUserId].Assignments[EducationAssignmentId].Categories.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Education.Users[EducationUserId].Assignments[EducationAssignmentId].Categories.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, EducationAssignmentId); return; } - - WriteObject(result); } } } diff --git a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationUserAssignmentDelta.g.cs b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationUserAssignmentDelta.g.cs index 4bad3d448a1..4313be75a43 100644 --- a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationUserAssignmentDelta.g.cs +++ b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationUserAssignmentDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Education { [GraphRoute("GET", "/education/users/{educationUser-id}/assignments/delta()")] - [Cmdlet(VerbsCommon.Get, "MgEducationUserAssignmentDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Education.Client.Education.Users.Item.Assignments.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgEducationUserAssignmentDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Education.Client.Models.EducationAssignment))] public class GetMgEducationUserAssignmentDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string EducationUserId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Education.Client.Education.Users.Item.Assignments.Delta.DeltaGetResponse? result; try { - result = client.Education.Users[EducationUserId].Assignments.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Education.Users[EducationUserId].Assignments.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Education.Users[EducationUserId].Assignments.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Education.Users[EducationUserId].Assignments.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, EducationUserId); return; } - - WriteObject(result); } } } diff --git a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationUserDelta.g.cs b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationUserDelta.g.cs index 3d9966375c8..102daca422b 100644 --- a/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationUserDelta.g.cs +++ b/src/Education/wrapper/v1.0/Cmdlets/GetMgEducationUserDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Education { [GraphRoute("GET", "/education/users/delta()")] - [Cmdlet(VerbsCommon.Get, "MgEducationUserDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Education.Client.Education.Users.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgEducationUserDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Education.Client.Models.EducationUser))] public class GetMgEducationUserDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Education.Client.Education.Users.Delta.DeltaGetResponse? result; try { - result = client.Education.Users.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Education.Users.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Education.Users.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Education.Users.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveItemDelta.g.cs b/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveItemDelta.g.cs index 8b8818e8b19..618ab04fa9f 100644 --- a/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveItemDelta.g.cs +++ b/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveItemDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Files { [GraphRoute("GET", "/drives/{drive-id}/items/{driveItem-id}/delta()")] - [Cmdlet(VerbsCommon.Get, "MgDriveItemDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Files.Client.Drives.Item.Items.Item.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgDriveItemDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Files.Client.Models.DriveItem))] public class GetMgDriveItemDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string DriveId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string DriveItemId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Files.Client.Drives.Item.Items.Item.Delta.DeltaGetResponse? result; try { - result = client.Drives[DriveId].Items[DriveItemId].Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Drives[DriveId].Items[DriveItemId].Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Drives[DriveId].Items[DriveItemId].Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Drives[DriveId].Items[DriveItemId].Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, DriveItemId); return; } - - WriteObject(result); } } } diff --git a/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveItemDeltaWithToken.g.cs b/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveItemDeltaWithToken.g.cs deleted file mode 100644 index 525176e5334..00000000000 --- a/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveItemDeltaWithToken.g.cs +++ /dev/null @@ -1,118 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Net.Http; -using Microsoft.Graph.Wrapper.Runtime; -using Microsoft.Graph.PowerShell.Files.Client; -using Microsoft.Graph.PowerShell.Files.Client.Models; -using Microsoft.Kiota.Abstractions; -using Microsoft.Kiota.Abstractions.Authentication; -using Microsoft.Kiota.Http.HttpClientLibrary; - -namespace Microsoft.Graph.PowerShell.Files -{ - [GraphRoute("GET", "/drives/{drive-id}/items/{driveItem-id}/delta(token='{token}')")] - [Cmdlet(VerbsCommon.Get, "MgDriveItemDeltaWithToken")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Files.Client.Drives.Item.Items.Item.DeltaWithToken.DeltaWithTokenGetResponse))] - public class GetMgDriveItemDeltaWithTokenCommand : GraphClientCmdlet - { - [Parameter(Mandatory = true, Position = 0)] - public string DriveId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] - public string DriveItemId { get; set; } = string.Empty; - - [Parameter(Mandatory = true, Position = 2, - HelpMessage = "Value for the 'token' parameter of this OData function.")] - public string Token { get; set; } = string.Empty; - - - - [Parameter(Mandatory = false)] - public string? Filter { get; set; } - - [Parameter(Mandatory = false)] - [Alias("Select")] - public string[]? Property { get; set; } - - [Parameter(Mandatory = false)] - [Alias("Expand")] - public string[]? ExpandProperty { get; set; } - - [Parameter(Mandatory = false)] - [Alias("OrderBy")] - public string[]? Sort { get; set; } - - [Parameter(Mandatory = false)] - public string? Search { get; set; } - - [Parameter(Mandatory = false)] - public int Top { get; set; } - - [Parameter(Mandatory = false)] - public int Skip { get; set; } - - [Parameter(Mandatory = false)] - public SwitchParameter Count { get; set; } - - - - - protected override void ProcessRecord() - { - - var requestAdapter = GetRequestAdapter(); - var client = new ApiClient(requestAdapter); - - var pathParameters = new Dictionary - { - { "baseurl", requestAdapter.BaseUrl! }, - { "drive%2Did", DriveId }, - { "driveItem%2Did", DriveItemId }, - { "token", Token }, - }; - var requestBuilder = new global::Microsoft.Graph.PowerShell.Files.Client.Drives.Item.Items.Item.DeltaWithToken.DeltaWithTokenRequestBuilder(pathParameters, requestAdapter); - - global::Microsoft.Graph.PowerShell.Files.Client.Drives.Item.Items.Item.DeltaWithToken.DeltaWithTokenGetResponse? result; - try - { - result = requestBuilder.GetAsDeltaWithTokenGetResponseAsync(requestConfiguration => - { - if (this.IsParameterBound(nameof(Filter))) - requestConfiguration.QueryParameters.Filter = Filter; - - if (this.IsParameterBound(nameof(Property))) - requestConfiguration.QueryParameters.Select = Property; - - if (this.IsParameterBound(nameof(ExpandProperty))) - requestConfiguration.QueryParameters.Expand = ExpandProperty; - - if (this.IsParameterBound(nameof(Sort))) - requestConfiguration.QueryParameters.Orderby = Sort; - - if (this.IsParameterBound(nameof(Search))) - requestConfiguration.QueryParameters.Search = Search; - - if (this.IsParameterBound(nameof(Top))) - requestConfiguration.QueryParameters.Top = Top; - - if (this.IsParameterBound(nameof(Skip))) - requestConfiguration.QueryParameters.Skip = Skip; - - if (Count.IsPresent) - requestConfiguration.QueryParameters.Count = true; - - AddRequestHeaders(requestConfiguration.Headers); - }).GetAwaiter().GetResult(); - } - catch (Exception ex) when (ex is not PipelineStoppedException) - { - ThrowGraphRequestFailed(ex, DriveItemId); - return; - } - - WriteObject(result); - } - } -} diff --git a/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveListItemDelta.g.cs b/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveListItemDelta.g.cs index c59789eccd1..44fca5361b3 100644 --- a/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveListItemDelta.g.cs +++ b/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveListItemDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Files { [GraphRoute("GET", "/drives/{drive-id}/list/items/delta()")] - [Cmdlet(VerbsCommon.Get, "MgDriveListItemDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Files.Client.Drives.Item.List.Items.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgDriveListItemDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Files.Client.Models.ListItem))] public class GetMgDriveListItemDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string DriveId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Files.Client.Drives.Item.List.Items.Delta.DeltaGetResponse? result; try { - result = client.Drives[DriveId].List.Items.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Drives[DriveId].List.Items.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Drives[DriveId].List.Items.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Drives[DriveId].List.Items.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, DriveId); return; } - - WriteObject(result); } } } diff --git a/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveListItemDeltaWithToken.g.cs b/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveListItemDeltaWithToken.g.cs deleted file mode 100644 index fcfd843dde7..00000000000 --- a/src/Files/wrapper/v1.0/Cmdlets/GetMgDriveListItemDeltaWithToken.g.cs +++ /dev/null @@ -1,115 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Net.Http; -using Microsoft.Graph.Wrapper.Runtime; -using Microsoft.Graph.PowerShell.Files.Client; -using Microsoft.Graph.PowerShell.Files.Client.Models; -using Microsoft.Kiota.Abstractions; -using Microsoft.Kiota.Abstractions.Authentication; -using Microsoft.Kiota.Http.HttpClientLibrary; - -namespace Microsoft.Graph.PowerShell.Files -{ - [GraphRoute("GET", "/drives/{drive-id}/list/items/delta(token='{token}')")] - [Cmdlet(VerbsCommon.Get, "MgDriveListItemDeltaWithToken")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Files.Client.Drives.Item.List.Items.DeltaWithToken.DeltaWithTokenGetResponse))] - public class GetMgDriveListItemDeltaWithTokenCommand : GraphClientCmdlet - { - [Parameter(Mandatory = true, Position = 0)] - public string DriveId { get; set; } = string.Empty; - - [Parameter(Mandatory = true, Position = 1, - HelpMessage = "Value for the 'token' parameter of this OData function.")] - public string Token { get; set; } = string.Empty; - - - - [Parameter(Mandatory = false)] - public string? Filter { get; set; } - - [Parameter(Mandatory = false)] - [Alias("Select")] - public string[]? Property { get; set; } - - [Parameter(Mandatory = false)] - [Alias("Expand")] - public string[]? ExpandProperty { get; set; } - - [Parameter(Mandatory = false)] - [Alias("OrderBy")] - public string[]? Sort { get; set; } - - [Parameter(Mandatory = false)] - public string? Search { get; set; } - - [Parameter(Mandatory = false)] - public int Top { get; set; } - - [Parameter(Mandatory = false)] - public int Skip { get; set; } - - [Parameter(Mandatory = false)] - public SwitchParameter Count { get; set; } - - - - - protected override void ProcessRecord() - { - - var requestAdapter = GetRequestAdapter(); - var client = new ApiClient(requestAdapter); - - var pathParameters = new Dictionary - { - { "baseurl", requestAdapter.BaseUrl! }, - { "drive%2Did", DriveId }, - { "token", Token }, - }; - var requestBuilder = new global::Microsoft.Graph.PowerShell.Files.Client.Drives.Item.List.Items.DeltaWithToken.DeltaWithTokenRequestBuilder(pathParameters, requestAdapter); - - global::Microsoft.Graph.PowerShell.Files.Client.Drives.Item.List.Items.DeltaWithToken.DeltaWithTokenGetResponse? result; - try - { - result = requestBuilder.GetAsDeltaWithTokenGetResponseAsync(requestConfiguration => - { - if (this.IsParameterBound(nameof(Filter))) - requestConfiguration.QueryParameters.Filter = Filter; - - if (this.IsParameterBound(nameof(Property))) - requestConfiguration.QueryParameters.Select = Property; - - if (this.IsParameterBound(nameof(ExpandProperty))) - requestConfiguration.QueryParameters.Expand = ExpandProperty; - - if (this.IsParameterBound(nameof(Sort))) - requestConfiguration.QueryParameters.Orderby = Sort; - - if (this.IsParameterBound(nameof(Search))) - requestConfiguration.QueryParameters.Search = Search; - - if (this.IsParameterBound(nameof(Top))) - requestConfiguration.QueryParameters.Top = Top; - - if (this.IsParameterBound(nameof(Skip))) - requestConfiguration.QueryParameters.Skip = Skip; - - if (Count.IsPresent) - requestConfiguration.QueryParameters.Count = true; - - AddRequestHeaders(requestConfiguration.Headers); - }).GetAwaiter().GetResult(); - } - catch (Exception ex) when (ex is not PipelineStoppedException) - { - ThrowGraphRequestFailed(ex, DriveId); - return; - } - - WriteObject(result); - } - } -} diff --git a/src/Files/wrapper/v1.0/Cmdlets/GetMgShareListItemDelta.g.cs b/src/Files/wrapper/v1.0/Cmdlets/GetMgShareListItemDelta.g.cs index 20172d19289..2ac21359a19 100644 --- a/src/Files/wrapper/v1.0/Cmdlets/GetMgShareListItemDelta.g.cs +++ b/src/Files/wrapper/v1.0/Cmdlets/GetMgShareListItemDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Files { [GraphRoute("GET", "/shares/{sharedDriveItem-id}/list/items/delta()")] - [Cmdlet(VerbsCommon.Get, "MgShareListItemDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Files.Client.Shares.Item.List.Items.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgShareListItemDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Files.Client.Models.ListItem))] public class GetMgShareListItemDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string SharedDriveItemId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Files.Client.Shares.Item.List.Items.Delta.DeltaGetResponse? result; try { - result = client.Shares[SharedDriveItemId].List.Items.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Shares[SharedDriveItemId].List.Items.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Shares[SharedDriveItemId].List.Items.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Shares[SharedDriveItemId].List.Items.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, SharedDriveItemId); return; } - - WriteObject(result); } } } diff --git a/src/Files/wrapper/v1.0/Cmdlets/GetMgShareListItemDeltaWithToken.g.cs b/src/Files/wrapper/v1.0/Cmdlets/GetMgShareListItemDeltaWithToken.g.cs deleted file mode 100644 index b9d9bfa4204..00000000000 --- a/src/Files/wrapper/v1.0/Cmdlets/GetMgShareListItemDeltaWithToken.g.cs +++ /dev/null @@ -1,115 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Net.Http; -using Microsoft.Graph.Wrapper.Runtime; -using Microsoft.Graph.PowerShell.Files.Client; -using Microsoft.Graph.PowerShell.Files.Client.Models; -using Microsoft.Kiota.Abstractions; -using Microsoft.Kiota.Abstractions.Authentication; -using Microsoft.Kiota.Http.HttpClientLibrary; - -namespace Microsoft.Graph.PowerShell.Files -{ - [GraphRoute("GET", "/shares/{sharedDriveItem-id}/list/items/delta(token='{token}')")] - [Cmdlet(VerbsCommon.Get, "MgShareListItemDeltaWithToken")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Files.Client.Shares.Item.List.Items.DeltaWithToken.DeltaWithTokenGetResponse))] - public class GetMgShareListItemDeltaWithTokenCommand : GraphClientCmdlet - { - [Parameter(Mandatory = true, Position = 0)] - public string SharedDriveItemId { get; set; } = string.Empty; - - [Parameter(Mandatory = true, Position = 1, - HelpMessage = "Value for the 'token' parameter of this OData function.")] - public string Token { get; set; } = string.Empty; - - - - [Parameter(Mandatory = false)] - public string? Filter { get; set; } - - [Parameter(Mandatory = false)] - [Alias("Select")] - public string[]? Property { get; set; } - - [Parameter(Mandatory = false)] - [Alias("Expand")] - public string[]? ExpandProperty { get; set; } - - [Parameter(Mandatory = false)] - [Alias("OrderBy")] - public string[]? Sort { get; set; } - - [Parameter(Mandatory = false)] - public string? Search { get; set; } - - [Parameter(Mandatory = false)] - public int Top { get; set; } - - [Parameter(Mandatory = false)] - public int Skip { get; set; } - - [Parameter(Mandatory = false)] - public SwitchParameter Count { get; set; } - - - - - protected override void ProcessRecord() - { - - var requestAdapter = GetRequestAdapter(); - var client = new ApiClient(requestAdapter); - - var pathParameters = new Dictionary - { - { "baseurl", requestAdapter.BaseUrl! }, - { "sharedDriveItem%2Did", SharedDriveItemId }, - { "token", Token }, - }; - var requestBuilder = new global::Microsoft.Graph.PowerShell.Files.Client.Shares.Item.List.Items.DeltaWithToken.DeltaWithTokenRequestBuilder(pathParameters, requestAdapter); - - global::Microsoft.Graph.PowerShell.Files.Client.Shares.Item.List.Items.DeltaWithToken.DeltaWithTokenGetResponse? result; - try - { - result = requestBuilder.GetAsDeltaWithTokenGetResponseAsync(requestConfiguration => - { - if (this.IsParameterBound(nameof(Filter))) - requestConfiguration.QueryParameters.Filter = Filter; - - if (this.IsParameterBound(nameof(Property))) - requestConfiguration.QueryParameters.Select = Property; - - if (this.IsParameterBound(nameof(ExpandProperty))) - requestConfiguration.QueryParameters.Expand = ExpandProperty; - - if (this.IsParameterBound(nameof(Sort))) - requestConfiguration.QueryParameters.Orderby = Sort; - - if (this.IsParameterBound(nameof(Search))) - requestConfiguration.QueryParameters.Search = Search; - - if (this.IsParameterBound(nameof(Top))) - requestConfiguration.QueryParameters.Top = Top; - - if (this.IsParameterBound(nameof(Skip))) - requestConfiguration.QueryParameters.Skip = Skip; - - if (Count.IsPresent) - requestConfiguration.QueryParameters.Count = true; - - AddRequestHeaders(requestConfiguration.Headers); - }).GetAwaiter().GetResult(); - } - catch (Exception ex) when (ex is not PipelineStoppedException) - { - ThrowGraphRequestFailed(ex, SharedDriveItemId); - return; - } - - WriteObject(result); - } - } -} diff --git a/src/GraphWrapperRuntime/Runtime/GraphClientCmdlet.cs b/src/GraphWrapperRuntime/Runtime/GraphClientCmdlet.cs index 1ca58994035..212c7a6c95a 100644 --- a/src/GraphWrapperRuntime/Runtime/GraphClientCmdlet.cs +++ b/src/GraphWrapperRuntime/Runtime/GraphClientCmdlet.cs @@ -79,6 +79,49 @@ protected void AddRequestHeaders(RequestHeaders requestHeaders) } } + // A caller-supplied continuation link (-DeltaLink) is handed to kiota's WithUrl, which + // sends it verbatim and ignores every other path and query parameter - and the auth header + // is attached afterwards. An off-tenant URL would therefore receive the caller's Graph + // token. The link is checked against the adapter's OWN BaseUrl rather than a hard-coded + // host, so national clouds and custom endpoints keep working with no list to maintain. + protected string ValidateContinuationUrl(string url, IRequestAdapter adapter, string parameterName) + { + // Each branch rethrows the same exception it reported: ThrowTerminatingError does not + // return, but the compiler cannot know that, and a bare throw is only legal in a catch. + if (!Uri.TryCreate(url, UriKind.Absolute, out var link)) + { + var invalid = new ArgumentException($"-{parameterName} must be an absolute URL.", parameterName); + ThrowTerminatingError(new ErrorRecord( + invalid, "InvalidContinuationUrl", ErrorCategory.InvalidArgument, url)); + throw invalid; + } + + // BaseUrl is set by the generated ApiClient constructor before any request is issued, + // so it is populated by the time a cmdlet reaches this call. + if (!Uri.TryCreate(adapter?.BaseUrl, UriKind.Absolute, out var expected)) + { + var unknown = new InvalidOperationException( + $"Cannot validate -{parameterName}: the request adapter has no base URL."); + ThrowTerminatingError(new ErrorRecord( + unknown, "UnknownServiceRoot", ErrorCategory.InvalidOperation, url)); + throw unknown; + } + + if (!string.Equals(link.Scheme, expected.Scheme, StringComparison.OrdinalIgnoreCase) || + !string.Equals(link.Host, expected.Host, StringComparison.OrdinalIgnoreCase)) + { + var mismatch = new ArgumentException( + $"-{parameterName} points at '{link.Scheme}://{link.Host}', but this session is connected to " + + $"'{expected.Scheme}://{expected.Host}'. Refusing to send credentials to another host.", + parameterName); + ThrowTerminatingError(new ErrorRecord( + mismatch, "ContinuationUrlHostMismatch", ErrorCategory.SecurityError, url)); + throw mismatch; + } + + return url; + } + // The single error surface for a failed Graph call, identical across every cmdlet. protected void ThrowGraphRequestFailed(Exception exception, object? targetObject) { diff --git a/src/Groups/wrapper/v1.0/Cmdlets/GetMgGroupDelta.g.cs b/src/Groups/wrapper/v1.0/Cmdlets/GetMgGroupDelta.g.cs index 7b565414fc6..aee910dd401 100644 --- a/src/Groups/wrapper/v1.0/Cmdlets/GetMgGroupDelta.g.cs +++ b/src/Groups/wrapper/v1.0/Cmdlets/GetMgGroupDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Groups { [GraphRoute("GET", "/groups/delta()")] - [Cmdlet(VerbsCommon.Get, "MgGroupDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Groups.Client.Groups.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgGroupDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Groups.Client.Models.Group))] public class GetMgGroupDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Groups.Client.Groups.Delta.DeltaGetResponse? result; try { - result = client.Groups.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Groups.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Groups.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Groups.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Groups/wrapper/v1.0/Cmdlets/GetMgGroupSettingTemplateDelta.g.cs b/src/Groups/wrapper/v1.0/Cmdlets/GetMgGroupSettingTemplateDelta.g.cs index 600affbf206..9b62bda3ae8 100644 --- a/src/Groups/wrapper/v1.0/Cmdlets/GetMgGroupSettingTemplateDelta.g.cs +++ b/src/Groups/wrapper/v1.0/Cmdlets/GetMgGroupSettingTemplateDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Groups { [GraphRoute("GET", "/groupSettingTemplates/delta()")] - [Cmdlet(VerbsCommon.Get, "MgGroupSettingTemplateDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Groups.Client.GroupSettingTemplates.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgGroupSettingTemplateDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Groups.Client.Models.DirectoryObject))] public class GetMgGroupSettingTemplateDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Groups.Client.GroupSettingTemplates.Delta.DeltaGetResponse? result; try { - result = client.GroupSettingTemplates.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.GroupSettingTemplates.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.GroupSettingTemplates.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.GroupSettingTemplates.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgContactDelta.g.cs b/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgContactDelta.g.cs index f2130b228a4..af0038b054b 100644 --- a/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgContactDelta.g.cs +++ b/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgContactDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Identity.DirectoryManagement { [GraphRoute("GET", "/contacts/delta()")] - [Cmdlet(VerbsCommon.Get, "MgContactDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.Contacts.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgContactDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.Models.OrgContact))] public class GetMgContactDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.Contacts.Delta.DeltaGetResponse? result; try { - result = client.Contacts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Contacts.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Contacts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Contacts.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgContractDelta.g.cs b/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgContractDelta.g.cs index 1dc6c199912..4fa59bba57f 100644 --- a/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgContractDelta.g.cs +++ b/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgContractDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Identity.DirectoryManagement { [GraphRoute("GET", "/contracts/delta()")] - [Cmdlet(VerbsCommon.Get, "MgContractDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.Contracts.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgContractDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.Models.DirectoryObject))] public class GetMgContractDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.Contracts.Delta.DeltaGetResponse? result; try { - result = client.Contracts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Contracts.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Contracts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Contracts.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDeviceDelta.g.cs b/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDeviceDelta.g.cs index 71a266f4e02..e3e5794695a 100644 --- a/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDeviceDelta.g.cs +++ b/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDeviceDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Identity.DirectoryManagement { [GraphRoute("GET", "/devices/delta()")] - [Cmdlet(VerbsCommon.Get, "MgDeviceDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.Devices.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgDeviceDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.Models.Device))] public class GetMgDeviceDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.Devices.Delta.DeltaGetResponse? result; try { - result = client.Devices.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Devices.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Devices.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Devices.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDirectoryAdministrativeUnitDelta.g.cs b/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDirectoryAdministrativeUnitDelta.g.cs index 2b939c8f68a..2b0c1386cf8 100644 --- a/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDirectoryAdministrativeUnitDelta.g.cs +++ b/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDirectoryAdministrativeUnitDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Identity.DirectoryManagement { [GraphRoute("GET", "/directory/administrativeUnits/delta()")] - [Cmdlet(VerbsCommon.Get, "MgDirectoryAdministrativeUnitDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.DirectoryNamespace.AdministrativeUnits.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgDirectoryAdministrativeUnitDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.Models.AdministrativeUnit))] public class GetMgDirectoryAdministrativeUnitDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.DirectoryNamespace.AdministrativeUnits.Delta.DeltaGetResponse? result; try { - result = client.Directory.AdministrativeUnits.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Directory.AdministrativeUnits.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Directory.AdministrativeUnits.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Directory.AdministrativeUnits.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDirectoryRoleDelta.g.cs b/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDirectoryRoleDelta.g.cs index 7a6b813e308..ed36ca6d003 100644 --- a/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDirectoryRoleDelta.g.cs +++ b/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDirectoryRoleDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Identity.DirectoryManagement { [GraphRoute("GET", "/directoryRoles/delta()")] - [Cmdlet(VerbsCommon.Get, "MgDirectoryRoleDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.DirectoryRoles.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgDirectoryRoleDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.Models.DirectoryRole))] public class GetMgDirectoryRoleDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.DirectoryRoles.Delta.DeltaGetResponse? result; try { - result = client.DirectoryRoles.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.DirectoryRoles.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.DirectoryRoles.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.DirectoryRoles.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDirectoryRoleTemplateDelta.g.cs b/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDirectoryRoleTemplateDelta.g.cs index 0349e90fd75..00ba39ab15f 100644 --- a/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDirectoryRoleTemplateDelta.g.cs +++ b/src/Identity.DirectoryManagement/wrapper/v1.0/Cmdlets/GetMgDirectoryRoleTemplateDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Identity.DirectoryManagement { [GraphRoute("GET", "/directoryRoleTemplates/delta()")] - [Cmdlet(VerbsCommon.Get, "MgDirectoryRoleTemplateDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.DirectoryRoleTemplates.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgDirectoryRoleTemplateDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.Models.DirectoryObject))] public class GetMgDirectoryRoleTemplateDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Identity.DirectoryManagement.Client.DirectoryRoleTemplates.Delta.DeltaGetResponse? result; try { - result = client.DirectoryRoleTemplates.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.DirectoryRoleTemplates.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.DirectoryRoleTemplates.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.DirectoryRoleTemplates.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Identity.SignIns/wrapper/v1.0/Cmdlets/GetMgOauth2PermissionGrantDelta.g.cs b/src/Identity.SignIns/wrapper/v1.0/Cmdlets/GetMgOauth2PermissionGrantDelta.g.cs index c2f93d516da..71e81e8370f 100644 --- a/src/Identity.SignIns/wrapper/v1.0/Cmdlets/GetMgOauth2PermissionGrantDelta.g.cs +++ b/src/Identity.SignIns/wrapper/v1.0/Cmdlets/GetMgOauth2PermissionGrantDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Identity.SignIns { [GraphRoute("GET", "/oauth2PermissionGrants/delta()")] - [Cmdlet(VerbsCommon.Get, "MgOauth2PermissionGrantDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Identity.SignIns.Client.Oauth2PermissionGrants.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgOauth2PermissionGrantDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Identity.SignIns.Client.Models.OAuth2PermissionGrant))] public class GetMgOauth2PermissionGrantDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Identity.SignIns.Client.Oauth2PermissionGrants.Delta.DeltaGetResponse? result; try { - result = client.Oauth2PermissionGrants.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Oauth2PermissionGrants.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Oauth2PermissionGrants.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Oauth2PermissionGrants.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderChildFolderDelta.g.cs b/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderChildFolderDelta.g.cs index d4208355bb9..acdd41d4ee5 100644 --- a/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderChildFolderDelta.g.cs +++ b/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderChildFolderDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Mail { [GraphRoute("GET", "/users/{user-id}/mailFolders/{mailFolder-id}/childFolders/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserMailFolderChildFolderDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Mail.Client.Users.Item.MailFolders.Item.ChildFolders.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserMailFolderChildFolderDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Mail.Client.Models.MailFolder))] public class GetMgUserMailFolderChildFolderDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string MailFolderId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Mail.Client.Users.Item.MailFolders.Item.ChildFolders.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].MailFolders[MailFolderId].ChildFolders.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].MailFolders[MailFolderId].ChildFolders.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].MailFolders[MailFolderId].ChildFolders.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].MailFolders[MailFolderId].ChildFolders.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, MailFolderId); return; } - - WriteObject(result); } } } diff --git a/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderChildFolderMessageDelta.g.cs b/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderChildFolderMessageDelta.g.cs index 56459e047fc..4a6c0dfe88a 100644 --- a/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderChildFolderMessageDelta.g.cs +++ b/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderChildFolderMessageDelta.g.cs @@ -14,47 +14,62 @@ namespace Microsoft.Graph.PowerShell.Mail { [GraphRoute("GET", "/users/{user-id}/mailFolders/{mailFolder-id}/childFolders/{mailFolder-id1}/messages/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserMailFolderChildFolderMessageDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Mail.Client.Users.Item.MailFolders.Item.ChildFolders.Item.Messages.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserMailFolderChildFolderMessageDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Mail.Client.Models.Message))] public class GetMgUserMailFolderChildFolderMessageDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string MailFolderId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, ParameterSetName = "DeltaSync")] public string MailFolderId1 { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -64,11 +79,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Mail.Client.Users.Item.MailFolders.Item.ChildFolders.Item.Messages.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].MailFolders[MailFolderId].ChildFolders[MailFolderId1].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].MailFolders[MailFolderId].ChildFolders[MailFolderId1].Messages.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].MailFolders[MailFolderId].ChildFolders[MailFolderId1].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -96,14 +121,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].MailFolders[MailFolderId].ChildFolders[MailFolderId1].Messages.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, MailFolderId1); return; } - - WriteObject(result); } } } diff --git a/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderDelta.g.cs b/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderDelta.g.cs index 652b4a2d141..f638569354b 100644 --- a/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderDelta.g.cs +++ b/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Mail { [GraphRoute("GET", "/users/{user-id}/mailFolders/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserMailFolderDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Mail.Client.Users.Item.MailFolders.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserMailFolderDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Mail.Client.Models.MailFolder))] public class GetMgUserMailFolderDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Mail.Client.Users.Item.MailFolders.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].MailFolders.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].MailFolders.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].MailFolders.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].MailFolders.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, UserId); return; } - - WriteObject(result); } } } diff --git a/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderMessageDelta.g.cs b/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderMessageDelta.g.cs index 5e2eadde123..c737e48336f 100644 --- a/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderMessageDelta.g.cs +++ b/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMailFolderMessageDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Mail { [GraphRoute("GET", "/users/{user-id}/mailFolders/{mailFolder-id}/messages/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserMailFolderMessageDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Mail.Client.Users.Item.MailFolders.Item.Messages.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserMailFolderMessageDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Mail.Client.Models.Message))] public class GetMgUserMailFolderMessageDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string MailFolderId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Mail.Client.Users.Item.MailFolders.Item.Messages.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].MailFolders[MailFolderId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].MailFolders[MailFolderId].Messages.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].MailFolders[MailFolderId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].MailFolders[MailFolderId].Messages.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, MailFolderId); return; } - - WriteObject(result); } } } diff --git a/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMessageDelta.g.cs b/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMessageDelta.g.cs index 34a4f6285be..14f0039174d 100644 --- a/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMessageDelta.g.cs +++ b/src/Mail/wrapper/v1.0/Cmdlets/GetMgUserMessageDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Mail { [GraphRoute("GET", "/users/{user-id}/messages/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserMessageDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Mail.Client.Users.Item.Messages.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserMessageDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Mail.Client.Models.Message))] public class GetMgUserMessageDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Mail.Client.Users.Item.Messages.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].Messages.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].Messages.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, UserId); return; } - - WriteObject(result); } } } diff --git a/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactDelta.g.cs b/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactDelta.g.cs index 626fc652b32..cf03eca524b 100644 --- a/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactDelta.g.cs +++ b/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.PersonalContacts { [GraphRoute("GET", "/users/{user-id}/contacts/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserContactDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.PersonalContacts.Client.Users.Item.Contacts.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserContactDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.PersonalContacts.Client.Models.Contact))] public class GetMgUserContactDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.PersonalContacts.Client.Users.Item.Contacts.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].Contacts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].Contacts.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].Contacts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].Contacts.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, UserId); return; } - - WriteObject(result); } } } diff --git a/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderChildFolderContactDelta.g.cs b/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderChildFolderContactDelta.g.cs index 45800953eef..27c631d570b 100644 --- a/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderChildFolderContactDelta.g.cs +++ b/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderChildFolderContactDelta.g.cs @@ -14,47 +14,62 @@ namespace Microsoft.Graph.PowerShell.PersonalContacts { [GraphRoute("GET", "/users/{user-id}/contactFolders/{contactFolder-id}/childFolders/{contactFolder-id1}/contacts/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserContactFolderChildFolderContactDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.PersonalContacts.Client.Users.Item.ContactFolders.Item.ChildFolders.Item.Contacts.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserContactFolderChildFolderContactDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.PersonalContacts.Client.Models.Contact))] public class GetMgUserContactFolderChildFolderContactDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ContactFolderId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, ParameterSetName = "DeltaSync")] public string ContactFolderId1 { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -64,11 +79,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.PersonalContacts.Client.Users.Item.ContactFolders.Item.ChildFolders.Item.Contacts.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].ContactFolders[ContactFolderId].ChildFolders[ContactFolderId1].Contacts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].ContactFolders[ContactFolderId].ChildFolders[ContactFolderId1].Contacts.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].ContactFolders[ContactFolderId].ChildFolders[ContactFolderId1].Contacts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -96,14 +121,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].ContactFolders[ContactFolderId].ChildFolders[ContactFolderId1].Contacts.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ContactFolderId1); return; } - - WriteObject(result); } } } diff --git a/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderChildFolderDelta.g.cs b/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderChildFolderDelta.g.cs index 9f130e58905..93384d2c9af 100644 --- a/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderChildFolderDelta.g.cs +++ b/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderChildFolderDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.PersonalContacts { [GraphRoute("GET", "/users/{user-id}/contactFolders/{contactFolder-id}/childFolders/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserContactFolderChildFolderDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.PersonalContacts.Client.Users.Item.ContactFolders.Item.ChildFolders.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserContactFolderChildFolderDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.PersonalContacts.Client.Models.ContactFolder))] public class GetMgUserContactFolderChildFolderDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ContactFolderId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.PersonalContacts.Client.Users.Item.ContactFolders.Item.ChildFolders.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].ContactFolders[ContactFolderId].ChildFolders.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].ContactFolders[ContactFolderId].ChildFolders.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].ContactFolders[ContactFolderId].ChildFolders.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].ContactFolders[ContactFolderId].ChildFolders.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ContactFolderId); return; } - - WriteObject(result); } } } diff --git a/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderContactDelta.g.cs b/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderContactDelta.g.cs index 5ce6d247c61..b25e253e0df 100644 --- a/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderContactDelta.g.cs +++ b/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderContactDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.PersonalContacts { [GraphRoute("GET", "/users/{user-id}/contactFolders/{contactFolder-id}/contacts/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserContactFolderContactDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.PersonalContacts.Client.Users.Item.ContactFolders.Item.Contacts.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserContactFolderContactDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.PersonalContacts.Client.Models.Contact))] public class GetMgUserContactFolderContactDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ContactFolderId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.PersonalContacts.Client.Users.Item.ContactFolders.Item.Contacts.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].ContactFolders[ContactFolderId].Contacts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].ContactFolders[ContactFolderId].Contacts.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].ContactFolders[ContactFolderId].Contacts.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].ContactFolders[ContactFolderId].Contacts.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ContactFolderId); return; } - - WriteObject(result); } } } diff --git a/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderDelta.g.cs b/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderDelta.g.cs index ec71eb06d74..9601a6e7757 100644 --- a/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderDelta.g.cs +++ b/src/PersonalContacts/wrapper/v1.0/Cmdlets/GetMgUserContactFolderDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.PersonalContacts { [GraphRoute("GET", "/users/{user-id}/contactFolders/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserContactFolderDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.PersonalContacts.Client.Users.Item.ContactFolders.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserContactFolderDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.PersonalContacts.Client.Models.ContactFolder))] public class GetMgUserContactFolderDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.PersonalContacts.Client.Users.Item.ContactFolders.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].ContactFolders.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].ContactFolders.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].ContactFolders.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].ContactFolders.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, UserId); return; } - - WriteObject(result); } } } diff --git a/src/Sites/wrapper/v1.0/Cmdlets/GetMgGroupSiteDelta.g.cs b/src/Sites/wrapper/v1.0/Cmdlets/GetMgGroupSiteDelta.g.cs index 28da7073f06..6e236fcc799 100644 --- a/src/Sites/wrapper/v1.0/Cmdlets/GetMgGroupSiteDelta.g.cs +++ b/src/Sites/wrapper/v1.0/Cmdlets/GetMgGroupSiteDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Sites { [GraphRoute("GET", "/groups/{group-id}/sites/delta()")] - [Cmdlet(VerbsCommon.Get, "MgGroupSiteDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Sites.Client.Groups.Item.Sites.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgGroupSiteDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Sites.Client.Models.Site))] public class GetMgGroupSiteDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string GroupId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Sites.Client.Groups.Item.Sites.Delta.DeltaGetResponse? result; try { - result = client.Groups[GroupId].Sites.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Groups[GroupId].Sites.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Groups[GroupId].Sites.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Groups[GroupId].Sites.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, GroupId); return; } - - WriteObject(result); } } } diff --git a/src/Sites/wrapper/v1.0/Cmdlets/GetMgGroupSiteListItemDelta.g.cs b/src/Sites/wrapper/v1.0/Cmdlets/GetMgGroupSiteListItemDelta.g.cs index 23f3e36f722..6f4d4cb2f8b 100644 --- a/src/Sites/wrapper/v1.0/Cmdlets/GetMgGroupSiteListItemDelta.g.cs +++ b/src/Sites/wrapper/v1.0/Cmdlets/GetMgGroupSiteListItemDelta.g.cs @@ -14,47 +14,62 @@ namespace Microsoft.Graph.PowerShell.Sites { [GraphRoute("GET", "/groups/{group-id}/sites/{site-id}/lists/{list-id}/items/delta()")] - [Cmdlet(VerbsCommon.Get, "MgGroupSiteListItemDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Sites.Client.Groups.Item.Sites.Item.Lists.Item.Items.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgGroupSiteListItemDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Sites.Client.Models.ListItem))] public class GetMgGroupSiteListItemDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string GroupId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string SiteId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, ParameterSetName = "DeltaSync")] public string ListId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -64,11 +79,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Sites.Client.Groups.Item.Sites.Item.Lists.Item.Items.Delta.DeltaGetResponse? result; try { - result = client.Groups[GroupId].Sites[SiteId].Lists[ListId].Items.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Groups[GroupId].Sites[SiteId].Lists[ListId].Items.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Groups[GroupId].Sites[SiteId].Lists[ListId].Items.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -96,14 +121,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Groups[GroupId].Sites[SiteId].Lists[ListId].Items.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ListId); return; } - - WriteObject(result); } } } diff --git a/src/Sites/wrapper/v1.0/Cmdlets/GetMgGroupSiteListItemDeltaWithToken.g.cs b/src/Sites/wrapper/v1.0/Cmdlets/GetMgGroupSiteListItemDeltaWithToken.g.cs deleted file mode 100644 index 292f7a2d62d..00000000000 --- a/src/Sites/wrapper/v1.0/Cmdlets/GetMgGroupSiteListItemDeltaWithToken.g.cs +++ /dev/null @@ -1,121 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Net.Http; -using Microsoft.Graph.Wrapper.Runtime; -using Microsoft.Graph.PowerShell.Sites.Client; -using Microsoft.Graph.PowerShell.Sites.Client.Models; -using Microsoft.Kiota.Abstractions; -using Microsoft.Kiota.Abstractions.Authentication; -using Microsoft.Kiota.Http.HttpClientLibrary; - -namespace Microsoft.Graph.PowerShell.Sites -{ - [GraphRoute("GET", "/groups/{group-id}/sites/{site-id}/lists/{list-id}/items/delta(token='{token}')")] - [Cmdlet(VerbsCommon.Get, "MgGroupSiteListItemDeltaWithToken")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Sites.Client.Groups.Item.Sites.Item.Lists.Item.Items.DeltaWithToken.DeltaWithTokenGetResponse))] - public class GetMgGroupSiteListItemDeltaWithTokenCommand : GraphClientCmdlet - { - [Parameter(Mandatory = true, Position = 0)] - public string GroupId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] - public string SiteId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 2)] - public string ListId { get; set; } = string.Empty; - - [Parameter(Mandatory = true, Position = 3, - HelpMessage = "Value for the 'token' parameter of this OData function.")] - public string Token { get; set; } = string.Empty; - - - - [Parameter(Mandatory = false)] - public string? Filter { get; set; } - - [Parameter(Mandatory = false)] - [Alias("Select")] - public string[]? Property { get; set; } - - [Parameter(Mandatory = false)] - [Alias("Expand")] - public string[]? ExpandProperty { get; set; } - - [Parameter(Mandatory = false)] - [Alias("OrderBy")] - public string[]? Sort { get; set; } - - [Parameter(Mandatory = false)] - public string? Search { get; set; } - - [Parameter(Mandatory = false)] - public int Top { get; set; } - - [Parameter(Mandatory = false)] - public int Skip { get; set; } - - [Parameter(Mandatory = false)] - public SwitchParameter Count { get; set; } - - - - - protected override void ProcessRecord() - { - - var requestAdapter = GetRequestAdapter(); - var client = new ApiClient(requestAdapter); - - var pathParameters = new Dictionary - { - { "baseurl", requestAdapter.BaseUrl! }, - { "group%2Did", GroupId }, - { "site%2Did", SiteId }, - { "list%2Did", ListId }, - { "token", Token }, - }; - var requestBuilder = new global::Microsoft.Graph.PowerShell.Sites.Client.Groups.Item.Sites.Item.Lists.Item.Items.DeltaWithToken.DeltaWithTokenRequestBuilder(pathParameters, requestAdapter); - - global::Microsoft.Graph.PowerShell.Sites.Client.Groups.Item.Sites.Item.Lists.Item.Items.DeltaWithToken.DeltaWithTokenGetResponse? result; - try - { - result = requestBuilder.GetAsDeltaWithTokenGetResponseAsync(requestConfiguration => - { - if (this.IsParameterBound(nameof(Filter))) - requestConfiguration.QueryParameters.Filter = Filter; - - if (this.IsParameterBound(nameof(Property))) - requestConfiguration.QueryParameters.Select = Property; - - if (this.IsParameterBound(nameof(ExpandProperty))) - requestConfiguration.QueryParameters.Expand = ExpandProperty; - - if (this.IsParameterBound(nameof(Sort))) - requestConfiguration.QueryParameters.Orderby = Sort; - - if (this.IsParameterBound(nameof(Search))) - requestConfiguration.QueryParameters.Search = Search; - - if (this.IsParameterBound(nameof(Top))) - requestConfiguration.QueryParameters.Top = Top; - - if (this.IsParameterBound(nameof(Skip))) - requestConfiguration.QueryParameters.Skip = Skip; - - if (Count.IsPresent) - requestConfiguration.QueryParameters.Count = true; - - AddRequestHeaders(requestConfiguration.Headers); - }).GetAwaiter().GetResult(); - } - catch (Exception ex) when (ex is not PipelineStoppedException) - { - ThrowGraphRequestFailed(ex, ListId); - return; - } - - WriteObject(result); - } - } -} diff --git a/src/Sites/wrapper/v1.0/Cmdlets/GetMgSiteDelta.g.cs b/src/Sites/wrapper/v1.0/Cmdlets/GetMgSiteDelta.g.cs index a9b431ec016..747ffc9314a 100644 --- a/src/Sites/wrapper/v1.0/Cmdlets/GetMgSiteDelta.g.cs +++ b/src/Sites/wrapper/v1.0/Cmdlets/GetMgSiteDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Sites { [GraphRoute("GET", "/sites/delta()")] - [Cmdlet(VerbsCommon.Get, "MgSiteDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Sites.Client.Sites.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgSiteDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Sites.Client.Models.Site))] public class GetMgSiteDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Sites.Client.Sites.Delta.DeltaGetResponse? result; try { - result = client.Sites.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Sites.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Sites.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Sites.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Sites/wrapper/v1.0/Cmdlets/GetMgSiteListItemDelta.g.cs b/src/Sites/wrapper/v1.0/Cmdlets/GetMgSiteListItemDelta.g.cs index c2217d33179..68ab1491e17 100644 --- a/src/Sites/wrapper/v1.0/Cmdlets/GetMgSiteListItemDelta.g.cs +++ b/src/Sites/wrapper/v1.0/Cmdlets/GetMgSiteListItemDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Sites { [GraphRoute("GET", "/sites/{site-id}/lists/{list-id}/items/delta()")] - [Cmdlet(VerbsCommon.Get, "MgSiteListItemDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Sites.Client.Sites.Item.Lists.Item.Items.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgSiteListItemDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Sites.Client.Models.ListItem))] public class GetMgSiteListItemDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string SiteId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ListId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Sites.Client.Sites.Item.Lists.Item.Items.Delta.DeltaGetResponse? result; try { - result = client.Sites[SiteId].Lists[ListId].Items.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Sites[SiteId].Lists[ListId].Items.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Sites[SiteId].Lists[ListId].Items.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Sites[SiteId].Lists[ListId].Items.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ListId); return; } - - WriteObject(result); } } } diff --git a/src/Sites/wrapper/v1.0/Cmdlets/GetMgSiteListItemDeltaWithToken.g.cs b/src/Sites/wrapper/v1.0/Cmdlets/GetMgSiteListItemDeltaWithToken.g.cs deleted file mode 100644 index df16e2e6e02..00000000000 --- a/src/Sites/wrapper/v1.0/Cmdlets/GetMgSiteListItemDeltaWithToken.g.cs +++ /dev/null @@ -1,118 +0,0 @@ -#nullable enable - -using System; -using System.Collections.Generic; -using System.Management.Automation; -using System.Net.Http; -using Microsoft.Graph.Wrapper.Runtime; -using Microsoft.Graph.PowerShell.Sites.Client; -using Microsoft.Graph.PowerShell.Sites.Client.Models; -using Microsoft.Kiota.Abstractions; -using Microsoft.Kiota.Abstractions.Authentication; -using Microsoft.Kiota.Http.HttpClientLibrary; - -namespace Microsoft.Graph.PowerShell.Sites -{ - [GraphRoute("GET", "/sites/{site-id}/lists/{list-id}/items/delta(token='{token}')")] - [Cmdlet(VerbsCommon.Get, "MgSiteListItemDeltaWithToken")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Sites.Client.Sites.Item.Lists.Item.Items.DeltaWithToken.DeltaWithTokenGetResponse))] - public class GetMgSiteListItemDeltaWithTokenCommand : GraphClientCmdlet - { - [Parameter(Mandatory = true, Position = 0)] - public string SiteId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] - public string ListId { get; set; } = string.Empty; - - [Parameter(Mandatory = true, Position = 2, - HelpMessage = "Value for the 'token' parameter of this OData function.")] - public string Token { get; set; } = string.Empty; - - - - [Parameter(Mandatory = false)] - public string? Filter { get; set; } - - [Parameter(Mandatory = false)] - [Alias("Select")] - public string[]? Property { get; set; } - - [Parameter(Mandatory = false)] - [Alias("Expand")] - public string[]? ExpandProperty { get; set; } - - [Parameter(Mandatory = false)] - [Alias("OrderBy")] - public string[]? Sort { get; set; } - - [Parameter(Mandatory = false)] - public string? Search { get; set; } - - [Parameter(Mandatory = false)] - public int Top { get; set; } - - [Parameter(Mandatory = false)] - public int Skip { get; set; } - - [Parameter(Mandatory = false)] - public SwitchParameter Count { get; set; } - - - - - protected override void ProcessRecord() - { - - var requestAdapter = GetRequestAdapter(); - var client = new ApiClient(requestAdapter); - - var pathParameters = new Dictionary - { - { "baseurl", requestAdapter.BaseUrl! }, - { "site%2Did", SiteId }, - { "list%2Did", ListId }, - { "token", Token }, - }; - var requestBuilder = new global::Microsoft.Graph.PowerShell.Sites.Client.Sites.Item.Lists.Item.Items.DeltaWithToken.DeltaWithTokenRequestBuilder(pathParameters, requestAdapter); - - global::Microsoft.Graph.PowerShell.Sites.Client.Sites.Item.Lists.Item.Items.DeltaWithToken.DeltaWithTokenGetResponse? result; - try - { - result = requestBuilder.GetAsDeltaWithTokenGetResponseAsync(requestConfiguration => - { - if (this.IsParameterBound(nameof(Filter))) - requestConfiguration.QueryParameters.Filter = Filter; - - if (this.IsParameterBound(nameof(Property))) - requestConfiguration.QueryParameters.Select = Property; - - if (this.IsParameterBound(nameof(ExpandProperty))) - requestConfiguration.QueryParameters.Expand = ExpandProperty; - - if (this.IsParameterBound(nameof(Sort))) - requestConfiguration.QueryParameters.Orderby = Sort; - - if (this.IsParameterBound(nameof(Search))) - requestConfiguration.QueryParameters.Search = Search; - - if (this.IsParameterBound(nameof(Top))) - requestConfiguration.QueryParameters.Top = Top; - - if (this.IsParameterBound(nameof(Skip))) - requestConfiguration.QueryParameters.Skip = Skip; - - if (Count.IsPresent) - requestConfiguration.QueryParameters.Count = true; - - AddRequestHeaders(requestConfiguration.Headers); - }).GetAwaiter().GetResult(); - } - catch (Exception ex) when (ex is not PipelineStoppedException) - { - ThrowGraphRequestFailed(ex, ListId); - return; - } - - WriteObject(result); - } - } -} diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgChatMessageDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgChatMessageDelta.g.cs index 5b6c79630dd..92eb60fb836 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgChatMessageDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgChatMessageDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/chats/{chat-id}/messages/delta()")] - [Cmdlet(VerbsCommon.Get, "MgChatMessageDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Chats.Item.Messages.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgChatMessageDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgChatMessageDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string ChatId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Chats.Item.Messages.Delta.DeltaGetResponse? result; try { - result = client.Chats[ChatId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Chats[ChatId].Messages.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Chats[ChatId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Chats[ChatId].Messages.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ChatId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgChatMessageReplyDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgChatMessageReplyDelta.g.cs index 89c9405f88c..c85287cc3f3 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgChatMessageReplyDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgChatMessageReplyDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/chats/{chat-id}/messages/{chatMessage-id}/replies/delta()")] - [Cmdlet(VerbsCommon.Get, "MgChatMessageReplyDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Chats.Item.Messages.Item.Replies.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgChatMessageReplyDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgChatMessageReplyDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string ChatId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ChatMessageId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Chats.Item.Messages.Item.Replies.Delta.DeltaGetResponse? result; try { - result = client.Chats[ChatId].Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Chats[ChatId].Messages[ChatMessageId].Replies.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Chats[ChatId].Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Chats[ChatId].Messages[ChatMessageId].Replies.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ChatMessageId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgChatTargetedMessageReplyDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgChatTargetedMessageReplyDelta.g.cs index 22157ddf2af..7407a59ca63 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgChatTargetedMessageReplyDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgChatTargetedMessageReplyDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/chats/{chat-id}/targetedMessages/{targetedChatMessage-id}/replies/delta()")] - [Cmdlet(VerbsCommon.Get, "MgChatTargetedMessageReplyDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Chats.Item.TargetedMessages.Item.Replies.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgChatTargetedMessageReplyDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgChatTargetedMessageReplyDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string ChatId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string TargetedChatMessageId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Chats.Item.TargetedMessages.Item.Replies.Delta.DeltaGetResponse? result; try { - result = client.Chats[ChatId].TargetedMessages[TargetedChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Chats[ChatId].TargetedMessages[TargetedChatMessageId].Replies.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Chats[ChatId].TargetedMessages[TargetedChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Chats[ChatId].TargetedMessages[TargetedChatMessageId].Replies.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, TargetedChatMessageId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamChannelMessageDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamChannelMessageDelta.g.cs index efff31ef184..b72bb2e7b96 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamChannelMessageDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamChannelMessageDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/groups/{group-id}/team/channels/{channel-id}/messages/delta()")] - [Cmdlet(VerbsCommon.Get, "MgGroupTeamChannelMessageDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Groups.Item.Team.Channels.Item.Messages.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgGroupTeamChannelMessageDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgGroupTeamChannelMessageDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string GroupId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ChannelId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Groups.Item.Team.Channels.Item.Messages.Delta.DeltaGetResponse? result; try { - result = client.Groups[GroupId].Team.Channels[ChannelId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Groups[GroupId].Team.Channels[ChannelId].Messages.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Groups[GroupId].Team.Channels[ChannelId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Groups[GroupId].Team.Channels[ChannelId].Messages.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ChannelId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamChannelMessageReplyDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamChannelMessageReplyDelta.g.cs index a5db4918e88..921b7da56e9 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamChannelMessageReplyDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamChannelMessageReplyDelta.g.cs @@ -14,47 +14,62 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/groups/{group-id}/team/channels/{channel-id}/messages/{chatMessage-id}/replies/delta()")] - [Cmdlet(VerbsCommon.Get, "MgGroupTeamChannelMessageReplyDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Groups.Item.Team.Channels.Item.Messages.Item.Replies.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgGroupTeamChannelMessageReplyDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgGroupTeamChannelMessageReplyDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string GroupId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ChannelId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, ParameterSetName = "DeltaSync")] public string ChatMessageId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -64,11 +79,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Groups.Item.Team.Channels.Item.Messages.Item.Replies.Delta.DeltaGetResponse? result; try { - result = client.Groups[GroupId].Team.Channels[ChannelId].Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Groups[GroupId].Team.Channels[ChannelId].Messages[ChatMessageId].Replies.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Groups[GroupId].Team.Channels[ChannelId].Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -96,14 +121,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Groups[GroupId].Team.Channels[ChannelId].Messages[ChatMessageId].Replies.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ChatMessageId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamPrimaryChannelMessageDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamPrimaryChannelMessageDelta.g.cs index aeb89653ab7..1c6f5bedc81 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamPrimaryChannelMessageDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamPrimaryChannelMessageDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/groups/{group-id}/team/primaryChannel/messages/delta()")] - [Cmdlet(VerbsCommon.Get, "MgGroupTeamPrimaryChannelMessageDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Groups.Item.Team.PrimaryChannel.Messages.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgGroupTeamPrimaryChannelMessageDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgGroupTeamPrimaryChannelMessageDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string GroupId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Groups.Item.Team.PrimaryChannel.Messages.Delta.DeltaGetResponse? result; try { - result = client.Groups[GroupId].Team.PrimaryChannel.Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Groups[GroupId].Team.PrimaryChannel.Messages.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Groups[GroupId].Team.PrimaryChannel.Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Groups[GroupId].Team.PrimaryChannel.Messages.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, GroupId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamPrimaryChannelMessageReplyDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamPrimaryChannelMessageReplyDelta.g.cs index 2ee6f258a79..ab0ffb45d04 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamPrimaryChannelMessageReplyDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgGroupTeamPrimaryChannelMessageReplyDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/groups/{group-id}/team/primaryChannel/messages/{chatMessage-id}/replies/delta()")] - [Cmdlet(VerbsCommon.Get, "MgGroupTeamPrimaryChannelMessageReplyDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Groups.Item.Team.PrimaryChannel.Messages.Item.Replies.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgGroupTeamPrimaryChannelMessageReplyDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgGroupTeamPrimaryChannelMessageReplyDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string GroupId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ChatMessageId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Groups.Item.Team.PrimaryChannel.Messages.Item.Replies.Delta.DeltaGetResponse? result; try { - result = client.Groups[GroupId].Team.PrimaryChannel.Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Groups[GroupId].Team.PrimaryChannel.Messages[ChatMessageId].Replies.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Groups[GroupId].Team.PrimaryChannel.Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Groups[GroupId].Team.PrimaryChannel.Messages[ChatMessageId].Replies.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ChatMessageId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamChannelMessageDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamChannelMessageDelta.g.cs index a8c1ffaf11d..ab5c339f2c3 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamChannelMessageDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamChannelMessageDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/teams/{team-id}/channels/{channel-id}/messages/delta()")] - [Cmdlet(VerbsCommon.Get, "MgTeamChannelMessageDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Teams.Item.Channels.Item.Messages.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgTeamChannelMessageDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgTeamChannelMessageDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string TeamId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ChannelId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Teams.Item.Channels.Item.Messages.Delta.DeltaGetResponse? result; try { - result = client.Teams[TeamId].Channels[ChannelId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Teams[TeamId].Channels[ChannelId].Messages.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Teams[TeamId].Channels[ChannelId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Teams[TeamId].Channels[ChannelId].Messages.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ChannelId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamChannelMessageReplyDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamChannelMessageReplyDelta.g.cs index 8bd72bfaec2..92e452da5ea 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamChannelMessageReplyDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamChannelMessageReplyDelta.g.cs @@ -14,47 +14,62 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/replies/delta()")] - [Cmdlet(VerbsCommon.Get, "MgTeamChannelMessageReplyDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Teams.Item.Channels.Item.Messages.Item.Replies.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgTeamChannelMessageReplyDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgTeamChannelMessageReplyDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string TeamId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ChannelId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, ParameterSetName = "DeltaSync")] public string ChatMessageId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -64,11 +79,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Teams.Item.Channels.Item.Messages.Item.Replies.Delta.DeltaGetResponse? result; try { - result = client.Teams[TeamId].Channels[ChannelId].Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Teams[TeamId].Channels[ChannelId].Messages[ChatMessageId].Replies.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Teams[TeamId].Channels[ChannelId].Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -96,14 +121,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Teams[TeamId].Channels[ChannelId].Messages[ChatMessageId].Replies.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ChatMessageId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamPrimaryChannelMessageDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamPrimaryChannelMessageDelta.g.cs index 6d33f7f0bac..429e1bdabd2 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamPrimaryChannelMessageDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamPrimaryChannelMessageDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/teams/{team-id}/primaryChannel/messages/delta()")] - [Cmdlet(VerbsCommon.Get, "MgTeamPrimaryChannelMessageDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Teams.Item.PrimaryChannel.Messages.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgTeamPrimaryChannelMessageDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgTeamPrimaryChannelMessageDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string TeamId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Teams.Item.PrimaryChannel.Messages.Delta.DeltaGetResponse? result; try { - result = client.Teams[TeamId].PrimaryChannel.Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Teams[TeamId].PrimaryChannel.Messages.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Teams[TeamId].PrimaryChannel.Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Teams[TeamId].PrimaryChannel.Messages.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, TeamId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamPrimaryChannelMessageReplyDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamPrimaryChannelMessageReplyDelta.g.cs index 57429eb8e67..dc9efbde022 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamPrimaryChannelMessageReplyDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamPrimaryChannelMessageReplyDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/teams/{team-id}/primaryChannel/messages/{chatMessage-id}/replies/delta()")] - [Cmdlet(VerbsCommon.Get, "MgTeamPrimaryChannelMessageReplyDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Teams.Item.PrimaryChannel.Messages.Item.Replies.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgTeamPrimaryChannelMessageReplyDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgTeamPrimaryChannelMessageReplyDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string TeamId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ChatMessageId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Teams.Item.PrimaryChannel.Messages.Item.Replies.Delta.DeltaGetResponse? result; try { - result = client.Teams[TeamId].PrimaryChannel.Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Teams[TeamId].PrimaryChannel.Messages[ChatMessageId].Replies.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Teams[TeamId].PrimaryChannel.Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Teams[TeamId].PrimaryChannel.Messages[ChatMessageId].Replies.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ChatMessageId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamworkDeletedTeamChannelMessageDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamworkDeletedTeamChannelMessageDelta.g.cs index e1ecbd14bd2..027b3f6579b 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamworkDeletedTeamChannelMessageDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamworkDeletedTeamChannelMessageDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/teamwork/deletedTeams/{deletedTeam-id}/channels/{channel-id}/messages/delta()")] - [Cmdlet(VerbsCommon.Get, "MgTeamworkDeletedTeamChannelMessageDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Teamwork.DeletedTeams.Item.Channels.Item.Messages.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgTeamworkDeletedTeamChannelMessageDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgTeamworkDeletedTeamChannelMessageDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string DeletedTeamId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ChannelId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Teamwork.DeletedTeams.Item.Channels.Item.Messages.Delta.DeltaGetResponse? result; try { - result = client.Teamwork.DeletedTeams[DeletedTeamId].Channels[ChannelId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Teamwork.DeletedTeams[DeletedTeamId].Channels[ChannelId].Messages.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Teamwork.DeletedTeams[DeletedTeamId].Channels[ChannelId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Teamwork.DeletedTeams[DeletedTeamId].Channels[ChannelId].Messages.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ChannelId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamworkDeletedTeamChannelMessageReplyDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamworkDeletedTeamChannelMessageReplyDelta.g.cs index aeb7e38637e..4b8b804d8c1 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamworkDeletedTeamChannelMessageReplyDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgTeamworkDeletedTeamChannelMessageReplyDelta.g.cs @@ -14,47 +14,62 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/teamwork/deletedTeams/{deletedTeam-id}/channels/{channel-id}/messages/{chatMessage-id}/replies/delta()")] - [Cmdlet(VerbsCommon.Get, "MgTeamworkDeletedTeamChannelMessageReplyDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Teamwork.DeletedTeams.Item.Channels.Item.Messages.Item.Replies.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgTeamworkDeletedTeamChannelMessageReplyDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgTeamworkDeletedTeamChannelMessageReplyDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string DeletedTeamId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ChannelId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, ParameterSetName = "DeltaSync")] public string ChatMessageId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -64,11 +79,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Teamwork.DeletedTeams.Item.Channels.Item.Messages.Item.Replies.Delta.DeltaGetResponse? result; try { - result = client.Teamwork.DeletedTeams[DeletedTeamId].Channels[ChannelId].Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Teamwork.DeletedTeams[DeletedTeamId].Channels[ChannelId].Messages[ChatMessageId].Replies.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Teamwork.DeletedTeams[DeletedTeamId].Channels[ChannelId].Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -96,14 +121,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Teamwork.DeletedTeams[DeletedTeamId].Channels[ChannelId].Messages[ChatMessageId].Replies.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ChatMessageId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgUserChatMessageDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgUserChatMessageDelta.g.cs index aaf589b82eb..61b7347e623 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgUserChatMessageDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgUserChatMessageDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/users/{user-id}/chats/{chat-id}/messages/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserChatMessageDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Users.Item.Chats.Item.Messages.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserChatMessageDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgUserChatMessageDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ChatId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Users.Item.Chats.Item.Messages.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].Chats[ChatId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].Chats[ChatId].Messages.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].Chats[ChatId].Messages.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].Chats[ChatId].Messages.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ChatId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgUserChatMessageReplyDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgUserChatMessageReplyDelta.g.cs index b50d9184dcc..0fba308fe1d 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgUserChatMessageReplyDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgUserChatMessageReplyDelta.g.cs @@ -14,47 +14,62 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/users/{user-id}/chats/{chat-id}/messages/{chatMessage-id}/replies/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserChatMessageReplyDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Users.Item.Chats.Item.Messages.Item.Replies.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserChatMessageReplyDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgUserChatMessageReplyDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ChatId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, ParameterSetName = "DeltaSync")] public string ChatMessageId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -64,11 +79,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Users.Item.Chats.Item.Messages.Item.Replies.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].Chats[ChatId].Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].Chats[ChatId].Messages[ChatMessageId].Replies.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].Chats[ChatId].Messages[ChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -96,14 +121,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].Chats[ChatId].Messages[ChatMessageId].Replies.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, ChatMessageId); return; } - - WriteObject(result); } } } diff --git a/src/Teams/wrapper/v1.0/Cmdlets/GetMgUserChatTargetedMessageReplyDelta.g.cs b/src/Teams/wrapper/v1.0/Cmdlets/GetMgUserChatTargetedMessageReplyDelta.g.cs index 664457921b8..4e9528ed3ee 100644 --- a/src/Teams/wrapper/v1.0/Cmdlets/GetMgUserChatTargetedMessageReplyDelta.g.cs +++ b/src/Teams/wrapper/v1.0/Cmdlets/GetMgUserChatTargetedMessageReplyDelta.g.cs @@ -14,47 +14,62 @@ namespace Microsoft.Graph.PowerShell.Teams { [GraphRoute("GET", "/users/{user-id}/chats/{chat-id}/targetedMessages/{targetedChatMessage-id}/replies/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserChatTargetedMessageReplyDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Teams.Client.Users.Item.Chats.Item.TargetedMessages.Item.Replies.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserChatTargetedMessageReplyDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Teams.Client.Models.ChatMessage))] public class GetMgUserChatTargetedMessageReplyDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string ChatId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 2)] + [Parameter(Mandatory = true, Position = 2, ParameterSetName = "DeltaSync")] public string TargetedChatMessageId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -64,11 +79,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Teams.Client.Users.Item.Chats.Item.TargetedMessages.Item.Replies.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].Chats[ChatId].TargetedMessages[TargetedChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].Chats[ChatId].TargetedMessages[TargetedChatMessageId].Replies.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].Chats[ChatId].TargetedMessages[TargetedChatMessageId].Replies.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -96,14 +121,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].Chats[ChatId].TargetedMessages[TargetedChatMessageId].Replies.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, TargetedChatMessageId); return; } - - WriteObject(result); } } } diff --git a/src/Users.Functions/wrapper/v1.0/Cmdlets/GetMgUserDelta.g.cs b/src/Users.Functions/wrapper/v1.0/Cmdlets/GetMgUserDelta.g.cs index deed3843cea..9dfd0fee3a3 100644 --- a/src/Users.Functions/wrapper/v1.0/Cmdlets/GetMgUserDelta.g.cs +++ b/src/Users.Functions/wrapper/v1.0/Cmdlets/GetMgUserDelta.g.cs @@ -14,42 +14,57 @@ namespace Microsoft.Graph.PowerShell.Users.Functions { [GraphRoute("GET", "/users/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Users.Functions.Client.Users.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Users.Functions.Client.Models.User))] public class GetMgUserDeltaCommand : GraphClientCmdlet { - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -59,11 +74,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Users.Functions.Client.Users.Delta.DeltaGetResponse? result; try { - result = client.Users.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -91,14 +116,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, null); return; } - - WriteObject(result); } } } diff --git a/src/Users/wrapper/v1.0/Cmdlets/GetMgUserTodoListDelta.g.cs b/src/Users/wrapper/v1.0/Cmdlets/GetMgUserTodoListDelta.g.cs index 172c4083a61..10818a409df 100644 --- a/src/Users/wrapper/v1.0/Cmdlets/GetMgUserTodoListDelta.g.cs +++ b/src/Users/wrapper/v1.0/Cmdlets/GetMgUserTodoListDelta.g.cs @@ -14,43 +14,58 @@ namespace Microsoft.Graph.PowerShell.Users { [GraphRoute("GET", "/users/{user-id}/todo/lists/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserTodoListDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Users.Client.Users.Item.Todo.Lists.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserTodoListDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Users.Client.Models.TodoTaskList))] public class GetMgUserTodoListDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -60,11 +75,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Users.Client.Users.Item.Todo.Lists.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].Todo.Lists.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].Todo.Lists.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].Todo.Lists.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -92,14 +117,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].Todo.Lists.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, UserId); return; } - - WriteObject(result); } } } diff --git a/src/Users/wrapper/v1.0/Cmdlets/GetMgUserTodoTaskDelta.g.cs b/src/Users/wrapper/v1.0/Cmdlets/GetMgUserTodoTaskDelta.g.cs index 791b749ad14..43b5d52bd24 100644 --- a/src/Users/wrapper/v1.0/Cmdlets/GetMgUserTodoTaskDelta.g.cs +++ b/src/Users/wrapper/v1.0/Cmdlets/GetMgUserTodoTaskDelta.g.cs @@ -14,45 +14,60 @@ namespace Microsoft.Graph.PowerShell.Users { [GraphRoute("GET", "/users/{user-id}/todo/lists/{todoTaskList-id}/tasks/delta()")] - [Cmdlet(VerbsCommon.Get, "MgUserTodoTaskDelta")] - [OutputType(typeof(global::Microsoft.Graph.PowerShell.Users.Client.Users.Item.Todo.Lists.Item.Tasks.Delta.DeltaGetResponse))] + [Cmdlet(VerbsCommon.Get, "MgUserTodoTaskDelta", DefaultParameterSetName = "DeltaSync")] + [OutputType(typeof(Microsoft.Graph.PowerShell.Users.Client.Models.TodoTask))] public class GetMgUserTodoTaskDeltaCommand : GraphClientCmdlet { - [Parameter(Mandatory = true, Position = 0)] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "DeltaSync")] public string UserId { get; set; } = string.Empty; - [Parameter(Mandatory = true, Position = 1)] + [Parameter(Mandatory = true, Position = 1, ParameterSetName = "DeltaSync")] public string TodoTaskListId { get; set; } = string.Empty; - - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Filter { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Select")] public string[]? Property { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("Expand")] public string[]? ExpandProperty { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] [Alias("OrderBy")] public string[]? Sort { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public string? Search { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Top { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public int Skip { get; set; } - [Parameter(Mandatory = false)] + [Parameter(Mandatory = false, ParameterSetName = "DeltaSync")] public SwitchParameter Count { get; set; } + // Resumes a previous sync from the link that run published. Universal: every delta + // request builder accepts a raw URL, whereas a token argument exists on only a few. + [Parameter(Mandatory = true, ParameterSetName = "Resume")] + public string DeltaLink { get; set; } = string.Empty; + + // Follows @odata.nextLink through the change set. Without it only the first page returns, + // plus a warning when more pages exist. + [Parameter(Mandatory = false)] + public SwitchParameter All { get; set; } + + // Receives the @odata.deltaLink that terminates the change set, for the next sync round. + // A named variable is how this SDK already returns a scalar alongside a pipeline + // (-CountVariable on the published list cmdlets). + [Parameter(Mandatory = false)] + [Alias("DLV")] + public string? DeltaLinkVariable { get; set; } @@ -62,11 +77,21 @@ protected override void ProcessRecord() var requestAdapter = GetRequestAdapter(); var client = new ApiClient(requestAdapter); + // Cleared before the request so a failed or interrupted run cannot leave the previous + // run's link readable, which would silently resume from the wrong point. + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, null); global::Microsoft.Graph.PowerShell.Users.Client.Users.Item.Todo.Lists.Item.Tasks.Delta.DeltaGetResponse? result; try { - result = client.Users[UserId].Todo.Lists[TodoTaskListId].Tasks.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => + result = ParameterSetName == "Resume" + ? client.Users[UserId].Todo.Lists[TodoTaskListId].Tasks.Delta.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult() + : client.Users[UserId].Todo.Lists[TodoTaskListId].Tasks.Delta.GetAsDeltaGetResponseAsync(requestConfiguration => { if (this.IsParameterBound(nameof(Filter))) requestConfiguration.QueryParameters.Filter = Filter; @@ -94,14 +119,61 @@ protected override void ProcessRecord() AddRequestHeaders(requestConfiguration.Headers); }).GetAwaiter().GetResult(); + + var fetched = 0; + while (true) + { + if (result?.Value is { } items) + { + WriteObject(items, enumerateCollection: true); + fetched += items.Count; + } + + var nextLink = result?.OdataNextLink; + var deltaLink = result?.OdataDeltaLink; + + // A response cannot be both continued and terminated; treating one as + // authoritative would silently drop pages or resume from a partial set. + if (!string.IsNullOrEmpty(nextLink) && !string.IsNullOrEmpty(deltaLink)) + { + ThrowTerminatingError(new ErrorRecord( + new InvalidOperationException("The response carries both @odata.nextLink and @odata.deltaLink, which is not a valid delta response."), + "InvalidDeltaResponse", ErrorCategory.InvalidData, targetObject: null)); + return; + } + + if (!string.IsNullOrEmpty(deltaLink)) + { + if (this.IsParameterBound(nameof(DeltaLinkVariable))) + SessionState.PSVariable.Set(DeltaLinkVariable, deltaLink); + break; + } + + // No link of either kind: the change set ends here and there is nothing to + // publish for a next round. + if (string.IsNullOrEmpty(nextLink)) break; + + if (!All.IsPresent) + { + WriteWarning("More results are available. Use -All to return all pages."); + break; + } + + if (Stopping) break; + if (this.IsParameterBound(nameof(Top)) && fetched >= Top) break; + + result = client.Users[UserId].Todo.Lists[TodoTaskListId].Tasks.Delta.WithUrl(nextLink).GetAsDeltaGetResponseAsync(requestConfiguration => + { + + AddRequestHeaders(requestConfiguration.Headers); + }).GetAwaiter().GetResult(); + } } catch (Exception ex) when (ex is not PipelineStoppedException) { ThrowGraphRequestFailed(ex, TodoTaskListId); return; } - - WriteObject(result); } } } diff --git a/tools/WrapperGenerator/CmdletEmitter.cs b/tools/WrapperGenerator/CmdletEmitter.cs index ed076a9211c..d906d6f0aa1 100644 --- a/tools/WrapperGenerator/CmdletEmitter.cs +++ b/tools/WrapperGenerator/CmdletEmitter.cs @@ -403,7 +403,9 @@ protected override void ProcessRecord() // Resume parameter set, reached through -DeltaLink, which works for every delta route // rather than only the five whose spec declares a token argument. // Contract and evidence: docs/edge-cases/delta-edge-cases.md. - public static string EmitDelta(CmdletNaming naming, EmitContext ctx, CallPlan call, IReadOnlySet queryParamNames) + // itemTypeName is the model the cmdlet actually writes; it falls back to the response type only + // when the item cannot be resolved, so OutputType is never left describing nothing. + public static string EmitDelta(CmdletNaming naming, EmitContext ctx, CallPlan call, IReadOnlySet queryParamNames, string? itemTypeName = null) { ArgumentNullException.ThrowIfNull(naming); ArgumentNullException.ThrowIfNull(ctx); @@ -445,7 +447,7 @@ namespace {{ctx.CmdletNamespace}} { {{RouteAttr(naming)}} [Cmdlet({{naming.VerbsClass}}.{{naming.VerbName}}, "{{EscapeLiteral(naming.Noun)}}", DefaultParameterSetName = "DeltaSync")] - [OutputType(typeof({{call.ReturnTypeName}}))] + [OutputType(typeof({{itemTypeName ?? call.ReturnTypeName}}))] public class {{naming.ClassName}} : GraphClientCmdlet { {{PathParams(naming, "DeltaSync")}} @@ -486,7 +488,7 @@ protected override void ProcessRecord() try { result = ParameterSetName == "Resume" - ? {{receiver}}.WithUrl(DeltaLink).{{call.MethodName}}(requestConfiguration => + ? {{receiver}}.WithUrl(ValidateContinuationUrl(DeltaLink!, requestAdapter, nameof(DeltaLink))).{{call.MethodName}}(requestConfiguration => {{{continuationConfig}} }).GetAwaiter().GetResult() : {{EmitCallOn(receiver, naming, call.MethodName, null, queryBindings)}}.GetAwaiter().GetResult(); diff --git a/tools/WrapperGenerator/PowerShellWrapperGenerationService.cs b/tools/WrapperGenerator/PowerShellWrapperGenerationService.cs index c3d9e71e6d3..f869c59994b 100644 --- a/tools/WrapperGenerator/PowerShellWrapperGenerationService.cs +++ b/tools/WrapperGenerator/PowerShellWrapperGenerationService.cs @@ -398,9 +398,21 @@ private async Task EmitDeltaOperationsAsync(List delt continue; } + // A delta cmdlet writes the envelope's items to the pipeline and never the envelope + // itself, so OutputType has to name the item model. Advertising the response type + // describes an object the cmdlet never emits, which is what help, IntelliSense and + // anything reading OutputType would then report. + string? deltaItemType = null; + if (TryGetSuccessJsonSchema(op.Operation) is { } deltaResponseSchema + && FindProperty(deltaResponseSchema, "value") is { } deltaValueSchema + && TryResolveListEntityTypeName(deltaValueSchema, ctx.ModelsNamespace, out var resolvedItemType)) + { + deltaItemType = resolvedItemType; + } + var source = CmdletEmitter.EmitDelta(op.Naming, ctx, new CmdletEmitter.CallPlan(methodName, returnType, BodyTypeName: null, returnsStream), - op.QueryParams.ToHashSet()); + op.QueryParams.ToHashSet(), deltaItemType); written += await WriteCmdletFileAsync(op.Naming, source, cancellationToken).ConfigureAwait(false); }