diff --git a/CHANGELOG.md b/CHANGELOG.md
index ebe1b2413..4dd2b880f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- Added `Add\Remove\Set-PnPAdaptiveScopeProperty` cmdlets to add/update/remove a property bag value while dealing with the noscript toggling in one cmdlet [#1556](https://github.com/pnp/powershell/pull/1556)
- Added support to add multiple owners and members in `New-PnPTeamsTeam` cmdlet [#1241](https://github.com/pnp/powershell/pull/1241)
- Added the ability to set the title of a new modern page in SharePoint Online using `Add-PnPPage` to be different from its filename by using `-Title`
+- Added optional `-UseBeta` parameter to `Get-PnPAzureADUser` to force it to use the Microsoft Graph beta endpoint. This can be necessary when i.e. using `-Select "PreferredDataLocation"` to query for users with a specific multi geo location as this property is only available through the beta endpoint. [#1559](https://github.com/pnp/powershell/pull/1559)
+- Added `-Content` option to `Add-PnPFile` which allows creating a new file on SharePoint Online and directly providing its textual content, i.e. to upload a log file of the execution [#1559](https://github.com/pnp/powershell/pull/1559)
- Added `Get-PnPTeamsPrimaryChannel` to get the primary Teams channel, general, of a Team [#1572](https://github.com/pnp/powershell/pull/1572)
- Added `Publish\Unpublish-PnPContentType` to allow for content types to be published or unpublished on hub sites [#1597](https://github.com/pnp/powershell/pull/1597)
- Added `Get-PnPContentTypePublishingStatus` to get te current publication state of a content type in the content type hub site [#1597](https://github.com/pnp/powershell/pull/1597)
@@ -30,6 +32,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- Improved `Add-PnPTeamsUser` cmdlet. The cmdlet executes faster and we can now add users in batches of 200. [#1548](https://github.com/pnp/powershell/pull/1548)
- The `Move\Remove\Rename-PnPFolder` cmdlets now support pipebinds.
- Changed `Add-PnPDataRowsToSiteTemplate`, it will return a warning if user(s) are not found during list item extraction. Earlier it used to throw error and stop extraction of list items.
+- Changed the return type of `Sync-PnPSharePointUserProfilesFromAzureActiveDirectory` to return our own entity instead of the one returned by CSOM [#1559](https://github.com/pnp/powershell/pull/1559)
+- Changed running `Sync-PnPSharePointUserProfilesFromAzureActiveDirectory` with `-WhatIf` to also provide a return entity providing the path to where the JSON file has been uploaded to [#1559](https://github.com/pnp/powershell/pull/1559)
- Disabling telemetry collection now requires either setting the environment variable or creating the telemetry file ([documentation](https://pnp.github.io/powershell/articles/configuration.html)) [#1504](https://github.com/pnp/powershell/pull/1504)
- Changed `Get-PnPAzureADUser` to now return all the users in Azure Active Directory by default, instead of only the first 999, unless you specified `-EndIndex:$null` [#1565](https://github.com/pnp/powershell/pull/1565)
- Changed `Get-PnPTenantDeletedSite -Identity` no longer returning an unknown exception when no site collection with the provided Url exists in the tenant recycle bin but instead returning no output to align with other cmdlets [#1596](https://github.com/pnp/powershell/pull/1596)
diff --git a/src/Commands/AzureAD/GetAzureADUser.cs b/src/Commands/AzureAD/GetAzureADUser.cs
index 7d4fcc019..bf2719f1e 100644
--- a/src/Commands/AzureAD/GetAzureADUser.cs
+++ b/src/Commands/AzureAD/GetAzureADUser.cs
@@ -45,6 +45,11 @@ public class GetAzureADUser : PnPGraphCmdlet
[Parameter(Mandatory = false, ParameterSetName = ParameterSet_DELTA)]
public int? EndIndex = null;
+ [Parameter(Mandatory = false, ParameterSetName = ParameterSet_BYID)]
+ [Parameter(Mandatory = false, ParameterSetName = ParameterSet_LIST)]
+ [Parameter(Mandatory = false, ParameterSetName = ParameterSet_DELTA)]
+ public SwitchParameter UseBeta;
+
protected override void ExecuteCmdlet()
{
if (PnPConnection.Current.ClientId == PnPConnection.PnPManagementShellClientId)
@@ -56,22 +61,22 @@ protected override void ExecuteCmdlet()
PnP.PowerShell.Commands.Model.AzureAD.User user;
if (Guid.TryParse(Identity, out Guid identityGuid))
{
- user = PnP.PowerShell.Commands.Utilities.AzureAdUtility.GetUser(AccessToken, identityGuid);
+ user = PnP.PowerShell.Commands.Utilities.AzureAdUtility.GetUser(AccessToken, identityGuid, useBetaEndPoint: UseBeta.IsPresent);
}
else
{
- user = PnP.PowerShell.Commands.Utilities.AzureAdUtility.GetUser(AccessToken, WebUtility.UrlEncode(Identity), Select);
+ user = PnP.PowerShell.Commands.Utilities.AzureAdUtility.GetUser(AccessToken, WebUtility.UrlEncode(Identity), Select, useBetaEndPoint: UseBeta.IsPresent);
}
WriteObject(user);
}
else if (ParameterSpecified(nameof(Delta)))
{
- var userDelta = PnP.PowerShell.Commands.Utilities.AzureAdUtility.ListUserDelta(AccessToken, DeltaToken, Filter, OrderBy, Select, StartIndex, EndIndex);
+ var userDelta = PnP.PowerShell.Commands.Utilities.AzureAdUtility.ListUserDelta(AccessToken, DeltaToken, Filter, OrderBy, Select, StartIndex, EndIndex, useBetaEndPoint: UseBeta.IsPresent);
WriteObject(userDelta);
}
else
{
- var users = PnP.PowerShell.Commands.Utilities.AzureAdUtility.ListUsers(AccessToken, Filter, OrderBy, Select, StartIndex, EndIndex);
+ var users = PnP.PowerShell.Commands.Utilities.AzureAdUtility.ListUsers(AccessToken, Filter, OrderBy, Select, StartIndex, EndIndex, useBetaEndPoint: UseBeta.IsPresent);
WriteObject(users, true);
}
}
diff --git a/src/Commands/Enums/SharePointUserProfileImportProfilePropertiesJobError.cs b/src/Commands/Enums/SharePointUserProfileImportProfilePropertiesJobError.cs
new file mode 100644
index 000000000..b2f099c47
--- /dev/null
+++ b/src/Commands/Enums/SharePointUserProfileImportProfilePropertiesJobError.cs
@@ -0,0 +1,16 @@
+namespace PnP.PowerShell.Commands.Enums
+{
+ ///
+ /// Types of errors that can occur while performing a SharePoint Online User Profile Import
+ ///
+ public enum SharePointUserProfileImportProfilePropertiesJobError
+ {
+ NoError = 0,
+ InternalError = 1,
+ DataFileNotExist = 20,
+ DataFileNotInTenant = 21,
+ DataFileTooBig = 22,
+ InvalidDataFile = 23,
+ ImportCompleteWithError = 30
+ }
+}
\ No newline at end of file
diff --git a/src/Commands/Enums/SharePointUserProfileImportProfilePropertiesJobState.cs b/src/Commands/Enums/SharePointUserProfileImportProfilePropertiesJobState.cs
new file mode 100644
index 000000000..35a538c74
--- /dev/null
+++ b/src/Commands/Enums/SharePointUserProfileImportProfilePropertiesJobState.cs
@@ -0,0 +1,43 @@
+namespace PnP.PowerShell.Commands.Enums
+{
+ ///
+ /// The states a SharePoint Online User Profile import job can be in
+ ///
+ public enum SharePointUserProfileImportProfilePropertiesJobState
+ {
+ ///
+ /// State is unknown
+ ///
+ Unknown = 0,
+
+ ///
+ /// The file has been submitted to SharePoint Online for processing
+ ///
+ Submitted = 1,
+
+ ///
+ /// The file is currently being processed to validate if it can be used
+ ///
+ Processing = 2,
+
+ ///
+ /// The file is queued and being executed
+ ///
+ Queued = 3,
+
+ ///
+ /// The import process has completed successfully
+ ///
+ Succeeded = 4,
+
+ ///
+ /// The import process has failed to complete
+ ///
+ Error = 5,
+
+ ///
+ /// The import process will not start
+ ///
+ WontStart = 99
+ }
+}
\ No newline at end of file
diff --git a/src/Commands/Files/AddFile.cs b/src/Commands/Files/AddFile.cs
index 38c910577..54c393ae4 100644
--- a/src/Commands/Files/AddFile.cs
+++ b/src/Commands/Files/AddFile.cs
@@ -13,6 +13,7 @@ public class AddFile : PnPWebCmdlet
{
private const string ParameterSet_ASFILE = "Upload file";
private const string ParameterSet_ASSTREAM = "Upload file from stream";
+ private const string ParameterSet_ASTEXT = "Upload file from text";
[Parameter(Mandatory = true, ParameterSetName = ParameterSet_ASFILE)]
[ValidateNotNullOrEmpty]
@@ -23,6 +24,7 @@ public class AddFile : PnPWebCmdlet
public FolderPipeBind Folder;
[Parameter(Mandatory = true, ParameterSetName = ParameterSet_ASSTREAM)]
+ [Parameter(Mandatory = true, ParameterSetName = ParameterSet_ASTEXT)]
[ValidateNotNullOrEmpty]
public string FileName = string.Empty;
@@ -34,6 +36,9 @@ public class AddFile : PnPWebCmdlet
[ValidateNotNullOrEmpty]
public Stream Stream;
+ [Parameter(Mandatory = true, ParameterSetName = ParameterSet_ASTEXT)]
+ public string Content;
+
[Parameter(Mandatory = false)]
public SwitchParameter Checkout;
@@ -112,14 +117,30 @@ protected override void ExecuteCmdlet()
{ // Swallow exception, file does not exist
}
}
+
Microsoft.SharePoint.Client.File file;
- if (ParameterSetName == ParameterSet_ASFILE)
- {
- file = folder.UploadFile(FileName, Path, true);
- }
- else
+ switch (ParameterSetName)
{
- file = folder.UploadFile(FileName, Stream, true);
+ case ParameterSet_ASFILE:
+ file = folder.UploadFile(FileName, Path, true);
+ break;
+
+ case ParameterSet_ASTEXT:
+ using (var stream = new MemoryStream())
+ {
+ using (var writer = new StreamWriter(stream))
+ {
+ writer.Write(Content);
+ writer.Flush();
+ stream.Position = 0;
+ file = folder.UploadFile(FileName, stream, true);
+ }
+ }
+ break;
+
+ default:
+ file = folder.UploadFile(FileName, Stream, true);
+ break;
}
bool updateRequired = false;
diff --git a/src/Commands/Model/SharePointUserProfileSync/SharePointUserProfileSyncStatus.cs b/src/Commands/Model/SharePointUserProfileSync/SharePointUserProfileSyncStatus.cs
new file mode 100644
index 000000000..05b28cb94
--- /dev/null
+++ b/src/Commands/Model/SharePointUserProfileSync/SharePointUserProfileSyncStatus.cs
@@ -0,0 +1,68 @@
+using System;
+using Microsoft.Online.SharePoint.TenantManagement;
+using PnP.PowerShell.Commands.Enums;
+
+namespace PnP.PowerShell.Commands.Model.SharePoint.SharePointUserProfileSync
+{
+ ///
+ /// Contains the status of a SharePoint Online User Profile Import job
+ ///
+ public class SharePointUserProfileSyncStatus
+ {
+ #region Properties
+
+ ///
+ /// Details on the type of error that occurred, if any
+ ///
+ public SharePointUserProfileImportProfilePropertiesJobError Error { get; set; }
+
+ ///
+ /// The error message, if an error occurred
+ ///
+ public string ErrorMessage { get; set; }
+
+ ///
+ /// Unique identifier of the import job
+ ///
+ public Guid? JobId { get; set; }
+
+ ///
+ ///
+ ///
+ public string LogFolderUri { get; set; }
+
+ ///
+ ///
+ ///
+ public string SourceUri { get; set; }
+
+ ///
+ /// State the user profile import process is in
+ ///
+ public SharePointUserProfileImportProfilePropertiesJobState State { get; set; }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Takes an instance of ImportProfilePropertiesJobInfo from CSOM and maps it to a local SharePointUserProfileSyncStatus entity
+ ///
+ /// Instance to map from
+ public static SharePointUserProfileSyncStatus ParseFromImportProfilePropertiesJobInfo(ImportProfilePropertiesJobInfo importProfilePropertiesJobInfo)
+ {
+ var result = new SharePointUserProfileSyncStatus
+ {
+ Error = Enum.TryParse(importProfilePropertiesJobInfo.Error.ToString(), out SharePointUserProfileImportProfilePropertiesJobError sharePointUserProfileImportProfilePropertiesJobError) ? sharePointUserProfileImportProfilePropertiesJobError : SharePointUserProfileImportProfilePropertiesJobError.NoError,
+ ErrorMessage = importProfilePropertiesJobInfo.ErrorMessage,
+ JobId = importProfilePropertiesJobInfo.JobId,
+ LogFolderUri = importProfilePropertiesJobInfo.LogFolderUri,
+ SourceUri = importProfilePropertiesJobInfo.SourceUri,
+ State = Enum.TryParse(importProfilePropertiesJobInfo.State.ToString(), out SharePointUserProfileImportProfilePropertiesJobState sharePointUserProfileImportProfilePropertiesJobState) ? sharePointUserProfileImportProfilePropertiesJobState : SharePointUserProfileImportProfilePropertiesJobState.Unknown
+ };
+ return result;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Commands/Utilities/AzureAdUtility.cs b/src/Commands/Utilities/AzureAdUtility.cs
index 6f0ef9ac8..0a79f147d 100644
--- a/src/Commands/Utilities/AzureAdUtility.cs
+++ b/src/Commands/Utilities/AzureAdUtility.cs
@@ -22,10 +22,11 @@ internal static class AzureAdUtility
/// Optional additional properties to fetch for the users
/// Optional start index indicating starting from which result to start returning users
/// Optional end index indicating up to which result to return users. By default all users will be returned.
+ /// Indicates if the v1.0 (false) or beta (true) endpoint should be used at Microsoft Graph to query for the data
/// UserDelta instance
- public static UserDelta ListUserDelta(string accessToken, string deltaToken, string filter, string orderby, string[] selectProperties = null, int startIndex = 0, int? endIndex = null)
+ public static UserDelta ListUserDelta(string accessToken, string deltaToken, string filter, string orderby, string[] selectProperties = null, int startIndex = 0, int? endIndex = null, bool useBetaEndPoint = false)
{
- var userDelta = PnP.Framework.Graph.UsersUtility.ListUserDelta(accessToken, deltaToken, filter, orderby, selectProperties, startIndex, endIndex);
+ var userDelta = PnP.Framework.Graph.UsersUtility.ListUserDelta(accessToken, deltaToken, filter, orderby, selectProperties, startIndex, endIndex, useBetaEndPoint: useBetaEndPoint);
var result = new UserDelta
{
@@ -45,28 +46,26 @@ public static UserDelta ListUserDelta(string accessToken, string deltaToken, str
/// Allows providing the names of properties to return regarding the users. If not provided, the standard properties will be returned.
/// First item in the results returned by Microsoft Graph to return
/// Last item in the results returned by Microsoft Graph to return. Provide NULL to return all results that exist.
- /// Number of times to retry the request in case of throttling
- /// Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry.
+ /// Indicates if the v1.0 (false) or beta (true) endpoint should be used at Microsoft Graph to query for the data
/// List with User objects
- public static List ListUsers(string accessToken, string filter, string orderby, string[] selectProperties = null, int startIndex = 0, int? endIndex = 999)
+ public static List ListUsers(string accessToken, string filter, string orderby, string[] selectProperties = null, int startIndex = 0, int? endIndex = 999, bool useBetaEndPoint = false)
{
- return PnP.Framework.Graph.UsersUtility.ListUsers(accessToken, filter, orderby, selectProperties, startIndex, endIndex).Select(User.CreateFrom).ToList();
+ return PnP.Framework.Graph.UsersUtility.ListUsers(accessToken, filter, orderby, selectProperties, startIndex, endIndex, useBetaEndPoint: useBetaEndPoint).Select(User.CreateFrom).ToList();
}
///
- /// Returns the user with the provided userId from Azure Active Directory
+ /// Returns the user with the provided from Azure Active Directory
///
/// The OAuth 2.0 Access Token to use for invoking the Microsoft Graph
/// The unique identifier of the user in Azure Active Directory to return
/// Allows providing the names of properties to return regarding the users. If not provided, the standard properties will be returned.
/// First item in the results returned by Microsoft Graph to return
/// Last item in the results returned by Microsoft Graph to return. Provide NULL to return all results that exist.
- /// Number of times to retry the request in case of throttling
- /// Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry.
+ /// Indicates if the v1.0 (false) or beta (true) endpoint should be used at Microsoft Graph to query for the data
/// List with User objects
- public static User GetUser(string accessToken, Guid userId, string[] selectProperties = null, int startIndex = 0, int? endIndex = 999)
+ public static User GetUser(string accessToken, Guid userId, string[] selectProperties = null, int startIndex = 0, int? endIndex = 999, bool useBetaEndPoint = false)
{
- return PnP.Framework.Graph.UsersUtility.ListUsers(accessToken, $"id eq '{userId}'", null, selectProperties, startIndex, endIndex).Select(User.CreateFrom).FirstOrDefault();
+ return PnP.Framework.Graph.UsersUtility.ListUsers(accessToken, $"id eq '{userId}'", null, selectProperties, startIndex, endIndex, useBetaEndPoint: useBetaEndPoint).Select(User.CreateFrom).FirstOrDefault();
}
///
@@ -77,12 +76,11 @@ public static User GetUser(string accessToken, Guid userId, string[] selectPrope
/// Allows providing the names of properties to return regarding the users. If not provided, the standard properties will be returned.
/// First item in the results returned by Microsoft Graph to return
/// Last item in the results returned by Microsoft Graph to return. Provide NULL to return all results that exist.
- /// Number of times to retry the request in case of throttling
- /// Milliseconds to wait before retrying the request. The delay will be increased (doubled) every retry.
+ /// Indicates if the v1.0 (false) or beta (true) endpoint should be used at Microsoft Graph to query for the data
/// User object
- public static User GetUser(string accessToken, string userPrincipalName, string[] selectProperties = null, int startIndex = 0, int? endIndex = 999)
+ public static User GetUser(string accessToken, string userPrincipalName, string[] selectProperties = null, int startIndex = 0, int? endIndex = 999, bool useBetaEndPoint = false)
{
- return PnP.Framework.Graph.UsersUtility.ListUsers(accessToken, $"userPrincipalName eq '{userPrincipalName}'", null, selectProperties, startIndex, endIndex).Select(User.CreateFrom).FirstOrDefault();
+ return PnP.Framework.Graph.UsersUtility.ListUsers(accessToken, $"userPrincipalName eq '{userPrincipalName}'", null, selectProperties, startIndex, endIndex, useBetaEndPoint: useBetaEndPoint).Select(User.CreateFrom).FirstOrDefault();
}
#endregion
diff --git a/src/Commands/Utilities/SharePointUserProfileSync.cs b/src/Commands/Utilities/SharePointUserProfileSync.cs
index 6825a7fb3..6e2df9bde 100644
--- a/src/Commands/Utilities/SharePointUserProfileSync.cs
+++ b/src/Commands/Utilities/SharePointUserProfileSync.cs
@@ -8,6 +8,7 @@
using PnP.Framework.Utilities;
using System.Threading.Tasks;
using System.Reflection;
+using PnP.PowerShell.Commands.Model.SharePoint.SharePointUserProfileSync;
namespace PnP.PowerShell.Commands.Utilities
{
@@ -24,7 +25,8 @@ public static class SharePointUserProfileSync
/// Hashtable with the mapping from the Azure Active Directory property (the value) to the SharePoint Online User Profile Property (the key)
/// Location in the currently connected to site where to upload the JSON file to with instructions to update the user profiles
/// Boolean indicating if only the mappings file should be created and uploaded to SharePoint Online (true) or if the import job on that file should also be invoked (false)
- public static async Task SyncFromAzureActiveDirectory(ClientContext clientContext, IEnumerable users, Hashtable userProfilePropertyMappings, string sharePointFolder, bool onlyCreateAndUploadMappingsFile = false)
+ /// Information on the status of the import job that has been created because of this action
+ public static async Task SyncFromAzureActiveDirectory(ClientContext clientContext, IEnumerable users, Hashtable userProfilePropertyMappings, string sharePointFolder, bool onlyCreateAndUploadMappingsFile = false)
{
var webServerRelativeUrl = clientContext.Web.EnsureProperty(w => w.ServerRelativeUrl);
if (!sharePointFolder.ToLower().StartsWith(webServerRelativeUrl))
@@ -109,7 +111,14 @@ public static async Task SyncFromAzureActiveDire
}
// Check if we should kick off the process to import the file
- if(onlyCreateAndUploadMappingsFile) return null;
+ if (onlyCreateAndUploadMappingsFile)
+ {
+ return new SharePointUserProfileSyncStatus
+ {
+ SourceUri = new Uri(clientContext.Url).GetLeftPart(UriPartial.Authority) + file.ServerRelativeUrl,
+ State = Enums.SharePointUserProfileImportProfilePropertiesJobState.WontStart
+ };
+ }
// Instruct SharePoint Online to process the JSON file
var o365 = new Office365Tenant(clientContext);
@@ -123,7 +132,9 @@ public static async Task SyncFromAzureActiveDire
clientContext.Load(job);
clientContext.ExecuteQueryRetry();
- return job;
+ // Map the CSOM result object to our own entity
+ var sharePointUserProfileSyncStatus = SharePointUserProfileSyncStatus.ParseFromImportProfilePropertiesJobInfo(job);
+ return sharePointUserProfileSyncStatus;
}
}
}
\ No newline at end of file