From 5dd7ac9533111e11f192226a228744098dba64d4 Mon Sep 17 00:00:00 2001 From: Peter Ombwa Date: Mon, 11 May 2020 18:02:02 -0700 Subject: [PATCH 1/3] Add support for paging of collection. --- .../Microsoft.Graph.Users.User.psd1 | 11 +- src/Beta/Users.User/Users.User/readme.md | 2 +- src/readme.graph.md | 16 ++- tools/Custom/ListCmdlet.cs | 118 ++++++++++++++++++ 4 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 tools/Custom/ListCmdlet.cs diff --git a/src/Beta/Users.User/Users.User/Microsoft.Graph.Users.User.psd1 b/src/Beta/Users.User/Users.User/Microsoft.Graph.Users.User.psd1 index ead5246d439..d6abb46fa8e 100644 --- a/src/Beta/Users.User/Users.User/Microsoft.Graph.Users.User.psd1 +++ b/src/Beta/Users.User/Users.User/Microsoft.Graph.Users.User.psd1 @@ -3,7 +3,7 @@ # # Generated by: Microsoft Corporation # -# Generated on: 3/12/2020 +# Generated on: 5/11/2020 # @{ @@ -12,13 +12,13 @@ RootModule = './Microsoft.Graph.Users.User.psm1' # Version number of this module. -ModuleVersion = '0.2.0' +ModuleVersion = '0.7.0' # Supported PSEditions CompatiblePSEditions = 'Core', 'Desktop' # ID used to uniquely identify this module -GUID = '89a6467f-8fac-4642-bb9d-f5d784682c8b' +GUID = '38118a66-314c-4095-9b3c-72f38ad12480' # Author of this module Author = 'Microsoft Corporation' @@ -51,7 +51,7 @@ DotNetFrameworkVersion = '4.7.2' # ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module -RequiredModules = @('Microsoft.Graph.Authentication') +RequiredModules = @(@{ModuleName = 'Microsoft.Graph.Authentication'; RequiredVersion = '0.5.1'; }) # Assemblies that must be loaded prior to importing this module RequiredAssemblies = './bin/Microsoft.Graph.Users.User.private.dll' @@ -69,7 +69,8 @@ FormatsToProcess = './Microsoft.Graph.Users.User.format.ps1xml' # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. -FunctionsToExport = '*' +FunctionsToExport = 'Get-MgUser', 'Get-MgUserPresence', 'New-MgUser', 'Remove-MgUser', + 'Update-MgUser', 'Update-MgUserPresence' # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() diff --git a/src/Beta/Users.User/Users.User/readme.md b/src/Beta/Users.User/Users.User/readme.md index f83779b1fa9..761faec1931 100644 --- a/src/Beta/Users.User/Users.User/readme.md +++ b/src/Beta/Users.User/Users.User/readme.md @@ -34,6 +34,6 @@ input-file: $(spec-doc-repo)/$(title).yml ### Versioning ``` yaml -module-version: 0.5.1 +module-version: 0.7.0 release-notes: See https://aka.ms/GraphPowerShell-Release. ``` diff --git a/src/readme.graph.md b/src/readme.graph.md index 9a5451e343e..79d1fa4f324 100644 --- a/src/readme.graph.md +++ b/src/readme.graph.md @@ -81,7 +81,9 @@ directive: parameter-name: Top set: parameter-name: PageSize - alias: Top + alias: + - Top + - Limit - where: parameter-name: Select set: @@ -378,7 +380,7 @@ directive: } return $; } -# Temporarily disable paging. +# Add custom -All parameter to *_List cmdlets that support Odata next link. - from: source-file-csharp where: $ transform: > @@ -388,7 +390,15 @@ directive: } else { let odataNextLinkRegex = /(^\s*)(if\s*\(\s*result.OdataNextLink\s*!=\s*null\s*\))/gmi if($.match(odataNextLinkRegex)) { - $ = $.replace(odataNextLinkRegex, '$1result.OdataNextLink = null;\n$1if (result.OdataNextLink != null)$1'); + $ = $.replace(odataNextLinkRegex, '$1if (result.OdataNextLink != null && this.ShouldIteratePages(this.InvocationInformation.BoundParameters, result.Value.Length))\n$1'); + let psBaseClassImplementationRegex = /(\s*:\s*)(global::System.Management.Automation.PSCmdlet)/gmi + $ = $.replace(psBaseClassImplementationRegex, '$1Microsoft.Graph.PowerShell.Cmdlets.Custom.ListCmdlet'); + + let processRecordRegex = /(^\s*)(protected\s*override\s*void\s*BeginProcessing\(\)\s*{)/gmi + $ = $.replace(processRecordRegex, '$1$2\n$1$1if (this.InvocationInformation.BoundParameters.ContainsKey("PageSize")){ InitializePaging(ref this.__invocationInfo, ref this._pageSize); }\n$1'); + + let odataNextLinkCallRegex = /(^\s*)(await\s*this\.Client\.UsersUserListUser_Call\(requestMessage\,\s*onOk\,\s*onDefault\,\s*this\,\s*Pipeline\)\;)/gmi + $ = $.replace(odataNextLinkCallRegex, '$1requestMessage.RequestUri = GetOverflowItemsNextLinkUri(requestMessage.RequestUri);\n$1$2'); } return $; } diff --git a/tools/Custom/ListCmdlet.cs b/tools/Custom/ListCmdlet.cs new file mode 100644 index 00000000000..2fd7cdd448c --- /dev/null +++ b/tools/Custom/ListCmdlet.cs @@ -0,0 +1,118 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ +namespace Microsoft.Graph.PowerShell.Cmdlets.Custom +{ + + public partial class ListCmdlet : global::System.Management.Automation.PSCmdlet + { + /// Backing field for property. + private global::System.Management.Automation.SwitchParameter _all; + + /// List All pages + [global::System.Management.Automation.Parameter(Mandatory = false, HelpMessage = "List all pages")] + [Microsoft.Graph.PowerShell.Runtime.Info( + Required = false, + ReadOnly = false, + Description = @"List all pages", + SerializedName = @"$all", + PossibleTypes = new[] { typeof(global::System.Management.Automation.SwitchParameter) })] + [global::Microsoft.Graph.PowerShell.Category(global::Microsoft.Graph.PowerShell.ParameterCategory.Runtime)] + public global::System.Management.Automation.SwitchParameter All { get => this._all; set => this._all = value; } + + /// + /// Default number of items per page. + /// + internal const int DefaultPageSize = 100; + + /// + /// Maximum number of items per page. + /// + internal const int MaxPageSize = 999; + + /// + /// Original page size/top/limit passed to Cmdlet via parameter. + /// + internal int originalPageSize = 0; + + /// + /// Total number of pages required to iterate page collections excluding overflow items. + /// + internal int requiredPages = 0; + + /// + /// A count of iterated pages thus far. + /// + internal int iteratedPages = 0; + + /// + /// Total number of overflow items, less than the maximum number of items in a page. + /// Modulus of original page size. + /// + internal int overflowItemsCount = 0; + + /// + /// Total number of fetched items. + /// + internal int totalFetchedItems = 0; + + /// + /// Initializes paging values. + /// + /// A reference to object. + /// A reference to page size parameter. + public void InitializePaging(ref global::System.Management.Automation.InvocationInfo invocationInfo, ref int PageSize) + { + if (PageSize > MaxPageSize) + { + invocationInfo.BoundParameters["All"] = true; + originalPageSize = PageSize; + requiredPages = PageSize / DefaultPageSize; + overflowItemsCount = PageSize % DefaultPageSize; + invocationInfo.BoundParameters["PageSize"] = DefaultPageSize; + PageSize = DefaultPageSize; + } + } + + /// + /// Determines whether the cmdlet should follow the OData next link url. + /// + /// The bound parameters of the cmdlet. + /// Current page items count. + /// True if it can iterate pages; False if it can't. + public bool ShouldIteratePages(global::System.Collections.Generic.Dictionary boundParameters, int itemsCount) + { + iteratedPages++; + totalFetchedItems += itemsCount; + if ((boundParameters.ContainsKey("All") && !boundParameters.ContainsKey("PageSize")) || + (boundParameters.ContainsKey("PageSize") && totalFetchedItems < originalPageSize)) + return true; + else + return false; + } + + /// + /// Gets an OData next link for the overflow items. + /// + /// The OData next link returned by the service. + /// An OData next link URI for the overflow items. + public global::System.Uri GetOverflowItemsNextLinkUri(global::System.Uri requestUri) + { + var nextLinkUri = new global::System.UriBuilder(requestUri); + if (requiredPages == iteratedPages && overflowItemsCount > 0) + { + if (nextLinkUri.Query.Contains("$top")) + { + global::System.Collections.Specialized.NameValueCollection queryString = global::System.Web.HttpUtility.ParseQueryString(nextLinkUri.Query); + queryString["$top"] = global::System.Uri.EscapeDataString(overflowItemsCount.ToString()); + nextLinkUri.Query = queryString.ToString(); + } + else + { + nextLinkUri.Query += $"$top=" + global::System.Uri.EscapeDataString(overflowItemsCount.ToString()); + } + } + return nextLinkUri.Uri; + } + } +} \ No newline at end of file From 0c644ea0c8caeb8eb00172e7f4d3858d1974a270 Mon Sep 17 00:00:00 2001 From: Peter Ombwa Date: Mon, 11 May 2020 18:23:19 -0700 Subject: [PATCH 2/3] Use begin processing to initialize paging values. --- .../Users.User/Microsoft.Graph.Users.User.psd1 | 11 +++++------ src/readme.graph.md | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Beta/Users.User/Users.User/Microsoft.Graph.Users.User.psd1 b/src/Beta/Users.User/Users.User/Microsoft.Graph.Users.User.psd1 index d6abb46fa8e..3b23b9e0017 100644 --- a/src/Beta/Users.User/Users.User/Microsoft.Graph.Users.User.psd1 +++ b/src/Beta/Users.User/Users.User/Microsoft.Graph.Users.User.psd1 @@ -3,7 +3,7 @@ # # Generated by: Microsoft Corporation # -# Generated on: 5/11/2020 +# Generated on: 3/12/2020 # @{ @@ -12,13 +12,13 @@ RootModule = './Microsoft.Graph.Users.User.psm1' # Version number of this module. -ModuleVersion = '0.7.0' +ModuleVersion = '0.2.0' # Supported PSEditions CompatiblePSEditions = 'Core', 'Desktop' # ID used to uniquely identify this module -GUID = '38118a66-314c-4095-9b3c-72f38ad12480' +GUID = '89a6467f-8fac-4642-bb9d-f5d784682c8b' # Author of this module Author = 'Microsoft Corporation' @@ -51,7 +51,7 @@ DotNetFrameworkVersion = '4.7.2' # ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module -RequiredModules = @(@{ModuleName = 'Microsoft.Graph.Authentication'; RequiredVersion = '0.5.1'; }) +RequiredModules = @('Microsoft.Graph.Authentication') # Assemblies that must be loaded prior to importing this module RequiredAssemblies = './bin/Microsoft.Graph.Users.User.private.dll' @@ -69,8 +69,7 @@ FormatsToProcess = './Microsoft.Graph.Users.User.format.ps1xml' # NestedModules = @() # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. -FunctionsToExport = 'Get-MgUser', 'Get-MgUserPresence', 'New-MgUser', 'Remove-MgUser', - 'Update-MgUser', 'Update-MgUserPresence' +FunctionsToExport = '*' # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. CmdletsToExport = @() diff --git a/src/readme.graph.md b/src/readme.graph.md index 79d1fa4f324..f8893b802a4 100644 --- a/src/readme.graph.md +++ b/src/readme.graph.md @@ -394,8 +394,8 @@ directive: let psBaseClassImplementationRegex = /(\s*:\s*)(global::System.Management.Automation.PSCmdlet)/gmi $ = $.replace(psBaseClassImplementationRegex, '$1Microsoft.Graph.PowerShell.Cmdlets.Custom.ListCmdlet'); - let processRecordRegex = /(^\s*)(protected\s*override\s*void\s*BeginProcessing\(\)\s*{)/gmi - $ = $.replace(processRecordRegex, '$1$2\n$1$1if (this.InvocationInformation.BoundParameters.ContainsKey("PageSize")){ InitializePaging(ref this.__invocationInfo, ref this._pageSize); }\n$1'); + let beginProcessingRegex = /(^\s*)(protected\s*override\s*void\s*BeginProcessing\(\)\s*{)/gmi + $ = $.replace(beginProcessingRegex, '$1$2\n$1$1if (this.InvocationInformation.BoundParameters.ContainsKey("PageSize")){ InitializePaging(ref this.__invocationInfo, ref this._pageSize); }\n$1'); let odataNextLinkCallRegex = /(^\s*)(await\s*this\.Client\.UsersUserListUser_Call\(requestMessage\,\s*onOk\,\s*onDefault\,\s*this\,\s*Pipeline\)\;)/gmi $ = $.replace(odataNextLinkCallRegex, '$1requestMessage.RequestUri = GetOverflowItemsNextLinkUri(requestMessage.RequestUri);\n$1$2'); From ebb811e020ad8040aef4839f69f9d1114dbaadbc Mon Sep 17 00:00:00 2001 From: Peter Ombwa Date: Mon, 11 May 2020 18:25:04 -0700 Subject: [PATCH 3/3] Update Microsoft.Graph.Users.User.psd1 --- src/Beta/Users.User/Users.User/Microsoft.Graph.Users.User.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Beta/Users.User/Users.User/Microsoft.Graph.Users.User.psd1 b/src/Beta/Users.User/Users.User/Microsoft.Graph.Users.User.psd1 index 3b23b9e0017..ead5246d439 100644 --- a/src/Beta/Users.User/Users.User/Microsoft.Graph.Users.User.psd1 +++ b/src/Beta/Users.User/Users.User/Microsoft.Graph.Users.User.psd1 @@ -51,7 +51,7 @@ DotNetFrameworkVersion = '4.7.2' # ProcessorArchitecture = '' # Modules that must be imported into the global environment prior to importing this module -RequiredModules = @('Microsoft.Graph.Authentication') +RequiredModules = @('Microsoft.Graph.Authentication') # Assemblies that must be loaded prior to importing this module RequiredAssemblies = './bin/Microsoft.Graph.Users.User.private.dll'