diff --git a/Binaries/SharePointPnP.Modernization.Framework.dll b/Binaries/SharePointPnP.Modernization.Framework.dll index 6e8398b08..b4a8e35e8 100644 Binary files a/Binaries/SharePointPnP.Modernization.Framework.dll and b/Binaries/SharePointPnP.Modernization.Framework.dll differ diff --git a/Binaries/SharePointPnP.Modernization.Framework.xml b/Binaries/SharePointPnP.Modernization.Framework.xml index af3fc29eb..999ccd0f6 100644 --- a/Binaries/SharePointPnP.Modernization.Framework.xml +++ b/Binaries/SharePointPnP.Modernization.Framework.xml @@ -4,6 +4,384 @@ SharePointPnP.Modernization.Framework + + + MemoryDistributedCache options class + + + MemoryDistributedCache options class + + + + + Prefix value that will be prepended to the provided key value + + + + + Default cache entry configuration, will be used to save items to the cache + + + + + Returns the key value to use by the caching system, in this case this will mean prepending the KeyPrefix + + Provided key + Key to use by the caching system + + + + Extensions methods to make it easier to work with the distributed cache + + + + + Converts an object into a bytearray + + Object to return as byte array + byte array + + + + Converts a byte array to an object + + Type of the object to return + Byte array + Object + + + + Sets an object of type T in connected cache system + + Type of the object to cache + Connected cache system + Key of the object in the cache + Value to be cached + Caching options + Cancellation token + + + + + Sets an object of type T in connected cache system + + Type of the object to cache + Connected cache system + Key of the object in the cache + Value to be cached + Caching options + + + + Gets an object from the connected cache system + + Type of the object to return from cache + Connected cache system + Key of the object in the cache + Object of the type T + + + + Gets an object from the connected cache system + + Type of the object to return from cache + Connected cache system + Key of the object in the cache + Object of the type T + + + + Gets an object from the connected cache system. If not cached the object will be created + + Type of the object to return from cache + Connected cache system + Key of the object in the cache + Object of the type T + + + + Interface to be implemented by each cache options implementation + + + + + Prefix value that will be prepended to the provided key value + + + + + Returns the key value to use by the caching system, typically this will mean prepending the KeyPrefix + + Provided key + Key to use by the caching system + + + + Default cache entry configuration, will be used to save items to the cache + + + + + Caching manager, singleton + Important: don't cache SharePoint Client objects as these are tied to a specific client context and hence will fail when there's context switching! + + + + + Get's the single cachemanager instance, singleton pattern + + + + + Get's the cached SharePoint version for a given site + + Site to get the SharePoint version for + Found SharePoint version or "Unknown" if not found in cache + + + + Sets the SharePoint version in cache + + Site to the set the SharePoint version for + SharePoint version of the site + + + + Get's the exact SharePoint version from cache + + Site to get the exact version for + Exact version from cache + + + + Adds exact SharePoint version for a given site to cache + + Site to add the SharePoint version for to cache + Version to add + + + + Returns the used AzureAD tenant id + + Url of the site + Azure AD tenant id + + + + Sets the Azure AD tenant Id in cache + + Tenant Id + Site url + + + + Get's the clientside components from cache or if needed retrieves and caches them + + Page to grab the components for + + + + + Clear the clientside component cache + + + + + Get's the base template that will be used to filter out "OOB" fields + + web to operate against + Provisioning template of the base template of STS#0 + + + + Clear base template cache + + + + + Get the list of fields that need to be copied from cache. If cache is empty the list will be calculated + + Web to operate against + Pages library instance + List of fields that need to be copied + + + + Get field information of a content type field + + Pages library list + ID of the content type + Name of the field to get information from + FieldData object holding field information + + + + Clear the fields to copy cache + + + + + Marks this web as a publishing web + + Url of the web + + + + Marks this web as a blog web + + Url of the web + + + + Checks if this is publishing web + + Web url to check + True if publishing, false otherwise + + + + Checks if this is blog web + + Web url to check + True if blog, false otherwise + + + + Get translation for the publishing pages library + + Context of the site + Translated name of the pages library + + + + Get translation for the blog list name + + Context of the site + Translated name of the blog list + + + + Returns the translated value for a resource string + + Context of the site + Key of the resource (e.g. $Resources:core,ScriptEditorWebPartDescription;) + Translated string + + + + Generate pagelayout mapping file for given publishing page + + Publishing page + Page layout mapping model + + + + Clear all the caches + + + + + Clears Cached SharePoint versions + + + + + Clears the cache of generated page layout mappings + + + + + Mapped users + + A dictionary of mapped users + + + + Adds a user to the dictionary of mapped users + + Principal to map + mapped user + + + + Run and cache the output value of EnsureUser for a given user + + ClientContext to operate on + User name of user to ensure + ResolvedUser instance holding information about the ensured user + + + + Lookup a user from the site's user list based upon the user's upn + + Context of the web holding the user list + Upn of the user to fetch + A UserEntity instance holding information about the user + + + + Lookup a user from the site's user list based upon the user's id + + Context of the web holding the user list + Id of the user to fetch + A UserEntity instance holding information about the user + + + + Get's the ID of a contenttype + + Pages library holding the content type + Name of the content type + ID of the content type + + + + Caches the last used page transformator instance, needed to postpone log writing when transforming multiple pages + + + + + + Gets the last used page transformator instance + + + + + + Returns a list of url mappings + + File with url mappings + Attached list of log observers + List of url mappings + + + + Gets the list of user mappings, if first time file will be laoded + + File with the user mappings + Attached list of log observers + List of user mappings + + + + Field data used to transfer information about a field + + + + + Internal name of the field + + + + + Id of the field + + + + + Type of the field + + Information to initiate the transformation of a Delve blog page @@ -769,6 +1147,13 @@ Server Relative Page Url + + + Uses the WebPartPages.asmx service to retrieve the page contents, needed to find ZoneId for SP2010 based v3 web parts + + Page to load + The found page + Call SharePoint Web Services to extract web part properties not exposed by CSOM @@ -1124,6 +1509,12 @@ Information about the publishing page to transform The path to the created modern page + + + Loads the default page layout mapping model file + + + Use reflection to read the object properties and detail the values @@ -2599,6 +2990,18 @@ Base page transformator class that contains logic that applies for all page transformations + + + Loads the default webpart mapping model + + + + + + Loads the default webpart mapping model file + + + Gets the version of the assembly @@ -3662,238 +4065,6 @@ List of web parts on the page Updated list of web parts - - - Caching manager, singleton - Important: don't cache SharePoint Client objects as these are tied to a specific client context and hence will fail when there's context switching! - - - - - Get's the single cachemanager instance, singleton pattern - - - - - List of URLs and SharePoint Versions - - - - - List of URLs and Exact SharePoint Versions - - - - - AADTenantID's used - - - - - List of assets transferred from source to destination - - - - - Get's the clientside components from cache or if needed retrieves and caches them - - Page to grab the components for - - - - - Clear the clientside component cache - - - - - Get's the base template that will be used to filter out "OOB" fields - - web to operate against - Provisioning template of the base template of STS#0 - - - - Clear base template cache - - - - - Get the list of fields that need to be copied from cache. If cache is empty the list will be calculated - - Web to operate against - Pages library instance - List of fields that need to be copied - - - - Get field information of a content type field - - Pages library list - ID of the content type - Name of the field to get information from - FieldData object holding field information - - - - Clear the fields to copy cache - - - - - Marks this web as a publishing web - - Url of the web - - - - Marks this web as a blog web - - Url of the web - - - - Checks if this is publishing web - - Web url to check - True if publishing, false otherwise - - - - Checks if this is blog web - - Web url to check - True if blog, false otherwise - - - - Get translation for the publishing pages library - - Context of the site - Translated name of the pages library - - - - Get translation for the blog list name - - Context of the site - Translated name of the blog list - - - - Returns the translated value for a resource string - - Context of the site - Key of the resource (e.g. $Resources:core,ScriptEditorWebPartDescription;) - Translated string - - - - Generate pagelayout mapping file for given publishing page - - Publishing page - Page layout mapping model - - - - Clear all the caches - - - - - Clears Cached SharePoint versions - - - - - Clears the cache of generated page layout mappings - - - - - Mapped users - - - - - Run and cache the output value of EnsureUser for a given user - - ClientContext to operate on - User name of user to ensure - ResolvedUser instance holding information about the ensured user - - - - Lookup a user from the site's user list based upon the user's upn - - Context of the web holding the user list - Upn of the user to fetch - A UserEntity instance holding information about the user - - - - Lookup a user from the site's user list based upon the user's id - - Context of the web holding the user list - Id of the user to fetch - A UserEntity instance holding information about the user - - - - Get's the ID of a contenttype - - Pages library holding the content type - Name of the content type - ID of the content type - - - - Caches the last used page transformator instance, needed to postpone log writing when transforming multiple pages - - - - - - Gets the last used page transformator instance - - - - - - Returns a list of url mappings - - File with url mappings - Attached list of log observers - List of url mappings - - - - Gets the list of user mappings, if first time file will be laoded - - File with the user mappings - Attached list of log observers - List of user mappings - - - - Field data used to transfer information about a field - - - - - Internal name of the field - - - - - Id of the field - - - - - Type of the field - - Constants used diff --git a/Binaries/release/SharePointPnP.Modernization.Framework.dll b/Binaries/release/SharePointPnP.Modernization.Framework.dll index 495261a3c..fdc42c017 100644 Binary files a/Binaries/release/SharePointPnP.Modernization.Framework.dll and b/Binaries/release/SharePointPnP.Modernization.Framework.dll differ diff --git a/Binaries/release/SharePointPnP.Modernization.Framework.xml b/Binaries/release/SharePointPnP.Modernization.Framework.xml index af3fc29eb..999ccd0f6 100644 --- a/Binaries/release/SharePointPnP.Modernization.Framework.xml +++ b/Binaries/release/SharePointPnP.Modernization.Framework.xml @@ -4,6 +4,384 @@ SharePointPnP.Modernization.Framework + + + MemoryDistributedCache options class + + + MemoryDistributedCache options class + + + + + Prefix value that will be prepended to the provided key value + + + + + Default cache entry configuration, will be used to save items to the cache + + + + + Returns the key value to use by the caching system, in this case this will mean prepending the KeyPrefix + + Provided key + Key to use by the caching system + + + + Extensions methods to make it easier to work with the distributed cache + + + + + Converts an object into a bytearray + + Object to return as byte array + byte array + + + + Converts a byte array to an object + + Type of the object to return + Byte array + Object + + + + Sets an object of type T in connected cache system + + Type of the object to cache + Connected cache system + Key of the object in the cache + Value to be cached + Caching options + Cancellation token + + + + + Sets an object of type T in connected cache system + + Type of the object to cache + Connected cache system + Key of the object in the cache + Value to be cached + Caching options + + + + Gets an object from the connected cache system + + Type of the object to return from cache + Connected cache system + Key of the object in the cache + Object of the type T + + + + Gets an object from the connected cache system + + Type of the object to return from cache + Connected cache system + Key of the object in the cache + Object of the type T + + + + Gets an object from the connected cache system. If not cached the object will be created + + Type of the object to return from cache + Connected cache system + Key of the object in the cache + Object of the type T + + + + Interface to be implemented by each cache options implementation + + + + + Prefix value that will be prepended to the provided key value + + + + + Returns the key value to use by the caching system, typically this will mean prepending the KeyPrefix + + Provided key + Key to use by the caching system + + + + Default cache entry configuration, will be used to save items to the cache + + + + + Caching manager, singleton + Important: don't cache SharePoint Client objects as these are tied to a specific client context and hence will fail when there's context switching! + + + + + Get's the single cachemanager instance, singleton pattern + + + + + Get's the cached SharePoint version for a given site + + Site to get the SharePoint version for + Found SharePoint version or "Unknown" if not found in cache + + + + Sets the SharePoint version in cache + + Site to the set the SharePoint version for + SharePoint version of the site + + + + Get's the exact SharePoint version from cache + + Site to get the exact version for + Exact version from cache + + + + Adds exact SharePoint version for a given site to cache + + Site to add the SharePoint version for to cache + Version to add + + + + Returns the used AzureAD tenant id + + Url of the site + Azure AD tenant id + + + + Sets the Azure AD tenant Id in cache + + Tenant Id + Site url + + + + Get's the clientside components from cache or if needed retrieves and caches them + + Page to grab the components for + + + + + Clear the clientside component cache + + + + + Get's the base template that will be used to filter out "OOB" fields + + web to operate against + Provisioning template of the base template of STS#0 + + + + Clear base template cache + + + + + Get the list of fields that need to be copied from cache. If cache is empty the list will be calculated + + Web to operate against + Pages library instance + List of fields that need to be copied + + + + Get field information of a content type field + + Pages library list + ID of the content type + Name of the field to get information from + FieldData object holding field information + + + + Clear the fields to copy cache + + + + + Marks this web as a publishing web + + Url of the web + + + + Marks this web as a blog web + + Url of the web + + + + Checks if this is publishing web + + Web url to check + True if publishing, false otherwise + + + + Checks if this is blog web + + Web url to check + True if blog, false otherwise + + + + Get translation for the publishing pages library + + Context of the site + Translated name of the pages library + + + + Get translation for the blog list name + + Context of the site + Translated name of the blog list + + + + Returns the translated value for a resource string + + Context of the site + Key of the resource (e.g. $Resources:core,ScriptEditorWebPartDescription;) + Translated string + + + + Generate pagelayout mapping file for given publishing page + + Publishing page + Page layout mapping model + + + + Clear all the caches + + + + + Clears Cached SharePoint versions + + + + + Clears the cache of generated page layout mappings + + + + + Mapped users + + A dictionary of mapped users + + + + Adds a user to the dictionary of mapped users + + Principal to map + mapped user + + + + Run and cache the output value of EnsureUser for a given user + + ClientContext to operate on + User name of user to ensure + ResolvedUser instance holding information about the ensured user + + + + Lookup a user from the site's user list based upon the user's upn + + Context of the web holding the user list + Upn of the user to fetch + A UserEntity instance holding information about the user + + + + Lookup a user from the site's user list based upon the user's id + + Context of the web holding the user list + Id of the user to fetch + A UserEntity instance holding information about the user + + + + Get's the ID of a contenttype + + Pages library holding the content type + Name of the content type + ID of the content type + + + + Caches the last used page transformator instance, needed to postpone log writing when transforming multiple pages + + + + + + Gets the last used page transformator instance + + + + + + Returns a list of url mappings + + File with url mappings + Attached list of log observers + List of url mappings + + + + Gets the list of user mappings, if first time file will be laoded + + File with the user mappings + Attached list of log observers + List of user mappings + + + + Field data used to transfer information about a field + + + + + Internal name of the field + + + + + Id of the field + + + + + Type of the field + + Information to initiate the transformation of a Delve blog page @@ -769,6 +1147,13 @@ Server Relative Page Url + + + Uses the WebPartPages.asmx service to retrieve the page contents, needed to find ZoneId for SP2010 based v3 web parts + + Page to load + The found page + Call SharePoint Web Services to extract web part properties not exposed by CSOM @@ -1124,6 +1509,12 @@ Information about the publishing page to transform The path to the created modern page + + + Loads the default page layout mapping model file + + + Use reflection to read the object properties and detail the values @@ -2599,6 +2990,18 @@ Base page transformator class that contains logic that applies for all page transformations + + + Loads the default webpart mapping model + + + + + + Loads the default webpart mapping model file + + + Gets the version of the assembly @@ -3662,238 +4065,6 @@ List of web parts on the page Updated list of web parts - - - Caching manager, singleton - Important: don't cache SharePoint Client objects as these are tied to a specific client context and hence will fail when there's context switching! - - - - - Get's the single cachemanager instance, singleton pattern - - - - - List of URLs and SharePoint Versions - - - - - List of URLs and Exact SharePoint Versions - - - - - AADTenantID's used - - - - - List of assets transferred from source to destination - - - - - Get's the clientside components from cache or if needed retrieves and caches them - - Page to grab the components for - - - - - Clear the clientside component cache - - - - - Get's the base template that will be used to filter out "OOB" fields - - web to operate against - Provisioning template of the base template of STS#0 - - - - Clear base template cache - - - - - Get the list of fields that need to be copied from cache. If cache is empty the list will be calculated - - Web to operate against - Pages library instance - List of fields that need to be copied - - - - Get field information of a content type field - - Pages library list - ID of the content type - Name of the field to get information from - FieldData object holding field information - - - - Clear the fields to copy cache - - - - - Marks this web as a publishing web - - Url of the web - - - - Marks this web as a blog web - - Url of the web - - - - Checks if this is publishing web - - Web url to check - True if publishing, false otherwise - - - - Checks if this is blog web - - Web url to check - True if blog, false otherwise - - - - Get translation for the publishing pages library - - Context of the site - Translated name of the pages library - - - - Get translation for the blog list name - - Context of the site - Translated name of the blog list - - - - Returns the translated value for a resource string - - Context of the site - Key of the resource (e.g. $Resources:core,ScriptEditorWebPartDescription;) - Translated string - - - - Generate pagelayout mapping file for given publishing page - - Publishing page - Page layout mapping model - - - - Clear all the caches - - - - - Clears Cached SharePoint versions - - - - - Clears the cache of generated page layout mappings - - - - - Mapped users - - - - - Run and cache the output value of EnsureUser for a given user - - ClientContext to operate on - User name of user to ensure - ResolvedUser instance holding information about the ensured user - - - - Lookup a user from the site's user list based upon the user's upn - - Context of the web holding the user list - Upn of the user to fetch - A UserEntity instance holding information about the user - - - - Lookup a user from the site's user list based upon the user's id - - Context of the web holding the user list - Id of the user to fetch - A UserEntity instance holding information about the user - - - - Get's the ID of a contenttype - - Pages library holding the content type - Name of the content type - ID of the content type - - - - Caches the last used page transformator instance, needed to postpone log writing when transforming multiple pages - - - - - - Gets the last used page transformator instance - - - - - - Returns a list of url mappings - - File with url mappings - Attached list of log observers - List of url mappings - - - - Gets the list of user mappings, if first time file will be laoded - - File with the user mappings - Attached list of log observers - List of user mappings - - - - Field data used to transfer information about a field - - - - - Internal name of the field - - - - - Id of the field - - - - - Type of the field - - Constants used diff --git a/CHANGELOG.md b/CHANGELOG.md index 078e37633..dedc39532 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,37 +5,49 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). -## [3.17.2001.0] (not yet released) +## [3.17.2001.0] ### Added -- Add/Remove/Set/Get-PnPApplicationCustomizer commands to allow working with SharePoint Framework Application Customizer Extensions [PR2312](https://github.com/SharePoint/PnP-PowerShell/pull/2312) -- Ability to pipe in a result from Get-PnPFolder to Get-PnPFolderItem using the -Identity parameter [PR2279](https://github.com/SharePoint/PnP-PowerShell/pull/2279) -- Added ability to pipe Get-PnPUnifiedGroup to Get-PnPUnifiedGroupOwners and Get-PnPUnifiedGroupMembers [PR2208](https://github.com/SharePoint/PnP-PowerShell/pull/2208) -- Added permissions required for each of the \*-PnPUnifiedGroup\* commands in the Azure Active Directory App Registration to the help text of the commands [PR2205](https://github.com/SharePoint/PnP-PowerShell/pull/2205) -- Added option to use Connect-PnPOnline with a base64 encoded private key for use in i.e. PnP PowerShell within an Azure Function v1 and an option to provide a certificate reference for use in i.e. Azure Function v2 [PR2201](https://github.com/SharePoint/PnP-PowerShell/pull/2201) -- Added option to use Connect-PnPOnline with a public key certificate for use in i.e. Azure Runbooks [PR2292](https://github.com/SharePoint/PnP-PowerShell/pull/2292) -- Added option -RowLimit to Get-PnPRecycleBinItem to avoid getting throttled on full recycle bins [PR2393](https://github.com/SharePoint/PnP-PowerShell/pull/2393) -- Added `-WriteToConsole` option to `Set-PnPTraceLog` to allow writing trace listener output from the PnP Templating commands to both the console and to a file. Doesn't work for .NET Core. [PR2161](https://github.com/SharePoint/PnP-PowerShell/pull/2161) -- Added `Reset-PnPLabel` command to allow removal of an Office 365 Retention Label from a list [PR2233](https://github.com/SharePoint/PnP-PowerShell/pull/2233) +- Add/Remove/Set/Get-PnPApplicationCustomizer commands to allow working with SharePoint Framework Application Customizer Extensions +- Ability to pipe in a result from Get-PnPFolder to Get-PnPFolderItem using the -Identity parameter +- Added ability to pipe Get-PnPUnifiedGroup to Get-PnPUnifiedGroupOwners and Get-PnPUnifiedGroupMembers +- Added permissions required for each of the \*-PnPUnifiedGroup\* commands in the Azure Active Directory App Registration to the help text of the commands +- Added option to use Connect-PnPOnline with a base64 encoded private key for use in i.e. PnP PowerShell within an Azure Function v1 and an option to provide a certificate reference for use in i.e. Azure Function v2 +- Added option to use Connect-PnPOnline with a public key certificate for use in i.e. Azure Runbooks +- Added option -RowLimit to Get-PnPRecycleBinItem to avoid getting throttled on full recycle bins +- Added `-WriteToConsole` option to `Set-PnPTraceLog` to allow writing trace listener output from the PnP Templating commands to both the console and to a file. Doesn't work for .NET Core. +- Added `Reset-PnPLabel` command to allow removal of an Office 365 Retention Label from a list +- Add/Remove/Get-PnPOrgNewsSite commands to set site collections as authoritive news sources to SharePoint Online +- Add/Remove/Get-PnPOrgAssetsLibrary commands to set document libraries as organizational asset sources on SharePoint Online +- `-Recursive` option to `Get-PnPFolderItem` to allow retrieving all files and folders recursively ### Changed - -- Fixes issues with connections not properly closing under some conditions when using Disconnect-PnPOnline [PR2207](https://github.com/SharePoint/PnP-PowerShell/pull/2207) -- When using commands that utilize the Graph API but not being connected to one of the Graph API Connect-PnPOnline methods, it would throw a NullReferenceException. It will now throw a cleaner exception indicating you should connect with the Graph API first. [PR2395](https://github.com/SharePoint/PnP-PowerShell/pull/2395) -- Fixed an issue where using Get-PnPUser -WithRightsAssigned would not return the proper users with actually having access to that site [PR1685](https://github.com/SharePoint/PnP-PowerShell/pull/1685) -- Fixed an issue when using ConvertTo-PnPClientSidePage to convert Delve Blog posts that it would throw a nullreference exception in some scenarios [PR2411](https://github.com/SharePoint/PnP-PowerShell/pull/2411) -- Fixed an issue using `Add-PnPDataRowsToProvisioningTemp` to add data from a list containing a multi choice to a PnP Provisioning Template where the data would be shown as `System.String[]` instead of the actual data [PR2064](https://github.com/SharePoint/PnP-PowerShell/pull/2064) +- Marked Get-PnPHealthScore as obsolete for SharePoint Online. +- Fixes issues with connections not properly closing under some conditions when using Disconnect-PnPOnline +- When using commands that utilize the Graph API but not being connected to one of the Graph API Connect-PnPOnline methods, it would throw a NullReferenceException. It will now throw a cleaner exception indicating you should connect with the Graph API first. +- Fixed an issue where using Get-PnPUser -WithRightsAssigned would not return the proper users with actually having access to that site +- Fixed an issue when using ConvertTo-PnPClientSidePage to convert Delve Blog posts that it would throw a nullreference exception in some scenarios +- Fixed an issue using `Add-PnPDataRowsToProvisioningTemp` to add data from a list containing a multi choice to a PnP Provisioning Template where the data would be shown as `System.String[]` instead of the actual data +- Bumped to .Net 4.6.1 as minimal .Net runtime version +- Changed the way properties are being set in Set-PnPField to support setting field specific properties such as the Lookup list on a Lookup field +- Fixed an issue where using Apply-PnPProvisioningTemplate -InputInstance $instance would throw a connectionString error if being executed from the root of a drive, i.e. c:\ or d:\ +- Fixed issue with access token not returning correctly after update to newer version of NewtonSoft JSON. +- Fixes issue with pipeline not returning object correctly. ### Contributors -- Koen Zomers \[[koenzomers](https://github.com/koenzomers)\] -- Robin Meure \[[robinmeure](https://github.com/robinmeure)\] -- Michael Rees Pullen \[[mrpullen](https://github.com/mrpullen)\] -- Giacomo Pozzoni \[[jackpoz](https://github.com/jackpoz)\] -- Rene Modery \[[modery](https://github.com/modery)\] -- Krystian Niepsuj \[[MrDoNotBreak](https://github.com/MrDoNotBreak)\] -- Piotr Siatka \[[siata13](https://github.com/siata13)\] -- Heinrich Ulbricht \[[heinrich-ulbricht](https://github.com/heinrich-ulbricht)\] -- Dan Cecil \[[danielcecil](https://github.com/danielcecil)\] +- Koen Zomers [koenzomers] +- Robin Meure [robinmeure] +- Michael Rees Pullen [mrpullen] +- Giacomo Pozzoni [jackpoz] +- Rene Modery [modery] +- Krystian Niepsuj [MrDoNotBreak] +- Piotr Siatka [siata13] +- Heinrich Ulbricht [heinrich-ulbricht] +- Dan Cecil [danielcecil] +- Gautam Sheth [gautamdsheth] +- Giacomo Pozzoni [jackpoz] +- Will Holland [willholland] +- Ivan Vagunin [ivanvagunin] ## [3.16.1912.0] diff --git a/Commands/Admin/AddOrgAssetsLibrary.cs b/Commands/Admin/AddOrgAssetsLibrary.cs new file mode 100644 index 000000000..5564af267 --- /dev/null +++ b/Commands/Admin/AddOrgAssetsLibrary.cs @@ -0,0 +1,42 @@ +#if !ONPREMISES +using Microsoft.SharePoint.Client; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base; +using System.Management.Automation; +using Microsoft.Online.SharePoint.TenantAdministration; + +namespace SharePointPnP.PowerShell.Commands.Admin +{ + [Cmdlet(VerbsCommon.Add, "PnPOrgAssetsLibrary")] + [CmdletHelp("Adds a given document library as a organizational asset source", + DetailedDescription = @"Adds a given document library as an organizational asset source in your Sharepoint Online Tenant. All organizational asset sources you add must reside in the same site collection. Document libraries specified as organizational asset must be enabled as an Office 365 CDN source, either as private or public. It may take some time before this change will be reflected in the webinterface.", + SupportedPlatform = CmdletSupportedPlatform.Online, + Category = CmdletHelpCategory.TenantAdmin)] + [CmdletExample( + Code = @"PS:> Add-PnPOrgAssetsLibrary -LibraryUrl https://yourtenant.sharepoint.com/sites/branding/logos", + Remarks = @"Adds the document library with the url ""logos"" located in the sitecollection at ""https://yourtenant.sharepoint.com/sites/branding"" as an organizational asset not specifying a thumbnail image for it and enabling the document library as a public Office 365 CDN source", SortOrder = 1)] + [CmdletExample( + Code = @"PS:> Add-PnPOrgAssetsLibrary -LibraryUrl https://yourtenant.sharepoint.com/sites/branding/logos -ThumbnailUrl https://yourtenant.sharepoint.com/sites/branding/logos/thumbnail.jpg", + Remarks = @"Adds the document library with the url ""logos"" located in the sitecollection at ""https://yourtenant.sharepoint.com/sites/branding"" as an organizational asset specifying the thumbnail image ""thumbnail.jpg"" residing in the same document library for it and enabling the document library as a public Office 365 CDN source", SortOrder = 2)] + [CmdletExample( + Code = @"PS:> Add-PnPOrgAssetsLibrary -LibraryUrl https://yourtenant.sharepoint.com/sites/branding/logos -CdnType Private", + Remarks = @"Adds the document library with the url ""logos"" located in the sitecollection at ""https://yourtenant.sharepoint.com/sites/branding"" as an organizational asset not specifying a thumbnail image for it and enabling the document library as a private Office 365 CDN source", SortOrder = 3)] + public class AddOrgAssetsLibrary : PnPAdminCmdlet + { + [Parameter(Mandatory = true, HelpMessage = "The full url of the document library to be marked as one of organization's assets sources")] + public string LibraryUrl; + + [Parameter(Mandatory = false, HelpMessage = "The full url to an image that should be used as a thumbnail for showing this source. The image must reside in the same site as the document library you specify.")] + public string ThumbnailUrl; + + [Parameter(Mandatory = false, HelpMessage = @"Indicates what type of Office 365 CDN source the document library will be added to")] + public SPOTenantCdnType CdnType = SPOTenantCdnType.Public; + + protected override void ExecuteCmdlet() + { + Tenant.AddToOrgAssetsLibAndCdn(CdnType, LibraryUrl, ThumbnailUrl); + ClientContext.ExecuteQueryRetry(); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Admin/AddOrgNewsSite.cs b/Commands/Admin/AddOrgNewsSite.cs new file mode 100644 index 000000000..1e26bcd77 --- /dev/null +++ b/Commands/Admin/AddOrgNewsSite.cs @@ -0,0 +1,29 @@ +#if !ONPREMISES +using Microsoft.SharePoint.Client; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base; +using System.Management.Automation; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; + +namespace SharePointPnP.PowerShell.Commands.Admin +{ + [Cmdlet(VerbsCommon.Add, "PnPOrgNewsSite")] + [CmdletHelp("Adds the site as an organization news source in your tenant", + SupportedPlatform = CmdletSupportedPlatform.Online, + Category = CmdletHelpCategory.TenantAdmin)] + [CmdletExample( + Code = @"PS:> Add-PnPOrgNewsSite -OrgNewsSiteUrl https://yourtenant.sharepoint.com/sites/news", + Remarks = @"Adds the site as one of multiple possible tenant's organizational news sites", SortOrder = 1)] + public class AddOrgNewsSite : PnPAdminCmdlet + { + [Parameter(Mandatory = true, HelpMessage = "The url of the site to be marked as one of organization's news sites")] + public SitePipeBind OrgNewsSiteUrl; + + protected override void ExecuteCmdlet() + { + Tenant.SetOrgNewsSite(OrgNewsSiteUrl.Url); + ClientContext.ExecuteQueryRetry(); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Admin/GetOrgAssetsLibrary.cs b/Commands/Admin/GetOrgAssetsLibrary.cs new file mode 100644 index 000000000..a1a2663e5 --- /dev/null +++ b/Commands/Admin/GetOrgAssetsLibrary.cs @@ -0,0 +1,30 @@ +#if !ONPREMISES +using Microsoft.SharePoint.Client; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Admin +{ + [Cmdlet(VerbsCommon.Get, "PnPOrgAssetsLibrary")] + [CmdletHelp("Returns the list of all the configured organizational asset libraries", + SupportedPlatform = CmdletSupportedPlatform.Online, + Category = CmdletHelpCategory.TenantAdmin)] + [CmdletExample( + Code = @"PS:> Get-PnPOrgAssetsLibrary", + Remarks = @"Returns the list of all the configured organizational asset sites", SortOrder = 1)] + [CmdletExample( + Code = @"PS:> (Get-PnPOrgAssetsLibrary)[0].OrgAssetsLibraries[0].LibraryUrl.DecodedUrl", + Remarks = @"Returns the server relative url of the first document library which has been flagged as organizational asset library, i.e. ""sites/branding/logos""", SortOrder = 2)] + // + public class GetOrgAssetsLibrary : PnPAdminCmdlet + { + protected override void ExecuteCmdlet() + { + var results = Tenant.GetOrgAssets(); + ClientContext.ExecuteQueryRetry(); + WriteObject(results.Value, true); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Admin/GetOrgNewsSite.cs b/Commands/Admin/GetOrgNewsSite.cs new file mode 100644 index 000000000..2a59a3a56 --- /dev/null +++ b/Commands/Admin/GetOrgNewsSite.cs @@ -0,0 +1,26 @@ +#if !ONPREMISES +using Microsoft.SharePoint.Client; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Admin +{ + [Cmdlet(VerbsCommon.Get, "PnPOrgNewsSite")] + [CmdletHelp("Returns the list of all the configured organizational news sites.", + SupportedPlatform = CmdletSupportedPlatform.Online, + Category = CmdletHelpCategory.TenantAdmin)] + [CmdletExample( + Code = @"PS:> Get-PnPOrgNewsSite", + Remarks = @"Returns the list of all the configured organizational news sites.", SortOrder = 1)] + public class GetOrgNewsSite : PnPAdminCmdlet + { + protected override void ExecuteCmdlet() + { + var results = Tenant.GetOrgNewsSites(); + ClientContext.ExecuteQueryRetry(); + WriteObject(results, true); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Admin/GetTenantId.cs b/Commands/Admin/GetTenantId.cs index 22346ab3c..4aae4ea86 100644 --- a/Commands/Admin/GetTenantId.cs +++ b/Commands/Admin/GetTenantId.cs @@ -47,6 +47,7 @@ protected override void ProcessRecord() } catch (Exception ex) { +#if !NETSTANDARD2_1 if (ex.InnerException != null) { if (ex.InnerException is HttpException) @@ -64,6 +65,9 @@ protected override void ProcessRecord() { throw ex; } +#else + throw ex; +#endif } } } diff --git a/Commands/Admin/RemoveOrgAssetsLibrary.cs b/Commands/Admin/RemoveOrgAssetsLibrary.cs new file mode 100644 index 000000000..b23985fc5 --- /dev/null +++ b/Commands/Admin/RemoveOrgAssetsLibrary.cs @@ -0,0 +1,42 @@ +#if !ONPREMISES +using Microsoft.Online.SharePoint.TenantAdministration; +using Microsoft.SharePoint.Client; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Admin +{ + [Cmdlet(VerbsCommon.Remove, "PnPOrgAssetsLibrary")] + [CmdletHelp("Removes a given document library as a organizational asset source", + DetailedDescription = @"Removes a given document library as a organizational asset source based on its server relative URL in your Sharepoint Online Tenant. It will not remove the document library itself. It may take some time before this change will be reflected in the webinterface.", + SupportedPlatform = CmdletSupportedPlatform.Online, + Category = CmdletHelpCategory.TenantAdmin)] + [CmdletExample( + Code = @"PS:> Remove-PnPOrgAssetsLibrary -LibraryUrl ""sites/branding/logos""", + Remarks = @"This example removes the document library with the url ""logos"" residing in the sitecollection with the url ""sites/branding/logos"" from the list with organizational assets keeping it as an Office 365 CDN source", SortOrder = 1)] + [CmdletExample( + Code = @"PS:> Remove-PnPOrgAssetsLibrary -LibraryUrl ""sites/branding/logos"" -ShouldRemoveFromCdn $true", + Remarks = @"This example removes the document library with the url ""logos"" residing in the sitecollection with the url ""sites/branding/logos"" from the list with organizational assets also removing it as a Public Office 365 CDN source", SortOrder = 2)] + [CmdletExample( + Code = @"PS:> Remove-PnPOrgAssetsLibrary -LibraryUrl ""sites/branding/logos"" -ShouldRemoveFromCdn $true -CdnType Private", + Remarks = @"This example removes the document library with the url ""logos"" residing in the sitecollection with the url ""sites/branding/logos"" from the list with organizational assets also removing it as a Private Office 365 CDN source", SortOrder = 3)] + public class RemoveOrgAssetsLibrary : PnPAdminCmdlet + { + [Parameter(Mandatory = true, HelpMessage = @"The server relative url of the document library flagged as organizational asset which you want to remove, i.e. ""sites/branding/logos""")] + public string LibraryUrl; + + [Parameter(Mandatory = false, HelpMessage = @"Boolean indicating if the document library that will no longer be flagged as an organizational asset also needs to be removed as an Office 365 CDN source")] + public bool ShouldRemoveFromCdn = false; + + [Parameter(Mandatory = false, HelpMessage = @"Indicates what type of Office 365 CDN source the document library that will no longer be flagged as an organizational asset was of")] + public SPOTenantCdnType CdnType = SPOTenantCdnType.Public; + + protected override void ExecuteCmdlet() + { + Tenant.RemoveFromOrgAssetsAndCdn(ShouldRemoveFromCdn, CdnType, LibraryUrl); + ClientContext.ExecuteQueryRetry(); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Admin/RemoveOrgNewsSite.cs b/Commands/Admin/RemoveOrgNewsSite.cs new file mode 100644 index 000000000..ffd1247e9 --- /dev/null +++ b/Commands/Admin/RemoveOrgNewsSite.cs @@ -0,0 +1,31 @@ +#if !ONPREMISES +using Microsoft.Online.SharePoint.TenantAdministration; +using Microsoft.SharePoint.Client; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base; +using System.Management.Automation; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; + +namespace SharePointPnP.PowerShell.Commands.Admin +{ + [Cmdlet(VerbsCommon.Remove, "PnPOrgNewsSite")] + [CmdletHelp("Removes a given site from the list of organizational news sites.", + DetailedDescription = @"Removes a given site from the list of organizational news sites based on its URL in your Sharepoint Online Tenant.", + SupportedPlatform = CmdletSupportedPlatform.Online, + Category = CmdletHelpCategory.TenantAdmin)] + [CmdletExample( + Code = @"PS:> Remove-PnPOrgNewsSite -OrgNewsSiteUrl https://tenant.sharepoint.com/sites/mysite", + Remarks = @"This example removes the specified site from list of organization's news sites.", SortOrder = 1)] + public class RemoveOrgNewsSite : PnPAdminCmdlet + { + [Parameter(Mandatory = true, HelpMessage = @"The site to be removed from list of organization's news sites")] + public SitePipeBind OrgNewsSiteUrl; + + protected override void ExecuteCmdlet() + { + Tenant.RemoveOrgNewsSite(OrgNewsSiteUrl.Url); + ClientContext.ExecuteQueryRetry(); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Admin/RemoveSiteCollectionAppCatalog.cs b/Commands/Admin/RemoveSiteCollectionAppCatalog.cs index 929a8547e..985284b64 100644 --- a/Commands/Admin/RemoveSiteCollectionAppCatalog.cs +++ b/Commands/Admin/RemoveSiteCollectionAppCatalog.cs @@ -16,7 +16,7 @@ namespace SharePointPnP.PowerShell.Commands.Admin SupportedPlatform = CmdletSupportedPlatform.Online, Category = CmdletHelpCategory.TenantAdmin)] [CmdletExample( - Code = @"PS:> Remove-PnPOffice365GroupToSite -Url ""https://contoso.sharepoint.com/sites/FinanceTeamsite""", + Code = @"PS:> Remove-PnPSiteCollectionAppCatalog -Site ""https://contoso.sharepoint.com/sites/FinanceTeamsite""", Remarks = @"This will remove a SiteCollection app catalog from the specified site", SortOrder = 1)] public class RemoveSiteCollectionAppCatalog: PnPAdminCmdlet { diff --git a/Commands/Apps/AddApplicationCustomizer.cs b/Commands/Apps/AddApplicationCustomizer.cs new file mode 100644 index 000000000..536fd7cfc --- /dev/null +++ b/Commands/Apps/AddApplicationCustomizer.cs @@ -0,0 +1,66 @@ +#if !SP2013 && !SP2016 +using System.Management.Automation; +using Microsoft.SharePoint.Client; +using OfficeDevPnP.Core.Entities; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Enums; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; + +namespace SharePointPnP.PowerShell.Commands.Branding +{ + [Cmdlet(VerbsCommon.Add, "PnPApplicationCustomizer")] + [CmdletHelp("Adds a SharePoint Framework client side extension application customizer", + "Adds a SharePoint Framework client side extension application customizer by registering a user custom action to a web or sitecollection", + Category = CmdletHelpCategory.Apps, + SupportedPlatform = CmdletSupportedPlatform.Online | CmdletSupportedPlatform.SP2019)] + [CmdletExample(Code = @"Add-PnPApplicationCustomizer -Title ""CollabFooter"" -ClientSideComponentId c0ab3b94-8609-40cf-861e-2a1759170b43 -ClientSideComponentProperties ""{`""sourceTermSet`"":`""PnP-CollabFooter-SharedLinks`"",`""personalItemsStorageProperty`"":`""PnP-CollabFooter-MyLinks`""}", + Remarks = @"Adds a new application customizer to the current web. This requires that a SharePoint Framework solution has been deployed containing the application customizer specified in its manifest. Be sure to run Install-PnPApp before trying this cmdlet on a site.", + SortOrder = 1)] + public class AddApplicationCustomizer : PnPWebCmdlet + { + [Parameter(Mandatory = false, HelpMessage = "The title of the application customizer")] + public string Title = string.Empty; + + [Parameter(Mandatory = false, HelpMessage = "The description of the application customizer")] + public string Description = string.Empty; + + [Parameter(Mandatory = false, HelpMessage = "Sequence of this application customizer being injected. Use when you have a specific sequence with which to have multiple application customizers being added to the page.")] + public int Sequence = 0; + + [Parameter(Mandatory = false, HelpMessage = "The scope of the CustomAction to add to. Either Web or Site; defaults to Web. 'All' is not valid for this command.")] + public CustomActionScope Scope = CustomActionScope.Web; + + [Parameter(Mandatory = true, HelpMessage = "The Client Side Component Id of the SharePoint Framework client side extension application customizer found in the manifest")] + public GuidPipeBind ClientSideComponentId; + + [Parameter(Mandatory = false, HelpMessage = "The Client Side Component Properties of the application customizer. Specify values as a json string : \"{Property1 : 'Value1', Property2: 'Value2'}\"")] + public string ClientSideComponentProperties; + + protected override void ExecuteCmdlet() + { + CustomActionEntity ca = new CustomActionEntity() + { + Title = Title, + Location = "ClientSideExtension.ApplicationCustomizer", + ClientSideComponentId = ClientSideComponentId.Id, + ClientSideComponentProperties = ClientSideComponentProperties + }; + + switch (Scope) + { + case CustomActionScope.Web: + SelectedWeb.AddCustomAction(ca); + break; + + case CustomActionScope.Site: + ClientContext.Site.AddCustomAction(ca); + break; + + case CustomActionScope.All: + WriteWarning("CustomActionScope 'All' is not supported for adding CustomActions"); + break; + } + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Apps/GetApplicationCustomizer.cs b/Commands/Apps/GetApplicationCustomizer.cs new file mode 100644 index 000000000..fe92f7f74 --- /dev/null +++ b/Commands/Apps/GetApplicationCustomizer.cs @@ -0,0 +1,86 @@ +#if !SP2013 && !SP2016 +using System.Management.Automation; +using Microsoft.SharePoint.Client; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; +using SharePointPnP.PowerShell.Commands.Enums; +using System.Collections.Generic; +using System.Linq; + +namespace SharePointPnP.PowerShell.Commands.Branding +{ + [Cmdlet(VerbsCommon.Get, "PnPApplicationCustomizer", ConfirmImpact = ConfirmImpact.High, SupportsShouldProcess = true)] + [CmdletHelp("Returns all SharePoint Framework client side extension application customizers", + "Returns all SharePoint Framework client side extension application customizers registered on the current web and/or site", + Category = CmdletHelpCategory.Apps, + SupportedPlatform = CmdletSupportedPlatform.Online | CmdletSupportedPlatform.SP2019)] + [CmdletExample(Code = @"PS:> Get-PnPApplicationCustomizer", + Remarks = @"Returns the custom action representing the SharePoint Framework client side extension registrations registered on the current site collection and web.", + SortOrder = 1)] + [CmdletExample(Code = @"PS:> Get-PnPApplicationCustomizer -Identity aa66f67e-46c0-4474-8a82-42bf467d07f2", + Remarks = @"Returns the custom action representing the SharePoint Framework client side extension registration with the id 'aa66f67e-46c0-4474-8a82-42bf467d07f2'.", + SortOrder = 2)] + [CmdletExample(Code = @"PS:> Get-PnPApplicationCustomizer -ClientSideComponentId aa66f67e-46c0-4474-8a82-42bf467d07f2 -Scope Web", + Remarks = @"Returns the custom action(s) being registered for a SharePoint Framework solution having the id 'aa66f67e-46c0-4474-8a82-42bf467d07f2' in its manifest from the current web.", + SortOrder = 3)] + public class GetApplicationCustomizer : PnPWebRetrievalsCmdlet + { + private const string ParameterSet_CUSTOMACTIONID = "Custom Action Id"; + private const string ParameterSet_CLIENTSIDECOMPONENTID = "Client Side Component Id"; + + [Parameter(Mandatory = false, HelpMessage = "Identity of the SharePoint Framework client side extension application customizer to return. Omit to return all SharePoint Frameworkclient side extension application customizer.", ParameterSetName = ParameterSet_CUSTOMACTIONID)] + public GuidPipeBind Identity; + + [Parameter(Mandatory = true, HelpMessage = "The Client Side Component Id of the SharePoint Framework client side extension application customizer found in the manifest for which existing custom action(s) should be removed", ParameterSetName = ParameterSet_CLIENTSIDECOMPONENTID)] + public GuidPipeBind ClientSideComponentId; + + [Parameter(Mandatory = false, HelpMessage = "Scope of the SharePoint Framework client side extension application customizer, either Web, Site or All to return both (all is the default)")] + public CustomActionScope Scope = CustomActionScope.All; + + [Parameter(Mandatory = false, HelpMessage = "Switch parameter if an exception should be thrown if the requested SharePoint Frameworkclient side extension application customizer does not exist (true) or if omitted, nothing will be returned in case the SharePoint Framework client side extension application customizer does not exist")] + public SwitchParameter ThrowExceptionIfCustomActionNotFound; + + protected override void ExecuteCmdlet() + { + List actions = new List(); + + if (Scope == CustomActionScope.All || Scope == CustomActionScope.Web) + { + actions.AddRange(SelectedWeb.GetCustomActions(RetrievalExpressions)); + } + if (Scope == CustomActionScope.All || Scope == CustomActionScope.Site) + { + actions.AddRange(ClientContext.Site.GetCustomActions(RetrievalExpressions)); + } + + if (Identity != null) + { + var foundAction = actions.FirstOrDefault(x => x.Id == Identity.Id && x.Location == "ClientSideExtension.ApplicationCustomizer"); + if (foundAction != null || !ThrowExceptionIfCustomActionNotFound) + { + WriteObject(foundAction, true); + } + else + { + throw new PSArgumentException($"No SharePoint Framework client side extension application customizer found with the Identity '{Identity.Id}' within the scope '{Scope}'", "Identity"); + } + } + else + { + switch (ParameterSetName) + { + case ParameterSet_CLIENTSIDECOMPONENTID: + actions = actions.Where(x => x.Location == "ClientSideExtension.ApplicationCustomizer" & x.ClientSideComponentId == ClientSideComponentId.Id).ToList(); + break; + + case ParameterSet_CUSTOMACTIONID: + actions = actions.Where(x => x.Location == "ClientSideExtension.ApplicationCustomizer").ToList(); + break; + } + + WriteObject(actions, true); + } + } + } +} +#endif diff --git a/Commands/Apps/RemoveApplicationCustomizer.cs b/Commands/Apps/RemoveApplicationCustomizer.cs new file mode 100644 index 000000000..b5a0b8fff --- /dev/null +++ b/Commands/Apps/RemoveApplicationCustomizer.cs @@ -0,0 +1,102 @@ +#if !SP2013 && !SP2016 +using System.Management.Automation; +using Microsoft.SharePoint.Client; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; +using SharePointPnP.PowerShell.Commands.Enums; +using Resources = SharePointPnP.PowerShell.Commands.Properties.Resources; +using System.Collections.Generic; +using System.Linq; + +namespace SharePointPnP.PowerShell.Commands.Branding +{ + [Cmdlet(VerbsCommon.Remove, "PnPApplicationCustomizer", ConfirmImpact = ConfirmImpact.High, SupportsShouldProcess = true)] + [CmdletHelp("Removes a SharePoint Framework client side extension application customizer", + "Removes a SharePoint Framework client side extension application customizer by removing a user custom action from a web or sitecollection", + Category = CmdletHelpCategory.Apps, + SupportedPlatform = CmdletSupportedPlatform.Online | CmdletSupportedPlatform.SP2019)] + [CmdletExample(Code = @"PS:> Remove-PnPCustomAction -Identity aa66f67e-46c0-4474-8a82-42bf467d07f2", + Remarks = @"Removes the custom action representing the client side extension registration with the id 'aa66f67e-46c0-4474-8a82-42bf467d07f2'.", + SortOrder = 1)] + [CmdletExample(Code = @"PS:> Remove-PnPCustomAction -ClientSideComponentId aa66f67e-46c0-4474-8a82-42bf467d07f2 -Scope web", + Remarks = @"Removes the custom action(s) being registered for a SharePoint Framework solution having the id 'aa66f67e-46c0-4474-8a82-42bf467d07f2' in its manifest from the current web.", + SortOrder = 2)] + public class RemoveApplicationCustomizer : PnPWebCmdlet + { + private const string ParameterSet_CUSTOMACTIONID = "Custom Action Id"; + private const string ParameterSet_CLIENTSIDECOMPONENTID = "Client Side Component Id"; + + [Parameter(Mandatory = false, Position = 0, ValueFromPipeline = true, HelpMessage = "The id or name of the CustomAction representing the client side extension registration that needs to be removed or a CustomAction instance itself", ParameterSetName = ParameterSet_CUSTOMACTIONID)] + public UserCustomActionPipeBind Identity; + + [Parameter(Mandatory = true, HelpMessage = "The Client Side Component Id of the SharePoint Framework client side extension application customizer found in the manifest for which existing custom action(s) should be removed", ParameterSetName = ParameterSet_CLIENTSIDECOMPONENTID)] + public GuidPipeBind ClientSideComponentId; + + [Parameter(Mandatory = false, HelpMessage = "Define if the CustomAction representing the client side extension registration is to be found at the web or site collection scope. Specify All to allow deletion from either web or site collection (default).")] + public CustomActionScope Scope = CustomActionScope.All; + + [Parameter(Mandatory = false, HelpMessage = "Use the -Force flag to bypass the confirmation question")] + public SwitchParameter Force; + + protected override void ExecuteCmdlet() + { + List actions = new List(); + + if (Identity != null && Identity.UserCustomAction != null) + { + actions.Add(Identity.UserCustomAction); + } + else + { + if (Scope == CustomActionScope.All || Scope == CustomActionScope.Web) + { + actions.AddRange(SelectedWeb.GetCustomActions()); + } + if (Scope == CustomActionScope.All || Scope == CustomActionScope.Site) + { + actions.AddRange(ClientContext.Site.GetCustomActions()); + } + + if (Identity != null) + { + actions = actions.Where(action => Identity.Id.HasValue ? Identity.Id.Value == action.Id : Identity.Name == action.Name).ToList(); + + if (!actions.Any()) + { + throw new PSArgumentException($"No CustomAction representing the client side extension registration found with the {(Identity.Id.HasValue ? "Id" : "name")} '{(Identity.Id.HasValue ? Identity.Id.Value.ToString() : Identity.Name)}' within the scope '{Scope}'", "Identity"); + } + } + } + + // Only take the customactions which are application customizers + actions = actions.Where(a => a.Location == "ClientSideExtension.ApplicationCustomizer").ToList(); + + // If a ClientSideComponentId has been provided, only leave those who have a matching client side component id + if (ParameterSetName == ParameterSet_CLIENTSIDECOMPONENTID) + { + actions = actions.Where(a => a.ClientSideComponentId == ClientSideComponentId.Id).ToList(); + } + + if (!actions.Any()) + { + WriteVerbose($"No CustomAction representing the client side extension registration found within the scope '{Scope}'"); + return; + } + + foreach (var action in actions.Where(action => Force || (MyInvocation.BoundParameters.ContainsKey("Confirm") && !bool.Parse(MyInvocation.BoundParameters["Confirm"].ToString())) || ShouldContinue(string.Format(Resources.RemoveCustomAction, action.Name, action.Id, action.Scope), Resources.Confirm))) + { + switch (action.Scope) + { + case UserCustomActionScope.Web: + SelectedWeb.DeleteCustomAction(action.Id); + break; + + case UserCustomActionScope.Site: + ClientContext.Site.DeleteCustomAction(action.Id); + break; + } + } + } + } +} +#endif diff --git a/Commands/Apps/SetApplicationCustomizer.cs b/Commands/Apps/SetApplicationCustomizer.cs new file mode 100644 index 000000000..ba98bc040 --- /dev/null +++ b/Commands/Apps/SetApplicationCustomizer.cs @@ -0,0 +1,126 @@ +#if !SP2013 && !SP2016 +using System.Management.Automation; +using Microsoft.SharePoint.Client; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; +using SharePointPnP.PowerShell.Commands.Enums; +using System.Collections.Generic; +using System.Linq; + +namespace SharePointPnP.PowerShell.Commands.Branding +{ + [Cmdlet(VerbsCommon.Set, "PnPApplicationCustomizer", ConfirmImpact = ConfirmImpact.High, SupportsShouldProcess = true)] + [CmdletHelp("Updates a SharePoint Framework client side extension application customizer", + "Updates a SharePoint Framework client side extension application customizer by updating its custom action. Only the properties that will be provided will be updated. Others will remain as they are.", + Category = CmdletHelpCategory.Apps, + SupportedPlatform = CmdletSupportedPlatform.Online | CmdletSupportedPlatform.SP2019)] + [CmdletExample(Code = @"PS:> Set-PnPCustomAction -Identity aa66f67e-46c0-4474-8a82-42bf467d07f2", + Remarks = @"Updates the custom action representing the client side extension registration with the id 'aa66f67e-46c0-4474-8a82-42bf467d07f2'.", + SortOrder = 1)] + [CmdletExample(Code = @"PS:> Set-PnPCustomAction -ClientSideComponentId aa66f67e-46c0-4474-8a82-42bf467d07f2 -Scope web -ClientSideComponentProperties ""{`""sourceTermSet`"":`""PnP-CollabFooter-SharedLinks`"",`""personalItemsStorageProperty`"":`""PnP-CollabFooter-MyLinks`""}", + Remarks = @"Updates the custom action(s) properties being registered for a SharePoint Framework solution having the id 'aa66f67e-46c0-4474-8a82-42bf467d07f2' in its manifest from the current web.", + SortOrder = 2)] + public class SetApplicationCustomizer : PnPWebCmdlet + { + private const string ParameterSet_CUSTOMACTIONID = "Custom Action Id"; + private const string ParameterSet_CLIENTSIDECOMPONENTID = "Client Side Component Id"; + + [Parameter(Mandatory = false, Position = 0, ValueFromPipeline = true, HelpMessage = "The id or name of the CustomAction representing the client side extension registration that needs to be updated or a CustomAction instance itself", ParameterSetName = ParameterSet_CUSTOMACTIONID)] + public UserCustomActionPipeBind Identity = null; + + [Parameter(Mandatory = false, HelpMessage = "The Client Side Component Id of the SharePoint Framework client side extension application customizer found in the manifest for which existing custom action(s) should be updated", ParameterSetName = ParameterSet_CLIENTSIDECOMPONENTID)] + public GuidPipeBind ClientSideComponentId = null; + + [Parameter(Mandatory = false, HelpMessage = "Define if the CustomAction representing the client side extension registration is to be found at the web or site collection scope. Specify All to update the component on both web and site collection level.")] + public CustomActionScope Scope = CustomActionScope.Web; + + [Parameter(Mandatory = false, HelpMessage = "The title of the application customizer. Omit to not update this property.")] + public string Title = null; + + [Parameter(Mandatory = false, HelpMessage = "The description of the application customizer. Omit to not update this property.")] + public string Description = null; + + [Parameter(Mandatory = false, HelpMessage = "Sequence of this application customizer being injected. Use when you have a specific sequence with which to have multiple application customizers being added to the page. Omit to not update this property.")] + public int? Sequence = null; + + [Parameter(Mandatory = false, HelpMessage = "The Client Side Component Properties of the application customizer to update. Specify values as a json string : \"{Property1 : 'Value1', Property2: 'Value2'}\". Omit to not update this property.")] + public string ClientSideComponentProperties = null; + + protected override void ExecuteCmdlet() + { + List actions = new List(); + + if (Identity != null && Identity.UserCustomAction != null) + { + actions.Add(Identity.UserCustomAction); + } + else + { + if (Scope == CustomActionScope.All || Scope == CustomActionScope.Web) + { + actions.AddRange(SelectedWeb.GetCustomActions()); + } + if (Scope == CustomActionScope.All || Scope == CustomActionScope.Site) + { + actions.AddRange(ClientContext.Site.GetCustomActions()); + } + + if (Identity != null) + { + actions = actions.Where(action => Identity.Id.HasValue ? Identity.Id.Value == action.Id : Identity.Name == action.Name).ToList(); + + if (!actions.Any()) + { + throw new PSArgumentException($"No CustomAction representing the client side extension registration found with the {(Identity.Id.HasValue ? "Id" : "name")} '{(Identity.Id.HasValue ? Identity.Id.Value.ToString() : Identity.Name)}' within the scope '{Scope}'", "Identity"); + } + } + } + + // If a ClientSideComponentId has been provided, only leave those who have a matching client side component id + if(ParameterSetName == ParameterSet_CLIENTSIDECOMPONENTID) + { + actions = actions.Where(a => a.ClientSideComponentId == ClientSideComponentId.Id && a.Location == "ClientSideExtension.ApplicationCustomizer").ToList(); + } + + if (!actions.Any()) + { + WriteVerbose($"No CustomAction representing the client side extension registration found within the scope '{Scope}'"); + return; + } + + // Update each of the matched custom actions + foreach (var action in actions) + { + bool isDirty = false; + + if(Title != null) + { + action.Title = Title; + isDirty = true; + } + if(Description != null) + { + action.Description = Description; + isDirty = true; + } + if (Sequence.HasValue) + { + action.Sequence = Sequence.Value; + isDirty = true; + } + if (ClientSideComponentProperties != null) + { + action.ClientSideComponentProperties = ClientSideComponentProperties; + isDirty = true; + } + + if (isDirty) + { + action.Update(); + ClientContext.ExecuteQueryRetry(); + } + } + } + } +} +#endif diff --git a/Commands/Base/AddStoredCredential.cs b/Commands/Base/AddStoredCredential.cs index 1f0e20f96..6f5c6a0f2 100644 --- a/Commands/Base/AddStoredCredential.cs +++ b/Commands/Base/AddStoredCredential.cs @@ -7,7 +7,7 @@ namespace SharePointPnP.PowerShell.Commands.Base { [Cmdlet(VerbsCommon.Add, "PnPStoredCredential")] -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 [CmdletHelp("Adds a credential to the Windows Credential Manager", @"Adds an entry to the Windows Credential Manager. If you add an entry in the form of the URL of your tenant/server PnP PowerShell will check if that entry is available when you connect using Connect-PnPOnline. If it finds a matching URL it will use the associated credentials. @@ -41,7 +41,7 @@ public class AddStoredCredential : PSCmdlet [Parameter(Mandatory = false, HelpMessage = @"If not specified you will be prompted to enter your password. If you want to specify this value use ConvertTo-SecureString -String 'YourPassword' -AsPlainText -Force")] public SecureString Password; -#if NETSTANDARD2_0 +#if NETSTANDARD2_1 [Parameter(Mandatory = false)] public SwitchParameter Overwrite; #endif @@ -53,7 +53,7 @@ protected override void ProcessRecord() Password = Host.UI.ReadLineAsSecureString(); } -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 Utilities.CredentialManager.AddCredential(Name, Username, Password); #else Utilities.CredentialManager.AddCredential(Name, Username, Password, Overwrite.ToBool()); diff --git a/Commands/Base/ConnectOnline.cs b/Commands/Base/ConnectOnline.cs index d22bfb160..52e134936 100644 --- a/Commands/Base/ConnectOnline.cs +++ b/Commands/Base/ConnectOnline.cs @@ -13,7 +13,8 @@ using System.Reflection; using System.Security; using File = System.IO.File; -#if NETSTANDARD2_0 +using System.Security.Cryptography.X509Certificates; +#if NETSTANDARD2_1 using System.IdentityModel.Tokens.Jwt; #endif #if !ONPREMISES @@ -51,82 +52,107 @@ namespace SharePointPnP.PowerShell.Commands.Base Code = @"PS:> Connect-PnPOnline -Url http://yourlocalserver -Credentials (Get-Credential) -UseAdfs", Remarks = @"This will prompt for username and password and creates a context using ADFS to authenticate.", SortOrder = 5)] +#if !NETSTANDARD2_1 + [CmdletExample( + Code = @"PS:> Connect-PnPOnline -Url http://yourlocalserver -UseAdfsCert", + Remarks = @"This will enable you to select a certificate to create a context using ADFS to authenticate.", + SortOrder = 6)] +#endif [CmdletExample( Code = @"PS:> Connect-PnPOnline -Url https://yourserver -Credentials (Get-Credential) -CreateDrive PS:> cd SPO:\\ PS:> dir", Remarks = @"This will prompt you for credentials and creates a context for the other PowerShell commands to use. It will also create a SPO:\\ drive you can use to navigate around the site", - SortOrder = 6)] + SortOrder = 7)] [CmdletExample( Code = @"PS:> Connect-PnPOnline -Url https://yourserver -Credentials (Get-Credential) -AuthenticationMode FormsAuthentication", Remarks = @"This will prompt you for credentials and creates a context for the other PowerShell commands to use. It assumes your server is configured for Forms Based Authentication (FBA)", - SortOrder = 7)] + SortOrder = 8)] #if !ONPREMISES [CmdletExample( Code = @"PS:> Connect-PnPOnline -Url https://contoso.sharepoint.de -AppId 344b8aab-389c-4e4a-8fa1-4c1ae2c0a60d -AppSecret a3f3faf33f3awf3a3sfs3f3ss3f4f4a3fawfas3ffsrrffssfd -AzureEnvironment Germany", Remarks = @"This will authenticate you to the German Azure environment using the German Azure endpoints for authentication", - SortOrder = 8)] + SortOrder = 9)] #endif #if !ONPREMISES [CmdletExample( Code = @"PS:> Connect-PnPOnline -Url https://contoso.sharepoint.com -SPOManagementShell", Remarks = @"This will authenticate you using the SharePoint Online Management Shell application", - SortOrder = 9)] + SortOrder = 10)] #endif #if !ONPREMISES [CmdletExample( Code = @"PS:> Connect-PnPOnline -Url https://contoso.sharepoint.com -PnPO365ManagementShell", Remarks = @"This will authenticate you using the PnP O365 Management Shell Multi-Tenant application. A browser window will have to be opened where you have to enter a code that is shown in your PowerShell window.", - SortOrder = 10)] + SortOrder = 11)] [CmdletExample( Code = @"PS:> Connect-PnPOnline -Url https://contoso.sharepoint.com -PnPO365ManagementShell -LaunchBrowser", Remarks = @"This will authenticate you using the PnP O365 Management Shell Multi-Tenant application. A browser window will automatically open and the code you need to enter will be automatically copied to your clipboard.", - SortOrder = 11)] + SortOrder = 12)] #endif #if !ONPREMISES [CmdletExample( Code = @"PS:> Connect-PnPOnline -Url https://contoso.sharepoint.com -AccessToken $myaccesstoken", Remarks = @"This will authenticate you using the provided access token", - SortOrder = 12)] + SortOrder = 13)] #endif #if !ONPREMISES -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 [CmdletExample( Code = "PS:> Connect-PnPOnline -Scopes \"Mail.Read\",\"Files.Read\"", Remarks = "Connects to Azure AD and gets and OAuth 2.0 Access Token to consume the Microsoft Graph API including the declared permission scopes. The available permission scopes are defined at the following URL: https://docs.microsoft.com/en-us/graph/permissions-reference", - SortOrder = 13)] + SortOrder = 14)] #endif #endif #if !ONPREMISES [CmdletExample( Code = "PS:> Connect-PnPOnline -AppId '' -AppSecret '' -AADDomain 'contoso.onmicrosoft.com'", Remarks = "Connects to the Microsoft Graph API using application permissions via an app's declared permission scopes. See https://github.com/SharePoint/PnP-PowerShell/tree/master/Samples/Graph.ConnectUsingAppPermissions for a sample on how to get started.", - SortOrder = 14)] + SortOrder = 15)] [CmdletExample( Code = "PS:> Connect-PnPOnline -Url https://contoso.sharepoint.com -ClientId '' -Tenant 'contoso.onmicrosoft.com' -CertificatePath c:\\absolute-path\\to\\pnp.pfx -CertificatePassword ", Remarks = "Connects to SharePoint using app-only tokens via an app's declared permission scopes. See https://github.com/SharePoint/PnP-PowerShell/tree/master/Samples/SharePoint.ConnectUsingAppPermissions for a sample on how to get started.", - SortOrder = 15)] + SortOrder = 16)] [CmdletExample( Code = "PS:> Connect-PnPOnline -Url https://contoso.sharepoint.com -ClientId '' -Tenant 'contoso.onmicrosoft.com' -Thumbprint 34CFAA860E5FB8C44335A38A097C1E41EEA206AA", Remarks = "Connects to SharePoint using app-only tokens via an app's declared permission scopes. See https://github.com/SharePoint/PnP-PowerShell/tree/master/Samples/SharePoint.ConnectUsingAppPermissions for a sample on how to get started.", - SortOrder = 16)] + SortOrder = 17)] [CmdletExample( Code = "PS:> Connect-PnPOnline -Url https://contoso.sharepoint.com -ClientId '' -Tenant 'contoso.onmicrosoft.com' -PEMCertificate -PEMPrivateKey -CertificatePassword ", Remarks = "Connects to SharePoint using app-only tokens via an app's declared permission scopes. See https://github.com/SharePoint/PnP-PowerShell/tree/master/Samples/SharePoint.ConnectUsingAppPermissions for a sample on how to get started.", SortOrder = 17)] + [CmdletExample( + Code = "PS:> Connect-PnPOnline -Url https://contoso.sharepoint.com -ClientId '' -Tenant 'contoso.onmicrosoft.com' -Certificate ", + Remarks = "Connects to SharePoint using app-only auth in combination with a certificate. See https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread#using-this-principal-in-your-powershell-script-using-the-pnp-sites-core-library for a sample on how to get started.", + SortOrder = 18)] #endif #if ONPREMISES [CmdletExample( Code = @"PS:> certutil.exe -csp 'Microsoft Enhanced RSA and AES Cryptographic Provider' -v -p 'password' -importpfx -user c:\HighTrust.pfx NoRoot PS:> Connect-PnPOnline -Url https://yourserver -ClientId -HighTrustCertificate (Get-Item Cert:\CurrentUser\My\)", Remarks = @"Connect to an on-premises SharePoint environment using a high trust certificate, stored in the Personal certificate store of the current user.", - SortOrder = 14)] + SortOrder = 15)] [CmdletExample( Code = @"PS:> Connect-PnPOnline -Url https://yourserver -ClientId 763d5e60-b57e-426e-8e87-b7258f7f8188 -HighTrustCertificatePath c:\HighTrust.pfx -HighTrustCertificatePassword 'password' -HighTrustCertificateIssuerId 6b9534d8-c2c1-49d6-9f4b-cd415620bca8", Remarks = @"Connect to an on-premises SharePoint environment using a high trust certificate stored in a .PFX file.", - SortOrder = 15)] + SortOrder = 16)] #endif - public class ConnectOnline : PSCmdlet +#if !ONPREMISES + [CmdletExample( + Code = "PS:> Connect-PnPOnline -ClientId -CertificatePath 'c:\\mycertificate.pfx' -CertificatePassword (ConvertTo-SecureString -AsPlainText 'myprivatekeypassword' -Force) -Url https://contoso.sharepoint.com -Tenant 'contoso.onmicrosoft.com'", + Remarks = "Connects using an Azure Active Directory registered application using a locally available certificate containing a private key. See https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread for a sample on how to get started.", + SortOrder = 16)] + [CmdletExample( + Code = "PS:> Connect-PnPOnline -ClientId -CertificateBase64Encoded 'xxxx' -CertificatePassword (ConvertTo-SecureString -AsPlainText 'myprivatekeypassword' -Force) -Url https://contoso.sharepoint.com -Tenant 'contoso.onmicrosoft.com'", + Remarks = "Connects using an Azure Active Directory registered application using a certificate containing a private key encoded in base 64 such as received in an Azure Function when using Azure KeyVault. See https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread for a sample on how to get started.", + SortOrder = 17)] + [CmdletExample( + Code = "PS:> Connect-PnPOnline -ClientId -Certificate $cert -CertificatePassword (ConvertTo-SecureString -AsPlainText 'myprivatekeypassword' -Force) -Url https://contoso.sharepoint.com -Tenant 'contoso.onmicrosoft.com'", + Remarks = "Connects using an Azure Active Directory registered application using a certificate instance containing a private key. See https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread for a sample on how to get started.", + SortOrder = 18)] + +#endif + public class ConnectOnline : BasePSCmdlet { private const string ParameterSet_MAIN = "Main"; private const string ParameterSet_TOKEN = "Token"; @@ -135,12 +161,12 @@ public class ConnectOnline : PSCmdlet private const string ParameterSet_NATIVEAAD = "Azure Active Directory"; private const string ParameterSet_APPONLYAAD = "App-Only with Azure Active Directory"; private const string ParameterSet_APPONLYAADPEM = "App-Only with Azure Active Directory using certificate as PEM strings"; - + private const string ParameterSet_APPONLYAADCER = "App-Only with Azure Active Directory using X502 certificates"; private const string ParameterSet_APPONLYAADThumb = "App-Only with Azure Active Directory using certificate from certificate store by thumbprint"; private const string ParameterSet_SPOMANAGEMENT = "SPO Management Shell Credentials"; private const string ParameterSet_DEVICELOGIN = "PnP O365 Management Shell / DeviceLogin"; private const string ParameterSet_GRAPHDEVICELOGIN = "PnP Office 365 Management Shell to the Microsoft Graph"; -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 private const string ParameterSet_GRAPHWITHSCOPE = "Microsoft Graph using Scopes"; #endif private const string ParameterSet_GRAPHWITHAAD = "Microsoft Graph using Azure Active Directory"; @@ -166,6 +192,7 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, ValueFromPipeline = true, HelpMessage = "Returns the connection for use with the -Connection parameter on cmdlets.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADPEM, ValueFromPipeline = true, HelpMessage = "Returns the connection for use with the -Connection parameter on cmdlets.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADThumb, ValueFromPipeline = true, HelpMessage = "Returns the connection for use with the -Connection parameter on cmdlets.")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCER, ValueFromPipeline = true, HelpMessage = "Returns the connection for use with the -Connection parameter on cmdlets.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_SPOMANAGEMENT, ValueFromPipeline = true, HelpMessage = "Returns the connection for use with the -Connection parameter on cmdlets.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_ACCESSTOKEN, ValueFromPipeline = true, HelpMessage = "Returns the connection for use with the -Connection parameter on cmdlets.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_DEVICELOGIN, ValueFromPipeline = true, HelpMessage = "Returns the connection for use with the -Connection parameter on cmdlets.")] @@ -183,7 +210,7 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = true, Position = 0, ParameterSetName = ParameterSet_NATIVEAAD, ValueFromPipeline = true, HelpMessage = "The Url of the site collection to connect to.")] [Parameter(Mandatory = true, Position = 0, ParameterSetName = ParameterSet_APPONLYAAD, ValueFromPipeline = true, HelpMessage = "The Url of the site collection to connect to.")] [Parameter(Mandatory = true, Position = 0, ParameterSetName = ParameterSet_APPONLYAADPEM, ValueFromPipeline = true, HelpMessage = "The Url of the site collection to connect to.")] - [Parameter(Mandatory = true, Position = 0, ParameterSetName = ParameterSet_APPONLYAADThumb, ValueFromPipeline = true, HelpMessage = "The Url of the site collection to connect to.")] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = ParameterSet_APPONLYAADCER, ValueFromPipeline = true, HelpMessage = "The Url of the site collection to connect to.")][Parameter(Mandatory = true, Position = 0, ParameterSetName = ParameterSet_APPONLYAADThumb, ValueFromPipeline = true, HelpMessage = "The Url of the site collection to connect to.")] [Parameter(Mandatory = true, Position = 0, ParameterSetName = ParameterSet_SPOMANAGEMENT, ValueFromPipeline = true, HelpMessage = "The Url of the site collection to connect to.")] [Parameter(Mandatory = false, Position = 0, ParameterSetName = ParameterSet_ACCESSTOKEN, ValueFromPipeline = true, HelpMessage = "The Url of the site collection to connect to.")] [Parameter(Mandatory = true, Position = 0, ParameterSetName = ParameterSet_DEVICELOGIN, ValueFromPipeline = true, HelpMessage = "The Url of the site collection to connect to.")] @@ -203,6 +230,9 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_MAIN, HelpMessage = "If you want to connect to your on-premises SharePoint farm using ADFS")] public SwitchParameter UseAdfs; + [Parameter(Mandatory = false, ParameterSetName = "Main", HelpMessage = "If you want to connect to your SharePoint farm using ADFS with Certificate Authentication")] + public SwitchParameter UseAdfsCert; + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_MAIN, HelpMessage = "Authenticate using Kerberos to an on-premises ADFS instance.")] public SwitchParameter Kerberos; @@ -217,6 +247,7 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "Specifies a minimal server healthscore before any requests are executed.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADPEM, HelpMessage = "Specifies a minimal server healthscore before any requests are executed.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADThumb, HelpMessage = "Specifies a minimal server healthscore before any requests are executed.")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCER, HelpMessage = "Specifies a minimal server healthscore before any requests are executed.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_SPOMANAGEMENT, HelpMessage = "Specifies a minimal server healthscore before any requests are executed.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_ACCESSTOKEN, HelpMessage = "Specifies a minimal server healthscore before any requests are executed.")] #endif @@ -234,6 +265,7 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "Defines how often a retry should be executed if the server healthscore is not sufficient. Default is 10 times.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADPEM, HelpMessage = "Defines how often a retry should be executed if the server healthscore is not sufficient. Default is 10 times.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADThumb, HelpMessage = "Defines how often a retry should be executed if the server healthscore is not sufficient. Default is 10 times.")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCER, HelpMessage = "Defines how often a retry should be executed if the server healthscore is not sufficient. Default is 10 times.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_SPOMANAGEMENT, HelpMessage = "Defines how often a retry should be executed if the server healthscore is not sufficient. Default is 10 times.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_ACCESSTOKEN, HelpMessage = "Defines how often a retry should be executed if the server healthscore is not sufficient. Default is 10 times.")] #endif @@ -251,6 +283,7 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "Defines how many seconds to wait before each retry. Default is 1 second.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADPEM, HelpMessage = "Defines how many seconds to wait before each retry. Default is 1 second.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADThumb, HelpMessage = "Defines how many seconds to wait before each retry. Default is 1 second.")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCER, HelpMessage = "Defines how many seconds to wait before each retry. Default is 1 second.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_SPOMANAGEMENT, HelpMessage = "Defines how many seconds to wait before each retry. Default is 1 second.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_ACCESSTOKEN, HelpMessage = "Defines how many seconds to wait before each retry. Default is 1 second.")] #endif @@ -268,6 +301,7 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "The request timeout. Default is 1800000")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADPEM, HelpMessage = "The request timeout. Default is 1800000")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADThumb, HelpMessage = "The request timeout. Default is 1800000")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCER, HelpMessage = "The request timeout. Default is 180000")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_SPOMANAGEMENT, HelpMessage = "The request timeout. Default is 1800000")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_ACCESSTOKEN, HelpMessage = "The request timeout. Default is 1800000")] #endif @@ -306,6 +340,7 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "If you want to create a PSDrive connected to the URL")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADPEM, HelpMessage = "If you want to create a PSDrive connected to the URL")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADThumb, HelpMessage = "If you want to create a PSDrive connected to the URL")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCER, HelpMessage = "If you want to create a PSDrive connected to the URL")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_SPOMANAGEMENT, HelpMessage = "If you want to create a PSDrive connected to the URL")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_ACCESSTOKEN, HelpMessage = "If you want to create a PSDrive connected to the URL")] #endif @@ -323,6 +358,7 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "Name of the PSDrive to create (default: SPO)")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADPEM, HelpMessage = "Name of the PSDrive to create (default: SPO)")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADThumb, HelpMessage = "Name of the PSDrive to create (default: SPO)")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCER, HelpMessage = "Name of the PSDrive to create (default: SPO)")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_SPOMANAGEMENT, HelpMessage = "Name of the PSDrive to create (default: SPO)")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_ACCESSTOKEN, HelpMessage = "Name of the PSDrive to create (default: SPO)")] #endif @@ -370,6 +406,7 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = true, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "The Client ID of the Azure AD Application")] [Parameter(Mandatory = true, ParameterSetName = ParameterSet_APPONLYAADPEM, HelpMessage = "The Client ID of the Azure AD Application")] [Parameter(Mandatory = true, ParameterSetName = ParameterSet_APPONLYAADThumb, HelpMessage = "The Client ID of the Azure AD Application")] + [Parameter(Mandatory = true, ParameterSetName = ParameterSet_APPONLYAADCER, HelpMessage = "The Client ID of the Azure AD Application")] #endif #if ONPREMISES [Parameter(Mandatory = true, ParameterSetName = ParameterSet_HIGHTRUST_CERT, HelpMessage = "The Client ID of the Add-In Registration in SharePoint. Used as the HighTrustCertificateIssuerId if none is specified.")] @@ -384,11 +421,18 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = true, ParameterSetName = ParameterSet_APPONLYAADPEM, HelpMessage = "The Azure AD Tenant name,e.g. mycompany.onmicrosoft.com")] [Parameter(Mandatory = true, ParameterSetName = ParameterSet_APPONLYAADThumb, HelpMessage = "The Azure AD Tenant name,e.g. mycompany.onmicrosoft.com")] [Parameter(Mandatory = true, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "The Azure AD Tenant name,e.g. mycompany.onmicrosoft.com")] + [Parameter(Mandatory = true, ParameterSetName = ParameterSet_APPONLYAADCER, HelpMessage = "The Azure AD Tenant name,e.g. mycompany.onmicrosoft.com")] public string Tenant; - [Parameter(Mandatory = true, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "Path to the certificate (*.pfx)")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "Path to the certificate containing the private key (*.pfx)")] public string CertificatePath; + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "Base64 Encoded X509Certificate2 certificate containing the private key to authenticate the requests to SharePoint Online such as retrieved in Azure Functions from Azure KeyVault")] + public string CertificateBase64Encoded; + + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "X509Certificate2 reference containing the private key to authenticate the requests to SharePoint Online")] + public System.Security.Cryptography.X509Certificates.X509Certificate2 Certificate; + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "Password to the certificate (*.pfx)")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADPEM, HelpMessage = "Password to the certificate (*.pfx)")] public SecureString CertificatePassword; @@ -410,9 +454,10 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "The Azure environment to use for authentication, the defaults to 'Production' which is the main Azure environment.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADPEM, HelpMessage = "The Azure environment to use for authentication, the defaults to 'Production' which is the main Azure environment.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADThumb, HelpMessage = "The Azure environment to use for authentication, the defaults to 'Production' which is the main Azure environment.")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCER, HelpMessage = "The Azure environment to use for authentication, the defaults to 'Production' which is the main Azure environment.")] public AzureEnvironment AzureEnvironment = AzureEnvironment.Production; -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 [Parameter(Mandatory = false, ParameterSetName = ParameterSet_MAIN, HelpMessage = "The array of permission scopes for the Microsoft Graph API.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_TOKEN, HelpMessage = "The array of permission scopes for the Microsoft Graph API.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_WEBLOGIN, HelpMessage = "The array of permission scopes for the Microsoft Graph API.")] @@ -437,6 +482,7 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "The url to the Tenant Admin site. If not specified, the cmdlets will assume to connect automatically to https://-admin.sharepoint.com where appropriate.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADPEM, HelpMessage = "The url to the Tenant Admin site. If not specified, the cmdlets will assume to connect automatically to https://-admin.sharepoint.com where appropriate.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADThumb, HelpMessage = "The url to the Tenant Admin site. If not specified, the cmdlets will assume to connect automatically to https://-admin.sharepoint.com where appropriate.")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCER, HelpMessage = "The url to the Tenant Admin site. If not specified, the cmdlets will assume to connect automatically to https://-admin.sharepoint.com where appropriate.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_SPOMANAGEMENT, HelpMessage = "The url to the Tenant Admin site. If not specified, the cmdlets will assume to connect automatically to https://-admin.sharepoint.com where appropriate.")] #endif #if ONPREMISES @@ -454,7 +500,7 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "Should we skip the check if this site is the Tenant admin site. Default is false")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADPEM, HelpMessage = "Should we skip the check if this site is the Tenant admin site. Default is false")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADThumb, HelpMessage = "Should we skip the check if this site is the Tenant admin site. Default is false")] - [Parameter(Mandatory = false, ParameterSetName = ParameterSet_SPOMANAGEMENT, HelpMessage = "Should we skip the check if this site is the Tenant admin site. Default is false")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCER, HelpMessage = "Should we skip the check if this site is the Tenant admin site. Default is false")][Parameter(Mandatory = false, ParameterSetName = ParameterSet_SPOMANAGEMENT, HelpMessage = "Should we skip the check if this site is the Tenant admin site. Default is false")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_ACCESSTOKEN, HelpMessage = "Should we skip the check if this site is the Tenant admin site. Default is false")] #endif #if ONPREMISES @@ -471,8 +517,11 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAAD, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting to a SharePoint farm using self signed certificates or using a certificate authority not trusted by this machine.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADPEM, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting to a SharePoint farm using self signed certificates or using a certificate authority not trusted by this machine.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADThumb, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting to a SharePoint farm using self signed certificates or using a certificate authority not trusted by this machine.")] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_APPONLYAADCER, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting to a SharePoint farm using self signed certificates or using a certificate authority not trusted by this machine.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_GRAPHWITHAAD, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting through a proxy to the Microsoft Graph API which has SSL interception enabled.")] +#if !NETSTANDARD2_1 [Parameter(Mandatory = false, ParameterSetName = ParameterSet_GRAPHWITHSCOPE, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting through a proxy to the Microsoft Graph API which has SSL interception enabled.")] +#endif [Parameter(Mandatory = false, ParameterSetName = ParameterSet_GRAPHDEVICELOGIN, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting through a proxy to the Microsoft Graph API which has SSL interception enabled.")] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_SPOMANAGEMENT, HelpMessage = "Ignores any SSL errors. To be used i.e. when connecting to a SharePoint farm using self signed certificates or using a certificate authority not trusted by this machine.")] #endif @@ -535,7 +584,7 @@ protected void Connect() } else if (UseWebLogin) { -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 connection = SPOnlineConnectionHelper.InstantiateWebloginConnection(new Uri(Url), MinimalHealthScore, RetryCount, RetryWait, RequestTimeout, TenantAdminUrl, Host, SkipTenantAdminCheck); #else WriteWarning(@"-UseWebLogin is not implemented, due to restrictions of the .NET Standard framework. @@ -551,7 +600,7 @@ protected void Connect() creds = Host.UI.PromptForCredential(Properties.Resources.EnterYourCredentials, "", "", ""); } } -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 connection = SPOnlineConnectionHelper.InstantiateAdfsConnection(new Uri(Url), Kerberos, creds, @@ -568,6 +617,22 @@ protected void Connect() throw new NotImplementedException(); #endif } +#if !NETSTANDARD2_1 + else if (UseAdfsCert) + { + // Modal Dialog to enable a user to select a certificate to use to authenticate against ADFS + X509Store store = new X509Store("MY", StoreLocation.CurrentUser); + store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + var certs = X509Certificate2UI.SelectFromCollection(store.Certificates, "Select ADFS User Certificate", "Selec the certificate to use to authenticate to ADFS", X509SelectionFlag.SingleSelection); + + if (certs[0] != null) + { + var serialNumber = certs[0].SerialNumber; + + SPOnlineConnection.CurrentConnection = SPOnlineConnectionHelper.InstantiateAdfsCertificateConnection(new Uri(Url), serialNumber, Host, MinimalHealthScore, RetryCount, RetryWait, RequestTimeout, TenantAdminUrl, SkipTenantAdminCheck); + } + } +#endif #if !ONPREMISES else if (ParameterSetName == ParameterSet_SPOMANAGEMENT) { @@ -587,16 +652,28 @@ protected void Connect() } else if (ParameterSetName == ParameterSet_APPONLYAAD) { -#if !NETSTANDARD2_0 - WriteWarning(@"Your certificate is copied by the operating system to c:\ProgramData\Microsoft\Crypto\RSA\MachineKeys. Over time this folder may increase heavily in size. Use Disconnect-PnPOnline in your scripts remove the certificate from this folder to clean up. Consider using -Thumbprint instead of -CertificatePath."); - connection = SPOnlineConnectionHelper.InitiateAzureADAppOnlyConnection(new Uri(Url), ClientId, Tenant, CertificatePath, CertificatePassword, MinimalHealthScore, RetryCount, RetryWait, RequestTimeout, TenantAdminUrl, Host, NoTelemetry, SkipTenantAdminCheck, AzureEnvironment); +#if !NETSTANDARD2_1 + if (MyInvocation.BoundParameters.ContainsKey("CertificatePath")) + { + connection = SPOnlineConnectionHelper.InitiateAzureADAppOnlyConnection(new Uri(Url), ClientId, Tenant, CertificatePath, CertificatePassword, MinimalHealthScore, RetryCount, RetryWait, RequestTimeout, TenantAdminUrl, Host, NoTelemetry, SkipTenantAdminCheck, AzureEnvironment); + WriteWarning(@"Your certificate is copied by the operating system to c:\ProgramData\Microsoft\Crypto\RSA\MachineKeys. Over time this folder may increase heavily in size. Use Disconnect-PnPOnline in your scripts remove the certificate from this folder to clean up. Consider using -Thumbprint instead of -CertificatePath."); + } else if (MyInvocation.BoundParameters.ContainsKey("Certificate")) + { + connection = SPOnlineConnectionHelper.InitiateAzureAdAppOnlyConnectionWithCert(new Uri(Url), ClientId, Tenant, MinimalHealthScore, RetryCount, RetryWait, RequestTimeout, TenantAdminUrl, Host, NoTelemetry, SkipTenantAdminCheck, AzureEnvironment, Certificate); + } else if (MyInvocation.BoundParameters.ContainsKey("CertificateBase64Encoded")) + { + connection = SPOnlineConnectionHelper.InitiateAzureAdAppOnlyConnectionWithCert(new Uri(Url), ClientId, Tenant, MinimalHealthScore, RetryCount, RetryWait, RequestTimeout, TenantAdminUrl, Host, NoTelemetry, SkipTenantAdminCheck, AzureEnvironment, CertificateBase64Encoded); + } else + { + throw new ArgumentException("You must either provide CertificatePath, Certificate or CertificateBase64Encoded when connecting using an Azure Active Directory registered application"); + } #else throw new NotImplementedException(); #endif } else if (ParameterSetName == ParameterSet_APPONLYAADPEM) { -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 connection = SPOnlineConnectionHelper.InitiateAzureADAppOnlyConnection(new Uri(Url), ClientId, Tenant, PEMCertificate, PEMPrivateKey, CertificatePassword, MinimalHealthScore, RetryCount, RetryWait, RequestTimeout, TenantAdminUrl, Host, NoTelemetry, SkipTenantAdminCheck, AzureEnvironment); #else throw new NotImplementedException(); @@ -604,13 +681,21 @@ protected void Connect() } else if (ParameterSetName == ParameterSet_APPONLYAADThumb) { -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 connection = SPOnlineConnectionHelper.InitiateAzureADAppOnlyConnection(new Uri(Url), ClientId, Tenant, Thumbprint, MinimalHealthScore, RetryCount, RetryWait, RequestTimeout, TenantAdminUrl, Host, NoTelemetry, SkipTenantAdminCheck, AzureEnvironment); #else throw new NotImplementedException(); #endif } -#if !NETSTANDARD2_0 + else if (ParameterSetName == ParameterSet_APPONLYAADCER) + { +#if !NETSTANDARD2_1 + connection = SPOnlineConnectionHelper.InitiateAzureADAppOnlyConnection(new Uri(Url), ClientId, Tenant, Certificate, MinimalHealthScore, RetryCount, RetryWait, RequestTimeout, TenantAdminUrl, Host, NoTelemetry, SkipTenantAdminCheck, AzureEnvironment); +#else + throw new NotImplementedException(); +#endif + } +#if !NETSTANDARD2_1 else if (ParameterSetName == ParameterSet_GRAPHWITHSCOPE) { ConnectGraphScopes(); @@ -675,7 +760,7 @@ protected void Connect() connection = SPOnlineConnectionHelper.InstantiateSPOnlineConnection(new Uri(Url), creds, Host, CurrentCredentials, MinimalHealthScore, RetryCount, RetryWait, RequestTimeout, TenantAdminUrl, NoTelemetry, SkipTenantAdminCheck, AuthenticationMode); } #if !ONPREMISES -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 if (MyInvocation.BoundParameters.ContainsKey("Scopes") && ParameterSetName != ParameterSet_GRAPHWITHSCOPE) { ConnectGraphScopes(); @@ -733,7 +818,7 @@ private SPOnlineConnection ConnectNativeAAD(string clientId, string redirectUrl) File.Delete(configFile); } } -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 return SPOnlineConnectionHelper.InitiateAzureADNativeApplicationConnection( new Uri(Url), clientId, new Uri(redirectUrl), MinimalHealthScore, RetryCount, RetryWait, RequestTimeout, TenantAdminUrl, Host, NoTelemetry, SkipTenantAdminCheck, AzureEnvironment); @@ -742,7 +827,7 @@ private SPOnlineConnection ConnectNativeAAD(string clientId, string redirectUrl) #endif } -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 private void ConnectGraphScopes() { var clientApplication = new PublicClientApplication(MSALPnPPowerShellClientId); @@ -841,7 +926,7 @@ private void ConnectGraphAAD() { var appCredentials = new ClientCredential(AppSecret); var authority = new Uri(GraphAADLogin, AADDomain).AbsoluteUri; -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 var clientApplication = new ConfidentialClientApplication(authority, AppId, RedirectUri, appCredentials, null); var authenticationResult = clientApplication.AcquireTokenForClient(GraphDefaultScope, null).GetAwaiter().GetResult(); SPOnlineConnection.AuthenticationResult = authenticationResult; diff --git a/Commands/Base/DisablePowerShellTelemetry.cs b/Commands/Base/DisablePowerShellTelemetry.cs index e052b3a61..d338eff1b 100644 --- a/Commands/Base/DisablePowerShellTelemetry.cs +++ b/Commands/Base/DisablePowerShellTelemetry.cs @@ -1,6 +1,6 @@ using SharePointPnP.PowerShell.CmdletHelpAttributes; using System; -#if NETSTANDARD2_0 +#if NETSTANDARD2_1 using System.IdentityModel.Tokens.Jwt; #else using System.IdentityModel.Tokens; diff --git a/Commands/Base/DisconnectSPOnline.cs b/Commands/Base/DisconnectSPOnline.cs index 48c41b733..4c175dc0c 100644 --- a/Commands/Base/DisconnectSPOnline.cs +++ b/Commands/Base/DisconnectSPOnline.cs @@ -25,11 +25,13 @@ protected override void ProcessRecord() #if !ONPREMISES if(SPOnlineConnection.CurrentConnection.CertFile != null) { +#if !NETSTANDARD2_1 SPOnlineConnectionHelper.CleanupCryptoMachineKey(SPOnlineConnection.CurrentConnection.CertFile); +#endif SPOnlineConnection.CurrentConnection.CertFile = null; } #endif - var success = false; + var success = false; if (Connection != null) { success = DisconnectProvidedService(Connection); @@ -47,7 +49,7 @@ protected override void ProcessRecord() if (provider != null) { //ImplementingAssembly was introduced in Windows PowerShell 5.0. -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 var drives = Host.Version.Major >= 5 ? provider.Drives.Where(d => d.Provider.Module.ImplementingAssembly.FullName == Assembly.GetExecutingAssembly().FullName) : provider.Drives; #else var drives = Host.Version.Major >= 5 ? provider.Drives.Where(d => d.Provider.Module.Name == Assembly.GetExecutingAssembly().FullName) : provider.Drives; @@ -61,25 +63,37 @@ protected override void ProcessRecord() internal static bool DisconnectProvidedService(SPOnlineConnection connection) { - if (connection == null) - return false; connection.AccessToken = string.Empty; Environment.SetEnvironmentVariable("PNPPSHOST", string.Empty); Environment.SetEnvironmentVariable("PNPPSSITE", string.Empty); + if (connection == null) + { + return false; + } + connection.Context = null; connection = null; return true; } internal static bool DisconnectCurrentService() - { - if (SPOnlineConnection.CurrentConnection == null) - return false; - SPOnlineConnection.CurrentConnection.AccessToken = string.Empty; + { Environment.SetEnvironmentVariable("PNPPSHOST", string.Empty); Environment.SetEnvironmentVariable("PNPPSSITE", string.Empty); - SPOnlineConnection.CurrentConnection.Context = null; - SPOnlineConnection.CurrentConnection = null; + if (SPOnlineConnection.CurrentConnection == null && SPOnlineConnection.AuthenticationResult == null) + { + return false; + } + if (SPOnlineConnection.CurrentConnection != null) + { + SPOnlineConnection.CurrentConnection.AccessToken = string.Empty; + SPOnlineConnection.CurrentConnection.Context = null; + SPOnlineConnection.CurrentConnection = null; + } + if (SPOnlineConnection.AuthenticationResult != null) + { + SPOnlineConnection.AuthenticationResult = null; + } return true; } } diff --git a/Commands/Base/GetAccessToken.cs b/Commands/Base/GetAccessToken.cs index 455016896..f453e98ac 100644 --- a/Commands/Base/GetAccessToken.cs +++ b/Commands/Base/GetAccessToken.cs @@ -1,7 +1,7 @@ using SharePointPnP.PowerShell.CmdletHelpAttributes; using System; using System.Collections.Generic; -#if NETSTANDARD2_0 +#if NETSTANDARD2_1 using System.IdentityModel.Tokens.Jwt; #else using System.IdentityModel.Tokens; diff --git a/Commands/Base/GetHealthScore.cs b/Commands/Base/GetHealthScore.cs index e7fe32fb2..5848f9297 100644 --- a/Commands/Base/GetHealthScore.cs +++ b/Commands/Base/GetHealthScore.cs @@ -6,10 +6,11 @@ namespace SharePointPnP.PowerShell.Commands.Base { [Cmdlet(VerbsCommon.Get, "PnPHealthScore")] - [CmdletHelp("Retrieves the healthscore value.", - "Retrieves the current X-SharePointHealthScore value of the server, or CPU, on which your SharePoint instance runs. X-SharePointHealthScore is a value between 0 and 10, where 0 indicates the server is idle and 10 indicates the server is very busy. For more information visit https://docs.microsoft.com/office365/enterprise/diagnosing-performance-issues-with-sharepoint-online and https://docs.microsoft.com/openspecs/sharepoint_protocols/ms-wsshp/c60ddeb6-4113-4a73-9e97-26b5c3907d33.", + [CmdletHelp("Retrieves the healthscore of the site given in his Url parameter or from the current connection if the Url parameter is not provided", + "Retrieves the current X-SharePointHealthScore value of the server, or CPU, on which your SharePoint instance runs. X-SharePointHealthScore is a value between 0 and 10, where 0 indicates the server is idle and 10 indicates the server is very busy. For more information visit https://docs.microsoft.com/office365/enterprise/diagnosing-performance-issues-with-sharepoint-online and https://docs.microsoft.com/openspecs/sharepoint_protocols/ms-wsshp/c60ddeb6-4113-4a73-9e97-26b5c3907d33.", Category = CmdletHelpCategory.Base, OutputType=typeof(int), + SupportedPlatform = CmdletSupportedPlatform.OnPremises, OutputTypeDescription = "Returns a int value representing the current health score value of the server.")] [CmdletExample( Code = "PS:> Get-PnPHealthScore", @@ -19,6 +20,9 @@ namespace SharePointPnP.PowerShell.Commands.Base Code = "PS:> Get-PnPHealthScore -Url https://contoso.sharepoint.com", Remarks = @"This will retrieve the current health score for the url https://contoso.sharepoint.com.", SortOrder = 2)] +#if !ONPREMISES + [Obsolete("Get-PnPHealthScore does not return valid data when using SharePoint Online")] +#endif public class GetHealthScore : PSCmdlet { [Parameter(Mandatory = false, HelpMessage = "The url of the WebApplication to retrieve the health score from", ValueFromPipeline = true)] diff --git a/Commands/Base/GetStoredCredential.cs b/Commands/Base/GetStoredCredential.cs index b7ff63465..fbb23dba4 100644 --- a/Commands/Base/GetStoredCredential.cs +++ b/Commands/Base/GetStoredCredential.cs @@ -9,7 +9,7 @@ namespace SharePointPnP.PowerShell.Commands.Base { [Cmdlet(VerbsCommon.Get, "PnPStoredCredential")] [CmdletHelp("Get a credential", -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 "Returns a stored credential from the Windows Credential Manager", #else "Returns a stored credential from the Windows Credential Manager or the MacOS KeyChain", @@ -18,7 +18,7 @@ namespace SharePointPnP.PowerShell.Commands.Base [CmdletExample(Code = "PS:> Get-PnPStoredCredential -Name O365", Remarks = "Returns the credential associated with the specified identifier", SortOrder = 1)] -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 [CmdletExample(Code = "PS:> Get-PnPStoredCredential -Name testEnvironment -Type OnPrem", Remarks = "Gets the credential associated with the specified identifier from the credential manager and then will return a credential that can be used for on-premises authentication", SortOrder = 2)] @@ -28,14 +28,14 @@ public class GetStoredCredential : PSCmdlet [Parameter(Mandatory = true, HelpMessage = "The credential to retrieve.")] public string Name; -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 [Parameter(Mandatory = false, HelpMessage = "The object type of the credential to return from the Credential Manager. Possible values are 'O365', 'OnPrem' or 'PSCredential'")] public CredentialType Type = CredentialType.O365; #endif protected override void ProcessRecord() { -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 var cred = Utilities.CredentialManager.GetCredential(Name); if (cred != null) { diff --git a/Commands/Base/InvokeSPRestMethod.cs b/Commands/Base/InvokeSPRestMethod.cs index 41fe59c5b..0d303adc2 100644 --- a/Commands/Base/InvokeSPRestMethod.cs +++ b/Commands/Base/InvokeSPRestMethod.cs @@ -13,7 +13,9 @@ using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; +#if !NETSTANDARD2_1 using System.Web.Script.Serialization; +#endif namespace SharePointPnP.PowerShell.Commands.Admin { @@ -123,7 +125,11 @@ protected override void ExecuteCmdlet() var responseString = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); if (responseString != null) { +#if NETSTANDARD2_1 + WriteObject(System.Text.Json.JsonSerializer.Deserialize(responseString)); +#else WriteObject(new JavaScriptSerializer().DeserializeObject(responseString)); +#endif } } else diff --git a/Commands/Base/PipeBinds/PagePipeBind.cs b/Commands/Base/PipeBinds/PagePipeBind.cs index c7c04d9f0..3e14366d7 100644 --- a/Commands/Base/PipeBinds/PagePipeBind.cs +++ b/Commands/Base/PipeBinds/PagePipeBind.cs @@ -1,9 +1,12 @@ #if !SP2013 && !SP2016 && !SP2019 using Microsoft.SharePoint.Client; using OfficeDevPnP.Core.Utilities; +#if !NETSTANDARD2_1 using SharePointPnP.Modernization.Framework.Transform; +#endif using SharePointPnP.PowerShell.Commands.ClientSidePages; using System; +using System.Text.Encodings.Web; namespace SharePointPnP.PowerShell.Commands.Base.PipeBinds { @@ -112,6 +115,7 @@ internal ListItem GetPage(Web web, string listToLoad) var listServerRelativeUrl = UrlUtility.Combine(web.ServerRelativeUrl, listToLoad); List libraryContainingPage = null; +#if !NETSTANDARD2_1 if (BaseTransform.GetVersion(web.Context) == SPVersion.SP2010) { libraryContainingPage = web.GetListByName(listToLoad); @@ -120,6 +124,9 @@ internal ListItem GetPage(Web web, string listToLoad) { libraryContainingPage = web.GetList(listServerRelativeUrl); } +#else + libraryContainingPage = web.GetList(listServerRelativeUrl); +#endif if (libraryContainingPage != null) { @@ -139,14 +146,14 @@ internal ListItem GetPage(Web web, string listToLoad) { query = new CamlQuery { - ViewXml = string.Format(CAMLQueryForBlogByTitle, this.name) + ViewXml = string.Format(CAMLQueryForBlogByTitle, System.Text.Encodings.Web.HtmlEncoder.Default.Encode(this.name)) }; } else { query = new CamlQuery { - ViewXml = string.Format(CAMLQueryByExtensionAndName, this.name) + ViewXml = string.Format(CAMLQueryByExtensionAndName, System.Text.Encodings.Web.HtmlEncoder.Default.Encode(this.name)) }; } diff --git a/Commands/Base/PnPCmdlet.cs b/Commands/Base/PnPCmdlet.cs index 6b2d56f06..2e84cc9e6 100644 --- a/Commands/Base/PnPCmdlet.cs +++ b/Commands/Base/PnPCmdlet.cs @@ -112,7 +112,9 @@ protected override void ProcessRecord() } catch (System.Management.Automation.PipelineStoppedException) { - //swallow pipeline stopped exception + //don't swallow pipeline stopped exception + //it makes select-object work weird + throw; } catch (Exception ex) { diff --git a/Commands/Base/PnPGraphCmdlet.cs b/Commands/Base/PnPGraphCmdlet.cs index 16d5cc692..f815e741a 100644 --- a/Commands/Base/PnPGraphCmdlet.cs +++ b/Commands/Base/PnPGraphCmdlet.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 using Microsoft.Graph; #endif using SharePointPnP.PowerShell.Commands.Properties; @@ -32,14 +32,14 @@ public String AccessToken } else { -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 return (SPOnlineConnection.AuthenticationResult.Token); #else return SPOnlineConnection.AuthenticationResult.AccessToken; #endif } } - else if (SPOnlineConnection.CurrentConnection.AccessToken != null) + else if (SPOnlineConnection.CurrentConnection?.AccessToken != null) { return SPOnlineConnection.CurrentConnection.AccessToken; } @@ -70,7 +70,7 @@ protected override void BeginProcessing() base.BeginProcessing(); -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 //if (SPOnlineConnection.CurrentConnection != null && SPOnlineConnection.CurrentConnection.ConnectionMethod == Model.ConnectionMethod.GraphDeviceLogin) //{ diff --git a/Commands/Base/RemoveStoredCredential.cs b/Commands/Base/RemoveStoredCredential.cs index be2d1ee1d..8f43bbd88 100644 --- a/Commands/Base/RemoveStoredCredential.cs +++ b/Commands/Base/RemoveStoredCredential.cs @@ -5,14 +5,14 @@ namespace SharePointPnP.PowerShell.Commands.Base { [Cmdlet(VerbsCommon.Remove, "PnPStoredCredential")] [CmdletHelp("Removes a credential", -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 "Removes a stored credential from the Windows Credential Manager", #else "Removes a stored credential from the Windows Credential Manager or the MacOS KeyChain", #endif Category = CmdletHelpCategory.Base)] [CmdletExample(Code = "PS:> Remove-PnPStoredCredential -Name https://tenant.sharepoint.com", -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 Remarks = "Removes the specified credential from the Windows Credential Manager", #else Remarks = "Removes the specified credential from the Windows Credential Manager or the MacOS KeyChain", diff --git a/Commands/Base/RequestAccessToken.cs b/Commands/Base/RequestAccessToken.cs index 2b78eb35b..b63544cb4 100644 --- a/Commands/Base/RequestAccessToken.cs +++ b/Commands/Base/RequestAccessToken.cs @@ -12,6 +12,7 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; +using System.Web; namespace SharePointPnP.PowerShell.Commands.Base { @@ -117,13 +118,13 @@ protected override void ProcessRecord() if (!string.IsNullOrEmpty(Resource)) { - body = $"grant_type=password&client_id={ClientId}&username={username}&password={password}&resource={Resource}"; + body = $"grant_type=password&client_id={ClientId}&username={HttpUtility.UrlEncode(username)}&password={HttpUtility.UrlEncode(password)}&resource={Resource}"; response = HttpHelper.MakePostRequestForString($"https://login.microsoftonline.com/{tenantId}/oauth2/token", body, "application/x-www-form-urlencoded"); } else { var scopes = string.Join(" ", Scopes); - body = $"grant_type=password&client_id={ClientId}&username={username}&password={password}&scope={scopes}"; + body = $"grant_type=password&client_id={ClientId}&username={HttpUtility.UrlEncode(username)}&password={HttpUtility.UrlEncode(password)}&scope={scopes}"; response = HttpHelper.MakePostRequestForString($"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", body, "application/x-www-form-urlencoded"); } var json = JToken.Parse(response); diff --git a/Commands/Base/SPOnlineConnection.cs b/Commands/Base/SPOnlineConnection.cs index 3afb7a9e9..270ba1280 100644 --- a/Commands/Base/SPOnlineConnection.cs +++ b/Commands/Base/SPOnlineConnection.cs @@ -37,6 +37,8 @@ public class SPOnlineConnection public int RetryCount { get; protected set; } public int RetryWait { get; protected set; } public PSCredential PSCredential { get; protected set; } + public string ClientId { get; protected set; } + public string ClientSecret { get; protected set; } public TelemetryClient TelemetryClient { get; set; } @@ -100,6 +102,12 @@ internal string AccessToken } } + internal SPOnlineConnection(ClientContext context, ConnectionType connectionType, int minimalHealthScore, int retryCount, int retryWait, PSCredential credential, string clientId, string clientSecret, string url, string tenantAdminUrl, string pnpVersionTag, System.Management.Automation.Host.PSHost host, bool disableTelemetry, InitializationType initializationType) + : this(context, connectionType, minimalHealthScore, retryCount, retryWait, credential, url, tenantAdminUrl, pnpVersionTag, host, disableTelemetry, initializationType) + { + this.ClientId = clientId; + this.ClientSecret = clientSecret; + } internal SPOnlineConnection(ClientContext context, ConnectionType connectionType, int minimalHealthScore, int retryCount, int retryWait, PSCredential credential, string url, string tenantAdminUrl, string pnpVersionTag, System.Management.Automation.Host.PSHost host, bool disableTelemetry, InitializationType initializationType) { @@ -205,7 +213,7 @@ internal ClientContext CloneContext(string url) } catch (Exception ex) { -#if !ONPREMISES && !NETSTANDARD2_0 +#if !ONPREMISES && !NETSTANDARD2_1 if ((ex is WebException || ex is NotSupportedException) && CurrentConnection.PSCredential != null) { // legacy auth? @@ -217,7 +225,7 @@ internal ClientContext CloneContext(string url) { #endif throw; -#if !ONPREMISES && !NETSTANDARD2_0 +#if !ONPREMISES && !NETSTANDARD2_1 } #endif } @@ -298,12 +306,22 @@ internal void InitializeTelemetry(ClientContext context, PSHost host, Initializa TelemetryClient.Context.Session.Id = Guid.NewGuid().ToString(); TelemetryClient.Context.Cloud.RoleInstance = "PnPPowerShell"; TelemetryClient.Context.Device.OperatingSystem = Environment.OSVersion.ToString(); +#if !NETSTANDARD2_1 TelemetryClient.Context.GlobalProperties.Add("ServerLibraryVersion", serverLibraryVersion); TelemetryClient.Context.GlobalProperties.Add("ServerVersion", serverVersion); TelemetryClient.Context.GlobalProperties.Add("ConnectionMethod", initializationType.ToString()); +#else + TelemetryClient.Context.Properties.Add("ServerLibraryVersion", serverLibraryVersion); + TelemetryClient.Context.Properties.Add("ServerVersion", serverVersion); + TelemetryClient.Context.Properties.Add("ConnectionMethod", initializationType.ToString()); +#endif var coreAssembly = Assembly.GetExecutingAssembly(); - +#if !NETSTANDARD2_1 TelemetryClient.Context.GlobalProperties.Add("Version", ((AssemblyFileVersionAttribute)coreAssembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute))).Version.ToString()); +#else + TelemetryClient.Context.Properties.Add("Version", ((AssemblyFileVersionAttribute)coreAssembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute))).Version.ToString()); +#endif + #if SP2013 TelemetryClient.Context.GlobalProperties.Add("Platform", "SP2013"); #elif SP2016 @@ -311,7 +329,11 @@ internal void InitializeTelemetry(ClientContext context, PSHost host, Initializa #elif SP2019 TelemetryClient.Context.GlobalProperties.Add("Platform", "SP2019"); #else +#if !NETSTANDARD2_1 TelemetryClient.Context.GlobalProperties.Add("Platform", "SPO"); +#else + TelemetryClient.Context.Properties.Add("Platform", "SPO"); +#endif #endif TelemetryClient.TrackEvent("Connect-PnPOnline"); } diff --git a/Commands/Base/SPOnlineConnectionHelper.cs b/Commands/Base/SPOnlineConnectionHelper.cs index 4bd45199b..5afe65fec 100644 --- a/Commands/Base/SPOnlineConnectionHelper.cs +++ b/Commands/Base/SPOnlineConnectionHelper.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 using Microsoft.IdentityModel.Clients.ActiveDirectory; #endif using Microsoft.Online.SharePoint.TenantAdministration; @@ -34,7 +34,7 @@ internal class SPOnlineConnectionHelper #endif private static bool VersionChecked; -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 public static AuthenticationContext AuthContext { get; set; } #endif static SPOnlineConnectionHelper() @@ -78,10 +78,10 @@ internal static SPOnlineConnection InstantiateSPOnlineConnection(Uri url, string connectionType = ConnectionType.TenantAdmin; } } - return new SPOnlineConnection(context, connectionType, minimalHealthScore, retryCount, retryWait, null, url.ToString(), tenantAdminUrl, PnPPSVersionTag, host, disableTelemetry, InitializationType.SPClientSecret); + return new SPOnlineConnection(context, connectionType, minimalHealthScore, retryCount, retryWait, null, clientId, clientSecret, url.ToString(), tenantAdminUrl, PnPPSVersionTag, host, disableTelemetry, InitializationType.SPClientSecret); } -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 #if ONPREMISES internal static SPOnlineConnection InstantiateHighTrustConnection(string url, string clientId, string hightrustCertificatePath, string hightrustCertificatePassword, string hightrustCertificateIssuerId, int minimalHealthScore, int retryCount, int retryWait, int requestTimeout, string tenantAdminUrl, PSHost host, bool disableTelemetry, bool skipAdminCheck) { @@ -133,7 +133,7 @@ internal static SPOnlineConnection InstantiateDeviceLoginConnection(string url, { Utilities.Clipboard.Copy(returnData["user_code"]); messageCallback("Code has been copied to clipboard"); -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 BrowserHelper.OpenBrowser(returnData["verification_url"], (success) => { if (success) @@ -208,7 +208,7 @@ internal static SPOnlineConnection InstantiateGraphDeviceLoginConnection(bool la { Utilities.Clipboard.Copy(returnData["user_code"]); messageCallback("Code has been copied to clipboard"); -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 BrowserHelper.OpenBrowser(returnData["verification_url"], (success) => { if (success) @@ -246,20 +246,19 @@ internal static SPOnlineConnection InstantiateGraphDeviceLoginConnection(bool la { messageCallback(returnData["message"]); - var tokenResult = GetTokenResult(connectionUri, returnData, messageCallback, progressCallback, cancelRequest); if (tokenResult != null) { progressCallback("Token received"); spoConnection = new SPOnlineConnection(tokenResult, ConnectionMethod.GraphDeviceLogin, ConnectionType.O365, minimalHealthScore, retryCount, retryWait, PnPPSVersionTag, host, disableTelemetry, InitializationType.GraphDeviceLogin); + spoConnection.ConnectionMethod = ConnectionMethod.GraphDeviceLogin; } else { progressCallback("No token received."); } - } - spoConnection.ConnectionMethod = ConnectionMethod.GraphDeviceLogin; + } return spoConnection; } @@ -334,7 +333,7 @@ internal static void OpenBrowser(string url) } } } -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 #if !ONPREMISES internal static SPOnlineConnection InitiateAzureADNativeApplicationConnection(Uri url, string clientId, Uri redirectUri, int minimalHealthScore, int retryCount, int retryWait, int requestTimeout, string tenantAdminUrl, PSHost host, bool disableTelemetry, bool skipAdminCheck = false, AzureEnvironment azureEnvironment = AzureEnvironment.Production) { @@ -363,6 +362,27 @@ internal static SPOnlineConnection InitiateAzureADNativeApplicationConnection(Ur return spoConnection; } + internal static SPOnlineConnection InitiateAzureADAppOnlyConnection(Uri url, string clientId, string tenant, X509Certificate2 certificate, int minimalHealthScore, int retryCount, int retryWait, int requestTimeout, string tenantAdminUrl, PSHost host, bool disableTelemetry, bool skipAdminCheck = false, AzureEnvironment azureEnvironment = AzureEnvironment.Production) + { + var authManager = new OfficeDevPnP.Core.AuthenticationManager(); + var context = PnPClientContext.ConvertFrom(authManager.GetAzureADAppOnlyAuthenticatedContext(url.ToString(), clientId, tenant, certificate, azureEnvironment), retryCount, retryWait * 1000); + var connectionType = ConnectionType.OnPrem; + if (url.Host.ToUpperInvariant().EndsWith("SHAREPOINT.COM")) + { + connectionType = ConnectionType.O365; + } + if (skipAdminCheck == false) + { + if (IsTenantAdminSite(context)) + { + connectionType = ConnectionType.TenantAdmin; + } + } + var spoConnection = new SPOnlineConnection(context, connectionType, minimalHealthScore, retryCount, retryWait, null, url.ToString(), tenantAdminUrl, PnPPSVersionTag, host, disableTelemetry,InitializationType.AADAppOnly); + spoConnection.ConnectionMethod = Model.ConnectionMethod.AzureADAppOnly; + return spoConnection; + } + internal static SPOnlineConnection InitiateAzureADAppOnlyConnection(Uri url, string clientId, string tenant, string certificatePath, SecureString certificatePassword, int minimalHealthScore, int retryCount, int retryWait, int requestTimeout, string tenantAdminUrl, PSHost host, bool disableTelemetry, bool skipAdminCheck = false, AzureEnvironment azureEnvironment = AzureEnvironment.Production) { X509Certificate2 certificate = CertificateHelper.GetCertificateFromPath(certificatePath, certificatePassword); @@ -391,6 +411,57 @@ internal static SPOnlineConnection InitiateAzureADAppOnlyConnection(Uri url, str return InitiateAzureAdAppOnlyConnectionWithCert(url, clientId, tenant, minimalHealthScore, retryCount, retryWait, requestTimeout, tenantAdminUrl, host, disableTelemetry, skipAdminCheck, azureEnvironment, certificate, true); } + /// + /// Takes a certificate encoded in Base64 such as retrieved from Azure KeyVault when using Azure Functions to authenticate to SharePoint + /// + /// See https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread how to set up a certificate which you can store in Azure KeyVault + /// Url of the SharePoint site to connect to + /// Application/client ID of the Azure Active Directory application registration + /// Tenant name to connect to, i.e. contoso.onmicrosoft.com + /// Value between 0 and 10 indicating the health of the SharePoint server connected to should report before commands get executed where 0 is the healthiest and 10 the least healthy. I.e. if you set it to 3, SharePoint must report a health score of 0, 1, 2 or 3 before it will execute the commands. If set to -1, no health check will be performed. + /// Amount of times to retry an operation that i.e. times out or runs into health issues before giving up on it + /// Time in seconds to wait between retry attempts + /// Time in milliseconds to allow a command to complete before considering it failed + /// Url of the admin site of the tenant. If not provided, it will assume to connect automatically to https://-admin.sharepoint.com. + /// Reference to the PowerShell session in which the commands will be executed + /// Boolean indicating whether or not telemetry should be disabled + /// Boolean indicating if it should check if the connection is being made to the Tenand admin site + /// Type of Azure environment connecting to + /// Base64 encoded string containing the certificate which grants access to SharePoint Online + /// A connection to SharePoint + internal static SPOnlineConnection InitiateAzureAdAppOnlyConnectionWithCert(Uri url, string clientId, string tenant, + int minimalHealthScore, int retryCount, int retryWait, int requestTimeout, string tenantAdminUrl, PSHost host, bool disableTelemetry, + bool skipAdminCheck, AzureEnvironment azureEnvironment, string base64EncodedCertificate) + { + X509Certificate2 certificate = CertificateHelper.GetCertificateFromBase64Encodedstring(base64EncodedCertificate); + return InitiateAzureAdAppOnlyConnectionWithCert(url, clientId, tenant, minimalHealthScore, retryCount, retryWait, requestTimeout, tenantAdminUrl, host, disableTelemetry, skipAdminCheck, azureEnvironment, certificate); + } + + /// + /// Takes a certificate encoded in Base64 such as retrieved from Azure KeyVault when using Azure Functions to authenticate to SharePoint + /// + /// See https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/security-apponly-azuread how to set up a certificate which you can store in Azure KeyVault + /// Url of the SharePoint site to connect to + /// Application/client ID of the Azure Active Directory application registration + /// Tenant name to connect to, i.e. contoso.onmicrosoft.com + /// Value between 0 and 10 indicating the health of the SharePoint server connected to should report before commands get executed where 0 is the healthiest and 10 the least healthy. I.e. if you set it to 3, SharePoint must report a health score of 0, 1, 2 or 3 before it will execute the commands. If set to -1, no health check will be performed. + /// Amount of times to retry an operation that i.e. times out or runs into health issues before giving up on it + /// Time in seconds to wait between retry attempts + /// Time in milliseconds to allow a command to complete before considering it failed + /// Url of the admin site of the tenant. If not provided, it will assume to connect automatically to https://-admin.sharepoint.com. + /// Reference to the PowerShell session in which the commands will be executed + /// Boolean indicating whether or not telemetry should be disabled + /// Boolean indicating if it should check if the connection is being made to the Tenand admin site + /// Type of Azure environment connecting to + /// The X509Certificate2 which grants access to SharePoint Online + /// A connection to SharePoint + internal static SPOnlineConnection InitiateAzureAdAppOnlyConnectionWithCert(Uri url, string clientId, string tenant, + int minimalHealthScore, int retryCount, int retryWait, int requestTimeout, string tenantAdminUrl, PSHost host, bool disableTelemetry, + bool skipAdminCheck, AzureEnvironment azureEnvironment, X509Certificate2 certificate) + { + return InitiateAzureAdAppOnlyConnectionWithCert(url, clientId, tenant, minimalHealthScore, retryCount, retryWait, requestTimeout, tenantAdminUrl, host, disableTelemetry, skipAdminCheck, azureEnvironment, certificate, false); + } + private static SPOnlineConnection InitiateAzureAdAppOnlyConnectionWithCert(Uri url, string clientId, string tenant, int minimalHealthScore, int retryCount, int retryWait, int requestTimeout, string tenantAdminUrl, PSHost host, bool disableTelemetry, bool skipAdminCheck, AzureEnvironment azureEnvironment, X509Certificate2 certificate, bool certificateFromFile) @@ -469,7 +540,7 @@ internal static SPOnlineConnection InitiateAccessTokenConnection(Uri url, string } #endif -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 internal static SPOnlineConnection InstantiateWebloginConnection(Uri url, int minimalHealthScore, int retryCount, int retryWait, int requestTimeout, string tenantAdminUrl, PSHost host, bool disableTelemetry, bool skipAdminCheck = false) { var authManager = new OfficeDevPnP.Core.AuthenticationManager(); @@ -540,12 +611,17 @@ internal static SPOnlineConnection InstantiateSPOnlineConnection(Uri url, PSCred context.ExecuteQueryRetry(); } #if !ONPREMISES - catch (NotSupportedException) + catch (NotSupportedException nox) { +#if NETSTANDARD2_1 + // Legacy auth is not supported with .NET Standard + throw nox; +#else // legacy auth? var authManager = new OfficeDevPnP.Core.AuthenticationManager(); context = PnPClientContext.ConvertFrom(authManager.GetAzureADCredentialsContext(url.ToString(), credentials.UserName, credentials.Password)); context.ExecuteQueryRetry(); +#endif } #endif catch (ClientRequestException) @@ -611,7 +687,7 @@ internal static SPOnlineConnection InstantiateSPOnlineConnection(Uri url, PSCred return spoConnection; } -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 internal static SPOnlineConnection InstantiateAdfsConnection(Uri url, bool useKerberos, PSCredential credentials, PSHost host, int minimalHealthScore, int retryCount, int retryWait, int requestTimeout, string tenantAdminUrl, bool disableTelemetry, bool skipAdminCheck = false, string loginProviderName = null) { var authManager = new OfficeDevPnP.Core.AuthenticationManager(); @@ -672,7 +748,43 @@ internal static SPOnlineConnection InstantiateAdfsConnection(Uri url, bool useKe } } var spoConnection = new SPOnlineConnection(context, connectionType, minimalHealthScore, retryCount, retryWait, null, url.ToString(), tenantAdminUrl, PnPPSVersionTag, host, disableTelemetry, InitializationType.ADFS); - spoConnection.ConnectionMethod = Model.ConnectionMethod.ADFS; + spoConnection.ConnectionMethod = ConnectionMethod.ADFS; + return spoConnection; + } + + internal static SPOnlineConnection InstantiateAdfsCertificateConnection(Uri url, string serialNumber, PSHost host, int minimalHealthScore, int retryCount, int retryWait, int requestTimeout, string tenantAdminUrl, bool disableTelemetry, bool skipAdminCheck = false, string loginProviderName = null) + { + var authManager = new OfficeDevPnP.Core.AuthenticationManager(); + + string adfsHost; + string adfsRelyingParty; + OfficeDevPnP.Core.AuthenticationManager.GetAdfsConfigurationFromTargetUri(url, loginProviderName, out adfsHost, out adfsRelyingParty); + + if (string.IsNullOrEmpty(adfsHost) || string.IsNullOrEmpty(adfsRelyingParty)) + { + throw new Exception("Cannot retrieve ADFS settings."); + } + + var context = authManager.GetADFSCertificateMixedAuthenticationContext(url.ToString(), serialNumber, adfsHost, adfsRelyingParty); + + context.ApplicationName = Properties.Resources.ApplicationName; + context.RequestTimeout = requestTimeout; +#if !ONPREMISES + context.DisableReturnValueCache = true; +#elif SP2016 || SP2019 + context.DisableReturnValueCache = true; +#endif + var connectionType = ConnectionType.OnPrem; + + if (skipAdminCheck == false) + { + if (IsTenantAdminSite(context)) + { + connectionType = ConnectionType.TenantAdmin; + } + } + var spoConnection = new SPOnlineConnection(context, connectionType, minimalHealthScore, retryCount, retryWait, null, url.ToString(), tenantAdminUrl, PnPPSVersionTag, host, disableTelemetry, InitializationType.ADFS); + spoConnection.ConnectionMethod = ConnectionMethod.ADFS; return spoConnection; } #endif diff --git a/Commands/Base/SetTraceLog.cs b/Commands/Base/SetTraceLog.cs index a9339fefb..d9cfc6dba 100644 --- a/Commands/Base/SetTraceLog.cs +++ b/Commands/Base/SetTraceLog.cs @@ -33,6 +33,9 @@ public class SetTraceLog : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = "On", HelpMessage = "The path and filename of the file to write the trace log to.")] public string LogFile; + [Parameter(Mandatory = false, ParameterSetName = "On", HelpMessage = "Turn on console trace output.")] + public SwitchParameter WriteToConsole; + [Parameter(Mandatory = false, ParameterSetName = "On", HelpMessage = "The level of events to capture. Possible values are 'Debug', 'Error', 'Warning', 'Information'. Defaults to 'Information'.")] public OfficeDevPnP.Core.Diagnostics.LogLevel Level = OfficeDevPnP.Core.Diagnostics.LogLevel.Information; @@ -48,76 +51,74 @@ public class SetTraceLog : PSCmdlet [Parameter(Mandatory = true, ParameterSetName = "Off", HelpMessage = "Turn off tracing to log file.")] public SwitchParameter Off; - private const string Listenername = "PNPPOWERSHELLTRACELISTENER"; + private const string FileListenername = "PNPPOWERSHELLFILETRACELISTENER"; + private const string ConsoleListenername = "PNPPOWERSHELLCONSOLETRACELISTENER"; protected override void ProcessRecord() { if (ParameterSetName == "On") { - var existingListener = Trace.Listeners[Listenername]; - if (existingListener != null) + // Setup Console Listener if Console switch has been specified or No file LogFile parameter has been set + if (WriteToConsole.IsPresent || string.IsNullOrEmpty(LogFile)) { - existingListener.Flush(); - existingListener.Close(); - Trace.Listeners.Remove(existingListener); + RemoveListener(ConsoleListenername); +#if !NETSTANDARD2_1 + ConsoleTraceListener consoleListener = new ConsoleTraceListener(false); + consoleListener.Name = ConsoleListenername; + Trace.Listeners.Add(consoleListener); + OfficeDevPnP.Core.Diagnostics.Log.LogLevel = Level; +#else + WriteWarning("Console logging not supported"); +#endif } + // Setup File Listener if (!string.IsNullOrEmpty(LogFile)) { + RemoveListener(FileListenername); + if (!System.IO.Path.IsPathRooted(LogFile)) { LogFile = System.IO.Path.Combine(SessionState.Path.CurrentFileSystemLocation.Path, LogFile); } - if (!string.IsNullOrEmpty(Delimiter)) - { - DelimitedListTraceListener delimitedListener = new DelimitedListTraceListener(LogFile) - { - Delimiter = Delimiter, - TraceOutputOptions = TraceOptions.DateTime, - Name = Listenername - }; - Trace.Listeners.Add(delimitedListener); - OfficeDevPnP.Core.Diagnostics.Log.LogLevel = Level; - } - else - { - TextWriterTraceListener listener = new TextWriterTraceListener(LogFile); - listener.Name = Listenername; - Trace.Listeners.Add(listener); - OfficeDevPnP.Core.Diagnostics.Log.LogLevel = Level; - } - } - else - { -#if !NETSTANDARD2_0 - ConsoleTraceListener consoleListener = new ConsoleTraceListener(false); - consoleListener.Name = Listenername; - Trace.Listeners.Add(consoleListener); + // Create DelimitedListTraceListener in case Delimiter parameter has been specified, if not create TextWritterTraceListener + TraceListener listener = !string.IsNullOrEmpty(Delimiter) ? + new DelimitedListTraceListener(LogFile) { Delimiter = Delimiter, TraceOutputOptions = TraceOptions.DateTime } : + new TextWriterTraceListener(LogFile); + + listener.Name = FileListenername; + Trace.Listeners.Add(listener); OfficeDevPnP.Core.Diagnostics.Log.LogLevel = Level; -#else - WriteWarning("Console logging not supported"); -#endif } + Trace.AutoFlush = AutoFlush; Trace.IndentSize = IndentSize; } else { - try - { - Trace.Flush(); - var traceListener = Trace.Listeners[Listenername]; - if (traceListener != null) - { - traceListener.Close(); - Trace.Listeners.Remove(Listenername); - } - } - catch (Exception) + Trace.Flush(); + RemoveListener(ConsoleListenername); + RemoveListener(FileListenername); + } + } + + private void RemoveListener(string listenerName) + { + try + { + var existingListener = Trace.Listeners[listenerName]; + if (existingListener != null) { - // ignored + existingListener.Flush(); + existingListener.Close(); + Trace.Listeners.Remove(existingListener); } } + catch (Exception) + { + // ignored + } + } } } \ No newline at end of file diff --git a/Commands/Base/TokenHandling.cs b/Commands/Base/TokenHandling.cs index 08890fac6..4b6783af6 100644 --- a/Commands/Base/TokenHandling.cs +++ b/Commands/Base/TokenHandling.cs @@ -1,6 +1,7 @@ using Microsoft.SharePoint.Client; using Newtonsoft.Json.Linq; using OfficeDevPnP.Core.Utilities; +using System.Web; namespace SharePointPnP.PowerShell.Commands.Base { @@ -9,14 +10,34 @@ internal static class TokenHandler { internal static string AcquireToken(string resource, string scope = null) { + if(SPOnlineConnection.CurrentConnection == null) + { + return null; + } + var tenantId = TenantExtensions.GetTenantIdByUrl(SPOnlineConnection.CurrentConnection.Url); if (tenantId == null) return null; - var clientId = "31359c7f-bd7e-475c-86db-fdb8c937548e"; - var username = SPOnlineConnection.CurrentConnection.PSCredential.UserName; - var password = EncryptionUtility.ToInsecureString(SPOnlineConnection.CurrentConnection.PSCredential.Password); - var body = $"grant_type=password&client_id={clientId}&username={username}&password={password}&resource={resource}"; + string body = ""; + if (SPOnlineConnection.CurrentConnection.PSCredential != null) + { + var clientId = "31359c7f-bd7e-475c-86db-fdb8c937548e"; + var username = SPOnlineConnection.CurrentConnection.PSCredential.UserName; + var password = EncryptionUtility.ToInsecureString(SPOnlineConnection.CurrentConnection.PSCredential.Password); + body = $"grant_type=password&client_id={clientId}&username={username}&password={password}&resource={resource}"; + } + else if (!string.IsNullOrEmpty(SPOnlineConnection.CurrentConnection.ClientId) && !string.IsNullOrEmpty(SPOnlineConnection.CurrentConnection.ClientSecret)) + { + var clientId = SPOnlineConnection.CurrentConnection.ClientId; + var clientSecret = HttpUtility.UrlEncode(SPOnlineConnection.CurrentConnection.ClientSecret); + body = $"grant_type=client_credentials&client_id={clientId}&client_secret={clientSecret}&resource={resource}"; + } + else + { + throw new System.UnauthorizedAccessException("Specify PowerShell Credentials or AppId and AppSecret"); + } + var response = HttpHelper.MakePostRequestForString($"https://login.microsoftonline.com/{tenantId}/oauth2/token", body, "application/x-www-form-urlencoded"); try { diff --git a/Commands/Branding/AddCustomAction.cs b/Commands/Branding/AddCustomAction.cs index f98acc6dd..9facf5a1f 100644 --- a/Commands/Branding/AddCustomAction.cs +++ b/Commands/Branding/AddCustomAction.cs @@ -4,8 +4,6 @@ using SharePointPnP.PowerShell.CmdletHelpAttributes; using SharePointPnP.PowerShell.Commands.Enums; using SharePointPnP.PowerShell.Commands.Base.PipeBinds; -using System; -using Newtonsoft.Json; namespace SharePointPnP.PowerShell.Commands.Branding { @@ -23,10 +21,10 @@ namespace SharePointPnP.PowerShell.Commands.Branding SortOrder = 2)] [CmdletRelatedLink( Text = "UserCustomAction", - Url = "https://msdn.microsoft.com/en-us/library/office/microsoft.sharepoint.client.usercustomaction.aspx")] + Url = "https://docs.microsoft.com/en-us/previous-versions/office/sharepoint-server/ee539583(v=office.15)")] [CmdletRelatedLink( Text = "BasePermissions", - Url = "https://msdn.microsoft.com/en-us/library/office/microsoft.sharepoint.client.basepermissions.aspx")] + Url = "https://docs.microsoft.com/en-us/previous-versions/office/sharepoint-server/ee543321(v=office.15)")] public class AddCustomAction : PnPWebCmdlet { private const string ParameterSet_DEFAULT = "Default"; @@ -56,6 +54,9 @@ public class AddCustomAction : PnPWebCmdlet public string Location = string.Empty; [Parameter(Mandatory = false, HelpMessage = "Sequence of this CustomAction being injected. Use when you have a specific sequence with which to have multiple CustomActions being added to the page.", ParameterSetName = ParameterSet_DEFAULT)] +#if !SP2013 && !SP2016 + [Parameter(Mandatory = false, HelpMessage = "Optional activation sequence order for the extensions. Used if multiple extensions are activated on a same scope.", ParameterSetName = ParameterSet_CLIENTSIDECOMPONENTID)] +#endif public int Sequence = 0; [Parameter(Mandatory = false, HelpMessage = "The URL, URI or ECMAScript (JScript, JavaScript) function associated with the action", ParameterSetName = ParameterSet_DEFAULT)] @@ -68,7 +69,7 @@ public class AddCustomAction : PnPWebCmdlet public string CommandUIExtension = string.Empty; [Parameter(Mandatory = false, HelpMessage = "The identifier of the object associated with the custom action.", ParameterSetName = ParameterSet_DEFAULT)] -#if !ONPREMISES +#if !SP2013 && !SP2016 [Parameter(Mandatory = false, HelpMessage = "The identifier of the object associated with the custom action.", ParameterSetName = ParameterSet_CLIENTSIDECOMPONENTID)] #endif public string RegistrationId = string.Empty; @@ -77,13 +78,13 @@ public class AddCustomAction : PnPWebCmdlet public PermissionKind[] Rights; [Parameter(Mandatory = false, HelpMessage = "Specifies the type of object associated with the custom action", ParameterSetName = ParameterSet_DEFAULT)] -#if !ONPREMISES +#if !SP2013 && !SP2016 [Parameter(Mandatory = false, HelpMessage = "Specifies the type of object associated with the custom action", ParameterSetName = ParameterSet_CLIENTSIDECOMPONENTID)] #endif public UserCustomActionRegistrationType RegistrationType; [Parameter(Mandatory = false, HelpMessage = "The scope of the CustomAction to add to. Either Web or Site; defaults to Web. 'All' is not valid for this command.", ParameterSetName = ParameterSet_DEFAULT)] -#if !ONPREMISES +#if !SP2013 && !SP2016 [Parameter(Mandatory = false, HelpMessage = "The scope of the CustomAction to add to. Either Web or Site; defaults to Web. 'All' is not valid for this command.", ParameterSetName = ParameterSet_CLIENTSIDECOMPONENTID)] #endif public CustomActionScope Scope = CustomActionScope.Web; @@ -132,6 +133,7 @@ protected override void ExecuteCmdlet() Name = Name, Title = Title, Location = Location, + Sequence = Sequence, ClientSideComponentId = ClientSideComponentId.Id, ClientSideComponentProperties = ClientSideComponentProperties }; diff --git a/Commands/ClientSidePages/ConvertToClientSidePage.cs b/Commands/ClientSidePages/ConvertToClientSidePage.cs index b9b2d65fd..e92265894 100644 --- a/Commands/ClientSidePages/ConvertToClientSidePage.cs +++ b/Commands/ClientSidePages/ConvertToClientSidePage.cs @@ -1,4 +1,4 @@ -#if !ONPREMISES +#if !ONPREMISES && !NETSTANDARD2_1 using SharePointPnP.PowerShell.CmdletHelpAttributes; using System; using System.Management.Automation; @@ -250,7 +250,7 @@ protected override void ExecuteCmdlet() } } - if (page == null && !this.Folder.Equals(rootFolder, StringComparison.InvariantCultureIgnoreCase)) + if (page == null && (Folder == null || !this.Folder.Equals(rootFolder, StringComparison.InvariantCultureIgnoreCase))) { throw new Exception($"Page '{Identity?.Name}' does not exist"); } @@ -271,17 +271,7 @@ protected override void ExecuteCmdlet() PageTransformation webPartMappingModel = null; if (string.IsNullOrEmpty(this.WebPartMappingFile)) { - // Load xml mapping data - XmlSerializer xmlMapping = new XmlSerializer(typeof(PageTransformation)); - - // Load the default one from resources into a model, no need for persisting this file - string webpartMappingFileContents = WebPartMappingLoader.LoadFile("SharePointPnP.PowerShell.Commands.ClientSidePages.webpartmapping.xml"); - - using (var stream = GenerateStreamFromString(webpartMappingFileContents)) - { - webPartMappingModel = (PageTransformation)xmlMapping.Deserialize(stream); - } - + webPartMappingModel = PageTransformator.LoadDefaultWebPartMapping(); this.WriteVerbose("Using embedded webpartmapping file. Use Export-PnPClientSidePageMapping to get that file in case you want to base your version of the embedded version."); } diff --git a/Commands/ClientSidePages/ExportClientSidePageMapping.cs b/Commands/ClientSidePages/ExportClientSidePageMapping.cs index e4d4dd100..cede1147d 100644 --- a/Commands/ClientSidePages/ExportClientSidePageMapping.cs +++ b/Commands/ClientSidePages/ExportClientSidePageMapping.cs @@ -1,4 +1,4 @@ -#if !ONPREMISES +#if !ONPREMISES && !NETSTANDARD2_1 using SharePointPnP.PowerShell.CmdletHelpAttributes; using System; using System.Management.Automation; @@ -10,6 +10,7 @@ using SharePointPnP.PowerShell.Commands.Base.PipeBinds; using SharePointPnP.Modernization.Framework.Cache; using SharePointPnP.Modernization.Framework.Telemetry.Observers; +using SharePointPnP.Modernization.Framework.Transform; namespace SharePointPnP.PowerShell.Commands.ClientSidePages { @@ -84,7 +85,7 @@ protected override void ExecuteCmdlet() else { // Load the default one from resources into a model, no need for persisting this file - string webpartMappingFileContents = WebPartMappingLoader.LoadFile("SharePointPnP.PowerShell.Commands.ClientSidePages.webpartmapping.xml"); + string webpartMappingFileContents = PageTransformator.LoadDefaultWebPartMappingFile(); System.IO.File.WriteAllText(fileName, webpartMappingFileContents); } } @@ -101,7 +102,7 @@ protected override void ExecuteCmdlet() else { // Load the default one from resources into a model, no need for persisting this file - string pageLayoutMappingFileContents = WebPartMappingLoader.LoadFile("SharePointPnP.PowerShell.Commands.ClientSidePages.pagelayoutmapping.xml"); + string pageLayoutMappingFileContents = PublishingPageTransformator.LoadDefaultPageLayoutMappingFile(); System.IO.File.WriteAllText(fileName, pageLayoutMappingFileContents); } } diff --git a/Commands/ClientSidePages/SaveClientSidePageConversionLog.cs b/Commands/ClientSidePages/SaveClientSidePageConversionLog.cs index 53b59fc34..8a411dbb6 100644 --- a/Commands/ClientSidePages/SaveClientSidePageConversionLog.cs +++ b/Commands/ClientSidePages/SaveClientSidePageConversionLog.cs @@ -1,4 +1,4 @@ -#if !ONPREMISES +#if !ONPREMISES && !NETSTANDARD2_1 using SharePointPnP.Modernization.Framework.Cache; using SharePointPnP.PowerShell.CmdletHelpAttributes; using System; diff --git a/Commands/ClientSidePages/WebPartMappingLoader.cs b/Commands/ClientSidePages/WebPartMappingLoader.cs deleted file mode 100644 index 1a1e13bee..000000000 --- a/Commands/ClientSidePages/WebPartMappingLoader.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.IO; - -namespace SharePointPnP.PowerShell.Commands.Utilities -{ - public static class WebPartMappingLoader - { - public static string LoadFile(string fileName) - { - var fileContent = ""; - using (Stream stream = typeof(WebPartMappingLoader).Assembly.GetManifestResourceStream(fileName)) - { - using (StreamReader reader = new StreamReader(stream)) - { - fileContent = reader.ReadToEnd(); - } - } - - return fileContent; - } - } -} diff --git a/Commands/ClientSidePages/pagelayoutmapping.xml b/Commands/ClientSidePages/pagelayoutmapping.xml deleted file mode 100644 index 16ade8a29..000000000 --- a/Commands/ClientSidePages/pagelayoutmapping.xml +++ /dev/null @@ -1,290 +0,0 @@ - - - - - -
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - - - -
- - -
- - - -
- - - - - - - - - - - - - - - - - - - - -
-
-
\ No newline at end of file diff --git a/Commands/ClientSidePages/webpartmapping.xml b/Commands/ClientSidePages/webpartmapping.xml deleted file mode 100644 index ff825198b..000000000 --- a/Commands/ClientSidePages/webpartmapping.xml +++ /dev/null @@ -1,1374 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Commands/Fields/AddField.cs b/Commands/Fields/AddField.cs index 96e64c14d..2ee25ed96 100644 --- a/Commands/Fields/AddField.cs +++ b/Commands/Fields/AddField.cs @@ -6,6 +6,7 @@ using SharePointPnP.PowerShell.Commands.Base.PipeBinds; using System.Collections; using Newtonsoft.Json; +using System.Collections.Generic; namespace SharePointPnP.PowerShell.Commands.Fields { @@ -145,10 +146,28 @@ protected override void ExecuteCmdlet() } else if (Type == FieldType.Calculated) { + // Either set the ResultType as input parameter or set it to the default Text + if (!string.IsNullOrEmpty(calculatedFieldParameters.ResultType)) + { + fieldCI.AdditionalAttributes = new List>() + { + new KeyValuePair("ResultType", calculatedFieldParameters.ResultType) + }; + } + else + { + fieldCI.AdditionalAttributes = new List>() + { + new KeyValuePair("ResultType", "Text") + }; + } + + fieldCI.AdditionalChildNodes = new List>() + { + new KeyValuePair("Formula", calculatedFieldParameters.Formula) + }; + f = list.CreateField(fieldCI); - ((FieldCalculated)f).Formula = calculatedFieldParameters.Formula; - f.Update(); - ClientContext.ExecuteQueryRetry(); } else { @@ -343,6 +362,9 @@ public class CalculatedFieldDynamicParameters { [Parameter(Mandatory = true)] public string Formula; + + [Parameter(Mandatory = false)] + public string ResultType; } } diff --git a/Commands/Fields/SetField.cs b/Commands/Fields/SetField.cs index d20bb0c01..c9838241f 100644 --- a/Commands/Fields/SetField.cs +++ b/Commands/Fields/SetField.cs @@ -87,11 +87,14 @@ protected override void ExecuteCmdlet() ClientContext.Load(field); ClientContext.ExecuteQueryRetry(); + // Get a reference to the type-specific object to allow setting type-specific properties, i.e. LookupList and LookupField for Microsoft.SharePoint.Client.FieldLookup + var typeSpecificField = field.TypedObject; + foreach (string key in Values.Keys) { var value = Values[key]; - var property = field.GetType().GetProperty(key); + var property = typeSpecificField.GetType().GetProperty(key); if (property == null) { WriteWarning($"No property '{key}' found on this field. Value will be ignored."); @@ -100,7 +103,7 @@ protected override void ExecuteCmdlet() { try { - property.SetValue(field, value); + property.SetValue(typeSpecificField, value); } catch (Exception e) { diff --git a/Commands/Files/GetFolderItem.cs b/Commands/Files/GetFolderItem.cs index f3eab6afa..db31c5854 100644 --- a/Commands/Files/GetFolderItem.cs +++ b/Commands/Files/GetFolderItem.cs @@ -5,12 +5,13 @@ using Microsoft.SharePoint.Client; using OfficeDevPnP.Core.Utilities; using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; using File = Microsoft.SharePoint.Client.File; namespace SharePointPnP.PowerShell.Commands.Files { [Cmdlet(VerbsCommon.Get, "PnPFolderItem")] - [CmdletHelp("List content in folder", Category = CmdletHelpCategory.Files)] + [CmdletHelp("List content in folder", Category = CmdletHelpCategory.Files, SupportedPlatform = CmdletSupportedPlatform.All)] [CmdletExample( Code = @"PS:> Get-PnPFolderItem -FolderSiteRelativeUrl ""SitePages""", Remarks = "Returns the contents of the folder SitePages which is located in the root of the current web", @@ -31,11 +32,22 @@ namespace SharePointPnP.PowerShell.Commands.Files Remarks = "Returns all files in the folder SitePages which is located in the root of the current web", SortOrder = 4 )] + [CmdletExample( + Code = @"PS:> Get-PnPFolderItem -FolderSiteRelativeUrl ""SitePages"" -Recursive", + Remarks = "Returns all files and folders, including contents of any subfolders, in the folder SitePages which is located in the root of the current web", + SortOrder = 5 + )] public class GetFolderItem : PnPWebCmdlet { - [Parameter(Mandatory = false, Position = 0, ValueFromPipeline = true, HelpMessage ="The site relative folder to retrieve")] + private const string ParameterSet_FOLDERSBYPIPE = "Folder via pipebind"; + private const string ParameterSet_FOLDERBYURL = "Folder via url"; + + [Parameter(Mandatory = false, Position = 0, ValueFromPipeline = true, HelpMessage = "The site relative URL of the folder to retrieve", ParameterSetName = ParameterSet_FOLDERBYURL)] public string FolderSiteRelativeUrl; + [Parameter(Mandatory = false, Position = 0, HelpMessage = "A folder instance to the folder to retrieve", ParameterSetName = ParameterSet_FOLDERSBYPIPE)] + public FolderPipeBind Identity; + [Parameter(Mandatory = false, HelpMessage = "The type of contents to retrieve, either File, Folder or All (default)")] [ValidateSet("Folder", "File", "All")] public string ItemType = "All"; @@ -43,20 +55,32 @@ public class GetFolderItem : PnPWebCmdlet [Parameter(Mandatory = false, HelpMessage = "Optional name of the item to retrieve")] public string ItemName = string.Empty; - protected override void ExecuteCmdlet() + [Parameter(Mandatory = false, Position = 4, HelpMessage = "A switch parameter to include contents of all subfolders in the specified folder")] + public SwitchParameter Recursive; + + private IEnumerable GetContents(string FolderSiteRelativeUrl) { - string serverRelativeUrl = null; - if (!string.IsNullOrEmpty(FolderSiteRelativeUrl)) + Folder targetFolder = null; + if (ParameterSetName == ParameterSet_FOLDERSBYPIPE && Identity != null) { - var webUrl = SelectedWeb.EnsureProperty(w => w.ServerRelativeUrl); - serverRelativeUrl = UrlUtility.Combine(webUrl, FolderSiteRelativeUrl); + targetFolder = Identity.GetFolder(SelectedWeb); } + else + { + string serverRelativeUrl = null; + if (!string.IsNullOrEmpty(FolderSiteRelativeUrl)) + { + var webUrl = SelectedWeb.EnsureProperty(w => w.ServerRelativeUrl); + serverRelativeUrl = UrlUtility.Combine(webUrl, FolderSiteRelativeUrl); + } #if ONPREMISES - var targetFolder = (string.IsNullOrEmpty(FolderSiteRelativeUrl)) ? SelectedWeb.RootFolder : SelectedWeb.GetFolderByServerRelativeUrl(serverRelativeUrl); + targetFolder = (string.IsNullOrEmpty(FolderSiteRelativeUrl)) ? SelectedWeb.RootFolder : SelectedWeb.GetFolderByServerRelativeUrl(serverRelativeUrl); #else - var targetFolder = (string.IsNullOrEmpty(FolderSiteRelativeUrl)) ? SelectedWeb.RootFolder : SelectedWeb.GetFolderByServerRelativePath(ResourcePath.FromDecodedUrl(serverRelativeUrl)); + targetFolder = (string.IsNullOrEmpty(FolderSiteRelativeUrl)) ? SelectedWeb.RootFolder : SelectedWeb.GetFolderByServerRelativePath(ResourcePath.FromDecodedUrl(serverRelativeUrl)); #endif + } + IEnumerable files = null; IEnumerable folders = null; @@ -65,10 +89,10 @@ protected override void ExecuteCmdlet() files = ClientContext.LoadQuery(targetFolder.Files).OrderBy(f => f.Name); if (!string.IsNullOrEmpty(ItemName)) { - files = files.Where(f=>f.Name.Equals(ItemName, StringComparison.InvariantCultureIgnoreCase)); + files = files.Where(f => f.Name.Equals(ItemName, StringComparison.InvariantCultureIgnoreCase)); } } - if (ItemType == "Folder" || ItemType == "All") + if (ItemType == "Folder" || ItemType == "All" || Recursive) { folders = ClientContext.LoadQuery(targetFolder.Folders).OrderBy(f => f.Name); if (!string.IsNullOrEmpty(ItemName)) @@ -77,20 +101,38 @@ protected override void ExecuteCmdlet() } } ClientContext.ExecuteQueryRetry(); - + + IEnumerable folderContent = null; switch (ItemType) { case "All": - var foldersAndFiles = folders.Concat(files); - WriteObject(foldersAndFiles, true); + folderContent = folders.Concat(files); break; case "Folder": - WriteObject(folders, true); + folderContent = folders; break; default: - WriteObject(files, true); + folderContent = files; break; } + + if(Recursive && folders.Count() > 0) + { + foreach(var folder in folders) + { + var relativeUrl = folder.ServerRelativeUrl.Replace(SelectedWeb.ServerRelativeUrl, ""); + var subFolderContents = GetContents(relativeUrl); + folderContent = folderContent.Concat(subFolderContents); + } + } + + return folderContent; + } + + protected override void ExecuteCmdlet() + { + var contents = GetContents(FolderSiteRelativeUrl); + WriteObject(contents, true); } } } diff --git a/Commands/Graph/GetUnifiedGroup.cs b/Commands/Graph/GetUnifiedGroup.cs index b0ef13b78..a156f0094 100644 --- a/Commands/Graph/GetUnifiedGroup.cs +++ b/Commands/Graph/GetUnifiedGroup.cs @@ -1,21 +1,15 @@ using OfficeDevPnP.Core.Entities; using OfficeDevPnP.Core.Framework.Graph; -using OfficeDevPnP.Core.Utilities; using SharePointPnP.PowerShell.CmdletHelpAttributes; using SharePointPnP.PowerShell.Commands.Base; using SharePointPnP.PowerShell.Commands.Base.PipeBinds; -using SharePointPnP.PowerShell.Commands.Model; -using System; using System.Collections.Generic; -using System.Linq; using System.Management.Automation; -using System.Text; -using System.Threading.Tasks; namespace SharePointPnP.PowerShell.Commands.Graph { [Cmdlet(VerbsCommon.Get, "PnPUnifiedGroup")] - [CmdletHelp("Gets one Office 365 Group (aka Unified Group) or a list of Office 365 Groups", + [CmdletHelp("Gets one Office 365 Group (aka Unified Group) or a list of Office 365 Groups. Requires the Azure Active Directory application permission 'Group.Read.All'.", Category = CmdletHelpCategory.Graph, SupportedPlatform = CmdletSupportedPlatform.Online)] [CmdletExample( diff --git a/Commands/Graph/GetUnifiedGroupMembers.cs b/Commands/Graph/GetUnifiedGroupMembers.cs index 84d25892a..9853a2326 100644 --- a/Commands/Graph/GetUnifiedGroupMembers.cs +++ b/Commands/Graph/GetUnifiedGroupMembers.cs @@ -4,14 +4,13 @@ using SharePointPnP.PowerShell.CmdletHelpAttributes; using SharePointPnP.PowerShell.Commands.Base; using SharePointPnP.PowerShell.Commands.Base.PipeBinds; -using System; using System.Collections.Generic; using System.Management.Automation; namespace SharePointPnP.PowerShell.Commands.Graph { [Cmdlet(VerbsCommon.Get, "PnPUnifiedGroupMembers")] - [CmdletHelp("Gets members of a particular Office 365 Group (aka Unified Group)", + [CmdletHelp("Gets members of a particular Office 365 Group (aka Unified Group). Requires the Azure Active Directory application permissions 'Group.Read.All' and 'User.Read.All'.", Category = CmdletHelpCategory.Graph, SupportedPlatform = CmdletSupportedPlatform.Online)] [CmdletExample( @@ -24,7 +23,7 @@ namespace SharePointPnP.PowerShell.Commands.Graph SortOrder = 2)] public class GetUnifiedGroupMembers : PnPGraphCmdlet { - [Parameter(Mandatory = true, HelpMessage = "The Identity of the Office 365 Group.")] + [Parameter(Mandatory = true, ValueFromPipeline = true, HelpMessage = "The Identity of the Office 365 Group.")] public UnifiedGroupPipeBind Identity; protected override void ExecuteCmdlet() diff --git a/Commands/Graph/GetUnifiedGroupOwners.cs b/Commands/Graph/GetUnifiedGroupOwners.cs index 3b8ea205f..790d9f029 100644 --- a/Commands/Graph/GetUnifiedGroupOwners.cs +++ b/Commands/Graph/GetUnifiedGroupOwners.cs @@ -4,14 +4,13 @@ using SharePointPnP.PowerShell.CmdletHelpAttributes; using SharePointPnP.PowerShell.Commands.Base; using SharePointPnP.PowerShell.Commands.Base.PipeBinds; -using System; using System.Collections.Generic; using System.Management.Automation; namespace SharePointPnP.PowerShell.Commands.Graph { [Cmdlet(VerbsCommon.Get, "PnPUnifiedGroupOwners")] - [CmdletHelp("Gets owners of a particular Office 365 Group (aka Unified Group)", + [CmdletHelp("Gets owners of a particular Office 365 Group (aka Unified Group). Requires the Azure Active Directory application permissions 'Group.Read.All' and 'User.Read.All'.", Category = CmdletHelpCategory.Graph, SupportedPlatform = CmdletSupportedPlatform.Online)] [CmdletExample( @@ -24,7 +23,7 @@ namespace SharePointPnP.PowerShell.Commands.Graph SortOrder = 2)] public class GetUnifiedGroupOwners : PnPGraphCmdlet { - [Parameter(Mandatory = true, HelpMessage = "The Identity of the Office 365 Group.")] + [Parameter(Mandatory = true, ValueFromPipeline = true, HelpMessage = "The Identity of the Office 365 Group.")] public UnifiedGroupPipeBind Identity; protected override void ExecuteCmdlet() diff --git a/Commands/Graph/NewUnifiedGroup.cs b/Commands/Graph/NewUnifiedGroup.cs index cf98b64da..897c1a1e8 100644 --- a/Commands/Graph/NewUnifiedGroup.cs +++ b/Commands/Graph/NewUnifiedGroup.cs @@ -1,20 +1,15 @@ -using OfficeDevPnP.Core.Entities; -using OfficeDevPnP.Core.Framework.Graph; -using OfficeDevPnP.Core.Utilities; +using OfficeDevPnP.Core.Framework.Graph; using SharePointPnP.PowerShell.CmdletHelpAttributes; using SharePointPnP.PowerShell.Commands.Base; using SharePointPnP.PowerShell.Commands.Properties; using System; -using System.Collections.Generic; using System.Linq; using System.Management.Automation; -using System.Text; -using System.Threading.Tasks; namespace SharePointPnP.PowerShell.Commands.Graph { [Cmdlet(VerbsCommon.New, "PnPUnifiedGroup")] - [CmdletHelp("Creates a new Office 365 Group (aka Unified Group)", + [CmdletHelp("Creates a new Office 365 Group (aka Unified Group). Requires the Azure Active Directory application permission 'Group.ReadWrite.All'.", Category = CmdletHelpCategory.Graph, SupportedPlatform = CmdletSupportedPlatform.Online)] [CmdletExample( diff --git a/Commands/Graph/RemoveUnifiedGroup.cs b/Commands/Graph/RemoveUnifiedGroup.cs index e9c9cf57c..c328caaaa 100644 --- a/Commands/Graph/RemoveUnifiedGroup.cs +++ b/Commands/Graph/RemoveUnifiedGroup.cs @@ -8,7 +8,7 @@ namespace SharePointPnP.PowerShell.Commands.Graph { [Cmdlet(VerbsCommon.Remove, "PnPUnifiedGroup")] - [CmdletHelp("Removes one Office 365 Group (aka Unified Group)", + [CmdletHelp("Removes one Office 365 Group (aka Unified Group). Requires the Azure Active Directory application permission 'Group.ReadWrite.All'.", Category = CmdletHelpCategory.Graph, SupportedPlatform = CmdletSupportedPlatform.Online)] [CmdletExample( diff --git a/Commands/Graph/SetUnifiedGroup.cs b/Commands/Graph/SetUnifiedGroup.cs index 24927ec45..7377d9d71 100644 --- a/Commands/Graph/SetUnifiedGroup.cs +++ b/Commands/Graph/SetUnifiedGroup.cs @@ -10,7 +10,7 @@ namespace SharePointPnP.PowerShell.Commands.Graph { [Cmdlet(VerbsCommon.Set, "PnPUnifiedGroup")] - [CmdletHelp("Sets Office 365 Group (aka Unified Group) properties", + [CmdletHelp("Sets Office 365 Group (aka Unified Group) properties. Requires the Azure Active Directory application permission 'Group.ReadWrite.All'.", Category = CmdletHelpCategory.Graph, SupportedPlatform = CmdletSupportedPlatform.Online)] [CmdletExample( diff --git a/Commands/InformationManagement/GetLabel.cs b/Commands/InformationManagement/GetLabel.cs index 3723f0882..d3bb0a5c5 100644 --- a/Commands/InformationManagement/GetLabel.cs +++ b/Commands/InformationManagement/GetLabel.cs @@ -12,7 +12,7 @@ namespace SharePointPnP.PowerShell.Commands.InformationManagement Code = @"PS:> Get-PnPLabel -List ""Demo List""", Remarks = @"This gets the Office 365 retention label which is set to a list or a library.", SortOrder = 1)] - public class GetListComplianceTag : PnPWebCmdlet + public class GetLabel : PnPWebCmdlet { [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0, HelpMessage = "The ID or Url of the list.")] public ListPipeBind List; diff --git a/Commands/InformationManagement/ResetLabel.cs b/Commands/InformationManagement/ResetLabel.cs new file mode 100644 index 000000000..b1a844f00 --- /dev/null +++ b/Commands/InformationManagement/ResetLabel.cs @@ -0,0 +1,51 @@ +#if !ONPREMISES +using System.Management.Automation; +using Microsoft.SharePoint.Client; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; + +namespace SharePointPnP.PowerShell.Commands.InformationManagement +{ + [Cmdlet(VerbsCommon.Reset, "PnPLabel")] + [CmdletHelp("Resets a label/tag on the specified list or library to None", Category = CmdletHelpCategory.InformationManagement, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample( + Code = @"PS:> Reset-PnPLabel -List ""Demo List""", + Remarks = @"This resets an O365 label on the specified list or library to None", SortOrder = 1)] + [CmdletExample( + Code = @"PS:> Reset-PnPLabel -List ""Demo List"" -SyncToItems $true", + Remarks = @"This resets an O365 label on the specified list or library to None and resets the label on all the items in the list and library except Folders and where the label has been manually or previously automatically assigned", SortOrder = 2)] + + public class ResetLabel : PnPWebCmdlet + { + [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0, HelpMessage = "The ID or Url of the list")] + public ListPipeBind List; + + [Parameter(Mandatory = false, HelpMessage = "Reset label on existing items in the library")] + public bool SyncToItems; + + protected override void ExecuteCmdlet() + { + var list = List.GetList(SelectedWeb); + if (list != null) + { + var listUrl = list.RootFolder.ServerRelativeUrl; + Microsoft.SharePoint.Client.CompliancePolicy.SPPolicyStoreProxy.SetListComplianceTag(ClientContext, listUrl, string.Empty, false, false, SyncToItems); + + try + { + ClientContext.ExecuteQueryRetry(); + } + catch (System.Exception error) + { + WriteWarning(error.Message.ToString()); + } + } + else + { + WriteWarning("List or library not found."); + + } + } + } +} +#endif \ No newline at end of file diff --git a/Commands/InformationManagement/SetLabel.cs b/Commands/InformationManagement/SetLabel.cs index 225919fa6..3bbdcb8af 100644 --- a/Commands/InformationManagement/SetLabel.cs +++ b/Commands/InformationManagement/SetLabel.cs @@ -7,7 +7,7 @@ namespace SharePointPnP.PowerShell.Commands.InformationManagement { [Cmdlet(VerbsCommon.Set, "PnPLabel")] - [CmdletHelp("Sets a label/tag on the specified list or library", Category = CmdletHelpCategory.InformationManagement, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletHelp("Sets a label/tag on the specified list or library. Use Reset-PnPLabel to remove the label again.", Category = CmdletHelpCategory.InformationManagement, SupportedPlatform = CmdletSupportedPlatform.Online)] [CmdletExample( Code = @"PS:> Set-PnPLabel -List ""Demo List"" -Label ""Project Documentation""", Remarks = @"This sets an O365 label on the specified list or library. ", SortOrder = 1)] @@ -18,7 +18,7 @@ namespace SharePointPnP.PowerShell.Commands.InformationManagement [CmdletExample( Code = @"PS:> Set-PnPLabel -List ""Demo List"" -Label ""Project Documentation"" -BlockDelete $true -BlockEdit $true", Remarks = @"This sets an O365 label on the specified list or library. Next, it also blocks the ability to either edit or delete the item. ", SortOrder = 3)] - public class SetListComplianceTag : PnPWebCmdlet + public class SetLabel : PnPWebCmdlet { [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0, HelpMessage = "The ID or Url of the list.")] public ListPipeBind List; diff --git a/Commands/Lists/AddListItem.cs b/Commands/Lists/AddListItem.cs index a2b817931..91ca2e1f5 100644 --- a/Commands/Lists/AddListItem.cs +++ b/Commands/Lists/AddListItem.cs @@ -4,12 +4,9 @@ using System.Linq; using System.Management.Automation; using Microsoft.SharePoint.Client; -using Microsoft.SharePoint.Client.Taxonomy; -using OfficeDevPnP.Core.Utilities; using SharePointPnP.PowerShell.CmdletHelpAttributes; using SharePointPnP.PowerShell.Commands.Base.PipeBinds; using SharePointPnP.PowerShell.Commands.Enums; -using SharePointPnP.PowerShell.Commands.Taxonomy; using SharePointPnP.PowerShell.Commands.Utilities; // IMPORTANT: If you make changes to this cmdlet, also make the similar/same changes to the Set-PnPListItem Cmdlet @@ -21,7 +18,7 @@ namespace SharePointPnP.PowerShell.Commands.Lists Description = "Adds an item to the list and sets the creation time to the current date and time. The author is set to the current authenticated user executing the cmdlet. In order to set the author to a different user, please refer to Set-PnPListItem.", Category = CmdletHelpCategory.Lists, OutputType = typeof(ListItem), - OutputTypeLink = "https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.client.listitem.aspx")] + OutputTypeLink = "https://docs.microsoft.com/en-us/previous-versions/office/sharepoint-server/ee539951(v=office.15)")] [CmdletExample( Code = @"Add-PnPListItem -List ""Demo List"" -Values @{""Title"" = ""Test Title""; ""Category""=""Test Category""}", Remarks = @"Adds a new list item to the ""Demo List"", and sets both the Title and Category fields with the specified values. Notice, use the internal names of fields.", @@ -168,5 +165,4 @@ protected override void ExecuteCmdlet() } } } - } diff --git a/Commands/Lists/SetListItemPermission.cs b/Commands/Lists/SetListItemPermission.cs index cba6a43ed..98ec3d634 100644 --- a/Commands/Lists/SetListItemPermission.cs +++ b/Commands/Lists/SetListItemPermission.cs @@ -8,7 +8,7 @@ namespace SharePointPnP.PowerShell.Commands.Lists { [Cmdlet(VerbsCommon.Set, "PnPListItemPermission", DefaultParameterSetName = "User")] - [CmdletHelp("Sets list item permissions", + [CmdletHelp("Sets list item permissions. Use Get-PnPRoleDefinition to retrieve all available roles you can add or remove using this cmdlet.", Category = CmdletHelpCategory.Lists)] [CmdletExample( Code = "PS:> Set-PnPListItemPermission -List 'Documents' -Identity 1 -User 'user@contoso.com' -AddRole 'Contribute'", diff --git a/Commands/PnPPowerShell.csproj b/Commands/PnPPowerShell.csproj index 9bd4555e5..669dccd09 100644 --- a/Commands/PnPPowerShell.csproj +++ b/Commands/PnPPowerShell.csproj @@ -1,7 +1,7 @@ - + - netstandard2.0 + netstandard2.1 false true SharePointPnP.PowerShell.Core @@ -37,6 +37,8 @@ + + @@ -75,7 +77,7 @@ - + diff --git a/Commands/Principals/GetUser.cs b/Commands/Principals/GetUser.cs index a7e0ac79d..d9cc9815e 100644 --- a/Commands/Principals/GetUser.cs +++ b/Commands/Principals/GetUser.cs @@ -12,7 +12,7 @@ namespace SharePointPnP.PowerShell.Commands.Principals [Cmdlet(VerbsCommon.Get, "PnPUser")] [CmdletHelp("Returns site users of current web", Category = CmdletHelpCategory.Principals, - DetailedDescription = "This command will return all the users that exist in the current site collection its User Information List", + DetailedDescription = "This command will return all users that exist in the current site collection's User Information List", OutputType = typeof(User), OutputTypeLink = "https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.client.user.aspx")] [CmdletExample( @@ -84,13 +84,13 @@ protected override void ExecuteCmdlet() var usersWithDirectPermissions = SelectedWeb.SiteUsers.Where(u => SelectedWeb.RoleAssignments.Any(ra => ra.Member.LoginName == u.LoginName)); // Get all the users contained in SharePoint Groups - SelectedWeb.Context.Load(SelectedWeb.SiteGroups, sg => sg.Include(u => u.Users.Include(retrievalExpressions))); + SelectedWeb.Context.Load(SelectedWeb.SiteGroups, sg => sg.Include(u => u.Users.Include(retrievalExpressions), u => u.LoginName)); SelectedWeb.Context.ExecuteQueryRetry(); + // Get all SharePoint groups that have been assigned access var usersWithGroupPermissions = new List(); - foreach (var group in SelectedWeb.SiteGroups) + foreach (var group in SelectedWeb.SiteGroups.Where(g => SelectedWeb.RoleAssignments.Any(ra => ra.Member.LoginName == g.LoginName))) { - // If they're in a SharePoint Group, they always have some kind of access rights, so add them all usersWithGroupPermissions.AddRange(group.Users); } diff --git a/Commands/Properties/Resources.Designer.cs b/Commands/Properties/Resources.Designer.cs index 5f3bf69c2..81fdd0e75 100644 --- a/Commands/Properties/Resources.Designer.cs +++ b/Commands/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace SharePointPnP.PowerShell.Commands.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/Commands/Provisioning/Site/AddDataRowsToProvisioningTemplate.cs b/Commands/Provisioning/Site/AddDataRowsToProvisioningTemplate.cs index 7fb75c4bd..237356cc1 100644 --- a/Commands/Provisioning/Site/AddDataRowsToProvisioningTemplate.cs +++ b/Commands/Provisioning/Site/AddDataRowsToProvisioningTemplate.cs @@ -22,11 +22,11 @@ namespace SharePointPnP.PowerShell.Commands.Provisioning.Site Category = CmdletHelpCategory.Provisioning)] [CmdletExample( Code = @"PS:> Add-PnPDataRowsToProvisioningTemplate -Path template.pnp -List 'PnPTestList' -Query '' -Fields 'Title','Choice'", - Remarks = "Adds datarows to a list in an in-memory PnP Provisioning Template", + Remarks = "Adds datarows from the provided list to the PnP Provisioning Template at the provided location", SortOrder = 1)] [CmdletExample( Code = @"PS:> Add-PnPDataRowsToProvisioningTemplate -Path template.pnp -List 'PnPTestList' -Query '' -Fields 'Title','Choice' -IncludeSecurity", - Remarks = "Adds datarows to a list in an in-memory PnP Provisioning Template", + Remarks = "Adds datarows from the provided list to the PnP Provisioning Template at the provided location", SortOrder = 2)] public class AddDataRowsToProvisioningTemplate : PnPWebCmdlet { @@ -258,6 +258,13 @@ private string GetFieldValueAsText(Web web, ListItem listItem, Microsoft.SharePo return string.Join(",", multipleUserValue.Select(lv => GetLoginName(web,lv.LookupId))); } throw new Exception("Invalid data in field"); + case FieldType.MultiChoice: + var multipleChoiceValue = rawValue as string[]; + if (multipleChoiceValue != null) + { + return string.Join(";#", multipleChoiceValue); + } + return Convert.ToString(rawValue); default: return Convert.ToString(rawValue); } @@ -295,4 +302,4 @@ private static string Tokenize(string input, Web web, SPSite site) return input; } } -} \ No newline at end of file +} diff --git a/Commands/Provisioning/Site/ApplyProvisioningTemplate.cs b/Commands/Provisioning/Site/ApplyProvisioningTemplate.cs index 15629949c..71756d33d 100644 --- a/Commands/Provisioning/Site/ApplyProvisioningTemplate.cs +++ b/Commands/Provisioning/Site/ApplyProvisioningTemplate.cs @@ -248,7 +248,7 @@ protected override void ExecuteCmdlet() Path = SessionState.Path.CurrentFileSystemLocation.Path; } var fileInfo = new FileInfo(Path); - fileConnector = new FileSystemConnector(fileInfo.DirectoryName, ""); + fileConnector = new FileSystemConnector(System.IO.Path.IsPathRooted(fileInfo.FullName) ? fileInfo.FullName : fileInfo.DirectoryName, ""); provisioningTemplate.Connector = fileConnector; } } @@ -416,7 +416,7 @@ private string AccessToken } else { -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 return (SPOnlineConnection.AuthenticationResult.Token); #else return SPOnlineConnection.AuthenticationResult.AccessToken; diff --git a/Commands/Provisioning/Site/ExportListToProvisioningTemplate.cs b/Commands/Provisioning/Site/ExportListToProvisioningTemplate.cs index f3cdb033e..7edc11b70 100644 --- a/Commands/Provisioning/Site/ExportListToProvisioningTemplate.cs +++ b/Commands/Provisioning/Site/ExportListToProvisioningTemplate.cs @@ -19,11 +19,11 @@ namespace SharePointPnP.PowerShell.Commands.Provisioning.Site [CmdletHelp("Exports one or more lists to provisioning template", Category = CmdletHelpCategory.Provisioning)] [CmdletExample( - Code = @"PS:> Export-PnPProvisioningTemplate -Out template.xml -List ""Documents""", + Code = @"PS:> Export-PnPListToProvisioningTemplate -Out template.xml -List ""Documents""", Remarks = "Extracts a list to a new provisioning template including the list specified by title or ID.", SortOrder = 1)] [CmdletExample( - Code = @"PS:> Export-PnPProvisioningTemplate -Out template.pnp -List ""Documents"",""Events""", + Code = @"PS:> Export-PnPListToProvisioningTemplate -Out template.pnp -List ""Documents"",""Events""", Remarks = "Extracts a list to a new provisioning template Office Open XML file, including the lists specified by title or ID.", SortOrder = 2)] public class ExportListToProvisioningTemplate : PnPWebCmdlet @@ -250,8 +250,9 @@ private void ExtractTemplate(XMLPnPSchemaVersion schema, string path, string pac if (extension == ".pnp") { +#if !NETSTANDARD2_1 IsolatedStorage.InitializeIsolatedStorage(); - +#endif XMLTemplateProvider provider = new XMLOpenXMLTemplateProvider( creationInformation.FileConnector as OpenXMLConnector); var templateFileName = packageName.Substring(0, packageName.LastIndexOf(".", StringComparison.Ordinal)) + ".xml"; diff --git a/Commands/Provisioning/Site/GetProvisioningTemplate.cs b/Commands/Provisioning/Site/GetProvisioningTemplate.cs index d21d95da1..0af97e175 100644 --- a/Commands/Provisioning/Site/GetProvisioningTemplate.cs +++ b/Commands/Provisioning/Site/GetProvisioningTemplate.cs @@ -518,8 +518,9 @@ private void ExtractTemplate(XMLPnPSchemaVersion schema, string path, string pac if (extension == ".pnp") { +#if !NETSTANDARD2_1 IsolatedStorage.InitializeIsolatedStorage(); - +#endif XMLTemplateProvider provider = new XMLOpenXMLTemplateProvider( creationInformation.FileConnector as OpenXMLConnector); var templateFileName = packageName.Substring(0, packageName.LastIndexOf(".", StringComparison.Ordinal)) + ".xml"; diff --git a/Commands/Provisioning/Site/SaveProvisioningTemplate.cs b/Commands/Provisioning/Site/SaveProvisioningTemplate.cs index 6ca4c98d8..6f65ce4fd 100644 --- a/Commands/Provisioning/Site/SaveProvisioningTemplate.cs +++ b/Commands/Provisioning/Site/SaveProvisioningTemplate.cs @@ -83,8 +83,9 @@ protected override void ProcessRecord() if (extension == ".pnp") { +#if !NETSTANDARD2_1 IsolatedStorage.InitializeIsolatedStorage(); - +#endif XMLTemplateProvider provider = new XMLOpenXMLTemplateProvider( Out, fileSystemConnector); var templateFileName = outFileName.Substring(0, outFileName.LastIndexOf(".", StringComparison.Ordinal)) + ".xml"; diff --git a/Commands/Provisioning/Tenant/ApplyTenantTemplate.cs b/Commands/Provisioning/Tenant/ApplyTenantTemplate.cs index fdcb57241..6a561c130 100644 --- a/Commands/Provisioning/Tenant/ApplyTenantTemplate.cs +++ b/Commands/Provisioning/Tenant/ApplyTenantTemplate.cs @@ -354,7 +354,7 @@ private string AccessToken } else { -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 return (SPOnlineConnection.AuthenticationResult.Token); #else return SPOnlineConnection.AuthenticationResult.AccessToken; diff --git a/Commands/Provisioning/Tenant/GetTenantTemplate.cs b/Commands/Provisioning/Tenant/GetTenantTemplate.cs index 18dea4054..460e3410b 100644 --- a/Commands/Provisioning/Tenant/GetTenantTemplate.cs +++ b/Commands/Provisioning/Tenant/GetTenantTemplate.cs @@ -208,7 +208,7 @@ private string AccessToken } else { -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 return (SPOnlineConnection.AuthenticationResult.Token); #else return SPOnlineConnection.AuthenticationResult.AccessToken; diff --git a/Commands/Provisioning/Tenant/SaveTenantTemplate.cs b/Commands/Provisioning/Tenant/SaveTenantTemplate.cs index 6bf96a58f..693308093 100644 --- a/Commands/Provisioning/Tenant/SaveTenantTemplate.cs +++ b/Commands/Provisioning/Tenant/SaveTenantTemplate.cs @@ -9,8 +9,6 @@ using System.IO; using System.Linq; using System.Management.Automation; -using System.Reflection; -using System.Security.Policy; namespace SharePointPnP.PowerShell.Commands.Provisioning.Tenant { @@ -80,9 +78,9 @@ protected override void ProcessRecord() if (extension == ".pnp") { - +#if !NETSTANDARD2_1 IsolatedStorage.InitializeIsolatedStorage(); - +#endif var templateFileName = outFileName.Substring(0, outFileName.LastIndexOf(".", StringComparison.Ordinal)) + ".xml"; XMLTemplateProvider provider = new XMLOpenXMLTemplateProvider( diff --git a/Commands/RecycleBin/GetRecycleBinItem.cs b/Commands/RecycleBin/GetRecycleBinItem.cs index 31eec4789..4d3f80cad 100644 --- a/Commands/RecycleBin/GetRecycleBinItem.cs +++ b/Commands/RecycleBin/GetRecycleBinItem.cs @@ -1,19 +1,20 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Management.Automation; using Microsoft.SharePoint.Client; using SharePointPnP.PowerShell.CmdletHelpAttributes; using SharePointPnP.PowerShell.Commands.Base.PipeBinds; -using SharePointPnP.PowerShell.Commands.Extensions; namespace SharePointPnP.PowerShell.Commands.RecycleBin { - [Cmdlet(VerbsCommon.Get, "PnPRecycleBinItem", DefaultParameterSetName = "All")] + [Cmdlet(VerbsCommon.Get, "PnPRecycleBinItem", DefaultParameterSetName = ParameterSet_ALL)] [CmdletHelp("Returns the items in the recycle bin from the context", Category = CmdletHelpCategory.RecycleBin, OutputType = typeof(RecycleBinItem), - OutputTypeLink = "https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.client.recyclebinitem.aspx")] + SupportedPlatform = CmdletSupportedPlatform.All, + OutputTypeLink = "https://docs.microsoft.com/en-us/previous-versions/office/sharepoint-server/ee541897(v=office.15)")] [CmdletExample( Code = @"PS:> Get-PnPRecycleBinItem", Remarks = "Returns all items in both the first and the second stage recycle bins in the current site collection", @@ -21,7 +22,7 @@ namespace SharePointPnP.PowerShell.Commands.RecycleBin [CmdletExample( Code = @"PS:> Get-PnPRecycleBinItem -Identity f3ef6195-9400-4121-9d1c-c997fb5b86c2", Remarks = "Returns all a specific recycle bin item by id", - SortOrder = 1)] + SortOrder = 2)] [CmdletExample( Code = @"PS:> Get-PnPRecycleBinItem -FirstStage", Remarks = "Returns all items in only the first stage recycle bin in the current site collection", @@ -30,21 +31,40 @@ namespace SharePointPnP.PowerShell.Commands.RecycleBin Code = @"PS:> Get-PnPRecycleBinItem -SecondStage", Remarks = "Returns all items in only the second stage recycle bin in the current site collection", SortOrder = 4)] +#if !SP2013 + [CmdletExample( + Code = @"PS:> Get-PnPRecycleBinItem -RowLimit 10000", + Remarks = "Returns items in recycle bin limited by number of results", + SortOrder = 5)] +#endif public class GetRecycleBinItems : PnPRetrievalsCmdlet { - [Parameter(Mandatory = false, HelpMessage = "Returns a recycle bin item with a specific identity", ParameterSetName = "Identity")] + private const string ParameterSet_ALL = "All"; + private const string ParameterSet_IDENTITY = "Identity"; + private const string ParameterSet_FIRSTSTAGE = "FirstStage"; + private const string ParameterSet_SECONDSTAGE = "SecondStage"; + + [Parameter(Mandatory = false, HelpMessage = "Returns a recycle bin item with a specific identity", ParameterSetName = ParameterSet_IDENTITY)] public GuidPipeBind Identity; - [Parameter(Mandatory = false, HelpMessage = "Return all items in the first stage recycle bin", ParameterSetName = "FirstStage")] + + [Parameter(Mandatory = false, HelpMessage = "Return all items in the first stage recycle bin", ParameterSetName = ParameterSet_FIRSTSTAGE)] public SwitchParameter FirstStage; - [Parameter(Mandatory = false, HelpMessage = "Return all items in the second stage recycle bin", ParameterSetName = "SecondStage")] + + [Parameter(Mandatory = false, HelpMessage = "Return all items in the second stage recycle bin", ParameterSetName = ParameterSet_SECONDSTAGE)] public SwitchParameter SecondStage; +#if !SP2013 + [Parameter(Mandatory = false, HelpMessage = "Limits return results to specified amount", ParameterSetName = ParameterSet_FIRSTSTAGE)] + [Parameter(Mandatory = false, HelpMessage = "Limits return results to specified amount", ParameterSetName = ParameterSet_SECONDSTAGE)] + [Parameter(Mandatory = false, HelpMessage = "Limits return results to specified amount", ParameterSetName = ParameterSet_ALL)] + public int RowLimit = -1; +#endif protected override void ExecuteCmdlet() { DefaultRetrievalExpressions = new Expression>[] {r => r.Id, r => r.Title, r => r.ItemType, r => r.LeafName, r => r.DirName}; - if (ParameterSetName == "Identity") + if (ParameterSetName == ParameterSet_IDENTITY) { - var item = ClientContext.Site.RecycleBin.GetById(Identity.Id); + RecycleBinItem item = ClientContext.Site.RecycleBin.GetById(Identity.Id); ClientContext.Load(item, RetrievalExpressions); ClientContext.ExecuteQueryRetry(); @@ -52,19 +72,71 @@ protected override void ExecuteCmdlet() } else { +#if !SP2013 + if (HasRowLimit()) + { + RecycleBinItemState recycleBinStage; + switch (ParameterSetName) + { + case ParameterSet_FIRSTSTAGE: + recycleBinStage = RecycleBinItemState.FirstStageRecycleBin; + break; + case ParameterSet_SECONDSTAGE: + recycleBinStage = RecycleBinItemState.SecondStageRecycleBin; + break; + default: + recycleBinStage = RecycleBinItemState.None; + break; + } + + RecycleBinItemCollection items = ClientContext.Site.GetRecycleBinItems(null, RowLimit, false, RecycleBinOrderBy.DeletedDate, + recycleBinStage); + ClientContext.Load(items); + ClientContext.ExecuteQueryRetry(); + + List recycleBinItemList = items.ToList(); + WriteObject(recycleBinItemList, true); + + } + else + { + ClientContext.Site.Context.Load(ClientContext.Site.RecycleBin, r => r.IncludeWithDefaultProperties(RetrievalExpressions)); + ClientContext.Site.Context.ExecuteQueryRetry(); + + List recycleBinItemList = ClientContext.Site.RecycleBin.ToList(); + + switch (ParameterSetName) + { + + case ParameterSet_FIRSTSTAGE: + WriteObject( + recycleBinItemList.Where(i => i.ItemState == RecycleBinItemState.FirstStageRecycleBin), true); + break; + case ParameterSet_SECONDSTAGE: + WriteObject( + recycleBinItemList.Where(i => i.ItemState == RecycleBinItemState.SecondStageRecycleBin), + true); + break; + default: + WriteObject(recycleBinItemList, true); + break; + } + + } +#else ClientContext.Site.Context.Load(ClientContext.Site.RecycleBin, r => r.IncludeWithDefaultProperties(RetrievalExpressions)); ClientContext.Site.Context.ExecuteQueryRetry(); - - var recycleBinItemList = ClientContext.Site.RecycleBin.ToList(); + List recycleBinItemList = ClientContext.Site.RecycleBin.ToList(); + switch (ParameterSetName) { - case "FirstStage": + case ParameterSet_FIRSTSTAGE: WriteObject( recycleBinItemList.Where(i => i.ItemState == RecycleBinItemState.FirstStageRecycleBin), true); break; - case "SecondStage": + case ParameterSet_SECONDSTAGE: WriteObject( recycleBinItemList.Where(i => i.ItemState == RecycleBinItemState.SecondStageRecycleBin), true); @@ -73,7 +145,15 @@ protected override void ExecuteCmdlet() WriteObject(recycleBinItemList, true); break; } +#endif } } + +#if !SP2013 + private bool HasRowLimit() + { + return RowLimit > 0; + } +#endif } } diff --git a/Commands/Search/GetSearchCrawlLog.cs b/Commands/Search/GetSearchCrawlLog.cs index 55d8b1df0..4abb7056f 100644 --- a/Commands/Search/GetSearchCrawlLog.cs +++ b/Commands/Search/GetSearchCrawlLog.cs @@ -55,7 +55,7 @@ public class CrawlEntry Remarks = @"Returns the last 100 crawl log entries for user profiles with the term ""mikael"" in the user principal name.", SortOrder = 4)] [CmdletExample( - Code = @"PS:> Get-PnPSearchCrawlLog -ContentSource Sites LogLevel Error -RowLimit 10", + Code = @"PS:> Get-PnPSearchCrawlLog -ContentSource Sites -LogLevel Error -RowLimit 10", Remarks = @"Returns the last 10 crawl log entries with a state of Error for site content.", SortOrder = 5)] [CmdletExample( diff --git a/Commands/SharePointPnP.PowerShell.Commands.csproj b/Commands/SharePointPnP.PowerShell.Commands.csproj index 3b5b43c8a..d02acb62f 100644 --- a/Commands/SharePointPnP.PowerShell.Commands.csproj +++ b/Commands/SharePointPnP.PowerShell.Commands.csproj @@ -1,5 +1,5 @@  - + Debug @@ -13,7 +13,7 @@ Properties SharePointPnP.PowerShell.Commands SharePointPnP.PowerShell.Online.Commands - v4.5 + v4.6.1 512 @@ -32,7 +32,7 @@ Properties SharePointPnP.PowerShell.Commands SharePointPnP.PowerShell.2013.Commands - v4.5 + v4.6.1 512 @@ -51,7 +51,7 @@ Properties SharePointPnP.PowerShell.Commands SharePointPnP.PowerShell.2016.Commands - v4.5 + v4.6.1 512 @@ -70,7 +70,7 @@ Properties SharePointPnP.PowerShell.Commands SharePointPnP.PowerShell.2019.Commands - v4.5 + v4.6.1 512 @@ -136,6 +136,7 @@ SharePointPnP.PowerShell.Commands + @@ -424,17 +425,20 @@ ..\packages\Microsoft.Data.Services.Client.5.8.4\lib\net40\Microsoft.Data.Services.Client.dll - - ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - True + + ..\packages\Microsoft.Extensions.Caching.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Abstractions.dll - - ..\packages\Microsoft.Extensions.Options.1.0.0\lib\netstandard1.0\Microsoft.Extensions.Options.dll - True + + ..\packages\Microsoft.Extensions.Caching.Memory.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Memory.dll - - ..\packages\Microsoft.Extensions.Primitives.1.0.0\lib\netstandard1.0\Microsoft.Extensions.Primitives.dll - True + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Options.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll + + + ..\packages\Microsoft.Extensions.Primitives.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll ..\packages\Microsoft.Extensions.WebEncoders.1.0.0\lib\netstandard1.0\Microsoft.Extensions.WebEncoders.dll @@ -487,7 +491,14 @@ + + ..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll + + + ..\packages\System.ComponentModel.Annotations.4.5.0\lib\net461\System.ComponentModel.Annotations.dll + + @@ -505,11 +516,21 @@ ..\packages\System.Management.Automation.dll.10.0.10586.0\lib\net40\System.Management.Automation.dll True + + ..\packages\System.Memory.4.5.1\lib\netstandard2.0\System.Memory.dll + ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll True + + + ..\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.1\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + @@ -536,13 +557,23 @@ + + + + + + + + + + @@ -619,6 +650,7 @@ + @@ -821,7 +853,6 @@ - @@ -1113,9 +1144,7 @@ - - diff --git a/Commands/Site/SetSite.cs b/Commands/Site/SetSite.cs index b48643191..d3db546df 100644 --- a/Commands/Site/SetSite.cs +++ b/Commands/Site/SetSite.cs @@ -154,7 +154,7 @@ protected override void ExecuteCmdlet() if (System.IO.File.Exists(LogoFilePath)) { var bytes = System.IO.File.ReadAllBytes(LogoFilePath); -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 var mimeType = System.Web.MimeMapping.GetMimeMapping(LogoFilePath); #else var mimeType = ""; diff --git a/Commands/Utilities/BrowserHelper.cs b/Commands/Utilities/BrowserHelper.cs index e66db9db9..b56966e55 100644 --- a/Commands/Utilities/BrowserHelper.cs +++ b/Commands/Utilities/BrowserHelper.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 using System; using System.Collections.Generic; using System.Linq; diff --git a/Commands/Utilities/CertificateHelper.cs b/Commands/Utilities/CertificateHelper.cs index 48cd6a75e..338e9943e 100644 --- a/Commands/Utilities/CertificateHelper.cs +++ b/Commands/Utilities/CertificateHelper.cs @@ -113,6 +113,19 @@ internal static X509Certificate2 GetCertificatFromStore(string thumbprint) return null; } + /// + /// Converts a public key certificate stored in Base64 encoding such as retrieved from Azure KeyVault to a X509Certificate2 + /// + /// Public key certificate endoded with Base64 + /// X509Certificate2 certificate + internal static X509Certificate2 GetCertificateFromBase64Encodedstring(string publicCert) + { + var certificateBytes = Convert.FromBase64String(publicCert); + var certificate = new X509Certificate2(certificateBytes); + + return certificate; + } + internal static X509Certificate2 GetCertificateFromPEMstring(string publicCert, string privateKey, string password) { if (string.IsNullOrWhiteSpace(password)) password = ""; diff --git a/Commands/Utilities/Clipboard.cs b/Commands/Utilities/Clipboard.cs index 2ddf7d332..8c67af7fa 100644 --- a/Commands/Utilities/Clipboard.cs +++ b/Commands/Utilities/Clipboard.cs @@ -8,7 +8,7 @@ public static class Clipboard { public static void Copy(string val) { -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 System.Windows.Forms.Clipboard.SetText(val); #else var tempfile = System.IO.Path.GetTempFileName(); diff --git a/Commands/Utilities/CredentialManager.cs b/Commands/Utilities/CredentialManager.cs index 8bb673f26..e5034c01f 100644 --- a/Commands/Utilities/CredentialManager.cs +++ b/Commands/Utilities/CredentialManager.cs @@ -11,7 +11,7 @@ namespace SharePointPnP.PowerShell.Commands.Utilities internal static class CredentialManager { -#if NETSTANDARD2_0 +#if NETSTANDARD2_1 public static bool AddCredential(string name, string username, SecureString password, bool overwrite) #else public static bool AddCredential(string name, string username, SecureString password) @@ -21,7 +21,7 @@ public static bool AddCredential(string name, string username, SecureString pass { name = $"PnPPS:{name}"; } -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 WriteWindowsCredentialManagerEntry(name, username, password); return true; #else @@ -39,7 +39,7 @@ public static bool AddCredential(string name, string username, SecureString pass public static PSCredential GetCredential(string name) { -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 var cred = ReadWindowsCredentialManagerEntry(name); if (cred == null) { @@ -71,7 +71,7 @@ public static PSCredential GetCredential(string name) public static bool RemoveCredential(string name) { -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 var success = DeleteWindowsCredentialManagerEntry(name); if(!success) { @@ -167,7 +167,7 @@ private static void WriteWindowsCredentialManagerEntry(string applicationName, s } } -#if NETSTANDARD2_0 +#if NETSTANDARD2_1 private static PSCredential ReadMacOSKeyChainEntry(string name) { var cmd = $"/usr/bin/security find-generic-password -s '{name}'"; @@ -196,7 +196,7 @@ private static PSCredential ReadMacOSKeyChainEntry(string name) } #endif -#if NETSTANDARD2_0 +#if NETSTANDARD2_1 private static void WriteMacOSKeyChainEntry(string applicationName, string username, SecureString password, bool overwrite) { var pw = SecureStringToString(password); @@ -209,7 +209,7 @@ private static void WriteMacOSKeyChainEntry(string applicationName, string usern } #endif -#if NETSTANDARD2_0 +#if NETSTANDARD2_1 private static bool DeleteMacOSKeyChainEntry(string name) { var cmd = $"/usr/bin/security delete-generic-password -s '{name}'"; @@ -298,10 +298,13 @@ public struct Credential public enum CRED_PERSIST : uint { +#pragma warning disable CA1712 // Do not prefix enum values with type name CRED_PERSIST_SESSION = 1, + CRED_PERSIST_LOCAL_MACHINE = 2, CRED_PERSIST_ENTERPRISE = 3 +#pragma warning restore CA1712 // Do not prefix enum values with type name } public enum CRED_TYPE : uint { diff --git a/Commands/Utilities/DynamicLinq.cs b/Commands/Utilities/DynamicLinq.cs index b077844ab..15c443062 100644 --- a/Commands/Utilities/DynamicLinq.cs +++ b/Commands/Utilities/DynamicLinq.cs @@ -258,7 +258,7 @@ static ClassFactory() { } // Trigger lazy initialization of static fields private ClassFactory() { AssemblyName name = new AssemblyName("DynamicClasses"); -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run); #else AssemblyBuilder assembly = AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run); @@ -316,7 +316,7 @@ Type CreateDynamicClass(DynamicProperty[] properties) FieldInfo[] fields = GenerateProperties(tb, properties); GenerateEquals(tb, fields); GenerateGetHashCode(tb, fields); -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 Type result = tb.CreateType(); #else Type result = tb.CreateTypeInfo().AsType(); diff --git a/Commands/Utilities/IsolatedStorage.cs b/Commands/Utilities/IsolatedStorage.cs index 71383a230..997aaac7d 100644 --- a/Commands/Utilities/IsolatedStorage.cs +++ b/Commands/Utilities/IsolatedStorage.cs @@ -1,4 +1,5 @@ -using System; +#if !NETSTANDARD2_1 +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -32,3 +33,4 @@ public static void InitializeIsolatedStorage() } } } +#endif \ No newline at end of file diff --git a/Commands/Utilities/OperatingSystem.cs b/Commands/Utilities/OperatingSystem.cs index 5ae771ed5..5a063323c 100644 --- a/Commands/Utilities/OperatingSystem.cs +++ b/Commands/Utilities/OperatingSystem.cs @@ -8,21 +8,21 @@ namespace SharePointPnP.PowerShell.Commands.Utilities public static class OperatingSystem { public static bool IsWindows() => -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 true; #else RuntimeInformation.IsOSPlatform(OSPlatform.Windows); #endif public static bool IsMacOS() => -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 false; #else RuntimeInformation.IsOSPlatform(OSPlatform.OSX); #endif public static bool IsLinux() => -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_1 false; #else RuntimeInformation.IsOSPlatform(OSPlatform.Linux); diff --git a/Commands/Utilities/Shell.cs b/Commands/Utilities/Shell.cs index 9929a2c2f..48359c809 100644 --- a/Commands/Utilities/Shell.cs +++ b/Commands/Utilities/Shell.cs @@ -1,4 +1,4 @@ -#if NETSTANDARD2_0 +#if NETSTANDARD2_1 using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/Commands/Web/SetWeb.cs b/Commands/Web/SetWeb.cs index b61c70f59..050869279 100644 --- a/Commands/Web/SetWeb.cs +++ b/Commands/Web/SetWeb.cs @@ -9,7 +9,7 @@ namespace SharePointPnP.PowerShell.Commands Category = CmdletHelpCategory.Webs)] public class SetWeb : PnPWebCmdlet { - [Parameter(Mandatory = false, HelpMessage = "Sets the logo of the web to the current url. If you want to set the logo to a modern team site, use Set-PnPSite -SiteLogoPath")] + [Parameter(Mandatory = false, HelpMessage = "Sets the logo of the web to the current url. If you want to set the logo to a modern team site, use Set-PnPSite -LogoFilePath.")] public string SiteLogoUrl; [Parameter(Mandatory = false)] diff --git a/Commands/app.config b/Commands/app.config index 6d8a8eb44..f8c096203 100644 --- a/Commands/app.config +++ b/Commands/app.config @@ -1,7 +1,7 @@  - + @@ -69,6 +69,14 @@ + + + + + + + + - \ No newline at end of file + diff --git a/Commands/packages.config b/Commands/packages.config index ca27ec9c1..e891ee5b9 100644 --- a/Commands/packages.config +++ b/Commands/packages.config @@ -2,39 +2,46 @@ - + - - - + + + + + - - - + + + + + - + - + - + + + + diff --git a/HelpAttributes/SharePointPnP.PowerShell.CmdletHelpAttributes.csproj b/HelpAttributes/SharePointPnP.PowerShell.CmdletHelpAttributes.csproj index a2ff2a4f9..ac11055ee 100644 --- a/HelpAttributes/SharePointPnP.PowerShell.CmdletHelpAttributes.csproj +++ b/HelpAttributes/SharePointPnP.PowerShell.CmdletHelpAttributes.csproj @@ -9,7 +9,7 @@ Properties SharePointPnP.PowerShell.CmdletHelpAttributes SharePointPnP.PowerShell.CmdletHelpAttributes - v4.5 + v4.6.1 512 @@ -19,6 +19,7 @@ + true diff --git a/HelpAttributes/SharePointPnP.PowerShell.Core.Attributes.csproj b/HelpAttributes/SharePointPnP.PowerShell.Core.Attributes.csproj index 4dbe4178f..c96fcd868 100644 --- a/HelpAttributes/SharePointPnP.PowerShell.Core.Attributes.csproj +++ b/HelpAttributes/SharePointPnP.PowerShell.Core.Attributes.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.1 diff --git a/ModuleFilesGenerator/CmdletsAnalyzer.cs b/ModuleFilesGenerator/CmdletsAnalyzer.cs index d87ba3a30..65d47e0c3 100644 --- a/ModuleFilesGenerator/CmdletsAnalyzer.cs +++ b/ModuleFilesGenerator/CmdletsAnalyzer.cs @@ -43,7 +43,7 @@ private List GetCmdlets() var cmdletAttribute = attribute as CmdletAttribute; if (cmdletAttribute != null) { -#if !NETCOREAPP2_0 +#if !NETCOREAPP3_0 var a = cmdletAttribute; cmdletInfo.Verb = a.VerbName; cmdletInfo.Noun = a.NounName; @@ -60,7 +60,7 @@ private List GetCmdlets() var aliasAttribute = attribute as AliasAttribute; if (aliasAttribute != null) { -#if !NETCOREAPP2_0 +#if !NETCOREAPP3_0 foreach (var name in aliasAttribute.AliasNames) { cmdletInfo.Aliases.Add(name); @@ -361,7 +361,7 @@ private List GetCmdletParameters(Model.CmdletInfo cmdletInf if (aliases != null && aliases.Any()) { -#if !NETCOREAPP2_0 +#if !NETCOREAPP3_0 foreach (var aliasAttribute in aliases) { cmdletParameterInfo.Aliases.AddRange(aliasAttribute.AliasNames); diff --git a/ModuleFilesGenerator/ModuleManifestGenerator.cs b/ModuleFilesGenerator/ModuleManifestGenerator.cs index 4ab98ab63..90adfd0d0 100644 --- a/ModuleFilesGenerator/ModuleManifestGenerator.cs +++ b/ModuleFilesGenerator/ModuleManifestGenerator.cs @@ -24,7 +24,7 @@ public ModuleManifestGenerator(List cmdlets, string assemblyPa } internal void Generate() { -#if NETCOREAPP2_0 +#if NETCOREAPP3_0 var spVersion = "Core"; #else var spVersion = string.Empty; @@ -68,7 +68,7 @@ internal void Generate() } // Create Module Manifest -#if !NETCOREAPP2_0 +#if !NETCOREAPP3_0 var psd1Path = $"{new FileInfo(_assemblyPath).Directory}\\ModuleFiles\\SharePointPnPPowerShell{spVersion}.psd1"; #else var psd1Path = $"{new FileInfo(_assemblyPath).Directory}\\ModuleFiles\\SharePointPnPPowerShellCore.psd1"; @@ -89,7 +89,7 @@ private void WriteModuleManifest(string path, string spVersion, string cmdletsTo //{ // aliases = $"{Environment.NewLine}AliasesToExport = {aliasesToExport}"; //} -#if !NETCOREAPP2_0 +#if !NETCOREAPP3_0 var manifest = $@"@{{ RootModule = 'SharePointPnP.PowerShell.{spVersion}.Commands.dll' ModuleVersion = '{_assemblyVersion}' diff --git a/ModuleFilesGenerator/SharePointPnP.PowerShell.Core.ModuleFilesGenerator.csproj b/ModuleFilesGenerator/SharePointPnP.PowerShell.Core.ModuleFilesGenerator.csproj index cb456a291..1cc36b74d 100644 --- a/ModuleFilesGenerator/SharePointPnP.PowerShell.Core.ModuleFilesGenerator.csproj +++ b/ModuleFilesGenerator/SharePointPnP.PowerShell.Core.ModuleFilesGenerator.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.0 + netcoreapp3.0 SharePointPnP.PowerShell.ModuleFilesGenerator SharePointPnP.PowerShell.ModuleFilesGenerator SharePointPnP.PowerShell.ModuleFilesGenerator.GenerateModuleFiles diff --git a/ModuleFilesGenerator/SharePointPnP.PowerShell.ModuleFilesGenerator.csproj b/ModuleFilesGenerator/SharePointPnP.PowerShell.ModuleFilesGenerator.csproj index 8aa5c0de2..4be35d9a6 100644 --- a/ModuleFilesGenerator/SharePointPnP.PowerShell.ModuleFilesGenerator.csproj +++ b/ModuleFilesGenerator/SharePointPnP.PowerShell.ModuleFilesGenerator.csproj @@ -9,7 +9,7 @@ Properties SharePointPnP.PowerShell.ModuleFilesGenerator SharePointPnP.PowerShell.ModuleFilesGenerator - v4.5 + v4.6.1 512 @@ -19,6 +19,7 @@ + true @@ -130,6 +131,7 @@ + diff --git a/ModuleFilesGenerator/app.config b/ModuleFilesGenerator/app.config new file mode 100644 index 000000000..3dbff35f4 --- /dev/null +++ b/ModuleFilesGenerator/app.config @@ -0,0 +1,3 @@ + + + diff --git a/README.md b/README.md index fa9ebd9b7..f82cec24d 100644 --- a/README.md +++ b/README.md @@ -6,25 +6,20 @@ This solution contains a library of PowerShell commands that allows you to perfo ![SharePoint Patterns and Practices](https://devofficecdn.azureedge.net/media/Default/PnP/sppnp.png) ### Applies to ### -- Office 365 Multi Tenant (MT) -- Office 365 Dedicated (D) -- SharePoint 2013 on-premises -- SharePoint 2016 on-premises +- Sharepoint Online (Multi Tenant & Dedicated) - SharePoint 2019 on-premises +- SharePoint 2016 on-premises +- SharePoint 2013 on-premises ### Prerequisites ### -In order to generate the Cmdlet help you need to have Windows Management Framework installed. +In order to generate the Cmdlet help you need to have the Windows Management Framework v4.0 installed, which you can download from http://www.microsoft.com/en-us/download/details.aspx?id=40855 -If it is not [pre-installed on your operating system](https://docs.microsoft.com/powershell/wmf/overview#wmf-availability-across-windows-operating-systems), you can find installation instructions in the [WMF release notes.](https://docs.microsoft.com/powershell/wmf/overview#wmf-release-notes) - -### Solution ### -Solution | Author(s) ----------|---------- -SharePointPnP.PowerShell | Erwin van Hunen and countless community contributors +If it is not [pre-installed on your operating system](https://docs.microsoft.com/powershell/scripting/wmf/overview#wmf-availability-across-windows-operating-systems), you can find installation instructions in the [WMF release notes.](https://docs.microsoft.com/powershell/scripting/wmf/overview#wmf-release-notes) +Check out the "Getting Started" section to make sure you have all requirements in place. -### Disclaimer ### -**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.** +### Latest Release Quick Download +The latest release can be found on [this link](https://github.com/SharePoint/PnP-PowerShell/releases) ---------- @@ -32,10 +27,9 @@ SharePointPnP.PowerShell | Erwin van Hunen and countless community contributors [Navigate here for an overview of all cmdlets and their parameters](Documentation/readme.md) # Installation # +There are two ways: -There are 2 ways to install the cmdlets. We recommend, where possible, to install them from the [PowerShell Gallery](https://www.powershellgallery.com). Check out the "Getting Started with the Gallery" section to make sure you have all requirements in place. Alternatively you can download the setup files and install the cmdlets directly. - -## PowerShell Gallery ## +## 1. Using the [PowerShell Gallery](https://www.powershellgallery.com) **(Recommended)** If you main OS is Windows 10, or if you have [PowerShellGet](https://github.com/powershell/powershellget) installed, you can run the following commands to install the PowerShell cmdlets: @@ -51,10 +45,11 @@ If you main OS is Windows 10, or if you have [PowerShellGet](https://github.com/ In order to install the cmdlets when you get this error specify the -SkipPublisherCheck switch with the Install-Module cmdlet, e.g. ```Install-Module SharePointPnPPowerShellOnline -SkipPublisherCheck -AllowClobber``` -## Setup files ## +## 2. Downloading the Files directly + You can download the setup files from the [releases](https://github.com/officedev/pnp-powershell/releases) section of the PnP PowerShell repository. These files will up be updated on a monthly basis. Run the install and restart any open instances of PowerShell to use the cmdlets. -# Updating # +### How to Update the Cmdlets Every month a new release will be made available of the PnP PowerShell Cmdlets. If you earlier installed the cmdlets using the setup file, simply download the [latest version](https://github.com/SharePoint/PnP-PowerShell/releases/latest) and run the setup. This will update your existing installation. If you have installed the cmdlets using PowerShellGet with ```Install-Module``` from the PowerShell Gallery then you will be able to use the following command to install the latest updated version: @@ -88,7 +83,7 @@ Connect-PnPOnline –Url https://yoursite.sharepoint.com –UseWebLogin To view all cmdlets, enter: ```powershell -Get-Command -Module *PnP* +Get-Command -Module SharePointPnPPowerShell* ``` At the following links you will find a few videos on how to get started with the cmdlets: @@ -104,6 +99,15 @@ See this [wiki page](https://github.com/OfficeDev/PnP-PowerShell/wiki/How-to-use If you want to contribute to this SharePoint Patterns and Practices PowerShell library, please [proceed here](CONTRIBUTING.md) +### Solution/Authors ### +Solution | Author(s) +---------|---------- +SharePointPnP.PowerShell | Erwin van Hunen and countless community contributors + +### Disclaimer ### +**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.** + + ## Building the source code ## If you have set up the projects and you are ready to build the source code, make sure to build the SharePointPnP.PowerShellModuleFilesGenerator project first. This project will be executed after every build and it will generate the required PSD1 and XML files with cmdlet documentation in them. diff --git a/Tests/ListDataRowProvisioningTemplate.cs b/Tests/ListDataRowProvisioningTemplate.cs index 60488c628..500cc7351 100644 --- a/Tests/ListDataRowProvisioningTemplate.cs +++ b/Tests/ListDataRowProvisioningTemplate.cs @@ -5,6 +5,7 @@ using System.Management.Automation.Runspaces; using OfficeDevPnP.Core.Framework.Provisioning.Model; using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml; +using System.Collections.Generic; namespace SharePointPnP.PowerShell.Tests { @@ -31,6 +32,8 @@ public void Initialize() } var list = ctx.Web.Lists.GetByTitle("PnPTestList"); var listFields = list.Fields; + // add field for multichoice export test + listFields.AddFieldAsXml($@" 1 2 c#;3 c#4 c;5 c,6 c.7 c-8 cö9 a b c ", true, AddFieldOptions.DefaultValue); ctx.Load(listFields, fields => fields.Include(field => field.Title, field => field.InternalName)); ctx.ExecuteQueryRetry(); //Create 10 list items. @@ -40,6 +43,7 @@ public void Initialize() var listItem = list.AddItem(itemCreateInfo); var titleField = listFields.FirstOrDefault(f => f.Title == "Title"); //resolve Field Internal Name by Title listItem[titleField.InternalName] = "Item " + i.ToString(); + listItem["MultiChoice"] = new List() { "a", "b", "c" }; listItem.Update(); if (i % 2 == 0) { @@ -138,6 +142,31 @@ public void GetDataRowsFromListWithFields() } } + [TestMethod] + public void GetDataRowsFromListWithMultiChoiceField() + { + using (var scope = new PSTestScope(true)) + { + var filePath = CreateUniqueCopyOfTemplateFile(@"Resources\PnPTestList.xml"); + try + { + string[] fields = new string[] { "MultiChoice" }; + var results = scope.ExecuteCommand("Add-PnPDataRowsToProvisioningTemplate", + new CommandParameter("Path", filePath), + new CommandParameter("List", "PnPTestList"), + new CommandParameter("Query", ""), + new CommandParameter("Fields", fields) + ); + var template = GetTemplateFromXmlFile(filePath); + Assert.AreEqual("a;#b;#c", template.Lists[0].DataRows[0].Values["MultiChoice"]); + } + finally + { + System.IO.File.Delete(filePath); + } + } + } + [TestMethod] public void GetDataRowsWithSecurityFromList() { diff --git a/Tests/SharePointPnP.PowerShell.Tests.csproj b/Tests/SharePointPnP.PowerShell.Tests.csproj index e884ff574..591bd42db 100644 --- a/Tests/SharePointPnP.PowerShell.Tests.csproj +++ b/Tests/SharePointPnP.PowerShell.Tests.csproj @@ -8,7 +8,7 @@ Properties SharePointPnP.PowerShell.Tests SharePointPnP.PowerShell.Tests - v4.5.2 + v4.6.1 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 10.0 @@ -16,6 +16,7 @@ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest + true diff --git a/version.txt b/version.txt index 6a8c4a899..17b1d6608 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.16.1912.0 \ No newline at end of file +3.17.2001.0 \ No newline at end of file