diff --git a/Binaries/SharePointPnP.Modernization.Framework.dll b/Binaries/SharePointPnP.Modernization.Framework.dll index 54c13b52c..c3fd0c623 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 10fd3b82f..547967944 100644 --- a/Binaries/SharePointPnP.Modernization.Framework.xml +++ b/Binaries/SharePointPnP.Modernization.Framework.xml @@ -61,12 +61,61 @@ Json serialized string of this web part instance - + - Set of native, builtin, functions + Determines if a string exists in another string regardless of casing + + original string + string to compare with + optional comparison mode + + + + + Class for holding data properties for the fields that will be used in the page header + + + + + Class that will be used to hold the fields that will be used the field to metadata mapping + + + + + Class for holding data properties for field to web part mapping + + + + + Contains a central point for defaults for publishing page processing + + + + + Field Control Defaults for mappings + + + + + Metadata field default mappings + + + + + Field to header mappings - + + + List of metadata fields in content types to ignore in mappings + + + + + List of field controls in page to ignore in mappings + + + Instantiates the base builtin function library @@ -74,439 +123,1392 @@ The ClientContext for the source Reference to the client side page - + - Html encodes a string + Returns an empty string - Text to html encode - Html encoded string + Empty string - + - Html encodes string for inclusion in JSON + Returns the server relative image url of a Publishing Image field value - Text to html encode - Html encoded string for inclusion in JSON + Publishing Image field value + Server relative image url - + - Return true + Returns the image alternate text of a Publishing Image field value. - True + PublishingPageImage + Image alternate text - + - Return false + Specific layout transformator for the 'AutoDetect' layout option for publishing pages - False - + - Transforms the incoming path into a server relative path + Creates a layout transformator instance - Path to transform - Server relative path + Client side page that will be receive the created layout - + - Returns the filename of the given path + Information used to configure the publishing page transformation process - - File name - + - Selector to allow to embed a spacer instead of an empty text + Instantiates the page transformation class - Text to evaluate - Text if text needs to be inserted, Spacer if text was empty and you want a spacer + Page we want to transform - + - Wiki html rewrite to work in RTE + Instantiates the page transformation class - Wiki html to rewrite - Html that's compatible with RTE + Page we want to transform + Do we overwrite the target page if it already exists - + - Checks if the provided html contains JavaScript + Transforms a classic publishing page into a modern client side page - Html content to check - True is the html contains script, false otherwise - + - Selector that returns the base type of the list as input for selecting the correct mapping + Transform the publishing page - Id of the list - Mapping to be used for the given list + Information about the publishing page to transform + The path to the created modern page - + - Returns the cross site collection save list id. + Use reflection to read the object properties and detail the values - Id of the list - Cross site collection safe list id + PageTransformationInformation object + - + - Function that returns the server relative url of the given list + Simple entity for the extracted blocks of data - Id of the list - Server relative url of the list - + - Function that returns the web relative url of the given list + Analyse Page Layouts class constructor - Id of the list - Web relative url of the list + This should be the context of the source web + List of log observers - + - Checks if an XSLTListView web part has a hidden toolbar + Main entry point into the class to analyse the page layouts - XmlDefinition attribute of the XSLTListViewWebPart - Boolean indicating if the toolbar should be hidden - + - Tries to find the id of the view used to configure the web part + Analyses a single page layout from a provided file - Id of the list - Webpart view definition - Id of the detected view if found or otherwise the id of the default list view + Page layout list item - + - Does return image properties based on given server relative image path + Determine the page layout from a publishing page - Server relative path of the image - A set of image properties + Publishing page to analyze the page layout for - + - Copy the asset to target site in cross site transformation + Generate the mapping file to output from the analysis - + Mapping file fully qualified path - + - Extracts the client side web part properties so they can be reused + Generate the mapping file to output from the analysis - Html defining the client side web part hosted on a classic page - Client side web part properties ready for reuse + Folder to generate the file in + Mapping file fully qualified path - + - Throws an exception when link to .aspx file. + Generate the mapping file to output from the analysis - Link value if set - Unused variable + Folder to generate the file in + name of the mapping file + Mapping file fully qualified path - + - Loads contents of a file as a string. + Determines the page layouts in the current web - Server relative url to the file to load - Text content of the file. Return empty string if file was not found - + - Maps content by search web part data into a properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + Get Metadata mapping from the page layout associated content type - - - - - A properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + Id of the content type - + + - Maps content by query web part data into a properties collection for the contentrollup (= Highlighted Content) web part + Extract the web parts from the page layout HTML outside of web part zones - - - - - - - - - - - - - - - - - - - - - - - - A properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part - + - Uses the SummaryLinksToQuickLinks mapping property provided via the PageTransformationInformation instance to determine the mapping + Cleans and encodes content data - The SummaryLinksToQuickLinks mapping property provided via the PageTransformationInformation instance - Whether to transform via the QuickLinks web part or via Text + web part value + - + - Rewrites summarylinks web part html to be compliant with the html supported by the client side text part + Sets the page layout header field defaults - Original wiki html content - Html compliant with client side text part + + - + - Maps summarylinks web part data into a properties collection and supporting serverProcessedContent nodes for the quicklinks web part + Ensures that we have context of the source site collection - Original wiki html content - Properties collection for the quicklinks web part + - + - Uses the UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance to determine the mapping + Gets property bag value - The UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance - Whether to transform via the community script editor web part + Cast to type of + Current Web + KeyValue Pair - Key + Default Value + - + - Checks if the passed value is a user or not + Cast a string to enum value - Account of the user - Indication if user is valid or not + Enum Type + string value + - + - Looks up a person from the UserInfo list and returns the needed details + Class used to manage SharePoint Publishing page layouts - User account to lookup (in i:0#.f|membership|joe@contoso.onmicrosoft.com format) - Information about the found user - + - Base attribute to document a function or selector + Constructs the page layout manager class + Client context of the source web - + - Class that executes functions and selectors defined in the mapping + Constructs the page layout manager class + Client context of the source web + Client context for the target web - + - Allowed function parameter types + Loads a page layout mapping file + + Path and name of the page mapping file + A instance. + + + + Load the default page layout mapping file + A instance. + + + + Transforms a string into a stream + + String to transform + Stream + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Allowed function parameter types + + + + + Definition of a function parameter + + + + + Name of the parameter + + + + + Type if the parameter + + + + + Value of the parameter + + + + + Definition of a function or selector + + + + + AddOn hosting the function/selector. Empty value means the function is hosted by the internal builtin functions library + + + + + Name of the function/selector + + + + + Parameter specifying the function result + + + + + List of input parameter used to call the function + + + + + Defines a loaded AddOn function/selector class instance + + + + + Name of the addon. The name is used to link the determine which class instance needs to be used to execute a function + + + + + Instance of the class that holds the functions/selectors + + + + + Assembly holding the functions/selector class + + + + + Type of the functions/selector class + + + + + Set of native, builtin, functions + + + + + Instantiates the base builtin function library + + ClientContext object for the site holding the page being transformed + The ClientContext for the source + Reference to the client side page + + + + Html encodes a string + + Text to html encode + Html encoded string + + + + Html encodes string for inclusion in JSON + + Text to html encode + Html encoded string for inclusion in JSON + + + + Return true + + True + + + + Return false + + False + + + + Transforms the incoming path into a server relative path + + Path to transform + Server relative path + + + + Returns the filename of the given path + + + File name + + + + Selector to allow to embed a spacer instead of an empty text + + Text to evaluate + Text if text needs to be inserted, Spacer if text was empty and you want a spacer + + + + Wiki html rewrite to work in RTE + + Wiki html to rewrite + Html that's compatible with RTE + + + + Checks if the provided html contains JavaScript + + Html content to check + True is the html contains script, false otherwise + + + + Selector that returns the base type of the list as input for selecting the correct mapping + + Id of the list + Mapping to be used for the given list + + + + Returns the cross site collection save list id. + + Id of the list + Cross site collection safe list id + + + + Function that returns the server relative url of the given list + + Id of the list + Server relative url of the list + + + + Function that returns the web relative url of the given list + + Id of the list + Web relative url of the list + + + + Checks if an XSLTListView web part has a hidden toolbar + + XmlDefinition attribute of the XSLTListViewWebPart + Boolean indicating if the toolbar should be hidden + + + + Tries to find the id of the view used to configure the web part + + Id of the list + Webpart view definition + Id of the detected view if found or otherwise the id of the default list view + + + + Does return image properties based on given server relative image path + + Server relative path of the image + A set of image properties + + + + Copy the asset to target site in cross site transformation + + + + + + Extracts the client side web part properties so they can be reused + + Html defining the client side web part hosted on a classic page + Client side web part properties ready for reuse + + + + Throws an exception when link to .aspx file. + + Link value if set + Unused variable + + + + Loads contents of a file as a string. + + Server relative url to the file to load + Text content of the file. Return empty string if file was not found + + + + Maps the user documents web part data into a properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + + + + + + A properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + + + + Maps content by search web part data into a properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + + + + + + A properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + + + + Maps content by query web part data into a properties collection for the contentrollup (= Highlighted Content) web part + + + + + + + + + + + + + + + + + + + + + + + + + A properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + + + + Uses the SummaryLinksToQuickLinks mapping property provided via the PageTransformationInformation instance to determine the mapping + + The SummaryLinksToQuickLinks mapping property provided via the PageTransformationInformation instance + Whether to transform via the QuickLinks web part or via Text + + + + Rewrites summarylinks web part html to be compliant with the html supported by the client side text part + + Original wiki html content + Html compliant with client side text part + + + + Maps summarylinks web part data into a properties collection and supporting serverProcessedContent nodes for the quicklinks web part + + Original wiki html content + Properties collection for the quicklinks web part + + + + Uses the UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance to determine the mapping + + The UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance + Whether to transform via the community script editor web part + + + + Checks if the passed value is a user or not + + Account of the user + Indication if user is valid or not + + + + Looks up a person from the UserInfo list and returns the needed details + + User account to lookup (in i:0#.f|membership|joe@contoso.onmicrosoft.com format) + Information about the found user + + + + Base attribute to document a function or selector + + + + + Class that executes functions and selectors defined in the mapping + + + + + Instantiates the function processor. Also loads the defined add-ons + + Client side page for which we're executing the functions/selectors as part of the mapping + Webpart mapping information + + + + Executes the defined functions and selectors in the provided web part + + Web Part mapping data + Definition of the web part to be transformed + The ouput of the mapping selector if there was one executed, null otherwise + + + + Executes the defined functions and selectors in the provided web part + + Web Part mapping data + Definition of the web part to be transformed + The ouput of the mapping selector if there was one executed, null otherwise + + + + Base class for all function libraries + + + + + Instantiates a function library class + + ClientContext object for the site holding the page being transformed + + + + Loosely compares XML documents for equality: + + Order of siblings in an element is ignored. + Text nodes are the only node in at the bottom of the tree so sibling text nodes are merged for comparison. + The prefix used for a namespace is ignored. + Comments are ignored. + + This type of comparison is useful when comparing the XML documents used as messages, configuration, etc. in various specifications. + + + + + The result of an equiality comparison with + + + + + Gets whether the match was successful + + + + + Gets or sets the object that failed the match + + + + + Gets or sets a descriptive error message if the match failed. + + + If set to null or not set the default Error Message is returned. + + + + + Log Information + + LogEntry object + + + + Warning Log + + LogEntry object + + + + Error Log + + LogEntry object + + + + Debug Log + + LogEntry object + + + + Pushes all output to destination + + + + + Sets the id of the page that's being transformed + + id of the page + + + + Defines an entry to log + + + + + Create a new Log Entry + + + + + Gets or sets Log message + + + + + Gets or sets CorrelationId of type Guid + + + + + Gets or sets Log source + + + + + Gets or sets Log Exception + + + + + Specified the logical grouping for the messages based on the stage of transformation + + + + + For those areas where we swallow errors or they are non-critical to report + + + + + Time in which the log entry was made + + + + + Page that's being transformed + + + + + Converts boolean value to Yes/No string + + + + + + + Formats a string that has the format ThisIsAClassName and formats in a friendly way + + + + + + + Use reflection to read the object properties and detail the values + + PageTransformationInformation object + + + + + Debug Log Level + + + + + Error Log Level + + + + + Warning Log Level + + + + + Information Log Level + + + + + + Console Observer constructor + + + + + Output on any warnings generated by the transform process + + + + + + Errors + + + + + + Output a summary to the console + + + + + Output on operations throughout the transform process + + + + + + Output on any warnings generated by the transform process + + + + + + Sets the id of the page that's being transformed + + Id of the page + + + + Cental method to output to console + + + + + + Markdown observer intended for end-user output + + + + + Constructor for specifying to include debug entries + + Name used to construct the log file name + Folder that will hold the log file + Include Debug Log Entries + + + + + Debug level of data not recorded unless in debug mode + + + + + + Errors + + + + + + Reporting operations throughout the transform process + + + + + + Report on any warnings generated by the reporting tool + + + + + + Sets the id of the page that's being transformed + + Id of the page + + + + Generates a markdown based report based on the logs + + + + + + Generates a markdown based report based on the logs + + + + + + Output the report when flush is called + + + + + Writes an MD log file to a folder (default = Transformation-Reports) inside the sitepages library + + + + + Constructor to save a markdown report to SharePoint Modern Site Assets library + + + + + + + + Ensure Folder - Just make sure the location exists + + + + + + Write the report to SharePoint + + + + + Instantiates the telemetry client + + + + + Ensure telemetry data is send to server + + + + + Class for operations for transferring the assets over to the target site collection + + + + + Constructor for the asset transfer class + + Source connection to SharePoint + Target connection to SharePoint + + + + Perform validation + + + + + Main entry point to perform the series of operations to transfer related assets + + + + + Checks if the URL is located in a supported location + + + + + Ensure the site assets and page sub-folder exists in the target location + + + + + Create a site assets library + + + + + Copy the file from the source to the target location + + + + + Based on the documentation: https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/upload-large-files-sample-app-for-sharepoint + + + + + Stores an asset transfer reference + + + + + - Definition of a function parameter + Get asset transfer details if they already exist - + - Name of the parameter + Converts the file name into a friendly format + + - + - Type if the parameter + Ensures that we have context of the source site collection - + - Value of the parameter + Ensures that we have context of the source site collection + Source site context - + - Definition of a function or selector + Base page transformator class that contains logic that applies for all page transformations - + - AddOn hosting the function/selector. Empty value means the function is hosted by the internal builtin functions library + Gets the version of the assembly + - + - Name of the function/selector + Loads the telemetry and properties for the client object + - + - Parameter specifying the function result + Base logging implementation - + - List of input parameter used to call the function + List of registered log observers - + - Defines a loaded AddOn function/selector class instance + Instantiation of base transform class - + - Name of the addon. The name is used to link the determine which class instance needs to be used to execute a function + Registers the observer + The observer. - + - Instance of the class that holds the functions/selectors + Flush all log observers - + - Assembly holding the functions/selector class + Flush Specific Observer of a type + - + - Type of the functions/selector class + Notifies the observers of error messages + The message. - + - Instantiates the function processor. Also loads the defined add-ons + Notifies the observers of info messages - Client side page for which we're executing the functions/selectors as part of the mapping - Webpart mapping information + The message. - + - Executes the defined functions and selectors in the provided web part + Notifies the observers of warning messages - Web Part mapping data - Definition of the web part to be transformed - The ouput of the mapping selector if there was one executed, null otherwise + The message. - + - Executes the defined functions and selectors in the provided web part + Notifies the observers of debug messages - Web Part mapping data - Definition of the web part to be transformed - The ouput of the mapping selector if there was one executed, null otherwise + The message. - + - Base class for all function libraries + Log entries into the observers + - + - Instantiates a function library class + Sets the page name of the page being transformed - ClientContext object for the site holding the page being transformed + Name of the page being transformed - + - Loosely compares XML documents for equality: - - Order of siblings in an element is ignored. - Text nodes are the only node in at the bottom of the tree so sibling text nodes are merged for comparison. - The prefix used for a namespace is ignored. - Comments are ignored. - - This type of comparison is useful when comparing the XML documents used as messages, configuration, etc. in various specifications. + Information used to configure the page transformation process which applies to all types of page transformations - + - The result of an equiality comparison with + Source wiki/webpart page we want to transform - + - Gets whether the match was successful + Folder where the page to transform lives in - + - Gets or sets the object that failed the match + Overwrite the target page if it already exists? - + - Gets or sets a descriptive error message if the match failed. + Name for the transformed page - - If set to null or not set the default Error Message is returned. - - + - Class for operations for transferring the assets over to the target site collection + Apply the item level page permissions on to the target page, defaults to true - + - Constructor for the asset transfer class + Removes empty sections and columns to optimize screen real estate - Source connection to SharePoint - Target connection to SharePoint - + - Main entry point to perform the series of operations to transfer related assets + If true images and videos embedded in wiki text will be transformed to actual image/video web parts, + else they'll get a placeholder and will be added as separate web parts at the end of the page - + - Checks if the URL is located in a supported location + Property bag for adding properties that will be exposed to the functions and selectors in the web part mapping file. + These properties are used to condition the transformation process. - + - Ensure the site assets and page sub-folder exists in the target location + Should the created page be immediately published (default = true) - + - Create a site assets library + Disable page comments on the created page - + - Copy the file from the source to the target location + Custom function callout that can be triggered to provide a tailored page title - - - - Based on the documentation: https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/upload-large-files-sample-app-for-sharepoint - - + - Stores an asset transfer reference + Custom layout transformator to be used for this page - - - + - Get asset transfer details if they already exist + Custom content transformator to be used for this page - + - Converts the file name into a friendly format + Disable telemetry: we use telemetry to make this tool better by sending anonymous data, but you're free to disable this - - @@ -579,6 +1581,13 @@ Client context for the web holding the source page + + + Generate contentrollup (=highlighted content) web part properties coming from a content by search web part + + Properties coming from the content by search web part + Properties for highlighted content web part + Generate contentrollup (=highlighted content) web part properties coming from a content by search web part @@ -598,7 +1607,7 @@ Transforms content from "classic" page to modern client side page - + Instantiates the content transformator @@ -627,11 +1636,11 @@ Interface implemented by all layout transformators - + Transforms a classic wiki/webpart page layout into a modern client side page layout - Source wiki/webpart page layout + Information about the analyed page @@ -657,11 +1666,11 @@ Client side page that will be receive the created layout - + Transforms a classic wiki/webpart page layout into a modern client side page layout - Source wiki/webpart page layout + Information about the analyed page @@ -680,7 +1689,7 @@ - Information used to configure the page transformation process + Information used to configure the wiki and web part page transformation process @@ -704,31 +1713,11 @@ Name of the target page Do we overwrite the target page if it already exists - - - Source wiki/webpart page we want to transform - - - - - Folder where the page to transform lives in - - - - - Name for the transformed page - - Target page will get the source page name, source page will be renamed. Used in conjunction with SourcePagePrefix - - - Overwrite the target page if it already exists? - - Prefix used to name the target page @@ -739,35 +1728,19 @@ Prefix used to name the source page. Used in conjunction with TargetPageTakesSourcePageName - - - Configuration of the page header to apply - - - - - Apply the item level page permissions on to the target page, defaults to true - - Copy the page metadata (if any) to the created modern client side page. Defaults to false - - - Configuration driven by the presence of a modernization center - - - + - Removes empty sections and columns to optimize screen real estate + Configuration of the page header to apply - + - If true images and videos embedded in wiki text will be transformed to actual image/video web parts, - else they'll get a placeholder and will be added as separate web parts at the end of the page + Configuration driven by the presence of a modernization center @@ -775,32 +1748,6 @@ If the page to be transformed is the web's home page then replace with stock modern home page instead of transforming it - - - Property bag for adding properties that will be exposed to the functions and selectors in the web part mapping file. - These properties are used to condition the transformation process. - - - - - Custom function callout that can be triggered to provide a tailored page title - - - - - Custom layout transformator to be used for this page - - - - - Custom content transformator to be used for this page - - - - - Disable telemetry: we use telemetry to make this tool better by sending anonymous data, but you're free to disable this - - Transforms a classic wiki/webpart page into a modern client side page @@ -854,7 +1801,7 @@ Transform the page Information about the page to transform - The path to created modern page + The path to the created modern page @@ -869,12 +1816,6 @@ File holding the page transformation model Page transformation model - - - Loads the telemetry and properties for the client object - - - Transforms the received Html in html that can be displayed and maintained in the modern client side text part @@ -952,6 +1893,20 @@ + + + Returns the table as a normalized table object. Includes replacing row and col spans by actual empty cells + + Table to normalize + Normalized table + + + + Gets the dimensions of a table, excluding the header + + Table to investigate + Tuple containing the columns and rows + The info of the source item where does the quick link point to. @@ -1181,7 +2136,7 @@ Web part information holding all possible tokens for this web part A string with tokens replaced by actual values - + Default constructor @@ -1248,6 +2203,21 @@ Clear the fields to copy cache + + + Get translation for the publishing pages library + + Context of the site + Translated name of the pages library + + + + Returns the translated value for a resource string + + Context of the site + Key of the resource (e.g. $Resources:core,ScriptEditorWebPartDescription;) + Translated string + Field data used to transfer information about a field @@ -1305,7 +2275,7 @@ Base class for the page analyzers - + Constructs the base page class instance @@ -1319,6 +2289,30 @@ Web part xml to analyze Type of the web part as fully qualified name + + + Stores text content as a fake web part + + Text to store + Row of the fake web part + Column of the fake web part + Order inside the row/column + A web part entity to add to the collection + + + + Does the tree of nodes somewhere contain a web part? + + Html content to analyze + True if it contains a web part + + + + Strips the div holding the web part from the html + + Html element holding one or more web part divs + Cleaned html with a placeholder for the web part div + Get's the type of the web part by detecting if from the available properties @@ -1345,14 +2339,21 @@ Analyzes a publishing page - + + + Instantiates a publishing page object + + ListItem holding the page to analyze + Page transformation information + + Instantiates a publishing page object ListItem holding the page to analyze Page transformation information - + Analyses a publishing page @@ -1425,30 +2426,6 @@ element to check true if embedded in a already processed element - - - Does the tree of nodes somewhere contain a web part? - - Html content to analyze - True if it contains a web part - - - - Strips the div holding the web part from the html - - Html element holding one or more web part divs - Cleaned html with a placeholder for the web part div - - - - Stores text content as a fake web part - - Text to store - Row of the fake web part - Column of the fake web part - Order inside the row/column - A web part entity to add to the collection - Analyzes the wiki page to determine which layout was used @@ -1456,16 +2433,6 @@ html object Layout of the wiki page - - - Instantiates the telemetry client - - - - - Ensure telemetry data is send to server - - @@ -1720,6 +2687,11 @@ Web part type constants + + + Use reflection to read the object fields in a list of strings + + Represents a reference to a field within a query. @@ -2739,6 +3711,15 @@ Page list item Page layout defined for this page + + + Gets the field value (if the field exists and a value is set) in the given type + + Type the get the fieldValue in + List item to get the field from + Name of the field to get the value from + Value of the field in the requested type + Transforms a classic wiki/webpart page into a modern page, using the default page transformation model (webpartmapping.xml) @@ -2791,6 +3772,16 @@ A list of pages (ListItem intances) + + + Returns the site pages from a web, optionally filtered on pagename + + Web to get the pages from + Web relative URL of the list (e.g. SiteAssets) + Filter to get all pages starting with + Folder to search in + A list of pages (ListItem intances) + Returns the admins of this site diff --git a/Binaries/release/SharePointPnP.Modernization.Framework.dll b/Binaries/release/SharePointPnP.Modernization.Framework.dll index cf37ce92c..fe527eb50 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 10fd3b82f..547967944 100644 --- a/Binaries/release/SharePointPnP.Modernization.Framework.xml +++ b/Binaries/release/SharePointPnP.Modernization.Framework.xml @@ -61,12 +61,61 @@ Json serialized string of this web part instance - + - Set of native, builtin, functions + Determines if a string exists in another string regardless of casing + + original string + string to compare with + optional comparison mode + + + + + Class for holding data properties for the fields that will be used in the page header + + + + + Class that will be used to hold the fields that will be used the field to metadata mapping + + + + + Class for holding data properties for field to web part mapping + + + + + Contains a central point for defaults for publishing page processing + + + + + Field Control Defaults for mappings + + + + + Metadata field default mappings + + + + + Field to header mappings - + + + List of metadata fields in content types to ignore in mappings + + + + + List of field controls in page to ignore in mappings + + + Instantiates the base builtin function library @@ -74,439 +123,1392 @@ The ClientContext for the source Reference to the client side page - + - Html encodes a string + Returns an empty string - Text to html encode - Html encoded string + Empty string - + - Html encodes string for inclusion in JSON + Returns the server relative image url of a Publishing Image field value - Text to html encode - Html encoded string for inclusion in JSON + Publishing Image field value + Server relative image url - + - Return true + Returns the image alternate text of a Publishing Image field value. - True + PublishingPageImage + Image alternate text - + - Return false + Specific layout transformator for the 'AutoDetect' layout option for publishing pages - False - + - Transforms the incoming path into a server relative path + Creates a layout transformator instance - Path to transform - Server relative path + Client side page that will be receive the created layout - + - Returns the filename of the given path + Information used to configure the publishing page transformation process - - File name - + - Selector to allow to embed a spacer instead of an empty text + Instantiates the page transformation class - Text to evaluate - Text if text needs to be inserted, Spacer if text was empty and you want a spacer + Page we want to transform - + - Wiki html rewrite to work in RTE + Instantiates the page transformation class - Wiki html to rewrite - Html that's compatible with RTE + Page we want to transform + Do we overwrite the target page if it already exists - + - Checks if the provided html contains JavaScript + Transforms a classic publishing page into a modern client side page - Html content to check - True is the html contains script, false otherwise - + - Selector that returns the base type of the list as input for selecting the correct mapping + Transform the publishing page - Id of the list - Mapping to be used for the given list + Information about the publishing page to transform + The path to the created modern page - + - Returns the cross site collection save list id. + Use reflection to read the object properties and detail the values - Id of the list - Cross site collection safe list id + PageTransformationInformation object + - + - Function that returns the server relative url of the given list + Simple entity for the extracted blocks of data - Id of the list - Server relative url of the list - + - Function that returns the web relative url of the given list + Analyse Page Layouts class constructor - Id of the list - Web relative url of the list + This should be the context of the source web + List of log observers - + - Checks if an XSLTListView web part has a hidden toolbar + Main entry point into the class to analyse the page layouts - XmlDefinition attribute of the XSLTListViewWebPart - Boolean indicating if the toolbar should be hidden - + - Tries to find the id of the view used to configure the web part + Analyses a single page layout from a provided file - Id of the list - Webpart view definition - Id of the detected view if found or otherwise the id of the default list view + Page layout list item - + - Does return image properties based on given server relative image path + Determine the page layout from a publishing page - Server relative path of the image - A set of image properties + Publishing page to analyze the page layout for - + - Copy the asset to target site in cross site transformation + Generate the mapping file to output from the analysis - + Mapping file fully qualified path - + - Extracts the client side web part properties so they can be reused + Generate the mapping file to output from the analysis - Html defining the client side web part hosted on a classic page - Client side web part properties ready for reuse + Folder to generate the file in + Mapping file fully qualified path - + - Throws an exception when link to .aspx file. + Generate the mapping file to output from the analysis - Link value if set - Unused variable + Folder to generate the file in + name of the mapping file + Mapping file fully qualified path - + - Loads contents of a file as a string. + Determines the page layouts in the current web - Server relative url to the file to load - Text content of the file. Return empty string if file was not found - + - Maps content by search web part data into a properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + Get Metadata mapping from the page layout associated content type - - - - - A properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + Id of the content type - + + - Maps content by query web part data into a properties collection for the contentrollup (= Highlighted Content) web part + Extract the web parts from the page layout HTML outside of web part zones - - - - - - - - - - - - - - - - - - - - - - - - A properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part - + - Uses the SummaryLinksToQuickLinks mapping property provided via the PageTransformationInformation instance to determine the mapping + Cleans and encodes content data - The SummaryLinksToQuickLinks mapping property provided via the PageTransformationInformation instance - Whether to transform via the QuickLinks web part or via Text + web part value + - + - Rewrites summarylinks web part html to be compliant with the html supported by the client side text part + Sets the page layout header field defaults - Original wiki html content - Html compliant with client side text part + + - + - Maps summarylinks web part data into a properties collection and supporting serverProcessedContent nodes for the quicklinks web part + Ensures that we have context of the source site collection - Original wiki html content - Properties collection for the quicklinks web part + - + - Uses the UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance to determine the mapping + Gets property bag value - The UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance - Whether to transform via the community script editor web part + Cast to type of + Current Web + KeyValue Pair - Key + Default Value + - + - Checks if the passed value is a user or not + Cast a string to enum value - Account of the user - Indication if user is valid or not + Enum Type + string value + - + - Looks up a person from the UserInfo list and returns the needed details + Class used to manage SharePoint Publishing page layouts - User account to lookup (in i:0#.f|membership|joe@contoso.onmicrosoft.com format) - Information about the found user - + - Base attribute to document a function or selector + Constructs the page layout manager class + Client context of the source web - + - Class that executes functions and selectors defined in the mapping + Constructs the page layout manager class + Client context of the source web + Client context for the target web - + - Allowed function parameter types + Loads a page layout mapping file + + Path and name of the page mapping file + A instance. + + + + Load the default page layout mapping file + A instance. + + + + Transforms a string into a stream + + String to transform + Stream + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Allowed function parameter types + + + + + Definition of a function parameter + + + + + Name of the parameter + + + + + Type if the parameter + + + + + Value of the parameter + + + + + Definition of a function or selector + + + + + AddOn hosting the function/selector. Empty value means the function is hosted by the internal builtin functions library + + + + + Name of the function/selector + + + + + Parameter specifying the function result + + + + + List of input parameter used to call the function + + + + + Defines a loaded AddOn function/selector class instance + + + + + Name of the addon. The name is used to link the determine which class instance needs to be used to execute a function + + + + + Instance of the class that holds the functions/selectors + + + + + Assembly holding the functions/selector class + + + + + Type of the functions/selector class + + + + + Set of native, builtin, functions + + + + + Instantiates the base builtin function library + + ClientContext object for the site holding the page being transformed + The ClientContext for the source + Reference to the client side page + + + + Html encodes a string + + Text to html encode + Html encoded string + + + + Html encodes string for inclusion in JSON + + Text to html encode + Html encoded string for inclusion in JSON + + + + Return true + + True + + + + Return false + + False + + + + Transforms the incoming path into a server relative path + + Path to transform + Server relative path + + + + Returns the filename of the given path + + + File name + + + + Selector to allow to embed a spacer instead of an empty text + + Text to evaluate + Text if text needs to be inserted, Spacer if text was empty and you want a spacer + + + + Wiki html rewrite to work in RTE + + Wiki html to rewrite + Html that's compatible with RTE + + + + Checks if the provided html contains JavaScript + + Html content to check + True is the html contains script, false otherwise + + + + Selector that returns the base type of the list as input for selecting the correct mapping + + Id of the list + Mapping to be used for the given list + + + + Returns the cross site collection save list id. + + Id of the list + Cross site collection safe list id + + + + Function that returns the server relative url of the given list + + Id of the list + Server relative url of the list + + + + Function that returns the web relative url of the given list + + Id of the list + Web relative url of the list + + + + Checks if an XSLTListView web part has a hidden toolbar + + XmlDefinition attribute of the XSLTListViewWebPart + Boolean indicating if the toolbar should be hidden + + + + Tries to find the id of the view used to configure the web part + + Id of the list + Webpart view definition + Id of the detected view if found or otherwise the id of the default list view + + + + Does return image properties based on given server relative image path + + Server relative path of the image + A set of image properties + + + + Copy the asset to target site in cross site transformation + + + + + + Extracts the client side web part properties so they can be reused + + Html defining the client side web part hosted on a classic page + Client side web part properties ready for reuse + + + + Throws an exception when link to .aspx file. + + Link value if set + Unused variable + + + + Loads contents of a file as a string. + + Server relative url to the file to load + Text content of the file. Return empty string if file was not found + + + + Maps the user documents web part data into a properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + + + + + + A properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + + + + Maps content by search web part data into a properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + + + + + + A properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + + + + Maps content by query web part data into a properties collection for the contentrollup (= Highlighted Content) web part + + + + + + + + + + + + + + + + + + + + + + + + + A properties collection and supporting serverProcessedContent nodes for the content rollup (= Highlighted Content) web part + + + + Uses the SummaryLinksToQuickLinks mapping property provided via the PageTransformationInformation instance to determine the mapping + + The SummaryLinksToQuickLinks mapping property provided via the PageTransformationInformation instance + Whether to transform via the QuickLinks web part or via Text + + + + Rewrites summarylinks web part html to be compliant with the html supported by the client side text part + + Original wiki html content + Html compliant with client side text part + + + + Maps summarylinks web part data into a properties collection and supporting serverProcessedContent nodes for the quicklinks web part + + Original wiki html content + Properties collection for the quicklinks web part + + + + Uses the UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance to determine the mapping + + The UseCommunityScriptEditor mapping property provided via the PageTransformationInformation instance + Whether to transform via the community script editor web part + + + + Checks if the passed value is a user or not + + Account of the user + Indication if user is valid or not + + + + Looks up a person from the UserInfo list and returns the needed details + + User account to lookup (in i:0#.f|membership|joe@contoso.onmicrosoft.com format) + Information about the found user + + + + Base attribute to document a function or selector + + + + + Class that executes functions and selectors defined in the mapping + + + + + Instantiates the function processor. Also loads the defined add-ons + + Client side page for which we're executing the functions/selectors as part of the mapping + Webpart mapping information + + + + Executes the defined functions and selectors in the provided web part + + Web Part mapping data + Definition of the web part to be transformed + The ouput of the mapping selector if there was one executed, null otherwise + + + + Executes the defined functions and selectors in the provided web part + + Web Part mapping data + Definition of the web part to be transformed + The ouput of the mapping selector if there was one executed, null otherwise + + + + Base class for all function libraries + + + + + Instantiates a function library class + + ClientContext object for the site holding the page being transformed + + + + Loosely compares XML documents for equality: + + Order of siblings in an element is ignored. + Text nodes are the only node in at the bottom of the tree so sibling text nodes are merged for comparison. + The prefix used for a namespace is ignored. + Comments are ignored. + + This type of comparison is useful when comparing the XML documents used as messages, configuration, etc. in various specifications. + + + + + The result of an equiality comparison with + + + + + Gets whether the match was successful + + + + + Gets or sets the object that failed the match + + + + + Gets or sets a descriptive error message if the match failed. + + + If set to null or not set the default Error Message is returned. + + + + + Log Information + + LogEntry object + + + + Warning Log + + LogEntry object + + + + Error Log + + LogEntry object + + + + Debug Log + + LogEntry object + + + + Pushes all output to destination + + + + + Sets the id of the page that's being transformed + + id of the page + + + + Defines an entry to log + + + + + Create a new Log Entry + + + + + Gets or sets Log message + + + + + Gets or sets CorrelationId of type Guid + + + + + Gets or sets Log source + + + + + Gets or sets Log Exception + + + + + Specified the logical grouping for the messages based on the stage of transformation + + + + + For those areas where we swallow errors or they are non-critical to report + + + + + Time in which the log entry was made + + + + + Page that's being transformed + + + + + Converts boolean value to Yes/No string + + + + + + + Formats a string that has the format ThisIsAClassName and formats in a friendly way + + + + + + + Use reflection to read the object properties and detail the values + + PageTransformationInformation object + + + + + Debug Log Level + + + + + Error Log Level + + + + + Warning Log Level + + + + + Information Log Level + + + + + + Console Observer constructor + + + + + Output on any warnings generated by the transform process + + + + + + Errors + + + + + + Output a summary to the console + + + + + Output on operations throughout the transform process + + + + + + Output on any warnings generated by the transform process + + + + + + Sets the id of the page that's being transformed + + Id of the page + + + + Cental method to output to console + + + + + + Markdown observer intended for end-user output + + + + + Constructor for specifying to include debug entries + + Name used to construct the log file name + Folder that will hold the log file + Include Debug Log Entries + + + + + Debug level of data not recorded unless in debug mode + + + + + + Errors + + + + + + Reporting operations throughout the transform process + + + + + + Report on any warnings generated by the reporting tool + + + + + + Sets the id of the page that's being transformed + + Id of the page + + + + Generates a markdown based report based on the logs + + + + + + Generates a markdown based report based on the logs + + + + + + Output the report when flush is called + + + + + Writes an MD log file to a folder (default = Transformation-Reports) inside the sitepages library + + + + + Constructor to save a markdown report to SharePoint Modern Site Assets library + + + + + + + + Ensure Folder - Just make sure the location exists + + + + + + Write the report to SharePoint + + + + + Instantiates the telemetry client + + + + + Ensure telemetry data is send to server + + + + + Class for operations for transferring the assets over to the target site collection + + + + + Constructor for the asset transfer class + + Source connection to SharePoint + Target connection to SharePoint + + + + Perform validation + + + + + Main entry point to perform the series of operations to transfer related assets + + + + + Checks if the URL is located in a supported location + + + + + Ensure the site assets and page sub-folder exists in the target location + + + + + Create a site assets library + + + + + Copy the file from the source to the target location + + + + + Based on the documentation: https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/upload-large-files-sample-app-for-sharepoint + + + + + Stores an asset transfer reference + + + + + - Definition of a function parameter + Get asset transfer details if they already exist - + - Name of the parameter + Converts the file name into a friendly format + + - + - Type if the parameter + Ensures that we have context of the source site collection - + - Value of the parameter + Ensures that we have context of the source site collection + Source site context - + - Definition of a function or selector + Base page transformator class that contains logic that applies for all page transformations - + - AddOn hosting the function/selector. Empty value means the function is hosted by the internal builtin functions library + Gets the version of the assembly + - + - Name of the function/selector + Loads the telemetry and properties for the client object + - + - Parameter specifying the function result + Base logging implementation - + - List of input parameter used to call the function + List of registered log observers - + - Defines a loaded AddOn function/selector class instance + Instantiation of base transform class - + - Name of the addon. The name is used to link the determine which class instance needs to be used to execute a function + Registers the observer + The observer. - + - Instance of the class that holds the functions/selectors + Flush all log observers - + - Assembly holding the functions/selector class + Flush Specific Observer of a type + - + - Type of the functions/selector class + Notifies the observers of error messages + The message. - + - Instantiates the function processor. Also loads the defined add-ons + Notifies the observers of info messages - Client side page for which we're executing the functions/selectors as part of the mapping - Webpart mapping information + The message. - + - Executes the defined functions and selectors in the provided web part + Notifies the observers of warning messages - Web Part mapping data - Definition of the web part to be transformed - The ouput of the mapping selector if there was one executed, null otherwise + The message. - + - Executes the defined functions and selectors in the provided web part + Notifies the observers of debug messages - Web Part mapping data - Definition of the web part to be transformed - The ouput of the mapping selector if there was one executed, null otherwise + The message. - + - Base class for all function libraries + Log entries into the observers + - + - Instantiates a function library class + Sets the page name of the page being transformed - ClientContext object for the site holding the page being transformed + Name of the page being transformed - + - Loosely compares XML documents for equality: - - Order of siblings in an element is ignored. - Text nodes are the only node in at the bottom of the tree so sibling text nodes are merged for comparison. - The prefix used for a namespace is ignored. - Comments are ignored. - - This type of comparison is useful when comparing the XML documents used as messages, configuration, etc. in various specifications. + Information used to configure the page transformation process which applies to all types of page transformations - + - The result of an equiality comparison with + Source wiki/webpart page we want to transform - + - Gets whether the match was successful + Folder where the page to transform lives in - + - Gets or sets the object that failed the match + Overwrite the target page if it already exists? - + - Gets or sets a descriptive error message if the match failed. + Name for the transformed page - - If set to null or not set the default Error Message is returned. - - + - Class for operations for transferring the assets over to the target site collection + Apply the item level page permissions on to the target page, defaults to true - + - Constructor for the asset transfer class + Removes empty sections and columns to optimize screen real estate - Source connection to SharePoint - Target connection to SharePoint - + - Main entry point to perform the series of operations to transfer related assets + If true images and videos embedded in wiki text will be transformed to actual image/video web parts, + else they'll get a placeholder and will be added as separate web parts at the end of the page - + - Checks if the URL is located in a supported location + Property bag for adding properties that will be exposed to the functions and selectors in the web part mapping file. + These properties are used to condition the transformation process. - + - Ensure the site assets and page sub-folder exists in the target location + Should the created page be immediately published (default = true) - + - Create a site assets library + Disable page comments on the created page - + - Copy the file from the source to the target location + Custom function callout that can be triggered to provide a tailored page title - - - - Based on the documentation: https://docs.microsoft.com/en-us/sharepoint/dev/solution-guidance/upload-large-files-sample-app-for-sharepoint - - + - Stores an asset transfer reference + Custom layout transformator to be used for this page - - - + - Get asset transfer details if they already exist + Custom content transformator to be used for this page - + - Converts the file name into a friendly format + Disable telemetry: we use telemetry to make this tool better by sending anonymous data, but you're free to disable this - - @@ -579,6 +1581,13 @@ Client context for the web holding the source page + + + Generate contentrollup (=highlighted content) web part properties coming from a content by search web part + + Properties coming from the content by search web part + Properties for highlighted content web part + Generate contentrollup (=highlighted content) web part properties coming from a content by search web part @@ -598,7 +1607,7 @@ Transforms content from "classic" page to modern client side page - + Instantiates the content transformator @@ -627,11 +1636,11 @@ Interface implemented by all layout transformators - + Transforms a classic wiki/webpart page layout into a modern client side page layout - Source wiki/webpart page layout + Information about the analyed page @@ -657,11 +1666,11 @@ Client side page that will be receive the created layout - + Transforms a classic wiki/webpart page layout into a modern client side page layout - Source wiki/webpart page layout + Information about the analyed page @@ -680,7 +1689,7 @@ - Information used to configure the page transformation process + Information used to configure the wiki and web part page transformation process @@ -704,31 +1713,11 @@ Name of the target page Do we overwrite the target page if it already exists - - - Source wiki/webpart page we want to transform - - - - - Folder where the page to transform lives in - - - - - Name for the transformed page - - Target page will get the source page name, source page will be renamed. Used in conjunction with SourcePagePrefix - - - Overwrite the target page if it already exists? - - Prefix used to name the target page @@ -739,35 +1728,19 @@ Prefix used to name the source page. Used in conjunction with TargetPageTakesSourcePageName - - - Configuration of the page header to apply - - - - - Apply the item level page permissions on to the target page, defaults to true - - Copy the page metadata (if any) to the created modern client side page. Defaults to false - - - Configuration driven by the presence of a modernization center - - - + - Removes empty sections and columns to optimize screen real estate + Configuration of the page header to apply - + - If true images and videos embedded in wiki text will be transformed to actual image/video web parts, - else they'll get a placeholder and will be added as separate web parts at the end of the page + Configuration driven by the presence of a modernization center @@ -775,32 +1748,6 @@ If the page to be transformed is the web's home page then replace with stock modern home page instead of transforming it - - - Property bag for adding properties that will be exposed to the functions and selectors in the web part mapping file. - These properties are used to condition the transformation process. - - - - - Custom function callout that can be triggered to provide a tailored page title - - - - - Custom layout transformator to be used for this page - - - - - Custom content transformator to be used for this page - - - - - Disable telemetry: we use telemetry to make this tool better by sending anonymous data, but you're free to disable this - - Transforms a classic wiki/webpart page into a modern client side page @@ -854,7 +1801,7 @@ Transform the page Information about the page to transform - The path to created modern page + The path to the created modern page @@ -869,12 +1816,6 @@ File holding the page transformation model Page transformation model - - - Loads the telemetry and properties for the client object - - - Transforms the received Html in html that can be displayed and maintained in the modern client side text part @@ -952,6 +1893,20 @@ + + + Returns the table as a normalized table object. Includes replacing row and col spans by actual empty cells + + Table to normalize + Normalized table + + + + Gets the dimensions of a table, excluding the header + + Table to investigate + Tuple containing the columns and rows + The info of the source item where does the quick link point to. @@ -1181,7 +2136,7 @@ Web part information holding all possible tokens for this web part A string with tokens replaced by actual values - + Default constructor @@ -1248,6 +2203,21 @@ Clear the fields to copy cache + + + Get translation for the publishing pages library + + Context of the site + Translated name of the pages library + + + + Returns the translated value for a resource string + + Context of the site + Key of the resource (e.g. $Resources:core,ScriptEditorWebPartDescription;) + Translated string + Field data used to transfer information about a field @@ -1305,7 +2275,7 @@ Base class for the page analyzers - + Constructs the base page class instance @@ -1319,6 +2289,30 @@ Web part xml to analyze Type of the web part as fully qualified name + + + Stores text content as a fake web part + + Text to store + Row of the fake web part + Column of the fake web part + Order inside the row/column + A web part entity to add to the collection + + + + Does the tree of nodes somewhere contain a web part? + + Html content to analyze + True if it contains a web part + + + + Strips the div holding the web part from the html + + Html element holding one or more web part divs + Cleaned html with a placeholder for the web part div + Get's the type of the web part by detecting if from the available properties @@ -1345,14 +2339,21 @@ Analyzes a publishing page - + + + Instantiates a publishing page object + + ListItem holding the page to analyze + Page transformation information + + Instantiates a publishing page object ListItem holding the page to analyze Page transformation information - + Analyses a publishing page @@ -1425,30 +2426,6 @@ element to check true if embedded in a already processed element - - - Does the tree of nodes somewhere contain a web part? - - Html content to analyze - True if it contains a web part - - - - Strips the div holding the web part from the html - - Html element holding one or more web part divs - Cleaned html with a placeholder for the web part div - - - - Stores text content as a fake web part - - Text to store - Row of the fake web part - Column of the fake web part - Order inside the row/column - A web part entity to add to the collection - Analyzes the wiki page to determine which layout was used @@ -1456,16 +2433,6 @@ html object Layout of the wiki page - - - Instantiates the telemetry client - - - - - Ensure telemetry data is send to server - - @@ -1720,6 +2687,11 @@ Web part type constants + + + Use reflection to read the object fields in a list of strings + + Represents a reference to a field within a query. @@ -2739,6 +3711,15 @@ Page list item Page layout defined for this page + + + Gets the field value (if the field exists and a value is set) in the given type + + Type the get the fieldValue in + List item to get the field from + Name of the field to get the value from + Value of the field in the requested type + Transforms a classic wiki/webpart page into a modern page, using the default page transformation model (webpartmapping.xml) @@ -2791,6 +3772,16 @@ A list of pages (ListItem intances) + + + Returns the site pages from a web, optionally filtered on pagename + + Web to get the pages from + Web relative URL of the list (e.g. SiteAssets) + Filter to get all pages starting with + Folder to search in + A list of pages (ListItem intances) + Returns the admins of this site diff --git a/CHANGELOG.md b/CHANGELOG.md index 508b27754..95ff59ab7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,30 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ### Added +- Added Sync-PnPAppToTeams to synchronize an app from the tenant app catalog to the Microsoft Teams App Catalog +- Added Export-PnPClientSidePageMapping to export the mapping files needed during publishing page transformation + ### Changed +- Added a -Kerberos switch to Connect-PnPOnline to facility authentication using Kerberos +- Added the ability to set the view fields using Set-PnPView -Fields +- Added the ability to add and removed indexed property keys to lists +- Added the option to search by title on Get-PnPAlert +- Added -CreateTeam to New-PnPUnifiedGroup and Set-PnPUnifiedGroup +- Added -ContentType parameter to Add-PnPClientSidePage and Set-PnPClientSidePage +- ConvertTo-PnPClientSidePage: added -Library and -Folder parameters to support modernization of pages living outside of the SitePages folder +- ConvertTo-PnPClientSidePage: added -LogType, -LogFolder, -LogSkipFlush and -LogVerbose parameters to support log generation to an md file or SharePoint page +- ConvertTo-PnPClientSidePage: added -DontPublish and -DisablePageComments parameters to control the page publishing and commenting +- ConvertTo-PnPClientSidePage: added -PublishingPage and -PageLayoutMapping to support publishing page transformation + ### Contributors +- Heinrich Ulbricht [heinrich-ulbricht] +- Gautam Sheth [gautamdsheth] +- Thomas Meckel [tmeckel] +- Jose Gabriel Ortega Castro [j0rt3g4] +- Fabian Seither [fabianseither] + ## [3.7.1903.0] ### Added diff --git a/Commands/Admin/AddOffice365GroupToSite.cs b/Commands/Admin/AddOffice365GroupToSite.cs index 133d27d5d..5e768b7d0 100644 --- a/Commands/Admin/AddOffice365GroupToSite.cs +++ b/Commands/Admin/AddOffice365GroupToSite.cs @@ -5,6 +5,7 @@ using SharePointPnP.PowerShell.Commands.Base; using System.Management.Automation; using OfficeDevPnP.Core.Sites; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; namespace SharePointPnP.PowerShell.Commands.Admin { @@ -39,8 +40,13 @@ public class AddOffice365GroupToSite: PnPAdminCmdlet [Parameter(Mandatory = false, HelpMessage = @"Specifies if the current site home page is kept. Defaults to false.")] public SwitchParameter KeepOldHomePage; + [Parameter(Mandatory = false, HelpMessage = "If specified the site will be associated to the hubsite as identified by this id")] + public GuidPipeBind HubSiteId; + + [Parameter(Mandatory = false, HelpMessage = "The array UPN values of the group's owners.")] + public string[] Owners; protected override void ExecuteCmdlet() - { + { var groupifyInformation = new TeamSiteCollectionGroupifyInformation() { Alias = Alias, @@ -48,9 +54,15 @@ protected override void ExecuteCmdlet() Description = Description, Classification = Classification, IsPublic = IsPublic, - KeepOldHomePage = KeepOldHomePage + KeepOldHomePage = KeepOldHomePage, + Owners = Owners }; + if (MyInvocation.BoundParameters.ContainsKey("HubSiteId")) + { + groupifyInformation.HubSiteId = HubSiteId.Id; + } + Tenant.GroupifySite(Url, groupifyInformation); } } diff --git a/Commands/Admin/GetHomeSite.cs b/Commands/Admin/GetHomeSite.cs new file mode 100644 index 000000000..684c88368 --- /dev/null +++ b/Commands/Admin/GetHomeSite.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, "PnPHomeSite")] + [CmdletHelp("Returns the home site url for your tenant", + SupportedPlatform = CmdletSupportedPlatform.Online, + Category = CmdletHelpCategory.TenantAdmin)] + [CmdletExample( + Code = @"PS:> Get-PnPHomeSite", + Remarks = @"Returns the home site url for your tenant", SortOrder = 1)] + public class GetHomeSite : PnPAdminCmdlet + { + protected override void ExecuteCmdlet() + { + var results = Tenant.GetSPHSiteUrl(); + ClientContext.ExecuteQueryRetry(); + WriteObject(results.Value); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Admin/NewTenantSite.cs b/Commands/Admin/NewTenantSite.cs index 18dab78a1..710451425 100644 --- a/Commands/Admin/NewTenantSite.cs +++ b/Commands/Admin/NewTenantSite.cs @@ -1,10 +1,12 @@ -using System; -using System.Management.Automation; -using Microsoft.SharePoint.Client; +using Microsoft.SharePoint.Client; using OfficeDevPnP.Core; +#if ONPREMISES using OfficeDevPnP.Core.Entities; +#endif using SharePointPnP.PowerShell.CmdletHelpAttributes; using SharePointPnP.PowerShell.Commands.Base; +using System; +using System.Management.Automation; using Resources = SharePointPnP.PowerShell.Commands.Properties.Resources; @@ -17,11 +19,11 @@ namespace SharePointPnP.PowerShell.Commands Category = CmdletHelpCategory.TenantAdmin)] [CmdletExample( Code = @"PS:> New-PnPTenantSite -Title Contoso -Url https://tenant.sharepoint.com/sites/contoso -Owner user@example.org -TimeZone 4 -Template STS#0", - Remarks = @"This will add a site collection with the title 'Contoso', the url 'https://tenant.sharepoint.com/sites/contoso', the timezone 'UTC+01:00',the owner 'user@example.org' and the template used will be STS#0, a TeamSite", + Remarks = @"This will add a site collection with the title 'Contoso', the url 'https://tenant.sharepoint.com/sites/contoso', the timezone 'UTC+01:00',the owner 'user@example.org' and the template used will be STS#0, a TeamSite", SortOrder = 1)] [CmdletExample( Code = @"PS:> New-PnPTenantSite -Title Contoso -Url /sites/contososite -Owner user@example.org -TimeZone 4 -Template STS#0", - Remarks = @"This will add a site collection with the title 'Contoso', the url 'https://tenant.sharepoint.com/sites/contososite' of which the base part will be picked up from your current connection, the timezone 'UTC+01:00', the owner 'user@example.org' and the template used will be STS#0, a TeamSite", + Remarks = @"This will add a site collection with the title 'Contoso', the url 'https://tenant.sharepoint.com/sites/contososite' of which the base part will be picked up from your current connection, the timezone 'UTC+01:00', the owner 'user@example.org' and the template used will be STS#0, a TeamSite", SortOrder = 2)] [CmdletRelatedLink( Text = "Locale IDs", @@ -40,7 +42,8 @@ public class NewTenantSite : PnPAdminCmdlet [Parameter(Mandatory = true, HelpMessage = @"Specifies the full URL of the new site collection. It must be in a valid managed path in the company's site. For example, for company contoso, valid managed paths are https://contoso.sharepoint.com/sites and https://contoso.sharepoint.com/teams.")] public string Url; - [Parameter(Mandatory = false, HelpMessage = @"Specifies the description of the new site collection")] + [Obsolete("This parameter is currently ignored due to server side API issues when setting this value.")] + [Parameter(Mandatory = false, HelpMessage = @"Specifies the description of the new site collection. Setting a value for this parameter will override the Wait parameter as we have to set the description after the site has been created.")] public string Description = string.Empty; [Parameter(Mandatory = true, HelpMessage = @"Specifies the user name of the site collection's primary owner. The owner must be a user instead of a security group or an email-enabled security group.")] @@ -103,11 +106,15 @@ protected override void ExecuteCmdlet() entity.UserCodeMaximumLevel = ResourceQuota; entity.UserCodeWarningLevel = ResourceQuotaWarningLevel; entity.Lcid = Lcid; - Tenant.CreateSiteCollection(entity); #else Func timeoutFunction = TimeoutFunction; + if (MyInvocation.BoundParameters.ContainsKey("Description")) + { + // We have to fall back to synchronous behaviour as we have to wait for the site to be present in order to set the description. + Wait = true; + } Tenant.CreateSiteCollection(Url, Title, Owner, Template, (int)StorageQuota, (int)StorageQuotaWarningLevel, TimeZone, (int)ResourceQuota, (int)ResourceQuotaWarningLevel, Lcid, diff --git a/Commands/Admin/RemoveHomeSite.cs b/Commands/Admin/RemoveHomeSite.cs new file mode 100644 index 000000000..583e5edb4 --- /dev/null +++ b/Commands/Admin/RemoveHomeSite.cs @@ -0,0 +1,40 @@ +#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.Remove, "PnPHomeSite")] + [CmdletHelp("Removes the currently set site as the home site", + SupportedPlatform = CmdletSupportedPlatform.Online, + Category = CmdletHelpCategory.TenantAdmin)] + [CmdletExample( + Code = @"PS:> Remove-PnPHomeSite", + Remarks = @"Removes the currently set site as the home site", SortOrder = 1)] + public class RemoveHomeSite : PnPAdminCmdlet + { + [Parameter(Mandatory = false, HelpMessage = "Specifying the Force parameter will skip the confirmation question.")] + public SwitchParameter Force; + + protected override void ExecuteCmdlet() + { + var homesiteUrl = Tenant.GetSPHSiteUrl(); + ClientContext.ExecuteQueryRetry(); + if (!string.IsNullOrEmpty(homesiteUrl.Value)) + { + if (Force || ShouldContinue($"Remove {homesiteUrl.Value} as the home site?", Properties.Resources.Confirm)) + { + Tenant.RemoveSPHSite(); + ClientContext.ExecuteQueryRetry(); + } + } + else + { + WriteWarning("There is currently not site collection set as a home site in your tenant."); + } + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Admin/SetHomeSite.cs b/Commands/Admin/SetHomeSite.cs new file mode 100644 index 000000000..6f677d162 --- /dev/null +++ b/Commands/Admin/SetHomeSite.cs @@ -0,0 +1,28 @@ +#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.Set, "PnPHomeSite")] + [CmdletHelp("Sets the home site for your tenant", + SupportedPlatform = CmdletSupportedPlatform.Online, + Category = CmdletHelpCategory.TenantAdmin)] + [CmdletExample( + Code = @"PS:> Set-PnPHomeSite -Url https://yourtenant.sharepoint.com/sites/myhome", + Remarks = @"Sets the home site to the provided site collection url", SortOrder = 1)] + public class SetHomeSite : PnPAdminCmdlet + { + [Parameter(Mandatory = true, HelpMessage = "The url of the site to set as the home site")] + public string Url; + + protected override void ExecuteCmdlet() + { + Tenant.SetSPHSite(Url); + ClientContext.ExecuteQueryRetry(); + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Apps/SyncAppToTeams.cs b/Commands/Apps/SyncAppToTeams.cs new file mode 100644 index 000000000..aa8173596 --- /dev/null +++ b/Commands/Apps/SyncAppToTeams.cs @@ -0,0 +1,38 @@ +#if !ONPREMISES +using OfficeDevPnP.Core.Enums; +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; +using System; +using System.Management.Automation; + +namespace SharePointPnP.PowerShell.Commands.Apps +{ + [Cmdlet(VerbsData.Sync, "PnPAppToTeams")] + [CmdletHelp("Synchronize an app from the tenant app catalog to the Microsoft Teams app catalog", SupportedPlatform = CmdletSupportedPlatform.Online, + Category = CmdletHelpCategory.Apps)] + [CmdletExample( + Code = @"PS:> Sync-PnPAppToTeams -Identity 99a00f6e-fb81-4dc7-8eac-e09c6f9132fe", + Remarks = @"This will synchronize the given app with the Microsoft Teams app catalog", SortOrder = 1)] + public class SyncAppToTeams : PnPCmdlet + { + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, HelpMessage = "Specifies the Id of the Addin Instance")] + public AppMetadataPipeBind Identity; + + protected override void ExecuteCmdlet() + { + var manager = new OfficeDevPnP.Core.ALM.AppManager(ClientContext); + + var app = Identity.GetAppMetadata(ClientContext, AppCatalogScope.Tenant); + + if (app != null) + { + manager.SyncToTeams(app); + } + else + { + throw new Exception("Cannot find app"); + } + } + } +} +#endif \ No newline at end of file diff --git a/Commands/Base/ConnectOnline.cs b/Commands/Base/ConnectOnline.cs index ab35e10a9..48f35d611 100644 --- a/Commands/Base/ConnectOnline.cs +++ b/Commands/Base/ConnectOnline.cs @@ -23,7 +23,7 @@ namespace SharePointPnP.PowerShell.Commands.Base { [Cmdlet(VerbsCommunications.Connect, "PnPOnline", SupportsShouldProcess = false)] [CmdletHelp("Connect to a SharePoint site", - @"Connects to a SharePoint site and creates a context that is required for the other PnP Cmdlets. + @"Connects to a SharePoint site and creates a context that is required for the other PnP Cmdlets. To automate authentication there are several options. The easiest would be to use the Windows Credential Manager. Either manually add a Generic Credential, or use the Add-PnPStoredCredential cmdlet to add an entry. The name you give to the credential can be used in two main ways. If you simply give it a name alike 'O365' or any other value you can specify this value for the Credentials parameter of this cmdlet. Alternatively you can specify a URL as a name, alike 'https://contoso.sharepoint.com'. Any site you connect to within the contoso tenant will then use the credentials you specified. For more information see the help for the Add-PnPStoredCredential cmdlet. (Get-Help Add-PnPStoredCredential). @@ -191,6 +191,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 = ParameterSet_MAIN, HelpMessage = "Authenticate using Kerberos to an on-premises ADFS instance.")] + public SwitchParameter Kerberos; + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_MAIN, HelpMessage = "The name of the ADFS trusted login provider")] public string LoginProviderName; @@ -316,8 +319,8 @@ public class ConnectOnline : PSCmdlet public SwitchParameter SPOManagementShell; - [Parameter(Mandatory = true, ParameterSetName = ParameterSet_DEVICELOGIN, HelpMessage = @"Log in using the PnP O365 Management Shell application. You will be asked to consent to: - + [Parameter(Mandatory = true, ParameterSetName = ParameterSet_DEVICELOGIN, HelpMessage = @"Log in using the PnP O365 Management Shell application. You will be asked to consent to: + * Read and write managed metadata * Have full control of all site collections * Read user profiles @@ -333,7 +336,7 @@ public class ConnectOnline : PSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_GRAPHDEVICELOGIN, HelpMessage = "Launch a browser automatically and copy the code to enter to the clipboard")] public SwitchParameter LaunchBrowser; - [Parameter(Mandatory = true, ParameterSetName = ParameterSet_GRAPHDEVICELOGIN, HelpMessage = @"Log in using the PnP O365 Management Shell application towards the Graph. You will be asked to consent to: + [Parameter(Mandatory = true, ParameterSetName = ParameterSet_GRAPHDEVICELOGIN, HelpMessage = @"Log in using the PnP O365 Management Shell application towards the Graph. You will be asked to consent to: * Read and write managed metadata * Have full control of all site collections @@ -497,13 +500,13 @@ protected override void ProcessRecord() #if !NETSTANDARD2_0 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. + WriteWarning(@"-UseWebLogin is not implemented, due to restrictions of the .NET Standard framework. Use -PnPO365ManagementShell instead"); #endif } else if (UseAdfs) { - if (creds == null) + if (!Kerberos && creds == null) { if ((creds = GetCredentials()) == null) { @@ -511,7 +514,18 @@ protected override void ProcessRecord() } } #if !NETSTANDARD2_0 - connection = SPOnlineConnectionHelper.InstantiateAdfsConnection(new Uri(Url), creds, Host, MinimalHealthScore, RetryCount, RetryWait, RequestTimeout, TenantAdminUrl, NoTelemetry, SkipTenantAdminCheck, LoginProviderName); + connection = SPOnlineConnectionHelper.InstantiateAdfsConnection(new Uri(Url), + Kerberos, + creds, + Host, + MinimalHealthScore, + RetryCount, + RetryWait, + RequestTimeout, + TenantAdminUrl, + NoTelemetry, + SkipTenantAdminCheck, + LoginProviderName); #else throw new NotImplementedException(); #endif @@ -765,7 +779,7 @@ private SPOnlineConnection ConnectGraphDeviceLogin(string accessToken) } } - + private void ConnectGraphAAD() { var appCredentials = new ClientCredential(AppSecret); diff --git a/Commands/Base/PipeBinds/PagePipeBind.cs b/Commands/Base/PipeBinds/PagePipeBind.cs index f8eccd11a..fd626c857 100644 --- a/Commands/Base/PipeBinds/PagePipeBind.cs +++ b/Commands/Base/PipeBinds/PagePipeBind.cs @@ -43,14 +43,22 @@ public PagePipeBind(string name) public string Name => this.name; - internal ListItem GetPage(Web web) + public string Library { get; set; } + + public string Folder { get; set; } + + internal ListItem GetPage(Web web, string listToLoad) { - // Get pages library + if (!string.IsNullOrEmpty(this.Library)) + { + listToLoad = this.Library; + } + web.EnsureProperty(w => w.ServerRelativeUrl); - var listServerRelativeUrl = UrlUtility.Combine(web.ServerRelativeUrl, "sitepages"); - var sitePagesLibrary = web.GetList(listServerRelativeUrl); + var listServerRelativeUrl = UrlUtility.Combine(web.ServerRelativeUrl, listToLoad); + var libraryContainingPage = web.GetList(listServerRelativeUrl); - if (sitePagesLibrary != null) + if (libraryContainingPage != null) { CamlQuery query = null; if (!string.IsNullOrEmpty(this.name)) @@ -60,7 +68,13 @@ internal ListItem GetPage(Web web) ViewXml = string.Format(CAMLQueryByExtensionAndName, this.name) }; - var page = sitePagesLibrary.GetItems(query); + if (!string.IsNullOrEmpty(this.Folder)) + { + libraryContainingPage.EnsureProperty(p => p.RootFolder); + query.FolderServerRelativeUrl = $"{libraryContainingPage.RootFolder.ServerRelativeUrl}/{Folder}"; + } + + var page = libraryContainingPage.GetItems(query); web.Context.Load(page); web.Context.ExecuteQueryRetry(); @@ -74,6 +88,11 @@ internal ListItem GetPage(Web web) return null; } + internal ListItem GetPublishingPage(Web web) + { + return null; + } + } } #endif \ No newline at end of file diff --git a/Commands/Base/SPOnlineConnectionHelper.cs b/Commands/Base/SPOnlineConnectionHelper.cs index 942919517..03924b9b5 100644 --- a/Commands/Base/SPOnlineConnectionHelper.cs +++ b/Commands/Base/SPOnlineConnectionHelper.cs @@ -563,11 +563,16 @@ internal static SPOnlineConnection InstantiateSPOnlineConnection(Uri url, PSCred context.Credentials = new NetworkCredential(credentials.UserName, credentials.Password); } } +#if SP2013 || SP2016 || SP2019 var connectionType = ConnectionType.OnPrem; +#else + var connectionType = ConnectionType.O365; +#endif if (url.Host.ToUpperInvariant().EndsWith("SHAREPOINT.COM")) { connectionType = ConnectionType.O365; } + if (skipAdminCheck == false) { if (IsTenantAdminSite(context)) @@ -581,22 +586,44 @@ internal static SPOnlineConnection InstantiateSPOnlineConnection(Uri url, PSCred } #if !NETSTANDARD2_0 - internal static SPOnlineConnection InstantiateAdfsConnection(Uri url, PSCredential credentials, PSHost host, int minimalHealthScore, int retryCount, int retryWait, int requestTimeout, string tenantAdminUrl, bool disableTelemetry, bool skipAdminCheck = false, string loginProviderName = null) + 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(); - var networkCredentials = credentials.GetNetworkCredential(); - string adfsHost; string adfsRelyingParty; - GetAdfsConfigurationFromTargetUri(url, loginProviderName, out adfsHost, out 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 = PnPClientContext.ConvertFrom(authManager.GetADFSUserNameMixedAuthenticatedContext(url.ToString(), networkCredentials.UserName, networkCredentials.Password, networkCredentials.Domain, adfsHost, adfsRelyingParty), retryCount, retryWait * 1000); + PnPClientContext context; + if (useKerberos) + { + context = PnPClientContext.ConvertFrom(authManager.GetADFSKerberosMixedAuthenticationContext(url.ToString(), + adfsHost, + adfsRelyingParty), + retryCount, + retryWait * 1000); + } + else + { + if (null == credentials) + { + throw new ArgumentNullException(nameof(credentials)); + } + var networkCredentials = credentials.GetNetworkCredential(); + context = PnPClientContext.ConvertFrom(authManager.GetADFSUserNameMixedAuthenticatedContext(url.ToString(), + networkCredentials.UserName, + networkCredentials.Password, + networkCredentials.Domain, + adfsHost, + adfsRelyingParty), + retryCount, + retryWait * 1000); + } context.RetryCount = retryCount; context.Delay = retryWait * 1000; @@ -672,35 +699,6 @@ public static string GetRealmFromTargetUrl(Uri targetApplicationUri) return null; } - public static void GetAdfsConfigurationFromTargetUri(Uri targetApplicationUri, string loginProviderName, out string adfsHost, out string adfsRelyingParty) - { - adfsHost = ""; - adfsRelyingParty = ""; - - var trustEndpoint = new Uri(new Uri(targetApplicationUri.GetLeftPart(UriPartial.Authority)), !string.IsNullOrWhiteSpace(loginProviderName) ? $"/_trust/?trust={loginProviderName}" : "/_trust/"); - var request = (HttpWebRequest)WebRequest.Create(trustEndpoint); - request.AllowAutoRedirect = false; - - try - { - using (var response = request.GetResponse()) - { - var locationHeader = response.Headers["Location"]; - if (locationHeader != null) - { - var redirectUri = new Uri(locationHeader); - Dictionary queryParameters = Regex.Matches(redirectUri.Query, "([^?=&]+)(=([^&]*))?").Cast().ToDictionary(x => x.Groups[1].Value, x => Uri.UnescapeDataString(x.Groups[3].Value)); - adfsHost = redirectUri.Host; - adfsRelyingParty = queryParameters["wtrealm"]; - } - } - } - catch (WebException ex) - { - throw new Exception("Endpoint does not use ADFS for authentication.", ex); - } - } - private static bool IsTenantAdminSite(ClientRuntimeContext clientContext) { try diff --git a/Commands/ClientSidePages/AddClientSidePage.cs b/Commands/ClientSidePages/AddClientSidePage.cs index 5f1f76b36..351a95794 100644 --- a/Commands/ClientSidePages/AddClientSidePage.cs +++ b/Commands/ClientSidePages/AddClientSidePage.cs @@ -2,6 +2,7 @@ using Microsoft.SharePoint.Client; using OfficeDevPnP.Core.Pages; using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; using System; using System.Management.Automation; @@ -14,6 +15,10 @@ namespace SharePointPnP.PowerShell.Commands.ClientSidePages Code = @"PS:> Add-PnPClientSidePage -Name ""NewPage""", Remarks = "Creates a new Client-Side page named 'NewPage'", SortOrder = 1)] + [CmdletExample( + Code = @"PS:> Add-PnPClientSidePage -Name ""NewPage"" -ContentType ""MyPageContentType""", + Remarks = "Creates a new Client-Side page named 'NewPage' and sets the content type to the content type specified", + SortOrder = 2)] public class AddClientSidePage : PnPWebCmdlet { [Parameter(Mandatory = true, Position = 0, HelpMessage = "Specifies the name of the page.")] @@ -25,6 +30,9 @@ public class AddClientSidePage : PnPWebCmdlet [Parameter(Mandatory = false, HelpMessage = "Allows to promote the page for a specific purpose (HomePage | NewsPage)")] public ClientSidePagePromoteType PromoteAs = ClientSidePagePromoteType.None; + [Parameter(Mandatory = false, HelpMessage = "Specify either the name, ID or an actual content type.")] + public ContentTypePipeBind ContentType; + [Parameter(Mandatory = false, HelpMessage = "Enables or Disables the comments on the page")] public SwitchParameter CommentsEnabled = false; @@ -62,6 +70,34 @@ protected override void ExecuteCmdlet() clientSidePage.LayoutType = LayoutType; clientSidePage.Save(name); + if(MyInvocation.BoundParameters.ContainsKey("ContentType")) + { + ContentType ct = null; + if (ContentType.ContentType == null) + { + if (ContentType.Id != null) + { + ct = SelectedWeb.GetContentTypeById(ContentType.Id, true); + } + else if (ContentType.Name != null) + { + ct = SelectedWeb.GetContentTypeByName(ContentType.Name, true); + } + } + else + { + ct = ContentType.ContentType; + } + if (ct != null) + { + ct.EnsureProperty(w => w.StringId); + + clientSidePage.PageListItem["ContentTypeId"] = ct.StringId; + clientSidePage.PageListItem.SystemUpdate(); + ClientContext.ExecuteQueryRetry(); + } + } + // If a specific promote type is specified, promote the page as Home or Article or ... switch (PromoteAs) { diff --git a/Commands/ClientSidePages/ClientSidePageTransformatorLogType.cs b/Commands/ClientSidePages/ClientSidePageTransformatorLogType.cs new file mode 100644 index 000000000..b8ba1e7f0 --- /dev/null +++ b/Commands/ClientSidePages/ClientSidePageTransformatorLogType.cs @@ -0,0 +1,11 @@ +#if !ONPREMISES +namespace SharePointPnP.PowerShell.Commands.ClientSidePages +{ + public enum ClientSidePageTransformatorLogType + { + None = 0, + File = 1, + SharePoint = 2, + } +} +#endif \ No newline at end of file diff --git a/Commands/ClientSidePages/ConvertToClientSidePage.cs b/Commands/ClientSidePages/ConvertToClientSidePage.cs index f8ef1317d..91ed047ce 100644 --- a/Commands/ClientSidePages/ConvertToClientSidePage.cs +++ b/Commands/ClientSidePages/ConvertToClientSidePage.cs @@ -11,6 +11,8 @@ using System.Xml.Serialization; using SharePointPnP.Modernization.Framework; using System.IO; +using SharePointPnP.Modernization.Framework.Telemetry.Observers; +using SharePointPnP.Modernization.Framework.Publishing; namespace SharePointPnP.PowerShell.Commands.ClientSidePages { @@ -28,16 +30,32 @@ namespace SharePointPnP.PowerShell.Commands.ClientSidePages SortOrder = 2)] [CmdletExample( Code = @"PS:> ConvertTo-PnPClientSidePage -Identity ""somepage.aspx"" -Overwrite -AddPageAcceptBanner", - Remarks = "Converts a wiki page named 'somepage' to a client side page and adds the page accept banner web part on top of the page. This requires that the SPFX solution holding the web part (https://github.com/SharePoint/sp-dev-modernization/blob/master/Solutions/PageTransformationUI/assets/sharepointpnp-pagetransformation-client.sppkg?raw=true) has been installed to the tenant app catalog.", + Remarks = "Converts a wiki page named 'somepage' to a client side page and adds the page accept banner web part on top of the page. This requires that the SPFX solution holding the web part (https://github.com/SharePoint/sp-dev-modernization/blob/master/Solutions/PageTransformationUI/assets/sharepointpnp-pagetransformation-client.sppkg?raw=true) has been installed to the tenant app catalog", SortOrder = 3)] [CmdletExample( Code = @"PS:> ConvertTo-PnPClientSidePage -Identity ""somepage.aspx"" -Overwrite -CopyPageMetadata", Remarks = "Converts a wiki page named 'somepage' to a client side page, including the copying of the page metadata (if any)", SortOrder = 4)] [CmdletExample( + Code = @"PS:> ConvertTo-PnPClientSidePage -Identity ""somepage.aspx"" -PublishingPage -Overwrite -TargetWebUrl https://contoso.sharepoint.com/sites/targetmodernsite", + Remarks = "Converts a publishing page named 'somepage' to a client side pagein the https://contoso.sharepoint.com/sites/targetmodernsite site", + SortOrder = 5)] + [CmdletExample( + Code = @"PS:> ConvertTo-PnPClientSidePage -Identity ""somepage.aspx"" -Library ""SiteAssets"" -Folder ""Folder1"" -Overwrite", + Remarks = "Converts a web part page named 'somepage' living inside the SiteAssets library in a folder named folder1 into a client side page", + SortOrder = 6)] + [CmdletExample( Code = @"PS:> ConvertTo-PnPClientSidePage -Identity ""somepage.aspx"" -Overwrite -TargetWebUrl https://contoso.sharepoint.com/sites/targetmodernsite", Remarks = "Converts a wiki page named 'somepage' to a client side page in the https://contoso.sharepoint.com/sites/targetmodernsite site", - SortOrder = 5)] + SortOrder = 7)] + [CmdletExample( + Code = @"PS:> ConvertTo-PnPClientSidePage -Identity ""somepage.aspx"" -LogType File -LogFolder c:\temp -LogVerbose -Overwrite", + Remarks = "Converts a web part page named 'somepage' and creates a log file in c:\temp using verbose logging", + SortOrder = 8)] + [CmdletExample( + Code = @"PS:> ConvertTo-PnPClientSidePage -Identity ""somepage.aspx"" -LogType SharePoint -LogSkipFlush", + Remarks = "Converts a web part page named 'somepage' and creates a log file in SharePoint but skip the actual write. Use this option to make multiple ConvertTo-PnPClientSidePage invocations create a single log", + SortOrder = 9)] public class ConvertToClientSidePage : PnPWebCmdlet { private Assembly modernizationAssembly; @@ -47,7 +65,13 @@ public class ConvertToClientSidePage : PnPWebCmdlet [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0, HelpMessage = "The name of the page to convert")] public PagePipeBind Identity; - [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, ValueFromPipeline = true, HelpMessage = "Path and name of the web part mapping file driving the transformation", ParameterSetName = "WebPartMappingFile")] + [Parameter(Mandatory = false, ValueFromPipeline = true, Position = 0, HelpMessage = "The name of the library containing the page. If SitePages then please omit this parameter")] + public string Library; + + [Parameter(Mandatory = false, ValueFromPipeline = true, Position = 0, HelpMessage = "The folder to load the provided page from. If not provided all folders are searched")] + public string Folder; + + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, ValueFromPipeline = true, HelpMessage = "Path and name of the web part mapping file driving the transformation")] public string WebPartMappingFile; [Parameter(Mandatory = false, HelpMessage = "Overwrites page if already existing")] @@ -62,37 +86,80 @@ public class ConvertToClientSidePage : PnPWebCmdlet [Parameter(Mandatory = false, HelpMessage = "Adds the page accept banner web part. The actual web part is specified in webpartmapping.xml file")] public SwitchParameter AddPageAcceptBanner = false; - [Parameter(Mandatory = false, HelpMessage = "By default the item level permissions on a page are copied to the created client side page. Use this switch to prevent the copy.")] + [Parameter(Mandatory = false, HelpMessage = "By default the item level permissions on a page are copied to the created client side page. Use this switch to prevent the copy")] public SwitchParameter SkipItemLevelPermissionCopyToClientSidePage = false; - [Parameter(Mandatory = false, HelpMessage = "Clears the cache. Can be needed if you've installed a new web part to the site and want to use that in a custom webpartmapping file. Restarting your PS session has the same effect.")] + [Parameter(Mandatory = false, HelpMessage = "Clears the cache. Can be needed if you've installed a new web part to the site and want to use that in a custom webpartmapping file. Restarting your PS session has the same effect")] public SwitchParameter ClearCache = false; - [Parameter(Mandatory = false, HelpMessage = "Copies the page metadata to the created modern page.")] + [Parameter(Mandatory = false, HelpMessage = "Copies the page metadata to the created modern page")] public SwitchParameter CopyPageMetadata = false; - [Parameter(Mandatory = false, HelpMessage = "Uses the community script editor (https://github.com/SharePoint/sp-dev-fx-webparts/tree/master/samples/react-script-editor) as replacement for the classic script editor web part.")] + [Parameter(Mandatory = false, HelpMessage = "Uses the community script editor (https://github.com/SharePoint/sp-dev-fx-webparts/tree/master/samples/react-script-editor) as replacement for the classic script editor web part")] public SwitchParameter UseCommunityScriptEditor = false; - [Parameter(Mandatory = false, HelpMessage = "By default summarylinks web parts are replaced by QuickLinks, but you can transform to plain html by setting this switch.")] + [Parameter(Mandatory = false, HelpMessage = "By default summarylinks web parts are replaced by QuickLinks, but you can transform to plain html by setting this switch")] public SwitchParameter SummaryLinksToHtml = false; - [Parameter(Mandatory = false, HelpMessage = "Url of the target web that will receive the modern page. Defaults to null which means in-place transformation.")] + [Parameter(Mandatory = false, HelpMessage = "Url of the target web that will receive the modern page. Defaults to null which means in-place transformation")] public string TargetWebUrl; + [Parameter(Mandatory = false, HelpMessage = "Allows to generate a transformation log (File | SharePoint)")] + public ClientSidePageTransformatorLogType LogType = ClientSidePageTransformatorLogType.None; + + [Parameter(Mandatory = false, HelpMessage = "Folder in where the log file will be created (if LogType==File)")] + public string LogFolder = ""; + + [Parameter(Mandatory = false, HelpMessage = "By default each cmdlet invocation will result in a log file, use the -SkipLogFlush to delay the log flushing. The first call without -SkipLogFlush will then write all log entries to a single log")] + public SwitchParameter LogSkipFlush = false; + + [Parameter(Mandatory = false, HelpMessage = "Configure logging to include verbose log entries")] + public SwitchParameter LogVerbose = false; + + [Parameter(Mandatory = false, HelpMessage = "Don't publish the created modern page")] + public SwitchParameter DontPublish = false; + + [Parameter(Mandatory = false, HelpMessage = "Disable comments for the created modern page")] + public SwitchParameter DisablePageComments = false; + + [Parameter(Mandatory = false, HelpMessage = "I'm transforming a publishing page")] + public SwitchParameter PublishingPage = false; + + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, ValueFromPipeline = true, HelpMessage = "Path and name of the page layout mapping file driving the publishing page transformation")] + public string PageLayoutMapping; + + protected override void ExecuteCmdlet() { //Fix loading of modernization framework FixAssemblyResolving(); - + // Load the page to transform - var page = Identity.GetPage(this.ClientContext.Web); + Identity.Library = this.Library; + Identity.Folder = this.Folder; + + ListItem page = null; + if (this.PublishingPage) + { + page = Identity.GetPage(this.ClientContext.Web, CacheManager.Instance.GetPublishingPagesLibraryName(this.ClientContext)); + } + else + { + page = Identity.GetPage(this.ClientContext.Web, "sitepages"); + } if (page == null) { throw new Exception($"Page '{Identity?.Name}' does not exist"); } + // Publishing specific validation + if (this.PublishingPage && string.IsNullOrEmpty(this.TargetWebUrl)) + { + throw new Exception($"Publishing page transformation is only supported when transformating into another site collection. Use the -TargetWebUrl to specify a modern target site."); + } + + // Load transformation models PageTransformation webPartMappingModel = null; if (string.IsNullOrEmpty(this.WebPartMappingFile)) { @@ -107,7 +174,7 @@ protected override void ExecuteCmdlet() webPartMappingModel = (PageTransformation)xmlMapping.Deserialize(stream); } - this.WriteVerbose("Using embedded webpartmapping file (https://github.com/SharePoint/PnP-PowerShell/blob/master/Commands/ClientSidePages/webpartmapping.xml)"); + 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."); } // Validate webpartmappingfile @@ -119,6 +186,11 @@ protected override void ExecuteCmdlet() } } + if (this.PublishingPage && !string.IsNullOrEmpty(this.PageLayoutMapping) && !System.IO.File.Exists(this.PageLayoutMapping)) + { + throw new Exception($"Provided pagelayout mapping file {this.PageLayoutMapping} does not exist"); + } + // Create target client context (when needed) ClientContext targetContext = null; if (!string.IsNullOrEmpty(TargetWebUrl)) @@ -128,35 +200,80 @@ protected override void ExecuteCmdlet() // Create transformator instance PageTransformator pageTransformator = null; + PublishingPageTransformator publishingPageTransformator = null; if (!string.IsNullOrEmpty(this.WebPartMappingFile)) { - // Use web part mapping file - pageTransformator = new PageTransformator(this.ClientContext, targetContext, this.WebPartMappingFile); + // Using custom web part mapping file + if (this.PublishingPage) + { + if (!string.IsNullOrEmpty(this.PageLayoutMapping)) + { + // Using custom page layout mapping file + default one (they're merged together) + publishingPageTransformator = new PublishingPageTransformator(this.ClientContext, targetContext, this.WebPartMappingFile, this.PageLayoutMapping); + } + else + { + // Using default page layout mapping file + publishingPageTransformator = new PublishingPageTransformator(this.ClientContext, targetContext, this.WebPartMappingFile, null); + } + } + else + { + // Use web part mapping file + pageTransformator = new PageTransformator(this.ClientContext, targetContext, this.WebPartMappingFile); + } } else { - // Use web part mapping model loaded from embedded mapping file - pageTransformator = new PageTransformator(this.ClientContext, targetContext, webPartMappingModel); + // Using default web part mapping file + if (this.PublishingPage) + { + if (!string.IsNullOrEmpty(this.PageLayoutMapping)) + { + // Load and validate the custom mapping file + PageLayoutManager pageLayoutManager = new PageLayoutManager(this.ClientContext); + var pageLayoutMappingModel = pageLayoutManager.LoadPageLayoutMappingFile(this.PageLayoutMapping); + + // Using custom page layout mapping file + default one (they're merged together) + publishingPageTransformator = new PublishingPageTransformator(this.ClientContext, targetContext, webPartMappingModel, pageLayoutMappingModel); + } + else + { + // Using default page layout mapping file + publishingPageTransformator = new PublishingPageTransformator(this.ClientContext, targetContext, webPartMappingModel, null); + } + } + else + { + // Use web part mapping model loaded from embedded mapping file + pageTransformator = new PageTransformator(this.ClientContext, targetContext, webPartMappingModel); + } } - // Setup Transformation information - PageTransformationInformation pti = new PageTransformationInformation(page) + // Setup logging + if (this.LogType == ClientSidePageTransformatorLogType.File) { - Overwrite = this.Overwrite, - TargetPageTakesSourcePageName = this.TakeSourcePageName, - ReplaceHomePageWithDefaultHomePage = this.ReplaceHomePageWithDefault, - KeepPageSpecificPermissions = !this.SkipItemLevelPermissionCopyToClientSidePage, - CopyPageMetadata = this.CopyPageMetadata, - ModernizationCenterInformation = new ModernizationCenterInformation() + if (this.PublishingPage) { - AddPageAcceptBanner = this.AddPageAcceptBanner - }, - }; - - // Set mapping properties - pti.MappingProperties["SummaryLinksToQuickLinks"] = (!SummaryLinksToHtml).ToString().ToLower(); - pti.MappingProperties["UseCommunityScriptEditor"] = UseCommunityScriptEditor.ToString().ToLower(); + publishingPageTransformator.RegisterObserver(new MarkdownObserver(folder: this.LogFolder, includeDebugEntries: this.LogVerbose)); + } + else + { + pageTransformator.RegisterObserver(new MarkdownObserver(folder: this.LogFolder, includeDebugEntries: this.LogVerbose)); + } + } + else if (this.LogType == ClientSidePageTransformatorLogType.SharePoint) + { + if (this.PublishingPage) + { + publishingPageTransformator.RegisterObserver(new MarkdownToSharePointObserver(targetContext ?? this.ClientContext, includeDebugEntries: this.LogVerbose)); + } + else + { + pageTransformator.RegisterObserver(new MarkdownToSharePointObserver(targetContext ?? this.ClientContext, includeDebugEntries: this.LogVerbose)); + } + } // Clear the client side component cache if (this.ClearCache) @@ -164,7 +281,64 @@ protected override void ExecuteCmdlet() CacheManager.Instance.ClearAllCaches(); } - string serverRelativeClientPageUrl = pageTransformator.Transform(pti); + string serverRelativeClientPageUrl = ""; + if (this.PublishingPage) + { + // Setup Transformation information + PublishingPageTransformationInformation pti = new PublishingPageTransformationInformation(page) + { + Overwrite = this.Overwrite, + KeepPageSpecificPermissions = !this.SkipItemLevelPermissionCopyToClientSidePage, + PublishCreatedPage = !this.DontPublish, + DisablePageComments = this.DisablePageComments, + }; + + // Set mapping properties + pti.MappingProperties["SummaryLinksToQuickLinks"] = (!SummaryLinksToHtml).ToString().ToLower(); + pti.MappingProperties["UseCommunityScriptEditor"] = UseCommunityScriptEditor.ToString().ToLower(); + + serverRelativeClientPageUrl = publishingPageTransformator.Transform(pti); + } + else + { + // Setup Transformation information + PageTransformationInformation pti = new PageTransformationInformation(page) + { + Overwrite = this.Overwrite, + TargetPageTakesSourcePageName = this.TakeSourcePageName, + ReplaceHomePageWithDefaultHomePage = this.ReplaceHomePageWithDefault, + KeepPageSpecificPermissions = !this.SkipItemLevelPermissionCopyToClientSidePage, + CopyPageMetadata = this.CopyPageMetadata, + PublishCreatedPage = !this.DontPublish, + DisablePageComments = this.DisablePageComments, + ModernizationCenterInformation = new ModernizationCenterInformation() + { + AddPageAcceptBanner = this.AddPageAcceptBanner + }, + }; + + // Set mapping properties + pti.MappingProperties["SummaryLinksToQuickLinks"] = (!SummaryLinksToHtml).ToString().ToLower(); + pti.MappingProperties["UseCommunityScriptEditor"] = UseCommunityScriptEditor.ToString().ToLower(); + + serverRelativeClientPageUrl = pageTransformator.Transform(pti); + } + + // Flush log + if (this.LogType != ClientSidePageTransformatorLogType.None) + { + if (!this.LogSkipFlush) + { + if (this.PublishingPage) + { + publishingPageTransformator.FlushObservers(); + } + else + { + pageTransformator.FlushObservers(); + } + } + } // Output the server relative url to the newly created page if (!string.IsNullOrEmpty(serverRelativeClientPageUrl)) diff --git a/Commands/ClientSidePages/ExportClientSidePageMapping.cs b/Commands/ClientSidePages/ExportClientSidePageMapping.cs new file mode 100644 index 000000000..e8943348d --- /dev/null +++ b/Commands/ClientSidePages/ExportClientSidePageMapping.cs @@ -0,0 +1,161 @@ +#if !ONPREMISES +using SharePointPnP.PowerShell.CmdletHelpAttributes; +using System; +using System.Management.Automation; +using SharePointPnP.PowerShell.Commands.Utilities; +using System.Reflection; +using Microsoft.SharePoint.Client; +using System.IO; +using SharePointPnP.Modernization.Framework.Publishing; + +namespace SharePointPnP.PowerShell.Commands.ClientSidePages +{ + [Cmdlet(VerbsData.Export, "PnPClientSidePageMapping")] + [CmdletHelp("Get's the built-in maping files or a custom mapping file for your publishing portal page layouts. These mapping files are used to tailor the page transformation experience.", + Category = CmdletHelpCategory.ClientSidePages, SupportedPlatform = CmdletSupportedPlatform.Online)] + [CmdletExample(Code = @"PS:> Export-PnPClientSidePageMapping -BuiltInPageLayoutMapping -CustomPageLayoutMapping -Folder c:\\temp -Overwrite", + Remarks = "Exports the built in page layout mapping and analyzes the current site's page layouts and exports these to files in folder c:\\temp", + SortOrder = 1)] + [CmdletExample(Code = @"PS:> Export-PnPClientSidePageMapping -BuiltInWebPartMapping -Folder c:\\temp -Overwrite", + Remarks = "Exports the built in webpart mapping to a file in folder c:\\temp. Use this a starting basis if you want to tailer the web part mapping behavior.", + SortOrder = 2)] + public class ExportClientSidePageMapping : PnPWebCmdlet + { + private Assembly modernizationAssembly; + private Assembly sitesCoreAssembly; + private Assembly newtonsoftAssembly; + + [Parameter(Mandatory = false, HelpMessage = "Exports the builtin web part mapping file")] + public SwitchParameter BuiltInWebPartMapping = false; + + [Parameter(Mandatory = false, HelpMessage = "Exports the builtin pagelayout mapping file (only needed for publishing page transformation)")] + public SwitchParameter BuiltInPageLayoutMapping = false; + + [Parameter(Mandatory = false, HelpMessage = "Analyzes the pagelayouts in the current publishing portal and exports them as a pagelayout mapping file")] + public SwitchParameter CustomPageLayoutMapping = false; + + [Parameter(Mandatory = false, ValueFromPipeline = true, Position = 0, HelpMessage = "The folder to created the mapping file(s) in")] + public string Folder; + + [Parameter(Mandatory = false, HelpMessage = "Overwrites existing mapping files")] + public SwitchParameter Overwrite = false; + + protected override void ExecuteCmdlet() + { + //Fix loading of modernization framework + FixAssemblyResolving(); + + // Configure folder to export + string folderToExportTo = Environment.CurrentDirectory; + if (!string.IsNullOrEmpty(this.Folder)) + { + if (!Directory.Exists(this.Folder)) + { + throw new Exception($"Folder '{this.Folder}' does not exist"); + } + + folderToExportTo = this.Folder; + } + + // Export built in web part mapping + if (this.BuiltInWebPartMapping) + { + string fileName = Path.Combine(folderToExportTo, "webpartmapping.xml"); + + if (System.IO.File.Exists(fileName) && !Overwrite) + { + Console.WriteLine($"Skipping the export from the built-in webpart mapping file {fileName} as this already exists. Use the -Overwrite flag to overwrite if needed."); + } + 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"); + System.IO.File.WriteAllText(fileName, webpartMappingFileContents); + } + } + + // Export built in page layout mapping + if (this.BuiltInPageLayoutMapping) + { + string fileName = Path.Combine(folderToExportTo, "pagelayoutmapping.xml"); + + if (System.IO.File.Exists(fileName) && !Overwrite) + { + Console.WriteLine($"Skipping the export from the built-in pagelayout mapping file {fileName} as this already exists. Use the -Overwrite flag to overwrite if needed."); + } + 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"); + System.IO.File.WriteAllText(fileName, pageLayoutMappingFileContents); + } + } + + // Export custom page layout mapping + if (this.CustomPageLayoutMapping) + { + if (!this.ClientContext.Web.IsPublishingWeb()) + { + throw new Exception("The -CustomPageLayoutMapping parameter only works for publishing sites."); + } + + Guid siteId = this.ClientContext.Site.EnsureProperty(p => p.Id); + string fileName = $"custompagelayoutmapping-{siteId.ToString()}.xml"; + + if (System.IO.File.Exists(Path.Combine(folderToExportTo, fileName)) && !Overwrite) + { + Console.WriteLine($"Skipping the export from the custom pagelayout mapping file {Path.Combine(folderToExportTo, fileName)} as this already exists. Use the -Overwrite flag to overwrite if needed."); + } + else + { + var analyzer = new PageLayoutAnalyser(this.ClientContext); + analyzer.AnalyseAll(); + + analyzer.GenerateMappingFile(folderToExportTo, fileName); + } + } + } + + private string AssemblyDirectory + { + get + { + string codeBase = Assembly.GetExecutingAssembly().CodeBase; + UriBuilder uri = new UriBuilder(codeBase); + string path = Uri.UnescapeDataString(uri.Path); + return Path.GetDirectoryName(path); + } + } + + private void FixAssemblyResolving() + { + try + { + newtonsoftAssembly = Assembly.LoadFrom(Path.Combine(AssemblyDirectory, "NewtonSoft.Json.dll")); + sitesCoreAssembly = Assembly.LoadFrom(Path.Combine(AssemblyDirectory, "OfficeDevPnP.Core.dll")); + modernizationAssembly = Assembly.LoadFrom(Path.Combine(AssemblyDirectory, "SharePointPnP.Modernization.Framework.dll")); + AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; + } + catch { } + } + + private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) + { + if (args.Name.StartsWith("OfficeDevPnP.Core")) + { + return sitesCoreAssembly; + } + if (args.Name.StartsWith("Newtonsoft.Json")) + { + return newtonsoftAssembly; + } + if (args.Name.StartsWith("SharePointPnP.Modernization.Framework")) + { + return modernizationAssembly; + } + return null; + } + + } +} +#endif \ No newline at end of file diff --git a/Commands/ClientSidePages/SetClientSidePage.cs b/Commands/ClientSidePages/SetClientSidePage.cs index c8d825e35..0fd782f19 100644 --- a/Commands/ClientSidePages/SetClientSidePage.cs +++ b/Commands/ClientSidePages/SetClientSidePage.cs @@ -4,6 +4,7 @@ using SharePointPnP.PowerShell.Commands.Base.PipeBinds; using System; using System.Management.Automation; +using Microsoft.SharePoint.Client; namespace SharePointPnP.PowerShell.Commands.ClientSidePages { @@ -65,6 +66,9 @@ public class SetClientSidePage : PnPWebCmdlet, IDynamicParameters [Parameter(Mandatory = false, HelpMessage = "Sets the page header type")] public ClientSidePageHeaderType HeaderType; + [Parameter(Mandatory = false, HelpMessage = "Specify either the name, ID or an actual content type.")] + public ContentTypePipeBind ContentType; + [Obsolete("This parameter value will be ignored")] [Parameter(Mandatory = false, HelpMessage = "Sets the message for publishing the page.")] public string PublishMessage = string.Empty; @@ -152,6 +156,34 @@ protected override void ExecuteCmdlet() } } + if(MyInvocation.BoundParameters.ContainsKey("ContentType")) + { + ContentType ct = null; + if (ContentType.ContentType == null) + { + if (ContentType.Id != null) + { + ct = SelectedWeb.GetContentTypeById(ContentType.Id, true); + } + else if (ContentType.Name != null) + { + ct = SelectedWeb.GetContentTypeByName(ContentType.Name, true); + } + } + else + { + ct = ContentType.ContentType; + } + if (ct != null) + { + ct.EnsureProperty(w => w.StringId); + + clientSidePage.PageListItem["ContentTypeId"] = ct.StringId; + clientSidePage.PageListItem.SystemUpdate(); + ClientContext.ExecuteQueryRetry(); + } + } + if (Publish) { clientSidePage.Publish(); diff --git a/Commands/ClientSidePages/pagelayoutmapping.xml b/Commands/ClientSidePages/pagelayoutmapping.xml new file mode 100644 index 000000000..7404a85e2 --- /dev/null +++ b/Commands/ClientSidePages/pagelayoutmapping.xml @@ -0,0 +1,282 @@ + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + +
+ + + + + + + + + + + + + + + +
+ + +
+ + + +
+ + + + + + + + + + + + + + + +
+ + +
+ + + +
+ + + + + + + + + + + + + + + +
+ + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + +
+ + + + + + + + + + + + + + + + +
+ + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + +
+ + + + + + + + + + +
+ + +
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/Commands/ClientSidePages/webpartmapping.xml b/Commands/ClientSidePages/webpartmapping.xml index a593ac9d8..b34dfdad6 100644 --- a/Commands/ClientSidePages/webpartmapping.xml +++ b/Commands/ClientSidePages/webpartmapping.xml @@ -182,7 +182,7 @@ configure the page transformation engine to use the community script editor. You - + + + + + + + + + + + + + + + @@ -981,6 +996,20 @@ configure the page transformation engine to use the community script editor. You + + + + + + + + + + + + + + @@ -1022,20 +1051,6 @@ configure the page transformation engine to use the community script editor. You - - - - - - - - - - - - - - @@ -1190,16 +1205,6 @@ configure the page transformation engine to use the community script editor. You - - - - - - - - - - diff --git a/Commands/Graph/EnableSiteClassification.cs b/Commands/Graph/EnableSiteClassification.cs index 81852f426..388358055 100644 --- a/Commands/Graph/EnableSiteClassification.cs +++ b/Commands/Graph/EnableSiteClassification.cs @@ -21,7 +21,7 @@ namespace SharePointPnP.PowerShell.Commands.Graph SortOrder = 1)] [CmdletExample( Code = @"PS:> Connect-PnPOnline -Scopes ""Directory.ReadWrite.All"" -PS:> Enable-PnPSiteClassification -Classifications ""HBI"",""LBI"",""Top Secret"" -UsageGuidelinesUrl http://aka.ms/sppnp", +PS:> Enable-PnPSiteClassification -Classifications ""HBI"",""LBI"",""Top Secret"" -UsageGuidelinesUrl https://aka.ms/sppnp", Remarks = @"Enables Site Classifications for your tenant and provides three classification values. The usage guideliness will be set to the specified URL.", SortOrder = 2)] public class EnableSiteClassification : PnPGraphCmdlet diff --git a/Commands/Graph/NewUnifiedGroup.cs b/Commands/Graph/NewUnifiedGroup.cs index 3a9e15591..cf98b64da 100644 --- a/Commands/Graph/NewUnifiedGroup.cs +++ b/Commands/Graph/NewUnifiedGroup.cs @@ -56,6 +56,9 @@ public class NewPnPUnifiedGroup : PnPGraphCmdlet [Parameter(Mandatory = false, HelpMessage = "The path to the logo file of to set.")] public string GroupLogoPath; + [Parameter(Mandatory = false, HelpMessage = "Creates a MS Teams team associated with created group.")] + public SwitchParameter CreateTeam; + [Parameter(Mandatory = false, HelpMessage = "Specifying the Force parameter will skip the confirmation question.")] public SwitchParameter Force; @@ -85,17 +88,18 @@ protected override void ExecuteCmdlet() if (forceCreation) { var group = UnifiedGroupsUtility.CreateUnifiedGroup( - DisplayName, - Description, - MailNickname, - AccessToken, - Owners, - Members, - GroupLogoPath, - IsPrivate); + displayName: DisplayName, + description: Description, + mailNickname: MailNickname, + accessToken: AccessToken, + owners: Owners, + members: Members, + groupLogoPath: GroupLogoPath, + isPrivate: IsPrivate, + createTeam: CreateTeam); WriteObject(group); } } } -} +} \ No newline at end of file diff --git a/Commands/Graph/SetUnifiedGroup.cs b/Commands/Graph/SetUnifiedGroup.cs index 604aa900e..e8603a8c9 100644 --- a/Commands/Graph/SetUnifiedGroup.cs +++ b/Commands/Graph/SetUnifiedGroup.cs @@ -56,6 +56,9 @@ public class SetUnifiedGroup : PnPGraphCmdlet [Parameter(Mandatory = false, HelpMessage = "The path to the logo file of to set.")] public string GroupLogoPath; + [Parameter(Mandatory = false, HelpMessage = "Creates a MS Teams team associated with created group.")] + public SwitchParameter CreateTeam; + protected override void ExecuteCmdlet() { UnifiedGroupEntity group = null; @@ -78,12 +81,20 @@ protected override void ExecuteCmdlet() groupLogoStream = new FileStream(GroupLogoPath, FileMode.Open, FileAccess.Read); } bool? isPrivateGroup = null; - if(IsPrivate.IsPresent) + if (IsPrivate.IsPresent) { isPrivateGroup = IsPrivate.ToBool(); } - UnifiedGroupsUtility.UpdateUnifiedGroup(group.GroupId, AccessToken, displayName: DisplayName, - description: Description, owners: Owners, members: Members, groupLogo: groupLogoStream, isPrivate: isPrivateGroup); + UnifiedGroupsUtility.UpdateUnifiedGroup( + groupId: group.GroupId, + accessToken: AccessToken, + displayName: DisplayName, + description: Description, + owners: Owners, + members: Members, + groupLogo: groupLogoStream, + isPrivate: isPrivateGroup, + createTeam: CreateTeam); } else { diff --git a/Commands/Graph/UpdateSiteClassification.cs b/Commands/Graph/UpdateSiteClassification.cs index d7055ddfc..198cfd15a 100644 --- a/Commands/Graph/UpdateSiteClassification.cs +++ b/Commands/Graph/UpdateSiteClassification.cs @@ -27,7 +27,7 @@ namespace SharePointPnP.PowerShell.Commands.Graph SortOrder = 2)] [CmdletExample( Code = @"PS:> Connect-PnPOnline -Scopes ""Directory.ReadWrite.All"" -PS:> Update-PnPSiteClassification -UsageGuidelinesUrl http://aka.ms/sppnp", +PS:> Update-PnPSiteClassification -UsageGuidelinesUrl https://aka.ms/sppnp", Remarks = @"sets the usage guideliness URL to the specified URL.", SortOrder = 3)] public class UpdateSiteClassification : PnPGraphCmdlet diff --git a/Commands/Lists/AddListItem.cs b/Commands/Lists/AddListItem.cs index 8b6cdced6..e5d433714 100644 --- a/Commands/Lists/AddListItem.cs +++ b/Commands/Lists/AddListItem.cs @@ -18,6 +18,7 @@ namespace SharePointPnP.PowerShell.Commands.Lists { [Cmdlet(VerbsCommon.Add, "PnPListItem")] [CmdletHelp("Adds an item to a list", + 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")] diff --git a/Commands/Lists/SetView.cs b/Commands/Lists/SetView.cs index 10d3a67bc..1ab97f4b5 100644 --- a/Commands/Lists/SetView.cs +++ b/Commands/Lists/SetView.cs @@ -21,6 +21,10 @@ namespace SharePointPnP.PowerShell.Commands.Fields Code = @"PS:> Get-PnPList -Identity ""Tasks"" | Get-PnPView | Set-PnPView -Values @{JSLink=""hierarchytaskslist.js|customrendering.js""}", Remarks = @"Updates all views on list ""Tasks"" to use hierarchytaskslist.js and customrendering.js for the JSLink", SortOrder = 2)] + [CmdletExample( + Code = @"PS:> Set-PnPView -List ""Documents"" -Identity ""Corporate Documents"" -Fields ""Title"",""Created""", + Remarks = @"Updates the Corporate Documents view on the Documents library to have two fields", + SortOrder = 2)] public class SetView : PnPWebCmdlet { [Parameter(Mandatory = false, Position = 0, HelpMessage = "The Id, Title or Url of the list")] @@ -29,9 +33,12 @@ public class SetView : PnPWebCmdlet [Parameter(Mandatory = true, ValueFromPipeline = true, HelpMessage = "The Id, Title or instance of the view")] public ViewPipeBind Identity; - [Parameter(Mandatory = true, HelpMessage = "Hashtable of properties to update on the view. Use the syntax @{property1=\"value\";property2=\"value\"}.")] + [Parameter(Mandatory = false, HelpMessage = "Hashtable of properties to update on the view. Use the syntax @{property1=\"value\";property2=\"value\"}.")] public Hashtable Values; + [Parameter(Mandatory = false, HelpMessage = "An array of fields to use in the view. Notice that specifying this value will remove the existing fields")] + public string[] Fields; + protected override void ExecuteCmdlet() { List list; @@ -70,35 +77,49 @@ protected override void ExecuteCmdlet() throw new PSArgumentException("View provided in the Identity argument could not be found", "Identity"); } - bool atLeastOnePropertyChanged = false; - foreach (string key in Values.Keys) + if (MyInvocation.BoundParameters.ContainsKey(nameof(Values))) { - var value = Values[key]; - - var property = view.GetType().GetProperty(key); - if (property == null) + bool atLeastOnePropertyChanged = false; + foreach (string key in Values.Keys) { - WriteWarning($"No property '{key}' found on this view. Value will be ignored."); - } - else - { - try + var value = Values[key]; + + var property = view.GetType().GetProperty(key); + if (property == null) { - property.SetValue(view, value); - atLeastOnePropertyChanged = true; + WriteWarning($"No property '{key}' found on this view. Value will be ignored."); } - catch (Exception e) + else { - WriteWarning($"Setting property '{key}' to '{value}' failed with exception '{e.Message}'. Value will be ignored."); + try + { + property.SetValue(view, value); + atLeastOnePropertyChanged = true; + } + catch (Exception e) + { + WriteWarning($"Setting property '{key}' to '{value}' failed with exception '{e.Message}'. Value will be ignored."); + } } } - } - if (atLeastOnePropertyChanged) + if (atLeastOnePropertyChanged) + { + view.Update(); + ClientContext.ExecuteQueryRetry(); + } + } + if(MyInvocation.BoundParameters.ContainsKey(nameof(Fields))) { + view.ViewFields.RemoveAll(); + foreach(var viewField in Fields) + { + view.ViewFields.Add(viewField); + } view.Update(); ClientContext.ExecuteQueryRetry(); } + } } } \ No newline at end of file diff --git a/Commands/Principals/AddAlert.cs b/Commands/Principals/AddAlert.cs index 00503a7a0..c442b2fd9 100644 --- a/Commands/Principals/AddAlert.cs +++ b/Commands/Principals/AddAlert.cs @@ -1,7 +1,6 @@ #if !SP2013 && !SP2016 using System; using System.Collections.Generic; -using System.Linq.Expressions; using System.Management.Automation; using Microsoft.SharePoint.Client; using SharePointPnP.PowerShell.CmdletHelpAttributes; diff --git a/Commands/Principals/GetAlert.cs b/Commands/Principals/GetAlert.cs index 03a1f0dc9..965cf4db3 100644 --- a/Commands/Principals/GetAlert.cs +++ b/Commands/Principals/GetAlert.cs @@ -4,7 +4,6 @@ using SharePointPnP.PowerShell.Commands.Base.PipeBinds; using System; using System.Linq; -using System.Linq.Expressions; using System.Management.Automation; namespace SharePointPnP.PowerShell.Commands.Principals @@ -25,6 +24,10 @@ namespace SharePointPnP.PowerShell.Commands.Principals Code = @"PS:> Get-PnPAlert -List ""Demo List"" -User ""i:0#.f|membership|Alice@contoso.onmicrosoft.com""", Remarks = @"Returns all alerts registered on the given list for the specified user.", SortOrder = 3)] + [CmdletExample( + Code = @"PS:> Get-PnPAlert -Title ""Demo Alert""", + Remarks = @"Returns all alerts with the given title for the current user. Title comparison is case sensitive.", + SortOrder = 4)] public class GetAlert : PnPWebCmdlet { [Parameter(Mandatory = false, ValueFromPipeline = true, Position = 0, HelpMessage = "The ID, Title or Url of the list.")] @@ -33,6 +36,9 @@ public class GetAlert : PnPWebCmdlet [Parameter(Mandatory = false, HelpMessage = "User to retrieve the alerts for (User ID, login name or actual User object). Skip this parameter to retrieve the alerts for the current user. Note: Only site owners can retrieve alerts for other users.")] public UserPipeBind User; + [Parameter(Mandatory = false, HelpMessage = "Retrieve alerts with this title. Title comparison is case sensitive.")] + public string Title; + protected override void ExecuteCmdlet() { List list = null; @@ -58,10 +64,18 @@ protected override void ExecuteCmdlet() } user.EnsureProperty(u => u.Alerts.IncludeWithDefaultProperties(a => a.ListID)); - if (list != null) + if (list != null && !string.IsNullOrWhiteSpace(Title)) + { + WriteObject(user.Alerts.Where(l => l.ListID == list.Id && l.Title == Title), true); + } + else if (list != null) { WriteObject(user.Alerts.Where(l => l.ListID == list.Id), true); } + else if (!string.IsNullOrWhiteSpace(Title)) + { + WriteObject(user.Alerts.Where(l => l.Title == Title), true); + } else { WriteObject(user.Alerts, true); diff --git a/Commands/Principals/RemoveAlert.cs b/Commands/Principals/RemoveAlert.cs index 0ce7eb41d..31a09316f 100644 --- a/Commands/Principals/RemoveAlert.cs +++ b/Commands/Principals/RemoveAlert.cs @@ -17,11 +17,11 @@ namespace SharePointPnP.PowerShell.Commands.Principals SortOrder = 1)] [CmdletExample( Code = @"PS:> Remove-PnPAlert -Identity 641ac67f-0ce0-4837-874a-743c8f8572a7 -User ""i:0#.f|membership|Alice@contoso.onmicrosoft.com""", - Remarks = @"Removes the laert with the specified ID for the user specified", + Remarks = @"Removes the alert with the specified ID for the user specified", SortOrder = 2)] public class RemoveAlert : PnPWebCmdlet { - [Parameter(Mandatory = false, HelpMessage = "User to remove the alert for (User ID, login name or actual User object). Skip this parameter to usethe current user. Note: Only site owners can remove alerts for other users.")] + [Parameter(Mandatory = false, HelpMessage = "User to remove the alert for (User ID, login name or actual User object). Skip this parameter to use the current user. Note: Only site owners can remove alerts for other users.")] public UserPipeBind User; [Parameter(Mandatory = true, HelpMessage = "The alert id, or the actual alert object to remove.")] diff --git a/Commands/Properties/AssemblyInfo.cs b/Commands/Properties/AssemblyInfo.cs index 4e9cc9ca8..01f50b5ee 100644 --- a/Commands/Properties/AssemblyInfo.cs +++ b/Commands/Properties/AssemblyInfo.cs @@ -48,6 +48,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.7.1903.0")] -[assembly: AssemblyFileVersion("3.7.1903.0")] +[assembly: AssemblyVersion("3.8.1904.0")] +[assembly: AssemblyFileVersion("3.8.1904.0")] [assembly: InternalsVisibleTo("SharePointPnP.PowerShell.Tests")] \ No newline at end of file diff --git a/Commands/Provisioning/Site/ApplyProvisioningTemplate.cs b/Commands/Provisioning/Site/ApplyProvisioningTemplate.cs index 37ede3ab0..e8eca2fd8 100644 --- a/Commands/Provisioning/Site/ApplyProvisioningTemplate.cs +++ b/Commands/Provisioning/Site/ApplyProvisioningTemplate.cs @@ -48,7 +48,7 @@ namespace SharePointPnP.PowerShell.Commands.Provisioning.Site [CmdletExample( Code = @" PS:> $handler1 = New-PnPExtensibilityHandlerObject -Assembly Contoso.Core.Handlers -Type Contoso.Core.Handlers.MyExtensibilityHandler1 -PS:> $handler2 = New-PnPExtensibilityHandlerObject -Assembly Contoso.Core.Handlers -Type Contoso.Core.Handlers.MyExtensibilityHandler1 +PS:> $handler2 = New-PnPExtensibilityHandlerObject -Assembly Contoso.Core.Handlers -Type Contoso.Core.Handlers.MyExtensibilityHandler2 PS:> Apply-PnPProvisioningTemplate -Path NewTemplate.xml -ExtensibilityHandlers $handler1,$handler2", Remarks = @"This will create two new ExtensibilityHandler objects that are run while provisioning the template", SortOrder = 7)] diff --git a/Commands/Provisioning/Site/GetProvisioningTemplate.cs b/Commands/Provisioning/Site/GetProvisioningTemplate.cs index 63b5d3edb..207a66ee3 100644 --- a/Commands/Provisioning/Site/GetProvisioningTemplate.cs +++ b/Commands/Provisioning/Site/GetProvisioningTemplate.cs @@ -50,7 +50,7 @@ namespace SharePointPnP.PowerShell.Commands.Provisioning.Site [CmdletExample( Code = @" PS:> $handler1 = New-PnPExtensibilityHandlerObject -Assembly Contoso.Core.Handlers -Type Contoso.Core.Handlers.MyExtensibilityHandler1 -PS:> $handler2 = New-PnPExtensibilityHandlerObject -Assembly Contoso.Core.Handlers -Type Contoso.Core.Handlers.MyExtensibilityHandler1 +PS:> $handler2 = New-PnPExtensibilityHandlerObject -Assembly Contoso.Core.Handlers -Type Contoso.Core.Handlers.MyExtensibilityHandler2 PS:> Get-PnPProvisioningTemplate -Out NewTemplate.xml -ExtensibilityHandlers $handler1,$handler2", Remarks = @"This will create two new ExtensibilityHandler objects that are run during extraction of the template", SortOrder = 8)] diff --git a/Commands/SharePointPnP.PowerShell.Commands.csproj b/Commands/SharePointPnP.PowerShell.Commands.csproj index f7a891978..4df3369ca 100644 --- a/Commands/SharePointPnP.PowerShell.Commands.csproj +++ b/Commands/SharePointPnP.PowerShell.Commands.csproj @@ -538,9 +538,13 @@ + + + + @@ -592,6 +596,8 @@ + + @@ -1090,6 +1096,7 @@ + diff --git a/Commands/UserProfiles/NewUPABulkImportJob.cs b/Commands/UserProfiles/NewUPABulkImportJob.cs index bd62f919b..1e34d7676 100644 --- a/Commands/UserProfiles/NewUPABulkImportJob.cs +++ b/Commands/UserProfiles/NewUPABulkImportJob.cs @@ -83,7 +83,7 @@ protected override void ExecuteCmdlet() var o365 = new Office365Tenant(ClientContext); var propDictionary = UserProfilePropertyMapping.Cast().ToDictionary(kvp => (string)kvp.Key, kvp => (string)kvp.Value); var url = new Uri(webCtx.Url).GetLeftPart(UriPartial.Authority) + file.ServerRelativeUrl; - var id = o365.QueueImportProfileProperties(ImportProfilePropertiesUserIdType.Email, IdProperty, propDictionary, url); + var id = o365.QueueImportProfileProperties(IdType, IdProperty, propDictionary, url); ClientContext.ExecuteQueryRetry(); var job = o365.GetImportProfilePropertyJob(id.Value); @@ -93,4 +93,4 @@ protected override void ExecuteCmdlet() } } } -#endif \ No newline at end of file +#endif diff --git a/Commands/Web/AddIndexedProperty.cs b/Commands/Web/AddIndexedProperty.cs index fa2eb6d73..ad8064020 100644 --- a/Commands/Web/AddIndexedProperty.cs +++ b/Commands/Web/AddIndexedProperty.cs @@ -1,6 +1,7 @@ using System.Management.Automation; using Microsoft.SharePoint.Client; using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; namespace SharePointPnP.PowerShell.Commands { @@ -12,11 +13,25 @@ public class AddIndexedProperty : PnPWebCmdlet [Parameter(Mandatory = true, Position = 0, HelpMessage = @"Key of the property bag value to be indexed")] public string Key; + [Parameter(Mandatory = false, ValueFromPipeline = true, HelpMessage = "The list object or name where to set the indexed property")] + public ListPipeBind List; + protected override void ExecuteCmdlet() { if (!string.IsNullOrEmpty(Key)) { - SelectedWeb.AddIndexedPropertyBagKey(Key); + if (List != null) + { + var list = List.GetList(SelectedWeb); + if (list != null) + { + list.AddIndexedPropertyBagKey(Key); + } + } + else + { + SelectedWeb.AddIndexedPropertyBagKey(Key); + } } } } diff --git a/Commands/Web/GetIndexedPropertyKeys.cs b/Commands/Web/GetIndexedPropertyKeys.cs index 9a2fdc73f..29e9e4e49 100644 --- a/Commands/Web/GetIndexedPropertyKeys.cs +++ b/Commands/Web/GetIndexedPropertyKeys.cs @@ -1,6 +1,7 @@ using System.Management.Automation; using Microsoft.SharePoint.Client; using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; namespace SharePointPnP.PowerShell.Commands { @@ -9,10 +10,25 @@ namespace SharePointPnP.PowerShell.Commands Category = CmdletHelpCategory.Webs)] public class GetIndexedProperties : PnPWebCmdlet { + [Parameter(Mandatory = false, ValueFromPipeline = true, HelpMessage = "The list object or name from where to get the indexed properties")] + public ListPipeBind List; + protected override void ExecuteCmdlet() { - var keys = SelectedWeb.GetIndexedPropertyBagKeys(); - WriteObject(keys); + if (List != null) + { + var list = List.GetList(SelectedWeb); + if (list != null) + { + var keys = list.GetIndexedPropertyBagKeys(); + WriteObject(keys); + } + } + else + { + var keys = SelectedWeb.GetIndexedPropertyBagKeys(); + WriteObject(keys); + } } } } diff --git a/Commands/Web/RemovedIndexedProperty.cs b/Commands/Web/RemovedIndexedProperty.cs index c3d1d9a28..e7a8c0de9 100644 --- a/Commands/Web/RemovedIndexedProperty.cs +++ b/Commands/Web/RemovedIndexedProperty.cs @@ -1,6 +1,7 @@ using System.Management.Automation; using Microsoft.SharePoint.Client; using SharePointPnP.PowerShell.CmdletHelpAttributes; +using SharePointPnP.PowerShell.Commands.Base.PipeBinds; namespace SharePointPnP.PowerShell.Commands { @@ -8,19 +9,32 @@ namespace SharePointPnP.PowerShell.Commands [CmdletHelp("Removes a key from propertybag to be indexed by search. The key and it's value remain in the propertybag, however it will not be indexed anymore.", Category = CmdletHelpCategory.Webs)] [CmdletExample( - Code = @"PS:> Remove-PnPIndexedProperty -key ""MyIndexProperty""", - Remarks = @"Removes the Indexed property ""MyIndexProperty"" from the current web", + Code = @"PS:> Remove-PnPIndexedProperty -key ""MyIndexProperty""", + Remarks = @"Removes the Indexed property ""MyIndexProperty"" from the current web", SortOrder = 1)] public class RemovedIndexedProperty : PnPWebCmdlet { [Parameter(Mandatory = true, Position = 0, HelpMessage = @"Key of the property bag value to be removed from indexing")] public string Key; + [Parameter(Mandatory = false, ValueFromPipeline = true, HelpMessage = "The list object or name from where to remove the indexed properties")] + public ListPipeBind List; protected override void ExecuteCmdlet() { if (!string.IsNullOrEmpty(Key)) { - SelectedWeb.RemoveIndexedPropertyBagKey(Key); + if (List != null) + { + var list = List.GetList(SelectedWeb); + if (list != null) + { + list.RemoveIndexedPropertyBagKey(Key); + } + } + else + { + SelectedWeb.RemoveIndexedPropertyBagKey(Key); + } } } } diff --git a/Tests/PrincipalsTests.cs b/Tests/PrincipalsTests.cs new file mode 100644 index 000000000..dffda2020 --- /dev/null +++ b/Tests/PrincipalsTests.cs @@ -0,0 +1,245 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.SharePoint.Client; +using System.Management.Automation.Runspaces; +using System.Linq; +using SharePointPnP.PowerShell.Commands.Enums; + +namespace SharePointPnP.PowerShell.Tests +{ + [TestClass] + public class PrincipalsTests + { + [TestInitialize] + public void Initialize() + { + using (var ctx = TestCommon.CreateClientContext()) + { + ctx.Web.CreateList(ListTemplateType.GenericList, "PnPTestList", false); + } + } + + [TestCleanup] + public void Cleanup() + { + using (var ctx = TestCommon.CreateClientContext()) + { + var list = ctx.Web.GetListByTitle("PnPTestList"); + if (list != null) + { + list.DeleteObject(); + ctx.ExecuteQueryRetry(); + } + + list = ctx.Web.GetListByTitle("PnPTestList2"); + if (list != null) + { + list.DeleteObject(); + ctx.ExecuteQueryRetry(); + } + } + } + +#if !SP2013 && !SP2016 + [TestMethod] + public void AddAlert_WithDefaultProperties_Test() + { + using (var ctx = TestCommon.CreateClientContext()) + { + ctx.Web.EnsureProperties(w => w.CurrentUser.Id, w => w.CurrentUser.LoginName); + var currentUser = ctx.Web.CurrentUser; + var randomizer = new Random(); + var randomAlertTitle = randomizer.Next(int.MaxValue).ToString(); + var list = ctx.Web.GetListByTitle("PnPTestList"); + list.EnsureProperties(l => l.Id); + var listId = list.Id; + + // Execute cmd-let + using (var scope = new PSTestScope(true)) + { + var results = scope.ExecuteCommand("Add-PnPAlert", + new CommandParameter("List", listId), + new CommandParameter("Title", randomAlertTitle)); + Assert.IsNotNull(results); + Assert.IsTrue(results.Count > 0); + Assert.IsTrue(results[0].BaseObject.GetType() == typeof(AlertCreationInformation)); + var alertInfo = results[0].BaseObject as AlertCreationInformation; + Assert.AreEqual(randomAlertTitle, alertInfo.Title); + } + + // get actual alert and check properties + ctx.Web.EnsureProperties(w => w.CurrentUser.Alerts); + var newAlerts = currentUser.Alerts.Where(a => a.Title == randomAlertTitle); + Assert.AreEqual(1, newAlerts.Count(), "Unexpected number of created alerts"); + var newAlert = newAlerts.First(); + newAlert.EnsureProperty(a => a.Properties); + newAlert.User.EnsureProperties(u => u.LoginName); + newAlert.List.EnsureProperties(l => l.Id); + ctx.ExecuteQueryRetry(); + + try + { + Assert.AreEqual(AlertFrequency.Immediate, newAlert.AlertFrequency); + Assert.AreEqual(AlertDeliveryChannel.Email, newAlert.DeliveryChannels); + Assert.AreEqual(AlertEventType.All, newAlert.EventType); + Assert.AreEqual(AlertStatus.On, newAlert.Status); + Assert.AreEqual(AlertType.List, newAlert.AlertType); + Assert.AreEqual(currentUser.LoginName, newAlert.User.LoginName); + Assert.AreEqual(listId, newAlert.List.Id); + Assert.AreEqual(1, newAlert.Properties.Count, "Unexpected number of properties"); + Assert.IsTrue(newAlert.Properties.ContainsKey("filterindex")); + Assert.AreEqual("0", newAlert.Properties["filterindex"]); + } + finally + { + // delete alert + currentUser.Alerts.DeleteAlert(newAlert.ID); + currentUser.Update(); + ctx.ExecuteQueryRetry(); + } + } + } +#endif + +#if !SP2013 && !SP2016 + [TestMethod] + public void AddAlert_WithNonDefaultProperties_Test() + { + using (var ctx = TestCommon.CreateClientContext()) + { + ctx.Web.EnsureProperties(w => w.CurrentUser.Id, w => w.CurrentUser.LoginName); + var currentUser = ctx.Web.CurrentUser; + // generate random alert title + var randomizer = new Random(); + var alertTitle = randomizer.Next(int.MaxValue).ToString(); + var list = ctx.Web.GetListByTitle("PnPTestList"); + list.EnsureProperties(l => l.Id); + var listId = list.Id; + var currentTime = DateTime.Now; + // note: SharePoint rounds the milliseconds away, so use a time without milliseconds for testing + var alertTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, currentTime.Minute, currentTime.Minute); + + // Execute cmd-let + using (var scope = new PSTestScope(true)) + { + var results = scope.ExecuteCommand("Add-PnPAlert", + new CommandParameter("List", "PnPTestList"), + new CommandParameter("Title", alertTitle), + new CommandParameter("DeliveryMethod", AlertDeliveryChannel.Email), // cannot use SmS without having Frequency set to "Immediate" + new CommandParameter("ChangeType", AlertEventType.DeleteObject), + new CommandParameter("Frequency", AlertFrequency.Weekly), + new CommandParameter("Time", alertTime), + new CommandParameter("Filter", AlertFilter.SomeoneElseChangesAnItem) + ); + Assert.IsNotNull(results); + Assert.IsTrue(results.Count > 0); + } + + // get actual alert and check properties + ctx.Web.EnsureProperties(w => w.CurrentUser.Alerts); + var newAlerts = currentUser.Alerts.Where(a => a.Title == alertTitle); + Assert.AreEqual(1, newAlerts.Count(), "Unexpected number of created alerts"); + var newAlert = newAlerts.First(); + newAlert.EnsureProperty(a => a.AlertTime); + newAlert.EnsureProperty(a => a.Properties); + newAlert.User.EnsureProperties(u => u.LoginName); + newAlert.List.EnsureProperties(l => l.Id); + ctx.ExecuteQueryRetry(); + + try + { + Assert.AreEqual(AlertFrequency.Weekly, newAlert.AlertFrequency); + Assert.AreEqual(AlertDeliveryChannel.Email, newAlert.DeliveryChannels); + Assert.AreEqual(AlertEventType.DeleteObject, newAlert.EventType); + Assert.AreEqual(AlertStatus.On, newAlert.Status); + Assert.AreEqual(AlertType.List, newAlert.AlertType); + Assert.AreEqual(currentUser.LoginName, newAlert.User.LoginName); + Assert.AreEqual(listId, newAlert.List.Id); + + Assert.AreEqual(alertTime, newAlert.AlertTime); + Assert.AreEqual(1, newAlert.Properties.Count, "Unexpected number of properties"); + Assert.IsTrue(newAlert.Properties.ContainsKey("filterindex")); + Assert.AreEqual("1", newAlert.Properties["filterindex"]); + } + finally + { + // delete alert + currentUser.Alerts.DeleteAlert(newAlert.ID); + currentUser.Update(); + ctx.ExecuteQueryRetry(); + } + } + } +#endif + +#if !SP2013 && !SP2016 + [TestMethod] + public void AddGetRemoveAlertTest() + { + using (var ctx = TestCommon.CreateClientContext()) + { + ctx.Web.EnsureProperties(w => w.CurrentUser.Id, w => w.CurrentUser.LoginName); + var currentUser = ctx.Web.CurrentUser; + var randomizer = new Random(); + var randomAlertTitle = randomizer.Next(int.MaxValue).ToString(); + var list = ctx.Web.GetListByTitle("PnPTestList"); + list.EnsureProperties(l => l.Id); + var listId = list.Id; + Guid alertId; + + using (var scope = new PSTestScope(true)) + { + var results = scope.ExecuteCommand("Add-PnPAlert", + new CommandParameter("List", listId), + new CommandParameter("Title", randomAlertTitle)); + Assert.IsNotNull(results); + Assert.IsTrue(results.Count > 0); + Assert.IsTrue(results[0].BaseObject.GetType() == typeof(AlertCreationInformation)); + } + + using (var scope = new PSTestScope(true)) + { + var results = scope.ExecuteCommand("Get-PnPAlert", + new CommandParameter("List", listId), + new CommandParameter("Title", randomAlertTitle)); + Assert.IsNotNull(results); + Assert.AreEqual(1, results.Count); + Assert.IsTrue(results[0].BaseObject.GetType() == typeof(Alert)); + var alert = results[0].BaseObject as Alert; + Assert.AreEqual(randomAlertTitle, alert.Title); + alertId = alert.ID; + } + + using (var scope = new PSTestScope(true)) + { + var results = scope.ExecuteCommand("Get-PnPAlert", + new CommandParameter("Title", randomAlertTitle)); + Assert.IsNotNull(results); + Assert.AreEqual(1, results.Count); + } + + using (var scope = new PSTestScope(true)) + { + var results = scope.ExecuteCommand("Get-PnPAlert", + new CommandParameter("List", listId)); + Assert.IsNotNull(results); + Assert.AreEqual(1, results.Count); + } + + using (var scope = new PSTestScope(true)) + { + var results = scope.ExecuteCommand("Remove-PnPAlert", + new CommandParameter("Identity", alertId), + new CommandParameter("Force")); + } + + // check that the alert has been deleted + ctx.Web.EnsureProperties(w => w.CurrentUser.Alerts); + var newAlerts = currentUser.Alerts.Where(a => a.Title == randomAlertTitle); + Assert.AreEqual(0, newAlerts.Count(), "Alert is still present"); + } + } +#endif + + } +} diff --git a/Tests/SharePointPnP.PowerShell.Tests.csproj b/Tests/SharePointPnP.PowerShell.Tests.csproj index 72cda4e62..2eabd2061 100644 --- a/Tests/SharePointPnP.PowerShell.Tests.csproj +++ b/Tests/SharePointPnP.PowerShell.Tests.csproj @@ -353,6 +353,7 @@ + diff --git a/Tests/WebTests.cs b/Tests/WebTests.cs index bd38c67c9..b86beab4e 100644 --- a/Tests/WebTests.cs +++ b/Tests/WebTests.cs @@ -4,6 +4,7 @@ using System.Management.Automation.Runspaces; using System.Linq; using SharePointPnP.PowerShell.Commands.Principals; +using SharePointPnP.PowerShell.Commands.Enums; namespace SharePointPnP.PowerShell.Tests { @@ -153,137 +154,6 @@ public void GetUser() } } } - -#if !ONPREMISES - [TestMethod] - public void AddAlert_WithDefaultProperties_Test() - { - using (var ctx = TestCommon.CreateClientContext()) - { - ctx.Web.EnsureProperties(w => w.CurrentUser.Id, w => w.CurrentUser.LoginName); - var currentUser = ctx.Web.CurrentUser; - var randomizer = new Random(); - var randomAlertTitle = randomizer.Next(int.MaxValue).ToString(); - var list = ctx.Web.DefaultDocumentLibrary(); - list.EnsureProperties(l => l.Id); - var listId = list.Id; - - // Execute cmd-let - using (var scope = new PSTestScope(true)) - { - var results = scope.ExecuteCommand("Add-PnPAlert", - new CommandParameter("List", listId), - new CommandParameter("Title", randomAlertTitle)); - Assert.IsNotNull(results); - Assert.IsTrue(results.Count > 0); - Assert.IsTrue(results[0].BaseObject.GetType() == typeof(Microsoft.SharePoint.Client.AlertCreationInformation)); - var alertInfo = results[0].BaseObject as AlertCreationInformation; - Assert.AreEqual(randomAlertTitle, alertInfo.Title); - } - - // get actual alert and check properties - ctx.Web.EnsureProperties(w => w.CurrentUser.Alerts); - var newAlerts = currentUser.Alerts.Where(a => a.Title == randomAlertTitle); - Assert.AreEqual(1, newAlerts.Count(), "Unexpected number of created alerts"); - var newAlert = newAlerts.First(); - newAlert.EnsureProperty(a => a.Properties); - newAlert.User.EnsureProperties(u => u.LoginName); - newAlert.List.EnsureProperties(l => l.Id); - ctx.ExecuteQueryRetry(); - - try - { - Assert.AreEqual(AlertFrequency.Immediate, newAlert.AlertFrequency); - Assert.AreEqual(AlertDeliveryChannel.Email, newAlert.DeliveryChannels); - Assert.AreEqual(AlertEventType.All, newAlert.EventType); - Assert.AreEqual(AlertStatus.On, newAlert.Status); - Assert.AreEqual(AlertType.List, newAlert.AlertType); - Assert.AreEqual(currentUser.LoginName, newAlert.User.LoginName); - Assert.AreEqual(listId, newAlert.List.Id); - Assert.AreEqual(1, newAlert.Properties.Count, "Unexpected number of properties"); - Assert.IsTrue(newAlert.Properties.ContainsKey("filterindex")); - Assert.AreEqual("0", newAlert.Properties["filterindex"]); - } - finally - { - // delete alert - currentUser.Alerts.DeleteAlert(newAlert.ID); - currentUser.Update(); - ctx.ExecuteQueryRetry(); - } - } - } -#endif - -#if !ONPREMISES - [TestMethod] - public void AddAlert_WithNonDefaultProperties_Test() - { - using (var ctx = TestCommon.CreateClientContext()) - { - ctx.Web.EnsureProperties(w => w.CurrentUser.Id, w => w.CurrentUser.LoginName); - var currentUser = ctx.Web.CurrentUser; - // generate random alert title - var randomizer = new Random(); - var alertTitle = randomizer.Next(int.MaxValue).ToString(); - var list = ctx.Web.DefaultDocumentLibrary(); - list.EnsureProperties(l => l.Id); - var listId = list.Id; - var currentTime = DateTime.Now; - // note: SharePoint rounds the milliseconds away, so use a time without milliseconds for testing - var alertTime = new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, currentTime.Minute, currentTime.Minute) ; - - // Execute cmd-let - using (var scope = new PSTestScope(true)) - { - var results = scope.ExecuteCommand("Add-PnPAlert", - new CommandParameter("List", "Shared Documents"), - new CommandParameter("Title", alertTitle), - new CommandParameter("DeliveryMethod", AlertDeliveryChannel.Email), // cannot use SmS without having Frequency set to "Immediate" - new CommandParameter("ChangeType", AlertEventType.DeleteObject), - new CommandParameter("Frequency", AlertFrequency.Weekly), - new CommandParameter("Time", alertTime), - new CommandParameter("Filter", AlertFilter.SomeoneElseChangesAnItem) - ); - Assert.IsNotNull(results); - Assert.IsTrue(results.Count > 0); - } - - // get actual alert and check properties - ctx.Web.EnsureProperties(w => w.CurrentUser.Alerts); - var newAlerts = currentUser.Alerts.Where(a => a.Title == alertTitle); - Assert.AreEqual(1, newAlerts.Count(), "Unexpected number of created alerts"); - var newAlert = newAlerts.First(); - newAlert.EnsureProperty(a => a.AlertTime); - newAlert.EnsureProperty(a => a.Properties); - newAlert.User.EnsureProperties(u => u.LoginName); - newAlert.List.EnsureProperties(l => l.Id); - ctx.ExecuteQueryRetry(); - - try - { - Assert.AreEqual(AlertFrequency.Weekly, newAlert.AlertFrequency); - Assert.AreEqual(AlertDeliveryChannel.Email, newAlert.DeliveryChannels); - Assert.AreEqual(AlertEventType.DeleteObject, newAlert.EventType); - Assert.AreEqual(AlertStatus.On, newAlert.Status); - Assert.AreEqual(AlertType.List, newAlert.AlertType); - Assert.AreEqual(currentUser.LoginName, newAlert.User.LoginName); - Assert.AreEqual(listId, newAlert.List.Id); - - Assert.AreEqual(alertTime, newAlert.AlertTime); - Assert.AreEqual(1, newAlert.Properties.Count, "Unexpected number of properties"); - Assert.IsTrue(newAlert.Properties.ContainsKey("filterindex")); - Assert.AreEqual("1", newAlert.Properties["filterindex"]); - } - finally - { - // delete alert - currentUser.Alerts.DeleteAlert(newAlert.ID); - currentUser.Update(); - ctx.ExecuteQueryRetry(); - } - } - } -#endif } } + diff --git a/version.txt b/version.txt index bb85408b2..caddf4045 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.7.1903.0 \ No newline at end of file +3.8.1904.0 \ No newline at end of file