From 527ebae7d2ccbeaa713a6749f0e4a4716dc44daa Mon Sep 17 00:00:00 2001 From: Release-Agent <> Date: Thu, 8 Jun 2023 19:16:31 +0000 Subject: [PATCH] '' --- src/Build.Common.core.props | 2 +- src/Build.Shared.props | 9 +- .../DataverseClient/Client/BatchManager.cs | 23 +- .../Client/ConnectionService.cs | 116 +- .../Client/DataverseDataTypeWrapper.cs | 2 +- .../Client/Extensions/DeploymentExtensions.cs | 367 ++- .../SupportClasses/AsyncStatusResponse.cs | 212 ++ ...soft.PowerPlatform.Dataverse.Client.csproj | 2 +- .../DataverseClient/Client/ServiceClient.cs | 192 +- .../Client/Utils/ErrorCodes.cs | 18 +- .../Client/Utils/ImportSolutionProperties.cs | 6 +- .../DataverseClient/Client/Utils/Utils.cs | 12 +- .../ConnectControl/ConnectionManager.cs | 2 +- .../Styles/BrushResourcesNormalMode.xaml | 2 +- .../ConnectControl/Utility/LoginTracer.cs | 7 +- .../Textblock/BrushResourcesNormalMode.xaml | 4 +- .../DataverseClient_Core_UnitTests.csproj | 6 + .../ServiceClientTests.cs | 512 +++- .../SkippableConnectionTestAttribute.cs | 8 +- ...erPlatformIPManagement_1_0_0_6_managed.zip | Bin 0 -> 10945 bytes ...erPlatformIPManagement_1_0_1_0_managed.zip | Bin 0 -> 11328 bytes .../LivePackageRunUnitTests.csproj | 2 +- .../LivePackageTestsConsole.csproj | 2 +- .../WebResourceUtility/TraceLogger.cs | 9 +- ...crosoft.PowerPlatform.Dataverse.Client.xml | 2728 +++++++++++++++++ ...Dataverse.Client.Dynamics.ReleaseNotes.txt | 5 +- ...rPlatform.Dataverse.Client.Dynamics.nuspec | 5 - ...Platform.Dataverse.Client.ReleaseNotes.txt | 21 +- ...soft.PowerPlatform.Dataverse.Client.nuspec | 39 +- 29 files changed, 4111 insertions(+), 202 deletions(-) create mode 100644 src/GeneralTools/DataverseClient/Client/Extensions/SupportClasses/AsyncStatusResponse.cs create mode 100644 src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/TestMaterial/PowerPlatformIPManagement_1_0_0_6_managed.zip create mode 100644 src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/TestMaterial/PowerPlatformIPManagement_1_0_1_0_managed.zip create mode 100644 src/SDK-IntelliSense/V9/PublishedXML/CE/Microsoft.PowerPlatform.Dataverse.Client.xml diff --git a/src/Build.Common.core.props b/src/Build.Common.core.props index 8f9bf13..0c2dc66 100644 --- a/src/Build.Common.core.props +++ b/src/Build.Common.core.props @@ -5,7 +5,7 @@ - net462;net472;net48;netcoreapp3.1;netstandard2.0;net6.0 + net462;net472;net48;netstandard2.0;net6.0 false diff --git a/src/Build.Shared.props b/src/Build.Shared.props index 211c850..7d8b708 100644 --- a/src/Build.Shared.props +++ b/src/Build.Shared.props @@ -8,17 +8,18 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 3.19.8 4.35.1 - 4.9.665-v9.0-master - 4.9.665-v9.0-master + 4.9.3165-v9.0-weekly-2304.2 + 4.9.3165-v9.0-weekly-2304.2 4.6.6061-weekly-2108.5 13.0.1 2.3.20 - 9.0.2.45 + 9.0.2.48 9.0.2.34 - 3.0.5 + 3.0.8 0.4.20 3.1.0 3.1.8 + 6.0.0 17.5.0 diff --git a/src/GeneralTools/DataverseClient/Client/BatchManager.cs b/src/GeneralTools/DataverseClient/Client/BatchManager.cs index f1e360d..37bb2d5 100644 --- a/src/GeneralTools/DataverseClient/Client/BatchManager.cs +++ b/src/GeneralTools/DataverseClient/Client/BatchManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,6 +6,7 @@ using Microsoft.Xrm.Sdk.Messages; using System.Globalization; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics; namespace Microsoft.PowerPlatform.Dataverse.Client { @@ -34,22 +35,24 @@ internal sealed class BatchManager /// private DataverseTraceLogger logger = null; - #endregion + #endregion - /// - /// Base Constructor.. - /// - /// Max number of concurrent batches possible - /// Max number of requests per Batch - /// TraceLogger - public BatchManager(DataverseTraceLogger traceLogger, int MaxBatches = 50000, int MaxRequestPerBatch = 5000) + /// + /// Base Constructor.. + /// + /// Max number of concurrent batches possible + /// Max number of requests per Batch + /// Indicates if the connection is being cloned + /// TraceLogger + public BatchManager(DataverseTraceLogger traceLogger, int MaxBatches = 50000, int MaxRequestPerBatch = 5000, bool IsClonedConnection = false) { logger = traceLogger; // Do a Version Check here? MaxNumberOfBatches = MaxBatches; MaxNumberOfRequestsInABatch = MaxRequestPerBatch; RequestBatches = new Dictionary(); - logger.Log(string.Format(CultureInfo.InvariantCulture, "New Batch Manager Created, Max #of Batches:{0}, Max #of RequestsPerBatch:{1}", MaxBatches, MaxRequestPerBatch), System.Diagnostics.TraceEventType.Verbose); + if (!IsClonedConnection) + logger.Log(string.Format(CultureInfo.InvariantCulture, "New Batch Manager Created, Max #of Batches:{0}, Max #of RequestsPerBatch:{1}", MaxBatches, MaxRequestPerBatch), TraceEventType.Verbose); } /// diff --git a/src/GeneralTools/DataverseClient/Client/ConnectionService.cs b/src/GeneralTools/DataverseClient/Client/ConnectionService.cs index 9039d50..ca19ddd 100644 --- a/src/GeneralTools/DataverseClient/Client/ConnectionService.cs +++ b/src/GeneralTools/DataverseClient/Client/ConnectionService.cs @@ -358,6 +358,11 @@ internal bool CalledbyExecuteRequest /// internal bool OrgDetailsRead = false; + /// + /// Bit flag used to detect if the current object is being cloned + /// + internal bool _isCloning = false; + /// /// Logging provider for DataverseConnectionServiceobject. /// @@ -399,19 +404,22 @@ internal OrganizationDetail ConnectedOrganizationDetail { get { - if (_OrgDetail == null || !OrgDetailsRead) + if (!_isCloning) { - lock (lockObject) + if (_OrgDetail == null || !OrgDetailsRead) { - _orgReadingDetails = true; - IOrganizationService svc = null; - if (WebClient != null || OnPremClient != null) + lock (lockObject) { - if (WebClient != null) svc = WebClient; else svc = OnPremClient; - RefreshInstanceDetails(svc, _targetInstanceUriToConnectTo).ConfigureAwait(false).GetAwaiter().GetResult(); + _orgReadingDetails = true; + IOrganizationService svc = null; + if (WebClient != null || OnPremClient != null) + { + if (WebClient != null) svc = WebClient; else svc = OnPremClient; + RefreshInstanceDetails(svc, _targetInstanceUriToConnectTo).ConfigureAwait(false).GetAwaiter().GetResult(); + } + _orgReadingDetails = false; + OrgDetailsRead = true; } - _orgReadingDetails = false; - OrgDetailsRead = true; } } return _OrgDetail; @@ -439,7 +447,6 @@ internal OrganizationWebProxyClientAsync WebClient { if (_svcWebClientProxy != null) { - RefreshClientTokenAsync().ConfigureAwait(false).GetAwaiter().GetResult(); // Only call this if the connection is not null try { if (!_svcWebClientProxy.Endpoint.EndpointBehaviors.Contains(typeof(DataverseTelemetryBehaviors))) @@ -462,7 +469,6 @@ internal OrganizationServiceProxyAsync OnPremClient { if (_svcOnPremClientProxy != null) { - RefreshClientTokenAsync().ConfigureAwait(false).GetAwaiter().GetResult(); // Only call this if the connection is not null try { AttachProxyHander(_svcOnPremClientProxy); @@ -697,7 +703,8 @@ internal ConnectionService(IOrganizationService testIOrganziationSvc, string bas /// This is an initialized organization web Service proxy /// Authentication Type to use /// incoming Log Sink - internal ConnectionService(OrganizationWebProxyClientAsync externalOrgWebProxyClient, AuthenticationType authType, DataverseTraceLogger logSink = null) + /// is this being created as part of a clone request + internal ConnectionService(OrganizationWebProxyClientAsync externalOrgWebProxyClient, AuthenticationType authType, DataverseTraceLogger logSink = null, bool isClone = false) { if (logSink == null) { @@ -710,6 +717,8 @@ internal ConnectionService(OrganizationWebProxyClientAsync externalOrgWebProxyCl isLogEntryCreatedLocaly = false; } + // is this a clone request + IsAClone = isClone; _externalWebClientProxy = externalOrgWebProxyClient; if (_externalWebClientProxy != null) @@ -727,18 +736,19 @@ internal ConnectionService(OrganizationWebProxyClientAsync externalOrgWebProxyCl } /// - /// Sets up a Connection to Dataverse OnPremsie - /// - /// Type of Authentication to use, AD or IDF - /// Host name to connect too - /// Port Number to Connect too - /// Organization to Connect to - /// Credential to use to connect - /// flag that will tell the instance to create a Unique Name for the Dataverse Cache Objects. - /// Dataverse Org Detail object, this is is returned from a query to the Dataverse Discovery Server service. not required. - /// incoming LogSink - /// instance to connect too - internal ConnectionService( + /// Sets up a Connection to Dataverse OnPremsie + /// + /// Type of Authentication to use, AD or IDF + /// Host name to connect too + /// Port Number to Connect too + /// Organization to Connect to + /// Credential to use to connect + /// flag that will tell the instance to create a Unique Name for the Dataverse Cache Objects. + /// Dataverse Org Detail object, this is is returned from a query to the Dataverse Discovery Server service. not required. + /// incoming LogSink + /// instance to connect too + /// is this part of a clone request + internal ConnectionService( AuthenticationType authType, // type of auth, AD string hostName, // Host name your connecting too.. AD only string port, // Host Port your connecting too.. AD only @@ -747,7 +757,8 @@ internal ConnectionService( bool useUniqueCacheName, // tells the system to create a unique cache name for this instance. OrganizationDetail orgDetail, // Tells the client connection to bypass all discovery server behaviors and use this detail object. DataverseTraceLogger logSink = null, - Uri instanceToConnectToo = null) + Uri instanceToConnectToo = null, + bool isClone = false) { if (authType != AuthenticationType.AD) throw new ArgumentOutOfRangeException(nameof(authType), "Invalid Authentication type"); @@ -763,6 +774,9 @@ internal ConnectionService( isLogEntryCreatedLocaly = false; } + // is this a clone request + IsAClone = isClone; + // Override WebAPI for Login flow. _configuration.Value.UseWebApiLoginFlow = false; @@ -797,6 +811,7 @@ internal ConnectionService( /// Targeted Instance to connector too. /// (optional) If true attempts login using current user ( Online ) /// (optional) if provided sets the path to write the token cache too. + /// Is this a clone request internal ConnectionService( AuthenticationType authType, // Only OAuth is supported in this constructor. string orgName, // Organization Name your connecting too @@ -814,7 +829,8 @@ internal ConnectionService( DataverseTraceLogger logSink = null, Uri instanceToConnectToo = null, bool useDefaultCreds = false, - string tokenCacheStorePath = null + string tokenCacheStorePath = null, + bool isClone = false ) { if (authType != AuthenticationType.OAuth && authType != AuthenticationType.ClientSecret) @@ -831,6 +847,8 @@ internal ConnectionService( isLogEntryCreatedLocaly = false; } + // is this a clone request + IsAClone = isClone; UseExternalConnection = false; _eAuthType = authType; _organization = orgName; @@ -867,6 +885,7 @@ internal ConnectionService( /// Direct Instance Uri to Connect To /// Incoming Log Sink data /// (optional) if provided, sets the path to store or read user tokens + /// Is this a clone request internal ConnectionService( AuthenticationType authType, // Only Certificate is supported in this constructor. Uri instanceToConnectToo, // set the connection instance to use. @@ -881,7 +900,8 @@ internal ConnectionService( string port, // Port used to connect to bool onPrem, DataverseTraceLogger logSink = null, - string tokenCacheStorePath = null) + string tokenCacheStorePath = null, + bool isClone = false) { if (authType != AuthenticationType.Certificate && authType != AuthenticationType.ExternalTokenManagement) throw new ArgumentOutOfRangeException(nameof(authType), "This constructor only supports the Certificate Auth type"); @@ -897,6 +917,8 @@ internal ConnectionService( isLogEntryCreatedLocaly = false; } + // is this a clone request + IsAClone = isClone; UseExternalConnection = false; _eAuthType = authType; _targetInstanceUriToConnectTo = instanceToConnectToo; @@ -1004,7 +1026,7 @@ private IOrganizationService GetCachedService(out ConnectionService ConnectionOb if (ConnectionObject == null) { // No Service found.. Init the Service and try to bring it online. - IOrganizationService localSvc = InitServiceAsync().Result; + IOrganizationService localSvc = InitServiceAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (localSvc == null) return null; @@ -1045,7 +1067,9 @@ private async Task InitServiceAsync() Stopwatch dtQueryTimer = new Stopwatch(); try { - logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Initialize Dataverse connection Started - AuthType: {0}", _eAuthType.ToString()), TraceEventType.Verbose); + if (!IsAClone) + logEntry.Log(string.Format(CultureInfo.InvariantCulture, "Initialize Dataverse connection Started - AuthType: {0}", _eAuthType.ToString()), TraceEventType.Verbose); + if (UseExternalConnection) { #region Use Externally provided connection @@ -1829,7 +1853,7 @@ internal void SetClonedProperties(ServiceClient sourceClient) try { System.Diagnostics.Trace.WriteLine($"Cloning {sourceClient._connectionSvc._ServiceCACHEName} to create {_ServiceCACHEName}"); - + _isCloning = true; OrgDetailsRead = sourceClient._connectionSvc.OrgDetailsRead; debugingCloneStateFilter++; user = sourceClient.SystemUser; @@ -1871,6 +1895,10 @@ internal void SetClonedProperties(ServiceClient sourceClient) { throw new DataverseOperationException($"Failed constructing cloned connection. debugstate={debugingCloneStateFilter}", ex); } + finally + { + _isCloning = false; // reset flag. + } } #region WebAPI Interface Utilities @@ -2652,8 +2680,9 @@ private bool ShouldRetryWebAPI(Exception ex, int retryCount, int maxRetryCount, /// (optional) If true attempts login using current user /// Logging provider /// (optional) path to log store + /// Cancellation token for the request /// The list of organizations discovered. - internal static async Task DiscoverOrganizationsAsync(Uri discoveryServiceUri, ClientCredentials clientCredentials, string clientId, Uri redirectUri, PromptBehavior promptBehavior, bool isOnPrem, string authority, DataverseTraceLogger logSink = null, bool useGlobalDisco = false, bool useDefaultCreds = false, string tokenCacheStorePath = null, ILogger externalLogger = null) + internal static async Task DiscoverOrganizationsAsync(Uri discoveryServiceUri, ClientCredentials clientCredentials, string clientId, Uri redirectUri, PromptBehavior promptBehavior, bool isOnPrem, string authority, DataverseTraceLogger logSink = null, bool useGlobalDisco = false, bool useDefaultCreds = false, string tokenCacheStorePath = null, ILogger externalLogger = null, CancellationToken cancellationToken = default) { bool isLogEntryCreatedLocaly = false; if (logSink == null) @@ -2666,10 +2695,10 @@ internal static async Task DiscoverOrganizationsAsy { logSink.Log("DiscoverOrganizations - Called using user of MFA Auth for : " + discoveryServiceUri.ToString()); if (!useGlobalDisco) - return await DiscoverOrganizations_InternalAsync(discoveryServiceUri, clientCredentials, null, clientId, redirectUri, promptBehavior, isOnPrem, authority, useDefaultCreds, tokenCacheStorePath: tokenCacheStorePath, logSink: logSink).ConfigureAwait(false); + return await DiscoverOrganizations_InternalAsync(discoveryServiceUri, clientCredentials, null, clientId, redirectUri, promptBehavior, isOnPrem, authority, useDefaultCreds, tokenCacheStorePath: tokenCacheStorePath, logSink: logSink, cancellationToken: cancellationToken).ConfigureAwait(false); else { - return await DiscoverGlobalOrganizationsAsync(discoveryServiceUri, clientCredentials, null, clientId, redirectUri, promptBehavior, isOnPrem, authority, logSink: logSink, useDefaultCreds: useDefaultCreds, tokenCacheStorePath: tokenCacheStorePath).ConfigureAwait(false); + return await DiscoverGlobalOrganizationsAsync(discoveryServiceUri, clientCredentials, null, clientId, redirectUri, promptBehavior, isOnPrem, authority, logSink: logSink, useDefaultCreds: useDefaultCreds, tokenCacheStorePath: tokenCacheStorePath, cancellationToken: cancellationToken).ConfigureAwait(false); } } @@ -2721,8 +2750,9 @@ internal static async Task DiscoverOrganizationsAsy /// Logging endpoint (optional) /// Logging provider /// (optional) path to log store + /// Cancellation token for the request /// Populated OrganizationDetailCollection or Null. - internal static async Task DiscoverGlobalOrganizationsAsync(Uri discoveryServiceUri, Func> tokenProviderFunction, DataverseTraceLogger logSink = null, string tokenCacheStorePath = null, ILogger externalLogger = null) + internal static async Task DiscoverGlobalOrganizationsAsync(Uri discoveryServiceUri, Func> tokenProviderFunction, DataverseTraceLogger logSink = null, string tokenCacheStorePath = null, ILogger externalLogger = null, CancellationToken cancellationToken = default) { bool isLogEntryCreatedLocaly = false; if (logSink == null) @@ -2743,7 +2773,7 @@ internal static async Task DiscoverGlobalOrganizat { logSink.Log("DiscoverOrganizations - : " + discoveryServiceUri.ToString()); string AuthToken = await tokenProviderFunction(discoveryServiceUri.ToString()).ConfigureAwait(false); - return await QueryGlobalDiscoveryAsync(AuthToken, discoveryServiceUri, logSink).ConfigureAwait(false); + return await QueryGlobalDiscoveryAsync(AuthToken, discoveryServiceUri, logSink, cancellationToken).ConfigureAwait(false); } finally { @@ -2766,8 +2796,9 @@ internal static async Task DiscoverGlobalOrganizat /// (optional) Initialized CdsTraceLogger Object /// (optional) If true, tries to login with current users credentials /// (optional) token filePath, if set to empty string, the default path is used + /// Cancellation token for the request /// The list of organizations discovered. - private static async Task DiscoverOrganizations_InternalAsync(Uri discoveryServiceUri, ClientCredentials clientCredentials, X509Certificate2 loginCertificate, string clientId, Uri redirectUri, PromptBehavior promptBehavior, bool isOnPrem, string authority, bool useDefaultCreds = false, string tokenCacheStorePath = null, DataverseTraceLogger logSink = null) + private static async Task DiscoverOrganizations_InternalAsync(Uri discoveryServiceUri, ClientCredentials clientCredentials, X509Certificate2 loginCertificate, string clientId, Uri redirectUri, PromptBehavior promptBehavior, bool isOnPrem, string authority, bool useDefaultCreds = false, string tokenCacheStorePath = null, DataverseTraceLogger logSink = null, CancellationToken cancellationToken = default) { bool createdLogSource = false; Stopwatch dtStartQuery = new Stopwatch(); @@ -2857,8 +2888,9 @@ private static async Task DiscoverOrganizations_Int /// (optional) utilize Global discovery service /// (optional) if true, attempts login with the current users credentials /// (optional) token filePath, if set to empty string, the default path is used + /// Cancellation token for the request /// The list of organizations discovered. - private static async Task DiscoverGlobalOrganizationsAsync(Uri discoveryServiceUri, ClientCredentials clientCredentials, X509Certificate2 loginCertificate, string clientId, Uri redirectUri, PromptBehavior promptBehavior, bool isOnPrem, string authority, DataverseTraceLogger logSink = null, bool useGlobalDisco = false, bool useDefaultCreds = false, string tokenCacheStorePath = null) + private static async Task DiscoverGlobalOrganizationsAsync(Uri discoveryServiceUri, ClientCredentials clientCredentials, X509Certificate2 loginCertificate, string clientId, Uri redirectUri, PromptBehavior promptBehavior, bool isOnPrem, string authority, DataverseTraceLogger logSink = null, bool useGlobalDisco = false, bool useDefaultCreds = false, string tokenCacheStorePath = null, CancellationToken cancellationToken = default) { bool createdLogSource = false; try @@ -2900,7 +2932,7 @@ private static async Task DiscoverGlobalOrganizatio authToken = authRequestResult.GetAuthTokenAndProperties(out AuthenticationResult authenticationResult, out targetServiceUrl, out object msalAuthClientOut, out authority, out resource, out IAccount user, out MemoryBackedTokenCache _); // Get the GD Info and return. - var organizationDetailCollection = await QueryGlobalDiscoveryAsync(authToken, discoveryServiceUri, logSink).ConfigureAwait(false); + var organizationDetailCollection = await QueryGlobalDiscoveryAsync(authToken, discoveryServiceUri, logSink, cancellationToken).ConfigureAwait(false); return new DiscoverOrganizationsResult(organizationDetailCollection, user); } @@ -2921,8 +2953,9 @@ private static async Task DiscoverGlobalOrganizatio /// /// /// + /// Cancellation token for the request /// - private static async Task QueryGlobalDiscoveryAsync(string authToken, Uri discoveryServiceUri, DataverseTraceLogger logSink = null) + private static async Task QueryGlobalDiscoveryAsync(string authToken, Uri discoveryServiceUri, DataverseTraceLogger logSink = null, CancellationToken cancellationToken = default) { bool createdLogSource = false; @@ -2947,7 +2980,7 @@ private static async Task QueryGlobalDiscoveryAsyn headers.Add(Utilities.RequestHeaders.AUTHORIZATION_HEADER, new List()); headers[Utilities.RequestHeaders.AUTHORIZATION_HEADER].Add(string.Format("Bearer {0}", authToken)); - var a = await ExecuteHttpRequestAsync(discoveryServiceUri.ToString(), HttpMethod.Get, customHeaders: headers, logSink: logSink).ConfigureAwait(false); + var a = await ExecuteHttpRequestAsync(discoveryServiceUri.ToString(), HttpMethod.Get, customHeaders: headers, logSink: logSink, cancellationToken: cancellationToken).ConfigureAwait(false); string body = await a.Content.ReadAsStringAsync().ConfigureAwait(false); // Parse the out put into a discovery request. var b = JsonConvert.DeserializeObject(body); @@ -3310,7 +3343,8 @@ private void WebProxyChannelFactory_Opening(object sender, EventArgs e) if (!fact.Endpoint.EndpointBehaviors.Contains(typeof(DataverseTelemetryBehaviors))) { fact.Endpoint.EndpointBehaviors.Add(new DataverseTelemetryBehaviors(this)); - logEntry.Log("Added WebClient Header Hooks to the Request object.", TraceEventType.Verbose); + if (!IsAClone) + logEntry.Log("Added WebClient Header Hooks to the Request object.", TraceEventType.Verbose); } } } diff --git a/src/GeneralTools/DataverseClient/Client/DataverseDataTypeWrapper.cs b/src/GeneralTools/DataverseClient/Client/DataverseDataTypeWrapper.cs index 05344a8..6c52aad 100644 --- a/src/GeneralTools/DataverseClient/Client/DataverseDataTypeWrapper.cs +++ b/src/GeneralTools/DataverseClient/Client/DataverseDataTypeWrapper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/GeneralTools/DataverseClient/Client/Extensions/DeploymentExtensions.cs b/src/GeneralTools/DataverseClient/Client/Extensions/DeploymentExtensions.cs index da12cb7..0c3afa7 100644 --- a/src/GeneralTools/DataverseClient/Client/Extensions/DeploymentExtensions.cs +++ b/src/GeneralTools/DataverseClient/Client/Extensions/DeploymentExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.Crm.Sdk.Messages; +using Microsoft.PowerPlatform.Dataverse.Client.Utils; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Sdk.Query; @@ -8,6 +9,9 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; namespace Microsoft.PowerPlatform.Dataverse.Client.Extensions { @@ -391,9 +395,93 @@ public static Guid ImportDataMap(this ServiceClient serviceClient, string dataMa /// Returns the Async Job ID. To find the status of the job, query the AsyncOperation Entity using GetEntityDataByID using the returned value of this method public static Guid ImportSolutionAsync(this ServiceClient serviceClient, string solutionPath, out Guid importId, bool activatePlugIns = true, bool overwriteUnManagedCustomizations = false, bool skipDependancyOnProductUpdateCheckOnInstall = false, bool importAsHoldingSolution = false, bool isInternalUpgrade = false, Dictionary extraParameters = null) { - return serviceClient.ImportSolutionToImpl(solutionPath, out importId, activatePlugIns, overwriteUnManagedCustomizations, skipDependancyOnProductUpdateCheckOnInstall, importAsHoldingSolution, isInternalUpgrade, true, extraParameters); + return serviceClient.ImportSolutionToImpl(solutionPath, Guid.Empty, out importId, activatePlugIns, overwriteUnManagedCustomizations, skipDependancyOnProductUpdateCheckOnInstall, importAsHoldingSolution, isInternalUpgrade, true, extraParameters); } + /// + /// Import Solution Async used Execute Async pattern to run a solution import. + /// + /// Activate Plugin's and workflows on the Solution + /// This will populate with the Import ID even if the request failed. + /// You can use this ID to request status on the import via a request to the ImportJob entity. + /// Staged Solution Upload Id, created from Stage Solution. + /// Forces an overwrite of unmanaged customizations of the managed solution you are installing, defaults to false + /// Skips dependency against dependencies flagged as product update, defaults to false + /// Applies only on Dataverse organizations version 7.2 or higher. This imports the Dataverse solution as a holding solution utilizing the “As Holding” capability of ImportSolution + /// Internal Microsoft use only + /// Extra parameters + /// ServiceClient + /// Returns the Async Job ID. To find the status of the job, query the AsyncOperation Entity using GetEntityDataByID using the returned value of this method + + public static Guid ImportSolutionAsync(this ServiceClient serviceClient, Guid StageSolutionUploadId, out Guid importId, bool activatePlugIns = true, bool overwriteUnManagedCustomizations = false, bool skipDependancyOnProductUpdateCheckOnInstall = false, bool importAsHoldingSolution = false, bool isInternalUpgrade = false, Dictionary extraParameters = null) + { + return serviceClient.ImportSolutionToImpl(string.Empty, StageSolutionUploadId, out importId, activatePlugIns, overwriteUnManagedCustomizations, skipDependancyOnProductUpdateCheckOnInstall, importAsHoldingSolution, isInternalUpgrade, true, extraParameters); + } + + /// + /// Stages a solution for Import. + /// A solution path or stream containing a solution is required to use this. + /// + /// + /// Path to the solution + /// memory stream containing the solution file to be staged. + /// StageSolutionResults, + public static async Task StageSolution(this ServiceClient serviceClient, string solutionPath, MemoryStream solutionStream = null) + { + if (serviceClient.DataverseService == null) + { + var Error = new DataverseOperationException("Dataverse Service not initialized", ErrorCodes.DataverseServiceClientNotIntialized, string.Empty, null); + serviceClient._logEntry.Log(Error); + throw Error; + } + + if (solutionStream == null && string.IsNullOrWhiteSpace(solutionPath)) + { + var Error = new DataverseOperationException("SolutionPath or Solution File Stream is required", ErrorCodes.SolutionFilePathNull, string.Empty, null); + serviceClient._logEntry.Log(Error); + throw Error; + } + + // determine if the system is connected to OnPrem + if (serviceClient._connectionSvc.ConnectedOrganizationDetail != null && string.IsNullOrEmpty(serviceClient._connectionSvc.ConnectedOrganizationDetail.Geo)) + { + var Error = new DataverseOperationException("StageSolution is not valid for OnPremise deployments", ErrorCodes.OperationInvalidOnPrem, string.Empty, null); + serviceClient._logEntry.Log(Error); + throw Error; + } + + bool streamLocalyCreated = false; + try + { + if (solutionStream == null && File.Exists(solutionPath)) + { + solutionStream = new MemoryStream(File.ReadAllBytes(solutionPath)); + streamLocalyCreated = true; + } + } + catch (Exception ex) + { + var Error = new DataverseOperationException("Read Solution Failed", ex); + serviceClient._logEntry.Log(Error); + throw Error; + + } + + //SolutionParameters + StageSolutionRequest stageSolutionRequest = new StageSolutionRequest() + { + CustomizationFile = solutionStream.ToArray() + }; + if (streamLocalyCreated) + { + solutionStream.Close(); + solutionStream.Dispose(); + } + + // submit request. + var solutionStageResp = (StageSolutionResponse)await serviceClient.ExecuteAsync(stageSolutionRequest).ConfigureAwait(false); + return solutionStageResp.StageSolutionResults; + } /// /// @@ -413,7 +501,28 @@ public static Guid ImportSolutionAsync(this ServiceClient serviceClient, string /// Extra parameters public static Guid ImportSolution(this ServiceClient serviceClient, string solutionPath, out Guid importId, bool activatePlugIns = true, bool overwriteUnManagedCustomizations = false, bool skipDependancyOnProductUpdateCheckOnInstall = false, bool importAsHoldingSolution = false, bool isInternalUpgrade = false, Dictionary extraParameters = null) { - return serviceClient.ImportSolutionToImpl(solutionPath, out importId, activatePlugIns, overwriteUnManagedCustomizations, skipDependancyOnProductUpdateCheckOnInstall, importAsHoldingSolution, isInternalUpgrade, false, extraParameters); + return serviceClient.ImportSolutionToImpl(solutionPath, Guid.Empty, out importId, activatePlugIns, overwriteUnManagedCustomizations, skipDependancyOnProductUpdateCheckOnInstall, importAsHoldingSolution, isInternalUpgrade, false, extraParameters); + } + + /// + /// + /// Imports a Dataverse solution to the Dataverse Server currently connected. + /// *** Note: this is a blocking call and will take time to Import to Dataverse *** + /// + /// + /// Staged Solution Upload Id, created from Stage Solution. + /// Activate Plugin's and workflows on the Solution + /// This will populate with the Import ID even if the request failed. + /// You can use this ID to request status on the import via a request to the ImportJob entity. + /// Forces an overwrite of unmanaged customizations of the managed solution you are installing, defaults to false + /// Skips dependency against dependencies flagged as product update, defaults to false + /// Applies only on Dataverse organizations version 7.2 or higher. This imports the Dataverse solution as a holding solution utilizing the “As Holding” capability of ImportSolution + /// Internal Microsoft use only + /// ServiceClient + /// Extra parameters + public static Guid ImportSolution(this ServiceClient serviceClient, Guid StageSolutionUploadId, out Guid importId, bool activatePlugIns = true, bool overwriteUnManagedCustomizations = false, bool skipDependancyOnProductUpdateCheckOnInstall = false, bool importAsHoldingSolution = false, bool isInternalUpgrade = false, Dictionary extraParameters = null) + { + return serviceClient.ImportSolutionToImpl(string.Empty, StageSolutionUploadId, out importId, activatePlugIns, overwriteUnManagedCustomizations, skipDependancyOnProductUpdateCheckOnInstall, importAsHoldingSolution, isInternalUpgrade, false, extraParameters); } /// @@ -585,6 +694,154 @@ public static ImportStatus IsSampleDataInstalled(this ServiceClient serviceClien //return false; } + /// + /// Retrieve solution import result from Dataverse + /// + /// ServiceClient + /// Import job Id + /// Check if the result need to be formatted + /// Solution import result. + public static SolutionOperationResult RetrieveSolutionImportResultAsync(this ServiceClient serviceClient, Guid importJobId, bool includeFormattedResults = false) + { + var res = new SolutionOperationResult(); + + if (!Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(serviceClient._connectionSvc?.OrganizationVersion, Utilities.FeatureVersionMinimums.AllowRetrieveSolutionImportResult)) + { + // Not supported on this version of Dataverse + serviceClient._logEntry.Log($"RetrieveSolutionImportResultAsync request is calling RetrieveSolutionImportResult API. This request requires Dataverse version {Utilities.FeatureVersionMinimums.AllowRetrieveSolutionImportResult.ToString()} or above. The current Dataverse version is {serviceClient._connectionSvc?.OrganizationVersion}. This request cannot be made", TraceEventType.Warning); + return null; + } + + if (!includeFormattedResults) + { + // Retrieve import job from Dataverse, doesn't include formatted results + var columnSet = new ColumnSet(); + columnSet.AddColumns(new string[] { "importjobid", "data" }); + var query = new QueryByAttribute(); + query.ColumnSet = columnSet; + query.EntityName = "importjob"; + query.AddAttributeValue("importjobid", importJobId); + var importJobs = serviceClient.RetrieveMultipleAsync(query); + + // If the importJobId is wrong, would return a entity collection with 0 record + if (importJobs.Result.Entities.Count == 0) + { + serviceClient._logEntry.Log($"The solution import Job with id {importJobId} is not found.", TraceEventType.Error); + return null; + } + + var importJob = importJobs.Result.Entities[0]; + + // Initialize result data member + res.Type = SolutionOperationType.Import; + res.ErrorMessages = new List(); + res.WarningMessages = new List(); + res.ActionLink = new ActionLink(); + + // Parse the Xml file + XmlDocument doc = XmlUtil.CreateXmlDocument((string)importJob["data"]); + var root = doc.DocumentElement; + if (root.Attributes != null && root.Attributes["succeeded"] != null) + { + res.Status = root.Attributes["succeeded"].Value == "failure" ? SolutionOperationStatus.Failed : SolutionOperationStatus.Passed; + } + + if (res.Status == SolutionOperationStatus.Failed) + { + // Add error message + if (root.Attributes != null && root.Attributes["status"] != null) + { + res.ErrorMessages.Add(root.Attributes["status"].Value); + } + } + else + { + // Add warning message + using (var warningNodes = doc.SelectNodes("//*[@result='warning']")) + { + if (warningNodes != null && warningNodes.Count > 0) + { + foreach (XmlNode node in warningNodes) + { + if (node.Attributes != null && node.Attributes["errortext"] != null) + { + res.WarningMessages.Add(node.Attributes["errortext"].Value); + } + } + } + } + } + + // Add action link + var actionlinkNode = doc.SelectSingleNode("/importexportxml/actionlink"); + if (actionlinkNode != null && actionlinkNode.Attributes != null) + { + var label = actionlinkNode.Attributes["label"]; + var target = actionlinkNode.Attributes["target"]; + + if (label != null) + { + res.ActionLink.Label = label.Value; + } + + if (target != null) + { + res.ActionLink.Target = target.Value; + } + } + } + else + { + // Retrieve import job from Dataverse by RetrieveSolutionImportResult API include formatted results + var req = new OrganizationRequest("RetrieveSolutionImportResult"); + req.Parameters.Add(new KeyValuePair("ImportJobId", importJobId)); + var importJob = serviceClient.Command_Execute(req, "Executing Request for RetrieveSolutionImportResult"); + if (importJob.Results.Contains("SolutionOperationResult")) + { + res = (SolutionOperationResult)importJob.Results["SolutionOperationResult"]; + } + } + return res; + } + + /// + /// Requests status on an Async Operation. + /// + /// + /// + /// + public static async Task GetAsyncOperationStatus(this ServiceClient serviceClient, Guid asyncOperationId) + { + var AsyncQuery = new QueryExpression("asyncoperation") + { + TopCount = 1 + }; + // Add columns necessary for a client to know if the system is acting. + AsyncQuery.ColumnSet.AddColumns( + "asyncoperationid", + "name", + "operationtype", + "breadcrumbid", + "friendlymessage", + "message", + "statecode", + "statuscode", + "correlationid", + "correlationupdatedtime" + ); + AsyncQuery.Criteria.AddCondition("asyncoperationid", ConditionOperator.Equal, asyncOperationId); + + try + { + var asyncOpResult = await serviceClient.RetrieveMultipleAsync(AsyncQuery).ConfigureAwait(false); + return new AsyncStatusResponse(asyncOpResult); + } + catch (Exception ex) + { + throw new DataverseOperationException("Failed to Get AsyncOperation Status", ex); + } + } + #region SupportClasses /// /// ImportStatus Reasons @@ -792,9 +1049,10 @@ public enum FileTypeCode /// Internal Microsoft use only /// Requires the use of an Async Job to do the import. /// ServiceClient + /// Upload ID for Solution that has been staged /// Extra parameters /// Returns the Import Solution Job ID. To find the status of the job, query the ImportJob Entity using GetEntityDataByID using the returned value of this method - internal static Guid ImportSolutionToImpl(this ServiceClient serviceClient, string solutionPath, out Guid importId, bool activatePlugIns, bool overwriteUnManagedCustomizations, bool skipDependancyOnProductUpdateCheckOnInstall, bool importAsHoldingSolution, bool isInternalUpgrade, bool useAsync, Dictionary extraParameters) + internal static Guid ImportSolutionToImpl(this ServiceClient serviceClient, string solutionPath, Guid stageSolutionUploadId, out Guid importId, bool activatePlugIns, bool overwriteUnManagedCustomizations, bool skipDependancyOnProductUpdateCheckOnInstall, bool importAsHoldingSolution, bool isInternalUpgrade, bool useAsync, Dictionary extraParameters) { serviceClient._logEntry.ResetLastError(); // Reset Last Error importId = Guid.Empty; @@ -804,7 +1062,7 @@ internal static Guid ImportSolutionToImpl(this ServiceClient serviceClient, stri return Guid.Empty; } - if (string.IsNullOrWhiteSpace(solutionPath)) + if (stageSolutionUploadId == Guid.Empty && string.IsNullOrWhiteSpace(solutionPath)) { serviceClient._logEntry.Log("************ Exception on ImportSolutionToImpl, SolutionPath is required", TraceEventType.Error); return Guid.Empty; @@ -821,6 +1079,7 @@ internal static Guid ImportSolutionToImpl(this ServiceClient serviceClient, stri bool? convertToManaged = null; bool? isTemplateModeImport = null; string templateSuffix = null; + bool? useStageSolutionProcess = null; if (extraParameters != null) { @@ -830,6 +1089,7 @@ internal static Guid ImportSolutionToImpl(this ServiceClient serviceClient, stri convertToManaged = extraParameters.ContainsKey(ImportSolutionProperties.CONVERTTOMANAGED) ? extraParameters[ImportSolutionProperties.CONVERTTOMANAGED] as bool? : null; isTemplateModeImport = extraParameters.ContainsKey(ImportSolutionProperties.ISTEMPLATEMODE) ? extraParameters[ImportSolutionProperties.ISTEMPLATEMODE] as bool? : null; templateSuffix = extraParameters.ContainsKey(ImportSolutionProperties.TEMPLATESUFFIX) ? extraParameters[ImportSolutionProperties.TEMPLATESUFFIX].ToString() : string.Empty; + useStageSolutionProcess = extraParameters.ContainsKey(ImportSolutionProperties.USESTAGEANDUPGRADEMODE) ? extraParameters[ImportSolutionProperties.USESTAGEANDUPGRADEMODE] as bool? : null; // Pick up the data from the request, if the request has the AsyncRibbonProcessing flag, pick up the value of it. asyncRibbonProcessing = extraParameters.ContainsKey(ImportSolutionProperties.ASYNCRIBBONPROCESSING) ? extraParameters[ImportSolutionProperties.ASYNCRIBBONPROCESSING] as bool? : null; @@ -848,7 +1108,7 @@ internal static Guid ImportSolutionToImpl(this ServiceClient serviceClient, stri if (!Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(serviceClient._connectionSvc?.OrganizationVersion, Utilities.FeatureVersionMinimums.AllowAsyncRibbonProcessing)) { // Not supported on this version of Dataverse - serviceClient._logEntry.Log($"ImportSolution request contains {ImportSolutionProperties.ASYNCRIBBONPROCESSING} property. This request Dataverse version {Utilities.FeatureVersionMinimums.AllowAsyncRibbonProcessing.ToString()} or above. Current Dataverse version is {serviceClient._connectionSvc?.OrganizationVersion}. This property will be removed", TraceEventType.Warning); + serviceClient._logEntry.Log($"ImportSolution request contains {ImportSolutionProperties.ASYNCRIBBONPROCESSING} property. This request requires Dataverse version {Utilities.FeatureVersionMinimums.AllowAsyncRibbonProcessing.ToString()} or above. Current Dataverse version is {serviceClient._connectionSvc?.OrganizationVersion}. This property will be removed", TraceEventType.Warning); asyncRibbonProcessing = null; } } @@ -866,7 +1126,7 @@ internal static Guid ImportSolutionToImpl(this ServiceClient serviceClient, stri if (!Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(serviceClient._connectionSvc?.OrganizationVersion, Utilities.FeatureVersionMinimums.AllowComponetInfoProcessing)) { // Not supported on this version of Dataverse - serviceClient._logEntry.Log($"ImportSolution request contains {ImportSolutionProperties.COMPONENTPARAMETERSPARAM} property. This request Dataverse version {Utilities.FeatureVersionMinimums.AllowComponetInfoProcessing.ToString()} or above. Current Dataverse version is {serviceClient._connectionSvc?.OrganizationVersion}. This property will be removed", TraceEventType.Warning); + serviceClient._logEntry.Log($"ImportSolution request contains {ImportSolutionProperties.COMPONENTPARAMETERSPARAM} property. This request requires Dataverse version {Utilities.FeatureVersionMinimums.AllowComponetInfoProcessing.ToString()} or above. Current Dataverse version is {serviceClient._connectionSvc?.OrganizationVersion}. This property will be removed", TraceEventType.Warning); componetsToProcess = null; } } @@ -884,30 +1144,58 @@ internal static Guid ImportSolutionToImpl(this ServiceClient serviceClient, stri if (!Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(serviceClient._connectionSvc?.OrganizationVersion, Utilities.FeatureVersionMinimums.AllowTemplateSolutionImport)) { // Not supported on this version of Dataverse - serviceClient._logEntry.Log($"ImportSolution request contains {ImportSolutionProperties.ISTEMPLATEMODE} property. This request Dataverse version {Utilities.FeatureVersionMinimums.AllowTemplateSolutionImport.ToString()} or above. Current Dataverse version is {serviceClient._connectionSvc?.OrganizationVersion}. This property will be removed", TraceEventType.Warning); + serviceClient._logEntry.Log($"ImportSolution request contains {ImportSolutionProperties.ISTEMPLATEMODE} property. This request requires Dataverse version {Utilities.FeatureVersionMinimums.AllowTemplateSolutionImport.ToString()} or above. Current Dataverse version is {serviceClient._connectionSvc?.OrganizationVersion}. This property will be removed", TraceEventType.Warning); isTemplateModeImport = null; } } } - } + if (useStageSolutionProcess != null) + { + if (isConnectedToOnPrem) + { + serviceClient._logEntry.Log($"StageAndUpgrade Mode is not valid for OnPremise deployments. Normal Solution Upgrade behavior will be utilized", TraceEventType.Warning); + useStageSolutionProcess = null; + } + else + { + if (!Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(serviceClient._connectionSvc?.OrganizationVersion, Utilities.FeatureVersionMinimums.AllowStageAndUpgrade)) + { + // Not supported on this version of Dataverse + serviceClient._logEntry.Log($"StageAndUpgrade Mode requires Dataverse version {Utilities.FeatureVersionMinimums.AllowStageAndUpgrade.ToString()} or above. Current Dataverse version is {serviceClient._connectionSvc?.OrganizationVersion}. Normal Solution Upgrade behavior will be utilized", TraceEventType.Warning); + useStageSolutionProcess = null; + } + } + } + } string solutionNameForLogging = string.IsNullOrWhiteSpace(solutionName) ? string.Empty : string.Concat(solutionName, " - "); // try to load the file from the file system - if (File.Exists(solutionPath)) + if ((!string.IsNullOrEmpty(solutionPath) && File.Exists(solutionPath)) + || stageSolutionUploadId != Guid.Empty) { try { importId = Guid.NewGuid(); - byte[] fileData = File.ReadAllBytes(solutionPath); ImportSolutionRequest SolutionImportRequest = new ImportSolutionRequest() { - CustomizationFile = fileData, PublishWorkflows = activatePlugIns, ImportJobId = importId, - OverwriteUnmanagedCustomizations = overwriteUnManagedCustomizations + OverwriteUnmanagedCustomizations = overwriteUnManagedCustomizations, }; + if (stageSolutionUploadId != Guid.Empty) + { + SolutionImportRequest.SolutionParameters = new SolutionParameters() + { + StageSolutionUploadId = stageSolutionUploadId, + }; + } + else + { + SolutionImportRequest.CustomizationFile = File.ReadAllBytes(solutionPath); + } + //If the desiredLayerOrder is null don't add it to the request. This ensures backward compatibility. It makes old packages work on old builds if (desiredLayerOrder != null) { @@ -975,7 +1263,30 @@ internal static Guid ImportSolutionToImpl(this ServiceClient serviceClient, stri Guid requestTrackingId = Guid.NewGuid(); SolutionImportRequest.RequestId = requestTrackingId; - if (!isConnectedToOnPrem && Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(serviceClient.ConnectedOrgVersion, Utilities.FeatureVersionMinimums.AllowImportSolutionAsyncV2)) + if (!isConnectedToOnPrem && useStageSolutionProcess.HasValue && useStageSolutionProcess.Value) + { + StageAndUpgradeAsyncRequest stgAndUpgradeReq = new StageAndUpgradeAsyncRequest + { + Parameters = SolutionImportRequest.Parameters + }; + + // remove unsupported parameter from importsolutionasync request. + if (stgAndUpgradeReq.Parameters.ContainsKey("ImportJobId")) + stgAndUpgradeReq.Parameters.Remove("ImportJobId"); + + serviceClient._logEntry.Log(string.Format(CultureInfo.InvariantCulture, "{1}Created Async StageAndUpgradeAsyncRequest : RequestID={0} ", requestTrackingId.ToString(), solutionNameForLogging), TraceEventType.Verbose); + StageAndUpgradeAsyncResponse asyncResp = (StageAndUpgradeAsyncResponse)serviceClient.Command_Execute(stgAndUpgradeReq, solutionNameForLogging + "Executing Request for StageAndUpgradeAsyncRequest : "); + if (asyncResp == null) + return Guid.Empty; + else + { + _ = Guid.TryParse(asyncResp.ImportJobKey, out Guid parsedImportKey); + importId = parsedImportKey; + return asyncResp.AsyncOperationId; + } + + } + else if (!isConnectedToOnPrem && Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(serviceClient.ConnectedOrgVersion, Utilities.FeatureVersionMinimums.AllowImportSolutionAsyncV2)) { // map import request to Async Model ImportSolutionAsyncRequest asynImportRequest = new ImportSolutionAsyncRequest() @@ -1003,7 +1314,12 @@ internal static Guid ImportSolutionToImpl(this ServiceClient serviceClient, stri if (asyncResp == null) return Guid.Empty; else + { + _ = Guid.TryParse(asyncResp.ImportJobKey, out Guid parsedImportKey); + importId = parsedImportKey; + return asyncResp.AsyncOperationId; + } } else { @@ -1020,11 +1336,27 @@ internal static Guid ImportSolutionToImpl(this ServiceClient serviceClient, stri } else { - ImportSolutionResponse resp = (ImportSolutionResponse)serviceClient.Command_Execute(SolutionImportRequest, solutionNameForLogging + "Executing ImportSolutionRequest for ImportSolution"); - if (resp == null) - return Guid.Empty; + if (useStageSolutionProcess.HasValue && useStageSolutionProcess.Value) + { + StageAndUpgradeRequest stageAndUpgrade = new StageAndUpgradeRequest(); + stageAndUpgrade.Parameters = SolutionImportRequest.Parameters; + + StageAndUpgradeResponse resp = (StageAndUpgradeResponse)serviceClient.Command_Execute(stageAndUpgrade, solutionNameForLogging + "Executing StageAndUpgradeRequest for ImportSolutionTo"); + if (resp == null) + return Guid.Empty; + else + { + return importId; + } + } else - return importId; + { + ImportSolutionResponse resp = (ImportSolutionResponse)serviceClient.Command_Execute(SolutionImportRequest, solutionNameForLogging + "Executing ImportSolutionRequest for ImportSolution"); + if (resp == null) + return Guid.Empty; + else + return importId; + } } } #region Exception handlers for files @@ -1071,7 +1403,6 @@ internal static Guid ImportSolutionToImpl(this ServiceClient serviceClient, stri return Guid.Empty; } } - #endregion } } diff --git a/src/GeneralTools/DataverseClient/Client/Extensions/SupportClasses/AsyncStatusResponse.cs b/src/GeneralTools/DataverseClient/Client/Extensions/SupportClasses/AsyncStatusResponse.cs new file mode 100644 index 0000000..051179a --- /dev/null +++ b/src/GeneralTools/DataverseClient/Client/Extensions/SupportClasses/AsyncStatusResponse.cs @@ -0,0 +1,212 @@ +#pragma warning disable CS1591 + +using Microsoft.Xrm.Sdk; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.PowerPlatform.Dataverse.Client.Extensions +{ + public class AsyncStatusResponse + { + /// + /// Status of the system job. + /// + public enum AsyncStatusResponse_statecode + { + Ready = 0, + Suspended = 1, + Locked = 2, + Completed = 3, + FailedParse = 999 + } + + /// + /// Reason for the status of the system job. + /// + public enum AsyncStatusResponse_statuscode + { + WaitingForResources = 0, + Waiting = 10, + InProgress = 20, + Pausing = 21, + Canceling = 22, + Succeeded = 30, + Failed = 31, + Canceled = 32, + FailedParse = 999 + } + + /// + /// Raw entity returned from the operation status poll. + /// + public Entity RetrievedEntity { get; set; } + + /// + /// Operation Id. + /// + public Guid AsyncOperationId + { + get + { + if (RetrievedEntity != null) + return RetrievedEntity.Id; + else + return Guid.Empty; + } + } + + /// + /// Name of the Operation + /// + public string OperationName + { + get + { + if (RetrievedEntity != null) + return RetrievedEntity.Attributes.ContainsKey("name") ? RetrievedEntity.GetAttributeValue("name") : string.Empty; + else + return string.Empty; + } + } + + /// + /// Type of the Operation + /// + public string OperationType + { + get + { + if (RetrievedEntity != null) + return RetrievedEntity.Attributes.ContainsKey("operationtype") ? RetrievedEntity.FormattedValues["operationtype"] : string.Empty; + else + return string.Empty; + } + } + + /// + /// User readable message, if available. + /// + public string FriendlyMessage + { + get + { + if (RetrievedEntity != null) + return RetrievedEntity.Attributes.ContainsKey("friendlymessage") ? RetrievedEntity.GetAttributeValue("friendlymessage") : string.Empty; + else + return string.Empty; + } + } + + /// + /// System message, if available + /// + public string Message + { + get + { + if (RetrievedEntity != null) + return RetrievedEntity.Attributes.ContainsKey("message") ? RetrievedEntity.GetAttributeValue("message") : string.Empty; + else + return string.Empty; + } + } + + /// + /// Correlation id + /// + public Guid CorrlationId + { + get + { + if (RetrievedEntity != null) + return RetrievedEntity.Attributes.ContainsKey("correlationid") ? RetrievedEntity.GetAttributeValue("correlationid") : Guid.Empty; + else + return Guid.Empty; + } + } + + /// + /// Operation Status Code. + /// + public AsyncStatusResponse_statuscode StatusCode { get; internal set; } + + /// + /// Localized text version of Status code, if available + /// + public string StatusCode_Localized { + get + { + if (RetrievedEntity != null) + return RetrievedEntity.Attributes.ContainsKey("statuscode") ? RetrievedEntity.FormattedValues["statuscode"] : string.Empty; + else + return string.Empty; + } + } + + /// + /// Operation State code + /// + public AsyncStatusResponse_statecode State { get; internal set; } + + /// + /// Localized text version of state code text, if available + /// + public string State_Localized + { + get + { + if (RetrievedEntity != null) + return RetrievedEntity.Attributes.ContainsKey("statecode") ? RetrievedEntity.FormattedValues["statecode"] : string.Empty; + else + return string.Empty; + } + } + + /// + /// Creates an AsyncStatusResponse Object + /// + /// + internal AsyncStatusResponse(EntityCollection asyncOperationResponses) + { + // parse the Async Operation type. + if (asyncOperationResponses == null) + { + // Do something Result is null. + + } + else if ( asyncOperationResponses != null && !asyncOperationResponses.Entities.Any()) { + // Do something ( no records ) + }else + { + // not null and have records. + this.RetrievedEntity = asyncOperationResponses.Entities.First(); // get first entity. + // Parse state and status + OptionSetValue ostatecode = RetrievedEntity.Attributes.ContainsKey("statecode") ? RetrievedEntity.GetAttributeValue("statecode") : new OptionSetValue(-1); + try + { + State = (AsyncStatusResponse_statecode)ostatecode.Value; + } + catch + { + State = AsyncStatusResponse_statecode.FailedParse; + } + + OptionSetValue ostatuscode = RetrievedEntity.Attributes.ContainsKey("statuscode") ? RetrievedEntity.GetAttributeValue("statuscode") : new OptionSetValue(-1); + try + { + StatusCode = (AsyncStatusResponse_statuscode)ostatuscode.Value; + } + catch + { + StatusCode = AsyncStatusResponse_statuscode.FailedParse; + } + + } + } + + } +} +#pragma warning restore CS1591 \ No newline at end of file diff --git a/src/GeneralTools/DataverseClient/Client/Microsoft.PowerPlatform.Dataverse.Client.csproj b/src/GeneralTools/DataverseClient/Client/Microsoft.PowerPlatform.Dataverse.Client.csproj index 6ef91aa..7fb5adf 100644 --- a/src/GeneralTools/DataverseClient/Client/Microsoft.PowerPlatform.Dataverse.Client.csproj +++ b/src/GeneralTools/DataverseClient/Client/Microsoft.PowerPlatform.Dataverse.Client.csproj @@ -39,7 +39,7 @@ - + diff --git a/src/GeneralTools/DataverseClient/Client/ServiceClient.cs b/src/GeneralTools/DataverseClient/Client/ServiceClient.cs index 945077d..d3cf36c 100644 --- a/src/GeneralTools/DataverseClient/Client/ServiceClient.cs +++ b/src/GeneralTools/DataverseClient/Client/ServiceClient.cs @@ -303,7 +303,7 @@ public string CurrentAccessToken _connectionSvc.AuthenticationTypeInUse == AuthenticationType.ExternalTokenManagement || _connectionSvc.AuthenticationTypeInUse == AuthenticationType.ClientSecret)) { - return _connectionSvc.RefreshClientTokenAsync().Result; + return _connectionSvc.RefreshClientTokenAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } else return string.Empty; @@ -390,8 +390,7 @@ internal WhoAmIResponse SystemUser return _connectionSvc.CurrentUser; else { - WhoAmIResponse resp = Task.Run(async () => await _connectionSvc.GetWhoAmIDetails(this).ConfigureAwait(false)).Result; - //WhoAmIResponse resp = _connectionSvc.GetWhoAmIDetails(this).Result; + WhoAmIResponse resp = Task.Run(async () => await _connectionSvc.GetWhoAmIDetails(this).ConfigureAwait(false)).ConfigureAwait(false).GetAwaiter().GetResult(); _connectionSvc.CurrentUser = resp; return resp; } @@ -1189,8 +1188,7 @@ internal void CreateServiceConnection( // if using an user provided connection,. if (externalOrgWebProxyClient != null) { - _connectionSvc = new ConnectionService(externalOrgWebProxyClient, requestedAuthType, _logEntry); - _connectionSvc.IsAClone = isCloned; + _connectionSvc = new ConnectionService(externalOrgWebProxyClient, requestedAuthType, _logEntry, isClone: isCloned); if (isCloned && incomingOrgVersion != null) { _connectionSvc.OrganizationVersion = incomingOrgVersion; @@ -1206,7 +1204,13 @@ internal void CreateServiceConnection( useUniqueInstance, orgDetail, clientId, redirectUri, certificateThumbPrint, - certificateStoreName, certificate, hostName, port, false, logSink: _logEntry, tokenCacheStorePath: tokenCacheStorePath); + certificateStoreName, + certificate, + hostName, + port, + false, + logSink: _logEntry, + tokenCacheStorePath: tokenCacheStorePath); if (GetAccessToken != null) _connectionSvc.GetAccessTokenAsync = GetAccessToken; @@ -1275,7 +1279,7 @@ internal void CreateServiceConnection( // Min supported version for batch operations. if (_connectionSvc?.OrganizationVersion != null && Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(_connectionSvc?.OrganizationVersion, Utilities.FeatureVersionMinimums.BatchOperations)) - _batchManager = new BatchManager(_logEntry); + _batchManager = new BatchManager(_logEntry, IsClonedConnection:isCloned); else _logEntry.Log("Batch System disabled, Dataverse Server does not support this message call", TraceEventType.Information); @@ -1350,63 +1354,73 @@ public ServiceClient Clone(System.Reflection.Assembly strongTypeAsm, ILogger log if (_connectionSvc.AuthenticationTypeInUse == AuthenticationType.AD) throw new DataverseOperationException("On-Premises Connections are not supported for clone operations at this time.", new NotImplementedException("OnPrem Auth Flow are not implemented for clone operations")); - OrganizationWebProxyClientAsync proxy = null; - if (_connectionSvc.ConnectOrgUriActual != null) - { - if (strongTypeAsm == null) - proxy = new OrganizationWebProxyClientAsync(_connectionSvc.ConnectOrgUriActual, true); - else - proxy = new OrganizationWebProxyClientAsync(_connectionSvc.ConnectOrgUriActual, strongTypeAsm); - } - else + _connectionSvc._isCloning = true; // set cloning behavior flag. + _configuration.Value.UseWebApiLoginFlow = false; // override default settings for clone ops. + + try { - var orgWebClient = _connectionSvc.WebClient; - if (orgWebClient != null) + OrganizationWebProxyClientAsync proxy = null; + if (_connectionSvc.ConnectOrgUriActual != null) { if (strongTypeAsm == null) - proxy = new OrganizationWebProxyClientAsync(orgWebClient.Endpoint.Address.Uri, true); + proxy = new OrganizationWebProxyClientAsync(_connectionSvc.ConnectOrgUriActual, true); else - proxy = new OrganizationWebProxyClientAsync(orgWebClient.Endpoint.Address.Uri, strongTypeAsm); + proxy = new OrganizationWebProxyClientAsync(_connectionSvc.ConnectOrgUriActual, strongTypeAsm); } else { - _logEntry.Log("Connection cannot be cloned. There is currently no OAuth based connection active."); - return null; - } - } - if (proxy != null) - { - try - { - // Get Current Access Token. - // This will get the current access token - if (logger == null) logger = _logEntry._logger; - proxy.HeaderToken = this.CurrentAccessToken; - var SvcClient = new ServiceClient(proxy, true, _connectionSvc.AuthenticationTypeInUse, _connectionSvc?.OrganizationVersion, logger: logger); - SvcClient._connectionSvc.SetClonedProperties(this); - SvcClient.CallerAADObjectId = CallerAADObjectId; - SvcClient.CallerId = CallerId; - SvcClient.MaxRetryCount = _configuration.Value.MaxRetryCount; - SvcClient.RetryPauseTime = _configuration.Value.RetryPauseTime; - SvcClient.GetAccessToken = GetAccessToken; - - return SvcClient; + var orgWebClient = _connectionSvc.WebClient; + if (orgWebClient != null) + { + if (strongTypeAsm == null) + proxy = new OrganizationWebProxyClientAsync(orgWebClient.Endpoint.Address.Uri, true); + else + proxy = new OrganizationWebProxyClientAsync(orgWebClient.Endpoint.Address.Uri, strongTypeAsm); + } + else + { + _logEntry.Log("Connection cannot be cloned. There is currently no OAuth based connection active."); + return null; + } } - catch (DataverseConnectionException) + if (proxy != null) { - // rethrow the Connection exception coming from the initial call. - throw; + try + { + // Get Current Access Token. + // This will get the current access token + if (logger == null) logger = _logEntry._logger; + proxy.HeaderToken = this.CurrentAccessToken; + var SvcClient = new ServiceClient(proxy, true, _connectionSvc.AuthenticationTypeInUse, _connectionSvc?.OrganizationVersion, logger: logger); + SvcClient._connectionSvc.SetClonedProperties(this); + SvcClient.CallerAADObjectId = CallerAADObjectId; + SvcClient.CallerId = CallerId; + SvcClient.MaxRetryCount = _configuration.Value.MaxRetryCount; + SvcClient.RetryPauseTime = _configuration.Value.RetryPauseTime; + SvcClient.GetAccessToken = GetAccessToken; + + return SvcClient; + } + catch (DataverseConnectionException) + { + // rethrow the Connection exception coming from the initial call. + throw; + } + catch (Exception ex) + { + _logEntry.Log(ex); + throw new DataverseConnectionException("Failed to Clone Connection", ex); + } } - catch (Exception ex) + else { - _logEntry.Log(ex); - throw new DataverseConnectionException("Failed to Clone Connection", ex); + _logEntry.Log("Connection cannot be cloned. There is currently no OAuth based connection active or it is mis-configured in the ServiceClient."); + return null; } - } - else + }finally { - _logEntry.Log("Connection cannot be cloned. There is currently no OAuth based connection active or it is mis-configured in the ServiceClient."); - return null; + _connectionSvc._isCloning = false; // set cloning behavior flag. + _configuration.Value.UseWebApiLoginFlow = false; // override default settings for clone ops. } } @@ -1504,6 +1518,23 @@ public static async Task DiscoverOnlineOrganizatio return await ConnectionService.DiscoverGlobalOrganizationsAsync(discoveryServiceUri, tokenProviderFunction, externalLogger: logger, tokenCacheStorePath: tokenCacheStorePath).ConfigureAwait(false); } + /// + /// Discovers Organizations Using the global discovery service and an external source for access tokens + /// + /// Global discovery base URI to use to connect too, if null will utilize the commercial Global Discovery Server. + /// Function that will provide access token to the discovery call. + /// (optional) path to log store + /// Logging provider + /// Cancellation token for the request + /// + public static async Task DiscoverOnlineOrganizationsAsync(Func> tokenProviderFunction, Uri discoveryServiceUri = null, string tokenCacheStorePath = null, ILogger logger = null, CancellationToken cancellationToken = default) + { + if (discoveryServiceUri == null) + discoveryServiceUri = new Uri(ConnectionService.GlobalDiscoveryAllInstancesUri); // use commercial GD + + return await ConnectionService.DiscoverGlobalOrganizationsAsync(discoveryServiceUri, tokenProviderFunction, externalLogger: logger, tokenCacheStorePath: tokenCacheStorePath, cancellationToken: cancellationToken).ConfigureAwait(false); + } + #endregion #endregion @@ -1577,7 +1608,60 @@ public HttpResponseMessage ExecuteWebRequest(HttpMethod method, string queryStri queryString = baseQueryString; } - var result = _connectionSvc.Command_WebExecuteAsync(queryString, body, method, customHeaders, contentType, string.Empty, CallerId, _disableConnectionLocking, MaxRetryCount, RetryPauseTime, cancellationToken: cancellationToken).Result; + var result = _connectionSvc.Command_WebExecuteAsync(queryString, body, method, customHeaders, contentType, string.Empty, CallerId, _disableConnectionLocking, MaxRetryCount, RetryPauseTime, cancellationToken: cancellationToken).ConfigureAwait(false).GetAwaiter().GetResult(); + if (result == null) + throw LastException; + else + return result; + } + + /// + /// Executes a web request against Xrm WebAPI Async. + /// + /// Here you would pass the path and query parameters that you wish to pass onto the WebAPI. + /// The format used here is as follows: + /// {APIURI}/api/data/v{instance version}/querystring. + /// For example, + /// if you wanted to get data back from an account, you would pass the following: + /// accounts(id) + /// which creates: get - https://myinstance.crm.dynamics.com/api/data/v9.0/accounts(id) + /// if you were creating an account, you would pass the following: + /// accounts + /// which creates: post - https://myinstance.crm.dynamics.com/api/data/v9.0/accounts - body contains the data. + /// + /// Method to use for the request + /// Content your passing to the request + /// Headers in addition to the default headers added by for Executing a web request + /// Content Type attach to the request. this defaults to application/json if not set. + /// Cancellation token for the request + /// + public async Task ExecuteWebRequestAsync(HttpMethod method, string queryString, string body, Dictionary> customHeaders, string contentType = default, CancellationToken cancellationToken = default) + { + _logEntry.ResetLastError(); // Reset Last Error + ValidateConnectionLive(); + if (DataverseService == null) + { + _logEntry.Log("Dataverse Service not initialized", TraceEventType.Error); + return null; + } + + if (string.IsNullOrEmpty(queryString) && string.IsNullOrEmpty(body)) + { + _logEntry.Log("Execute Web Request failed, queryString and body cannot be null", TraceEventType.Error); + return null; + } + + if (Uri.TryCreate(queryString, UriKind.Absolute, out var urlPath)) + { + // Was able to create a URL here... Need to make sure that we strip out everything up to the last segment. + string baseQueryString = urlPath.Segments.Last(); + if (!string.IsNullOrEmpty(urlPath.Query)) + queryString = baseQueryString + urlPath.Query; + else + queryString = baseQueryString; + } + + var result = await _connectionSvc.Command_WebExecuteAsync(queryString, body, method, customHeaders, contentType, string.Empty, CallerId, _disableConnectionLocking, MaxRetryCount, RetryPauseTime, cancellationToken: cancellationToken).ConfigureAwait(false); if (result == null) throw LastException; else @@ -1626,7 +1710,7 @@ internal OrganizationResponse ExecuteOrganizationRequestImpl(OrganizationRequest else { // use Web API. - return _connectionSvc.Command_WebAPIProcess_ExecuteAsync(req, logMessageTag, bypassPluginExecution, _metadataUtlity, CallerId, _disableConnectionLocking, MaxRetryCount, RetryPauseTime, new CancellationToken()).Result; + return _connectionSvc.Command_WebAPIProcess_ExecuteAsync(req, logMessageTag, bypassPluginExecution, _metadataUtlity, CallerId, _disableConnectionLocking, MaxRetryCount, RetryPauseTime, new CancellationToken()).ConfigureAwait(false).GetAwaiter().GetResult(); } } else @@ -1742,6 +1826,7 @@ internal async Task Command_ExecuteAsyncImpl(OrganizationR ), TraceEventType.Verbose); logDt.Restart(); + _= await _connectionSvc.RefreshClientTokenAsync().ConfigureAwait(false); rsp = await DataverseServiceAsync.ExecuteAsync(req).ConfigureAwait(false); logDt.Stop(); @@ -1838,6 +1923,7 @@ internal OrganizationResponse Command_Execute(OrganizationRequest req, string er ), TraceEventType.Verbose); logDt.Restart(); + _connectionSvc.RefreshClientTokenAsync().Wait(); // Refresh the token if needed.. if (!_disableConnectionLocking) // Allow Developer to override Cross Thread Safeties lock (_lockObject) { diff --git a/src/GeneralTools/DataverseClient/Client/Utils/ErrorCodes.cs b/src/GeneralTools/DataverseClient/Client/Utils/ErrorCodes.cs index 5ad74f2..47af0c0 100644 --- a/src/GeneralTools/DataverseClient/Client/Utils/ErrorCodes.cs +++ b/src/GeneralTools/DataverseClient/Client/Utils/ErrorCodes.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -25,5 +25,21 @@ internal static class ErrorCodes /// Number of concurrent requests exceeded the limit of {0}. /// public const int ThrottlingConcurrencyLimitExceededError = unchecked((int)0x80072326); // -2147015898 + + /// + /// Dataverse ServiceClient is not Initialized + /// + public const int DataverseServiceClientNotIntialized = unchecked((int)0x8004426C); // -2147204500 + + /// + /// Solution Path and or File Stream is Null + /// + public const int SolutionFilePathNull = unchecked((int)0x800443FC); // - 2147204100; + + /// + /// Operation is not valid onprem. + /// + public const int OperationInvalidOnPrem = unchecked((int)0x80044262); // -2147204510; + } } diff --git a/src/GeneralTools/DataverseClient/Client/Utils/ImportSolutionProperties.cs b/src/GeneralTools/DataverseClient/Client/Utils/ImportSolutionProperties.cs index c8c9356..cfbbccf 100644 --- a/src/GeneralTools/DataverseClient/Client/Utils/ImportSolutionProperties.cs +++ b/src/GeneralTools/DataverseClient/Client/Utils/ImportSolutionProperties.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -39,6 +39,10 @@ public static class ImportSolutionProperties /// Internal use only /// public static string ISTEMPLATEMODE = "IsTemplateMode"; + /// + /// When set to true, causes ImportSolution process to use the Stage and Upgrade Process. + /// + public static string USESTAGEANDUPGRADEMODE = "StageAndUpgradeSolution"; } } diff --git a/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs b/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs index c24fbad..94b6ac4 100644 --- a/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs +++ b/src/GeneralTools/DataverseClient/Client/Utils/Utils.cs @@ -1026,7 +1026,7 @@ internal static bool IsFeatureValidForEnviroment(Version instanceVersion, Versio /// /// MinVersion that supports Session ID Telemetry Tracking. /// - internal static Version SessionTrackingSupported = new Version("9.0.2.0"); + internal static Version SessionTrackingSupported = new Version("9.0.0.0"); /// /// MinVersion that supports Forcing Cache Sync. @@ -1063,6 +1063,16 @@ internal static bool IsFeatureValidForEnviroment(Version instanceVersion, Versio /// internal static Version AllowImportSolutionAsyncV2 = new Version("9.2.21013.00131"); + /// + /// Minimum version support for StageAndUpgrade API. + /// + internal static Version AllowStageAndUpgrade = new Version("9.2.21013.00131"); + + /// + /// Minimum version support for RetrieveSolutionImportResult API. + /// + internal static Version AllowRetrieveSolutionImportResult = new Version("9.2.23034.30"); + } diff --git a/src/GeneralTools/DataverseClient/ConnectControl/ConnectionManager.cs b/src/GeneralTools/DataverseClient/ConnectControl/ConnectionManager.cs index 4ae804d..59b0e2b 100644 --- a/src/GeneralTools/DataverseClient/ConnectControl/ConnectionManager.cs +++ b/src/GeneralTools/DataverseClient/ConnectControl/ConnectionManager.cs @@ -387,7 +387,7 @@ public bool RequireUserLogin() /// public void SetConfigKeyInformation(Dictionary configKeys) { - _tracer.Log(string.Format("SetConfigKeyInfo, Key Count = {0}", configKeys.Count), TraceEventType.Information); + _tracer.Log($"{nameof(SetConfigKeyInformation)}, Setting {nameof(ServerConfigKeys)} with key Count = {configKeys?.Count}", TraceEventType.Information); ServerConfigKeys = configKeys; } diff --git a/src/GeneralTools/DataverseClient/ConnectControl/Styles/BrushResourcesNormalMode.xaml b/src/GeneralTools/DataverseClient/ConnectControl/Styles/BrushResourcesNormalMode.xaml index dbd80a5..164c529 100644 --- a/src/GeneralTools/DataverseClient/ConnectControl/Styles/BrushResourcesNormalMode.xaml +++ b/src/GeneralTools/DataverseClient/ConnectControl/Styles/BrushResourcesNormalMode.xaml @@ -4,6 +4,6 @@ - + \ No newline at end of file diff --git a/src/GeneralTools/DataverseClient/ConnectControl/Utility/LoginTracer.cs b/src/GeneralTools/DataverseClient/ConnectControl/Utility/LoginTracer.cs index 7a16af6..5f62ddc 100644 --- a/src/GeneralTools/DataverseClient/ConnectControl/Utility/LoginTracer.cs +++ b/src/GeneralTools/DataverseClient/ConnectControl/Utility/LoginTracer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Linq; using System.Web.Services.Protocols; @@ -57,8 +57,9 @@ public LoginTracer(string traceSourceName = "") public override void ResetLastError() { - base.LastError.Remove(0, LastError.Length); - LastException = null; + if (base.LastError.Length > 0) + base.LastError = base.LastError.Remove(0, LastError.Length - 1); + LastException = null; } diff --git a/src/GeneralTools/DataverseClient/UIStyles/Resources/Textblock/BrushResourcesNormalMode.xaml b/src/GeneralTools/DataverseClient/UIStyles/Resources/Textblock/BrushResourcesNormalMode.xaml index 048ebfc..c1ae6bd 100644 --- a/src/GeneralTools/DataverseClient/UIStyles/Resources/Textblock/BrushResourcesNormalMode.xaml +++ b/src/GeneralTools/DataverseClient/UIStyles/Resources/Textblock/BrushResourcesNormalMode.xaml @@ -1,9 +1,9 @@ - - + \ No newline at end of file diff --git a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/DataverseClient_Core_UnitTests.csproj b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/DataverseClient_Core_UnitTests.csproj index ca10adc..95f5f47 100644 --- a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/DataverseClient_Core_UnitTests.csproj +++ b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/DataverseClient_Core_UnitTests.csproj @@ -49,6 +49,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs index b7be126..14a6dd3 100644 --- a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs +++ b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/ServiceClientTests.cs @@ -13,12 +13,15 @@ using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Messages; using Microsoft.Xrm.Sdk.Metadata; +using Microsoft.Xrm.Sdk.Query; using Moq; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Net.Http; +using System.Runtime.CompilerServices; using System.Security; using Xunit; using Xunit.Abstractions; @@ -92,7 +95,7 @@ public void TestThrowDisposedOperationCheck() Assert.ThrowsAsync(async () => { cli.Dispose(); - _ = (WhoAmIResponse) await cli.ExecuteAsync(new WhoAmIRequest()).ConfigureAwait(false); + _ = (WhoAmIResponse)await cli.ExecuteAsync(new WhoAmIRequest()).ConfigureAwait(false); }); } @@ -246,7 +249,7 @@ public void CreateRequestTests() System.Threading.CancellationToken tok = new System.Threading.CancellationToken(true); respId = cli.CreateAsync(acctEntity, tok).GetAwaiter().GetResult(); } - catch(Exception ex) + catch (Exception ex) { Assert.IsType(ex); } @@ -738,6 +741,34 @@ public void TestResponseHeaderBehavior() #region LiveConnectedTests + [SkippableConnectionTest] + [Trait("Category", "Live Connect Required")] + public void RetrieveSolutionImportResultAsyncTest() + { + // Import + var client = CreateServiceClient(); + if (!Utilities.FeatureVersionMinimums.IsFeatureValidForEnviroment(client._connectionSvc?.OrganizationVersion, Utilities.FeatureVersionMinimums.AllowRetrieveSolutionImportResult)) + { + // Not supported on this version of Dataverse + client._logEntry.Log($"RetrieveSolutionImportResultAsync request is calling RetrieveSolutionImportResult API. This request requires Dataverse version {Utilities.FeatureVersionMinimums.AllowRetrieveSolutionImportResult.ToString()} or above. The current Dataverse version is {client._connectionSvc?.OrganizationVersion}. This request cannot be made", TraceEventType.Warning); + return; + } + + client.ImportSolution(Path.Combine("TestData", "TestSolution_1_0_0_1.zip"), out var importId); + + // Wait a little bit because solution might not be immediately available + System.Threading.Thread.Sleep(30000); + + // Response doesn't include formatted results + var resWithoutFormatted = client.RetrieveSolutionImportResultAsync(importId); + resWithoutFormatted.Should().NotBeNull(); + + // Response include formatted results + var resWithFormatted = client.RetrieveSolutionImportResultAsync(importId, true); + resWithFormatted.Should().NotBeNull(); + resWithFormatted.FormattedResults.Should().NotBeEmpty(); + } + [SkippableConnectionTest] [Trait("Category", "Live Connect Required")] public void ConnectUsingServiceIdentity_ClientSecret_CtorV1() @@ -792,7 +823,7 @@ public void ConnectUsingServiceIdentity_ClientSecret_ConStr() client = new ServiceClient(connStr, Ilogger); Assert.True(client.IsReady, "Failed to Create Connection via Connection string"); } - catch(Exception ex) + catch (Exception ex) { Assert.Null(ex); } @@ -845,11 +876,11 @@ public void ConnectUsingServiceIdentity_ClientSecret_ExternalAuth_CtorV1() var Conn_Url = System.Environment.GetEnvironmentVariable("XUNITCONNTESTURI"); // Connection params. - var client = new ServiceClient(new Uri(Conn_Url), testSupport.GetS2SAccessTokenForRequest , true, Ilogger); + var client = new ServiceClient(new Uri(Conn_Url), testSupport.GetS2SAccessTokenForRequest, true, Ilogger); Assert.True(client.IsReady, "Failed to Create Connection via Constructor"); // Validate connection - ValidateConnection(client , usingExternalAuth:true); + ValidateConnection(client, usingExternalAuth: true); } [SkippableConnectionTest] @@ -873,7 +904,7 @@ public void ConnectUsingServiceIdentity_ClientSecret_ExternalAuth_Consetup() Assert.True(client.IsReady, "Failed to Create Connection via Constructor"); // Validate connection - ValidateConnection(client , usingExternalAuth:true); + ValidateConnection(client, usingExternalAuth: true); // test clone var clientClone = client.Clone(); @@ -993,7 +1024,7 @@ public void ConnectUsingUserIdentity_UIDPW_CtorV2() [Trait("Category", "Live Connect Required")] public void ConnectUsingUserIdentity_UIDPW_ConSetup() { - string UrlProspect = System.Environment.GetEnvironmentVariable("XUNITCONNTESTURI"); + string UrlProspect = System.Environment.GetEnvironmentVariable("XUNITCONNTESTURI"); if (UrlProspect.EndsWith("/") || UrlProspect.EndsWith("\\")) { UrlProspect = UrlProspect.Substring(0, UrlProspect.Length - 1); @@ -1042,7 +1073,7 @@ public void ConnectUsingUserIdentity_UIDPW_ConSetup() #region connectionValidationHelper - private void ValidateConnection(ServiceClient client , bool usingExternalAuth = false) + private void ValidateConnection(ServiceClient client, bool usingExternalAuth = false) { if (!usingExternalAuth) client._connectionSvc.AuthContext.Should().NotBeNull(); @@ -1197,13 +1228,14 @@ public void ImportListDelete_Solution_Test() // Import var client = CreateServiceClient(); - client.ImportSolution(Path.Combine("TestData", "TestSolution_1_0_0_1.zip"), out var importId); + client.ImportSolution(Path.Combine("TestMaterial", "TestSolution_1_0_0_1.zip"), out var importId); // List solutions and find the one that was imported - // Wait a little bit because solution might not be immediately available - System.Threading.Thread.Sleep(30000); + client.ForceServerMetadataCacheConsistency = true; var listRequest = new RetrieveOrganizationInfoRequest(); var listResponse = client.Execute(listRequest) as RetrieveOrganizationInfoResponse; + client.ForceServerMetadataCacheConsistency = false; + listResponse.Should().NotBeNull(); listResponse.organizationInfo.Should().NotBeNull(); listResponse.organizationInfo.Solutions.Should().NotBeEmpty(); @@ -1222,6 +1254,371 @@ public void ImportListDelete_Solution_Test() } } + [SkippableConnectionTest] + [Trait("Category", "Live Connect Required")] + public void ImportListDelete_Solution_TestAsync() + { + try + { + Stopwatch _HoldTime = new Stopwatch(); + Stopwatch _RunTime = new Stopwatch(); + // Import + var client = CreateServiceClient(); + var asyncTrackingId = client.ImportSolutionAsync(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_0_6_managed.zip"), out var importId); + Assert.NotEqual(asyncTrackingId, Guid.Empty); + + // Wait for Operation to complete + WaitForAsyncOperationToComplete(_HoldTime, _RunTime, client, asyncTrackingId); + + System.Threading.Thread.Sleep(10000); + // List solutions and find the one that was imported + client.ForceServerMetadataCacheConsistency = true; + var listRequest = new RetrieveOrganizationInfoRequest(); + var listResponse = client.Execute(listRequest) as RetrieveOrganizationInfoResponse; + client.ForceServerMetadataCacheConsistency = false; + listResponse.Should().NotBeNull(); + listResponse.organizationInfo.Should().NotBeNull(); + listResponse.organizationInfo.Solutions.Should().NotBeEmpty(); + + var solution = listResponse.organizationInfo.Solutions.Find(s => string.Compare(s.SolutionUniqueName, "PowerPlatformIPManagement", StringComparison.OrdinalIgnoreCase) == 0); + solution.Should().NotBeNull(); + + // Delete it + var deleteRequest = new DeleteRequest() { Target = new EntityReference("solution", solution.Id) }; + var deleteResponse = client.Execute(deleteRequest) as DeleteResponse; + deleteResponse.Should().NotBeNull(); + Ilogger.LogInformation($"Hold Time: {_HoldTime.Elapsed}. Run Time:{_RunTime.Elapsed}"); + } + finally + { + } + } + + [SkippableConnectionTest(true, "Test Does not work underload")] + [Trait("Category", "Live Connect Required")] + public void ImportAndUpgradePromote_Delete_Solution_TestAsync() + { + try + { + Stopwatch _HoldTime = new Stopwatch(); + Stopwatch _RunTime = new Stopwatch(); + + // Import + var client = CreateServiceClient(); + + Ilogger.LogInformation("SETTING UP INTIAL SOLUTION TO UPGRADE"); + var asyncTrackingId = client.ImportSolutionAsync(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_0_6_managed.zip"), out var importId); + Assert.NotEqual(asyncTrackingId, Guid.Empty); + + // Wait for Operation to complete + WaitForAsyncOperationToComplete(_HoldTime, _RunTime, client, asyncTrackingId); + + Ilogger.LogInformation("SET UP INTIAL SOLUTION TO UPGRADE COMPLETE"); + + _RunTime.Start(); + asyncTrackingId = client.ImportSolutionAsync(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_1_0_managed.zip"), out importId, importAsHoldingSolution: true); + Assert.NotEqual(asyncTrackingId, Guid.Empty); + + // Wait for Operation to complete + WaitForAsyncOperationToComplete(_HoldTime, _RunTime, client, asyncTrackingId); + + System.Threading.Thread.Sleep(10000); + + var listRequest = new RetrieveOrganizationInfoRequest(); + client.ForceServerMetadataCacheConsistency = true; + var listResponse = client.Execute(listRequest) as RetrieveOrganizationInfoResponse; + client.ForceServerMetadataCacheConsistency = false; + listResponse.Should().NotBeNull(); + listResponse.organizationInfo.Should().NotBeNull(); + listResponse.organizationInfo.Solutions.Should().NotBeEmpty(); + var solution = listResponse.organizationInfo.Solutions.Find(s => string.Compare(s.SolutionUniqueName, "PowerPlatformIPManagement", StringComparison.OrdinalIgnoreCase) == 0); + solution.Should().NotBeNull(); + + System.Threading.Thread.Sleep(10000); + _RunTime.Start(); + asyncTrackingId = client.DeleteAndPromoteSolutionAsync("PowerPlatformIPManagement"); + Assert.NotEqual(asyncTrackingId, Guid.Empty); + _RunTime.Stop(); + + // Wait for Operation to complete + WaitForAsyncOperationToComplete(_HoldTime, _RunTime, client, asyncTrackingId); + listRequest = new RetrieveOrganizationInfoRequest(); + client.ForceServerMetadataCacheConsistency = true; + listResponse = client.Execute(listRequest) as RetrieveOrganizationInfoResponse; + client.ForceServerMetadataCacheConsistency = false; + listResponse.Should().NotBeNull(); + listResponse.organizationInfo.Should().NotBeNull(); + listResponse.organizationInfo.Solutions.Should().NotBeEmpty(); + solution = listResponse.organizationInfo.Solutions.Find(s => string.Compare(s.SolutionUniqueName, "PowerPlatformIPManagement", StringComparison.OrdinalIgnoreCase) == 0); + solution.Should().NotBeNull(); + + System.Threading.Thread.Sleep(10000); + // Delete it - CLEAN IT UP + var deleteRequest = new DeleteRequest() { Target = new EntityReference("solution", solution.Id) }; + var deleteResponse = client.Execute(deleteRequest) as DeleteResponse; + deleteResponse.Should().NotBeNull(); + + Ilogger.LogInformation($"Hold Time: {_HoldTime.Elapsed}. Run Time:{_RunTime.Elapsed}"); + } + finally + { + } + } + + [SkippableConnectionTest(true, "Test Does not work underload")] + [Trait("Category", "Live Connect Required")] + public void ImportAndStageAndUpgrade_Solution_TestAsync() + { + try + { + Stopwatch _HoldTime = new Stopwatch(); + Stopwatch _RunTime = new Stopwatch(); + // Import + var client = CreateServiceClient(); + + var asyncTrackingId = client.ImportSolutionAsync(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_0_6_managed.zip"), out var importId); + Assert.NotEqual(asyncTrackingId, Guid.Empty); + + // Wait for Operation to complete + WaitForAsyncOperationToComplete(_HoldTime, _RunTime, client, asyncTrackingId); + // List solutions and find the one that was imported + System.Threading.Thread.Sleep(10000); + + _RunTime.Start(); + client.ForceServerMetadataCacheConsistency = true; + Dictionary solutionPrams = new Dictionary(); + solutionPrams.Add(ImportSolutionProperties.USESTAGEANDUPGRADEMODE, true); + asyncTrackingId = client.ImportSolutionAsync(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_1_0_managed.zip"), out importId, extraParameters: solutionPrams); + client.ForceServerMetadataCacheConsistency = false; + + Assert.NotEqual(asyncTrackingId, Guid.Empty); + + // Wait for Operation to complete + WaitForAsyncOperationToComplete(_HoldTime, _RunTime, client, asyncTrackingId); + _RunTime.Stop(); + + System.Threading.Thread.Sleep(10000); + DeleteSolutionFromSystem(client, "PowerPlatformIPManagement"); + + Ilogger.LogInformation($"Hold Time: {_HoldTime.Elapsed}. Run Time:{_RunTime.Elapsed}"); + } + finally + { + } + } + + [SkippableConnectionTest(true, "Test Does not work underload")] + [Trait("Category", "Live Connect Required")] + public void ImportAndStageAndUpgrade_Solution_TestSync() + { + try + { + Stopwatch _RunTime = new Stopwatch(); + // Import + var client = CreateServiceClient(); + + var ImportJobId = client.ImportSolution(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_0_6_managed.zip"), out var importId); + + _RunTime.Start(); + // List solutions and find the one that was imported + client.ForceServerMetadataCacheConsistency = true; + Dictionary solutionPrams = new Dictionary(); + solutionPrams.Add(ImportSolutionProperties.USESTAGEANDUPGRADEMODE, true); + ImportJobId = client.ImportSolution(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_1_0_managed.zip"), out importId, extraParameters: solutionPrams); + client.ForceServerMetadataCacheConsistency = false; + _RunTime.Stop(); + + DeleteSolutionFromSystem(client, "PowerPlatformIPManagement"); + + Assert.NotEqual(ImportJobId, Guid.Empty); + + Ilogger.LogInformation($"Run Time:{_RunTime.Elapsed}"); + } + finally + { + } + } + + [SkippableConnectionTest] + [Trait("Category", "Live Connect Required")] + public void StageSolution_File_TestSync() + { + Stopwatch _RunTime = new Stopwatch(); + // Import + var client = CreateServiceClient(); + var StageSolutionResults = client.StageSolution(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_0_6_managed.zip")).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.NotNull(StageSolutionResults); + Assert.NotEqual(StageSolutionResults.StageSolutionUploadId, Guid.Empty); + + // Clean up stages import + client.Delete("stagesolutionupload", StageSolutionResults.StageSolutionUploadId); + } + + //[SkippableConnectionTest] + [Fact(Skip = "Broken API")] + // [Trait("Category", "Live Connect Required")] + public void StageSolution_File_Import_TestSync() + { + Stopwatch _RunTime = new Stopwatch(); + // Import + var client = CreateServiceClient(); + + // clean up existing solution if present + DeleteSolutionFromSystem(client, "PowerPlatformIPManagement"); + + var StageSolutionResults = client.StageSolution(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_0_6_managed.zip")).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.NotNull(StageSolutionResults); + if (!ValidateSolutionStageResults(StageSolutionResults)) + { + throw new OperationCanceledException("StageSolution Failed due to Validation Error"); + } + Assert.NotEqual(StageSolutionResults.StageSolutionUploadId, Guid.Empty); + + // Import Staged Solution Sync. + Guid returnedImportId = client.ImportSolution(StageSolutionResults.StageSolutionUploadId, out Guid guImportId); + Assert.Null(client.LastException); + Assert.NotEqual(returnedImportId, Guid.Empty); + + // Clean up Solution: + DeleteSolutionFromSystem(client, "PowerPlatformIPManagement"); + } + + [SkippableConnectionTest] + [Trait("Category", "Live Connect Required")] + public void StageSolution_File_Import_TestAsync() + { + try + { + Stopwatch _HoldTime = new Stopwatch(); + Stopwatch _RunTime = new Stopwatch(); + // Import + var client = CreateServiceClient(); + + // clean up existing solution if present + DeleteSolutionFromSystem(client, "PowerPlatformIPManagement"); + + _RunTime.Start(); + var StageSolutionResults = client.StageSolution(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_0_6_managed.zip")).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.NotNull(StageSolutionResults); + if (!ValidateSolutionStageResults(StageSolutionResults)) + { + throw new OperationCanceledException("StageSolution Failed due to Validation Error"); + } + Assert.NotEqual(StageSolutionResults.StageSolutionUploadId, Guid.Empty); + + var asyncTrackingId = client.ImportSolutionAsync(StageSolutionResults.StageSolutionUploadId, out var importId); + Assert.Null(client.LastException); + Assert.NotEqual(asyncTrackingId, Guid.Empty); + + // Wait for Operation to complete + WaitForAsyncOperationToComplete(_HoldTime, _RunTime, client, asyncTrackingId); + + // Clean up Solution: + DeleteSolutionFromSystem(client, "PowerPlatformIPManagement"); + Ilogger.LogInformation($"Hold Time: {_HoldTime.Elapsed}. Run Time:{_RunTime.Elapsed}"); + } + finally + { + } + } + + [SkippableConnectionTest(true, "Test Does not work underload")] + [Trait("Category", "Live Connect Required")] + public void StageSolution_File_StageAndUpgrade_TestAsync() + { + try + { + Stopwatch _HoldTime = new Stopwatch(); + Stopwatch _RunTime = new Stopwatch(); + // Import + var client = CreateServiceClient(); + + // clean up existing solution if present + var guTrackingId = client.ImportSolution(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_0_6_managed.zip"), out var importId); + + + _RunTime.Start(); + var StageSolutionResults = client.StageSolution(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_1_0_managed.zip")).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.NotNull(StageSolutionResults); + if (!ValidateSolutionStageResults(StageSolutionResults)) + { + throw new OperationCanceledException("StageSolution Failed due to Validation Error"); + } + Assert.NotEqual(StageSolutionResults.StageSolutionUploadId, Guid.Empty); + + + Dictionary solutionPrams = new Dictionary(); + solutionPrams.Add(ImportSolutionProperties.USESTAGEANDUPGRADEMODE, true); + var asyncTrackingId = client.ImportSolutionAsync(StageSolutionResults.StageSolutionUploadId, out importId, extraParameters: solutionPrams); + Assert.Null(client.LastException); + Assert.NotEqual(asyncTrackingId, Guid.Empty); + + // Wait for Operation to complete + WaitForAsyncOperationToComplete(_HoldTime, _RunTime, client, asyncTrackingId); + + // Clean up Solution: + DeleteSolutionFromSystem(client, "PowerPlatformIPManagement"); + Ilogger.LogInformation($"Hold Time: {_HoldTime.Elapsed}. Run Time:{_RunTime.Elapsed}"); + } + finally + { + } + } + + [SkippableConnectionTest(true, "Test Does not work underload")] + [Trait("Category", "Live Connect Required")] + public void StageSolution_File_DeleteAndPromote_TestAsync() + { + try + { + Stopwatch _HoldTime = new Stopwatch(); + Stopwatch _RunTime = new Stopwatch(); + // Import + var client = CreateServiceClient(); + + client.ForceServerMetadataCacheConsistency = true; + // clean up existing solution if present + var guTrackingId = client.ImportSolution(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_0_6_managed.zip"), out var importId); + System.Threading.Thread.Sleep(10000); + + _RunTime.Start(); + var StageSolutionResults = client.StageSolution(Path.Combine("TestMaterial", "PowerPlatformIPManagement_1_0_1_0_managed.zip")).ConfigureAwait(false).GetAwaiter().GetResult(); + Assert.NotNull(StageSolutionResults); + if (!ValidateSolutionStageResults(StageSolutionResults)) + { + throw new OperationCanceledException("StageSolution Failed due to Validation Error"); + } + Assert.NotEqual(StageSolutionResults.StageSolutionUploadId, Guid.Empty); + System.Threading.Thread.Sleep(10000); + + + Dictionary solutionPrams = new Dictionary(); + //solutionPrams.Add(ImportSolutionProperties.USESTAGEANDUPGRADEMODE, true); // Cannot use stage Mode + staged solutionid for delete and promote. + var asyncTrackingId = client.ImportSolutionAsync(StageSolutionResults.StageSolutionUploadId, out importId, importAsHoldingSolution: true, extraParameters: solutionPrams); + Assert.Null(client.LastException); + Assert.NotEqual(asyncTrackingId, Guid.Empty); + + // Wait for Operation to complete + WaitForAsyncOperationToComplete(_HoldTime, _RunTime, client, asyncTrackingId); + + System.Threading.Thread.Sleep(10000); + + _RunTime.Start(); + asyncTrackingId = client.DeleteAndPromoteSolutionAsync("PowerPlatformIPManagement"); + Assert.NotEqual(asyncTrackingId, Guid.Empty); + // Wait for Operation to complete + WaitForAsyncOperationToComplete(_HoldTime, _RunTime, client, asyncTrackingId); + + System.Threading.Thread.Sleep(10000); + // Clean up Solution: + DeleteSolutionFromSystem(client, "PowerPlatformIPManagement"); + Ilogger.LogInformation($"Hold Time: {_HoldTime.Elapsed}. Run Time:{_RunTime.Elapsed}"); + } + finally + { + } + } + // Not yet implemented //[SkippableConnectionTest] //[Trait("Category", "Live Connect Required")] @@ -1259,6 +1656,99 @@ private ServiceClient CreateServiceClient() return client; } + + private void WaitForAsyncOperationToComplete(Stopwatch _HoldTime, Stopwatch _RunTime, ServiceClient client, Guid? asyncTrackingId) + { + if (asyncTrackingId != null && asyncTrackingId != Guid.Empty) + { + // poll for status + var resp = client.GetAsyncOperationStatus(asyncTrackingId.Value).GetAwaiter().GetResult(); + if (resp != null && resp.State != AsyncStatusResponse.AsyncStatusResponse_statecode.FailedParse) + { + while (resp.State != AsyncStatusResponse.AsyncStatusResponse_statecode.Completed) + { + // Wait a little bit because solution might not be immediately available + Ilogger.LogInformation($"Operation progress: {resp.State} - {resp.StatusCode_Localized}"); + if (resp.State != AsyncStatusResponse.AsyncStatusResponse_statecode.Suspended && resp.State != AsyncStatusResponse.AsyncStatusResponse_statecode.Ready) + { + _HoldTime.Stop(); + _RunTime.Start(); + } + else + { + _HoldTime.Start(); + _RunTime.Stop(); + } + System.Threading.Thread.Sleep(5000); + resp = client.GetAsyncOperationStatus(asyncTrackingId.Value).GetAwaiter().GetResult(); + } + } + Ilogger.LogInformation($"Operation Completed: {resp.State} - {resp.StatusCode_Localized}"); + _HoldTime.Stop(); + _RunTime.Stop(); + + if (resp.StatusCode == AsyncStatusResponse.AsyncStatusResponse_statuscode.Failed) + { + Ilogger.LogError(resp.Message); + throw new OperationCanceledException("Operation failed"); + } + + } + } + + private void DeleteSolutionFromSystem(ServiceClient client, string solutionUnqiueName) + { + client.ForceServerMetadataCacheConsistency = true; + var listRequest = new RetrieveOrganizationInfoRequest(); + var listResponse = client.Execute(listRequest) as RetrieveOrganizationInfoResponse; + listResponse.Should().NotBeNull(); + listResponse.organizationInfo.Should().NotBeNull(); + listResponse.organizationInfo.Solutions.Should().NotBeEmpty(); + + var solution = listResponse.organizationInfo.Solutions.Find(s => string.Compare(s.SolutionUniqueName, solutionUnqiueName, StringComparison.OrdinalIgnoreCase) == 0); + if (solution != null) + { + // Delete it + var deleteRequest = new DeleteRequest() { Target = new EntityReference("solution", solution.Id) }; + var deleteResponse = client.Execute(deleteRequest) as DeleteResponse; + deleteResponse.Should().NotBeNull(); + } + client.ForceServerMetadataCacheConsistency = false; + } + + + private bool ValidateSolutionStageResults(StageSolutionResults StageSolutionResults) + { + if (StageSolutionResults.SolutionValidationResults != null + && StageSolutionResults.SolutionValidationResults.Any()) + { + // Found validation errors. Emit them and throw. + foreach (var itm in StageSolutionResults.SolutionValidationResults) + { + string msg = $"ErrorCode={itm.ErrorCode} | Msg={itm.Message} | AddInfo={itm.AdditionalInfo}"; + + switch (itm.SolutionValidationResultType) + { + case SolutionValidationResultType.Info: + Ilogger.LogInformation(msg); + break; + case SolutionValidationResultType.Warning: + Ilogger.LogWarning(msg); + break; + case SolutionValidationResultType.Error: + Ilogger.LogError(msg); + break; + default: + Ilogger.LogInformation(msg); + break; + } + } + if (StageSolutionResults.SolutionValidationResults.Where(w => w.SolutionValidationResultType.Equals(SolutionValidationResultType.Error)).Any()) + return false; + } + return true; + } + #endregion } diff --git a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/SkippableConnectionTestAttribute.cs b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/SkippableConnectionTestAttribute.cs index 0ec9a32..a23798a 100644 --- a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/SkippableConnectionTestAttribute.cs +++ b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/SkippableConnectionTestAttribute.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.Crm.Sdk.Messages; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -18,5 +19,10 @@ public SkippableConnectionTestAttribute() Skip = "Ignored test as connection info is not present"; } } + + public SkippableConnectionTestAttribute(bool skip , string skipMessage) + { + Skip = skipMessage; + } } } diff --git a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/TestMaterial/PowerPlatformIPManagement_1_0_0_6_managed.zip b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/TestMaterial/PowerPlatformIPManagement_1_0_0_6_managed.zip new file mode 100644 index 0000000000000000000000000000000000000000..4d95c60510bc27930a8f227248cf442c0550a104 GIT binary patch literal 10945 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 q`j;d9ACl^SI{0^n`mYXVXrca literal 0 HcmV?d00001 diff --git a/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/TestMaterial/PowerPlatformIPManagement_1_0_1_0_managed.zip b/src/GeneralTools/DataverseClient/UnitTests/CdsClient_Core_Tests/TestMaterial/PowerPlatformIPManagement_1_0_1_0_managed.zip new file mode 100644 index 0000000000000000000000000000000000000000..990cf4a24ecb062ede229d05a7d0cf57aa8ffe1d GIT binary patch literal 11328 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 literal 0 HcmV?d00001 diff --git a/src/GeneralTools/DataverseClient/UnitTests/LivePackageRunUnitTests/LivePackageRunUnitTests.csproj b/src/GeneralTools/DataverseClient/UnitTests/LivePackageRunUnitTests/LivePackageRunUnitTests.csproj index d85efa8..e1d811c 100644 --- a/src/GeneralTools/DataverseClient/UnitTests/LivePackageRunUnitTests/LivePackageRunUnitTests.csproj +++ b/src/GeneralTools/DataverseClient/UnitTests/LivePackageRunUnitTests/LivePackageRunUnitTests.csproj @@ -4,7 +4,7 @@ true false - net462;net472;net48;netcoreapp3.1;net6.0 + net462;net472;net48;net6.0 true DataverseClient-Tests-Package false diff --git a/src/GeneralTools/DataverseClient/UnitTests/LivePackageTestsConsole/LivePackageTestsConsole.csproj b/src/GeneralTools/DataverseClient/UnitTests/LivePackageTestsConsole/LivePackageTestsConsole.csproj index 6de87de..dbf5dc7 100644 --- a/src/GeneralTools/DataverseClient/UnitTests/LivePackageTestsConsole/LivePackageTestsConsole.csproj +++ b/src/GeneralTools/DataverseClient/UnitTests/LivePackageTestsConsole/LivePackageTestsConsole.csproj @@ -2,7 +2,7 @@ Exe - net462;netcoreapp3.1 + net462;net6.0 true DataverseClient-Tests-Package false diff --git a/src/GeneralTools/DataverseClient/WebResourceUtility/TraceLogger.cs b/src/GeneralTools/DataverseClient/WebResourceUtility/TraceLogger.cs index a1165a6..b65ee84 100644 --- a/src/GeneralTools/DataverseClient/WebResourceUtility/TraceLogger.cs +++ b/src/GeneralTools/DataverseClient/WebResourceUtility/TraceLogger.cs @@ -1,4 +1,4 @@ -//=================================================================================== +//=================================================================================== // Microsoft – subject to the terms of the Microsoft EULA and other agreements // Microsoft.PowerPlatform.Dataverse.WebResourceUtility // copyright 2003-2012 Microsoft Corp. @@ -48,9 +48,10 @@ public TraceLogger(string traceSourceName = "") /// public override void ResetLastError() { - LastError.Remove(0, LastError.Length); - LastException = null; - } + if (base.LastError.Length > 0) + base.LastError = base.LastError.Remove(0, LastError.Length - 1); + LastException = null; + } /// /// Log a Message diff --git a/src/SDK-IntelliSense/V9/PublishedXML/CE/Microsoft.PowerPlatform.Dataverse.Client.xml b/src/SDK-IntelliSense/V9/PublishedXML/CE/Microsoft.PowerPlatform.Dataverse.Client.xml new file mode 100644 index 0000000..696ed2a --- /dev/null +++ b/src/SDK-IntelliSense/V9/PublishedXML/CE/Microsoft.PowerPlatform.Dataverse.Client.xml @@ -0,0 +1,2728 @@ + + + + Microsoft.PowerPlatform.Dataverse.Client + + + + + Summary description for AttributeData + + + + + + + + + + + Details of expected authentication. + + + Initializes a new instance of the class. + + + + Authority to initiate OAuth flow with. + + + + OAuth resource to request authentication for. + + + + True if probing returned a WWW-Authenticate header. + + + + Probes API endpoint to elicit a 401 response with the WWW-Authenticate header and processes the found information + + + + instantiate resolver, using specified HttpClient to be used. + + + + + + Attemtps to solicit a WWW-Authenticate reply using an unauthenticated GET call to the given endpoint. + Parses returned header for details + endpoint to challenge for authority and resource + if true, this is an OnPremsies server + + + + + + ORGINAL CODE FROM https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/blob/68d7dea3643e075be85abbca1ab88ba614465541/src/Microsoft.IdentityModel.Clients.ActiveDirectory/Features/NonWinCommon/PromptBehavior.cs + PORTED TO THIS LIB TO ACT AS A BRIDGE BETWEEN ADAL.NET AND MSAL. + Indicates whether AcquireToken should automatically prompt only if necessary or whether + it should prompt regardless of whether there is a cached token. + + + + The user will be prompted for credentials even if there is a token that meets the requirements + already in the cache. + + + + Acquire token will prompt the user for credentials only when necessary. If a token + that meets the requirements is already cached then the user will not be prompted. + + + + Never Prompt + + + + Re-authorizes (through displaying webview) the resource usage, making sure that the resulting access + token contains updated claims. If user logon cookies are available, the user will not be asked for + credentials again and the logon dialog will dismiss automatically. + + + + Prompt the user to select a user account even if there is a token that meets the requirements + already in the cache. This enables an user who has multiple accounts at the Authorization Server to select amongst + the multiple accounts that they might have current sessions for. + + + + Decision switch for the sort of Auth to login to Dataverse with + + + + Active Directory Auth + + + + Certificate based Auth + + + + Client Id + Secret Auth type. + + + + Enabled Host to manage Auth token for Dataverse connections. + + + + Invalid connection + + + + OAuth based Auth + + + + Request object. + + + + + Organization Service request for the batch + + + + Request debug Message. + + + + Reference Correlation ID + + + + Status of the batch. + + + + Batch has completed. + + + + Batch is currently executing + + + + Batch is waiting to be run + + + + + + + Contains a variable definition. + + + + Create a new Data Type + Default Constructor + + + + Create a new Data Type + Data to Set + Type of Data to Set + + + + Create a new Data Type + Data to Set + Type of Data to Set + Name of the related entity, applies to the Field Types: Customer and Lookup + + + + Name of the entity that a Lookup or Related Customer Entity + + + + Value Type. + + + + Value to set + + + + Allowed typelist of entities + + + + Bool - Converts from bool + + + + Ref Type for Dataverse, Creates an EntityReference + You need to provide a Guid as a value, and a the name of an entity for the lookup key + + + + DateTime - Converts from a DataTime Object + + + + Decimal, for money, use Money - Converts from a decimal type + + + + The File column is used for storing binary data + + + + Double type, while Dataverse calls this a float, it is actually a double for money, use Money - Converts from a double type + + + + Use image columns to display a single image per row in the application + + + + Primary Key - Converts from a Guid Type + + + + Ref Type for Dataverse, Creates an EntityReference + You need to provide a Guid as a value, and a the name of an entity for the lookup key + + + + Money Type - Converts from a decimal type + + + + Whole number - Converts from a Int type + + + + Pick List value - Converts from a Int type + + + + User Specified type... will be appended directly. This type must be one of the valid Dataverse types + + + + String type - Converts from a string type. + + + + Guid Type - Converts from a Guid Type + + + + TBD + + + + + + + + Extension to the FileLogTraceListner class. + + + + The class constructor. + + + + The class constructor. + Source Path + + + + Format the message with additional information + Message to be logged + Formatted message + + + + Adding MaxFileCount to the suported attribute list + + + + + Checks if the CustomLocation Path has write permission + Custom Location Path + boolean + + + + Trace event is Overriden to check the Log file Access. + TraceEventCache + Source + TraceEventType + Id + message string + + + + Trace event is Overriden to check the Log file Access. + TraceEventCache + Source + TraceEventType + Id + Format Options + Array of message objects + + + + Trace event. Allows overriden behavior in inheriting classes + + + + + + + + + Number of files to keep while rolling. + + + + Dataverse Service Client extensions for batch operations. + + + + Create a Batch Request for executing batch operations. This returns an ID that will be used to identify a request as a batch request vs a "normal" request. + Name of the Batch + Should Results be returned + Should the process continue on an error. + ServiceClient + + + + + Begins running the Batch command. + ServiceClient + ID of the batch to run + true if the batch begins, false if not. + + + + Returns a request batch by BatchID + ServiceClient + ID of the batch + + + + + Returns the batch id for a given batch name. + Name of Batch + ServiceClient + + + + + Returns the organization request at a give position + ID of the batch + Position + ServiceClient + + + + + Release a batch from the stack + Once you have completed using a batch, you must release it from the system. + ServiceClient + ID of the batch + + + + Executes the batch command and then parses the retrieved items into a list. + If there exists a exception then the LastException would be filled with the first item that has the exception. + ServiceClient + ID of the batch to run + results which is a list of responses(type >> ]]>) in the order of each request or null or complete failure + + + + Extensions to support more generic record interaction mechanic's + + + + Closes the Activity type specified. + The Activity Entity type supports fax , letter , and phonecall + *Note: This will default to using English names for Status. if you need to use Non-English, you should populate the names for completed for the status and state. + Type of Activity you would like to close.. Supports fax, letter, phonecall + ID of the Activity you want to close + State Code configured on the activity + Status code on the activity + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + true if success false if not. + + + + This creates a annotation [note] entry, related to a an existing entity + Required Properties in the fieldListnotetext (string) = Text of the note,subject (string) = this is the title of the note + Target Entity TypeID + Target Entity ID + Fields to populate + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + + + + + Associates one Entity to another where an M2M Relationship Exists. + Entity on one side of the relationship + The Id of the record on the first side of the relationship + Entity on the second side of the relationship + The Id of the record on the second side of the relationship + Relationship name between the 2 entities + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + true on success, false on fail + + + + Associates multiple entities of the same time to a single entity + Entity that things will be related too. + ID of entity that things will be related too + Entity that you are relating from + ID's of the entities you are relating from + Name of the relationship between the target and the source entities. + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Optional: if set to true, indicates that this is a N:N using a reflexive relationship + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + true on success, false on fail + + + + Creates a new activity against the target entity type + Type of Activity you would like to create + Entity type of the Entity you want to associate with. + Subject Line of the Activity + Description Text of the Activity + ID of the Entity to associate the Activity too + User ID that Created the Activity *Calling user must have necessary permissions to assign to another user + Additional fields to add as part of the activity creation + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + Guid of Activity ID or Guid.empty + + + + Uses the dynamic entity patter to create a new entity + Name of Entity To create + Initial Values + Optional: Applies the update with a solution by Unique name + Optional: if true, enabled Dataverse onboard duplicate detection + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + Guid on Success, Guid.Empty on fail + + + + Deletes an entity from the Dataverse + entity type name + entity id + Optional : Batch ID to attach this request too. + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + true on success, false on failure + + + + Removes the Association between 2 entity items where an M2M Relationship Exists. + Entity on one side of the relationship + The Id of the record on the first side of the relationship + Entity on the second side of the relationship + The Id of the record on the second side of the relationship + Relationship name between the 2 entities + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + true on success, false on fail + + + + Generic update entity + String version of the entity name + Key fieldname of the entity + Guid ID of the entity to update + Fields to update + Optional: Applies the update with a solution by Unique name + Optional: if true, enabled Dataverse onboard duplicate detection + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + true on success, false on fail + + + + Updates the State and Status of the Entity passed in. + Name of the entity + Guid ID of the entity you are updating + Int version of the new state + Int Version of the new status + Optional : Batch ID to attach this request too. + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + true on success. + + + + Updates the State and Status of the Entity passed in. + Name of the entity + Guid ID of the entity you are updating + String version of the new state + String Version of the new status + Optional : Batch ID to attach this request too. + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + true on success. + + + + Dataverse Filter item. + + + + + Dataverse Field name to Filter on + + + + Dataverse Operator to apply + + + + Value to use for the Filter + + + + Dataverse Filter class. + + + + Creates an empty Dataverse Search Filter. + + + + Dataverse Filter Operator + + + + List of Dataverse Filter conditions + + + + Extensions to support deploying solutions and data to Dataverse. + + + + Executes a Delete and Propmote Request against Dataverse using the Async Pattern. + Unique Name of solution to be upgraded + ServiceClient + Returns the Async Job ID. To find the status of the job, query the AsyncOperation Entity using GetEntityDataByID using the returned value of this method + + + + Used to upload a data map to the Dataverse + XML of the datamap in string form + True to have Dataverse replace ID's on inbound data, False to have inbound data retain its ID's + if true, dataMapXml is expected to be a File name and path to load. + ServiceClient + Returns ID of the datamap or Guid.Empty + + + + + Imports a Dataverse solution to the Dataverse Server currently connected. + Note: this is a blocking call and will take time to Import to Dataverse + + Path to the Solution File + Activate Plugin's and workflows on the Solution + + This will populate with the Import ID even if the request failed. + You can use this ID to request status on the import via a request to the ImportJob entity. + + Forces an overwrite of unmanaged customizations of the managed solution you are installing, defaults to false + Skips dependency against dependencies flagged as product update, defaults to false + Applies only on Dataverse organizations version 7.2 or higher. This imports the Dataverse solution as a holding solution utilizing the “As Holding” capability of ImportSolution + Internal Microsoft use only + ServiceClient + Extra parameters + + + + Import Solution Async used Execute Async pattern to run a solution import. + Path to the Solution File + Activate Plugin's and workflows on the Solution + + This will populate with the Import ID even if the request failed. + You can use this ID to request status on the import via a request to the ImportJob entity. + + Forces an overwrite of unmanaged customizations of the managed solution you are installing, defaults to false + Skips dependency against dependencies flagged as product update, defaults to false + Applies only on Dataverse organizations version 7.2 or higher. This imports the Dataverse solution as a holding solution utilizing the “As Holding” capability of ImportSolution + Internal Microsoft use only + Extra parameters + ServiceClient + Returns the Async Job ID. To find the status of the job, query the AsyncOperation Entity using GetEntityDataByID using the returned value of this method + + + + + Request Dataverse to install sample data shipped with Dataverse. Note this is process will take a few moments to execute. + This method will return once the request has been submitted. + + ServiceClient + ID of the Async job executing the request + + + + Determines if the Dataverse sample data has been installed + ServiceClient + True if the sample data is installed, False if not. + + + + Starts an Import request for CDS. + Supports a single file per Import request. + Delays the import jobs till specified time - Use DateTime.MinValue to Run immediately + Import Data Request + ServiceClient + Guid of the Import Request, or Guid.Empty. If Guid.Empty then request failed. + + + + + Request Dataverse to remove sample data shipped with Dataverse. Note this is process will take a few moments to execute. + This method will return once the request has been submitted. + + ServiceClient + ID of the Async job executing the request + + + + Describes an Individual Import Item. + + + + + This is the delimiter for the Data, + + + + This enabled duplicate detection rules + + + + this is the field separator + + + + This is the CSV file you wish to import, + + + + File Name of Individual file + + + + Type of Import file.. XML or CSV + + + + Is the first row of the CSV the RowHeader? + + + + Set true if the Record Owner is a Team + + + + UserID or Team ID of the Record Owner ( from systemuser ) + + + + Name of the entity that Originated the data. + + + + Name of the entity that Target Entity the data. + + + + Key used to delimit data in the import file + + + + Specifies " + + + + Specifies no delimiter + + + + Specifies ' + + + + Key used to delimit fields in the import file + + + + Specifies : + + + + Specifies , + + + + Specifies ' + + + + Type if file described in the FileContentToImport + + + + CSV File Type + + + + XML File type + + + + Describes an import request for Dataverse + + + + Default constructor + + + + ID of the DataMap to use + + + + Name of the DataMap File to use + ID or Name is required + + + + List of files to import in this job, there must be at least one. + + + + Name of the Import Request. this Name will appear in Dataverse + + + + Sets or gets the Import Mode. + + + + if True, infers the map from the type of entity requested.. + + + + Mode of the Import, Update or Create + + + + Create a new Import + + + + Update to Imported Items + + + + ImportStatus Reasons + + + Import has Completed + + + Import has Failed + + + Import is in Progress + + + Not Yet Imported + + + + Used with GetFormIdsForEntity Call + + + + Appointment book, for service requests. + + + + Dashboard form + + + + Main or default form + + + + Mobile default form + + + + User defined forms + + + + General Extensions for the Dataverse ServiceClient + + + + This will route a Entity to a public queue, + ID of the Entity to route + Name of the Entity that the Id describes + Name of the Queue to Route Too + ID of the user id to set as the working system user + if true Set the worked by when doing the assign + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + true on success + + + + Assign an Entity to the specified user ID + User ID to assign too + Target entity Name + Target entity id + Batch ID of to use, Optional + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + + + + + Executes a named workflow on an object. + name of the workflow to run + ID to exec against + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + Async Op ID of the WF or Guid.Empty + + + + Returns the user ID of the currently logged in user. + ServiceClient + + + + + this will send an Email to the + ID of the Email activity + Tracking Token or Null + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + + + + + Logical Search Pram to apply to over all search. + + + + And Search + + + + Do not apply the Search Operator + + + + Or Search + + + + Logical Search Pram to apply to over all search. + + + + Sort in Ascending + + + + Sort in Descending + + + + Extensions for interacting with the Dataverse Metadata system. + + + + Adds an option to a pick list on an entity. + Entity Name to Target + Attribute Name on the Entity + List of Localized Labels + integer Value + Publishes the Update to the Live system.. note this is a time consuming process.. if you are doing a batch up updates, call PublishEntity Separately when you are finished. + ServiceClient + true on success, on fail check last error. + + + + Returns all attributes on a entity + returns all attributes on a entity + ServiceClient + + + + + Returns a list of entities with basic data from Dataverse + defaults to true, will only return published information + EntityFilter to apply to this request, note that filters other then Default will consume more time. + ServiceClient + + + + + Gets metadata for a specific entity's attribute. + Name of the entity + Attribute Name + ServiceClient + + + + + Gets an Entity Name by Logical name or Type code. + logical name of the entity + Type code for the entity + ServiceClient + Localized name for the entity in the current users language + + + + Gets an Entity Name by Logical name or Type code. + logical name of the entity + Type code for the entity + ServiceClient + Localized plural name for the entity in the current users language + + + + Returns the Form Entity References for a given form type. + logical name of the entity you are querying for form data. + Form Type you want + ServiceClient + List of Entity References for the form type requested. + + + + Returns the Metadata for an entity from Dataverse, defaults to basic data only. + Logical name of the entity + filter to apply to the query, defaults to default entity data. + ServiceClient + + + + + Returns the Entity name for the given Type code + + ServiceClient + + + + + Gets the typecode of an entity by name. + name of the entity to get the type code on + ServiceClient + + + + + Gets a global option set from Dataverse. + Name of the Option Set To get + ServiceClient + OptionSetMetadata or null + + + + Gets a PickList, Status List or StateList from the metadata of an attribute + text name of the entity to query + name of the attribute to query + ServiceClient + + + + + Publishes an entity to the production system, + used in conjunction with the Metadata services. + Name of the entity to publish + ServiceClient + True on success + + + + This will clear the Metadata cache for either all entities or the specified entity + ServiceClient + Optional: name of the entity to clear cached info for + + + + PickList Item + + + + Default Constructor + + + + Constructor with data. + + + + + + Display label for the PickList Item + + + + ID of the picklist item + + + + PickList data + + + + Default Constructor + + + + Constructs a PickList item with data. + + + + + + + Current value of the PickList Item + + + + Displayed value for the PickList + + + + Array of Potential Pick List Items. + + + + Displayed Label + + + + Extentions to support query builder and untyped object returns. + + + + Returns all Activities Related to a given Entity ID. + Only Account, Contact and Opportunity entities are supported. + Type of Entity to search against + ID of the entity to search against. + List of Field to return for the entity , null indicates all fields. + Search Operator to use + Filters responses based on search prams. + Sort order + Number of Pages + Current Page number + inbound place holder cookie + outbound place holder cookie + is there more records or not + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + ServiceClient + Array of Activities + + + + Returns all Activities Related to a given Entity ID. + Only Account, Contact and Opportunity entities are supported. + Type of Entity to search against + ID of the entity to search against. + List of Field to return for the entity , null indicates all fields. + Search Operator to use + Filters responses based on search prams. + Sort order + Number of Pages + Current Page number + inbound place holder cookie + outbound place holder cookie + is there more records or not + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + ServiceClient + Array of Activities + + + + This function gets data from a Dictionary object, where "string" identifies the field name, and Object contains the data, + this method then attempts to cast the result to the Type requested, if it cannot be cast an empty object is returned. + Results from the query + key name you want + ServiceClient + Type if object to return + object + + + + Searches for data based on a FetchXML query + Fetch XML query data. + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + results or null + + + + Searches for data based on a FetchXML query + Fetch XML query data. + Number records per Page + Current Page number + inbound place holder cookie + outbound place holder cookie + is there more records or not + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + ServiceClient + results or null + + + + Searches for data based on a FetchXML query + Fetch XML query data. + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + results as an entity collection or null + + + + Searches for data based on a FetchXML query + Fetch XML query data. + Number records per Page + Current Page number + inbound place holder cookie + outbound place holder cookie + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + is there more records or not + ServiceClient + results as an Entity Collection or null + + + + Gets a List of variables from the account based on the list of field specified in the Fields List + The entity to be searched. + ID of Entity to query + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + Populated Array of Key value pairs with the Results of the Search + ServiceClient + + + + + Queries an Object via a M to M Link + Name of the entity you want return data from + Search Prams for the Return Entity + Name of the entity you are linking too + Search Prams for the Entity you are linking too + Key field on the Entity you are linking too + Dataverse Name of the Relationship + Key field on the Entity you want to return data from + Search Operator to apply + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + List of Fields from the Returned Entity you want + ServiceClient + + + + + Queries an Object via a M to M Link + Name of the entity you want return data from + Search Prams for the Return Entity + Name of the entity you are linking too + Search Prams for the Entity you are linking too + Key field on the Entity you are linking too + Dataverse Name of the Relationship + Key field on the Entity you want to return data from + Search Operator to apply + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + List of Fields from the Returned Entity you want + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + If the relationship is defined as Entity:Entity or Account N:N Account, this parameter should be set to true + ServiceClient + + + + + Returns all Activities Related to a given Entity ID. + Only Account, Contact and Opportunity entities are supported. + Type of Entity to search against + ID of the entity to search against. + List of Field to return for the entity , null indicates all fields. + + Filters responses based on search prams. + Sort Order + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Entity to Rollup from + ServiceClient + Array of Activities + + + + Returns all Activities Related to a given Entity ID. + Only Account, Contact and Opportunity entities are supported. + Type of Entity to search against + ID of the entity to search against. + List of Field to return for the entity , null indicates all fields. + Entity to Rollup from + Search Operator to user + Dataverse Filter list to apply + Sort by + Number of Pages + Current Page number + inbound place holder cookie + outbound place holder cookie + is there more records or not + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + + + + + Gets a list of accounts based on the search parameters. + Dataverse Entity Type Name to search + Array of Search Parameters + List of fields to retrieve, Null indicates all Fields + Logical Search Operator + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + ServiceClient + List of matching Entity Types. + + + + Searches for data from an entity based on the search parameters. + Name of the entity to search + Array of Search Parameters + List of fields to retrieve, Null indicates all Fields + Logical Search Operator + Number records per Page + Current Page number + inbound place holder cookie + outbound place holder cookie + is there more records or not + Sort order + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + List of matching Entity Types. + + + + Gets a list of accounts based on the search parameters. + Dataverse Entity Type Name to search + Array of Search Parameters + List of fields to retrieve, Null indicates all Fields + Logical Search Operator + Optional: if set to a valid GUID, generated by the Create Batch Request Method, will assigned the request to the batch for later execution, on fail, runs the request immediately + Adds the bypass plugin behavior to this request. Note: this will only apply if the caller has the prvBypassPlugins permission to bypass plugins. If its attempted without the permission the request will fault. + ServiceClient + List of matching Entity Types. + + + + Properties valid for the extraParameters collection of ImportSolution. + + + + Parameter used to specify whether Solution Import processed ribbon metadata asynchronously + + + + Parameter used to pass a collection of component parameters to the import job. + + + + Direct the system to convert any matching unmanaged customizations into your managed solution + + + + Parameter used to change the default layering behavior during solution import + + + + Internal use only + + + + Parameter used to pass the solution name - Telemetry only + + + + Internal use only + + + + Interface containing extension methods provided by the DataverseServiceClient for the IOrganizationService Interface. + These extensions will only operate from within the client and are not supported server side. + + + + Associate an entity with a set of entities + + + + + + + + Create an entity and process any related entities + entity to create + The ID of the created record + + + + Delete instance of an entity + Logical name of entity + Id of entity + + + + Disassociate an entity with a set of entities + + + + + + + + Perform an action in an organization specified by the request. + Refer to SDK documentation for list of messages that can be used. + Results from processing the request + + + + Retrieves instance of an entity + Logical name of entity + Id of entity + Column Set collection to return with the request + Selected Entity + + + + Retrieves a collection of entities + + Returns an EntityCollection Object containing the results of the query + + + + Updates an entity and process any related entities + entity to update + + + + Interface containing extension methods provided by the DataverseServiceClient for the IOrganizationService Interface. + These extensions will only operate from within the client and are not supported server side. + + + + Associate an entity with a set of entities + + + + + Propagates notification that operations should be canceled. + + + + Create an entity and process any related entities + entity to create + Propagates notification that operations should be canceled. + Returns the newly created record + + + + Create an entity and process any related entities + entity to create + Propagates notification that operations should be canceled. + The ID of the created record + + + + Delete instance of an entity + Logical name of entity + Id of entity + Propagates notification that operations should be canceled. + + + + Disassociate an entity with a set of entities + + + + + Propagates notification that operations should be canceled. + + + + Perform an action in an organization specified by the request. + Refer to SDK documentation for list of messages that can be used. + Propagates notification that operations should be canceled. + Results from processing the request + + + + Retrieves instance of an entity + Logical name of entity + Id of entity + Column Set collection to return with the request + Propagates notification that operations should be canceled. + Selected Entity + + + + Retrieves a collection of entities + + Propagates notification that operations should be canceled. + Returns an EntityCollection Object containing the results of the query + + + + Updates an entity and process any related entities + entity to update + Propagates notification that operations should be canceled. + + + + Client Configuration Options Array. + + + + + Updates the instance of Options with a previously created Options Object. + PreLoaded Options Array + + + + Defaults to True. + When true, this setting applies the default connection routing strategy to connections to Dataverse.This will 'prefer' a given node when interacting with Dataverse which improves overall connection performance.When set to false, each call to Dataverse will be routed to any given node supporting your organization.See https://docs.microsoft.com/en-us/powerapps/developer/data-platform/api-limits#remove-the-affinity-cookie for proper use. + + + + MaxBufferPoolSize override. - Use under Microsoft Direction only. + + + + MaxFaultSize override. - Use under Microsoft Direction only. + + + + MaxReceivedMessageSize override. - Use under Microsoft Direction only. + + + + Number of retries for an execute operation + + + + Enabled Logging of PII in MSAL Log. - defaults to false. + + + + Amount of time to wait for MSAL/AAD to wait for a token response before timing out + + + + Number of retries to Get a token from MSAL. + + + + Amount of time to wait between retries + + + + Use Web API instead of org service + + + + Use Web API instead of org service for logging into and getting boot up data. + + + + Describes connection Options for the Dataverse ServiceClient + + + + + Function that Dataverse ServiceClient will call to request an access token for a given connection. + + + + Defines which type of login will be used to connect to Dataverse + + + + Certificate store name to look up thumbprint. + + + + Certificate ThumbPrint to use to lookup machine certificate to use for authentication. + + + + Client \ Application ID to be used when logging into Dataverse. + + + + Client Secret Id to use to login to Dataverse + + + + User Domain to use - Use with Interactive Login for On Premises + + + + Home Realm to use when working with AD Federation. + + + + ILogger Interface for Dataverse ServiceClient. + + + + Type of Login prompt to use. + + + + User Password to use - Used with Interactive Login scenarios + + + + Redirect Uri to use when connecting to dataverse. Required for OAuth Authentication. + + + + Function that Dataverse ServiceClient will call to request custom headers + + + + Require a unique instance of the Dataverse ServiceClient per Login. + + + + URL of the Dataverse Instance to connect too. + + + + Skip discovery leg when connecting to Dataverse + + + + Path and FileName for MSAL Token Cache. Used only for OAuth - User Interactive flows. + + + + (Windows Only) If True, Uses the current user of windows to attempt the login with + + + + User Name to use - Used with Interactive Login scenarios + + + + Result of call to DiscoverOrganizationsAsync + + + + Constructor + OrganizationDetailCollection + account + + + + MSAL account selected as part of dicovery authentication + + + + OrganizationDetailCollection + + + + Describes a discovery server that can be used to determine what organizations a user is a member of. + + + + Raised when a property changes + + + + Default constructor + + + + Accepts a Server Info object + + + + + Discovery server Uri, this is the URI necessary to connect to the Discovery server + + + + Display name of the Discovery Server + + + + Geo that hosts this Disco endpoint + + + + Server used to override the regional discovery server, if present its treated as using the Global Discovery server + + + + When true, the global discovery server cannot be used to locate this instance, it must be a regional discovery query + + + + Short name of the Discovery Server, this is used to store the server in the users config for later use. + + + + Dataverse online Discovery server enumeration + + + + Raised when a property changes + + + + Default constructor, Builds baseline data for the Servers. + + + + Clean up + + + + Clean up + + + + + Finds a Server Info by GEO Code. Note NorthAmerica and GCC cannot be located this way. + to use with NA you must short name, NorthAmerica for NAM and NorthAmerica2 for GCC. + GEO of Discovery Instance you are looking for + DiscoveryServer Data or Null + + + + Parses an OrgURI to determine what the supporting discovery server is. + + + + + + Finds a Server by Name in the List or return null. + Short Name of the server you are looking for + DiscoveryServer Data or Null + + + + Finds the server short name by server uri + Name of the Server to find + + + + + Public Property to discovery servers + + + + Describes a Single Organization returned from a Discovery server + + + + WCF EVENT hook + + + + + This is the URI needed to connect to the Organization + + + + This is the name assigned to the Discovery Server, this is used to visual separate organizations returned by Discovery server used, or Premise solutions. + + + + This is the display name for the organization that a user sees when working in Dataverse + + + + This is the details of the Organization, returned directly from Dataverse + + + + This is the actual name for the organization in Dataverse, and is required to connect to Dataverse + + + + This is the actual name for the organization in Dataverse, and is required to connect to Dataverse + + + + Describes the Collection of Orgs that a user may select from. This is used to display the list to the user + + + + WPF EVENT HOOK + + + + Container for Dataverse Orgs List. + + + + List of Orgs + + + + + + + Refresh listener delegate + + + + + Container class for Batches. + + + + Default constructor + True to return responses, False to not return responses + True to continue if anyone item trips an error, False to stop on the first error. + String name of the batch, if blank, a GUID is used + + + + ID of the batch. + + + + Items to execute + + + + DisplayName of the batch. + + + + Settings for this Execute Multiple Request. + + + + Results from the Batch. + + + + Status of the batch. + + + + Data container for Live/OSDP debug env. + + + + Default constructor + + + + Gives the discovery server url + + + + Gets/Sets the display name. + + + + Geo Code + + + + regional global discovery server + + + + Sets the restricted status of the instance. ( restricted means it is not in the global discovery servers ) + + + + Gets/Sets the shortname(should be unique). + + + + Primary implementation of the API interface for Dataverse. + + + + SDK Version property backer. + + + + Creating the ServiceClient Connection with a ConnectionOptions Object and ConfigurationOptions Object. This allows for deferred create of a Dataverse Service Client. + Describes how the Connection should be created. + False by Default, if True, stages the properties of the connection and returns. You must call .Connect() to complete the connection. + Described Configuration Options for the connection. + + + + Log in with Certificate Auth OnLine connections. + This requires the org API URI. + Certificate to use during login + StoreName to look in for certificate identified by certificateThumbPrint + ThumbPrint of the Certificate to load + API URL of the Dataverse instance to connect too + if set, will force the system to create a unique connection + Dataverse Org Detail object, this is is returned from a query to the Dataverse Discovery Server service. not required. + The registered client Id on Azure portal. + The redirect URI application will be redirected post OAuth authentication. + Logging provider + + + + Log in with Certificate Auth On-Premises connections. + Certificate to use during login + StoreName to look in for certificate identified by certificateThumbPrint + ThumbPrint of the Certificate to load + URL of the Dataverse instance to connect too + Organization name for the Dataverse Instance. + if true, https:// used + if set, will force the system to create a unique connection + Dataverse Org Detail object, this is is returned from a query to the Dataverse Discovery Server service. not required. + The registered client Id on Azure portal. + The redirect URI application will be redirected post OAuth authentication. + Logging provider + + + + ServiceClient to accept the connectionstring as a parameter + + Logging provider + + + + Log in with OAuth for online connections, + + Utilizes the discovery system to resolve the correct endpoint to use given the provided server orgName, user name and password. + User Id supplied + Password for login + Region where server is provisioned in for login + Name of the organization to connect + if set, will force the system to create a unique connection + Dataverse Org Detail object, this is is returned from a query to the Dataverse Discovery Server service. not required. + The registered client Id on Azure portal. + The redirect URI application will be redirected post OAuth authentication. + The prompt Behavior. + (optional) If true attempts login using current user ( Online ) + (Optional)The token cache path where token cache file is placed. if string.empty, will use default cache file store, if null, will use in memory cache + Logging provider + + + + Log in with OAuth for On-Premises connections. + User Id supplied + Password for login + Domain + Host name of the server that is hosting the Dataverse web service + Port number on the Dataverse Host Server ( usually 444 ) + Organization name for the Dataverse Instance. + if true, https:// used + if set, will force the system to create a unique connection + Dataverse Org Detail object, this is returned from a query to the Dataverse Discovery Server service. not required. + The registered client Id on Azure portal. + The redirect URI application will be redirected post OAuth authentication. + The prompt Behavior. + (Optional)The token cache path where token cache file is placed. if string.empty, will use default cache file store, if null, will use in memory cache + Logging provider + + + + Log in with OAuth for online connections, + + Will attempt to connect directly to the URL provided for the API endpoint. + User Id supplied + Password for login + if set, will force the system to create a unique connection + The registered client Id on Azure portal. + The redirect URI application will be redirected post OAuth authentication. + The prompt Behavior. + (optional) If true attempts login using current user ( Online ) + API or Instance URI to access the Dataverse environment. + (Optional)The token cache path where token cache file is placed. if string.empty, will use default cache file store, if null, will use in memory cache + Logging provider + + + + Creates an instance of ServiceClient who's authentication is managed by the caller. + This requires the caller to implement a function that will accept the InstanceURI as a string will return the access token as a string on demand when the ServiceClient requires it. + This approach is recommended when working with WebApplications or applications that are required to implement an on Behalf of flow for user authentication. + URL of the Dataverse instance to connect too. + Function that will be called when the access token is require for interaction with Dataverse. This function must accept a string (InstanceURI) and return a string (accesstoken) + A value of "true" Forces the ServiceClient to create a new connection to the Dataverse instance vs reusing an existing connection, Defaults to true. + Logging provider + + + + ClientID \ ClientSecret Based Authentication flow, allowing for Secure Client ID passing. + Direct URL of Dataverse instance to connect too. + The registered client Id on Azure portal. + Client Secret for Client Id. + Use unique instance or reuse current connection. + Logging provider + + + + ClientID \ ClientSecret Based Authentication flow. + Direct URL of Dataverse instance to connect too. + The registered client Id on Azure portal. + Client Secret for Client Id. + Use unique instance or reuse current connection. + Logging provider + + + + Issues an Associate Request to Dataverse. + Entity Name to associate to + ID if Entity to associate to + Relationship Name + Entities to associate + + + + Associate an entity with a set of entities + + + + + + + + Associate an entity with a set of entities + + + + + Propagates notification that operations should be canceled. + + + + Clone, 'Clones" the current Dataverse ServiceClient with a new connection to Dataverse. + Clone only works for connections creating using OAuth Protocol. + Logging provider + returns an active ServiceClient or null + + + + Clone, 'Clones" the current Dataverse Service client with a new connection to Dataverse. + Clone only works for connections creating using OAuth Protocol. + Strong Type Assembly to reference as part of the create of the clone. + Logging provider + + + + + Connects the Dataverse Service Client instance when staged with the Deferd Connection constructor. + + + + + Issues a Create request to Dataverse + Entity to create + ID of newly created entity + + + + Create an entity and process any related entities + entity to create + Returns the newly created record + + + + Create an entity and process any related entities + entity to create + Propagates notification that operations should be canceled. + Returns the newly created record + + + + Create an entity and process any related entities + entity to create + The ID of the created record + + + + Create an entity and process any related entities + entity to create + Propagates notification that operations should be canceled. + The ID of the created record + + + + Issues a Delete request to Dataverse + Entity name to delete + ID if entity to delete + + + + Delete instance of an entity + Logical name of entity + Id of entity + + + + Delete instance of an entity + Logical name of entity + Id of entity + Propagates notification that operations should be canceled. + + + + Issues a Disassociate Request to Dataverse. + Entity Name to disassociate from + ID if Entity to disassociate from + Relationship Name + Entities to disassociate + + + + Disassociate an entity with a set of entities + + + + + + + + Disassociate an entity with a set of entities + + + + + Propagates notification that operations should be canceled. + + + + Discovers Organizations Using the global discovery service and an external source for access tokens + Global discovery base URI to use to connect too, if null will utilize the commercial Global Discovery Server. + Function that will provide access token to the discovery call. + (optional) path to log store + Logging provider + + + + + Discovers Organizations Using the global discovery service. + Provides a User ID / Password flow for authentication to the online discovery system. + You can also provide the discovery instance you wish to use, or not pass it. If you do not specify a discovery region, the commercial global region is used + User ID to login with + Password to use to login with + (Optional) URI of the discovery server + The client Id. + The redirect uri. + The prompt behavior. + The deployment type: OnPrem or Online. + The authority provider for OAuth tokens. Unique if any already known. + (Optional) if specified, tries to use the current user + (optional) path to log store + Logging provider + A collection of organizations + + + + Discovers the organizations, used for OAuth. + The discovery service URI. + The client credentials. + The client Id. + The redirect uri. + The prompt behavior. + The deployment type: OnPrem or Online. + The authority provider for OAuth tokens. Unique if any already known. + (Optional) if specified, tries to use the current user + (optional) path to log store + Logging provider + A collection of organizations + + + + Discovers the organizations against an On-Premises deployment. + The discovery service URI. + The client credentials. + The client Id. + The redirect uri. + The prompt behavior. + The authority provider for OAuth tokens. Unique if any already known. + (Optional) if specified, tries to use the current user + (optional) path to log store + Logging provider + A collection of organizations + + + + Disposed the resources used by the ServiceClient. + + + + Executes a general organization request + Request object + Response object + + + + Perform an action in an organization specified by the request. + Refer to SDK documentation for list of messages that can be used. + Results from processing the request + + + + Perform an action in an organization specified by the request. + Refer to SDK documentation for list of messages that can be used. + Propagates notification that operations should be canceled. + Results from processing the request + + + + Executes a Dataverse Organization Request (thread safe) and returns the organization response object. Also adds metrics for logging support. + Organization Request to run + Message identifying what this request in logging. + When True, uses the webAPI to execute the organization Request. This works for only Create at this time. + Result of request or null. + + + + Executes a Dataverse Organization Request (In Async mode) and returns the organization response object. Also adds metrics for logging support. + Organization Request to run + Message identifying what this request in logging. + When True, uses the webAPI to execute the organization Request. This works for only Create at this time. + Propagates notification that operations should be canceled. + Result of request or null. + + + + Executes a web request against Xrm WebAPI. + Here you would pass the path and query parameters that you wish to pass onto the WebAPI. + The format used here is as follows: + {APIURI}/api/data/v{instance version}/querystring. + For example, + if you wanted to get data back from an account, you would pass the following: + accounts(id) + which creates: get - https://myinstance.crm.dynamics.com/api/data/v9.0/accounts(id) + if you were creating an account, you would pass the following: + accounts + which creates: post - https://myinstance.crm.dynamics.com/api/data/v9.0/accounts - body contains the data. + Method to use for the request + Content your passing to the request + Headers in addition to the default headers added by for Executing a web request + Content Type attach to the request. this defaults to application/json if not set. + Cancellation token for the request + + + + + Enabled only if InMemoryLogCollectionEnabled is true. + Return all logs currently stored for the ServiceClient in queue. + + + + Enabled only if InMemoryLogCollectionEnabled is true. + Return all logs currently stored for the ServiceClient in queue in string list format with [UTCDateTime][LogEntry]. + + + + Makes a secure string + + + + + + Clear the persistent and in-memory store cache + + + + + + Issues a Retrieve Request to Dataverse + Entity name to request + ID of the entity to request + ColumnSet to request + Entity object + + + + Retrieves instance of an entity + Logical name of entity + Id of entity + Column Set collection to return with the request + Selected Entity + + + + Retrieves instance of an entity + Logical name of entity + Id of entity + Column Set collection to return with the request + Propagates notification that operations should be canceled. + Selected Entity + + + + Issues a RetrieveMultiple Request to Dataverse + Query to Request + EntityCollection Result + + + + Retrieves a collection of entities + + Returns an EntityCollection Object containing the results of the query + + + + Retrieves a collection of entities + + Propagates notification that operations should be canceled. + Returns an EntityCollection Object containing the results of the query + + + + Issues an update to Dataverse. + Entity to update into Dataverse + + + + Updates an entity and process any related entities + entity to update + + + + Updates an entity and process any related entities + entity to update + Propagates notification that operations should be canceled. + + + + Authentication Type to use + + + + OAuth Authority. + + + + Gets or Sets the AAD Object ID of the caller. + This is supported for Xrm 8.1 + only + + + + Gets or Sets the current caller ID + + + + Returns the friendly name of the connected Dataverse instance. + + + + ID of the connected organization. + + + + Returns the endpoint collection for the connected org. + + + + + Returns the unique name for the org that has been connected. + + + + Returns the Actual URI used to connect to Dataverse. + this URI could be influenced by user defined variables. + + + + Returns the Version Number of the connected Dataverse organization. + If access before the Organization is connected, value returned will be null or 0.0 + + + + Returns the current access token in Use to connect to Dataverse. + Note: this is only available when a token based authentication process is in use. + + + + Disabled internal cross thread safeties, this will gain much higher performance, however it places the requirements of thread safety on you, the developer. + + + + Defaults to True. + When true, this setting applies the default connection routing strategy to connections to Dataverse.This will 'prefer' a given node when interacting with Dataverse which improves overall connection performance.When set to false, each call to Dataverse will be routed to any given node supporting your organization.See https://docs.microsoft.com/en-us/powerapps/developer/data-platform/api-limits#remove-the-affinity-cookie for proper use. + + + + Gets the PowerPlatform Environment Id of the environment that is hosting this instance of Dataverse + + + + This will force the Dataverse server to refresh the current metadata cache with current DB config. + Note, that this is a performance impacting property. + Use of this flag will slow down operations server side as the server is required to check for consistency of the platform metadata against disk on each API call executed. + It is recommended to use this ONLY in conjunction with solution import or delete operations. + + + + Enabled Log Capture in memory + This capability enables logs that would normally be sent to your configured + + + + This is the number of minuets that logs will be retained before being purged from memory. Default is 5 min. + This capability controls how long the log cache is kept in memory. + + + + if true then Batch Operations are available. + + + + if true the service is ready to accept requests. + + + + Returns the Last String Error that was created by the Dataverse Connection + + + + Returns the Last Exception from Dataverse. + + + + Gets or sets the maximum timeout for the connection. + Default is 4 minutes. + + + + Gets or sets max retry count. + + + + Logged in Office365 UserId using OAuth. + + + + OrganizationDetails for the currently connected environment. + + + + Server Hint for the number of concurrent threads that would provide optimal processing. + + + + Gets or sets retry pause time. + + + + Get the Client SDK version property + + + + This ID is used to support Dataverse Telemetry when trouble shooting SDK based errors. + When Set by the caller, all Dataverse API Actions executed by this client will be tracked under a single session id for later troubleshooting. + For example, you are able to group all actions in a given run of your client ( several creates / reads and such ) under a given tracking id that is shared on all requests. + providing this ID when reporting a problem will aid in trouble shooting your issue. + + + + Gets the Tenant Id of the current connection. + + + + Use Dataverse Web API instead of Dataverse Object Model service where possible - Defaults to False. + + + + TBD + + + + TBD + + + + + + + + + + Helper class that gets/sets the data for connecting to debug online env. + + + + Gets the issuer Uri for the selected debug env. + + + + + Method to check if currently selected online region in UI is custom debug env. or not. + + + + + Returns an instance of this class. + + + + Stores the string identifier for the currently selected online region(the one selected from online region drop down). + + + + This class provides an override for the default trace settings. + These settings must be set before the components in the control are used for them to be effective. + + + + + Adds a listener to the trace listen array + Trace Listener you wish to add + true on success, false on fail. + + + + Closes any trace listeners that were configured + + + + Override Trace Level setting. + + + + Trace listener broker class + + + + + Method to refresh listeners for all the registered trace loggers + + + + Method to register trace logger + + + + + Method to un register trace logger + + + + + TraceLoggerBase Class. + + + + default TraceLoggerBase constructor + + + + + Initialize Trace Source + + + + Logg an error with an Exception + + + + + Log a Message as an Information event. + + + + + Log a Trace event + + + + + + Log a Trace event + + + + + + + To refresh listeners + + + + + Reset the last Stored Error + + + + Current Trace level + + + + Last Error from Dataverse + + + + Last Exception from Dataverse + + + + Trace source + + + + Trace Name + + + + Parameter for delegate - RegisterdTraceListeners in TraceControlSettingsBase class + + + + Constructor + + + + + + Source name of trace listner + + + + Override Trace Level setting + + + + List of trace listners + + + + Trace setting store + + + + + + + + + + + + Source name of trace listner + + + + Used to encompass a ServiceClient Connection Centric exceptions + + + + Creates a CdsService Client Exception + + + + + + Creates a CdsService Client Exception + Error Message + + + + Creates a CdsService Client Exception + Error Message + Supporting Exception + + + + Used to encompass a ServiceClient Operation Exception + + + + Creates a CdsService Client Exception + + + + + + Creates a CdsService Client Exception + Error Message + + + + Creates a CdsService Client Exception + Error Message + Supporting Exception + + + + Creates a CdsService Client Exception + Error Message + Error code + Data Properties + Help Link + + + + + Creates a CdsService Client Exception from a httpOperationResult. + + + + + Utility class for XML related operations. + + + + Prevent XmlUtil from ever being constructed + + + + Creates an XmlDocument object with secure default property values. + the new XmlDocument object + + + + Creates an XmlDocument object with secure default property values. + Extracts xml from the given stream and reads it into the XmlDocument. + The XML stream to load. + the new XmlDocument object + + + + Creates an XmlDocument object with secure default property values. + Loads the given XML into the XmlDocument. + The XML to load. + the new XmlDocument object + + + + Creates an XmlDocument object with secure default property values. + Loads the given XML into the XmlDocument. + This overload is useful when a whitespace only element value is valid content. + The XML to load. + Whether the whitespaces are to be preserved or not. + the new XmlDocument object + + + + Creates an XmlDocument object with secure default property values. + The XmlTextReader to get the data from. + the new XmlDocument object + + + + Creates an XmlReader object with secure default property values. + Xml stream. + The new XmlReader object. + + + + Creates an XmlReader object with secure default property values. + The string to get the data from. + the new XmlReader object + + + + Creates an XmlReader object with secure default property values and given whitespace setting. + The string to get the data from. + Whether the whitespaces are to be preserved or not. + the new XmlReader object + + + + Creates an XmlWriter on top of the provided TextWriter as per + the .Net Framework guidelines. + TextWriter to write into + True to indent the output + An XmlWriter + + + + Creates an XmlWriter which writes to the specified filename using + the specified encoding. + File to write to + Encoding to use + True to indent the output + An XmlWriter + + + \ No newline at end of file diff --git a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.Dynamics.ReleaseNotes.txt b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.Dynamics.ReleaseNotes.txt index 3b6b303..db2991c 100644 --- a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.Dynamics.ReleaseNotes.txt +++ b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.Dynamics.ReleaseNotes.txt @@ -1,8 +1,11 @@ Notice: This package is an extension to the Microsoft.PowerPlatform.Dataverse.Client Nuget package. - This package is intended to work with .net full framework 4.6.2, 4.7.2 and 4.8, .net 3.1, net 5, and .net 6.0 + This package is intended to work with .net full framework 4.6.2, 4.7.2 and 4.8, and .net 6.0 ++CURRENTRELEASEID++ +removed support for .net 3.1 and .net 5.0. + +1.0.39: No updates 1.0.1: diff --git a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.Dynamics.nuspec b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.Dynamics.nuspec index 8ce8b0f..248e9f5 100644 --- a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.Dynamics.nuspec +++ b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.Dynamics.nuspec @@ -23,9 +23,6 @@ - - - @@ -45,8 +42,6 @@ - - diff --git a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt index 1bb0a88..84cca9f 100644 --- a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt +++ b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.ReleaseNotes.txt @@ -1,5 +1,5 @@ Notice: - This package is intended to work with .net full framework 4.6.2, 4.7.2 and 4.8, .net core 3.1, 5.0 and 6.0 + This package is intended to work with .net full framework 4.6.2, 4.7.2 and 4.8, and 6.0 General Documentation can be found here: https://docs.microsoft.com/en-us/dotnet/api/microsoft.powerplatform.dataverse.client?view=dataverse-sdk-latest Connection String Docs can be found here: @@ -7,6 +7,25 @@ Notice: Note: Only AD on FullFramework, OAuth, Certificate, ClientSecret Authentication types are supported at this time. ++CURRENTRELEASEID++ +REMOVED .net 3.1 and .net 5 support as they are out of support frameworks. +Added new DiscoverOnlineOrganizationsAsync which supports CancellationToken +Added new RetrieveSolutionImportResultAsync for retrieving solution import result from Dataverse +Added StageSolution API to Deployment Extensions. +Added Support for "StageSolution" as source for import solution. to use it, you must first stage the solution, then call importsolution passing the staged solution id. +Added GetAsyncOperationStatus API to Deployment Extensions. This provides a simple way to get the status of an async operation. +Added async version of ExecuteWebRequest. Git: https://github.com/microsoft/PowerPlatform-DataverseServiceClient/issues/354 +Updated telemetry behavior to remove some unnecessary log noise when cloning connections. +Updated auth flow to reduce access token acquire frequency. Git: https://github.com/microsoft/PowerPlatform-DataverseServiceClient/issues/377 +Updated min version of DV required for SessionID Support. +Updated Async via sync calls to support more current patterns. Git: https://github.com/microsoft/PowerPlatform-DataverseServiceClient/issues/372 +Fix for cleaning up Last Error reported by Git: https://github.com/microsoft/PowerPlatform-DataverseServiceClient/issues/359 +Fix for Object Null exception that occurs when using .Clone under request load in a multi threaded use case. Git issue: https://github.com/microsoft/PowerPlatform-DataverseServiceClient/issues/362 +Dependency changes: + System.ServiceModel.Security moved to 4.10.2 due min dependency (transient dependency is vulnerable to CVE-2022-34716). + System.ServiceModel.Http moved to 4.10.2 due min dependency (transient dependency is vulnerable to CVE-2022-34716). + + +1.0.39: Added .net 6.0 Target for the Client. Added implementation for CreateAndReturnAsync variations. Updated Default Login flow to use WebAPI to allow for detecting Admin Mode on server during login. diff --git a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.nuspec b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.nuspec index 4915fd4..576015c 100644 --- a/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.nuspec +++ b/src/nuspecs/Microsoft.PowerPlatform.Dataverse.Client.nuspec @@ -47,36 +47,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -89,7 +59,7 @@ - + @@ -124,13 +94,6 @@ - - - - - - -